blob: a5cbc3f6b99b936adbed2185ec771a27ac62797b [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.royale.swf.io;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.royale.swf.Header;
import org.apache.royale.swf.Header.Compression;
import org.apache.royale.swf.ISWF;
import org.apache.royale.swf.SWF;
import org.apache.royale.swf.SWFFrame;
import org.apache.royale.swf.TagType;
import org.apache.royale.swf.io.SWFReader.CurrentStyles;
import org.apache.royale.swf.tags.*;
import org.apache.royale.swf.types.ARGB;
import org.apache.royale.swf.types.BevelFilter;
import org.apache.royale.swf.types.BlurFilter;
import org.apache.royale.swf.types.ButtonRecord;
import org.apache.royale.swf.types.CXForm;
import org.apache.royale.swf.types.CXFormWithAlpha;
import org.apache.royale.swf.types.ConvolutionFilter;
import org.apache.royale.swf.types.CurvedEdgeRecord;
import org.apache.royale.swf.types.DropShadowFilter;
import org.apache.royale.swf.types.EndShapeRecord;
import org.apache.royale.swf.types.FillStyle;
import org.apache.royale.swf.types.FillStyleArray;
import org.apache.royale.swf.types.Filter;
import org.apache.royale.swf.types.FocalGradient;
import org.apache.royale.swf.types.GlowFilter;
import org.apache.royale.swf.types.GlyphEntry;
import org.apache.royale.swf.types.GradRecord;
import org.apache.royale.swf.types.Gradient;
import org.apache.royale.swf.types.GradientBevelFilter;
import org.apache.royale.swf.types.GradientGlowFilter;
import org.apache.royale.swf.types.IFillStyle;
import org.apache.royale.swf.types.ILineStyle;
import org.apache.royale.swf.types.KerningRecord;
import org.apache.royale.swf.types.LineStyle;
import org.apache.royale.swf.types.LineStyle2;
import org.apache.royale.swf.types.LineStyleArray;
import org.apache.royale.swf.types.Matrix;
import org.apache.royale.swf.types.MorphFillStyle;
import org.apache.royale.swf.types.MorphGradRecord;
import org.apache.royale.swf.types.MorphGradient;
import org.apache.royale.swf.types.MorphLineStyle;
import org.apache.royale.swf.types.MorphLineStyle2;
import org.apache.royale.swf.types.RGB;
import org.apache.royale.swf.types.RGBA;
import org.apache.royale.swf.types.Rect;
import org.apache.royale.swf.types.Shape;
import org.apache.royale.swf.types.ShapeRecord;
import org.apache.royale.swf.types.ShapeWithStyle;
import org.apache.royale.swf.types.SoundEnvelope;
import org.apache.royale.swf.types.SoundInfo;
import org.apache.royale.swf.types.StraightEdgeRecord;
import org.apache.royale.swf.types.StyleChangeRecord;
import org.apache.royale.swf.types.Styles;
import org.apache.royale.swf.types.TextRecord;
import org.apache.royale.swf.types.ZoneRecord;
import com.google.common.primitives.Doubles;
import com.google.common.primitives.Ints;
/**
* The implementation of SWF tag, type encoding logic. The SWF file body are
* buffered in memory using {@code IOutputBitStream}. ZLIB compression is
* optional. If enabled, compression is on-the-fly via a filtered output stream.
*/
public class SWFWriter implements ISWFWriter
{
/**
* Default SWF writer factory.
*/
private static class SWFWriterFactory implements ISWFWriterFactory
{
@Override
public ISWFWriter createSWFWriter(ISWF swf, Compression useCompression,
boolean enableDebug, boolean enableTelemetry)
{
return new SWFWriter(swf, useCompression, enableDebug, enableTelemetry);
}
}
/**
* A factory for default SWF writers. These SWF writers just write SWFs
* without any additional features.
*/
public static final ISWFWriterFactory DEFAULT_SWF_WRITER_FACTORY = new SWFWriterFactory();
private static final int RESERVED = 0;
private static final int SHORT_TAG_MAX_LENGTH = 62;
/**
* Compares the absolute values of 4 signed integers and returns the
* unsigned magnitude of the number with the greatest absolute value.
*/
public static int maxNum(int a, int b, int c, int d)
{
// take the absolute values of the given numbers
a = Math.abs(a);
b = Math.abs(b);
c = Math.abs(c);
d = Math.abs(d);
// compare the numbers and return the unsigned value of the one with the greatest magnitude
return Ints.max(a, b, c, d);
}
/**
* Compares the absolute values of 4 signed doubles and returns the unsigned
* magnitude of the number with the greatest absolute value.
*/
public static double maxNum(double a, double b, double c, double d)
{
// take the absolute values of the given numbers
a = Math.abs(a);
b = Math.abs(b);
c = Math.abs(c);
d = Math.abs(d);
// compare the numbers and return the unsigned value of the one with the greatest magnitude
return Doubles.max(a, b, c, d);
}
/**
* Calculate number of bits required to represent the given value in double
* bit value.
*
* @param value signed integer
* @return number of bits required for SB[]
*/
public static int requireFBCount(double value)
{
return requireSBCount((int)(value * 0x10000));
}
/**
* Calculate number of bits required to represent the given value in signed
* bit values.
*
* @param value signed integer
* @return number of bits required for SB[]
*/
public static int requireSBCount(int value)
{
return minBits(Math.abs(value), 1);
}
public static int requireSBCount(int... values)
{
final int array[] = new int[values.length];
for (int i = 0; i < values.length; i++)
array[i] = requireSBCount(values[i]);
Arrays.sort(array); // ascending order: last one is the biggest
return array[array.length - 1];
}
/**
* Calculate number of bits required to represent the given value in
* unsigned bit values.
*
* @param value signed integer
* @return number of bits required for SB[]
*/
public static int requireUBCount(int value)
{
assert (value >= 0) : "requireUBCount called with negative number";
return minBits(value, 0);
}
/**
* Calculates the minimum number of bits necessary to represent the given
* number. The number should be given in its unsigned form with the starting
* bits equal to 1 if it is signed. Repeatedly compares number to another
* unsigned int called x. x is initialized to 1. The value of x is shifted
* left i times until x is greater than number. Now i is equal to the number
* of bits the UNSIGNED value of number needs. The signed value will need
* one more bit for the sign so i+1 is returned if the number is signed, and
* i is returned if the number is unsigned.
*
* @param number the number to compute the size of
* @param bits 1 if number is signed, 0 if unsigned
*/
private static int minBits(int number, int bits)
{
int val = 1;
for (int x = 1; val <= number && !(bits > 32); x <<= 1)
{
val = val | x;
bits++;
}
assert (bits <= 32) : ("minBits " + bits + " must not exceed 32");
return bits;
}
private void writeLengthString(String name)
{
try
{
assert (tagBuffer.getBitPos() == 8);
byte[] b = swf.getVersion() >= 6 ? name.getBytes("UTF8") : name.getBytes();
// [paul] Flash Authoring and the player expect the String
// to be null terminated.
tagBuffer.writeUI8(b.length + 1);
tagBuffer.write(b);
tagBuffer.writeUI8(0);
}
catch (UnsupportedEncodingException e)
{
assert false;
}
}
// Tag buffer
protected IOutputBitStream tagBuffer;
// SWF model
private final ISWF swf;
// This buffer contains the SWF data after FileLength field.
protected final IOutputBitStream outputBuffer;
// True if the encoded SWF file is compressed.
private final Header.Compression useCompression;
// True if debugging of the SWF is enabled.
private final boolean enableDebug;
// True if telemetry features of the SWF are enabled.
private final boolean enableTelemetry;
// Current frame index. Updated in writeFrames().
private int currentFrameIndex;
// Prevent writing out the same tag twice.
private Set<ITag> writtenTags;
/**
* Create a SWF writer.
*
* @param swf the SWF model to be encoded
* @param useCompression use ZLIB compression if true
*/
public SWFWriter(ISWF swf, Header.Compression useCompression)
{
this(swf, useCompression, false, false);
}
/**
* Create a SWF writer.
*
* @param swf the SWF model to be encoded
* @param useCompression use ZLIB compression if true
* @param enableDebug enable debugging of the SWF if true
* @param enableTelemetry enable telemetry
*/
public SWFWriter(ISWF swf, Header.Compression useCompression, boolean enableDebug, boolean enableTelemetry)
{
this.swf = swf;
this.useCompression = useCompression;
this.enableDebug = enableDebug;
this.enableTelemetry = enableTelemetry;
this.outputBuffer = new OutputBitStream(false);
this.tagBuffer = new OutputBitStream(false);
computeCharacterID();
}
/**
* Compute the character ID for all the {@code ICharacterTag}s.
*/
private void computeCharacterID()
{
int id = 1; // need to start at 1, as index 0 has special meaning
for (int frameIndex = 0; frameIndex < swf.getFrameCount(); frameIndex++)
{
final SWFFrame frame = swf.getFrameAt(frameIndex);
for (final ITag tag : frame)
{
if (tag instanceof CharacterTag)
{
final CharacterTag characterTag = (CharacterTag)tag;
characterTag.setCharacterID(id);
id++;
}
}
}
}
/**
* Compute the tag length for the tag header, then write the header and the
* buffered tag body to target output stream.
*
* @param tag tag object
* @param tagData serialized tag body
* @param out target output stream
*/
protected void finishTag(
final ITag tag,
final IOutputBitStream tagData,
final IOutputBitStream out)
{
tagData.flush();
final int tagLength = tagData.size();
// write tag header
if (tag instanceof IAlwaysLongTag || tagLength > SHORT_TAG_MAX_LENGTH)
{
// use long tag header
out.writeUI16((tag.getTagType().getValue() << 6) | 0x3F);
out.writeSI32(tagLength);
}
else
{
// use short tag header
out.writeUI16((tag.getTagType().getValue() << 6) | tagLength);
}
out.write(tagData.getBytes(), 0, tagLength);
}
public void writeARGB(ARGB argb)
{
tagBuffer.writeUI8(argb.getAlpha());
tagBuffer.writeUI8(argb.getRed());
tagBuffer.writeUI8(argb.getGreen());
tagBuffer.writeUI8(argb.getBlue());
}
protected void writeCompressibleHeader()
{
// Frame size
final Rect rect = swf.getFrameSize();
tagBuffer.reset();
writeRect(rect);
outputBuffer.write(tagBuffer.getBytes(), 0, tagBuffer.size());
// Frame rate
outputBuffer.writeFIXED8(swf.getFrameRate());
// Frame count
outputBuffer.writeUI16(swf.getFrameCount());
}
/**
* @see SWFReader#readCurvedEdgeRecord
*/
private void writeCurvedEdgeRecord(CurvedEdgeRecord shape)
{
tagBuffer.writeBit(true); // This is an edge. Always 1.
tagBuffer.writeBit(false); // StraightFlag is always false.
int numBits = shape.getNumBits();
tagBuffer.writeUB(numBits, 4);
tagBuffer.writeSB(shape.getControlDeltaX(), numBits + 2);
tagBuffer.writeSB(shape.getControlDeltaY(), numBits + 2);
tagBuffer.writeSB(shape.getAnchorDeltaX(), numBits + 2);
tagBuffer.writeSB(shape.getAnchorDeltaY(), numBits + 2);
}
private void writeDefineBinaryData(DefineBinaryDataTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUI32(0); // Reserved, always zero.
tagBuffer.write(tag.getData());
}
/**
* This method treats the bytes after the color table as a binary blob so
* both the lossless and lossless2 tags can be written using this method.
*
* @param tag
*/
private void writeDefineBitsLossless(DefineBitsLosslessTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUI8(tag.getBitmapFormat());
tagBuffer.writeUI16(tag.getBitmapWidth());
tagBuffer.writeUI16(tag.getBitmapHeight());
if (DefineBitsLossless2Tag.BF_8BIT_COLORMAPPED_IMAGE == tag.getBitmapFormat())
{
tagBuffer.writeUI8(tag.getBitmapColorTableSize() - 1);
}
tagBuffer.write(tag.getZlibBitmapData());
}
/**
* @see SWFReader#readDefineShape
*/
private void writeDefineShape(DefineShapeTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
writeRect(tag.getShapeBounds());
writeShapeWithStyle(tag.getShapes(), tag.getTagType());
}
/**
* @see SWFReader#readDefineShape2
*/
private void writeDefineShape2(DefineShape2Tag tag)
{
writeDefineShape(tag);
}
/**
* @see SWFReader#readDefineShape3
*/
private void writeDefineShape3(DefineShape3Tag tag)
{
writeDefineShape2(tag);
}
/**
* @see SWFReader#readDefineShape4
*/
private void writeDefineShape4(DefineShape4Tag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
writeRect(tag.getShapeBounds());
writeRect(tag.getEdgeBounds());
tagBuffer.byteAlign();
tagBuffer.writeUB(0, 5); // Reserved. Must be 0.
tagBuffer.writeBit(tag.isUsesFillWindingRule());
tagBuffer.writeBit(tag.isUsesNonScalingStrokes());
tagBuffer.writeBit(tag.isUsesScalingStrokes());
tagBuffer.byteAlign();
writeShapeWithStyle(tag.getShapes(), tag.getTagType());
}
/**
* @see SWFReader#readDefineMorphShape2
* @see SWFWriter#writeDefineMorphShape
*/
private void writeDefineMorphShape2(DefineMorphShape2Tag tag)
{
writeDefineMorphShape(tag);
}
/**
* @see SWFReader#readDefineMorphShape
*/
private void writeDefineMorphShape(DefineMorphShapeTag tag)
{
// Write to another buffer to calculate offset to EndEdges field.
final IOutputBitStream originalTagBuffer = tagBuffer;
tagBuffer = new OutputBitStream();
// fields before "offset"
tagBuffer.writeUI16(tag.getCharacterID());
writeRect(tag.getStartBounds());
writeRect(tag.getEndBounds());
if (TagType.DefineMorphShape2 == tag.getTagType())
{
final DefineMorphShape2Tag tag2 = (DefineMorphShape2Tag)tag;
writeRect(tag2.getStartEdgeBounds());
writeRect(tag2.getEndEdgeBounds());
tagBuffer.writeUB(0, 6);
tagBuffer.writeBit(tag2.isUsesNonScalingStrokes());
tagBuffer.writeBit(tag2.isUsesScalingStrokes());
tagBuffer.byteAlign();
}
final int sizeBeforeOffset = tagBuffer.size();
// fields after "offset"
Shape startEdges = tag.getStartEdges();
writeShapeWithStyle((ShapeWithStyle)startEdges, tag.getTagType());
final int sizeAfterOffsetToEnd = tagBuffer.size() - sizeBeforeOffset;
// put together
originalTagBuffer.write(tagBuffer.getBytes(), 0, sizeBeforeOffset);
originalTagBuffer.writeUI32(sizeAfterOffsetToEnd);
originalTagBuffer.write(tagBuffer.getBytes(), sizeBeforeOffset, sizeAfterOffsetToEnd);
// recover current tag buffer
tagBuffer = originalTagBuffer;
writeShape(tag.getEndEdges(), tag.getTagType(), 0, 0);
}
/**
* @see SWFReader#readShape
*/
private void writeShape(Shape shape, TagType tagType, int fillStyleCount, int lineStyleCount)
{
CurrentStyles currentStyles = new CurrentStyles();
currentStyles.numFillBits = requireUBCount(fillStyleCount);
currentStyles.numLineBits = requireUBCount(lineStyleCount);
writeShape(shape, tagType, currentStyles);
}
/**
* @see SWFReader#readShape
*/
private void writeShape(Shape shape, TagType tagType, CurrentStyles currentStyles)
{
tagBuffer.writeUB(currentStyles.numFillBits, 4);
tagBuffer.writeUB(currentStyles.numLineBits, 4);
for (final ShapeRecord shapeRecord : shape.getShapeRecords())
{
writeShapeRecord(shapeRecord, tagType, currentStyles);
}
writeShapeRecord(new EndShapeRecord(), tagType, currentStyles);
tagBuffer.byteAlign();
}
/**
* @see SWFReader#readMorphLineStyleArray
*/
// private void writeMorphLineStyleArray(MorphLineStyleArray lineStyles)
// {
// writeExtensibleCount(lineStyles.size());
// for (MorphLineStyle lineStyle : lineStyles)
// {
// writeMorphLineStyle(lineStyle);
// }
// }
/**
* @see SWFReader#readMorphLineStyle
*/
private void writeMorphLineStyle(MorphLineStyle lineStyle)
{
tagBuffer.writeUI16(lineStyle.getStartWidth());
tagBuffer.writeUI16(lineStyle.getEndWidth());
writeRGBA(lineStyle.getStartColor());
writeRGBA(lineStyle.getEndColor());
}
private void writeMorphLineStyle2(MorphLineStyle2 lineStyle, TagType tagType)
{
// widths
tagBuffer.writeUI16(lineStyle.getStartWidth());
tagBuffer.writeUI16(lineStyle.getEndWidth());
// misc fields byte
tagBuffer.writeUB(lineStyle.getStartCapStyle(), 2);
tagBuffer.writeUB(lineStyle.getJoinStyle(), 2);
tagBuffer.writeBit(lineStyle.isHasFillFlag());
tagBuffer.writeBit(lineStyle.isNoHScaleFlag());
tagBuffer.writeBit(lineStyle.isNoVScaleFlag());
tagBuffer.writeBit(lineStyle.isPixelHintingFlag());
// next mixed byte
tagBuffer.writeUB(0, 5); // reserved
tagBuffer.writeBit(lineStyle.isNoClose());
tagBuffer.writeUB(lineStyle.getEndCapStyle(), 2);
//
if (lineStyle.getJoinStyle() == LineStyle2.JS_MITER_JOIN)
{
tagBuffer.writeUI16(lineStyle.getMiterLimitFactor());
}
if (!lineStyle.isHasFillFlag())
{
writeRGBA(lineStyle.getStartColor());
writeRGBA(lineStyle.getEndColor());
}
else
{
writeMorphFillStyle(lineStyle.getFillType(), tagType);
}
}
/**
* @see SWFReader#readMorphFillStyle
*/
private void writeMorphFillStyle(MorphFillStyle fillStyle, TagType tagType)
{
int fillStyleType = fillStyle.getFillStyleType();
tagBuffer.writeUI8(fillStyleType);
switch (fillStyle.getFillStyleType())
{
case FillStyle.SOLID_FILL:
writeRGBA(fillStyle.getStartColor());
writeRGBA(fillStyle.getEndColor());
break;
case FillStyle.LINEAR_GRADIENT_FILL:
case FillStyle.RADIAL_GRADIENT_FILL:
case FillStyle.FOCAL_RADIAL_GRADIENT_FILL:
writeMatrix(fillStyle.getStartGradientMatrix());
writeMatrix(fillStyle.getEndGradientMatrix());
writeMorphGradient(fillStyle.getGradient());
if (fillStyleType == FillStyle.FOCAL_RADIAL_GRADIENT_FILL &&
tagType.getValue() == TagType.DefineMorphShape2.getValue())
{
tagBuffer.writeSI16(fillStyle.getRatio1());
tagBuffer.writeSI16(fillStyle.getRatio2());
}
break;
case FillStyle.REPEATING_BITMAP_FILL:
case FillStyle.CLIPPED_BITMAP_FILL:
case FillStyle.NON_SMOOTHED_CLIPPED_BITMAP:
case FillStyle.NON_SMOOTHED_REPEATING_BITMAP:
tagBuffer.writeUI16(fillStyle.getBitmap().getCharacterID());
writeMatrix(fillStyle.getStartBitmapMatrix());
writeMatrix(fillStyle.getEndBitmapMatrix());
break;
}
}
/**
* @see SWFReader#readMorphGradient
*/
private void writeMorphGradient(MorphGradient gradient)
{
tagBuffer.writeUI8(gradient.size());
for (MorphGradRecord morphGradRecord : gradient)
{
writeMorphGradRecord(morphGradRecord);
}
}
/**
* @see SWFReader#readMorphGradRecord
*/
private void writeMorphGradRecord(MorphGradRecord morphGradRecord)
{
tagBuffer.writeUI8(morphGradRecord.getStartRatio());
writeRGBA(morphGradRecord.getStartColor());
tagBuffer.writeUI8(morphGradRecord.getEndRatio());
writeRGBA(morphGradRecord.getEndColor());
}
/**
* @see SWFReader#readExtensibleCount
* @param count
*/
private void writeExtensibleCount(int count)
{
if (count < 0xFF)
{
tagBuffer.writeUI8(count);
}
else
{
tagBuffer.writeUI8(0xFF);
tagBuffer.writeUI16(count);
}
}
public void writeDoABC(DoABCTag tag)
{
assert swf.getUseAS3() : "DoABC tag requires FileAttributes.Actionscript3=true.";
tagBuffer.writeUI32(tag.getFlags());
tagBuffer.writeString(tag.getName());
tagBuffer.write(tag.getABCData());
}
private void writeEnableDebugger2(EnableDebugger2Tag tag)
{
tagBuffer.writeUI16(0); // reserved always zero
tagBuffer.writeString(tag.getPassword());
}
private void writeEnableTelemetry(EnableTelemetryTag tag)
{
// Tag Code (Upper 10 bits = tag type, lower 16 bit = tag length)
tagBuffer.writeUI16(0); // reserved always zero
// PasswordHash: Optional SHA-256 hash of the UTF-8 representation of the password.
// If not present telemetry clients can connect without using a password, if set they
// have to authenticate.
if(tag.getPassword() != null) {
tagBuffer.writeString(tag.getPassword());
}
}
private void writeEnd()
{
// End tag has no tag body.
}
private void writeEndShapeRecord(EndShapeRecord shape)
{
tagBuffer.writeBit(shape.getTypeFlag());
tagBuffer.writeUB(0, 5); // EndOfShape always 0.
}
public void writeFileAttributes(FileAttributesTag tag)
{
tagBuffer.writeBit(false);
tagBuffer.writeBit(tag.isUseDirectBlit());
tagBuffer.writeBit(tag.isUseGPU());
tagBuffer.writeBit(tag.isHasMetadata());
tagBuffer.writeBit(tag.isAS3());
tagBuffer.writeUB(RESERVED, 2);
tagBuffer.writeBit(tag.isUseNetwork());
tagBuffer.writeUB(RESERVED, 24);
tagBuffer.byteAlign();
}
private void writeFillStyle(IFillStyle fillStyle, TagType tagType)
{
if (fillStyle instanceof FillStyle)
writeFillStyle((FillStyle)fillStyle, tagType);
else if (fillStyle instanceof MorphFillStyle)
writeMorphFillStyle((MorphFillStyle)fillStyle, tagType);
else
assert false;
}
private void writeFillStyle(FillStyle fillStyle, TagType tagType)
{
assert fillStyle != null;
final int fillStyleType = fillStyle.getFillStyleType();
tagBuffer.writeUI8(fillStyleType);
switch (fillStyleType)
{
case FillStyle.SOLID_FILL:
switch (tagType)
{
case DefineShape3:
case DefineShape4:
writeRGBA((RGBA)fillStyle.getColor());
break;
case DefineShape:
case DefineShape2:
writeRGB(fillStyle.getColor());
break;
default:
throw new IllegalArgumentException("Invalid tag: " + tagType);
}
break;
case FillStyle.LINEAR_GRADIENT_FILL:
case FillStyle.RADIAL_GRADIENT_FILL:
writeMatrix(fillStyle.getGradientMatrix());
writeGradient(fillStyle.getGradient(), tagType);
break;
case FillStyle.FOCAL_RADIAL_GRADIENT_FILL:
writeMatrix(fillStyle.getGradientMatrix());
writeFocalGradient((FocalGradient)fillStyle.getGradient(), tagType);
break;
case FillStyle.REPEATING_BITMAP_FILL:
case FillStyle.CLIPPED_BITMAP_FILL:
case FillStyle.NON_SMOOTHED_REPEATING_BITMAP:
case FillStyle.NON_SMOOTHED_CLIPPED_BITMAP:
tagBuffer.writeUI16(
fillStyle.getBitmapCharacter().getCharacterID());
writeMatrix(fillStyle.getBitmapMatrix());
break;
default:
throw new IllegalArgumentException(
"Invalid FillStyleType: " + fillStyleType);
}
}
private void writeFillStyles(FillStyleArray fillStyles, TagType tagType)
{
assert fillStyles != null;
final int fillStyleCount = fillStyles.size();
writeExtensibleCount(fillStyleCount);
for (final IFillStyle fillStyle : fillStyles)
{
writeFillStyle(fillStyle, tagType);
}
}
private void writeFocalGradient(FocalGradient gradient, TagType tagType)
{
assert TagType.DefineShape4 == tagType;
writeGradient(gradient, tagType);
tagBuffer.writeFIXED8(gradient.getFocalPoint());
}
private void writeFrames()
{
for (currentFrameIndex = 0; currentFrameIndex < swf.getFrameCount(); currentFrameIndex++)
{
final SWFFrame frame = swf.getFrameAt(currentFrameIndex);
// If the SWF has a top level class name, the first frame must contain a SymbolClass tag.
if (0 == currentFrameIndex && null != swf.getTopLevelClass())
{
SWFFrame.forceSymbolClassTag(frame);
}
for (final ITag tag : frame)
{
writeTag(tag);
}
}
}
private void writeGradient(Gradient gradient, TagType tagType)
{
assert gradient != null;
assert gradient.getGradientRecords() != null;
tagBuffer.writeUB(gradient.getSpreadMode(), 2);
tagBuffer.writeUB(gradient.getInterpolationMode(), 2);
tagBuffer.writeUB(gradient.getGradientRecords().size(), 4);
tagBuffer.byteAlign();
for (final GradRecord gradRecord : gradient.getGradientRecords())
{
writeGradRecord(gradRecord, tagType);
}
}
private void writeGradRecord(GradRecord gradRecord, TagType tagType)
{
assert gradRecord != null;
tagBuffer.writeUI8(gradRecord.getRatio());
switch (tagType)
{
case DefineShape:
case DefineShape2:
writeRGB(gradRecord.getColor());
break;
case DefineShape3:
case DefineShape4:
writeRGBA((RGBA)gradRecord.getColor());
break;
default:
throw new IllegalArgumentException("Invalid tag: " + tagType);
}
}
private void writeLineStyle(LineStyle lineStyle, TagType tagType)
{
assert lineStyle != null;
tagBuffer.writeUI16(lineStyle.getWidth());
switch (tagType)
{
case DefineShape:
case DefineShape2:
writeRGB(lineStyle.getColor());
break;
case DefineShape3:
writeRGBA((RGBA)lineStyle.getColor());
break;
default:
throw new IllegalArgumentException("Invalid tag: " + tagType);
}
}
private void writeLineStyle2(LineStyle2 lineStyle, TagType tagType)
{
assert lineStyle != null;
tagBuffer.writeUI16(lineStyle.getWidth());
tagBuffer.writeUB(lineStyle.getStartCapStyle(), 2);
tagBuffer.writeUB(lineStyle.getJoinStyle(), 2);
tagBuffer.writeBit(lineStyle.isHasFillFlag());
tagBuffer.writeBit(lineStyle.isNoHScaleFlag());
tagBuffer.writeBit(lineStyle.isNoVScaleFlag());
tagBuffer.writeBit(lineStyle.isPixelHintingFlag());
tagBuffer.writeUB(0, 5); // Reserved
tagBuffer.writeBit(lineStyle.isNoClose());
tagBuffer.writeUB(lineStyle.getEndCapStyle(), 2);
tagBuffer.byteAlign();
if (LineStyle2.JS_MITER_JOIN == lineStyle.getJoinStyle())
{
tagBuffer.writeFIXED8(lineStyle.getMiterLimitFactor());
}
if (lineStyle.isHasFillFlag())
{
writeFillStyle(lineStyle.getFillType(), tagType);
}
else
{
writeRGBA((RGBA)lineStyle.getColor());
}
}
private void writeLineStyles(LineStyleArray lineStyles, TagType tagType)
{
assert lineStyles != null;
final int lineStyleCount = lineStyles.size();
writeExtensibleCount(lineStyleCount);
for (final ILineStyle lineStyle : lineStyles)
{
switch (tagType)
{
case DefineShape:
case DefineShape2:
case DefineShape3:
writeLineStyle((LineStyle)lineStyle, tagType);
break;
case DefineShape4:
writeLineStyle2((LineStyle2)lineStyle, tagType);
break;
case DefineMorphShape2:
writeMorphLineStyle2((MorphLineStyle2)lineStyle, tagType);
break;
case DefineMorphShape:
writeMorphLineStyle((MorphLineStyle)lineStyle);
break;
default:
throw new IllegalArgumentException("Invalid tag: " + tagType);
}
}
}
/**
* @param matrix
*/
private void writeMatrix(Matrix matrix)
{
// scale (optional)
tagBuffer.writeBit(matrix.hasScale());
if (matrix.hasScale())
{
final double scaleX = matrix.getScaleX();
final double scaleY = matrix.getScaleY();
final int nScaleBits = requireFBCount(maxNum(scaleX, scaleY, 0, 0));
tagBuffer.writeUB(nScaleBits, 5);
tagBuffer.writeFB(scaleX, nScaleBits);
tagBuffer.writeFB(scaleY, nScaleBits);
}
// rotate-skew (optional)
tagBuffer.writeBit(matrix.hasRotate());
if (matrix.hasRotate())
{
final double rotateSkew0 = matrix.getRotateSkew0();
final double rotateSkew1 = matrix.getRotateSkew1();
final int nRotateBits = requireFBCount(maxNum(rotateSkew0, rotateSkew1, 0, 0));
tagBuffer.writeUB(nRotateBits, 5);
tagBuffer.writeFB(rotateSkew0, nRotateBits);
tagBuffer.writeFB(rotateSkew1, nRotateBits);
}
// translate (always)
final int translateX = matrix.getTranslateX();
final int translateY = matrix.getTranslateY();
final int nTranslateBits = requireSBCount(maxNum(translateX, translateY, 0, 0));
tagBuffer.writeUB(nTranslateBits, 5);
tagBuffer.writeSB(translateX, nTranslateBits);
tagBuffer.writeSB(translateY, nTranslateBits);
tagBuffer.byteAlign();
}
/* Tag Encoders */
private void writeMetadata(MetadataTag tag)
{
tagBuffer.writeString(tag.getMetadata());
}
private void writeProductInfo(ProductInfoTag tag)
{
tagBuffer.writeUI32(tag.getProduct().getCode());
tagBuffer.writeUI32(tag.getEdition().getCode());
tagBuffer.writeUI8(tag.getMajorVersion());
tagBuffer.writeUI8(tag.getMinorVersion());
tagBuffer.writeSI64(tag.getBuild());
tagBuffer.writeSI64(tag.getCompileDate());
}
public void writeRect(Rect rect)
{
int maxRectNum = maxNum(rect.xMin(), rect.xMax(), rect.yMin(), rect.yMax());
final int nbits = requireSBCount(maxRectNum);
tagBuffer.writeUB(nbits, 5);
tagBuffer.writeSB(rect.xMin(), nbits);
tagBuffer.writeSB(rect.xMax(), nbits);
tagBuffer.writeSB(rect.yMin(), nbits);
tagBuffer.writeSB(rect.yMax(), nbits);
tagBuffer.byteAlign();
}
public void writeRGB(RGB rgb)
{
tagBuffer.writeUI8(rgb.getRed());
tagBuffer.writeUI8(rgb.getGreen());
tagBuffer.writeUI8(rgb.getBlue());
}
public void writeRGBA(RGBA rgba)
{
tagBuffer.writeUI8(rgba.getRed());
tagBuffer.writeUI8(rgba.getGreen());
tagBuffer.writeUI8(rgba.getBlue());
tagBuffer.writeUI8(rgba.getAlpha());
}
private void writeScriptLimits(ScriptLimitsTag tag)
{
tagBuffer.writeUI16(tag.getMaxRecursionDepth());
tagBuffer.writeUI16(tag.getScriptTimeoutSeconds());
}
private void writeSetBackgroundColor(SetBackgroundColorTag tag)
{
writeRGB(tag.getColor());
}
private void writeShapeRecord(
final ShapeRecord shape,
final TagType tagType,
final CurrentStyles currentStyles)
{
switch (shape.getShapeRecordType())
{
case END_SHAPE:
writeEndShapeRecord((EndShapeRecord)shape);
break;
case CURVED_EDGE:
writeCurvedEdgeRecord((CurvedEdgeRecord)shape);
break;
case STRAIGHT_EDGE:
writeStraightEdgeRecord((StraightEdgeRecord)shape);
break;
case STYLE_CHANGE:
writeStyleChangeRecord(
(StyleChangeRecord)shape,
tagType,
currentStyles);
break;
}
}
private void writeShapeWithStyle(ShapeWithStyle shape, TagType tagType)
{
writeFillStyles(shape.getFillStyles(), tagType);
writeLineStyles(shape.getLineStyles(), tagType);
CurrentStyles currentStyles = new CurrentStyles();
currentStyles.styles = new Styles(shape.getFillStyles(), shape.getLineStyles());
currentStyles.numFillBits = requireUBCount(shape.getFillStyles().size());
currentStyles.numLineBits = requireUBCount(shape.getLineStyles().size());
writeShape(shape, tagType, currentStyles);
}
private void writeShowFrame()
{
// ShowFrame tag has no tag body.
}
/**
* @see SWFReader#readStraightEdgeRecord
*/
private void writeStraightEdgeRecord(StraightEdgeRecord shape)
{
tagBuffer.writeBit(true); // This is an edge. Always 1.
tagBuffer.writeBit(true); // StraightFlag is always true.
int numBits = shape.getNumBits();
tagBuffer.writeUB(numBits, 4);
switch (shape.getLineType())
{
case GENERAL:
tagBuffer.writeBit(true);
tagBuffer.writeSB(shape.getDeltaX(), numBits + 2);
tagBuffer.writeSB(shape.getDeltaY(), numBits + 2);
break;
case VERTICAL:
tagBuffer.writeBit(false);
tagBuffer.writeBit(true);
tagBuffer.writeSB(shape.getDeltaY(), numBits + 2);
break;
case HORIZONTAL:
tagBuffer.writeBit(false);
tagBuffer.writeBit(false);
tagBuffer.writeSB(shape.getDeltaX(), numBits + 2);
break;
}
}
/**
* @see SWFReader#readStyleChangeRecord
*/
private void writeStyleChangeRecord(
StyleChangeRecord shape,
TagType tagType,
CurrentStyles currentStyles)
{
tagBuffer.writeBit(shape.getTypeFlag());
tagBuffer.writeBit(shape.isStateNewStyles());
tagBuffer.writeBit(shape.isStateLineStyle());
tagBuffer.writeBit(shape.isStateFillStyle1());
tagBuffer.writeBit(shape.isStateFillStyle0());
tagBuffer.writeBit(shape.isStateMoveTo());
if (shape.isStateMoveTo())
{
final int moveBits = requireSBCount(maxNum(shape.getMoveDeltaX(), shape.getMoveDeltaY(), 0, 0));
tagBuffer.writeUB(moveBits, 5);
tagBuffer.writeSB(shape.getMoveDeltaX(), moveBits);
tagBuffer.writeSB(shape.getMoveDeltaY(), moveBits);
}
// 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;
if (shape.isStateFillStyle0())
{
int fs0;
if (ignoreStyle)
fs0 = 1;
else
fs0 = currentStyles.styles.getFillStyles().indexOf(shape.getFillstyle0()) + 1;
tagBuffer.writeUB(fs0, currentStyles.numFillBits);
}
if (shape.isStateFillStyle1())
{
int fs1;
if (ignoreStyle)
fs1 = 1;
else
fs1 = currentStyles.styles.getFillStyles().indexOf(shape.getFillstyle1()) + 1;
tagBuffer.writeUB(fs1, currentStyles.numFillBits);
}
if (shape.isStateLineStyle())
{
int ls;
if (ignoreStyle)
ls = 0;
else
ls = currentStyles.styles.getLineStyles().indexOf(shape.getLinestyle()) + 1;
tagBuffer.writeUB(ls, currentStyles.numLineBits);
}
if (shape.isStateNewStyles())
{
tagBuffer.byteAlign();
// encode
writeFillStyles(shape.getStyles().getFillStyles(), tagType);
writeLineStyles(shape.getStyles().getLineStyles(), tagType);
final int nFillBits = shape.getNumFillBits();
final int nLineBits = shape.getNumLineBits();
tagBuffer.writeUB(nFillBits, 4);
tagBuffer.writeUB(nLineBits, 4);
// update context
currentStyles.styles = shape.getStyles();
currentStyles.numFillBits = nFillBits;
currentStyles.numLineBits = nLineBits;
}
}
/**
* @see SWFReader#readSymbolClass
*/
private void writeSymbolClass(SymbolClassTag tag)
{
final boolean writeRootClass = currentFrameIndex == 0 && swf.getTopLevelClass() != null;
// number of symbols
final int count = writeRootClass ? tag.size() + 1 : tag.size();
tagBuffer.writeUI16(count);
// export symbols
for (String symbolName : tag.getSymbolNames())
{
final ICharacterTag characterTag = tag.getSymbol(symbolName);
tagBuffer.writeUI16(characterTag.getCharacterID());
tagBuffer.writeString(symbolName);
}
// root class name
if (writeRootClass)
{
tagBuffer.writeUI16(0);
tagBuffer.writeString(swf.getTopLevelClass());
}
}
/**
* This is the entry-point for encoding a SWF tag. In order to calculate the
* size of a tag, each tag data is buffered on a OutputBitStream object.
* This method initialize the buffer, select encoding method according to
* the tag type, encode the tag body and then write the tag header and tag
* body onto the target output stream.
*
* @param tag tag to encode
*/
private void writeTag(ITag tag)
{
if (!writtenTags.contains(tag))
{
tagBuffer.reset();
writeTag(tag, tagBuffer, outputBuffer);
writtenTags.add(tag);
}
}
/**
* Encode {@code tag}'s body onto buffer {@code tagData}. Then compute the
* tag header and length. Finally, write the tag header and tag body to
* {@code out}.
* <p>
* This method assumes that {@code tagData} is a clean, initialized
* {@code IOutputBitStream} object.
*
* @param tag tag object
* @param tagData tag buffer
* @param out target output
*/
private void writeTag(ITag tag, IOutputBitStream tagData, IOutputBitStream out)
{
assert tag != null;
assert tagData != null;
assert out != null;
assert tagData != out;
if (tag == SWFReader.INVALID_TAG)
return;
// redirect tag buffer to "tagData"
IOutputBitStream swfTagBuffer = null;
if (tagData != this.tagBuffer)
{
swfTagBuffer = this.tagBuffer;
this.tagBuffer = tagData;
}
boolean skipRawTag = false;
Collection<ITag> extraTags = new LinkedList<ITag>();
if (tag instanceof RawTag)
{
skipRawTag = writeRawTag((RawTag)tag);
}
else
{
switch (tag.getTagType())
{
case DoABC:
writeDoABC((DoABCTag)tag);
break;
case FileAttributes:
writeFileAttributes((FileAttributesTag) tag);
break;
case SymbolClass:
writeSymbolClass((SymbolClassTag) tag);
break;
case ShowFrame:
writeShowFrame();
break;
case SetBackgroundColor:
writeSetBackgroundColor((SetBackgroundColorTag) tag);
break;
case EnableDebugger2:
writeEnableDebugger2((EnableDebugger2Tag) tag);
break;
case EnableTelemetry:
writeEnableTelemetry((EnableTelemetryTag) tag);
break;
case ScriptLimits:
writeScriptLimits((ScriptLimitsTag)tag);
break;
case ProductInfo:
writeProductInfo((ProductInfoTag)tag);
break;
case Metadata:
writeMetadata((MetadataTag)tag);
break;
case DefineBits:
writeDefineBits((DefineBitsTag)tag);
break;
case DefineBitsJPEG2:
writeDefineBitsJPEG2((DefineBitsJPEG2Tag)tag);
break;
case DefineBitsJPEG3:
writeDefineBitsJPEG3((DefineBitsJPEG3Tag)tag);
break;
case DefineBitsLossless:
case DefineBitsLossless2:
writeDefineBitsLossless((DefineBitsLosslessTag)tag);
break;
case DefineBinaryData:
writeDefineBinaryData((DefineBinaryDataTag)tag);
break;
case DefineShape:
writeDefineShape((DefineShapeTag)tag);
break;
case DefineShape2:
writeDefineShape2((DefineShape2Tag)tag);
break;
case DefineShape3:
writeDefineShape3((DefineShape3Tag)tag);
break;
case DefineShape4:
writeDefineShape4((DefineShape4Tag)tag);
break;
case DefineSprite:
writeDefineSprite((DefineSpriteTag)tag);
break;
case ExportAssets:
writeExportAssets((ExportAssetsTag)tag);
break;
case DefineScalingGrid:
writeDefineScalingGrid((DefineScalingGridTag)tag);
break;
case DefineFont:
writeDefineFont((DefineFontTag)tag, extraTags);
break;
case DefineFont2:
writeDefineFont2((DefineFont2Tag)tag, extraTags);
break;
case DefineFont3:
writeDefineFont3((DefineFont3Tag)tag, extraTags);
break;
case DefineFont4:
writeDefineFont4((DefineFont4Tag)tag, extraTags);
break;
case DefineFontInfo:
writeDefineFontInfo((IFontInfo)tag);
break;
case DefineFontInfo2:
writeDefineFontInfo2((DefineFontInfo2Tag)tag);
break;
case DefineFontAlignZones:
writeDefineFontAlignZones((DefineFontAlignZonesTag)tag);
break;
case DefineFontName:
writeDefineFontName((DefineFontNameTag)tag);
break;
case DefineText:
writeDefineText((DefineTextTag)tag, extraTags);
break;
case DefineText2:
writeDefineText2((DefineText2Tag)tag, extraTags);
break;
case DefineEditText:
writeDefineEditText((DefineEditTextTag)tag, extraTags);
break;
case DefineSound:
writeDefineSound((DefineSoundTag)tag);
break;
case DefineVideoStream:
writeDefineVideoStream((DefineVideoStreamTag)tag);
break;
case VideoFrame:
writeVideoFrame((VideoFrameTag)tag);
break;
case StartSound:
writeStartSound((StartSoundTag)tag);
break;
case StartSound2:
writeStartSound2((StartSound2Tag)tag);
break;
case SoundStreamHead:
writeSoundStreamHead((SoundStreamHeadTag)tag);
break;
case SoundStreamHead2:
writeSoundStreamHead((SoundStreamHead2Tag)tag);
break;
case SoundStreamBlock:
writeSoundStreamBlock((SoundStreamBlockTag)tag);
break;
case DefineButton:
writeDefineButton((DefineButtonTag)tag);
break;
case DefineButton2:
writeDefineButton2((DefineButton2Tag)tag);
break;
case DefineButtonSound:
writeDefineButtonSound((DefineButtonSoundTag)tag);
break;
case CSMTextSettings:
writeCSMTextSettings((CSMTextSettingsTag)tag);
break;
case End:
writeEnd();
break;
case JPEGTables:
writeJPEGTables(((JPEGTablesTag)tag));
break;
case DefineMorphShape:
writeDefineMorphShape((DefineMorphShapeTag)tag);
break;
case DefineMorphShape2:
writeDefineMorphShape2((DefineMorphShape2Tag)tag);
break;
case PlaceObject:
writePlaceObject((PlaceObjectTag)tag);
break;
case PlaceObject2:
writePlaceObject2((PlaceObject2Tag)tag);
break;
case PlaceObject3:
writePlaceObject3((PlaceObject3Tag)tag);
break;
case RemoveObject:
writeRemoveObject((RemoveObjectTag)tag);
break;
case RemoveObject2:
writeRemoveObject2((RemoveObject2Tag)tag);
break;
case SetTabIndex:
writeSetTabIndex((SetTabIndexTag)tag);
break;
case FrameLabel:
writeFrameLabel((FrameLabelTag)tag);
break;
default:
throw new RuntimeException("Tag not supported: " + tag);
}
}
// reset tag buffer
if (swfTagBuffer != null)
{
this.tagBuffer = swfTagBuffer;
}
if (!skipRawTag)
finishTag(tag, tagData, out);
for (ITag extraTag : extraTags)
writeTag(extraTag);
}
@SuppressWarnings("incomplete-switch")
private boolean writeRawTag(RawTag tag)
{
boolean skipTag = false;
// if writing out an AS3 swf, there are a number of
// tags which need to be ignored as they're not valid
// in AS3. These can get in when embedding tags from an
// old SWF into a AS3 type SWF.
if (swf.getUseAS3())
{
switch (tag.getTagType())
{
case DoAction:
case DoInitAction:
skipTag = true;
break;
}
}
if (!skipTag)
{
tagBuffer.write(tag.getTagBody());
}
return skipTag;
}
private void writeSetTabIndex(SetTabIndexTag tag)
{
tagBuffer.writeUI16(tag.getDepth());
tagBuffer.writeUI16(tag.getTabIndex());
}
private void writeRemoveObject2(RemoveObject2Tag tag)
{
tagBuffer.writeUI16(tag.getDepth());
}
private void writeRemoveObject(RemoveObjectTag tag)
{
tagBuffer.writeUI16(tag.getCharacter().getCharacterID());
tagBuffer.writeUI16(tag.getDepth());
}
private void writePlaceObject(PlaceObjectTag tag)
{
tagBuffer.writeUI16(tag.getCharacter().getCharacterID());
tagBuffer.writeUI16(tag.getDepth());
writeMatrix(tag.getMatrix());
final CXForm colorTransform = tag.getColorTransform();
if (colorTransform != null)
writeColorTransform(colorTransform);
}
private void writeColorTransform(CXForm cx)
{
final int nbits = requireSBCount(
cx.getRedMultTerm(),
cx.getGreenMultTerm(),
cx.getBlueMultTerm(),
cx.getRedAddTerm(),
cx.getGreenAddTerm(),
cx.getBlueAddTerm());
tagBuffer.writeBit(cx.hasAdd());
tagBuffer.writeBit(cx.hasMult());
tagBuffer.writeUB(nbits, 4);
if (cx.hasMult())
{
tagBuffer.writeSB(cx.getRedMultTerm(), nbits);
tagBuffer.writeSB(cx.getGreenMultTerm(), nbits);
tagBuffer.writeSB(cx.getBlueMultTerm(), nbits);
}
if (cx.hasAdd())
{
tagBuffer.writeSB(cx.getRedAddTerm(), nbits);
tagBuffer.writeSB(cx.getGreenAddTerm(), nbits);
tagBuffer.writeSB(cx.getBlueAddTerm(), nbits);
}
tagBuffer.byteAlign();
}
private void writePlaceObject2(PlaceObject2Tag tag)
{
tagBuffer.writeBit(tag.isHasClipActions());
tagBuffer.writeBit(tag.isHasClipDepth());
tagBuffer.writeBit(tag.isHasName());
tagBuffer.writeBit(tag.isHasRatio());
tagBuffer.writeBit(tag.isHasColorTransform());
tagBuffer.writeBit(tag.isHasMatrix());
tagBuffer.writeBit(tag.isHasCharacter());
tagBuffer.writeBit(tag.isMove());
tagBuffer.writeUI16(tag.getDepth());
if (tag.isHasCharacter())
tagBuffer.writeUI16(tag.getCharacter().getCharacterID());
if (tag.isHasMatrix())
writeMatrix(tag.getMatrix());
if (tag.isHasColorTransform())
writeColorTransformWithAlpha(tag.getColorTransform());
if (tag.isHasRatio())
tagBuffer.writeUI16(tag.getRatio());
if (tag.isHasName())
tagBuffer.writeString(tag.getName());
if (tag.isHasClipDepth())
tagBuffer.writeUI16(tag.getClipDepth());
if (tag.isHasClipActions())
tagBuffer.write(tag.getClipActions().data);
}
private void writePlaceObject3(PlaceObject3Tag tag)
{
tagBuffer.writeBit(tag.isHasClipActions());
tagBuffer.writeBit(tag.isHasClipDepth());
tagBuffer.writeBit(tag.isHasName());
tagBuffer.writeBit(tag.isHasRatio());
tagBuffer.writeBit(tag.isHasColorTransform());
tagBuffer.writeBit(tag.isHasMatrix());
tagBuffer.writeBit(tag.isHasCharacter());
tagBuffer.writeBit(tag.isMove());
tagBuffer.writeUB(0, 3); // reserved
tagBuffer.writeBit(tag.isHasImage());
tagBuffer.writeBit(tag.isHasClassName());
tagBuffer.writeBit(tag.isHasCacheAsBitmap());
tagBuffer.writeBit(tag.isHasBlendMode());
tagBuffer.writeBit(tag.isHasFilterList());
tagBuffer.writeUI16(tag.getDepth());
if (tag.isHasClassName())
tagBuffer.writeString(tag.getClassName());
if (tag.isHasCharacter())
tagBuffer.writeUI16(tag.getCharacter().getCharacterID());
if (tag.isHasMatrix())
writeMatrix(tag.getMatrix());
if (tag.isHasColorTransform())
writeColorTransformWithAlpha(tag.getColorTransform());
if (tag.isHasRatio())
tagBuffer.writeUI16(tag.getRatio());
if (tag.isHasName())
tagBuffer.writeString(tag.getName());
if (tag.isHasClipDepth())
tagBuffer.writeUI16(tag.getClipDepth());
if (tag.isHasFilterList())
{
tagBuffer.writeUI8(tag.getSurfaceFilterList().length);
for (final Filter filter : tag.getSurfaceFilterList())
writeFilter(filter);
}
if (tag.isHasBlendMode())
tagBuffer.writeUI8(tag.getBlendMode());
if (tag.isHasCacheAsBitmap())
tagBuffer.writeUI8(tag.getBitmapCache());
if (tag.isHasClipActions())
tagBuffer.write(tag.getClipActions().data);
}
private void writeVideoFrame(VideoFrameTag tag)
{
tagBuffer.writeUI16(tag.getStreamTag().getCharacterID());
tagBuffer.writeUI16(tag.getFrameNum());
tagBuffer.write(tag.getVideoData());
}
private void writeDefineVideoStream(DefineVideoStreamTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUI16(tag.getNumFrames());
tagBuffer.writeUI16(tag.getWidth());
tagBuffer.writeUI16(tag.getHeight());
tagBuffer.writeUB(0, 4); // reserved
tagBuffer.writeUB(tag.getDeblocking(), 3);
tagBuffer.writeBit(tag.isSmoothing());
tagBuffer.writeUI8(tag.getCodecID());
}
private void writeDefineButtonSound(DefineButtonSoundTag tag)
{
tagBuffer.writeUI16(tag.getButtonTag().getCharacterID());
for (int i = 0; i < DefineButtonSoundTag.TOTAL_SOUND_STYLE; i++)
{
if (tag.getSoundChar()[i] == null)
{
// write out a zero sound id if there is no sound info.
tagBuffer.writeUI16(0);
continue;
}
assert tag.getSoundChar()[i].getTagType() == TagType.DefineSound;
tagBuffer.writeUI16(tag.getSoundChar()[i].getCharacterID());
writeSoundInfo(tag.getSoundInfo()[i]);
}
}
private void writeDefineButton2(DefineButton2Tag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUB(0, 7); // reserved
tagBuffer.writeBit(tag.isTrackAsMenu());
tagBuffer.writeUI16(tag.getActionOffset()); // TODO: need calculation
for (final ButtonRecord r : tag.getCharacters())
writeButtonRecord(r, tag.getTagType());
tagBuffer.writeUI8(0); // end of character tag
tagBuffer.write(tag.getActions());
}
private void writeDefineButton(DefineButtonTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
for (final ButtonRecord record : tag.getCharacters())
{
writeButtonRecord(record, tag.getTagType());
}
tagBuffer.writeUI8(0); // end of characters
tagBuffer.write(tag.getActions());
tagBuffer.writeUI8(0); // end of actions
}
private void writeButtonRecord(ButtonRecord record, TagType tagType)
{
tagBuffer.writeUB(0, 2); // reserved
tagBuffer.writeBit(record.isHasBlendMode());
tagBuffer.writeBit(record.isHasFilterList());
tagBuffer.writeBit(record.isStateHitTest());
tagBuffer.writeBit(record.isStateDown());
tagBuffer.writeBit(record.isStateOver());
tagBuffer.writeBit(record.isStateUp());
tagBuffer.writeUI16(record.getCharacterID());
tagBuffer.writeUI16(record.getPlaceDepth());
writeMatrix(record.getPlaceMatrix());
if (tagType == TagType.DefineButton2)
{
writeColorTransformWithAlpha(record.getColorTransform());
if (record.isHasFilterList())
{
tagBuffer.writeUI8(record.getFilterList().length);
for (final Filter filter : record.getFilterList())
writeFilter(filter);
}
if (record.isHasBlendMode())
tagBuffer.writeUI8(record.getBlendMode());
}
}
private void writeFilter(Filter filter)
{
tagBuffer.writeUI8(filter.getFilterID());
switch (filter.getFilterID())
{
case Filter.DROP_SHADOW:
writeDropShadowFilter(filter.getDropShadowFilter());
break;
case Filter.BLUR:
writeBlurFilter(filter.getBlurFilter());
break;
case Filter.GLOW:
writeGlowFilter(filter.getGlowFilter());
break;
case Filter.BEVEL:
writeBevelFilter(filter.getBevelFilter());
break;
case Filter.GRADIENT_GLOW:
writeGradientGlowFilter(filter.getGradientGlowFilter());
break;
case Filter.CONVOLUTION:
writeConvolutionFilter(filter.getConvolutionFilter());
break;
case Filter.COLOR_MATRIX:
writeColorMatrixFilter(filter.getColorMatrixFilter());
break;
case Filter.GRADIENT_BEVEL:
writeGradientBevelFilter(filter.getGradientBevelFilter());
break;
}
}
private void writeGradientBevelFilter(GradientBevelFilter filter)
{
assert filter.getNumColors() == filter.getGradientColors().length;
assert filter.getNumColors() == filter.getGradientRatio().length;
tagBuffer.writeUI8(filter.getNumColors());
for (RGBA color : filter.getGradientColors())
writeRGBA(color);
for (int ratio : filter.getGradientRatio())
tagBuffer.writeUI8(ratio);
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeFIXED(filter.getAngle());
tagBuffer.writeFIXED(filter.getDistance());
tagBuffer.writeFIXED8(filter.getStrength());
tagBuffer.writeBit(filter.isInnerShadow());
tagBuffer.writeBit(filter.isKnockout());
tagBuffer.writeBit(filter.isCompositeSource());
tagBuffer.writeBit(filter.isOnTop());
tagBuffer.writeUB(filter.getPasses(), 4);
}
private void writeGradientGlowFilter(GradientGlowFilter filter)
{
assert filter.getNumColors() == filter.getGradientColors().length;
assert filter.getNumColors() == filter.getGradientRatio().length;
tagBuffer.writeUI8(filter.getNumColors());
for (RGBA color : filter.getGradientColors())
writeRGBA(color);
for (int ratio : filter.getGradientRatio())
tagBuffer.writeUI8(ratio);
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeFIXED(filter.getAngle());
tagBuffer.writeFIXED(filter.getDistance());
tagBuffer.writeFIXED8(filter.getStrength());
tagBuffer.writeBit(filter.isInnerGlow());
tagBuffer.writeBit(filter.isKnockout());
tagBuffer.writeBit(filter.isCompositeSource());
tagBuffer.writeBit(filter.isOnTop());
tagBuffer.writeUB(filter.getPasses(), 4);
}
private void writeBevelFilter(BevelFilter filter)
{
//Note: The SWF File Format Specifications Version 10 switches these two colors (it writes ShadowColor before HighlightColor).
//A bug has been logged in JIRA against the specs for this issue
writeRGBA(filter.getHighlightColor());
writeRGBA(filter.getShadowColor());
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeFIXED(filter.getAngle());
tagBuffer.writeFIXED(filter.getDistance());
tagBuffer.writeFIXED8(filter.getStrength());
tagBuffer.writeBit(filter.isInnerShadow());
tagBuffer.writeBit(filter.isKnockout());
tagBuffer.writeBit(filter.isCompositeSource());
tagBuffer.writeBit(filter.isOnTop());
tagBuffer.writeUB(filter.getPasses(), 4);
}
private void writeGlowFilter(GlowFilter filter)
{
writeRGBA(filter.getGlowColor());
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeFIXED8(filter.getStrength());
tagBuffer.writeBit(filter.isInnerGlow());
tagBuffer.writeBit(filter.isKnockout());
tagBuffer.writeBit(filter.isCompositeSource());
tagBuffer.writeUB(filter.getPasses(), 5);
}
private void writeDropShadowFilter(DropShadowFilter filter)
{
writeRGBA(filter.getDropShadowColor());
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeFIXED(filter.getAngle());
tagBuffer.writeFIXED(filter.getDistance());
tagBuffer.writeFIXED8(filter.getStrength());
tagBuffer.writeBit(filter.isInnerShadow());
tagBuffer.writeBit(filter.isKnockout());
tagBuffer.writeBit(filter.isCompositeSource());
tagBuffer.writeUB(filter.getPasses(), 5);
}
private void writeBlurFilter(BlurFilter filter)
{
tagBuffer.writeFIXED(filter.getBlurX());
tagBuffer.writeFIXED(filter.getBlurY());
tagBuffer.writeUB(filter.getPasses(), 5);
tagBuffer.writeUB(0, 3); // reserved
}
private void writeConvolutionFilter(ConvolutionFilter filter)
{
assert filter.getMatrixX() * filter.getMatrixY() == filter.getMatrix().length;
tagBuffer.writeUI8(filter.getMatrixX());
tagBuffer.writeUI8(filter.getMatrixY());
tagBuffer.writeFLOAT(filter.getDivisor());
tagBuffer.writeFLOAT(filter.getBias());
for (final float f : filter.getMatrix())
tagBuffer.writeFLOAT(f);
writeRGBA(filter.getDefaultColor());
tagBuffer.writeUB(0, 6); // reserved
tagBuffer.writeBit(filter.isClamp());
tagBuffer.writeBit(filter.isPreserveAlpha());
tagBuffer.byteAlign();
}
private void writeColorMatrixFilter(float[] filter)
{
assert filter.length == 20;
for (float f : filter)
tagBuffer.writeFLOAT(f);
}
private void writeColorTransformWithAlpha(CXFormWithAlpha cx)
{
final int nbits = requireSBCount(
cx.getRedMultTerm(),
cx.getGreenMultTerm(),
cx.getBlueMultTerm(),
cx.getAlphaMultTerm(),
cx.getRedAddTerm(),
cx.getGreenAddTerm(),
cx.getBlueAddTerm(),
cx.getAlphaAddTerm());
tagBuffer.writeBit(cx.hasAdd());
tagBuffer.writeBit(cx.hasMult());
tagBuffer.writeUB(nbits, 4);
if (cx.hasMult())
{
tagBuffer.writeSB(cx.getRedMultTerm(), nbits);
tagBuffer.writeSB(cx.getGreenMultTerm(), nbits);
tagBuffer.writeSB(cx.getBlueMultTerm(), nbits);
tagBuffer.writeSB(cx.getAlphaMultTerm(), nbits);
}
if (cx.hasAdd())
{
tagBuffer.writeSB(cx.getRedAddTerm(), nbits);
tagBuffer.writeSB(cx.getGreenAddTerm(), nbits);
tagBuffer.writeSB(cx.getBlueAddTerm(), nbits);
tagBuffer.writeSB(cx.getAlphaAddTerm(), nbits);
}
tagBuffer.byteAlign();
}
private void writeSoundStreamBlock(SoundStreamBlockTag tag)
{
tagBuffer.write(tag.getStreamSoundData());
}
private void writeSoundStreamHead(SoundStreamHeadTag tag)
{
tagBuffer.byteAlign();
tagBuffer.writeUB(0, 4); // reserved
tagBuffer.writeUB(tag.getPlaybackSoundRate(), 2);
tagBuffer.writeUB(tag.getPlaybackSoundSize(), 1);
tagBuffer.writeUB(tag.getPlaybackSoundType(), 1);
tagBuffer.writeUB(tag.getStreamSoundCompression(), 4);
tagBuffer.writeUB(tag.getStreamSoundRate(), 2);
tagBuffer.writeUB(tag.getStreamSoundSize(), 1);
tagBuffer.writeUB(tag.getStreamSoundType(), 1);
tagBuffer.writeUI16(tag.getStreamSoundSampleCount());
if (tag.getStreamSoundCompression() == SoundStreamHeadTag.SSC_MP3)
tagBuffer.writeSI16(tag.getLatencySeek());
}
private void writeStartSound2(StartSound2Tag tag)
{
tagBuffer.writeString(tag.getSoundClassName());
writeSoundInfo(tag.getSoundInfo());
}
private void writeStartSound(StartSoundTag tag)
{
tagBuffer.writeUI16(tag.getSoundTag().getCharacterID());
writeSoundInfo(tag.getSoundInfo());
}
private void writeSoundInfo(SoundInfo soundInfo)
{
tagBuffer.byteAlign();
tagBuffer.writeUB(0, 2); // reserved
tagBuffer.writeBit(soundInfo.isSyncStop());
tagBuffer.writeBit(soundInfo.isSyncNoMultiple());
tagBuffer.writeBit(soundInfo.isHasEnvelope());
tagBuffer.writeBit(soundInfo.isHasLoops());
tagBuffer.writeBit(soundInfo.isHasOutPoint());
tagBuffer.writeBit(soundInfo.isHasInPoint());
if (soundInfo.isHasInPoint())
tagBuffer.writeUI32(soundInfo.getInPoint());
if (soundInfo.isHasOutPoint())
tagBuffer.writeUI32(soundInfo.getOutPoint());
if (soundInfo.isHasLoops())
tagBuffer.writeUI16(soundInfo.getLoopCount());
if (soundInfo.isHasEnvelope())
{
tagBuffer.writeUI8(soundInfo.getEnvPoints());
for (final SoundEnvelope env : soundInfo.getEnvelopeRecords())
{
tagBuffer.writeUI32(env.getPos44());
tagBuffer.writeUI16(env.getLeftLevel());
tagBuffer.writeUI16(env.getRightLevel());
}
}
}
private void writeDefineSound(DefineSoundTag tag)
{
tagBuffer.byteAlign();
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUB(tag.getSoundFormat(), 4);
tagBuffer.writeUB(tag.getSoundRate(), 2);
tagBuffer.writeUB(tag.getSoundSize(), 1);
tagBuffer.writeUB(tag.getSoundType(), 1);
tagBuffer.writeUI32(tag.getSoundSampleCount());
tagBuffer.write(tag.getSoundData());
}
private void writeDefineFont4(DefineFont4Tag tag, Collection<ITag> extraTags)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUB(0, 5); // reserved
tagBuffer.writeBit(tag.isFontFlagsHasFontData());
tagBuffer.writeBit(tag.isFontFlagsItalic());
tagBuffer.writeBit(tag.isFontFlagsBold());
// 8 bits - no need to align
tagBuffer.writeString(tag.getFontName());
tagBuffer.write(tag.getFontData());
DefineFontNameTag license = tag.getLicense();
if (license != null)
extraTags.add(license);
}
private void writeCSMTextSettings(CSMTextSettingsTag tag)
{
tagBuffer.writeUI16(tag.getTextTag().getCharacterID());
tagBuffer.writeUB(tag.getUseFlashType(), 2);
tagBuffer.writeUB(tag.getGridFit(), 3);
tagBuffer.writeUB(0, 3);
// 8 bits - no need to align
tagBuffer.writeFLOAT(tag.getThickness());
tagBuffer.writeFLOAT(tag.getSharpness());
tagBuffer.writeUI8(0); // reserved
}
private void writeDefineEditText(DefineEditTextTag tag, Collection<ITag> extraTags)
{
tagBuffer.writeUI16(tag.getCharacterID());
writeRect(tag.getBounds());
tagBuffer.writeBit(tag.isHasText());
tagBuffer.writeBit(tag.isWordWrap());
tagBuffer.writeBit(tag.isMultiline());
tagBuffer.writeBit(tag.isPassword());
tagBuffer.writeBit(tag.isReadOnly());
tagBuffer.writeBit(tag.isHasTextColor());
tagBuffer.writeBit(tag.isHasMaxLength());
tagBuffer.writeBit(tag.isHasFont());
tagBuffer.writeBit(tag.isHasFontClass());
tagBuffer.writeBit(tag.isAutoSize());
tagBuffer.writeBit(tag.isHasLayout());
tagBuffer.writeBit(tag.isNoSelect());
tagBuffer.writeBit(tag.isBorder());
tagBuffer.writeBit(tag.isWasStatic());
tagBuffer.writeBit(tag.isHtml());
tagBuffer.writeBit(tag.isUseOutlines());
// Both HasFont and HasFontClass requires a Height field.
if (tag.isHasFont())
{
tagBuffer.writeUI16(tag.getFontTag().getCharacterID());
tagBuffer.writeUI16(tag.getFontHeight());
}
else if (tag.isHasFontClass())
{
tagBuffer.writeString(tag.getFontClass());
tagBuffer.writeUI16(tag.getFontHeight());
}
if (tag.isHasTextColor())
writeRGBA(tag.getTextColor());
if (tag.isHasMaxLength())
tagBuffer.writeUI16(tag.getMaxLength());
if (tag.isHasLayout())
{
tagBuffer.writeUI8(tag.getAlign());
tagBuffer.writeUI16(tag.getLeftMargin());
tagBuffer.writeUI16(tag.getRightMargin());
tagBuffer.writeUI16(tag.getIndent());
tagBuffer.writeSI16(tag.getLeading());
}
tagBuffer.writeString(tag.getVariableName());
if (tag.isHasText())
tagBuffer.writeString(tag.getInitialText());
CSMTextSettingsTag textSettings = tag.getCSMTextSettings();
if (textSettings != null)
extraTags.add(textSettings);
}
private void writeDefineText2(DefineText2Tag tag, Collection<ITag> extraTags)
{
writeDefineText(tag, extraTags);
}
private void writeDefineText(DefineTextTag tag, Collection<ITag> extraTags)
{
tagBuffer.writeUI16(tag.getCharacterID());
writeRect(tag.getTextBounds());
writeMatrix(tag.getTextMatrix());
tagBuffer.writeUI8(tag.getGlyphBits());
tagBuffer.writeUI8(tag.getAdvanceBits());
for (TextRecord textRecord : tag.getTextRecords())
{
writeTextRecord(textRecord, tag);
}
tagBuffer.byteAlign();
tagBuffer.writeUI8(0); // end of records
CSMTextSettingsTag textSettings = tag.getCSMTextSettings();
if (textSettings != null)
extraTags.add(textSettings);
}
private void writeTextRecord(TextRecord textRecord, DefineTextTag tag)
{
tagBuffer.byteAlign();
tagBuffer.writeBit(true); // TextRecordType always 1.
tagBuffer.writeUB(0, 3); // reserved
tagBuffer.writeBit(textRecord.isStyleFlagsHasFont());
tagBuffer.writeBit(textRecord.isStyleFlagsHasColor());
tagBuffer.writeBit(textRecord.isStyleFlagsHasYOffset());
tagBuffer.writeBit(textRecord.isStyleFlagsHasXOffset());
// 8 bits - no need to align
if (textRecord.isStyleFlagsHasFont())
{
tagBuffer.writeUI16(textRecord.getFontTag().getCharacterID());
}
if (textRecord.isStyleFlagsHasColor())
{
if (tag.getTagType() == TagType.DefineText2)
{
assert textRecord.getTextColor() instanceof RGBA;
writeRGBA((RGBA)textRecord.getTextColor());
}
else
{
writeRGB(textRecord.getTextColor());
}
}
if (textRecord.isStyleFlagsHasXOffset())
{
tagBuffer.writeSI16(textRecord.getxOffset());
}
if (textRecord.isStyleFlagsHasYOffset())
{
tagBuffer.writeSI16(textRecord.getyOffset());
}
if (textRecord.isStyleFlagsHasFont())
{
tagBuffer.writeUI16(textRecord.getTextHeight());
}
tagBuffer.writeUI8(textRecord.getGlyphCount());
assert textRecord.getGlyphCount() == textRecord.getGlyphEntries().length;
for (final GlyphEntry entry : textRecord.getGlyphEntries())
{
writeGlyphEntry(entry, tag);
}
}
/**
* @param entry
* @param tag
*/
private void writeGlyphEntry(GlyphEntry entry, DefineTextTag tag)
{
tagBuffer.writeUB(entry.getGlyphIndex(), tag.getGlyphBits());
tagBuffer.writeSB(entry.getGlyphAdvance(), tag.getAdvanceBits());
}
private void writeDefineFontName(DefineFontNameTag tag)
{
tagBuffer.writeUI16(tag.getFontTag().getCharacterID());
tagBuffer.writeString(tag.getFontName());
tagBuffer.writeString(tag.getFontCopyright());
}
private void writeDefineFontAlignZones(DefineFontAlignZonesTag tag)
{
tagBuffer.writeUI16(tag.getFontTag().getCharacterID());
tagBuffer.writeUB(tag.getCsmTableHint(), 2);
tagBuffer.writeUB(0, 6); // reserved
tagBuffer.byteAlign();
for (final ZoneRecord zoneRecord : tag.getZoneTable())
{
writeZoneRecord(zoneRecord);
}
}
/**
* @param zoneRecord
*/
private void writeZoneRecord(ZoneRecord zoneRecord)
{
assert zoneRecord.getNumZoneData() == 2;
tagBuffer.writeUI8(2); // always 2
tagBuffer.writeUI32(zoneRecord.getZoneData0().getData());
tagBuffer.writeUI32(zoneRecord.getZoneData1().getData());
tagBuffer.writeUB(0, 6); // reserved
tagBuffer.writeBit(zoneRecord.isZoneMaskY());
tagBuffer.writeBit(zoneRecord.isZoneMaskX());
}
private void writeDefineFont3(DefineFont3Tag tag, Collection<ITag> extraTags)
{
DefineFontAlignZonesTag zones = tag.getZones();
if (zones != null)
extraTags.add(zones);
writeDefineFont2(tag, extraTags);
}
/**
* @see SWFReader#readDefineFont2
*/
private void writeDefineFont2(DefineFont2Tag tag, Collection<ITag> extraTags)
{
// need to write the glyphTable to a buffer first, so as to work out
// size size of the table, so we know whether wide offsets are needed
final int numGlyphs = tag.getNumGlyphs();
int[] shapeSizes = new int[numGlyphs];
IOutputBitStream shapeBuffer = writeGlyphTableToBuffer(numGlyphs, tag, shapeSizes);
// if the shape table is bigger that 65535 bytes, we need to use
// wide offsets if we're not already
if (!tag.isFontFlagsWideOffsets() && shapeBuffer.size() > 65535)
{
tag.setFontFlagsWideOffsets(true);
}
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeBit(tag.isFontFlagsHasLayout());
tagBuffer.writeBit(tag.isFontFlagsShiftJIS());
tagBuffer.writeBit(tag.isFontFlagsSmallText());
tagBuffer.writeBit(tag.isFontFlagsANSI());
tagBuffer.writeBit(tag.isFontFlagsWideOffsets());
tagBuffer.writeBit(tag.isFontFlagsWideCodes());
tagBuffer.writeBit(tag.isFontFlagsItalic());
tagBuffer.writeBit(tag.isFontFlagsBold());
// 8bits - no need to align
tagBuffer.writeUI8(tag.getLanguageCode());
writeLengthString(tag.getFontName());
tagBuffer.writeUI16(numGlyphs);
writeFontOffsetAndGlyphTable(shapeBuffer, shapeSizes, numGlyphs, tag.getTagType(), tag.isFontFlagsWideOffsets());
assert tag.getCodeTable().length == tag.getNumGlyphs();
for (int code : tag.getCodeTable())
{
if (tag.isFontFlagsWideCodes())
{
tagBuffer.writeUI16(code);
}
else
{
tagBuffer.writeUI8(code);
}
}
if (tag.isFontFlagsHasLayout())
{
assert tag.getFontAdvanceTable().length == tag.getNumGlyphs();
tagBuffer.writeSI16(tag.getFontAscent());
tagBuffer.writeSI16(tag.getFontDescent());
tagBuffer.writeSI16(tag.getFontLeading());
for (int fontAdvance : tag.getFontAdvanceTable())
{
tagBuffer.writeSI16(fontAdvance);
}
assert tag.getFontBoundsTable().length == tag.getNumGlyphs();
for (Rect bound : tag.getFontBoundsTable())
{
writeRect(bound);
}
tagBuffer.writeUI16(tag.getKerningCount());
assert tag.getKerningCount() == tag.getFontKerningTable().length;
for (KerningRecord kerning : tag.getFontKerningTable())
{
writeKerningRecord(kerning, tag.isFontFlagsWideCodes());
}
}
DefineFontNameTag license = tag.getLicense();
if (license != null)
extraTags.add(license);
}
/**
* @param kerning
* @param fontFlagsWideCodes
*/
private void writeKerningRecord(KerningRecord kerning, boolean fontFlagsWideCodes)
{
if (fontFlagsWideCodes)
{
tagBuffer.writeUI16(kerning.getCode1());
tagBuffer.writeUI16(kerning.getCode2());
}
else
{
tagBuffer.writeUI32(kerning.getCode1());
tagBuffer.writeUI32(kerning.getCode2());
}
tagBuffer.writeSI16(kerning.getAdjustment());
}
private void writeDefineFontInfo2(DefineFontInfo2Tag tag)
{
tagBuffer.writeUI16(tag.getFontTag().getCharacterID());
writeLengthString(tag.getFontName());
tagBuffer.writeUB(tag.getFontFlagsReserved(), 2);
tagBuffer.writeBit(tag.isFontFlagsSmallText());
tagBuffer.writeBit(tag.isFontFlagsShiftJIS());
tagBuffer.writeBit(tag.isFontFlagsANSI());
tagBuffer.writeBit(tag.isFontFlagsItalic());
tagBuffer.writeBit(tag.isFontFlagsBold());
tagBuffer.writeBit(tag.isFontFlagsWideCodes());
// 8 bits - no need to align
tagBuffer.writeUI8(tag.getLanguageCode());
for (final int code : tag.getCodeTable())
{
if (tag.isFontFlagsWideCodes())
{
tagBuffer.writeUI16(code);
}
else
{
tagBuffer.writeUI8(code);
}
}
}
/**
* @see SWFReader#readDefineFontInfo
*/
private void writeDefineFontInfo(IFontInfo tag)
{
tagBuffer.writeUI16(tag.getFontTag().getCharacterID());
writeLengthString(tag.getFontName());
tagBuffer.writeUB(tag.getFontFlagsReserved(), 2);
tagBuffer.writeBit(tag.isFontFlagsSmallText());
tagBuffer.writeBit(tag.isFontFlagsShiftJIS());
tagBuffer.writeBit(tag.isFontFlagsANSI());
tagBuffer.writeBit(tag.isFontFlagsItalic());
tagBuffer.writeBit(tag.isFontFlagsBold());
tagBuffer.writeBit(tag.isFontFlagsWideCodes());
// 8 bits - no need to align
for (final int code : tag.getCodeTable())
{
if (tag.isFontFlagsWideCodes())
{
tagBuffer.writeUI16(code);
}
else
{
tagBuffer.writeUI8(code);
}
}
}
private IOutputBitStream writeGlyphTableToBuffer(int numGlyphs, DefineFontTag tag, int[] shapeSizes)
{
// create a separate buffer for the glyph table to calculate offsets
// and then write it out at the end
final IOutputBitStream currentTagBuffer = tagBuffer;
final IOutputBitStream shapeBuffer = new OutputBitStream();
tagBuffer = shapeBuffer;
int currentOffset = 0;
int previousOffset = 0;
Shape[] shapes = tag.getGlyphShapeTable();
for (int i = 0; i < numGlyphs; i++)
{
/**
* 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.
*/
writeShape(shapes[i], tag.getTagType(), 1, 0);
currentOffset = shapeBuffer.size();
shapeSizes[i] = currentOffset - previousOffset;
previousOffset = currentOffset;
}
// restore the original tag buffer;
tagBuffer = currentTagBuffer;
return shapeBuffer;
}
private void writeFontOffsetAndGlyphTable(IOutputBitStream shapeBuffer, int[] shapeSizes, int numGlyphs, TagType tagType, boolean wideOffsets)
{
int offsetTableElementSize = wideOffsets ? 4 : 2;
int baseOffset = numGlyphs * offsetTableElementSize;
if (tagType != TagType.DefineFont)
{
// baseOffset is now at the end of the GlyphShapeTable,
// so add space for the CodeTableOffset value (2 or 4 bytes)
// and that gets us to the start of the CodeTable
if (wideOffsets)
baseOffset += 4;
else
baseOffset += 2;
}
// Write offset table
int currentOffset = baseOffset;
for (int i = 0; i < numGlyphs; i++)
{
if (wideOffsets)
tagBuffer.writeUI32(currentOffset);
else
tagBuffer.writeUI16(currentOffset);
currentOffset += shapeSizes[i];
}
// Only write the CodeTableOffset if numGlyphs is > 0
if (tagType != TagType.DefineFont && numGlyphs > 0)
{
assert (currentOffset == (baseOffset + shapeBuffer.size())) : "offset mismatch writing font glyph table";
if (wideOffsets)
tagBuffer.writeUI32(currentOffset);
else
tagBuffer.writeUI16(currentOffset);
}
// Write GlyphShapeTable from the already created buffer
tagBuffer.write(shapeBuffer.getBytes(), 0, shapeBuffer.size());
try
{
shapeBuffer.close();
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
/**
* @see SWFReader#readDefineFont
*/
private void writeDefineFont(DefineFontTag tag, Collection<ITag> extraTags)
{
tagBuffer.writeUI16(tag.getCharacterID());
final int numGlyphs = tag.getGlyphShapeTable().length;
int[] shapeSizes = new int[numGlyphs];
IOutputBitStream shapeBuffer = writeGlyphTableToBuffer(numGlyphs, tag, shapeSizes);
writeFontOffsetAndGlyphTable(shapeBuffer, shapeSizes, numGlyphs, tag.getTagType(), false);
DefineFontNameTag license = tag.getLicense();
if (license != null)
extraTags.add(license);
}
/**
* @see SWFReader#readDefineBitsJPEG3
*/
private void writeDefineBitsJPEG3(DefineBitsJPEG3Tag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUI32(tag.getAlphaDataOffset());
tagBuffer.write(tag.getImageData());
tagBuffer.write(tag.getBitmapAlphaData());
}
/**
* @see SWFReader#readDefineBitsJPEG2
*/
private void writeDefineBitsJPEG2(DefineBitsJPEG2Tag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.write(tag.getImageData());
}
/**
* @see SWFReader#readJPEGTables
*/
private void writeJPEGTables(JPEGTablesTag tag)
{
tagBuffer.write(tag.getJpegData());
}
/**
* @see SWFReader#readDefineBits
*/
private void writeDefineBits(DefineBitsTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.write(tag.getImageData());
}
/**
* @see SWFReader#readDefineScalingGrid
*/
private void writeDefineScalingGrid(DefineScalingGridTag tag)
{
tagBuffer.writeUI16(tag.getCharacter().getCharacterID());
writeRect(tag.getSplitter());
}
/**
* @see SWFReader#readExportAssets
*/
private void writeExportAssets(ExportAssetsTag tag)
{
tagBuffer.writeUI16(tag.size());
for (final String name : tag.getCharacterNames())
{
final ICharacterTag characterTag = tag.getCharacterTagByName(name);
tagBuffer.writeUI16(characterTag.getCharacterID());
tagBuffer.writeString(name);
}
}
/**
* @see SWFReader#readDefineSprite
*/
protected void writeDefineSprite(DefineSpriteTag tag)
{
tagBuffer.writeUI16(tag.getCharacterID());
tagBuffer.writeUI16(tag.getFrameCount());
// Tag buffer for embedded control tags.
final IOutputBitStream controlTagBuffer = new OutputBitStream();
for (final ITag controlTag : tag.getControlTags())
{
controlTagBuffer.reset();
// DefineSprite's tagBuffer is the target output for the embedded
// tags.
writeTag(controlTag, controlTagBuffer, tagBuffer);
}
// write end marker
tagBuffer.writeUI16(0);
}
/**
* This method does not close the {@code output} stream.
*/
@Override
public void writeTo(OutputStream output)
{
assert output != null;
writtenTags = new HashSet<ITag>();
// The SWF data after the first 8 bytes can be compressed. At this
// moment, we only encode the "compressible" part.
writeCompressibleHeader();
// FileAttributes must be the first tag.
writeTag(SWF.getFileAttributes(swf));
// Raw Metadata
String metadata = swf.getMetadata();
if (metadata != null) {
writeTag(new MetadataTag(metadata));
}
// SetBackgroundColor tag
final RGB backgroundColor = swf.getBackgroundColor();
if (backgroundColor != null) {
writeTag(new SetBackgroundColorTag(backgroundColor));
}
// EnableDebugger2 tag
if (enableDebug) {
writeTag(new EnableDebugger2Tag("NO-PASSWORD"));
}
// EnableTelemetry tag
if (enableTelemetry) {
writeTag(new EnableTelemetryTag());
}
// ProductInfo tag for Flex compatibility
ProductInfoTag productInfo = swf.getProductInfo();
if (productInfo != null) {
writeTag(productInfo);
}
// ScriptLimits tag
final ScriptLimitsTag scriptLimitsTag = swf.getScriptLimits();
if (scriptLimitsTag != null) {
writeTag(scriptLimitsTag);
}
// Frames and enclosed tags.
writeFrames();
// End of SWF
writeTag(new EndTag());
writtenTags = null;
// Compute the size of the SWF file.
long length = outputBuffer.size() + 8;
try
{
// write the first 8 bytes
switch (useCompression)
{
case LZMA:
output.write('Z');
break;
case ZLIB:
output.write('C');
break;
case NONE:
output.write('F');
break;
default:
assert false;
}
output.write('W');
output.write('S');
output.write(swf.getVersion());
writeInt(output, (int)length);
// write the "compressible" part
switch (useCompression)
{
case LZMA:
{
LZMACompressor compressor = new LZMACompressor();
compressor.compress(outputBuffer);
// now write the compressed length
final long compressedLength = compressor.getLengthOfCompressedPayload();
assert compressedLength <= 0xffffffffl;
writeInt(output, (int)compressedLength);
// now write the LZMA props
compressor.writeLZMAProperties(output);
// Normally LZMA (7zip) would write an 8 byte length here, but we don't, because the
// SWF header already has this info
// now write the n bytes of LZMA data, followed by the 6 byte EOF
compressor.writeDataAndEnd(output);
output.flush();
}
break;
case ZLIB:
{
int compressionLevel = enableDebug ? Deflater.BEST_SPEED : Deflater.BEST_COMPRESSION;
Deflater deflater = new Deflater(compressionLevel);
DeflaterOutputStream deflaterStream = new DeflaterOutputStream(output, deflater);
deflaterStream.write(outputBuffer.getBytes(), 0, outputBuffer.size());
deflaterStream.finish();
deflater.end();
deflaterStream.flush();
break;
}
case NONE:
{
output.write(outputBuffer.getBytes(), 0, outputBuffer.size());
output.flush();
break;
}
default:
assert false;
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
/**
* write a 32 bit integer into an output stream, in SWF byte ordering, which
* is little-endian.
*/
private void writeInt(OutputStream output, int theInt) throws IOException
{
output.write(theInt);
output.write((theInt >> 8));
output.write((theInt >> 16));
output.write((theInt >> 24));
}
@Override
public int writeTo(File outputFile) throws FileNotFoundException, IOException
{
// Ensure that the directory for the SWF exists.
final File outputDirectory = new File(outputFile.getAbsoluteFile().getParent());
outputDirectory.mkdirs();
// Write out the SWF, counting how many bytes were written.
final CountingOutputStream output =
new CountingOutputStream(new BufferedOutputStream(new FileOutputStream(outputFile)));
writeTo(output);
output.flush();
output.close();
close();
final int swfSize = output.getCount();
return swfSize;
}
private void writeFrameLabel(FrameLabelTag tag)
{
tagBuffer.writeString(tag.getName());
}
/**
* Close the internal output buffer that stores the encoded SWF tags and
* part of the SWF header. It does not close the {@link OutputStream}
* argument in {@link #writeTo(OutputStream)}.
*/
@Override
public void close() throws IOException
{
outputBuffer.close();
}
}