/* * 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.exporters.commonshape; import com.jpexs.decompiler.flash.SWF; import com.jpexs.decompiler.flash.configuration.Configuration; import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.helpers.Helper; import java.awt.Color; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Stack; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * * @author JPEXS */ public class SVGExporter { protected static final String sNamespace = "http://www.w3.org/2000/svg"; protected static final String xlinkNamespace = "http://www.w3.org/1999/xlink"; protected Document _svg; protected Element _svgDefs; protected CDATASection _svgStyle; protected Stack<Element> _svgGs = new Stack<>(); public List<Element> gradients; protected int lastPatternId; public Map<Tag, String> exportedTags = new HashMap<>(); public Map<Tag, Map<Character, String>> exportedChars = new HashMap<>(); private final Map<String, Integer> lastIds = new HashMap<>(); private final HashSet<String> fontFaces = new HashSet<>(); public boolean useTextTag = Configuration.textExportExportFontFace.get(); public SVGExporter(ExportRectangle bounds, double zoom) { DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); try { DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); DOMImplementation impl = docBuilder.getDOMImplementation(); DocumentType svgDocType = impl.createDocumentType("svg", "-//W3C//DTD SVG 1.0//EN", "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"); _svg = impl.createDocument(sNamespace, "svg", svgDocType); Element svgRoot = _svg.getDocumentElement(); svgRoot.setAttribute("xmlns:xlink", xlinkNamespace); if (bounds != null) { svgRoot.setAttribute("width", (bounds.getWidth() / SWF.unitDivisor) + "px"); svgRoot.setAttribute("height", (bounds.getHeight() / SWF.unitDivisor) + "px"); createDefGroup(bounds, null, zoom); } } catch (ParserConfigurationException ex) { Logger.getLogger(SVGExporter.class.getName()).log(Level.SEVERE, null, ex); } gradients = new ArrayList<>(); } private Element getDefs() { if (_svgDefs == null) { _svgDefs = _svg.createElement("defs"); _svg.getDocumentElement().appendChild(_svgDefs); } return _svgDefs; } private CDATASection getStyle() { if (_svgStyle == null) { Element style = _svg.createElement("style"); _svgStyle = _svg.createCDATASection(""); style.appendChild(_svgStyle); getDefs().appendChild(style); } return _svgStyle; } 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"); if (bounds != null) { Matrix mat = Matrix.getTranslateInstance(-bounds.xMin, -bounds.yMin); mat.scale(zoom); g.setAttribute("transform", mat.getSvgTransformationString(SWF.unitDivisor, 1)); } if (id != null) { g.setAttribute("id", id); } if (_svgGs.size() == 0) { _svg.getDocumentElement().appendChild(g); } else { getDefs().appendChild(g); } _svgGs.add(g); } public void endGroup() { Element g = _svgGs.pop(); if (g.getChildNodes().getLength() == 0) { g.getParentNode().removeChild(g); } } public final Element createSubGroup(Matrix transform, String id) { Element group = createSubGroup(id, "g"); if (transform != null) { group.setAttribute("transform", transform.getSvgTransformationString(SWF.unitDivisor, 1)); } return group; } public final Element createClipPath(Matrix transform, String id) { Element group = createSubGroup(id, "clipPath"); Node parent = group.getParentNode(); if (parent instanceof Element) { Element parentElement = (Element) parent; group.setAttribute("transform", parentElement.getAttribute("transform")); } return group; } private Element createSubGroup(String id, String tagName) { Element group = _svg.createElement(tagName); if (id != null) { group.setAttribute("id", id); } _svgGs.peek().appendChild(group); _svgGs.add(group); return group; } public void addToGroup(Node newChild) { _svgGs.peek().appendChild(newChild); } public void addToDefs(Node newChild) { getDefs().appendChild(newChild); } public Element createElement(String tagName) { return _svg.createElement(tagName); } public String getSVG() { TransformerFactory transformerFactory = TransformerFactory.newInstance(); StringWriter writer = new StringWriter(); try { Transformer transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); DOMSource source = new DOMSource(_svg); StreamResult result = new StreamResult(writer); transformer.transform(source, result); } catch (TransformerException ex) { Logger.getLogger(SVGExporter.class.getName()).log(Level.SEVERE, null, ex); } return writer.toString(); } public void setBackGroundColor(Color backGroundColor) { Attr attr = _svg.createAttribute("style"); attr.setValue("background: " + new RGBA(backGroundColor).toHexARGB()); } public Element addUse(Matrix transform, RECT boundRect, String href, String instanceName) { Element image = _svg.createElement("use"); if (transform != null) { image.setAttribute("transform", transform.getSvgTransformationString(SWF.unitDivisor, 1)); image.setAttribute("width", Double.toString(boundRect.getWidth() / (double) SWF.unitDivisor)); image.setAttribute("height", Double.toString(boundRect.getHeight() / (double) SWF.unitDivisor)); } if (instanceName != null) { image.setAttribute("id", instanceName); } image.setAttribute("xlink:href", "#" + href); _svgGs.peek().appendChild(image); return image; } public void addStyle(String fontFace, byte[] data, FontExportMode mode) { if (!fontFaces.contains(fontFace)) { fontFaces.add(fontFace); String base64Data = Helper.byteArrayToBase64String(data); String value = getStyle().getTextContent(); value += Helper.newLine; value += " @font-face {" + Helper.newLine; value += " font-family: \"" + fontFace + "\";" + Helper.newLine; switch (mode) { case TTF: value += " src: url('data:font/truetype;base64," + base64Data + "') format(\"truetype\");" + Helper.newLine; break; case WOFF: value += " src: url('data:font/woff;base64," + base64Data + "') format(\"woff\");" + Helper.newLine; break; } value += " }" + Helper.newLine; getStyle().setTextContent(value); } } public String getUniqueId(String prefix) { Integer lastId = lastIds.get(prefix); if (lastId == null) { lastId = 0; } else { lastId++; } lastIds.put(prefix, lastId); return prefix + lastId; } protected static double roundPixels20(double pixels) { return Math.round(pixels * 100) / 100.0; } }