/* * Copyright (C) 2010-2016 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.importers.svg; import com.jpexs.decompiler.flash.ReadOnlyTagList; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.exporters.ShapeExporter; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.exporters.commonshape.Point; import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; import com.jpexs.decompiler.flash.importers.ShapeImporter; import com.jpexs.decompiler.flash.tags.DefineShape4Tag; import com.jpexs.decompiler.flash.tags.ExportAssetsTag; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.types.FILLSTYLE; import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; import com.jpexs.decompiler.flash.types.FOCALGRADIENT; import com.jpexs.decompiler.flash.types.GRADIENT; import com.jpexs.decompiler.flash.types.GRADRECORD; import com.jpexs.decompiler.flash.types.LINESTYLE; import com.jpexs.decompiler.flash.types.LINESTYLE2; import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGB; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; import com.jpexs.helpers.Helper; import com.jpexs.helpers.SerializableImage; import java.awt.Color; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * * @author JPEXS */ public class SvgImporter { private final Set<String> shownWarnings = new HashSet<>(); ShapeTag shapeTag; private Rectangle2D.Double viewBox; public Tag importSvg(ShapeTag st, String svgXml) { return importSvg(st, svgXml, true); } public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { shapeTag = st; SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); shapes.fillStyles = new FILLSTYLEARRAY(); shapes.lineStyles = new LINESTYLEARRAY(); shapes.fillStyles.fillStyles = new FILLSTYLE[0]; shapes.lineStyles.lineStyles = new LINESTYLE[0]; int shapeNum = st.getShapeNum(); shapes.shapeRecords = new ArrayList<>(); Rectangle2D.Double viewBox = null; try { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); /*docFactory.setValidating(false); docFactory.setNamespaceAware(true); docFactory.setFeature("http://xml.org/sax/features/namespaces", false); docFactory.setFeature("http://xml.org/sax/features/validation", false); docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);*/ docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); Element rootElement = doc.getDocumentElement(); Map<String, Element> idMap = new HashMap<>(); populateIds(rootElement, idMap); if (!"svg".equals(rootElement.getTagName())) { throw new IOException("SVG root element should be 'svg'"); } double width = 800; double height = 600; if (rootElement.hasAttribute("viewBox")) { String params = rootElement.getAttribute("viewBox"); String[] args = Matrix.parseSvgNumberList(params); viewBox = new Rectangle2D.Double(); if (args.length > 0) { viewBox.x = parseNumber(args[0]); } if (args.length > 1) { viewBox.y = parseNumber(args[1]); } if (args.length > 2) { viewBox.width = parseNumber(args[2]); } if (args.length > 3) { viewBox.height = parseNumber(args[3]); } width = viewBox.width; height = viewBox.height; } if (rootElement.hasAttribute("width")) { width = parseLength(rootElement.getAttribute("width"), width); } if (rootElement.hasAttribute("height")) { height = parseLength(rootElement.getAttribute("height"), height); } if (viewBox == null) { viewBox = new Rectangle2D.Double(); viewBox.width = width; viewBox.height = height; } this.viewBox = viewBox; SvgStyle style = new SvgStyle(this, idMap, rootElement); Matrix transform = new Matrix(); processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style); } catch (SAXException | IOException | ParserConfigurationException ex) { Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); } shapes.shapeRecords.add(new EndShapeRecord()); RECT rect = st.getRect(); int origXmin = rect.Xmin; int origYmin = rect.Ymin; rect.Xmin -= origXmin; rect.Xmax -= origXmin; rect.Ymin -= origYmin; rect.Ymax -= origYmin; if (!fill && viewBox != null) { rect.Xmin = (int) Math.round(viewBox.x * SWF.unitDivisor); rect.Ymin = (int) Math.round(viewBox.y * SWF.unitDivisor); rect.Xmax = (int) Math.round((viewBox.x + viewBox.width) * SWF.unitDivisor); rect.Ymax = (int) Math.round((viewBox.y + viewBox.height) * SWF.unitDivisor); } st.shapes = shapes; st.setModified(true); return (Tag) st; } // Generate id-element map, because getElementById does not work in some cases (namespaces?) protected void populateIds(Element el, Map<String, Element> out) { if (el.hasAttribute("id")) { out.put(el.getAttribute("id"), el); } NodeList nodes = el.getChildNodes(); for (int i = 0; i < nodes.getLength(); i++) { if (nodes.item(i) instanceof Element) { populateIds((Element) nodes.item(i), out); } } } private void processSvgObject(Map<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { for (int i = 0; i < element.getChildNodes().getLength(); i++) { Node childNode = element.getChildNodes().item(i); if (childNode instanceof Element) { Element childElement = (Element) childNode; String tagName = childElement.getTagName(); SvgStyle newStyle = new SvgStyle(this, idMap, childElement); Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1); Matrix m2 = m == null ? transform : transform.concatenate(m); if ("g".equals(tagName)) { processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle); } else if ("path".equals(tagName)) { processPath(shapeNum, shapes, childElement, m2, newStyle); } else if ("circle".equals(tagName)) { processCircle(shapeNum, shapes, childElement, m2, newStyle); } else if ("ellipse".equals(tagName)) { processEllipse(shapeNum, shapes, childElement, m2, newStyle); } else if ("rect".equals(tagName)) { processRect(shapeNum, shapes, childElement, m2, newStyle); } else if ("line".equals(tagName)) { processLine(shapeNum, shapes, childElement, m2, newStyle); } else if ("polyline".equals(tagName)) { processPolyline(shapeNum, shapes, childElement, m2, newStyle); } else if ("polygon".equals(tagName)) { processPolygon(shapeNum, shapes, childElement, m2, newStyle); } else if ("defs".equals(tagName) || "title".equals(tagName) || "desc".equals(tagName) || "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) { // ignore } else { showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); } } } } private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List<PathCommand> commands, Matrix transform, SvgStyle style) { Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); Point prevPoint = new Point(0, 0); Point startPoint = prevPoint; double x0 = 0; double y0 = 0; 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; List<SHAPERECORD> newRecords = new ArrayList<>(); newRecords.add(scrStyle); LINESTYLE lineStyleObj = scrStyle.lineStyles.lineStyles.length < 1 ? null : scrStyle.lineStyles.lineStyles[0]; LINESTYLE2 lineStyle2Obj = null; if (lineStyleObj instanceof LINESTYLE2) { lineStyle2Obj = (LINESTYLE2) lineStyleObj; lineStyle2Obj.noClose = true; } for (PathCommand command : commands) { double x = x0; double y = y0; Point p; char cmd = Character.toUpperCase(command.command); switch (cmd) { case 'M': StyleChangeRecord scr = new StyleChangeRecord(); if (fillStyle != 0) { scr.stateFillStyle1 = true; scr.fillStyle1 = fillStyle; } if (lineStyle != 0) { scr.lineStyle = lineStyle; scr.stateLineStyle = true; } x = command.params[0]; y = command.params[1]; p = transform2.transform(x, y); scr.moveDeltaX = (int) Math.round(p.x); scr.moveDeltaY = (int) Math.round(p.y); prevPoint = p; scr.stateMoveTo = true; newRecords.add(scr); startPoint = p; break; case 'Z': StraightEdgeRecord serz = new StraightEdgeRecord(); p = startPoint; serz.deltaX = (int) Math.round(p.x - prevPoint.x); serz.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; serz.generalLineFlag = true; newRecords.add(serz); if (lineStyle2Obj != null) { lineStyle2Obj.noClose = false; } break; case 'L': StraightEdgeRecord serl = new StraightEdgeRecord(); x = command.params[0]; y = command.params[1]; p = transform2.transform(x, y); serl.deltaX = (int) Math.round(p.x - prevPoint.x); serl.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; serl.generalLineFlag = true; serl.simplify(); newRecords.add(serl); break; case 'H': StraightEdgeRecord serh = new StraightEdgeRecord(); x = command.params[0]; p = transform2.transform(x, y); serh.deltaX = (int) Math.round(p.x - prevPoint.x); prevPoint = p; newRecords.add(serh); break; case 'V': StraightEdgeRecord serv = new StraightEdgeRecord(); y = command.params[0]; p = transform2.transform(x, y); serv.deltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; serv.vertLineFlag = true; newRecords.add(serv); break; case 'Q': CurvedEdgeRecord cer = new CurvedEdgeRecord(); x = command.params[0]; y = command.params[1]; p = transform2.transform(x, y); cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; x = command.params[2]; y = command.params[3]; p = transform2.transform(x, y); cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; newRecords.add(cer); break; case 'C': showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); // create at least something... Point pStart = prevPoint; Point pControl1; x = command.params[0]; y = command.params[1]; pControl1 = transform2.transform(x, y); x = command.params[2]; y = command.params[3]; Point pControl2 = transform2.transform(x, y); x = command.params[4]; y = command.params[5]; p = transform2.transform(x, y); //StraightEdgeRecord serc = new StraightEdgeRecord(); //serc.generalLineFlag = true; //serc.deltaX = (int) Math.round(p.x - prevPoint.x); //serc.deltaY = (int) Math.round(p.y - prevPoint.y); //newRecords.add(serc); List<Double> quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 1); for (int i = 2; i < quadCoordinates.size();) { CurvedEdgeRecord cerc = new CurvedEdgeRecord(); p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); prevPoint = p; newRecords.add(cerc); } break; default: Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); return; } x0 = x; y0 = y; } applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style); shapes.shapeRecords.addAll(newRecords); } 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<PathCommand> pathCommands = new ArrayList<>(); SvgPathReader pathReader = new SvgPathReader(data); try { 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; 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 fi = pathReader.readDouble() * Math.PI / 180; boolean largeFlag = (int) pathReader.readDouble() != 0; boolean sweepFlag = (int) pathReader.readDouble() != 0; x = pathReader.readDouble(); y = pathReader.readDouble(); if (isRelative) { x += x0; y += y0; } if (rx == 0 || ry == 0) { // straight line to (x, y) PathCommand sera = new PathCommand(); sera.command = 'L'; sera.params = new double[]{x, y}; pathCommands.add(sera); } else { rx = Math.abs(rx); ry = Math.abs(ry); double x1 = x0; double y1 = y0; double x2 = x; double y2 = y; double d1 = (x1 - x2) / 2; double d2 = (y1 - y2) / 2; double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; // Correction of out-of-range radii double lambda = x1Comma * x1Comma / (rx * rx) + y1Comma * y1Comma / (ry * ry); if (lambda > 1) { double sqrtLambda = Math.sqrt(lambda); rx = sqrtLambda * rx; ry = sqrtLambda * ry; } double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); double cxComma = c * rx * y1Comma / ry; double cyComma = c * -ry * x1Comma / rx; if (largeFlag == sweepFlag) { cxComma = -cxComma; cyComma = -cyComma; } double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; double px1 = (x1Comma - cxComma) / rx; double py1 = (y1Comma - cyComma) / ry; double theta1 = calcAngle(1, 0, px1, py1); double px2 = (-x1Comma - cxComma) / rx; double py2 = (-y1Comma - cyComma) / ry; double deltaTheta = calcAngle(px1, py1, px2, py2); if (sweepFlag) { if (deltaTheta < 0) { deltaTheta += 2 * Math.PI; } } else if (deltaTheta > 0) { deltaTheta -= 2 * Math.PI; } double rcp = Math.sqrt(4 - 2 * Math.sqrt(2)); double delta = Math.signum(deltaTheta) * Math.PI / 4; int segmentCount = (int) Math.ceil(deltaTheta / delta); double theta = theta1; PathCommand sera; for (int i = 0; i < segmentCount - 1; i++) { theta += delta; /*sera = new PathCommand(); sera.command = 'L'; double x12 = Math.cos(theta) * rx; double y12 = Math.sin(theta) * ry; x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; sera.params = new double[]{cx + x1Comma, cy + y1Comma}; pathCommands.add(sera);*/ sera = new PathCommand(); sera.command = 'Q'; double x12 = Math.cos(theta) * rx; double y12 = Math.sin(theta) * ry; x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; double theta2 = theta - delta / 2; x12 = Math.cos(theta2) * rx * rcp; y12 = Math.sin(theta2) * ry * rcp; double x1Comma2 = Math.cos(fi) * x12 - Math.sin(fi) * y12; double y1Comma2 = Math.sin(fi) * x12 + Math.cos(fi) * y12; sera.params = new double[]{cx + x1Comma2, cy + y1Comma2, cx + x1Comma, cy + y1Comma}; pathCommands.add(sera); } sera = new PathCommand(); sera.command = 'Q'; theta += delta; double diff = theta1 + deltaTheta - theta; diff = -delta - diff; theta = theta - delta - diff / 2; double rcpm = 1 + (rcp - 1) * (diff / delta) * (diff / delta); double x12 = Math.cos(theta) * rx * rcpm; double y12 = Math.sin(theta) * ry * rcpm; x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; sera.params = new double[]{cx + x1Comma, cy + y1Comma, x, y}; pathCommands.add(sera); /*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; } } catch (NumberFormatException e) { // ignore remaining data as specified in SVG Specification F.2 Error processing } processCommands(shapeNum, shapes, pathCommands, transform, style); } private double calcAngle(double ux, double uy, double vx, double vy) { double lu = Math.sqrt(ux * ux + uy * uy); double lv = Math.sqrt(ux * ux + uy * uy); double sign = Math.signum(ux * vy - uy * vx); if (sign == 0) { sign = 1; } return sign * Math.acos(ux * vx + uy * vy / (lu * lv)); } private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { String attr = childElement.getAttribute("cx"); double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; attr = childElement.getAttribute("cy"); double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; attr = childElement.getAttribute("r"); double r = attr.length() > 0 ? parseLength(attr, viewBox.width/* todo: how much is 100%? */) : 0; processEllipse(shapeNum, shapes, transform, style, cx, cy, r, r); } private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { String attr = childElement.getAttribute("cx"); double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; attr = childElement.getAttribute("cy"); double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; attr = childElement.getAttribute("rx"); double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; attr = childElement.getAttribute("ry"); double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; processEllipse(shapeNum, shapes, transform, style, cx, cy, rx, ry); } private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, double cx, double cy, double rx, double ry) { double sqrt2RXHalf = Math.sqrt(2) * rx / 2; double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; double sqrt2RYHalf = Math.sqrt(2) * ry / 2; double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; List<PathCommand> pathCommands = new ArrayList<>(); PathCommand scr = new PathCommand(); scr.command = 'M'; scr.params = new double[]{cx + rx, cy}; pathCommands.add(scr); double[] points = new double[]{ rx, -sqrt2Minus1RY, sqrt2RXHalf, -sqrt2RYHalf, sqrt2Minus1RX, -ry, 0, -ry, -sqrt2Minus1RX, -ry, -sqrt2RXHalf, -sqrt2RYHalf, -rx, -sqrt2Minus1RY, -rx, 0, -rx, sqrt2Minus1RY, -sqrt2RXHalf, sqrt2RYHalf, -sqrt2Minus1RX, ry, 0, ry, sqrt2Minus1RX, ry, sqrt2RXHalf, sqrt2RYHalf, rx, sqrt2Minus1RY, rx, 0}; for (int i = 0; i < points.length; i += 4) { PathCommand cer = new PathCommand(); cer.command = 'Q'; cer.params = new double[]{cx + points[i], cy + points[i + 1], cx + points[i + 2], cy + points[i + 3]}; /*double tetha = 30; tetha *= Math.PI / 180; double x1 = points[i]; double y1 = points[i + 1]; double x2 = points[i + 2]; double y2 = points[i + 3]; double x1Comma = Math.cos(tetha) * x1 + Math.sin(tetha) * y1; double y1Comma = -Math.sin(tetha) * x1 + Math.cos(tetha) * y1; double x2Comma = Math.cos(tetha) * x2 + Math.sin(tetha) * y2; double y2Comma = -Math.sin(tetha) * x2 + Math.cos(tetha) * y2; cer.params = new double[]{cx + x1Comma, cy + y1Comma, cx + x2Comma, cy + y2Comma};*/ pathCommands.add(cer); } PathCommand serz = new PathCommand(); serz.command = 'Z'; pathCommands.add(serz); processCommands(shapeNum, shapes, pathCommands, transform, style); } private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { String attr = childElement.getAttribute("x"); double x = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; attr = childElement.getAttribute("y"); double y = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; attr = childElement.getAttribute("width"); double width = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; attr = childElement.getAttribute("height"); double height = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; attr = childElement.getAttribute("rx"); double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; attr = childElement.getAttribute("ry"); double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; if (rx == 0 && ry != 0) { rx = ry; } else if (rx != 0 && ry == 0) { ry = rx; } if (rx > width / 2) { rx = width / 2; } if (ry > height / 2) { ry = height / 2; } List<PathCommand> pathCommands = new ArrayList<>(); if (rx > 0 || ry > 0) { PathCommand scr = new PathCommand(); scr.command = 'M'; scr.params = new double[]{x + width, y + ry}; pathCommands.add(scr); double sqrt2RXHalf = Math.sqrt(2) * rx / 2; double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; double sqrt2RYHalf = Math.sqrt(2) * ry / 2; double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; double[] points = new double[]{ x + width, y + ry - sqrt2Minus1RY, x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, x + width - rx + sqrt2Minus1RX, y, x + width - rx, y, x + rx, y, x + rx - sqrt2Minus1RX, y, x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, x, y + ry - sqrt2Minus1RY, x, y + ry, x, y + height - ry, x, y + height - ry + sqrt2Minus1RY, x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, x + rx - sqrt2Minus1RX, y + height, x + rx, y + height, x + width - rx, y + height, x + width - rx + sqrt2Minus1RX, y + height, x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, x + width, y + height - ry + sqrt2Minus1RY, x + width, y + height - ry, x + width, y + ry}; for (int i = 0; i < points.length;) { if (i % 10 == 8) { PathCommand cer = new PathCommand(); cer.command = 'L'; cer.params = new double[]{points[i], points[i + 1]}; pathCommands.add(cer); i += 2; } else { PathCommand cer = new PathCommand(); cer.command = 'Q'; cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; pathCommands.add(cer); i += 4; } } } else { PathCommand scr = new PathCommand(); scr.command = 'M'; scr.params = new double[]{x, y}; pathCommands.add(scr); double[] points = new double[]{ x + width, y, x + width, y + height, x, y + height, x, y}; for (int i = 0; i < points.length; i += 2) { PathCommand cer = new PathCommand(); cer.command = 'L'; cer.params = new double[]{points[i], points[i + 1]}; pathCommands.add(cer); } } PathCommand serz = new PathCommand(); serz.command = 'Z'; pathCommands.add(serz); processCommands(shapeNum, shapes, pathCommands, transform, style); } private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { String attr = childElement.getAttribute("x1"); double x1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; attr = childElement.getAttribute("y1"); double y1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; attr = childElement.getAttribute("x2"); double x2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; attr = childElement.getAttribute("y2"); double y2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; List<PathCommand> pathCommands = new ArrayList<>(); PathCommand scr = new PathCommand(); scr.command = 'M'; scr.params = new double[]{x1, y1}; pathCommands.add(scr); PathCommand cer = new PathCommand(); cer.command = 'L'; cer.params = new double[]{x2, y2}; pathCommands.add(cer); processCommands(shapeNum, shapes, pathCommands, transform, style); } private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { processPolyline(shapeNum, shapes, childElement, transform, style, false); } private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { processPolyline(shapeNum, shapes, childElement, transform, style, true); } private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { String data = childElement.getAttribute("points"); char command = 'M'; double x0 = 0; double y0 = 0; List<PathCommand> pathCommands = new ArrayList<>(); SvgPathReader pathReader = new SvgPathReader(data); try { while (pathReader.hasNext()) { double x = x0; double y = y0; Point p = null; switch (command) { case 'M': PathCommand scr = new PathCommand(); scr.command = 'M'; x = pathReader.readDouble(); y = pathReader.readDouble(); scr.params = new double[]{x, y}; pathCommands.add(scr); break; case 'L': PathCommand serl = new PathCommand(); serl.command = 'L'; x = pathReader.readDouble(); y = pathReader.readDouble(); serl.params = new double[]{x, y}; pathCommands.add(serl); break; } x0 = x; y0 = y; command = 'L'; } } catch (NumberFormatException e) { // ignore remaining data as specified in SVG Specification F.2 Error processing } if (close) { PathCommand serz = new PathCommand(); serz.command = 'Z'; pathCommands.add(serz); } processCommands(shapeNum, shapes, pathCommands, transform, style); } //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file private static void svgTest(String name) throws IOException, InterruptedException { if (!new File(name + ".original.svg").exists()) { URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg"); byte[] svgData = Helper.readStream(svgUrl.openStream()); Helper.writeFile(name + ".orig.svg", svgData); URL pngUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/png/full-" + name + ".png"); byte[] pngData = Helper.readStream(pngUrl.openStream()); Helper.writeFile(name + ".orig.png", pngData); } String svgDataS = Helper.readTextFile(name + ".orig.svg"); SWF swf = new SWF(); DefineShape4Tag st = new DefineShape4Tag(swf); st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS)); swf.addTag(st); SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR); BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null); List<Tag> li = new ArrayList<>(); li.add(st); ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); ExportAssetsTag eat = new ExportAssetsTag(swf); eat.tags.add(st.getCharacterId()); eat.names.add(name); swf.addTag(eat); swf.assignExportNamesToSymbols(); st.shapeBounds.Xmax = (int) (si.getWidth() * SWF.unitDivisor); st.shapeBounds.Ymax = (int) (si.getHeight() * SWF.unitDivisor); new ShapeExporter().exportShapes(null, "./outex/", new ReadOnlyTagList(li), new ShapeExportSettings(ShapeExportMode.SVG, 1), null); } //Test for SVG public static void main(String[] args) throws IOException, InterruptedException { // svgTest("animate-elem-02-t"); // svgTest("animate-elem-03-t"); // svgTest("animate-elem-04-t"); // svgTest("animate-elem-05-t"); // svgTest("animate-elem-06-t"); // svgTest("animate-elem-07-t"); // svgTest("animate-elem-08-t"); // svgTest("animate-elem-09-t"); // svgTest("animate-elem-10-t"); // svgTest("animate-elem-11-t"); // svgTest("animate-elem-12-t"); // svgTest("animate-elem-13-t"); // svgTest("animate-elem-14-t"); // svgTest("animate-elem-15-t"); // svgTest("animate-elem-17-t"); // svgTest("animate-elem-19-t"); // svgTest("animate-elem-20-t"); // svgTest("animate-elem-21-t"); // svgTest("animate-elem-22-b"); // svgTest("animate-elem-23-t"); // svgTest("animate-elem-24-t"); // svgTest("animate-elem-25-t"); // svgTest("animate-elem-26-t"); // svgTest("animate-elem-27-t"); // svgTest("animate-elem-28-t"); // svgTest("animate-elem-29-b"); // svgTest("animate-elem-30-t"); // svgTest("animate-elem-31-t"); // svgTest("animate-elem-32-t"); // svgTest("animate-elem-33-t"); // svgTest("animate-elem-34-t"); // svgTest("animate-elem-36-t"); // svgTest("animate-elem-37-t"); // svgTest("animate-elem-39-t"); // svgTest("animate-elem-40-t"); // svgTest("animate-elem-41-t"); // svgTest("animate-elem-44-t"); // svgTest("animate-elem-46-t"); // svgTest("animate-elem-52-t"); // svgTest("animate-elem-60-t"); // svgTest("animate-elem-61-t"); // svgTest("animate-elem-62-t"); // svgTest("animate-elem-63-t"); // svgTest("animate-elem-64-t"); // svgTest("animate-elem-65-t"); // svgTest("animate-elem-66-t"); // svgTest("animate-elem-67-t"); // svgTest("animate-elem-68-t"); // svgTest("animate-elem-69-t"); // svgTest("animate-elem-70-t"); // svgTest("animate-elem-77-t"); // svgTest("animate-elem-78-t"); // svgTest("animate-elem-80-t"); // svgTest("animate-elem-81-t"); // svgTest("animate-elem-82-t"); // svgTest("animate-elem-83-t"); // svgTest("animate-elem-84-t"); // svgTest("animate-elem-85-t"); svgTest("color-prof-01-f"); svgTest("color-prop-01-b"); svgTest("color-prop-02-f"); svgTest("color-prop-03-t"); svgTest("coords-coord-01-t"); svgTest("coords-coord-02-t"); svgTest("coords-trans-01-b"); svgTest("coords-trans-02-t"); svgTest("coords-trans-03-t"); svgTest("coords-trans-04-t"); svgTest("coords-trans-05-t"); svgTest("coords-trans-06-t"); svgTest("coords-units-01-b"); svgTest("coords-units-02-b"); svgTest("coords-units-03-b"); svgTest("coords-viewattr-01-b"); svgTest("coords-viewattr-02-b"); svgTest("coords-viewattr-03-b"); svgTest("extend-namespace-01-f"); // svgTest("filters-blend-01-b"); // svgTest("filters-color-01-b"); // svgTest("filters-composite-02-b"); // svgTest("filters-comptran-01-b"); // svgTest("filters-conv-01-f"); // svgTest("filters-diffuse-01-f"); // svgTest("filters-displace-01-f"); // svgTest("filters-example-01-b"); // svgTest("filters-felem-01-b"); // svgTest("filters-gauss-01-b"); // svgTest("filters-image-01-b"); // svgTest("filters-light-01-f"); // svgTest("filters-morph-01-f"); // svgTest("filters-offset-01-b"); // svgTest("filters-specular-01-f"); // svgTest("filters-tile-01-b"); // svgTest("filters-turb-01-f"); // svgTest("fonts-desc-02-t"); // svgTest("fonts-elem-01-t"); // svgTest("fonts-elem-02-t"); // svgTest("fonts-elem-03-b"); // svgTest("fonts-elem-04-b"); // svgTest("fonts-elem-05-t"); // svgTest("fonts-elem-06-t"); // svgTest("fonts-elem-07-b"); // svgTest("fonts-glyph-02-t"); // svgTest("fonts-glyph-03-t"); // svgTest("fonts-glyph-04-t"); // svgTest("fonts-kern-01-t"); // svgTest("interact-cursor-01-f"); // svgTest("interact-dom-01-b"); // svgTest("interact-events-01-b"); // svgTest("interact-order-01-b"); // svgTest("interact-order-02-b"); // svgTest("interact-order-03-b"); // svgTest("interact-zoom-01-t"); // svgTest("linking-a-01-b"); // svgTest("linking-a-02-b"); // svgTest("linking-a-03-b"); // svgTest("linking-a-04-t"); // svgTest("linking-a-05-t"); // svgTest("linking-a-07-t"); // svgTest("linking-uri-01-b"); // svgTest("linking-uri-02-b"); // svgTest("linking-uri-03-t"); // svgTest("masking-intro-01-f"); // svgTest("masking-mask-01-b"); // svgTest("masking-opacity-01-b"); // svgTest("masking-path-01-b"); // svgTest("masking-path-02-b"); // svgTest("masking-path-03-b"); // svgTest("masking-path-04-b"); // svgTest("masking-path-05-f"); // svgTest("metadata-example-01-b"); svgTest("painting-fill-01-t"); svgTest("painting-fill-02-t"); svgTest("painting-fill-03-t"); svgTest("painting-fill-04-t"); svgTest("painting-fill-05-b"); svgTest("painting-marker-01-f"); svgTest("painting-marker-02-f"); svgTest("painting-marker-03-f"); svgTest("painting-render-01-b"); svgTest("painting-stroke-01-t"); svgTest("painting-stroke-02-t"); svgTest("painting-stroke-03-t"); svgTest("painting-stroke-04-t"); svgTest("painting-stroke-07-t"); svgTest("paths-data-01-t"); svgTest("paths-data-02-t"); svgTest("paths-data-03-f"); svgTest("paths-data-04-t"); svgTest("paths-data-05-t"); svgTest("paths-data-06-t"); svgTest("paths-data-07-t"); svgTest("paths-data-08-t"); svgTest("paths-data-09-t"); svgTest("paths-data-10-t"); svgTest("paths-data-12-t"); svgTest("paths-data-13-t"); svgTest("paths-data-14-t"); svgTest("paths-data-15-t"); svgTest("pservers-grad-01-b"); svgTest("pservers-grad-02-b"); svgTest("pservers-grad-03-b"); svgTest("pservers-grad-04-b"); svgTest("pservers-grad-05-b"); svgTest("pservers-grad-06-b"); svgTest("pservers-grad-07-b"); svgTest("pservers-grad-08-b"); svgTest("pservers-grad-09-b"); svgTest("pservers-grad-10-b"); svgTest("pservers-grad-11-b"); svgTest("pservers-grad-12-b"); svgTest("pservers-grad-13-b"); svgTest("pservers-grad-14-b"); svgTest("pservers-grad-15-b"); svgTest("pservers-grad-16-b"); svgTest("pservers-grad-17-b"); svgTest("pservers-grad-18-b"); svgTest("pservers-grad-19-b"); svgTest("pservers-pattern-01-b"); svgTest("render-elems-01-t"); svgTest("render-elems-02-t"); svgTest("render-elems-03-t"); svgTest("render-elems-06-t"); svgTest("render-elems-07-t"); svgTest("render-elems-08-t"); svgTest("render-groups-01-b"); svgTest("render-groups-03-t"); // svgTest("script-handle-01-b"); // svgTest("script-handle-02-b"); // svgTest("script-handle-03-b"); // svgTest("script-handle-04-b"); svgTest("shapes-circle-01-t"); svgTest("shapes-circle-02-t"); svgTest("shapes-ellipse-01-t"); svgTest("shapes-ellipse-02-t"); svgTest("shapes-intro-01-t"); svgTest("shapes-line-01-t"); svgTest("shapes-polygon-01-t"); svgTest("shapes-polyline-01-t"); svgTest("shapes-rect-01-t"); svgTest("shapes-rect-02-t"); // svgTest("struct-cond-01-t"); // svgTest("struct-cond-02-t"); // svgTest("struct-cond-03-t"); // svgTest("struct-defs-01-t"); // svgTest("struct-dom-01-b"); // svgTest("struct-dom-02-b"); // svgTest("struct-dom-03-b"); // svgTest("struct-dom-04-b"); // svgTest("struct-dom-05-b"); // svgTest("struct-dom-06-b"); // svgTest("struct-frag-01-t"); // svgTest("struct-frag-02-t"); // svgTest("struct-frag-03-t"); // svgTest("struct-frag-04-t"); // svgTest("struct-frag-05-t"); // svgTest("struct-frag-06-t"); // svgTest("struct-group-01-t"); // svgTest("struct-group-02-b"); // svgTest("struct-group-03-t"); // svgTest("struct-image-01-t"); // svgTest("struct-image-02-b"); // svgTest("struct-image-03-t"); // svgTest("struct-image-04-t"); // svgTest("struct-image-05-b"); // svgTest("struct-image-06-t"); // svgTest("struct-image-07-t"); // svgTest("struct-image-08-t"); // svgTest("struct-image-09-t"); // svgTest("struct-image-10-t"); // svgTest("struct-symbol-01-b"); // svgTest("struct-use-01-t"); // svgTest("struct-use-03-t"); // svgTest("struct-use-05-b"); // svgTest("styling-css-01-b"); // svgTest("styling-css-02-b"); // svgTest("styling-css-03-b"); // svgTest("styling-css-04-f"); // svgTest("styling-css-05-b"); // svgTest("styling-css-06-b"); // svgTest("styling-inherit-01-b"); // svgTest("styling-pres-01-t"); // svgTest("text-align-01-b"); // svgTest("text-align-02-b"); // svgTest("text-align-03-b"); // svgTest("text-align-04-b"); // svgTest("text-align-05-b"); // svgTest("text-align-06-b"); // svgTest("text-align-08-b"); // svgTest("text-altglyph-01-b"); // svgTest("text-deco-01-b"); // svgTest("text-fonts-01-t"); // svgTest("text-fonts-02-t"); // svgTest("text-fonts-03-t"); // svgTest("text-intro-01-t"); // svgTest("text-intro-02-b"); // svgTest("text-intro-03-b"); // svgTest("text-intro-04-t"); // svgTest("text-intro-05-t"); // svgTest("text-path-01-b"); // svgTest("text-spacing-01-b"); // svgTest("text-text-01-b"); // svgTest("text-text-03-b"); // svgTest("text-text-04-t"); // svgTest("text-text-05-t"); // svgTest("text-text-06-t"); // svgTest("text-text-07-t"); // svgTest("text-text-08-b"); // svgTest("text-tref-01-b"); // svgTest("text-tselect-01-b"); // svgTest("text-tselect-02-f"); // svgTest("text-tspan-01-b"); // svgTest("text-ws-01-t"); // svgTest("text-ws-02-t"); // svgTest("types-basicDOM-01-b"); } private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { if (fill == null || fillStyle == null) { return; } if (fill instanceof SvgGradient) { SvgGradient gfill = (SvgGradient) fill; Matrix gradientMatrix = Matrix.parseSvgMatrix(gfill.gradientTransform, SWF.unitDivisor, 1); gradientMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(gradientMatrix); fillStyle.gradientMatrix = gradientMatrix.toMATRIX(); if (fill instanceof SvgLinearGradient) { SvgLinearGradient lgfill = (SvgLinearGradient) fill; fillStyle.fillStyleType = FILLSTYLE.LINEAR_GRADIENT; fillStyle.gradient = new GRADIENT(); double x1 = parseCoordinate(lgfill.x1, 1/* todo: how much is 100%? */); double y1 = parseCoordinate(lgfill.y1, 1/* todo: how much is 100%? */); double x2 = parseCoordinate(lgfill.x2, 1/* todo: how much is 100%? */); double y2 = parseCoordinate(lgfill.y2, 1/* todo: how much is 100%? */); x1 = x1 * SWF.unitDivisor; y1 = y1 * SWF.unitDivisor; x2 = x2 * SWF.unitDivisor; y2 = y2 * SWF.unitDivisor; Matrix boundingBoxMatrix = new Matrix(); if (lgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; boundingBoxMatrix.rotateSkew0 = 0; boundingBoxMatrix.rotateSkew1 = 0; boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; boundingBoxMatrix.translateX = bounds.Xmin; boundingBoxMatrix.translateY = bounds.Ymin; } Matrix xyMatrix = new Matrix(); xyMatrix.scaleX = x2 - x1; xyMatrix.rotateSkew0 = y2 - y1; xyMatrix.rotateSkew1 = -xyMatrix.rotateSkew0; xyMatrix.scaleY = xyMatrix.scaleX; xyMatrix = xyMatrix.preConcatenate(boundingBoxMatrix); Matrix zeroStartMatrix = Matrix.getTranslateInstance(0.5, 0); Matrix scaleMatrix = Matrix.getScaleInstance(1 / 16384.0 / 2); Matrix transMatrix = Matrix.getTranslateInstance(x1, y1); Matrix tMatrix = new Matrix(); tMatrix = tMatrix.preConcatenate(scaleMatrix); tMatrix = tMatrix.preConcatenate(zeroStartMatrix); tMatrix = tMatrix.preConcatenate(xyMatrix); tMatrix = tMatrix.preConcatenate(transMatrix); Point p1 = tMatrix.transform(new Point(-16384, 0)); Point p2 = tMatrix.transform(new Point(16384, 0)); tMatrix = tMatrix.preConcatenate(new Matrix(fillStyle.gradientMatrix)); fillStyle.gradientMatrix = tMatrix.toMATRIX(); } else if (fill instanceof SvgRadialGradient) { SvgRadialGradient rgfill = (SvgRadialGradient) fill; double cx = parseCoordinate(rgfill.cx, 1/* todo: how much is 100%? */); double cy = parseCoordinate(rgfill.cy, 1/* todo: how much is 100%? */); double r = parseLength(rgfill.r, 1/* todo: how much is 100%? */); Matrix boundingBoxMatrix = new Matrix(); if (rgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; boundingBoxMatrix.rotateSkew0 = 0; boundingBoxMatrix.rotateSkew1 = 0; boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; boundingBoxMatrix.translateX = bounds.Xmin; boundingBoxMatrix.translateY = bounds.Ymin; } fillStyle.gradientMatrix = Matrix.getTranslateInstance(SWF.unitDivisor * cx, SWF.unitDivisor * cy).concatenate(new Matrix(fillStyle.gradientMatrix)).concatenate(Matrix.getScaleInstance(r / 819.2)).preConcatenate(boundingBoxMatrix).toMATRIX(); double fx = parseCoordinate(rgfill.fx, 1/* todo: how much is 100%? */); double fy = parseCoordinate(rgfill.fy, 1/* todo: how much is 100%? */); if (!rgfill.fx.equals(rgfill.cx) || !rgfill.fy.equals(rgfill.cy)) { fillStyle.fillStyleType = FILLSTYLE.FOCAL_RADIAL_GRADIENT; fillStyle.gradient = new FOCALGRADIENT(); FOCALGRADIENT fg = (FOCALGRADIENT) fillStyle.gradient; double f = Math.sqrt((fx - cx) * (fx - cx) + (fy - cy) * (fy - cy)) / 819.2; fg.focalPoint = (float) f; } else { fillStyle.fillStyleType = FILLSTYLE.RADIAL_GRADIENT; fillStyle.gradient = new GRADIENT(); } } switch (gfill.spreadMethod) { case PAD: fillStyle.gradient.spreadMode = GRADIENT.SPREAD_PAD_MODE; break; case REFLECT: fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REFLECT_MODE; break; case REPEAT: fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REPEAT_MODE; break; } switch (gfill.interpolation) { case LINEAR_RGB: fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_LINEAR_RGB_MODE; break; case SRGB: fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_RGB_MODE; break; } fillStyle.gradient.gradientRecords = new GRADRECORD[gfill.stops.size()]; int prevRatio = -1; for (int i = 0; i < gfill.stops.size(); i++) { SvgStop stop = gfill.stops.get(i); Color color = stop.color; color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) Math.round(color.getAlpha() * style.getOpacity())); fillStyle.gradient.gradientRecords[i] = new GRADRECORD(); fillStyle.gradient.gradientRecords[i].inShape3 = shapeNum >= 3; fillStyle.gradient.gradientRecords[i].color = getRGB(shapeNum, color); int ratio = Math.max((int) Math.round(stop.offset * 255), prevRatio + 1); fillStyle.gradient.gradientRecords[i].ratio = ratio; prevRatio = ratio; if (prevRatio == 255) { break; } } } else if (fill instanceof SvgBitmapFill) { SvgBitmapFill bfill = (SvgBitmapFill) fill; fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; fillStyle.bitmapId = bfill.characterId; Matrix fillMatrix = Matrix.parseSvgMatrix(bfill.patternTransform, SWF.unitDivisor, SWF.unitDivisor); fillMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(fillMatrix); fillStyle.bitmapMatrix = fillMatrix.toMATRIX(); } } private void applyStyleGradients(RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { SvgFill fill = style.getFillWithOpacity(); if (fill != null && fill != SvgTransparentFill.INSTANCE) { applyFillGradients(fill, scr.fillStyles.fillStyles[0], bounds, scr, transform, shapeNum, style); } SvgFill strokeFill = style.getStrokeFillWithOpacity(); if (strokeFill != null) { if (scr.lineStyles.lineStyles.length > 0 && scr.lineStyles.lineStyles[0] instanceof LINESTYLE2) { applyFillGradients(strokeFill, ((LINESTYLE2) scr.lineStyles.lineStyles[0]).fillType, bounds, scr, transform, shapeNum, style); } } } private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { StyleChangeRecord scr = new StyleChangeRecord(); scr.stateNewStyles = true; scr.fillStyles = new FILLSTYLEARRAY(); scr.stateFillStyle1 = true; scr.stateLineStyle = true; SvgFill fill = style.getFillWithOpacity(); if (fill != null && fill != SvgTransparentFill.INSTANCE) { scr.fillStyles.fillStyles = new FILLSTYLE[1]; scr.fillStyles.fillStyles[0] = new FILLSTYLE(); if (fill instanceof SvgColor) { Color colorFill = fill.toColor(); scr.fillStyles.fillStyles[0].color = getRGB(shapeNum, colorFill); scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; } else if (fill instanceof SvgGradient) { //...apply in second step - applyStyleGradients } scr.fillStyle1 = 1; } else { scr.fillStyles.fillStyles = new FILLSTYLE[0]; scr.fillStyle1 = 0; } scr.lineStyles = new LINESTYLEARRAY(); SvgFill strokeFill = style.getStrokeFillWithOpacity(); if (strokeFill != null && strokeFill != SvgTransparentFill.INSTANCE) { Color lineColor = strokeFill.toColor(); scr.lineStyles.lineStyles = new LINESTYLE[1]; LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); lineStyle.color = getRGB(shapeNum, lineColor); lineStyle.width = (int) Math.round(style.getStrokeWidth() * SWF.unitDivisor); SvgLineCap lineCap = style.getStrokeLineCap(); SvgLineJoin lineJoin = style.getStrokeLineJoin(); 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; if (!(strokeFill instanceof SvgColor)) { lineStyle2.hasFillFlag = true; lineStyle2.fillType = new FILLSTYLE(); //...apply in second step - applyStyleGradients } // Single color does not need fillType attribute 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 = (float) style.getStrokeMiterLimit(); } 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]; scr.lineStyle = 0; } return scr; } private RGB getRGB(int shapeNum, Color color) { if (shapeNum < 3 && color.getAlpha() != 0xff) { showWarning("transparentColorNotSupported", "Transparent color is not supported in shape " + shapeNum); } return shapeNum >= 3 ? new RGBA(color) : new RGB(color); } private double parseCoordinate(String value, double relativeTo) { return parseLength(value, relativeTo); } private double parseLength(String value, double relativeTo) { if (value == null) { throw new NumberFormatException(); } value = value.toLowerCase(); String unit = null; if (value.endsWith("em") || value.endsWith("ex") || value.endsWith("px") || value.endsWith("in") || value.endsWith("cm") || value.endsWith("mm") || value.endsWith("pt") || value.endsWith("pc")) { unit = value.substring(value.length() - 2); value = value.substring(0, value.length() - 2); } else if (value.endsWith("%")) { unit = "%"; value = value.substring(0, value.length() - 1); } double result = Double.parseDouble(value); if (unit != null) { switch (unit) { case "em": case "ex": // todo: font things break; case "in": result *= getDpi(); break; case "pt": result *= getDpi() / 72; break; case "pc": result *= getDpi() / 6; break; case "cm": result *= getDpi() / 2.54; break; case "mm": result *= getDpi() / 25.4; break; case "%": result = relativeTo * result / 100; break; } } return result; } public double parseNumber(String value) { if (value == null) { throw new NumberFormatException(); } double result = Double.parseDouble(value); return result; } public double parseNumberOrPercent(String value) { if (value == null) { throw new NumberFormatException(); } boolean percent = value.endsWith("%"); if (percent) { value = value.substring(0, value.length() - 1); } double result = Double.parseDouble(value); if (percent) { result /= 100; } return result; } private double getDpi() { return 96; } class PathCommand { public char command; public double[] params; } void showWarning(String name, String text) { if (!shownWarnings.contains(name)) { Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); shownWarnings.add(name); } } }