From 5f82b2f8406355b06dab8415218de05ff398ed98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 21 Mar 2026 20:20:05 +0100 Subject: [PATCH] feat: XAML export (#2674) Shape exporter - solid fill, gradient, bitmapfill, strokes Timeline exporting: Morphshapes as exported shapes at given ratio. Clipping, Zoom, Texts, Background color, Creating project. Missing: blend modes, filters, nonscaling strokes, sound, video --- libsrc/ffdec_lib/build.xml | 1 + .../flash/exporters/XamlExporter.java | 1194 +++++++++++++++++ .../flash/exporters/XamlExporterStub.zip | Bin 0 -> 7998 bytes .../flash/exporters/commonshape/Matrix.java | 11 + .../exporters/shape/XamlShapeExporter.java | 551 ++++++++ .../decompiler/flash/gui/MainFrameMenu.java | 109 +- .../jpexs/decompiler/flash/gui/MainPanel.java | 172 ++- .../flash/gui/graphics/exportall16.png | Bin 4734 -> 4709 bytes .../flash/gui/graphics/exportall32.png | Bin 5800 -> 5358 bytes .../flash/gui/locales/MainFrame.properties | 6 +- .../flash/gui/locales/MainFrame_cs.properties | 6 +- .../flash/gui/locales/MainFrame_de.properties | 9 +- .../flash/gui/locales/MainFrame_sk.properties | 9 +- .../flash/gui/tagtree/TagTreeContextMenu.java | 17 +- 14 files changed, 1966 insertions(+), 119 deletions(-) create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporter.java create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporterStub.zip create mode 100644 libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/XamlShapeExporter.java diff --git a/libsrc/ffdec_lib/build.xml b/libsrc/ffdec_lib/build.xml index cba7d1f20..b8ed427e2 100644 --- a/libsrc/ffdec_lib/build.xml +++ b/libsrc/ffdec_lib/build.xml @@ -36,6 +36,7 @@ + diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporter.java new file mode 100644 index 000000000..709cebc6e --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporter.java @@ -0,0 +1,1194 @@ +/* + * Copyright (C) 2010-2026 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.exporters; + +import com.jpexs.decompiler.flash.FontNormalizer; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; +import com.jpexs.decompiler.flash.exporters.shape.XamlShapeExporter; +import com.jpexs.decompiler.flash.tags.DefineEditTextTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.StaticTextTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.types.MATRIX; +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.TEXTRECORD; +import com.jpexs.decompiler.flash.xfl.XFLXmlWriter; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.Stack; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLStreamException; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; + +/** + * + * @author JPEXS + */ +public class XamlExporter { + + public void exportSwf(SWF swf, File projectParentFolder, String projectName, double zoom) throws IOException { + + createProject(projectParentFolder, projectName); + + String projectIdentifier = makeIdentifier(projectName); + + File mainWindowXaml = new File(projectParentFolder.getAbsolutePath() + "/" + projectName + "/" + projectName + "/MainWindow.xaml"); + File csProj = new File(projectParentFolder.getAbsolutePath() + "/" + projectName + "/" + projectName + "/" + projectName + ".csproj"); + + StringBuilder imageData = new StringBuilder(); + StringBuilder fontData = new StringBuilder(); + Map normalizedFonts = new HashMap<>(); + Map normalizedTexts = new HashMap<>(); + FontNormalizer fontNormalizer = new FontNormalizer(); + fontNormalizer.normalizeFonts(swf, normalizedFonts, normalizedTexts); + + XFLXmlWriter writer = new XFLXmlWriter(); + SetBackgroundColorTag bkgColorTag = swf.getBackgroundColor(); + RGB bgColor = new RGB(255, 255, 255); + if (bkgColorTag != null) { + bgColor = bkgColorTag.backgroundColor; + } + try { + writer.writeStartElement("Window", new String[]{ + "x:Class", projectIdentifier + ".MainWindow", + "xmlns", "http://schemas.microsoft.com/winfx/2006/xaml/presentation", + "xmlns:x", "http://schemas.microsoft.com/winfx/2006/xaml", + "Title", "MainWindow", + "SizeToContent", "WidthAndHeight", + "Background", bgColor.toHexRGB() + }); + writer.writeStartElement("Window.Resources"); + Map characters = swf.getCharacters(false); + Set characterSet = new HashSet<>(characters.values()); + + Map characterNames = new HashMap<>(); + for (int charId : characters.keySet()) { + CharacterTag character = characters.get(charId); + String type = "Character"; + if (character instanceof ShapeTag) { + type = "Shape"; + } + if (character instanceof DefineSpriteTag) { + type = "Sprite"; + } + if (character instanceof ImageTag) { + type = "Image"; + } + if (character instanceof TextTag) { + type = "Text"; + } + String name = type + charId; + characterNames.put(charId, name); + } + + Map imageFiles = new HashMap<>(); + Set systemFonts = new HashSet<>(); + + File outputDir = mainWindowXaml.getParentFile(); + + File imagesDir = new File(outputDir.getAbsolutePath() + "/Images"); + File fontsDir = new File(outputDir.getAbsolutePath() + "/Fonts"); + + for (int charId : characters.keySet()) { + CharacterTag character = characters.get(charId); + + if (character instanceof MorphShapeTag) { + continue; + } + if (character instanceof DefineVideoStreamTag) { + continue; + } + if (character instanceof ImageTag) { + ImageTag image = (ImageTag) character; + byte[] data = Helper.readStream(image.getConvertedImageData()); + if (!imagesDir.exists()) { + imagesDir.mkdirs(); + } + ImageFormat format = ImageTag.getImageFormat(data); + imageFiles.put(charId, "Images\\image" + charId + format.getExtension()); + Helper.writeFile(imagesDir.getAbsolutePath() + "/image" + charId + format.getExtension(), data); + imageData.append(" \r\n"); + continue; + } + + if (character instanceof FontTag) { + FontTag font = (FontTag) character; + if (normalizedFonts.containsKey(charId)) { + font = normalizedFonts.get(charId); + } + if (!fontsDir.exists()) { + fontsDir.mkdirs(); + } + FontExporter exporter = new FontExporter(); + String fontFile = fontsDir.getAbsolutePath() + "/font" + charId + ".ttf"; + exporter.exportFont(font, FontExportMode.TTF, new File(fontFile)); + if (!new File(fontFile).exists()) { + systemFonts.add(font.getFontNameIntag()); + } else { + fontData.append(" \r\n"); + } + continue; + } + + String name = characterNames.get(charId); + writer.writeStartElement("DataTemplate", new String[]{"x:Key", name}); + if (character instanceof ShapeTag) { + ShapeTag shape = (ShapeTag) character; + XamlShapeExporter exporter = new XamlShapeExporter(shape.getWindingRule(), shape.getShapeNum(), swf, shape.getShapes(), charId, null, null, 1, 1, new Matrix(), imageFiles, false, null); + exporter.export(); + String shapeCanvas = exporter.getResultAsString(); + writer.writeCharactersRaw(shapeCanvas); + } + if (character instanceof DefineSpriteTag) { + String storyBoardName = name + "StoryBoard"; + DefineSpriteTag sprite = (DefineSpriteTag) character; + writer.writeStartElement("DataTemplate.Resources"); + writeStoryBoard(writer, sprite, swf, storyBoardName); + writer.writeEndElement(); //DataTemplate.Resources + writeTimeline(sprite, writer, characterNames, swf, 1, storyBoardName, imageFiles, outputDir); + } + if (character instanceof ButtonTag) { + ButtonTag button = (ButtonTag) character; + Frame frame = button.getTimeline().getFrame(0); + writer.writeStartElement("Canvas"); + writeFrame(frame, writer, characterNames, swf, 1, imageFiles, mainWindowXaml.getParentFile()); + writer.writeEndElement(); //Canvas + } + if (character instanceof StaticTextTag) { + StaticTextTag text = (StaticTextTag) character; + if (normalizedTexts.containsKey(charId)) { + text = (StaticTextTag) normalizedTexts.get(charId); + } + int fontId = -1; + FontTag font = null; + String fontName = null; + int textHeight = -1; + RGB textColor = null; + RGBA textColorA = null; + double lastLineHeight = -1; + double lastLeftMargin = -1; + double lastRightMargin = -1; + boolean firstRun = true; + + writer.writeStartElement("Canvas"); + + writer.writeStartElement("RichTextBox", new String[]{ + "Width", "" + (twipToPixel(text.getBounds().getWidth()) + 20), + "Height", "" + (twipToPixel(text.getBounds().getHeight()) + 20), + "BorderBrush", "Transparent", + "Background", "Transparent", + "IsReadOnly", "True", + "Focusable", "False", + "IsHitTestVisible", "False", //"Canvas.Left", "-5", + "Canvas.Left", "" + twipToPixel(text.getBounds().Xmin), + "Canvas.Top", "" + twipToPixel(text.getBounds().Ymin) + }); + + //Some magic to get RichTextBox behave correctly + writer.writeAttribute("Padding", "-4,-2,0,0"); + + writer.writeStartElement("RichTextBox.RenderTransform"); + writer.writeStartElement("MatrixTransform", new String[]{ + "Matrix", new Matrix(text.getTextMatrix()).getXamlTransformationString(SWF.unitDivisor / zoom, 1 / zoom) + }); + writer.writeEndElement(); //MatrixTransform + writer.writeEndElement(); //RichTextBox.RenderTransform + + writer.writeStartElement("FlowDocument"); + + boolean first = true; + Map attrs = TextTag.getTextRecordsAttributes(text.textRecords, swf, normalizedFonts); + if ((int) attrs.get("lineSpacing") < 0) { + attrs.put("lineSpacing", 0); + } + @SuppressWarnings("unchecked") + List leftMargins = (List) attrs.get("allLeftMargins"); + for (int r = 0; r < text.textRecords.size(); r++) { + TEXTRECORD rec = text.textRecords.get(r); + if (rec.styleFlagsHasColor) { + if (text instanceof DefineTextTag) { + textColor = rec.textColor; + } else { + textColorA = rec.textColorA; + } + } + if (rec.styleFlagsHasFont) { + fontId = rec.fontId; + fontName = null; + textHeight = rec.textHeight; + font = ((Tag) text).getSwf().getFont(fontId); + if (normalizedFonts.containsKey(fontId)) { + font = normalizedFonts.get(fontId); + } + + if (font != null) { + fontName = font.getFontNameIntag(); + } + if (fontName == null) { + fontName = FontTag.getDefaultFontName(); + } + int fontStyle = 0; + if (font != null) { + fontStyle = font.getFontStyle(); + } + } + boolean newline = false; + if (!firstRun && rec.styleFlagsHasYOffset) { + newline = true; + } + firstRun = false; + if (font != null) { + + boolean newParagraph = false; + if ((int) attrs.get("indent") > 0) { + newParagraph = true; + } + if (newline) { + newParagraph = true; + } + if (lastLineHeight != twipToPixel(textHeight) + twipToPixel((int) attrs.get("lineSpacing"))) { + newParagraph = true; + } + if (lastLeftMargin != twipToPixel(leftMargins.get(r))) { + newParagraph = true; + } + if (lastRightMargin != twipToPixel((int) attrs.get("rightMargin"))) { + newParagraph = true; + } + if (first) { + newParagraph = true; + } + + if (newParagraph) { + if (!first) { + writer.writeEndElement(); //Paragraph + } + lastLineHeight = twipToPixel(textHeight) + twipToPixel((int) attrs.get("lineSpacing")); + lastLeftMargin = twipToPixel(leftMargins.get(r)); + lastRightMargin = twipToPixel((int) attrs.get("rightMargin")); + writer.writeStartElement("Paragraph", new String[]{ + "TextIndent", "" + (int) attrs.get("indent"), + "LineHeight", doubleToString(lastLineHeight), + "Margin", doubleToString(lastLeftMargin) + ",0," + doubleToString(lastRightMargin) + ",0" + }); + } + writer.writeStartElement("Run", new String[]{ + "FontSize", doubleToString(twipToPixel(textHeight)), + "FontFamily", systemFonts.contains(fontName) ? fontName : "/Fonts/#" + fontName + }); + + if (textColor != null) { + writer.writeAttribute("Foreground", textColor.toHexRGB()); + } else if (textColorA != null) { + writer.writeAttribute("Foreground", textColorA.toHexARGB()); + } + if (font.isBold()) { + writer.writeAttribute("FontWeight", "Bold"); + } + if (font.isItalic()) { + writer.writeAttribute("FontStyle", "Italic"); + } + writer.writeCharacters(rec.getText(font)); + writer.writeEndElement(); //Run + first = false; + } + } + if (!first) { + writer.writeEndElement(); //Paragraph + } + writer.writeEndElement(); //FlowDocument + writer.writeEndElement(); //RichTextBox + writer.writeEndElement(); //Canvas + } + if (character instanceof DefineEditTextTag) { + DefineEditTextTag defineEditText = (DefineEditTextTag) character; + if (normalizedTexts.containsKey(charId)) { + defineEditText = (DefineEditTextTag) normalizedTexts.get(charId); + } + + writer.writeStartElement("Canvas"); + /* + if (defineEditText.border) { + writer.writeStartElement("Border", new String[] { + "BorderBrush", "Black", + "BorderThickness", "1", + "Background", "White", + "Padding", "0", + "Width", "" + twipToPixel(defineEditText.getBounds().getWidth()), + "Height", "" + twipToPixel(defineEditText.getBounds().getHeight()), + //"Canvas.Left", "" + twipToPixel(defineEditText.getBounds().Xmin), + //"Canvas.Top", "" + twipToPixel(defineEditText.getBounds().Ymin), + }); + writer.writeStartElement("Border.RenderTransform"); + writer.writeStartElement("MatrixTransform", new String[] { + "Matrix", new Matrix(defineEditText.getTextMatrix()).getXamlTransformationString(SWF.unitDivisor / zoom, 1 / zoom) + }); + writer.writeEndElement(); //MatrixTransform + writer.writeEndElement(); //Border.RenderTransform + + writer.writeEndElement(); //Border + }*/ + writer.writeStartElement("RichTextBox", new String[]{ + "Width", "" + twipToPixel(defineEditText.getBounds().getWidth()), + "Height", "" + twipToPixel(defineEditText.getBounds().getHeight()), + "BorderBrush", defineEditText.border ? "Black" : "Transparent", + "BorderThickness", defineEditText.border ? "1" : "0", + "Background", defineEditText.border ? "White" : "Transparent", + //"Canvas.Left", "-3", + //"Canvas.Top", "4" + "Canvas.Left", "" + (twipToPixel(defineEditText.getBounds().Xmin)), // - 3), + "Canvas.Top", "" + (twipToPixel(defineEditText.getBounds().Ymin)), // + 4), + }); + //Note: I tried to make it more pixel perfect, but unfortunately, RichTextBox does not work very well + + int fontId = defineEditText.fontId; + FontTag font = null; + if (fontId == -1) { + if (defineEditText.fontClass != null) { + font = swf.getFontByClass(defineEditText.fontClass); + fontId = swf.getCharacterId(font); + } + } + if (normalizedFonts.containsKey(fontId)) { + font = normalizedFonts.get(fontId); + } + + if (font != null) { + if (font.hasLayout()) { + //Some magic to get RichTextBox behave correctly + double originalSize = defineEditText.fontHeight * (font.getAscent() + font.getDescent()) / font.getDivider() / 1024.0 / SWF.unitDivisor; + double normalizedSize = defineEditText.fontHeight / SWF.unitDivisor; + double padding = originalSize - normalizedSize - 2; + writer.writeAttribute("Padding", "-4," + padding + ",0,0"); + } + } + + if (defineEditText.readOnly) { + writer.writeAttribute("IsReadOnly", "True"); + } + if (defineEditText.noSelect) { + writer.writeAttribute("Focusable", "False"); + writer.writeAttribute("IsHitTestVisible", "False"); + } + + writer.writeStartElement("RichTextBox.RenderTransform"); + writer.writeStartElement("MatrixTransform", new String[]{ + "Matrix", new Matrix(defineEditText.getTextMatrix()).getXamlTransformationString(SWF.unitDivisor / zoom, 1 / zoom) + }); + writer.writeEndElement(); //MatrixTransform + writer.writeEndElement(); //RichTextBox.RenderTransform + + writer.writeStartElement("FlowDocument"); + + String txt = ""; + if (defineEditText.hasText) { + txt = defineEditText.initialText; + } + String paragraphs = convertHtmlText(characterSet, defineEditText, txt, swf, systemFonts, normalizedFonts); + writer.writeCharactersRaw(paragraphs); + writer.writeEndElement(); //FlowDocument + writer.writeEndElement(); //RichTextBox + writer.writeEndElement(); //Canvas + } + + writer.writeEndElement(); //DataTemplate + } + writeStoryBoard(writer, swf, swf, "MainTimeline"); + writer.writeEndElement(); //Window.Resources + + writer.writeStartElement("Grid", new String[]{ + "Width", "" + twipToPixel(swf.getRect().getWidth() * zoom), + "Height", "" + twipToPixel(swf.getRect().getHeight() * zoom) + }); + writeTimeline(swf, writer, characterNames, swf, zoom, "MainTimeline", imageFiles, outputDir); + writer.writeEndElement(); //Grid + writer.writeEndElement(); //Window + + } catch (XMLStreamException ex) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + String mainWindowsStr = writer.toString(); + + try { + //String formatted = new XmlPrettyFormat().prettyFormat(mainWindowsStr, 5, false); + String formatted = mainWindowsStr; + try (FileOutputStream fos = new FileOutputStream(mainWindowXaml)) { + fos.write(formatted.getBytes(Utf8Helper.charset)); + } + } catch (Exception te) { + System.out.println(mainWindowsStr); + } + + if (imageData.length() > 0 || fontData.length() > 0) { + String projContents = Helper.readTextFile(csProj.getAbsolutePath()); + String itemGroupEnd = "\r\n"; + int pos = projContents.lastIndexOf(itemGroupEnd) + itemGroupEnd.length(); + StringBuilder fullData = new StringBuilder(); + if (imageData.length() > 0) { + fullData.append(" \r\n").append(imageData.toString()).append(" \r\n"); + } + if (fontData.length() > 0) { + fullData.append(" \r\n").append(fontData.toString()).append(" \r\n"); + } + projContents = projContents.substring(0, pos) + fullData.toString() + projContents.substring(pos); + try (FileOutputStream fos = new FileOutputStream(csProj)) { + fos.write(projContents.getBytes(Utf8Helper.charset)); + } + } + } + + private static String convertHtmlText(Set characterTags, DefineEditTextTag det, String html, SWF swf, Set systemFonts, Map normalizedFonts) { + HtmlTextParser tparser = new HtmlTextParser(characterTags, det, swf, systemFonts, normalizedFonts); + XMLReader parser; + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser sparser = factory.newSAXParser(); + parser = sparser.getXMLReader(); + parser.setContentHandler(tparser); + parser.setErrorHandler(tparser); + html = "\n" + + " \n" + + "]>" + html + ""; + try { + parser.parse(new InputSource(new StringReader(html))); + } catch (SAXParseException spe) { + System.out.println(html); + System.err.println(tparser.result); + } + } catch (SAXException | IOException | ParserConfigurationException e) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.SEVERE, "Error while converting HTML", e); + } + return tparser.result.toString(); + } + + private static class HtmlTextParser extends DefaultHandler { + + public XFLXmlWriter result = new XFLXmlWriter(); + + private String fontFace = ""; + + private String color = ""; + + private int colorAlpha = 255; + + private boolean autoKern = false; + + private int size = -1; + + private int indent = -1; + + private int leftMargin = -1; + + private int rightMargin = -1; + + private int lineSpacing = -1; + + private double letterSpacing = 0; + + private String alignment = null; + + private final Set characterTags; + + private boolean bold = false; + + private boolean italic = false; + + private boolean underline = false; + + private boolean li = false; + + private String url = null; + + private String target = null; + private boolean first = true; + + private Stack fontLetterSpacingStack = new Stack<>(); + private Stack fontSizeStack = new Stack<>(); + private Stack fontFaceStack = new Stack<>(); + private Stack fontColorStack = new Stack<>(); + private Stack fontColorAlphaStack = new Stack<>(); + private Stack fontKerningStack = new Stack<>(); + private final SWF swf; + + String lastAlignment = null; + private int lastIndent = -1; + private int lastLeftMargin = -1; + private int lastRightMargin = -1; + private int lastLineSize = -1; + private final Set systemFonts; + private final Map normalizedFonts; + + @Override + public void error(SAXParseException e) throws SAXException { + } + + @Override + public void fatalError(SAXParseException e) throws SAXException { + } + + @Override + public void warning(SAXParseException e) throws SAXException { + } + + public HtmlTextParser( + Set characterTags, + DefineEditTextTag det, + SWF swf, + Set systemFonts, + Map normalizedFonts + ) { + if (det.hasFont || det.hasFontClass) { + String fontName = null; + FontTag ft = null; + if (det.hasFont) { + ft = (FontTag) det.getSwf().getCharacter(det.fontId); + } + if (det.hasFontClass) { + ft = det.getSwf().getFontByClass(det.fontClass); + } + if (ft != null) { + /*DefineFontNameTag fnt = ft.getFontNameTag(); + if (fnt != null) { + fontName = fnt.fontName; + }*/ + + if (fontName == null) { + fontName = ft.getFontNameIntag(); + } + if (fontName == null) { + fontName = FontTag.getDefaultFontName(); + } + italic = ft.isItalic(); + bold = ft.isBold(); + size = (int) twipToPixel(det.fontHeight); + + fontFace = fontName; + fontFaceStack.push(fontFace); + fontSizeStack.push(size); + } + } + if (det.hasLayout) { + leftMargin = det.leftMargin; + rightMargin = det.rightMargin; + indent = det.indent; + lineSpacing = (int) twipToPixel(det.leading); + String[] alignNames = {"left", "right", "center", "justify"}; + if (det.align < alignNames.length) { + alignment = alignNames[det.align]; + } else { + alignment = "unknown"; + } + } + if (det.hasTextColor) { + color = det.textColor.toHexRGB(); + colorAlpha = det.textColor.alpha; + } + + this.characterTags = characterTags; + this.swf = swf; + this.systemFonts = systemFonts; + this.normalizedFonts = normalizedFonts; + } + + @Override + public void startDocument() throws SAXException { + } + + @Override + public void startElement(String uri, String localName, + String qName, Attributes attributes) throws SAXException { + switch (qName) { + case "a": + String href = attributes.getValue("href"); + if (href != null) { + url = href; + } + String t = attributes.getValue("target"); + if (t != null) { + target = t; + } + break; + case "b": + bold = true; + break; + case "i": + italic = true; + break; + case "u": + underline = true; + break; + case "li": + li = true; + break; + case "p": + String a = attributes.getValue("align"); + if (a != null) { + alignment = a; + } + if (!result.isEmpty()) { + putText("\r\n"); + } + break; + case "font": + String k = attributes.getValue("kerning"); + if (k != null) { + autoKern = k.equals("1"); + } + String ls = attributes.getValue("letterSpacing"); + if (ls != null) { + try { + letterSpacing = Double.parseDouble(ls); + } catch (NumberFormatException ex) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.WARNING, "Invalid letter spacing value: {0}", ls); + } + } + String s = attributes.getValue("size"); + if (s != null) { + + try { + if (s.startsWith("+")) { + size += Integer.parseInt(s.substring(1)); + } else if (s.startsWith("-")) { + size -= Integer.parseInt(s.substring(1)); + } else { + size = Integer.parseInt(s); + } + } catch (NumberFormatException ex) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.WARNING, "Invalid font size: {0}", s); + } + } + String c = attributes.getValue("color"); + if (c != null) { + if (c.matches("^#[0-9a-fA-F]{8}$")) { + color = "#" + c.substring(3); + } else if (c.matches("^#[0-9a-fA-F]{6}$")) { + color = c; + colorAlpha = 255; + } else { + //wrong format, do not change color + } + } + String f = attributes.getValue("face"); + if (f != null) { + for (Tag tag : characterTags) { + if (tag instanceof FontTag) { + FontTag ft = (FontTag) tag; + String fontName = null; + if (f.equals(ft.getFontNameIntag())) { + /*DefineFontNameTag fnt = ft.getFontNameTag(); + if (fnt != null) { + fontName = fnt.fontName; + }*/ + if (fontName == null) { + fontName = ft.getFontNameIntag(); + } + fontFace = fontName; + break; + } + } + } + } + fontColorStack.push(color); + fontColorAlphaStack.push(colorAlpha); + fontFaceStack.push(fontFace); + fontSizeStack.push(size); + fontLetterSpacingStack.push(letterSpacing); + fontKerningStack.push(autoKern); + break; + } + } + + @Override + public void endElement(String uri, String localName, + String qName) throws SAXException { + if (qName.equals("a")) { + url = null; + target = null; + } + if (qName.equals("b")) { + bold = false; + } + if (qName.equals("i")) { + italic = false; + } + if (qName.equals("u")) { + underline = false; + } + if (qName.equals("li")) { + li = false; + } + if (qName.equals("font")) { + fontColorStack.pop(); + fontColorAlphaStack.pop(); + fontFaceStack.pop(); + fontKerningStack.pop(); + fontLetterSpacingStack.pop(); + fontSizeStack.pop(); + color = null; + colorAlpha = 255; + if (!fontColorStack.isEmpty()) { + color = fontColorStack.peek(); + colorAlpha = fontColorAlphaStack.peek(); + } + fontFace = null; + if (!fontFaceStack.isEmpty()) { + fontFace = fontFaceStack.peek(); + } + autoKern = false; + if (!fontKerningStack.isEmpty()) { + autoKern = fontKerningStack.peek(); + } + letterSpacing = 0; + if (!fontLetterSpacingStack.isEmpty()) { + letterSpacing = fontLetterSpacingStack.peek(); + } + size = 10; //?? + if (!fontSizeStack.isEmpty()) { + size = fontSizeStack.peek(); + } + } + } + + private void putText(String txt) { + try { + int newLineSize = 0; + if (fontFace != null && size > -1) { + FontTag font = swf.getFontByNameInTag(fontFace, bold, italic); + if (font != null) { + int fontId = swf.getCharacterId(font); + if (normalizedFonts.containsKey(fontId)) { + font = normalizedFonts.get(fontId); + } + if (font.hasLayout()) { + newLineSize = (int) Math.round(size * (font.getAscent() + font.getDescent()) / font.getDivider() / 1024.0); + if (lineSpacing > -1) { + newLineSize += lineSpacing; + } + } + } + } + + if ((alignment != null && !Objects.equals(lastAlignment, alignment)) + || (indent > -1 && lastIndent != indent) + || (leftMargin > -1 && lastLeftMargin != leftMargin) + || (rightMargin > -1 && lastRightMargin != rightMargin) + || (lineSpacing > -1 && size > -1 && lastLineSize != newLineSize) + || first + || "\r\n".equals(txt)) { + + lastAlignment = alignment; + lastIndent = indent; + lastLeftMargin = leftMargin; + lastRightMargin = rightMargin; + lastLineSize = newLineSize; + if (!first) { + result.writeEndElement(); //Paragraph + } + result.writeStartElement("Paragraph"); + if (alignment != null) { + String alignmentValue = alignment.substring(0, 1).toUpperCase() + alignment.substring(1); + result.writeAttribute("TextAlignment", alignmentValue); + } + if (indent > -1) { + result.writeAttribute("TextIndent", twipToPixel(indent)); + } + if (leftMargin > -1 || rightMargin > -1) { + result.writeAttribute("Margin", twipToPixel(leftMargin == -1 ? 0 : leftMargin) + ",0," + twipToPixel(rightMargin == -1 ? 0 : rightMargin) + ",0"); + } + if (lineSpacing > -1 && size > -1) { + result.writeAttribute("LineHeight", newLineSize); + } + } + result.writeStartElement("Run"); + + result.writeAttribute("Typography.Kerning", autoKern ? "True" : "False"); + /*if (letterSpacing != 0) { + result.writeAttribute("letterSpacing", letterSpacing); + }*/ + + if (size > -1) { + result.writeAttribute("FontSize", size); + //result.writeAttribute("bitmapSize", (int) (size * SWF.unitDivisor)); + } + if (fontFace != null) { + result.writeAttribute("FontFamily", systemFonts.contains(fontFace) ? fontFace : "/Fonts/#" + fontFace); + } + if (color != null && !color.isEmpty()) { + if (colorAlpha != 255) { + result.writeAttribute("Foreground", "#" + String.format("%02x", colorAlpha) + color.substring(1)); + } else { + result.writeAttribute("Foreground", color); + } + } + if (bold) { + result.writeAttribute("FontWeight", "Bold"); + } + if (italic) { + result.writeAttribute("FontStyle", "Italic"); + } + + if (url != null) { + result.writeStartElement("Hyperlink"); + result.writeAttribute("NavigateUri", url); + result.writeAttribute("Foreground", "Blue"); + result.writeAttribute("TextDecorations", "Underline"); + } + result.writeCharacters(txt); + if (url != null) { + result.writeEndElement(); //Hyperlink + } + if (target != null) { + //result.writeAttribute("target", target); + } + result.writeEndElement(); //Run + + first = false; + } catch (XMLStreamException ex) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + public void characters(char[] ch, int start, int length) + throws SAXException { + putText(new String(ch, start, length)); + } + + @Override + public void endDocument() { + if (this.result.isEmpty()) { + putText(""); + } + if (!first) { + try { + result.writeEndElement(); //Paragraph + } catch (XMLStreamException ex) { + Logger.getLogger(XamlExporter.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + + public static String formatTime(double totalSeconds) { + long totalMillis = (long) (totalSeconds * 1000); + + long hours = totalMillis / 3600000; + long minutes = (totalMillis % 3600000) / 60000; + double secondsWithFraction = (totalMillis % 60000) / 1000.0; + + return String.format(Locale.ENGLISH, "%d:%d:%.3f", hours, minutes, secondsWithFraction); + } + + private void writeStoryBoard(XFLXmlWriter writer, Timelined timelined, SWF swf, String storyBoardName) throws XMLStreamException { + writer.writeStartElement("Storyboard", new String[]{ + "x:Key", storyBoardName, + "RepeatBehavior", "Forever" + }); + Timeline timeline = timelined.getTimeline(); + for (int i = 0; i < timeline.getFrameCount(); i++) { + writer.writeStartElement("ObjectAnimationUsingKeyFrames", new String[]{ + "Storyboard.TargetName", "frame" + (i + 1), + "Storyboard.TargetProperty", "Visibility" + }); + if (i == 0) { + writer.writeStartElement("DiscreteObjectKeyFrame", new String[]{ + "KeyTime", "0:0:0.0", + "Value", "{x:Static Visibility.Visible}" + }); + writer.writeEndElement(); + writer.writeStartElement("DiscreteObjectKeyFrame", new String[]{ + "KeyTime", formatTime(1 / swf.frameRate), + "Value", "{x:Static Visibility.Collapsed}" + }); + writer.writeEndElement(); + } else { + writer.writeStartElement("DiscreteObjectKeyFrame", new String[]{ + "KeyTime", "0:0:0.0", + "Value", "{x:Static Visibility.Collapsed}" + }); + writer.writeEndElement(); + writer.writeStartElement("DiscreteObjectKeyFrame", new String[]{ + "KeyTime", formatTime(i * 1 / swf.frameRate), + "Value", "{x:Static Visibility.Visible}" + }); + writer.writeEndElement(); + writer.writeStartElement("DiscreteObjectKeyFrame", new String[]{ + "KeyTime", formatTime((i + 1) * 1 / swf.frameRate), + "Value", "{x:Static Visibility.Collapsed}" + }); + writer.writeEndElement(); + + } + writer.writeEndElement(); //ObjectAnimationUsingKeyFrames + } + + writer.writeEndElement();; // StoryBoard + } + + private void writeTimeline(Timelined timelined, XFLXmlWriter writer, Map characterNames, SWF swf, double zoom, String storyBoardName, Map imageFiles, File outputDir) throws XMLStreamException, IOException { + writer.writeStartElement("Grid"); + + writer.writeStartElement("Grid.Triggers"); + writer.writeStartElement("EventTrigger", new String[]{ + "RoutedEvent", "FrameworkElement.Loaded" + }); + writer.writeStartElement("BeginStoryboard", new String[]{ + "Storyboard", "{StaticResource " + storyBoardName + "}" + }); + writer.writeEndElement(); //BeginStoryboard + writer.writeEndElement(); //EventTrigger + writer.writeEndElement(); //Grid.Triggers + + Timeline timeline = timelined.getTimeline(); + + for (int i = 0; i < timeline.getFrameCount(); i++) { + writer.writeStartElement("Canvas", new String[]{ + "x:Name", "frame" + (i + 1) + }); + if (i > 0) { + writer.writeAttribute("Visibility", "Collapsed"); + } + + Frame frame = timeline.getFrame(i); + writeFrame(frame, writer, characterNames, swf, zoom, imageFiles, outputDir); + + writer.writeEndElement(); //Canvas + } + + writer.writeEndElement(); //Grid + } + + private void writeFrame(Frame frame, XFLXmlWriter writer, Map characterNames, SWF swf, double zoom, Map imageFiles, File outputDir) throws XMLStreamException, IOException { + Matrix zoomMatrix = Matrix.getScaleInstance(zoom); + Stack clipDepths = new Stack<>(); + for (Integer depth : frame.layers.keySet()) { + DepthState depthState = frame.layers.get(depth); + + while (!clipDepths.isEmpty()) { + int topDepth = clipDepths.peek(); + if (depth >= topDepth) { + clipDepths.pop(); + writer.writeEndElement(); //Canvas + } else { + break; + } + } + + if (depthState.characterId > -1) { + String characterElement; + CharacterTag character = depthState.getCharacter(); + + if (depthState.clipDepth > -1) { + writer.writeStartElement("Canvas"); + writer.writeStartElement("Canvas.Clip"); + if (character instanceof ShapeTag) { + ShapeTag shape = (ShapeTag) character; + Matrix matrix = depthState.matrix == null ? new Matrix() : new Matrix(depthState.matrix); + matrix = matrix.preConcatenate(zoomMatrix); + XamlShapeExporter exporter = new XamlShapeExporter(shape.getWindingRule(), shape.getShapeNum(), swf, shape.getShapes(), shape.getCharacterId(), null, null, 1, 1, new Matrix(), imageFiles, true, matrix); + exporter.export(); + String shapeGeometry = exporter.getResultAsString(); + writer.writeCharactersRaw(shapeGeometry); + } + writer.writeEndElement(); //Canvas.Clip + clipDepths.push(depthState.clipDepth); + continue; + } + + if (character instanceof MorphShapeTag) { + writer.writeStartElement("Canvas"); + characterElement = "Canvas"; + + MorphShapeTag morphShape = (MorphShapeTag) character; + SHAPEWITHSTYLE shape = morphShape.getShapeAtRatio(depthState.ratio); + XamlShapeExporter exporter = new XamlShapeExporter(ShapeTag.WIND_EVEN_ODD, morphShape.getShapeNum() == 2 ? 4 : 1, swf, shape, morphShape.getCharacterId(), null, null, 1, 1, new Matrix(), imageFiles, false, null); + exporter.export(); + String shapeCanvas = exporter.getResultAsString(); + writer.writeCharactersRaw(shapeCanvas); + } else if (character instanceof DefineVideoStreamTag) { + + //No support for Video, this is just a stub, but it extracts all PNG frames to disk... + continue; + /*DefineVideoStreamTag video = (DefineVideoStreamTag) character; + + int ratio = depthState.ratio; + if (ratio == -1) { + ratio = 0; + } + File videoDir = new File(outputDir.getAbsolutePath() + "/Video"); + if (!videoDir.exists()) { + videoDir.mkdirs(); + } + File videoFrameFile = new File(videoDir.getAbsolutePath() + "/video" + video.getCharacterId()+"_" + ratio + ".png"); + + if (!videoFrameFile.exists()) { + SerializableImage img = new SerializableImage(video.width, video.height, BufferedImage.TYPE_INT_ARGB); + Matrix matrix = new Matrix(); + video.toImage(0, 0, ratio, new RenderContext(), img, img, false, matrix, null, null, null, null, 1, true, new ExportRectangle(swf.getRect()), new ExportRectangle(swf.getRect()), true, 0, 0, false, 1); + ImageIO.write(img.getBufferedImage(), "PNG", videoFrameFile); + } + + writer.writeStartElement("Image", new String[] { + "Source", "/Video/video" + video.getCharacterId() + "_" + ratio + ".png" + }); + characterElement = "Image";*/ + + } else { + writer.writeStartElement("ContentControl", new String[]{ + "ContentTemplate", "{StaticResource " + characterNames.get(depthState.characterId) + "}" + }); + characterElement = "ContentControl"; + } + MATRIX matrix = new MATRIX(); + if (depthState.matrix != null) { + matrix = depthState.matrix; + } + writer.writeStartElement(characterElement + ".RenderTransform"); + writer.writeStartElement("MatrixTransform", new String[]{ + "Matrix", new Matrix(matrix).getXamlTransformationString(SWF.unitDivisor / zoom, 1 / zoom) + }); + writer.writeEndElement(); //MatrixTransform + writer.writeEndElement(); //characterElement + .RenderTransform + + writer.writeEndElement(); //ContentControl + } + } + while (!clipDepths.isEmpty()) { + clipDepths.pop(); + writer.writeEndElement(); //Canvas + } + } + + private static double twipToPixel(double tw) { + return tw / SWF.unitDivisor; + } + + private static String doubleToString(double d, int precision) { + double m = Math.pow(10, precision); + d = Math.round(d * m) / m; + return doubleToString(d); + } + + private static String doubleToString(double d) { + String ds = "" + d; + if (ds.endsWith(".0")) { + ds = ds.substring(0, ds.length() - 2); + } + return ds; + } + + private static String makeIdentifier(String name) { + return name.replace(" ", "_"); + } + + private static void createProject(File destDir, String projectName) throws IOException { + InputStream is = XamlExporter.class.getResourceAsStream("/com/jpexs/decompiler/flash/exporters/XamlExporterStub.zip"); + String templateName = "My Wpf App"; + String templateIdentifier = makeIdentifier(templateName); + + String projectIdentifier = makeIdentifier(projectName); + + destDir = new File(destDir, projectName); + + try (ZipInputStream zis = new ZipInputStream(is)) { + ZipEntry entry; + + while ((entry = zis.getNextEntry()) != null) { + + String newName = entry.getName(); + if (newName.contains(templateName)) { + newName = newName.replace(templateName, projectName); + } + + File newFile = new File(destDir, newName); + + if (entry.isDirectory()) { + newFile.mkdirs(); + } else { + new File(newFile.getParent()).mkdirs(); + + String contents = new String(Helper.readStream(zis), "UTF-8"); + contents = contents.replace(templateIdentifier, projectIdentifier); + contents = contents.replace(templateName, projectName); + + String projectGuidBegin = ""; + String projectGuidEnd = ""; + + if (contents.contains(projectGuidBegin)) { + String projectGuid = "{" + UUID.randomUUID().toString().toUpperCase() + "}"; + int start = contents.indexOf(projectGuidBegin) + projectGuidBegin.length(); + int end = contents.indexOf(projectGuidEnd, start); + contents = contents.substring(0, start) + + projectGuid + + contents.substring(end); + } + + try (FileOutputStream fos = new FileOutputStream(newFile)) { + fos.write(contents.getBytes("UTF-8")); + } + } + + zis.closeEntry(); + } + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporterStub.zip b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporterStub.zip new file mode 100644 index 0000000000000000000000000000000000000000..2d5f3adf82700b979d9805b20e1abc064326f251 GIT binary patch literal 7998 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 imageFiles; + + /** + * Geometry only + */ + private final boolean geometryOnly; + + /** + * Drawing lines? + */ + private boolean inLines = false; + private final Matrix geometryMatrix; + + /** + * Constructor. + * + * @param windingRule Winding rule + * @param shapeNum Shape number + * @param swf SWF + * @param shape Shape + * @param id Id + * @param defaultColor Default color + * @param colorTransform Color transform + * @param zoom Zoom - shape zoom + * @param displayZoom Display zoom - overall SVG zoom + * @param strokeTransformation Stroke transformation + * @param imageFiles Image files + * @param geometryOnly Geometry only + * @param geometryMatrix Geometry matrix + */ + public XamlShapeExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, int id, Color defaultColor, ColorTransform colorTransform, double zoom, double displayZoom, Matrix strokeTransformation, Map imageFiles, boolean geometryOnly, Matrix geometryMatrix) { + super(windingRule, shapeNum, swf, shape, colorTransform); + this.swf = swf; + this.id = id; + this.defaultColor = defaultColor; + this.zoom = zoom; + this.displayZoom = displayZoom; + this.imageFiles = imageFiles; + this.geometryOnly = geometryOnly; + this.geometryMatrix = geometryMatrix; + + thicknessScale = Math.sqrt(Math.abs(strokeTransformation.scaleX * strokeTransformation.scaleY - strokeTransformation.rotateSkew0 * strokeTransformation.rotateSkew1)); + + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder docBuilder; + try { + docBuilder = docFactory.newDocumentBuilder(); + document = docBuilder.newDocument(); + + if (geometryOnly) { + pathGeometry = document.createElement("PathGeometry"); + pathGeometry.setAttribute("FillRule", windingRule == ShapeTag.WIND_NONZERO ? "Nonzero" : "EvenOdd"); + applyMatrix(); + document.appendChild(pathGeometry); + root = pathGeometry; + } else { + root = document.createElement("Canvas"); + document.appendChild(root); + } + } catch (ParserConfigurationException ex) { + Logger.getLogger(XamlShapeExporter.class.getName()).log(Level.SEVERE, null, ex); + } + + } + + private void applyMatrix() { + if (geometryMatrix == null) { + return; + } + Element transform = document.createElement("PathGeometry.Transform"); + Element matrixTransform = document.createElement("MatrixTransform"); + matrixTransform.setAttribute("Matrix", geometryMatrix.getXamlTransformationString(SWF.unitDivisor / zoom, 1)); //zoom is applied to individual coordinates + transform.appendChild(matrixTransform); + pathGeometry.appendChild(transform); + } + + @Override + public void beginShape() { + } + + @Override + public void endShape() { + } + + @Override + public void beginFills() { + } + + @Override + public void endFills() { + } + + @Override + public void beginLines() { + inLines = true; + } + + @Override + public void endLines(boolean close) { + inLines = false; + if (close && !geometryOnly) { + pathFigure.setAttribute("IsClosed", "True"); + } + + finalizePath(); + } + + @Override + public void beginFill(RGB color) { + if (color == null && defaultColor != null) { + color = new RGB(defaultColor); + } + finalizePath(); + //path.setAttribute("Stroke", ""); + if (color != null) { + String colorHex; + if (color instanceof RGBA) { + colorHex = ((RGBA) color).toHexARGB(); + } else { + colorHex = color.toHexRGB(); + } + path.setAttribute("Fill", colorHex); + } + } + + @Override + public void beginGradientFill(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + finalizePath(); + Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) + ? document.createElement("LinearGradientBrush") + : document.createElement("RadialGradientBrush"); + populateGradientElement(gradient, type, gradientRecords, matrix, spreadMethod, interpolationMethod, focalPointRatio); + + //path.setAttribute("Stroke", ""); + Element pathFill = document.createElement("Path.Fill"); + pathFill.appendChild(gradient); + path.appendChild(pathFill); + } + + @Override + public void beginBitmapFill(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { + finalizePath(); + Element imageBrush = document.createElement("ImageBrush"); + if (matrix != null) { + Element transform = document.createElement("ImageBrush.Transform"); + Element matrixTransform = document.createElement("MatrixTransform"); + matrixTransform.setAttribute("Matrix", matrix.getXamlTransformationString(SWF.unitDivisor / zoom, SWF.unitDivisor)); + transform.appendChild(matrixTransform); + imageBrush.appendChild(transform); + } + imageBrush.setAttribute("ImageSource", imageFiles.get(bitmapId)); + ImageTag image = (ImageTag) swf.getCharacter(bitmapId); + imageBrush.setAttribute("Viewport", "0,0," + image.getImageDimension().width + "," + image.getImageDimension().height); + imageBrush.setAttribute("ViewportUnits", "Absolute"); + if (repeat) { + imageBrush.setAttribute("TileMode", "Tile"); + } + if (smooth) { + path.setAttribute("RenderOptions.BitmapScalingMode", "HighQuality"); + } else { + path.setAttribute("RenderOptions.BitmapScalingMode", "NearestNeighbor"); + } + Element pathFill = document.createElement("Path.Fill"); + pathFill.appendChild(imageBrush); + path.appendChild(pathFill); + } + + @Override + public void endFill() { + finalizePath(); + } + + @Override + public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit, boolean noClose) { + finalizePath(); + + //always display minimum stroke of 1 pixel, no matter how zoomed it is + if (thickness * displayZoom * thicknessScale < 1 * SWF.unitDivisor) { + //path.setAttribute("ffdec:has-small-stroke", "true"); + //path.setAttribute("ffdec:original-stroke-width", Double.toString(thickness * displayZoom / SWF.unitDivisor)); + thickness = 1 * SWF.unitDivisor / displayZoom / thicknessScale; + } + + thickness *= zoom / SWF.unitDivisor; + //path.setAttribute("Fill", ""); + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + path.setAttribute("Stroke", colorA.toHexARGB()); + } else if (color != null) { + path.setAttribute("Stroke", color.toHexRGB()); + } + path.setAttribute("StrokeThickness", formatDouble(thickness)); + + switch (startCaps) { + case LINESTYLE2.NO_CAP: + path.setAttribute("StrokeStartLineCap", "Flat"); + path.setAttribute("StrokeEndLineCap", "Flat"); + break; + case LINESTYLE2.SQUARE_CAP: + path.setAttribute("StrokeStartLineCap", "Square"); + path.setAttribute("StrokeEndLineCap", "Square"); + break; + default: + path.setAttribute("StrokeStartLineCap", "Round"); + path.setAttribute("StrokeEndLineCap", "Round"); + break; + } + switch (joints) { + case LINESTYLE2.BEVEL_JOIN: + path.setAttribute("StrokeLineJoin", "Bevel"); + break; + case LINESTYLE2.ROUND_JOIN: + path.setAttribute("StrokeLineJoin", "Round"); + break; + default: + path.setAttribute("StrokeLineJoin", "Miter"); + if (miterLimit >= 1) { + path.setAttribute("StrokeMiterLimit", formatDouble(miterLimit)); + } + break; + } + } + + @Override + public void lineGradientStyle(int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + Element gradient = (type == FILLSTYLE.LINEAR_GRADIENT) + ? document.createElement("LinearGradientBrush") + : document.createElement("RadialGradientBrush"); + populateGradientElement(gradient, type, gradientRecords, matrix, spreadMethod, interpolationMethod, focalPointRatio); + Element pathStroke = document.createElement("Path.Stroke"); + pathStroke.appendChild(gradient); + path.appendChild(pathStroke); + //path.setAttribute("Fill", ""); + } + + @Override + public void lineBitmapStyle(int bitmapId, Matrix matrix, boolean repeat, boolean smooth, ColorTransform colorTransform) { + Element imageBrush = document.createElement("ImageBrush"); + if (matrix != null) { + Element transform = document.createElement("ImageBrush.Transform"); + Element matrixTransform = document.createElement("MatrixTransform"); + matrixTransform.setAttribute("Matrix", matrix.getXamlTransformationString(SWF.unitDivisor / zoom, SWF.unitDivisor)); + transform.appendChild(matrixTransform); + imageBrush.appendChild(transform); + } + imageBrush.setAttribute("ImageSource", imageFiles.get(bitmapId)); + ImageTag image = (ImageTag) swf.getCharacter(bitmapId); + imageBrush.setAttribute("Viewport", "0,0," + image.getImageDimension().width + "," + image.getImageDimension().height); + imageBrush.setAttribute("ViewportUnits", "Absolute"); + if (repeat) { + imageBrush.setAttribute("TileMode", "Tile"); + } + if (smooth) { + imageBrush.setAttribute("RenderOptions.BitmapScalingMode", "HighQuality"); + } else { + path.setAttribute("RenderOptions.BitmapScalingMode", "NearestNeighbor"); + } + Element pathStroke = document.createElement("Path.Stroke"); + pathStroke.appendChild(imageBrush); + path.appendChild(pathStroke); + } + + @Override + public void moveTo(double x, double y) { + if (geometryOnly && inLines) { + return; + } + pathFigure = document.createElement("PathFigure"); + pathFigure.setAttribute("StartPoint", formatDouble(roundPixels20(x * zoom / SWF.unitDivisor)) + "," + formatDouble(roundPixels20(y * zoom / SWF.unitDivisor))); + pathGeometry.appendChild(pathFigure); + } + + @Override + public void lineTo(double x, double y) { + if (geometryOnly && inLines) { + return; + } + Element lineSegment = document.createElement("LineSegment"); + lineSegment.setAttribute("Point", formatDouble(roundPixels20(x * zoom / SWF.unitDivisor)) + "," + formatDouble(roundPixels20(y * zoom / SWF.unitDivisor))); + pathFigure.appendChild(lineSegment); + } + + @Override + public void curveTo(double controlX, double controlY, double anchorX, double anchorY) { + if (geometryOnly && inLines) { + return; + } + Element quadraticBezierSegment = document.createElement("QuadraticBezierSegment"); + quadraticBezierSegment.setAttribute("Point1", formatDouble(roundPixels20(controlX * zoom / SWF.unitDivisor)) + "," + formatDouble(roundPixels20(controlY * zoom / SWF.unitDivisor))); + quadraticBezierSegment.setAttribute("Point2", formatDouble(roundPixels20(anchorX * zoom / SWF.unitDivisor)) + "," + formatDouble(roundPixels20(anchorY * zoom / SWF.unitDivisor))); + pathFigure.appendChild(quadraticBezierSegment); + } + + /** + * Finalizes path. + */ + protected void finalizePath() { + if (!geometryOnly && path != null && pathData != null && pathGeometry.getChildNodes().getLength() > 0) { + root.appendChild(path); + path.appendChild(pathData); + if (!geometryOnly) { + pathData.appendChild(pathGeometry); + } + } + path = document.createElement("Path"); + pathData = document.createElement("Path.Data"); + if (!geometryOnly) { + pathGeometry = document.createElement("PathGeometry"); + pathGeometry.setAttribute("FillRule", windingRule == ShapeTag.WIND_NONZERO ? "Nonzero" : "EvenOdd"); + applyMatrix(); + } + pathFigure = document.createElement("PathFigure"); + } + + /** + * Rounds pixels to 20. + * + * @param pixels Pixels + * @return Rounded pixels + */ + protected double roundPixels20(double pixels) { + return Math.round(pixels * 100) / 100.0; + } + + /** + * Populates gradient element. + * + * @param gradient Gradient + * @param type Type + * @param gradientRecords Gradient records + * @param matrix Matrix + * @param spreadMethod Spread method + * @param interpolationMethod Interpolation method + * @param focalPointRatio Focal point ratio + */ + protected void populateGradientElement(Element gradient, int type, GRADRECORD[] gradientRecords, Matrix matrix, int spreadMethod, int interpolationMethod, float focalPointRatio) { + gradient.setAttribute("MappingMode", "Absolute"); + if (type == FILLSTYLE.LINEAR_GRADIENT) { + gradient.setAttribute("StartPoint", "-819.2,0"); + gradient.setAttribute("EndPoint", "819.2,0"); + } else { + gradient.setAttribute("RadiusX", "819.2"); + gradient.setAttribute("RadiusY", "819.2"); + gradient.setAttribute("Center", "0,0"); + if (focalPointRatio != 0) { + gradient.setAttribute("GradientOrigin", Double.toString(819.2 * focalPointRatio) + ",0"); + } + } + switch (spreadMethod) { + case GRADIENT.SPREAD_PAD_MODE: + gradient.setAttribute("SpreadMethod", "Pad"); + break; + case GRADIENT.SPREAD_REFLECT_MODE: + gradient.setAttribute("SpreadMethod", "Reflect"); + break; + case GRADIENT.SPREAD_REPEAT_MODE: + gradient.setAttribute("SpreadMethod", "Repeat"); + break; + } + if (interpolationMethod == GRADIENT.INTERPOLATION_LINEAR_RGB_MODE) { + gradient.setAttribute("ColorInterpolationMode", "ScRgbLinearInterpolation"); + } + //default is SRgbLinearInterpolation + if (matrix != null) { + String prefix; + if (type == FILLSTYLE.LINEAR_GRADIENT) { + prefix = "LinearGradientBrush"; + } else { + prefix = "RadialGradientBrush"; + } + Element gradientTransformElement = document.createElement(prefix + ".Transform"); + Element matrixTransformElement = document.createElement("MatrixTransform"); + matrixTransformElement.setAttribute("Matrix", matrix.getXamlTransformationString(SWF.unitDivisor / zoom, 1 / zoom)); + gradientTransformElement.appendChild(matrixTransformElement); + gradient.appendChild(gradientTransformElement); + } + for (int i = 0; i < gradientRecords.length; i++) { + GRADRECORD record = gradientRecords[i]; + Element gradientEntry = document.createElement("GradientStop"); + gradientEntry.setAttribute("Offset", Double.toString(record.ratio / 255.0)); + RGB color = record.color; + if (color instanceof RGBA) { + RGBA colorA = (RGBA) color; + gradientEntry.setAttribute("Color", colorA.toHexARGB()); + } else { + gradientEntry.setAttribute("Color", color.toHexRGB()); + } + gradient.appendChild(gradientEntry); + } + } + + public String getResultAsString() { + try { + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + + StringWriter writer = new StringWriter(); + transformer.transform(new DOMSource(document), new StreamResult(writer)); + + return writer.toString(); + } catch (TransformerConfigurationException ex) { + Logger.getLogger(XamlShapeExporter.class.getName()).log(Level.SEVERE, null, ex); + } catch (TransformerException ex) { + Logger.getLogger(XamlShapeExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return ""; + } + + protected String formatDouble(double val) { + String ret = "" + val; + if (ret.endsWith(".0")) { + ret = ret.substring(0, ret.length() - 2); + } + return ret; + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java index b4e9cff62..3b8a7d5e0 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/MainFrameMenu.java @@ -100,7 +100,6 @@ public abstract class MainFrameMenu implements MenuBuilder { private ConfigurationItemChangeListener configListenerGotoMainClassOnStartup; //private ConfigurationItemChangeListener configListenerAutoRenameIdentifiers; - private ConfigurationItemChangeListener configListenerAutoDeobfuscateIdentifiers; private ConfigurationItemChangeListener configListenerAutoOpenLoadedSWFs; @@ -113,7 +112,7 @@ public abstract class MainFrameMenu implements MenuBuilder { } protected final Map menuActions = new HashMap<>(); - + public MainFrameMenu(MainFrame mainFrame) { registerHotKeys(); this.mainFrame = mainFrame; @@ -308,7 +307,7 @@ public abstract class MainFrameMenu implements MenuBuilder { } Set listsToClose = new LinkedHashSet<>(); List binaryDataClosedSwfs = new ArrayList<>(); - + if (mainFrame.getPanel().getCurrentView() == MainPanel.VIEW_EASY) { Openable itemOpenable = mainFrame.getPanel().easyPanel.getSwf(); enumerateListsToClose(listsToClose, itemOpenable, binaryDataClosedSwfs); @@ -326,12 +325,12 @@ public abstract class MainFrameMenu implements MenuBuilder { enumerateListsToClose(listsToClose, openable, binaryDataClosedSwfs); } openable = null; - + for (OpenableList list : listsToClose) { Main.closeFile(list); } mainFrame.getPanel().refreshTree(); - + Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override @@ -510,7 +509,7 @@ public abstract class MainFrameMenu implements MenuBuilder { mainFrame.getPanel().exportIdea((SWF) openable); } - + protected void exportVsCodeActionPerformed(ActionEvent evt) { if (Main.isWorking()) { return; @@ -521,7 +520,7 @@ public abstract class MainFrameMenu implements MenuBuilder { mainFrame.getPanel().exportVsCode((SWF) openable); } - + protected void exportJavaActionPerformed(ActionEvent evt) { if (Main.isWorking()) { return; @@ -532,11 +531,22 @@ public abstract class MainFrameMenu implements MenuBuilder { List items = new ArrayList<>(); items.add(openable); - + mainFrame.getPanel().exportJavaSource(items); } - - /*protected void exportXamlActionPerformed(ActionEvent evt) { + + protected void exportSwcActionPerformed(ActionEvent evt) { + if (Main.isWorking()) { + return; + } + if (mainFrame.getPanel().checkEdited()) { + return; + } + + Main.saveSwc((SWF) openable); + } + + protected void exportXamlActionPerformed(ActionEvent evt) { if (Main.isWorking()) { return; } @@ -545,7 +555,7 @@ public abstract class MainFrameMenu implements MenuBuilder { } mainFrame.getPanel().exportXaml((SWF) openable); - }*/ + } protected void exportFlaActionPerformed(ActionEvent evt) { if (Main.isWorking()) { @@ -831,7 +841,7 @@ public abstract class MainFrameMenu implements MenuBuilder { ViewMessages.showMessageDialog(Main.getDefaultMessagesComponent(), translate("message.homepage").replace("%url%", homePageURL)); } } - + protected void wikiActionPerformed(ActionEvent evt) { if (Main.isWorking()) { return; @@ -900,7 +910,7 @@ public abstract class MainFrameMenu implements MenuBuilder { } Main.advancedSettings(); } - + protected void solEditorActionPerformed(ActionEvent evt) { Main.openSolEditor(); } @@ -941,12 +951,12 @@ public abstract class MainFrameMenu implements MenuBuilder { button.setSelected(Configuration.autoRenameIdentifiers.get()); } } - + protected void autoDeobfuscateIdentifiersActionPerformed(ActionEvent evt) { AbstractButton button = (AbstractButton) evt.getSource(); boolean selected = button.isSelected(); - Configuration.autoDeobfuscateIdentifiers.set(selected); + Configuration.autoDeobfuscateIdentifiers.set(selected); mainFrame.getPanel().autoDeobfuscateChanged(); } @@ -1157,7 +1167,11 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/file/export/exportMore/exportIdea", allSameSwf && openableSelected && isAs3 && !isWorking && canExportIdea); //setMenuEnabled("_/exportVsCode", swfSelected && !isWorking && canExportVsCode); setMenuEnabled("/file/export/exportMore/exportVsCode", allSameSwf && openableSelected && isAs3 && !isWorking && canExportVsCode); - + setMenuEnabled("/file/export/exportMore/exportJava", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportMore/exportSwc", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportMore/exportExe", swfSelected && !isWorking); + setMenuEnabled("/file/export/exportMore/exportXaml", swfSelected && !isWorking); + setMenuEnabled("_/exportSelected", openableSelected && !isWorking); setMenuEnabled("/file/export/exportSelected", openableSelected && !isWorking); //setMenuEnabled("/file/export/exportXml", swfSelected && !isWorking); @@ -1186,7 +1200,6 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/tools/abcExplorer", isAs3); setMenuEnabled("/tools/gotoDocumentClass", hasAbc); - setMenuEnabled("/tools/saveAsExe", swfSelected && !isWorking); /*setMenuEnabled("/tools/debugger/debuggerSwitch", hasAbc); setMenuChecked("/tools/debugger/debuggerSwitch", hasDebugger); @@ -1205,12 +1218,12 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/file/start/debugpcode", swfSelected && !isRunningOrDebugging); setMenuEnabled("/file/start/debuglisten", !isDebugRunning); - setMenuEnabled("/file/start/stop", isRunningOrDebugging); + setMenuEnabled("/file/start/stop", isRunningOrDebugging); setMenuEnabled("/debugging/debug/stop", isRunningOrDebugging); //same as previous - + setMenuEnabled("/debuggingListen/debug/stopListening", isListening); - setMenuEnabled("/debuggingListen/debug/disconnectSession", isSessionConnected); - + setMenuEnabled("/debuggingListen/debug/disconnectSession", isSessionConnected); + setPathVisible("/debugging", !isListening && isDebugRunning); setPathVisible("/debugging/debug", !isListening && isDebugRunning); //setMenuEnabled("/debugging/debug/pause", isDebugRunning); @@ -1220,16 +1233,14 @@ public abstract class MainFrameMenu implements MenuBuilder { setMenuEnabled("/debugging/debug/continue", isDebugPaused); //setMenuEnabled("/debugging/debug/stack", isDebugPaused); //setMenuEnabled("/debugging/debug/watch", isDebugPaused); - + setPathVisible("/debuggingListen", isListening); setPathVisible("/debuggingListen/debug", isListening); setMenuEnabled("/debuggingListen/debug/stepOver", isDebugPaused); setMenuEnabled("/debuggingListen/debug/stepInto", isDebugPaused); setMenuEnabled("/debuggingListen/debug/stepOut", isDebugPaused); setMenuEnabled("/debuggingListen/debug/continue", isDebugPaused); - - - + StringBuilder titleBuilder = new StringBuilder(); titleBuilder.append(ApplicationInfo.applicationVerName); @@ -1278,7 +1289,7 @@ public abstract class MainFrameMenu implements MenuBuilder { /*addMenuItem("_/exportFlashDevelop", translate("menu.file.export.flashDevelop"), "exportflashdevelop32", this::exportFlashDevelopActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportIdea", translate("menu.file.export.idea"), "exportidea32", this::exportIdeaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportVsCode", translate("menu.file.export.vsCode"), "exportvscode32", this::exportVsCodeActionPerformed, PRIORITY_TOP, null, true, null, false); - */ + */ addMenuItem("_/exportAll", translate("menu.file.export.all"), "export32", this::exportAllActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("_/exportSelected", translate("menu.file.export.selection"), "exportsel32", this::exportSelectedActionPerformed, PRIORITY_TOP, null, true, null, false); addSeparator("_"); @@ -1315,17 +1326,19 @@ public abstract class MainFrameMenu implements MenuBuilder { addMenuItem("/file/export/exportFla", translate("menu.file.export.fla"), "exportfla32", this::exportFlaActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/file/export/exportAll", translate("menu.file.export.all"), "exportall16", this::exportAllActionPerformed, PRIORITY_MEDIUM, null, true, new HotKey("CTRL+SHIFT+E"), false); addMenuItem("/file/export/exportSelected", translate("menu.file.export.selection"), "exportsel16", this::exportSelectedActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - + addMenuItem("/file/export/exportMore", translate("menu.export.file"), "export16", null, PRIORITY_MEDIUM, null, false, null, false); - addMenuItem("/file/export/exportMore/exportXml", "XML", "exportxml32", this::exportXmlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/export/exportMore/exportFlashDevelop", "Flash Develop", "exportflashdevelop32", this::exportFlashDevelopActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/export/exportMore/exportIdea", "IntelliJ Idea", "exportidea32", this::exportIdeaActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/export/exportMore/exportVsCode", "Visual Studio Code", "exportvscode32", this::exportVsCodeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - addMenuItem("/file/export/exportMore/exportJava", "Java", "exportjava32", this::exportJavaActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - //addMenuItem("/file/export/exportMore/exportXaml", "XAML", "exportxml32", this::exportXamlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); - + addMenuItem("/file/export/exportMore/exportXml", "XML", "exportxml16", this::exportXmlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportFlashDevelop", "Flash Develop", "exportflashdevelop16", this::exportFlashDevelopActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportIdea", "IntelliJ Idea", "exportidea16", this::exportIdeaActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportVsCode", "Visual Studio Code", "exportvscode16", this::exportVsCodeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportJava", "Java", "exportjava16", this::exportJavaActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportSwc", "SWC", "bundleswc16", this::exportSwcActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportExe", "EXE", "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + addMenuItem("/file/export/exportMore/exportXaml", "XAML", "exportxml16", this::exportXamlActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + finishMenu("/file/export/exportMore"); - + finishMenu("/file/export"); addMenuItem("/import", translate("menu.import"), null, null, 0, null, false, null, false); @@ -1359,7 +1372,7 @@ public abstract class MainFrameMenu implements MenuBuilder { addToggleMenuItem("/file/view/viewTagList", translate("menu.file.view.tagList"), "view", "taglist16", this::viewTagListActionPerformed, PRIORITY_MEDIUM, null); addToggleMenuItem("/file/view/viewHex", translate("menu.file.view.hex"), "view", "viewhex16", this::viewHexActionPerformed, PRIORITY_MEDIUM, null); addToggleMenuItem("/file/view/easy", translate("menu.file.view.easy"), null, "easy32", this::easyActionPerformed, PRIORITY_TOP, null); - + finishMenu("/file/view"); addSeparator("/file"); @@ -1399,8 +1412,7 @@ public abstract class MainFrameMenu implements MenuBuilder { //addMenuItem("/debugging/debug/watch", translate("menu.debugging.debug.watch"), "watch32", this::watchActionPerformed, PRIORITY_MEDIUM, null, true, null, false); finishMenu("/debugging/debug"); finishMenu("/debugging"); - - + addMenuItem("/debuggingListen", translate("menu.debugging"), null, null, 0, null, false, null, true); addMenuItem("/debuggingListen/debug", translate("menu.debugging.debug"), null, null, 0, null, false, null, false); addMenuItem("/debuggingListen/debug/stopListening", translate("menu.debugging.debug.stopListening"), "stop32", this::stopActionPerformed, PRIORITY_TOP, null, true, null, false); @@ -1423,14 +1435,14 @@ public abstract class MainFrameMenu implements MenuBuilder { } addMenuItem("/tools/replace", translate("menu.tools.replace"), "replace32", this::replaceActionPerformed, PRIORITY_TOP, null, true, null, false); - + addMenuItem("/tools/abcExplorer", translate("menu.tools.abcexplorer"), "abcexplorer32", this::abcExplorerActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/tools/gotoDocumentClass", translate("menu.tools.gotoDocumentClass"), "gotomainclass32", this::gotoDocumentClassActionPerformed, PRIORITY_TOP, null, true, null, false); addMenuItem("/tools/solEditor", translate("menu.tools.solEditor"), "soleditor32", this::solEditorActionPerformed, PRIORITY_TOP, null, true, null, false); if (Platform.isWindows()) { addMenuItem("/tools/searchMemory", translate("menu.tools.searchMemory"), "loadmemory16", this::searchMemoryActionPerformed, PRIORITY_MEDIUM, null, true, null, false); } - addMenuItem("/tools/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); + //addMenuItem("/tools/saveAsExe", translate("menu.file.saveasexe"), "saveasexe16", this::saveAsExeActionPerformed, PRIORITY_MEDIUM, null, true, null, false); //addMenuItem("/tools/searchCache", translate("menu.tools.searchCache"), "loadcache16", this::searchCacheActionPerformed, PRIORITY_MEDIUM, null, true, null); addMenuItem("/tools/deobfuscation", translate("menu.tools.deobfuscation"), "deobfuscate16", null, 0, null, false, null, false); @@ -1526,12 +1538,11 @@ public abstract class MainFrameMenu implements MenuBuilder { Configuration.autoRenameIdentifiers.addListener(configListenerAutoRenameIdentifiers = (Boolean newValue) -> { setMenuChecked("/settings/autoRenameIdentifiers", newValue); });*/ - setMenuChecked("/settings/autoDeobfuscateIdentifiers", Configuration.autoDeobfuscateIdentifiers.get()); Configuration.autoDeobfuscateIdentifiers.addListener(configListenerAutoDeobfuscateIdentifiers = (Boolean newValue) -> { setMenuChecked("/settings/autoDeobfuscateIdentifiers", newValue); }); - + setMenuChecked("/settings/autoOpenLoadedSWFs", Configuration.autoOpenLoadedSWFs.get()); Configuration.autoOpenLoadedSWFs.addListener(configListenerAutoOpenLoadedSWFs = (Boolean newValue) -> { setMenuChecked("/settings/autoOpenLoadedSWFs", newValue); @@ -1636,7 +1647,7 @@ public abstract class MainFrameMenu implements MenuBuilder { break; } } - + private void alwaysOnTopActionPerformed(ActionEvent evt) { mainFrame.getWindow().setAlwaysOnTop(!mainFrame.getWindow().isAlwaysOnTop()); setMenuChecked("/file/view/alwaysOnTop", mainFrame.getWindow().isAlwaysOnTop()); @@ -1650,7 +1661,7 @@ public abstract class MainFrameMenu implements MenuBuilder { Configuration.dumpView.set(false); mainFrame.getPanel().showView(MainPanel.VIEW_RESOURCES); setGroupSelection("view", "/file/view/viewResources"); - setMenuChecked("/file/view/easy", false); + setMenuChecked("/file/view/easy", false); } private void viewHexActionPerformed(ActionEvent evt) { @@ -1678,7 +1689,7 @@ public abstract class MainFrameMenu implements MenuBuilder { MainPanel mainPanel = mainFrame.getPanel(); mainPanel.showView(MainPanel.VIEW_TAGLIST); setGroupSelection("view", "/file/view/viewTagList"); - setMenuChecked("/file/view/easy", false); + setMenuChecked("/file/view/easy", false); } private void debuggerSwitchActionPerformed(ActionEvent evt) { @@ -1799,13 +1810,13 @@ public abstract class MainFrameMenu implements MenuBuilder { Main.runDebug((SWF) openable, false); return true; } - + public void debugListenActionPerformed(ActionEvent evt) { if (ViewMessages.showConfirmDialog(mainFrame.getPanel(), "
" + translate("message.info.debugListen").replace("\n", "
") + "
", translate("message.info"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.INFORMATION_MESSAGE, Configuration.showDebugListenInfo, JOptionPane.OK_OPTION) != JOptionPane.OK_OPTION) { return; } - - Main.startDebugListening(); + + Main.startDebugListening(); } public boolean debugPCodeActionPerformed(ActionEvent evt) { @@ -1817,7 +1828,7 @@ public abstract class MainFrameMenu implements MenuBuilder { Main.stopRun(); return true; } - + public boolean disconnectSessionActionPerformed(ActionEvent evt) { Main.disconnectSession(); return true; diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index e99fb818a..58133f39c 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -61,6 +61,7 @@ import com.jpexs.decompiler.flash.exporters.ShapeExporter; import com.jpexs.decompiler.flash.exporters.SoundExporter; import com.jpexs.decompiler.flash.exporters.SymbolClassExporter; import com.jpexs.decompiler.flash.exporters.TextExporter; +import com.jpexs.decompiler.flash.exporters.XamlExporter; import com.jpexs.decompiler.flash.exporters.modes.BinaryDataExportMode; import com.jpexs.decompiler.flash.exporters.modes.ButtonExportMode; import com.jpexs.decompiler.flash.exporters.modes.Font4ExportMode; @@ -449,9 +450,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private Map breakpointsListDialogs = new WeakHashMap<>(); private boolean loadingScrollPosEnabled = true; - + private static final DataFlavor TREE_FILE_FLAVOR = new DataFlavor(TreeFileFlavor.class, "TreeFile"); - + private boolean editingStatusSet = false; public synchronized void setLoadingScrollPosEnabled(boolean loadingScrollPosEnabled) { @@ -844,11 +845,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se || (actionPanel != null && actionPanel.isEditing()) || previewPanel.isEditing() || headerPanel.isEditing(); } - + private class TreeFileFlavor { - + } - + private class MyTreeSelectionModel extends DefaultTreeSelectionModel { @Override @@ -1167,19 +1168,19 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se tagListTree = new TagListTree(null, this); tagListTree.addTreeSelectionListener(this); - tagListTree.setSelectionModel(new MyTreeSelectionModel()); - + tagListTree.setSelectionModel(new MyTreeSelectionModel()); + DragSource dragSource = DragSource.getDefaultDragSource(); - dragSource.createDefaultDragGestureRecognizer(tagTree, DnDConstants.ACTION_COPY_OR_MOVE, new DragGestureListener() { + dragSource.createDefaultDragGestureRecognizer(tagTree, DnDConstants.ACTION_COPY_OR_MOVE, new DragGestureListener() { @Override public void dragGestureRecognized(DragGestureEvent dge) { if (!Configuration.allowDragAndDropFromResourcesTree.get()) { return; } dge.startDrag(DragSource.DefaultCopyDrop, new Transferable() { - + private List cachedFiles = null; - + @Override public DataFlavor[] getTransferDataFlavors() { return new DataFlavor[]{TREE_FILE_FLAVOR, DataFlavor.javaFileListFlavor}; @@ -1188,8 +1189,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se @Override public boolean isDataFlavorSupported(DataFlavor flavor) { return flavor.equals(TREE_FILE_FLAVOR) || flavor.equals(DataFlavor.javaFileListFlavor); - } - + } + @Override public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException { if (flavor.equals(DataFlavor.javaFileListFlavor)) { @@ -1197,7 +1198,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se Main.stopWork(); return cachedFiles; } - + List files; String tempDir = System.getProperty("java.io.tmpdir"); if (!tempDir.endsWith(File.separator)) { @@ -1232,13 +1233,14 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } new File(tempDir).deleteOnExit(); cachedFiles = files; - + return cachedFiles; } return null; } - }, new DragSourceAdapter() {}); + }, new DragSourceAdapter() { + }); } }); @@ -1430,33 +1432,33 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se @Override public void dragEnter(DropTargetDragEvent dtde) { if (dtde.isDataFlavorSupported(TREE_FILE_FLAVOR) - || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { dtde.rejectDrag(); } else { dtde.acceptDrag(DnDConstants.ACTION_COPY); } - } + } @Override public void dragOver(DropTargetDragEvent dtde) { - if (dtde.isDataFlavorSupported(TREE_FILE_FLAVOR) - || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + if (dtde.isDataFlavorSupported(TREE_FILE_FLAVOR) + || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { dtde.rejectDrag(); } else { dtde.acceptDrag(DnDConstants.ACTION_COPY); } - } + } @Override - public void drop(DropTargetDropEvent dtde) { + public void drop(DropTargetDropEvent dtde) { if (dtde.isDataFlavorSupported(TREE_FILE_FLAVOR) - || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { + || !dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { dtde.rejectDrop(); return; } try { dtde.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); - @SuppressWarnings("unchecked") + @SuppressWarnings("unchecked") List droppedFiles = (List) dtde.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); if (droppedFiles != null && !droppedFiles.isEmpty()) { OpenableSourceInfo[] sourceInfos = new OpenableSourceInfo[droppedFiles.size()]; @@ -1470,9 +1472,9 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se dtde.dropComplete(false); //ignored } - } - }); - + } + }); + calculateMissingNeededThread = new CalculateMissingNeededThread(); calculateMissingNeededThread.start(); pinsPanel.load(); @@ -1655,7 +1657,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (!abcFound) { getABCPanel().setAbc(abcList.get(0).getABC()); } - } + } } mainMenu.updateComponents(openable); @@ -1948,7 +1950,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se return true; } - public void updateClassesList() { String selectionPath = getCurrentTree().getSelectionPathString(); List nodes = getASTreeNodes(tagTree); @@ -2123,7 +2124,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se List ret = new ArrayList<>(); List sel = getSelection(null, selection); - + Set usedOpenables = new HashSet<>(); Set usedOpenableLists = new HashSet<>(); @@ -2138,7 +2139,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se usedOpenableLists.add(list); } } - + Map usedSwfsIds = new HashMap<>(); for (Openable openable : usedOpenables) { @@ -2280,10 +2281,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se selFile2 = selFile; } - int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; - - + EventListener evl = swf.getExportEventListener(); if (export.isOptionEnabled(ImageExportMode.class)) { @@ -2403,7 +2402,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; - + EventListener evl = swf.getExportEventListener(); if (export.isOptionEnabled(ImageExportMode.class)) { @@ -2499,7 +2498,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se EventListener evl = swf.getExportEventListener(); int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; - + if (export.isOptionEnabled(ImageExportMode.class)) { for (ImageExportMode exportMode : ImageExportMode.values()) { new ImageExporter().exportImages(handler, Path.combine(selFile, ImageExportSettings.EXPORT_FOLDER_NAME, exportMode.name()), swf.getTags(), @@ -2878,7 +2877,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se if (asms.containsKey(rawScriptName)) { oldItem = null; getCurrentTree().setSelectionPath(null); - setTagTreeSelectedNode(getCurrentTree(), asms.get(rawScriptName)); + setTagTreeSelectedNode(getCurrentTree(), asms.get(rawScriptName)); return true; } /*if (actionPanel != null && asms.containsKey(rawScriptName)) { @@ -2987,7 +2986,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public void searchInActionScriptOrText(Boolean searchInText, Openable openable, boolean useSelection) { View.checkAccess(); - + if (checkEdited()) { return; } @@ -3435,7 +3434,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se updateClassesList(); reload(true); } - + public void skipDetectionOfUninitializedClassFieldsChanged() { clearAllScriptCache(); updateClassesList(); @@ -4562,9 +4561,57 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } } } - + public void exportXaml(SWF swf) { - //TODO + JFileChooser fc = View.getFileChooserWithIcon("exportxml"); + fc.setDialogTitle(AppStrings.translate("menu.file.export.xaml")); + String selDir = Configuration.lastExportDir.get(); + fc.setCurrentDirectory(new File(selDir)); + if (!selDir.endsWith(File.separator)) { + selDir += File.separator; + } + String swfFileName = swf.getTitleOrShortFileName(); + String slnxFileName = swfFileName + ".slnx"; + if (swfFileName.toLowerCase(Locale.ENGLISH).endsWith(".swf") + || swfFileName.toLowerCase(Locale.ENGLISH).endsWith(".gfx")) { + slnxFileName = swfFileName.substring(0, swfFileName.lastIndexOf(".")) + ".slnx"; + } + fc.setSelectedFile(new File(selDir + slnxFileName)); + fc.setFileFilter(new FileFilter() { + @Override + public boolean accept(File f) { + return f.isDirectory() || f.getName().toLowerCase(Locale.ENGLISH).endsWith(".slnx"); + } + + @Override + public String getDescription() { + return AppStrings.translate("filter.slnx"); + } + }); + if (fc.showSaveDialog(Main.getDefaultMessagesComponent()) != JFileChooser.APPROVE_OPTION) { + return; + } + String selFile = Helper.fixDialogFile(fc.getSelectedFile()).getAbsolutePath(); + if (selFile.toLowerCase(Locale.ENGLISH).endsWith(".slnx")) { + selFile = selFile.substring(0, selFile.lastIndexOf(".")); + } + File parentDir = new File(selFile).getParentFile(); + String projectName = new File(selFile).getName(); + + AbortRetryIgnoreHandler handler = new GuiAbortRetryIgnoreHandler(); + + Main.startWork(translate("work.exporting.xaml") + "...", null, true); + try { + new RetryTask(() -> { + XamlExporter xamlExporter = new XamlExporter(); + xamlExporter.exportSwf(swf, parentDir, projectName, 1); + }, handler).run(); + } catch (IOException ex) { + logger.log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + //ignore + } + Main.stopWork(); } public void exportJavaSource(List items) { @@ -4953,7 +5000,8 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } List tagsToRemove = new ArrayList<>(); - loopTags: for (Tag tag : swf.getTags()) { + loopTags: + for (Tag tag : swf.getTags()) { if (tag instanceof CharacterTag) { CharacterTag characterTag = (CharacterTag) tag; for (String cls : characterTag.getClassNames()) { @@ -5091,10 +5139,10 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se }; } - public boolean saveText(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor, UndoManager undoManager) { - if (undoManager != null) { + public boolean saveText(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor, UndoManager undoManager) { + if (undoManager != null) { String prevText = textTag.getFormattedText(false).text; - if (saveTextInternal(textTag, formattedText, texts, editor)) { + if (saveTextInternal(textTag, formattedText, texts, editor)) { undoManager.doOperation(new DoableOperation() { boolean first = true; @@ -5110,7 +5158,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se @Override public void undoOperation() { - saveTextInternal(textTag, prevText, texts, editor); + saveTextInternal(textTag, prevText, texts, editor); } @Override @@ -5122,11 +5170,11 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } return false; } - return saveTextInternal(textTag, formattedText, texts, editor); + return saveTextInternal(textTag, formattedText, texts, editor); } - private boolean saveTextInternal(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor) { - + private boolean saveTextInternal(TextTag textTag, String formattedText, String[] texts, LineMarkedEditorPane editor) { + try { if (textTag.setFormattedText(getMissingCharacterHandler(), formattedText, texts)) { return true; @@ -5626,8 +5674,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se AbortRetryIgnoreHandler errorHandler = new GuiAbortRetryIgnoreHandler(); int aaScale = Configuration.reduceAntialiasConflationByScalingForExport.get() ? Configuration.reduceAntialiasConflationByScalingValueForExport.get() : 1; - - + FrameExporter frameExporter = new FrameExporter(); FrameExportSettings fes = new FrameExportSettings(mode, dialog.getZoom(), dialog.isTransparentFrameBackgroundEnabled(), aaScale); String subFolder = FrameExportSettings.EXPORT_FOLDER_NAME; @@ -5782,15 +5829,14 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se private void valueChanged(Object source, TreePath selectedPath) { TreeItem treeItem = selectedPath == null ? null : (TreeItem) selectedPath.getLastPathComponent(); - - if (treeItem == null) { + + if (treeItem == null) { updateUi(null); reload(false); return; } - - //Main.updateSession(); + //Main.updateSession(); if (!(treeItem instanceof OpenableList)) { Openable openable = treeItem.getOpenable(); if (openables.isEmpty()) { @@ -6354,12 +6400,12 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se @Override public Dimension getFilterDimensions() { return new Dimension(0, 0); - } + } @Override public RECT getRectWithFilters() { return getRect(); - } + } }; previewPanel.showImagePanel(tim, origSwf, 0, true, true, !Configuration.animateSubsprites.get(), false, !Configuration.playFrameSounds.get(), true, false, true, true, true); } else if (treeItem instanceof DefineFont4Tag) { @@ -6523,7 +6569,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se Set needed; Set neededClasses; - + if (neededCharacters.containsKey(treeItem)) { needed = neededCharacters.get(treeItem); neededClasses = neededCharacterClasses.get(treeItem); @@ -6534,8 +6580,6 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se neededCharacters.put(treeItem, needed); neededCharacterClasses.put(treeItem, neededClasses); } - - if (needed.size() > 0) { tagInfo.addInfo("general", "neededCharacters", Helper.joinStrings(needed, ", ")); @@ -6905,13 +6949,13 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se Set resultNeeded = new LinkedHashSet<>(); Set resultNeededClasses = new LinkedHashSet<>(); t.getMissingNeededCharacters(needed, neededClasses, resultNeeded, resultNeededClasses); - - missingNeededCharacters.put(t, resultNeeded); + + missingNeededCharacters.put(t, resultNeeded); missingNeededCharacterClasses.put(t, resultNeededClasses); if (characterId != -1 && tim.getTimeline().swf.getCharacter(characterId) == null) { missingNeededCharacters.get(t).add(characterId); } - + //FIXME: missingNeededCharacterClasses ?? } /*if (t instanceof DefineSpriteTag) { @@ -6936,7 +6980,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se } this.neededCharacters = neededCharacters; this.missingNeededCharacters = missingNeededCharacters; - + tagTree.setMissingNeededCharacters(missingNeededCharacters); tagListTree.setMissingNeededCharacters(missingNeededCharacters); } @@ -7071,7 +7115,7 @@ public final class MainPanel extends JPanel implements TreeSelectionListener, Se public EasyPanel getEasyPanel() { return easyPanel; } - + public void showDebugStackFrame() { showDetail(DETAILCARDDEBUGSTACKFRAME); } diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/exportall16.png b/src/com/jpexs/decompiler/flash/gui/graphics/exportall16.png index 5dd60266c2773aa6c308fe77418ef407e946234d..9102341337aa7d12d9ff413cf90dcdaa46ade81d 100644 GIT binary patch delta 995 zcmV<9104MRB;_QKBmuyYB_Dsm4Z|=9{4+&Iz<>!J$7xcj8+82QK&rUyNj?s+&KTHV z&j;KFs0c`1Oq(h?q)DUh!Z6SeE+_|Qf~T13@M(~j*ds@3n)lCo!O5v~SM_3K_iU_l zqe=SPmk~waG11Ul8nrZHibhF;9^XQ3wp_ok?l{BZz#y@C?4Nw`?QJU6-(jMi3!^i$ z#Q;2x0(TeeOw1JJ?s+{})65t0Z%-%S4c9GM4?GxMwXyKr10*ytV=y*1IX5k1H#Rga zG%#jiEn;RhFfBJ@W-((qH#cK5VPcc;1Ro?gHDNO}VmV6iG5A zDyd~s3yUDIGzb?V1?_C- zzVm*adrym;!I_*6Je%j<`|#s%pBR5<5a84q)34#(dweMp0ycL`091@B6yA zxVQie1M&8N@hFOn<2dTNZd0{dO)Hg(?(OZVh-j%)(xs)Pe?nrwxQM9dd5R9G=Xp9a zGt*SLTvid$dcCfW<7hUUZG;dz)GZNNH^wxPthJ~rB0>-Zq*AFCE&`cp1pd28yqk{{igMPdw`e%04pmiE&B*e#9+@Tk5?zjr9X8JXb?PIy^E?4S|JqS zeCjfz{SzGcF4LJSbPXgp6jBmu*bB_DrV4#OY}`_CzMgpi~(9EYw<+6{L6nUYRrn7N{`8^8T zTqqX){$(WN(mqkwdl;24QW=F526cRkbMf{1g{|=nABQ@b&tw0@E4D4SFL*~qnkHqi zs*eG*pG;aI6fBCN&{UuexeA@b3=8?UrxWxB*TGp4dai_=vF+RgBw;Z!GGQ<`Vl8Aa zVm2)_W@a%hVK!!EEipJ{V>2-`WjQc8Ws~g$A0#qkIAdfrIXEpdF=1paG%#W_En+b; zWi4i8H8f>mHD)w5VK9?01t=pqIA%F8F*z+TWi?|hG&V6bEjcqVG%YtdF*Y`2Vl_84 zVrG+j1z`#?GBGwWI50ObIFsfDa|bawIy5jkF_T~hCkHV(Iy5jkF_Vr4izGH=W@9xq zW??O3IAk&{G+{7fEn+xjF)cDSHZd_~VPj-DHD;4N2TunvIXW~jIx(}82Wbd@*G&H+ z00006VoOIv00000008+zyMF)x010qNS#tmY3ljhU3ljkVnw%H_000McNliru>H`-N zC=jkKPGSH60x3yEK~y-)eUr^gRACf{pZQRWBuHhH)F`QiMNnKDgD?oGb?w@es}@Cz zplyFZL5l)~TnynR@Godr7}JM;2!=u{7ok%QcSe~H@67#p&%LKbql}K@fwOpd56?Nj z-}{~z|6>T~2NE0|1z-b+4qjNE=ar2yp@`^V>3N<`Pfx!E&I0j+fV43tbR0)(wc1`* zDitl2N^0A-iiqa(d7YV=X@SImw1}vkvDEi{bzN6o*Ht@XX}Mh1LZP64BBJ$rT^+~K z+1c4$$bNwD`)X$_)s;7DXDqGP>sqZ=wOA~+NCMJ8EY<*A6OqO3x$zd)y6=BML@>q> zMG+!GDwQIcOpXIf`^@N_S_B|9_5gsMhd)Rp5+svJIy*b*=;)xUtE;8SJ`bA5v@@0( zW7P9JefHs|K6-yuw@bx;eG(3xp!pn&#fZn__@QB_ZO7+ZWlMoD*$mLeivZw?&)oOd1W|w|0V91Exju4-rS&YUo2zVn=CSjQpS$&AzVFvW zr0p_UFHh*y+@z*o4eGByD>cBPspD{Co#X8!oqNGTsEK36kKsLcnOtMFbN>J?f14rK TE+aw!0000^OHa0LaFkvw@Ej2J< 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 bSN8t^GQY7F3O{|Y00000NkvXXu0mjfE0@TC diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index b20f4d660..0bbac8350 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1141,4 +1141,8 @@ menu.debugging.debug.disconnectSession = Disconnect session sort.alphabetically = Sort alphabetically #after 25.1.3 -menu.export.file = Export file as... \ No newline at end of file +menu.export.file = Export file to... +filter.slnx = .NET solution XML (*.slnx) +menu.file.export.xaml = Export XAML +work.exporting.xaml = Exporting XAML +contextmenu.exportXaml = Export XAML \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties index d40d6e98d..a9d7c3717 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_cs.properties @@ -1141,4 +1141,8 @@ menu.debugging.debug.disconnectSession = Odpojit sezen\u00ed sort.alphabetically = Se\u0159adit abecedn\u011b #after 25.1.3 -menu.export.file = Exportovat soubor jako... \ No newline at end of file +menu.export.file = Exportovat soubor do... +filter.slnx = .NET solution XML (*.slnx) +menu.file.export.xaml = Exportovat XAML +work.exporting.xaml = Exportov\u00e1n\u00ed XAML +contextmenu.exportXaml = Exportovat XAML \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties index 127ca5c5e..c50952046 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_de.properties @@ -999,4 +999,11 @@ variables.header.watches = \u00dcberwachungen menu.debugging.debug.stopListening = Listening beenden menu.debugging.debug.disconnectSession = Sitzung trennen -sort.alphabetically = Alphabetisch sortieren \ No newline at end of file +sort.alphabetically = Alphabetisch sortieren + +#after 25.1.3 +menu.export.file = Datei exportieren nach... +filter.slnx = .NET solution XML (*.slnx) +menu.file.export.xaml = Exportiere als XAML +work.exporting.xaml = XAML exportieren +contextmenu.exportXaml = Exportiere als XAML \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties index ec9a8f00c..e4dd3a744 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame_sk.properties @@ -1138,4 +1138,11 @@ variables.header.watches = Sledovania menu.debugging.debug.stopListening = Zastavi\u0165 po\u010d\u00favanie menu.debugging.debug.disconnectSession = Odpoji\u0165 sedenie -sort.alphabetically = Zoradi\u0165 abecedne \ No newline at end of file +sort.alphabetically = Zoradi\u0165 abecedne + +#after 25.1.3 +menu.export.file = Exportova\u0165 s\u00fabor do... +filter.slnx = .NET solution XML (*.slnx) +menu.file.export.xaml = Exportova\u0165 XAML +work.exporting.xaml = Exportovanie XAML +contextmenu.exportXaml = Exportova\u0165 XAML \ No newline at end of file diff --git a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java index e06ea52b1..0d1c68815 100644 --- a/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java +++ b/src/com/jpexs/decompiler/flash/gui/tagtree/TagTreeContextMenu.java @@ -255,6 +255,8 @@ public class TagTreeContextMenu extends JPopupMenu { private JMenuItem saveExeMenuItem; + private JMenuItem exportXamlMenuItem; + private JMenuItem importSwfXmlMenuItem; private JMenuItem importScriptsMenuItem; @@ -591,6 +593,16 @@ public class TagTreeContextMenu extends JPopupMenu { saveExeMenuItem.setIcon(View.getIcon("saveasexe16")); add(saveExeMenuItem); + exportXamlMenuItem = new JMenuItem(mainPanel.translate("contextmenu.exportXaml")); + exportXamlMenuItem.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mainPanel.exportXaml((SWF) getCurrentItem().getOpenable()); + } + }); + exportXamlMenuItem.setIcon(View.getIcon("exportxml16")); + add(exportXamlMenuItem); + addSeparator(); rawEditMenuItem = new JMenuItem(mainPanel.translate("contextmenu.rawEdit")); @@ -1423,6 +1435,7 @@ public class TagTreeContextMenu extends JPopupMenu { exportSwfXmlMenuItem.setVisible(allSelectedIsSwf); saveSwcMenuItem.setVisible(allSelectedIsSwf && items.size() == 1); saveExeMenuItem.setVisible(allSelectedIsSwf && items.size() == 1); + exportXamlMenuItem.setVisible(allSelectedIsSwf && items.size() == 1); importImagesMenuItem.setVisible(false); importShapesMenuItem.setVisible(false); @@ -3169,7 +3182,7 @@ public class TagTreeContextMenu extends JPopupMenu { protected void onStart() { Main.startWork(AppStrings.translate("work.prepareDebug"), this, true); } - + @Override protected Object doInBackground() throws Exception { List tempFiles = new ArrayList<>(); @@ -3198,7 +3211,7 @@ public class TagTreeContextMenu extends JPopupMenu { public void workerCancelled() { Main.stopWork(); } - }; + }; prepareDebugWorker.execute(); }