From 821fa68a6b3351672a635a9f89b777bdd20a4bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sun, 1 Jun 2025 23:41:14 +0200 Subject: [PATCH] Fixed: #2464 SVG export - minimum stroke width of 1 px --- CHANGELOG.md | 2 + .../flash/exporters/FrameExporter.java | 2 +- .../flash/exporters/MorphShapeExporter.java | 54 ++++++++++--------- .../flash/exporters/ShapeExporter.java | 22 ++++---- .../flash/exporters/TextExporter.java | 4 +- .../exporters/commonshape/SVGExporter.java | 42 +++++++++++++-- .../flash/exporters/shape/BitmapExporter.java | 8 +-- .../exporters/shape/SVGShapeExporter.java | 34 ++++++++++-- .../flash/tags/DefineEditTextTag.java | 6 +-- .../flash/tags/DefineSpriteTag.java | 4 +- .../flash/tags/DefineVideoStreamTag.java | 2 +- .../decompiler/flash/tags/base/ButtonTag.java | 4 +- .../flash/tags/base/DrawableTag.java | 4 +- .../decompiler/flash/tags/base/FontTag.java | 2 +- .../decompiler/flash/tags/base/ImageTag.java | 4 +- .../flash/tags/base/MorphShapeTag.java | 6 +-- .../decompiler/flash/tags/base/ShapeTag.java | 4 +- .../flash/tags/base/StaticTextTag.java | 5 +- .../decompiler/flash/tags/base/TextTag.java | 26 +++++++-- .../decompiler/flash/timeline/Timeline.java | 21 +++++--- 20 files changed, 172 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bccd101a..ad7e69edf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ All notable changes to this project will be documented in this file. - AS3 native functions outside class allowed - AS3 difference between namespace keyword and const of type Namespace - [#2462] AS3 debugger - incorrect line info injected +- [#2464] SVG export - minimum stroke width of 1 px ## [23.0.1] - 2025-05-16 ### Fixed @@ -3849,6 +3850,7 @@ Major version of SWF to XML export changed to 2. [#2460]: https://www.free-decompiler.com/flash/issues/2460 [#2461]: https://www.free-decompiler.com/flash/issues/2461 [#2462]: https://www.free-decompiler.com/flash/issues/2462 +[#2464]: https://www.free-decompiler.com/flash/issues/2464 [#2427]: https://www.free-decompiler.com/flash/issues/2427 [#1826]: https://www.free-decompiler.com/flash/issues/1826 [#2448]: https://www.free-decompiler.com/flash/issues/2448 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java index 8f4ebb170..2266dd6f4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/FrameExporter.java @@ -312,7 +312,7 @@ public class FrameExporter { rect.yMin *= settings.zoom; SVGExporter exporter = new SVGExporter(rect, settings.zoom, "frame", fbackgroundColor); - tim.toSVG(frame, 0, null, 0, exporter, null, 0); + tim.toSVG(frame, 0, null, 0, exporter, null, 0, new Matrix(), new Matrix()); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } ret.add(f); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java index f2f0b9a02..bcf2c572a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/MorphShapeExporter.java @@ -113,46 +113,52 @@ public class MorphShapeExporter { MorphShapeTag mst = (MorphShapeTag) t; new RetryTask(() -> { + ShapeTag st = mst.getStartShapeTag(); + Matrix m; + RECT rect = st.getRect(); + m = Matrix.getScaleInstance(settings.zoom); + m.translate(-rect.Xmin, -rect.Ymin); + switch (settings.mode) { case SVG_START_END: try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileStart))) { - ExportRectangle rect = new ExportRectangle(mst.getStartBounds()); - rect.xMax *= settings.zoom; - rect.yMax *= settings.zoom; - rect.xMin *= settings.zoom; - rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom, "shape"); - mst.getStartShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); + ExportRectangle rect2 = new ExportRectangle(mst.getStartBounds()); + rect2.xMax *= settings.zoom; + rect2.yMax *= settings.zoom; + rect2.xMin *= settings.zoom; + rect2.yMin *= settings.zoom; + SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); + mst.getStartShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fileEnd))) { - ExportRectangle rect = new ExportRectangle(mst.getStartBounds()); - rect.xMax *= settings.zoom; - rect.yMax *= settings.zoom; - rect.xMin *= settings.zoom; - rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom, "shape"); - mst.getEndShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); + ExportRectangle rect2 = new ExportRectangle(mst.getStartBounds()); + rect2.xMax *= settings.zoom; + rect2.yMax *= settings.zoom; + rect2.xMin *= settings.zoom; + rect2.yMin *= settings.zoom; + SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); + mst.getEndShapeTag().toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; case SVG: try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { - ExportRectangle rect = new ExportRectangle(mst.getRect()); - rect.xMax *= settings.zoom; - rect.yMax *= settings.zoom; - rect.xMin *= settings.zoom; - rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom, "morphshape"); - mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); + ExportRectangle rect2 = new ExportRectangle(mst.getRect()); + rect2.xMax *= settings.zoom; + rect2.yMax *= settings.zoom; + rect2.xMin *= settings.zoom; + rect2.yMin *= settings.zoom; + SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "morphshape"); + mst.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; case PNG_START_END: case BMP_START_END: double unzoom = settings.zoom; - ShapeTag st = mst.getStartShapeTag(); - RECT rect = st.getRect(); + st = mst.getStartShapeTag(); + rect = st.getRect(); int newWidth = (int) (rect.getWidth() * settings.zoom / SWF.unitDivisor) + 1; int newHeight = (int) (rect.getHeight() * settings.zoom / SWF.unitDivisor) + 1; SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); @@ -165,7 +171,7 @@ public class MorphShapeExporter { g.fillRect(0, 0, img.getWidth(), img.getHeight()); } } - Matrix m = Matrix.getScaleInstance(settings.zoom); + m = Matrix.getScaleInstance(settings.zoom); m.translate(-rect.Xmin, -rect.Ymin); st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true); if (settings.mode == MorphShapeExportMode.PNG_START_END) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java index 65e55d073..d85296532 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/ShapeExporter.java @@ -102,22 +102,24 @@ public class ShapeExporter { final File file = new File(outdir + File.separator + Helper.makeFileName(st.getCharacterExportFileName() + settings.getFileExtension())); new RetryTask(() -> { + Matrix m = Matrix.getScaleInstance(settings.zoom); + RECT rect = st.getRect(); + m.translate(-rect.Xmin, -rect.Ymin); switch (settings.mode) { case SVG: try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { - ExportRectangle rect = new ExportRectangle(st.getRect()); - rect.xMax *= settings.zoom; - rect.yMax *= settings.zoom; - rect.xMin *= settings.zoom; - rect.yMin *= settings.zoom; - SVGExporter exporter = new SVGExporter(rect, settings.zoom, "shape"); - st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); + ExportRectangle rect2 = new ExportRectangle(st.getRect()); + rect2.xMax *= settings.zoom; + rect2.yMax *= settings.zoom; + rect2.xMin *= settings.zoom; + rect2.yMin *= settings.zoom; + SVGExporter exporter = new SVGExporter(rect2, settings.zoom, "shape"); + st.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } break; case PNG: case BMP: - RECT rect = st.getRect(); int newWidth = (int) (rect.getWidth() * settings.zoom / SWF.unitDivisor) + 1; int newHeight = (int) (rect.getHeight() * settings.zoom / SWF.unitDivisor) + 1; SerializableImage img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); @@ -129,9 +131,7 @@ public class ShapeExporter { g.setColor(backColor.toColor()); g.fillRect(0, 0, img.getWidth(), img.getHeight()); } - } - Matrix m = Matrix.getScaleInstance(settings.zoom); - m.translate(-rect.Xmin, -rect.Ymin); + } st.toImage(0, 0, 0, new RenderContext(), img, img, false, m, m, m, m, new CXFORMWITHALPHA(), unzoom, false, new ExportRectangle(rect), new ExportRectangle(rect), true, Timeline.DRAW_MODE_ALL, 0, true); if (settings.mode == ShapeExportMode.PNG) { ImageHelper.write(img.getBufferedImage(), ImageFormat.PNG, file); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java index 4d909e86c..95d8dc112 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/TextExporter.java @@ -22,6 +22,7 @@ import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.RetryTask; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; import com.jpexs.decompiler.flash.exporters.modes.TextExportMode; import com.jpexs.decompiler.flash.exporters.settings.TextExportSettings; @@ -86,10 +87,11 @@ public class TextExporter { final TextTag textTag = (TextTag) t; final File file = new File(outdir + File.separator + Helper.makeFileName(textTag.getCharacterExportFileName() + ".svg")); new RetryTask(() -> { + Matrix m = Matrix.getScaleInstance(settings.zoom); try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) { ExportRectangle rect = new ExportRectangle(textTag.getRect()); SVGExporter exporter = new SVGExporter(rect, settings.zoom, "text"); - textTag.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0); + textTag.toSVG(exporter, -2, new CXFORMWITHALPHA(), 0, m, m); fos.write(Utf8Helper.getBytes(exporter.getSVG())); } }, handler).run(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java index 80bd75bbc..cbff13bd8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java @@ -34,6 +34,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; @@ -52,6 +53,7 @@ import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.w3c.dom.NodeList; /** * SVG exporter. @@ -82,6 +84,8 @@ public class SVGExporter { public Map exportedTags = new HashMap<>(); + public Set exportedSmallStrokesTags = new HashSet<>(); + public Map> exportedChars = new HashMap<>(); private final Map lastIds = new HashMap<>(); @@ -90,6 +94,8 @@ public class SVGExporter { public boolean useTextTag = Configuration.textExportExportFontFace.get(); + private double zoom; + public static class ExportKey { private final Tag tag; @@ -136,7 +142,7 @@ public class SVGExporter { return false; } return Objects.equals(this.colorTransform, other.colorTransform); - } + } } public SVGExporter(ExportRectangle bounds, double zoom, String objectType) { @@ -144,6 +150,7 @@ public class SVGExporter { } public SVGExporter(ExportRectangle bounds, double zoom, String objectType, Color backgroundColor) { + this.zoom = zoom; DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); @@ -185,6 +192,10 @@ public class SVGExporter { gradients = new ArrayList<>(); } + public double getZoom() { + return zoom; + } + private Element getDefs() { if (_svgDefs == null) { _svgDefs = _svg.createElement("defs"); @@ -202,10 +213,28 @@ public class SVGExporter { } return _svgStyle; } + + public final boolean hasSmallStrokes() { + NodeList nodes = _svgGs.peek().getElementsByTagName("path"); + for (int i = 0; i < nodes.getLength(); i++) { + Element e = (Element) nodes.item(i); + if ("true".equals(e.getAttribute("ffdec:has-small-stroke"))) { + return true; + } + } + nodes = _svgGs.peek().getElementsByTagName("use"); + for (int i = 0; i < nodes.getLength(); i++) { + Element e = (Element) nodes.item(i); + if ("true".equals(e.getAttribute("ffdec:has-small-stroke"))) { + return true; + } + } + return false; + } public final void createDefGroup(ExportRectangle bounds, String id) { createDefGroup(bounds, id, 1); - } + } public final void createDefGroup(ExportRectangle bounds, String id, double zoom) { Element g = _svg.createElement("g"); @@ -521,11 +550,11 @@ public class SVGExporter { _svgGs.peek().appendChild(filtersElement); } - public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect) { - return addUse(transform, boundRect, href, instanceName, scalingRect, null, null, BlendMode.NORMAL, new ArrayList<>()); + public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, boolean hasSmallStrokes) { + return addUse(transform, boundRect, href, instanceName, scalingRect, null, null, BlendMode.NORMAL, new ArrayList<>(), hasSmallStrokes); } - public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, String characterId, String characterName, int blendMode, List filters) { + public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName, RECT scalingRect, String characterId, String characterName, int blendMode, List filters, boolean hasSmallStrokes) { if (scalingRect != null && (transform == null || (Double.compare(transform.rotateSkew0, 0.0) == 0 && Double.compare(transform.rotateSkew1, 0.0) == 0))) { addScalingGridUse(transform, boundRect, href, instanceName, scalingRect, characterId, characterName, blendMode, filters); return null; //?? @@ -545,6 +574,9 @@ public class SVGExporter { if (characterName != null && !characterName.isEmpty()) { image.setAttribute("ffdec:characterName", characterName); } + if (hasSmallStrokes) { + image.setAttribute("ffdec:has-small-stroke", "true"); + } setBlendMode(image, blendMode); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java index fc451518a..d4b051c5f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/BitmapExporter.java @@ -207,13 +207,7 @@ public class BitmapExporter extends ShapeExporterBase { com.jpexs.decompiler.flash.exporters.commonshape.Point p11 = strokeTransformation.transform(1, 1); thicknessScale = p00.distanceTo(p11) / Math.sqrt(2); thicknessScaleX = Math.abs(p11.x - p00.x); - thicknessScaleY = Math.abs(p11.y - p00.y); - - /*Matrix transPre = transformation.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor, 1 / SWF.unitDivisor)); - p00 = transPre.transform(0, 0); - p11 = transPre.transform(1, 1); - realZoom = p00.distanceTo(p11) / Math.sqrt(2); - System.out.println("realZoom=" + realZoom);*/ + thicknessScaleY = Math.abs(p11.y - p00.y); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java index bd30c576f..b64689d4f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/exporters/shape/SVGShapeExporter.java @@ -17,6 +17,7 @@ package com.jpexs.decompiler.flash.exporters.shape; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; import com.jpexs.decompiler.flash.tags.base.ImageTag; @@ -74,6 +75,16 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { * Exporter */ private final SVGExporter exporter; + + /** + * Display zoom + */ + private final double displayZoom; + + /** + * Thickness scale + */ + private final double thicknessScale; /** * Constructor. @@ -85,14 +96,21 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { * @param exporter Exporter * @param defaultColor Default color * @param colorTransform Color transform - * @param zoom Zoom + * @param zoom Zoom - shape zoom + * @param displayZoom Display zoom - overal SVG zoom + * @param strokeTransformation Stroke transformation */ - public SVGShapeExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, int id, SVGExporter exporter, Color defaultColor, ColorTransform colorTransform, double zoom) { + public SVGShapeExporter(int windingRule, int shapeNum, SWF swf, SHAPE shape, int id, SVGExporter exporter, Color defaultColor, ColorTransform colorTransform, double zoom, double displayZoom, Matrix strokeTransformation) { super(windingRule, shapeNum, swf, shape, colorTransform, zoom); this.swf = swf; this.id = id; this.defaultColor = defaultColor; this.exporter = exporter; + this.displayZoom = displayZoom; + + com.jpexs.decompiler.flash.exporters.commonshape.Point p00 = strokeTransformation.transform(0, 0); + com.jpexs.decompiler.flash.exporters.commonshape.Point p11 = strokeTransformation.transform(1, 1); + thicknessScale = p00.distanceTo(p11) / Math.sqrt(2); } @Override @@ -217,11 +235,19 @@ public class SVGShapeExporter extends DefaultSVGShapeExporter { } path.setAttribute("fill", "#ff0000"); } - + @Override public void lineStyle(double thickness, RGB color, boolean pixelHinting, String scaleMode, int startCaps, int endCaps, int joints, float miterLimit, boolean noClose) { finalizePath(); - thickness *= zoom / SWF.unitDivisor; + + //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", "none"); if (color != null) { path.setAttribute("stroke", color.toHexRGB()); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index d0d9a5aa0..3d851792d 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -1040,7 +1040,7 @@ public class DefineEditTextTag extends TextTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { render(TextRenderMode.SVG, null, exporter, null, new Matrix(), colorTransform, 1); } @@ -1062,7 +1062,7 @@ public class DefineEditTextTag extends TextTag { drawBorderHtmlCanvas(swf, htmlCanvasBuilder, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); break; case SVG: - drawBorderSVG(swf, svgExporter, borderColor, fillColor, getRect(), getTextMatrix(), colorTransform, zoom); + drawBorderSVG(swf, svgExporter, borderColor, fillColor, getRect(), getTextMatrix(), transformation, colorTransform, zoom); break; } } @@ -1076,7 +1076,7 @@ public class DefineEditTextTag extends TextTag { staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), getTextMatrix(), colorTransform); break; case SVG: - staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), getTextMatrix(), colorTransform, zoom); + staticTextToSVG(swf, allTextRecords, 2, svgExporter, getBounds(), getTextMatrix(), colorTransform, zoom, transformation); break; } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java index 95db1ac76..bd766aab8 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSpriteTag.java @@ -419,8 +419,8 @@ public class DefineSpriteTag extends DrawableTag implements Timelined { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { - getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1); + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1, transformation, strokeTransformation); } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java index b469f65ca..27424aef7 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineVideoStreamTag.java @@ -420,7 +420,7 @@ public class DefineVideoStreamTag extends DrawableTag implements BoundedTag, Tim } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java index 79c185db9..d78e7deef 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ButtonTag.java @@ -131,8 +131,8 @@ public abstract class ButtonTag extends DrawableTag implements Timelined { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { - getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1); + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + getTimeline().toSVG(0, 0, null, 0, exporter, colorTransform, level + 1, transformation, strokeTransformation); } /** diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java index 0c4eb1a55..68b462653 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/DrawableTag.java @@ -113,9 +113,11 @@ public abstract class DrawableTag extends CharacterTag implements BoundedTag { * @param ratio Ratio * @param colorTransform Color transform * @param level Level + * @param transformation Transformation + * @param strokeTransformation Stroke transformation * @throws IOException On I/O error */ - public abstract void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException; + public abstract void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException; /** * Converts the drawable to HTML canvas. diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java index 651c8f956..3915a6cd3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java @@ -686,7 +686,7 @@ public abstract class FontTag extends DrawableTag implements AloneTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java index 121fa847a..7385cf5ac 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ImageTag.java @@ -346,8 +346,8 @@ public abstract class ImageTag extends DrawableTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { - SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, getShape(1), getCharacterId(), exporter, null, colorTransform, 1); + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, getShape(1), getCharacterId(), exporter, null, colorTransform, 1, exporter.getZoom(), strokeTransformation); shapeExporter.export(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java index 16d08ad4f..3ca7ee0e1 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/MorphShapeTag.java @@ -420,16 +420,16 @@ public abstract class MorphShapeTag extends DrawableTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { if (ratio == -2) { SHAPEWITHSTYLE beginShapes = getShapeAtRatio(0); SHAPEWITHSTYLE endShapes = getShapeAtRatio(65535); - SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(getShapeNum(), swf, beginShapes, endShapes, getCharacterId(), exporter, null, colorTransform, 1); + SVGMorphShapeExporter shapeExporter = new SVGMorphShapeExporter(getShapeNum(), swf, beginShapes, endShapes, getCharacterId(), exporter, null, colorTransform, exporter.getZoom()); shapeExporter.export(); } else { SHAPEWITHSTYLE shapes = getShapeAtRatio(ratio); - SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD /*??? FIXME*/, getShapeNum() == 2 ? 4 : 1, swf, shapes, getCharacterId(), exporter, null, colorTransform, 1); + SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD /*??? FIXME*/, getShapeNum() == 2 ? 4 : 1, swf, shapes, getCharacterId(), exporter, null, colorTransform, 1, exporter.getZoom(), strokeTransformation); shapeExporter.export(); } } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java index 9074da550..b89bff41e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/ShapeTag.java @@ -277,8 +277,8 @@ public abstract class ShapeTag extends DrawableTag implements LazyObject { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) throws IOException { - SVGShapeExporter shapeExporter = new SVGShapeExporter(getWindingRule(), getShapeNum(), swf, getShapes(), getCharacterId(), exporter, null, colorTransform, 1); + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { + SVGShapeExporter shapeExporter = new SVGShapeExporter(getWindingRule(), getShapeNum(), swf, getShapes(), getCharacterId(), exporter, null, colorTransform, 1, exporter.getZoom(), strokeTransformation); shapeExporter.export(); } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java index 76b8d7cce..fdbe5963e 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java @@ -1032,9 +1032,8 @@ public abstract class StaticTextTag extends TextTag { } @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { - - staticTextToSVG(swf, textRecords, getTextNum(), exporter, getRect(), textMatrix, colorTransform, 1); + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { + staticTextToSVG(swf, textRecords, getTextNum(), exporter, getRect(), textMatrix, colorTransform, exporter.getZoom(), transformation); } @Override diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java index 5c541b14a..f24852279 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -558,10 +558,14 @@ public abstract class TextTag extends DrawableTag { * @param colorTransform Color transform * @param zoom Zoom */ - public static void drawBorderSVG(SWF swf, SVGExporter exporter, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, ColorTransform colorTransform, double zoom) { + public static void drawBorderSVG(SWF swf, SVGExporter exporter, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform, double zoom) { exporter.createSubGroup(new Matrix(textMatrix), null); SHAPE shape = getBorderShape(borderColor, fillColor, rect); - SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, 0, exporter, null, colorTransform, zoom); + + Matrix mat = transformation.clone(); + mat = mat.concatenate(new Matrix(textMatrix)); + //??FIXME + SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, 0, exporter, null, colorTransform, 1, zoom, mat); shapeExporter.export(); exporter.endGroup(); } @@ -838,8 +842,9 @@ public abstract class TextTag extends DrawableTag { * @param textMatrix Text matrix * @param colorTransform Color transform * @param zoom Zoom + * @param transformation Transformation */ - public static void staticTextToSVG(SWF swf, List textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform, double zoom) { + public static void staticTextToSVG(SWF swf, List textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform, double zoom, Matrix transformation) { int textColor = 0; FontTag font = null; int textHeight = 12; @@ -914,8 +919,19 @@ public abstract class TextTag extends DrawableTag { exporter.endGroup(); } } else { + Matrix mat0 = transformation.clone(); + mat0 = mat0.concatenate(new Matrix(textMatrix)); + Matrix matScale = Matrix.getScaleInstance(rat); + + for (GLYPHENTRY entry : rec.glyphEntries) { Matrix mat = Matrix.getTranslateInstance(x, y).concatenate(Matrix.getScaleInstance(rat)); + + + matScale.translateX = x; + matScale.translateY = y; + + Matrix matX = mat0.concatenate(matScale); if (entry.glyphIndex != -1) { // shapeNum: 1 SHAPE shape = glyphs.get(entry.glyphIndex); @@ -936,7 +952,7 @@ public abstract class TextTag extends DrawableTag { if (charId == null) { charId = exporter.getUniqueId(Helper.getValidHtmlId("font_" + font.getFontNameIntag() + "_" + ch)); exporter.createDefGroup(null, charId); - SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, 0, exporter, null, colorTransform, zoom); + SVGShapeExporter shapeExporter = new SVGShapeExporter(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, 0, exporter, null, colorTransform, zoom, zoom, matX); shapeExporter.export(); if (!exporter.endGroup()) { charId = ""; @@ -946,7 +962,7 @@ public abstract class TextTag extends DrawableTag { } if (!"".equals(charId)) { - Element charImage = exporter.addUse(mat, bounds, charId, null, null); + Element charImage = exporter.addUse(mat, bounds, charId, null, null, false); RGBA colorA = new RGBA(textColor); charImage.setAttribute("fill", colorA.toHexRGB()); if (colorA.alpha != 255) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 799a27264..3268c54e9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -1597,7 +1597,7 @@ public class Timeline { * @param level Level * @throws IOException On I/O error */ - public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { + public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) throws IOException { if (getFrameCount() <= frame) { return; } @@ -1650,6 +1650,9 @@ public class Timeline { clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); } + Matrix layerMatrix = new Matrix(layer.getDrawingMatrix()); + Matrix absMat = strokeTransformation.concatenate(layerMatrix); + if (character instanceof DrawableTag) { DrawableTag drawable = (DrawableTag) character; @@ -1669,26 +1672,32 @@ public class Timeline { exporter.createClipPath(mat, clipName); SvgClip clip = new SvgClip(clipName, layer.clipDepth); clips.add(clip); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); + drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1, transformation, strokeTransformation); exporter.endGroup(); } else { boolean createNew = false; SVGExporter.ExportKey exportKey = new SVGExporter.ExportKey(drawableTag, clrTrans, layer.ratio, layer.clipDepth > -1); - if (exporter.exportedTags.containsKey(exportKey)) { + if (exporter.exportedTags.containsKey(exportKey) + && !exporter.exportedSmallStrokesTags.contains(exportKey)) { assetName = exporter.exportedTags.get(exportKey); } else { assetName = getTagIdPrefix(drawableTag, exporter); exporter.exportedTags.put(exportKey, assetName); createNew = true; } + boolean hasSmallStrokes = false; if (createNew) { exporter.createDefGroup(new ExportRectangle(boundRect), assetName); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); - exporter.endGroup(); + drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1, transformation, absMat); + hasSmallStrokes = exporter.hasSmallStrokes(); + exporter.endGroup(); } Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName, layer.instanceName, scalingGrid == null ? null : scalingGrid.splitter, String.valueOf(drawable.getCharacterId()), String.join("___", drawable.getClassNames()), layer.blendMode, layer.filters); + exporter.addUse(mat, boundRect, assetName, layer.instanceName, scalingGrid == null ? null : scalingGrid.splitter, String.valueOf(drawable.getCharacterId()), String.join("___", drawable.getClassNames()), layer.blendMode, layer.filters, hasSmallStrokes); + if (hasSmallStrokes) { + exporter.exportedSmallStrokesTags.add(exportKey); + } } } }