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 000000000..2d5f3adf8 Binary files /dev/null and b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/XamlExporterStub.zip differ diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java index f3fc4d3fb..81d8e31ab 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/Matrix.java @@ -304,6 +304,17 @@ public final class Matrix implements Cloneable { return "matrix(" + scaleX + ", " + rotateSkew0 + ", " + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"; } + + public String getXamlTransformationString(double translateDivisor, double unitDivisor) { + double translateX = roundPixels400(this.translateX / translateDivisor); + double translateY = roundPixels400(this.translateY / translateDivisor); + double rotateSkew0 = roundPixels400(this.rotateSkew0 / unitDivisor); + double rotateSkew1 = roundPixels400(this.rotateSkew1 / unitDivisor); + double scaleX = roundPixels400(this.scaleX / unitDivisor); + double scaleY = roundPixels400(this.scaleY / unitDivisor); + return "" + scaleX + " " + rotateSkew0 + " " + + rotateSkew1 + " " + scaleY + " " + translateX + " " + translateY; + } public static String[] parseSvgNumberList(String params) { while (params.contains(" ")) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/XamlShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/XamlShapeExporter.java new file mode 100644 index 000000000..a4ff16c0b --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/XamlShapeExporter.java @@ -0,0 +1,551 @@ +/* + * 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.shape; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.GRADIENT; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPE; +import java.awt.Color; +import java.io.StringWriter; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +/** + * + * @author JPEXS + */ +public class XamlShapeExporter extends ShapeExporterBase { + + /** + * Draw command L + */ + protected static final String DRAW_COMMAND_L = "L"; + + /** + * Draw command Q + */ + protected static final String DRAW_COMMAND_Q = "Q"; + + /** + * Path data + */ + protected Element pathData; + + /** + * Path geometry + */ + protected Element pathGeometry; + + /** + * Path figure + */ + protected Element pathFigure; + + /** + * Zoom + */ + protected double zoom; + + /** + * Path + */ + protected Element path; + + /** + * Id + */ + protected int id; + + /** + * Last pattern id + */ + protected int lastPatternId; + + /** + * Default color + */ + private final Color defaultColor; + + /** + * SWF + */ + private final SWF swf; + + /** + * Display zoom + */ + private final double displayZoom; + + /** + * Thickness scale + */ + private final double thicknessScale; + + /** + * XML document + */ + private Document document; + + /** + * XML root element + */ + private Element root; + + /** + * Image files + */ + private final Map 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 5dd60266c..910234133 100644 Binary files a/src/com/jpexs/decompiler/flash/gui/graphics/exportall16.png and b/src/com/jpexs/decompiler/flash/gui/graphics/exportall16.png differ diff --git a/src/com/jpexs/decompiler/flash/gui/graphics/exportall32.png b/src/com/jpexs/decompiler/flash/gui/graphics/exportall32.png index 141f86a94..67475d721 100644 Binary files a/src/com/jpexs/decompiler/flash/gui/graphics/exportall32.png and b/src/com/jpexs/decompiler/flash/gui/graphics/exportall32.png differ 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(); }