Changed #1913 SVG export/import of shapes - shape exact position (bounds) is retained

This commit is contained in:
Jindra Petřík
2022-12-28 16:02:56 +01:00
parent 4f5fc62adc
commit e18575f2cd
20 changed files with 120 additions and 47 deletions

View File

@@ -223,7 +223,7 @@ public class FrameExporter {
rect.xMax *= settings.zoom;
rect.yMax *= settings.zoom;
rect.xMin *= settings.zoom;
rect.yMin *= settings.zoom;
rect.yMin *= settings.zoom;
SVGExporter exporter = new SVGExporter(rect, settings.zoom);
if (fbackgroundColor != null) {
exporter.setBackGroundColor(fbackgroundColor);

View File

@@ -461,7 +461,7 @@ public class PreviewExporter {
List<SHAPE> shapes = ft.getGlyphShapeTable();
int maxw = 0;
for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) {
RECT b = shapes.get(f).getBounds();
RECT b = shapes.get(f).getBounds(1);
if (b.Xmin == Integer.MAX_VALUE) {
continue;
}
@@ -494,7 +494,7 @@ public class PreviewExporter {
List<TEXTRECORD> rec = new ArrayList<>();
TEXTRECORD tr = new TEXTRECORD();
RECT b = shapes.get(f).getBounds();
RECT b = shapes.get(f).getBounds(1);
int xmin = b.Xmin == Integer.MAX_VALUE ? 0 : (int) (b.Xmin / ft.getDivider());
xmin *= textHeight / 1024.0;
int ymin = b.Ymin == Integer.MAX_VALUE ? 0 : (int) (b.Ymin / ft.getDivider());

View File

@@ -137,8 +137,8 @@ public class ShapeExporter {
case CANVAS:
try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(file))) {
SHAPE shp = st.getShapes();
int deltaX = -shp.getBounds().Xmin;
int deltaY = -shp.getBounds().Ymin;
int deltaX = -shp.getBounds(1).Xmin;
int deltaY = -shp.getBounds(1).Ymin;
CanvasShapeExporter cse = new CanvasShapeExporter(st.getShapeNum(), null, SWF.unitDivisor / settings.zoom, ((Tag) st).getSwf(), shp, new CXFORMWITHALPHA(), deltaX, deltaY);
cse.export();
Set<Integer> needed = new HashSet<>();

View File

@@ -96,8 +96,8 @@ public class SVGExporter {
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");
svgRoot.setAttribute("width", (bounds.xMax / SWF.unitDivisor) + "px");
svgRoot.setAttribute("height", (bounds.yMax / SWF.unitDivisor) + "px");
createDefGroup(bounds, null, zoom);
}
} catch (ParserConfigurationException ex) {
@@ -131,7 +131,7 @@ public class SVGExporter {
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);
Matrix mat = new Matrix(); //Matrix.getTranslateInstance(-bounds.xMin, -bounds.yMin);
mat.scale(zoom);
g.setAttribute("transform", mat.getSvgTransformationString(SWF.unitDivisor, 1));
}

View File

@@ -168,7 +168,7 @@ public class BitmapExporter extends ShapeExporterBase {
public static void export(int shapeNum, SWF swf, SHAPE shape, Color defaultColor, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, ColorTransform colorTransform, boolean scaleStrokes) {
BitmapExporter exporter = new BitmapExporter(shapeNum, swf, shape, defaultColor, colorTransform);
exporter.exportTo(image, unzoom, transformation, strokeTransformation, scaleStrokes);
exporter.exportTo(shapeNum, image, unzoom, transformation, strokeTransformation, scaleStrokes);
}
private BitmapExporter(int shapeNum, SWF swf, SHAPE shape, Color defaultColor, ColorTransform colorTransform) {
@@ -177,10 +177,10 @@ public class BitmapExporter extends ShapeExporterBase {
this.defaultColor = defaultColor;
}
private void exportTo(SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, boolean scaleStrokes) {
private void exportTo(int shapeNum, SerializableImage image, double unzoom, Matrix transformation, Matrix strokeTransformation, boolean scaleStrokes) {
this.image = image;
this.scaleStrokes = scaleStrokes;
ExportRectangle bounds = new ExportRectangle(shape.getBounds());
ExportRectangle bounds = new ExportRectangle(shape.getBounds(shapeNum));
this.strokeTransformation = strokeTransformation;
calculateThicknessScale(bounds, transformation);

View File

@@ -154,7 +154,7 @@ public class IggyToSwfConvertor {
SHAPE shp;
if (glyph != null) {
shp = IggyShapeToSwfConvertor.convertCharToShape(glyph);
fontTag.fontBoundsTable.add(shp.getBounds());
fontTag.fontBoundsTable.add(shp.getBounds(1));
} else {
shp = new SHAPE();
shp.shapeRecords = new ArrayList<>();

View File

@@ -37,12 +37,12 @@ public class SwfShapeToIggyConvertor {
return (val / 1024f);
}
public static IggyShape convertShape(SHAPE swfShape) {
public static IggyShape convertShape(int shapeNum, SHAPE swfShape) {
/*if (swfShape.shapeRecords.size() == 1) { //no glyphs, maybe space
return null;
}*/
List<IggyShapeNode> nodes = new ArrayList<>();
RECT bounds = swfShape.getBounds();
RECT bounds = swfShape.getBounds(shapeNum);
boolean first = true;
float curX = 0f;
float curY = 0f;

View File

@@ -126,7 +126,7 @@ public class SwfToIggyConvertor {
List<IggyShape> glyphs = new ArrayList<>();
for (SHAPE s : fontTag.glyphShapeTable) {
glyphs.add(SwfShapeToIggyConvertor.convertShape(s));
glyphs.add(SwfShapeToIggyConvertor.convertShape(1, s));
}
List<Character> chars = new ArrayList<>();

View File

@@ -179,16 +179,7 @@ public class SvgImporter {
SvgStyle style = new SvgStyle(this, idMap, rootElement);
Matrix transform = new Matrix();
if (!fill) {
rect.Xmin -= origXmin;
rect.Xmax -= origXmin;
rect.Ymin -= origYmin;
rect.Ymax -= origYmin;
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);
} else {
if (fill) {
double ratioX = rect.getWidth() / width / SWF.unitDivisor;
double ratioY = rect.getHeight() / height / SWF.unitDivisor;
transform = Matrix.getScaleInstance(ratioX, ratioY);
@@ -202,7 +193,10 @@ public class SvgImporter {
shapes.shapeRecords.add(new EndShapeRecord());
st.shapes = shapes;
st.shapes = shapes;
if (!fill) {
st.shapeBounds = shapes.getBounds(st.getShapeNum());
}
st.setModified(true);
return (Tag) st;
@@ -522,7 +516,7 @@ public class SvgImporter {
x0 = x;
y0 = y;
}
applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style);
applyStyleGradients(SHAPERECORD.getBounds(newRecords, shapes.lineStyles, shapeNum), scrStyle, transform2, shapeNum, style);
shapes.shapeRecords.addAll(newRecords);
}

View File

@@ -320,7 +320,7 @@ public class DefineFont2Tag extends FontTag {
@Override
public int getGlyphWidth(int glyphIndex) {
return glyphShapeTable.get(glyphIndex).getBounds().getWidth();
return glyphShapeTable.get(glyphIndex).getBounds(1).getWidth();
}
@Override
@@ -481,10 +481,10 @@ public class DefineFont2Tag extends FontTag {
if (fontFlagsHasLayout) {
Font advanceFont = font.deriveFont(fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k
if (!exists) {
fontBoundsTable.add(pos, shp.getBounds());
fontBoundsTable.add(pos, shp.getBounds(1));
fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, character)));
} else {
fontBoundsTable.set(pos, shp.getBounds());
fontBoundsTable.set(pos, shp.getBounds(1));
fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, character)));
}
@@ -577,7 +577,7 @@ public class DefineFont2Tag extends FontTag {
continue;
}
SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch);
newFontBoundsTable.add(shp.getBounds());
newFontBoundsTable.add(shp.getBounds(1));
int fontStyle = getFontStyle();
Font advanceFont = font.deriveFont(fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k
newFontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, ch)));

View File

@@ -308,7 +308,7 @@ public class DefineFont3Tag extends FontTag {
@Override
public int getGlyphWidth(int glyphIndex) {
return glyphShapeTable.get(glyphIndex).getBounds().getWidth();
return glyphShapeTable.get(glyphIndex).getBounds(1).getWidth();
}
@Override
@@ -470,10 +470,10 @@ public class DefineFont3Tag extends FontTag {
Font advanceFont = font.deriveFont(fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k
if (!exists) {
fontBoundsTable.add(pos, shp.getBounds());
fontBoundsTable.add(pos, shp.getBounds(1));
fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, character)));
} else {
fontBoundsTable.set(pos, shp.getBounds());
fontBoundsTable.set(pos, shp.getBounds(1));
fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, character)));
}
@@ -578,7 +578,7 @@ public class DefineFont3Tag extends FontTag {
continue;
}
SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch);
newFontBoundsTable.add(shp.getBounds());
newFontBoundsTable.add(shp.getBounds(1));
int fontStyle = getFontStyle();
Font advanceFont = font.deriveFont(fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k
newFontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(advanceFont, ch)));

View File

@@ -134,7 +134,7 @@ public class DefineFontTag extends FontTag {
@Override
public int getGlyphWidth(int glyphIndex) {
return glyphShapeTable.get(glyphIndex).getBounds().getWidth();
return glyphShapeTable.get(glyphIndex).getBounds(1).getWidth();
}
private void ensureFontInfo() {

View File

@@ -517,7 +517,7 @@ public abstract class FontTag extends DrawableTag implements AloneTag {
}
public RECT getGlyphBounds(int glyphIndex) {
return getGlyphShapeTable().get(glyphIndex).getBounds();
return getGlyphShapeTable().get(glyphIndex).getBounds(1);
}
public FontTag toClassicFont() {

View File

@@ -38,6 +38,7 @@ 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.LINESTYLE2;
import com.jpexs.decompiler.flash.types.LINESTYLEARRAY;
import com.jpexs.decompiler.flash.types.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
@@ -330,7 +331,10 @@ public abstract class TextTag extends DrawableTag {
if (e < rec.glyphEntries.size() - 1) {
nextEntry = rec.glyphEntries.get(e + 1);
}
RECT rect = SHAPERECORD.getBounds(glyphs.get(entry.glyphIndex).shapeRecords);
LINESTYLEARRAY lsa = new LINESTYLEARRAY();
lsa.lineStyles = new LINESTYLE[0];
lsa.lineStyles2 = new LINESTYLE2[0];
RECT rect = SHAPERECORD.getBounds(glyphs.get(entry.glyphIndex).shapeRecords, lsa, 1);
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));
@@ -516,7 +520,7 @@ public abstract class TextTag extends DrawableTag {
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();
RECT bounds = shape.getBounds(1);
mat = Matrix.getTranslateInstance(bounds.Xmin, bounds.Ymin).preConcatenate(mat);
TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), mat, colorTransform);
}
@@ -558,7 +562,7 @@ public abstract class TextTag extends DrawableTag {
if (entry.glyphIndex != -1 && glyphs != null) {
// shapeNum: 1
SHAPE shape = glyphs.get(entry.glyphIndex);
RECT glyphBounds = shape.getBounds();
RECT glyphBounds = shape.getBounds(1);
int glyphWidth = glyphBounds.getWidth();
int glyphHeight = glyphBounds.getHeight();
glyphBounds.Xmin -= glyphWidth / 2;

View File

@@ -303,7 +303,7 @@ public final class DefineCompactedFont extends FontTag {
@Override
public int getGlyphWidth(int glyphIndex) {
return resize(getGlyphShapeTable().get(glyphIndex).getBounds().getWidth());
return resize(getGlyphShapeTable().get(glyphIndex).getBounds(1).getWidth());
}
@Override

View File

@@ -73,8 +73,11 @@ public class SHAPE implements NeedsCharacters, Serializable {
return modified;
}
public RECT getBounds() {
return SHAPERECORD.getBounds(shapeRecords);
public RECT getBounds(int shapeNum) {
LINESTYLEARRAY lsa = new LINESTYLEARRAY();
lsa.lineStyles = new LINESTYLE[0];
lsa.lineStyles2 = new LINESTYLE2[0];
return SHAPERECORD.getBounds(shapeRecords, lsa, shapeNum);
}
public Shape getOutline(int shapeNum, SWF swf, boolean stroked) {

View File

@@ -97,4 +97,11 @@ public class SHAPEWITHSTYLE extends SHAPE implements NeedsCharacters, Serializab
return ret;
}
@Override
public RECT getBounds(int shapeNum) {
return SHAPERECORD.getBounds(shapeRecords, lineStyles, shapeNum);
}
}

View File

@@ -16,6 +16,9 @@
*/
package com.jpexs.decompiler.flash.types.gfx;
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.SHAPE;
import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord;
@@ -46,7 +49,10 @@ public class GlyphType implements Serializable {
public GlyphType(List<SHAPERECORD> records) {
RECT bounds = SHAPERECORD.getBounds(records);
LINESTYLEARRAY lsa = new LINESTYLEARRAY();
lsa.lineStyles = new LINESTYLE[0];
lsa.lineStyles2 = new LINESTYLE2[0];
RECT bounds = SHAPERECORD.getBounds(records, lsa, 1);
boundingBox = new int[4];
boundingBox[0] = bounds.Xmin;
boundingBox[1] = bounds.Ymin;

View File

@@ -24,6 +24,9 @@ import com.jpexs.decompiler.flash.helpers.FontHelper;
import com.jpexs.decompiler.flash.tags.base.NeedsCharacters;
import com.jpexs.decompiler.flash.tags.base.TextTag;
import com.jpexs.decompiler.flash.types.ColorTransform;
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.MATRIX;
import com.jpexs.decompiler.flash.types.RECT;
import com.jpexs.decompiler.flash.types.RGB;
@@ -77,7 +80,7 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
public abstract void flip();
public static RECT getBounds(List<SHAPERECORD> records) {
public static RECT getBounds(List<SHAPERECORD> records, LINESTYLEARRAY lineStyles, int shapeNum) {
int x = 0;
int y = 0;
int max_x = 0;
@@ -85,7 +88,29 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
int min_x = Integer.MAX_VALUE;
int min_y = Integer.MAX_VALUE;
boolean started = false;
int lineStyle = 0;
int lineWidth = 0;
int lineWidthHalf = 0;
for (SHAPERECORD r : records) {
if (r instanceof StyleChangeRecord) {
StyleChangeRecord style = (StyleChangeRecord) r;
if (style.stateNewStyles) {
lineStyles = style.lineStyles;
}
if (style.stateLineStyle) {
lineStyle = style.lineStyle;
if (lineStyle == 0) {
lineWidth = 0;
} else {
if (shapeNum <= 3) {
lineWidth = lineStyles.lineStyles[lineStyle - 1].width;
} else {
lineWidth = lineStyles.lineStyles2[lineStyle - 1].width;
}
}
lineWidthHalf = lineWidth / 2;
}
}
if (r instanceof CurvedEdgeRecord) {
CurvedEdgeRecord curverEdge = (CurvedEdgeRecord) r;
int x2 = x + curverEdge.controlDeltaX;
@@ -96,6 +121,12 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
if (y2 > max_y) {
max_y = y2;
}
if (x2 + lineWidthHalf > max_x) {
max_x = x2 + lineWidthHalf;
}
if (y2 + lineWidthHalf > max_y) {
max_y = y2 + lineWidthHalf;
}
if (started) {
if (y2 < min_y) {
min_y = y2;
@@ -103,6 +134,14 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
if (x2 < min_x) {
min_x = x2;
}
if (y2 - lineWidthHalf < min_y) {
min_y = y2 - lineWidthHalf;
}
if (x2 - lineWidthHalf < min_x) {
min_x = x2 - lineWidthHalf;
}
}
}
@@ -114,6 +153,12 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
if (y > max_y) {
max_y = y;
}
if (x + lineWidthHalf > max_x) {
max_x = x + lineWidthHalf;
}
if (y + lineWidthHalf > max_y) {
max_y = y + lineWidthHalf;
}
if (r.isMove()) {
started = true;
}
@@ -124,6 +169,13 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
if (x < min_x) {
min_x = x;
}
if (y - lineWidthHalf < min_y) {
min_y = y - lineWidthHalf;
}
if (x - lineWidthHalf < min_x) {
min_x = x - lineWidthHalf;
}
}
}
return new RECT(min_x, max_x, min_y, max_y);
@@ -150,8 +202,11 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
int maxh = 0;
int minXMin = 0;
int minYMin = 0;
LINESTYLEARRAY lsa = new LINESTYLEARRAY();
lsa.lineStyles = new LINESTYLE[0];
lsa.lineStyles2 = new LINESTYLE2[0];
for (SHAPE s : shapes) {
RECT r = SHAPERECORD.getBounds(s.shapeRecords);
RECT r = SHAPERECORD.getBounds(s.shapeRecords, lsa, shapeNum);
if (r.Xmax < r.Xmin || r.Ymax < r.Ymin) {
continue;
}
@@ -207,7 +262,7 @@ public abstract class SHAPERECORD implements Cloneable, NeedsCharacters, Seriali
// shapeNum: 1
SHAPE shape = shapes.get(pos);
List<SHAPERECORD> records = shape.shapeRecords;
RECT bounds = SHAPERECORD.getBounds(records);
RECT bounds = SHAPERECORD.getBounds(records, lsa, shapeNum);
int w1 = bounds.getWidth();
int h1 = bounds.getHeight();