blob: 2ce3c04b1ea2ab3b34e3f96867128568e04bd569 [file] [log] [blame]
/*
*
* 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);
}
}
}