From 3feb26f16e96098aea3856e2c787299ad44592b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jindra=20Pet=C5=99=C3=ADk?= Date: Sat, 5 Jul 2025 09:29:12 +0200 Subject: [PATCH] Added: Selectable (not yet copyable) text (DefineEditTexts with noselect=0) --- CHANGELOG.md | 1 + .../flash/tags/DefineEditTextTag.java | 10 +- .../flash/tags/base/RenderContext.java | 44 ++++- .../flash/tags/base/StaticTextTag.java | 2 +- .../decompiler/flash/tags/base/TextTag.java | 127 +++++++++++++- .../decompiler/flash/timeline/Timeline.java | 157 ++++++++++++++++++ .../decompiler/flash/gui/ImagePanel.java | 113 +++++++++++-- 7 files changed, 435 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 346ac6fff..d53bbdb95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file. and shows its progress - AS2 - show deobfuscated class/package names in the class tree - Allow obfuscated DefineEditText variable identifiers +- Selectable text (DefineEditTexts with noselect=0) ### Fixed - [#2474] Gotos incorrectly decompiled diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index a577dbe14..a26ae9fb4 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -1040,20 +1040,20 @@ public class DefineEditTextTag 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) { - render(TextRenderMode.BITMAP, image, null, null, transformation, colorTransform, 1); + render(TextRenderMode.BITMAP, image, null, null, transformation, colorTransform, 1, renderContext.selectionText == this ? renderContext.selectionStart : 0, renderContext.selectionText == this ? renderContext.selectionEnd : 0); } @Override public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level, Matrix transformation, Matrix strokeTransformation) { - render(TextRenderMode.SVG, null, exporter, null, new Matrix(), colorTransform, 1); + render(TextRenderMode.SVG, null, exporter, null, new Matrix(), colorTransform, 1, 0, 0); } @Override public void toHtmlCanvas(StringBuilder result, double unitDivisor) { - render(TextRenderMode.HTML5_CANVAS, null, null, result, new Matrix(), null, unitDivisor); + render(TextRenderMode.HTML5_CANVAS, null, null, result, new Matrix(), null, unitDivisor, 0, 0); } - private void render(TextRenderMode renderMode, SerializableImage image, SVGExporter svgExporter, StringBuilder htmlCanvasBuilder, Matrix transformation, ColorTransform colorTransform, double zoom) { + private void render(TextRenderMode renderMode, SerializableImage image, SVGExporter svgExporter, StringBuilder htmlCanvasBuilder, Matrix transformation, ColorTransform colorTransform, double zoom, int selectionStart, int selectionEnd) { if (border) { // border is always black, fill color is always white? RGB borderColor = new RGBA(Color.black); @@ -1074,7 +1074,7 @@ public class DefineEditTextTag extends TextTag { List allTextRecords = getTextRecords(swf); switch (renderMode) { case BITMAP: - staticTextToImage(swf, allTextRecords, 2, image, getTextMatrix(), transformation, colorTransform); + staticTextToImage(swf, allTextRecords, 2, image, getTextMatrix(), transformation, colorTransform, selectionStart, selectionEnd); break; case HTML5_CANVAS: staticTextToHtmlCanvas(zoom, swf, allTextRecords, 2, htmlCanvasBuilder, getBounds(), getTextMatrix(), colorTransform); diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/RenderContext.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/RenderContext.java index 07cb5466d..1c9d30f91 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/RenderContext.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/RenderContext.java @@ -20,6 +20,7 @@ import com.jpexs.decompiler.flash.timeline.DepthState; import com.jpexs.helpers.Cache; import com.jpexs.helpers.SerializableImage; import java.awt.Point; +import java.awt.geom.Rectangle2D; import java.util.List; /** @@ -48,6 +49,42 @@ public class RenderContext { * Mouse over button. */ public ButtonTag mouseOverButton; + + /** + * Mouse over text. + */ + public TextTag mouseOverText; + + /** + * Selection text tag + */ + public TextTag selectionText; + + /** + * Text selection start + */ + public int selectionStart = 0; + + /** + * Text selection end + */ + public int selectionEnd = 0; + + /** + * Position of glyph in the text under cursor + */ + public int glyphPosUnderCursor = -1; + + /** + * Bouding rect of the glyph under cursor + */ + public Rectangle2D glyphUnderCursorRect = null; + + /** + * Glyph under cursor X position + */ + public double glyphUnderCursorXPosition = 0; + /** * Border image. @@ -60,10 +97,15 @@ public class RenderContext { public Cache displayObjectCache; /** - * Enable handling buttons + * Enable handling buttons. */ public boolean enableButtons = true; + /** + * Enable handling texts. + */ + public boolean enableTexts = true; + /** * Clear display object cache. * diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java index fdbe5963e..6f065571b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/StaticTextTag.java @@ -1019,7 +1019,7 @@ 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) { - staticTextToImage(swf, textRecords, getTextNum(), image, textMatrix, transformation, colorTransform); + 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(); if (isModified()) { diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java index eecca199a..10a0d1b75 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -63,6 +63,8 @@ import java.awt.Rectangle; import java.awt.Shape; import java.awt.font.LineMetrics; import java.awt.font.TextAttribute; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.HashMap; @@ -297,6 +299,78 @@ public abstract class TextTag extends DrawableTag { } } + /** + * Gets rectangle positions of glyph entries of the TEXTRECORDs. + * @param list Text record list + * @param swf SWF + * @return List of RECTs + */ + public static List getGlyphEntriesPositions(List list, SWF swf) { + + List ret = new ArrayList<>(); + int x = 0; + int y = 0; + FontTag font = null; + double ascent = 0; + double descent = 0; + double leading = 0; + int textHeight = 12; + + FontMetrics fontMetrics; + BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + Graphics graphics = bi.getGraphics(); + Font aFont; + + for (int r = 0; r < list.size(); r++) { + TEXTRECORD rec = list.get(r); + if (rec.styleFlagsHasXOffset) { + x = rec.xOffset; + } + if (rec.styleFlagsHasYOffset) { + y = rec.yOffset; + } + + if (rec.styleFlagsHasFont) { + FontTag font2 = rec.getFont(swf); + if (font2 != null) { + font = font2; + } + textHeight = rec.textHeight; + if (font == null) { + Logger.getLogger(TextTag.class.getName()).log(Level.SEVERE, "Font with id={0} was not found.", rec.fontId); + continue; + } + + if (!font.hasLayout()) { + String fontName = FontTag.getFontNameWithFallback(font.getFontNameIntag()); + aFont = new Font(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor)); + + Map 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() * SWF.unitDivisor; + descent = lm.getDescent() * SWF.unitDivisor; + leading = lm.getLeading() * SWF.unitDivisor; + } else { + ascent = ((double) font.getAscent() * textHeight / 1024.0 / font.getDivider()); + descent = ((double) font.getDescent() * textHeight / 1024.0 / font.getDivider()); + leading = ((double) font.getLeading() * textHeight / 1024.0 / font.getDivider()); + } + } + + for (GLYPHENTRY entry : rec.glyphEntries) { + ret.add(new RECT(x, x + entry.glyphAdvance, (int) Math.round(y - ascent), (int) Math.round(y + descent + leading))); + x += entry.glyphAdvance; + } + } + return ret; + } + /** * Gets text records attributes. * @param list Text records @@ -579,8 +653,10 @@ public abstract class TextTag extends DrawableTag { * @param textMatrix Text matrix * @param transformation Transformation * @param colorTransform Color transform + * @param selectionStart Selection start + * @param selectionEnd Selection end */ - public static void staticTextToImage(SWF swf, List textRecords, int numText, SerializableImage image, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform) { + public static void staticTextToImage(SWF swf, List textRecords, int numText, SerializableImage image, MATRIX textMatrix, Matrix transformation, ColorTransform colorTransform, int selectionStart, int selectionEnd) { if (image.getGraphics() instanceof GraphicsTextDrawable) { //custom drawing ((GraphicsTextDrawable) image.getGraphics()).drawTextRecords(swf, textRecords, numText, textMatrix, transformation, colorTransform); @@ -591,6 +667,13 @@ public abstract class TextTag extends DrawableTag { int textHeight = 12; int x = 0; int y = 0; + int pos = 0; + double ascent = 0; + double descent = 0; + double leading = 0; + FontMetrics fontMetrics; + BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB); + Graphics graphics = bi.getGraphics(); List glyphs = null; Matrix mat0 = transformation.clone(); mat0 = mat0.concatenate(new Matrix(textMatrix)); @@ -613,6 +696,28 @@ public abstract class TextTag extends DrawableTag { } glyphs = font == null ? null : font.getGlyphShapeTable(); textHeight = rec.textHeight; + + + if (!font.hasLayout()) { + String fontName = FontTag.getFontNameWithFallback(font.getFontNameIntag()); + Font aFont = new Font(fontName, font.getFontStyle(), (int) (textHeight / SWF.unitDivisor)); + + Map 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() * 1024 * font.getDivider() / (textHeight / SWF.unitDivisor); + descent = lm.getDescent() * 1024 * font.getDivider() / (textHeight / SWF.unitDivisor); + leading = lm.getLeading() * 1024 * font.getDivider() / (textHeight / SWF.unitDivisor); + } else { + ascent = font.getAscent(); //((double) font.getAscent() * textHeight / 1024.0 / font.getDivider()); + descent = font.getDescent(); //(double) font.getDescent() * textHeight / 1024.0 / font.getDivider()); + leading = font.getLeading(); //(double) font.getLeading()* textHeight / 1024.0 / font.getDivider()); + } } if (rec.styleFlagsHasXOffset) { x = rec.xOffset; @@ -651,8 +756,24 @@ public abstract class TextTag extends DrawableTag { } } + + if (pos >= selectionStart && pos < selectionEnd) { + Graphics2D g = (Graphics2D) image.getGraphics(); + + RGB borderColor = new RGBA(Color.black); + RGB fillColor = new RGBA(Color.black); + RECT bounds = new RECT(0, (int) Math.round(entry.glyphAdvance * 1024 * font.getDivider() / textHeight), + (int) Math.round(-ascent), (int) Math.round((descent + leading)) + ); + //bounds = shape.getBounds(1); + //bounds.Ymin = (int) Math.round(-ascent); + //bounds.Ymax = (int) Math.round(descent + leading); + Matrix mat2 = Matrix.getTranslateInstance(bounds.Xmin, bounds.Ymin).preConcatenate(mat); + TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), mat2, colorTransform); + } + if (shape != null) { - BitmapExporter.export(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, textColor3, image, 1 /*FIXME??*/, mat, mat, colorTransform, true, false); + BitmapExporter.export(ShapeTag.WIND_EVEN_ODD, 1, swf, shape, pos >= selectionStart && pos < selectionEnd ? Color.white : textColor3, image, 1 /*FIXME??*/, mat, mat, colorTransform, true, false); if (SHAPERECORD.DRAW_BOUNDING_BOX) { RGB borderColor = new RGBA(Color.black); RGB fillColor = new RGBA(new Color(255, 255, 255, 0)); @@ -661,6 +782,8 @@ public abstract class TextTag extends DrawableTag { TextTag.drawBorder(swf, image, borderColor, fillColor, bounds, new MATRIX(), mat, colorTransform); } } + + pos++; x += entry.glyphAdvance; } diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 811adb939..58dd319f5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -25,6 +25,7 @@ import com.jpexs.decompiler.flash.exporters.FrameExporter; 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.tags.DefineEditTextTag; import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; import com.jpexs.decompiler.flash.tags.DefineSceneAndFrameLabelDataTag; import com.jpexs.decompiler.flash.tags.DefineSpriteTag; @@ -53,6 +54,7 @@ import com.jpexs.decompiler.flash.tags.base.RemoveTag; import com.jpexs.decompiler.flash.tags.base.RenderContext; import com.jpexs.decompiler.flash.tags.base.ShapeTag; import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.StaticTextTag; import com.jpexs.decompiler.flash.tags.base.TextTag; import com.jpexs.decompiler.flash.tags.gfx.DefineExternalStreamSound; import com.jpexs.decompiler.flash.types.BlendMode; @@ -66,6 +68,7 @@ import com.jpexs.decompiler.flash.types.MATRIX; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGBA; import com.jpexs.decompiler.flash.types.SOUNDINFO; +import com.jpexs.decompiler.flash.types.TEXTRECORD; import com.jpexs.decompiler.flash.types.filters.BlendComposite; import com.jpexs.decompiler.flash.types.filters.FILTER; import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; @@ -75,6 +78,7 @@ import com.jpexs.helpers.SerializableImage; import java.awt.AlphaComposite; import java.awt.BasicStroke; import java.awt.Color; +import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; @@ -98,6 +102,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.Stack; +import javax.swing.SwingUtilities; import org.w3c.dom.Element; /** @@ -1179,6 +1184,8 @@ public class Timeline { Matrix m = mat.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); + + if (drawable instanceof ButtonTag) { dtime = time; dframe = ButtonTag.FRAME_UP; @@ -1222,6 +1229,156 @@ public class Timeline { img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); img.fillTransparent(); } + + if (drawable instanceof TextTag) { + if (renderContext.cursorPosition != null && renderContext.enableTexts) { + int dx = (int) (viewRect.xMin * unzoom); + int dy = (int) (viewRect.yMin * unzoom); + Point cursorPositionInView = new Point((int) Math.round(renderContext.cursorPosition.x * unzoom) - dx, (int) Math.round(renderContext.cursorPosition.y * unzoom) - dy); + + Shape textShape = ((TextTag) drawable).getOutline(true, 0, 0, 0, renderContext, absMat, true, viewRect, unzoom); + + TextTag textTag = (TextTag) drawable; + + if (textShape.contains(cursorPositionInView)) { + renderContext.mouseOverText = textTag; + } + if (textShape.contains(cursorPositionInView) || (drawable == renderContext.selectionText && renderContext.mouseButton == 1)) { + Rectangle textBounds = textShape.getBounds(); + List textRecords = new ArrayList<>(); + Matrix textMatrix = new Matrix(); + if (textTag instanceof StaticTextTag) { + textRecords = ((StaticTextTag) textTag).textRecords; + MATRIX tm = ((StaticTextTag) textTag).textMatrix; + if (tm != null) { + textMatrix = new Matrix(tm); + } + } + if (textTag instanceof DefineEditTextTag) { + textRecords = ((DefineEditTextTag) textTag).getTextRecords(textTag.getSwf()); + } + + List glyphPositions = TextTag.getGlyphEntriesPositions(textRecords, textTag.getSwf()); + int pos = 0; + renderContext.glyphPosUnderCursor = -1; + int closestPos = -1; + double closestDistance = Double.MAX_VALUE; + Point cursorPosNoTrans = absMat.concatenate(textMatrix).inverse().transform(cursorPositionInView); + for (RECT gp : glyphPositions) { + /*Rectangle2D r = new Rectangle2D.Double( + textBounds.x + gp.Xmin * unzoom, + textBounds.y + gp.Ymin * unzoom, + (gp.Xmax - gp.Xmin)* unzoom, + (gp.Ymax - gp.Ymin)* unzoom + );*/ + Rectangle2D r = new Rectangle2D.Double( + gp.Xmin, + gp.Ymin, + gp.Xmax - gp.Xmin, + gp.Ymax - gp.Ymin + ); + //Shape ts = absMat.toTransform().createTransformedShape(r); + + + + /* + double tx = Math.max(r.getMinX() - cursorPositionInView.getX(), 0); + tx = Math.max(tx, cursorPositionInView.getX() - r.getMaxX()); + + double ty = Math.max(r.getMinY() - cursorPositionInView.getY(), 0); + ty = Math.max(ty, cursorPositionInView.getY() - r.getMaxY()); + + double distance = Math.hypot(tx, ty); + + if (distance < closestDistance) { + closestDistance = distance; + closestPos = pos; + }*/ + + if (r.contains(cursorPosNoTrans)) { + closestPos = pos; + closestDistance = 0; + break; + } + + if (cursorPosNoTrans.y >= r.getY() && cursorPosNoTrans.y <= r.getMaxY()) { + double tx = Math.max(r.getMinX() - cursorPosNoTrans.getX(), 0); + tx = Math.max(tx, cursorPosNoTrans.getX() - r.getMaxX()); + + if (tx < closestDistance) { + closestDistance = tx; + closestPos = pos; + } + } + + /*if (pos >= renderContext.selectionStart && pos < renderContext.selectionEnd) { + Rectangle rPx = new Rectangle( + (int) Math.round(r.getX() / SWF.unitDivisor), + (int) Math.round(r.getY() / SWF.unitDivisor), + (int) Math.round(r.getWidth() / SWF.unitDivisor), + (int) Math.round(r.getHeight() / SWF.unitDivisor)); + + Graphics gi = img.getGraphics(); + gi.setColor(Color.black); + gi.drawRect( + rPx.x, + rPx.y, + rPx.width, + rPx.height + ); + }*/ + + pos++; + } + + if (closestPos == -1) { + if (!glyphPositions.isEmpty()) { + RECT gp = glyphPositions.get(0); + Rectangle2D r = new Rectangle2D.Double( + gp.Xmin * unzoom, + gp.Ymin * unzoom, + gp.Xmax - gp.Xmin, + gp.Ymax - gp.Ymin + ); + if (cursorPosNoTrans.y < r.getY()) { + closestPos = 0; + } + + gp = glyphPositions.get(glyphPositions.size() - 1); + r = new Rectangle2D.Double( + gp.Xmin * unzoom, + gp.Ymin * unzoom, + gp.Xmax - gp.Xmin, + gp.Ymax - gp.Ymin + ); + if (cursorPosNoTrans.y > r.getMaxY()) { + closestPos = glyphPositions.size() - 1; + } + } + } + + if (closestPos > -1) { + RECT gp = glyphPositions.get(closestPos); + Rectangle2D r = new Rectangle2D.Double( + gp.Xmin * unzoom, + gp.Ymin * unzoom, + gp.Xmax - gp.Xmin, + gp.Ymax - gp.Ymin + ); + renderContext.glyphUnderCursorRect = r; + renderContext.glyphUnderCursorXPosition = cursorPosNoTrans.x; + if (renderContext.glyphUnderCursorXPosition < r.getX()) { + renderContext.glyphUnderCursorXPosition = r.getX(); + } + if (renderContext.glyphUnderCursorXPosition > r.getMaxX()) { + renderContext.glyphUnderCursorXPosition = r.getMaxX(); + } + + renderContext.glyphPosUnderCursor = closestPos; + } + } + } + } ColorTransform mergedColorTransform2 = mergedColorTransform; diff --git a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java index 53b9d4b86..27273e641 100644 --- a/src/com/jpexs/decompiler/flash/gui/ImagePanel.java +++ b/src/com/jpexs/decompiler/flash/gui/ImagePanel.java @@ -36,6 +36,7 @@ import com.jpexs.decompiler.flash.math.BezierUtils; import com.jpexs.decompiler.flash.tags.DefineButton2Tag; import com.jpexs.decompiler.flash.tags.DefineButtonSoundTag; import com.jpexs.decompiler.flash.tags.DefineButtonTag; +import com.jpexs.decompiler.flash.tags.DefineEditTextTag; import com.jpexs.decompiler.flash.tags.DoActionTag; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.ButtonTag; @@ -263,6 +264,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private static Cursor addPointCursor; private static Cursor guideXCursor; private static Cursor guideYCursor; + private static Cursor textCursor; private Point2D offsetPoint = new Point2D.Double(0, 0); @@ -283,6 +285,8 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private boolean mutable = false; private boolean alwaysDisplay = false; + + private boolean allowSelectAllTextTypes = false; private RegistrationPointPosition registrationPointPosition = RegistrationPointPosition.CENTER; @@ -389,6 +393,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private SWF guidesSwf = null; private int guidesCharacterId = -1; + + private int textSelectionStart = 0; + private int textSelectionEnd = 0; + private Double textSelectionStartPrecise = null; + private Double textSelectionEndPrecise = null; + private TextTag lastMouseOverText = null; + private TextTag textSelectionText = null; + private boolean selectingText = false; private static int getSnapGuidesDistance() { return Configuration.guidesSnapAccuracy.get().getDistance(); @@ -691,6 +703,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { addPointCursor = loadCursor("add_point", 0, 0); guideXCursor = loadCursor("guide_x", 0, 0); guideYCursor = loadCursor("guide_y", 0, 0); + textCursor = Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR); } catch (IOException ex) { Logger.getLogger(MainPanel.class.getName()).log(Level.SEVERE, null, ex); } @@ -984,6 +997,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { private ButtonTag mouseOverButton = null; + private TextTag mouseOverText = null; + + private int glyphPosUnderCursor = -1; + + private Rectangle2D glyphUnderCursorRect = null; + + private double glyphUnderCursorXPosition = 0; + private boolean autoFit = false; private boolean allowMove = true; @@ -2468,7 +2489,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { return; } } - if (dragStart != null && allowMove && mode == Cursor.DEFAULT_CURSOR) { + if (dragStart != null && allowMove && mode == Cursor.DEFAULT_CURSOR && !selectingText) { Point2D dragEnd = e.getPoint(); Point2D delta = new Point2D.Double(dragEnd.getX() - dragStart.getX(), dragEnd.getY() - dragStart.getY()); Point2D regPointImage = registrationPoint == null ? null : toImagePoint(registrationPoint); @@ -3232,17 +3253,17 @@ public final class ImagePanel extends JPanel implements MediaDisplay { Integer newMode = null; if (nearGuideX) { newMode = MODE_GUIDE_X; - cursor = guideXCursor; + //cursor = guideXCursor; } else if (nearGuideY) { newMode = MODE_GUIDE_Y; - cursor = guideYCursor; + //cursor = guideYCursor; } else { newMode = Cursor.DEFAULT_CURSOR; - cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + //cursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); } - if (getCursor() != cursor) { + /*if (getCursor() != cursor) { setCursor(cursor); - } + }*/ mode = newMode; } } @@ -4044,6 +4065,27 @@ public final class ImagePanel extends JPanel implements MediaDisplay { mouseButton = e.getButton(); lastMouseEvent = e; redraw(); + + TextTag text = iconPanel.mouseOverText; + if (text == null || (!allowSelectAllTextTypes && (!(text instanceof DefineEditTextTag) || ((DefineEditTextTag) text).noSelect))) { + text = null; + } + if (text != null && !doFreeTransform) { + Rectangle2D glyphRect = iconPanel.glyphUnderCursorRect; + if (iconPanel.glyphPosUnderCursor > -1 && glyphRect != null) { + if (mouseButton == 1) { + textSelectionText = text; + textSelectionStartPrecise = iconPanel.glyphPosUnderCursor + (glyphRect.getWidth() == 0 ? 0 : (iconPanel.glyphUnderCursorXPosition - glyphRect.getX()) / glyphRect.getWidth()); + textSelectionEndPrecise = null; + selectingText = true; + } + } else { + textSelectionStartPrecise = null; + } + } else { + textSelectionStartPrecise = null; + } + ButtonTag button = iconPanel.mouseOverButton; if (button != null && !doFreeTransform && !frozenButtons) { DefineButtonSoundTag sounds = button.getSounds(); @@ -4088,7 +4130,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { synchronized (ImagePanel.this) { mouseButton = 0; lastMouseEvent = e; - redraw(); + redraw(); ButtonTag button = iconPanel.mouseOverButton; if (!muted && button != null && !doFreeTransform && !frozenButtons) { DefineButtonSoundTag sounds = button.getSounds(); @@ -4099,6 +4141,7 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } } + selectingText = false; } } }); @@ -4116,6 +4159,17 @@ public final class ImagePanel extends JPanel implements MediaDisplay { synchronized (ImagePanel.this) { lastMouseEvent = e; redraw(); + + TextTag text = iconPanel.mouseOverText; + if (text == null || (!allowSelectAllTextTypes && (!(text instanceof DefineEditTextTag) || ((DefineEditTextTag) text).noSelect))) { + text = null; + } + if (selectingText && textSelectionStartPrecise != null && text != null && !doFreeTransform && SwingUtilities.isLeftMouseButton(e)) { + Rectangle2D glyphRect = iconPanel.glyphUnderCursorRect; + if (iconPanel.glyphPosUnderCursor > -1 && glyphRect != null) { + textSelectionEndPrecise = iconPanel.glyphPosUnderCursor + (glyphRect.getWidth() == 0 ? 0 : (iconPanel.glyphUnderCursorXPosition - glyphRect.getX()) / glyphRect.getWidth()); + } + } } } }); @@ -5269,6 +5323,31 @@ public final class ImagePanel extends JPanel implements MediaDisplay { renderContext.mouseButton = mouseButton; renderContext.stateUnderCursor = new ArrayList<>(); renderContext.enableButtons = !frozenButtons; + + Double selStart = textSelectionStartPrecise; + Double selEnd = textSelectionEndPrecise; + if (selStart != null && selEnd != null) { + if (selStart > selEnd) { + double tmp = selStart; + selStart = selEnd; + selEnd = tmp; + } + int selStartInt = (int) Math.floor(selStart); + double startFract = selStart - selStartInt; + int selEndInt = (int) Math.floor(selEnd); + double endFract = selEnd - selEndInt; + + if (endFract > 0.3) { + selEndInt++; + } + if (startFract > 0.7) { + selStartInt++; + } + + renderContext.selectionStart = selStartInt; + renderContext.selectionEnd = selEndInt; + renderContext.selectionText = textSelectionText; + } SerializableImage img; try { @@ -5460,7 +5539,12 @@ public final class ImagePanel extends JPanel implements MediaDisplay { if (timer == thisTimer) { iconPanel.setImg(img); lastMouseOverButton = iconPanel.mouseOverButton; + lastMouseOverText = iconPanel.mouseOverText; iconPanel.mouseOverButton = renderContext.mouseOverButton; + iconPanel.mouseOverText = renderContext.mouseOverText; + iconPanel.glyphUnderCursorRect = renderContext.glyphUnderCursorRect; + iconPanel.glyphUnderCursorXPosition = renderContext.glyphUnderCursorXPosition; + iconPanel.glyphPosUnderCursor = renderContext.glyphPosUnderCursor; View.execInEventDispatchLater(new Runnable() { @Override public void run() { @@ -5494,16 +5578,18 @@ public final class ImagePanel extends JPanel implements MediaDisplay { newCursor = guideYCursor; } else if (iconPanel.isAltDown() && !selectionMode && !doFreeTransform) { if (depthStateUnderCursor == null) { - newCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + newCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); } else { newCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); } } else if (handCursor) { newCursor = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); + } else if (selectingText || (iconPanel.mouseOverText != null && (allowSelectAllTextTypes || (iconPanel.mouseOverText instanceof DefineEditTextTag && !((DefineEditTextTag) iconPanel.mouseOverText).noSelect)))) { + newCursor = textCursor; } else if (zoomAvailable && iconPanel.hasAllowMove()) { newCursor = Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR); } else { - newCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + newCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); } if (iconPanel.getCursor() != newCursor) { //call setcursor only when needed to avoid cursor flickering when dragging in the tree iconPanel.setCursor(newCursor); @@ -5512,7 +5598,14 @@ public final class ImagePanel extends JPanel implements MediaDisplay { } } ); - + + if (lastMouseOverText != renderContext.mouseOverText) { + /*textSelectionStart = 0; + textSelectionEnd = 0; + textSelectionStartPrecise = null; + textSelectionEndPrecise = null;*/ + } + if (!muted) { if (lastMouseOverButton != renderContext.mouseOverButton) { ButtonTag b = renderContext.mouseOverButton;