mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-07-01 16:04:35 +00:00
Fixed: #2471 PDF export - ignore control characters Fixed: #2471 SVG export with typeface - white-space:pre style
This commit is contained in:
@@ -0,0 +1,376 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2025 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
|
||||
import com.jpexs.decompiler.flash.types.GLYPHENTRY;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
import com.jpexs.decompiler.flash.types.SHAPE;
|
||||
import com.jpexs.decompiler.flash.types.TEXTRECORD;
|
||||
import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord;
|
||||
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 java.awt.Point;
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Font size / orientation normalizer.
|
||||
* Does following:
|
||||
* - shrinks oversized fonts to 1024 em
|
||||
* - fixes vertically flipped fonts / texts
|
||||
* - fixes zero/1unit spaces font glyph advance
|
||||
* - fixes zero last glyph advance in texts
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class FontNormalizer {
|
||||
|
||||
/**
|
||||
* Normalizes fonts in the SWF file in place.
|
||||
* @param swf SWF
|
||||
*/
|
||||
public void normalizeFonts(SWF swf) {
|
||||
normalizeFonts(swf, true, new LinkedHashMap<>(), new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes fonts in the SWF file creating clones of font/text tags.
|
||||
* @param swf SWF file
|
||||
* @param outFonts Modified fonts (clone) - fontId to fontTag
|
||||
* @param outTexts Modified texts (clone) - textId to textTag
|
||||
*/
|
||||
public void normalizeFonts(SWF swf, Map<Integer, FontTag> outFonts, Map<Integer, StaticTextTag> outTexts) {
|
||||
normalizeFonts(swf, false, outFonts, outTexts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes fonts in the SWF file.
|
||||
* @param swf SWF file
|
||||
* @param inPlace Modify tags in SWF file (true) or create clones (false)
|
||||
* @param outFonts Modified fonts - fontId to fontTag
|
||||
* @param outTexts Modified texts - textId to textTag
|
||||
*/
|
||||
public void normalizeFonts(SWF swf, boolean inPlace, Map<Integer, FontTag> outFonts, Map<Integer, StaticTextTag> outTexts) {
|
||||
Map<Integer, CharacterTag> characters = swf.getCharacters(!inPlace);
|
||||
|
||||
Map<Integer, StaticTextTag> texts = new LinkedHashMap<>();
|
||||
|
||||
for (int characterId : characters.keySet()) {
|
||||
CharacterTag character = characters.get(characterId);
|
||||
if (character instanceof StaticTextTag) {
|
||||
texts.put(characterId, (StaticTextTag) character);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Integer> invertedFontIds = new LinkedHashSet<>();
|
||||
Set<Integer> notInvertedFontIds = new LinkedHashSet<>();
|
||||
Set<Integer> fontIds = new LinkedHashSet<>();
|
||||
|
||||
for (StaticTextTag text : texts.values()) {
|
||||
boolean inverted = false;
|
||||
if (text.textMatrix != null) {
|
||||
if (text.textMatrix.scaleY < 0) {
|
||||
inverted = true;
|
||||
}
|
||||
}
|
||||
for (TEXTRECORD rec : text.textRecords) {
|
||||
if (rec.styleFlagsHasFont) {
|
||||
if (inverted) {
|
||||
invertedFontIds.add(rec.fontId);
|
||||
} else {
|
||||
notInvertedFontIds.add(rec.fontId);
|
||||
}
|
||||
fontIds.add(rec.fontId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Map<Integer, Double> fontNewScale = new LinkedHashMap<>();
|
||||
|
||||
for (int fontId : fontIds) {
|
||||
if (notInvertedFontIds.contains(fontId)) {
|
||||
invertedFontIds.remove(fontId);
|
||||
}
|
||||
|
||||
CharacterTag fontCharacter = characters.get(fontId);
|
||||
if (fontCharacter == null || !(fontCharacter instanceof FontTag)) {
|
||||
continue;
|
||||
}
|
||||
FontTag font = (FontTag) fontCharacter;
|
||||
|
||||
int minY = Integer.MAX_VALUE;
|
||||
int maxY = Integer.MIN_VALUE;
|
||||
for (SHAPE shp : font.getGlyphShapeTable()) {
|
||||
RECT b = shp.getBounds(1);
|
||||
if (b.Ymin < minY) {
|
||||
minY = b.Ymin;
|
||||
}
|
||||
if (b.Ymax > maxY) {
|
||||
maxY = b.Ymax;
|
||||
}
|
||||
}
|
||||
int maxH = maxY - minY;
|
||||
|
||||
int originalEmSize = (int) Math.round(maxH / font.getDivider());
|
||||
double scale = 1.0;
|
||||
boolean willModify = false;
|
||||
if (originalEmSize > 1024) {
|
||||
scale = 1024.0 / originalEmSize;
|
||||
willModify = true;
|
||||
}
|
||||
if (invertedFontIds.contains(fontId)) {
|
||||
willModify = true;
|
||||
}
|
||||
|
||||
int spaceGlyph = font.charToGlyph(' ');
|
||||
int nonBreakingSpaceGlyph = font.charToGlyph((char) 0xA0);
|
||||
|
||||
if (spaceGlyph != -1 && font.getGlyphAdvance(spaceGlyph) <= 1) {
|
||||
willModify = true;
|
||||
}
|
||||
|
||||
if (nonBreakingSpaceGlyph != -1 && font.getGlyphAdvance(nonBreakingSpaceGlyph) <= 1) {
|
||||
willModify = true;
|
||||
}
|
||||
|
||||
if (!willModify) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
//scale = 1;
|
||||
|
||||
fontNewScale.put(fontId, scale);
|
||||
FontTag font2;
|
||||
if (inPlace) {
|
||||
font2 = font;
|
||||
} else {
|
||||
try {
|
||||
font2 = (FontTag) font.cloneTag();
|
||||
} catch (InterruptedException | IOException ex) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
outFonts.put(fontId, font2);
|
||||
|
||||
List<SHAPE> shapes = font2.getGlyphShapeTable();
|
||||
|
||||
Matrix matrix = new Matrix();
|
||||
|
||||
matrix = matrix.preConcatenate(Matrix.getScaleInstance(scale, scale));
|
||||
|
||||
if (invertedFontIds.contains(fontId)) {
|
||||
matrix = matrix.preConcatenate(Matrix.getScaleInstance(1, -1));
|
||||
}
|
||||
|
||||
for (int i = 0; i < shapes.size(); i++) {
|
||||
SHAPE shp = shapes.get(i);
|
||||
transformSHAPE(matrix, shp);
|
||||
if (font2.hasLayout()) {
|
||||
font2.setGlyphAdvance(i, font2.getGlyphAdvance(i) * scale);
|
||||
}
|
||||
}
|
||||
if (font2.hasLayout()) {
|
||||
font2.updateBounds();
|
||||
|
||||
font2.setAscent((int) Math.round(font2.getAscent() * scale));
|
||||
font2.setDescent((int) Math.round(font2.getDescent() * scale));
|
||||
font2.setLeading((int) Math.round(font2.getLeading() * scale));
|
||||
|
||||
if (invertedFontIds.contains(fontId)) {
|
||||
int ascent = font2.getAscent();
|
||||
int descent = font2.getDescent();
|
||||
//switch ascent and descent
|
||||
font2.setAscent(descent);
|
||||
font2.setDescent(ascent);
|
||||
|
||||
//what to do with leading?
|
||||
}
|
||||
}
|
||||
|
||||
if (spaceGlyph != -1 && font.getGlyphAdvance(spaceGlyph) <= 1) {
|
||||
font2.setGlyphAdvance(spaceGlyph, 512);
|
||||
}
|
||||
|
||||
if (nonBreakingSpaceGlyph != -1 && font.getGlyphAdvance(nonBreakingSpaceGlyph) <= 1) {
|
||||
font2.setGlyphAdvance(nonBreakingSpaceGlyph, 512);
|
||||
}
|
||||
|
||||
font2.setModified(true);
|
||||
}
|
||||
|
||||
for (int textId : texts.keySet()) {
|
||||
int fontId = -1;
|
||||
int textHeight = 12 * 20;
|
||||
StaticTextTag text = texts.get(textId);
|
||||
StaticTextTag text2 = null;
|
||||
for (int i = 0; i < text.textRecords.size(); i++) {
|
||||
TEXTRECORD rec = text.textRecords.get(i);
|
||||
if (rec.styleFlagsHasFont) {
|
||||
fontId = rec.fontId;
|
||||
if (fontNewScale.containsKey(fontId) || invertedFontIds.contains(fontId)) {
|
||||
if (text2 == null) {
|
||||
if (inPlace) {
|
||||
text2 = text;
|
||||
} else {
|
||||
try {
|
||||
text2 = (StaticTextTag) text.cloneTag();
|
||||
} catch (InterruptedException | IOException ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
outTexts.put(textId, text2);
|
||||
text2.setModified(true);
|
||||
}
|
||||
}
|
||||
textHeight = text.textRecords.get(i).textHeight;
|
||||
|
||||
if (fontNewScale.containsKey(fontId)) {
|
||||
text2.textRecords.get(i).textHeight /= fontNewScale.get(fontId);
|
||||
textHeight = text2.textRecords.get(i).textHeight;
|
||||
}
|
||||
if (invertedFontIds.contains(fontId)) {
|
||||
if (text2.textMatrix != null && text2.textMatrix.scaleY < 0) {
|
||||
text2.textMatrix.scaleY *= -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (invertedFontIds.contains(fontId)) {
|
||||
if (rec.styleFlagsHasYOffset) {
|
||||
text2.textRecords.get(i).yOffset = - rec.yOffset;
|
||||
}
|
||||
}
|
||||
if (!rec.glyphEntries.isEmpty() && rec.glyphEntries.get(rec.glyphEntries.size() - 1).glyphAdvance == 0) {
|
||||
FontTag font;
|
||||
if (outFonts.containsKey(fontId)) {
|
||||
font = outFonts.get(fontId);
|
||||
} else {
|
||||
font = swf.getFont(fontId);
|
||||
}
|
||||
if (font != null) {
|
||||
if (text2 == null) {
|
||||
if (inPlace) {
|
||||
text2 = text;
|
||||
} else {
|
||||
try {
|
||||
text2 = (StaticTextTag) text.cloneTag();
|
||||
} catch (InterruptedException | IOException ex) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
outTexts.put(textId, text2);
|
||||
text2.setModified(true);
|
||||
}
|
||||
|
||||
GLYPHENTRY lastGlyphEntry = text2.textRecords.get(i).glyphEntries.get(rec.glyphEntries.size() - 1);
|
||||
lastGlyphEntry.glyphAdvance = (int) Math.round(font.getGlyphAdvance(lastGlyphEntry.glyphIndex) * textHeight / (1024.0 * font.getDivider()));
|
||||
|
||||
if (i + 1 < text.textRecords.size()) {
|
||||
TEXTRECORD nextRec = text2.textRecords.get(i + 1);
|
||||
if (!nextRec.styleFlagsHasXOffset && !nextRec.glyphEntries.isEmpty()) {
|
||||
nextRec.glyphEntries.get(0).glyphAdvance -= lastGlyphEntry.glyphAdvance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
swf.clearShapeCache();
|
||||
}
|
||||
|
||||
private void transformSHAPE(Matrix matrix, SHAPE shape) {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
StyleChangeRecord lastStyleChangeRecord = null;
|
||||
boolean wasMoveTo = false;
|
||||
for (SHAPERECORD rec : shape.shapeRecords) {
|
||||
if (rec instanceof StyleChangeRecord) {
|
||||
StyleChangeRecord scr = (StyleChangeRecord) rec;
|
||||
lastStyleChangeRecord = scr;
|
||||
if (scr.stateNewStyles) {
|
||||
//transformStyles(matrix, scr.fillStyles, scr.lineStyles, shapeNum);
|
||||
}
|
||||
if (scr.stateMoveTo) {
|
||||
Point nextPoint = new Point(scr.moveDeltaX, scr.moveDeltaY);
|
||||
x = scr.changeX(x);
|
||||
y = scr.changeY(y);
|
||||
Point nextPoint2 = matrix.transform(nextPoint);
|
||||
scr.moveDeltaX = nextPoint2.x;
|
||||
scr.moveDeltaY = nextPoint2.y;
|
||||
scr.calculateBits();
|
||||
wasMoveTo = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (((rec instanceof StraightEdgeRecord) || (rec instanceof CurvedEdgeRecord)) && !wasMoveTo) {
|
||||
if (lastStyleChangeRecord != null) {
|
||||
Point nextPoint2 = matrix.transform(new Point(x, y));
|
||||
if (nextPoint2.x != 0 || nextPoint2.y != 0) {
|
||||
lastStyleChangeRecord.stateMoveTo = true;
|
||||
lastStyleChangeRecord.moveDeltaX = nextPoint2.x;
|
||||
lastStyleChangeRecord.moveDeltaY = nextPoint2.y;
|
||||
lastStyleChangeRecord.calculateBits();
|
||||
wasMoveTo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rec instanceof StraightEdgeRecord) {
|
||||
StraightEdgeRecord ser = (StraightEdgeRecord) rec;
|
||||
ser.generalLineFlag = true;
|
||||
ser.vertLineFlag = false;
|
||||
Point currentPoint = new Point(x, y);
|
||||
Point nextPoint = new Point(x + ser.deltaX, y + ser.deltaY);
|
||||
x = ser.changeX(x);
|
||||
y = ser.changeY(y);
|
||||
Point currentPoint2 = matrix.transform(currentPoint);
|
||||
Point nextPoint2 = matrix.transform(nextPoint);
|
||||
ser.deltaX = nextPoint2.x - currentPoint2.x;
|
||||
ser.deltaY = nextPoint2.y - currentPoint2.y;
|
||||
ser.simplify();
|
||||
}
|
||||
if (rec instanceof CurvedEdgeRecord) {
|
||||
CurvedEdgeRecord cer = (CurvedEdgeRecord) rec;
|
||||
Point currentPoint = new Point(x, y);
|
||||
Point controlPoint = new Point(x + cer.controlDeltaX, y + cer.controlDeltaY);
|
||||
Point anchorPoint = new Point(x + cer.controlDeltaX + cer.anchorDeltaX, y + cer.controlDeltaY + cer.anchorDeltaY);
|
||||
x = cer.changeX(x);
|
||||
y = cer.changeY(y);
|
||||
|
||||
Point currentPoint2 = matrix.transform(currentPoint);
|
||||
Point controlPoint2 = matrix.transform(controlPoint);
|
||||
Point anchorPoint2 = matrix.transform(anchorPoint);
|
||||
|
||||
cer.controlDeltaX = controlPoint2.x - currentPoint2.x;
|
||||
cer.controlDeltaY = controlPoint2.y - currentPoint2.y;
|
||||
cer.anchorDeltaX = anchorPoint2.x - controlPoint2.x;
|
||||
cer.anchorDeltaY = anchorPoint2.y - controlPoint2.y;
|
||||
cer.calculateBits();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ import java.awt.image.renderable.RenderableImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.AttributedCharacterIterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
@@ -62,13 +63,16 @@ import java.util.logging.Logger;
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable, GraphicsGroupable, GraphicsTextDrawable {
|
||||
public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable, GraphicsGroupable, GraphicsTextDrawable, RequiresNormalizedFonts {
|
||||
|
||||
private final Graphics2D imageGraphics;
|
||||
|
||||
private final PDFGraphics pdfGraphics;
|
||||
private final Map<Integer, Font> existingFonts;
|
||||
|
||||
private Map<Integer, FontTag> normalizedFonts = new LinkedHashMap<>();
|
||||
private Map<Integer, StaticTextTag> normalizedTexts = new LinkedHashMap<>();
|
||||
|
||||
public DualPdfGraphics2D(Graphics2D first, PDFGraphics second, Map<Integer, Font> existingFonts) {
|
||||
this.imageGraphics = first;
|
||||
this.pdfGraphics = second;
|
||||
@@ -563,6 +567,7 @@ public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable,
|
||||
Matrix mat0 = mat.concatenate(textMatrix);
|
||||
Matrix trans = mat0.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor));
|
||||
FontTag font = null;
|
||||
int fontId = -1;
|
||||
int textHeight = 12;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
@@ -582,7 +587,11 @@ public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable,
|
||||
}
|
||||
|
||||
if (rec.styleFlagsHasFont) {
|
||||
font = rec.getFont(swf);
|
||||
font = rec.getFont(swf);
|
||||
fontId = swf.getCharacterId(font);
|
||||
if (normalizedFonts.containsKey(fontId)) {
|
||||
font = normalizedFonts.get(fontId);
|
||||
}
|
||||
textHeight = rec.textHeight;
|
||||
}
|
||||
if (rec.styleFlagsHasXOffset) {
|
||||
@@ -612,7 +621,7 @@ public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable,
|
||||
char ch = font.glyphToChar(entry.glyphIndex);
|
||||
if (spacing != 0) {
|
||||
text.append(currentChar);
|
||||
drawText(swf, x, y, trans, textColor, existingFonts, font, text.toString(), textHeight, pdfGraphics);
|
||||
drawText(swf, x, y, trans, textColor, existingFonts, fontId, font, text.toString(), textHeight, pdfGraphics);
|
||||
text = new StringBuilder();
|
||||
x = x + deltaX + entry.glyphAdvance;
|
||||
deltaX = 0;
|
||||
@@ -628,14 +637,13 @@ public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable,
|
||||
}
|
||||
}
|
||||
if (text.length() > 0) {
|
||||
drawText(swf, x, y, trans, textColor, existingFonts, font, text.toString(), textHeight, pdfGraphics);
|
||||
drawText(swf, x, y, trans, textColor, existingFonts, fontId, font, text.toString(), textHeight, pdfGraphics);
|
||||
}
|
||||
x = x + deltaX;
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawText(SWF swf, float x, float y, Matrix trans, int textColor, Map<Integer, Font> existingFonts, FontTag font, String text, int textHeight, PDFGraphics g) {
|
||||
int fontId = swf.getCharacterId(font);
|
||||
private static void drawText(SWF swf, float x, float y, Matrix trans, int textColor, Map<Integer, Font> existingFonts, int fontId, FontTag font, String text, int textHeight, PDFGraphics g) {
|
||||
if (existingFonts.containsKey(fontId)) {
|
||||
g.setExistingTtfFont(existingFonts.get(fontId).deriveFont((float) textHeight));
|
||||
} else {
|
||||
@@ -679,6 +687,25 @@ public class DualPdfGraphics2D extends Graphics2D implements BlendModeSettable,
|
||||
g.setTransform(trans.toTransform());
|
||||
Color textColor2 = new Color(textColor, true);
|
||||
g.setColor(textColor2);
|
||||
|
||||
text = text.replaceAll("\\p{Cc}", " "); //Replace control characters with space
|
||||
|
||||
g.drawString(text, (float) x, (float) y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNormalizedFonts(Map<Integer, FontTag> normalizedFonts, Map<Integer, StaticTextTag> normalizedTexts) {
|
||||
this.normalizedFonts = normalizedFonts;
|
||||
this.normalizedTexts = normalizedTexts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, FontTag> getNormalizedFonts() {
|
||||
return normalizedFonts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, StaticTextTag> getNormalizedTexts() {
|
||||
return normalizedTexts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,9 @@ public class FontExporter {
|
||||
f.setCreationDate(date);
|
||||
f.setModificationDate(date);
|
||||
}
|
||||
|
||||
|
||||
f.setDefaultMetrics();
|
||||
|
||||
int ascent = t.getAscent();
|
||||
if (ascent != -1) {
|
||||
float value = Math.round(ascent / divider);
|
||||
|
||||
@@ -19,6 +19,7 @@ package com.jpexs.decompiler.flash.exporters;
|
||||
import com.jpacker.JPacker;
|
||||
import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
|
||||
import com.jpexs.decompiler.flash.EventListener;
|
||||
import com.jpexs.decompiler.flash.FontNormalizer;
|
||||
import com.jpexs.decompiler.flash.RetryTask;
|
||||
import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.action.parser.ActionParseException;
|
||||
@@ -40,6 +41,7 @@ import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.base.CharacterTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.RenderContext;
|
||||
import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
|
||||
import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
|
||||
import com.jpexs.decompiler.flash.timeline.DepthState;
|
||||
import com.jpexs.decompiler.flash.timeline.Frame;
|
||||
@@ -86,6 +88,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -318,6 +321,13 @@ public class FrameExporter {
|
||||
}
|
||||
|
||||
if (settings.mode == FrameExportMode.SVG) {
|
||||
|
||||
FontNormalizer normalizer = new FontNormalizer();
|
||||
Map<Integer, FontTag> normalizedFonts = new LinkedHashMap<>();
|
||||
Map<Integer, StaticTextTag> normalizedTexts = new LinkedHashMap<>();
|
||||
normalizer.normalizeFonts(tim.timelined.getSwf(), normalizedFonts, normalizedTexts);
|
||||
|
||||
|
||||
int max = frames.size();
|
||||
if (subFramesLength > 1) {
|
||||
max = subFramesLength;
|
||||
@@ -341,7 +351,7 @@ public class FrameExporter {
|
||||
rect.xMin *= settings.zoom;
|
||||
rect.yMin *= settings.zoom;
|
||||
SVGExporter exporter = new SVGExporter(rect, settings.zoom, "frame", fbackgroundColor);
|
||||
|
||||
exporter.setNormalizedFonts(normalizedFonts, normalizedTexts);
|
||||
tim.toSVG(frame, subFramesLength > 1 ? fi : 0, null, 0, exporter, null, 0, new Matrix(), new Matrix());
|
||||
fos.write(Utf8Helper.getBytes(exporter.getSVG()));
|
||||
}
|
||||
@@ -595,6 +605,12 @@ public class FrameExporter {
|
||||
if (frameImages.hasNext()) {
|
||||
for (File foutdir : foutdirs) {
|
||||
new RetryTask(() -> {
|
||||
|
||||
FontNormalizer normalizer = new FontNormalizer();
|
||||
Map<Integer, FontTag> normalizedFonts = new LinkedHashMap<>();
|
||||
Map<Integer, StaticTextTag> normalizedTexts = new LinkedHashMap<>();
|
||||
normalizer.normalizeFonts(tim.timelined.getSwf(), normalizedFonts, normalizedTexts);
|
||||
|
||||
File f = new File(foutdir + File.separator + "frames.pdf");
|
||||
PDFJob job = new PDFJob(new BufferedOutputStream(new FileOutputStream(f)));
|
||||
PageFormat pf = new PageFormat();
|
||||
@@ -643,7 +659,8 @@ public class FrameExporter {
|
||||
return compositeGraphics;
|
||||
}
|
||||
final Graphics2D parentGraphics = (Graphics2D) super.getGraphics();
|
||||
compositeGraphics = new DualPdfGraphics2D(parentGraphics, (PDFGraphics) g, existingFonts);
|
||||
compositeGraphics = new DualPdfGraphics2D(parentGraphics, (PDFGraphics) g, existingFonts);
|
||||
((DualPdfGraphics2D) compositeGraphics).setNormalizedFonts(normalizedFonts, normalizedTexts);
|
||||
return compositeGraphics;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (C) 2010-2025 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;
|
||||
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This exporter requires normalized fonts.
|
||||
* @see com.jpexs.decompiler.flash.FontNormalizer
|
||||
* @author JPEXS
|
||||
*/
|
||||
public interface RequiresNormalizedFonts {
|
||||
public void setNormalizedFonts(Map<Integer, FontTag> normalizedFonts, Map<Integer, StaticTextTag> normalizedTexts);
|
||||
|
||||
public Map<Integer, FontTag> getNormalizedFonts();
|
||||
|
||||
public Map<Integer, StaticTextTag> getNormalizedTexts();
|
||||
|
||||
}
|
||||
@@ -18,8 +18,11 @@ 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.RequiresNormalizedFonts;
|
||||
import com.jpexs.decompiler.flash.exporters.modes.FontExportMode;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
import com.jpexs.decompiler.flash.tags.base.FontTag;
|
||||
import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
|
||||
import com.jpexs.decompiler.flash.types.BlendMode;
|
||||
import com.jpexs.decompiler.flash.types.ColorTransform;
|
||||
import com.jpexs.decompiler.flash.types.RECT;
|
||||
@@ -31,6 +34,7 @@ import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
@@ -59,7 +63,7 @@ import org.w3c.dom.NodeList;
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class SVGExporter {
|
||||
public class SVGExporter implements RequiresNormalizedFonts {
|
||||
|
||||
protected static final String sNamespace = "http://www.w3.org/2000/svg";
|
||||
|
||||
@@ -92,6 +96,25 @@ public class SVGExporter {
|
||||
public boolean useTextTag = Configuration.textExportExportFontFace.get();
|
||||
|
||||
private double zoom;
|
||||
|
||||
private Map<Integer, FontTag> normalizedFonts = new LinkedHashMap<>();
|
||||
private Map<Integer, StaticTextTag> normalizedTexts = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setNormalizedFonts(Map<Integer, FontTag> normalizedFonts, Map<Integer, StaticTextTag> normalizedTexts) {
|
||||
this.normalizedFonts = normalizedFonts;
|
||||
this.normalizedTexts = normalizedTexts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, FontTag> getNormalizedFonts() {
|
||||
return normalizedFonts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Integer, StaticTextTag> getNormalizedTexts() {
|
||||
return normalizedTexts;
|
||||
}
|
||||
|
||||
public static class ExportKey {
|
||||
|
||||
|
||||
@@ -358,6 +358,15 @@ public class DefineFont2Tag extends FontTag {
|
||||
}
|
||||
return super.getGlyphBounds(glyphIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBounds() {
|
||||
if (fontFlagsHasLayout) {
|
||||
for (int i = 0; i < fontBoundsTable.size(); i++) {
|
||||
fontBoundsTable.set(i, super.getGlyphBounds(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized double getGlyphAdvance(int glyphIndex) {
|
||||
@@ -367,6 +376,13 @@ public class DefineFont2Tag extends FontTag {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setGlyphAdvance(int glyphIndex, double advanceValue) {
|
||||
if (fontFlagsHasLayout && glyphIndex != -1) {
|
||||
fontAdvanceTable.set(glyphIndex, (int) Math.round(advanceValue));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized List<SHAPE> getGlyphShapeTable() {
|
||||
|
||||
@@ -349,6 +349,13 @@ public class DefineFont3Tag extends FontTag {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setGlyphAdvance(int glyphIndex, double advanceValue) {
|
||||
if (fontFlagsHasLayout && glyphIndex != -1) {
|
||||
fontAdvanceTable.set(glyphIndex, (int) Math.round(advanceValue));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized char glyphToChar(int glyphIndex) {
|
||||
@@ -646,6 +653,15 @@ public class DefineFont3Tag extends FontTag {
|
||||
return super.getGlyphBounds(glyphIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBounds() {
|
||||
if (fontFlagsHasLayout) {
|
||||
for (int i = 0; i < fontBoundsTable.size(); i++) {
|
||||
fontBoundsTable.set(i, super.getGlyphBounds(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) {
|
||||
if (glyphIndex == -1 || nextGlyphIndex == -1) {
|
||||
|
||||
@@ -150,6 +150,16 @@ public class DefineFontTag extends FontTag {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGlyphAdvance(int glyphIndex, double advanceValue) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBounds() {
|
||||
throw new UnsupportedOperationException("Not supported yet."); // Generated from nbfs://nbhost/SystemFileSystem/Templates/Classes/Code/GeneratedMethodBody
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getGlyphWidth(int glyphIndex) {
|
||||
return glyphShapeTable.get(glyphIndex).getBounds(1).getWidth();
|
||||
|
||||
@@ -102,6 +102,13 @@ public abstract class FontTag extends DrawableTag implements AloneTag {
|
||||
*/
|
||||
public abstract void setAdvanceValues(Font font);
|
||||
|
||||
/**
|
||||
* Sets advance value for glyph.
|
||||
* @param glyphIndex Glyph index
|
||||
* @param advanceValue Advance value
|
||||
*/
|
||||
public abstract void setGlyphAdvance(int glyphIndex, double advanceValue);
|
||||
|
||||
/**
|
||||
* Converts glyph to character.
|
||||
* @param glyphIndex Glyph index
|
||||
@@ -815,4 +822,9 @@ public abstract class FontTag extends DrawableTag implements AloneTag {
|
||||
*/
|
||||
public abstract String getCodesCharset();
|
||||
|
||||
|
||||
/**
|
||||
* Update tables of bounds
|
||||
*/
|
||||
public abstract void updateBounds();
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.jpexs.decompiler.flash.SWF;
|
||||
import com.jpexs.decompiler.flash.SWFInputStream;
|
||||
import com.jpexs.decompiler.flash.SWFOutputStream;
|
||||
import com.jpexs.decompiler.flash.configuration.Configuration;
|
||||
import com.jpexs.decompiler.flash.exporters.RequiresNormalizedFonts;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.Matrix;
|
||||
import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter;
|
||||
@@ -1027,6 +1028,16 @@ public abstract class StaticTextTag extends TextTag {
|
||||
|
||||
@Override
|
||||
public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, SerializableImage fullImage, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, Matrix fullTransformation, ColorTransform colorTransform, double unzoom, boolean sameImage, ExportRectangle viewRect, ExportRectangle viewRectRaw, boolean scaleStrokes, int drawMode, int blendMode, boolean canUseSmoothing) {
|
||||
if (image.getGraphics() instanceof RequiresNormalizedFonts) {
|
||||
RequiresNormalizedFonts g = (RequiresNormalizedFonts) image.getGraphics();
|
||||
Map<Integer, StaticTextTag> normalizedTexts = g.getNormalizedTexts();
|
||||
int realTextId = getSwf().getCharacterId(this);
|
||||
if (normalizedTexts.containsKey(realTextId)) {
|
||||
StaticTextTag normalizedText = normalizedTexts.get(realTextId);
|
||||
staticTextToImage(swf, normalizedText.textRecords, getTextNum(), image, normalizedText.textMatrix, transformation, colorTransform, renderContext.selectionText == this ? renderContext.selectionStart : 0, renderContext.selectionText == this ? renderContext.selectionEnd : 0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
staticTextToImage(swf, textRecords, getTextNum(), image, textMatrix, transformation, colorTransform, renderContext.selectionText == this ? renderContext.selectionStart : 0, renderContext.selectionText == this ? renderContext.selectionEnd : 0);
|
||||
/*try {
|
||||
TextTag originalTag = (TextTag) getOriginalTag();
|
||||
@@ -1041,6 +1052,12 @@ public abstract class StaticTextTag extends TextTag {
|
||||
|
||||
@Override
|
||||
public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) {
|
||||
int realTextId = getSwf().getCharacterId(this);
|
||||
if (exporter.getNormalizedTexts().containsKey(realTextId)) {
|
||||
StaticTextTag normalizedText = exporter.getNormalizedTexts().get(realTextId);
|
||||
staticTextToSVG(swf, normalizedText.textRecords, getTextNum(), exporter, getRect(), normalizedText.textMatrix, colorTransform, exporter.getZoom(), transformation);
|
||||
return;
|
||||
}
|
||||
staticTextToSVG(swf, textRecords, getTextNum(), exporter, getRect(), textMatrix, colorTransform, exporter.getZoom(), transformation);
|
||||
}
|
||||
|
||||
|
||||
@@ -990,6 +990,7 @@ public abstract class TextTag extends DrawableTag {
|
||||
public static void staticTextToSVG(SWF swf, List<TEXTRECORD> textRecords, int numText, SVGExporter exporter, RECT bounds, MATRIX textMatrix, ColorTransform colorTransform, double zoom, Matrix transformation) {
|
||||
int textColor = 0;
|
||||
FontTag font = null;
|
||||
int fontId = -1;
|
||||
double textHeight = 12;
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
@@ -1009,6 +1010,10 @@ public abstract class TextTag extends DrawableTag {
|
||||
}
|
||||
if (rec.styleFlagsHasFont) {
|
||||
font = rec.getFont(swf);
|
||||
fontId = swf.getCharacterId(font);
|
||||
if (exporter.getNormalizedFonts().containsKey(fontId)) {
|
||||
font = exporter.getNormalizedFonts().get(fontId);
|
||||
}
|
||||
glyphs = font.getGlyphShapeTable();
|
||||
textHeight = rec.textHeight;
|
||||
}
|
||||
@@ -1049,6 +1054,7 @@ public abstract class TextTag extends DrawableTag {
|
||||
textElement.setAttribute("font-family", fontFamily);
|
||||
textElement.setAttribute("textLength", Double.toString(totalAdvance / SWF.unitDivisor));
|
||||
textElement.setAttribute("lengthAdjust", "spacing");
|
||||
textElement.setAttribute("style", "white-space: pre");
|
||||
textElement.setTextContent(text.toString());
|
||||
|
||||
RGBA colorA = new RGBA(textColor);
|
||||
|
||||
@@ -283,6 +283,11 @@ public final class DefineCompactedFont extends FontTag {
|
||||
public double getGlyphAdvance(int glyphIndex) {
|
||||
return resize(fonts.get(0).glyphInfo.get(glyphIndex).advanceX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setGlyphAdvance(int glyphIndex, double advanceValue) {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) {
|
||||
@@ -401,6 +406,11 @@ public final class DefineCompactedFont extends FontTag {
|
||||
GlyphType gt = fonts.get(0).glyphs.get(glyphIndex);
|
||||
return new RECT(resize(gt.boundingBox[0]), resize(gt.boundingBox[1]), resize(gt.boundingBox[2]), resize(gt.boundingBox[3]));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateBounds() {
|
||||
throw new UnsupportedOperationException("Not implemented yet");
|
||||
}
|
||||
|
||||
public SHAPE resizeShape(SHAPE shp) {
|
||||
SHAPE ret = new SHAPE();
|
||||
|
||||
Reference in New Issue
Block a user