/* * 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 . */ 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.action.Action; import com.jpexs.decompiler.flash.helpers.HilightedTextWriter; 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.ExportMode; import com.jpexs.helpers.utf8.Utf8Helper; import java.awt.Font; 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.StringReader; import java.io.StringWriter; 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.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; 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 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 "" + "" + "" + "" + ""; } public static String convertLineStyle(HashMap 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 += ""; } else { ret += convertFillStyle(null/* FIXME */, characters, ls.fillType, shapeNum); } ret += ""; ret += ""; return ret; } private static float toFloat(int i) { return ((float) i) / (1 << 16); } public static String convertFillStyle(MATRIX mat, HashMap characters, FILLSTYLE fs, int shapeNum) { if (mat == null) { mat = new MATRIX(); } String ret = ""; //ret += ""; switch (fs.fillStyleType) { case FILLSTYLE.SOLID: ret += "= 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 += ""; } ret += "\""; if ((fs.fillStyleType == FILLSTYLE.CLIPPED_BITMAP) || (fs.fillStyleType == FILLSTYLE.NON_SMOOTHED_CLIPPED_BITMAP)) { ret += " bitmapIsClipped=\"true\""; } ret += ">"; ret += "" + convertMatrix(fs.bitmapMatrix) + ""; ret += ""; break; case FILLSTYLE.LINEAR_GRADIENT: case FILLSTYLE.RADIAL_GRADIENT: case FILLSTYLE.FOCAL_RADIAL_GRADIENT: if (fs.fillStyleType == FILLSTYLE.LINEAR_GRADIENT) { ret += ""; GRADRECORD[] records; if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) { records = fs.focalGradient.gradientRecords; } else { records = fs.gradient.gradientRecords; } for (GRADRECORD rec : records) { ret += "= 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 += ""; } else { ret += ""; } break; } //ret += ""; return ret; } public static String convertMatrix(MATRIX m) { String ret = ""; if (m == null) { m = new MATRIX(); } ret += " characters, MATRIX mat, int shapeNum, SHAPE shape) { return convertShape(characters, mat, shapeNum, shape.shapeRecords); } public static String convertShape(HashMap characters, MATRIX mat, int shapeNum, SHAPEWITHSTYLE shape) { return convertShape(characters, mat, shapeNum, shape.shapeRecords, shape.fillStyles, shape.lineStyles, false); } public static String convertShape(HashMap characters, MATRIX mat, ShapeTag shape) { return convertShape(characters, mat, shape.getShapeNum(), shape.getShapes()); } public static String convertShape(HashMap characters, MATRIX mat, int shapeNum, List shapeRecords) { return convertShape(characters, mat, shapeNum, shapeRecords, null, null, false); } public static String convertShape(HashMap characters, MATRIX mat, int shapeNum, List shapeRecords, FILLSTYLEARRAY fillStyles, LINESTYLEARRAY lineStyles, boolean morphshape) { String ret = ""; if (mat == null) { mat = new MATRIX(); } List 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 += ""; strokesStr += ""; //edgesStr += ""; List layers = new ArrayList<>(); String currentLayer = ""; int fillStyleCount = 0; //List fillStylesStr = new ArrayList<>(); if (fillStyles != null) { for (FILLSTYLE fs : fillStyles.fillStyles) { fillsStr += ""; fillsStr += convertFillStyle(mat, characters, fs, shapeNum); fillsStr += ""; fillStyleCount++; } } if (lineStyles != null) { if ((shapeNum == 4) && (lineStyles.lineStyles2 != null)) { //(shapeNum == 4) { for (int l = 0; l < lineStyles.lineStyles2.length; l++) { strokesStr += ""; strokesStr += convertLineStyle(characters, lineStyles.lineStyles2[l], shapeNum); strokesStr += ""; lineStyleCount++; } } else if (lineStyles.lineStyles != null) { for (int l = 0; l < lineStyles.lineStyles.length; l++) { strokesStr += ""; strokesStr += convertLineStyle(lineStyles.lineStyles[l], shapeNum); strokesStr += ""; lineStyleCount++; } } } fillsStr += ""; strokesStr += ""; int layer = 1; if ((fillStyleCount > 0) || (lineStyleCount > 0)) { currentLayer += ""; //color=\"#4FFF4F\" currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += fillsStr; currentLayer += strokesStr; currentLayer += ""; } 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 = ""; strokesStr = ""; 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 += " -1) { currentLayer += " fillStyle0=\"" + fillStyle0 + "\""; } if (fillStyle1 > -1) { currentLayer += " fillStyle1=\"" + fillStyle1 + "\""; } if (strokeStyle > -1) { currentLayer += " strokeStyle=\"" + strokeStyle + "\""; } currentLayer += " edges=\"" + convertShapeEdges(startEdgeX, startEdgeY, mat, edges) + "\" />"; } } currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; layers.add(currentLayer); currentLayer = ""; } currentLayer += ""; //color=\"#4FFF4F\" currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; //ret += convertShape(characters, null, shape); for (int f = 0; f < scr.fillStyles.fillStyles.length; f++) { fillsStr += ""; fillsStr += convertFillStyle(mat, characters, scr.fillStyles.fillStyles[f], shapeNum); fillsStr += ""; fillStyleCount++; } lineStyleCount = 0; if (shapeNum == 4) { for (int l = 0; l < scr.lineStyles.lineStyles2.length; l++) { strokesStr += ""; strokesStr += convertLineStyle(characters, scr.lineStyles.lineStyles2[l], shapeNum); strokesStr += ""; lineStyleCount++; } } else { for (int l = 0; l < scr.lineStyles.lineStyles.length; l++) { strokesStr += ""; strokesStr += convertLineStyle(scr.lineStyles.lineStyles[l], shapeNum); strokesStr += ""; lineStyleCount++; } } fillsStr += ""; strokesStr += ""; currentLayer += fillsStr; currentLayer += strokesStr; currentLayer += ""; //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 += " -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 += " -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 += ""; strokesStr += ""; if (!currentLayer.isEmpty()) { currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; currentLayer += ""; layers.add(currentLayer); } for (int l = layers.size() - 1; l >= 0; l--) { ret += layers.get(l); } return ret; } private static int getLayerCount(List 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 getOneInstanceShapes(List tags, HashMap characters) { if (true) { //the feature was removed due to multiple layers in some shapes return new ArrayList<>(); } HashMap 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 ret = new ArrayList<>(); for (int ch : usages.keySet()) { if (usages.get(ch) < 2) { ret.add(ch); } } return ret; } private static HashMap getCharacters(List tags) { HashMap 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 += " filters, boolean isVisible, RGBA backgroundColor, CLIPACTIONS clipActions, CharacterTag tag, HashMap characters, List 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 += "()); 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 += ""; ret += convertMatrix(matrix); ret += ""; ret += ""; if (backgroundColor != null) { ret += " characterVariables, Map characterClasses, List oneInstanceShapes, String backgroundColor, List tags, HashMap characters, HashMap files, HashMap datfiles) { //TODO: Imported assets //linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf" String ret = ""; List media = new ArrayList<>(); List 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 += ""; symbolStr += ""; ButtonTag button = (ButtonTag) symbol; List 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 += ""; symbolStr += ""; 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 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 += ""; symbolStr += ""; symbolStr += ""; symbolStr += ""; } symbolStr += ""; symbolStr += ""; symbolStr += recCharStr; symbolStr += ""; symbolStr += ""; } } } } symbolStr += ""; symbolStr += ""; } symbolStr += ""; symbolStr += ""; } 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 += ""; symbolStr += ""; symbolStr += convertShape(characters, null, shape); symbolStr += ""; symbolStr += ""; } symbolStr += ""; symbolStr += ""; symbolStr = prettyFormatXML(symbolStr); String symbolFile = "Symbol " + symbol.getCharacterId() + ".xml"; files.put(symbolFile, Utf8Helper.getBytes(symbolStr)); String symbLinkStr = ""; symbLinkStr += ""; //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 = "\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 = ""; //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 = " characters, List 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 += " 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 += ""; soundEnvelopeStr += ""; soundEnvelopeStr += ""; } 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 += ""; if (startSound.soundInfo.hasEnvelope) { for (SOUNDENVELOPE env : startSound.soundInfo.envelopeRecords) { soundEnvelopeStr += ""; } 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 += ""; } soundEnvelopeStr += ""; } ret += ">"; ret += soundEnvelopeStr; if (!actionScript.isEmpty()) { ret += ""; } ret += ""; ret += elements; ret += ""; ret += ""; return ret; } private static String convertVideoInstance(String instanceName, MATRIX matrix, DefineVideoStreamTag video, CLIPACTIONS clipActions) { String ret = " oneInstanceShapes, List tags, List timelineTags, HashMap characters, int depth) { String ret = ""; prevStr += ""; 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 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 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.isEmpty()) { frame++; ret += convertFrame(lastShapeTween, characters, tags, null, null, (frame - duration < 0 ? 0 : frame - duration), duration, "", lastElements); } afterStr = "" + afterStr; if (!ret.isEmpty()) { ret = prevStr + ret + afterStr; } return ret; } public static String convertActionScriptLayer(int spriteId, List tags, List 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.isEmpty()) { 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.isEmpty()) { duration++; } else { if (duration > 0) { ret += " 1) { ret += " duration=\"" + duration + "\""; } ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">"; ret += ""; ret += ""; ret += ""; } ret += ""; ret += ""; ret += ""; ret += ""; ret += ""; script = ""; duration = 0; } frame++; } } if (!ret.isEmpty()) { ret = "" + "" + ret + "" + ""; } return ret; } public static String convertLabelsLayer(int spriteId, List tags, List 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.isEmpty()) { duration++; } else { if (duration > 0) { ret += " 1) { ret += " duration=\"" + duration + "\""; } ret += " keyMode=\"" + KEY_MODE_NORMAL + "\">"; ret += ""; ret += ""; ret += ""; } ret += "" + "" + ret + "" + ""; } return ret; } public static String convertSoundLayer(int layerIndex, String backgroundColor, HashMap characters, List tags, List 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.isEmpty()) { ret = "" + "" + ret + "" + ""; } 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 oneInstanceShapes, String backgroundColor, List tags, List timelineTags, HashMap characters, String name) { String ret = ""; ret += ""; ret += ""; ret += convertLabelsLayer(spriteId, tags, timelineTags, backgroundColor); ret += convertActionScriptLayer(spriteId, tags, timelineTags, backgroundColor); int layerCount = getLayerCount(timelineTags); Stack 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 += " getCharacterClasses(List tags) { Map 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 getCharacterVariables(List tags) { Map 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 tags, TextTag tag, MATRIX matrix, List filters, CLIPACTIONS clipActions) { String ret = ""; if (matrix == null) { matrix = new MATRIX(); } CSMTextSettingsTag csmts = null; String filterStr = ""; if (filters != null) { filterStr += ""; for (FILTER f : filters) { filterStr += convertFilter(f); } filterStr += ""; } 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 += ""; 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 += ""; if ((tag instanceof DefineTextTag) || (tag instanceof DefineText2Tag)) { List 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 += " attrs = TextTag.getTextRecordsAttributes(textRecords, tags); ret += " width=\"" + tag.getBounds().getWidth() / 2 + "\" height=\"" + tag.getBounds().getHeight() + "\" autoExpand=\"true\" isSelectable=\"false\">"; ret += matStr; ret += ""; int fontId = -1; FontTag font = null; String fontName = null; String psFontName = null; int textHeight = -1; RGB textColor = null; RGBA textColorA = null; boolean newline = false; boolean firstRun = true; @SuppressWarnings("unchecked") List leftMargins = (List) attrs.get("allLeftMargins"); @SuppressWarnings("unchecked") List letterSpacings = (List) 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); } int fontStyle = 0; if (font != null) { fontStyle = font.getFontStyle(); } String installedFont; if ((installedFont = FontTag.isFontInstalled(fontName)) != null) { psFontName = new Font(installedFont, fontStyle, 10).getPSName(); } else { psFontName = fontName; } } newline = false; if (!firstRun && rec.styleFlagsHasYOffset) { newline = true; } firstRun = false; if (font != null) { ret += ""; ret += "" + xmlString((newline ? "\r" : "") + rec.getText(tags, font)) + ""; ret += ""; ret += ""; 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 = fontName; String installedFont = null; if ((installedFont = FontTag.isFontInstalled(fontName)) != null) { fontName = installedFont; fontFace = new Font(installedFont, (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 += ""; ret += " -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 += ""; ret += ""; } ret += ""; ret += filterStr; ret += ""; } 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 files = new HashMap<>(); final HashMap datfiles = new HashMap<>(); HashMap characters = getCharacters(swf.tags); List oneInstaceShapes = getOneInstanceShapes(swf.tags, characters); Map characterClasses = getCharacterClasses(swf.tags); Map 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 += "\n" + " \n" + " 1\n" + " 1\n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " 1\n" + " " + baseName + ".swf\n" + " " + baseName + ".exe\n" + " " + baseName + ".app\n" + " " + baseName + ".html\n" + " " + baseName + ".gif\n" + " " + baseName + ".jpg\n" + " " + baseName + ".png\n" + " " + baseName + ".mov\n" + " " + baseName + ".smil\n" + " " + baseName + ".swc\n" + " \n" + " \n" + " 0\n" + " 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;\n" + " 1\n" + " 1\n" + " " + baseName + "_content.html\n" + " " + baseName + "_alternate.html\n" + " 0\n" + " \n" + " " + width + "\n" + " " + height + "\n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " 0\n" + " 1\n" + " 1\n" + " 4\n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " \n" + " 1\n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " 0\n" + " 0\n" + " 80\n" + " 0\n" + " 0\n" + " 7\n" + " 0\n" + " 7\n" + " 0\n" + " 15\n" + " FlashPlayer11.2\n" + " " + (useAS3 ? "3" : "2") + "\n" + " 1\n" + " \n" + " .\n" + " CONFIG::FLASH_AUTHORING="true";\n" + " 0\n" + " \n" + " " + (swf.compressed ? "1" : "0") + "\n" + " " + (swf.lzma ? "1" : "0") + "\n" + " 1\n" + " 0\n" + " 0\n" + " 0\n" + " " + (useNetwork ? 1 : 0) + "\n" + " " + xmlString(characterClasses.containsKey(0) ? characterClasses.get(0) : "") + "\n" + " 2\n" + " 4\n" + " 4096\n" + " AS3\n" + " 1\n" + " 1\n" + " 0\n" + " 15\n" + " 1\n" + " 0\n" + " 4102\n" + " rsl\n" + " wrap\n" + " $(AppConfig)/ActionScript 3.0/rsls/loader_animation.swf\n" + " \n" + " \n" + " $(AppConfig)/ActionScript 3.0/libs\n" + " merge\n" + " \n" + " \n" + " $(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc\n" + " rsl\n" + " http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz\n" + " http://fpdownload.adobe.com/pub/swz/crossdomain.xml\n" + " textLayout_2.0.0.232.swz\n" + " \n" + " \n" + " \n" + " \n" + " $(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc\n" + " \n" + " http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz\n" + " http://fpdownload.adobe.com/pub/swz/crossdomain.xml\n" + " textLayout_2.0.0.232.swz\n" + " \n" + " \n" + " \n" + " \n" + " " + width + "\n" + " " + height + "\n" + " 0\n" + " 4718592\n" + " 0\n" + " 80\n" + " 1\n" + " \n" + " \n" + " 1\n" + " 0\n" + " 1\n" + " 0\n" + " 0\n" + " 100000\n" + " 1\n" + " 1\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 1\n" + " \n" + " \n" + " " + width + "\n" + " " + height + "\n" + " 0\n" + " 1\n" + " 1\n" + " \n" + " 1\n" + " 0\n" + " 1\n" + " 0\n" + " 0\n" + " \n" + " 128\n" + " \n" + " \n" + " 255\n" + " \n" + " \n" + " \n" + " " + width + "\n" + " " + height + "\n" + " 1\n" + " 0\n" + " 0\n" + " 1\n" + " 0\n" + " 0\n" + " 1\n" + " \n" + " \n" + " \n" + " 24-bit with Alpha\n" + " 255\n" + " \n" + " \n" + " \n" + " " + width + "\n" + " " + height + "\n" + " 1\n" + " 0\n" + " \n" + " \n" + " 00000000\n" + " 0\n" + " 0\n" + " 0\n" + " 0\n" + " 1\n" + " \n" + "\n" + ""; 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(Utf8Helper.getBytes(domDocumentF)); out.putNextEntry(new ZipEntry("PublishSettings.xml")); out.write(Utf8Helper.getBytes(publishSettingsF)); 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); } } } writeFile(handler, Utf8Helper.getBytes(domDocument), outDir.getAbsolutePath() + File.separator + "DOMDocument.xml"); writeFile(handler, Utf8Helper.getBytes(publishSettings), outDir.getAbsolutePath() + File.separator + "PublishSettings.xml"); 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); } writeFile(handler, Utf8Helper.getBytes("PROXY-CS5"), outfile); } if (useAS3) { try { swf.exportActionScript(handler, outDir.getAbsolutePath(), ExportMode.SOURCE, 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 ""; } private static String convertHTMLText(List 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 = "\n" + " \n" + "]>" + html + ""; try { parser.parse(new InputSource(new StringReader(html))); } 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 + ""; reader.parse(new InputSource(new StringReader(html)));*/ } catch (SAXException | IOException 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("<", "<").replace(">", ">").replace("\"", """).replace("&", "&").replace("\r\n", " ").replace("\r", " ").replace("\n", " "); } 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 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 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 { switch (qName) { case "a": String href = attributes.getValue("href"); if (href != null) { url = href; } String t = attributes.getValue("target"); if (t != null) { target = t; } break; case "b": bold = true; break; case "i": italic = true; break; case "u": underline = true; break; case "li": li = true; break; case "p": String a = attributes.getValue("align"); if (a != null) { alignment = a; } if (!result.isEmpty()) { putText("\r\n"); } break; case "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 tag : tags) { if (tag instanceof FontTag) { FontTag ft = (FontTag) tag; String fontName = null; 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); } String installedFont; if ((installedFont = FontTag.isFontInstalled(fontName)) != null) { fontFace = new Font(installedFont, (italic ? Font.ITALIC : 0) | (bold ? Font.BOLD : 0) | (!italic && !bold ? Font.PLAIN : 0), size < 0 ? 10 : size).getPSName(); } else { fontFace = fontName; } break; } } } } break; } } @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 += ""; result += "" + xmlString(txt) + ""; result += ""; result += " -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 += ""; result += ""; } @Override public void characters(char[] ch, int start, int length) throws SAXException { putText(new String(ch, start, length)); } @Override public void endDocument() { if (this.result.isEmpty()) { putText(""); } } } }