diff --git a/CHANGELOG.md b/CHANGELOG.md index fa318b2cf..6e863aa08 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. - [#2100] Copy/paste frames (same SWF only) - Updated portugese-brasil translation - AS3 Debugging - export/import ByteArray variable data -- [#2123] FLA export - show some progress info (which symbols are exported) +- [#2123] FLA export - show some progress info ### Fixed - [#2021], [#2000] Caret position in editors when using tabs and / or unicode @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file. - Debugging - do not invoke getter when there is none - avoid freezing - Debugging - properly getting variable value through getter - [#2123] FLA export - IndexOutOfBounds in shape fixer +- [#2123] FLA export - morphshapes fixer ### Removed - [#2123] FLA export - Using shape fixer for morphshapes (needs something better) diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java index 85d2acc21..cb18476fc 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/BezierEdge.java @@ -17,6 +17,10 @@ package com.jpexs.decompiler.flash.math; import com.jpexs.helpers.Reference; +import java.awt.Shape; +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.io.Serializable; @@ -489,12 +493,68 @@ public class BezierEdge implements Serializable { BezierEdge qm = new BezierEdge(-1957, 2676, -1957, 2676.5, -1957.5, 2676.5); BezierEdge qn = new BezierEdge(-1957, 2675.5, -1957, 2676, -1957, 2676); - List ps = new ArrayList<>(); + /*List ps = new ArrayList<>(); qm.intersects(qn, t1, t2, ps); System.err.println("t1 is " + t1); System.err.println("t2 is " + t2); System.err.println("intersections is " + ps); +*/ + /*Shape r1 = new Rectangle2D.Double(0, 0, 200, 100); + GeneralPath r2 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + r2.moveTo(0, 0); + r2.lineTo(100, 0); + r2.lineTo(150, 0); + r2.lineTo(200, 0); + r2.lineTo(200, 100); + r2.lineTo(0, 100); + r2.closePath(); + + GeneralPath sh1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + sh1.moveTo(0, 0); + sh1.quadTo(100, 100, 0, 100); + sh1.lineTo(0, 0); + sh1.closePath(); + + BezierEdge bex = new BezierEdge(0, 0, 100, 100, 0, 100); + Reference bex1Ref = new Reference<>(null); + Reference bex2Ref = new Reference<>(null); + bex.split(0.2, bex1Ref, bex2Ref); + BezierEdge bex1 = bex1Ref.getVal(); + BezierEdge bex2 = bex2Ref.getVal(); + GeneralPath sh2 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + sh2.moveTo(bex1.getBeginPoint().getX(), bex1.getBeginPoint().getY()); + sh2.quadTo(10+bex1.points.get(1).getX(), bex1.points.get(1).getY(), bex1.getEndPoint().getX(), bex1.getEndPoint().getY()); + sh2.quadTo(bex2.points.get(1).getX(), bex2.points.get(1).getY(), bex2.getEndPoint().getX(), bex2.getEndPoint().getY()); + sh2.lineTo(0, 0); + sh2.closePath(); + + Area a1 = new Area(r1); + Area a2 = new Area(r2); + //a1.exclusiveOr(a2); + + + */ + + /*GeneralPath p1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + p1.moveTo(0, 0); + p1.lineTo(200, 0); + p1.lineTo(200, 200); + p1.lineTo(0, 200); + p1.lineTo(0, 0); + p1.closePath(); + p1.moveTo(50, 50); + p1.lineTo(150, 50); + p1.lineTo(150, 150); + p1.lineTo(50, 150); + p1.lineTo(50, 50); + p1.closePath(); + System.err.println("cont:" + p1.contains(100, 100)); + System.err.println("cont:" + p1.contains(150, 150)); */ + + //System.err.println("minDist = " + minDist+", maxDist = "+ maxDist); + + //System.err.println("eArea = " + Areas.calcArea(a1)); /*Point2D c = new Point2D.Double( (1 - t1.get(0)) * be5.points.get(0).getX() + t1.get(0) * be5.points.get(1).getX(), diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Distances.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Distances.java new file mode 100644 index 000000000..69d8e6851 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/math/Distances.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2010-2023 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.math; + +import java.awt.geom.Area; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.awt.geom.Point2D; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +public class Distances { + public static double getBatchDistance(List batch1, List batch2) { + Area a1 = batchToArea(batch1); + Area a2 = batchToArea(batch2); + return areaDist(a1, a2); + } + + private static Area batchToArea(List batch) { + GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + if (batch.isEmpty()) { + return new Area(); + } + path.moveTo(batch.get(0).getBeginPoint().getX(), batch.get(0).getBeginPoint().getY()); + for (BezierEdge be : batch) { + if (be.points.size() == 3) { + path.quadTo(be.points.get(1).getX(), + be.points.get(1).getY(), + be.points.get(2).getX(), + be.points.get(2).getY()); + } else { + path.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY()); + } + } + path.closePath(); + return new Area(path); + } + + private static double areaDist(Area a1, Area a2) { + List points1 = getAreaPoints(a1); + List points2 = getAreaPoints(a2); + double minDist = Double.MAX_VALUE; + double maxDist = 0; + for (Point2D p : points1) { + double dist = Double.MAX_VALUE; + for (Point2D p2 : points2) { + double d = p.distance(p2); + if (d < dist) { + dist = d; + } + } + if (dist < minDist) { + minDist = dist; + } + if (dist > maxDist) { + maxDist = dist; + } + } + return maxDist; + } + + private static List getAreaPoints(Area area) { + double F = 1.0; + List points = new ArrayList<>(); + PathIterator pi = area.getPathIterator(null, 0.1); + double[] coords = new double[6]; + double xPrev = 0; + double yPrev = 0; + double xBegin = 0; + double yBegin = 0; + while (!pi.isDone()) { + int code = pi.currentSegment(coords); + switch (code) { + case PathIterator.SEG_MOVETO: + xBegin = coords[0]; + yBegin = coords[1]; + points.add(new Point2D.Double(xBegin, yBegin)); + break; + case PathIterator.SEG_LINETO: + //coords[0], coords[1] + //Point2D np = new Point2D.Double(coords[0], coords[1]); + double dx = coords[0] - xPrev; + double dy = coords[1] - yPrev; + double dz = Math.sqrt(dx * dx + dy * dy); + double divisor; + /*if (dy > dx) { + divisor = dy / F; + } else { + divisor = dx / F; + }*/ + divisor = dz / F; + for (int d = 1; d <= divisor; d++) { + Point2D p2 = new Point2D.Double(xPrev + d * dx / divisor, yPrev + d * dy / divisor); + points.add(p2); + } + //points.add(np); + //System.err.println("dx, dy: "+dx+", "+dy); + break; + case PathIterator.SEG_CLOSE: + + break; + default: + throw new RuntimeException("Curved edge not expected"); + } + xPrev = coords[0]; + yPrev = coords[1]; + //System.err.println("pos: "+xPrev+", "+ yPrev); + pi.next(); + } + return points; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java index 9aac4f555..985494028 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/FILLSTYLE.java @@ -30,6 +30,7 @@ import com.jpexs.decompiler.flash.types.annotations.SWFType; import com.jpexs.decompiler.flash.types.annotations.SWFVersion; import java.io.Serializable; import java.lang.reflect.Field; +import java.util.Objects; import java.util.Set; /** @@ -248,4 +249,52 @@ public class FILLSTYLE implements NeedsCharacters, FieldChangeObserver, Serializ return morphFillStyle; } + + @Override + public int hashCode() { + int hash = 7; + hash = 23 * hash + this.fillStyleType; + hash = 23 * hash + (this.inShape3 ? 1 : 0); + hash = 23 * hash + Objects.hashCode(this.color); + hash = 23 * hash + Objects.hashCode(this.gradientMatrix); + hash = 23 * hash + Objects.hashCode(this.gradient); + hash = 23 * hash + this.bitmapId; + hash = 23 * hash + Objects.hashCode(this.bitmapMatrix); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FILLSTYLE other = (FILLSTYLE) obj; + if (this.fillStyleType != other.fillStyleType) { + return false; + } + if (this.inShape3 != other.inShape3) { + return false; + } + if (this.bitmapId != other.bitmapId) { + return false; + } + if (!Objects.equals(this.color, other.color)) { + return false; + } + if (!Objects.equals(this.gradientMatrix, other.gradientMatrix)) { + return false; + } + if (!Objects.equals(this.gradient, other.gradient)) { + return false; + } + return Objects.equals(this.bitmapMatrix, other.bitmapMatrix); + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java index 7990f813d..89e05fbc0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADIENT.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.SWFArray; import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.Serializable; +import java.util.Arrays; /** * @@ -109,4 +110,36 @@ public class GRADIENT implements Serializable { } return morphGradient; } + + @Override + public int hashCode() { + int hash = 7; + hash = 97 * hash + this.spreadMode; + hash = 97 * hash + this.interpolationMode; + hash = 97 * hash + Arrays.deepHashCode(this.gradientRecords); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final GRADIENT other = (GRADIENT) obj; + if (this.spreadMode != other.spreadMode) { + return false; + } + if (this.interpolationMode != other.interpolationMode) { + return false; + } + return Arrays.deepEquals(this.gradientRecords, other.gradientRecords); + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADRECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADRECORD.java index 7d48b9a87..035e2e665 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADRECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/GRADRECORD.java @@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.types; import com.jpexs.decompiler.flash.types.annotations.Internal; import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.Serializable; +import java.util.Objects; /** * @@ -55,4 +56,36 @@ public class GRADRECORD implements Serializable { morphGradRecord.endRatio = endGradRecord.ratio; return morphGradRecord; } + + @Override + public int hashCode() { + int hash = 5; + hash = 19 * hash + this.ratio; + hash = 19 * hash + (this.inShape3 ? 1 : 0); + hash = 19 * hash + Objects.hashCode(this.color); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final GRADRECORD other = (GRADRECORD) obj; + if (this.ratio != other.ratio) { + return false; + } + if (this.inShape3 != other.inShape3) { + return false; + } + return Objects.equals(this.color, other.color); + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java index 1f36aba2e..971d69d32 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.tags.base.NeedsCharacters; import com.jpexs.decompiler.flash.types.annotations.ConditionalType; import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.Serializable; +import java.util.Objects; import java.util.Set; /** @@ -84,4 +85,32 @@ public class LINESTYLE implements NeedsCharacters, Serializable, ILINESTYLE { morphLineStyle.endWidth = width; return morphLineStyle; } + + @Override + public int hashCode() { + int hash = 7; + hash = 53 * hash + this.width; + hash = 53 * hash + Objects.hashCode(this.color); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LINESTYLE other = (LINESTYLE) obj; + if (this.width != other.width) { + return false; + } + return Objects.equals(this.color, other.color); + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java index 809fbc551..cdf716a49 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java @@ -23,6 +23,7 @@ import com.jpexs.decompiler.flash.types.annotations.EnumValue; import com.jpexs.decompiler.flash.types.annotations.Reserved; import com.jpexs.decompiler.flash.types.annotations.SWFType; import java.io.Serializable; +import java.util.Objects; import java.util.Set; /** @@ -232,4 +233,76 @@ public class LINESTYLE2 implements NeedsCharacters, Serializable, ILINESTYLE { } return morphLineStyle2; } + + @Override + public int hashCode() { + int hash = 7; + hash = 83 * hash + this.width; + hash = 83 * hash + this.startCapStyle; + hash = 83 * hash + this.joinStyle; + hash = 83 * hash + (this.hasFillFlag ? 1 : 0); + hash = 83 * hash + (this.noHScaleFlag ? 1 : 0); + hash = 83 * hash + (this.noVScaleFlag ? 1 : 0); + hash = 83 * hash + (this.pixelHintingFlag ? 1 : 0); + hash = 83 * hash + this.reserved; + hash = 83 * hash + (this.noClose ? 1 : 0); + hash = 83 * hash + this.endCapStyle; + hash = 83 * hash + Float.floatToIntBits(this.miterLimitFactor); + hash = 83 * hash + Objects.hashCode(this.color); + hash = 83 * hash + Objects.hashCode(this.fillType); + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final LINESTYLE2 other = (LINESTYLE2) obj; + if (this.width != other.width) { + return false; + } + if (this.startCapStyle != other.startCapStyle) { + return false; + } + if (this.joinStyle != other.joinStyle) { + return false; + } + if (this.hasFillFlag != other.hasFillFlag) { + return false; + } + if (this.noHScaleFlag != other.noHScaleFlag) { + return false; + } + if (this.noVScaleFlag != other.noVScaleFlag) { + return false; + } + if (this.pixelHintingFlag != other.pixelHintingFlag) { + return false; + } + if (this.reserved != other.reserved) { + return false; + } + if (this.noClose != other.noClose) { + return false; + } + if (this.endCapStyle != other.endCapStyle) { + return false; + } + if (Float.floatToIntBits(this.miterLimitFactor) != Float.floatToIntBits(other.miterLimitFactor)) { + return false; + } + if (!Objects.equals(this.color, other.color)) { + return false; + } + return Objects.equals(this.fillType, other.fillType); + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGB.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGB.java index 4b0055500..1933d63c6 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGB.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGB.java @@ -112,4 +112,36 @@ public class RGB implements Serializable { return "[RGB red:" + red + ", green:" + green + ", blue:" + blue + "]"; } } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + this.red; + hash = 97 * hash + this.green; + hash = 97 * hash + this.blue; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final RGB other = (RGB) obj; + if (this.red != other.red) { + return false; + } + if (this.green != other.green) { + return false; + } + return this.blue == other.blue; + } + + } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGBA.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGBA.java index 2624db49d..caaf02703 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGBA.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/RGBA.java @@ -110,4 +110,54 @@ public class RGBA extends RGB implements Serializable { return "[RGB red:" + red + ", green:" + green + ", blue:" + blue + ", alpha:" + alpha + "]"; } } + + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + this.red; + hash = 97 * hash + this.green; + hash = 97 * hash + this.blue; + hash = 97 * hash + this.alpha; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (obj instanceof RGB) { + final RGB other = (RGBA) obj; + if (this.alpha != 255) { + return false; + } + if (this.red != other.red) { + return false; + } + if (this.green != other.green) { + return false; + } + if (this.blue != other.blue) { + return false; + } + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + final RGBA other = (RGBA) obj; + if (this.red != other.red) { + return false; + } + if (this.green != other.green) { + return false; + } + if (this.blue != other.blue) { + return false; + } + return this.alpha == other.alpha; + } } 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 89c51fbfd..461e909e0 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 @@ -241,6 +241,34 @@ public class XFLConverter { EDGE_DECIMAL_FORMAT.setGroupingUsed(false); } + + private static class StatusStack { + private Stack statuses = new Stack<>(); + private ProgressListener progressListener; + + public StatusStack(ProgressListener progressListener) { + this.progressListener = progressListener; + } + + private void update() { + if (progressListener == null) { + return; + } + String status = String.join(", ", statuses); + progressListener.status(status); + } + + public void pushStatus(String status) { + statuses.push(status); + update(); + } + + public void popStatus() { + statuses.pop(); + update(); + } + } + private static String formatEdgeDouble(double value, boolean curved, boolean morphshape) { if (value % 1 == 0) { return "" + (int) value; @@ -783,9 +811,10 @@ public class XFLConverter { } List shapeRecordsAdvanced; + useFixer = true; if (useFixer) { ShapeFixer fixer = new ShapeFixer(); - shapeRecordsAdvanced = fixer.fix(shapeRecords); + shapeRecordsAdvanced = fixer.fix(shapeRecords, morphshape, shapeNum, fillStyles, lineStyles); } else { shapeRecordsAdvanced = new ArrayList<>(); for (SHAPERECORD rec : shapeRecords) { @@ -1029,6 +1058,10 @@ public class XFLConverter { * @return */ private static String removeOnlyStrokeEdgesBeforeSameFilled(String layer) { + if (true) { + //This is moved to ShapeFixer + return layer; + } DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setNamespaceAware(false); @@ -1061,6 +1094,16 @@ public class XFLConverter { String fillStyle0 = fillStyle0Node != null ? fillStyle0Node.getNodeValue() : null; String fillStyle1 = fillStyle1Node != null ? fillStyle1Node.getNodeValue() : null; + if ("0".equals(fillStyle0)) { + fillStyle0 = null; + } + if ("0".equals(fillStyle1)) { + fillStyle1 = null; + } + if ("0".equals(strokeStyle)) { + strokeStyle = null; + } + if (prevStrokeOnly != null && strokeStyle != null && strokeStyle.equals(prevStrokeOnly) @@ -1631,29 +1674,33 @@ 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, Map placeToMaskedSymbol, List multiUsageMorphShapes, ProgressListener progressListener) 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, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { //TODO: Imported assets //linkageImportForRS="true" linkageIdentifier="xxx" linkageURL="yyy.swf" - convertMedia(swf, characterVariables, characterClasses, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, progressListener); - convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, progressListener); + statusStack.pushStatus("media"); + convertMedia(swf, characterVariables, characterClasses, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, statusStack); + statusStack.popStatus(); + statusStack.pushStatus("symbols"); + convertSymbols(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, tags, characters, files, datfiles, flaVersion, writer, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + statusStack.popStatus(); } - 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, List multiUsageMorphShapes, ProgressListener progressListener) throws XMLStreamException { + 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, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { //boolean hasSymbol = false; Reference nextClipId = new Reference<>(-1); writer.writeStartElement("symbols"); for (int ch : characters.keySet()) { CharacterTag symbol = characters.get(ch); - if (progressListener != null && symbol != null) { - progressListener.status(symbol.toString()); - } + if (symbol == null) { + continue; + } if ((symbol instanceof ShapeTag) && nonLibraryShapes.contains(symbol.getCharacterId())) { continue; //shapes with 1 ocurrence and single layer are not added to library - } - + } + if ((symbol instanceof ShapeTag) || (symbol instanceof DefineSpriteTag) || (symbol instanceof ButtonTag)) { + statusStack.pushStatus(symbol.toString()); XFLXmlWriter symbolStr = new XFLXmlWriter(); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ @@ -1848,13 +1895,14 @@ public class XFLConverter { } else if (symbol instanceof DefineSpriteTag) { DefineSpriteTag sprite = (DefineSpriteTag) symbol; if (sprite.getTags().isEmpty()) { //probably AS2 class + statusStack.popStatus(); continue; } final ScriptPack spriteScriptPack = characterScriptPacks.containsKey(sprite.spriteId) ? characterScriptPacks.get(sprite.spriteId) : null; - extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); + extractMultilevelClips(sprite.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); - convertTimeline(swf, swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); + convertTimeline(swf, swf.getAbcIndex(), sprite.spriteId, characterVariables.get(sprite.spriteId), nonLibraryShapes, backgroundColor, tags, sprite.getTags(), characters, "Symbol " + symbol.getCharacterId(), flaVersion, files, symbolStr, spriteScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); } else if (symbol instanceof ShapeTag) { symbolStr.writeStartElement("timeline"); @@ -1888,19 +1936,25 @@ public class XFLConverter { //TODO: itemID="518de416-00000341" } writer.writeEndElement(); - //hasSymbol = true; - } + //hasSymbol = true; + statusStack.popStatus(); + } } - extractMultilevelClips(swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); - extractMultiUsageMorphShapes(writer, swf, nonLibraryShapes, backgroundColor, characters, flaVersion, files, multiUsageMorphShapes); + statusStack.pushStatus("extracting multilevel clips"); + extractMultilevelClips(swf.getTags(), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + statusStack.popStatus(); + + statusStack.pushStatus("converting multiusage morphshapes"); + extractMultiUsageMorphShapes(writer, swf, nonLibraryShapes, backgroundColor, characters, flaVersion, files, multiUsageMorphShapes, statusStack); + statusStack.popStatus(); /*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, ProgressListener progressListener) throws XMLStreamException { + 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, StatusStack statusStack) throws XMLStreamException { boolean hasMedia = false; for (int ch : characters.keySet()) { CharacterTag symbol = characters.get(ch); @@ -1923,10 +1977,7 @@ public class XFLConverter { CharacterTag symbol = characters.get(ch); if (symbol instanceof ImageTag) { - if (progressListener != null) { - progressListener.status(symbol.toString()); - } - + statusStack.pushStatus(symbol.toString()); ImageTag imageTag = (ImageTag) symbol; boolean allowSmoothing = false; @@ -2013,7 +2064,9 @@ public class XFLConverter { writer.writeAttribute("frameBottom", image.getHeight()); writer.writeEndElement(); mediaCount++; + statusStack.popStatus(); } else if (/*(symbol instanceof SoundStreamHeadTypeTag) || FIXME */(symbol instanceof DefineSoundTag)) { + statusStack.pushStatus(symbol.toString()); int soundFormat = 0; int soundRate = 0; boolean soundType = false; @@ -2180,7 +2233,9 @@ public class XFLConverter { writer.writeEndElement(); mediaCount++; + statusStack.popStatus(); } else if (symbol instanceof DefineVideoStreamTag) { + statusStack.pushStatus(symbol.toString()); DefineVideoStreamTag video = (DefineVideoStreamTag) symbol; String videoType = "no media"; switch (video.codecID) { @@ -2260,6 +2315,7 @@ public class XFLConverter { } mediaCount++; + statusStack.popStatus(); } } @@ -2446,7 +2502,7 @@ public class XFLConverter { writer.writeEndElement(); } - private static void convertFrames(SWF swf, List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes) throws XMLStreamException { + private static void convertFrames(SWF swf, List onlyFrames, int startFrame, int endFrame, String prevStr, String afterStr, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, int depth, FLAVersion flaVersion, HashMap files, XFLXmlWriter writer, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { boolean lastIn = true; XFLXmlWriter writer2 = new XFLXmlWriter(); prevStr += ""; @@ -2648,8 +2704,10 @@ public class XFLConverter { } else { if (lastCharacter == m && Objects.equals(matrix, lastMatrix)) { elementsWriter.writeCharactersRaw(lastElements); - } else { + } else { + statusStack.pushStatus(m.toString()); convertShape(swf, characters, matrix, m.getShapeNum() == 1 ? 3 : 4, m.getStartEdges().shapeRecords, m.getFillStyles().getStartFillStyles(), m.getLineStyles().getStartLineStyles(m.getShapeNum()), true, false, elementsWriter, false); + statusStack.popStatus(); } lastCharacter = m; @@ -2659,7 +2717,9 @@ public class XFLConverter { } else { shapeTween = false; if (character instanceof TextTag) { + statusStack.pushStatus(character.toString()); convertText(instanceName, (TextTag) character, matrix, filters, clipActions, elementsWriter); + statusStack.popStatus(); } else if (character instanceof DefineVideoStreamTag) { convertVideoInstance(instanceName, matrix, (DefineVideoStreamTag) character, clipActions, elementsWriter); } else if (character instanceof ImageTag) { @@ -2724,7 +2784,7 @@ public class XFLConverter { } } - private static void convertFonts(Map characterClasses, ReadOnlyTagList tags, XFLXmlWriter writer, ProgressListener progressListener) throws XMLStreamException { + private static void convertFonts(Map characterClasses, ReadOnlyTagList tags, XFLXmlWriter writer, StatusStack statusStack) throws XMLStreamException { boolean hasFont = false; for (Tag t : tags) { if (t instanceof FontTag) { @@ -2744,9 +2804,7 @@ public class XFLConverter { for (Tag t : tags) { if (t instanceof FontTag) { - if (progressListener != null) { - progressListener.status(t.toString()); - } + statusStack.pushStatus(t.toString()); SWF swf = t.getSwf(); FontTag font = (FontTag) t; int fontId = font.getFontId(); @@ -2767,6 +2825,7 @@ public class XFLConverter { String fontChars = font.getCharacters(); if ("".equals(fontChars)) { + statusStack.popStatus(); continue; } String embeddedCharacters = fontChars; @@ -2827,6 +2886,7 @@ public class XFLConverter { } writer.writeEndElement(); + statusStack.popStatus(); } } @@ -3214,7 +3274,8 @@ public class XFLConverter { FLAVersion flaVersion, HashMap files, Map placeToMaskedSymbol, - List multiUsageMorphShapes + List multiUsageMorphShapes, + StatusStack statusStack ) throws XMLStreamException { XFLXmlWriter symbolStr = new XFLXmlWriter(); @@ -3233,7 +3294,7 @@ public class XFLConverter { "lastModified", Long.toString(getTimestamp(swf))}); symbolStr.writeAttribute("symbolType", "graphic"); - convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes); + convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, timelineTags, timelineTags, characters, generateMaskedSymbolName(objectId), flaVersion, files, symbolStr, null, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); @@ -3249,7 +3310,7 @@ public class XFLConverter { } writer.writeEndElement(); - extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); + extractMultilevelClips(timelineTags, writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); } private String generateMaskedSymbolName(int symbolId) { @@ -3320,10 +3381,13 @@ public class XFLConverter { HashMap characters, FLAVersion flaVersion, HashMap files, - List multiUsageMorphShapes + List multiUsageMorphShapes, + StatusStack statusStack ) throws XMLStreamException { for (int objectId : multiUsageMorphShapes) { + statusStack.pushStatus(characters.get(objectId).toString()); + XFLXmlWriter symbolStr = new XFLXmlWriter(); symbolStr.writeStartElement("DOMSymbolItem", new String[]{ "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance", @@ -3348,7 +3412,7 @@ public class XFLConverter { } } - convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>()); + convertTimeline(swf, swf.getAbcIndex(), objectId, "", nonLibraryShapes, backgroundColor, swf.getTags(), new ReadOnlyTagList(timelineTags), characters, "Symbol " + objectId, flaVersion, files, symbolStr, null, new HashMap<>(), new ArrayList<>(), statusStack); symbolStr.writeEndElement(); // DOMSymbolItem String symbolStr2 = prettyFormatXML(symbolStr.toString()); @@ -3363,6 +3427,7 @@ public class XFLConverter { //TODO: itemID="518de416-00000341" } writer.writeEndElement(); + statusStack.popStatus(); } } @@ -3376,7 +3441,8 @@ public class XFLConverter { FLAVersion flaVersion, HashMap files, Map placeToMaskedSymbol, - List multiUsageMorphShapes + List multiUsageMorphShapes, + StatusStack statusStack ) throws XMLStreamException { int f = 0; @@ -3642,14 +3708,14 @@ public class XFLConverter { } } System.err.println("clip repeats in " + found);*/ - addExtractedClip(new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes); + addExtractedClip(new ReadOnlyTagList(delegatedTimeline), writer, swf, nextClipId, nonLibraryShapes, backgroundColor, characters, flaVersion, files, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); placeToMaskedSymbol.put(secondPlace, new MultiLevelClip(secondPlace, nextClipId.getVal(), numFrames)); } } } } - private void convertTimeline(SWF swf, 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, List multiUsageMorphShapes) throws XMLStreamException { + private void convertTimeline(SWF swf, 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, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { List classNames = new ArrayList<>(); //Searches for Object.registerClass("linkageIdentifier",mypkg.MyClass); @@ -3918,7 +3984,7 @@ public class XFLConverter { "color", randomOutlineColor(), "layerType", "mask", "locked", "true"}); - convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes); + convertFrames(swf, depthToFramesList.get(po.getDepth()), clipFrame, lastFrame, "", "", nonLibraryShapes, tags, timelineTags, characters, po.getDepth(), flaVersion, files, writer, multiUsageMorphShapes, statusStack); writer.writeEndElement(); int parentIndex = index; @@ -4007,7 +4073,7 @@ public class XFLConverter { } for (int nd = po.getClipDepth() - 1; nd > po.getDepth(); nd--) { - boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); + boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(nd), nd, clipFrame, lastFrame, parentIndex, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); for (int i = clipFrame; i <= lastFrame; i++) { depthToFramesList.get(nd).remove((Integer) i); } @@ -4021,7 +4087,7 @@ public class XFLConverter { } } - boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes); + boolean nonEmpty = writeLayer(swf, index, depthToFramesList.get(d), d, 0, Integer.MAX_VALUE, -1, writer, nonLibraryShapes, tags, timelineTags, characters, flaVersion, files, multiUsageMorphShapes, statusStack); if (nonEmpty) { index++; } @@ -4059,8 +4125,9 @@ public class XFLConverter { writer.writeEndElement(); //DOMLayer } - private boolean writeLayer(SWF swf, int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes) throws XMLStreamException { + private boolean writeLayer(SWF swf, int index, List onlyFrames, int d, int startFrame, int endFrame, int parentLayer, XFLXmlWriter writer, List nonLibraryShapes, ReadOnlyTagList tags, ReadOnlyTagList timelineTags, HashMap characters, FLAVersion flaVersion, HashMap files, List multiUsageMorphShapes, StatusStack statusStack) throws XMLStreamException { XFLXmlWriter layerPrev = new XFLXmlWriter(); + statusStack.pushStatus("layer " + (index + 1)); //System.err.println("- writing layer " + (index + 1) + (startFrame == 0 && endFrame == Integer.MAX_VALUE ? ", all frames": ", frame " + startFrame + " to " + endFrame)); layerPrev.writeStartElement("DOMLayer", new String[]{ "name", "Layer " + (index + 1) + (DEBUG_EXPORT_LAYER_DEPTHS ? " (depth " + d + ")" : ""), @@ -4077,7 +4144,8 @@ public class XFLConverter { layerPrev.writeCharacters(""); // todo honfika: hack to close start tag String layerAfter = ""; int prevLength = writer.length(); - convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes); + convertFrames(swf, onlyFrames, startFrame, endFrame, layerPrev.toString(), layerAfter, nonLibraryShapes, tags, timelineTags, characters, d, flaVersion, files, writer, multiUsageMorphShapes, statusStack); + statusStack.popStatus(); return writer.length() != prevLength; } @@ -4608,16 +4676,17 @@ public class XFLConverter { } Map placeToMaskedSymbol = new HashMap<>(); + + StatusStack statusStack = new StatusStack(progressListener); - convertFonts(characterClasses, swf.getTags(), domDocument, progressListener); - convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, progressListener); + convertFonts(characterClasses, swf.getTags(), domDocument, statusStack); + convertLibrary(swf, characterVariables, characterClasses, characterScriptPacks, nonLibraryShapes, backgroundColor, swf.getTags(), characters, files, datfiles, flaVersion, domDocument, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); //domDocument.writeStartElement("timelines"); ScriptPack documentScriptPack = characterScriptPacks.containsKey(0) ? characterScriptPacks.get(0) : null; - if (progressListener != null) { - progressListener.status("main timeline"); - } - convertTimeline(swf, swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes); + statusStack.pushStatus("main timeline"); + convertTimeline(swf, swf.getAbcIndex(), -1, null, nonLibraryShapes, backgroundColor, swf.getTags(), swf.getTags(), characters, "Scene 1", flaVersion, files, domDocument, documentScriptPack, placeToMaskedSymbol, multiUsageMorphShapes, statusStack); + statusStack.popStatus(); //domDocument.writeEndElement(); if (hasAmfMetadata) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java index a4f6f2ccb..a79fa4aec 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/xfl/shapefixer/ShapeFixer.java @@ -17,7 +17,11 @@ package com.jpexs.decompiler.flash.xfl.shapefixer; import com.jpexs.decompiler.flash.math.BezierEdge; +import com.jpexs.decompiler.flash.math.Distances; +import com.jpexs.decompiler.flash.types.FILLSTYLE; import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +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.shaperecords.CurvedEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; @@ -25,18 +29,26 @@ 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.helpers.Reference; +import java.awt.Rectangle; +import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; /** - * Shape fixer. - * This will walk a shape and split crossed edges so FLA editor can properly load it. + * Shape fixer. This will walk a shape and split crossed edges so FLA editor can + * properly load it. It also fixes morphshape - removes duplicated paths, calculate holes. + * * @author JPEXS */ public class ShapeFixer { - + boolean DEBUG_PRINT = false; private class BezierPair { @@ -47,7 +59,7 @@ public class ShapeFixer { public BezierPair(BezierEdge be1, BezierEdge be2) { this.be1 = be1; - this.be2 = be2; + this.be2 = be2; } @Override @@ -60,7 +72,7 @@ public class ShapeFixer { @Override public boolean equals(Object obj) { - //simplifyed to be fast, not so neccessarily accurate + //simplified to be fast, not so neccessarily accurate return hashCode() == obj.hashCode(); /*if (this == obj) { return true; @@ -81,10 +93,22 @@ public class ShapeFixer { } - public List fix( - List records - ) { + private boolean isEmptyBatch(List batch) { + for (BezierEdge be : batch) { + if (!be.isEmpty()) { + return false; + } + } + return true; + } + public List fix( + List records, + boolean morphshape, + int shapeNum, + FILLSTYLEARRAY baseFillStyles, + LINESTYLEARRAY baseLineStyles + ) { List> shapes = new ArrayList<>(); List currentShape = new ArrayList<>(); List fillStyles0 = new ArrayList<>(); @@ -164,6 +188,416 @@ public class ShapeFixer { y = rec.changeY(y); } + if (morphshape) { + + //Remove empty edges + for (int i = 0; i < shapes.size(); i++) { + List list = shapes.get(i); + for (int j = 0; j < list.size(); j++) { + if (list.get(j).isEmpty()) { + list.remove(j); + j--; + } + } + } + + List> closedShapes = new ArrayList<>(); + List closedFillStyle = new ArrayList<>(); + List closedLineStyle = new ArrayList<>(); + List closedLayers = new ArrayList<>(); + List closedShapesI = new ArrayList<>(); + List closedShapesJ = new ArrayList<>(); + + Map replacements = new LinkedHashMap<>(); + + for (int i = 0; i < shapes.size(); i++) { + Point2D lastMoveTo = null; + BezierEdge lastEdge = null; + int moveToIndex = 0; + List batch = new ArrayList<>(); + for (int j = 0; j < shapes.get(i).size(); j++) { + BezierEdge be = shapes.get(i).get(j); + batch.add(be); + if (lastMoveTo != null && be.getEndPoint().equals(lastMoveTo)) { + closedFillStyle.add(fillStyles0.get(i)); + closedLineStyle.add(lineStyles.get(i)); + closedShapes.add(batch); + closedLayers.add(layers.get(i)); + closedShapesI.add(i); + closedShapesJ.add(moveToIndex); + moveToIndex = j + 1; + lastMoveTo = be.getEndPoint(); + batch = new ArrayList<>(); + } + if (lastEdge != null) { + if (!lastEdge.getEndPoint().equals(be.getBeginPoint())) { + lastMoveTo = be.getBeginPoint(); + moveToIndex = j; + } + } else { + lastMoveTo = be.getBeginPoint(); + } + lastEdge = be; + } + } + + Set removedShapes = new HashSet<>(); + for (int i1 = 0; i1 < closedShapes.size(); i1++) { + if (replacements.containsKey(i1)) { + continue; + } + for (int i2 = 0; i2 < closedShapes.size(); i2++) { + if (i1 == i2) { + continue; + } + if (replacements.containsKey(i2)) { + continue; + } + if (closedLayers.get(i1) != closedLayers.get(i2)) { + continue; + } + if (closedFillStyle.get(i1) > 0 && closedFillStyle.get(i2) > 0) { + if (closedFillStyle.get(i1) != closedFillStyle.get(i2)) { + FILLSTYLEARRAY fa = closedLayers.get(i1) == -1 ? baseFillStyles : fillStyleLayers.get(closedLayers.get(i1)); + FILLSTYLE fs1 = fa.fillStyles[closedFillStyle.get(i1) - 1]; + FILLSTYLE fs2 = fa.fillStyles[closedFillStyle.get(i2) - 1]; + if (!fs1.equals(fs2)) { + continue; + } + } + } + if (closedLineStyle.get(i1) > 0 && closedLineStyle.get(i2) > 0) { + if (closedLineStyle.get(i1) != closedLineStyle.get(i2)) { + LINESTYLEARRAY lsa = closedLayers.get(i1) == -1 ? baseLineStyles : lineStyleLayers.get(closedLayers.get(i1)); + if (shapeNum <= 3) { + LINESTYLE ls1 = lsa.lineStyles[closedLineStyle.get(i1) - 1]; + LINESTYLE ls2 = lsa.lineStyles[closedLineStyle.get(i2) - 1]; + if (!ls1.equals(ls2)) { + continue; + } + } else { + LINESTYLE2 ls1 = lsa.lineStyles2[closedLineStyle.get(i1) - 1]; + LINESTYLE2 ls2 = lsa.lineStyles2[closedLineStyle.get(i2) - 1]; + if (!ls1.equals(ls2)) { + continue; + } + } + } + } + + if (closedShapes.get(i1).size() <= 1 || closedShapes.get(i2).size() <= 1) { + continue; + } + + if (closedLineStyle.get(i1) > 0 && closedLineStyle.get(i2) == 0) { + continue; + } + + if (isEmptyBatch(closedShapes.get(i1)) || isEmptyBatch(closedShapes.get(i2))) { + continue; + } + + double dist = Distances.getBatchDistance(closedShapes.get(i1), closedShapes.get(i2)); + + if (dist <= 10) { //magic + /*System.err.println("dist = " + dist); + System.err.println("removed"); + System.err.println("removed shape["+closedShapesI.get(i2)+"]["+closedShapesJ.get(i2)+"], fs "+ closedFillStyle.get(i2) + ", ls " + closedLineStyle.get(i2)); + System.err.println("left shape["+closedShapesI.get(i1)+"]["+closedShapesJ.get(i1)+"], fs "+ closedFillStyle.get(i1)+ ", ls " + closedLineStyle.get(i1)); + */ + replacements.put(i2, i1); + removedShapes.add(i2); + } + } + } + + for (int i = 0; i < closedShapes.size(); i++) { + int repI = replacements.containsKey(i) ? replacements.get(i) : i; + List listI = closedShapes.get(repI); + + for (int j = i + 1; j < closedShapes.size(); j++) { + /*if (i == j) { + break; + }*/ + if (removedShapes.contains(j) && !replacements.containsKey(j)) { + continue; + } + int repJ = replacements.containsKey(j) ? replacements.get(j) : j; + List listJ = closedShapes.get(repJ); + + if (closedFillStyle.get(i) != closedFillStyle.get(j) + || closedLineStyle.get(i) != closedLineStyle.get(j)) { + continue; + } + + if (listI.equals(listJ)) { + replacements.remove(j); + removedShapes.add(j); + } + } + } + + for (int i = closedShapes.size() - 1; i >= 0; i--) { + int to = closedShapesJ.get(i) + closedShapes.get(i).size() - 1; + int from = closedShapesJ.get(i); + List list = shapes.get(closedShapesI.get(i)); + + if (removedShapes.contains(i)) { + //System.err.println("removing shape["+closedShapesI.get(i)+"]["+from+" to "+to+"]"); + for (int j = to; j >= from; j--) { + list.remove(j); + } + } + if (replacements.containsKey(i)) { + list.addAll(from, closedShapes.get(replacements.get(i))); + } + + if (!removedShapes.contains(i)) { + for (int j = to; j >= from; j--) { + if (list.get(j).isEmpty()) { + list.remove(j); + } + } + } + } + + /* + * This will remove a stroked path with no fill which has same + * stroke as subsequent path (or is its prefix). This happens in the + * morphshape edges. This needs to be cleaned up before exporting to FLA. + */ + List prevList = null; + int prevI = -1; + for (int i = 0; i < shapes.size(); i++) { + List list = shapes.get(i); + if (list.isEmpty()) { + continue; + } + if (prevList != null) { + + int prevFillStyle0 = fillStyles0.get(i - 1); + int prevFillStyle1 = fillStyles1.get(i - 1); + int prevLineStyle = lineStyles.get(i - 1); + lineStyle = lineStyles.get(i); + fillStyle0 = fillStyles0.get(i); + fillStyle1 = fillStyles1.get(i); + + if (fillStyle0 == 0 && fillStyle1 == 0 && lineStyle != 0 && lineStyle == prevLineStyle) { + if (prevList.size() >= list.size()) { + boolean isPrefix = true; + for (int j = 0; j < list.size(); j++) { + if (!prevList.get(j).equals(list.get(j))) { + isPrefix = false; + break; + } + } + if (isPrefix) { + shapes.get(i).clear(); + continue; + } + } + } else if (prevFillStyle0 == 0 && prevFillStyle1 == 0 && prevLineStyle != 0 && lineStyle == prevLineStyle) { + //list startswitch prevList + if (list.size() >= prevList.size()) { + boolean isPrefix = true; + for (int j = 0; j < prevList.size(); j++) { + if (!prevList.get(j).equals(list.get(j))) { + isPrefix = false; + break; + } + } + if (isPrefix) { + shapes.get(prevI).clear(); + } + } + } + } + prevI = i; + prevList = list; + } + + //Clear obvious duplicates + //System.err.println("Clear obvious duplicates..."); + prevList = null; + prevI = -1; + //if (false) + for (int i = 0; i < shapes.size(); i++) { + List list = shapes.get(i); + if (list.isEmpty()) { + continue; + } + if (prevList != null) { + if (fillStyles0.get(i) == fillStyles0.get(prevI) + && lineStyles.get(i) == lineStyles.get(prevI)) { + if (list.equals(prevList)) { + System.err.println("clearing " + i); + prevList.clear(); + } + } + } + prevI = i; + prevList = list; + } + + //----------------------------FIND "holes" = apply wind even odd ------------- + closedShapes = new ArrayList<>(); + closedFillStyle = new ArrayList<>(); + closedLineStyle = new ArrayList<>(); + closedLayers = new ArrayList<>(); + closedShapesI = new ArrayList<>(); + closedShapesJ = new ArrayList<>(); + Set closedHolesI = new LinkedHashSet<>(); + + for (int i = 0; i < shapes.size(); i++) { + Point2D lastMoveTo = null; + BezierEdge lastEdge = null; + int moveToIndex = 0; + List batch = new ArrayList<>(); + for (int j = 0; j < shapes.get(i).size(); j++) { + BezierEdge be = shapes.get(i).get(j); + batch.add(be); + if (lastMoveTo != null && be.getEndPoint().equals(lastMoveTo)) { + closedFillStyle.add(fillStyles0.get(i)); + closedLineStyle.add(lineStyles.get(i)); + closedShapes.add(batch); + closedLayers.add(layers.get(i)); + closedShapesI.add(i); + closedShapesJ.add(moveToIndex); + moveToIndex = j + 1; + lastMoveTo = be.getEndPoint(); + batch = new ArrayList<>(); + } + if (lastEdge != null) { + if (!lastEdge.getEndPoint().equals(be.getBeginPoint())) { + lastMoveTo = be.getBeginPoint(); + moveToIndex = j; + } + } else { + lastMoveTo = be.getBeginPoint(); + } + lastEdge = be; + } + } + + //reversing anti-clockwise + for (int i = 0; i < closedShapes.size(); i++) { + List list = closedShapes.get(i); + if (list.isEmpty()) { + continue; + } + List points = new ArrayList<>(); + points.add(list.get(0).getBeginPoint()); + for (BezierEdge be : list) { + if (be.points.size() == 3) { + points.add(be.points.get(1)); + } + points.add(be.getEndPoint()); + } + double sum = 0; + for (int j = 0; j < points.size(); j++) { + Point2D p1 = points.get(j); + Point2D p2 = points.get((j + 1) % points.size()); + sum += (p1.getX() * p2.getY() - p2.getX() * p1.getY()); + } + + if (sum < 0) { //anti clockwise + //reverse the list + List rev = new ArrayList<>(); + for (int j = 0; j < list.size(); j++) { + rev.add(list.get(list.size() - 1 - j).reverse()); + } + + int shapeI = closedShapesI.get(i); + int shapeJ = closedShapesJ.get(i); + for (int j = 0; j < list.size(); j++) { + shapes.get(shapeI).set(shapeJ + j, rev.get(j)); + } + } + } + + Map> fillStyleToClosed = new LinkedHashMap<>(); + for (int i = 0; i < closedShapes.size(); i++) { + int fs = closedFillStyle.get(i); + if (fs != 0) { + if (!fillStyleToClosed.containsKey(fs)) { + fillStyleToClosed.put(fs, new ArrayList<>()); + } + fillStyleToClosed.get(fs).add(i); + } + } + for (int fs : fillStyleToClosed.keySet()) { + GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + Map closedPaths = new LinkedHashMap<>(); + for (int i : fillStyleToClosed.get(fs)) { + List closed = closedShapes.get(i); + if (closed.isEmpty()) { + continue; + } + GeneralPath closedPath = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + + closedPath.moveTo(closed.get(0).getBeginPoint().getX(), closed.get(0).getBeginPoint().getY()); + path.moveTo(closed.get(0).getBeginPoint().getX(), closed.get(0).getBeginPoint().getY()); + boolean isEmpty = true; + for (BezierEdge be : closed) { + if (be.isEmpty()) { + continue; + } + isEmpty = false; + if (be.points.size() == 3) { + closedPath.quadTo(be.points.get(1).getX(), be.points.get(1).getY(), be.getEndPoint().getX(), be.getEndPoint().getY()); + path.quadTo(be.points.get(1).getX(), be.points.get(1).getY(), be.getEndPoint().getX(), be.getEndPoint().getY()); + } else { + closedPath.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY()); + path.lineTo(be.getEndPoint().getX(), be.getEndPoint().getY()); + } + } + closedPath.closePath(); + path.closePath(); + if (!isEmpty) { + closedPaths.put(i, closedPath); + } + } + + for (int i : closedPaths.keySet()) { + GeneralPath region = closedPaths.get(i); + Rectangle r = region.getBounds(); + double px; + double py; + do { + px = r.getX() + r.getWidth() * Math.random(); + py = r.getY() + r.getHeight() * Math.random(); + } while (!region.contains(px, py)); + + if (!path.contains(px, py)) { + closedHolesI.add(i); + } + } + } + + for (int i = closedShapes.size() - 1; i >= 0; i--) { + if (closedHolesI.contains(i)) { + int to = closedShapesJ.get(i) + closedShapes.get(i).size() - 1; + int from = closedShapesJ.get(i); + List list = shapes.get(closedShapesI.get(i)); + + //System.err.println("removing hole["+closedShapesI.get(i)+"]["+from+" to "+to+"]"); + for (int j = to; j >= from; j--) { + list.remove(j); + } + + int shapeI = closedShapesI.get(i); + shapes.add(shapeI + 1, closedShapes.get(i)); + closedShapes.set(i, new ArrayList<>()); + fillStyles0.add(shapeI + 1, 0); + fillStyles1.add(shapeI + 1, fillStyles0.get(shapeI)); + lineStyles.add(shapeI + 1, lineStyles.get(shapeI)); + layers.add(shapeI + 1, layers.get(shapeI)); + } + } + } + + + //------------------- detecting overlapping edges -------------------- List splittedPairs = new ArrayList<>(); loopi1: @@ -173,193 +607,182 @@ public class ShapeFixer { for (int j1 = 0; j1 < shapes.get(i1).size(); j1++) { BezierEdge be1 = shapes.get(i1).get(j1); for (int i2 = 0; i2 < shapes.size(); i2++) { - if (layers.get(i2) == layer) { - loopj2: - for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) { - BezierEdge be2 = shapes.get(i2).get(j2); + if (layers.get(i2) != layer) { + continue; + } + loopj2: + for (int j2 = 0; j2 < shapes.get(i2).size(); j2++) { + BezierEdge be2 = shapes.get(i2).get(j2); - if (i1 == i2 && j1 == j2) { - continue; + if (i1 == i2 && j1 == j2) { + continue; + } + + if (be1.isEmpty()) { + shapes.get(i1).remove(j1); + if (i1 == i2 && j2 > j1) { + j2--; } - - if (be1.isEmpty()) { - shapes.get(i1).remove(j1); - if (i1 == i2 && j2 > j1) { - j2--; - } + j1--; + continue loopj1; + } + if (be2.isEmpty()) { + shapes.get(i2).remove(j2); + if (i1 == i2 && j1 > j2) { j1--; - continue loopj1; - } - if (be2.isEmpty()) { - shapes.get(i2).remove(j2); - if (i1 == i2 && j1 > j2) { - j1--; - } - j2--; - continue loopj2; } + j2--; + continue loopj2; + } - //duplicated edge - if (be1.equals(be2) || be1.equals(be2.reverse())) { - shapes.get(i2).remove(j2); - if (i1 == i2 && j1 > j2) { - j1--; - } - j2--; - if (DEBUG_PRINT) { - System.err.println("removing duplicate " + be1.toSvg() + " and " + be2.toSvg()); - } + //duplicated edge + if (be1.equals(be2) || be1.equals(be2.reverse())) { + shapes.get(i2).remove(j2); + if (i1 == i2 && j1 > j2) { + j1--; + } + j2--; + if (DEBUG_PRINT) { + System.err.println("removing duplicate " + be1.toSvg() + " and " + be2.toSvg()); + } + continue; + } + + BezierPair pair = new BezierPair(be1, be2); + if (splittedPairs.contains(pair)) { + continue; + } + + List t1Ref = new ArrayList<>(); + List t2Ref = new ArrayList<>(); + List intPoints = new ArrayList<>(); + + if (DEBUG_PRINT) { + //System.err.print("checking shape[" + i1 + "][" + j1 + "] to shape[" + i2 + "][" + j2 + "] : " + be1.toSvg() + " and " + be2.toSvg()); + } + + boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints); + if (DEBUG_PRINT) { + //System.err.println(" " + isint); + } + + if (!t1Ref.isEmpty()) { + + if ((be1.getBeginPoint().equals(be2.getBeginPoint()) + || be1.getBeginPoint().equals(be2.getEndPoint()) + || be1.getEndPoint().equals(be2.getBeginPoint()) + || be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) { continue; } - BezierPair pair = new BezierPair(be1, be2); - if (splittedPairs.contains(pair)) { - continue; - } - - List t1Ref = new ArrayList<>(); - List t2Ref = new ArrayList<>(); - List intPoints = new ArrayList<>(); - if (DEBUG_PRINT) { - System.err.print("checking shape[" + i1 + "][" + j1 + "] to shape[" + i2 + "][" + j2 + "] : " + be1.toSvg() + " and " + be2.toSvg()); - } + System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg()); + System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2)); + System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2)); + System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2)); - boolean isint = be1.intersects(be2, t1Ref, t2Ref, intPoints); - if (DEBUG_PRINT) { - System.err.println(" " + isint); - if (isint) { - System.err.println("xxx"); + for (int n = 0; n < t1Ref.size(); n++) { + System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n)); } } - if (!t1Ref.isEmpty()) { + if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) + && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) { + t1Ref.add(0, t1Ref.remove(1)); + t2Ref.add(0, t2Ref.remove(1)); + intPoints.add(0, intPoints.remove(1)); + } - if (DEBUG_PRINT) { - System.err.println("be1 " + be1); - System.err.println("be2 " + be2); - System.err.println("intersects " + be1.toSvg() + " " + be2.toSvg()); - System.err.println(" fillstyle0: " + fillStyles0.get(i1) + " , " + fillStyles0.get(i2)); - System.err.println(" fillstyle1: " + fillStyles1.get(i1) + " , " + fillStyles1.get(i2)); - System.err.println(" linestyle: " + lineStyles.get(i1) + " , " + lineStyles.get(i2)); - - for (int n = 0; n < t1Ref.size(); n++) { - System.err.println("- " + t1Ref.get(n) + " , " + t2Ref.get(n) + " : " + intPoints.get(n)); - } - } + int splitPointIndex = 0; + if (intPoints.size() > 1) { + splitPointIndex = 1; + } - + splittedPairs.add(pair); - if ((be1.getBeginPoint().equals(be2.getBeginPoint()) - || be1.getBeginPoint().equals(be2.getEndPoint()) - || be1.getEndPoint().equals(be2.getBeginPoint()) - || be1.getEndPoint().equals(be2.getEndPoint())) && (t1Ref.size() == 1)) { - if (DEBUG_PRINT) { - System.err.println("Same begin/end - continued"); - } - continue; - } + Reference be1LRef = new Reference<>(null); + Reference be1RRef = new Reference<>(null); + be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef); + Reference be2LRef = new Reference<>(null); + Reference be2RRef = new Reference<>(null); + be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef); - if ((t1Ref.size() == 2) && !((t1Ref.get(0) == 0 || t1Ref.get(0) == 1) - && (t2Ref.get(0) == 0 || t2Ref.get(0) == 1))) { - t1Ref.add(0, t1Ref.remove(1)); - t2Ref.add(0, t2Ref.remove(1)); - intPoints.add(0, intPoints.remove(1)); - } + BezierEdge be1L = be1LRef.getVal(); + BezierEdge be1R = be1RRef.getVal(); + BezierEdge be2L = be2LRef.getVal(); + BezierEdge be2R = be2RRef.getVal(); - int splitPointIndex = 0; - if (intPoints.size() > 1) { - splitPointIndex = 1; - } + Point2D intP = intPoints.get(splitPointIndex); - splittedPairs.add(pair); + be1L.setEndPoint(intP); + be1R.setBeginPoint(intP); + be2L.setEndPoint(intP); + be2R.setBeginPoint(intP); - Reference be1LRef = new Reference<>(null); - Reference be1RRef = new Reference<>(null); - be1.split(t1Ref.get(splitPointIndex), be1LRef, be1RRef); - Reference be2LRef = new Reference<>(null); - Reference be2RRef = new Reference<>(null); - be2.split(t2Ref.get(splitPointIndex), be2LRef, be2RRef); - - BezierEdge be1L = be1LRef.getVal(); - BezierEdge be1R = be1RRef.getVal(); - BezierEdge be2L = be2LRef.getVal(); - BezierEdge be2R = be2RRef.getVal(); - - Point2D intP = intPoints.get(splitPointIndex); - - be1L.setEndPoint(intP); - be1R.setBeginPoint(intP); - be2L.setEndPoint(intP); - be2R.setBeginPoint(intP); - - be1L.roundHalf(); - be1R.roundHalf(); - be2L.roundHalf(); - be2R.roundHalf(); - - if (i1 == i2 && j1 < j2) { + be1L.roundHalf(); + be1R.roundHalf(); + be2L.roundHalf(); + be2R.roundHalf(); + if (i1 == i2) { + if (j1 < j2) { shapes.get(i1).remove(j2); shapes.get(i2).remove(j1); j2--; } else { shapes.get(i1).remove(j1); shapes.get(i2).remove(j2); - if (i1 == i2) { - j1--; - } + j1--; } - - int n1 = j1; - int n2 = j2; - - if (!be1L.isEmpty()) { - shapes.get(i1).add(n1, be1L); - if (i1 == i2 && n2 > n1) { - n2++; - } - n1++; - if (DEBUG_PRINT) { - System.err.println("added " + be1L.toSvg()); - } - } - - if (!be1R.isEmpty()) { - shapes.get(i1).add(n1, be1R); - if (i1 == i2 && n2 > n1) { - n2++; - } - n1++; - if (DEBUG_PRINT) { - System.err.println("added " + be1R.toSvg()); - } - } - - if (!be2L.isEmpty()) { - shapes.get(i2).add(n2, be2L); - if (i1 == i2 && n1 > n2) { - n1++; - } - n2++; - if (DEBUG_PRINT) { - System.err.println("added " + be2L.toSvg()); - } - } - - if (!be2R.isEmpty()) { - shapes.get(i2).add(n2, be2R); - if (i1 == i2 && n1 > n2) { - n1++; - } - n2++; - if (DEBUG_PRINT) { - System.err.println("added " + be2R.toSvg()); - } - } - - j1--; - continue loopj1; + } else { + shapes.get(i1).remove(j1); + shapes.get(i2).remove(j2); } + int n1 = j1; + int n2 = j2; + + if (!be1L.isEmpty()) { + shapes.get(i1).add(n1, be1L); + if (DEBUG_PRINT) { + System.err.println("added " + be1L.toSvg() + " to j1=" + n1); + } + if (i1 == i2 && n2 >= n1) { + n2++; + } + n1++; + + } + + if (!be1R.isEmpty()) { + shapes.get(i1).add(n1, be1R); + if (DEBUG_PRINT) { + System.err.println("added " + be1R.toSvg() + " to j1=" + n1); + } + if (i1 == i2 && n2 >= n1) { + n2++; + } + n1++; + + } + + if (!be2L.isEmpty()) { + shapes.get(i2).add(n2, be2L); + if (DEBUG_PRINT) { + System.err.println("added " + be2L.toSvg() + " to j2=" + n2); + } + n2++; + + } + + if (!be2R.isEmpty()) { + shapes.get(i2).add(n2, be2R); + if (DEBUG_PRINT) { + System.err.println("added " + be2R.toSvg() + " to j2=" + n2); + } + n2++; + } + + j1--; + continue loopj1; } } } diff --git a/src/com/jpexs/decompiler/flash/gui/Main.java b/src/com/jpexs/decompiler/flash/gui/Main.java index acf0c5806..69c852b40 100644 --- a/src/com/jpexs/decompiler/flash/gui/Main.java +++ b/src/com/jpexs/decompiler/flash/gui/Main.java @@ -854,7 +854,11 @@ public class Main { if (mainFrame == null) { return; } - MainFrameStatusPanel sp = mainFrame.getPanel().getStatusPanel(); + MainPanel mainPanel = mainFrame.getPanel(); + if (mainPanel == null) { + return; + } + MainFrameStatusPanel sp = mainPanel.getStatusPanel(); if (sp != null && sp.isStatusHidden()) { long nowTime = System.currentTimeMillis(); if (nowTime > lastTimeStartWork + 5000) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 49ad60848..b109da4ab 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -1072,6 +1072,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } public String translate(String key) { + if (mainFrame == null) { + return ""; + } return mainFrame.translate(key); }