From 669ffbd1a7143d39c35cbc987de5650cee298bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 29 Oct 2023 12:27:29 +0100 Subject: [PATCH] Fixed #1866 FLA export - multilevel clipping handling Fixed #1866 FLA export - morphshape rounding fix --- CHANGELOG.md | 3 + .../flash/exporters/commonshape/Matrix.java | 6 + .../flash/tags/PlaceObject2Tag.java | 7 +- .../flash/tags/PlaceObject3Tag.java | 5 + .../flash/tags/PlaceObject4Tag.java | 5 + .../decompiler/flash/tags/PlaceObjectTag.java | 5 + .../flash/tags/base/PlaceObjectTypeTag.java | 46 ++ .../decompiler/flash/xfl/XFLConverter.java | 713 ++++++++++++++++-- .../src/com/jpexs/helpers/ByteArrayRange.java | 3 +- .../testdata/nested_masks/nested_masks.html | 49 ++ .../testdata/nested_masks/nested_masks.swf | Bin 0 -> 1652 bytes .../nested_masks/nested_masks/DOMDocument.xml | 146 ++++ .../nested_masks/LIBRARY/Rectangles.xml | 197 +++++ .../nested_masks/META-INF/metadata.xml | 67 ++ .../nested_masks/MobileSettings.xml | 0 .../nested_masks/PublishSettings.xml | 206 +++++ .../nested_masks/bin/SymDepend.cache | Bin 0 -> 46 bytes .../nested_masks/nested_masks.xfl | 1 + 18 files changed, 1390 insertions(+), 69 deletions(-) create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks.html create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks.swf create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/DOMDocument.xml create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/LIBRARY/Rectangles.xml create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/META-INF/metadata.xml create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/MobileSettings.xml create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/PublishSettings.xml create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/bin/SymDepend.cache create mode 100644 libsrc/ffdec_lib/testdata/nested_masks/nested_masks/nested_masks.xfl diff --git a/CHANGELOG.md b/CHANGELOG.md index 7be8c80d3..b6c745ee2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,8 @@ All notable changes to this project will be documented in this file. - SVG import - Do not use fill winding nonzero when only stroking - Morphshape SVG export - closing the stroke - [#2031] FLA export - morphshapes with duplicated strokes, timelines with multiple shape tweens +- [#1866] FLA export - multilevel clipping handling +- [#1866] FLA export - morphshape rounding fix ### Changed - Basic tag info panel always visible even when nothing to display (to avoid flickering) @@ -3232,6 +3234,7 @@ Major version of SWF to XML export changed to 2. [#2013]: https://www.free-decompiler.com/flash/issues/2013 [#2104]: https://www.free-decompiler.com/flash/issues/2104 [#2031]: https://www.free-decompiler.com/flash/issues/2031 +[#1866]: https://www.free-decompiler.com/flash/issues/1866 [#2099]: https://www.free-decompiler.com/flash/issues/2099 [#2090]: https://www.free-decompiler.com/flash/issues/2090 [#2079]: https://www.free-decompiler.com/flash/issues/2079 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java index 493cd2d6d..aa2b45d63 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java @@ -18,6 +18,7 @@ package com.jpexs.decompiler.flash.exporters.commonshape; import com.jpexs.decompiler.flash.types.MATRIX; import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; /** * @@ -163,6 +164,11 @@ public final class Matrix implements Cloneable { Point p = transform(point.x, point.y); return new java.awt.Point((int) p.x, (int) p.y); } + + public Point2D transform(Point2D point) { + Point p = transform(point.getX(), point.getY()); + return new Point2D.Double(p.x, p.y); + } public ExportRectangle transform(ExportRectangle rect) { double minX = Double.MAX_VALUE; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java index ad0f7db6e..e24820680 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject2Tag.java @@ -472,7 +472,12 @@ public class PlaceObject2Tag extends PlaceObjectTypeTag implements ASMSourceCont public void setPlaceFlagHasMatrix(boolean placeFlagHasMatrix) { this.placeFlagHasMatrix = placeFlagHasMatrix; } - + + @Override + public void setPlaceFlagMove(boolean placeFlagMove) { + this.placeFlagMove = placeFlagMove; + } + @Override public boolean hasImage() { return false; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java index 74055ae64..c6b0c3ba5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject3Tag.java @@ -676,6 +676,11 @@ public class PlaceObject3Tag extends PlaceObjectTypeTag implements ASMSourceCont this.placeFlagHasMatrix = placeFlagHasMatrix; } + @Override + public void setPlaceFlagMove(boolean placeFlagMove) { + this.placeFlagMove = placeFlagMove; + } + @Override public boolean hasImage() { return placeFlagHasImage; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java index 50cc16dae..791f571fc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObject4Tag.java @@ -697,6 +697,11 @@ public class PlaceObject4Tag extends PlaceObjectTypeTag implements ASMSourceCont this.placeFlagHasMatrix = placeFlagHasMatrix; } + @Override + public void setPlaceFlagMove(boolean placeFlagMove) { + this.placeFlagMove = placeFlagMove; + } + @Override public boolean hasImage() { return placeFlagHasImage; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java index 66dc06c0b..6803d26a7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/PlaceObjectTag.java @@ -287,6 +287,11 @@ public class PlaceObjectTag extends PlaceObjectTypeTag { } + @Override + public void setPlaceFlagMove(boolean placeFlagMove) { + + } + @Override public boolean hasImage() { return false; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java index 5128cb74c..a0f0c04f9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/PlaceObjectTypeTag.java @@ -28,6 +28,7 @@ import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.helpers.ByteArrayRange; import java.io.IOException; import java.util.List; +import java.util.Objects; /** * @@ -90,6 +91,51 @@ public abstract class PlaceObjectTypeTag extends Tag implements CharacterIdTag { public abstract void setPlaceFlagHasClipActions(boolean placeFlagHasClipActions); public abstract void setPlaceFlagHasMatrix(boolean placeFlagHasMatrix); + + public abstract void setPlaceFlagMove(boolean placeFlagMove); + + public boolean placeEquals(PlaceObjectTypeTag other) { + if (getDepth() != other.getDepth()) { + return false; + } + if (!Objects.equals(getMatrix(), other.getMatrix())) { + return false; + } + if (!Objects.equals(getInstanceName(), other.getInstanceName())) { + return false; + } + if (!Objects.equals(getClassName(), other.getClassName())) { + return false; + } + if (cacheAsBitmap() != other.cacheAsBitmap()) { + return false; + } + if (hasImage() != other.hasImage()) { + return false; + } + if (isVisible() != other.isVisible()) { + return false; + } + if (!Objects.equals(getVisible(), other.getVisible())) { + return false; + } + if (!Objects.equals(getBackgroundColor(), other.getBackgroundColor())) { + return false; + } + if (flagMove() != other.flagMove()) { + return false; + } + if (getRatio() != other.getRatio()) { + return false; + } + if (!Objects.equals(getClipActions(), other.getClipActions())) { //? + return false; + } + if (!Objects.equals(getAmfData(), other.getAmfData())) { //? + return false; + } + return true; + } @Override public String getName() { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java index 710e8d3a2..0f5fd53e6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/XFLConverter.java @@ -79,6 +79,7 @@ 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.PlaceObject2Tag; import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; import com.jpexs.decompiler.flash.tags.ShowFrameTag; import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; @@ -113,6 +114,7 @@ import com.jpexs.decompiler.flash.types.FOCALGRADIENT; import com.jpexs.decompiler.flash.types.GRADIENT; import com.jpexs.decompiler.flash.types.GRADRECORD; import com.jpexs.decompiler.flash.types.ILINESTYLE; +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; @@ -147,6 +149,7 @@ import com.jpexs.decompiler.graph.GraphTargetItem; import com.jpexs.decompiler.graph.ScopeStack; import com.jpexs.helpers.Helper; import com.jpexs.helpers.Path; +import com.jpexs.helpers.Reference; import com.jpexs.helpers.SerializableImage; import com.jpexs.helpers.XmlPrettyFormat; import com.jpexs.helpers.utf8.Utf8Helper; @@ -166,11 +169,14 @@ import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import java.util.Set; +import java.util.Stack; +import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; @@ -227,11 +233,14 @@ public class XFLConverter { */ private final boolean DEBUG_EXPORT_LAYER_DEPTHS = false; - private static String formatEdgeDouble(double value, boolean curved) { + private static String formatEdgeDouble(double value, boolean curved, boolean morphshape) { if (value % 1 == 0) { return "" + (int) value; } - DecimalFormat df = new DecimalFormat("0.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); + if (morphshape) { + value = Math.round(value * 2.0) / 2.0; + } + DecimalFormat df = new DecimalFormat(morphshape ? "0.#" : "0.##", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); df.setGroupingUsed(false); String strValue = "" + df.format(value); @@ -242,33 +251,43 @@ public class XFLConverter { return "" + strValue; } - private static String convertShapeEdge(ShapeRecordAdvanced record, double x, double y) { + private static String convertShapeEdge(MATRIX mat, ShapeRecordAdvanced record, double x, double y, boolean morphshape) { if (record instanceof StyleChangeRecordAdvanced) { StyleChangeRecordAdvanced scr = (StyleChangeRecordAdvanced) record; Point2D p = new Point2D.Double(scr.moveDeltaX, scr.moveDeltaY); + //p = new Matrix(mat).transform(p); if (scr.stateMoveTo) { - return "! " + formatEdgeDouble(p.getX(), false) + " " + formatEdgeDouble(p.getY(), false); + //return "! " + formatEdgeDouble(p.getX(), false) + " " + formatEdgeDouble(p.getY(), false); + return ""; } } else if (record instanceof StraightEdgeRecordAdvanced) { StraightEdgeRecordAdvanced ser = (StraightEdgeRecordAdvanced) record; + Point2D p1 = new Point2D.Double(x, y); Point2D p = new Point2D.Double(x + ser.deltaX, y + ser.deltaY); - return "! " + formatEdgeDouble(x, false) + " " + formatEdgeDouble(y, false) - + "| " + formatEdgeDouble(p.getX(), false) + " " + formatEdgeDouble(p.getY(), false); + p = new Matrix(mat).transform(p); + p1 = new Matrix(mat).transform(p1); + + return "! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) + + "| " + formatEdgeDouble(p.getX(), false, morphshape) + " " + formatEdgeDouble(p.getY(), false, morphshape); } else if (record instanceof CurvedEdgeRecordAdvanced) { CurvedEdgeRecordAdvanced cer = (CurvedEdgeRecordAdvanced) record; double controlX = cer.controlDeltaX + x; double controlY = cer.controlDeltaY + y; double anchorX = cer.anchorDeltaX + controlX; double anchorY = cer.anchorDeltaY + controlY; + Point2D p1 = new Point2D.Double(x, y); Point2D control = new Point2D.Double(controlX, controlY); Point2D anchor = new Point.Double(anchorX, anchorY); - return "! " + formatEdgeDouble(x, false) + " " + formatEdgeDouble(y, false) - + "[ " + formatEdgeDouble(control.getX(), true) + " " + formatEdgeDouble(control.getY(), true) + " " + formatEdgeDouble(anchor.getX(), true) + " " + formatEdgeDouble(anchor.getY(), true); + p1 = new Matrix(mat).transform(p1); + control = new Matrix(mat).transform(control); + anchor = new Matrix(mat).transform(anchor); + return "! " + formatEdgeDouble(p1.getX(), false, morphshape) + " " + formatEdgeDouble(p1.getY(), false, morphshape) + + "[ " + formatEdgeDouble(control.getX(), true, morphshape) + " " + formatEdgeDouble(control.getY(), true, morphshape) + " " + formatEdgeDouble(anchor.getX(), true, morphshape) + " " + formatEdgeDouble(anchor.getY(), true, morphshape); } return ""; } - private static void convertShapeEdges(boolean close, double startX, double startY, MATRIX mat, List recordsAdvanced, StringBuilder ret) { + private static void convertShapeEdges(boolean close, double startX, double startY, MATRIX mat, List recordsAdvanced, StringBuilder ret, boolean morphshape) { double x = startX; double y = startY; @@ -282,7 +301,7 @@ public class XFLConverter { } } if (!hasMove) { - ret.append("! ").append(formatEdgeDouble(startX, false)).append(" ").append(formatEdgeDouble(startY, false)); + //ret.append("! ").append(formatEdgeDouble(startX, false)).append(" ").append(formatEdgeDouble(startY, false)); } double lastMoveToX = startX; double lastMoveToY = startY; @@ -315,7 +334,7 @@ public class XFLConverter { } - String edge = convertShapeEdge(rec, x, y); + String edge = convertShapeEdge(mat, rec, x, y, morphshape); //ignore duplicated edges with only strokes #2031 if (fillStyle0 == 0 && fillStyle1 == 0 && lineStyle != 0) { @@ -334,7 +353,7 @@ public class XFLConverter { //hack for morphshapes. TODO: make this better if (close && (Double.compare(lastMoveToX, x) != 0 || Double.compare(lastMoveToY, y) != 0)) { StraightEdgeRecordAdvanced ser = new StraightEdgeRecordAdvanced(lastMoveToX - x, lastMoveToY - y); - ret.append(convertShapeEdge(ser, x, y)); + ret.append(convertShapeEdge(mat, ser, x, y, morphshape)); } } @@ -355,28 +374,24 @@ public class XFLConverter { return "normal"; } - private static void convertLineStyle(ILINESTYLE ls, int shapeNum, XFLXmlWriter writer) throws XMLStreamException { + private static void convertLineStyle1(LINESTYLE ls, int shapeNum, XFLXmlWriter writer) throws XMLStreamException { writer.writeStartElement("SolidStroke", new String[]{ "scaleMode", getScaleMode(ls), "weight", Double.toString(((float) ls.getWidth()) / SWF.unitDivisor)}); writer.writeStartElement("fill"); - if (!(ls instanceof LINESTYLE2) || !((LINESTYLE2) ls).hasFillFlag) { - writer.writeStartElement("SolidColor", new String[]{"color", ls.getColor().toHexRGB()}); - if (shapeNum >= 3) { - writer.writeAttribute("alpha", ((RGBA) ls.getColor()).getAlphaFloat()); - } - - writer.writeEndElement(); - } else { - // todo: line fill + writer.writeStartElement("SolidColor", new String[]{"color", ls.getColor().toHexRGB()}); + if (shapeNum >= 3) { + writer.writeAttribute("alpha", ((RGBA) ls.getColor()).getAlphaFloat()); } - writer.writeEndElement(); - writer.writeEndElement(); + writer.writeEndElement(); //SolidColor + + writer.writeEndElement(); //fill + writer.writeEndElement(); //SolidStroke } - private static void convertLineStyle(HashMap characters, LINESTYLE2 ls, int shapeNum, XFLXmlWriter writer) throws XMLStreamException { + private static void convertLineStyle2(MATRIX mat, HashMap characters, LINESTYLE2 ls, int shapeNum, XFLXmlWriter writer) throws XMLStreamException { writer.writeStartElement("SolidStroke", new String[]{"weight", Double.toString(((float) ls.width) / SWF.unitDivisor)}); if (ls.pixelHintingFlag) { writer.writeAttribute("pixelHinting", true); @@ -418,7 +433,7 @@ public class XFLConverter { writer.writeEndElement(); } else { - convertFillStyle(null/* FIXME */, characters, ls.fillType, shapeNum, writer); + convertFillStyle(mat, characters, ls.fillType, shapeNum, writer); } writer.writeEndElement(); @@ -426,10 +441,9 @@ public class XFLConverter { } private static void convertFillStyle(MATRIX mat, HashMap characters, FILLSTYLE fs, int shapeNum, XFLXmlWriter writer) throws XMLStreamException { - /* todo: use matrix - if (mat == null) { - mat = new MATRIX(); - }*/ + if (mat == null) { + mat = new MATRIX(); + } //ret.append(""); switch (fs.fillStyleType) { case FILLSTYLE.SOLID: @@ -461,8 +475,10 @@ public class XFLConverter { writer.writeAttribute("bitmapIsClipped", true); } - writer.writeStartElement("matrix"); - convertMatrix(fs.bitmapMatrix, writer); + writer.writeStartElement("matrix"); + MATRIX bitmapMatrix = fs.bitmapMatrix; + bitmapMatrix = (new Matrix(mat)).concatenate(new Matrix(bitmapMatrix)).toMATRIX(); + convertMatrix(bitmapMatrix, writer); writer.writeEndElement(); writer.writeEndElement(); break; @@ -512,7 +528,11 @@ public class XFLConverter { } writer.writeStartElement("matrix"); - convertMatrix(fs.gradientMatrix, writer); + + MATRIX gradientMatrix = fs.gradientMatrix; + gradientMatrix = (new Matrix(mat)).concatenate(new Matrix(gradientMatrix)).toMATRIX(); + + convertMatrix(gradientMatrix, writer); writer.writeEndElement(); GRADRECORD[] records; if (fs.fillStyleType == FILLSTYLE.FOCAL_RADIAL_GRADIENT) { @@ -793,14 +813,14 @@ public class XFLConverter { if (shapeNum <= 3 && lineStyles.lineStyles != null) { for (int l = 0; l < lineStyles.lineStyles.length; l++) { strokesStr.writeStartElement("StrokeStyle", new String[]{"index", Integer.toString(lineStyleCount + 1)}); - convertLineStyle(lineStyles.lineStyles[l], shapeNum, strokesStr); + convertLineStyle1(lineStyles.lineStyles[l], shapeNum, strokesStr); strokesStr.writeEndElement(); lineStyleCount++; } } else if (lineStyles.lineStyles2 != null) { for (int l = 0; l < lineStyles.lineStyles2.length; l++) { strokesStr.writeStartElement("StrokeStyle", new String[]{"index", Integer.toString(lineStyleCount + 1)}); - convertLineStyle(characters, (LINESTYLE2) lineStyles.lineStyles2[l], shapeNum, strokesStr); + convertLineStyle2(mat, characters, (LINESTYLE2) lineStyles.lineStyles2[l], shapeNum, strokesStr); strokesStr.writeEndElement(); lineStyleCount++; } @@ -855,7 +875,7 @@ public class XFLConverter { currentLayer.writeAttribute("strokeStyle", strokeStyle); } StringBuilder edgesSb = new StringBuilder(); - convertShapeEdges(((fillStyle0 > 0 || fillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb); + convertShapeEdges(((fillStyle0 > 0 || fillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb, morphshape); currentLayer.writeAttribute("edges", edgesSb.toString()); currentLayer.writeEndElement(); hasEdge = true; @@ -887,14 +907,14 @@ public class XFLConverter { if (shapeNum <= 3) { for (int l = 0; l < scr.lineStyles.lineStyles.length; l++) { strokesNewStr.writeStartElement("StrokeStyle", new String[]{"index", Integer.toString(lineStyleCount + 1)}); - convertLineStyle(scr.lineStyles.lineStyles[l], shapeNum, strokesNewStr); + convertLineStyle1(scr.lineStyles.lineStyles[l], shapeNum, strokesNewStr); strokesNewStr.writeEndElement(); lineStyleCount++; } } else { for (int l = 0; l < scr.lineStyles.lineStyles2.length; l++) { strokesNewStr.writeStartElement("StrokeStyle", new String[]{"index", Integer.toString(lineStyleCount + 1)}); - convertLineStyle(characters, (LINESTYLE2) scr.lineStyles.lineStyles2[l], shapeNum, strokesNewStr); + convertLineStyle2(mat, characters, (LINESTYLE2) scr.lineStyles.lineStyles2[l], shapeNum, strokesNewStr); strokesNewStr.writeEndElement(); lineStyleCount++; } @@ -942,7 +962,7 @@ public class XFLConverter { currentLayer.writeAttribute("strokeStyle", lastStrokeStyle); } StringBuilder edgesSb = new StringBuilder(); - convertShapeEdges(((lastFillStyle0 > 0 || lastFillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb); + convertShapeEdges(((lastFillStyle0 > 0 || lastFillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb, morphshape); currentLayer.writeAttribute("edges", edgesSb.toString()); currentLayer.writeEndElement(); hasEdge = true; @@ -970,7 +990,7 @@ public class XFLConverter { currentLayer.writeAttribute("strokeStyle", strokeStyle); } StringBuilder edgesSb = new StringBuilder(); - convertShapeEdges(((fillStyle0 > 0 || fillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb); + convertShapeEdges(((fillStyle0 > 0 || fillStyle1 > 0) && morphshape), startEdgeX, startEdgeY, mat, edges, edgesSb, morphshape); currentLayer.writeAttribute("edges", edgesSb.toString()); currentLayer.writeEndElement(); hasEdge = true; @@ -1546,21 +1566,27 @@ public class XFLConverter { return date.getTime() / 1000; } - private void convertLibrary(SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { + private void convertLibrary(SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol) throws XMLStreamException { //TODO: Imported assets //linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf" convertMedia(swf, characterVariables, characterClasses, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer); - convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer); + convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, placeToMaskedSymbol); } - private void convertSymbols(SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { - boolean hasSymbol = false; + private void convertSymbols(SWF swf, Map characterVariables, Map characterClasses, Map characterScriptPacks, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer, Map placeToMaskedSymbol) throws XMLStreamException { + boolean hasSymbol = false; + Reference nextClipId = new Reference<>(-1); + writer.writeStartElement("symbols"); for (int ch : characters.keySet()) { CharacterTag symbol = characters.get(ch); if ((symbol instanceof ShapeTag) && nonLibraryShapes.contains(symbol.getCharacterId())) { continue; //shapes with 1 ocurrence and single layer are not added to library } + + /*if (!hasSymbol) { + + }*/ if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) { XFLXmlWriter symbolStr = new XFLXmlWriter(); @@ -1760,7 +1786,10 @@ public class XFLConverter { continue; } final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite.spriteId) ? characterScriptPacks.get(sprite.spriteId) : null; - convertTimeline(swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack); + + extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol); + + convertTimeline(swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol); } else if (symbol instanceof ShapeTag) { symbolStr.writeStartElement("timeline"); @@ -1781,11 +1810,7 @@ public class XFLConverter { symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); String symbolFile = "Symbol " + symbol.getCharacterId() + ".xml"; - files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); - - if (!hasSymbol) { - writer.writeStartElement("symbols"); - } + files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); // write symbLink writer.writeStartElement("Include", new String[]{"href", symbolFile}); @@ -1802,9 +1827,11 @@ public class XFLConverter { } } - if (hasSymbol) { - writer.writeEndElement(); - } + extractMultilevelClips(swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol); + /*if (hasSymbol) { + + }*/ + writer.writeEndElement(); } private void convertMedia(SWF swf, Map characterVariables, Map characterClasses, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, HashMap characters, HashMap files, HashMap datfiles, FLAVersion flaVersion, XFLXmlWriter writer) throws XMLStreamException { @@ -3051,8 +3078,348 @@ public class XFLConverter { ret.add(0, varName); return String.join(".", ret); } + + + private void addExtractedClip( + ReadOnlyTagList timelineTags, + XFLXmlWriter writer, + SWF swf, + Reference nextClipId, + List nonLibraryShapes, + String backgroundColor, + HashMap characters, + FLAVersion flaVersion, + HashMap files, + Map placeToMaskedSymbol + ) throws XMLStreamException { + XFLXmlWriter symbolStr = new XFLXmlWriter(); - private void convertTimeline(AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, String name, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, ScriptPack scriptPack) throws XMLStreamException { + + if (nextClipId.getVal() < 0) { + nextClipId.setVal(swf.getNextCharacterId()); + } else { + nextClipId.setVal(nextClipId.getVal() + 1); + } + + int objectId = nextClipId.getVal(); + + symbolStr.writeStartElement("DOMSymbolItem", new String[]{ + "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", + "xmlns", "http://ns.adobe.com/xfl/2008/", + "name", generateMaskedSymbolName(objectId), + "lastModified", Long.toString(getTimestamp(swf))}); + symbolStr.writeAttribute("symbolType", "graphic"); + + convertTimeline(swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol); + + + symbolStr.writeEndElement(); // DOMSymbolItem + String symbolStr2 = prettyFormatXML(symbolStr.toString()); + String symbolFile = generateMaskedSymbolName(objectId) + ".xml"; + files.put(symbolFile, Utf8Helper.getBytes(symbolStr2)); + + writer.writeStartElement("Include", new String[]{"href", symbolFile}); + writer.writeAttribute("itemIcon", "1"); + writer.writeAttribute("loadImmediate", false); + if (flaVersion.ordinal() >= FLAVersion.CS5_5.ordinal()) { + writer.writeAttribute("lastModified", getTimestamp(swf)); + //TODO: itemID="518de416-00000341" + } + writer.writeEndElement(); + + extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol); + } + + private String generateMaskedSymbolName(int symbolId) { + return (DEBUG_EXPORT_LAYER_DEPTHS ? "MaskedSymbol " : "Symbol ") + symbolId; + } + + private void extractMultilevelClips(ReadOnlyTagList timelineTags, + XFLXmlWriter writer, + SWF swf, + Reference nextClipId, + List nonLibraryShapes, + String backgroundColor, + HashMap characters, + FLAVersion flaVersion, + HashMap files, + Map placeToMaskedSymbol + ) throws XMLStreamException { + int f = 0; + + List clipPlaces = new ArrayList<>(); + Map depthToClipPlace = new HashMap<>(); + Map clipFinishFrames = new HashMap<>(); + Map clipStartFrames = new HashMap<>(); + + int maxDepth = getMaxDepth(timelineTags); + Tag lastTag = null; + for (Tag t : timelineTags) { + if (t instanceof ShowFrameTag) { + f++; + } + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + if (po.getClipDepth() > -1) { + clipStartFrames.put(po, f); + clipPlaces.add(po); + if (depthToClipPlace.containsKey(po.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); + } + depthToClipPlace.put(po.getDepth(), po); + } else { + if (!po.flagMove() && depthToClipPlace.containsKey(po.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); + depthToClipPlace.remove(po.getDepth()); + } + } + } + if (t instanceof RemoveTag) { + RemoveTag re = (RemoveTag) t; + if (depthToClipPlace.containsKey(re.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(re.getDepth()), f - 1); + depthToClipPlace.remove(re.getDepth()); + } + } + lastTag = t; + } + + if (clipPlaces.isEmpty()) { + return; + } + + //Some sprites do not end with ShowFrame: + if (lastTag != null && !(lastTag instanceof ShowFrameTag)) { + f++; + } + + int frameCount = f; + + if (!depthToClipPlace.isEmpty()) { + for (PlaceObjectTypeTag po : depthToClipPlace.values()) { + clipFinishFrames.put(po, frameCount - 1); + } + } + + Map> depthToFramesList = new HashMap<>(); + for (int d = maxDepth; d >= 0; d--) { + depthToFramesList.put(d, new ArrayList<>()); + for (int i = 0; i < frameCount; i++) { + depthToFramesList.get(d).add(i); + } + } + + Map>> frameToDepthToClips = new TreeMap<>(); + + for (f = 0; f < frameCount; f++) { + for (int d = 0; d < maxDepth; d++) { + for (int p = 0; p < clipPlaces.size(); p++) { + PlaceObjectTypeTag po = clipPlaces.get(p); + int startFrame = clipStartFrames.get(po); + int finishFrame = clipFinishFrames.get(po); + if (f >= startFrame && f <= finishFrame) { + if (d >= po.getDepth() && d <= po.getClipDepth()) { + if (!frameToDepthToClips.containsKey(f)) { + frameToDepthToClips.put(f, new TreeMap<>()); + } + if (!frameToDepthToClips.get(f).containsKey(d)) { + frameToDepthToClips.get(f).put(d, new ArrayList<>()); + } + frameToDepthToClips.get(f).get(d).add(po); + } + } + } + } + } + + Set delegatedPlaces = new HashSet<>(); + + for (int fr : frameToDepthToClips.keySet()) { + for (int d : frameToDepthToClips.get(fr).keySet()) { + List places = frameToDepthToClips.get(fr).get(d); + if (places.size() > 1) { + depthToFramesList.get(d).remove((Integer) fr); + PlaceObjectTypeTag secondPlace = places.get(1); + if (delegatedPlaces.contains(secondPlace)) { + continue; + } + delegatedPlaces.add(secondPlace); + + List delegatedTimeline = new ArrayList<>(); + f = 0; + boolean removed = false; + int numFrames = 0; + lastTag = null; + Map depthStates = new HashMap<>(); + + if (nextClipId.getVal() == 134) { + System.err.println("xxx"); + } + for (Tag t : timelineTags) { + if (f < fr) { + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; + if (depthStates.containsKey(place.getDepth()) && !place.flagMove()) { + continue; + } + if (place.getCharacterId() != -1) { + depthStates.put(place.getDepth(), place.getCharacterId()); + } + } + if (t instanceof RemoveTag) { + depthStates.remove(((RemoveTag) t).getDepth()); + } + } + if (f >= fr) { + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; + if (place.getDepth() == secondPlace.getDepth()) { + if (place.flagMove()) { + removed = false; + } else if (place.getClipDepth() == secondPlace.getClipDepth()) { + removed = false; + delegatedPlaces.add(place); + } else { + removed = true; + } + } + if (!removed && place.getDepth() >= secondPlace.getDepth() && place.getDepth() <= secondPlace.getClipDepth()) { + delegatedTimeline.add(place); + } + } + if (t instanceof RemoveTag) { + RemoveTag rt = (RemoveTag) t; + if (rt.getDepth() == secondPlace.getDepth()) { + removed = true; + } + } + } + lastTag = t; + if (t instanceof ShowFrameTag) { + if (f >= fr) { + delegatedTimeline.add(t); + numFrames++; + } + if (removed) { + break; + } + f++; + } + } + if (!(lastTag instanceof ShowFrameTag)) { + numFrames++; + ShowFrameTag showFrame = new ShowFrameTag(swf); + //set timelined? + delegatedTimeline.add(showFrame); + } + + /* + List delegatedTimeline2 = Helper.deepCopy(delegatedTimeline); + for (int i = 0; i < delegatedTimeline2.size(); i++) { + delegatedTimeline2.get(i).setSwf(swf); + } + + for (Tag t : delegatedTimeline2) { + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; + if (depthStates.containsKey(place.getDepth()) && !place.flagMove()) { + continue; + } + if (place.getCharacterId() != -1) { + depthStates.put(place.getDepth(), place.getCharacterId()); + } + if (place.flagMove()) { + place.setCharacterId(depthStates.get(place.getDepth())); + place.setPlaceFlagMove(false); + } + } + if (t instanceof RemoveTag) { + depthStates.remove(((RemoveTag)t).getDepth()); + } + } + + //find shape looping, find largest loop + int found = -1; + loopi: for (int i = numFrames - 1; i >= 1; i--) { + System.err.println("checking len = " + i); + List firstBatch = new ArrayList<>(); + Map> firstFramesDepthStates = new HashMap<>(); + Map curDepthStates = new LinkedHashMap<>(); + + f = 0; + for (Tag t : delegatedTimeline2) { + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; + curDepthStates.put(place.getDepth(), place); + } + if (t instanceof RemoveTag) { + RemoveTag rem = (RemoveTag) t; + curDepthStates.remove(rem.getDepth()); + } + if (t instanceof ShowFrameTag) { + firstFramesDepthStates.put(f, new LinkedHashMap<>(curDepthStates)); + f++; + if (f == i) { + break; + } + } + } + + int numParts = (int) Math.ceil(numFrames / (float) i); //example 4 + if (numParts < 2) { + continue; + } + + + f = 0; + curDepthStates = new LinkedHashMap<>(); + boolean same = true; + loopt: for (Tag t : delegatedTimeline2) { + if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag place = (PlaceObjectTypeTag) t; + curDepthStates.put(place.getDepth(), place); + } + if (t instanceof RemoveTag) { + RemoveTag rem = (RemoveTag) t; + curDepthStates.remove(rem.getDepth()); + } + if (t instanceof ShowFrameTag) { + int firstF = f % i; + Map firstFDepthStates = firstFramesDepthStates.get(firstF); + if (firstFDepthStates.size() == curDepthStates.size()) { + for (int k : curDepthStates.keySet()) { + if (!firstFDepthStates.containsKey(k)) { + same = false; + break loopt; + } + PlaceObjectTypeTag p1 = firstFDepthStates.get(k); + PlaceObjectTypeTag p2 = curDepthStates.get(k); + if (!p1.placeEquals(p2)) { + same = false; + break loopt; + } + } + } else { + same = false; + break loopt; + } + f++; + } + } + if (same) { + System.err.println("same"); + found = f; + } + } + System.err.println("clip repeats in " + found);*/ + addExtractedClip(new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol); + placeToMaskedSymbol.put(secondPlace, new MultiLevelClip(secondPlace, nextClipId.getVal(), numFrames)); + } + } + } + } + + private void convertTimeline(AbcIndexing abcIndex, int spriteId, String linkageIdentifier, List nonLibraryShapes, String backgroundColor, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, String name, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, ScriptPack scriptPack, Map placeToMaskedSymbol) throws XMLStreamException { List classNames = new ArrayList<>(); //Searches for Object.registerClass("linkageIdentifier",mypkg.MyClass); @@ -3151,7 +3518,10 @@ public class XFLConverter { Map depthToClipPlace = new HashMap<>(); Map clipFinishFrames = new HashMap<>(); + Map clipStartFrames = new HashMap<>(); + Map placeToFirstCharacterDepth = new HashMap<>(); Tag lastTag = null; + int tpos = 0; for (Tag t : timelineTags) { if (t instanceof ShowFrameTag) { f++; @@ -3160,8 +3530,28 @@ public class XFLConverter { PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; if (po.getClipDepth() > -1) { clipFrameSplitters.add(f); + clipStartFrames.put(po, f); clipPlaces.add(po); - depthToClipPlace.put(po.getDepth(), po); + + if (depthToClipPlace.containsKey(po.getDepth())) { + clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); + } + + depthToClipPlace.put(po.getDepth(), po); + for (int j = tpos + 1; j <= timelineTags.size(); j++) { + Tag t2 = timelineTags.get(j); + if (t2 instanceof PlaceObject2Tag) { + PlaceObject2Tag pl = (PlaceObject2Tag) t2; + int d = pl.getDepth(); + if (d >= po.getDepth() && d <= po.getClipDepth()) { + placeToFirstCharacterDepth.put(po, d); + } + } + if (t2 instanceof ShowFrameTag) { + break; + } + + } } else { if (!po.flagMove() && depthToClipPlace.containsKey(po.getDepth())) { clipFinishFrames.put(depthToClipPlace.get(po.getDepth()), f - 1); @@ -3177,6 +3567,7 @@ public class XFLConverter { } } lastTag = t; + tpos++; } //Some sprites do not end with ShowFrame: @@ -3207,33 +3598,207 @@ public class XFLConverter { depthToFramesList.get(d).add(i); } } - for (int d = maxDepth; d >= 0; d--) { + + Map>> frameToDepthToClips = new TreeMap<>(); + + /*if (spriteId == 116) { + System.err.println("xxx"); + }*/ + for (f = 0; f < frameCount; f++) { + for (int d = 0; d < maxDepth; d++) { + for (int p = 0; p < clipPlaces.size() - 1; p++) { + PlaceObjectTypeTag po = clipPlaces.get(p); + if (po == null) { + continue; + } + int startFrame = clipStartFrames.get(po); + int finishFrame = clipFinishFrames.get(po); + if (f >= startFrame && f <= finishFrame) { + if (d >= po.getDepth() && d <= po.getClipDepth()) { + if (!frameToDepthToClips.containsKey(f)) { + frameToDepthToClips.put(f, new TreeMap<>()); + } + if (!frameToDepthToClips.get(f).containsKey(d)) { + frameToDepthToClips.get(f).put(d, new ArrayList<>()); + } + frameToDepthToClips.get(f).get(d).add(po); + } + } + } + } + } + + Set multiLevelsPlaces = new HashSet<>(); + + Set secondLevelPlaces = new HashSet<>(); + Map secondToFirstLevelPlace = new HashMap<>(); + for (int fr : frameToDepthToClips.keySet()) { + for (int d : frameToDepthToClips.get(fr).keySet()) { + List places = frameToDepthToClips.get(fr).get(d); + if (places.size() > 1) { + //depthToFramesList.get(d).remove((Integer) fr); + for (int i = 1; i < places.size(); i++) { + multiLevelsPlaces.add(places.get(i)); + } + secondLevelPlaces.add(places.get(1)); + secondToFirstLevelPlace.put(places.get(1), places.get(0)); + } + } + } + - for (int p = 0; p < clipPlaces.size() - 1; p++) { + + Set handledClips = new HashSet<>(); + for (int d = maxDepth; d >= 0; d--) { + loopp: for (int p = 0; p < clipPlaces.size() - 1; p++) { PlaceObjectTypeTag po = clipPlaces.get(p); + /* if (po != null && po.getClipDepth() == d && secondLevelPlaces.contains(po)) { + int clipFrame = clipFrameSplitters.get(p); + int nextFrame = clipFinishFrames.get(po); + + + continue; + }*/ + if (po != null && multiLevelsPlaces.contains(po)) { + continue; + } + if (po != null && handledClips.contains(po)) { + continue; + } if (po != null && po.getClipDepth() == d) { int clipFrame = clipFrameSplitters.get(p); - int nextFrame = clipFinishFrames.get(po); + int nextFrame = clipFinishFrames.get(po); + handledClips.add(po); + + + int lastFrame = nextFrame; + for (int p2 = 0; p2 < clipPlaces.size() - 1; p2++) { + PlaceObjectTypeTag po2 = clipPlaces.get(p2); + if (po2 == null) { + continue; + } + int clipFrame2 = clipFrameSplitters.get(p2); + int nextFrame2 = clipFinishFrames.get(po2); + if (lastFrame + 1 == clipFrame2 + && po.getDepth() == po2.getDepth() + && po.getClipDepth() == po2.getClipDepth() + && !multiLevelsPlaces.contains(po2) + ) { + lastFrame = nextFrame2; + handledClips.add(po2); + } + } + writer.writeStartElement("DOMLayer", new String[]{ "name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + po.getDepth() + " clipdepth:" + po.getClipDepth() + ")" : ""), "color", randomOutlineColor(), "layerType", "mask", "locked", "true"}); - convertFrames(null, clipFrame, nextFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer); + convertFrames(depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer); writer.writeEndElement(); - + int parentIndex = index; index++; - for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { - boolean nonEmpty = writeLayer(index, depthToFramesList.get(nd), nd, clipFrame, nextFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files); - for (int i = clipFrame; i <= nextFrame; i++) { + + //Set processedClips = new HashSet<>(); + for (int fx = clipFrame; fx <= lastFrame; fx++) { + for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { + if (!depthToFramesList.containsKey(nd) || !depthToFramesList.get(nd).contains(fx)) { + continue; + } + if (frameToDepthToClips.containsKey(fx) + && frameToDepthToClips.get(fx).containsKey(nd)) { + List clips = frameToDepthToClips.get(fx).get(nd); + if (clips.size() > 1) { + PlaceObjectTypeTag po2 = clips.get(1); + if (handledClips.contains(po2)) { + continue; + } + handledClips.add(po2); + + MultiLevelClip mlc = placeToMaskedSymbol.get(po2); + + writer.writeStartElement("DOMLayer", new String[]{ + "name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + po2.getDepth() + " clipdepth:" + po2.getClipDepth() + " maskedid:" + mlc.symbol + ")" : ""), + "color", randomOutlineColor(), + "parentLayerIndex", "" + parentIndex, + "locked", "true" + }); + writer.writeStartElement("frames"); + + int clipFrame2 = 0; + for (int p2 = 0; p2 < clipPlaces.size() - 1; p2++) { + if (clipPlaces.get(p2) == po2) { + clipFrame2 = clipFrameSplitters.get(p2); + } + } + //int nextFrame2 = clipFinishFrames.get(po2); + + if (clipFrame2 > 0) { + writer.writeStartElement("DOMFrame", new String[]{ + "index", "0", + "duration", "" + clipFrame2, + "keyMode", "" + KEY_MODE_NORMAL + }); + writer.writeEmptyElement("elements"); + writer.writeEndElement(); + } + + + + if (mlc.symbol == 135) { + System.err.println("xxx"); + } + writer.writeStartElement("DOMFrame", new String[]{ + "index", "" + clipFrame2, + "duration", "" + mlc.numFrames, + "keyMode", "" + KEY_MODE_NORMAL + }); + writer.writeStartElement("elements"); + writer.writeStartElement("DOMSymbolInstance", new String[]{ + "libraryItemName", generateMaskedSymbolName(mlc.symbol), + "symbolType", "graphic", + "loop", "loop" + }); + + writer.writeStartElement("matrix"); + convertMatrix(new MATRIX(), writer); + writer.writeEndElement(); //matrix + + writer.writeStartElement("transformationPoint"); + writer.writeEmptyElement("Point"); + writer.writeEndElement(); //transformationPoint + + writer.writeEndElement(); //DOMSymbolInstance + writer.writeEndElement(); //elements + writer.writeEndElement(); //DOMFrame + + + writer.writeEndElement(); //frames + + writer.writeEndElement(); + index++; + + for (int nd2 = po2.getDepth(); nd2 <= po2.getClipDepth(); nd2++) { + for (int i = clipFrame2; i < clipFrame2 + mlc.numFrames; i++) { + depthToFramesList.get(nd2).remove((Integer) i); + } + } + } + } + } + } + + for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { + boolean nonEmpty = writeLayer(index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files); + for (int i = clipFrame; i <= lastFrame; i++) { depthToFramesList.get(nd).remove((Integer) i); } if (nonEmpty) { index++; } } - for (int i = clipFrame; i <= nextFrame; i++) { + for (int i = clipFrame; i <= lastFrame; i++) { depthToFramesList.get(po.getDepth()).remove((Integer) i); } } @@ -3842,12 +4407,14 @@ public class XFLConverter { domDocument.writeAttribute("height", doubleToString(height)); } - convertFonts(characterClasses, swf.getTags(), domDocument); - convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument); + Map placeToMaskedSymbol = new HashMap<>(); + + convertFonts(characterClasses, swf.getTags(), domDocument); + convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol); //domDocument.writeStartElement("timelines"); ScriptPack documentScriptPack = characterScriptPacks.containsKey(0) ? characterScriptPacks.get(0) : null; - convertTimeline(swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack); + convertTimeline(swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol); //domDocument.writeEndElement(); if (hasAmfMetadata) { @@ -4624,4 +5191,16 @@ public class XFLConverter { } } } + + private class MultiLevelClip { + public PlaceObjectTypeTag startClipPlaceTag; + public int symbol; + public int numFrames; + + public MultiLevelClip(PlaceObjectTypeTag startClipPlaceTag, int symbol, int numFrames) { + this.startClipPlaceTag = startClipPlaceTag; + this.symbol = symbol; + this.numFrames = numFrames; + } + } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/helpers/ByteArrayRange.java b/libsrc/ffdec_lib/src/com/jpexs/helpers/ByteArrayRange.java index 48345ef9f..9ecb2baea 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/helpers/ByteArrayRange.java +++ b/libsrc/ffdec_lib/src/com/jpexs/helpers/ByteArrayRange.java @@ -17,12 +17,13 @@ package com.jpexs.helpers; import com.jpexs.decompiler.flash.SWFInputStream; +import java.io.Serializable; /** * * @author JPEXS */ -public class ByteArrayRange { +public class ByteArrayRange implements Serializable { public static final ByteArrayRange EMPTY = new ByteArrayRange(SWFInputStream.BYTE_ARRAY_EMPTY); diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks.html b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks.html new file mode 100644 index 000000000..a8d93c232 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks.html @@ -0,0 +1,49 @@ + + + + nested_masks + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + Get Adobe Flash player + + + + + +
+ + diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks.swf b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks.swf new file mode 100644 index 0000000000000000000000000000000000000000..5fb9656ae900fb807bac64c167ebfda8f67b8e7e GIT binary patch literal 1652 zcmV-)28;PaS5psj3IG6joTXF?OjBnV{aTJ;46_=0W5L^hFSE-I*ViW~Tt#^E+~vHNdn zD?Vn_HEsIc+jCCOcfRkx4hWb9Ft7$7Iuw)<;Q+8GGCTlal2xadPgklihskKB<>+d1 zOtH;ok>j}CZs*%Ye9Bsc3uH1GP6%#-@3iEELJbgj}51pZo;YL#K2Gec4NqA0x3z_)|%~T(&x` zFIr2f)##p=PK%R9(nOkVG>TW?j#sCZ>nW>AV@uLlEJlM?!wBLI9$idnXWKP%NuHjG zdJ_H^__~%*XKM9}2W~RqUNmi+PU>HbrnAc|BtD&_sZy(!MDWp`g;A^JQ_QqYW7d*W zlu76Y-(b+mWeGxYvP_jC5GP8~(gXsTKqgF0kR_{xX%c}-DRpQ6B-N{?q_m|>ZXPw= zE2#COdet(lh9U!6+2^PVMTshjP%2I#5)&9zsUU%niV_o~0-01oq)NQ1&ynzXC9Q_J zq)ug}OztUIG*+5qlA0Xjjf#oMov+;RY)lEMLa|bmmZ%i>S$MXwXQ*B^-Rm^;4ArYf z{hyfrQv&l1=5bEXUuoR)5KO`#mF;r5nqf40K(e6hn7HCfQE)ga0bCFs)idOH+Xq>} z=k0Bs$L7X_!*ULX#r)U|YJ^~eV|8!zH~woLIT->VLfbE*RXbC@JF(%2up#%(k2`H_|EBRyHaC$~hc=1RZ*q?nb;R2onI7aVyr(UssNKTi88{{o1wR8TPSKIfD`25_>nj&>aux)0bY0JdH_gh`;d9jhTTE-*? zQ9UM85hoBe7nBjDw|&BLHx?eX=O@d6iVGhfY~k_=zr@ZR)txPovX)4R`p}1Q)Y}Sf zL_3@uQ>5GO3_afxdj61-5e-5*9#JOXAh6_cwnsP*MEg2_M-}+h@dXdpa2m3t9T;x6U`o^My5rsp+^cj+!Ryc@9n zmo{&|VCd?Acy$eQZj9d;@xza$Kg*Plb~^{=fZy;%v4gsrnn`DXBpq~XCMqj4Abh5I z*fQ=RCwp)~NnUzeZL)&BJ_SO9^S3R@Z3%2}vaWC4aEQL2fA})H`WT4*tlf0QHTZf3 zw;kdzG~SC_Mw1Fw!2aG_u~#!<+gVdhz`4+vzIW}~>M!oM#@#c*yn2YfdY{-acEiI7 z5Z_ebH^$I0W|IS!d~I0~AJ+~k--o?BrRq%mTQGg4g0*rQ~Sk-h~S01n3c^^=M^d&#VEh>9+P2g&oWZ2tz0Yk3sNIuv{(r zYh2zP<{zr65d7OU4f!`!TT8?b`T3pJb_PEX!9alyTL3x*+eF<=72vBlzADI91^X(l zuL|v}6tIjF!G)Y0PqDK>0qj67^0N^62?OtnxYxtI8(3Ivr=3nXwK?sSEH2|cf~Bs6 zAy{M9m|X>aHx%$-|Ba~F%Z+ttOeMfMo4Lvz=QRZ@gh=3jDPzau!;o8URuB+l&C7%H yVPpdYHZ08TIepE1+sQTm4W~Oc&R+dbeofy5!n{5iSKgVt$oZ?Y0R9C)jAKtQHXA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/LIBRARY/Rectangles.xml b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/LIBRARY/Rectangles.xml new file mode 100644 index 000000000..4cdba6bd8 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/LIBRARY/Rectangles.xml @@ -0,0 +1,197 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/META-INF/metadata.xml b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/META-INF/metadata.xml new file mode 100644 index 000000000..bac0a5fdc --- /dev/null +++ b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/META-INF/metadata.xml @@ -0,0 +1,67 @@ + + + + + Adobe Flash Professional CS6 - build 481 + 2023-10-28T09:36:22-07:00 + 2023-10-28T13:59:37-07:00 + 2023-10-28T13:59:37-07:00 + + + application/vnd.adobe.fla + + + xmp.iid:5C24D3E7D475EE119192C59AF2E61FD8 + xmp.did:237F6284B075EE1181508375819860C6 + xmp.did:237F6284B075EE1181508375819860C6 + + + + created + xmp.iid:237F6284B075EE1181508375819860C6 + 2023-10-28T09:36:22-07:00 + Adobe Flash Professional CS6 - build 481 + + + created + xmp.iid:277F6284B075EE1181508375819860C6 + 2023-10-28T09:36:22-07:00 + Adobe Flash Professional CS6 - build 481 + + + created + xmp.iid:5C24D3E7D475EE119192C59AF2E61FD8 + 2023-10-28T09:36:22-07:00 + Adobe Flash Professional CS6 - build 481 + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/MobileSettings.xml b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/MobileSettings.xml new file mode 100644 index 000000000..e69de29bb diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/PublishSettings.xml b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/PublishSettings.xml new file mode 100644 index 000000000..8d6af5533 --- /dev/null +++ b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/PublishSettings.xml @@ -0,0 +1,206 @@ + + + + 1 + 1 + 0 + 0 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + nested_masks.swf + nested_masks.exe + nested_masks.app + nested_masks.html + nested_masks.gif + nested_masks.jpg + nested_masks.png + nested_masks.mov + nested_masks.smil + nested_masks.swc + + + 0 + 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; + 1 + 1 + nested_masks.xfl_content.html + nested_masks.xfl_alternate.html + 0 + + 550 + 400 + 0 + 0 + 1 + 0 + 0 + 1 + 1 + 4 + 0 + 0 + 1 + 0 + C:\Users\MyUser\AppData\Local\Adobe\Flash CS6\en_US\Configuration\HTML\Default.html + 1 + + + + + 0 + 0 + 0 + 80 + 0 + 0 + 7 + 0 + 7 + 0 + 15 + FlashPlayer11.2 + 2 + 1 + + . + CONFIG::FLASH_AUTHORING="true"; + 0 + + 1 + 0 + 1 + 0 + 0 + 0 + 0 + + 2 + 4 + 4096 + AS3 + 1 + 1 + 0 + 15 + 1 + 0 + 4102 + rsl + wrap + $(AppConfig)/ActionScript 3.0/rsls/loader_animation.swf + + + $(AppConfig)/ActionScript 3.0/libs + merge + + + $(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc + rsl + http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz + http://fpdownload.adobe.com/pub/swz/crossdomain.xml + textLayout_2.0.0.232.swz + + + + + $(AppConfig)/ActionScript 3.0/libs/11.0/textLayout.swc + + http://fpdownload.adobe.com/pub/swz/tlf/2.0.0.232/textLayout_2.0.0.232.swz + http://fpdownload.adobe.com/pub/swz/crossdomain.xml + textLayout_2.0.0.232.swz + + + + + 550 + 400 + 0 + 4718592 + 0 + 80 + 1 + + + 1 + 0 + 1 + 0 + 0 + 100000 + 1 + 1 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + + + 550 + 400 + 0 + 1 + 1 + + 1 + 0 + 1 + 0 + 0 + + 128 + + + 255 + + + + 550 + 400 + 1 + 0 + 0 + 1 + 0 + 0 + 1 + + + + 24-bit with Alpha + 255 + + + + 550 + 400 + 1 + 0 + + + 00000000 + 0 + 0 + 0 + 0 + 1 + + + \ No newline at end of file diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/bin/SymDepend.cache b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/bin/SymDepend.cache new file mode 100644 index 0000000000000000000000000000000000000000..c46b02f0be88c36f107fabf7e6f4309ac5c85892 GIT binary patch literal 46 wcmYdiU|@L8&c(pSAj2Tepur#j4?|FDa!F!ddQNIFgW$fkKp`Li0F+AyVgLXD literal 0 HcmV?d00001 diff --git a/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/nested_masks.xfl b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/nested_masks.xfl new file mode 100644 index 000000000..860a820ec --- /dev/null +++ b/libsrc/ffdec_lib/testdata/nested_masks/nested_masks/nested_masks.xfl @@ -0,0 +1 @@ +PROXY-CS5 \ No newline at end of file