diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java index cb82cd668..4b6ef7b14 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/SWFInputStream.java @@ -2687,10 +2687,9 @@ public class SWFInputStream implements AutoCloseable { LINESTYLE ret = new LINESTYLE(); newDumpLevel(name, "LINESTYLE"); ret.width = readUI16("width"); - if ((shapeNum == 1) || (shapeNum == 2)) { + if (shapeNum == 1 || shapeNum == 2) { ret.color = readRGB("color"); - } - if (shapeNum == 3) { + } else if (shapeNum == 3) { ret.color = readRGBA("color"); } endDumpLevel(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 7f1444791..0cf58179d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -529,6 +529,10 @@ public class Configuration { @ConfigurationCategory("script") public static final ConfigurationItem debugHalt = null; + @ConfigurationDefaultBoolean(false) + @ConfigurationCategory("import") + public static final ConfigurationItem experimentalSvgImportEnabled = null; + private enum OSId { WINDOWS, OSX, UNIX diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index 18e06c861..28353990c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -31,12 +31,35 @@ import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ImageTag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +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.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import java.awt.Dimension; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; /** * @@ -118,6 +141,132 @@ public class ShapeImporter { return (Tag) st; } + public Tag importSvg(ShapeTag st, String svgXml) { + SWF swf = st.getSwf(); + + SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); + shapes.fillStyles = new FILLSTYLEARRAY(); + shapes.lineStyles = new LINESTYLEARRAY(); + + int shapeNum = st.getShapeNum(); + shapes.fillStyles.fillStyles = new FILLSTYLE[1]; + shapes.fillStyles.fillStyles[0] = new FILLSTYLE(); + shapes.fillStyles.fillStyles[0].color = shapeNum >= 3 ? new RGBA(0, 255, 0, 255) : new RGB(0, 255, 0); + shapes.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; + shapes.lineStyles.lineStyles = new LINESTYLE[1]; + shapes.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); + shapes.lineStyles.lineStyles[0].color = shapeNum >= 3 ? new RGBA(255, 0, 0, 255) : new RGB(255, 0, 0); + shapes.lineStyles.lineStyles[0].width = (int) SWF.unitDivisor; + + shapes.shapeRecords = new ArrayList<>(); + + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); + Element rootElement = doc.getDocumentElement(); + + if (!"svg".equals(rootElement.getTagName())) { + throw new IOException("SVG root element should be 'svg'"); + } + + processSvgObject(shapes, rootElement); + + } catch (SAXException | IOException | ParserConfigurationException ex) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); + } + + shapes.shapeRecords.add(new EndShapeRecord()); + + st.shapes = shapes; + st.setModified(true); + + return (Tag) st; + } + + private void processSvgObject(SHAPEWITHSTYLE shapes, Element element) { + for (int i = 0; i < element.getChildNodes().getLength(); i++) { + Node childNode = element.getChildNodes().item(i); + if (childNode instanceof Element) { + Element childElement = (Element) childNode; + String tagName = childElement.getTagName(); + if ("g".equals(tagName)) { + processSvgObject(shapes, childElement); + } else if ("path".equals(tagName)) { + processPath(shapes, childElement); + } + } + } + } + + private void processPath(SHAPEWITHSTYLE shapes, Element childElement) { + String data = childElement.getAttribute("d"); + if (data == null) { + return; + } + + char command = 0; + String[] parts = data.split(" "); + double x0 = 0; + double y0 = 0; + for (int i = 0; i < parts.length; i += 2) { + String part = parts[i]; + if (part.length() == 0) { + continue; + } + + char ch = part.charAt(0); + if (ch == 'M' || ch == 'L' || ch == 'Q') { + command = ch; + part = part.substring(1); + } + + double x = Double.parseDouble(part); + + part = parts[i + 1]; + double y = Double.parseDouble(part); + + switch (command) { + case 'M': + StyleChangeRecord scr = new StyleChangeRecord(); + scr.moveDeltaX = (int) ((x - x0) * SWF.unitDivisor); + scr.moveDeltaY = (int) ((y - y0) * SWF.unitDivisor); + scr.stateMoveTo = true; + scr.stateFillStyle0 = true; + scr.stateLineStyle = true; + scr.fillStyle0 = 1; + scr.lineStyle = 1; + shapes.shapeRecords.add(scr); + break; + case 'L': + StraightEdgeRecord ser = new StraightEdgeRecord(); + ser.deltaX = (int) ((x - x0) * SWF.unitDivisor); + ser.deltaY = (int) ((y - y0) * SWF.unitDivisor); + ser.generalLineFlag = true; + shapes.shapeRecords.add(ser); + break; + case 'Q': + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + cer.controlDeltaX = (int) ((x - x0) * SWF.unitDivisor); + cer.controlDeltaY = (int) ((y - y0) * SWF.unitDivisor); + x0 = x; + y0 = y; + part = parts[i + 2]; + x = Double.parseDouble(part); + part = parts[i + 3]; + y = Double.parseDouble(part); + i += 2; + cer.anchorDeltaX = (int) ((x - x0) * SWF.unitDivisor); + cer.anchorDeltaY = (int) ((y - y0) * SWF.unitDivisor); + shapes.shapeRecords.add(cer); + break; + } + + x0 = x; + y0 = y; + } + } + public static int getShapeTagType(String format) { int res = 0; switch (format) { diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 2f342745e..f7b42fcfb 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -2617,12 +2617,24 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } if (item instanceof ShapeTag) { ShapeTag st = (ShapeTag) item; - File selectedFile = showImportFileChooser("filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp"); + String filter = "filter.images|*.jpg;*.jpeg;*.gif;*.png;*.bmp"; + if (Configuration.experimentalSvgImportEnabled.get()) { + filter += ";*.svg"; + } + + File selectedFile = showImportFileChooser(filter); if (selectedFile != null) { File selfile = Helper.fixDialogFile(selectedFile); - byte[] data = Helper.readFile(selfile.getAbsolutePath()); + byte[] data = null; + String svgText = null; + if (".svg".equals(Path.getExtension(selfile))) { + svgText = Helper.readTextFile(selfile.getAbsolutePath()); + } else { + data = Helper.readFile(selfile.getAbsolutePath()); + } try { - Tag newTag = new ShapeImporter().importImage(st, data); + ShapeImporter shapeImporter = new ShapeImporter(); + Tag newTag = svgText != null ? shapeImporter.importSvg(st, svgText) : shapeImporter.importImage(st, data); SWF swf = st.getSwf(); if (newTag != null) { refreshTree(swf); diff --git a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties index a5b48e386..b78076af9 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/AdvancedSettingsDialog.properties @@ -401,4 +401,7 @@ config.description.gui.avm2.splitPane.vars.dividerLocationPercent = tip = Tip:\u0020 config.name.gui.action.splitPane.vars.dividerLocationPercent = (Internal) AS1/2 Debug menu splitter location -config.description.gui.action.splitPane.vars.dividerLocationPercent = \ No newline at end of file +config.description.gui.action.splitPane.vars.dividerLocationPercent = + +config.name.experimentalSvgImportEnabled = Enable experimental SVG import +config.description.experimentalSvgImportEnabled =