/*
 * Copyright (C) 2013 JPEXS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.decompiler.flash.xfl;

import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
import com.jpexs.decompiler.flash.RetryTask;
import com.jpexs.decompiler.flash.RunnableIOEx;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.SWFInputStream;
import com.jpexs.decompiler.flash.helpers.Helper;
import com.jpexs.decompiler.flash.helpers.Highlighting;
import com.jpexs.decompiler.flash.tags.CSMTextSettingsTag;
import com.jpexs.decompiler.flash.tags.DefineButton2Tag;
import com.jpexs.decompiler.flash.tags.DefineButtonCxformTag;
import com.jpexs.decompiler.flash.tags.DefineButtonTag;
import com.jpexs.decompiler.flash.tags.DefineEditTextTag;
import com.jpexs.decompiler.flash.tags.DefineFontNameTag;
import com.jpexs.decompiler.flash.tags.DefineSoundTag;
import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
import com.jpexs.decompiler.flash.tags.DefineText2Tag;
import com.jpexs.decompiler.flash.tags.DefineTextTag;
import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag;
import com.jpexs.decompiler.flash.tags.DoActionTag;
import com.jpexs.decompiler.flash.tags.DoInitActionTag;
import com.jpexs.decompiler.flash.tags.ExportAssetsTag;
import com.jpexs.decompiler.flash.tags.FileAttributesTag;
import com.jpexs.decompiler.flash.tags.FrameLabelTag;
import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
import com.jpexs.decompiler.flash.tags.ShowFrameTag;
import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag;
import com.jpexs.decompiler.flash.tags.StartSoundTag;
import com.jpexs.decompiler.flash.tags.SymbolClassTag;
import com.jpexs.decompiler.flash.tags.Tag;
import com.jpexs.decompiler.flash.tags.base.ASMSource;
import com.jpexs.decompiler.flash.tags.base.ButtonTag;
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
import com.jpexs.decompiler.flash.tags.base.FontTag;
import com.jpexs.decompiler.flash.tags.base.ImageTag;
import com.jpexs.decompiler.flash.tags.base.MorphShapeTag;
import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag;
import com.jpexs.decompiler.flash.tags.base.RemoveTag;
import com.jpexs.decompiler.flash.tags.base.ShapeTag;
import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.types.BUTTONCONDACTION;
import com.jpexs.decompiler.flash.types.BUTTONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONRECORD;
import com.jpexs.decompiler.flash.types.CLIPACTIONS;
import com.jpexs.decompiler.flash.types.CXFORM;
import com.jpexs.decompiler.flash.types.CXFORMWITHALPHA;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.GRADIENT;
import com.jpexs.decompiler.flash.types.GRADRECORD;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGB;
import com.jpexs.decompiler.flash.types.RGBA;
import com.jpexs.decompiler.flash.types.SHAPE;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.SOUNDENVELOPE;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
import com.jpexs.decompiler.flash.types.filters.BEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.BLURFILTER;
import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER;
import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER;
import com.jpexs.decompiler.flash.types.filters.FILTER;
import com.jpexs.decompiler.flash.types.filters.GLOWFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER;
import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER;
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD;
import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord;
import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord;
import com.jpexs.decompiler.flash.types.sound.MP3FRAME;
import com.jpexs.decompiler.graph.Graph;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

/**
 *
 * @author JPEXS
 */
public class XFLConverter {

    public static final int KEY_MODE_NORMAL = 9728;
    public static final int KEY_MODE_CLASSIC_TWEEN = 22017;
    public static final int KEY_MODE_SHAPE_TWEEN = 17922;
    public static final int KEY_MODE_MOTION_TWEEN = 8195;
    public static final int KEY_MODE_SHAPE_LAYERS = 8192;

    private XFLConverter() {
    }

    public static String convertShapeEdge(MATRIX mat, SHAPERECORD record, int x, int y) {
        String ret = "";
        if (record instanceof StyleChangeRecord) {
            StyleChangeRecord scr = (StyleChangeRecord) record;
            Point p = new Point(scr.moveDeltaX, scr.moveDeltaY);
            //p = mat.apply(p);
            if (scr.stateMoveTo) {
                return "! " + p.x + " " + p.y;
            }
            return "";
        }
        if (record instanceof StraightEdgeRecord) {
            StraightEdgeRecord ser = (StraightEdgeRecord) record;
            if (ser.generalLineFlag || ser.vertLineFlag) {
                y += ser.deltaY;
            }
            if (ser.generalLineFlag || (!ser.vertLineFlag)) {
                x += ser.deltaX;
            }
            Point p = new Point(x, y);
            //p = mat.apply(p);
            return "| " + p.x + " " + p.y;
        }
        if (record instanceof CurvedEdgeRecord) {
            CurvedEdgeRecord cer = (CurvedEdgeRecord) record;
            int controlX = cer.controlDeltaX + x;
            int controlY = cer.controlDeltaY + y;
            int anchorX = cer.anchorDeltaX + controlX;
            int anchorY = cer.anchorDeltaY + controlY;
            Point control = new Point(controlX, controlY);
            //control = mat.apply(control);
            Point anchor = new Point(anchorX, anchorY);
            //anchor = mat.apply(anchor);
            return "[ " + control.x + " " + control.y + " " + anchor.x + " " + anchor.y;
        }
        return ret;
    }

    public static String convertShapeEdges(int startX, int startY, MATRIX mat, List<SHAPERECORD> records) {
        String ret = "";
        int x = startX;
        int y = startY;
        ret += "!" + startX + " " + startY;
        for (SHAPERECORD rec : records) {
            ret += convertShapeEdge(mat, rec, x, y);
            x = rec.changeX(x);
            y = rec.changeY(y);
        }
        return ret;
    }

    public static String convertLineStyle(LINESTYLE ls, int shapeNum) {
        return "<SolidStroke weight=\"" + (((float) ls.width) / 20.0) + "\">"
                + "<fill>"
                + "<SolidColor color=\"" + (shapeNum == 3 ? ls.colorA.toHexRGB() : ls.color.toHexRGB()) + "\""
                + (shapeNum == 3 ? " alpha=\"" + ls.colorA.getAlphaFloat() + "\"" : "")
                + " />"
                + "</fill>"
                + "</SolidStroke>";
    }

    public static String convertLineStyle(HashMap<Integer, CharacterTag> characters, LINESTYLE2 ls, int shapeNum) {
        String ret = "";
        String params = "";
        if (ls.pixelHintingFlag) {
            params += " pixelHinting=\"true\"";
        }
        if (ls.width == 1) {
            params += " solidStyle=\"hairline\"";
        }
        if ((!ls.noHScaleFlag) && (!ls.noVScaleFlag)) {
            params += " scaleMode=\"normal\"";
        } else if ((!ls.noHScaleFlag) && ls.noVScaleFlag) {
            params += " scaleMode=\"horizontal\"";
        } else if (ls.noHScaleFlag && (!ls.noVScaleFlag)) {
            params += " scaleMode=\"vertical\"";
        }

        switch (ls.endCapStyle) {  //What about endCapStyle?
            case LINESTYLE2.NO_CAP:
                params += " caps=\"none\"";
                break;
            case LINESTYLE2.SQUARE_CAP:
                params += " caps=\"square\"";
                break;
        }
        switch (ls.joinStyle) {
            case LINESTYLE2.BEVEL_JOIN:
                params += " joints=\"bevel\"";
                break;
            case LINESTYLE2.MITER_JOIN:
                params += " joints=\"miter\"";
                float miterLimitFactor = toFloat(ls.miterLimitFactor);
                if (miterLimitFactor != 3.0f) {
                    params += " miterLimit=\"" + miterLimitFactor + "\"";
                }
                break;
        }

        ret += "<SolidStroke weight=\"" + (((float) ls.width) / 20.0) + "\"";
        ret += params;
        ret += ">";
        ret += "<fill>";

        if (!ls.hasFillFlag) {
            ret += "<SolidColor color=\"" + ls.color.toHexRGB() + "\""
                    + (ls.color.getAlphaFloat() != 1 ? " alpha=\"" + ls.color.getAlphaFloat() + "\"" : "")
                    + " />";
        } else {
            ret += convertFillStyle(null/* FIXME */, characters, ls.fillType, shapeNum);
        }
        ret += "</fill>";
        ret += "</SolidStroke>";
        return ret;
    }

    private static float toFloat(int i) {
        return ((float) i) / (1 << 16);
    }

    public static String convertFillStyle(MATRIX mat, HashMap<Integer, CharacterTag> characters, FILLSTYLE fs, int shapeNum) {
        if (mat == null) {
            mat = new MATRIX();
        }
        String ret = "";
        //ret += "<FillStyle index=\"" + index + "\">";
        switch (fs.fillStyleType) {
            case FILLSTYLE.SOLID:
                ret += "<SolidColor color=\"";
                if (shapeNum >= 3) {
                    ret += fs.colorA.toHexRGB();
                } else {
                    ret += fs.color.toHexRGB();
                }
                ret += "\"";
                if (shapeNum >= 3) {
                    ret += " alpha=\"" + fs.colorA.getAlphaFloat() + "\"";
                }
                ret += " />";
                break;
            case FILLSTYLE.REPEATING_BITMAP:
            case FILLSTYLE.CLIPPED_BITMAP:
            case FILLSTYLE.NON_SMOOTHED_REPEATING_BITMAP:
            case FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP:
                ret += "<BitmapFill";
                ret += " bitmapPath=\"";
                CharacterTag bitmapCh = characters.get(fs.bitmapId);
                if (bitmapCh instanceof ImageTag) {
                    ImageTag it = (ImageTag) bitmapCh;
                    ret += "bitmap" + bitmapCh.getCharacterId() + "." + it.getImageFormat();
                }
                ret += "\"";

                if ((fs.fillStyleType == FILLSTYLE.CLIPPED_BITMAP) || (fs.fillStyleType == FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP)) {
                    ret += " bitmapIsClipped=\"true\"";
                }

                ret += ">";
                ret += "<matrix>" + convertMatrix(fs.bitmapMatrix) + "</matrix>";
                ret += "</BitmapFill>";
                break;
            case FILLSTYLE.LINEAR_GRADIENT:
            case FILLSTYLE.RADIAL_GRADIENT:
            case FILLSTYLE.FOCAL_RADIAL_GRADIENT:

                if (fs.fillStyleType == FILLSTYLE.LINEAR_GRADIENT) {
                    ret += "<LinearGradient";
                } else {
                    ret += "<RadialGradient";
                    ret += " focalPointRatio=\"";
                    if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                        ret += fs.focalGradient.focalPoint;
                    } else {
                        ret += "0";
                    }
                    ret += "\"";
                }

                int interpolationMode;
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    interpolationMode = fs.focalGradient.interPolationMode;
                } else {
                    interpolationMode = fs.gradient.interPolationMode;
                }
                int spreadMode;
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    spreadMode = fs.focalGradient.spreadMode;
                } else {
                    spreadMode = fs.gradient.spreadMode;
                }
                if (interpolationMode == GRADIENT.INTERPOLATION_LINEAR_RGB_MODE) {
                    ret += " interpolationMethod=\"linearRGB\"";
                }
                switch (spreadMode) {
                    case GRADIENT.SPREAD_PAD_MODE:

                        break;
                    case GRADIENT.SPREAD_REFLECT_MODE:
                        ret += " spreadMethod=\"reflect\"";
                        break;
                    case GRADIENT.SPREAD_REPEAT_MODE:
                        ret += " spreadMethod=\"repeat\"";
                        break;
                }

                ret += ">";

                ret += "<matrix>" + convertMatrix(fs.gradientMatrix) + "</matrix>";
                GRADRECORD records[];
                if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) {
                    records = fs.focalGradient.gradientRecords;
                } else {
                    records = fs.gradient.gradientRecords;
                }
                for (GRADRECORD rec : records) {
                    ret += "<GradientEntry";
                    ret += " color=\"" + (shapeNum >= 3 ? rec.colorA.toHexRGB() : rec.color.toHexRGB()) + "\"";
                    if (shapeNum >= 3) {
                        ret += " alpha=\"" + rec.colorA.getAlphaFloat() + "\"";
                    }
                    ret += " ratio=\"" + rec.getRatioFloat() + "\"";
                    ret += " />";
                }
                if (fs.fillStyleType == FILLSTYLE.LINEAR_GRADIENT) {
                    ret += "</LinearGradient>";
                } else {
                    ret += "</RadialGradient>";
                }
                break;
        }
        //ret += "</FillStyle>";
        return ret;

    }

    public static String convertMatrix(MATRIX m) {
        String ret = "";
        if (m == null) {
            m = new MATRIX();
        }
        ret += "<Matrix ";
        ret += "tx=\"" + (((float) m.translateX) / 20.0) + "\" ";
        ret += "ty=\"" + (((float) m.translateY) / 20.0) + "\" ";
        if (m.hasScale) {
            ret += "a=\"" + m.toFloat(m.scaleX) + "\" ";
            ret += "d=\"" + m.toFloat(m.scaleY) + "\" ";
        }
        if (m.hasRotate) {
            ret += "b=\"" + m.toFloat(m.rotateSkew0) + "\" ";
            ret += "c=\"" + m.toFloat(m.rotateSkew1) + "\" ";
        }
        ret += "/>";
        return ret;
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, SHAPE shape) {
        return convertShape(characters, mat, shapeNum, shape.shapeRecords);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, SHAPEWITHSTYLE shape) {
        return convertShape(characters, mat, shapeNum, shape.shapeRecords, shape.fillStyles, shape.lineStyles, false);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, ShapeTag shape) {
        return convertShape(characters, mat, shape.getShapeNum(), shape.getShapes());
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords) {
        return convertShape(characters, mat, shapeNum, shapeRecords, null, null, false);
    }

    public static String convertShape(HashMap<Integer, CharacterTag> characters, MATRIX mat, int shapeNum, List<SHAPERECORD> shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) {
        String ret = "";
        if (mat == null) {
            mat = new MATRIX();
        }
        List<SHAPERECORD> edges = new ArrayList<>();
        //int fillStyleCount = 0;
        int lineStyleCount = 0;
        int fillStyle0 = -1;
        int fillStyle1 = -1;
        int strokeStyle = -1;
        //String edgesStr = "";
        String fillsStr = "";
        String strokesStr = "";
        fillsStr += "<fills>";
        strokesStr += "<strokes>";
        //edgesStr += "<edges>";
        List<String> layers = new ArrayList<>();
        String currentLayer = "";

        int fillStyleCount = 0;
        //List<String> fillStylesStr = new ArrayList<>();
        if (fillStyles != null) {
            for (FILLSTYLE fs : fillStyles.fillStyles) {
                fillsStr += "<FillStyle index=\"" + (fillStyleCount + 1) + "\">";
                fillsStr += convertFillStyle(mat, characters, fs, shapeNum);
                fillsStr += "</FillStyle>";
                fillStyleCount++;
            }
        }
        if (lineStyles != null) {
            if ((shapeNum == 4) && (lineStyles.lineStyles2 != null)) { //(shapeNum == 4) {
                for (int l = 0; l < lineStyles.lineStyles2.length; l++) {
                    strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                    strokesStr += convertLineStyle(characters, lineStyles.lineStyles2[l], shapeNum);
                    strokesStr += "</StrokeStyle>";
                    lineStyleCount++;
                }
            } else if (lineStyles.lineStyles != null) {
                for (int l = 0; l < lineStyles.lineStyles.length; l++) {
                    strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                    strokesStr += convertLineStyle(lineStyles.lineStyles[l], shapeNum);
                    strokesStr += "</StrokeStyle>";
                    lineStyleCount++;
                }
            }
        }

        fillsStr += "</fills>";
        strokesStr += "</strokes>";

        int layer = 1;

        if ((fillStyleCount > 0) || (lineStyleCount > 0)) {
            currentLayer += "<DOMLayer name=\"Layer " + (layer++) + "\">"; //color=\"#4FFF4F\"
            currentLayer += "<frames>";
            currentLayer += "<DOMFrame index=\"0\" motionTweenScale=\"false\" keyMode=\"" + KEY_MODE_SHAPE_LAYERS + "\">";
            currentLayer += "<elements>";
            currentLayer += "<DOMShape isFloating=\"true\">";
            currentLayer += fillsStr;
            currentLayer += strokesStr;
            currentLayer += "<edges>";
        }
        //edgesStr += "</edges>";

        /*for (SHAPERECORD edge : shapeRecords) {
         if (edge instanceof StyleChangeRecord) {
         StyleChangeRecord scr = (StyleChangeRecord) edge;
         if (scr.stateNewStyles) {
         for (int f = 0; f < scr.fillStyles.fillStyles.length; f++) {
         fillStylesStr.add(convertFillStyle(mat, characters, scr.fillStyles.fillStyles[f], shapeNum));
         }
         }
         }
         }
         boolean usedFillStyles[] = new boolean[fillStylesStr.size()];
         int fillStyleCount = 0;
         lastFillStyleCount = 0;
         if (fillStyles != null) {
         lastFillStyleCount = fillStyles.fillStyles.length;
         fillStyleCount += lastFillStyleCount;
         }
         for (SHAPERECORD edge : shapeRecords) {
         if (edge instanceof StyleChangeRecord) {
         StyleChangeRecord scr = (StyleChangeRecord) edge;
         if (scr.stateNewStyles) {
         lastFillStyleCount = scr.fillStyles.fillStyles.length;
         fillStyleCount += lastFillStyleCount;
         }
         if (scr.stateFillStyle0 && scr.fillStyle0 > 0) {
         usedFillStyles[fillStyleCount - lastFillStyleCount + scr.fillStyle0 - 1] = true;
         }
         if (scr.stateFillStyle1 && scr.fillStyle1 > 0) {
         usedFillStyles[fillStyleCount - lastFillStyleCount + scr.fillStyle1 - 1] = true;
         }
         }
         }
         */
        /*int mapPos = 0;
         HashMap<Integer, Integer> fillStylesMap = new HashMap<>();
         for (int f = 0; f < fillStyleCount; f++) {
         if (usedFillStyles[f]) {
         fillStylesMap.put(f, mapPos);
         mapPos++;
         fillsStr += "<FillStyle index=\"" + mapPos + "\">";
         fillsStr += fillStylesStr.get(f);
         fillsStr += "</FillStyle>";
         }
         }*/

        int x = 0;
        int y = 0;
        int startEdgeX = 0;
        int startEdgeY = 0;


        LINESTYLEARRAY actualLinestyles = lineStyles;
        int strokeStyleOrig = 0;
        fillStyleCount = fillStyles.fillStyles.length;
        for (SHAPERECORD edge : shapeRecords) {
            if (edge instanceof StyleChangeRecord) {
                StyleChangeRecord scr = (StyleChangeRecord) edge;
                boolean styleChange = false;
                int lastFillStyle1 = fillStyle1;
                int lastFillStyle0 = fillStyle0;
                int lastStrokeStyle = strokeStyle;
                if (scr.stateNewStyles) {
                    fillsStr = "<fills>";
                    strokesStr = "<strokes>";
                    if (fillStyleCount > 0 || lineStyleCount > 0) {

                        if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {

                            boolean empty = false;
                            if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                                if (shapeNum == 4) {
                                    if (strokeStyleOrig > 0) {
                                        if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                            empty = true;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                            if (!empty) {
                                currentLayer += "<Edge";
                                if (fillStyle0 > -1) {
                                    currentLayer += " fillStyle0=\"" + fillStyle0 + "\"";
                                }
                                if (fillStyle1 > -1) {
                                    currentLayer += " fillStyle1=\"" + fillStyle1 + "\"";
                                }
                                if (strokeStyle > -1) {
                                    currentLayer += " strokeStyle=\"" + strokeStyle + "\"";
                                }
                                currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                            }
                        }

                        currentLayer += "</edges>";
                        currentLayer += "</DOMShape>";
                        currentLayer += "</elements>";
                        currentLayer += "</DOMFrame>";
                        currentLayer += "</frames>";
                        currentLayer += "</DOMLayer>";
                        layers.add(currentLayer);
                        currentLayer = "";
                    }

                    currentLayer += "<DOMLayer name=\"Layer " + (layer++) + "\">"; //color=\"#4FFF4F\"
                    currentLayer += "<frames>";
                    currentLayer += "<DOMFrame index=\"0\" motionTweenScale=\"false\" keyMode=\"" + KEY_MODE_SHAPE_LAYERS + "\">";
                    currentLayer += "<elements>";
                    currentLayer += "<DOMShape isFloating=\"true\">";
                    //ret += convertShape(characters, null, shape);
                    for (int f = 0; f < scr.fillStyles.fillStyles.length; f++) {
                        fillsStr += "<FillStyle index=\"" + (f + 1) + "\">";
                        fillsStr += convertFillStyle(mat, characters, scr.fillStyles.fillStyles[f], shapeNum);
                        fillsStr += "</FillStyle>";
                        fillStyleCount++;
                    }

                    if (shapeNum == 4) {
                        for (int l = 0; l < scr.lineStyles.lineStyles2.length; l++) {
                            strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                            strokesStr += convertLineStyle(characters, scr.lineStyles.lineStyles2[l], shapeNum);
                            strokesStr += "</StrokeStyle>";
                            lineStyleCount++;
                        }
                    } else {
                        for (int l = 0; l < scr.lineStyles.lineStyles.length; l++) {
                            strokesStr += "<StrokeStyle index=\"" + (lineStyleCount + 1) + "\">";
                            strokesStr += convertLineStyle(scr.lineStyles.lineStyles[l], shapeNum);
                            strokesStr += "</StrokeStyle>";
                            lineStyleCount++;
                        }
                    }
                    fillsStr += "</fills>";
                    strokesStr += "</strokes>";
                    currentLayer += fillsStr;
                    currentLayer += strokesStr;
                    currentLayer += "<edges>";
                    //lastFillStyleCount = scr.fillStyles.fillStyles.length;
                    //fillStyleCount += lastFillStyleCount;
                    //lastLineStyleCount = (shapeNum == 4) ? scr.lineStyles.lineStyles2.length : scr.lineStyles.lineStyles.length;
                    actualLinestyles = scr.lineStyles;
                }
                if (scr.stateFillStyle0) {
                    /*edgeStyle += " fillStyle0=\"";
                     edgeStyle += fillStyleCount - lastFillStyleCount + scr.fillStyle0;
                     edgeStyle += "\"";*/
                    int fillStyle0_new = scr.fillStyle0;// == 0 ? 0 : fillStylesMap.get(fillStyleCount - lastFillStyleCount + scr.fillStyle0 - 1) + 1;
                    //fillStyle0 = fillStyle0_new;
                    if (morphshape) { //???
                        fillStyle1 = fillStyle0_new;
                    } else {
                        fillStyle0 = fillStyle0_new;
                    }
                    styleChange = true;
                }
                if (scr.stateFillStyle1) {
                    /*edgeStyle += " fillStyle1=\"";
                     edgeStyle += fillStyleCount - lastFillStyleCount + scr.fillStyle1;
                     edgeStyle += "\"";*/
                    int fillStyle1_new = scr.fillStyle1;// == 0 ? 0 : fillStylesMap.get(fillStyleCount - lastFillStyleCount + scr.fillStyle1 - 1) + 1;
                    if (morphshape) {
                        fillStyle0 = fillStyle1_new;
                    } else {
                        fillStyle1 = fillStyle1_new;
                    }
                    //fillStyle1 = fillStyle1_new;
                    styleChange = true;
                }
                if (scr.stateLineStyle) {
                    /*edgeStyle += " strokeStyle=\"";
                     edgeStyle += lineStyleCount - lastLineStyleCount + scr.lineStyle;
                     edgeStyle += "\"";*/
                    strokeStyle = scr.lineStyle;// == 0 ? 0 : lineStyleCount - lastLineStyleCount + scr.lineStyle;
                    strokeStyleOrig = scr.lineStyle - 1;
                    styleChange = true;
                }
                if (!edges.isEmpty()) {
                    if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {
                        //if(true){
                        boolean empty = false;
                        if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                            if (shapeNum == 4) {
                                if (strokeStyleOrig > 0) {
                                    if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                        empty = true;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        if (!empty) {
                            currentLayer += "<Edge";
                            if (fillStyle0 > -1) {
                                currentLayer += " fillStyle0=\"" + lastFillStyle0 + "\"";
                            }
                            if (fillStyle1 > -1) {
                                currentLayer += " fillStyle1=\"" + lastFillStyle1 + "\"";
                            }
                            if (strokeStyle > -1) {
                                currentLayer += " strokeStyle=\"" + lastStrokeStyle + "\"";
                            }
                            currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                        }

                        startEdgeX = x;
                        startEdgeY = y;
                        //strokeStyle = -1;
                        //fillStyle0 = -1;
                        //fillStyle1 = -1;
                    }
                    edges.clear();
                }
            }
            edges.add(edge);
            x = edge.changeX(x);
            y = edge.changeY(y);
        }
        if (!edges.isEmpty()) {
            if ((fillStyle0 > 0) || (fillStyle1 > 0) || (strokeStyle > 0)) {

                boolean empty = false;
                if ((fillStyle0 <= 0) && (fillStyle1 <= 0) && (strokeStyle > 0) && morphshape) {
                    if (shapeNum == 4) {
                        if (strokeStyleOrig > 0) {
                            if (!actualLinestyles.lineStyles2[strokeStyleOrig].hasFillFlag) {
                                if (actualLinestyles.lineStyles2[strokeStyleOrig].color.alpha == 0) {
                                    if (actualLinestyles.lineStyles2[strokeStyleOrig].color.red == 0) {
                                        if (actualLinestyles.lineStyles2[strokeStyleOrig].color.green == 0) {
                                            if (actualLinestyles.lineStyles2[strokeStyleOrig].color.blue == 0) {
                                                empty = true;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                if (!empty) {
                    currentLayer += "<Edge";
                    if (fillStyle0 > -1) {
                        currentLayer += " fillStyle0=\"" + fillStyle0 + "\"";
                    }
                    if (fillStyle1 > -1) {
                        currentLayer += " fillStyle1=\"" + fillStyle1 + "\"";
                    }
                    if (strokeStyle > -1) {
                        currentLayer += " strokeStyle=\"" + strokeStyle + "\"";
                    }
                    currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />";
                }
            }
        }
        edges.clear();
        fillsStr += "</fills>";
        strokesStr += "</strokes>";
        if (!currentLayer.equals("")) {
            currentLayer += "</edges>";
            currentLayer += "</DOMShape>";
            currentLayer += "</elements>";
            currentLayer += "</DOMFrame>";
            currentLayer += "</frames>";
            currentLayer += "</DOMLayer>";
            layers.add(currentLayer);
        }
        for (int l = layers.size() - 1; l >= 0; l--) {
            ret += layers.get(l);
        }
        return ret;
    }

    private static int getLayerCount(List<Tag> tags) {
        int maxDepth = 0;
        for (Tag t : tags) {
            if (t instanceof PlaceObjectTypeTag) {
                int d = ((PlaceObjectTypeTag) t).getDepth();
                if (d > maxDepth) {
                    maxDepth = d;
                }
                int cd = ((PlaceObjectTypeTag) t).getClipDepth();
                if (cd > maxDepth) {
                    maxDepth = cd;
                }
            }
        }
        return maxDepth;
    }

    private static List<Integer> getOneInstanceShapes(List<Tag> tags, HashMap<Integer, CharacterTag> characters) {
        if (true) { //the feature was removed due to multiple layers in some shapes            
            return new ArrayList<>();
        }
        HashMap<Integer, Integer> usages = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                int ch = po.getCharacterId();
                if (ch > -1) {
                    if (!usages.containsKey(ch)) {
                        usages.put(ch, 0);
                    }
                    int usageCount = usages.get(ch);
                    if (po.getInstanceName() != null) {
                        usageCount++;
                    }
                    if (po.getColorTransform() != null) {
                        usageCount++;
                    }
                    if (po.getColorTransformWithAlpha() != null) {
                        usageCount++;
                    }
                    if (po.cacheAsBitmap()) {
                        usageCount++;
                    }
                    MATRIX mat = po.getMatrix();
                    if (mat != null) {
                        if (!mat.isEmpty()) {
                            usageCount++;
                        }
                    }
                    usages.put(ch, usageCount + 1);
                }
            }
        }
        List<Integer> ret = new ArrayList<>();
        for (int ch : usages.keySet()) {
            if (usages.get(ch) < 2) {
                ret.add(ch);
            }
        }
        return ret;
    }

    private static HashMap<Integer, CharacterTag> getCharacters(List<Tag> tags) {
        HashMap<Integer, CharacterTag> ret = new HashMap<>();
        int maxId = 0;
        for (Tag t : tags) {
            if (t instanceof CharacterTag) {
                CharacterTag ct = (CharacterTag) t;
                if (ct.getCharacterId() > maxId) {
                    maxId = ct.getCharacterId();
                }
            }
        }
        for (Tag t : tags) {
            if (t instanceof SoundStreamHeadTypeTag) {
                SoundStreamHeadTypeTag ssh = (SoundStreamHeadTypeTag) t;
                ssh.setVirtualCharacterId(++maxId);
            }
            if (t instanceof CharacterTag) {
                CharacterTag ct = (CharacterTag) t;
                ret.put(ct.getCharacterId(), ct);
            }
        }
        return ret;
    }
    private static final String[] BLENDMODES = {
        null,
        null,
        "layer",
        "multiply",
        "screen",
        "lighten",
        "darken",
        "difference",
        "add",
        "subtract",
        "invert",
        "alpha",
        "erase",
        "overlay",
        "hardligh"
    };

    private static double radToDeg(double rad) {
        return rad * 180 / Math.PI;
    }

    private static String doubleToString(double d, int precision) {
        double m = Math.pow(10, precision);
        d = Math.round(d * m) / m;
        return doubleToString(d);
    }

    private static String doubleToString(double d) {
        String ds = "" + d;
        if (ds.endsWith(".0")) {
            ds = ds.substring(0, ds.length() - 2);
        }
        return ds;
    }

    public static String convertFilter(FILTER filter) {
        String ret = "";
        if (filter instanceof DROPSHADOWFILTER) {
            DROPSHADOWFILTER dsf = (DROPSHADOWFILTER) filter;
            ret += "<DropShadowFilter";
            if (dsf.dropShadowColor.alpha != 255) {
                ret += " alpha=\"" + doubleToString(dsf.dropShadowColor.getAlphaFloat()) + "\"";
            }
            ret += " angle=\"" + doubleToString(radToDeg(dsf.angle)) + "\"";
            ret += " blurX=\"" + doubleToString(dsf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(dsf.blurY) + "\"";
            ret += " color=\"" + dsf.dropShadowColor.toHexRGB() + "\"";
            ret += " distance=\"" + doubleToString(dsf.distance) + "\"";
            if (!dsf.compositeSource) {
                ret += " hideObject=\"true\"";
            }
            if (dsf.innerShadow) {
                ret += " inner=\"true\"";
            }
            if (dsf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " quality=\"" + dsf.passes + "\"";
            ret += " strength=\"" + doubleToString(dsf.strength, 2) + "\"";
            ret += " />";
        } else if (filter instanceof BLURFILTER) {
            BLURFILTER bf = (BLURFILTER) filter;
            ret += "<BlurFilter";
            ret += " blurX=\"" + doubleToString(bf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(bf.blurY) + "\"";
            ret += " quality=\"" + bf.passes + "\"";
            ret += " />";
        } else if (filter instanceof GLOWFILTER) {
            GLOWFILTER gf = (GLOWFILTER) filter;
            ret += "<GlowFilter";
            if (gf.glowColor.alpha != 255) {
                ret += " alpha=\"" + gf.glowColor.getAlphaFloat() + "\"";
            }
            ret += " blurX=\"" + doubleToString(gf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(gf.blurY) + "\"";
            ret += " color=\"" + gf.glowColor.toHexRGB() + "\"";

            if (gf.innerGlow) {
                ret += " inner=\"true\"";
            }
            if (gf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " quality=\"" + gf.passes + "\"";
            ret += " strength=\"" + doubleToString(gf.strength, 2) + "\"";
            ret += " />";
        } else if (filter instanceof BEVELFILTER) {
            BEVELFILTER bf = (BEVELFILTER) filter;
            ret += "<BevelFilter";
            ret += " blurX=\"" + doubleToString(bf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(bf.blurY) + "\"";
            ret += " quality=\"" + bf.passes + "\"";
            ret += " angle=\"" + doubleToString(radToDeg(bf.angle)) + "\"";
            ret += " distance=\"" + bf.distance + "\"";
            if (bf.highlightColor.alpha != 255) {
                ret += " highlightAlpha=\"" + bf.highlightColor.getAlphaFloat() + "\"";
            }
            ret += " highlightColor=\"" + bf.highlightColor.toHexRGB() + "\"";
            if (bf.knockout) {
                ret += " knockout=\"true\"";
            }
            if (bf.shadowColor.alpha != 255) {
                ret += " shadowAlpha=\"" + bf.shadowColor.getAlphaFloat() + "\"";
            }
            ret += " shadowColor=\"" + bf.shadowColor.toHexRGB() + "\"";
            ret += " strength=\"" + doubleToString(bf.strength, 2) + "\"";
            if (bf.onTop && !bf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!bf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += " />";
        } else if (filter instanceof GRADIENTGLOWFILTER) {
            GRADIENTGLOWFILTER ggf = (GRADIENTGLOWFILTER) filter;
            ret += "<GradientGlowFilter";
            ret += " angle=\"" + doubleToString(radToDeg(ggf.angle)) + "\"";

            ret += " blurX=\"" + doubleToString(ggf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(ggf.blurY) + "\"";
            ret += " quality=\"" + ggf.passes + "\"";
            ret += " distance=\"" + doubleToString(ggf.distance) + "\"";
            if (ggf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " strength=\"" + doubleToString(ggf.strength, 2) + "\"";
            if (ggf.onTop && !ggf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!ggf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += ">";
            for (int g = 0; g < ggf.gradientColors.length; g++) {
                RGBA gc = ggf.gradientColors[g];
                ret += "<GradientEntry color=\"" + gc.toHexRGB() + "\"";
                if (gc.alpha != 255) {
                    ret += " alpha=\"" + gc.getAlphaFloat() + "\"";
                }
                ret += " ratio=\"" + doubleToString(((float) ggf.gradientRatio[g]) / 255.0) + "\"";
                ret += "/>";
            }
            ret += "</GradientGlowFilter>";
        } else if (filter instanceof GRADIENTBEVELFILTER) {
            GRADIENTBEVELFILTER gbf = (GRADIENTBEVELFILTER) filter;
            ret += "<GradientBevelFilter";
            ret += " angle=\"" + doubleToString(radToDeg(gbf.angle)) + "\"";

            ret += " blurX=\"" + doubleToString(gbf.blurX) + "\"";
            ret += " blurY=\"" + doubleToString(gbf.blurY) + "\"";
            ret += " quality=\"" + gbf.passes + "\"";
            ret += " distance=\"" + doubleToString(gbf.distance) + "\"";
            if (gbf.knockout) {
                ret += " knockout=\"true\"";
            }
            ret += " strength=\"" + doubleToString(gbf.strength, 2) + "\"";
            if (gbf.onTop && !gbf.innerShadow) {
                ret += " type=\"full\"";
            } else if (!gbf.innerShadow) {
                ret += " type=\"outer\"";
            }
            ret += ">";
            for (int g = 0; g < gbf.gradientColors.length; g++) {
                RGBA gc = gbf.gradientColors[g];
                ret += "<GradientEntry color=\"" + gc.toHexRGB() + "\"";
                if (gc.alpha != 255) {
                    ret += " alpha=\"" + gc.getAlphaFloat() + "\"";
                }
                ret += " ratio=\"" + doubleToString(((float) gbf.gradientRatio[g]) / 255.0) + "\"";
                ret += "/>";
            }
            ret += "</GradientBevelFilter>";
        } else if (filter instanceof COLORMATRIXFILTER) {
            COLORMATRIXFILTER cmf = (COLORMATRIXFILTER) filter;
            ret += convertAdjustColorFilter(cmf);
        }
        return ret;
    }

    public static String convertSymbolInstance(String name, MATRIX matrix, CXFORM colorTransform, CXFORMWITHALPHA colorTransformAlpha, boolean cacheAsBitmap, int blendMode, List<FILTER> filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, CharacterTag tag, HashMap<Integer, CharacterTag> characters, List<Tag> tags) {
        String ret = "";
        if (matrix == null) {
            matrix = new MATRIX();
        }
        if (tag instanceof DefineButtonTag) {
            DefineButtonTag bt = (DefineButtonTag) tag;
            for (Tag t : tags) {
                if (t instanceof DefineButtonCxformTag) {
                    DefineButtonCxformTag bcx = (DefineButtonCxformTag) t;
                    if (bcx.buttonId == bt.buttonId) {
                        colorTransform = bcx.buttonColorTransform;
                    }
                }
            }
        }

        ret += "<DOMSymbolInstance libraryItemName=\"" + "Symbol " + tag.getCharacterId() + "\"";
        if (name != null) {
            ret += " name=\"" + xmlString(name) + "\"";
        }
        String blendModeStr = null;
        if (blendMode < BLENDMODES.length) {
            blendModeStr = BLENDMODES[blendMode];
        }
        if (blendModeStr != null) {
            ret += " blendMode=\"" + blendModeStr + "\"";
        }
        if (tag instanceof ShapeTag) {
            ret += " symbolType=\"graphic\" loop=\"loop\"";
        } else if (tag instanceof DefineSpriteTag) {
            DefineSpriteTag sprite = (DefineSpriteTag) tag;
            RECT spriteRect = sprite.getRect(characters, new Stack<Integer>());
            double centerPoint3DX = twipToPixel(matrix.translateX + spriteRect.getWidth() / 2);
            double centerPoint3DY = twipToPixel(matrix.translateY + spriteRect.getHeight() / 2);
            ret += " centerPoint3DX=\"" + centerPoint3DX + "\" centerPoint3DY=\"" + centerPoint3DY + "\"";
        } else if (tag instanceof ButtonTag) {
            ret += " symbolType=\"button\"";
        }
        if (cacheAsBitmap) {
            ret += " cacheAsBitmap=\"true\"";
        }
        if (!isVisible) {
            ret += " isVisible=\"false\"";
        }
        ret += ">";
        ret += "<matrix>";
        ret += convertMatrix(matrix);
        ret += "</matrix>";
        ret += "<transformationPoint><Point/></transformationPoint>";

        if (backgroundColor != null) {
            ret += "<MatteColor color=\"" + backgroundColor.toHexRGB() + "\"";
            if (backgroundColor.alpha != 255) {
                ret += " alpha=\"" + doubleToString(backgroundColor.getAlphaFloat()) + "\"";
            }
            ret += "/>";
        }
        if (colorTransform != null) {
            ret += "<color><Color";
            if (colorTransform.hasMultTerms) {
                ret += " redMultiplier=\"" + (((float) colorTransform.redMultTerm) / 256.0f) + "\"";
                ret += " greenMultiplier=\"" + (((float) colorTransform.greenMultTerm) / 256.0f) + "\"";
                ret += " blueMultiplier=\"" + (((float) colorTransform.blueMultTerm) / 256.0f) + "\"";
            }
            if (colorTransform.hasAddTerms) {
                ret += " redOffset=\"" + colorTransform.redAddTerm + "\"";
                ret += " greenOffset=\"" + colorTransform.greenAddTerm + "\"";
                ret += " blueOffset=\"" + colorTransform.blueAddTerm + "\"";
            }
            ret += "/></color>";
        } else if (colorTransformAlpha != null) {
            ret += "<color><Color";
            if (colorTransformAlpha.hasMultTerms) {
                ret += " alphaMultiplier=\"" + (((float) colorTransformAlpha.alphaMultTerm) / 256.0f) + "\"";
                ret += " redMultiplier=\"" + (((float) colorTransformAlpha.redMultTerm) / 256.0f) + "\"";
                ret += " greenMultiplier=\"" + (((float) colorTransformAlpha.greenMultTerm) / 256.0f) + "\"";
                ret += " blueMultiplier=\"" + (((float) colorTransformAlpha.blueMultTerm) / 256.0f) + "\"";
            }
            if (colorTransformAlpha.hasAddTerms) {
                ret += " alphaOffset=\"" + colorTransformAlpha.alphaAddTerm + "\"";
                ret += " redOffset=\"" + colorTransformAlpha.redAddTerm + "\"";
                ret += " greenOffset=\"" + colorTransformAlpha.greenAddTerm + "\"";
                ret += " blueOffset=\"" + colorTransformAlpha.blueAddTerm + "\"";
            }
            ret += "/></color>";
        }
        if (filters != null) {
            ret += "<filters>";
            for (FILTER f : filters) {
                ret += convertFilter(f);
            }
            ret += "</filters>";
        }
        if (tag instanceof DefineButtonTag) {
            ret += "<Actionscript><script><![CDATA[";
            ret += "on(press){\r\n";
            ret += convertActionScript(((DefineButtonTag) tag));
            ret += "}";
            ret += "]]></script></Actionscript>";
        }
        if (tag instanceof DefineButton2Tag) {
            DefineButton2Tag db2 = (DefineButton2Tag) tag;
            if (!db2.actions.isEmpty()) {
                ret += "<Actionscript><script><![CDATA[";
                for (BUTTONCONDACTION bca : db2.actions) {
                    ret += convertActionScript(bca);
                }
                ret += "]]></script></Actionscript>";
            }
        }
        if (clipActions != null) {
            ret += "<Actionscript><script><![CDATA[";
            for (CLIPACTIONRECORD rec : clipActions.clipActionRecords) {
                ret += convertActionScript(rec);
            }
            ret += "]]></script></Actionscript>";
        }
        ret += "</DOMSymbolInstance>";
        return ret;
    }

    private static String convertActionScript(ASMSource as) {
        String decompiledASHilighted = com.jpexs.decompiler.flash.action.Action.actionsToSource(as.getActions(SWF.DEFAULT_VERSION), SWF.DEFAULT_VERSION, as.toString());
        return as.getActionSourcePrefix() + Helper.indentRows(as.getActionSourceIndent(), Highlighting.stripHilights(decompiledASHilighted), Graph.INDENT_STRING) + as.getActionSourceSuffix();
    }

    private static long getTimestamp() {
        return new Date().getTime() / 1000;
    }

    public static String convertLibrary(SWF swf, Map<Integer, String> characterVariables, Map<Integer, String> characterClasses, List<Integer> oneInstanceShapes, String backgroundColor, List<Tag> tags, HashMap<Integer, CharacterTag> characters, HashMap<String, byte[]> files, HashMap<String, byte[]> datfiles) {

        //TODO: Imported assets
        //linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf"
        String ret = "";
        List<String> media = new ArrayList<>();
        List<String> symbols = new ArrayList<>();
        for (int ch : characters.keySet()) {
            CharacterTag symbol = characters.get(ch);
            if ((symbol instanceof ShapeTag) && oneInstanceShapes.contains(symbol.getCharacterId())) {
                continue; //shapes with 1 ocurrence are not added to library
            }
            if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) {
                String symbolStr = "";

                symbolStr += "<DOMSymbolItem xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://ns.adobe.com/xfl/2008/\" name=\"Symbol " + symbol.getCharacterId() + "\" lastModified=\"" + getTimestamp() + "\""; //TODO:itemID
                if (symbol instanceof ShapeTag) {
                    symbolStr += " symbolType=\"graphic\"";
                } else if (symbol instanceof ButtonTag) {
                    symbolStr += " symbolType=\"button\"";
                    if (((ButtonTag) symbol).trackAsMenu()) {
                        symbolStr += "  trackAsMenu=\"true\"";
                    }
                }
                boolean linkageExportForAS = false;
                if (characterClasses.containsKey(symbol.getCharacterId())) {
                    linkageExportForAS = true;
                    symbolStr += " linkageClassName=\"" + xmlString(characterClasses.get(symbol.getCharacterId())) + "\"";
                }
                if (characterVariables.containsKey(symbol.getCharacterId())) {
                    linkageExportForAS = true;
                    symbolStr += " linkageIdentifier=\"" + xmlString(characterVariables.get(symbol.getCharacterId())) + "\"";
                }
                if (linkageExportForAS) {
                    symbolStr += " linkageExportForAS=\"true\"";
                }
                symbolStr += ">";
                symbolStr += "<timeline>";
                String itemIcon = null;
                if (symbol instanceof ButtonTag) {
                    itemIcon = "0";
                    symbolStr += "<DOMTimeline name=\"Symbol " + symbol.getCharacterId() + "\" currentFrame=\"0\">";
                    symbolStr += "<layers>";

                    ButtonTag button = (ButtonTag) symbol;
                    List<BUTTONRECORD> records = button.getRecords();
                    String frames[] = {"", "", "", ""};

                    int maxDepth = 0;
                    for (BUTTONRECORD rec : records) {
                        if (rec.placeDepth > maxDepth) {
                            maxDepth = rec.placeDepth;
                        }
                    }
                    for (int i = maxDepth; i >= 1; i--) {
                        symbolStr += "<DOMLayer name=\"Layer " + (maxDepth - i + 1) + "\"";
                        if (i == 1) {
                            symbolStr += " current=\"true\" isSelected=\"true\"";
                        }
                        symbolStr += " color=\"" + randomOutlineColor() + "\">";
                        symbolStr += "<frames>";
                        int lastFrame = 0;
                        loopframes:
                        for (int frame = 1; frame <= 4; frame++) {
                            for (BUTTONRECORD rec : records) {
                                if (rec.placeDepth == i) {
                                    boolean ok = false;
                                    switch (frame) {
                                        case 1:
                                            ok = rec.buttonStateUp;
                                            break;
                                        case 2:
                                            ok = rec.buttonStateOver;
                                            break;
                                        case 3:
                                            ok = rec.buttonStateDown;
                                            break;
                                        case 4:
                                            ok = rec.buttonStateHitTest;
                                            break;
                                    }
                                    if (!ok) {
                                        continue;
                                    }
                                    CXFORMWITHALPHA colorTransformAlpha = null;
                                    int blendMode = 0;
                                    List<FILTER> filters = new ArrayList<>();
                                    if (button instanceof DefineButton2Tag) {
                                        colorTransformAlpha = rec.colorTransform;
                                        if (rec.buttonHasBlendMode) {
                                            blendMode = rec.blendMode;
                                        }
                                        if (rec.buttonHasFilterList) {
                                            filters = rec.filterList;
                                        }
                                    }
                                    CharacterTag character = characters.get(rec.characterId);
                                    MATRIX matrix = rec.placeMatrix;
                                    String recCharStr = "";
                                    if (character instanceof TextTag) {
                                        recCharStr = convertText(null, tags, (TextTag) character, matrix, filters, null);
                                    } else if (character instanceof DefineVideoStreamTag) {
                                        recCharStr = convertVideoInstance(null, matrix, (DefineVideoStreamTag) character, null);
                                    } else {
                                        recCharStr = convertSymbolInstance(null, matrix, null, colorTransformAlpha, false, blendMode, filters, true, null, null, characters.get(rec.characterId), characters, tags);
                                    }
                                    int duration = frame - lastFrame;
                                    lastFrame = frame;
                                    if (duration > 0) {
                                        if (duration > 1) {
                                            symbolStr += "<DOMFrame index=\"";
                                            symbolStr += (frame - duration);
                                            symbolStr += "\"";
                                            symbolStr += " duration=\"" + (duration - 1) + "\"";
                                            symbolStr += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                                            symbolStr += "<elements>";
                                            symbolStr += "</elements>";
                                            symbolStr += "</DOMFrame>";
                                        }
                                        symbolStr += "<DOMFrame index=\"";
                                        symbolStr += (frame - 1);
                                        symbolStr += "\"";
                                        symbolStr += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                                        symbolStr += "<elements>";
                                        symbolStr += recCharStr;
                                        symbolStr += "</elements>";
                                        symbolStr += "</DOMFrame>";
                                    }
                                }
                            }
                        }
                        symbolStr += "</frames>";
                        symbolStr += "</DOMLayer>";
                    }
                    symbolStr += "</layers>";
                    symbolStr += "</DOMTimeline>";
                } else if (symbol instanceof DefineSpriteTag) {
                    DefineSpriteTag sprite = (DefineSpriteTag) symbol;
                    if (sprite.subTags.isEmpty()) { //probably AS2 class
                        continue;
                    }
                    symbolStr += convertTimeline(sprite.spriteId, oneInstanceShapes, backgroundColor, tags, sprite.getSubTags(), characters, "Symbol " + symbol.getCharacterId());
                } else if (symbol instanceof ShapeTag) {
                    itemIcon = "1";
                    ShapeTag shape = (ShapeTag) symbol;
                    symbolStr += "<DOMTimeline name=\"Symbol " + symbol.getCharacterId() + "\" currentFrame=\"0\">";
                    symbolStr += "<layers>";
                    symbolStr += convertShape(characters, null, shape);
                    symbolStr += "</layers>";
                    symbolStr += "</DOMTimeline>";
                }
                symbolStr += "</timeline>";
                symbolStr += "</DOMSymbolItem>";
                symbolStr = prettyFormatXML(symbolStr);
                String symbolFile = "Symbol " + symbol.getCharacterId() + ".xml";
                try {
                    files.put(symbolFile, symbolStr.getBytes("UTF-8"));
                } catch (UnsupportedEncodingException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbLinkStr = "";
                symbLinkStr += "<Include href=\"" + symbolFile + "\"";
                if (itemIcon != null) {
                    symbLinkStr += " itemIcon=\"" + itemIcon + "\"";
                }
                symbLinkStr += " loadImmediate=\"false\" lastModified=\"" + getTimestamp() + "\"/>"; //TODO: itemID=\"518de416-00000341\"
                symbols.add(symbLinkStr);
            } else if (symbol instanceof ImageTag) {
                ImageTag imageTag = (ImageTag) symbol;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                BufferedImage image = imageTag.getImage(tags);
                String format = imageTag.getImageFormat();
                try {
                    ImageIO.write(image, format.toUpperCase(), baos);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbolFile = "bitmap" + symbol.getCharacterId() + "." + imageTag.getImageFormat();
                files.put(symbolFile, baos.toByteArray());
                String mediaLinkStr = "<DOMBitmapItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + baos.toByteArray().length + "\"";
                if (format.equals("png") || format.equals("gif")) {
                    mediaLinkStr += " useImportedJPEGData=\"false\" compressionType=\"lossless\" originalCompressionType=\"lossless\"";
                } else if (format.equals("jpg")) {
                    mediaLinkStr += " isJPEG=\"true\"";
                }
                if (characterClasses.containsKey(symbol.getCharacterId())) {
                    mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterId()) + "\"";
                }
                mediaLinkStr += " quality=\"50\" href=\"" + symbolFile + "\" bitmapDataHRef=\"M " + (media.size() + 1) + " " + getTimestamp() + ".dat\" frameRight=\"" + image.getWidth() + "\" frameBottom=\"" + image.getHeight() + "\"/>\n";
                media.add(mediaLinkStr);

            } else if ((symbol instanceof SoundStreamHeadTypeTag) || (symbol instanceof DefineSoundTag)) {
                int soundFormat = 0;
                int soundRate = 0;
                int soundType = 0;
                int soundSize = 0;
                long soundSampleCount = 0;
                byte soundData[] = new byte[0];
                int rateMap[] = {5, 11, 22, 44};
                String exportFormat = "flv";
                if (symbol instanceof SoundStreamHeadTypeTag) {
                    SoundStreamHeadTypeTag sstream = (SoundStreamHeadTypeTag) symbol;
                    soundFormat = sstream.getSoundFormat();
                    soundRate = sstream.getSoundRate();
                    soundType = sstream.getSoundType();
                    soundSize = sstream.getSoundSize();
                    soundSampleCount = sstream.getSoundSampleCount();
                    boolean found = false;
                    for (Tag t : tags) {
                        if (found && (t instanceof SoundStreamBlockTag)) {
                            SoundStreamBlockTag bl = (SoundStreamBlockTag) t;
                            soundData = bl.getData(SWF.DEFAULT_VERSION);
                            break;
                        }
                        if (t == symbol) {
                            found = true;
                        }
                    }
                } else if (symbol instanceof DefineSoundTag) {
                    DefineSoundTag sound = (DefineSoundTag) symbol;
                    soundFormat = sound.soundFormat;
                    soundRate = sound.soundRate;
                    soundType = sound.soundType;
                    soundData = sound.soundData;
                    soundSize = sound.soundSize;
                    soundSampleCount = sound.soundSampleCount;
                }
                int format = 0;
                int bits = 0;
                if ((soundFormat == DefineSoundTag.FORMAT_ADPCM)
                        || (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN)
                        || (soundFormat == DefineSoundTag.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN)) {
                    if (soundType == 1) { //stereo
                        format += 1;
                    }
                    switch (soundRate) {
                        case 0:
                            format += 2;
                            break;
                        case 1:
                            format += 6;
                            break;
                        case 2:
                            format += 10;
                            break;
                        case 3:
                            format += 14;
                            break;
                    }
                }
                if (soundFormat == DefineSoundTag.FORMAT_SPEEX) {
                    bits = 18;
                }
                if (soundFormat == DefineSoundTag.FORMAT_ADPCM) {
                    SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(soundData), SWF.DEFAULT_VERSION);
                    exportFormat = "wav";
                    try {
                        int adpcmCodeSize = (int) sis.readUB(2);
                        bits = 2 + adpcmCodeSize;
                    } catch (IOException ex) {
                        Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                if (soundFormat == DefineSoundTag.FORMAT_MP3) {
                    exportFormat = "mp3";
                    if (soundType == 0) { //mono
                        format += 1;
                    }
                    format += 4; //quality best
                    SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(soundData), SWF.DEFAULT_VERSION);
                    try {
                        sis.readSI16();
                        MP3FRAME frame = new MP3FRAME(sis);
                        int bitRate = frame.getBitrate();

                        switch (bitRate) {
                            case 8:
                                bits = 6;
                                break;
                            case 16:
                                bits = 7;
                                break;
                            case 20:
                                bits = 8;
                                break;
                            case 24:
                                bits = 9;
                                break;
                            case 32:
                                bits = 10;
                                break;
                            case 48:
                                bits = 11;
                                break;
                            case 56:
                                bits = 12;
                                break;
                            case 64:
                                bits = 13;
                                break;
                            case 80:
                                bits = 14;
                                break;
                            case 112:
                                bits = 15;
                                break;
                            case 128:
                                bits = 16;
                                break;
                            case 160:
                                bits = 17;
                                break;

                        }
                    } catch (IOException ex) {
                        Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                byte data[] = new byte[0];
                try {
                    data = swf.exportSound(symbol);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                if (!exportFormat.equals("flv")) { //FLV import format unsupported
                    String symbolFile = "sound" + symbol.getCharacterId() + "." + exportFormat;
                    files.put(symbolFile, data);
                    String mediaLinkStr = "<DOMSoundItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + data.length + "\"";
                    mediaLinkStr += " href=\"" + symbolFile + "\"";
                    mediaLinkStr += " format=\"";
                    mediaLinkStr += rateMap[soundRate] + "kHz";
                    mediaLinkStr += " " + (soundSize == 1 ? "16bit" : "8bit");
                    mediaLinkStr += " " + (soundType == 1 ? "Stereo" : "Mono");
                    mediaLinkStr += "\"";
                    mediaLinkStr += " exportFormat=\"" + format + "\" exportBits=\"" + bits + "\" sampleCount=\"" + soundSampleCount + "\"";

                    if (characterClasses.containsKey(symbol.getCharacterId())) {
                        mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterId()) + "\"";
                    }

                    if (characterVariables.containsKey(symbol.getCharacterId())) {
                        mediaLinkStr += " linkageExportForAS=\"true\" linkageIdentifier=\"" + xmlString(characterVariables.get(symbol.getCharacterId())) + "\"";
                    }

                    mediaLinkStr += "/>\n";
                    media.add(mediaLinkStr);
                }
            } else if (symbol instanceof DefineVideoStreamTag) {
                DefineVideoStreamTag video = (DefineVideoStreamTag) symbol;
                String videoType = "no media";
                switch (video.codecID) {
                    case 2:
                        videoType = "h263 media";
                        break;
                    case 3:
                        videoType = "screen share media";
                        break;
                    case 4:
                        videoType = "vp6 media";
                        break;
                    case 5:
                        videoType = "vp6 alpha media";
                        break;
                }

                byte data[] = new byte[0];
                try {
                    data = swf.exportMovie(video);
                } catch (IOException ex) {
                    Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                }
                String symbolFile = "movie" + symbol.getCharacterId() + "." + "flv";
                String mediaLinkStr = "";
                if (data.length == 0) { //Video has zero length, this probably means it is "Video - Actionscript-controlled"                    
                    long ts = getTimestamp();
                    String datFileName = "M " + (datfiles.size() + 1) + " " + ts + ".dat";
                    mediaLinkStr = "<DOMVideoItem name=\"" + symbolFile + "\" sourceExternalFilepath=\"./LIBRARY/" + symbolFile + "\" sourceLastImported=\"" + ts + "\" videoDataHRef=\"" + datFileName + "\" channels=\"0\" isSpecial=\"true\" />";
                    //Use the dat file, otherwise it does not work
                    datfiles.put(datFileName, new byte[]{ //Magic numbers, if anybody knows why, please tell me
                        (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xA0, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x78, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x01, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x01,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x59, (byte) 0x40, (byte) 0x18, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFE, (byte) 0xFF,
                        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00
                    });
                } else {
                    files.put(symbolFile, data);
                    mediaLinkStr = "<DOMVideoItem name=\"" + symbolFile + "\" sourceLastImported=\"" + getTimestamp() + "\" externalFileSize=\"" + data.length + "\"";
                    mediaLinkStr += " href=\"" + symbolFile + "\"";
                    mediaLinkStr += " videoType=\"" + videoType + "\"";
                    mediaLinkStr += " fps=\"" + swf.frameRate + "\"";
                    mediaLinkStr += " width=\"" + video.width + "\"";
                    mediaLinkStr += " height=\"" + video.height + "\"";
                    double len = ((double) video.numFrames) / ((double) swf.frameRate);
                    mediaLinkStr += " length=\"" + len + "\"";
                    if (characterClasses.containsKey(symbol.getCharacterId())) {
                        mediaLinkStr += " linkageExportForAS=\"true\" linkageClassName=\"" + characterClasses.get(symbol.getCharacterId()) + "\"";
                    }
                    if (characterVariables.containsKey(symbol.getCharacterId())) {
                        mediaLinkStr += " linkageExportForAS=\"true\" linkageIdentifier=\"" + xmlString(characterVariables.get(symbol.getCharacterId())) + "\"";
                    }
                    mediaLinkStr += "/>\n";
                }
                media.add(mediaLinkStr);
            }

        }
        if (!media.isEmpty()) {
            ret += "<media>";
            for (String m : media) {
                ret += m;
            }
            ret += "</media>";
        }
        if (!symbols.isEmpty()) {
            ret += "<symbols>";
            for (String s : symbols) {
                ret += s;
            }
            ret += "</symbols>";
        }
        return ret;
    }

    private static String prettyFormatXML(String input) {
        int indent = 5;
        try {
            Source xmlInput = new StreamSource(new StringReader(input));
            StringWriter stringWriter = new StringWriter();
            StreamResult xmlOutput = new StreamResult(stringWriter);
            TransformerFactory transformerFactory = TransformerFactory.newInstance();
            transformerFactory.setAttribute("indent-number", indent);
            Transformer transformer = transformerFactory.newTransformer();
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(xmlInput, xmlOutput);
            return xmlOutput.getWriter().toString();
        } catch (Exception e) {
            System.err.println(input);
            Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Pretty print error", e);
            return input;
        }
    }

    private static String convertFrame(boolean shapeTween, HashMap<Integer, CharacterTag> characters, List<Tag> tags, SoundStreamHeadTypeTag soundStreamHead, StartSoundTag startSound, int frame, int duration, String actionScript, String elements) {
        String ret = "";
        DefineSoundTag sound = null;
        if (startSound != null) {
            for (Tag t : tags) {
                if (t instanceof DefineSoundTag) {
                    DefineSoundTag s = (DefineSoundTag) t;
                    if (s.soundId == startSound.soundId) {
                        sound = s;
                        break;
                    }
                }
            }
        }

        ret += "<DOMFrame index=\"" + (frame) + "\"";
        if (duration > 1) {
            ret += " duration=\"" + duration + "\"";
        }
        if (shapeTween) {
            ret += " tweenType=\"shape\" keyMode=\"" + KEY_MODE_SHAPE_TWEEN + "\"";
        } else {
            ret += " keyMode=\"" + KEY_MODE_NORMAL + "\"";
        }
        String soundEnvelopeStr = "";
        if (soundStreamHead != null) {
            ret += " soundName=\"sound" + soundStreamHead.getCharacterId() + "." + soundStreamHead.getExportFormat() + "\"";
            ret += " soundSync=\"stream\"";
            soundEnvelopeStr += "<SoundEnvelope>";
            soundEnvelopeStr += "<SoundEnvelopePoint level0=\"32768\" level1=\"32768\"/>";
            soundEnvelopeStr += "</SoundEnvelope>";
        }
        if (startSound != null && sound != null) {
            ret += " soundName=\"sound" + sound.soundId + "." + sound.getExportFormat() + "\"";
            if (startSound.soundInfo.hasInPoint) {
                ret += " inPoint44=\"" + startSound.soundInfo.inPoint + "\"";
            }
            if (startSound.soundInfo.hasOutPoint) {
                ret += " outPoint44=\"" + startSound.soundInfo.outPoint + "\"";
            }
            if (startSound.soundInfo.hasLoops) {
                if (startSound.soundInfo.loopCount == 32767) {
                    ret += " soundLoopMode=\"loop\"";
                }
                ret += " soundLoop=\"" + startSound.soundInfo.loopCount + "\"";
            }

            if (startSound.soundInfo.syncStop) {
                ret += " soundSync=\"stop\"";
            } else if (startSound.soundInfo.syncNoMultiple) {
                ret += " soundSync=\"start\"";
            };
            soundEnvelopeStr += "<SoundEnvelope>";
            if (startSound.soundInfo.hasEnvelope) {
                for (SOUNDENVELOPE env : startSound.soundInfo.envelopeRecords) {
                    soundEnvelopeStr += "<SoundEnvelopePoint mark44=\"" + env.pos44 + "\" level0=\"" + env.leftLevel + "\" level1=\"" + env.rightLevel + "\"/>";
                }

                if (startSound.soundInfo.envelopeRecords.length == 1
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 0) {
                    ret += " soundEffect=\"left channel\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 1
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 32768) {
                    ret += " soundEffect=\"right channel\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 2
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 0
                        && startSound.soundInfo.envelopeRecords[1].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[1].pos44 == sound.soundSampleCount
                        && startSound.soundInfo.envelopeRecords[1].rightLevel == 32768) {
                    ret += " soundEffect=\"fade left to right\"";
                } else if (startSound.soundInfo.envelopeRecords.length == 2
                        && startSound.soundInfo.envelopeRecords[0].leftLevel == 0
                        && startSound.soundInfo.envelopeRecords[0].pos44 == 0
                        && startSound.soundInfo.envelopeRecords[0].rightLevel == 32768
                        && startSound.soundInfo.envelopeRecords[1].leftLevel == 32768
                        && startSound.soundInfo.envelopeRecords[1].pos44 == sound.soundSampleCount
                        && startSound.soundInfo.envelopeRecords[1].rightLevel == 0) {
                    ret += " soundEffect=\"fade right to left\"";
                } else {
                    ret += " soundEffect=\"custom\"";
                }
                //TODO: fade in, fade out

            } else {
                soundEnvelopeStr += "<SoundEnvelopePoint level0=\"32768\" level1=\"32768\"/>";
            }
            soundEnvelopeStr += "</SoundEnvelope>";
        }
        ret += ">";

        ret += soundEnvelopeStr;
        if (!actionScript.equals("")) {
            ret += "<Actionscript><script><![CDATA[";
            ret += actionScript;
            ret += "]]></script></Actionscript>";
        }
        ret += "<elements>";
        ret += elements;
        ret += "</elements>";
        ret += "</DOMFrame>";
        return ret;
    }

    private static String convertVideoInstance(String instanceName, MATRIX matrix, DefineVideoStreamTag video, CLIPACTIONS clipActions) {
        String ret = "<DOMVideoInstance libraryItemName=\"movie" + video.characterID + ".flv\" frameRight=\"" + (20 * video.width) + "\" frameBottom=\"" + (20 * video.height) + "\"";
        if (instanceName != null) {
            ret += " name=\"" + xmlString(instanceName) + "\"";
        }
        ret += ">";
        ret += "<matrix>";
        ret += convertMatrix(matrix);
        ret += "</matrix>";
        ret += "<transformationPoint>";
        ret += "<Point />";
        ret += "</transformationPoint>";
        ret += "</DOMVideoInstance>";
        return ret;
    }

    private static String convertFrames(String prevStr, String afterStr, List<Integer> oneInstanceShapes, List<Tag> tags, List<Tag> timelineTags, HashMap<Integer, CharacterTag> characters, int depth) {
        String ret = "";
        prevStr += "<frames>";
        int frame = -1;
        String elements = "";
        String lastElements = "";

        int duration = 1;

        CharacterTag character = null;
        MATRIX matrix = null;
        String instanceName = null;
        CXFORM colorTransForm = null;
        CXFORMWITHALPHA colorTransFormAlpha = null;
        boolean cacheAsBitmap = false;
        int blendMode = 0;
        List<FILTER> filters = new ArrayList<>();
        boolean isVisible = true;
        RGBA backGroundColor = null;
        CLIPACTIONS clipActions = null;
        int characterId = -1;
        int ratio = -1;
        boolean shapeTween = false;
        boolean lastShapeTween = false;

        for (Tag t : timelineTags) {
            if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                if (po.getDepth() == depth) {
                    int newCharId = po.getCharacterId();
                    if (newCharId == -1) {
                        newCharId = characterId;
                    }
                    characterId = newCharId;
                    if (characters.containsKey(characterId)) {
                        character = characters.get(characterId);
                        if (po.flagMove()) {
                            MATRIX matrix2 = po.getMatrix();
                            if (matrix2 != null) {
                                matrix = matrix2;
                            }
                            String instanceName2 = po.getInstanceName();
                            if (instanceName2 != null) {
                                instanceName = instanceName2;
                            }
                            CXFORM colorTransForm2 = po.getColorTransform();
                            if (colorTransForm2 != null) {
                                colorTransForm = colorTransForm2;
                            }

                            CXFORMWITHALPHA colorTransFormAlpha2 = po.getColorTransformWithAlpha();
                            if (colorTransFormAlpha2 != null) {
                                colorTransFormAlpha = colorTransFormAlpha2;
                            }

                            CLIPACTIONS clipActions2 = po.getClipActions();
                            if (clipActions2 != null) {
                                clipActions = clipActions2;
                            }
                            if (po.cacheAsBitmap()) {
                                cacheAsBitmap = true;
                            }
                            int blendMode2 = po.getBlendMode();
                            if (blendMode2 > 0) {
                                blendMode = blendMode2;
                            }
                            List<FILTER> filters2 = po.getFilters();
                            if (filters2 != null) {
                                filters = filters2;
                            }
                            int ratio2 = po.getRatio();
                            if (ratio2 > -1) {
                                ratio = ratio2;
                            }
                        } else {
                            matrix = po.getMatrix();
                            instanceName = po.getInstanceName();
                            colorTransForm = po.getColorTransform();
                            colorTransFormAlpha = po.getColorTransformWithAlpha();
                            cacheAsBitmap = po.cacheAsBitmap();
                            blendMode = po.getBlendMode();
                            filters = po.getFilters();
                            ratio = po.getRatio();
                            clipActions = po.getClipActions();
                        }



                    }
                }
            }

            if (t instanceof RemoveTag) {
                RemoveTag rt = (RemoveTag) t;
                if (rt.getDepth() == depth) {
                    character = null;
                    matrix = null;
                    instanceName = null;
                    colorTransForm = null;
                    colorTransFormAlpha = null;
                    cacheAsBitmap = false;
                    blendMode = 0;
                    filters = new ArrayList<>();
                    isVisible = true;
                    backGroundColor = null;
                    characterId = -1;
                    clipActions = null;
                }
            }

            if (t instanceof ShowFrameTag) {
                elements = "";

                if ((character instanceof ShapeTag) && oneInstanceShapes.contains(characterId)) {
                    elements += convertShape(characters, matrix, (ShapeTag) character);
                    shapeTween = false;
                } else if (character != null) {
                    if (character instanceof MorphShapeTag) {
                        MorphShapeTag m = (MorphShapeTag) character;
                        if (ratio == 65535) {
                            elements += convertShape(characters, matrix, 3, m.getEndEdges().shapeRecords, m.getFillStyles().getEndFillStyles(), m.getLineStyles().getEndLineStyles(m.getShapeNum()), true);
                            shapeTween = false;
                        } else {
                            elements += convertShape(characters, matrix, 3, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true);
                            shapeTween = true;
                        }
                    } else {
                        shapeTween = false;
                        if (character instanceof TextTag) {
                            elements += convertText(instanceName, tags, (TextTag) character, matrix, filters, clipActions);
                        } else if (character instanceof DefineVideoStreamTag) {
                            elements += convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions);
                        } else {
                            elements += convertSymbolInstance(instanceName, matrix, colorTransForm, colorTransFormAlpha, cacheAsBitmap, blendMode, filters, isVisible, backGroundColor, clipActions, character, characters, tags);
                        }
                    }
                }

                frame++;
                if (!elements.equals(lastElements) && frame > 0) {
                    ret += convertFrame(lastShapeTween, characters, tags, null, null, frame - duration, duration, "", lastElements);
                    duration = 1;
                } else if (frame == 0) {
                    duration = 1;
                } else {
                    duration++;
                }

                lastShapeTween = shapeTween;
                lastElements = elements;
            }
        }
        if (!lastElements.equals("")) {
            frame++;
            ret += convertFrame(lastShapeTween, characters, tags, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements);
        }
        afterStr = "</frames>" + afterStr;
        if (!ret.equals("")) {
            ret = prevStr + ret + afterStr;
        }
        return ret;
    }

    public static String convertActionScriptLayer(int spriteId, List<Tag> tags, List<Tag> timeLineTags, String backgroundColor) {
        String ret = "";

        String script = "";
        int duration = 0;
        int frame = 0;
        for (Tag t : tags) {
            if (t instanceof DoInitActionTag) {
                DoInitActionTag dia = (DoInitActionTag) t;
                if (dia.spriteId == spriteId) {
                    script += convertActionScript(dia);
                }
            }
        }
        if (!script.equals("")) {
            script = "#initclip\r\n" + script + "#endinitclip\r\n";
        }
        for (Tag t : timeLineTags) {
            if (t instanceof DoActionTag) {
                DoActionTag da = (DoActionTag) t;
                script += convertActionScript(da);
            }
            if (t instanceof ShowFrameTag) {

                if (script.equals("")) {
                    duration++;
                } else {
                    if (duration > 0) {
                        ret += "<DOMFrame index=\"" + (frame - duration) + "\"";
                        if (duration > 1) {
                            ret += " duration=\"" + duration + "\"";
                        }
                        ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                        ret += "<elements>";
                        ret += "</elements>";
                        ret += "</DOMFrame>";
                    }
                    ret += "<DOMFrame index=\"" + frame + "\"";
                    ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                    ret += "<Actionscript><script><![CDATA[";
                    ret += script;
                    ret += "]]></script></Actionscript>";
                    ret += "<elements>";
                    ret += "</elements>";
                    ret += "</DOMFrame>";
                    script = "";
                    duration = 0;
                }
                frame++;
            }
        }
        if (!ret.equals("")) {
            ret = "<DOMLayer name=\"Script Layer\" color=\"" + randomOutlineColor() + "\">"
                    + "<frames>"
                    + ret
                    + "</frames>"
                    + "</DOMLayer>";
        }
        return ret;
    }

    public static String convertLabelsLayer(int spriteId, List<Tag> tags, List<Tag> timeLineTags, String backgroundColor) {
        String ret = "";
        int duration = 0;
        int frame = 0;
        String frameLabel = "";
        boolean isAnchor = false;
        for (Tag t : timeLineTags) {
            if (t instanceof FrameLabelTag) {
                FrameLabelTag fl = (FrameLabelTag) t;
                frameLabel = fl.getLabelName();
                isAnchor = fl.isNamedAnchor();
            }
            if (t instanceof ShowFrameTag) {

                if (frameLabel.equals("")) {
                    duration++;
                } else {
                    if (duration > 0) {
                        ret += "<DOMFrame index=\"" + (frame - duration) + "\"";
                        if (duration > 1) {
                            ret += " duration=\"" + duration + "\"";
                        }
                        ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">";
                        ret += "<elements>";
                        ret += "</elements>";
                        ret += "</DOMFrame>";
                    }
                    ret += "<DOMFrame index=\"" + frame + "\"";
                    ret += " keyMode=\"" + KEY_MODE_NORMAL + "\"";
                    ret += " name=\"" + frameLabel + "\"";
                    if (isAnchor) {
                        ret += " labelType=\"anchor\" bookmark=\"true\"";
                    } else {
                        ret += " labelType=\"name\"";
                    }
                    ret += ">";
                    ret += "<elements>";
                    ret += "</elements>";
                    ret += "</DOMFrame>";
                    frameLabel = "";
                    duration = 0;
                }
                frame++;
            }
        }
        if (!ret.equals("")) {
            ret = "<DOMLayer name=\"Labels Layer\" color=\"" + randomOutlineColor() + "\">"
                    + "<frames>"
                    + ret
                    + "</frames>"
                    + "</DOMLayer>";
        }
        return ret;
    }

    public static String convertSoundLayer(int layerIndex, String backgroundColor, HashMap<Integer, CharacterTag> characters, List<Tag> tags, List<Tag> timeLineTags) {
        String ret = "";
        StartSoundTag lastStartSound = null;
        SoundStreamHeadTypeTag lastSoundStreamHead = null;
        StartSoundTag startSound = null;
        SoundStreamHeadTypeTag soundStreamHead = null;
        int duration = 1;
        int frame = 0;
        for (Tag t : timeLineTags) {
            if (t instanceof StartSoundTag) {
                startSound = (StartSoundTag) t;
            }
            if (t instanceof SoundStreamHeadTypeTag) {
                soundStreamHead = (SoundStreamHeadTypeTag) t;
            }
            if (t instanceof ShowFrameTag) {
                if (soundStreamHead != null || startSound != null) {
                    if (lastSoundStreamHead != null || lastStartSound != null) {
                        ret += convertFrame(false, characters, tags, lastSoundStreamHead, lastStartSound, frame, duration, "", "");
                    }
                    frame += duration;
                    duration = 1;
                    lastSoundStreamHead = soundStreamHead;
                    lastStartSound = startSound;
                    soundStreamHead = null;
                    startSound = null;
                } else {
                    duration++;
                }
            }
        }
        if (lastSoundStreamHead != null || lastStartSound != null) {
            if (frame < 0) {
                frame = 0;
                duration = 1;
            }
            ret += convertFrame(false, characters, tags, lastSoundStreamHead, lastStartSound, frame, duration, "", "");
        }
        if (!ret.equals("")) {
            ret = "<DOMLayer name=\"Layer " + layerIndex + "\" color=\"" + randomOutlineColor() + "\">"
                    + "<frames>" + ret + "</frames>"
                    + "</DOMLayer>";
        }
        return ret;
    }

    private static String randomOutlineColor() {
        RGB outlineColor = new RGB();
        Random rnd = new Random();
        do {
            outlineColor.red = rnd.nextInt(256);
            outlineColor.green = rnd.nextInt(256);
            outlineColor.blue = rnd.nextInt(256);
        } while ((outlineColor.red + outlineColor.green + outlineColor.blue) / 3 < 128);
        return outlineColor.toHexRGB();
    }

    public static String convertTimeline(int spriteId, List<Integer> oneInstanceShapes, String backgroundColor, List<Tag> tags, List<Tag> timelineTags, HashMap<Integer, CharacterTag> characters, String name) {
        String ret = "";
        ret += "<DOMTimeline name=\"" + name + "\">";
        ret += "<layers>";

        ret += convertLabelsLayer(spriteId, tags, timelineTags, backgroundColor);
        ret += convertActionScriptLayer(spriteId, tags, timelineTags, backgroundColor);

        int layerCount = getLayerCount(timelineTags);
        Stack<Integer> parentLayers = new Stack<>();
        int index = 0;
        for (int d = layerCount; d >= 1; d--, index++) {
            for (Tag t : timelineTags) {
                if (t instanceof PlaceObjectTypeTag) {
                    PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                    if (po.getClipDepth() == d) {
                        for (int m = po.getDepth(); m < po.getClipDepth(); m++) {
                            parentLayers.push(index);
                        }

                        ret += "<DOMLayer name=\"Layer " + (index + 1) + "\" color=\"" + randomOutlineColor() + "\" ";
                        ret += " layerType=\"mask\" locked=\"true\"";
                        ret += ">";
                        ret += convertFrames("", "", oneInstanceShapes, tags, timelineTags, characters, po.getDepth());
                        ret += "</DOMLayer>";
                        index++;
                        break;
                    }
                }
            }


            boolean hasClipDepth = false;
            for (Tag t : timelineTags) {
                if (t instanceof PlaceObjectTypeTag) {
                    PlaceObjectTypeTag po = (PlaceObjectTypeTag) t;
                    if (po.getDepth() == d) {
                        if (po.getClipDepth() != -1) {
                            hasClipDepth = true;
                            break;
                        }
                    }
                }
            }
            if (hasClipDepth) {
                index--;
                continue;
            }
            int parentLayer = -1;
            if (!parentLayers.isEmpty()) {
                parentLayer = parentLayers.pop();
            }
            String layerPrev = "";

            layerPrev += "<DOMLayer name=\"Layer " + (index + 1) + "\" color=\"" + randomOutlineColor() + "\" ";
            if (d == 1) {
                layerPrev += " current=\"true\" isSelected=\"true\"";
            }
            if (parentLayer != -1) {
                if (parentLayer != d) {
                    layerPrev += " parentLayerIndex=\"" + (parentLayer) + "\" locked=\"true\"";
                }
            }
            layerPrev += ">";
            String layerAfter = "</DOMLayer>";
            String cf = convertFrames(layerPrev, layerAfter, oneInstanceShapes, tags, timelineTags, characters, d);
            if (cf.equals("")) {
                index--;
            }
            ret += cf;
        }

        int soundLayerIndex = layerCount;
        layerCount++;
        ret += convertSoundLayer(soundLayerIndex, backgroundColor, characters, tags, timelineTags);
        ret += "</layers>";
        ret += "</DOMTimeline>";
        return ret;
    }

    private static void writeFile(AbortRetryIgnoreHandler handler, final byte data[], final String file) throws IOException {
        new RetryTask(new RunnableIOEx() {
            @Override
            public void run() throws IOException {
                try (FileOutputStream fos = new FileOutputStream(file)) {
                    fos.write(data);
                }
            }
        }, handler).run();
    }

    private static Map<Integer, String> getCharacterClasses(List<Tag> tags) {
        Map<Integer, String> ret = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof SymbolClassTag) {
                SymbolClassTag sc = (SymbolClassTag) t;
                for (int i = 0; i < sc.tagIDs.length; i++) {
                    if (!ret.containsKey(sc.tagIDs[i]) && !ret.containsValue(sc.classNames[i])) {
                        ret.put(sc.tagIDs[i], sc.classNames[i]);
                    }
                }
            }
        }
        return ret;
    }

    private static Map<Integer, String> getCharacterVariables(List<Tag> tags) {
        Map<Integer, String> ret = new HashMap<>();
        for (Tag t : tags) {
            if (t instanceof ExportAssetsTag) {
                ExportAssetsTag ea = (ExportAssetsTag) t;
                for (int i = 0; i < ea.tags.size(); i++) {
                    if (!ret.containsKey(ea.tags.get(i))) {
                        ret.put(ea.tags.get(i), ea.names.get(i));
                    }
                }
            }
        }
        return ret;
    }

    public static String convertText(String instanceName, List<Tag> tags, TextTag tag, MATRIX matrix, List<FILTER> filters, CLIPACTIONS clipActions) {
        String ret = "";
        if (matrix == null) {
            matrix = new MATRIX();
        }
        CSMTextSettingsTag csmts = null;
        String filterStr = "";
        if (filters != null) {
            filterStr += "<filters>";
            for (FILTER f : filters) {
                filterStr += convertFilter(f);
            }
            filterStr += "</filters>";
        }

        for (Tag t : tags) {
            if (t instanceof CSMTextSettingsTag) {
                CSMTextSettingsTag c = (CSMTextSettingsTag) t;
                if (c.textID == tag.getCharacterId()) {
                    csmts = c;
                    break;
                }
            }
        }
        String fontRenderingMode = "standard";
        String antiAlias = "";
        if (csmts != null) {
            if (csmts.thickness == 0 & csmts.sharpness == 0) {
                fontRenderingMode = null;
            } else {
                fontRenderingMode = "customThicknessSharpness";
            }
            antiAlias = " antiAliasSharpness=\"" + doubleToString(csmts.sharpness) + "\" antiAliasThickness=\"" + doubleToString(csmts.thickness) + "\"";
        }
        String matStr = "";
        matStr += "<matrix>";
        RECT bounds = tag.getBounds();
        if ((tag instanceof DefineTextTag) || (tag instanceof DefineText2Tag)) {
            MATRIX textMatrix = tag.getTextMatrix();
            matrix = matrix.merge(textMatrix);
        } else {
            matrix.translateX += bounds.Xmin;
            matrix.translateY += bounds.Ymin;

            //I do not know why, but there is (always?) 2px difference
            matrix.translateX += 2 * 20;
            matrix.translateY += 2 * 20;
        }
        matStr += convertMatrix(matrix);
        matStr += "</matrix>";
        if ((tag instanceof DefineTextTag) || (tag instanceof DefineText2Tag)) {
            List<TEXTRECORD> textRecords = new ArrayList<>();
            if (tag instanceof DefineTextTag) {
                textRecords = ((DefineTextTag) tag).textRecords;
            } else if (tag instanceof DefineText2Tag) {
                textRecords = ((DefineText2Tag) tag).textRecords;
            }


            looprec:
            for (TEXTRECORD rec : textRecords) {
                if (rec.styleFlagsHasFont) {
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            FontTag ft = (FontTag) t;
                            if (ft.getFontId() == rec.fontId) {
                                if (ft.isSmall()) {
                                    fontRenderingMode = "bitmap";
                                    break looprec;
                                }
                            }
                        }
                    }
                }
            }

            ret += "<DOMStaticText";
            if (fontRenderingMode != null) {
                ret += " fontRenderingMode=\"" + fontRenderingMode + "\"";
            }
            if (instanceName != null) {
                ret += " instanceName=\"" + xmlString(instanceName) + "\"";
            }
            ret += antiAlias;
            Map<String, Object> attrs = TextTag.getTextRecordsAttributes(textRecords, tags);

            ret += " width=\"" + tag.getBounds().getWidth() / 2 + "\" height=\"" + tag.getBounds().getHeight() + "\" autoExpand=\"true\" isSelectable=\"false\">";
            ret += matStr;

            ret += "<textRuns>";
            int fontId = -1;
            FontTag font = null;
            String fontName = null;
            String psFontName = null;
            String availableFonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
            int textHeight = -1;
            RGB textColor = null;
            RGBA textColorA = null;
            boolean newline = false;
            boolean firstRun = true;
            @SuppressWarnings("unchecked")
            List<Integer> leftMargins = (List<Integer>) attrs.get("allLeftMargins");
            @SuppressWarnings("unchecked")
            List<Integer> letterSpacings = (List<Integer>) attrs.get("allLetterSpacings");
            for (int r = 0; r < textRecords.size(); r++) {
                TEXTRECORD rec = textRecords.get(r);
                if (rec.styleFlagsHasColor) {
                    if (tag instanceof DefineTextTag) {
                        textColor = rec.textColor;
                    } else {
                        textColorA = rec.textColorA;
                    }
                }
                if (rec.styleFlagsHasFont) {
                    fontId = rec.fontId;
                    fontName = null;
                    textHeight = rec.textHeight;
                    font = null;
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            if (((FontTag) t).getFontId() == fontId) {
                                font = (FontTag) t;
                            }
                        }
                        if (t instanceof DefineFontNameTag) {
                            if (((DefineFontNameTag) t).fontId == fontId) {
                                fontName = ((DefineFontNameTag) t).fontName;
                            }
                        }
                    }
                    if ((fontName == null) && (font != null)) {
                        fontName = font.getFontName(tags);
                    }
                    psFontName = fontName;
                    if (fontName != null) {
                        for (String avFont : availableFonts) {
                            if (avFont.equals(fontName)) {
                                Font f = new Font(fontName, 0, 10);
                                psFontName = f.getPSName();
                            }
                        }
                    }
                }
                newline = false;
                if (!firstRun && rec.styleFlagsHasYOffset) {
                    newline = true;
                }
                firstRun = false;
                if (font != null) {
                    ret += "<DOMTextRun>";
                    ret += "<characters>" + xmlString((newline ? "\r" : "") + rec.getText(tags, font)) + "</characters>";
                    ret += "<textAttrs>";

                    ret += "<DOMTextAttrs aliasText=\"false\" rotation=\"true\" size=\"" + twipToPixel(textHeight) + "\" bitmapSize=\"" + textHeight + "\"";
                    ret += " letterSpacing=\"" + doubleToString(twipToPixel(letterSpacings.get(r))) + "\"";
                    ret += " indent=\"" + doubleToString(twipToPixel((int) attrs.get("indent"))) + "\"";
                    ret += " leftMargin=\"" + doubleToString(twipToPixel(leftMargins.get(r))) + "\"";
                    ret += " lineSpacing=\"" + doubleToString(twipToPixel((int) attrs.get("lineSpacing"))) + "\"";
                    ret += " rightMargin=\"" + doubleToString(twipToPixel((int) attrs.get("rightMargin"))) + "\"";

                    if (textColor != null) {
                        ret += " fillColor=\"" + textColor.toHexRGB() + "\"";
                    } else if (textColorA != null) {
                        ret += " fillColor=\"" + textColorA.toHexRGB() + "\" alpha=\"" + textColorA.getAlphaFloat() + "\"";
                    }
                    ret += " face=\"" + psFontName + "\"";
                    ret += "/>";

                    ret += "</textAttrs>";
                    ret += "</DOMTextRun>";
                }
            }
            ret += "</textRuns>";
            ret += filterStr;
            ret += "</DOMStaticText>";
        } else if (tag instanceof DefineEditTextTag) {
            DefineEditTextTag det = (DefineEditTextTag) tag;
            String tagName;
            for (Tag t : tags) {
                if (t instanceof FontTag) {
                    FontTag ft = (FontTag) t;
                    if (ft.getFontId() == det.fontId) {
                        if (ft.isSmall()) {
                            fontRenderingMode = "bitmap";
                            break;
                        }
                    }
                }
            }
            if (!det.useOutlines) {
                fontRenderingMode = "device";
            }
            if (det.wasStatic) {
                tagName = "DOMStaticText";
            } else if (det.readOnly) {
                tagName = "DOMDynamicText";
            } else {
                tagName = "DOMInputText";
            }
            ret += "<" + tagName;
            if (fontRenderingMode != null) {
                ret += " fontRenderingMode=\"" + fontRenderingMode + "\"";
            }
            if (instanceName != null) {
                ret += " name=\"" + xmlString(instanceName) + "\"";
            }
            ret += antiAlias;
            double width = twipToPixel(bounds.getWidth());
            double height = twipToPixel(bounds.getHeight());
            //There is usually 4px difference between width/height and XML width/height
            //If somebody knows what that means, tell me
            double padding = 2;
            width -= 2 * padding;
            height -= 2 * padding;
            if (det.hasLayout) {
                width -= twipToPixel(det.rightMargin);
                width -= twipToPixel(det.leftMargin);
            }
            ret += " width=\"" + width + "\"";
            ret += " height=\"" + height + "\"";
            if (det.border) {
                ret += "  border=\"true\"";
            }
            if (det.html) {
                ret += " renderAsHTML=\"true\"";
            }
            if (det.noSelect) {
                ret += " isSelectable=\"false\"";
            }
            if (det.multiline && det.wordWrap) {
                ret += " lineType=\"multiline\"";
            } else if (det.multiline && (!det.wordWrap)) {
                ret += " lineType=\"multiline no wrap\"";
            } else if (det.password) {
                ret += " lineType=\"password\"";
            }
            if (det.hasMaxLength) {
                ret += " maxCharacters=\"" + det.maxLength + "\"";
            }
            if (!det.variableName.equals("")) {
                ret += " variableName=\"" + det.variableName + "\"";
            }
            ret += ">";
            ret += matStr;
            ret += "<textRuns>";
            if (det.hasText) {
                if (det.html) {
                    ret += convertHTMLText(tags, det, det.initialText);
                } else {
                    ret += "<DOMTextRun>";
                    ret += "<characters>" + xmlString(det.initialText) + "</characters>";
                    int leftMargin = -1;
                    int rightMargin = -1;
                    int indent = -1;
                    int lineSpacing = -1;
                    String alignment = null;
                    boolean italic = false;
                    boolean bold = false;
                    String fontFace = null;
                    int size = -1;
                    RGBA textColor = null;
                    if (det.hasTextColor) {
                        textColor = det.textColor;
                    }
                    if (det.hasFont) {
                        String fontName = null;
                        FontTag ft = null;
                        for (Tag u : tags) {
                            if (u instanceof DefineFontNameTag) {
                                if (((DefineFontNameTag) u).fontId == det.fontId) {
                                    fontName = ((DefineFontNameTag) u).fontName;
                                }
                            }
                            if (u instanceof FontTag) {
                                if (((FontTag) u).getFontId() == det.fontId) {
                                    ft = (FontTag) u;
                                }
                            }
                            if (fontName != null && ft != null) {
                                break;
                            }
                        }
                        if (ft != null) {
                            if (fontName == null) {
                                fontName = ft.getFontName(tags);
                            }
                            italic = ft.isItalic();
                            bold = ft.isBold();
                            size = det.fontHeight;
                            fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                        }
                    }
                    if (det.hasLayout) {
                        leftMargin = det.leftMargin;
                        rightMargin = det.rightMargin;
                        indent = det.indent;
                        lineSpacing = det.leading;
                        String alignNames[] = {"left", "right", "center", "justify"};
                        alignment = alignNames[det.align];
                    }
                    ret += "<textAttrs>";
                    ret += "<DOMTextAttrs";
                    if (alignment != null) {
                        ret += " alignment=\"" + alignment + "\"";
                    }
                    ret += " rotation=\"true\""; //?
                    if (indent > -1) {
                        ret += " indent=\"" + twipToPixel(indent) + "\"";
                    }
                    if (leftMargin > -1) {
                        ret += " leftMargin=\"" + twipToPixel(leftMargin) + "\"";
                    }
                    if (lineSpacing > -1) {
                        ret += " lineSpacing=\"" + twipToPixel(lineSpacing) + "\"";
                    }
                    if (rightMargin > -1) {
                        ret += " rightMargin=\"" + twipToPixel(rightMargin) + "\"";
                    }
                    if (size > -1) {
                        ret += " size=\"" + twipToPixel(size) + "\"";
                        ret += " bitmapSize=\"" + size + "\"";
                    }
                    if (fontFace != null) {
                        ret += " face=\"" + fontFace + "\"";
                    }
                    if (textColor != null) {
                        ret += " fillColor=\"" + textColor.toHexRGB() + "\" alpha=\"" + textColor.getAlphaFloat() + "\"";
                    }
                    ret += "/>";
                    ret += "</textAttrs>";
                    ret += "</DOMTextRun>";
                }
            }
            ret += "</textRuns>";
            ret += filterStr;
            ret += "</" + tagName + ">";
        }
        return ret;
    }

    public static void convertSWF(AbortRetryIgnoreHandler handler, SWF swf, String swfFileName, String outfile, boolean compressed, String generator, String generatorVerName, String generatorVersion, boolean parallel) throws IOException {
        File file = new File(outfile);
        File outDir = file.getParentFile();
        if (!outDir.exists()) {
            if (!outDir.mkdirs()) {
                if (!outDir.exists()) {
                    throw new IOException("cannot create directory " + outDir);
                }
            }
        }
        String domDocument = "";
        String baseName = swfFileName;
        File f = new File(baseName);
        baseName = f.getName();
        if (baseName.contains(".")) {
            baseName = baseName.substring(0, baseName.lastIndexOf("."));
        }
        final HashMap<String, byte[]> files = new HashMap<>();
        final HashMap<String, byte[]> datfiles = new HashMap<>();
        HashMap<Integer, CharacterTag> characters = getCharacters(swf.tags);
        List<Integer> oneInstaceShapes = getOneInstanceShapes(swf.tags, characters);
        Map<Integer, String> characterClasses = getCharacterClasses(swf.tags);
        Map<Integer, String> characterVariables = getCharacterVariables(swf.tags);

        FileAttributesTag fa = null;
        for (Tag t : swf.tags) {
            if (t instanceof FileAttributesTag) {
                fa = (FileAttributesTag) t;
            }
        }

        boolean useAS3 = false;
        boolean useNetwork = false;
        if (fa != null) {
            useAS3 = fa.actionScript3;
            useNetwork = fa.useNetwork;
        }
        String backgroundColor = "#ffffff";
        for (Tag t : swf.tags) {
            if (t instanceof SetBackgroundColorTag) {
                SetBackgroundColorTag sbc = (SetBackgroundColorTag) t;
                backgroundColor = sbc.backgroundColor.toHexRGB();
            }
        }
        domDocument += "<DOMDocument xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://ns.adobe.com/xfl/2008/\" currentTimeline=\"1\" xflVersion=\"2.2\" creatorInfo=\"" + generator + "\" platform=\"Windows\" versionInfo=\"Saved by " + generatorVerName + "\" majorVersion=\"" + generatorVersion + "\" buildNumber=\"\" nextSceneIdentifier=\"2\" playOptionsPlayLoop=\"false\" playOptionsPlayPages=\"false\" playOptionsPlayFrameActions=\"false\" autoSaveHasPrompted=\"true\"";
        domDocument += " backgroundColor=\"" + backgroundColor + "\"";
        domDocument += " frameRate=\"" + swf.frameRate + "\"";

        double width = twipToPixel(swf.displayRect.getWidth());
        double height = twipToPixel(swf.displayRect.getHeight());
        if (Double.compare(width, 550) != 0) {
            domDocument += " width=\"" + doubleToString(width) + "\"";
        }
        if (Double.compare(height, 400) != 0) {
            domDocument += " height=\"" + doubleToString(height) + "\"";
        }
        domDocument += ">";

        domDocument += convertLibrary(swf, characterVariables, characterClasses, oneInstaceShapes, backgroundColor, swf.tags, characters, files, datfiles);
        domDocument += "<timelines>";
        domDocument += convertTimeline(0, oneInstaceShapes, backgroundColor, swf.tags, swf.tags, characters, "Scene 1");
        domDocument += "</timelines>";
        domDocument += "</DOMDocument>";
        domDocument = prettyFormatXML(domDocument);

        for (Tag t : swf.tags) {
            if (t instanceof DoInitActionTag) {
                DoInitActionTag dia = (DoInitActionTag) t;
                int chid = dia.getCharacterId();
                if (characters.containsKey(chid)) {
                    if (characters.get(chid) instanceof DefineSpriteTag) {
                        DefineSpriteTag sprite = (DefineSpriteTag) characters.get(chid);
                        if (sprite.subTags.isEmpty()) {
                            String data = convertActionScript(dia);
                            String expPath = dia.getExportName();
                            final String prefix = "__Packages.";
                            if (expPath.startsWith(prefix)) {
                                expPath = expPath.substring(prefix.length());
                            }
                            String expDir = "";
                            if (expPath.contains(".")) {
                                expDir = expPath.substring(0, expPath.lastIndexOf("."));
                                expDir = expDir.replace(".", File.separator);
                            }
                            expPath = expPath.replace(".", File.separator);
                            File cdir = new File(outDir.getAbsolutePath() + File.separator + expDir);
                            if (!cdir.exists()) {
                                if (!cdir.mkdirs()) {
                                    if (!cdir.exists()) {
                                        throw new IOException("cannot create directory " + cdir);
                                    }
                                }
                            }
                            try {
                                writeFile(handler, data.getBytes("UTF-8"), outDir.getAbsolutePath() + File.separator + expPath + ".as");
                            } catch (UnsupportedEncodingException ex) {
                                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                    }
                }
            }
        }

        String publishSettings = "<flash_profiles>\n"
                + "<flash_profile version=\"1.0\" name=\"Default\" current=\"true\">\n"
                + "  <PublishFormatProperties enabled=\"true\">\n"
                + "    <defaultNames>1</defaultNames>\n"
                + "    <flash>1</flash>\n"
                + "    <projectorWin>0</projectorWin>\n"
                + "    <projectorMac>0</projectorMac>\n"
                + "    <html>1</html>\n"
                + "    <gif>0</gif>\n"
                + "    <jpeg>0</jpeg>\n"
                + "    <png>0</png>\n"
                + "    <qt>0</qt>\n"
                + "    <rnwk>0</rnwk>\n"
                + "    <swc>0</swc>\n"
                + "    <flashDefaultName>1</flashDefaultName>\n"
                + "    <projectorWinDefaultName>1</projectorWinDefaultName>\n"
                + "    <projectorMacDefaultName>1</projectorMacDefaultName>\n"
                + "    <htmlDefaultName>1</htmlDefaultName>\n"
                + "    <gifDefaultName>1</gifDefaultName>\n"
                + "    <jpegDefaultName>1</jpegDefaultName>\n"
                + "    <pngDefaultName>1</pngDefaultName>\n"
                + "    <qtDefaultName>1</qtDefaultName>\n"
                + "    <rnwkDefaultName>1</rnwkDefaultName>\n"
                + "    <swcDefaultName>1</swcDefaultName>\n"
                + "    <flashFileName>" + baseName + ".swf</flashFileName>\n"
                + "    <projectorWinFileName>" + baseName + ".exe</projectorWinFileName>\n"
                + "    <projectorMacFileName>" + baseName + ".app</projectorMacFileName>\n"
                + "    <htmlFileName>" + baseName + ".html</htmlFileName>\n"
                + "    <gifFileName>" + baseName + ".gif</gifFileName>\n"
                + "    <jpegFileName>" + baseName + ".jpg</jpegFileName>\n"
                + "    <pngFileName>" + baseName + ".png</pngFileName>\n"
                + "    <qtFileName>" + baseName + ".mov</qtFileName>\n"
                + "    <rnwkFileName>" + baseName + ".smil</rnwkFileName>\n"
                + "    <swcFileName>" + baseName + ".swc</swcFileName>\n"
                + "  </PublishFormatProperties>\n"
                + "  <PublishHtmlProperties enabled=\"true\">\n"
                + "    <VersionDetectionIfAvailable>0</VersionDetectionIfAvailable>\n"
                + "    <VersionInfo>12,0,0,0;11,2,0,0;11,1,0,0;10,3,0,0;10,2,153,0;10,1,52,0;9,0,124,0;8,0,24,0;7,0,14,0;6,0,79,0;5,0,58,0;4,0,32,0;3,0,8,0;2,0,1,12;1,0,0,1;</VersionInfo>\n"
                + "    <UsingDefaultContentFilename>1</UsingDefaultContentFilename>\n"
                + "    <UsingDefaultAlternateFilename>1</UsingDefaultAlternateFilename>\n"
                + "    <ContentFilename>" + baseName + "_content.html</ContentFilename>\n"
                + "    <AlternateFilename>" + baseName + "_alternate.html</AlternateFilename>\n"
                + "    <UsingOwnAlternateFile>0</UsingOwnAlternateFile>\n"
                + "    <OwnAlternateFilename></OwnAlternateFilename>\n"
                + "    <Width>" + width + "</Width>\n"
                + "    <Height>" + height + "</Height>\n"
                + "    <Align>0</Align>\n"
                + "    <Units>0</Units>\n"
                + "    <Loop>1</Loop>\n"
                + "    <StartPaused>0</StartPaused>\n"
                + "    <Scale>0</Scale>\n"
                + "    <HorizontalAlignment>1</HorizontalAlignment>\n"
                + "    <VerticalAlignment>1</VerticalAlignment>\n"
                + "    <Quality>4</Quality>\n"
                + "    <DeblockingFilter>0</DeblockingFilter>\n"
                + "    <WindowMode>0</WindowMode>\n"
                + "    <DisplayMenu>1</DisplayMenu>\n"
                + "    <DeviceFont>0</DeviceFont>\n"
                + "    <TemplateFileName></TemplateFileName>\n"
                + "    <showTagWarnMsg>1</showTagWarnMsg>\n"
                + "  </PublishHtmlProperties>\n"
                + "  <PublishFlashProperties enabled=\"true\">\n"
                + "    <TopDown></TopDown>\n"
                + "    <FireFox></FireFox>\n"
                + "    <Report>0</Report>\n"
                + "    <Protect>0</Protect>\n"
                + "    <OmitTraceActions>0</OmitTraceActions>\n"
                + "    <Quality>80</Quality>\n"
                + "    <DeblockingFilter>0</DeblockingFilter>\n"
                + "    <StreamFormat>0</StreamFormat>\n"
                + "    <StreamCompress>7</StreamCompress>\n"
                + "    <EventFormat>0</EventFormat>\n"
                + "    <EventCompress>7</EventCompress>\n"
                + "    <OverrideSounds>0</OverrideSounds>\n"
                + "    <Version>15</Version>\n"
                + "    <ExternalPlayer>FlashPlayer11.2</ExternalPlayer>\n"
                + "    <ActionScriptVersion>" + (useAS3 ? "3" : "2") + "</ActionScriptVersion>\n"
                + "    <PackageExportFrame>1</PackageExportFrame>\n"
                + "    <PackagePaths></PackagePaths>\n"
                + "    <AS3PackagePaths>.</AS3PackagePaths>\n"
                + "    <AS3ConfigConst>CONFIG::FLASH_AUTHORING=&quot;true&quot;;</AS3ConfigConst>\n"
                + "    <DebuggingPermitted>0</DebuggingPermitted>\n"
                + "    <DebuggingPassword></DebuggingPassword>\n"
                + "    <CompressMovie>" + (swf.compressed ? "1" : "0") + "</CompressMovie>\n"
                + "    <CompressionType>" + (swf.lzma ? "1" : "0") + "</CompressionType>\n"
                + "    <InvisibleLayer>1</InvisibleLayer>\n"
                + "    <DeviceSound>0</DeviceSound>\n"
                + "    <StreamUse8kSampleRate>0</StreamUse8kSampleRate>\n"
                + "    <EventUse8kSampleRate>0</EventUse8kSampleRate>\n"
                + "    <UseNetwork>" + (useNetwork ? 1 : 0) + "</UseNetwork>\n"
                + "    <DocumentClass>" + xmlString(characterClasses.containsKey(0) ? characterClasses.get(0) : "") + "</DocumentClass>\n"
                + "    <AS3Strict>2</AS3Strict>\n"
                + "    <AS3Coach>4</AS3Coach>\n"
                + "    <AS3AutoDeclare>4096</AS3AutoDeclare>\n"
                + "    <AS3Dialect>AS3</AS3Dialect>\n"
                + "    <AS3ExportFrame>1</AS3ExportFrame>\n"
                + "    <AS3Optimize>1</AS3Optimize>\n"
                + "    <ExportSwc>0</ExportSwc>\n"
                + "    <ScriptStuckDelay>15</ScriptStuckDelay>\n"
                + "    <IncludeXMP>1</IncludeXMP>\n"
                + "    <HardwareAcceleration>0</HardwareAcceleration>\n"
                + "    <AS3Flags>4102</AS3Flags>\n"
                + "    <DefaultLibraryLinkage>rsl</DefaultLibraryLinkage>\n"
                + "    <RSLPreloaderMethod>wrap</RSLPreloaderMethod>\n"
                + "    <RSLPreloaderSWF>$(AppConfig)/ActionScript 3.0/rsls/loader_animation.swf</RSLPreloaderSWF>\n"
                + "    <LibraryPath>\n"
                + "      <library-path-entry>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs</swc-path>\n"
                + "        <linkage>merge</linkage>\n"
                + "      </library-path-entry>\n"
                + "      <library-path-entry>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc</swc-path>\n"
                + "        <linkage usesDefault=\"true\">rsl</linkage>\n"
                + "        <rsl-url>http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz</rsl-url>\n"
                + "        <policy-file-url>http://fpdownload.adobe.com/pub/swz/crossdomain.xml</policy-file-url>\n"
                + "        <rsl-url>textLayout_2.0.0.232.swz</rsl-url>\n"
                + "      </library-path-entry>\n"
                + "    </LibraryPath>\n"
                + "    <LibraryVersions>\n"
                + "      <library-version>\n"
                + "        <swc-path>$(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc</swc-path>\n"
                + "        <feature name=\"tlfText\" majorVersion=\"2\" minorVersion=\"0\" build=\"232\"/>\n"
                + "        <rsl-url>http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz</rsl-url>\n"
                + "        <policy-file-url>http://fpdownload.adobe.com/pub/swz/crossdomain.xml</policy-file-url>\n"
                + "        <rsl-url>textLayout_2.0.0.232.swz</rsl-url>\n"
                + "      </library-version>\n"
                + "    </LibraryVersions>\n"
                + "  </PublishFlashProperties>\n"
                + "  <PublishJpegProperties enabled=\"true\">\n"
                + "    <Width>" + width + "</Width>\n"
                + "    <Height>" + height + "</Height>\n"
                + "    <Progressive>0</Progressive>\n"
                + "    <DPI>4718592</DPI>\n"
                + "    <Size>0</Size>\n"
                + "    <Quality>80</Quality>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "  </PublishJpegProperties>\n"
                + "  <PublishRNWKProperties enabled=\"true\">\n"
                + "    <exportFlash>1</exportFlash>\n"
                + "    <flashBitRate>0</flashBitRate>\n"
                + "    <exportAudio>1</exportAudio>\n"
                + "    <audioFormat>0</audioFormat>\n"
                + "    <singleRateAudio>0</singleRateAudio>\n"
                + "    <realVideoRate>100000</realVideoRate>\n"
                + "    <speed28K>1</speed28K>\n"
                + "    <speed56K>1</speed56K>\n"
                + "    <speedSingleISDN>0</speedSingleISDN>\n"
                + "    <speedDualISDN>0</speedDualISDN>\n"
                + "    <speedCorporateLAN>0</speedCorporateLAN>\n"
                + "    <speed256K>0</speed256K>\n"
                + "    <speed384K>0</speed384K>\n"
                + "    <speed512K>0</speed512K>\n"
                + "    <exportSMIL>1</exportSMIL>\n"
                + "  </PublishRNWKProperties>\n"
                + "  <PublishGifProperties enabled=\"true\">\n"
                + "    <Width>" + width + "</Width>\n"
                + "    <Height>" + height + "</Height>\n"
                + "    <Animated>0</Animated>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <Loop>1</Loop>\n"
                + "    <LoopCount></LoopCount>\n"
                + "    <OptimizeColors>1</OptimizeColors>\n"
                + "    <Interlace>0</Interlace>\n"
                + "    <Smooth>1</Smooth>\n"
                + "    <DitherSolids>0</DitherSolids>\n"
                + "    <RemoveGradients>0</RemoveGradients>\n"
                + "    <TransparentOption></TransparentOption>\n"
                + "    <TransparentAlpha>128</TransparentAlpha>\n"
                + "    <DitherOption></DitherOption>\n"
                + "    <PaletteOption></PaletteOption>\n"
                + "    <MaxColors>255</MaxColors>\n"
                + "    <PaletteName></PaletteName>\n"
                + "  </PublishGifProperties>\n"
                + "  <PublishPNGProperties enabled=\"true\">\n"
                + "    <Width>" + width + "</Width>\n"
                + "    <Height>" + height + "</Height>\n"
                + "    <OptimizeColors>1</OptimizeColors>\n"
                + "    <Interlace>0</Interlace>\n"
                + "    <Transparent>0</Transparent>\n"
                + "    <Smooth>1</Smooth>\n"
                + "    <DitherSolids>0</DitherSolids>\n"
                + "    <RemoveGradients>0</RemoveGradients>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <DitherOption></DitherOption>\n"
                + "    <FilterOption></FilterOption>\n"
                + "    <PaletteOption></PaletteOption>\n"
                + "    <BitDepth>24-bit with Alpha</BitDepth>\n"
                + "    <MaxColors>255</MaxColors>\n"
                + "    <PaletteName></PaletteName>\n"
                + "  </PublishPNGProperties>\n"
                + "  <PublishQTProperties enabled=\"true\">\n"
                + "    <Width>" + width + "</Width>\n"
                + "    <Height>" + height + "</Height>\n"
                + "    <MatchMovieDim>1</MatchMovieDim>\n"
                + "    <UseQTSoundCompression>0</UseQTSoundCompression>\n"
                + "    <AlphaOption></AlphaOption>\n"
                + "    <LayerOption></LayerOption>\n"
                + "    <QTSndSettings>00000000</QTSndSettings>\n"
                + "    <ControllerOption>0</ControllerOption>\n"
                + "    <Looping>0</Looping>\n"
                + "    <PausedAtStart>0</PausedAtStart>\n"
                + "    <PlayEveryFrame>0</PlayEveryFrame>\n"
                + "    <Flatten>1</Flatten>\n"
                + "  </PublishQTProperties>\n"
                + "</flash_profile>\n"
                + "</flash_profiles>";

        if (compressed) {
            final String domDocumentF = domDocument;
            final String publishSettingsF = publishSettings;
            final String outfileF = outfile;
            new RetryTask(new RunnableIOEx() {
                @Override
                public void run() throws IOException {
                    try (ZipOutputStream out = new ZipOutputStream(new FileOutputStream(outfileF))) {
                        out.putNextEntry(new ZipEntry("DOMDocument.xml"));
                        out.write(domDocumentF.getBytes("UTF-8"));
                        out.putNextEntry(new ZipEntry("PublishSettings.xml"));
                        out.write(publishSettingsF.getBytes("UTF-8"));
                        for (String fileName : files.keySet()) {
                            out.putNextEntry(new ZipEntry("LIBRARY/" + fileName));
                            out.write(files.get(fileName));
                        }
                        for (String fileName : datfiles.keySet()) {
                            out.putNextEntry(new ZipEntry("bin/" + fileName));
                            out.write(datfiles.get(fileName));
                        }
                    }
                }
            }, handler).run();


        } else {

            if (!outDir.exists()) {
                if (!outDir.mkdirs()) {
                    if (!outDir.exists()) {
                        throw new IOException("cannot create directory " + outDir);
                    }
                }
            }
            try {
                writeFile(handler, domDocument.getBytes("UTF-8"), outDir.getAbsolutePath() + File.separator + "DOMDocument.xml");
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                writeFile(handler, publishSettings.getBytes("UTF-8"), outDir.getAbsolutePath() + File.separator + "PublishSettings.xml");
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }
            File libraryDir = new File(outDir.getAbsolutePath() + File.separator + "LIBRARY");
            libraryDir.mkdir();
            File binDir = new File(outDir.getAbsolutePath() + File.separator + "bin");
            binDir.mkdir();
            for (String fileName : files.keySet()) {
                writeFile(handler, files.get(fileName), libraryDir.getAbsolutePath() + File.separator + fileName);
            }
            for (String fileName : datfiles.keySet()) {
                writeFile(handler, datfiles.get(fileName), binDir.getAbsolutePath() + File.separator + fileName);
            }
            try {
                writeFile(handler, "PROXY-CS5".getBytes("utf-8"), outfile);
            } catch (UnsupportedEncodingException ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        if (useAS3) {
            try {
                swf.exportActionScript(handler, outDir.getAbsolutePath(), false, parallel);
            } catch (Exception ex) {
                Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Error during ActionScript3 export", ex);
            }
        }
    }

    private static int normHue(double h) {
        if (Double.isNaN(h)) {
            h = -Math.PI;
        }
        int ret = (int) Math.round(h * 180 / Math.PI);
        while (ret > 180) {
            ret -= 360;
        }
        while (ret < -180) {
            ret += 360;
        }
        return ret;
    }

    private static int normBrightness(double b) {
        if (Double.isNaN(b)) {
            b = -100;
        }
        return (int) Math.round(b);
    }

    private static int normSaturation(double s) {
        if (Double.isNaN(s)) {
            return -100;
        } else if (s == 1) {
            return 0;
        } else if (s - 1 < 0) {
            return (int) Math.round((s - 1) * 100);
        } else {
            return (int) Math.round(((s - 1) * 100) / 3);
        }
    }

    private static int normContrast(double c) {
        double ctrMap[] = {
            //      0     1     2     3     4     5     6     7     8     9
            /*0*/0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
            /*1*/ 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
            /*2*/ 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
            /*3*/ 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68,
            /*4*/ 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98,
            /*5*/ 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54,
            /*6*/ 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25,
            /*7*/ 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8,
            /*8*/ 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0,
            /*9*/ 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
            /*10*/ 10.0};
        if (c == 127) {
            return 0;
        } else if (c - 127 < 0) {
            return (int) Math.round((c - 127) * 100.0 / 127.0);
        } else {
            c = (c - 127) / 127;
            for (int i = 0; i < ctrMap.length; i++) {
                if (ctrMap[i] >= c) {
                    return i;
                }
            }
        }
        return ctrMap.length - 1;
    }

    private static boolean sameDouble(double a, double b) {
        final double EPSILON = 0.00001;
        return a == b ? true : Math.abs(a - b) < EPSILON;
    }

    public static String convertAdjustColorFilter(COLORMATRIXFILTER filter) {
        float matrix[][] = new float[5][5];
        int index = 0;
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 5; j++) {
                matrix[j][i] = filter.matrix[index];
                index++;
            }
        }
        double a11 = matrix[0][0], a12 = matrix[0][1], a13 = matrix[0][2],
                a21 = matrix[1][0], a22 = matrix[1][1], a23 = matrix[1][2],
                a31 = matrix[2][0], a32 = matrix[2][1], a33 = matrix[2][2],
                a41 = matrix[4][0];

        double b, c, h, s;
        b = (24872168661075.0 * a11 * a11 - 151430415740925.0 * a12 + 341095051289483.0 * a12 * a12 - 15302094789450.0 * a13 + 82428663495404.0 * a12 * a13
                - 4592294873812.0 * a13 * a13 + 43556251470.0 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13)) + 2384730956550.0 * a12 * a41 + 240977870700.0 * a13 * a41
                - 685925220 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))
                * a41 + 465 * a11 * (466201717582.0 * a12 + 55756962908.0 * a13 + 764132175 * (-127 + 2 * a41)))
                / (391687695450.0 * a11 * a11 + 5371575610858.0 * a12 * a12 + 1298089188904.0 * a12 * a13 - 72319604312.0 * a13 * a13
                + 1860 * a11 * (1835439833 * a12 + 219515602 * a13));
        c = (127 * (495225 * a11 + 1661845 * a12 + 167930 * a13
                + 478 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))))
                / 717495;
        h = 2 * (Math.atan((-465 * a11 + 287 * a12 + 178 * a13 + Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13))) / (500. * (a12 - a13))) + Math.PI/*+ Pi*C(1)*/);
        s = (1543 * (-103355550 * a11 * a11 - 158872382 * a12 * a12 + 190161784 * a12 * a13 - 134644952 * a13 * a13
                + 1661845 * a12 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13
                - 930 * a11 * (287 * a12 + 178 * a13)) + 167930 * a13
                * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13 + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13))
                + 465 * a11 * (274372 * a12 + 170168 * a13 + 1065 * Math.sqrt(216225 * a11 * a11 + 332369 * a12 * a12 - 397828 * a12 * a13
                + 281684 * a13 * a13 - 930 * a11 * (287 * a12 + 178 * a13)))))
                / (195843847725.0 * a11 * a11 + 2685787805429.0 * a12 * a12 + 649044594452.0 * a12 * a13 - 36159802156.0 * a13 * a13
                + 930 * a11 * (1835439833 * a12 + 219515602 * a13));

        if (sameDouble(410 * a12, 1543 * a31) && sameDouble(410 * a12, 1543 * a32) && sameDouble(3047 * a12, 1543 * a21) && sameDouble(3047 * a12, 1543 * a23)
                && sameDouble(a22, a11 + (1504 * a12) / 1543.) && sameDouble((1133 * a12) / 1543. + a33, a11)
                /*&& (b == (195961 * a11 + 439039 * a12 + 1543 * (-127 + 2 * a41)) / (3086 * a11 + 6914 * a12))
                 && (c == 127 * a11 + (439039 * a12) / 1543.) && (s == (1543 * (a11 - a12)) / (1543 * a11 + 3457 * a12))
                 */ && !sameDouble(a11, a12) && !sameDouble(1543 * a11 + 3457 * a12, 0)) {
            h = 0;
        }

        return "<AdjustColorFilter brightness=\"" + normBrightness(b) + "\" contrast=\"" + normContrast(c) + "\" saturation=\"" + normSaturation(s) + "\" hue=\"" + normHue(h) + "\"/>";
    }

    private static String convertHTMLText(List<Tag> tags, DefineEditTextTag det, String html) {
        HTMLTextParser tparser = new HTMLTextParser(tags, det);
        XMLReader parser;
        try {
            SAXParserFactory factory = SAXParserFactory.newInstance();
            //factory.setValidating(false);
            parser = XMLReaderFactory.createXMLReader();
            parser.setContentHandler(tparser);
            parser.setErrorHandler(tparser);
            //parser.setFeature("http://xml.org/sax/features/validation", false);
            html = "<?xml version=\"1.0\"?>\n"
                    + "<!DOCTYPE some_name [ \n"
                    + "<!ENTITY nbsp \"&#160;\"> \n"
                    + "]><html>" + html + "</html>";
            try {
                parser.parse(new InputSource(new InputStreamReader(new ByteArrayInputStream(html.getBytes("UTF-8")), "UTF-8")));
            } catch (SAXParseException spe) {
                System.out.println(html);
                System.err.println(tparser.result);
            }
            //XMLReader reader = parser.getXMLReader();


            /*reader.setContentHandler(tparser);
             reader.setErrorHandler(tparser);
             reader.setEntityResolver(tparser);
             html = "<html>" + html + "</html>";
             reader.parse(new InputSource(new InputStreamReader(new ByteArrayInputStream(html.getBytes("UTF-8")), "UTF-8")));*/

        } catch (Exception e) {
            Logger.getLogger(XFLConverter.class.getName()).log(Level.SEVERE, "Error while converting HTML", e);
        }
        return tparser.result;
    }

    private static String xmlString(String s) {
        return s.replace("<", "&lt;").replace(">", "&gt;").replace("\"", "&quot;").replace("&", "&amp;").replace("\r\n", "&#xD;").replace("\r", "&#xD;").replace("\n", "&#xD;");
    }

    private static double twipToPixel(double tw) {
        return tw / 20.0;
    }

    private static class HTMLTextParser extends DefaultHandler {

        public String result = "";
        private String fontFace = "";
        private String color = "";
        private int size = -1;
        private int indent = -1;
        private int leftMargin = -1;
        private int rightMargin = -1;
        private int lineSpacing = -1;
        private double letterSpacing = -1;
        private String alignment = null;
        private List<Tag> tags;
        private boolean bold = false;
        private boolean italic = false;
        private boolean underline = false;
        private boolean li = false;
        private String url = null;
        private String target = null;

        @Override
        public void error(SAXParseException e) throws SAXException {
        }

        @Override
        public void fatalError(SAXParseException e) throws SAXException {
        }

        @Override
        public void warning(SAXParseException e) throws SAXException {
        }

        public HTMLTextParser(List<Tag> tags, DefineEditTextTag det) {
            if (det.hasFont) {
                String fontName = null;
                FontTag ft = null;
                for (Tag u : tags) {
                    if (u instanceof DefineFontNameTag) {
                        if (((DefineFontNameTag) u).fontId == det.fontId) {
                            fontName = ((DefineFontNameTag) u).fontName;
                        }
                    }
                    if (u instanceof FontTag) {
                        if (((FontTag) u).getFontId() == det.fontId) {
                            ft = (FontTag) u;
                        }
                    }
                    if (fontName != null && ft != null) {
                        break;
                    }
                }
                if (ft != null) {
                    if (fontName == null) {
                        fontName = ft.getFontName(tags);
                    }
                    italic = ft.isItalic();
                    bold = ft.isBold();
                    size = det.fontHeight;
                    fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                }
            }
            if (det.hasLayout) {
                leftMargin = det.leftMargin;
                rightMargin = det.rightMargin;
                indent = det.indent;
                lineSpacing = det.leading;
                String alignNames[] = {"left", "right", "center", "justify"};
                alignment = alignNames[det.align];
            }
            this.tags = tags;
        }

        @Override
        public void startDocument() throws SAXException {
        }

        @Override
        public void startElement(String uri, String localName,
                String qName, Attributes attributes) throws SAXException {
            if (qName.equals("a")) {
                String href = attributes.getValue("href");
                if (href != null) {
                    url = href;
                }
                String t = attributes.getValue("target");
                if (t != null) {
                    target = t;
                }
            } else if (qName.equals("b")) {
                bold = true;
            } else if (qName.equals("i")) {
                italic = true;
            } else if (qName.equals("u")) {
                underline = true;
            } else if (qName.equals("li")) {
                li = true;
            } else if (qName.equals("p")) {
                String a = attributes.getValue("align");
                if (a != null) {
                    alignment = a;
                }
                if (!result.equals("")) {
                    putText("\r\n");
                }
            } else if (qName.equals("font")) {
                //kerning  ?
                String ls = attributes.getValue("letterSpacing");
                if (ls != null) {
                    letterSpacing = Double.parseDouble(ls);
                }
                String s = attributes.getValue("size");
                if (s != null) {
                    size = Integer.parseInt(s);
                }
                String c = attributes.getValue("color");
                if (c != null) {
                    color = c;
                }
                String f = attributes.getValue("face");
                if (f != null) {
                    for (Tag t : tags) {
                        if (t instanceof FontTag) {
                            FontTag ft = (FontTag) t;
                            String fontName = "";
                            if (f.equals(ft.getFontName(tags))) {
                                for (Tag u : tags) {
                                    if (u instanceof DefineFontNameTag) {
                                        if (((DefineFontNameTag) u).fontId == ft.getFontId()) {
                                            fontName = ((DefineFontNameTag) u).fontName;
                                        }
                                    }
                                }
                                if (fontName == null) {
                                    fontName = ft.getFontName(tags);
                                }
                                fontFace = new Font(fontName, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName();
                                break;
                            }
                        }
                    }
                }
            }
            //textformat,tab,br
        }

        @Override
        public void endElement(String uri, String localName,
                String qName) throws SAXException {
            if (qName.equals("a")) {
                url = null;
                target = null;
            }
            if (qName.equals("b")) {
                bold = false;
            }
            if (qName.equals("i")) {
                italic = false;
            }
            if (qName.equals("u")) {
                underline = false;
            }
            if (qName.equals("li")) {
                li = false;
            }
        }

        private void putText(String txt) {

            result += "<DOMTextRun>";
            result += "<characters>" + xmlString(txt) + "</characters>";
            result += "<textAttrs>";
            result += "<DOMTextAttrs";
            if (alignment != null) {
                result += " alignment=\"" + alignment + "\"";
            }
            result += " rotation=\"true\""; //?
            if (indent > -1) {
                result += " indent=\"" + twipToPixel(indent) + "\"";
            }
            if (leftMargin > -1) {
                result += " leftMargin=\"" + twipToPixel(leftMargin) + "\"";
            }
            if (letterSpacing > -1) {
                result += " letterSpacing=\"" + letterSpacing + "\"";
            }
            if (lineSpacing > -1) {
                result += " lineSpacing=\"" + twipToPixel(lineSpacing) + "\"";
            }
            if (rightMargin > -1) {
                result += " rightMargin=\"" + twipToPixel(rightMargin) + "\"";
            }
            if (size > -1) {
                result += " size=\"" + size + "\"";
                result += " bitmapSize=\"" + (size * 20) + "\"";
            }
            if (fontFace != null) {
                result += " face=\"" + fontFace + "\"";
            }
            if (color != null) {
                result += " fillColor=\"" + color + "\"";
            }
            if (url != null) {
                result += " url=\"" + url + "\"";
            }
            if (target != null) {
                result += " target=\"" + target + "\"";
            }
            result += "/>";
            result += "</textAttrs>";
            result += "</DOMTextRun>";
        }

        @Override
        public void characters(char[] ch, int start, int length)
                throws SAXException {
            putText(new String(ch, start, length));

        }

        @Override
        public void endDocument() {
        }
    }
}
