/*
* 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.tags.base;
import com.jpexs.decompiler.flash.SWF;
import com.jpexs.decompiler.flash.configuration.Configuration;
import com.jpexs.decompiler.flash.exporters.FontExporter;
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
import com.jpexs.decompiler.flash.exporters.modes.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;
import com.jpexs.decompiler.flash.helpers.HighlightedText;
import com.jpexs.decompiler.flash.importers.TextImportResizeTextBoundsMode;
import com.jpexs.decompiler.flash.tags.text.JustifyAlignGlyphEntry;
import com.jpexs.decompiler.flash.tags.text.TextAlign;
import com.jpexs.decompiler.flash.tags.text.TextParseException;
import com.jpexs.decompiler.flash.types.ColorTransform;
import com.jpexs.decompiler.flash.types.DynamicTextGlyphEntry;
import com.jpexs.decompiler.flash.types.FILLSTYLE;
import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY;
import com.jpexs.decompiler.flash.types.GLYPHENTRY;
import com.jpexs.decompiler.flash.types.LINESTYLE;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
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.SHAPE;
import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE;
import com.jpexs.decompiler.flash.types.TEXTRECORD;
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.ByteArrayRange;
import com.jpexs.helpers.Helper;
import com.jpexs.helpers.SerializableImage;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.font.LineMetrics;
import java.awt.font.TextAttribute;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.w3c.dom.Element;
/**
*
* @author JPEXS
*/
public abstract class TextTag extends DrawableTag {
public TextTag(SWF swf, int id, String name, ByteArrayRange data) {
super(swf, id, name, data);
}
public abstract MATRIX getTextMatrix();
public abstract List<String> getTexts();
public abstract List<Integer> getFontIds();
public abstract HighlightedText getFormattedText(boolean ignoreLetterSpacing);
// use the texts from the "texts" argument when it is not null
public abstract boolean setFormattedText(MissingCharacterHandler missingCharHandler, String formattedText, String[] texts) throws TextParseException;
public abstract void updateTextBounds();
public abstract boolean alignText(TextAlign textAlign);
public abstract boolean translateText(int diff);
public abstract RECT getBounds();
public abstract void setBounds(RECT r);
public abstract ExportRectangle calculateTextBounds();
@Override
public RECT getRect() {
return getRect(null); // parameter not used
}
private static void updateRect(RECT ret, int x, int y) {
if (x < ret.Xmin) {
ret.Xmin = x;
}
if (x > ret.Xmax) {
ret.Xmax = x;
}
if (y < ret.Ymin) {
ret.Ymin = y;
}
if (y > ret.Ymax) {
ret.Ymax = y;
}
}
public static void alignText(SWF swf, List<TEXTRECORD> textRecords, TextAlign textAlign) {
// Remove Justify align entries
for (TEXTRECORD tr : textRecords) {
for (int i = 0; i < tr.glyphEntries.size(); i++) {
GLYPHENTRY ge = tr.glyphEntries.get(i);
if (ge instanceof JustifyAlignGlyphEntry) {
JustifyAlignGlyphEntry jge = (JustifyAlignGlyphEntry) ge;
ge = new GLYPHENTRY();
ge.glyphAdvance = jge.originalAdvance;
ge.glyphIndex = jge.glyphIndex;
tr.glyphEntries.set(i, ge);
}
}
}
int xMin = Integer.MAX_VALUE;
int maxWidth = 0;
for (TEXTRECORD tr : textRecords) {
int xOffset = tr.styleFlagsHasXOffset ? tr.xOffset : 0;
if (xOffset < xMin) {
xMin = xOffset;
}
int width = tr.getTotalAdvance();
if (width > maxWidth) {
maxWidth = width;
}
}
FontTag font = null;
for (TEXTRECORD tr : textRecords) {
if (tr.styleFlagsHasFont) {
FontTag font2 = swf.getFont(tr.fontId);
if (font2 != null) {
font = font2;
}
}
int width = tr.getTotalAdvance();
switch (textAlign) {
case LEFT:
tr.xOffset = xMin;
tr.styleFlagsHasXOffset = true;
break;
case CENTER:
tr.xOffset = xMin + (maxWidth - width) / 2;
tr.styleFlagsHasXOffset = true;
break;
case RIGHT:
tr.xOffset = xMin + maxWidth - width;
tr.styleFlagsHasXOffset = true;
break;
case JUSTIFY:
tr.xOffset = xMin;
tr.styleFlagsHasXOffset = true;
if (font != null) {
int diff = maxWidth - width;
if (diff > 0) {
int spaces = 0;
int spaces2 = 0;
int state = 0;
List<GLYPHENTRY> glyphEntries = new ArrayList<>();
List<GLYPHENTRY> glyphEntries2 = new ArrayList<>();
for (GLYPHENTRY ge : tr.glyphEntries) {
char ch = font.glyphToChar(ge.glyphIndex);
boolean whitespace = Character.isWhitespace(ch);
switch (state) {
case 0:
if (!whitespace) {
state = 1;
}
break;
case 1:
if (whitespace) {
spaces2++;
glyphEntries2.add(ge);
} else {
spaces += spaces2;
spaces2 = 0;
glyphEntries.addAll(glyphEntries2);
glyphEntries2.clear();
}
break;
}
}
if (spaces > 0 && glyphEntries.size() > 0) {
int fix = diff / spaces;
int remaining = diff - fix * spaces;
for (GLYPHENTRY ge : glyphEntries) {
int diff2 = fix;
if (remaining-- > 0) {
diff2++;
}
JustifyAlignGlyphEntry jge = new JustifyAlignGlyphEntry();
jge.originalAdvance = ge.glyphAdvance;
jge.glyphAdvance = ge.glyphAdvance + diff2;
jge.glyphIndex = ge.glyphIndex;
int idx = tr.glyphEntries.indexOf(ge);
tr.glyphEntries.set(idx, jge);
}
}
}
}
break;
}
}
}
public static Map<String, Object> getTextRecordsAttributes(List<TEXTRECORD> list, SWF swf) {
Map<String, Object> att = new HashMap<>();
RECT textBounds = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE);
FontTag font = null;
int x = 0;
int y = 0;
int textHeight = 12;
int lineSpacing = 0;
double leading = 0;
double ascent = 0;
double descent = 0;
double lineDistance = 0;
List<SHAPE> glyphs = null;
boolean firstLine = true;
double top = 0;
List<Integer> allLeftMargins = new ArrayList<>();
List<Integer> allLetterSpacings = new ArrayList<>();
FontMetrics fontMetrics;
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
Graphics graphics = bi.getGraphics();
Font aFont = null;
int currentLeftMargin;
for (int r = 0; r < list.size(); r++) {
TEXTRECORD rec = list.get(r);
if (rec.styleFlagsHasFont) {
FontTag font2 = swf.getFont(rec.fontId);
if (font2 != null) {
font = font2;
}
textHeight = rec.textHeight;
if (font == null) {
Logger.getLogger(TextTag.class.getName()).log(Level.SEVERE, "Font with id=" + rec.fontId + " was not found.");
continue;
}
glyphs = font.getGlyphShapeTable();
if (!font.hasLayout()) {
String fontName = FontTag.getFontNameWithFallback(font.getFontNameIntag());
aFont = new Font(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor));
Map<TextAttribute, Integer> attr = new HashMap<>();
attr.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
attr.put(TextAttribute.LIGATURES, TextAttribute.LIGATURES_ON);
aFont = aFont.deriveFont(attr);
fontMetrics = graphics.getFontMetrics(aFont);
LineMetrics lm = fontMetrics.getLineMetrics("A", graphics);
ascent = lm.getAscent();
descent = lm.getDescent();
leading = lm.getLeading();
lineDistance = ascent + descent;
} else {
leading = ((double) font.getLeading() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor);
ascent = ((double) font.getAscent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor);
descent = ((double) font.getDescent() * textHeight / 1024.0 / font.getDivider() / SWF.unitDivisor);
lineDistance = ascent + descent;
}
}
currentLeftMargin = 0;
if (rec.styleFlagsHasXOffset) {
x = rec.xOffset;
currentLeftMargin = x;
}
if (rec.styleFlagsHasYOffset) {
if (!firstLine) {
top += ascent + descent;
int topint = (int) (Math.round(top) * SWF.unitDivisor);
lineSpacing = rec.yOffset - topint;
top += lineSpacing / SWF.unitDivisor;
} else {
top = ascent;
}
y = rec.yOffset;
}
firstLine = false;
allLeftMargins.add(currentLeftMargin);
if (glyphs == null) {
Logger.getLogger(TextTag.class.getName()).log(Level.SEVERE, "Glyphs not found.");
continue;
}
int letterSpacing = Integer.MAX_VALUE;
for (int e = 0; e < rec.glyphEntries.size(); e++) {
GLYPHENTRY entry = rec.glyphEntries.get(e);
GLYPHENTRY nextEntry = null;
if (e < rec.glyphEntries.size() - 1) {
nextEntry = rec.glyphEntries.get(e + 1);
}
RECT rect = SHAPERECORD.getBounds(glyphs.get(entry.glyphIndex).shapeRecords);
rect.Xmax = (int) Math.round(((double) rect.Xmax * textHeight) / (font.getDivider() * 1024));
rect.Xmin = (int) Math.round(((double) rect.Xmin * textHeight) / (font.getDivider() * 1024));
rect.Ymax = (int) Math.round(((double) rect.Ymax * textHeight) / (font.getDivider() * 1024));
rect.Ymin = (int) Math.round(((double) rect.Ymin * textHeight) / (font.getDivider() * 1024));
updateRect(textBounds, x + rect.Xmin, y + rect.Ymin);
updateRect(textBounds, x + rect.Xmax, y + rect.Ymax);
int adv = entry.glyphAdvance;
int defaultAdvance;
if (font.hasLayout()) {
int kerningAdjustment = 0;
if (nextEntry != null) {
kerningAdjustment = font.getGlyphKerningAdjustment(entry.glyphIndex, nextEntry.glyphIndex);
}
defaultAdvance = (int) (Math.round(textHeight * (font.getGlyphAdvance(entry.glyphIndex) + kerningAdjustment) / (1024.0 * font.getDivider())));
} else {
defaultAdvance = (int) Math.round(SWF.unitDivisor * FontTag.getSystemFontAdvance(aFont, font.glyphToChar(entry.glyphIndex), nextEntry == null ? null : font.glyphToChar(nextEntry.glyphIndex)));
}
int newLetterSpacing = adv - defaultAdvance;
if (e == 0 || e == rec.glyphEntries.size() - 1) {
if (rec.glyphEntries.size() == 1) {
letterSpacing = 0;
}
} else if (newLetterSpacing < letterSpacing) {
letterSpacing = newLetterSpacing;
}
x += adv;
}
allLetterSpacings.add(letterSpacing);
}
att.put("indent", 0); //?
att.put("rightMargin", 0); //?
att.put("lineSpacing", lineSpacing);
att.put("textBounds", textBounds);
att.put("allLeftMargins", allLeftMargins);
att.put("allLetterSpacings", allLetterSpacings);
return att;
}
public static SHAPE getBorderShape(RGB borderColor, RGB fillColor, RECT rect) {
SHAPEWITHSTYLE shape = new SHAPEWITHSTYLE();
shape.fillStyles = new FILLSTYLEARRAY();
if (fillColor != null) {
shape.fillStyles.fillStyles = new FILLSTYLE[1];
FILLSTYLE fillStyle = new FILLSTYLE();
fillStyle.fillStyleType = FILLSTYLE.SOLID;
fillStyle.color = fillColor;
shape.fillStyles.fillStyles[0] = fillStyle;
} else {
shape.fillStyles.fillStyles = new FILLSTYLE[0];
}
shape.lineStyles = new LINESTYLEARRAY();
shape.lineStyles.lineStyles = new LINESTYLE[1];
LINESTYLE lineStyle = new LINESTYLE();
lineStyle.color = borderColor;
lineStyle.width = 20;
shape.lineStyles.lineStyles[0] = lineStyle;
shape.shapeRecords = new ArrayList<>();
StyleChangeRecord style = new StyleChangeRecord();
style.lineStyle = 1;
style.stateLineStyle = true;
if (fillColor != null) {
style.stateFillStyle0 = true;
style.fillStyle0 = 1;
}
style.stateMoveTo = true;
shape.shapeRecords.add(style);
StraightEdgeRecord top = new StraightEdgeRecord();
top.generalLineFlag = true;
top.deltaX = rect.getWidth();
StraightEdgeRecord right = new StraightEdgeRecord();
right.generalLineFlag = true;
right.deltaY = rect.getHeight();
StraightEdgeRecord bottom = new StraightEdgeRecord();
bottom.generalLineFlag = true;
bottom.deltaX = -rect.getWidth();
StraightEdgeRecord left = new StraightEdgeRecord();
left.generalLineFlag = true;
left.deltaY = -rect.getHeight();
shape.shapeRecords.add(top);
shape.shapeRecords.add(right);
shape.shapeRecords.add(bottom);
shape.shapeRecords.add(left);
shape.shapeRecords.add(new EndShapeRecord());
return shape;
}
public static void drawBorder(SWF swf, SerializableImage image, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) {
Graphics2D g = (Graphics2D) image.getGraphics();
Matrix mat = transformation.clone();
mat = mat.concatenate(new Matrix(textMatrix));
BitmapExporter.export(swf, getBorderShape(borderColor, fillColor, rect), null, image, mat, mat, colorTransform);
}
public static void drawBorderHtmlCanvas(SWF swf, StringBuilder result, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, ColorTransform colorTransform, double unitDivisor) {
Matrix mat = new Matrix(textMatrix);
result.append("\tctx.save();\r\n");
result.append("\tctx.transform(").append(mat.scaleX).append(",").append(mat.rotateSkew0).append(",").append(mat.rotateSkew1).append(",").append(mat.scaleY).append(",").append(mat.translateX).append(",").append(mat.translateY).append(");\r\n");
SHAPE shape = getBorderShape(borderColor, fillColor, rect);
CanvasShapeExporter cse = new CanvasShapeExporter(null, unitDivisor, swf, shape, colorTransform, 0, 0);
cse.export();
result.append(cse.getShapeData());
result.append("\tctx.restore();\r\n");
}
public static void drawBorderSVG(SWF swf, SVGExporter exporter, RGB borderColor, RGB fillColor, RECT rect, MATRIX textMatrix, ColorTransform colorTransform, double zoom) {
exporter.createSubGroup(new Matrix(textMatrix), null);
SHAPE shape = getBorderShape(borderColor, fillColor, rect);
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform, zoom);
shapeExporter.export();
exporter.endGroup();
}
public static void staticTextToImage(SWF swf, List<TEXTRECORD> textRecords, int numText, SerializableImage image, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) {
int textColor = 0;
FontTag font = null;
int textHeight = 12;
int x = 0;
int y = 0;
List<SHAPE> glyphs = null;
Matrix mat0 = transformation.clone();
mat0 = mat0.concatenate(new Matrix(textMatrix));
for (TEXTRECORD rec : textRecords) {
if (rec.styleFlagsHasColor) {
if (numText == 2) {
textColor = rec.textColorA.toInt();
} else {
textColor = rec.textColor.toInt();
}
if (colorTransform != null) {
textColor = colorTransform.apply(textColor);
}
}
if (rec.styleFlagsHasFont) {
FontTag font2 = swf.getFont(rec.fontId);
if (font2 != null) {
font = font2;
}
glyphs = font == null ? null : font.getGlyphShapeTable();
textHeight = rec.textHeight;
}
if (rec.styleFlagsHasXOffset) {
x = rec.xOffset;
}
if (rec.styleFlagsHasYOffset) {
y = rec.yOffset;
}
double divider = font == null ? 1 : font.getDivider();
double rat = textHeight / 1024.0 / divider;
Matrix matScale = Matrix.getScaleInstance(rat);
Color textColor2 = new Color(textColor, true);
for (GLYPHENTRY entry : rec.glyphEntries) {
matScale.translateX = x;
matScale.translateY = y;
Matrix mat = mat0.concatenate(matScale);
SHAPE shape = null;
if (entry.glyphIndex != -1 && glyphs != null) {
// shapeNum: 1
shape = glyphs.get(entry.glyphIndex);
} else if (entry instanceof DynamicTextGlyphEntry) {
DynamicTextGlyphEntry dynamicEntry = (DynamicTextGlyphEntry) entry;
if (dynamicEntry.fontFace != null) {
FontTag fnt = swf.getFontByName(dynamicEntry.fontFace);
if (fnt != null && entry.glyphIndex != -1) {
shape = fnt.getGlyphShapeTable().get(entry.glyphIndex);
} else {
shape = SHAPERECORD.fontCharacterToSHAPE(new Font(dynamicEntry.fontFace, dynamicEntry.fontStyle, 12), (int) Math.round(divider * 1024), dynamicEntry.character);
}
}
}
if (shape != null) {
BitmapExporter.export(swf, shape, textColor2, image, mat, mat, colorTransform);
if (SHAPERECORD.DRAW_BOUNDING_BOX) {
RGB borderColor = new RGBA(Color.black);
RGB fillColor = new RGBA(new Color(255, 255, 255, 0));
RECT bounds = shape.getBounds();
mat = Matrix.getTranslateInstance(bounds.Xmin, bounds.Ymin).preConcatenate(mat);
TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), mat, colorTransform);
}
}
x += entry.glyphAdvance;
}
}
}
public static ExportRectangle calculateTextBounds(SWF swf, List<TEXTRECORD> textRecords, MATRIX textMatrix) {
FontTag font = null;
int textHeight = 12;
int x = 0;
int y = 0;
List<SHAPE> glyphs = null;
ExportRectangle result = null;
for (TEXTRECORD rec : textRecords) {
if (rec.styleFlagsHasFont) {
font = swf.getFont(rec.fontId);
glyphs = font == null ? null : font.getGlyphShapeTable();
textHeight = rec.textHeight;
}
if (rec.styleFlagsHasXOffset) {
x = rec.xOffset;
}
if (rec.styleFlagsHasYOffset) {
y = rec.yOffset;
}
double rat = textHeight / 1024.0 / (font == null ? 1 : font.getDivider());
for (GLYPHENTRY entry : rec.glyphEntries) {
Matrix mat = new Matrix();
mat = mat.concatenate(new Matrix(textMatrix));
Matrix matTr = Matrix.getTranslateInstance(x, y);
mat = mat.concatenate(matTr);
mat = mat.concatenate(Matrix.getScaleInstance(rat));
if (entry.glyphIndex != -1 && glyphs != null) {
// shapeNum: 1
SHAPE shape = glyphs.get(entry.glyphIndex);
RECT glyphBounds = shape.getBounds();
int glyphWidth = glyphBounds.getWidth();
int glyphHeight = glyphBounds.getHeight();
glyphBounds.Xmin -= glyphWidth / 2;
glyphBounds.Ymin -= glyphHeight / 2;
glyphBounds.Xmax += glyphWidth;
glyphBounds.Ymax += glyphHeight;
if (glyphBounds.Xmax > glyphBounds.Xmin && glyphBounds.Ymax > glyphBounds.Ymin) {
ExportRectangle rect = mat.transform(new ExportRectangle(glyphBounds));
if (result == null) {
result = rect;
} else {
result.xMin = Math.min(result.xMin, rect.xMin);
result.yMin = Math.min(result.yMin, rect.yMin);
result.xMax = Math.max(result.xMax, rect.xMax);
result.yMax = Math.max(result.yMax, rect.yMax);
}
}
x += entry.glyphAdvance;
}
}
}
return result;
}
protected void updateTextBounds(RECT textBounds) {
TextImportResizeTextBoundsMode resizeMode = Configuration.textImportResizeTextBoundsMode.get();
if (resizeMode != null && (resizeMode.equals(TextImportResizeTextBoundsMode.GROW_ONLY) || resizeMode.equals(TextImportResizeTextBoundsMode.GROW_AND_SHRINK))) {
ExportRectangle newBounds = calculateTextBounds();
if (newBounds != null) {
int xMin = (int) Math.floor(newBounds.xMin);
int yMin = (int) Math.floor(newBounds.yMin);
int xMax = (int) Math.ceil(newBounds.xMax);
int yMax = (int) Math.ceil(newBounds.yMax);
if (resizeMode.equals(TextImportResizeTextBoundsMode.GROW_ONLY)) {
textBounds.Xmin = Math.min(xMin, textBounds.Xmin);
textBounds.Ymin = Math.min(yMin, textBounds.Ymin);
textBounds.Xmax = Math.max(xMax, textBounds.Xmax);
textBounds.Ymax = Math.max(yMax, textBounds.Ymax);
} else if (resizeMode.equals(TextImportResizeTextBoundsMode.GROW_AND_SHRINK)) {
textBounds.Xmin = xMin;
textBounds.Ymin = yMin;
textBounds.Xmax = xMax;
textBounds.Ymax = yMax;
}
}
}
}
public static void staticTextToHtmlCanvas(double unitDivisor, SWF swf, List<TEXTRECORD> textRecords, int numText, StringBuilder result, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform) {
int textColor = 0;
FontTag font = null;
int fontId = -1;
int textHeight = 12;
int x = 0;
int y = 0;
List<SHAPE> glyphs = new ArrayList<>();
for (TEXTRECORD rec : textRecords) {
if (rec.styleFlagsHasColor) {
if (numText == 2) {
textColor = rec.textColorA.toInt();
} else {
textColor = rec.textColor.toInt();
}
if (colorTransform != null) {
textColor = colorTransform.apply(textColor);
}
}
if (rec.styleFlagsHasFont) {
font = swf.getFont(rec.fontId);
fontId = rec.fontId;
glyphs = font.getGlyphShapeTable();
textHeight = rec.textHeight;
}
if (rec.styleFlagsHasXOffset) {
x = rec.xOffset;
}
if (rec.styleFlagsHasYOffset) {
y = rec.yOffset;
}
double rat = textHeight / 1024.0 / font.getDivider();
result.append("\tvar textColor = ").append(CanvasShapeExporter.color(textColor)).append(";\r\n");
for (GLYPHENTRY entry : rec.glyphEntries) {
Matrix mat = (new Matrix(textMatrix).concatenate(Matrix.getTranslateInstance(x, y))).concatenate(Matrix.getScaleInstance(rat));
if (entry.glyphIndex != -1) {
// shapeNum: 1
result.append("\tctx.save();\r\n");
result.append("\tctx.transform(").append(mat.scaleX).append(",").append(mat.rotateSkew0).append(",").append(mat.rotateSkew1).append(",").append(mat.scaleY).append(",").append(mat.translateX).append(",").append(mat.translateY).append(");\r\n");
result.append("\tfont").append(fontId).append("(ctx,\"").append(("" + font.glyphToChar(entry.glyphIndex)).replace("\\", "\\\\").replace("\"", "\\\"")).append("\",textColor);\r\n");
result.append("\tctx.restore();\r\n");
x += entry.glyphAdvance;
}
}
}
}
public static void staticTextToSVG(SWF swf, List<TEXTRECORD> textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform, double zoom) {
int textColor = 0;
FontTag font = null;
int textHeight = 12;
int x = 0;
int y = 0;
List<SHAPE> glyphs = new ArrayList<>();
for (TEXTRECORD rec : textRecords) {
if (rec.styleFlagsHasColor) {
if (numText == 2) {
textColor = rec.textColorA.toInt();
} else {
textColor = rec.textColor.toInt();
}
if (colorTransform != null) {
textColor = colorTransform.apply(textColor);
}
}
if (rec.styleFlagsHasFont) {
font = swf.getFont(rec.fontId);
glyphs = font.getGlyphShapeTable();
textHeight = rec.textHeight;
}
int offsetX = 0;
int offsetY = 0;
if (rec.styleFlagsHasXOffset) {
offsetX = rec.xOffset;
x = offsetX;
}
if (rec.styleFlagsHasYOffset) {
offsetY = rec.yOffset;
y = offsetY;
}
double rat = textHeight / 1024.0 / font.getDivider();
exporter.createSubGroup(new Matrix(textMatrix), null);
if (exporter.useTextTag) {
StringBuilder text = new StringBuilder();
int totalAdvance = 0;
for (GLYPHENTRY entry : rec.glyphEntries) {
if (entry.glyphIndex != -1) {
char ch = font.glyphToChar(entry.glyphIndex);
text.append(ch);
totalAdvance += entry.glyphAdvance;
}
}
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.getFontNameIntag());
textElement.setAttribute("textLength", Double.toString(totalAdvance / SWF.unitDivisor));
textElement.setAttribute("lengthAdjust", "spacing");
textElement.setTextContent(text.toString());
RGBA colorA = new RGBA(textColor);
textElement.setAttribute("fill", colorA.toHexRGB());
if (colorA.alpha != 255) {
textElement.setAttribute("fill-opacity", Float.toString(colorA.getAlphaFloat()));
}
exporter.addToGroup(textElement);
FontExportMode fontExportMode = FontExportMode.WOFF;
exporter.addStyle(font.getFontNameIntag(), new FontExporter().exportFont(font, fontExportMode), fontExportMode);
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<Character, String> 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.getFontNameIntag() + "_" + ch));
exporter.createDefGroup(null, charId);
SVGShapeExporter shapeExporter = new SVGShapeExporter(swf, shape, exporter, null, colorTransform, zoom);
shapeExporter.export();
exporter.endGroup();
chs.put(ch, charId);
}
Element charImage = exporter.addUse(mat, bounds, charId, 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;
}
}
}
exporter.endGroup();
}
}
@Override
public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) {
RECT r = getBounds();
Shape shp = new Rectangle.Double(r.Xmin, r.Ymin, r.getWidth(), r.getHeight());
return transformation.toTransform().createTransformedShape(shp); //TODO: match character shapes (?)
}
}