/* * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.flex.swf.io; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.flex.compiler.problems.FileIOProblem; import org.apache.flex.compiler.problems.ICompilerProblem; import org.apache.flex.compiler.problems.SWFCSMTextSettingsWrongReferenceTypeProblem; import org.apache.flex.compiler.problems.SWFCharacterIDNotFoundProblem; import org.apache.flex.compiler.problems.SWFDefineFontAlignZonesLinkToIncorrectFontProblem; import org.apache.flex.compiler.problems.SWFInvalidSignatureProblem; import org.apache.flex.compiler.problems.SWFFrameCountMismatchProblem; import org.apache.flex.compiler.problems.SWFTagLengthTooLongProblem; import org.apache.flex.compiler.problems.SWFUnableToReadTagBodyProblem; import org.apache.flex.compiler.problems.SWFUnexpectedEndOfFileProblem; import org.apache.flex.compiler.problems.SWFUnknownFillStyleProblem; import org.apache.flex.swf.Header; import org.apache.flex.swf.Header.Compression; import org.apache.flex.swf.ISWF; import org.apache.flex.swf.ITagContainer; import org.apache.flex.swf.SWF; import org.apache.flex.swf.SWFFrame; import org.apache.flex.swf.TagType; import org.apache.flex.swf.tags.*; import org.apache.flex.swf.types.BevelFilter; import org.apache.flex.swf.types.BlurFilter; import org.apache.flex.swf.types.ButtonRecord; import org.apache.flex.swf.types.CXForm; import org.apache.flex.swf.types.CXFormWithAlpha; import org.apache.flex.swf.types.ClipActions; import org.apache.flex.swf.types.ConvolutionFilter; import org.apache.flex.swf.types.CurvedEdgeRecord; import org.apache.flex.swf.types.DropShadowFilter; import org.apache.flex.swf.types.FillStyle; import org.apache.flex.swf.types.FillStyleArray; import org.apache.flex.swf.types.Filter; import org.apache.flex.swf.types.FocalGradient; import org.apache.flex.swf.types.GlowFilter; import org.apache.flex.swf.types.GlyphEntry; import org.apache.flex.swf.types.GradRecord; import org.apache.flex.swf.types.Gradient; import org.apache.flex.swf.types.GradientBevelFilter; import org.apache.flex.swf.types.GradientGlowFilter; import org.apache.flex.swf.types.IFillStyle; import org.apache.flex.swf.types.ILineStyle; import org.apache.flex.swf.types.KerningRecord; import org.apache.flex.swf.types.LineStyle; import org.apache.flex.swf.types.LineStyle2; import org.apache.flex.swf.types.LineStyleArray; import org.apache.flex.swf.types.Matrix; import org.apache.flex.swf.types.MorphFillStyle; import org.apache.flex.swf.types.MorphGradRecord; import org.apache.flex.swf.types.MorphGradient; import org.apache.flex.swf.types.MorphLineStyle; import org.apache.flex.swf.types.MorphLineStyle2; import org.apache.flex.swf.types.RGB; import org.apache.flex.swf.types.RGBA; import org.apache.flex.swf.types.Rect; import org.apache.flex.swf.types.Shape; import org.apache.flex.swf.types.ShapeRecord; import org.apache.flex.swf.types.ShapeWithStyle; import org.apache.flex.swf.types.SoundEnvelope; import org.apache.flex.swf.types.SoundInfo; import org.apache.flex.swf.types.StraightEdgeRecord; import org.apache.flex.swf.types.StyleChangeRecord; import org.apache.flex.swf.types.Styles; import org.apache.flex.swf.types.TextRecord; import org.apache.flex.swf.types.ZoneData; import org.apache.flex.swf.types.ZoneRecord; import org.apache.flex.utils.FilenameNormalization; /** * Implementation of {@link ISWFReader}. This is a recursive-descent decoder of * a SWF file. Error handling for malformed SWFs: 1. Catch RuntimeExceptions * thrown by InputBitStream and report problems. 2. Handle errors in SWF tag * bodies by logging problems and throwing MalformedTagExceptions. 3. Recover * from #1 and #2 by throwing out the current tag and reading up to the start of * the next tag. */ public class SWFReader implements ISWFReader, ITagContainer { /** * There is an error in the tag body that prevents the tag from being * completely and correctly read. */ private static class MalformedTagException extends Exception { /** * */ private static final long serialVersionUID = -8030549610732167171L; } /** * A made-up tag to substitute for a tag with an invalid character id. */ private static class InvalidTag extends CharacterTag implements ICharacterTag { /** * Some SWFs contained bad character id references. */ public static final int BAD_CHARACTER_ID = 65535; public InvalidTag() { super(TagType.End); // Set a bogus character id that matches the bogus input value. // This lets us round trip reading/writing a SWF. setCharacterID(BAD_CHARACTER_ID); } } public static final InvalidTag INVALID_TAG = new InvalidTag(); /** * Wrapper class for "type" and "length" field in a SWF tag header. */ protected static class TagHeader { TagHeader(TagType type, int length) { this.type = type; this.length = length; } final TagType type; final int length; } /** * Mask on the TagCodeAndLength field to get the lower 6 bits of tag length. */ protected static final int MASK_TAG_LENGTH = 0x3F; /** * The lower 6 bits in the TagCodeAndLength field in the SWF tag header is * the tag length. */ protected static final int BITS_TAG_LENGTH = 6; // 2 bytes for UI16 private static final int UI16_LENGTH = 2; // 4 bytes for SI32 private static final int SI32_LENGTH = 4; /** * SWF input bit stream. */ protected InputBitStream bitStream; /** * Model of the SWF file. */ protected SWF swf; private String swfPath; // path associated with bitStream // Dictionary for resolving character ID to tag. private final Map<Integer, ICharacterTag> dictionary; // Flag for whether buildFramesFromTags() needs to be called. private final boolean buildFrames; /** * All the tags in the SWF file. The frame building process is based on * these tags. */ protected final List<ITag> tags; protected final Collection<ICompilerProblem> problems = new ArrayList<ICompilerProblem>(); /** * Create a SWFReader and initialize field members. */ public SWFReader() { this(true); } /** * Create a SWFReader and initialize field members. * * @param isBuildFrames if true, the reader will build SWF frames from tags * read */ public SWFReader(boolean isBuildFrames) { this.buildFrames = isBuildFrames; tags = new ArrayList<ITag>(); dictionary = new HashMap<Integer, ICharacterTag>(); swf = new SWF(); } @Override public ISWF readFrom(InputStream input, String path) { assert input != null && path != null; swfPath = FilenameNormalization.normalize(path); bitStream = new InputBitStream(input); try { if (readHeader()) readTags(); } catch (IOException e) { problems.add(new FileIOProblem(e)); } if (buildFrames) { int expectedFrames = swf.getFrameCount(); int foundFrames = swf.getFrames().size(); if (expectedFrames != foundFrames) { problems.add(new SWFFrameCountMismatchProblem( expectedFrames, foundFrames, swfPath)); } } return swf; } /** * Get the SWF tied to this reader. Note that the returned SWF may or not be * initialized depending on whether readFrom() has been called or not * * @return swf */ public ISWF getSWF() { return swf; } @Override public Collection<ICompilerProblem> getProblems() { return problems; } /** * Read the header and body of the next SWF tag. * * @return SWF tag model, may be null if the tag is invalid. * @throws IOException error */ private ITag nextTag() throws IOException { final TagHeader header = nextTagHeader(); return readTag(header); } /** * Read SWF tags and add each tag to the tag list. Stop at the End tag. * * @throws IOException error */ protected void readTags() throws IOException { SWFFrame currentFrame = buildFrames ? new SWFFrame() : null; ITag tag; do { tag = nextTag(); if (tag == null) continue; // deposit character tag to dictionary if (tag instanceof ICharacterTag) { addToDictionary((ICharacterTag)tag); } // save to tags list tags.add(tag); if (buildFrames) currentFrame = buildFramesFromTags(currentFrame, tag); } while (tag == null || tag.getTagType() != TagType.End); } /** * Read the next tag's header field and get the tag length and type. * * @return next tag header */ protected TagHeader nextTagHeader() { try { bitStream.setReadBoundary(bitStream.getOffset() + UI16_LENGTH); // get tag code and length final int tagCodeAndLength = bitStream.readUI16(); final TagType tagType = TagType.getTagType(tagCodeAndLength >>> BITS_TAG_LENGTH); int tagLength = tagCodeAndLength & MASK_TAG_LENGTH; if (tagLength == MASK_TAG_LENGTH) { bitStream.setReadBoundary(bitStream.getOffset() + SI32_LENGTH); // long tag header uses an SI32 field for tag length tagLength = bitStream.readSI32(); } return new TagHeader(tagType, tagLength); } catch (Exception e) { // Unexpected end of file. // Log a problem and return an end tag so the // outer loop will terminate normally. problems.add(new SWFUnexpectedEndOfFileProblem(swfPath)); return new TagHeader(TagType.End, 0); } } /** * Read a tag body. A "read boundary" is marked to the length of the tag to * prevent invalid tag or incorrect decoding logic from contaminating the * following tags or having left-over bytes after decoding a tag. * * @param header tag header * @return tag model or null if the tag is invalid. * @throws IOException error */ protected ITag readTag(TagHeader header) throws IOException { bitStream.setReadBoundary(bitStream.getOffset() + header.length); ITag tag = null; try { tag = readTagBody(header.type); } catch (RuntimeException e) { problems.add(new SWFUnableToReadTagBodyProblem(header.type.getValue(), header.length, swfPath, bitStream.getOffset())); // recover by reading the rest of the tag. } catch (MalformedTagException e) { // We have already logged problems for these. // recover by reading the rest of the tag. } // If the read-boundary was not reached, consume the additional bytes // assuming an incorrectly formatted SWF tag was encountered. if (bitStream.getOffset() < bitStream.getReadBoundary()) { try { // The tag is too long but there is no reason to assume // the data we read is invalid. We'll treat the data as // valid. Only report a problem if any of the remaining // bytes are non-zero. boolean nonZeroBytes = false; long oldOffset = bitStream.getOffset(); while (bitStream.getOffset() < bitStream.getReadBoundary()) { if (bitStream.readByte() != 0) nonZeroBytes = true; } if (nonZeroBytes) { problems.add(new SWFTagLengthTooLongProblem(header.type.getValue(), swfPath, oldOffset, bitStream.getReadBoundary())); } } catch (Exception e) { // Unable to skip to the end of the tag. return null; } } return tag; } /** * Add an {@code ICharacterTag} to the character dictionary. * * @param tag character tag */ private void addToDictionary(ICharacterTag tag) { dictionary.put(tag.getCharacterID(), tag); } /** * Build {@code SWFFrame} model from a series of tags as they are * encountered in the SWF. * * @param currentFrame The current frame to add the tag to. * @param tag The current tag. * @return The current frame. A new frame will be returned when a ShowFrame * tag is encountered. Otherwise the currentFrame parameter will be * returned. */ private SWFFrame buildFramesFromTags(SWFFrame currentFrame, ITag tag) { if (tag instanceof IManagedTag) { // managed tags switch (tag.getTagType()) { case ShowFrame: swf.addFrame(currentFrame); currentFrame = new SWFFrame(); break; case FrameLabel: final FrameLabelTag frameLabel = (FrameLabelTag)tag; currentFrame.setName(frameLabel.getName(), frameLabel.isNamedAnchorTag()); break; case Metadata: swf.setMetadata(((MetadataTag)tag).getMetadata()); break; case FileAttributes: final FileAttributesTag fileAttributes = (FileAttributesTag)tag; swf.setUseAS3(fileAttributes.isAS3()); swf.setUseDirectBlit(fileAttributes.isUseDirectBlit()); swf.setUseGPU(fileAttributes.isUseGPU()); swf.setUseNetwork(fileAttributes.isUseNetwork()); break; case SetBackgroundColor: swf.setBackgroundColor(((SetBackgroundColorTag)tag).getColor()); break; case SymbolClass: final SymbolClassTag symbolClass = (SymbolClassTag)tag; for (final String name : symbolClass.getSymbolNames()) { final ICharacterTag exportedCharacter = symbolClass.getSymbol(name); currentFrame.defineSymbol(exportedCharacter, name, dictionary); } break; case EnableDebugger2: swf.setEnableDebugger2((EnableDebugger2Tag)tag); break; case ProductInfo: swf.setProductInfo((ProductInfoTag)tag); break; case DefineSceneAndFrameLabelData: case ScriptLimits: case ExportAssets: case ImportAssets: case End: // TODO: store on ISWF instance break; default: assert false : "Unhandled managed tag: " + tag; } } else { currentFrame.addTag(tag); } return currentFrame; } /** * Close the reader an the underlying input stream. */ @Override public void close() throws IOException { if (bitStream != null) bitStream.close(); } private ICharacterTag getTagById(int id, TagType tagType) throws MalformedTagException { if (dictionary.containsKey(id)) { return dictionary.get(id); } else { // [tpr 7/6/04] work around authoring tool bug of bogus 65535 ids if (id != InvalidTag.BAD_CHARACTER_ID) { problems.add(new SWFCharacterIDNotFoundProblem(id, tagType.getValue(), swfPath, bitStream.getOffset())); throw new MalformedTagException(); } else { return INVALID_TAG; } } } /** * Get all the tags in this SWF file. */ @Override public Iterator<ITag> iterator() { return tags.iterator(); } private CXFormWithAlpha readColorTransformWithAlpha() { bitStream.byteAlign(); final CXFormWithAlpha cxFormWithAlpha = new CXFormWithAlpha(); final boolean hasAddTerms = bitStream.readBit(); final boolean hasMultTerms = bitStream.readBit(); final int nbits = bitStream.readUB(4); if (hasMultTerms) { cxFormWithAlpha.setMultTerm( bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits)); } if (hasAddTerms) { cxFormWithAlpha.setAddTerm( bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits)); } return cxFormWithAlpha; } private CurvedEdgeRecord readCurvedEdgeRecord() throws IOException { final CurvedEdgeRecord curvedEdgeRecord = new CurvedEdgeRecord(); final int nbits = 2 + bitStream.readUB(4); curvedEdgeRecord.setControlDeltaX(bitStream.readSB(nbits)); curvedEdgeRecord.setControlDeltaY(bitStream.readSB(nbits)); curvedEdgeRecord.setAnchorDeltaX(bitStream.readSB(nbits)); curvedEdgeRecord.setAnchorDeltaY(bitStream.readSB(nbits)); return curvedEdgeRecord; } private DefineBinaryDataTag readDefineBinaryData() throws IOException { final int characterId = bitStream.readUI16(); bitStream.readUI32(); // Skip reserved UI32. final byte[] data = bitStream.readToBoundary(); final DefineBinaryDataTag result = new DefineBinaryDataTag(data); result.setCharacterID(characterId); return result; } // The following are decoding methods for SWF tags and types. private DefineBitsLosslessTag readDefineBitsLossless() throws IOException { return readDefineBitsLossless(new DefineBitsLosslessTag()); } private DefineBitsLossless2Tag readDefineBitsLossless2() throws IOException { return (DefineBitsLossless2Tag)readDefineBitsLossless(new DefineBitsLossless2Tag()); } /** * This method treats the bytes after the color table as a binary blob so * both the lossless and lossless2 tags can be read using this method. * * @param tag * @return reference to tag parameter. * @throws IOException */ private DefineBitsLosslessTag readDefineBitsLossless(DefineBitsLosslessTag tag) throws IOException { tag.setCharacterID(bitStream.readUI16()); tag.setBitmapFormat(bitStream.readUI8()); tag.setBitmapWidth(bitStream.readUI16()); tag.setBitmapHeight(bitStream.readUI16()); if (tag.getBitmapFormat() == DefineBitsLosslessTag.BF_8BIT_COLORMAPPED_IMAGE) { tag.setBitmapColorTableSize(bitStream.readUI8() + 1); } tag.setZlibBitmapData(bitStream.readToBoundary()); addToDictionary(tag); return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineScalingGrid */ private DefineScalingGridTag readDefineScalingGrid() throws MalformedTagException { final int characterId = bitStream.readUI16(); final ICharacterTag character = getTagById(characterId, TagType.DefineScalingGrid); final Rect splitter = readRect(); return new DefineScalingGridTag(character, splitter); } private ITag readDefineSceneAndFrameLabelData() { final DefineSceneAndFrameLabelDataTag tag = new DefineSceneAndFrameLabelDataTag(); final long sceneCount = bitStream.readEncodedU32(); for (long i = 0; i < sceneCount; i++) { final long offset = bitStream.readEncodedU32(); final String name = bitStream.readString(); tag.addScene(name, offset); } final long frameLabelCount = bitStream.readEncodedU32(); for (long i = 0; i < frameLabelCount; i++) { final long frameNum = bitStream.readEncodedU32(); final String frameLabel = bitStream.readString(); tag.addFrame(frameLabel, frameNum); } return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineShape */ private DefineShapeTag readDefineShape() throws IOException, MalformedTagException { final DefineShapeTag tag = new DefineShapeTag(); tag.setCharacterID(bitStream.readUI16()); tag.setShapeBounds(readRect()); final ShapeWithStyle shapeWithStyle = readShapeWithStyle(TagType.DefineShape); tag.setShapes(shapeWithStyle); return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineShape2 */ private DefineShape2Tag readDefineShape2() throws IOException, MalformedTagException { final DefineShape2Tag tag = new DefineShape2Tag(); tag.setCharacterID(bitStream.readUI16()); tag.setShapeBounds(readRect()); final ShapeWithStyle shapeWithStyle = readShapeWithStyle(TagType.DefineShape2); tag.setShapes(shapeWithStyle); return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineShape3 */ private DefineShape3Tag readDefineShape3() throws IOException, MalformedTagException { final DefineShape3Tag tag = new DefineShape3Tag(); tag.setCharacterID(bitStream.readUI16()); tag.setShapeBounds(readRect()); final ShapeWithStyle shapeWithStyle = readShapeWithStyle(TagType.DefineShape3); tag.setShapes(shapeWithStyle); return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineShape4 */ private DefineShape4Tag readDefineShape4() throws IOException, MalformedTagException { final DefineShape4Tag tag = new DefineShape4Tag(); tag.setCharacterID(bitStream.readUI16()); tag.setShapeBounds(readRect()); tag.setEdgeBounds(readRect()); bitStream.readUB(5); // skip reserved UB[5] tag.setUsesFillWindingRule(bitStream.readBit()); tag.setUsesNonScalingStrokes(bitStream.readBit()); tag.setUsesScalingStrokes(bitStream.readBit()); // 8 bits. No need to align. final ShapeWithStyle shapeWithStyle = readShapeWithStyle(TagType.DefineShape4); tag.setShapes(shapeWithStyle); return tag; } /** * @see SWFWriter#writeDefineSprite */ private DefineSpriteTag readDefineSprite() throws IOException { final long boundary = bitStream.getReadBoundary(); final int spriteId = bitStream.readUI16(); final int frameCount = bitStream.readUI16(); final List<ITag> spriteTags = new ArrayList<ITag>(); ITag spriteTag; do { spriteTag = nextTag(); if (spriteTag != null && spriteTag.getTagType() != TagType.End) spriteTags.add(spriteTag); } while (spriteTag == null || spriteTag.getTagType() != TagType.End); bitStream.setReadBoundary(boundary); DefineSpriteTag sprite = new DefineSpriteTag(frameCount, spriteTags); sprite.setCharacterID(spriteId); return sprite; } protected DoABCTag readDoABC() throws IOException { final long flag = bitStream.readUI32(); final String name = bitStream.readString(); final byte[] abcData = bitStream.readToBoundary(); return new DoABCTag(flag, name, abcData); } private EnableDebugger2Tag readEnableDebugger2() { bitStream.readUI16(); return new EnableDebugger2Tag(bitStream.readString()); } private EnableTelemetryTag readEnableTelemetry() { // Read the reserved 2 bytes bitStream.readUI16(); String password = bitStream.readString(); return new EnableTelemetryTag(password); } private EndTag readEnd() { return new EndTag(); } /** * @throws MalformedTagException * @see SWFWriter#writeExportAssets */ private ExportAssetsTag readExportAssets() throws MalformedTagException { final ExportAssetsTag tag = new ExportAssetsTag(); final int count = bitStream.readUI16(); for (int i = 0; i < count; i++) { final int id = bitStream.readUI16(); final String name = bitStream.readString(); tag.addExport(getTagById(id, tag.getTagType()), name); } return tag; } private FileAttributesTag readFileAttributes() { final FileAttributesTag tag = new FileAttributesTag(); bitStream.readUB(1); tag.setUseDirectBlit(bitStream.readBit()); tag.setUseGPU(bitStream.readBit()); tag.setHasMetadata(bitStream.readBit()); tag.setAS3(bitStream.readBit()); bitStream.readUB(2); tag.setUseNetwork(bitStream.readBit()); bitStream.readUB(24); return tag; } /** * Reads in appropriate type of IFillStyle, as determined by tagType * * @return valid FillStyle. * @throws MalformedTagException */ private IFillStyle readFillStyle(TagType tagType) throws MalformedTagException { switch (tagType) { case DefineMorphShape: case DefineMorphShape2: return readMorphFillStyle(tagType); default: return readStandardFillStyle(tagType); } } /** * Reads the non-morph fill styles * * @return A {@link FillStyle}. * @throws MalformedTagException * @throws RuntimeException if the FillStyle is invalid. */ private FillStyle readStandardFillStyle(TagType tagType) throws MalformedTagException { final FillStyle s = new FillStyle(); final int type = bitStream.readUI8(); s.setFillStyleType(type); switch (type) { case FillStyle.SOLID_FILL: switch (tagType) { case DefineShape3: case DefineShape4: s.setColor(readRGBA()); break; case DefineShape2: case DefineShape: s.setColor(readRGB()); break; default: throw new IllegalArgumentException("Invalid tag: " + tagType); } break; case FillStyle.LINEAR_GRADIENT_FILL: case FillStyle.RADIAL_GRADIENT_FILL: s.setGradientMatrix(readMatrix()); s.setGradient(readGradient(tagType)); break; case FillStyle.FOCAL_RADIAL_GRADIENT_FILL: s.setGradientMatrix(readMatrix()); s.setGradient(readFocalGradient(tagType)); break; case FillStyle.REPEATING_BITMAP_FILL: // 0x40 tiled bitmap fill case FillStyle.CLIPPED_BITMAP_FILL: // 0x41 clipped bitmap fill case FillStyle.NON_SMOOTHED_REPEATING_BITMAP: // 0x42 tiled non-smoothed fill case FillStyle.NON_SMOOTHED_CLIPPED_BITMAP: // 0x43 clipped non-smoothed fill final int idref = bitStream.readUI16(); s.setBitmapCharacter(getTagById(idref, tagType)); s.setBitmapMatrix(readMatrix()); break; default: problems.add(new SWFUnknownFillStyleProblem(type, false, swfPath, bitStream.getOffset())); throw new MalformedTagException(); } return s; } private FillStyleArray readFillStyleArray(TagType tagType) throws MalformedTagException { final FillStyleArray fillStyleArray = new FillStyleArray(); final int count = readExtensibleCount(); for (int i = 0; i < count; i++) { final IFillStyle fillStyle = readFillStyle(tagType); fillStyleArray.add(fillStyle); } return fillStyleArray; } /** * @param tagType * @return a FocalGradient record */ private FocalGradient readFocalGradient(TagType tagType) { bitStream.byteAlign(); final FocalGradient gradient = new FocalGradient(); gradient.setSpreadMode(bitStream.readUB(2)); gradient.setInterpolationMode(bitStream.readUB(2)); final int numGradients = bitStream.readUB(4); for (int i = 0; i < numGradients; i++) { gradient.getGradientRecords().add(readGradRecord(tagType)); } gradient.setFocalPoint(bitStream.readFIXED8()); return gradient; } private FrameLabelTag readFrameLabel() throws IOException { final String name = bitStream.readString(); final FrameLabelTag tag = new FrameLabelTag(name); if (bitStream.getOffset() < bitStream.getReadBoundary()) { final int flag = bitStream.readUI8(); assert flag == 1 : "FrameLabel::NamedAnchorFlag must be 1."; tag.setNamedAnchorTag(true); } return tag; } private Gradient readGradient(TagType tagType) { bitStream.byteAlign(); final Gradient gradient = new Gradient(); gradient.setSpreadMode(bitStream.readUB(2)); gradient.setInterpolationMode(bitStream.readUB(2)); final int numGradients = bitStream.readUB(4); for (int i = 0; i < numGradients; i++) { gradient.getGradientRecords().add(readGradRecord(tagType)); } return gradient; } /** * @param tagType * @return A gradient record. */ private GradRecord readGradRecord(TagType tagType) { final int ratio = bitStream.readUI8(); RGB color = null; if (TagType.DefineShape == tagType || TagType.DefineShape2 == tagType) { color = readRGB(); } else if (TagType.DefineShape3 == tagType || TagType.DefineShape4 == tagType) { color = readRGBA(); } else { throw new IllegalArgumentException("Invalid tag: " + tagType); } return new GradRecord(ratio, color); } /** * Read SWF header. * * @return true if successful, false if there is an error in the header. * @throws IOException */ protected boolean readHeader() throws IOException { Header header = swf.getHeader(); try { bitStream.setReadBoundary(8); // 4 x UI8 and 1 x UI32 final char[] signature = new char[] { (char)bitStream.readUI8(), (char)bitStream.readUI8(), (char)bitStream.readUI8()}; if (!header.isSignatureValid(signature)) { problems.add(new SWFInvalidSignatureProblem(swfPath)); return false; } header.setSignature(signature); header.setVersion((byte)bitStream.readUI8()); header.setLength(bitStream.readUI32()); if (header.getCompression() == Compression.LZMA) { bitStream.setReadBoundary(bitStream.getOffset() + 4); long compressedSize = bitStream.readUI32(); // read the 4 bytes compressedLen; header.setCompressedLength(compressedSize); } bitStream.setCompress(header.getCompression()); // Max length of a Rect is 17 bytes bitStream.setReadBoundary(bitStream.getOffset() + 17); header.setFrameSize(readRect()); bitStream.setReadBoundary(bitStream.getOffset() + 4); header.setFrameRate(bitStream.readFIXED8()); header.setFrameCount(bitStream.readUI16()); } catch (RuntimeException e) { problems.add(new SWFUnexpectedEndOfFileProblem(swfPath)); return false; } return true; } private ILineStyle readLineStyle(TagType tagType) throws MalformedTagException { ILineStyle result = null; if (tagType == TagType.DefineShape4) { final LineStyle2 s = new LineStyle2(); s.setWidth(bitStream.readUI16()); s.setStartCapStyle(bitStream.readUB(2)); s.setJoinStyle(bitStream.readUB(2)); s.setHasFillFlag(bitStream.readBit()); s.setNoHScaleFlag(bitStream.readBit()); s.setNoVScaleFlag(bitStream.readBit()); s.setPixelHintingFlag(bitStream.readBit()); bitStream.readUB(5); s.setNoClose(bitStream.readBit()); s.setEndCapStyle(bitStream.readUB(2)); if (s.getJoinStyle() == LineStyle2.JS_MITER_JOIN) { s.setMiterLimitFactor(bitStream.readUI16()); // 8.8 fixed point } if (s.isHasFillFlag()) { IFillStyle fillStyle = readFillStyle(tagType); s.setFillType((FillStyle)fillStyle); // Default to #00000000 when there's no color, // to match behavior of old SWF reader s.setColor(new RGBA(0, 0, 0, 0)); } else { s.setColor(readRGBA()); } result = s; } else if (tagType == TagType.DefineMorphShape) { result = readMorphLineStyle(); } else if (tagType == TagType.DefineMorphShape2) { result = readMorphLineStyle2(tagType); } else if (tagType == TagType.DefineShape3) { LineStyle ls = new LineStyle(); result = ls; ls.setWidth(bitStream.readUI16()); ls.setColor(readRGBA()); } else { LineStyle ls = new LineStyle(); result = ls; ls.setWidth(bitStream.readUI16()); ls.setColor(readRGB()); } return result; } private LineStyleArray readLineStyleArray(TagType tagType) throws MalformedTagException { final LineStyleArray lineStyleArray = new LineStyleArray(); final int count = readExtensibleCount(); for (int i = 0; i < count; i++) { lineStyleArray.add(readLineStyle(tagType)); } return lineStyleArray; } protected Matrix readMatrix() { bitStream.byteAlign(); final Matrix matrix = new Matrix(); if (bitStream.readBit()) { final int nScaleBits = bitStream.readUB(5); matrix.setScale(bitStream.readFB(nScaleBits), bitStream.readFB(nScaleBits)); } if (bitStream.readBit()) { final int nRotateBits = bitStream.readUB(5); matrix.setRotate(bitStream.readFB(nRotateBits), bitStream.readFB(nRotateBits)); } final int nTranslateBits = bitStream.readUB(5); matrix.setTranslate(bitStream.readSB(nTranslateBits), bitStream.readSB(nTranslateBits)); bitStream.byteAlign(); return matrix; } private MetadataTag readMetadata() { return new MetadataTag(bitStream.readString()); } private PlaceObject2Tag readPlaceObject2() throws IOException, MalformedTagException { final PlaceObject2Tag tag = new PlaceObject2Tag(); tag.setHasClipActions(bitStream.readBit()); tag.setHasClipDepth(bitStream.readBit()); tag.setHasName(bitStream.readBit()); tag.setHasRatio(bitStream.readBit()); tag.setHasColorTransform(bitStream.readBit()); tag.setHasMatrix(bitStream.readBit()); tag.setHasCharacter(bitStream.readBit()); tag.setMove(bitStream.readBit()); tag.setDepth(bitStream.readUI16()); if (tag.isHasCharacter()) tag.setCharacter(getTagById(bitStream.readUI16(), tag.getTagType())); if (tag.isHasMatrix()) tag.setMatrix(readMatrix()); if (tag.isHasColorTransform()) tag.setColorTransform(readColorTransformWithAlpha()); if (tag.isHasRatio()) tag.setRatio(bitStream.readUI16()); if (tag.isHasName()) tag.setName(bitStream.readString()); if (tag.isHasClipDepth()) tag.setClipDepth(bitStream.readUI16()); ClipActions clipActions = new ClipActions(); clipActions.data = bitStream.readToBoundary(); tag.setClipActions(clipActions); return tag; } private ProductInfoTag readProductInfo() { final ProductInfoTag.Product product = ProductInfoTag.Product.fromCode( bitStream.readSI32()); final ProductInfoTag.Edition edition = ProductInfoTag.Edition.fromCode( bitStream.readSI32()); final byte majorVersion = bitStream.readSI8(); final byte minorVersion = bitStream.readSI8(); final long build = bitStream.readSI64(); final long compileDate = bitStream.readSI64(); return new ProductInfoTag( product, edition, majorVersion, minorVersion, build, compileDate); } private RawTag readRawTag(TagType type) throws IOException { final RawTag rawTag = new RawTag(type); rawTag.setTagBody(bitStream.readToBoundary()); return rawTag; } private Rect readRect() { bitStream.byteAlign(); final int nbits = bitStream.readUB(5); final Rect rect = new Rect( bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits)); bitStream.byteAlign(); return rect; } private RGB readRGB() { return new RGB( bitStream.readUI8(), bitStream.readUI8(), bitStream.readUI8()); } private RGBA readRGBA() { return new RGBA( bitStream.readUI8(), bitStream.readUI8(), bitStream.readUI8(), bitStream.readUI8()); } private ScriptLimitsTag readScriptLimits() { return new ScriptLimitsTag(bitStream.readUI16(), bitStream.readUI16()); } private SetBackgroundColorTag readSetBackgroundColor() { return new SetBackgroundColorTag( bitStream.readUI8(), bitStream.readUI8(), bitStream.readUI8()); } private List<ShapeRecord> readShapeRecords( final TagType tagType, final Shape shape, final CurrentStyles currentStyles) throws IOException, MalformedTagException { final ArrayList<ShapeRecord> list = new ArrayList<ShapeRecord>(); boolean endShapeRecord = false; do { final boolean isEdge = bitStream.readBit(); if (isEdge) { final boolean isStraight = bitStream.readBit(); if (isStraight) { final StraightEdgeRecord straightEdge = readStraightEdgeRecord(); list.add(straightEdge); } else { final CurvedEdgeRecord curvedEdge = readCurvedEdgeRecord(); list.add(curvedEdge); } } else { final boolean stateNewStyles = bitStream.readBit(); final boolean stateLineStyle = bitStream.readBit(); final boolean stateFillStyle1 = bitStream.readBit(); final boolean stateFillStyle0 = bitStream.readBit(); final boolean stateMoveTo = bitStream.readBit(); if (stateNewStyles || stateLineStyle || stateFillStyle1 || stateFillStyle0 || stateMoveTo) { final StyleChangeRecord styleChange = readStyleChangeRecord( stateNewStyles, stateLineStyle, stateFillStyle1, stateFillStyle0, stateMoveTo, tagType, shape, currentStyles); list.add(styleChange); } else { endShapeRecord = true; } } } while (!endShapeRecord); return list; } private ShapeWithStyle readShapeWithStyle(TagType tagType) throws IOException, MalformedTagException { // Read styles from SWF. final FillStyleArray fillStyles = readFillStyleArray(tagType); final LineStyleArray lineStyles = readLineStyleArray(tagType); bitStream.byteAlign(); final int numFillBits = bitStream.readUB(4); final int numLineBits = bitStream.readUB(4); final Styles styles = new Styles(fillStyles, lineStyles); // Create styles context. final CurrentStyles currentStyles = new CurrentStyles(); currentStyles.styles = styles; currentStyles.numFillBits = numFillBits; currentStyles.numLineBits = numLineBits; // Create ShapeWithStyle tag. final ShapeWithStyle shapes = new ShapeWithStyle(styles); shapes.setNumFillBits(numFillBits); shapes.setNumLineBits(numLineBits); // Read ShapeRecords and passing in the style context. final List<ShapeRecord> shapeRecords = readShapeRecords(tagType, shapes, currentStyles); shapes.addShapeRecords(shapeRecords); return shapes; } private Shape readShape(TagType tagType) throws IOException, MalformedTagException { bitStream.byteAlign(); // Read styles from SWF. final int numFillBits = bitStream.readUB(4); final int numLineBits = bitStream.readUB(4); // Create styles context. final CurrentStyles currentStyles = new CurrentStyles(); currentStyles.styles = null; // No initial style set. currentStyles.numFillBits = numFillBits; currentStyles.numLineBits = numLineBits; // Create ShapeWithStyle tag. final Shape shapes = new Shape(); shapes.setNumFillBits(numFillBits); shapes.setNumLineBits(numLineBits); // Read ShapeRecords and passing in the style context. final List<ShapeRecord> shapeRecords = readShapeRecords(tagType, shapes, currentStyles); shapes.addShapeRecords(shapeRecords); return shapes; } /** * @see SWFWriter#writeMorphGradRecord */ private MorphGradRecord readMorphGradRecord() { final int startRatio = bitStream.readUI8(); final RGBA startColor = readRGBA(); final int endRatio = bitStream.readUI8(); final RGBA endColor = readRGBA(); final MorphGradRecord result = new MorphGradRecord(); result.setStartRatio(startRatio); result.setStartColor(startColor); result.setEndRatio(endRatio); result.setEndColor(endColor); return result; } /** * @see SWFWriter#writeMorphGradient */ private MorphGradient readMorphGradient() { final MorphGradient result = new MorphGradient(); final int numGradients = bitStream.readUI8(); for (int idx = 0; idx < numGradients; idx++) { final MorphGradRecord gradientRecord = readMorphGradRecord(); result.add(gradientRecord); } return result; } /** * @throws MalformedTagException * @see SWFWriter#writeMorphFillStyle */ private MorphFillStyle readMorphFillStyle(TagType tagType) throws MalformedTagException { final MorphFillStyle result = new MorphFillStyle(); final int fillStyleType = bitStream.readUI8(); result.setFillStyleType(fillStyleType); switch (fillStyleType) { case FillStyle.SOLID_FILL: final RGBA startColor = readRGBA(); final RGBA endColor = readRGBA(); result.setStartColor(startColor); result.setEndColor(endColor); break; case FillStyle.LINEAR_GRADIENT_FILL: case FillStyle.RADIAL_GRADIENT_FILL: case FillStyle.FOCAL_RADIAL_GRADIENT_FILL: final Matrix startGradientMatrix = readMatrix(); final Matrix endGradientMatrix = readMatrix(); final MorphGradient gradient = readMorphGradient(); result.setStartGradientMatrix(startGradientMatrix); result.setEndGradientMatrix(endGradientMatrix); result.setGradient(gradient); if (fillStyleType == FillStyle.FOCAL_RADIAL_GRADIENT_FILL && tagType.getValue() == TagType.DefineMorphShape2.getValue()) { result.setRatio1(bitStream.readSI16()); result.setRatio2(bitStream.readSI16()); } break; case FillStyle.REPEATING_BITMAP_FILL: case FillStyle.CLIPPED_BITMAP_FILL: case FillStyle.NON_SMOOTHED_REPEATING_BITMAP: case FillStyle.NON_SMOOTHED_CLIPPED_BITMAP: final int bitmapId = bitStream.readUI16(); final ICharacterTag bitmap = getTagById(bitmapId, tagType); final Matrix startBitmapMatrix = readMatrix(); final Matrix endBitmapMatrix = readMatrix(); result.setBitmap(bitmap); result.setStartBitmapMatrix(startBitmapMatrix); result.setEndBitmapMatrix(endBitmapMatrix); break; default: problems.add(new SWFUnknownFillStyleProblem(fillStyleType, true, swfPath, bitStream.getOffset())); throw new MalformedTagException(); } return result; } /** * @see SWFWriter#writeMorphLineStyle */ private MorphLineStyle readMorphLineStyle() { final int startWidth = bitStream.readUI16(); final int endWidth = bitStream.readUI16(); final RGBA startColor = readRGBA(); final RGBA endColor = readRGBA(); final MorphLineStyle result = new MorphLineStyle(); result.setStartWidth(startWidth); result.setEndWidth(endWidth); result.setStartColor(startColor); result.setEndColor(endColor); return result; } /** * @throws MalformedTagException * @see SWFWriter#writeMorphLineStyle2 */ private MorphLineStyle2 readMorphLineStyle2(TagType tagType) throws MalformedTagException { final MorphLineStyle2 result = new MorphLineStyle2(); result.setStartWidth(bitStream.readUI16()); result.setEndWidth(bitStream.readUI16()); result.setStartCapStyle(bitStream.readUB(2)); result.setJoinStyle(bitStream.readUB(2)); result.setHasFillFlag(bitStream.readBit()); result.setNoHScaleFlag(bitStream.readBit()); result.setNoVScaleFlag(bitStream.readBit()); result.setPixelHintingFlag(bitStream.readBit()); bitStream.readUB(5); // Reserved result.setNoClose(bitStream.readBit()); result.setEndCapStyle(bitStream.readUB(2)); bitStream.byteAlign(); if (LineStyle2.JS_MITER_JOIN == result.getJoinStyle()) { result.setMiterLimitFactor(bitStream.readUI16()); } if (!result.isHasFillFlag()) { result.setStartColor(readRGBA()); result.setEndColor(readRGBA()); } else { result.setFillType(readMorphFillStyle(tagType)); } return result; } /** * @see SWFWriter#writeDefineMorphShape */ public DefineMorphShapeTag readDefineMorphShape() throws IOException, MalformedTagException { final int characterId = bitStream.readUI16(); final Rect startBounds = readRect(); final Rect endBounds = readRect(); final long offset = bitStream.readUI32(); final Shape startEdges = readShapeWithStyle(TagType.DefineMorphShape); final Shape endEdges = readShape(TagType.DefineMorphShape); final DefineMorphShapeTag tag = new DefineMorphShapeTag(); tag.setCharacterID(characterId); tag.setStartBounds(startBounds); tag.setEndBounds(endBounds); tag.setOffset(offset); tag.setStartEdges(startEdges); tag.setEndEdges(endEdges); return tag; } /** * @see SWFWriter#writeDefineMorphShape2 */ public DefineMorphShape2Tag readDefineMorphShape2() throws IOException, MalformedTagException { final int characterId = bitStream.readUI16(); final Rect startBounds = readRect(); final Rect endBounds = readRect(); final Rect startEdgeBounds = readRect(); final Rect endEdgeBounds = readRect(); bitStream.readUB(6); // Reserved final boolean usesNonScalingStrokes = bitStream.readBit(); final boolean usesScalingStrokes = bitStream.readBit(); // 8 bits already. No need to align. final long offset = bitStream.readUI32(); final Shape startEdges = readShapeWithStyle(TagType.DefineMorphShape2); final Shape endEdges = readShape(TagType.DefineMorphShape2); final DefineMorphShape2Tag tag = new DefineMorphShape2Tag(); tag.setCharacterID(characterId); tag.setStartBounds(startBounds); tag.setEndBounds(endBounds); tag.setOffset(offset); tag.setStartEdges(startEdges); tag.setEndEdges(endEdges); // new fields in MorphShape2 tag.setStartEdgeBounds(startEdgeBounds); tag.setEndEdgeBounds(endEdgeBounds); tag.setUsesNonScalingStrokes(usesNonScalingStrokes); tag.setUsesScalingStrokes(usesScalingStrokes); return tag; } /** * Extensible count is common in SWF types. They share a pattern of: <br> * count : UI8 <br> * countExtended: UI16 if count=0xFF <br> * * @return count value * @see SWFWriter#writeExtensibleCount */ private int readExtensibleCount() { final int count = bitStream.readUI8(); if (count == 0xFF) { final int countExtended = bitStream.readUI16(); return countExtended; } else { return count; } } private ShowFrameTag readShowFrame() { return new ShowFrameTag(); } private StraightEdgeRecord readStraightEdgeRecord() throws IOException { StraightEdgeRecord straightEdgeRecord = null; final int nbits = 2 + bitStream.readUB(4); final boolean isGeneralLine = bitStream.readBit(); if (isGeneralLine) { final int dx = bitStream.readSB(nbits); final int dy = bitStream.readSB(nbits); straightEdgeRecord = new StraightEdgeRecord(dx, dy); } else { final boolean isVertLine = bitStream.readBit(); if (isVertLine) { final int dy = bitStream.readSB(nbits); straightEdgeRecord = new StraightEdgeRecord(0, dy); } else { final int dx = bitStream.readSB(nbits); straightEdgeRecord = new StraightEdgeRecord(dx, 0); } } return straightEdgeRecord; } /** * A wrapper for a reference to a {@code Style} object. */ static class CurrentStyles { Styles styles; int numFillBits; int numLineBits; } private StyleChangeRecord readStyleChangeRecord( boolean stateNewStyles, boolean stateLineStyle, boolean stateFillStyle1, boolean stateFillStyle0, boolean stateMoveTo, TagType tagType, Shape shape, CurrentStyles currentStyles) throws IOException, MalformedTagException { assert tagType != null; assert currentStyles != null; final StyleChangeRecord styleChange = new StyleChangeRecord(); // move draw point if (stateMoveTo) { final int moveBits = bitStream.readUB(5); final int moveDeltaX = bitStream.readSB(moveBits); final int moveDeltaY = bitStream.readSB(moveBits); styleChange.setMove(moveDeltaX, moveDeltaY); } // there shouldn't be any styles on a shape for fonts, as the // tag is a Shape, not ShapeWithStyle, but the fillStyle0 can be 1 because // of the following from the SWF spec: // "The first STYLECHANGERECORD of each SHAPE in the GlyphShapeTable does not use // the LineStyle and LineStyles fields. In addition, the first STYLECHANGERECORD of each // shape must have both fields StateFillStyle0 and FillStyle0 set to 1." boolean ignoreStyle = tagType == TagType.DefineFont || tagType == TagType.DefineFont2 || tagType == TagType.DefineFont3; // select a style final int indexFillStyle0 = stateFillStyle0 ? bitStream.readUB(currentStyles.numFillBits) : 0; final int indexFillStyle1 = stateFillStyle1 ? bitStream.readUB(currentStyles.numFillBits) : 0; final int indexLineStyle = stateLineStyle ? bitStream.readUB(currentStyles.numLineBits) : 0; final IFillStyle fillStyle0; if (indexFillStyle0 > 0 && !ignoreStyle) fillStyle0 = currentStyles.styles.getFillStyles().get(indexFillStyle0 - 1); else fillStyle0 = null; final IFillStyle fillStyle1; if (indexFillStyle1 > 0 && !ignoreStyle) fillStyle1 = currentStyles.styles.getFillStyles().get(indexFillStyle1 - 1); else fillStyle1 = null; final ILineStyle lineStyle; if (indexLineStyle > 0 && !ignoreStyle) lineStyle = currentStyles.styles.getLineStyles().get(indexLineStyle - 1); else lineStyle = null; styleChange.setDefinedStyles(fillStyle0, fillStyle1, lineStyle, stateFillStyle0, stateFillStyle1, stateLineStyle, currentStyles.styles); // "StateNewStyles" field is only used by DefineShape 2, 3 and 4 tags. final boolean isDefineShape234 = tagType == TagType.DefineShape2 || tagType == TagType.DefineShape3 || tagType == TagType.DefineShape4; // replace styles if (stateNewStyles && isDefineShape234) { // read from SWF final FillStyleArray fillStyles = readFillStyleArray(tagType); final LineStyleArray lineStyles = readLineStyleArray(tagType); bitStream.byteAlign(); final int numFillBits = bitStream.readUB(4); final int numLineBits = bitStream.readUB(4); // update StyleChangeRecord final Styles newStyles = new Styles(fillStyles, lineStyles); styleChange.setNumFillBits(numFillBits); styleChange.setNumLineBits(numLineBits); styleChange.setNewStyles(newStyles); // update style context variable currentStyles.styles = newStyles; currentStyles.numFillBits = numFillBits; currentStyles.numLineBits = numLineBits; } return styleChange; } /** * @throws MalformedTagException * @see SWFWriter#writeSymbolClass */ private SymbolClassTag readSymbolClass() throws MalformedTagException { final SymbolClassTag symbolClass = new SymbolClassTag(); final int numSymbols = bitStream.readUI16(); for (int i = 0; i < numSymbols; i++) { final int id = bitStream.readUI16(); final String name = bitStream.readString(); if (id == 0) { if (swf.getTopLevelClass() == null) swf.setTopLevelClass(name); } else { symbolClass.addSymbol(getTagById(id, symbolClass.getTagType()), name); } } return symbolClass; } /** * Select the tag decoding function by its type. * * @param type tag type * @return tag model */ protected ITag readTagBody(TagType type) throws IOException, MalformedTagException { // Sort "case" conditions alphabetically. switch (type) { case CSMTextSettings: return readCSMTextSettings(); case DoABC: return readDoABC(); case DefineBinaryData: return readDefineBinaryData(); case DefineBits: return readDefineBits(); case DefineBitsJPEG2: return readDefineBitsJPEG2(); case DefineBitsJPEG3: return readDefineBitsJPEG3(); case DefineBitsLossless: return readDefineBitsLossless(); case DefineBitsLossless2: return readDefineBitsLossless2(); case DefineScalingGrid: return readDefineScalingGrid(); case DefineShape: return readDefineShape(); case DefineShape2: return readDefineShape2(); case DefineShape3: return readDefineShape3(); case DefineShape4: return readDefineShape4(); case DefineSprite: return readDefineSprite(); case DefineSound: return readDefineSound(); case StartSound: return readStartSound(); case StartSound2: return readStartSound2(); case SoundStreamHead: return readSoundStreamHead(type); case SoundStreamHead2: return readSoundStreamHead(type); case SoundStreamBlock: return readSoundStreamBlock(); case DefineMorphShape: return readDefineMorphShape(); case DefineMorphShape2: return readDefineMorphShape2(); case DefineSceneAndFrameLabelData: return readDefineSceneAndFrameLabelData(); case DefineFont: return readDefineFont(); case DefineFontInfo: return readDefineFontInfo(type); case DefineFont2: return readDefineFont2(); case DefineFont3: return readDefineFont3(); case DefineFont4: return readDefineFont4(); case DefineFontAlignZones: return readDefineFontAlignZones(); case DefineFontName: return readFontName(); case DefineText: return readDefineText(type); case DefineText2: return readDefineText(type); case DefineEditText: return readDefineEditText(); case DefineButton: return readDefineButton(); case DefineButton2: return readDefineButton2(); case DefineButtonSound: return readDefineButtonSound(); case DefineVideoStream: return readDefineVideoStream(); case VideoFrame: return readVideoFrame(); case End: return readEnd(); case EnableDebugger2: return readEnableDebugger2(); case ExportAssets: return readExportAssets(); case FileAttributes: return readFileAttributes(); case FrameLabel: return readFrameLabel(); case JPEGTables: return readJPEGTables(); case Metadata: return readMetadata(); case ProductInfo: return readProductInfo(); case PlaceObject: return readPlaceObject(); case PlaceObject2: return readPlaceObject2(); case PlaceObject3: return readPlaceObject3(); case RemoveObject: return readRemoveObject(); case RemoveObject2: return readRemoveObject2(); case ScriptLimits: return readScriptLimits(); case SetBackgroundColor: return readSetBackgroundColor(); case SetTabIndex: return readSetTabIndex(); case ShowFrame: return readShowFrame(); case SymbolClass: return readSymbolClass(); case EnableTelemetry: return readEnableTelemetry(); default: return readRawTag(type); } } private ITag readSetTabIndex() { final SetTabIndexTag tag = new SetTabIndexTag(); tag.setDepth(bitStream.readUI16()); tag.setTabIndex(bitStream.readUI16()); return tag; } private RemoveObject2Tag readRemoveObject2() { final RemoveObject2Tag tag = new RemoveObject2Tag(); tag.setDepth(bitStream.readUI16()); return tag; } private RemoveObjectTag readRemoveObject() throws MalformedTagException { final RemoveObjectTag tag = new RemoveObjectTag(); tag.setCharacter(getTagById(bitStream.readUI16(), tag.getTagType())); tag.setDepth(bitStream.readUI16()); return tag; } private PlaceObject3Tag readPlaceObject3() throws IOException, MalformedTagException { final PlaceObject3Tag tag = new PlaceObject3Tag(); tag.setHasClipActions(bitStream.readBit()); tag.setHasClipDepth(bitStream.readBit()); tag.setHasName(bitStream.readBit()); tag.setHasRatio(bitStream.readBit()); tag.setHasColorTransform(bitStream.readBit()); tag.setHasMatrix(bitStream.readBit()); tag.setHasCharacter(bitStream.readBit()); tag.setMove(bitStream.readBit()); bitStream.readUB(3); // reserved; tag.setHasImage(bitStream.readBit()); tag.setHasClassName(bitStream.readBit()); tag.setHasCacheAsBitmap(bitStream.readBit()); tag.setHasBlendMode(bitStream.readBit()); tag.setHasFilterList(bitStream.readBit()); tag.setDepth(bitStream.readUI16()); if (tag.isHasClassName()) tag.setClassName(bitStream.readString()); if (tag.isHasCharacter()) tag.setCharacter(getTagById(bitStream.readUI16(), tag.getTagType())); if (tag.isHasMatrix()) tag.setMatrix(readMatrix()); if (tag.isHasColorTransform()) tag.setColorTransform(readColorTransformWithAlpha()); if (tag.isHasRatio()) tag.setRatio(bitStream.readUI16()); if (tag.isHasName()) tag.setName(bitStream.readString()); if (tag.isHasClipDepth()) tag.setClipDepth(bitStream.readUI16()); if (tag.isHasFilterList()) { final int count = bitStream.readUI8(); final Filter[] filterList = new Filter[count]; for (int i = 0; i < count; i++) filterList[i] = readFilter(); tag.setSurfaceFilterList(filterList); } if (tag.isHasBlendMode()) tag.setBlendMode(bitStream.readUI8()); if (tag.isHasCacheAsBitmap()) tag.setBitmapCache(bitStream.readUI8()); ClipActions clipActions = new ClipActions(); clipActions.data = bitStream.readToBoundary(); tag.setClipActions(clipActions); return tag; } private PlaceObjectTag readPlaceObject() throws IOException, MalformedTagException { final PlaceObjectTag tag = new PlaceObjectTag(); tag.setCharacter(getTagById(bitStream.readUI16(), tag.getTagType())); tag.setDepth(bitStream.readUI16()); tag.setMatrix(readMatrix()); if (bitStream.available() > 0) tag.setColorTransform(readColorTransform()); return tag; } private CXForm readColorTransform() { bitStream.byteAlign(); final CXForm cx = new CXForm(); final boolean hasAddTerms = bitStream.readBit(); final boolean hasMultTerms = bitStream.readBit(); final int nbits = bitStream.readUB(4); if (hasAddTerms) { cx.setAddTerm( bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits)); } if (hasMultTerms) { cx.setMultTerm( bitStream.readSB(nbits), bitStream.readSB(nbits), bitStream.readSB(nbits)); } return cx; } private ITag readVideoFrame() throws IOException, MalformedTagException { final int id = bitStream.readUI16(); final ICharacterTag streamTag = getTagById(id, TagType.VideoFrame); assert streamTag.getTagType() == TagType.DefineVideoStream; final int frameNum = bitStream.readUI16(); final byte[] videoData = bitStream.readToBoundary(); final VideoFrameTag tag = new VideoFrameTag(); tag.setStreamTag((DefineVideoStreamTag)streamTag); tag.setFrameNum(frameNum); tag.setVideoData(videoData); return tag; } private ITag readDefineVideoStream() { final int characterID = bitStream.readUI16(); final int numFrames = bitStream.readUI16(); final int width = bitStream.readUI16(); final int height = bitStream.readUI16(); bitStream.byteAlign(); bitStream.readUB(4); // reserved final int deblocking = bitStream.readUB(3); final boolean smoothing = bitStream.readBit(); final int codecID = bitStream.readUI8(); final DefineVideoStreamTag tag = new DefineVideoStreamTag(); tag.setCharacterID(characterID); tag.setNumFrames(numFrames); tag.setWidth(width); tag.setHeight(height); tag.setDeblocking(deblocking); tag.setSmoothing(smoothing); tag.setCodecID(codecID); return tag; } private DefineButtonSoundTag readDefineButtonSound() throws MalformedTagException { final int buttonID = bitStream.readUI16(); final DefineButtonSoundTag tag = new DefineButtonSoundTag(); tag.setButtonTag(getTagById(buttonID, tag.getTagType())); for (int i = 0; i < DefineButtonSoundTag.TOTAL_SOUND_STYLE; i++) { final int soundID = bitStream.readUI16(); if (soundID == 0) continue; final ICharacterTag soundTag = getTagById(soundID, tag.getTagType()); assert soundTag instanceof DefineSoundTag; tag.getSoundChar()[i] = (DefineSoundTag)soundTag; tag.getSoundInfo()[i] = readSoundInfo(); } return tag; } private DefineButton2Tag readDefineButton2() throws IOException { final int buttonID = bitStream.readUI16(); bitStream.byteAlign(); bitStream.readUB(7); // reserved; final boolean trackAsMenu = bitStream.readBit(); final int actionOffset = bitStream.readUI16(); final ButtonRecord[] characters = readButtonRecords(TagType.DefineButton2); final byte[] actions = bitStream.readToBoundary(); final DefineButton2Tag tag = new DefineButton2Tag(); tag.setTrackAsMenu(trackAsMenu); tag.setActionOffset(actionOffset); tag.setCharacterID(buttonID); tag.setCharacters(characters); tag.setActions(actions); return tag; } private DefineButtonTag readDefineButton() throws IOException { final int buttonID = bitStream.readUI16(); final ButtonRecord[] characters = readButtonRecords(TagType.DefineButton); final byte[] actionsWithEndFlag = bitStream.readToBoundary(); final int actionSize = actionsWithEndFlag.length - 1; final byte[] actions = new byte[actionSize]; System.arraycopy(actionsWithEndFlag, 0, actions, 0, actionSize); final DefineButtonTag tag = new DefineButtonTag(); tag.setCharacterID(buttonID); tag.setCharacters(characters); tag.setActions(actions); return tag; } private ButtonRecord[] readButtonRecords(final TagType type) { final ArrayList<ButtonRecord> characters = new ArrayList<ButtonRecord>(6); // loop until CharacterEndFlag (0x00) is read while (true) { final int firstByte = bitStream.readUI8(); if (firstByte == 0) break; final ButtonRecord record = new ButtonRecord(); record.setHasBlendMode((firstByte & 0x20) > 0); record.setHasFilterList((firstByte & 0x10) > 0); record.setStateHitTest((firstByte & 0x08) > 0); record.setStateDown((firstByte & 0x04) > 0); record.setStateOver((firstByte & 0x02) > 0); record.setStateUp((firstByte & 0x01) > 0); record.setCharacterID(bitStream.readUI16()); record.setPlaceDepth(bitStream.readUI16()); record.setPlaceMatrix(readMatrix()); if (type == TagType.DefineButton2) { record.setColorTransform(readColorTransformWithAlpha()); if (record.isHasFilterList()) { final int count = bitStream.readUI8(); final Filter[] filterList = new Filter[count]; for (int i = 0; i < count; i++) filterList[i] = readFilter(); record.setFilterList(filterList); } if (record.isHasBlendMode()) record.setBlendMode(bitStream.readUI8()); } characters.add(record); } return characters.toArray(new ButtonRecord[characters.size()]); } private Filter readFilter() { final Filter filter = new Filter(); final int type = bitStream.readUI8(); filter.setFilterID(type); switch (type) { case Filter.DROP_SHADOW: filter.setDropShadowFilter(readDropShadowFilter()); break; case Filter.BLUR: filter.setBlurFilter(readBlurFilter()); break; case Filter.GLOW: filter.setGlowFilter(readGlowFilter()); break; case Filter.BEVEL: filter.setBevelFilter(readBevelFilter()); break; case Filter.GRADIENT_GLOW: filter.setGradientGlowFilter(readGradientGlowFilter()); break; case Filter.CONVOLUTION: filter.setConvolutionFilter(readConvolutionFilter()); break; case Filter.COLOR_MATRIX: filter.setColorMatrixFilter(readColorMatrixFilter()); break; case Filter.GRADIENT_BEVEL: filter.setGradientBevelFilter(readGradientBevelFilter()); break; } return filter; } private GradientBevelFilter readGradientBevelFilter() { final GradientBevelFilter filter = new GradientBevelFilter(); final short numColors = bitStream.readUI8(); final RGBA[] gradientColors = new RGBA[numColors]; final int[] gradientRatio = new int[numColors]; for (short i = 0; i < numColors; i++) { gradientColors[i] = readRGBA(); gradientRatio[i] = bitStream.readUI8(); } filter.setNumColors(numColors); filter.setGradientColors(gradientColors); filter.setGradientRatio(gradientRatio); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setAngle(bitStream.readFIXED()); filter.setDistance(bitStream.readFIXED()); filter.setStrength(bitStream.readFIXED8()); filter.setInnerShadow(bitStream.readBit()); filter.setKnockout(bitStream.readBit()); filter.setCompositeSource(bitStream.readBit()); filter.setPasses(bitStream.readUB(4)); return filter; } private float[] readColorMatrixFilter() { final float[] result = new float[20]; for (int i = 0; i < 20; i++) result[i] = bitStream.readFLOAT(); return result; } private ConvolutionFilter readConvolutionFilter() { final ConvolutionFilter filter = new ConvolutionFilter(); filter.setMatrixX(bitStream.readUI8()); filter.setMatrixY(bitStream.readUI8()); filter.setDivisor(bitStream.readFLOAT()); filter.setBias(bitStream.readFLOAT()); int length = filter.getMatrixX() * filter.getMatrixY(); final float[] matrix = new float[length]; for (int i = 0; i < length; i++) matrix[i] = bitStream.readFLOAT(); filter.setMatrix(matrix); filter.setDefaultColor(readRGBA()); bitStream.byteAlign(); bitStream.readUB(6); // reserved filter.setClamp(bitStream.readBit()); filter.setPreserveAlpha(bitStream.readBit()); return filter; } private GradientGlowFilter readGradientGlowFilter() { final GradientGlowFilter filter = new GradientGlowFilter(); final short numColors = bitStream.readUI8(); final RGBA[] gradientColors = new RGBA[numColors]; final int[] gradientRatio = new int[numColors]; for (short i = 0; i < numColors; i++) { gradientColors[i] = readRGBA(); gradientRatio[i] = bitStream.readUI8(); } filter.setNumColors(numColors); filter.setGradientColors(gradientColors); filter.setGradientRatio(gradientRatio); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setAngle(bitStream.readFIXED()); filter.setDistance(bitStream.readFIXED()); filter.setStrength(bitStream.readFIXED8()); filter.setInnerGlow(bitStream.readBit()); filter.setKnockout(bitStream.readBit()); filter.setCompositeSource(bitStream.readBit()); filter.setPasses(bitStream.readUB(4)); return filter; } private BevelFilter readBevelFilter() { final BevelFilter filter = new BevelFilter(); filter.setShadowColor(readRGBA()); filter.setHighlightColor(readRGBA()); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setAngle(bitStream.readFIXED()); filter.setDistance(bitStream.readFIXED()); filter.setStrength(bitStream.readFIXED8()); filter.setInnerShadow(bitStream.readBit()); filter.setKnockout(bitStream.readBit()); filter.setCompositeSource(bitStream.readBit()); filter.setOnTop(bitStream.readBit()); filter.setPasses(bitStream.readUB(4)); return filter; } private GlowFilter readGlowFilter() { final GlowFilter filter = new GlowFilter(); filter.setGlowColor(readRGBA()); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setStrength(bitStream.readFIXED8()); filter.setInnerGlow(bitStream.readBit()); filter.setKnockout(bitStream.readBit()); filter.setCompositeSource(bitStream.readBit()); filter.setPasses(bitStream.readUB(5)); return filter; } private BlurFilter readBlurFilter() { final BlurFilter filter = new BlurFilter(); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setPasses(bitStream.readUB(5)); bitStream.readUB(3); // reserved return filter; } private DropShadowFilter readDropShadowFilter() { final DropShadowFilter filter = new DropShadowFilter(); filter.setDropShadowColor(readRGBA()); filter.setBlurX(bitStream.readFIXED()); filter.setBlurY(bitStream.readFIXED()); filter.setAngle(bitStream.readFIXED()); filter.setDistance(bitStream.readFIXED()); filter.setStrength(bitStream.readFIXED8()); filter.setInnerShadow(bitStream.readBit()); filter.setKnockout(bitStream.readBit()); filter.setCompositeSource(bitStream.readBit()); filter.setPasses(bitStream.readUB(5)); return filter; } private SoundStreamBlockTag readSoundStreamBlock() throws IOException { final byte streamSoundData[] = bitStream.readToBoundary(); final SoundStreamBlockTag tag = new SoundStreamBlockTag(); tag.setStreamSoundData(streamSoundData); return tag; } private SoundStreamHeadTag readSoundStreamHead(final TagType tagType) { bitStream.byteAlign(); bitStream.readUB(4); // reserved final int playbackSoundRate = bitStream.readUB(2); final int playbackSoundSize = bitStream.readUB(1); final int playbackSoundType = bitStream.readUB(1); final int streamSoundCompression = bitStream.readUB(4); final int streamSoundRate = bitStream.readUB(2); final int streamSoundSize = bitStream.readUB(1); final int streamSoundType = bitStream.readUB(1); final int streamSoundSampleCount = bitStream.readUI16(); final int latencySeek = streamSoundCompression == SoundStreamHeadTag.SSC_MP3 ? bitStream.readSI16() : 0; final SoundStreamHeadTag tag = (tagType == TagType.SoundStreamHead) ? new SoundStreamHeadTag() : new SoundStreamHead2Tag(); tag.setPlaybackSoundRate(playbackSoundRate); tag.setPlaybackSoundSize(playbackSoundSize); tag.setPlaybackSoundType(playbackSoundType); tag.setStreamSoundCompression(streamSoundCompression); tag.setStreamSoundRate(streamSoundRate); tag.setStreamSoundSize(streamSoundSize); tag.setStreamSoundType(streamSoundType); tag.setStreamSoundSampleCount(streamSoundSampleCount); tag.setLatencySeek(latencySeek); return tag; } private StartSoundTag readStartSound() throws MalformedTagException { final int soundId = bitStream.readUI16(); final SoundInfo soundInfo = readSoundInfo(); final StartSoundTag tag = new StartSoundTag(); tag.setSoundTag(getTagById(soundId, tag.getTagType())); tag.setSoundInfo(soundInfo); return tag; } private StartSound2Tag readStartSound2() { final String soundClassName = bitStream.readString(); final SoundInfo soundInfo = readSoundInfo(); final StartSound2Tag tag = new StartSound2Tag(); tag.setSoundClassName(soundClassName); tag.setSoundInfo(soundInfo); return tag; } private SoundInfo readSoundInfo() { bitStream.byteAlign(); bitStream.readUB(2); // reserved final boolean syncStop = bitStream.readBit(); final boolean syncNoMultiple = bitStream.readBit(); final boolean hasEnvelope = bitStream.readBit(); final boolean hasLoops = bitStream.readBit(); final boolean hasOutPoint = bitStream.readBit(); final boolean hasInPoint = bitStream.readBit(); final long inPoint = hasInPoint ? bitStream.readUI32() : 0; final long outPoint = hasOutPoint ? bitStream.readUI32() : 0; final int loopCount = hasLoops ? bitStream.readUI16() : 0; final int envPoints = hasEnvelope ? bitStream.readUI8() : 0; final SoundEnvelope envelopeRecords[] = new SoundEnvelope[envPoints]; for (int i = 0; i < envPoints; i++) { envelopeRecords[i] = new SoundEnvelope(); envelopeRecords[i].setPos44(bitStream.readUI32()); envelopeRecords[i].setLeftLevel(bitStream.readUI16()); envelopeRecords[i].setRightLevel(bitStream.readUI16()); } final SoundInfo soundInfo = new SoundInfo(); soundInfo.setSyncStop(syncStop); soundInfo.setSyncNoMultiple(syncNoMultiple); soundInfo.setHasEnvelope(hasEnvelope); soundInfo.setHasLoops(hasLoops); soundInfo.setHasOutPoint(hasOutPoint); soundInfo.setHasInPoint(hasInPoint); soundInfo.setInPoint(inPoint); soundInfo.setOutPoint(outPoint); soundInfo.setLoopCount(loopCount); soundInfo.setEnvPoints(envPoints); soundInfo.setEnvelopeRecords(envelopeRecords); return soundInfo; } private ITag readDefineSound() throws IOException { bitStream.byteAlign(); final int soundId = bitStream.readUI16(); final int soundFormat = bitStream.readUB(4); final int soundRate = bitStream.readUB(2); final int soundSize = bitStream.readUB(1); final int soundType = bitStream.readUB(1); final long soundSampleCount = bitStream.readUI32(); final byte soundData[] = bitStream.readToBoundary(); final DefineSoundTag tag = new DefineSoundTag(); tag.setCharacterID(soundId); tag.setSoundFormat(soundFormat); tag.setSoundRate(soundRate); tag.setSoundSize(soundSize); tag.setSoundType(soundType); tag.setSoundSampleCount(soundSampleCount); tag.setSoundData(soundData); return tag; } private DefineFont4Tag readDefineFont4() throws IOException { final DefineFont4Tag tag = new DefineFont4Tag(); tag.setCharacterID(bitStream.readUI16()); bitStream.byteAlign(); bitStream.readUB(5); // reserved tag.setFontFlagsHasFontData(bitStream.readBit()); tag.setFontFlagsItalic(bitStream.readBit()); tag.setFontFlagsBold(bitStream.readBit()); // 8 bits - no need to align tag.setFontName(bitStream.readString()); tag.setFontData(bitStream.readToBoundary()); return tag; } private CSMTextSettingsTag readCSMTextSettings() throws MalformedTagException { final int id = bitStream.readUI16(); final CSMTextSettingsTag tag = new CSMTextSettingsTag(); final ICharacterTag textTag = getTagById(id, tag.getTagType()); tag.setTextTag(textTag); bitStream.byteAlign(); tag.setUseFlashType(bitStream.readUB(2)); tag.setGridFit(bitStream.readUB(3)); bitStream.readUB(3); // reserved // 8 bits - no need to align tag.setThickness(bitStream.readFLOAT()); tag.setSharpness(bitStream.readFLOAT()); bitStream.readUI8(); // reserved if (textTag instanceof DefineTextTag) ((DefineTextTag)textTag).setCSMTextSettings(tag); else if (textTag instanceof DefineEditTextTag) ((DefineEditTextTag)textTag).setCSMTextSettings(tag); else problems.add(new SWFCSMTextSettingsWrongReferenceTypeProblem(swfPath, id)); return tag; } private DefineEditTextTag readDefineEditText() throws MalformedTagException { final DefineEditTextTag tag = new DefineEditTextTag(); tag.setCharacterID(bitStream.readUI16()); tag.setBounds(readRect()); bitStream.byteAlign(); tag.setHasText(bitStream.readBit()); tag.setWordWrap(bitStream.readBit()); tag.setMultiline(bitStream.readBit()); tag.setPassword(bitStream.readBit()); tag.setReadOnly(bitStream.readBit()); tag.setHasTextColor(bitStream.readBit()); tag.setHasMaxLength(bitStream.readBit()); tag.setHasFont(bitStream.readBit()); tag.setHasFontclass(bitStream.readBit()); tag.setAutoSize(bitStream.readBit()); tag.setHasLayout(bitStream.readBit()); tag.setNoSelect(bitStream.readBit()); tag.setBorder(bitStream.readBit()); tag.setWasStatic(bitStream.readBit()); tag.setHtml(bitStream.readBit()); tag.setUseOutlines(bitStream.readBit()); // HasFont and HasFontClass is exclusive, but we tolerate the situation where both // are set. if (tag.isHasFont()) { final int id = bitStream.readUI16(); final ICharacterTag fontTag = getTagById(id, tag.getTagType()); tag.setFontTag(fontTag); tag.setFontHeight(bitStream.readUI16()); } if (tag.isHasFontClass()) { tag.setFontClass(bitStream.readString()); // HasFontClass needs a Height field as well. tag.setFontHeight(bitStream.readUI16()); } if (tag.isHasTextColor()) { tag.setTextColor(readRGBA()); } if (tag.isHasMaxLength()) { tag.setMaxLength(bitStream.readUI16()); } if (tag.isHasLayout()) { tag.setAlign(bitStream.readUI8()); tag.setLeftMargin(bitStream.readUI16()); tag.setRightMargin(bitStream.readUI16()); tag.setIndent(bitStream.readUI16()); tag.setLeading(bitStream.readSI16()); } tag.setVariableName(bitStream.readString()); if (tag.isHasText()) { tag.setInitialText(bitStream.readString()); } return tag; } private DefineTextTag readDefineText(TagType tagType) throws IOException, MalformedTagException { assert tagType == TagType.DefineText || tagType == TagType.DefineText2; final int characterId = bitStream.readUI16(); final Rect textBounds = readRect(); final Matrix textMatrix = readMatrix(); final int glyphBits = bitStream.readUI8(); final int advanceBits = bitStream.readUI8(); final ArrayList<TextRecord> textRecords = new ArrayList<TextRecord>(); while (true) { final TextRecord textRecord = readTextRecord(tagType, glyphBits, advanceBits); if (textRecord == null) break; textRecords.add(textRecord); } final DefineTextTag tag = new DefineTextTag(); tag.setCharacterID(characterId); tag.setTextBounds(textBounds); tag.setTextMatrix(textMatrix); tag.setGlyphBits(glyphBits); tag.setAdvanceBits(advanceBits); tag.setTextRecords(textRecords.toArray(new TextRecord[0])); return tag; } private TextRecord readTextRecord(TagType type, int glyphBits, int advanceBits) throws MalformedTagException { bitStream.byteAlign(); final boolean textRecordType = bitStream.readBit(); if (!textRecordType) return null; bitStream.readUB(3); // reserved final TextRecord textRecord = new TextRecord(); textRecord.setStyleFlagsHasFont(bitStream.readBit()); textRecord.setStyleFlagsHasColor(bitStream.readBit()); textRecord.setStyleFlagsHasYOffset(bitStream.readBit()); textRecord.setStyleFlagsHasXOffset(bitStream.readBit()); if (textRecord.isStyleFlagsHasFont()) { final int fontId = bitStream.readUI16(); final ICharacterTag fontTag = getTagById(fontId, type); textRecord.setFontTag(fontTag); } if (textRecord.isStyleFlagsHasColor()) { if (type == TagType.DefineText2) { textRecord.setTextColor(readRGBA()); } else { textRecord.setTextColor(readRGB()); } } if (textRecord.isStyleFlagsHasXOffset()) { textRecord.setxOffset(bitStream.readSI16()); } if (textRecord.isStyleFlagsHasYOffset()) { textRecord.setyOffset(bitStream.readSI16()); } if (textRecord.isStyleFlagsHasFont()) { textRecord.setTextHeight(bitStream.readUI16()); } textRecord.setGlyphCount(bitStream.readUI8()); final GlyphEntry[] glyphEntries = new GlyphEntry[textRecord.getGlyphCount()]; for (int i = 0; i < textRecord.getGlyphCount(); i++) { glyphEntries[i] = readGlyphEntry(glyphBits, advanceBits); } textRecord.setGlyphEntries(glyphEntries); return textRecord; } private GlyphEntry readGlyphEntry(int glyphBits, int advanceBits) { final GlyphEntry entry = new GlyphEntry(); entry.setGlyphIndex(bitStream.readUB(glyphBits)); entry.setGlyphAdvance(bitStream.readSB(advanceBits)); return entry; } private DefineFontNameTag readFontName() throws MalformedTagException { final int fontId = bitStream.readUI16(); final DefineFontNameTag tag = new DefineFontNameTag(); final ICharacterTag character = getTagById(fontId, tag.getTagType()); final String fontName = bitStream.readString(); final String fontCopyright = bitStream.readString(); tag.setFontTag(character); tag.setFontName(fontName); tag.setFontCopyright(fontCopyright); ((IDefineFontTag)character).setLicense(tag); return tag; } /** * @return a valid tag. * @throws MalformedTagException * @throws RuntimeException if the record is invalid. */ private DefineFontAlignZonesTag readDefineFontAlignZones() throws MalformedTagException { final int fontId = bitStream.readUI16(); final DefineFontAlignZonesTag tag = new DefineFontAlignZonesTag(); final ICharacterTag character = getTagById(fontId, tag.getTagType()); if (character instanceof DefineFont3Tag) { final DefineFont3Tag fontTag = (DefineFont3Tag)character; bitStream.byteAlign(); final int csmTableHint = bitStream.readUB(2); bitStream.byteAlign(); // skip reserved final ZoneRecord[] zoneTable = new ZoneRecord[fontTag.getNumGlyphs()]; for (int i = 0; i < fontTag.getNumGlyphs(); i++) { zoneTable[i] = readZoneRecord(); } tag.setFontTag(fontTag); tag.setCsmTableHint(csmTableHint); tag.setZoneTable(zoneTable); fontTag.setZones(tag); return tag; } else { problems.add(new SWFDefineFontAlignZonesLinkToIncorrectFontProblem(fontId, swfPath, bitStream.getOffset())); throw new MalformedTagException(); } } /** * @return */ private ZoneRecord readZoneRecord() { final int numZoneData = bitStream.readUI8(); assert 2 == numZoneData; final ZoneData zoneData0 = readZoneData(); final ZoneData zoneData1 = readZoneData(); bitStream.byteAlign(); bitStream.readUB(6); // reserved final boolean zoneMaskY = bitStream.readBit(); final boolean zoneMaskX = bitStream.readBit(); final ZoneRecord zoneRecord = new ZoneRecord(); zoneRecord.setZoneData0(zoneData0); zoneRecord.setZoneData1(zoneData1); zoneRecord.setZoneMaskY(zoneMaskY); zoneRecord.setZoneMaskX(zoneMaskX); return zoneRecord; } private ZoneData readZoneData() { final ZoneData zoneData = new ZoneData(); zoneData.setData(bitStream.readUI32()); return zoneData; } private DefineFont3Tag readDefineFont3() throws IOException, MalformedTagException { final DefineFont3Tag tag = new DefineFont3Tag(); readDefineFont2And3(tag); return tag; } /** * @throws MalformedTagException * @see SWFWriter#writeDefineFont2 */ private DefineFont2Tag readDefineFont2() throws IOException, MalformedTagException { final DefineFont2Tag tag = new DefineFont2Tag(); readDefineFont2And3(tag); return tag; } private void readDefineFont2And3(DefineFont2Tag tag) throws IOException, MalformedTagException { // reading final int fontId = bitStream.readUI16(); bitStream.byteAlign(); final boolean fontFlagsHasLayout = bitStream.readBit(); final boolean fontFlagsShiftJIS = bitStream.readBit(); final boolean fontFlagsSmallText = bitStream.readBit(); final boolean fontFlagsANSI = bitStream.readBit(); final boolean fontFlagsWideOffsets = bitStream.readBit(); final boolean fontFlagsWideCodes = bitStream.readBit(); final boolean fontFlagsItalic = bitStream.readBit(); final boolean fontFlagsBold = bitStream.readBit(); final int languageCode = bitStream.readUI8(); final String fontName = readLengthString(); final int numGlyphs = bitStream.readUI16(); // read offset table final long[] offsetTable = new long[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { offsetTable[i] = fontFlagsWideOffsets ? bitStream.readUI32() : bitStream.readUI16(); } // Only read the CodeTableOffset if numGlyphs > 0 long codeTableOffset = 0; if (numGlyphs > 0) { codeTableOffset = fontFlagsWideOffsets ? bitStream.readUI32() : bitStream.readUI16(); } final Shape[] glyphShapeTable = new Shape[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { glyphShapeTable[i] = readShape(tag.getTagType()); } // read code table final int[] codeTable = new int[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { codeTable[i] = fontFlagsWideCodes ? bitStream.readUI16() : bitStream.readUI8(); } int fontAscent = 0; int fontDescent = 0; int fontLeading = 0; int[] fontAdvanceTable = null; Rect[] fontBoundsTable = null; int kerningCount = 0; KerningRecord[] fontKerningTable = null; if (fontFlagsHasLayout) { fontAscent = bitStream.readSI16(); fontDescent = bitStream.readSI16(); fontLeading = bitStream.readSI16(); fontAdvanceTable = new int[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { fontAdvanceTable[i] = bitStream.readSI16(); } fontBoundsTable = new Rect[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { fontBoundsTable[i] = readRect(); } kerningCount = bitStream.readUI16(); fontKerningTable = new KerningRecord[kerningCount]; for (int i = 0; i < kerningCount; i++) { fontKerningTable[i] = readKerningRecord(fontFlagsWideCodes); } } // construct tag tag.setCharacterID(fontId); tag.setFontFlagsHasLayout(fontFlagsHasLayout); tag.setFontFlagsShiftJIS(fontFlagsShiftJIS); tag.setFontFlagsSmallText(fontFlagsSmallText); tag.setFontFlagsANSI(fontFlagsANSI); tag.setFontFlagsWideOffsets(fontFlagsWideOffsets); tag.setFontFlagsWideCodes(fontFlagsWideCodes); tag.setFontFlagsItalic(fontFlagsItalic); tag.setFontFlagsBold(fontFlagsBold); tag.setLanguageCode(languageCode); tag.setFontName(fontName); tag.setNumGlyphs(numGlyphs); tag.setOffsetTable(offsetTable); tag.setCodeTableOffset(codeTableOffset); tag.setGlyphShapeTable(glyphShapeTable); tag.setCodeTable(codeTable); tag.setFontAscent(fontAscent); tag.setFontDescent(fontDescent); tag.setFontLeading(fontLeading); tag.setFontAdvanceTable(fontAdvanceTable); tag.setFontBoundsTable(fontBoundsTable); tag.setKerningCount(kerningCount); tag.setFontKerningTable(fontKerningTable); } /** * @see SWFWriter#writeKerningRecord */ private KerningRecord readKerningRecord(boolean fontFlagsWideCodes) { final int code1 = fontFlagsWideCodes ? bitStream.readUI16() : bitStream.readUI8(); final int code2 = fontFlagsWideCodes ? bitStream.readUI16() : bitStream.readUI8(); final int adjustment = bitStream.readSI16(); final KerningRecord rec = new KerningRecord(); rec.setCode1(code1); rec.setCode2(code2); rec.setAdjustment(adjustment); return rec; } private ITag readDefineFontInfo(TagType type) throws IOException, MalformedTagException { assert type == TagType.DefineFontInfo || type == TagType.DefineFontInfo2 : "unknown tag type in readDefineFontInfo"; final int fontId = bitStream.readUI16(); final ICharacterTag fontTag = getTagById(fontId, type); final String fontName = readLengthString(); final int reserved = bitStream.readUB(2); final boolean smallText = bitStream.readBit(); final boolean shiftJIS = bitStream.readBit(); final boolean ansi = bitStream.readBit(); final boolean italic = bitStream.readBit(); final boolean bold = bitStream.readBit(); final boolean wideCodes = bitStream.readBit(); int langCode = 0; if (type == TagType.DefineFontInfo2) langCode = bitStream.readUI8(); final byte[] codeTableRaw = bitStream.readToBoundary(); final int numGlyphs = codeTableRaw.length / (wideCodes ? 2 : 1); final int[] codeTable = new int[numGlyphs]; final IInputBitStream codeTableStream = new InputBitStream(codeTableRaw); codeTableStream.setReadBoundary(codeTableRaw.length); for (int i = 0; i < numGlyphs; i++) { codeTable[i] = wideCodes ? codeTableStream.readUI16() : codeTableStream.readUI8(); } codeTableStream.close(); DefineFontInfoTag tag = null; if (type == TagType.DefineFontInfo) tag = new DefineFontInfoTag(); else tag = new DefineFontInfo2Tag(); tag.setFontTag(fontTag); tag.setFontName(fontName); tag.setFontFlagsReserved(reserved); tag.setFontFlagsSmallText(smallText); tag.setFontFlagsShiftJIS(shiftJIS); tag.setFontFlagsANSI(ansi); tag.setFontFlagsItalic(italic); tag.setFontFlagsBold(bold); tag.setFontFlagsWideCodes(wideCodes); if (type == TagType.DefineFontInfo2) ((DefineFontInfo2Tag)tag).setLanguageCode(langCode); tag.setCodeTable(codeTable); return tag; } /** * The OffsetTable and GlyphShapeTable are used together. These tables have * the same number of entries, and there is a one-to-one ordering match * between the order of the offsets and the order of the shapes. The * OffsetTable points to locations in the GlyphShapeTable. Each offset entry * stores the difference (in bytes) between the start of the offset table * and the location of the corresponding shape. Because the GlyphShapeTable * immediately follows the OffsetTable, the number of entries in each table * (the number of glyphs in the font) can be inferred by dividing the first * entry in the OffsetTable by two. * * @throws MalformedTagException * @see SWFWriter#writeDefineFont */ private ITag readDefineFont() throws IOException, MalformedTagException { final int id = bitStream.readUI16(); final int firstGlyphShapeOffset = bitStream.readUI16(); final int numGlyphs = firstGlyphShapeOffset / 2; final DefineFontTag tag = new DefineFontTag(); tag.setCharacterID(id); final long[] offsetTable = new long[numGlyphs]; offsetTable[0] = firstGlyphShapeOffset; for (int i = 1; i < numGlyphs; i++) { offsetTable[i] = bitStream.readUI16(); } tag.setOffsetTable(offsetTable); final Shape[] glyphShapeTable = new Shape[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { glyphShapeTable[i] = readShape(tag.getTagType()); } tag.setGlyphShapeTable(glyphShapeTable); return tag; } /** * @see SWFWriter#writeDefineBitsJPEG3 */ private ITag readDefineBitsJPEG3() throws IOException { final int id = bitStream.readUI16(); final int alphaDataOffset = (int)bitStream.readUI32(); final byte[] imageData = bitStream.read(alphaDataOffset); final byte[] bitmapAlphaData = bitStream.readToBoundary(); final DefineBitsJPEG3Tag tag = new DefineBitsJPEG3Tag(); tag.setCharacterID(id); tag.setAlphaDataOffset(alphaDataOffset); tag.setImageData(imageData); tag.setBitmapAlphaData(bitmapAlphaData); return tag; } /** * @see SWFWriter#writeDefineBitsJPEG2 */ private ITag readDefineBitsJPEG2() throws IOException { final DefineBitsJPEG2Tag tag = new DefineBitsJPEG2Tag(); tag.setCharacterID(bitStream.readUI16()); tag.setImageData(bitStream.readToBoundary()); return tag; } /** * @see SWFWriter#writeJPEGTables */ private ITag readJPEGTables() throws IOException { final JPEGTablesTag tag = new JPEGTablesTag(); tag.setJpegData(bitStream.readToBoundary()); return tag; } /** * @see SWFWriter#writeDefineBits */ private ITag readDefineBits() throws IOException { final DefineBitsTag tag = new DefineBitsTag(); tag.setCharacterID(bitStream.readUI16()); tag.setImageData(bitStream.readToBoundary()); return tag; } private String readLengthString() throws IOException { int length = bitStream.readUI8(); byte[] b = new byte[length]; bitStream.read(b); // [paul] Flash Authoring and the player null terminate the // string, so ignore the last byte when constructing the String. if (swf.getVersion() >= 6) { return new String(b, 0, length - 1, "UTF8"); } else { // use platform encoding return new String(b, 0, length - 1); } } }