From 7e3a38bda4606a7ff4e889e34af8bde40168270c Mon Sep 17 00:00:00 2001 From: Honfika Date: Sat, 26 Apr 2014 15:03:10 +0200 Subject: [PATCH] svg export: text export as real text --- trunk/src/com/jpexs/decompiler/flash/SWF.java | 2 +- .../flash/configuration/Configuration.java | 3 + .../flash/exporters/FontExporter.java | 23 ++-- .../exporters/commonshape/SVGExporter.java | 50 ++++++++- .../decompiler/flash/tags/base/TextTag.java | 104 ++++++++++++------ 5 files changed, 131 insertions(+), 51 deletions(-) diff --git a/trunk/src/com/jpexs/decompiler/flash/SWF.java b/trunk/src/com/jpexs/decompiler/flash/SWF.java index 1e74eefee..d7aa3b96f 100644 --- a/trunk/src/com/jpexs/decompiler/flash/SWF.java +++ b/trunk/src/com/jpexs/decompiler/flash/SWF.java @@ -2291,7 +2291,7 @@ public final class SWF implements TreeItem, Timelined { // TODO: if (layer.filters != null) // TODO: if (layer.blendMode > 1) Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addImage(mat, boundRect, assetName); + exporter.addUse(mat, boundRect, assetName); // TODO: if (layer.clipDepth > -1)... } diff --git a/trunk/src/com/jpexs/decompiler/flash/configuration/Configuration.java b/trunk/src/com/jpexs/decompiler/flash/configuration/Configuration.java index 2ee7f8bdb..8117eb239 100644 --- a/trunk/src/com/jpexs/decompiler/flash/configuration/Configuration.java +++ b/trunk/src/com/jpexs/decompiler/flash/configuration/Configuration.java @@ -274,6 +274,9 @@ public class Configuration { @ConfigurationDefaultBoolean(true) public static final ConfigurationItem packJavaScripts = null; + @ConfigurationDefaultBoolean(false) + public static final ConfigurationItem textExportExportFontFace = null; + private enum OSId { WINDOWS, OSX, UNIX diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/FontExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/FontExporter.java index 274475c99..a87e1600d 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/FontExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/FontExporter.java @@ -27,6 +27,7 @@ import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.tags.base.FontTag; import com.jpexs.decompiler.flash.types.ColorTransform; import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.helpers.Helper; import fontastic.FGlyph; import fontastic.FPoint; import fontastic.Fontastic; @@ -35,6 +36,8 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; /** * @@ -76,6 +79,17 @@ public class FontExporter { return ret; } + public byte[] exportFont(final FontTag t, FontExportMode mode) { + try { + File f = File.createTempFile("temp", ".ttf"); + exportFont(t, mode, f); + return Helper.readFile(f.getPath()); + } catch (IOException ex) { + Logger.getLogger(FontExporter.class.getName()).log(Level.SEVERE, null, ex); + } + return new byte[0]; + } + public void exportFont(final FontTag t, FontExportMode mode, File file) throws IOException { List shapes = t.getGlyphShapeTable(); Fontastic f = new Fontastic(t.getFontName(), file); @@ -86,7 +100,6 @@ public class FontExporter { f.setVersion("1.0"); f.setAscender(t.getAscent() / t.getDivider()); f.setDescender(t.getDescent() / t.getDivider()); - //f.set for (int i = 0; i < shapes.size(); i++) { SHAPE s = shapes.get(i); @@ -102,8 +115,6 @@ public class FontExporter { } List path = new ArrayList<>(); - private double lastX = 0; - private double lastY = 0; @Override protected void finalizePath() { @@ -117,22 +128,16 @@ public class FontExporter { @Override public void moveTo(double x, double y) { finalizePath(); - lastX = x; - lastY = y; path.add(new FPoint(new PVector(transformX(x), transformY(y)))); } @Override public void lineTo(double x, double y) { - lastX = x; - lastY = y; path.add(new FPoint(new PVector(transformX(x), transformY(y)))); } @Override public void curveTo(double controlX, double controlY, double anchorX, double anchorY) { - lastX = anchorX; - lastY = anchorY; path.add(new FPoint( new PVector(transformX(anchorX), transformY(anchorY)), new PVector(transformX(controlX), transformY(controlY)) diff --git a/trunk/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java b/trunk/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java index 78b54ea26..508a41d68 100644 --- a/trunk/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java +++ b/trunk/src/com/jpexs/decompiler/flash/exporters/commonshape/SVGExporter.java @@ -17,18 +17,22 @@ 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.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.bind.DatatypeConverter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -39,6 +43,7 @@ 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; @@ -56,12 +61,15 @@ public class SVGExporter { protected Document _svg; protected Element _svgDefs; + protected CDATASection _svgStyle; protected Stack _svgGs = new Stack<>(); public List gradients; protected int lastPatternId; public Map exportedTags = new HashMap<>(); public Map> exportedChars = new HashMap<>(); - private Map lastIds = new HashMap<>(); + private final Map lastIds = new HashMap<>(); + private final HashSet fontFaces = new HashSet<>(); + public boolean useTextTag = Configuration.textExportExportFontFace.get(); public SVGExporter(ExportRectangle bounds) { @@ -74,8 +82,6 @@ public class SVGExporter { _svg = impl.createDocument(sNamespace, "svg", svgDocType); Element svgRoot = _svg.getDocumentElement(); svgRoot.setAttribute("xmlns:xlink", xlinkNamespace); - _svgDefs = _svg.createElement("defs"); - svgRoot.appendChild(_svgDefs); if (bounds != null) { svgRoot.setAttribute("width", (bounds.getWidth() / SWF.unitDivisor) + "px"); svgRoot.setAttribute("height", (bounds.getHeight() / SWF.unitDivisor) + "px"); @@ -87,6 +93,24 @@ public class SVGExporter { 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); + _svgDefs.appendChild(style); + } + return _svgStyle; + } + public final void createDefGroup(ExportRectangle bounds, String id) { Element g = _svg.createElement("g"); if (bounds != null) { @@ -99,7 +123,7 @@ public class SVGExporter { if (_svgGs.size() == 0) { _svg.getDocumentElement().appendChild(g); } else { - _svgDefs.appendChild(g); + getDefs().appendChild(g); } _svgGs.add(g); } @@ -133,7 +157,7 @@ public class SVGExporter { } public void addToDefs(Node newChild) { - _svgDefs.appendChild(newChild); + getDefs().appendChild(newChild); } public Element createElement(String tagName) { @@ -161,7 +185,7 @@ public class SVGExporter { attr.setValue("background: " + new RGBA(backGroundColor).toHexARGB()); } - public Element addImage(Matrix transform, RECT boundRect, String href) { + public Element addUse(Matrix transform, RECT boundRect, String href) { Element image = _svg.createElement("use"); if (transform != null) { double translateX = roundPixels400(transform.translateX / SWF.unitDivisor); @@ -180,6 +204,20 @@ public class SVGExporter { return image; } + public void addStyle(String fontFace, byte[] data) { + if (!fontFaces.contains(fontFace)){ + fontFaces.add(fontFace); + String base64Data = DatatypeConverter.printBase64Binary(data); + String value = getStyle().getTextContent(); + value += Helper.newLine; + value += " @font-face {" + Helper.newLine; + value += " font-family: \"" + fontFace + "\";" + Helper.newLine; + value += " src: url('data:font/truetype;base64,[" + base64Data + "]') format(\"truetype\");" + Helper.newLine; + value += " }" + Helper.newLine; + getStyle().setTextContent(value); + } + } + public String getUniqueId(String prefix) { Integer lastId = lastIds.get(prefix); if (lastId == null) { diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java index a0268132f..b22ef6cac 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -17,8 +17,10 @@ package com.jpexs.decompiler.flash.tags.base; import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.FontExporter; import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.modes.FontExportMode; import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; import com.jpexs.decompiler.flash.exporters.shape.SVGShapeExporter; @@ -384,54 +386,86 @@ public abstract class TextTag extends CharacterTag implements BoundedTag, Drawab glyphs = font.getGlyphShapeTable(); textHeight = rec.textHeight; } + int offsetX = 0; + int offsetY = 0; if (rec.styleFlagsHasXOffset) { - x = rec.xOffset; + offsetX = rec.xOffset; + x = offsetX; } if (rec.styleFlagsHasYOffset) { - y = rec.yOffset; + offsetY = rec.yOffset; + y = offsetY; } double rat = textHeight / 1024.0 / font.getDivider(); - for (GLYPHENTRY entry : rec.glyphEntries) { - Matrix mat = (new Matrix(textMatrix).concatenate(Matrix.getTranslateInstance(x - bounds.Xmin, y - bounds.Ymin))).concatenate(Matrix.getScaleInstance(rat)); - if (entry.glyphIndex != -1) { - // shapeNum: 1 - SHAPE shape = glyphs.get(entry.glyphIndex); - char ch = font.glyphToChar(entry.glyphIndex); - - String charId = null; - Map chs; - if (exporter.exportedChars.containsKey(font)) { - chs = exporter.exportedChars.get(font); - if (chs.containsKey(ch)) { - charId = chs.get(ch); - } - } else { - chs = new HashMap<>(); - exporter.exportedChars.put(font, chs); - } - - if (charId == null) { - charId = exporter.getUniqueId(Helper.getValidHtmlId("font_" + font.getFontName() + "_" + ch)); - exporter.createDefGroup(null, charId); - SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform); - shapeExporter.export(); - exporter.endGroup(); - chs.put(ch, charId); + exporter.createSubGroup(new Matrix(textMatrix), null); + if (true || exporter.useTextTag) { + StringBuilder text = new StringBuilder(); + for (GLYPHENTRY entry : rec.glyphEntries) { + if (entry.glyphIndex != -1) { + char ch = font.glyphToChar(entry.glyphIndex); + text.append(ch); } + } - Element charImage = exporter.addImage(mat, bounds, charId); - if (textColor != null) { - RGBA colorA = new RGBA(textColor); - charImage.setAttribute("fill", colorA.toHexRGB()); - if (colorA.alpha != 255) { - charImage.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); + boolean hasOffset = offsetX != 0 || offsetY != 0; + if (hasOffset) { + exporter.createSubGroup(Matrix.getTranslateInstance(offsetX, offsetY), null); + } + + Element textElement = exporter.createElement("text"); + textElement.setAttribute("font-size", Double.toString(rat * 1024)); + textElement.setAttribute("font-family", font.getFontName()); + textElement.setTextContent(text.toString()); + exporter.addToGroup(textElement); + exporter.addStyle(font.getFontName(), new FontExporter().exportFont(font, FontExportMode.TTF)); + + if (hasOffset) { + exporter.endGroup(); + } + } else { + for (GLYPHENTRY entry : rec.glyphEntries) { + Matrix mat = Matrix.getTranslateInstance(x, y).concatenate(Matrix.getScaleInstance(rat)); + if (entry.glyphIndex != -1) { + // shapeNum: 1 + SHAPE shape = glyphs.get(entry.glyphIndex); + char ch = font.glyphToChar(entry.glyphIndex); + + String charId = null; + Map chs; + if (exporter.exportedChars.containsKey(font)) { + chs = exporter.exportedChars.get(font); + if (chs.containsKey(ch)) { + charId = chs.get(ch); + } + } else { + chs = new HashMap<>(); + exporter.exportedChars.put(font, chs); } + + if (charId == null) { + charId = exporter.getUniqueId(Helper.getValidHtmlId("font_" + font.getFontName() + "_" + ch)); + exporter.createDefGroup(null, charId); + SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform); + shapeExporter.export(); + exporter.endGroup(); + chs.put(ch, charId); + } + + Element charImage = exporter.addUse(mat, bounds, charId); + if (textColor != null) { + RGBA colorA = new RGBA(textColor); + charImage.setAttribute("fill", colorA.toHexRGB()); + if (colorA.alpha != 255) { + charImage.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat())); + } + } + x += entry.glyphAdvance; } - x += entry.glyphAdvance; } } + exporter.endGroup(); } }