| /* |
| * |
| * 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 flash.swf; |
| |
| import flash.swf.tags.*; |
| import flash.swf.types.ButtonCondAction; |
| import flash.swf.types.ButtonRecord; |
| import flash.swf.types.CXForm; |
| import flash.swf.types.CXFormWithAlpha; |
| import flash.swf.types.CurvedEdgeRecord; |
| import flash.swf.types.EdgeRecord; |
| import flash.swf.types.FillStyle; |
| import flash.swf.types.GlyphEntry; |
| import flash.swf.types.GradRecord; |
| import flash.swf.types.ImportRecord; |
| import flash.swf.types.KerningRecord; |
| import flash.swf.types.LineStyle; |
| import flash.swf.types.Matrix; |
| import flash.swf.types.MorphFillStyle; |
| import flash.swf.types.MorphGradRecord; |
| import flash.swf.types.MorphLineStyle; |
| import flash.swf.types.Rect; |
| import flash.swf.types.Shape; |
| import flash.swf.types.ShapeRecord; |
| import flash.swf.types.ShapeWithStyle; |
| import flash.swf.types.SoundInfo; |
| import flash.swf.types.StraightEdgeRecord; |
| import flash.swf.types.StyleChangeRecord; |
| import flash.swf.types.TextRecord; |
| import flash.swf.types.Filter; |
| import flash.swf.types.DropShadowFilter; |
| import flash.swf.types.BlurFilter; |
| import flash.swf.types.ColorMatrixFilter; |
| import flash.swf.types.GlowFilter; |
| import flash.swf.types.ConvolutionFilter; |
| import flash.swf.types.BevelFilter; |
| import flash.swf.types.GradientGlowFilter; |
| import flash.swf.types.GradientBevelFilter; |
| import flash.swf.types.Gradient; |
| import flash.swf.types.FocalGradient; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| /** |
| * A SWF tag encoder. It is typically used by calling one or more of |
| * the TagHandler methods and then writeTo(). |
| */ |
| public class TagEncoder extends TagHandler |
| implements TagValues |
| { |
| // changed from private to protected to support Flash Authoring - jkamerer 2007.07.30 |
| protected SwfEncoder writer; |
| private SwfEncoder tagw; |
| private int width; |
| private int height; |
| private int frames; |
| private int framecountPos; |
| protected DebugEncoder debug; |
| private Header header; |
| |
| protected Dictionary dict; |
| private int uuidOffset; |
| |
| public TagEncoder() |
| { |
| dict = new Dictionary(); |
| } |
| |
| public TagEncoder( Dictionary dict ) |
| { |
| this.dict = dict; |
| } |
| |
| public void productInfo(ProductInfo tag) |
| { |
| tagw.write32( tag.getProduct() ); |
| tagw.write32( tag.getEdition() ); |
| tagw.write( new byte[] { tag.getMajorVersion(), tag.getMinorVersion() } ); |
| tagw.write64( tag.getBuild() ); |
| tagw.write64( tag.getCompileDate() ); |
| encodeTag(tag); |
| } |
| |
| public void fileAttributes(FileAttributes tag) |
| { |
| tagw.writeUBits(0, 1); |
| tagw.writeBit(tag.useDirectBlit); |
| tagw.writeBit(tag.useGPU); |
| tagw.writeBit(tag.hasMetadata); |
| tagw.writeBit(tag.actionScript3); |
| tagw.writeBit(tag.suppressCrossDomainCaching); |
| tagw.writeBit(tag.swfRelativeUrls); |
| tagw.writeBit(tag.useNetwork); |
| tagw.writeBit(tag.brokerProductManager); |
| tagw.writeBit(tag.brokerLocalConnection); |
| tagw.writeUBits(0, 22); |
| encodeTag(tag); |
| } |
| |
| public void enableTelemetry(EnableTelemetry tag) |
| { |
| if (tag.enabled) { |
| tagw.writeUBits(0, 16); //reserved |
| encodeTag(tag); |
| } |
| } |
| |
| public void metadata(Metadata tag) |
| { |
| tagw.writeString( tag.xml ); |
| encodeTag(tag); |
| } |
| |
| public int getPos() |
| { |
| return writer.getPos(); |
| } |
| |
| protected int getSwfVersion() |
| { |
| return header.version; |
| } |
| |
| protected int getFrameRate() |
| { |
| return header.rate; |
| } |
| |
| public void setEncoderDictionary(Dictionary dict) |
| { |
| assert ( (this.dict == null) || (this.dict.ids.size() == 0)); |
| this.dict = dict; |
| } |
| |
| public Dictionary getDictionary() |
| { |
| return dict; |
| } |
| |
| protected SwfEncoder createEncoder(int swfVersion) |
| { |
| return new SwfEncoder(swfVersion); |
| } |
| |
| public boolean isDebug() |
| { |
| return debug != null; |
| } |
| |
| public CompressionLevel getCompressionLevel() |
| { |
| return isDebug() ? CompressionLevel.BestSpeed : CompressionLevel.BestCompression; |
| } |
| |
| public void header(Header header) |
| { |
| // get some header properties we need to know |
| int swfVersion = header.version; |
| this.header = header; |
| this.writer = createEncoder(swfVersion); |
| this.tagw = createEncoder(swfVersion); |
| width = header.size.getWidth(); |
| height = header.size.getHeight(); |
| frames = 0; |
| |
| // write the header |
| writer.writeUI8(header.compressed ? 'C' : 'F'); |
| writer.writeUI8('W'); |
| writer.writeUI8('S'); |
| writer.writeUI8(header.version); |
| writer.write32((int)header.length); |
| if (header.compressed) |
| { |
| writer.markComp(); |
| } |
| encodeRect(header.size, writer); |
| writer.writeUI8(header.rate >> 8); |
| writer.writeUI8(header.rate & 255); |
| framecountPos = writer.getPos(); |
| writer.writeUI16(header.framecount); |
| } |
| |
| public int getWidth() |
| { |
| return width/20; |
| } |
| |
| public int getHeight() |
| { |
| return height/20; |
| } |
| |
| public void finish() |
| { |
| // write end marker |
| writer.writeUI16(0); |
| |
| // update the length |
| writer.write32at(4, writer.getPos()); |
| |
| // update the frame count |
| writer.writeUI16at(framecountPos, frames); |
| } |
| |
| public void writeTo(OutputStream out) throws IOException |
| { |
| writer.writeTo(out, getCompressionLevel()); |
| } |
| |
| public void writeDebugTo(OutputStream out) throws IOException |
| { |
| debug.writeTo(out); |
| } |
| |
| public void setMainDebugScript(String path) |
| { |
| debug.setMainDebugScript(path); |
| } |
| |
| public void encodeRect(Rect r, SwfEncoder w) |
| { |
| int nBits = r.nbits(); |
| w.writeUBits(nBits, 5); |
| w.writeSBits(r.xMin, nBits); |
| w.writeSBits(r.xMax, nBits); |
| w.writeSBits(r.yMin, nBits); |
| w.writeSBits(r.yMax, nBits); |
| w.flushBits(); |
| } |
| |
| public void debugID(DebugID tag) |
| { |
| encodeTagHeader(tag.code, tag.uuid.bytes.length, false); |
| uuidOffset = writer.getPos(); |
| writer.write(tag.uuid.bytes); |
| |
| debug = new DebugEncoder(); |
| debug.header(getSwfVersion()); |
| debug.uuid(tag.uuid); |
| } |
| |
| private void encodeTag(Tag tag) |
| { |
| try |
| { |
| tagw.compress(getCompressionLevel()); |
| encodeTagHeader(tag.code, tagw.getPos(), isLongHeader(tag)); |
| tagw.writeTo(writer, getCompressionLevel()); |
| tagw.reset(); |
| } |
| catch (IOException e) |
| { |
| assert (false); |
| } |
| } |
| |
| private boolean isLongHeader(Tag t) |
| { |
| switch(t.code) |
| { |
| // [preilly] In the player code, ScriptThread::DefineBits() assumes all DefineBits |
| // tags use a long header. See "ch->data = AttachData(pos-8);". If the player |
| // also supported a short header, it would use "pos-4". |
| case stagDefineBits: |
| case stagDefineBitsJPEG2: |
| case stagDefineBitsJPEG3: |
| case stagDefineBitsLossless: |
| case stagDefineBitsLossless2: |
| return true; |
| |
| // [ed] the FlashPaper codebase also indicates that stagSoundStreamBlock must use |
| // a long format header. todo - verify by looking at the player code. |
| case stagSoundStreamBlock: |
| return true; |
| |
| // [edsmith] these tags have code in them. When we're writing a SWD, we use long headers |
| // so we can predict SWF offsets correctly when writing SWD line/offset records. |
| case stagDefineButton: |
| case stagDefineButton2: |
| case stagDefineSprite: |
| case stagDoInitAction: |
| case stagDoAction: |
| return isDebug(); |
| |
| case stagPlaceObject2: |
| return isDebug() && ((PlaceObject)t).hasClipAction(); |
| |
| // all other tags will use short/long headers depending on their length |
| default: |
| return false; |
| } |
| } |
| |
| private void encodeTagHeader(int code, int length, boolean longHeader) |
| { |
| if (longHeader || length >= 63) |
| { |
| writer.writeUI16((code << 6) | 63); |
| writer.write32(length); |
| } |
| else |
| { |
| writer.writeUI16((code << 6) | length); |
| } |
| } |
| |
| public void defineScalingGrid(DefineScalingGrid tag) |
| { |
| int idref = dict.getId(tag.scalingTarget); |
| tagw.writeUI16(idref); |
| encodeRect(tag.rect, tagw); |
| encodeTag(tag); |
| } |
| |
| public void defineBinaryData(DefineBinaryData tag) |
| { |
| encodeTagHeader(tag.code, 6+tag.data.length, false); |
| int id = dict.add(tag); |
| writer.writeUI16(id); |
| writer.write32(tag.reserved); |
| writer.write(tag.data); |
| } |
| |
| public void defineBits(DefineBits tag) |
| { |
| encodeTagHeader(tag.code, 2+tag.data.length, true); |
| int id = dict.add(tag); |
| writer.writeUI16(id); |
| writer.write(tag.data); |
| } |
| |
| public void defineBitsJPEG2(DefineBits tag) |
| { |
| defineBits(tag); |
| } |
| |
| public void defineBitsJPEG3(DefineBitsJPEG3 tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.write32(tag.data.length); |
| tagw.write(tag.data); |
| tagw.markComp(); |
| tagw.write(tag.alphaData); |
| encodeTag(tag); |
| } |
| |
| public void defineBitsLossless(DefineBitsLossless tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUI8(tag.format); |
| tagw.writeUI16(tag.width); |
| tagw.writeUI16(tag.height); |
| switch (tag.format) |
| { |
| case 3: |
| tagw.writeUI8(tag.colorData.length - 1); |
| tagw.markComp(); |
| encodeColorMapData(tag.colorData, tag.data, tagw); |
| break; |
| case 4: |
| case 5: |
| tagw.markComp(); |
| encodeBitmapData(tag.data, tagw); |
| break; |
| } |
| encodeTag(tag); |
| } |
| |
| private void encodeBitmapData(byte[] data, SwfEncoder w) |
| { |
| w.write(data); |
| } |
| |
| private void encodeColorMapData(int[] colorData, byte[] pixelData, SwfEncoder w) |
| { |
| for (int i = 0; i < colorData.length; i++) |
| { |
| encodeRGB(colorData[i], w); |
| } |
| w.write(pixelData); |
| } |
| |
| /** |
| * @param rgb as 0x00RRGGBB |
| * @param w |
| */ |
| private void encodeRGB(int rgb, SwfEncoder w) |
| { |
| w.writeUI8(rgb>>>16); // red. we don't mask this because if rgb has an Alpha value, something's wrong |
| w.writeUI8((rgb>>>8)&255); |
| w.writeUI8(rgb&255); // blue |
| } |
| |
| public void defineBitsLossless2(DefineBitsLossless tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUI8(tag.format); |
| tagw.writeUI16(tag.width); |
| tagw.writeUI16(tag.height); |
| switch (tag.format) |
| { |
| case 3: |
| tagw.writeUI8(tag.colorData.length - 1); |
| tagw.markComp(); |
| encodeAlphaColorMapData(tag.colorData, tag.data, tagw); |
| break; |
| case 4: |
| case 5: |
| tagw.markComp(); |
| encodeBitmapData(tag.data, tagw); |
| break; |
| } |
| encodeTag(tag); |
| } |
| |
| private void encodeAlphaColorMapData(int[] colorData, byte[] pixelData, SwfEncoder w) |
| { |
| for (int i = 0; i < colorData.length; i++) |
| { |
| encodeRGBA(colorData[i], w); |
| } |
| w.write(pixelData); |
| } |
| |
| /** |
| * @param rgba as 0xAARRGGBB |
| * @param w |
| */ |
| private void encodeRGBA(int rgba, SwfEncoder w) |
| { |
| w.writeUI8((rgba>>>16)&255); // red |
| w.writeUI8((rgba>>>8)&255); // green |
| w.writeUI8(rgba&255); // blue |
| w.writeUI8(rgba>>>24); // alpha |
| } |
| |
| public void defineButton(DefineButton tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| |
| if (isDebug()) |
| { |
| debug.adjust = writer.getPos()+6; |
| } |
| |
| for (int i = 0; i < tag.buttonRecords.length; i++) |
| { |
| encodeButtonRecord(tag.buttonRecords[i], tagw, tag.code); |
| } |
| tagw.writeUI8(0); // no more button records |
| |
| // assume there is only one condition we will handle |
| new ActionEncoder(tagw,debug).encode(tag.condActions[0].actionList); |
| tagw.writeUI8(0); // write action end flag, must be zero |
| encodeTag(tag); |
| |
| if (isDebug()) |
| { |
| debug.adjust = 0; |
| } |
| } |
| |
| private void encodeButtonRecord(ButtonRecord record, SwfEncoder w, int defineButton) |
| { |
| if (defineButton == stagDefineButton2) |
| { |
| w.writeUBits(0, 2); |
| w.writeBit(record.blendMode != -1); |
| w.writeBit(record.filters != null); |
| } |
| else |
| { |
| w.writeUBits(0, 4); |
| } |
| w.writeBit(record.hitTest); |
| w.writeBit(record.down); |
| w.writeBit(record.over); |
| w.writeBit(record.up); |
| |
| w.writeUI16(dict.getId(record.characterRef)); |
| w.writeUI16(record.placeDepth); |
| encodeMatrix(record.placeMatrix, w); |
| |
| if (defineButton == stagDefineButton2) |
| { |
| encodeCxforma(record.colorTransform, w); |
| if (record.filters != null) |
| { |
| this.encodeFilterList(record.filters, w); |
| } |
| if (record.blendMode != -1) |
| { |
| w.writeUI8(record.blendMode); |
| } |
| } |
| } |
| |
| private void encodeCxforma(CXFormWithAlpha cxforma, SwfEncoder w) |
| { |
| w.writeBit(cxforma.hasAdd); |
| w.writeBit(cxforma.hasMult); |
| |
| int nbits = cxforma.nbits(); |
| w.writeUBits(nbits, 4); |
| |
| if (cxforma.hasMult) |
| { |
| w.writeSBits(cxforma.redMultTerm, nbits); |
| w.writeSBits(cxforma.greenMultTerm, nbits); |
| w.writeSBits(cxforma.blueMultTerm, nbits); |
| w.writeSBits(cxforma.alphaMultTerm, nbits); |
| } |
| |
| if (cxforma.hasAdd) |
| { |
| w.writeSBits(cxforma.redAddTerm, nbits); |
| w.writeSBits(cxforma.greenAddTerm, nbits); |
| w.writeSBits(cxforma.blueAddTerm, nbits); |
| w.writeSBits(cxforma.alphaAddTerm, nbits); |
| } |
| |
| w.flushBits(); |
| } |
| |
| private void encodeMatrix(Matrix matrix, SwfEncoder w) |
| { |
| w.writeBit(matrix.hasScale); |
| if (matrix.hasScale) |
| { |
| int nScaleBits = matrix.nScaleBits(); |
| w.writeUBits(nScaleBits, 5); |
| w.writeSBits(matrix.scaleX, nScaleBits); |
| w.writeSBits(matrix.scaleY, nScaleBits); |
| } |
| |
| w.writeBit(matrix.hasRotate); |
| if (matrix.hasRotate) |
| { |
| int nRotateBits = matrix.nRotateBits(); |
| w.writeUBits(nRotateBits, 5); |
| w.writeSBits(matrix.rotateSkew0, nRotateBits); |
| w.writeSBits(matrix.rotateSkew1, nRotateBits); |
| } |
| |
| int nTranslateBits = matrix.nTranslateBits(); |
| w.writeUBits(nTranslateBits, 5); |
| w.writeSBits(matrix.translateX, nTranslateBits); |
| w.writeSBits(matrix.translateY, nTranslateBits); |
| |
| w.flushBits(); |
| } |
| |
| public void defineButton2(DefineButton tag) |
| { |
| if (isDebug()) |
| { |
| debug.adjust = writer.getPos()+6; |
| } |
| |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUBits(0, 7); // reserved |
| tagw.writeBit(tag.trackAsMenu); |
| int offsetPos = tagw.getPos(); |
| tagw.writeUI16(0); // actionOffset |
| |
| for (int i = 0; i < tag.buttonRecords.length; i++) |
| { |
| encodeButtonRecord(tag.buttonRecords[i], tagw, tag.code); |
| } |
| |
| tagw.writeUI8(0); // charEndFlag |
| |
| if (tag.condActions.length > 0) |
| { |
| tagw.writeUI16at(offsetPos, tagw.getPos()-offsetPos); |
| |
| for (int i = 0; i < tag.condActions.length; i++) |
| { |
| boolean isLast = i+1 == tag.condActions.length; |
| encodeButtonCondAction(tag.condActions[i], tagw, isLast); |
| } |
| } |
| encodeTag(tag); |
| |
| if (isDebug()) |
| { |
| debug.adjust = 0; |
| } |
| } |
| |
| private void encodeButtonCondAction(ButtonCondAction condAction, SwfEncoder w, boolean last) |
| { |
| int pos = w.getPos(); |
| w.writeUI16(0); |
| |
| w.writeUBits(condAction.keyPress, 7); |
| w.writeBit(condAction.overDownToIdle); |
| |
| w.writeBit(condAction.idleToOverDown); |
| w.writeBit(condAction.outDownToIdle); |
| w.writeBit(condAction.outDownToOverDown); |
| w.writeBit(condAction.overDownToOutDown); |
| w.writeBit(condAction.overDownToOverUp); |
| w.writeBit(condAction.overUpToOverDown); |
| w.writeBit(condAction.overUpToIdle); |
| w.writeBit(condAction.idleToOverUp); |
| |
| new ActionEncoder(w,debug).encode(condAction.actionList); |
| w.writeUI8(0); // end action byte |
| |
| if (!last) |
| { |
| w.writeUI16at(pos, w.getPos()-pos); |
| } |
| } |
| |
| public void defineButtonCxform(DefineButtonCxform tag) |
| { |
| int idref = dict.getId(tag.button); |
| tagw.writeUI16(idref); |
| encodeCxform(tag.colorTransform, tagw); |
| encodeTag(tag); |
| } |
| |
| private void encodeCxform(CXForm cxform, SwfEncoder w) |
| { |
| |
| w.writeBit(cxform.hasAdd); |
| w.writeBit(cxform.hasMult); |
| |
| int nbits = cxform.nbits(); |
| w.writeUBits(nbits, 4); |
| |
| if (cxform.hasMult) |
| { |
| w.writeSBits(cxform.redMultTerm, nbits); |
| w.writeSBits(cxform.greenMultTerm, nbits); |
| w.writeSBits(cxform.blueMultTerm, nbits); |
| } |
| |
| if (cxform.hasAdd) |
| { |
| w.writeSBits(cxform.redAddTerm, nbits); |
| w.writeSBits(cxform.greenAddTerm, nbits); |
| w.writeSBits(cxform.blueAddTerm, nbits); |
| } |
| |
| w.flushBits(); |
| } |
| |
| public void defineButtonSound(DefineButtonSound tag) |
| { |
| int idref = dict.getId(tag.button); |
| tagw.writeUI16(idref); |
| if (tag.sound0 != null) |
| { |
| tagw.writeUI16(dict.getId(tag.sound0)); |
| encodeSoundInfo(tag.info0, tagw); |
| } |
| else |
| { |
| tagw.writeUI16(0); |
| } |
| if (tag.sound1 != null) |
| { |
| tagw.writeUI16(dict.getId(tag.sound1)); |
| encodeSoundInfo(tag.info1, tagw); |
| } |
| else |
| { |
| tagw.writeUI16(0); |
| } |
| if (tag.sound2 != null) |
| { |
| tagw.writeUI16(dict.getId(tag.sound2)); |
| encodeSoundInfo(tag.info2, tagw); |
| } |
| else |
| { |
| tagw.writeUI16(0); |
| } |
| if (tag.sound3 != null) |
| { |
| tagw.writeUI16(dict.getId(tag.sound3)); |
| encodeSoundInfo(tag.info3, tagw); |
| } |
| else |
| { |
| tagw.writeUI16(0); |
| } |
| encodeTag(tag); |
| } |
| |
| private void encodeSoundInfo(SoundInfo info, SwfEncoder w) |
| { |
| w.writeUBits(0, 2); // reserved |
| w.writeBit(info.syncStop); |
| w.writeBit(info.syncNoMultiple); |
| w.writeBit(info.records != null); |
| w.writeBit(info.loopCount != SoundInfo.UNINITIALIZED); |
| w.writeBit(info.outPoint != SoundInfo.UNINITIALIZED); |
| w.writeBit(info.inPoint != SoundInfo.UNINITIALIZED); |
| |
| if (info.inPoint != SoundInfo.UNINITIALIZED) |
| { |
| w.write32((int)info.inPoint); |
| } |
| if (info.outPoint != SoundInfo.UNINITIALIZED) |
| { |
| w.write32((int)info.outPoint); |
| } |
| if (info.loopCount != SoundInfo.UNINITIALIZED) |
| { |
| w.writeUI16(info.loopCount); |
| } |
| if (info.records != null) |
| { |
| w.writeUI8(info.records.length); |
| for (int k = 0; k < info.records.length; k++) |
| { |
| w.write64(info.records[k]); |
| } |
| } |
| } |
| |
| public void defineEditText(DefineEditText tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| encodeRect(tag.bounds, tagw); |
| |
| tagw.writeBit(tag.hasText); |
| tagw.writeBit(tag.wordWrap); |
| tagw.writeBit(tag.multiline); |
| tagw.writeBit(tag.password); |
| tagw.writeBit(tag.readOnly); |
| tagw.writeBit(tag.hasTextColor); |
| tagw.writeBit(tag.hasMaxLength); |
| tagw.writeBit(tag.hasFont); |
| |
| tagw.writeBit(tag.hasFontClass && !tag.hasFont); // FP 9.0.45 or later |
| tagw.writeBit(tag.autoSize); |
| tagw.writeBit(tag.hasLayout); |
| tagw.writeBit(tag.noSelect); |
| tagw.writeBit(tag.border); |
| tagw.writeBit(tag.wasStatic); |
| tagw.writeBit(tag.html); |
| tagw.writeBit(tag.useOutlines); |
| |
| tagw.flushBits(); |
| |
| if (tag.hasFont) |
| { |
| int idref = dict.getId(tag.font); |
| tagw.writeUI16(idref); |
| tagw.writeUI16(tag.height); |
| } |
| else if (tag.hasFontClass) |
| { |
| tagw.writeString(tag.fontClass); |
| tagw.writeUI16(tag.height); |
| } |
| |
| if (tag.hasTextColor) |
| { |
| encodeRGBA(tag.color, tagw); |
| } |
| |
| if (tag.hasMaxLength) |
| { |
| tagw.writeUI16(tag.maxLength); |
| } |
| |
| if (tag.hasLayout) |
| { |
| tagw.writeUI8(tag.align); |
| tagw.writeUI16(tag.leftMargin); |
| tagw.writeUI16(tag.rightMargin); |
| tagw.writeUI16(tag.ident); |
| tagw.writeSI16(tag.leading); // see errata, leading is signed |
| } |
| |
| tagw.writeString(tag.varName); |
| if (tag.hasText) |
| { |
| tagw.writeString(tag.initialText); |
| } |
| encodeTag(tag); |
| } |
| |
| public void defineFont(DefineFont1 tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| |
| int count = tag.glyphShapeTable.length; |
| |
| int offsetPos = tagw.getPos(); |
| |
| // write offset placeholders |
| for (int i = 0; i < count; i++) |
| { |
| tagw.writeUI16(0); |
| } |
| |
| // now write glyphs and update the encoded offset table |
| for (int i = 0; i < count; i++) |
| { |
| tagw.writeUI16at(offsetPos+2*i, tagw.getPos()-offsetPos); |
| encodeShape(tag.glyphShapeTable[i], tagw, stagDefineShape3, 1, 0); |
| } |
| |
| encodeTag(tag); |
| } |
| |
| public void encodeShape(Shape s, SwfEncoder w, int shape, int nFillStyles, int nLineStyles) |
| { |
| int[] numFillBits = new int[] { SwfEncoder.minBits(nFillStyles,0) }; |
| int[] numLineBits = new int[] { SwfEncoder.minBits(nLineStyles,0) }; |
| |
| w.writeUBits(numFillBits[0], 4); |
| w.writeUBits(numLineBits[0], 4); |
| |
| if (s != null && s.shapeRecords != null) |
| { |
| Iterator<ShapeRecord> it = s.shapeRecords.iterator(); |
| while (it.hasNext()) |
| { |
| ShapeRecord record = it.next(); |
| if (record instanceof StyleChangeRecord) |
| { |
| // style change |
| w.writeBit(false); |
| StyleChangeRecord change = (StyleChangeRecord) record; |
| encodeStyleChangeRecord(w, change, numFillBits, numLineBits, shape); |
| } |
| else |
| { |
| // edge |
| w.writeBit(true); |
| EdgeRecord e = (EdgeRecord) record; |
| boolean straight = e instanceof StraightEdgeRecord; |
| w.writeBit(straight); |
| int nbits = straight ? calcBits((StraightEdgeRecord)e) : calcBits((CurvedEdgeRecord)e); |
| if (nbits < 2) |
| nbits = 2; |
| w.writeUBits(nbits-2, 4); |
| if (straight) |
| { |
| // line |
| StraightEdgeRecord line = (StraightEdgeRecord) e; |
| encodeStraightEdgeRecord(line, w, nbits); |
| } |
| else |
| { |
| // curve |
| CurvedEdgeRecord curve = (CurvedEdgeRecord) e; |
| w.writeSBits(curve.controlDeltaX, nbits); |
| w.writeSBits(curve.controlDeltaY, nbits); |
| w.writeSBits(curve.anchorDeltaX, nbits); |
| w.writeSBits(curve.anchorDeltaY, nbits); |
| } |
| } |
| } |
| } |
| |
| // endshaperecord |
| w.writeUBits(0, 6); |
| |
| w.flushBits(); |
| } |
| |
| private int calcBits(StraightEdgeRecord edge) |
| { |
| return SwfEncoder.minBits(SwfEncoder.maxNum(edge.deltaX,edge.deltaY,0,0),1); |
| } |
| |
| private int calcBits(CurvedEdgeRecord edge) |
| { |
| return SwfEncoder.minBits(SwfEncoder.maxNum(edge.controlDeltaX, |
| edge.controlDeltaY, |
| edge.anchorDeltaX, |
| edge.anchorDeltaY), 1); |
| } |
| |
| private void encodeStraightEdgeRecord(StraightEdgeRecord line, SwfEncoder w, int nbits) |
| { |
| if (line.deltaX == 0) |
| { |
| w.writeUBits(01, 2); // vertical line |
| w.writeSBits(line.deltaY, nbits); |
| } |
| else if (line.deltaY == 0) |
| { |
| w.writeUBits(00, 2); // horizontal line |
| w.writeSBits(line.deltaX, nbits); |
| } |
| else |
| { |
| w.writeBit(true); // general line |
| w.writeSBits(line.deltaX, nbits); |
| w.writeSBits(line.deltaY, nbits); |
| } |
| } |
| |
| private void encodeStyleChangeRecord(SwfEncoder w, StyleChangeRecord s, |
| int[] numFillBits, int[] numLineBits, int shape) |
| { |
| w.writeBit(s.stateNewStyles); |
| w.writeBit(s.stateLineStyle); |
| w.writeBit(s.stateFillStyle1); |
| w.writeBit(s.stateFillStyle0); |
| w.writeBit(s.stateMoveTo); |
| |
| if (s.stateMoveTo) |
| { |
| int moveBits = s.nMoveBits(); |
| w.writeUBits(moveBits, 5); |
| w.writeSBits(s.moveDeltaX, moveBits); |
| w.writeSBits(s.moveDeltaY, moveBits); |
| } |
| |
| if (s.stateFillStyle0) |
| { |
| w.writeUBits(s.fillstyle0, numFillBits[0]); |
| } |
| |
| if (s.stateFillStyle1) |
| { |
| w.writeUBits(s.fillstyle1, numFillBits[0]); |
| } |
| |
| if (s.stateLineStyle) |
| { |
| w.writeUBits(s.linestyle, numLineBits[0]); |
| } |
| |
| if (s.stateNewStyles) |
| { |
| w.flushBits(); |
| |
| encodeFillstyles(s.fillstyles, w, shape); |
| encodeLinestyles(s.linestyles, w, shape); |
| |
| numFillBits[0] = SwfEncoder.minBits(s.fillstyles.size(), 0); |
| numLineBits[0] = SwfEncoder.minBits(s.linestyles.size(), 0); |
| w.writeUBits(numFillBits[0], 4); |
| w.writeUBits(numLineBits[0], 4); |
| } |
| } |
| |
| private void encodeLinestyles(ArrayList<LineStyle> linestyles, SwfEncoder w, int shape) |
| { |
| int count = 0; |
| |
| if (linestyles != null) |
| count = linestyles.size(); |
| |
| if (count > 0xFF) |
| { |
| w.writeUI8(0xFF); |
| w.writeUI16(count); |
| } |
| else |
| { |
| w.writeUI8(count); |
| } |
| |
| for (int i = 0; i < count; i++) |
| { |
| encodeLineStyle((LineStyle)linestyles.get(i), w, shape); |
| } |
| } |
| |
| private void encodeLineStyle(LineStyle lineStyle, SwfEncoder w, int shape) |
| { |
| w.writeUI16(lineStyle.width); |
| |
| if (shape == stagDefineShape4) |
| { |
| w.writeUI16( lineStyle.flags ); |
| if (lineStyle.hasMiterJoint()) |
| w.writeUI16( lineStyle.miterLimit ); |
| } |
| |
| if (shape == stagDefineShape4 && lineStyle.hasFillStyle()) |
| { |
| encodeFillStyle( lineStyle.fillStyle, w, shape ); |
| } |
| else if ((shape == stagDefineShape3) || (shape == stagDefineShape4)) |
| { |
| encodeRGBA(lineStyle.color, w); |
| } |
| else |
| { |
| encodeRGB(lineStyle.color, w); |
| } |
| } |
| |
| private void encodeFillstyles(ArrayList<FillStyle> fillstyles, SwfEncoder w, int shape) |
| { |
| int count = 0; |
| if (fillstyles != null) |
| count = fillstyles.size(); |
| |
| if (count >= 0xFF) |
| { |
| w.writeUI8(0xFF); |
| w.writeUI16(count); |
| } |
| else |
| { |
| w.writeUI8(count); |
| } |
| |
| if (count > 0) |
| { |
| Iterator<FillStyle> it = fillstyles.iterator(); |
| while (it.hasNext()) |
| { |
| FillStyle style = (FillStyle) it.next(); |
| encodeFillStyle(style, w, shape); |
| } |
| } |
| } |
| |
| private void encodeFillStyle(FillStyle style, SwfEncoder w, int shape) |
| { |
| w.writeUI8(style.type); |
| switch (style.type) |
| { |
| case FillStyle.FILL_SOLID: // 0x00 |
| if ((shape == stagDefineShape3) || (shape == stagDefineShape4)) encodeRGBA(style.color, w); |
| else encodeRGB(style.color, w); |
| break; |
| case FillStyle.FILL_GRADIENT: // 0x10 linear gradient fill |
| case FillStyle.FILL_RADIAL_GRADIENT: // 0x12 radial gradient fill |
| case FillStyle.FILL_FOCAL_RADIAL_GRADIENT: // 0x13 focal radial gradient fill |
| encodeMatrix(style.matrix, w); |
| encodeGradient(style.gradient, w, shape); |
| break; |
| case FillStyle.FILL_BITS: // 0x40 tiled bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP): // 0x41 clipped bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_NOSMOOTH): // 0x42 tiled non-smoothed fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP | FillStyle.FILL_BITS_NOSMOOTH): // 0x43 clipped non-smoothed fill |
| int id = dict.add(style.bitmap); |
| w.writeUI16(id); |
| encodeMatrix(style.matrix, w); |
| break; |
| } |
| } |
| |
| private void encodeGradient( Gradient gradient, SwfEncoder w, int shape) |
| { |
| w.writeUBits( gradient.spreadMode, 2 ); |
| w.writeUBits( gradient.interpolationMode, 2 ); |
| w.writeUBits( gradient.records.length, 4 ); |
| for (int i = 0; i < gradient.records.length; i++) |
| { |
| encodeGradRecord(gradient.records[i], w, shape); |
| } |
| if (gradient instanceof FocalGradient) |
| { |
| w.writeFixed8( ((FocalGradient)gradient).focalPoint ); |
| } |
| } |
| |
| private void encodeGradRecord(GradRecord record, SwfEncoder w, int shape) |
| { |
| w.writeUI8(record.ratio); |
| if ((shape == stagDefineShape3) || (shape == stagDefineShape4)) |
| encodeRGBA(record.color, w); |
| else |
| encodeRGB(record.color, w); |
| } |
| |
| public void defineFont2(DefineFont2 tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| int startPos = tagw.getPos(); |
| boolean again; |
| |
| if (tag.code == stagDefineFont3) |
| { |
| tag.wideCodes = true; |
| } |
| |
| if (! tag.wideCodes) |
| { |
| for (int i=0; i < tag.codeTable.length; i++) |
| { |
| if (tag.codeTable[i] > 255) |
| { |
| tag.wideCodes = true; |
| break; |
| } |
| } |
| } |
| |
| loop: |
| do |
| { |
| again = false; |
| tagw.writeBit(tag.hasLayout); |
| tagw.writeBit(tag.shiftJIS); |
| tagw.writeBit(tag.smallText); |
| tagw.writeBit(tag.ansi); |
| tagw.writeBit(tag.wideOffsets); |
| tagw.writeBit(tag.wideCodes); |
| tagw.writeBit(tag.italic); |
| tagw.writeBit(tag.bold); |
| tagw.flushBits(); |
| |
| tagw.writeUI8(tag.langCode); |
| |
| tagw.writeLengthString(tag.fontName); |
| int count = tag.glyphShapeTable.length; |
| |
| tagw.writeUI16(count); |
| int offsetPos = tagw.getPos(); |
| |
| // save space for the offset table |
| if (tag.wideOffsets) |
| { |
| for (int i=0; i < count; i++) |
| { |
| tagw.write32(0); |
| } |
| } |
| else |
| { |
| for (int i=0; i < count; i++) |
| { |
| tagw.writeUI16(0); |
| } |
| } |
| |
| //PJF: write placeholder for codeTableOffset, this will be changed after shapes encoded |
| if (count > 0) |
| { |
| if (tag.wideOffsets) |
| { |
| tagw.write32(0); |
| } |
| else |
| { |
| tagw.writeUI16(0); |
| } |
| } |
| |
| for (int i = 0; i < count; i++) |
| { |
| // save offset to this glyph |
| int offset = tagw.getPos()-offsetPos; |
| if (!tag.wideOffsets && offset > 65535) |
| { |
| again = true; |
| tag.wideOffsets = true; |
| tagw.setPos(startPos); |
| continue loop; |
| } |
| if (tag.wideOffsets) |
| tagw.write32at(offsetPos+4*i, offset); |
| else |
| tagw.writeUI16at(offsetPos+2*i, offset); |
| |
| encodeShape(tag.glyphShapeTable[i], tagw, stagDefineShape3, 1, 0); |
| } |
| |
| // update codeTableOffset |
| int offset = tagw.getPos()-offsetPos; |
| if (!tag.wideOffsets && offset > 65535) |
| { |
| again = true; |
| tag.wideOffsets = true; |
| tagw.setPos(startPos); |
| continue loop; |
| } |
| if (tag.wideOffsets) |
| { |
| tagw.write32at(offsetPos+4*count, offset); |
| } |
| else |
| { |
| tagw.writeUI16at(offsetPos+2*count, offset); |
| } |
| |
| // now write the codetable |
| |
| if (tag.wideCodes) |
| { |
| for (int i = 0; i < tag.codeTable.length; i++) |
| { |
| tagw.writeUI16(tag.codeTable[i]); |
| } |
| } |
| else |
| { |
| for (int i = 0; i < tag.codeTable.length; i++) |
| { |
| tagw.writeUI8(tag.codeTable[i]); |
| } |
| } |
| |
| if (tag.hasLayout) |
| { |
| tagw.writeSI16(tag.ascent); |
| tagw.writeSI16(tag.descent); |
| tagw.writeSI16(tag.leading); |
| |
| for (int i = 0; i < tag.advanceTable.length; i++) |
| { |
| tagw.writeSI16(tag.advanceTable[i]); |
| } |
| |
| for (int i = 0; i < tag.boundsTable.length; i++) |
| { |
| encodeRect(tag.boundsTable[i], tagw); |
| } |
| |
| tagw.writeUI16(tag.kerningTable.length); |
| |
| for (int i = 0; i < tag.kerningTable.length; i++) |
| { |
| if (!tag.wideCodes && ((tag.kerningTable[i].code1 > 255) || |
| (tag.kerningTable[i].code2 > 255))) |
| { |
| again = true; |
| tag.wideCodes = true; |
| tagw.setPos(startPos); |
| continue loop; |
| } |
| |
| encodeKerningRecord(tag.kerningTable[i], tagw, tag.wideCodes); |
| } |
| } |
| } |
| while (again); |
| |
| encodeTag(tag); |
| } |
| |
| public void defineFont3(DefineFont3 tag) |
| { |
| defineFont2(tag); |
| } |
| |
| public void defineFont4(DefineFont4 tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| |
| tagw.writeUBits(0, 5); // reserved |
| tagw.writeBit(tag.hasFontData); |
| //tagw.writeBit(tag.smallText); |
| tagw.writeBit(tag.italic); |
| tagw.writeBit(tag.bold); |
| tagw.flushBits(); |
| |
| //tagw.writeUI8(tag.langCode); |
| tagw.writeString(tag.fontName); |
| if (tag.hasFontData) |
| { |
| tagw.write(tag.data); |
| } |
| |
| encodeTag(tag); |
| } |
| |
| public void defineFontAlignZones(DefineFontAlignZones tag) |
| { |
| int fontID = dict.getId(tag.font); |
| tagw.writeUI16(fontID); |
| tagw.writeUBits(tag.csmTableHint, 2); |
| tagw.writeUBits(0, 6); // reserved |
| for (int i = 0; i < tag.zoneTable.length; i++) |
| { |
| ZoneRecord record = tag.zoneTable[i]; |
| tagw.writeUI8(record.numZoneData); |
| for (int j = 0; j < record.numZoneData; j++) |
| { |
| tagw.write32((int)record.zoneData[j]); |
| } |
| tagw.writeUI8(record.zoneMask); |
| } |
| encodeTag(tag); |
| } |
| |
| public void csmTextSettings(CSMTextSettings tag) |
| { |
| int textID = 0; |
| if (tag.textReference != null) |
| { |
| textID = dict.getId(tag.textReference); |
| } |
| tagw.writeUI16(textID); |
| tagw.writeUBits(tag.styleFlagsUseSaffron, 2); |
| tagw.writeUBits(tag.gridFitType, 3); |
| tagw.writeUBits(0, 3); // reserved |
| // FIXME: thickness/sharpness should be written out as 32 bit IEEE Single Precision format in little Endian |
| tagw.writeUBits((int)tag.thickness, 32); |
| tagw.writeUBits((int)tag.sharpness, 32); |
| tagw.writeUBits(0, 8); //reserved |
| |
| encodeTag(tag); |
| } |
| |
| public void defineFontName(DefineFontName tag) |
| { |
| int fontID = dict.getId(tag.font); |
| tagw.writeUI16(fontID); |
| if (tag.fontName != null) |
| { |
| tagw.writeString(tag.fontName); |
| } |
| else |
| { |
| tagw.writeString(""); |
| } |
| if (tag.copyright != null) |
| { |
| tagw.writeString(tag.copyright); |
| } |
| else |
| { |
| tagw.writeString(""); |
| } |
| |
| encodeTag(tag); |
| } |
| |
| private void encodeKerningRecord(KerningRecord kerningRecord, SwfEncoder w, boolean wideCodes) |
| { |
| if (wideCodes) |
| { |
| w.writeUI16(kerningRecord.code1); |
| w.writeUI16(kerningRecord.code2); |
| } |
| else |
| { |
| w.writeUI8(kerningRecord.code1); |
| w.writeUI8(kerningRecord.code2); |
| } |
| w.writeUI16(kerningRecord.adjustment); |
| } |
| |
| public void defineFontInfo(DefineFontInfo tag) |
| { |
| int idref = dict.getId(tag.font); |
| tagw.writeUI16(idref); |
| |
| tagw.writeLengthString(tag.name); |
| |
| tagw.writeUBits(0, 3); // reserved |
| tagw.writeBit(tag.shiftJIS); |
| tagw.writeBit(tag.ansi); |
| tagw.writeBit(tag.italic); |
| tagw.writeBit(tag.bold); |
| |
| if (tag.code == stagDefineFontInfo2) |
| { |
| tagw.writeBit(tag.wideCodes = true); |
| tagw.writeUI8(tag.langCode); |
| } |
| else |
| { |
| if (! tag.wideCodes) |
| { |
| for (int i=0; i < tag.codeTable.length; i++) |
| { |
| if (tag.codeTable[i] > 255) |
| { |
| tag.wideCodes = true; |
| break; |
| } |
| } |
| } |
| tagw.writeBit(tag.wideCodes); |
| } |
| |
| if (tag.wideCodes) |
| { |
| for (int i = 0; i < tag.codeTable.length; i++) |
| tagw.writeUI16(tag.codeTable[i]); |
| } |
| else |
| { |
| for (int i = 0; i < tag.codeTable.length; i++) |
| tagw.writeUI8(tag.codeTable[i]); |
| } |
| encodeTag(tag); |
| } |
| |
| public void defineFontInfo2(DefineFontInfo tag) |
| { |
| defineFontInfo(tag); |
| } |
| |
| public void defineMorphShape(DefineMorphShape tag) |
| { |
| defineMorphShape2(tag); |
| } |
| |
| public void defineMorphShape2(DefineMorphShape tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| encodeRect(tag.startBounds, tagw); |
| encodeRect(tag.endBounds, tagw); |
| if (tag.code == stagDefineMorphShape2) |
| { |
| encodeRect(tag.startEdgeBounds, tagw); |
| encodeRect(tag.endEdgeBounds, tagw); |
| tagw.writeUBits(tag.reserved, 6); |
| tagw.writeUBits(tag.usesNonScalingStrokes ? 1 : 0, 1); |
| tagw.writeUBits(tag.usesScalingStrokes ? 1 : 0, 1); |
| } |
| tagw.write32(0); |
| int pos = tagw.getPos(); |
| encodeMorphFillstyles(tag.fillStyles, tagw, tag.code); |
| encodeMorphLinestyles(tag.lineStyles, tagw, tag.code); |
| encodeShape(tag.startEdges, tagw, stagDefineShape3, tag.fillStyles.length, tag.lineStyles.length); |
| tagw.write32at(pos-4, tagw.getPos() - pos); |
| // end shape contains only edges, no style information |
| encodeShape(tag.endEdges, tagw, stagDefineShape3, 0, 0); |
| encodeTag(tag); |
| } |
| |
| private void encodeMorphFillstyles(MorphFillStyle[] fillStyles, SwfEncoder w, int code) |
| { |
| int count = fillStyles.length; |
| if (count >= 0xFF) |
| { |
| w.writeUI8(0xFF); |
| w.writeUI16(count); |
| } |
| else |
| { |
| w.writeUI8(count); |
| } |
| |
| for (int i = 0; i < count; i++) |
| { |
| encodeMorphFillstyle(fillStyles[i], w, code); |
| } |
| } |
| |
| private void encodeMorphFillstyle(MorphFillStyle style, SwfEncoder w, int code) |
| { |
| w.writeUI8(style.type); |
| switch (style.type) |
| { |
| case FillStyle.FILL_SOLID: // 0x00 |
| encodeRGBA(style.startColor, w); |
| encodeRGBA(style.endColor, w); |
| break; |
| case FillStyle.FILL_GRADIENT: // 0x10 linear gradient fill |
| case FillStyle.FILL_RADIAL_GRADIENT: // 0x12 radial gradient fill |
| case FillStyle.FILL_FOCAL_RADIAL_GRADIENT: // 0x13 focal radial gradient fill |
| encodeMatrix(style.startGradientMatrix, w); |
| encodeMatrix(style.endGradientMatrix, w); |
| encodeMorphGradient(style.gradRecords, w); |
| if (style.type == FillStyle.FILL_FOCAL_RADIAL_GRADIENT && code == stagDefineMorphShape2) |
| { |
| w.writeSI16(style.ratio1); |
| w.writeSI16(style.ratio2); |
| } |
| break; |
| case FillStyle.FILL_BITS: // 0x40 tiled bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP): // 0x41 clipped bitmap fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_NOSMOOTH): // 0x42 tiled non-smoothed fill |
| case (FillStyle.FILL_BITS | FillStyle.FILL_BITS_CLIP | FillStyle.FILL_BITS_NOSMOOTH): // 0x43 clipped non-smoothed fill |
| int id = dict.add(style.bitmap); |
| w.writeUI16(id); |
| encodeMatrix(style.startBitmapMatrix, w); |
| encodeMatrix(style.endBitmapMatrix, w); |
| break; |
| default: |
| assert (false); |
| //throw new IOException("unrecognized fill style type: " + style.type); |
| } |
| } |
| |
| private void encodeMorphGradient(MorphGradRecord[] gradRecords, SwfEncoder w) |
| { |
| w.writeUI8(gradRecords.length); |
| for (int i = 0; i < gradRecords.length; i++) |
| { |
| MorphGradRecord record = gradRecords[i]; |
| w.writeUI8(record.startRatio); |
| encodeRGBA(record.startColor, w); |
| w.writeUI8(record.endRatio); |
| encodeRGBA(record.endColor, w); |
| } |
| } |
| |
| private void encodeMorphLinestyles(MorphLineStyle[] lineStyles, SwfEncoder w, int code) |
| { |
| if (lineStyles.length >= 0xFF) |
| { |
| w.writeUI8(0xFF); |
| w.writeUI16(lineStyles.length); |
| } |
| else |
| { |
| w.writeUI8(lineStyles.length); |
| } |
| |
| for (int i = 0; i < lineStyles.length; i++) |
| { |
| MorphLineStyle style = lineStyles[i]; |
| w.writeUI16(style.startWidth); |
| w.writeUI16(style.endWidth); |
| if (code == stagDefineMorphShape2) |
| { |
| w.writeUBits(style.startCapsStyle, 2); |
| w.writeUBits(style.jointStyle, 2); |
| w.writeBit(style.hasFill); |
| w.writeBit(style.noHScale); |
| w.writeBit(style.noVScale); |
| w.writeBit(style.pixelHinting); |
| w.writeUBits(0, 5); // reserved |
| w.writeBit(style.noClose); |
| w.writeUBits(style.endCapsStyle, 2); |
| if (style.jointStyle == 2) |
| { |
| w.writeUI16(style.miterLimit); |
| } |
| } |
| if (!style.hasFill) |
| { |
| encodeRGBA(style.startColor,w); |
| encodeRGBA(style.endColor,w); |
| } |
| if (style.hasFill) |
| { |
| encodeMorphFillstyle(style.fillType, w, code); |
| } |
| } |
| } |
| |
| public void defineShape(DefineShape tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| encodeRect(tag.bounds, tagw); |
| if (tag.code == stagDefineShape4) |
| { |
| encodeRect(tag.edgeBounds, tagw); |
| tagw.writeUBits(0, 5); |
| tagw.writeBit(tag.usesFillWindingRule); |
| tagw.writeBit(tag.usesNonScalingStrokes); |
| tagw.writeBit(tag.usesScalingStrokes); |
| } |
| encodeShapeWithStyle(tag.shapeWithStyle, tagw, tag.code); |
| encodeTag(tag); |
| } |
| |
| private void encodeShapeWithStyle(ShapeWithStyle sws, SwfEncoder w, int shape) |
| { |
| encodeFillstyles(sws.fillstyles, w, shape); |
| encodeLinestyles(sws.linestyles, w, shape); |
| |
| int fillStyleCount = sws.fillstyles == null ? 0 : sws.fillstyles.size(); |
| int lineStyleCount = sws.linestyles == null ? 0 : sws.linestyles.size(); |
| encodeShape(sws, w, shape, fillStyleCount, lineStyleCount); |
| } |
| |
| public void defineShape2(DefineShape tag) |
| { |
| defineShape(tag); |
| } |
| |
| public void defineShape3(DefineShape tag) |
| { |
| defineShape(tag); |
| } |
| |
| public void defineShape4(DefineShape tag) |
| { |
| defineShape(tag); |
| } |
| |
| public void defineSound(DefineSound tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUBits(tag.format, 4); |
| tagw.writeUBits(tag.rate, 2); |
| tagw.writeUBits(tag.size, 1); |
| tagw.writeUBits(tag.type, 1); |
| tagw.write32((int)tag.sampleCount); |
| tagw.write(tag.data); |
| encodeTag(tag); |
| } |
| |
| public void defineSprite(DefineSprite tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUI16(tag.framecount); |
| |
| if (isDebug()) |
| { |
| debug.adjust = writer.getPos()+6; |
| } |
| |
| // save frame count |
| int oldFrames = frames; |
| frames = 0; |
| |
| // save the movie writer, and push a new writer |
| SwfEncoder oldWriter = writer; |
| writer = tagw; |
| tagw = createEncoder(getSwfVersion()); |
| |
| // write sprite tags |
| List tags = tag.tagList.tags; |
| int size = tags.size(); |
| for (int i = 0; i < size; i++) |
| { |
| Tag t = (Tag) tags.get(i); |
| if (!(t instanceof DefineTag)) |
| t.visit(this); |
| } |
| |
| // terminate with end marker |
| writer.writeUI16(0); |
| |
| // update frame count |
| writer.writeUI16at(2, frames); |
| |
| // restore writers |
| tagw = writer; |
| writer = oldWriter; |
| frames = oldFrames; |
| |
| if (isDebug()) |
| { |
| debug.adjust = 0; |
| } |
| |
| encodeTag(tag); |
| } |
| |
| public void defineText(DefineText tag) |
| { |
| encodeDefineText(tag, tagw, tag.code); |
| encodeTag(tag); |
| } |
| |
| private void encodeDefineText(DefineText tag, SwfEncoder w, int type) |
| { |
| int id = dict.add(tag); |
| w.writeUI16(id); |
| encodeRect(tag.bounds, w); |
| encodeMatrix(tag.matrix, w); |
| int length = tag.records.size(); |
| |
| // compute necessary bit width |
| int glyphBits = 0; |
| int advanceBits = 0; |
| for (int i=0; i < length; i++) |
| { |
| TextRecord tr = tag.records.get(i); |
| |
| for (int j = 0; j < tr.entries.length; j++) |
| { |
| GlyphEntry entry = tr.entries[j]; |
| |
| while (entry.getIndex() > (1<<glyphBits)) |
| glyphBits++; |
| while (Math.abs(entry.advance) > (1<<advanceBits)) |
| advanceBits++; |
| } |
| } |
| |
| // increment to get from bit index to bit count. |
| ++glyphBits; |
| ++advanceBits; |
| |
| w.writeUI8(glyphBits); |
| w.writeUI8(++advanceBits); // add one extra bit because advances are signed |
| |
| for (int i = 0; i < length; i++) |
| { |
| TextRecord record = tag.records.get(i); |
| encodeTextRecord(record, w, type, glyphBits, advanceBits); |
| } |
| |
| w.writeUI8(0); |
| } |
| |
| private void encodeFilterList(List<Filter> filters, SwfEncoder w) |
| { |
| int count = filters.size(); |
| w.writeUI8( count ); |
| for (Iterator<Filter> it = filters.iterator(); it.hasNext();) |
| { |
| Filter f = (Filter) it.next(); |
| w.writeUI8(f.getID()); |
| // I've never quite understood why the serialization code isn't in the tags themselves.. |
| switch(f.getID()) |
| { |
| case DropShadowFilter.ID: encodeDropShadowFilter( w, (DropShadowFilter) f ); break; |
| case BlurFilter.ID: encodeBlurFilter( w, (BlurFilter) f ); break; |
| case ConvolutionFilter.ID: encodeConvolutionFilter( w, (ConvolutionFilter) f ); break; |
| case GlowFilter.ID: encodeGlowFilter( w, (GlowFilter) f ); break; |
| case BevelFilter.ID: encodeBevelFilter( w, (BevelFilter) f ); break; |
| case ColorMatrixFilter.ID: encodeColorMatrixFilter( w, (ColorMatrixFilter) f ); break; |
| case GradientGlowFilter.ID: encodeGradientGlowFilter( w, (GradientGlowFilter) f ); break; |
| case GradientBevelFilter.ID: encodeGradientBevelFilter( w, (GradientBevelFilter) f ); break; |
| |
| } |
| |
| } |
| } |
| |
| private void encodeDropShadowFilter( SwfEncoder w, DropShadowFilter f ) |
| { |
| encodeRGBA( f.color, w ); |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.write32( f.angle ); |
| w.write32( f.distance ); |
| w.writeUI16( f.strength ); |
| w.writeUI8( f.flags ); |
| } |
| |
| private void encodeBlurFilter( SwfEncoder w, BlurFilter f ) |
| { |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.writeUI8( f.passes ); |
| } |
| private void encodeColorMatrixFilter( SwfEncoder w, ColorMatrixFilter f ) |
| { |
| for (int i = 0; i < 20; ++i) |
| { |
| w.writeFloat( f.values[i]); |
| } |
| } |
| private void encodeConvolutionFilter( SwfEncoder w, ConvolutionFilter f ) |
| { |
| w.writeUI8( f.matrixX ); |
| w.writeUI8( f.matrixY ); |
| w.writeFloat( f.divisor ); |
| w.writeFloat( f.bias ); |
| for (int i = 0; i < f.matrix.length; ++i) |
| w.writeFloat( f.matrix[i] ); |
| w.writeUI8( f.flags ); |
| } |
| private void encodeGlowFilter( SwfEncoder w, GlowFilter f ) |
| { |
| encodeRGBA( f.color, w ); |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.writeUI16( f.strength ); |
| w.writeUI8( f.flags ); |
| } |
| private void encodeBevelFilter( SwfEncoder w, BevelFilter f ) |
| { |
| encodeRGBA(f.highlightColor, w); |
| encodeRGBA(f.shadowColor, w); |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.write32( f.angle ); |
| w.write32( f.distance ); |
| w.writeUI16( f.strength ); |
| w.writeUI8( f.flags ); |
| } |
| |
| private void encodeGradientGlowFilter( SwfEncoder w, GradientGlowFilter f ) |
| { |
| w.writeUI8( f.numcolors ); |
| for (int i = 0; i < f.numcolors; ++i) |
| encodeRGBA( f.gradientColors[i], w ); |
| for (int i = 0; i < f.numcolors; ++i) |
| w.writeUI8( f.gradientRatio[i] ); |
| //w.write32( f.color ); |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.write32( f.angle ); |
| w.write32( f.distance ); |
| w.writeUI16( f.strength ); |
| w.writeUI8( f.flags ); |
| |
| } |
| private void encodeGradientBevelFilter( SwfEncoder w, GradientBevelFilter f ) |
| { |
| w.writeUI8( f.numcolors ); |
| for (int i = 0; i < f.numcolors; ++i) |
| encodeRGBA( f.gradientColors[i], w ); |
| for (int i = 0; i < f.numcolors; ++i) |
| w.writeUI8( f.gradientRatio[i] ); |
| |
| // w.write32( f.shadowColor ); |
| // w.write32( f.highlightColor ); |
| w.write32( f.blurX ); |
| w.write32( f.blurY ); |
| w.write32( f.angle ); |
| w.write32( f.distance ); |
| w.writeUI16( f.strength ); |
| w.writeUI8( f.flags ); |
| |
| } |
| |
| private void encodeTextRecord(TextRecord record, SwfEncoder w, int type, int glyphBits, int advanceBits) |
| { |
| w.writeUI8(record.flags); |
| |
| if (record.hasFont()) |
| { |
| w.writeUI16(dict.getId(record.font)); |
| } |
| |
| if (record.hasColor()) |
| { |
| if (type == stagDefineText2) |
| encodeRGBA(record.color, w); |
| else |
| encodeRGB(record.color, w); |
| } |
| |
| if (record.hasX()) |
| { |
| w.writeSI16(record.xOffset); |
| } |
| |
| if (record.hasY()) |
| { |
| w.writeSI16(record.yOffset); |
| } |
| |
| if (record.hasHeight()) |
| { |
| w.writeUI16(record.height); |
| } |
| |
| w.writeUI8(record.entries.length); |
| |
| for (int i = 0; i < record.entries.length; i++) |
| { |
| w.writeUBits(record.entries[i].getIndex(), glyphBits); |
| w.writeSBits(record.entries[i].advance, advanceBits); |
| } |
| w.flushBits(); |
| } |
| |
| public void defineText2(DefineText tag) |
| { |
| defineText(tag); |
| } |
| |
| public void defineVideoStream(DefineVideoStream tag) |
| { |
| int id = dict.add(tag); |
| tagw.writeUI16(id); |
| tagw.writeUI16(tag.numFrames); |
| tagw.writeUI16(tag.width); |
| tagw.writeUI16(tag.height); |
| |
| tagw.writeUBits(0, 4); // reserved |
| tagw.writeUBits(tag.deblocking, 3); |
| tagw.writeBit(tag.smoothing); |
| |
| tagw.writeUI8(tag.codecID); |
| encodeTag(tag); |
| } |
| |
| public void doAction(DoAction tag) |
| { |
| int adjust = 0; |
| if (isDebug()) |
| { |
| adjust = writer.getPos()+6; |
| debug.adjust += adjust; |
| } |
| |
| new ActionEncoder(tagw,debug).encode(tag.actionList); |
| tagw.writeUI8(0); |
| encodeTag(tag); |
| |
| if (isDebug()) |
| { |
| debug.adjust -= adjust; |
| } |
| } |
| |
| public void doInitAction(DoInitAction tag) |
| { |
| int adjust = 0; |
| if (isDebug()) |
| { |
| adjust = writer.getPos()+6; |
| debug.adjust += adjust; |
| } |
| |
| int idref = dict.getId(tag.sprite); |
| tagw.writeUI16(idref); |
| new ActionEncoder(tagw,debug).encode(tag.actionList); |
| tagw.writeUI8(0); |
| encodeTag(tag); |
| |
| if (isDebug()) |
| { |
| debug.adjust -= adjust; |
| } |
| } |
| |
| public void enableDebugger(EnableDebugger tag) |
| { |
| tagw.writeString(tag.password); |
| encodeTag(tag); |
| } |
| |
| public void enableDebugger2(EnableDebugger tag) |
| { |
| // This corresponds to the constant used in the player, |
| // core/splay.cpp, in ScriptThread::EnableDebugger(). |
| tagw.writeUI16(0x1975); |
| tagw.writeString(tag.password); |
| encodeTag(tag); |
| } |
| |
| public void exportAssets(ExportAssets tag) |
| { |
| tagw.writeUI16(tag.exports.size()); |
| Iterator it = tag.exports.iterator(); |
| while (it.hasNext()) |
| { |
| DefineTag ref = (DefineTag) it.next(); |
| int idref = dict.getId(ref); |
| tagw.writeUI16(idref); |
| assert (ref.name != null); // exported symbols must have names |
| tagw.writeString(ref.name); |
| dict.addName(ref, ref.name); |
| } |
| encodeTag(tag); |
| } |
| |
| public void symbolClass(SymbolClass tag) |
| { |
| tagw.writeUI16(tag.class2tag.size() + (tag.topLevelClass != null ? 1 : 0)); |
| Iterator it = tag.class2tag.entrySet().iterator(); |
| while (it.hasNext()) |
| { |
| Map.Entry e = (Map.Entry) it.next(); |
| String name = (String) e.getKey(); |
| DefineTag ref = (DefineTag) e.getValue(); |
| |
| int idref = dict.getId(ref); |
| tagw.writeUI16(idref); |
| tagw.writeString(name); |
| } |
| if (tag.topLevelClass != null) |
| { |
| tagw.writeUI16(0); |
| tagw.writeString(tag.topLevelClass); |
| } |
| encodeTag(tag); |
| } |
| |
| public void frameLabel(FrameLabel tag) |
| { |
| tagw.writeString(tag.label); |
| if (tag.anchor && getSwfVersion() >= 6) |
| { |
| tagw.writeUI8(1); |
| } |
| encodeTag(tag); |
| } |
| |
| public void importAssets(ImportAssets tag) |
| { |
| tagw.writeString(tag.url); |
| if (tag.code == stagImportAssets2) |
| { |
| tagw.writeUI8(tag.downloadNow ? 1 : 0); |
| tagw.writeUI8(tag.SHA1 != null ? 1 : 0); |
| if (tag.SHA1 != null) |
| { |
| tagw.write(tag.SHA1); |
| } |
| } |
| tagw.writeUI16(tag.importRecords.size()); |
| Iterator<ImportRecord> it = tag.importRecords.iterator(); |
| while (it.hasNext()) |
| { |
| ImportRecord record = (ImportRecord) it.next(); |
| int id = dict.add(record); |
| tagw.writeUI16(id); |
| tagw.writeString(record.name); |
| } |
| encodeTag(tag); |
| } |
| |
| public void importAssets2(ImportAssets tag) |
| { |
| importAssets(tag); |
| } |
| |
| public void jpegTables(GenericTag tag) |
| { |
| encodeTagHeader(tag.code, tag.data.length, false); |
| writer.write(tag.data); |
| } |
| |
| public void placeObject(PlaceObject tag) |
| { |
| int idref = dict.getId(tag.ref); |
| tagw.writeUI16(idref); |
| tagw.writeUI16(tag.depth); |
| encodeMatrix(tag.matrix, tagw); |
| if (tag.colorTransform != null) |
| { |
| encodeCxform(tag.colorTransform, tagw); |
| } |
| encodeTag(tag); |
| } |
| |
| public void placeObject2(PlaceObject tag) |
| { |
| placeObject23(tag); |
| } |
| |
| public void placeObject3(PlaceObject tag) |
| { |
| placeObject23(tag); |
| } |
| |
| public void placeObject23(PlaceObject tag) |
| { |
| tagw.writeUI8(tag.flags); |
| if (tag.code == stagPlaceObject3) |
| { |
| tagw.writeUI8(tag.flags2); |
| } |
| tagw.writeUI16(tag.depth); |
| if (tag.hasClassName()) { |
| tagw.writeString(tag.className); |
| } |
| if (tag.hasCharID()) |
| { |
| int idref = dict.getId(tag.ref); |
| tagw.writeUI16(idref); |
| } |
| if (tag.hasMatrix()) |
| { |
| encodeMatrix(tag.matrix, tagw); |
| } |
| if (tag.hasCxform()) |
| { |
| // ed 5/22/03 the SWF 6 file format spec says this should be a CXFORM, but |
| // the spec is wrong. the player expects a CXFORMA. |
| encodeCxforma(((CXFormWithAlpha) tag.colorTransform), tagw); |
| } |
| if (tag.hasRatio()) |
| { |
| tagw.writeUI16(tag.ratio); |
| } |
| if (tag.hasName()) |
| { |
| tagw.writeString(tag.name); |
| } |
| if (tag.hasClipDepth()) |
| { |
| tagw.writeUI16(tag.clipDepth); |
| } |
| if (tag.code == stagPlaceObject3) |
| { |
| if (tag.hasFilterList()) |
| { |
| encodeFilterList( tag.filters, tagw ); |
| } |
| if (tag.hasBlendMode()) |
| { |
| tagw.writeUI8(tag.blendMode); |
| } |
| } |
| if (tag.hasClipAction()) |
| { |
| int adjust=0; |
| if (isDebug()) |
| { |
| adjust = writer.getPos()+6; |
| debug.adjust += adjust; |
| } |
| new ActionEncoder(tagw,debug).encodeClipActions(tag.clipActions); |
| if (isDebug()) |
| { |
| debug.adjust -= adjust; |
| } |
| } |
| encodeTag(tag); |
| } |
| |
| public void protect(GenericTag tag) |
| { |
| if (tag.data != null) |
| { |
| encodeTagHeader(tag.code, tag.data.length, false); |
| writer.write(tag.data); |
| } |
| else |
| { |
| encodeTagHeader(tag.code, 0, false); |
| } |
| } |
| |
| public void removeObject(RemoveObject tag) |
| { |
| encodeTagHeader(tag.code, 4, false); |
| int idref = dict.getId(tag.ref); |
| writer.writeUI16(idref); |
| writer.writeUI16(tag.depth); |
| } |
| |
| public void removeObject2(RemoveObject tag) |
| { |
| encodeTagHeader(tag.code, 2, false); |
| writer.writeUI16(tag.depth); |
| } |
| |
| public void setBackgroundColor(SetBackgroundColor tag) |
| { |
| encodeTagHeader(tag.code, 3, false); |
| encodeRGB(tag.color, writer); |
| } |
| |
| public void showFrame(ShowFrame tag) |
| { |
| encodeTagHeader(tag.code, 0, false); |
| frames++; |
| } |
| |
| public void soundStreamBlock(GenericTag tag) |
| { |
| encodeTagHeader(tag.code, tag.data.length, false); |
| writer.write(tag.data); |
| } |
| |
| public void soundStreamHead(SoundStreamHead tag) |
| { |
| int length = 4; |
| |
| // we need to add two bytes for an extra SI16 (latencySeek) |
| if (tag.compression == SoundStreamHead.sndCompressMP3) |
| { |
| length += 2; |
| } |
| |
| encodeTagHeader(tag.code, length, false); |
| |
| // 1 byte |
| writer.writeUBits(0, 4); // reserved |
| writer.writeUBits(tag.playbackRate, 2); |
| writer.writeUBits(tag.playbackSize, 1); |
| writer.writeUBits(tag.playbackType, 1); |
| |
| // 1 byte |
| writer.writeUBits(tag.compression, 4); |
| writer.writeUBits(tag.streamRate, 2); |
| writer.writeUBits(tag.streamSize, 1); |
| writer.writeUBits(tag.streamType, 1); |
| |
| // 2 bytes |
| writer.writeUI16(tag.streamSampleCount); |
| |
| if (tag.compression == SoundStreamHead.sndCompressMP3) |
| { |
| // 2 bytes |
| writer.writeSI16(tag.latencySeek); |
| } |
| } |
| |
| public void soundStreamHead2(SoundStreamHead tag) |
| { |
| soundStreamHead(tag); |
| } |
| |
| public void startSound(StartSound tag) |
| { |
| int idref = dict.getId(tag.sound); |
| tagw.writeUI16(idref); |
| encodeSoundInfo(tag.soundInfo, tagw); |
| encodeTag(tag); |
| } |
| |
| public void videoFrame(VideoFrame tag) |
| { |
| encodeTagHeader(tag.code, 4+tag.videoData.length, false); |
| int idref = dict.getId(tag.stream); |
| writer.writeUI16(idref); |
| writer.writeUI16(tag.frameNum); |
| writer.write(tag.videoData); |
| } |
| |
| public void defineSceneAndFrameLabelData(DefineSceneAndFrameLabelData tag) |
| { |
| encodeTagHeader(tag.code, tag.data.length, false); |
| writer.write(tag.data); |
| } |
| |
| public void doABC(DoABC tag) |
| { |
| if (tag.code == stagDoABC2) |
| { |
| encodeTagHeader(tag.code, 4 + tag.name.length() + 1 + tag.abc.length, false); |
| writer.write32( tag.flag ); |
| writer.writeString( tag.name ); |
| |
| } |
| else |
| { |
| encodeTagHeader(tag.code, tag.abc.length, false); |
| } |
| |
| writer.write(tag.abc); |
| } |
| |
| public void unknown(GenericTag tag) |
| { |
| encodeTagHeader(tag.code, tag.data.length, false); |
| writer.write(tag.data); |
| } |
| |
| public byte[] toByteArray() throws IOException |
| { |
| //TODO this could be improved, tricky bit is that writeTo is not trivial |
| // and has the side effect of compressing (meaning the writer.size() |
| // may be larger than necessary) |
| ByteArrayOutputStream out = new ByteArrayOutputStream(writer.size()); |
| writeTo(out); |
| return out.toByteArray(); |
| } |
| |
| public void scriptLimits(ScriptLimits tag) |
| { |
| tagw.writeUI16(tag.scriptRecursionLimit); |
| tagw.writeUI16(tag.scriptTimeLimit); |
| encodeTag(tag); |
| } |
| |
| public void setTabIndex(SetTabIndex tag) |
| { |
| tagw.writeUI16(tag.depth); |
| tagw.writeUI16(tag.index); |
| encodeTag(tag); |
| } |
| } |