From 0f416b09442298b8bff0496d3f01ac5fc3d09c6b Mon Sep 17 00:00:00 2001 From: "honfika@gmail.com" Date: Fri, 18 Dec 2015 12:24:56 +0100 Subject: [PATCH] svg import improved --- .../flash/exporters/commonshape/Matrix.java | 135 +++-- .../flash/importers/ShapeImporter.java | 565 ++++++++++++++---- .../flash/importers/SvgPathReader.java | 13 + .../decompiler/flash/types/LINESTYLE2.java | 12 +- 4 files changed, 550 insertions(+), 175 deletions(-) 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 82e6ea91e..30562d598 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 @@ -16,7 +16,6 @@ */ package com.jpexs.decompiler.flash.exporters.commonshape; -import com.jpexs.decompiler.flash.importers.SvgPathReader; import com.jpexs.decompiler.flash.types.MATRIX; import java.awt.geom.AffineTransform; @@ -241,45 +240,107 @@ public final class Matrix implements Cloneable { + rotateSkew1 + ", " + scaleY + ", " + translateX + ", " + translateY + ")"; } - public static Matrix parseSvgMatrix(String matrixStr, double translateDivisor, double unitDivisor) { - matrixStr = matrixStr.trim(); - if (matrixStr.startsWith("matrix")) { - matrixStr = matrixStr.substring(6).trim(); - if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { - matrixStr = matrixStr.substring(1, matrixStr.length() - 1); - SvgPathReader reader = new SvgPathReader(matrixStr); - double scaleX = reader.readDouble() * unitDivisor; - double rotateSkew0 = reader.readDouble() * unitDivisor; - double rotateSkew1 = reader.readDouble() * unitDivisor; - double scaleY = reader.readDouble() * unitDivisor; - double translateX = reader.readDouble() * translateDivisor; - double translateY = reader.readDouble() * translateDivisor; - Matrix result = new Matrix(); - result.translateX = translateX; - result.translateY = translateY; - result.rotateSkew0 = rotateSkew0; - result.rotateSkew1 = rotateSkew1; - result.scaleX = scaleX; - result.scaleY = scaleY; - return result; - } - } else if (matrixStr.startsWith("translate")) { - matrixStr = matrixStr.substring(9).trim(); - if (matrixStr.startsWith("(") && matrixStr.endsWith(")")) { - matrixStr = matrixStr.substring(1, matrixStr.length() - 1); - SvgPathReader reader = new SvgPathReader(matrixStr); - double translateX = reader.readDouble() * translateDivisor; - double translateY = reader.readDouble() * translateDivisor; - Matrix result = new Matrix(); - result.translateX = translateX; - result.translateY = translateY; - result.scaleX = unitDivisor; - result.scaleY = unitDivisor; - return result; + public static Matrix parseSvgMatrix(String transformStr, double translateDivisor, double unitDivisor) { + Matrix ret = new Matrix(); + while (transformStr.length() > 0) { + String funcName = transformStr.split("\\(")[0]; + transformStr = transformStr.substring(funcName.length() + 1); + String params = transformStr.split("\\)")[0]; + transformStr = transformStr.substring(params.length() + 1); + String[] args = params.split(","); + funcName = funcName.trim(); + switch (funcName) { + case "matrix": + if (args.length == 6) { + double scaleX = Double.parseDouble(args[0].trim()) * unitDivisor; + double rotateSkew0 = Double.parseDouble(args[1].trim()) * unitDivisor; + double rotateSkew1 = Double.parseDouble(args[2].trim()) * unitDivisor; + double scaleY = Double.parseDouble(args[3].trim()) * unitDivisor; + double translateX = Double.parseDouble(args[4].trim()) * translateDivisor; + double translateY = Double.parseDouble(args[5].trim()) * translateDivisor; + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.rotateSkew0 = rotateSkew0; + result.rotateSkew1 = rotateSkew1; + result.scaleX = scaleX; + result.scaleY = scaleY; + ret = ret.concatenate(result); + } + break; + case "translate": + if (args.length == 1 || args.length == 2) { + double translateX = Double.parseDouble(args[0].trim()) * translateDivisor; + double translateY = 0; + if (args.length == 2) { + translateY = Double.parseDouble(args[1].trim()) * translateDivisor; + } + + Matrix result = new Matrix(); + result.translateX = translateX; + result.translateY = translateY; + result.scaleX = unitDivisor; + result.scaleY = unitDivisor; + ret = ret.concatenate(result); + } + break; + case "scale": + if (args.length == 1 || args.length == 2) { + double scaleX = Double.parseDouble(args[0].trim()); + double scaleY = scaleX; + if (args.length == 2) { + scaleY = Double.parseDouble(args[1].trim()); + } + + Matrix result = new Matrix(); + result.scaleX = scaleX; + result.scaleY = scaleY; + ret = ret.concatenate(result); + } + break; + case "skewX": + if (args.length == 1) { + double angle = Double.parseDouble(args[0].trim()) * Math.PI / 180; + + Matrix result = new Matrix(); + result.rotateSkew1 = Math.tan(angle); + ret = ret.concatenate(result); + } + break; + case "skewY": + if (args.length == 1) { + double angle = Double.parseDouble(args[0].trim()) * Math.PI / 180; + + Matrix result = new Matrix(); + result.rotateSkew0 = Math.tan(angle); + ret = ret.concatenate(result); + } + break; + case "rotate": + if (args.length == 1 || args.length == 3) { + double rotateAngle = Double.parseDouble(args[0].trim()); + double tx = 0; + double ty = 0; + if (args.length > 1) { + tx = Double.parseDouble(args[1].trim()); + ty = Double.parseDouble(args[2].trim()); + } + + double angleRad = -rotateAngle * Math.PI / 180; + Matrix result = new Matrix(); + result.rotateSkew0 = -Math.sin(angleRad); + result.rotateSkew1 = Math.sin(angleRad); + result.scaleX = Math.cos(angleRad); + result.scaleY = Math.cos(angleRad); + result = result.preConcatenate(getTranslateInstance(tx * translateDivisor, ty * translateDivisor)) + .concatenate(getTranslateInstance(-tx * translateDivisor, -ty * translateDivisor)); + ret = ret.concatenate(result); + } + break; } } - return null; + return ret; } private double roundPixels400(double pixels) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java index 01896c801..2072766f4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/ShapeImporter.java @@ -54,7 +54,10 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Random; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; @@ -72,7 +75,11 @@ import org.xml.sax.SAXException; */ public class ShapeImporter { - private boolean cubicWarning = false; + private final Set shownWarnings = new HashSet<>(); + + private static final Color TRANSPARENT = new Color(0, true); + + private final Random random = new Random(); public Tag importImage(ShapeTag st, byte[] newData) throws IOException { return importImage(st, newData, 0, true); @@ -212,58 +219,56 @@ public class ShapeImporter { Element childElement = (Element) childNode; String tagName = childElement.getTagName(); SvgStyle newStyle = style.apply(childElement); + Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), SWF.unitDivisor, 1); + Matrix m2 = m == null ? transform : m.concatenate(transform); if ("g".equals(tagName)) { - Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), SWF.unitDivisor, 1); - Matrix m2 = m == null ? transform : m.concatenate(transform); processSvgObject(shapeNum, shapes, childElement, m2, newStyle); } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, childElement, transform, newStyle); + processPath(shapeNum, shapes, childElement, m2, newStyle); + } else { + showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); } } } } - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String data = childElement.getAttribute("d"); - - char command = 0; + private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { Point prevPoint = new Point(0, 0); Point startPoint = prevPoint; - Point prevCControlPoint = null; double x0 = 0; double y0 = 0; - boolean newShape = true; - SvgPathReader pathReader = new SvgPathReader(data); - while (pathReader.hasNext()) { - char newCommand; - if ((newCommand = pathReader.readCommand()) != 0) { - command = newCommand; - } + StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); + int fillStyle = scrStyle.fillStyle1; + int lineStyle = scrStyle.lineStyle; + scrStyle.stateFillStyle0 = true; + scrStyle.stateFillStyle1 = true; + scrStyle.stateLineStyle = true; + scrStyle.fillStyle0 = 0; + scrStyle.fillStyle1 = 0; + scrStyle.lineStyle = 0; + shapes.shapeRecords.add(scrStyle); - boolean isRelative = Character.isLowerCase(command); + for (PathCommand command : commands) { double x = x0; double y = y0; - Point p = null; - char cmd = Character.toUpperCase(command); + char cmd = Character.toUpperCase(command.command); switch (cmd) { case 'M': - StyleChangeRecord scr; - if (newShape) { - newShape = false; - scr = getStyleChangeRecord(shapeNum, style); - } else { - scr = new StyleChangeRecord(); + StyleChangeRecord scr = new StyleChangeRecord(); + if (fillStyle != 0) { + scr.stateFillStyle1 = true; + scr.fillStyle1 = fillStyle; + } + if (lineStyle != 0) { + scr.lineStyle = lineStyle; + scr.stateLineStyle = true; } - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); scr.moveDeltaX = (int) Math.round(p.x); @@ -287,12 +292,8 @@ public class ShapeImporter { break; case 'L': StraightEdgeRecord serl = new StraightEdgeRecord(); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); serl.deltaX = (int) Math.round(p.x - prevPoint.x); @@ -300,14 +301,12 @@ public class ShapeImporter { prevPoint = p; System.out.println("L" + serl.deltaX + "," + serl.deltaY); serl.generalLineFlag = true; + serl.simplify(); shapes.shapeRecords.add(serl); break; case 'H': StraightEdgeRecord serh = new StraightEdgeRecord(); - x = pathReader.readDouble(); - if (isRelative) { - x += x0; - } + x = command.params[0]; p = transform.transform(x, y); serh.deltaX = (int) Math.round(p.x - prevPoint.x); @@ -317,38 +316,27 @@ public class ShapeImporter { break; case 'V': StraightEdgeRecord serv = new StraightEdgeRecord(); - y = pathReader.readDouble(); - if (isRelative) { - y += y0; - } + y = command.params[0]; p = transform.transform(x, y); - serv.deltaY = (int) Math.round(p.x - prevPoint.x); + serv.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; - System.out.println("V" + serv.deltaX); + System.out.println("V" + serv.deltaY); serv.vertLineFlag = true; shapes.shapeRecords.add(serv); break; case 'Q': CurvedEdgeRecord cer = new CurvedEdgeRecord(); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; p = transform.transform(x, y); cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[2]; + y = command.params[3]; p = transform.transform(x, y); cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); @@ -358,49 +346,24 @@ public class ShapeImporter { shapes.shapeRecords.add(cer); break; case 'C': - case 'S': - if (!cubicWarning) { - cubicWarning = true; - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Cubic curves are not supported by Flash."); - } + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); // create at least something... Point pStart = prevPoint; Point pControl1; - if (cmd == 'C') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[0]; + y = command.params[1]; - pControl1 = transform.transform(x, y); - } else { - if (prevCControlPoint != null) { - pControl1 = new Point(2 * pStart.x - prevCControlPoint.x, 2 * pStart.y - prevCControlPoint.y); - } else { - pControl1 = pStart; - } - } + pControl1 = transform.transform(x, y); - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[2]; + y = command.params[3]; Point pControl2 = transform.transform(x, y); - prevCControlPoint = pControl2; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } + x = command.params[4]; + y = command.params[5]; p = transform.transform(x, y); @@ -430,13 +393,216 @@ public class ShapeImporter { return; } - if (cmd != 'C') { + x0 = x; + y0 = y; + } + } + + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String data = childElement.getAttribute("d"); + + char command = 0; + Point startPoint = new Point(0, 0); + Point prevCControlPoint = null; + Point prevQControlPoint = null; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + while (pathReader.hasNext()) { + char newCommand; + if ((newCommand = pathReader.readCommand()) != 0) { + command = newCommand; + } + + boolean isRelative = Character.isLowerCase(command); + + double x = x0; + double y = y0; + + char cmd = Character.toUpperCase(command); + switch (cmd) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + startPoint = new Point(x, y); + + command = isRelative ? 'l' : 'L'; + break; + case 'Z': + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + x = startPoint.x; + y = startPoint.y; + serz.params = new double[]{x, y}; + pathCommands.add(serz); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + case 'H': + PathCommand serh = new PathCommand(); + serh.command = 'H'; + x = pathReader.readDouble(); + if (isRelative) { + x += x0; + } + + serh.params = new double[]{x}; + pathCommands.add(serh); + break; + case 'V': + PathCommand serv = new PathCommand(); + serv.command = 'V'; + y = pathReader.readDouble(); + if (isRelative) { + y += y0; + } + + serv.params = new double[]{y}; + pathCommands.add(serv); + break; + case 'Q': + case 'T': + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + + Point pControl; + if (cmd == 'Q') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl = new Point(x, y); + } else { + if (prevQControlPoint != null) { + pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); + } else { + pControl = new Point(x0, y0); + } + } + + prevQControlPoint = pControl; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + cer.params = new double[]{pControl.x, pControl.y, x, y}; + pathCommands.add(cer); + break; + case 'C': + case 'S': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pControl1; + if (cmd == 'C') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl1 = new Point(x, y); + } else { + if (prevCControlPoint != null) { + pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); + } else { + pControl1 = new Point(x0, y0); + } + } + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + Point pControl2 = new Point(x, y); + prevCControlPoint = pControl2; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + PathCommand cerc = new PathCommand(); + cerc.command = 'C'; + cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; + pathCommands.add(cerc); + + break; + case 'A': + double rx = pathReader.readDouble(); + double ry = pathReader.readDouble(); + double xRot = pathReader.readDouble(); + int largeFlag = (int) pathReader.readDouble(); + int sweepFlag = (int) pathReader.readDouble(); + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + // todo: draw arc, now draw only a line + PathCommand sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera); + + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + if (cmd != 'C' && cmd != 'S') { prevCControlPoint = null; } + if (cmd != 'Q' && cmd != 'T') { + prevQControlPoint = null; + } + x0 = x; y0 = y; } + + processCommands(shapeNum, shapes, pathCommands, transform, style); } private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { @@ -462,9 +628,34 @@ public class ShapeImporter { Color lineColor = style.getStrokeColorWithOpacity(); if (lineColor != null) { scr.lineStyles.lineStyles = new LINESTYLE[1]; - scr.lineStyles.lineStyles[0] = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); - scr.lineStyles.lineStyles[0].color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); - scr.lineStyles.lineStyles[0].width = (int) Math.round(style.strokeWidth * SWF.unitDivisor); + LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2();; + lineStyle.color = shapeNum >= 3 ? new RGBA(lineColor) : new RGB(lineColor); + lineStyle.width = (int) Math.round(style.strokeWidth * SWF.unitDivisor); + SvgLineCap lineCap = style.strokeLineCap; + SvgLineJoin lineJoin = style.strokeLineJoin; + if (lineStyle instanceof LINESTYLE2) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP + : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP + : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; + lineStyle2.startCapStyle = swfCap; + lineStyle2.endCapStyle = swfCap; + + int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN + : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN + : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; + lineStyle2.joinStyle = swfJoin; + lineStyle2.miterLimitFactor = (int) style.strokeMiterLimit; + } else { + if (lineCap != SvgLineCap.ROUND) { + showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); + } + if (lineJoin != SvgLineJoin.ROUND) { + showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); + } + } + + scr.lineStyles.lineStyles[0] = lineStyle; scr.lineStyle = 1; } else { scr.lineStyles.lineStyles = new LINESTYLE[0]; @@ -482,7 +673,7 @@ public class ShapeImporter { // todo: honfika: named colors: http://www.w3.org/TR/SVG/types.html#ColorKeywords switch (rgbStr) { case "none": - return new Color(0, true); + return TRANSPARENT; } if (rgbStr.startsWith("#")) { @@ -493,12 +684,30 @@ public class ShapeImporter { int i = Integer.parseInt(s, 16); return new Color(i, false); + } else { + showWarning("fillNotSupported", "Only solid fills are supported. Random color assigned."); + return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); } - - return null; } - class SvgStyle { + class PathCommand { + + public char command; + + public double[] params; + } + + enum SvgLineCap { + + BUTT, ROUND, SQUARE + } + + enum SvgLineJoin { + + MITER, ROUND, BEVEL + } + + class SvgStyle implements Cloneable { public Color fillColor; @@ -510,12 +719,24 @@ public class ShapeImporter { public double strokeWidth; + public double strokeOpacity; + + public SvgLineCap strokeLineCap; + + public SvgLineJoin strokeLineJoin; + + public double strokeMiterLimit; + public SvgStyle() { fillColor = Color.BLACK; fillOpacity = 1; strokeColor = null; strokeWidth = 1; + strokeOpacity = 1; opacity = 1; + strokeLineCap = SvgLineCap.BUTT; + strokeLineJoin = SvgLineJoin.MITER; + strokeMiterLimit = 4; } public Color getFillColorWithOpacity() { @@ -536,7 +757,7 @@ public class ShapeImporter { return null; } - int opacity = (int) Math.round(this.opacity * 255); + int opacity = (int) Math.round(this.opacity * strokeOpacity * 255); if (opacity == 255) { return strokeColor; } @@ -544,47 +765,127 @@ public class ShapeImporter { return new Color(strokeColor.getRed(), strokeColor.getGreen(), strokeColor.getBlue(), opacity); } + @Override + public SvgStyle clone() { + try { + SvgStyle ret = (SvgStyle) super.clone(); + return ret; + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(); + } + } + + private void applyStyle(SvgStyle style, String name, String value) { + if (value == null || value.length() == 0) { + return; + } + + switch (name) { + case "fill": { + Color fillColor = parseColor(value); + if (fillColor != null) { + style.fillColor = fillColor == TRANSPARENT ? null : fillColor; + } + } + break; + case "fill-opacity": { + double opacity = Double.parseDouble(value); + style.fillOpacity = opacity; + } + break; + case "stroke": { + Color strokeColor = parseColor(value); + if (strokeColor != null) { + style.strokeColor = strokeColor == TRANSPARENT ? null : strokeColor; + } + } + break; + case "stroke-width": { + double strokeWidth = Double.parseDouble(value); + style.strokeWidth = strokeWidth; + } + break; + case "stroke-opacity": { + double opacity = Double.parseDouble(value); + style.strokeOpacity = opacity; + } + break; + case "stroke-linecap": { + switch (value) { + case "butt": + style.strokeLineCap = SvgLineCap.BUTT; + break; + case "round": + style.strokeLineCap = SvgLineCap.ROUND; + break; + case "square": + style.strokeLineCap = SvgLineCap.SQUARE; + break; + } + } + break; + case "stroke-linejoin": { + switch (value) { + case "miter": + style.strokeLineJoin = SvgLineJoin.MITER; + break; + case "round": + style.strokeLineJoin = SvgLineJoin.ROUND; + break; + case "bevel": + style.strokeLineJoin = SvgLineJoin.BEVEL; + break; + } + } + break; + case "stroke-miterlimit": { + double strokeMiterLimit = Double.parseDouble(value); + style.strokeMiterLimit = strokeMiterLimit; + } + case "opacity": { + double opacity = Double.parseDouble(value); + style.opacity = opacity; + } + break; + } + } + private SvgStyle apply(Element element) { - SvgStyle result = new SvgStyle(); - result.fillColor = fillColor; - result.fillOpacity = fillOpacity; - result.strokeColor = strokeColor; - result.strokeWidth = strokeWidth; + SvgStyle result = clone(); - String attr = element.getAttribute("fill"); - Color fillColor = parseColor(attr); - if (fillColor != null) { - result.fillColor = fillColor; + String[] styles = new String[]{ + "fill", "fill-opacity", + "stroke", "stroke-width", "stroke-opacity", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", + "opacity" + }; + + for (String style : styles) { + if (element.hasAttribute(style)) { + String attr = element.getAttribute(style); + applyStyle(result, style, attr); + + } } - attr = element.getAttribute("fill-opacity"); - if (attr.length() > 0) { - double opacity = Double.parseDouble(attr); - result.fillOpacity = opacity; - } - - attr = element.getAttribute("stroke"); - Color strokeColor = parseColor(attr); - if (strokeColor != null) { - result.strokeColor = strokeColor; - } - - attr = element.getAttribute("stroke-width"); - if (attr.length() > 0) { - double strokeWidth = Double.parseDouble(attr); - result.strokeWidth = strokeWidth; - } - - attr = element.getAttribute("opacity"); - if (attr.length() > 0) { - double opacity = Double.parseDouble(attr); - result.opacity = opacity; + if (element.hasAttribute("style")) { + String[] styleDefs = element.getAttribute("style").split(";"); + for (String styleDef : styleDefs) { + String[] parts = styleDef.split(":", 2); + applyStyle(result, parts[0], parts[1].trim()); + } } return result; } } + private void showWarning(String name, String text) { + if (!shownWarnings.contains(name)) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); + shownWarnings.add(name); + } + } + public static int getShapeTagType(String format) { int res = 0; switch (format) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java index f3604e4f6..85611823b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/SvgPathReader.java @@ -49,6 +49,7 @@ public class SvgPathReader { return 0; } + readWhiteSpaces(); char ch = peek(); char command = 0; if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { @@ -63,6 +64,7 @@ public class SvgPathReader { public double readDouble() { int startPos = pos; + readWhiteSpaces(); if (peek() == '-') { pos++; } @@ -89,6 +91,17 @@ public class SvgPathReader { return result; } + private void readWhiteSpaces() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } + private void readSeparators() { while (hasNext()) { char ch = peek(); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java index 599622932..ac3d67571 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/LINESTYLE2.java @@ -34,6 +34,12 @@ public class LINESTYLE2 extends LINESTYLE implements Serializable { @SWFType(value = BasicType.UB, count = 2) public int joinStyle; + public static final int ROUND_CAP = 0; + + public static final int NO_CAP = 1; + + public static final int SQUARE_CAP = 2; + public static final int ROUND_JOIN = 0; public static final int BEVEL_JOIN = 1; @@ -57,12 +63,6 @@ public class LINESTYLE2 extends LINESTYLE implements Serializable { @SWFType(value = BasicType.UB, count = 2) public int endCapStyle; - public static final int ROUND_CAP = 0; - - public static final int NO_CAP = 1; - - public static final int SQUARE_CAP = 2; - @SWFType(BasicType.UI16) @Conditional(value = "joinStyle", options = MITER_JOIN) public int miterLimitFactor;