Package flash.swf.tools

Source Code of flash.swf.tools.SwfxPrinter

/*
*
*  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.tools;

import macromedia.abc.AbcParser;
import macromedia.asc.embedding.CompilerHandler;
import macromedia.asc.embedding.avmplus.ActionBlockEmitter;
import macromedia.asc.parser.ProgramNode;
import macromedia.asc.semantics.ObjectValue;
import macromedia.asc.semantics.TypeValue;
import macromedia.asc.util.Context;
import macromedia.asc.util.ContextStatics;
import macromedia.asc.util.StringPrintWriter;
import flash.swf.ActionDecoder;
import flash.swf.Dictionary;
import flash.swf.Header;
import flash.swf.SwfDecoder;
import flash.swf.Tag;
import flash.swf.TagDecoder;
import flash.swf.TagEncoder;
import flash.swf.TagHandler;
import flash.swf.TagValues;
import flash.swf.tags.*;
import flash.swf.types.ActionList;
import flash.swf.types.ButtonCondAction;
import flash.swf.types.ButtonRecord;
import flash.swf.types.ClipActionRecord;
import flash.swf.types.CurvedEdgeRecord;
import flash.swf.types.EdgeRecord;
import flash.swf.types.FillStyle;
import flash.swf.types.Filter;
import flash.swf.types.GlyphEntry;
import flash.swf.types.GradRecord;
import flash.swf.types.ImportRecord;
import flash.swf.types.LineStyle;
import flash.swf.types.MorphFillStyle;
import flash.swf.types.MorphGradRecord;
import flash.swf.types.MorphLineStyle;
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.FocalGradient;
import flash.swf.types.KerningRecord;
import flash.util.Base64;
import flash.util.FileUtils;
import flash.util.SwfImageUtils;
import flash.util.Trace;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import java.net.URLConnection;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
* This class supports printing out a SWF in a human readable XML
* format.
*
* @author Clement Wong
* @author Edwin Smith
*/
public final class SwfxPrinter extends TagHandler
  {
    /**
     * this value should get set after the header is parsed
     */
    private Integer swfVersion = null;
   
    private boolean abc = false;
    private boolean showActions = true;
    private boolean showOffset = false;
    private boolean showByteCode = false;
    private boolean showDebugSource = false;
    private boolean glyphs = true;
    private boolean external = false;
    private String externalPrefix = null;
    private String externalDirectory = null;
    private boolean decompile;
    private boolean defunc;
    private int indent = 0;
    private boolean tabbedGlyphs = false;
   
    static
    {
      TypeValue.init();
      ObjectValue.init();
    }
   
    public SwfxPrinter(PrintWriter out)
    {
      this.out = out;
    }
   
    private void printActions(ActionList list)
    {
      if (decompile)
      {
        /*
         AsNode node;
         try
         {
         node = new Decompiler(defunc).decompile(list);
         new PrettyPrinter(out, indent).list(node);
         return;
         }
         catch (Exception e)
         {
         indent();
         out.println("// error while decompiling.  falling back to disassembler");
         }
         */
      }
     
      Disassembler disassembler = new Disassembler(out, showOffset, indent);
      if (showDebugSource)
      {
        disassembler.setShowDebugSource(showDebugSource);
        disassembler.setComment("// ");
      }
      list.visitAll(disassembler);
    }
   
    private void setExternal(boolean b, String path)
    {
      external = b;
     
      if (external)
      {
        if (path != null)
        {
          externalPrefix = baseName(path);
          externalDirectory = dirName(path);
        }
       
        if (externalPrefix == null)
          externalPrefix = "";
        else
          externalPrefix += "-";
        if (externalDirectory == null)
          externalDirectory = "";
      }
    }
   
    private void indent()
    {
      for (int i = 0; i < indent; i++)
      {
        out.print("  ");
      }
    }
   
    public void header(Header h)
    {
      swfVersion = h.version;
      out.println("<!-- ?xml version=\"1.0\" encoding=\"UTF-8\"? -->");
      out.println("<swf xmlns='http://macromedia/2003/swfx'" +
            " version='" + h.version + "'" +
            " framerate='" + h.rate + "'" +
            " size='" + h.size + "'" +
            " compressed='" + h.compressed + "'" +
            " >");
      indent++;
      indent();
      out.println("<!-- framecount=" + h.framecount + " length=" + h.length + " -->");
    }
   
    public void productInfo(ProductInfo productInfo)
    {
      open(productInfo);
      out.print(" product='" + productInfo.getProductString() + "'");
      out.print(" edition='" + productInfo.getEditionString() + "'");
      out.print(" version='" + productInfo.getMajorVersion() + "." + productInfo.getMinorVersion() + "'");
      out.print(" build='" + productInfo.getBuild() + "'");
      out.print(" compileDate='" + DateFormat.getInstance().format(new Date(productInfo.getCompileDate())) + "'");
      close();
    }
   
    public void metadata(Metadata tag)
    {
      open(tag);
      end();
      indent();
      out.println(tag.xml);
      close(tag);
    }
   
    public void fileAttributes(FileAttributes tag)
    {
      open(tag);
      out.print(" useDirectBlit='" + tag.useDirectBlit + "'");
      out.print(" useGPU='" + tag.useGPU + "'");
      out.print(" hasMetadata='" + tag.hasMetadata + "'");
      out.print(" actionScript3='" + tag.actionScript3 + "'");
      out.print(" suppressCrossDomainCaching='" + tag.suppressCrossDomainCaching + "'");
      out.print(" swfRelativeUrls='" + tag.swfRelativeUrls + "'");
      out.print(" useNetwork='" + tag.useNetwork + "'");
      close();
    }
   
   
    private final PrintWriter out;
   
    private Dictionary dict;
   
    public void setDecoderDictionary(Dictionary dict)
    {
      this.dict = dict;
    }
   
    public void setOffsetAndSize(int offset, int size)
    {
      // Note: 'size' includes the size of the tag's header
      // so it is either length + 2 or length + 6.
     
      if (showOffset)
      {
        indent();
        out.println("<!--" +
              " offset=" + offset +
              " size=" + size +
              " -->");
      }
    }
   
    private void open(Tag tag)
    {
      indent();
      out.print("<" + TagValues.names[tag.code]);
    }
   
    private void end()
    {
      out.println(">");
      indent++;
    }
   
    private void openCDATA()
    {
      indent();
      out.println("<![CDATA[");
      indent++;
    }
   
    private void closeCDATA()
    {
      indent--;
      indent();
      out.println("]]>");
    }
   
    private void close()
    {
      out.println("/>");
    }
   
    private void close(Tag tag)
    {
      indent--;
      indent();
      out.println("</" + TagValues.names[tag.code] + ">");
    }
   
    public void error(String s)
    {
      indent();
      out.println("<!-- error: " + s + " -->");
    }
   
    public void unknown(GenericTag tag)
    {
      indent();
      out.println("<!-- unknown tag=" + tag.code + " length=" +
            (tag.data != null ? tag.data.length : 0) + " -->");
    }
   
    public void showFrame(ShowFrame tag)
    {
      open(tag);
      close();
    }
   
    public void defineShape(DefineShape tag)
    {
      printDefineShape(tag, false);
    }
   
    private void printDefineShape(DefineShape tag, boolean alpha)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" bounds='" + tag.bounds + "'");
      if (tag.code == Tag.stagDefineShape4)
      {
        out.print(" edgebounds='" + tag.edgeBounds + "'");
        out.print(" usesNonScalingStrokes='" + tag.usesNonScalingStrokes + "'");
        out.print(" usesScalingStrokes='" + tag.usesScalingStrokes + "'");
      }
     
      end();
     
      printShapeWithStyles(tag.shapeWithStyle, alpha);
     
      close(tag);
    }
   
    private String id(DefineTag tag)
    {
      final int id = dict.getId(tag);
      return String.valueOf(id);
    }
   
    static final char[] digits = new char[]{
        '0', '1', '2', '3', '4', '5', '6', '7',
        '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
    };
   
    /**
     * @param rgb as an integer, 0x00RRGGBB
     * @return string formatted as #RRGGBB
     */
    public String printRGB(int rgb)
    {
      StringBuilder b = new StringBuilder();
      b.append('#');
      int red = (rgb >> 16) & 255;
      b.append(digits[(red >> 4) & 15]);
      b.append(digits[red & 15]);
      int green = (rgb >> 8) & 255;
      b.append(digits[(green >> 4) & 15]);
      b.append(digits[green & 15]);
      int blue = rgb & 255;
      b.append(digits[(blue >> 4) & 15]);
      b.append(digits[blue & 15]);
      return b.toString();
    }
   
    /**
     * @param rgb as an integer, 0xAARRGGBB
     * @return string formatted as #RRGGBBAA
     */
    public String printRGBA(int rgb)
    {
      StringBuilder b = new StringBuilder();
      b.append('#');
      int red = (rgb >> 16) & 255;
      b.append(digits[(red >> 4) & 15]);
      b.append(digits[red & 15]);
      int green = (rgb >> 8) & 255;
      b.append(digits[(green >> 4) & 15]);
      b.append(digits[green & 15]);
      int blue = rgb & 255;
      b.append(digits[(blue >> 4) & 15]);
      b.append(digits[blue & 15]);
      int alpha = (rgb >> 24) & 255;
      b.append(digits[(alpha >> 4) & 15]);
      b.append(digits[alpha & 15]);
      return b.toString();
    }
   
    public void placeObject(PlaceObject tag)
    {
      open(tag);
      out.print(" idref='" + idRef(tag.ref) + "'");
      out.print(" depth='" + tag.depth + "'");
      out.print(" matrix='" + tag.matrix + "'");
      if (tag.colorTransform != null)
        out.print(" colorXform='" + tag.colorTransform + "'");
      close();
    }
   
    public void removeObject(RemoveObject tag)
    {
      open(tag);
      out.print(" idref='" + idRef(tag.ref) + "'");
      close();
    }
   
    public void outputBase64(byte[] data)
    {
      Base64.Encoder e = new Base64.Encoder(1024);
     
      indent();
      int remain = data.length;
      while (remain > 0)
      {
        int block = 1024;
        if (block > remain)
          block = remain;
        e.encode(data, data.length - remain, block);
        out.print(e.drain());
        remain -= block;
      }
      out.println(e.flush());
    }
    //private byte[]  jpegTable = null;
   
    public void defineBits(DefineBits tag)
    {
      if (tag.jpegTables == null)
      {
        out.println("<!-- warning: no JPEG table tag found. -->");
      }
     
      open(tag);
      out.print(" id='" + id(tag) + "'");
     
      if (external)
      {
        String path = externalDirectory
        + externalPrefix
        + "image"
        + dict.getId(tag)
        + ".jpg";
       
        out.println(" src='" + path + "' />");
        try
        {
          FileOutputStream image = new FileOutputStream(path, false);
          SwfImageUtils.JPEG jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data);
          jpeg.write(image);
          image.close();
        }
        catch (IOException e)
        {
          out.println("<!-- error: unable to write external asset file " + path + "-->");
        }
      }
      else
      {
        out.print(" encoding='base64'");
        end();
        outputBase64(tag.data);
        close(tag);
      }
    }
   
    public void defineButton(DefineButton tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      end();
      if (showActions)
      {
        openCDATA();
        // todo print button records
        printActions(tag.condActions[0].actionList);
        closeCDATA();
      }
      else
      {
        out.println("<!-- " + tag.condActions[0].actionList.size() + " action(s) elided -->");
      }
      close(tag);
    }
   
    public void jpegTables(GenericTag tag)
    {
      open(tag);
      out.print(" encoding='base64'");
      end();
      outputBase64(tag.data);
      close(tag);
    }
   
    public void setBackgroundColor(SetBackgroundColor tag)
    {
      open(tag);
      out.print(" color='" + printRGB(tag.color) + "'");
      close();
    }
   
    public void defineFont(DefineFont1 tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      end();
     
      if (glyphs)
      {
        for (int i = 0; i < tag.glyphShapeTable.length; i++)
        {
          indent();
          out.println("<glyph>");
         
          Shape shape = tag.glyphShapeTable[i];
          indent++;
          printShapeWithTabs(shape);
          indent--;
         
          indent();
          out.println("</glyph>");
        }
      }
      close(tag);
    }
   
    public void defineText(DefineText tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" bounds='" + tag.bounds + "'");
      out.print(" matrix='" + tag.matrix + "'");
     
      end();
     
      Iterator it = tag.records.iterator();
     
      while (it.hasNext())
      {
        TextRecord tr = (TextRecord)it.next();
        printTextRecord(tr, tag.code);
      }
     
      close(tag);
    }
   
    public void doAction(DoAction tag)
    {
      open(tag);
      end();
     
      if (showActions)
      {
        openCDATA();
        printActions(tag.actionList);
        closeCDATA();
      }
      else
      {
        out.println("<!-- " + tag.actionList.size() + " action(s) elided -->");
      }
      close(tag);
    }
   
    public void defineFontInfo(DefineFontInfo tag)
    {
      open(tag);
      out.print(" idref='" + idRef(tag.font) + "'");
      out.print(" ansi='" + tag.ansi + "'");
      out.print(" italic='" + tag.italic + "'");
      out.print(" bold='" + tag.bold + "'");
      out.print(" wideCodes='" + tag.wideCodes + "'");
      out.print(" langCold='" + tag.langCode + "'");
      out.print(" name='" + tag.name + "'");
      out.print(" shiftJIS='" + tag.shiftJIS + "'");
      end();
      indent();
      for (int i = 0; i < tag.codeTable.length; i++)
      {
        out.print((int)tag.codeTable[i]);
        if ((i + 1) % 16 == 0)
        {
          out.println();
          indent();
        }
        else
        {
          out.print(' ');
        }
      }
      if (tag.codeTable.length % 16 != 0)
      {
        out.println();
        indent();
      }
      close(tag);
    }
   
    public void defineSound(DefineSound tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" format='" + tag.format + "'");
      out.print(" rate='" + tag.rate + "'");
      out.print(" size='" + tag.size + "'");
      out.print(" type='" + tag.type + "'");
      out.print(" sampleCount='" + tag.sampleCount + "'");
      out.print(" soundDataSize='" + tag.data.length + "'");
      end();
      openCDATA();
      outputBase64(tag.data);
      closeCDATA();
      close(tag);
    }
   
    public void startSound(StartSound tag)
    {
      open(tag);
      out.print(" soundid='" + idRef(tag.sound) + "'");
      printSoundInfo(tag.soundInfo);
      close(tag);
    }
   
    private void printSoundInfo(SoundInfo info)
    {
      out.print(" syncStop='" + info.syncStop + "'");
      out.print(" syncNoMultiple='" + info.syncNoMultiple + "'");
      if (info.inPoint != SoundInfo.UNINITIALIZED)
      {
        out.print(" inPoint='" + info.inPoint + "'");
      }
      if (info.outPoint != SoundInfo.UNINITIALIZED)
      {
        out.print(" outPoint='" + info.outPoint + "'");
      }
      if (info.loopCount != SoundInfo.UNINITIALIZED)
      {
        out.print(" loopCount='" + info.loopCount + "'");
      }
      end();
      if (info.records != null && info.records.length > 0)
      {
        openCDATA();
        for (int i = 0; i < info.records.length; i++)
        {
          out.println(info.records[i]);
        }
        closeCDATA();
      }
    }
   
    public void defineButtonSound(DefineButtonSound tag)
    {
      open(tag);
      out.print(" buttonId='" + idRef(tag.button) + "'");
      close();
    }
   
    public void soundStreamHead(SoundStreamHead tag)
    {
      open(tag);
      close();
    }
   
    public void soundStreamBlock(GenericTag tag)
    {
      open(tag);
      close();
    }
   
    public void defineBinaryData(DefineBinaryData tag)
    {
      open(tag);
      out.println(" id='" + id(tag) + "' length='" + tag.data.length + "' />" );
    }
    public void defineBitsLossless(DefineBitsLossless tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "' width='" + tag.width + "' height='" + tag.height + "'");
     
      if (external)
      {
        String path = externalDirectory
        + externalPrefix
        + "image"
        + dict.getId(tag)
        + ".bitmap";
       
        out.println(" src='" + path + "' />");
        try
        {
          FileOutputStream image = new FileOutputStream(path, false);
          image.write(tag.data);
          image.close();
        }
        catch (IOException e)
        {
          out.println("<!-- error: unable to write external asset file " + path + "-->");
        }
      }
      else
      {
        out.print(" encoding='base64'");
        end();
        outputBase64(tag.data);
        close(tag);
      }
    }
   
    public void defineBitsJPEG2(DefineBits tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
     
      if (external)
      {
        String path = externalDirectory
        + externalPrefix
        + "image"
        + dict.getId(tag)
        + ".jpg";
       
        out.println(" src='" + path + "' />");
        try
        {
          FileOutputStream image = new FileOutputStream(path, false);
          image.write(tag.data);
          image.close();
        }
        catch (IOException e)
        {
          out.println("<!-- error: unable to write external asset file " + path + "-->");
        }
      }
      else
      {
        out.print(" encoding='base64'");
        end();
        outputBase64(tag.data);
        close(tag);
      }
    }
   
    public void defineShape2(DefineShape tag)
    {
      printDefineShape(tag, false);
    }
   
    public void defineButtonCxform(DefineButtonCxform tag)
    {
      open(tag);
      out.print(" buttonId='" + idRef(tag.button) + "'");
      close();
    }
   
    public void protect(GenericTag tag)
    {
      open(tag);
      if (tag.data != null)
        out.print(" password='" + hexify(tag.data) + "'");
      close();
    }
   
    public void placeObject2(PlaceObject tag)
    {
      placeObject23(tag);
    }
   
    public void placeObject3(PlaceObject tag)
    {
      placeObject23(tag);
    }
   
    public void placeObject23(PlaceObject tag)
    {
      if (tag.hasCharID())
      {
        if (tag.ref.name != null)
        {
          indent();
          out.println("<!-- instance of " + tag.ref.name + " -->");
        }
      }
     
      open(tag);
      if (tag.hasClassName())
        out.print(" className='" + tag.className + "'");
      if (tag.hasImage())
        out.print(" hasImage='true' ");
      if (tag.hasCharID())
        out.print(" idref='" + idRef(tag.ref) + "'");
      if (tag.hasName())
        out.print(" name='" + tag.name + "'");
      out.print(" depth='" + tag.depth + "'");
      if (tag.hasClipDepth())
        out.print(" clipDepth='" + tag.clipDepth + "'");
      if (tag.hasCacheAsBitmap())
        out.print(" cacheAsBitmap='true'");
      if (tag.hasRatio())
        out.print(" ratio='" + tag.ratio + "'");
      if (tag.hasCxform())
        out.print(" cxform='" + tag.colorTransform + "'");
      if (tag.hasMatrix())
        out.print(" matrix='" + tag.matrix + "'");
      if (tag.hasBlendMode())
        out.print(" blendmode='" + tag.blendMode + "'");
      if (tag.hasFilterList())
      {
        // todo - pretty print this once we actually care
        out.print(" filters='");
        for (Iterator it = tag.filters.iterator(); it.hasNext();)
        {
          out.print( (((Filter) it.next()).getID() ) + " ");
        }
        out.print("'");
      }
     
      if (tag.hasClipAction())
      {
        end();
        Iterator it = tag.clipActions.clipActionRecords.iterator();
       
        openCDATA();
        while (it.hasNext())
        {
          ClipActionRecord record = (ClipActionRecord)it.next();
          indent();
          out.println("onClipEvent(" + printClipEventFlags(record.eventFlags) +
                (record.hasKeyPress() ? "<" + record.keyCode + ">" : "") +
                ") {");
          indent++;
          if (showActions)
          {
            printActions(record.actionList);
          }
          else
          {
            indent();
            out.println("// " + record.actionList.size() + " action(s) elided");
          }
          indent--;
          indent();
          out.println("}");
        }
        closeCDATA();
        close(tag);
      }
      else
      {
        close();
      }
    }
   
    public void removeObject2(RemoveObject tag)
    {
      open(tag);
      out.print(" depth='" + tag.depth + "'");
      close();
    }
   
    public void defineShape3(DefineShape tag)
    {
      printDefineShape(tag, true);
    }
   
    public void defineShape4(DefineShape tag)
    {
      printDefineShape(tag, true);
    }
   
    private void printShapeWithStyles(ShapeWithStyle shapes, boolean alpha)
    {
      printFillStyles(shapes.fillstyles, alpha);
      printLineStyles(shapes.linestyles, alpha);
      printShape(shapes, alpha);
    }
   
    private void printMorphLineStyles(MorphLineStyle[] lineStyles)
    {
      for (int i = 0; i < lineStyles.length; i++)
      {
        MorphLineStyle lineStyle = lineStyles[i];
        indent();
        out.print("<linestyle ");
        out.print("startColor='" + printRGBA(lineStyle.startColor) + "' ");
        out.print("endColor='" + printRGBA(lineStyle.startColor) + "' ");
        out.print("startWidth='" + lineStyle.startWidth + "' ");
        out.print("endWidth='" + lineStyle.endWidth + "' ");
        out.println("/>");
      }
    }
   
    private void printLineStyles(ArrayList linestyles, boolean alpha)
    {
      Iterator it = linestyles.iterator();
      while (it.hasNext())
      {
        LineStyle lineStyle = (LineStyle)it.next();
        indent();
        out.print("<linestyle ");
        String color = alpha ? printRGBA(lineStyle.color) : printRGB(lineStyle.color);
        out.print("color='" + color + "' ");
        out.print("width='" + lineStyle.width + "' ");
        if (lineStyle.flags != 0)
          out.print("flags='" + lineStyle.flags + "' ");
        if (lineStyle.hasMiterJoint())
        {
          out.print("miterLimit='" + lineStyle.miterLimit + "' ");
        }
        if (lineStyle.hasFillStyle())
        {
          out.println(">");
          indent();
          ArrayList<FillStyle> fillStyles = new ArrayList<FillStyle>(1);
          fillStyles.add(lineStyle.fillStyle);
          printFillStyles(fillStyles, alpha);
          indent();
          out.println("</linestyle>");
        }
        else
        {
          out.println("/>");
        }
      }
    }
   
    private void printFillStyles(ArrayList fillstyles, boolean alpha)
    {
      Iterator it = fillstyles.iterator();
      while (it.hasNext())
      {
        FillStyle fillStyle = (FillStyle)it.next();
        indent();
        out.print("<fillstyle");
        out.print(" type='" + fillStyle.getType() + "'");
        if (fillStyle.getType() == FillStyle.FILL_SOLID)
        {
          out.print(" color='" + (alpha ? printRGBA(fillStyle.color) : printRGB(fillStyle.color)) + "'");
        }
        if ((fillStyle.getType() & FillStyle.FILL_LINEAR_GRADIENT) != 0)
        {
          if (fillStyle.getType() == FillStyle.FILL_RADIAL_GRADIENT)
            out.print( " typeName='radial'");
          else if (fillStyle.getType() == FillStyle.FILL_FOCAL_RADIAL_GRADIENT)
            out.print( " typeName='focal' focalPoint='" + ((FocalGradient)fillStyle.gradient).focalPoint + "'");
          // todo print linear or radial or focal
          out.print(" gradient='" + formatGradient(fillStyle.gradient.records, alpha) + "'");
          out.print(" matrix='" + fillStyle.matrix + "'");
        }
        if ((fillStyle.getType() & FillStyle.FILL_BITS) != 0)
        {
          // todo print tiled or clipped
          out.print(" idref='" + idRef(fillStyle.bitmap) + "'");
          out.print(" matrix='" + fillStyle.matrix + "'");
        }
        out.println(" />");
      }
    }
   
    private void printMorphFillStyles(MorphFillStyle[] fillStyles)
    {
      for (int i = 0; i < fillStyles.length; i++)
      {
        MorphFillStyle fillStyle = fillStyles[i];
        indent();
        out.print("<fillstyle");
        out.print(" type='" + fillStyle.type + "'");
        if (fillStyle.type == FillStyle.FILL_SOLID)
        {
          out.print(" startColor='" + printRGBA(fillStyle.startColor) + "'");
          out.print(" endColor='" + printRGBA(fillStyle.endColor) + "'");
        }
        if ((fillStyle.type & FillStyle.FILL_LINEAR_GRADIENT) != 0)
        {
          // todo print linear or radial
          out.print(" gradient='" + formatMorphGradient(fillStyle.gradRecords) + "'");
          out.print(" startMatrix='" + fillStyle.startGradientMatrix + "'");
          out.print(" endMatrix='" + fillStyle.endGradientMatrix + "'");
        }
        if ((fillStyle.type & FillStyle.FILL_BITS) != 0)
        {
          // todo print tiled or clipped
          out.print(" idref='" + idRef(fillStyle.bitmap) + "'");
          out.print(" startMatrix='" + fillStyle.startBitmapMatrix + "'");
          out.print(" endMatrix='" + fillStyle.endBitmapMatrix + "'");
        }
        out.println(" />");
      }
    }
   
    private String formatGradient(GradRecord[] records, boolean alpha)
    {
      StringBuilder b = new StringBuilder();
      for (int i = 0; i < records.length; i++)
      {
        b.append(records[i].ratio);
        b.append(' ');
        b.append(alpha ? printRGBA(records[i].color) : printRGB(records[i].color));
        if (i + 1 < records.length)
          b.append(' ');
      }
      return b.toString();
    }
   
    private String formatMorphGradient(MorphGradRecord[] records)
    {
      StringBuilder b = new StringBuilder();
      for (int i = 0; i < records.length; i++)
      {
        b.append(records[i].startRatio);
        b.append(',');
        b.append(records[i].endRatio);
        b.append(' ');
        b.append(printRGBA(records[i].startColor));
        b.append(',');
        b.append(printRGBA(records[i].endColor));
        if (i + 1 < records.length)
          b.append(' ');
      }
      return b.toString();
    }
   
    private void printShape(Shape shapes, boolean alpha)
    {
        if (shapes == null)
            return;

      Iterator it = shapes.shapeRecords.iterator();
      while (it.hasNext())
      {
        indent();
        ShapeRecord shape = (ShapeRecord)it.next();
        if (shape instanceof StyleChangeRecord)
        {
          StyleChangeRecord styleChange = (StyleChangeRecord)shape;
          out.print("<styleChange ");
          if (styleChange.stateMoveTo)
          {
            out.print("dx='" + styleChange.moveDeltaX + "' dy='" + styleChange.moveDeltaY + "' ");
          }
          if (styleChange.stateFillStyle0)
          {
            out.print("fillStyle0='" + styleChange.fillstyle0 + "' ");
          }
          if (styleChange.stateFillStyle1)
          {
            out.print("fillStyle1='" + styleChange.fillstyle1 + "' ");
          }
          if (styleChange.stateLineStyle)
          {
            out.print("lineStyle='" + styleChange.linestyle + "' ");
          }
          if (styleChange.stateNewStyles)
          {
            out.println(">");
            indent++;
            printFillStyles(styleChange.fillstyles, alpha);
            printLineStyles(styleChange.linestyles, alpha);
            indent--;
            indent();
            out.println("</styleChange>");
          }
          else
          {
            out.println("/>");
          }
        }
        else
        {
          EdgeRecord edge = (EdgeRecord)shape;
          if (edge instanceof StraightEdgeRecord)
          {
            StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge;
            out.println("<line dx='" + straightEdge.deltaX + "' dy='" + straightEdge.deltaY + "' />");
          }
          else
          {
            CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge;
            out.print("<curve ");
            out.print("cdx='" + curvedEdge.controlDeltaX + "' cdy='" + curvedEdge.controlDeltaY + "' ");
            out.print("dx='" + curvedEdge.anchorDeltaX + "' dy='" + curvedEdge.anchorDeltaY + "' ");
            out.println("/>");
          }
        }
      }
    }
   
    private void printShapeWithTabs(Shape shapes)
    {
            if (shapes == null)
              return;

      Iterator it = shapes.shapeRecords.iterator();
      int startX = 0;
      int startY = 0;
     
      int x = 0;
      int y = 0;
     
      while (it.hasNext())
      {
        indent();
        ShapeRecord shape = (ShapeRecord)it.next();
        if (shape instanceof StyleChangeRecord)
        {
          StyleChangeRecord styleChange = (StyleChangeRecord)shape;
          out.print("SSCR" + styleChange.nMoveBits() + "\t");
          if (styleChange.stateMoveTo)
          {
            out.print(styleChange.moveDeltaX + "\t" + styleChange.moveDeltaY);
           
            if (startX == 0 && startY == 0)
            {
              startX = styleChange.moveDeltaX;
              startY = styleChange.moveDeltaY;
            }
           
            x = styleChange.moveDeltaX;
            y = styleChange.moveDeltaY;
           
            out.print("\t\t");
          }
        }
        else
        {
          EdgeRecord edge = (EdgeRecord)shape;
          if (edge instanceof StraightEdgeRecord)
          {
            StraightEdgeRecord straightEdge = (StraightEdgeRecord)edge;
            out.print("SER" + "\t");
            out.print(straightEdge.deltaX + "\t" + straightEdge.deltaY);
            x += straightEdge.deltaX;
            y += straightEdge.deltaY;
            out.print("\t\t");
          }
          else
          {
            CurvedEdgeRecord curvedEdge = (CurvedEdgeRecord)edge;
            out.print("CER" + "\t");
            out.print(curvedEdge.controlDeltaX + "\t" + curvedEdge.controlDeltaY + "\t");
            out.print(curvedEdge.anchorDeltaX + "\t" + curvedEdge.anchorDeltaY);
            x += (curvedEdge.controlDeltaX + curvedEdge.anchorDeltaX);
            y += (curvedEdge.controlDeltaY + curvedEdge.anchorDeltaY);
          }
        }
       
        out.println("\t\t" + x + "\t" + y);
      }
    }
   
    private String printClipEventFlags(int flags)
    {
      StringBuilder b = new StringBuilder();
     
      if ((flags & ClipActionRecord.unused31) != 0) b.append("res31,");
      if ((flags & ClipActionRecord.unused30) != 0) b.append("res30,");
      if ((flags & ClipActionRecord.unused29) != 0) b.append("res29,");
      if ((flags & ClipActionRecord.unused28) != 0) b.append("res28,");
      if ((flags & ClipActionRecord.unused27) != 0) b.append("res27,");
      if ((flags & ClipActionRecord.unused26) != 0) b.append("res26,");
      if ((flags & ClipActionRecord.unused25) != 0) b.append("res25,");
      if ((flags & ClipActionRecord.unused24) != 0) b.append("res24,");
     
      if ((flags & ClipActionRecord.unused23) != 0) b.append("res23,");
      if ((flags & ClipActionRecord.unused22) != 0) b.append("res22,");
      if ((flags & ClipActionRecord.unused21) != 0) b.append("res21,");
      if ((flags & ClipActionRecord.unused20) != 0) b.append("res20,");
      if ((flags & ClipActionRecord.unused19) != 0) b.append("res19,");
      if ((flags & ClipActionRecord.construct) != 0) b.append("construct,");
      if ((flags & ClipActionRecord.keyPress) != 0) b.append("keyPress,");
      if ((flags & ClipActionRecord.dragOut) != 0) b.append("dragOut,");
     
      if ((flags & ClipActionRecord.dragOver) != 0) b.append("dragOver,");
      if ((flags & ClipActionRecord.rollOut) != 0) b.append("rollOut,");
      if ((flags & ClipActionRecord.rollOver) != 0) b.append("rollOver,");
      if ((flags & ClipActionRecord.releaseOutside) != 0) b.append("releaseOutside,");
      if ((flags & ClipActionRecord.release) != 0) b.append("release,");
      if ((flags & ClipActionRecord.press) != 0) b.append("press,");
      if ((flags & ClipActionRecord.initialize) != 0) b.append("initialize,");
      if ((flags & ClipActionRecord.data) != 0) b.append("data,");
     
      if ((flags & ClipActionRecord.keyUp) != 0) b.append("keyUp,");
      if ((flags & ClipActionRecord.keyDown) != 0) b.append("keyDown,");
      if ((flags & ClipActionRecord.mouseUp) != 0) b.append("mouseUp,");
      if ((flags & ClipActionRecord.mouseDown) != 0) b.append("mouseDown,");
      if ((flags & ClipActionRecord.mouseMove) != 0) b.append("moseMove,");
      if ((flags & ClipActionRecord.unload) != 0) b.append("unload,");
      if ((flags & ClipActionRecord.enterFrame) != 0) b.append("enterFrame,");
      if ((flags & ClipActionRecord.load) != 0) b.append("load,");
      if (b.length() > 1)
      {
        b.setLength(b.length() - 1);
      }
      return b.toString();
    }
   
    public void defineText2(DefineText tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      end();
     
      Iterator it = tag.records.iterator();
     
      while (it.hasNext())
      {
        TextRecord tr = (TextRecord)it.next();
        printTextRecord(tr, tag.code);
      }
     
      close(tag);
    }
   
    public void printTextRecord(TextRecord tr, int tagCode)
    {
      indent();
      out.print("<textRecord ");
      if (tr.hasFont())
        out.print(" font='" + tr.font.getFontName() + "'");
     
      if (tr.hasHeight())
        out.print(" height='" + tr.height + "'");
     
      if (tr.hasX())
        out.print(" xOffset='" + tr.xOffset + "'");
     
      if (tr.hasY())
        out.print(" yOffset='" + tr.yOffset + "'");
     
      if (tr.hasColor())
        out.print(" color='" +
              (tagCode == TagValues.stagDefineEditText ? printRGB(tr.color) : printRGBA(tr.color)) +
              "'");
      out.println(">");
     
      indent++;
      printGlyphEntries(tr);
      indent--;
      indent();
      out.println("</textRecord>");
     
    }
   
    private void printGlyphEntries(TextRecord tr)
    {
      indent();
      for (int i = 0; i < tr.entries.length; i++)
      {
        GlyphEntry ge = tr.entries[i];
        out.print(ge.getIndex());
        if (ge.advance >= 0)
          out.print('+');
        out.print(ge.advance);
        out.print(' ');
        if ((i + 1) % 10 == 0)
        {
          out.println();
          indent();
        }
      }
      if (tr.entries.length % 10 != 0)
        out.println();
    }
   
   
    public void defineButton2(DefineButton tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" trackAsMenu='" + tag.trackAsMenu + "'");
      end();
     
      for (int i = 0; i < tag.buttonRecords.length; i++)
      {
        ButtonRecord record = tag.buttonRecords[i];
        indent();
        out.println("<buttonRecord " +
              "idref='" + idRef(record.characterRef) + "' " +
              "depth='" + record.placeDepth + "' " +
              "matrix='" + record.placeMatrix + "' " +
              "states='" + record.getFlags() + "'/>");
        // todo print optional cxforma
      }
     
      // print conditional actions
      if (tag.condActions.length > 0 && showActions)
      {
        indent();
        out.println("<buttonCondAction>");
        openCDATA();
        for (int i = 0; i < tag.condActions.length; i++)
        {
          ButtonCondAction cond = tag.condActions[i];
          indent();
          out.println("on(" + cond + ") {");
          indent++;
          printActions(cond.actionList);
          indent--;
          indent();
          out.println("}");
        }
        closeCDATA();
        indent();
        out.println("</buttonCondAction>");
      }
     
      close(tag);
    }

        public void defineBitsJPEG3(DefineBitsJPEG3 tag)
        {
            open(tag);
            out.print(" id='" + id(tag) + "'");

            if (external)
            {
                String path = externalDirectory
                    + externalPrefix
                    + "image"
                    + dict.getId(tag)
                    + ".jpg";
   
                out.println(" src='" + path + "' />");

                try
                {
                    FileOutputStream image = new FileOutputStream(path, false);
                    SwfImageUtils.JPEG jpeg = null;

                    if (tag.jpegTables != null)
                    {
                        jpeg = new SwfImageUtils.JPEG(tag.jpegTables.data, tag.data);
                    }
                    else
                    {
                        jpeg = new SwfImageUtils.JPEG(tag.data, true);
                    }

                    jpeg.write(image);
                    image.close();
                }
                catch (IOException e)
                {
                    out.println("<!-- error: unable to write external asset file " + path + "-->");
                }
            }
            else
            {
                out.print(" encoding='base64'");
                end();
                outputBase64(tag.data);
                close(tag);
            }
        }

    public void defineBitsLossless2(DefineBitsLossless tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
     
      if (external)
      {
        String path = externalDirectory
        + externalPrefix
        + "image"
        + dict.getId(tag)
        + ".bitmap";
       
        out.println(" src='" + path + "' />");
        try
        {
          FileOutputStream image = new FileOutputStream(path, false);
          image.write(tag.data);
          image.close();
        }
        catch (IOException e)
        {
          out.println("<!-- error: unable to write external asset file " + path + "-->");
        }
      }
      else
      {
        out.print(" encoding='base64'");
        end();
        outputBase64(tag.data);
        close(tag);
      }
    }
   
    String escape(String s)
    {
      if (s == null)
        return null;
     
      StringBuilder b = new StringBuilder(s.length());
      for (int i = 0; i < s.length(); i++)
      {
        char c = s.charAt(i);
        switch (c)
        {
          case '<':
            b.append("&lt;");
            break;
          case '>':
            b.append("&gt;");
            break;
          case '&':
            b.append("&amp;");
            break;
        }
      }
     
      return b.toString();
    }
   
    public void defineEditText(DefineEditText tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
     
      if (tag.hasText)
        out.print(" text='" + escape(tag.initialText) + "'");
     
      if (tag.hasFont)
      {
        out.print(" fontId='" + id(tag.font) + "'");
        out.print(" fontName='" + tag.font.getFontName() + "'");
        out.print(" fontHeight='" + tag.height + "'");
      }
      else if (tag.hasFontClass)
      {
                out.print(" fontClass='" + tag.fontClass + "'");
                out.print(" fontHeight='" + tag.height + "'");
      }

      out.print(" bounds='" + tag.bounds + "'");
     
      if (tag.hasTextColor)
        out.print(" color='" + printRGBA(tag.color) + "'");
     
      out.print(" html='" + tag.html + "'");
      out.print(" autoSize='" + tag.autoSize + "'");
      out.print(" border='" + tag.border + "'");
     
      if (tag.hasMaxLength)
        out.print(" maxLength='" + tag.maxLength + "'");
     
      out.print(" multiline='" + tag.multiline + "'");
      out.print(" noSelect='" + tag.noSelect + "'");
      out.print(" password='" + tag.password + "'");
      out.print(" readOnly='" + tag.readOnly + "'");
      out.print(" useOutlines='" + tag.useOutlines + "'");
      out.print(" varName='" + tag.varName + "'");
      out.print(" wordWrap='" + tag.wordWrap + "'");
     
      if (tag.hasLayout)
      {
        out.print(" align='" + tag.align + "'");
        out.print(" indent='" + tag.ident + "'");
        out.print(" leading='" + tag.leading + "'");
        out.print(" leftMargin='" + tag.leftMargin + "'");
        out.print(" rightMargin='" + tag.rightMargin + "'");
      }
      close();
    }
   
   
    public void defineSprite(DefineSprite tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      end();
      indent();
      out.println("<!-- sprite framecount=" + tag.framecount + " -->");
     
      tag.tagList.visitTags(this);
     
      close(tag);
    }
   
    public void finish()
    {
      --indent;
      indent();
      out.println("</swf>");
    }
   
    public void frameLabel(FrameLabel tag)
    {
      open(tag);
      out.print(" label='" + tag.label + "'");
      if (tag.anchor)
        out.print(" anchor='" + "true" + "'");
      close();
    }
   
    public void soundStreamHead2(SoundStreamHead tag)
    {
      open(tag);
      out.print(" playbackRate='" + tag.playbackRate + "'");
      out.print(" playbackSize='" + tag.playbackSize + "'");
      out.print(" playbackType='" + tag.playbackType + "'");
      out.print(" compression='" + tag.compression + "'");
      out.print(" streamRate='" + tag.streamRate + "'");
      out.print(" streamSize='" + tag.streamSize + "'");
      out.print(" streamType='" + tag.streamType + "'");
      out.print(" streamSampleCount='" + tag.streamSampleCount + "'");
     
      if (tag.compression == 2)
      {
        out.print(" latencySeek='" + tag.latencySeek + "'");
      }
      close();
    }
   
    public void defineScalingGrid(DefineScalingGrid tag)
    {
      open(tag);
      out.print(" idref='" + id(tag.scalingTarget) + "'");
      out.print( " grid='" + tag.rect + "'" );
      close();
    }
   
    public void defineMorphShape(DefineMorphShape tag)
    {
      defineMorphShape2(tag);
    }
   
    public void defineMorphShape2(DefineMorphShape tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" startBounds='" + tag.startBounds + "'");
      out.print(" endBounds='" + tag.endBounds + "'");
      if (tag.code == TagValues.stagDefineMorphShape2)
      {
        out.print(" startEdgeBounds='" + tag.startEdgeBounds + "'");
        out.print(" endEdgeBounds='" + tag.endEdgeBounds + "'");
        out.print(" usesNonScalingStrokes='" + tag.usesNonScalingStrokes + "'");
        out.print(" usesScalingStrokes='" + tag.usesScalingStrokes + "'");
      }
      end();
      printMorphLineStyles(tag.lineStyles);
      printMorphFillStyles(tag.fillStyles);
     
      indent();
      out.println("<start>");
      indent++;
      printShape(tag.startEdges, true);
      indent--;
      indent();
      out.println("</start>");
     
      indent();
      out.println("<end>");
      indent++;
      printShape(tag.endEdges, true);
      indent--;
      indent();
      out.println("</end>");
     
      close(tag);
    }
   
    public void defineFont2(DefineFont2 tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" font='" + tag.fontName + "'");
      out.print(" numGlyphs='" + tag.glyphShapeTable.length + "'");
      out.print(" italic='" + tag.italic + "'");
      out.print(" bold='" + tag.bold + "'");
      out.print(" ansi='" + tag.ansi + "'");
      out.print(" wideOffsets='" + tag.wideOffsets + "'");
      out.print(" wideCodes='" + tag.wideCodes + "'");
      out.print(" shiftJIS='" + tag.shiftJIS + "'");
      out.print(" langCode='" + tag.langCode + "'");
      out.print(" hasLayout='" + tag.hasLayout + "'");
      out.print(" ascent='" + tag.ascent + "'");
      out.print(" descent='" + tag.descent + "'");
      out.print(" leading='" + tag.leading + "'");
      out.print(" kerningCount='" + tag.kerningCount + "'");
     
      out.print(" codepointCount='" + tag.codeTable.length + "'");
     
      if (tag.hasLayout)
      {
        out.print(" advanceCount='" + tag.advanceTable.length + "'");
        out.print(" boundsCount='" + tag.boundsTable.length + "'");
      }
      end();
     
      if (glyphs)
      {
        for (int i=0; i < tag.kerningCount; i++)
        {
          KerningRecord rec = tag.kerningTable[i];
          indent();
          out.println("<kerningRecord adjustment='" + rec.adjustment + "' code1='" + rec.code1 + "' code2='" + rec.code2 + "' />");
        }
       
        for (int i = 0; i < tag.glyphShapeTable.length; i++)
        {
          indent();
          out.print("<glyph");
          out.print(" codepoint='" + ((int)tag.codeTable[i]) + (isPrintable(tag.codeTable[i]) ? ("(" + tag.codeTable[i] + ")") : "(?)") + "'");
          if (tag.hasLayout)
          {
            out.print(" advance='" + tag.advanceTable[i] + "'");
            out.print(" bounds='" + tag.boundsTable[i] + "'");
          }
          out.println(">");
         
          Shape shape = tag.glyphShapeTable[i];
          indent++;
          if (tabbedGlyphs)
            printShapeWithTabs(shape);
          else
            printShape(shape, true);
          indent--;
          indent();
          out.println("</glyph>");
        }
      }
     
      close(tag);
    }
   
    public void defineFont3(DefineFont3 tag)
    {
      defineFont2(tag);
    }
   
    public void defineFont4(DefineFont4 tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      out.print(" font='" + tag.fontName + "'");
      out.print(" hasFontData='" + tag.hasFontData + "'");
      out.print(" smallText='" + tag.smallText + "'");
      out.print(" italic='" + tag.italic + "'");
      out.print(" bold='" + tag.bold + "'");
      out.print(" langCode='" + tag.langCode + "'");
      end();
     
      if (glyphs && tag.hasFontData)
      {
        outputBase64(tag.data);
      }
     
      close(tag);
    }
   
    public void defineFontAlignZones(DefineFontAlignZones tag)
    {
      open(tag);
      if (tag.name != null)
        out.print(" id='" + id(tag) + "'");
      out.print(" fontID='" + id(tag.font) + "'");
      out.print(" CSMTableHint='" + tag.csmTableHint + "'");
      out.println(">");
      indent++;
      indent();
      out.println("<ZoneTable length='" + tag.zoneTable.length + "'>");
      indent++;
      if (glyphs)
      {
        for (int i = 0; i < tag.zoneTable.length; i++)
        {
          ZoneRecord record = tag.zoneTable[i];
          indent();
          out.print("<ZoneRecord num='" + record.numZoneData + "' mask='" + record.zoneMask + "'>");
          for (int j = 0; j < record.zoneData.length; j++)
          {
            out.print(record.zoneData[j] + " ");
          }
          out.println("</ZoneRecord>");
        }
      }
      indent--;
      indent();
      out.println("</ZoneTable>");
      close(tag);
    }
   
    public void csmTextSettings(CSMTextSettings tag)
    {
      open(tag);
      if (tag.name != null)
        out.print(" id='" + id(tag) + "'");
     
      String textID = tag.textReference == null ? "0" : id(tag.textReference);
      out.print(" textID='" + textID + "'");
      out.print(" styleFlagsUseSaffron='" + tag.styleFlagsUseSaffron + "'");
      out.print(" gridFitType='" + tag.gridFitType + "'");
      out.print(" thickness='" + tag.thickness + "'");
      out.print(" sharpness='" + tag.sharpness + "'");
      close();
    }
   
    public void defineFontName(DefineFontName tag)
    {
      open(tag);
      if (tag.name != null)
        out.print(" id='" + id(tag) + "'");
      out.print(" fontID='" + id(tag.font) + "'");
      if (tag.fontName != null)
      {
        out.print(" name='" + tag.fontName + "'");
      }
      if (tag.copyright != null)
      {
        out.print(" copyright='" + tag.copyright + "'");
      }
     
      close();
    }
   
    private boolean isPrintable(char c)
    {
      int i = c & 0xFFFF;
      if (i < ' ' || i == '<' || i == '&' || i == '\'')
        return false;
      else
        return true;
    }
   
   
    public void exportAssets(ExportAssets tag)
    {
      open(tag);
      end();
     
      Iterator it = tag.exports.iterator();
      while (it.hasNext())
      {
        DefineTag ref = (DefineTag)it.next();
        indent();
        out.println("<Export idref='" + dict.getId(ref) + "' name='" + ref.name + "' />");
      }
     
      close(tag);
    }
   
    public void symbolClass(SymbolClass tag)
    {
      open(tag);
      end();
     
      Iterator it = tag.class2tag.entrySet().iterator();
      while (it.hasNext())
      {
        Map.Entry e = (Map.Entry)it.next();
        String className = (String)e.getKey();
        DefineTag ref = (DefineTag)e.getValue();
        indent();
        out.println("<Symbol idref='" + dict.getId(ref) + "' className='" + className + "' />");
      }
     
      if (tag.topLevelClass != null)
      {
        indent();
        out.println("<Symbol idref='0' className='" + tag.topLevelClass + "' />");
      }
     
     
      close(tag);
    }
   
    public void importAssets(ImportAssets tag)
    {
      open(tag);
      out.print(" url='" + tag.url + "'");
      end();
     
      Iterator it = tag.importRecords.iterator();
      while (it.hasNext())
      {
        ImportRecord record = (ImportRecord)it.next();
        indent();
        out.println("<Import name='" + record.name + "' id='" + dict.getId(record) + "' />");
      }
     
      close(tag);
    }
   
    public void importAssets2(ImportAssets tag)
    {
      // TODO: add support for tag.downloadNow and SHA1...
      importAssets(tag);
    }
   
    public void enableDebugger(EnableDebugger tag)
    {
      open(tag);
      out.print(" password='" + tag.password + "'");
      close();
    }
   
    public void doInitAction(DoInitAction tag)
    {
      if (tag.sprite != null && tag.sprite.name != null)
      {
        indent();
        out.println("<!-- init " + tag.sprite.name + " " + dict.getId(tag.sprite) + " -->");
      }
     
      open(tag);
      if (tag.sprite != null)
        out.print(" idref='" + idRef(tag.sprite) + "'");
      end();
     
      if (showActions)
      {
        openCDATA();
        printActions(tag.actionList);
        closeCDATA();
      }
      else
      {
        indent();
        out.println("<!-- " + tag.actionList.size() + " action(s) elided -->");
      }
      close(tag);
    }
   
    private String idRef(DefineTag tag)
    {
      if (tag == null)
      {
        // if tag is null then it isn't in the dict -- the SWF is invalid.
        // lets be lax and print something; Matador generates invalid SWF sometimes.
        return "-1";
      }
      else if (tag.name == null)
      {
        // just print the character id since no name was exported
        return String.valueOf(dict.getId(tag));
      }
      else
      {
        return tag.name;
      }
    }
   
    public void defineVideoStream(DefineVideoStream tag)
    {
      open(tag);
      out.print(" id='" + id(tag) + "'");
      close();
    }
   
    public void videoFrame(VideoFrame tag)
    {
      open(tag);
      out.print(" streamId='" + idRef(tag.stream) + "'");
      out.print(" frame='" + tag.frameNum + "'");
      close();
    }
   
    public void defineFontInfo2(DefineFontInfo tag)
    {
      defineFontInfo(tag);
    }
   
    public void enableDebugger2(EnableDebugger tag)
    {
      open(tag);
      out.print(" password='" + tag.password + "'");
      out.print(" reserved='0x" + Integer.toHexString(tag.reserved) + "'");
      close();
    }
   
    public void debugID(DebugID tag)
    {
      open(tag);
      out.print(" uuid='" + tag.uuid + "'");
      close();
    }
   
    public void scriptLimits(ScriptLimits tag)
    {
      open(tag);
      out.print(" scriptRecursionLimit='" + tag.scriptRecursionLimit + "'" +
            " scriptTimeLimit='" + tag.scriptTimeLimit + "'");
      close();
    }
   
    public void setTabIndex(SetTabIndex tag)
    {
      open(tag);
      out.print(" depth='" + tag.depth + "'");
      out.print(" index='" + tag.index + "'");
      close();
    }
   
    public void doABC(DoABC tag)
    {
      if (abc)
      {
        open(tag);
        end();
        AbcPrinter abcPrinter = new AbcPrinter(tag.abc, out, showOffset, indent, showByteCode);
        abcPrinter.print();
        close(tag);
      }
      else if (showActions)
      {
        open(tag);
        if (tag.code == TagValues.stagDoABC2)
          out.print( " name='" + tag.name + "'");
        end();
       
        ContextStatics contextStatics = new ContextStatics();
        contextStatics.use_static_semantics = true;
        contextStatics.dialect = 9;
       
        assert swfVersion != null : "header should have been parsed already, but wasn't";
        contextStatics.setAbcVersion(ContextStatics.getTargetAVM(swfVersion));
        contextStatics.use_namespaces.addAll(ContextStatics.getRequiredUseNamespaces(swfVersion));
       
        Context context = new Context(contextStatics);
        context.setHandler(new CompilerHandler());
        AbcParser abcParser = new AbcParser(context, tag.abc);
        context.setEmitter(new ActionBlockEmitter(context, tag.name, new StringPrintWriter(),
                              new StringPrintWriter(), false, false, false, false));
        ProgramNode programNode = abcParser.parseAbc();
       
        if (programNode == null)
        {
          out.println("<!-- Error: could not parse abc -->");
        }
        else if (decompile)
        {
          //                PrettyPrinter prettyPrinter = new PrettyPrinter(out);
          //                programNode.evaluate(context, prettyPrinter);
        }
        else
        {
          SyntaxTreeDumper syntaxTreeDumper = new SyntaxTreeDumper(out, indent);
          programNode.evaluate(context, syntaxTreeDumper);
        }
       
        close(tag);
      }
      else
      {
        open(tag);
        close();
      }
    }
   
    private String hexify(byte[] id)
    {
      StringBuilder b = new StringBuilder(id.length * 2);
      for (int i = 0; i < id.length; i++)
      {
        b.append(Character.forDigit((id[i] >> 4) & 15, 16));
        b.append(Character.forDigit(id[i] & 15, 16));
      }
      return b.toString().toUpperCase();
    }
   
    public static String baseName(String path)
    {
      int start = path.lastIndexOf(File.separatorChar);
     
      if (File.separatorChar != '/')
      {
        // some of us are grouchy about unix paths not being
        // parsed since they are totally legit at the system
        // level of win32.
        int altstart = path.lastIndexOf('/');
        if ((start == -1) || (altstart > start))
          start = altstart;
      }
     
      if (start == -1)
        start = 0;
      else
        ++start;
     
     
      int end = path.lastIndexOf('.');
     
      if (end == -1)
        end = path.length();
     
     
      if (start > end)
        end = path.length();
     
      return path.substring(start, end);
     
    }
   
    public static String dirName(String path)
    {
      int end = path.lastIndexOf(File.pathSeparatorChar);
     
     
      if (File.pathSeparatorChar != '/')
      {
        // some of us are grouchy about unix paths not being
        // parsed since they are totally legit at the system
        // level of win32.
        int altend = path.lastIndexOf('/');
        if ((end == -1) || (altend < end))
          end = altend;
      }
     
      if (end == -1)
        return "";
      else
        ++end;
     
      return path.substring(0, end);
    }
   
    // options
    static boolean abcOption = false;
    static boolean encodeOption = false;
    static boolean showActionsOption = true;
    static boolean showOffsetOption = false;
    static boolean showByteCodeOption = false;
    static boolean showDebugSourceOption = false;
    static boolean glyphsOption = true;
    static boolean externalOption = false;
    static boolean decompileOption = true;
    static boolean defuncOption = true;
    static boolean saveOption = false;
    static boolean tabbedGlyphsOption = true;
   
   
    /**
     * swfdump usage:  swfdump [-encode] [-noactions] [-showoffset] files ...
     * -encode       ?
     * -noactions    don't output ActionScript byte code
     * -showoffset   output an XML comment line in the output before each
     * tag, displaying the tag's byte offset and size in the file
     * <p/>
     * Swfdump will dump a SWF file as XML.  Swf tags are shown as XML tags.  Swf Actions are shown
     * commented out assembly language.  If a SWD file is found that matches this SWF file, then
     * we will show intermixed source code and assembly language.
     * <p/>
     * The format of the output (swfx) is according to the SWFX doctype.  The optional -dtd flag will
     * include the doctype declaration before the actual content.  This format can be edited in any
     * text or xml editor, and then converted back into SWF using the Swfxc utility.
     */
    public static void main(String[] args) throws IOException
    {
      if (args.length == 0)
      {
        System.err.println("Usage: java tools.SwfxPrinter [-encode] [-asm] [-abc] [-showbytecode] [-noactions] [-showdebugsource] [-showoffset] [-noglyphs] [-external] [-save file.swf] [-nofunctions] [-out file.swfx] file1.swf ...");
        System.exit(1);
      }
     
      int index = 0;
      PrintWriter out = null;
      String outfile = null;
     
      while ((index < args.length) && (args[index].startsWith("-")))
      {
        if (args[index].equals("-encode"))
        {
          encodeOption = true;
          ++index;
        }
        else if (args[index].equals("-save"))
        {
          ++index;
          saveOption = true;
          outfile = args[index++];
        }
        else if (args[index].equals("-decompile"))
        {
          decompileOption = true;
          ++index;
        }
        else if (args[index].equals("-nofunctions"))
        {
          defuncOption = false;
          ++index;
        }
        else if (args[index].equals("-asm"))
        {
          decompileOption = false;
          ++index;
        }
        else if (args[index].equals("-abc"))
        {
          abcOption = true;
          ++index;
        }
        else if (args[index].equals("-noactions"))
        {
          showActionsOption = false;
          ++index;
        }
        else if (args[index].equals("-showoffset"))
        {
          showOffsetOption = true;
          ++index;
        }
        else if (args[index].equals("-showbytecode"))
        {
          showByteCodeOption = true;
          ++index;
        }
        else if (args[index].equals("-showdebugsource"))
        {
          showDebugSourceOption = true;
          ++index;
        }
        else if (args[index].equals("-noglyphs"))
        {
          glyphsOption = false;
          ++index;
        }
        else if (args[index].equals("-out"))
        {
          if (index + 1 == args.length)
          {
            System.err.println("-out requires a filename or - for stdout");
            System.exit(1);
          }
          if (!args[index + 1].equals("-"))
          {
           
            outfile = args[index + 1];
            out = new PrintWriter(new FileOutputStream(outfile, false));
          }
          index += 2;
        }
        else if (args[index].equals("-external"))
        {
          externalOption = true;
          ++index;
        }
        else if (args[index].equalsIgnoreCase("-tabbedGlyphs"))
        {
          tabbedGlyphsOption = true;
          ++index;
        }
        else
        {
          System.err.println("unknown argument " + args[index]);
          ++index;
        }
      }
     
      if (out == null)
        out = new PrintWriter(System.out, true);
     
      File f = new File(args[index]);
      URL[] urls;
      if (!f.exists())
      {
        urls = new URL[]{new URL(args[index])};
      }
      else
      {
        if (f.isDirectory())
        {
          File[] list = FileUtils.listFiles(f);
          urls = new URL[list.length];
          for (int i = 0; i < list.length; i++)
          {
            urls[i] = FileUtils.toURL(list[i]);
          }
        }
        else
        {
          urls = new URL[]{FileUtils.toURL(f)};
        }
      }
     
      for (int i = 0; i < urls.length; i++)
      {
        try
        {
          URL url = urls[i];
          if (saveOption)
          {
            InputStream in = new BufferedInputStream(url.openStream());
            try
            {
              OutputStream fileOut = new BufferedOutputStream(new FileOutputStream(outfile));
              try
              {
                int c;
                while ((c = in.read()) != -1)
                {
                  fileOut.write(c);
                }
              }
              finally
              {
                fileOut.close();
              }
            }
            finally
            {
              in.close();
            }
          }
         
          if (isSwf(url))
          {
            dumpSwf(out, url, outfile);
          }
          else if (isZip(url) && !url.toString().endsWith(".abj"))
          {
            dumpZip(out, url, outfile);
          }
          else
          {
            out.println("<!-- Parsing actions from " + url + " -->");
            // we have no way of knowing the swf version, so assume latest
            URLConnection connection = url.openConnection();
            ActionDecoder actionDecoder = new ActionDecoder(new SwfDecoder(connection.getInputStream(), 7));
            actionDecoder.setKeepOffsets(true);
            ActionList actions = actionDecoder.decode(connection.getContentLength());
            SwfxPrinter printer = new SwfxPrinter(out);
            printer.decompile = decompileOption;
            printer.defunc = defuncOption;
            printer.printActions(actions);
          }
          out.flush();
        }
        catch (Error e)
        {
          if (Trace.error)
            e.printStackTrace();
         
          System.err.println("");
          System.err.println("An unrecoverable error occurred.  The given file " + urls[i] + " may not be");
          System.err.println("a valid swf.");
        }
        catch (FileNotFoundException e)
        {
          System.err.println("Error: " + e.getMessage());
          System.exit(1);
        }
      }
    }
   
    private static void dumpZip(PrintWriter out, URL url, String outfile) throws IOException
    {
      InputStream in = new BufferedInputStream(url.openStream());
      try
      {
        ZipInputStream zipIn = new ZipInputStream(in);
        ZipEntry zipEntry = zipIn.getNextEntry();
        while ((zipEntry != null))
        {
          URL fileUrl = new URL("jar:" + url.toString() + "!/" + zipEntry.getName());
          if (isSwf(fileUrl))
            dumpSwf(out, fileUrl, outfile);
          zipEntry = zipIn.getNextEntry();
        }
      }
      finally
      {
        in.close();
      }
    }
   
    private static void dumpSwf(PrintWriter out, URL url, String outfile)
    throws IOException
    {
      out.println("<!-- Parsing swf " + url + " -->");
      InputStream in;
      SwfxPrinter debugPrinter = new SwfxPrinter(out);
     
      debugPrinter.showActions = showActionsOption;
      debugPrinter.showOffset = showOffsetOption;
      debugPrinter.showByteCode = showByteCodeOption;
      debugPrinter.showDebugSource = showDebugSourceOption;
      debugPrinter.glyphs = glyphsOption;
      debugPrinter.setExternal(externalOption, outfile);
      debugPrinter.decompile = decompileOption;
      debugPrinter.abc = abcOption;
      debugPrinter.defunc = defuncOption;
      debugPrinter.tabbedGlyphs = tabbedGlyphsOption;
     
      if (encodeOption)
      {
        // decode -> encode -> decode -> print
        TagEncoder encoder = new TagEncoder();
        in = url.openStream();
        new TagDecoder(in, url).parse(encoder);
        encoder.finish();
        in = new ByteArrayInputStream(encoder.toByteArray());
      }
      else
      {
        // decode -> print
        in = url.openStream();
      }
      TagDecoder t = new TagDecoder(in, url);
      t.setKeepOffsets(debugPrinter.showOffset);
      t.parse(debugPrinter);
    }
   
    private static boolean isSwf(URL url) throws IOException
    {
      InputStream in = new BufferedInputStream(url.openStream());
      try
      {
        return isSwf(in);
      }
      finally
      {
        in.close();
      }
    }
   
    public static boolean isSwf(InputStream in)
    {
      try
      {
        DataInputStream data = new DataInputStream(in);
        byte[] b = new byte[3];
        data.mark(b.length);
        data.readFully(b);
        if (b[0] == 'C' && b[1] == 'W' && b[2] == 'S' ||
                    b[0] == 'F' && b[1] == 'W' && b[2] == 'S')
        {
          data.reset();
          return true;
        }
        else
        {
          data.reset();
          return false;
        }
      }
      catch (IOException e)
      {
        return false;
      }
    }
   
    private static boolean isZip(URL url) throws IOException
    {
      InputStream in = new BufferedInputStream(url.openStream());
      try
      {
        return isZip(in);
      }
      finally
      {
        in.close();
      }
    }
   
    public static boolean isZip(InputStream in)
    {
      try
      {
        ZipInputStream swcZipInputStream = new ZipInputStream(in);
        swcZipInputStream.getNextEntry();
        return true;
      }
      catch (IOException e)
      {
        return false;
      }
    }
   
    // Handy dandy for dumping an action list during debugging
    public static String actionListToString(ActionList al, String[] args)
    {
      // cut and paste arg code from main() could be better but it works
      boolean showActions = true;
      boolean showOffset = false;
      boolean showDebugSource = false;
      boolean decompile = false;
      boolean defunc = true;
      boolean tabbedGlyphs = true;
      int index = 0;
     
      while (args != null && (index < args.length) && (args[index].startsWith("-")))
      {
        if (args[index].equals("-decompile"))
        {
          decompile = true;
          ++index;
        }
        else if (args[index].equals("-nofunctions"))
        {
          defunc = false;
          ++index;
        }
        else if (args[index].equals("-asm"))
        {
          decompile = false;
          ++index;
        }
        else if (args[index].equals("-noactions"))
        {
          showActions = false;
          ++index;
        }
        else if (args[index].equals("-showoffset"))
        {
          showOffset = true;
          ++index;
        }
        else if (args[index].equals("-showdebugsource"))
        {
          showDebugSource = true;
          ++index;
        }
        else if (args[index].equalsIgnoreCase("-tabbedGlyphs"))
        {
          tabbedGlyphs = true;
          ++index;
        }
      }
     
      StringWriter sw = new StringWriter();
      PrintWriter out = new PrintWriter(sw);
      SwfxPrinter printer = new SwfxPrinter(out);
      printer.showActions = showActions;
      printer.showOffset = showOffset;
      printer.showDebugSource = showDebugSource;
      printer.decompile = decompile;
      printer.defunc = defunc;
      printer.tabbedGlyphs = tabbedGlyphs;
     
      printer.printActions(al);
      out.flush();
      return sw.toString();
    }
  }
TOP

Related Classes of flash.swf.tools.SwfxPrinter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.