diff --git a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java index 1c0611604..1e099ad6b 100644 --- a/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java +++ b/trunk/src/com/jpexs/decompiler/flash/gui/MainFrame.java @@ -74,6 +74,8 @@ import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; import com.jpexs.decompiler.flash.tags.base.Container; import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.text.ParseException; import com.jpexs.decompiler.flash.types.GLYPHENTRY; import com.jpexs.decompiler.flash.types.MATRIX; import com.jpexs.decompiler.flash.types.RECT; @@ -103,6 +105,7 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; +import javax.swing.JButton; import javax.swing.JCheckBoxMenuItem; import javax.swing.JFileChooser; import javax.swing.JFrame; @@ -156,6 +159,8 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi final static String CARDACTIONSCRIPTPANEL = "ActionScript card"; final static String DETAILCARDAS3NAVIGATOR = "Traits list"; final static String DETAILCARDEMPTYPANEL = "Empty card"; + final static String CARDTEXTPANEL = "Text card"; + private JTextField textValue = new JTextField(""); private JPEGTablesTag jtt; private HashMap characters; private List abcList; @@ -165,6 +170,7 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi private JTextField filterField = new JTextField(""); private JPanel searchPanel; private JCheckBoxMenuItem autoDeobfuscateMenuItem; + private JPanel displayWithPreview; public void setPercent(int percent) { progressBar.setValue(percent); @@ -451,18 +457,36 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi list2.addAll(swf.tags); parseCharacters(list2); - //setLayout(new BorderLayout()); + + JPanel textPanel = new JPanel(new BorderLayout()); + textPanel.add(textValue, BorderLayout.CENTER); + JButton textSaveButton = new JButton("Save text", View.getIcon("save16")); + textPanel.add(textSaveButton, BorderLayout.EAST); + textSaveButton.setActionCommand("SAVETEXT"); + textSaveButton.addActionListener(this); + + + + displayWithPreview = new JPanel(new CardLayout()); + displayWithPreview.add(textPanel, CARDTEXTPANEL); + displayWithPreview.setVisible(false); + + JPanel panWithPreview = new JPanel(new BorderLayout()); + panWithPreview.add(displayWithPreview, BorderLayout.SOUTH); + try { flashPanel = new FlashPlayerPanel(this); } catch (FlashUnsupportedException fue) { } displayPanel = new JPanel(new CardLayout()); if (flashPanel != null) { - displayPanel.add(flashPanel, CARDFLASHPANEL); + panWithPreview.add(flashPanel, BorderLayout.CENTER); + displayPanel.add(panWithPreview, CARDFLASHPANEL); } else { JPanel swtPanel = new JPanel(new BorderLayout()); swtPanel.add(new JLabel("
Preview of this object is not available on this platform. (Windows only)
", JLabel.CENTER), BorderLayout.CENTER); swtPanel.setBackground(Color.white); + panWithPreview.add(swtPanel, BorderLayout.CENTER); displayPanel.add(swtPanel, CARDFLASHPANEL); } imagePanel = new ImagePanel(); @@ -974,6 +998,17 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi @Override public void actionPerformed(ActionEvent e) { + if (e.getActionCommand().equals("SAVETEXT")) { + if (oldValue instanceof TextTag) { + try { + ((TextTag) oldValue).setFormattedText(swf.tags, textValue.getText()); + reload(true); + } catch (ParseException ex) { + JOptionPane.showMessageDialog(null, "Invalid text: " + ex.text + " on line " + ex.line, "Error", JOptionPane.ERROR_MESSAGE); + } + } + } + if (e.getActionCommand().equals("AUTODEOBFUSCATE")) { if (JOptionPane.showConfirmDialog(this, "Automatic deobfuscation is a way to decompile obfuscated code.\r\nDeobfuscation leads to slower decompilation and some of the dead code may be eliminated.\r\nIf the code is not obfuscated, it's better to turn autodeobfuscation off.\r\nDo you really want to " + (autoDeobfuscateMenuItem.getState() ? "turn ON" : "turn OFF") + " automatic debfuscation?", "Confirm", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) { Configuration.setConfig("autoDeobfuscate", autoDeobfuscateMenuItem.getState()); @@ -1346,6 +1381,11 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi } private int splitPos = 0; + public void showDetailWithPreview(String card) { + CardLayout cl = (CardLayout) (displayWithPreview.getLayout()); + cl.show(displayWithPreview, card); + } + public void showDetail(String card) { CardLayout cl = (CardLayout) (detailPanel.getLayout()); cl.show(detailPanel, card); @@ -1632,6 +1672,14 @@ public class MainFrame extends JFrame implements ActionListener, TreeSelectionLi Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); } + if (tagObj instanceof TextTag) { + displayWithPreview.setVisible(true); + showDetailWithPreview(CARDTEXTPANEL); + textValue.setText(((TextTag) tagObj).getFormattedText(swf.tags)); + } else { + displayWithPreview.setVisible(false); + } + } else { showCard(CARDEMPTYPANEL); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java index 3e49c1077..162fd8f8a 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineEditTextTag.java @@ -20,6 +20,8 @@ import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.text.ParseException; import com.jpexs.decompiler.flash.types.RECT; import com.jpexs.decompiler.flash.types.RGBA; import java.io.ByteArrayInputStream; @@ -28,6 +30,7 @@ import java.io.IOException; import java.io.OutputStream; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Set; /** @@ -35,7 +38,7 @@ import java.util.Set; * * @author JPEXS */ -public class DefineEditTextTag extends CharacterTag implements BoundedTag { +public class DefineEditTextTag extends CharacterTag implements BoundedTag, TextTag { public int characterID; public RECT bounds; @@ -68,6 +71,25 @@ public class DefineEditTextTag extends CharacterTag implements BoundedTag { public String variableName; public String initialText; + @Override + public String getText(List tags) { + if (hasText) { + return initialText; + } + return ""; + } + + @Override + public String getFormattedText(List tags) { + return getText(tags); + } + + @Override + public void setFormattedText(List tags, String text) throws ParseException { + initialText = text; + hasText = true; + } + @Override public RECT getRect(HashMap allCharacters) { return bounds; diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java index f7bb8a2bd..d5df36abc 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java @@ -27,6 +27,8 @@ import com.jpexs.decompiler.flash.types.SHAPE; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; /** * @@ -49,7 +51,7 @@ public class DefineFont2Tag extends CharacterTag implements FontTag { public int numGlyphs; public long offsetTable[]; public SHAPE glyphShapeTable[]; - public int codeTable[]; + public List codeTable; public int fontAscent; public int fontDescent; public int fontLeading; @@ -57,6 +59,15 @@ public class DefineFont2Tag extends CharacterTag implements FontTag { public RECT fontBoundsTable[]; public KERNINGRECORD fontKerningTable[]; + @Override + public int getGlyphAdvance(int glyphIndex) { + if (fontFlagsHasLayout) { + return fontAdvanceTable[glyphIndex]; + } else { + return glyphShapeTable[glyphIndex].getBounds().getWidth(); + } + } + /** * Gets data bytes * @@ -109,9 +120,9 @@ public class DefineFont2Tag extends CharacterTag implements FontTag { for (int i = 0; i < numGlyphs; i++) { if (fontFlagsWideCodes) { - sos.writeUI16(codeTable[i]); + sos.writeUI16(codeTable.get(i)); } else { - sos.writeUI8(codeTable[i]); + sos.writeUI8(codeTable.get(i)); } } if (fontFlagsHasLayout) { @@ -178,12 +189,12 @@ public class DefineFont2Tag extends CharacterTag implements FontTag { glyphShapeTable[i] = sis.readSHAPE(1); } - codeTable = new int[numGlyphs]; + codeTable = new ArrayList(); //[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { if (fontFlagsWideCodes) { - codeTable[i] = sis.readUI16(); + codeTable.add(sis.readUI16()); } else { - codeTable[i] = sis.readUI8(); + codeTable.add(sis.readUI8()); } } if (fontFlagsHasLayout) { @@ -220,4 +231,14 @@ public class DefineFont2Tag extends CharacterTag implements FontTag { public int getCharacterID() { return fontId; } + + @Override + public char glyphToChar(List tags, int glyphIndex) { + return (char) (int) codeTable.get(glyphIndex); + } + + @Override + public int charToGlyph(List tags, char c) { + return codeTable.indexOf((Integer) (int) c); + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java index dbcdcc9a5..39b99d7b8 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java @@ -30,6 +30,8 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; public class DefineFont3Tag extends CharacterTag implements FontTag { @@ -47,7 +49,7 @@ public class DefineFont3Tag extends CharacterTag implements FontTag { public int numGlyphs; public long offsetTable[]; public SHAPE glyphShapeTable[]; - public int codeTable[]; + public List codeTable; public int fontAscent; public int fontDescent; public int fontLeading; @@ -55,6 +57,25 @@ public class DefineFont3Tag extends CharacterTag implements FontTag { public RECT fontBoundsTable[]; public KERNINGRECORD fontKerningTable[]; + @Override + public int getGlyphAdvance(int glyphIndex) { + if (fontFlagsHasLayout) { + return fontAdvanceTable[glyphIndex] / 20; + } else { + return glyphShapeTable[glyphIndex].getBounds().getWidth(); + } + } + + @Override + public char glyphToChar(List tags, int glyphIndex) { + return (char) (int) codeTable.get(glyphIndex); + } + + @Override + public int charToGlyph(List tags, char c) { + return codeTable.indexOf((Integer) (int) c); + } + public DefineFont3Tag(byte[] data, int version, long pos) throws IOException { super(75, "DefineFont3", data, pos); SWFInputStream sis = new SWFInputStream(new ByteArrayInputStream(data), version); @@ -89,12 +110,12 @@ public class DefineFont3Tag extends CharacterTag implements FontTag { for (int i = 0; i < numGlyphs; i++) { glyphShapeTable[i] = sis.readSHAPE(1); } - codeTable = new int[numGlyphs]; + codeTable = new ArrayList(); //int[numGlyphs]; for (int i = 0; i < numGlyphs; i++) { if (fontFlagsWideCodes) { - codeTable[i] = sis.readUI16(); + codeTable.add(sis.readUI16()); } else { - codeTable[i] = sis.readUI8(); + codeTable.add(sis.readUI8()); } } if (fontFlagsHasLayout) { @@ -177,9 +198,9 @@ public class DefineFont3Tag extends CharacterTag implements FontTag { for (int i = 0; i < numGlyphs; i++) { if (fontFlagsWideCodes) { - sos.writeUI16(codeTable[i]); + sos.writeUI16(codeTable.get(i)); } else { - sos.writeUI8(codeTable[i]); + sos.writeUI8(codeTable.get(i)); } } if (fontFlagsHasLayout) { diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java index ca0e6bd42..349cf0756 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java @@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.util.List; /** * @@ -36,6 +37,41 @@ public class DefineFontTag extends CharacterTag implements FontTag { public int fontId; public int offsetTable[]; public SHAPE glyphShapeTable[]; + private DefineFontInfoTag fontInfoTag = null; + + @Override + public int getGlyphAdvance(int glyphIndex) { + return glyphShapeTable[glyphIndex].getBounds().getWidth(); + } + + private void ensureFontInfo(List tags) { + if (fontInfoTag == null) { + for (Tag t : tags) { + if (t instanceof DefineFontInfoTag) { + if (((DefineFontInfoTag) t).fontId == fontId) { + fontInfoTag = (DefineFontInfoTag) t; + break; + } + } + } + } + } + + @Override + public char glyphToChar(List tags, int glyphIndex) { + ensureFontInfo(tags); + if (fontInfoTag == null) { + return '?'; + } else { + return (char) (int) fontInfoTag.codeTable.get(glyphIndex); + } + } + + @Override + public int charToGlyph(List tags, char c) { + ensureFontInfo(tags); + return fontInfoTag.codeTable.indexOf(c); + } /** * Gets data bytes diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineText2Tag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineText2Tag.java index f4a4410c1..409f2c445 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineText2Tag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineText2Tag.java @@ -18,27 +18,47 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.gui.MainFrame; +import com.jpexs.decompiler.flash.helpers.Helper; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.text.ParseException; +import com.jpexs.decompiler.flash.tags.text.ParsedSymbol; +import static com.jpexs.decompiler.flash.tags.text.SymbolType.COLOR; +import static com.jpexs.decompiler.flash.tags.text.SymbolType.FONT; +import static com.jpexs.decompiler.flash.tags.text.SymbolType.TEXT; +import static com.jpexs.decompiler.flash.tags.text.SymbolType.X; +import static com.jpexs.decompiler.flash.tags.text.SymbolType.Y; +import com.jpexs.decompiler.flash.tags.text.TextLexer; +import com.jpexs.decompiler.flash.types.GLYPHENTRY; 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.TEXTRECORD; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * * * @author JPEXS */ -public class DefineText2Tag extends CharacterTag implements BoundedTag { +public class DefineText2Tag extends CharacterTag implements BoundedTag, TextTag { public int characterID; public RECT textBounds; @@ -47,6 +67,151 @@ public class DefineText2Tag extends CharacterTag implements BoundedTag { public int advanceBits; public List textRecords; + @Override + public String getText(List tags) { + FontTag fnt = null; + String ret = ""; + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasFont) { + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == rec.fontId) { + fnt = ((FontTag) t); + break; + } + } + } + } + ret += rec.getText(tags, fnt); + } + return ret; + } + + @Override + public String getFormattedText(List tags) { + FontTag fnt = null; + String ret = ""; + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasFont) { + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == rec.fontId) { + fnt = ((FontTag) t); + break; + } + } + } + ret += "[font " + rec.fontId + " height:" + rec.textHeight + "]"; + } + if (rec.styleFlagsHasColor) { + ret += "[color " + rec.textColorA.toHexARGB() + "]"; + } + if (rec.styleFlagsHasXOffset) { + ret += "[x " + rec.xOffset + "]"; + } + if (rec.styleFlagsHasYOffset) { + ret += "[y " + rec.yOffset + "]"; + } + ret += Helper.escapeString(rec.getText(tags, fnt)).replace("[", "\\[").replace("]", "\\]"); + } + return ret; + } + + @Override + public void setFormattedText(List tags, String text) throws ParseException { + try { + TextLexer lexer = new TextLexer(new InputStreamReader(new ByteArrayInputStream(text.getBytes("UTF-8")), "UTF-8")); + ParsedSymbol s = null; + textRecords = new ArrayList(); + RGBA colorA = null; + int fontId = -1; + int textHeight = -1; + FontTag font = null; + Integer x = null; + Integer y = null; + glyphBits = 0; + advanceBits = 0; + while ((s = lexer.yylex()) != null) { + switch (s.type) { + case COLOR: + Matcher m = Pattern.compile("([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])").matcher(s.values[0].toString()); + if (m.matches()) { + colorA = new RGBA(Integer.parseInt(m.group(2), 16), Integer.parseInt(m.group(3), 16), Integer.parseInt(m.group(4), 16), Integer.parseInt(m.group(1), 16)); + } + break; + case FONT: + fontId = (Integer) s.values[0]; + textHeight = (Integer) s.values[1]; + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == fontId) { + font = (FontTag) t; + break; + } + } + } + break; + case X: + x = (Integer) s.values[0]; + break; + case Y: + y = (Integer) s.values[0]; + break; + case TEXT: + if (font == null) { + throw new ParseException("Font not defined", lexer.yyline()); + } + TEXTRECORD tr = new TEXTRECORD(); + if (fontId > -1) { + tr.fontId = fontId; + tr.textHeight = textHeight; + fontId = -1; + tr.styleFlagsHasFont = true; + } + if (colorA != null) { + tr.textColorA = colorA; + tr.styleFlagsHasColor = true; + colorA = null; + } + if (x != null) { + tr.xOffset = x; + tr.styleFlagsHasXOffset = true; + x = null; + } + if (y != null) { + tr.yOffset = y; + tr.styleFlagsHasYOffset = true; + y = null; + } + String txt = (String) s.values[0]; + tr.glyphEntries = new GLYPHENTRY[txt.length()]; + for (int i = 0; i < txt.length(); i++) { + char c = txt.charAt(i); + tr.glyphEntries[i] = new GLYPHENTRY(); + tr.glyphEntries[i].glyphIndex = font.charToGlyph(tags, c); + if (tr.glyphEntries[i].glyphIndex == -1) { + throw new ParseException("Font does not contain glyph for character '" + c + "'", lexer.yyline()); + } + tr.glyphEntries[i].glyphAdvance = textHeight * font.getGlyphAdvance(tr.glyphEntries[i].glyphIndex) / 1024; + if (SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphIndex) > glyphBits) { + glyphBits = SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphIndex); + } + if (SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphAdvance) > advanceBits) { + advanceBits = SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphAdvance); + } + + } + textRecords.add(tr); + break; + } + + } + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + } + } + @Override public RECT getRect(HashMap characters) { return textBounds; diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/DefineTextTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/DefineTextTag.java index 597b54260..8c442b15b 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/DefineTextTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/DefineTextTag.java @@ -18,27 +18,42 @@ package com.jpexs.decompiler.flash.tags; import com.jpexs.decompiler.flash.SWFInputStream; import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.gui.MainFrame; +import com.jpexs.decompiler.flash.helpers.Helper; import com.jpexs.decompiler.flash.tags.base.BoundedTag; import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.text.ParseException; +import com.jpexs.decompiler.flash.tags.text.ParsedSymbol; +import com.jpexs.decompiler.flash.tags.text.TextLexer; +import com.jpexs.decompiler.flash.types.GLYPHENTRY; 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.TEXTRECORD; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.InputStreamReader; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * * * @author JPEXS */ -public class DefineTextTag extends CharacterTag implements BoundedTag { +public class DefineTextTag extends CharacterTag implements BoundedTag, TextTag { public int characterID; public RECT textBounds; @@ -47,6 +62,151 @@ public class DefineTextTag extends CharacterTag implements BoundedTag { public int advanceBits; public List textRecords; + @Override + public String getText(List tags) { + FontTag fnt = null; + String ret = ""; + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasFont) { + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == rec.fontId) { + fnt = ((FontTag) t); + break; + } + } + } + } + ret += rec.getText(tags, fnt); + } + return ret; + } + + @Override + public String getFormattedText(List tags) { + FontTag fnt = null; + String ret = ""; + for (TEXTRECORD rec : textRecords) { + if (rec.styleFlagsHasFont) { + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == rec.fontId) { + fnt = ((FontTag) t); + break; + } + } + } + ret += "[font " + rec.fontId + " height:" + rec.textHeight + "]"; + } + if (rec.styleFlagsHasColor) { + ret += "[color " + rec.textColor.toHexRGB() + "]"; + } + if (rec.styleFlagsHasXOffset) { + ret += "[x " + rec.xOffset + "]"; + } + if (rec.styleFlagsHasYOffset) { + ret += "[y " + rec.yOffset + "]"; + } + ret += Helper.escapeString(rec.getText(tags, fnt)).replace("[", "\\[").replace("]", "\\]"); + } + return ret; + } + + @Override + public void setFormattedText(List tags, String text) throws ParseException { + try { + TextLexer lexer = new TextLexer(new InputStreamReader(new ByteArrayInputStream(text.getBytes("UTF-8")), "UTF-8")); + ParsedSymbol s = null; + textRecords = new ArrayList(); + RGB color = null; + int fontId = -1; + int textHeight = -1; + FontTag font = null; + Integer x = null; + Integer y = null; + glyphBits = 0; + advanceBits = 0; + while ((s = lexer.yylex()) != null) { + switch (s.type) { + case COLOR: + Matcher m = Pattern.compile("([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])").matcher(s.values[0].toString()); + if (m.matches()) { + color = new RGB(Integer.parseInt(m.group(1), 16), Integer.parseInt(m.group(2), 16), Integer.parseInt(m.group(3), 16)); + } + break; + case FONT: + fontId = (Integer) s.values[0]; + textHeight = (Integer) s.values[1]; + for (Tag t : tags) { + if (t instanceof FontTag) { + if (((FontTag) t).getFontId() == fontId) { + font = (FontTag) t; + break; + } + } + } + break; + case X: + x = (Integer) s.values[0]; + break; + case Y: + y = (Integer) s.values[0]; + break; + case TEXT: + if (font == null) { + throw new ParseException("Font not defined", lexer.yyline()); + } + TEXTRECORD tr = new TEXTRECORD(); + if (fontId > -1) { + tr.fontId = fontId; + tr.textHeight = textHeight; + fontId = -1; + tr.styleFlagsHasFont = true; + } + if (color != null) { + tr.textColor = color; + tr.styleFlagsHasColor = true; + color = null; + } + if (x != null) { + tr.xOffset = x; + tr.styleFlagsHasXOffset = true; + x = null; + } + if (y != null) { + tr.yOffset = y; + tr.styleFlagsHasYOffset = true; + y = null; + } + String txt = (String) s.values[0]; + tr.glyphEntries = new GLYPHENTRY[txt.length()]; + for (int i = 0; i < txt.length(); i++) { + char c = txt.charAt(i); + tr.glyphEntries[i] = new GLYPHENTRY(); + tr.glyphEntries[i].glyphIndex = font.charToGlyph(tags, c); + if (tr.glyphEntries[i].glyphIndex == -1) { + throw new ParseException("Font does not contain glyph for character '" + c + "'", lexer.yyline()); + } + tr.glyphEntries[i].glyphAdvance = textHeight * font.getGlyphAdvance(tr.glyphEntries[i].glyphIndex) / 1024; + if (SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphIndex) > glyphBits) { + glyphBits = SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphIndex); + } + if (SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphAdvance) > advanceBits) { + advanceBits = SWFOutputStream.getNeededBitsS(tr.glyphEntries[i].glyphAdvance); + } + + } + textRecords.add(tr); + break; + } + + } + } catch (UnsupportedEncodingException ex) { + Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); + } catch (IOException ex) { + } + } + @Override public int getCharacterID() { return characterID; diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/FontTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/FontTag.java index a9689f0c3..50c8c9048 100644 --- a/trunk/src/com/jpexs/decompiler/flash/tags/base/FontTag.java +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/FontTag.java @@ -16,7 +16,9 @@ */ package com.jpexs.decompiler.flash.tags.base; +import com.jpexs.decompiler.flash.tags.Tag; import com.jpexs.decompiler.flash.types.SHAPE; +import java.util.List; /** * @@ -27,4 +29,10 @@ public interface FontTag extends AloneTag { public int getFontId(); public SHAPE[] getGlyphShapeTable(); + + public char glyphToChar(List tags, int glyphIndex); + + public int charToGlyph(List tags, char c); + + public int getGlyphAdvance(int glyphIndex); } diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java b/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java new file mode 100644 index 000000000..8466df034 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/base/TextTag.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2013 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.text.ParseException; +import java.util.List; + +/** + * + * @author JPEXS + */ +public interface TextTag { + + public String getText(List tags); + + public String getFormattedText(List tags); + + public void setFormattedText(List tags, String text) throws ParseException; +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/text/ParseException.java b/trunk/src/com/jpexs/decompiler/flash/tags/text/ParseException.java new file mode 100644 index 000000000..17873ebf6 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/text/ParseException.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2010-2013 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.text; + +public class ParseException extends Exception { + + public long line; + public String text; + + public ParseException(String text, long line) { + super("ParseException:" + text + " on line " + line); + this.line = line; + this.text = text; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/text/ParsedSymbol.java b/trunk/src/com/jpexs/decompiler/flash/tags/text/ParsedSymbol.java new file mode 100644 index 000000000..9e02a941f --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/text/ParsedSymbol.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.text; + +/** + * + * @author JPEXS + */ +public class ParsedSymbol { + + public Object[] values; + public SymbolType type; + + public ParsedSymbol(SymbolType type) { + this.type = type; + this.values = new Object[0]; + } + + public ParsedSymbol(SymbolType type, Object... values) { + this.type = type; + this.values = values; + } + + @Override + public String toString() { + String v = ""; + boolean first = true; + for (Object o : values) { + if (!first) { + v += ", "; + } + first = false; + v += o.toString(); + } + return type.toString() + " " + v; + } + + public boolean isType(SymbolType... types) { + for (SymbolType t : types) { + if (type == t) { + return true; + } + } + return false; + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/text/SymbolType.java b/trunk/src/com/jpexs/decompiler/flash/tags/text/SymbolType.java new file mode 100644 index 000000000..bb6fd2d01 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/text/SymbolType.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2013 JPEXS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package com.jpexs.decompiler.flash.tags.text; + +/** + * + * @author JPEXS + */ +public enum SymbolType { + + TEXT, + FONT, + COLOR, + X, + Y +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/text/TextLexer.java b/trunk/src/com/jpexs/decompiler/flash/tags/text/TextLexer.java new file mode 100644 index 000000000..5dcc6306a --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/text/TextLexer.java @@ -0,0 +1,801 @@ +/* The following code was generated by JFlex 1.4.3 on 28.4.13 11:46 */ + +/* Flash assembler language lexer specification */ +package com.jpexs.decompiler.flash.tags.text; + +import java.util.regex.*; + +/** + * This class is a scanner generated by + * JFlex 1.4.3 on 28.4.13 11:46 from the + * specification file + * D:/Dokumenty/Programovani/JavaSE/FFDec/trunk/src/com/jpexs/decompiler/flash/tags/text/text.flex + */ +public final class TextLexer { + + /** + * This character denotes the end of file + */ + public static final int YYEOF = -1; + /** + * initial size of the lookahead buffer + */ + private static final int ZZ_BUFFERSIZE = 16384; + /** + * lexical states + */ + public static final int YYINITIAL = 0; + /** + * ZZ_LEXSTATE[l] is the state in the DFA for the lexical state l + * ZZ_LEXSTATE[l+1] is the state in the DFA for the lexical state l at the + * beginning of a line l is of the form l = 2*k, k a non negative integer + */ + private static final int ZZ_LEXSTATE[] = { + 0, 0 + }; + /** + * Translates characters to character classes + */ + private static final String ZZ_CMAP_PACKED = + "\12\0\1\33\25\0\1\12\1\0\1\31\1\24\3\0\1\32\5\0" + + "\1\3\2\0\1\1\11\2\1\17\40\0\1\5\1\27\1\20\3\0" + + "\1\4\1\30\1\21\1\4\1\14\1\6\1\16\1\13\1\15\2\0" + + "\1\22\1\0\1\10\1\7\2\0\1\23\1\0\1\11\3\0\1\25" + + "\1\26\uff86\0"; + /** + * Translates characters to character classes + */ + private static final char[] ZZ_CMAP = zzUnpackCMap(ZZ_CMAP_PACKED); + /** + * Translates DFA states to action switch labels. + */ + private static final int[] ZZ_ACTION = zzUnpackAction(); + private static final String ZZ_ACTION_PACKED_0 = + "\1\0\3\1\1\2\4\0\1\3\1\4\1\5\1\6" + + "\1\7\1\10\1\11\1\12\1\13\1\14\1\15\16\0" + + "\1\16\1\17\25\0\1\20\3\0\1\21"; + + private static int[] zzUnpackAction() { + int[] result = new int[62]; + int offset = 0; + offset = zzUnpackAction(ZZ_ACTION_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAction(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { + result[j++] = value; + } while (--count > 0); + } + return j; + } + /** + * Translates a state to a row index in the transition table + */ + private static final int[] ZZ_ROWMAP = zzUnpackRowMap(); + private static final String ZZ_ROWMAP_PACKED_0 = + "\0\0\0\34\0\70\0\124\0\34\0\160\0\214\0\250" + + "\0\304\0\34\0\34\0\34\0\34\0\34\0\34\0\34" + + "\0\34\0\34\0\34\0\34\0\340\0\374\0\u0118\0\u0134" + + "\0\u0150\0\u016c\0\u0188\0\u01a4\0\u01c0\0\u01dc\0\u01f8\0\u0214" + + "\0\u0230\0\u024c\0\34\0\34\0\u0268\0\u0284\0\u02a0\0\u02bc" + + "\0\u02d8\0\u02f4\0\u0310\0\u032c\0\u0348\0\u0364\0\u0380\0\u039c" + + "\0\u03b8\0\u03d4\0\u03f0\0\u040c\0\u0428\0\u0444\0\u0460\0\u047c" + + "\0\u0498\0\34\0\u04b4\0\u04d0\0\u04ec\0\34"; + + private static int[] zzUnpackRowMap() { + int[] result = new int[62]; + int offset = 0; + offset = zzUnpackRowMap(ZZ_ROWMAP_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackRowMap(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int high = packed.charAt(i++) << 16; + result[j++] = high | packed.charAt(i++); + } + return j; + } + /** + * The transition table of the DFA + */ + private static final int[] ZZ_TRANS = zzUnpackTrans(); + private static final String ZZ_TRANS_PACKED_0 = + "\5\2\1\3\21\2\1\4\3\2\1\5\42\0\1\6" + + "\12\0\1\7\3\0\1\10\1\11\5\0\5\12\1\13" + + "\1\14\1\12\1\15\1\16\6\12\1\17\2\12\1\20" + + "\3\12\1\21\1\22\1\23\1\24\10\0\1\25\33\0" + + "\1\26\36\0\1\27\33\0\1\30\31\0\1\31\45\0" + + "\1\32\12\0\1\33\1\34\1\35\31\0\1\36\1\37" + + "\1\40\41\0\1\41\31\0\1\42\44\0\1\43\14\0" + + "\2\34\15\0\1\43\14\0\1\33\1\34\51\0\1\44" + + "\14\0\2\37\15\0\1\44\14\0\1\36\1\37\43\0" + + "\1\45\44\0\1\46\11\0\1\47\1\50\43\0\1\51" + + "\33\0\1\52\22\0\2\50\7\0\1\52\45\0\1\53" + + "\22\0\1\54\21\0\2\55\1\0\1\55\1\0\1\55" + + "\5\0\1\55\4\0\1\55\6\0\1\55\17\0\1\56" + + "\20\0\2\57\1\0\1\57\1\0\1\57\5\0\1\57" + + "\4\0\1\57\6\0\1\57\20\0\1\60\17\0\2\61" + + "\1\0\1\61\1\0\1\61\5\0\1\61\4\0\1\61" + + "\6\0\1\61\21\0\1\62\16\0\2\63\1\0\1\63" + + "\1\0\1\63\5\0\1\63\4\0\1\63\6\0\1\63" + + "\16\0\1\64\21\0\2\65\1\0\1\65\1\0\1\65" + + "\5\0\1\65\4\0\1\65\6\0\1\65\14\0\1\66" + + "\23\0\2\67\1\0\1\67\1\0\1\67\5\0\1\67" + + "\4\0\1\67\6\0\1\67\22\0\1\70\15\0\2\71" + + "\1\0\1\71\1\0\1\71\5\0\1\71\3\0\1\72" + + "\1\71\6\0\1\71\4\0\1\73\1\74\32\0\2\75" + + "\1\0\1\75\1\0\1\75\5\0\1\75\4\0\1\75" + + "\6\0\1\75\23\0\1\76\14\0\2\74\15\0\1\76" + + "\33\0\1\72\13\0"; + + private static int[] zzUnpackTrans() { + int[] result = new int[1288]; + int offset = 0; + offset = zzUnpackTrans(ZZ_TRANS_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackTrans(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + value--; + do { + result[j++] = value; + } while (--count > 0); + } + return j; + } + /* error codes */ + private static final int ZZ_UNKNOWN_ERROR = 0; + private static final int ZZ_NO_MATCH = 1; + private static final int ZZ_PUSHBACK_2BIG = 2; + + /* error messages for the codes above */ + private static final String ZZ_ERROR_MSG[] = { + "Unkown internal scanner error", + "Error: could not match input", + "Error: pushback value was too large" + }; + /** + * ZZ_ATTRIBUTE[aState] contains the attributes of state + * aState + */ + private static final int[] ZZ_ATTRIBUTE = zzUnpackAttribute(); + private static final String ZZ_ATTRIBUTE_PACKED_0 = + "\1\0\1\11\2\1\1\11\4\0\13\11\16\0\2\11" + + "\25\0\1\11\3\0\1\11"; + + private static int[] zzUnpackAttribute() { + int[] result = new int[62]; + int offset = 0; + offset = zzUnpackAttribute(ZZ_ATTRIBUTE_PACKED_0, offset, result); + return result; + } + + private static int zzUnpackAttribute(String packed, int offset, int[] result) { + int i = 0; /* index in packed string */ + int j = offset; /* index in unpacked array */ + int l = packed.length(); + while (i < l) { + int count = packed.charAt(i++); + int value = packed.charAt(i++); + do { + result[j++] = value; + } while (--count > 0); + } + return j; + } + /** + * the input device + */ + private java.io.Reader zzReader; + /** + * the current state of the DFA + */ + private int zzState; + /** + * the current lexical state + */ + private int zzLexicalState = YYINITIAL; + /** + * this buffer contains the current text to be matched and is the source of + * the yytext() string + */ + private char zzBuffer[] = new char[ZZ_BUFFERSIZE]; + /** + * the textposition at the last accepting state + */ + private int zzMarkedPos; + /** + * the current text position in the buffer + */ + private int zzCurrentPos; + /** + * startRead marks the beginning of the yytext() string in the buffer + */ + private int zzStartRead; + /** + * endRead marks the last character in the buffer, that has been read from + * input + */ + private int zzEndRead; + /** + * number of newlines encountered up to the start of the matched text + */ + private int yyline; + /** + * the number of characters up to the start of the matched text + */ + private int yychar; + /** + * the number of characters from the last newline up to the start of the + * matched text + */ + private int yycolumn; + /** + * zzAtBOL == true <=> the scanner is currently at the beginning of a line + */ + private boolean zzAtBOL = true; + /** + * zzAtEOF == true <=> the scanner is at the EOF + */ + private boolean zzAtEOF; + /** + * denotes if the user-EOF-code has already been executed + */ + private boolean zzEOFDone; + + /* user code: */ + StringBuffer string = null; + boolean finish = false; + + /** + * Create an empty lexer, yyrset will be called later to reset and assign + * the reader + */ + public TextLexer() { + } + + public int yychar() { + return yychar; + } + + public int yyline() { + return yyline + 1; + } + + /** + * Creates a new scanner There is also a java.io.InputStream version of this + * constructor. + * + * @param in the java.io.Reader to read input from. + */ + public TextLexer(java.io.Reader in) { + this.zzReader = in; + } + + /** + * Creates a new scanner. There is also java.io.Reader version of this + * constructor. + * + * @param in the java.io.Inputstream to read input from. + */ + public TextLexer(java.io.InputStream in) { + this(new java.io.InputStreamReader(in)); + } + + /** + * Unpacks the compressed character translation table. + * + * @param packed the packed character translation table + * @return the unpacked character translation table + */ + private static char[] zzUnpackCMap(String packed) { + char[] map = new char[0x10000]; + int i = 0; /* index in packed string */ + int j = 0; /* index in unpacked array */ + while (i < 84) { + int count = packed.charAt(i++); + char value = packed.charAt(i++); + do { + map[j++] = value; + } while (--count > 0); + } + return map; + } + + /** + * Refills the input buffer. + * + * @return false, iff there was new input. + * + * @exception java.io.IOException if any I/O-Error occurs + */ + private boolean zzRefill() throws java.io.IOException { + + /* first: make room (if you can) */ + if (zzStartRead > 0) { + System.arraycopy(zzBuffer, zzStartRead, + zzBuffer, 0, + zzEndRead - zzStartRead); + + /* translate stored positions */ + zzEndRead -= zzStartRead; + zzCurrentPos -= zzStartRead; + zzMarkedPos -= zzStartRead; + zzStartRead = 0; + } + + /* is the buffer big enough? */ + if (zzCurrentPos >= zzBuffer.length) { + /* if not: blow it up */ + char newBuffer[] = new char[zzCurrentPos * 2]; + System.arraycopy(zzBuffer, 0, newBuffer, 0, zzBuffer.length); + zzBuffer = newBuffer; + } + + /* finally: fill the buffer with new input */ + int numRead = zzReader.read(zzBuffer, zzEndRead, + zzBuffer.length - zzEndRead); + + if (numRead > 0) { + zzEndRead += numRead; + return false; + } + // unlikely but not impossible: read 0 characters, but not at end of stream + if (numRead == 0) { + int c = zzReader.read(); + if (c == -1) { + return true; + } else { + zzBuffer[zzEndRead++] = (char) c; + return false; + } + } + + // numRead < 0 + return true; + } + + /** + * Closes the input stream. + */ + public final void yyclose() throws java.io.IOException { + zzAtEOF = true; /* indicate end of file */ + zzEndRead = zzStartRead; /* invalidate buffer */ + + if (zzReader != null) { + zzReader.close(); + } + } + + /** + * Resets the scanner to read from a new input stream. Does not close the + * old reader. + * + * All internal variables are reset, the old input stream + * cannot be reused (internal buffer is discarded and lost). Lexical + * state is set to ZZ_INITIAL. + * + * @param reader the new input stream + */ + public final void yyreset(java.io.Reader reader) { + zzReader = reader; + zzAtBOL = true; + zzAtEOF = false; + zzEOFDone = false; + zzEndRead = zzStartRead = 0; + zzCurrentPos = zzMarkedPos = 0; + yyline = yychar = yycolumn = 0; + zzLexicalState = YYINITIAL; + } + + /** + * Returns the current lexical state. + */ + public final int yystate() { + return zzLexicalState; + } + + /** + * Enters a new lexical state + * + * @param newState the new lexical state + */ + public final void yybegin(int newState) { + zzLexicalState = newState; + } + + /** + * Returns the text matched by the current regular expression. + */ + public final String yytext() { + return new String(zzBuffer, zzStartRead, zzMarkedPos - zzStartRead); + } + + /** + * Returns the character at position pos from the matched text. + * + * It is equivalent to yytext().charAt(pos), but faster + * + * @param pos the position of the character to fetch. A value from 0 to + * yylength()-1. + * + * @return the character at position pos + */ + public final char yycharat(int pos) { + return zzBuffer[zzStartRead + pos]; + } + + /** + * Returns the length of the matched text region. + */ + public final int yylength() { + return zzMarkedPos - zzStartRead; + } + + /** + * Reports an error that occured while scanning. + * + * In a wellformed scanner (no or only correct usage of yypushback(int) and + * a match-all fallback rule) this method will only be called with things + * that "Can't Possibly Happen". If this method is called, something is + * seriously wrong (e.g. a JFlex bug producing a faulty scanner etc.). + * + * Usual syntax/scanner level error handling should be done in error + * fallback rules. + * + * @param errorCode the code of the errormessage to display + */ + private void zzScanError(int errorCode) { + String message; + try { + message = ZZ_ERROR_MSG[errorCode]; + } catch (ArrayIndexOutOfBoundsException e) { + message = ZZ_ERROR_MSG[ZZ_UNKNOWN_ERROR]; + } + + throw new Error(message); + } + + /** + * Pushes the specified amount of characters back into the input stream. + * + * They will be read again by then next call of the scanning method + * + * @param number the number of characters to be read again. This number must + * not be greater than yylength()! + */ + public void yypushback(int number) { + if (number > yylength()) { + zzScanError(ZZ_PUSHBACK_2BIG); + } + + zzMarkedPos -= number; + } + + /** + * Resumes scanning until the next regular expression is matched, the end of + * input is encountered or an I/O-Error occurs. + * + * @return the next token + * @exception java.io.IOException if any I/O-Error occurs + */ + public ParsedSymbol yylex() throws java.io.IOException, ParseException { + int zzInput; + int zzAction; + + // cached fields: + int zzCurrentPosL; + int zzMarkedPosL; + int zzEndReadL = zzEndRead; + char[] zzBufferL = zzBuffer; + char[] zzCMapL = ZZ_CMAP; + + int[] zzTransL = ZZ_TRANS; + int[] zzRowMapL = ZZ_ROWMAP; + int[] zzAttrL = ZZ_ATTRIBUTE; + + while (true) { + zzMarkedPosL = zzMarkedPos; + + yychar += zzMarkedPosL - zzStartRead; + + boolean zzR = false; + for (zzCurrentPosL = zzStartRead; zzCurrentPosL < zzMarkedPosL; + zzCurrentPosL++) { + switch (zzBufferL[zzCurrentPosL]) { + case '\u000B': + case '\u000C': + case '\u0085': + case '\u2028': + case '\u2029': + yyline++; + yycolumn = 0; + zzR = false; + break; + case '\r': + yyline++; + yycolumn = 0; + zzR = true; + break; + case '\n': + if (zzR) { + zzR = false; + } else { + yyline++; + yycolumn = 0; + } + break; + default: + zzR = false; + yycolumn++; + } + } + + if (zzR) { + // peek one character ahead if it is \n (if we have counted one line too much) + boolean zzPeek; + if (zzMarkedPosL < zzEndReadL) { + zzPeek = zzBufferL[zzMarkedPosL] == '\n'; + } else if (zzAtEOF) { + zzPeek = false; + } else { + boolean eof = zzRefill(); + zzEndReadL = zzEndRead; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + if (eof) { + zzPeek = false; + } else { + zzPeek = zzBufferL[zzMarkedPosL] == '\n'; + } + } + if (zzPeek) { + yyline--; + } + } + zzAction = -1; + + zzCurrentPosL = zzCurrentPos = zzStartRead = zzMarkedPosL; + + zzState = ZZ_LEXSTATE[zzLexicalState]; + + + zzForAction: + { + while (true) { + + if (zzCurrentPosL < zzEndReadL) { + zzInput = zzBufferL[zzCurrentPosL++]; + } else if (zzAtEOF) { + zzInput = YYEOF; + break zzForAction; + } else { + // store back cached positions + zzCurrentPos = zzCurrentPosL; + zzMarkedPos = zzMarkedPosL; + boolean eof = zzRefill(); + // get translated positions and possibly new buffer + zzCurrentPosL = zzCurrentPos; + zzMarkedPosL = zzMarkedPos; + zzBufferL = zzBuffer; + zzEndReadL = zzEndRead; + if (eof) { + zzInput = YYEOF; + break zzForAction; + } else { + zzInput = zzBufferL[zzCurrentPosL++]; + } + } + int zzNext = zzTransL[ zzRowMapL[zzState] + zzCMapL[zzInput]]; + if (zzNext == -1) { + break zzForAction; + } + zzState = zzNext; + + int zzAttributes = zzAttrL[zzState]; + if ((zzAttributes & 1) == 1) { + zzAction = zzState; + zzMarkedPosL = zzCurrentPosL; + if ((zzAttributes & 8) == 8) { + break zzForAction; + } + } + + } + } + + // store back cached position + zzMarkedPos = zzMarkedPosL; + + switch (zzAction < 0 ? zzAction : ZZ_ACTION[zzAction]) { + case 1: { + if (string == null) { + string = new StringBuffer(); + } + string.append(yytext()); + } + case 18: + break; + case 3: { + throw new ParseException("Illegal escape sequence \"" + yytext() + "\"", yyline + 1); + } + case 19: + break; + case 6: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\n'); + } + case 20: + break; + case 11: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\b'); + } + case 21: + break; + case 13: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\''); + } + case 22: + break; + case 16: { + if (string == null) { + return new ParsedSymbol(SymbolType.COLOR, yytext().substring(8, yytext().length() - 1)); + } else { + yypushback(yylength()); + String ret = string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT, ret.toString()); + } + } + case 23: + break; + case 8: { + if (string == null) { + string = new StringBuffer(); + } + string.append(']'); + } + case 24: + break; + case 15: { + if (string == null) { + return new ParsedSymbol(SymbolType.Y, Integer.parseInt(yytext().substring(3, yytext().length() - 1))); + } else { + yypushback(yylength()); + String ret = string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT, ret.toString()); + } + } + case 25: + break; + case 14: { + if (string == null) { + return new ParsedSymbol(SymbolType.X, Integer.parseInt(yytext().substring(3, yytext().length() - 1))); + } else { + yypushback(yylength()); + String ret = string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT, ret.toString()); + } + } + case 26: + break; + case 17: { + if (string == null) { + Pattern pat = Pattern.compile("\\[font ([0-9]+) height:([0-9]+)\\]"); + Matcher m = pat.matcher(yytext()); + if (m.matches()) { + return new ParsedSymbol(SymbolType.FONT, Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2))); + } + } else { + yypushback(yylength()); + String ret = string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT, ret.toString()); + } + } + case 27: + break; + case 7: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\t'); + } + case 28: + break; + case 4: { + if (string == null) { + string = new StringBuffer(); + } + string.append('['); + } + case 29: + break; + case 10: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\\'); + } + case 30: + break; + case 12: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\"'); + } + case 31: + break; + case 9: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\r'); + } + case 32: + break; + case 5: { + if (string == null) { + string = new StringBuffer(); + } + string.append('\f'); + } + case 33: + break; + case 2: { + } + case 34: + break; + default: + if (zzInput == YYEOF && zzStartRead == zzCurrentPos) { + zzAtEOF = true; + switch (zzLexicalState) { + case YYINITIAL: { + if (finish) { + return null; + } else { + finish = true; + return new ParsedSymbol(SymbolType.TEXT, string.toString()); + } + } + case 63: + break; + default: { + return null; + } + } + } else { + zzScanError(ZZ_NO_MATCH); + } + } + } + } +} diff --git a/trunk/src/com/jpexs/decompiler/flash/tags/text/text.flex b/trunk/src/com/jpexs/decompiler/flash/tags/text/text.flex new file mode 100644 index 000000000..9fb3f5d68 --- /dev/null +++ b/trunk/src/com/jpexs/decompiler/flash/tags/text/text.flex @@ -0,0 +1,122 @@ +/* Flash assembler language lexer specification */ + +package com.jpexs.decompiler.flash.tags.text; + +import java.util.regex.*; + +%% + +%public +%class TextLexer +%final +%unicode +%char +%line +%column +%type ParsedSymbol +%throws ParseException + +%{ + + StringBuffer string = null; + boolean finish=false; + + + /** + * Create an empty lexer, yyrset will be called later to reset and assign + * the reader + */ + public TextLexer() { + + } + + public int yychar() { + return yychar; + } + + public int yyline() { + return yyline+1; + } + +%} + +PositiveNumber = 0 | [1-9][0-9]* +NegativeNumber = - {PositiveNumber} +Number = {PositiveNumber}|{NegativeNumber} +Hex = [0-9a-f][0-9a-f] + +Font = "[font " {PositiveNumber} " height:" {PositiveNumber} "]" +Color = "[color #" {Hex}{Hex}{Hex} "]" +ColorA = "[color #" {Hex}{Hex}{Hex}{Hex} "]" +X = "[x " {Number} "]" +Y = "[y " {Number} "]" + + +%% + + { + {Font} { + if(string==null){ + Pattern pat = Pattern.compile("\\[font ([0-9]+) height:([0-9]+)\\]"); + Matcher m=pat.matcher(yytext()); + if(m.matches()){ + return new ParsedSymbol(SymbolType.FONT,Integer.parseInt(m.group(1)),Integer.parseInt(m.group(2))); + } + }else{ + yypushback(yylength()); + String ret=string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT,ret.toString()); + } + } + {Color}|{ColorA} { + if(string==null){ + return new ParsedSymbol(SymbolType.COLOR,yytext().substring(8,yytext().length()-1)); + }else{ + yypushback(yylength()); + String ret=string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT,ret.toString()); + } + } + {X} { + if(string==null){ + return new ParsedSymbol(SymbolType.X,Integer.parseInt(yytext().substring(3,yytext().length()-1))); + }else{ + yypushback(yylength()); + String ret=string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT,ret.toString()); + } + } + {Y} { + if(string==null){ + return new ParsedSymbol(SymbolType.Y,Integer.parseInt(yytext().substring(3,yytext().length()-1))); + }else{ + yypushback(yylength()); + String ret=string.toString(); + string = null; + return new ParsedSymbol(SymbolType.TEXT,ret.toString()); + } + } + /* escape sequences */ + "\\[" { if(string==null) string=new StringBuffer(); string.append( '[' ); } + "\\]" { if(string==null) string=new StringBuffer(); string.append( ']' ); } + "\\b" { if(string==null) string=new StringBuffer(); string.append( '\b' ); } + "\\t" { if(string==null) string=new StringBuffer(); string.append( '\t' ); } + "\\n" { if(string==null) string=new StringBuffer(); string.append( '\n' ); } + "\\f" { if(string==null) string=new StringBuffer(); string.append( '\f' ); } + "\\r" { if(string==null) string=new StringBuffer(); string.append( '\r' ); } + "\\\"" { if(string==null) string=new StringBuffer(); string.append( '\"' ); } + "\\'" { if(string==null) string=new StringBuffer(); string.append( '\'' ); } + "\\\\" { if(string==null) string=new StringBuffer(); string.append( '\\' ); } + + /* error cases */ + \\. { throw new ParseException("Illegal escape sequence \""+yytext()+"\"",yyline+1); } + . { if(string==null) string=new StringBuffer(); string.append( yytext() ); } + <> { if(finish){return null;}else{finish=true; return new ParsedSymbol(SymbolType.TEXT,string.toString());}} +} + +/* error fallback */ +.|\n { } +<> { return null; } diff --git a/trunk/src/com/jpexs/decompiler/flash/types/RGBA.java b/trunk/src/com/jpexs/decompiler/flash/types/RGBA.java index 91da19884..4aad985f0 100644 --- a/trunk/src/com/jpexs/decompiler/flash/types/RGBA.java +++ b/trunk/src/com/jpexs/decompiler/flash/types/RGBA.java @@ -55,4 +55,34 @@ public class RGBA { } return "#" + rh + gh + bh; } + + public String toHexARGB() { + String ra = Integer.toHexString(alpha); + if (ra.length() < 2) { + ra = "0" + ra; + } + String rh = Integer.toHexString(red); + if (rh.length() < 2) { + rh = "0" + rh; + } + String gh = Integer.toHexString(green); + if (gh.length() < 2) { + gh = "0" + gh; + } + String bh = Integer.toHexString(blue); + if (bh.length() < 2) { + bh = "0" + bh; + } + return "#" + ra + rh + gh + bh; + } + + public RGBA(int red, int green, int blue, int alpha) { + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + + public RGBA() { + } } diff --git a/trunk/src/com/jpexs/decompiler/flash/types/TEXTRECORD.java b/trunk/src/com/jpexs/decompiler/flash/types/TEXTRECORD.java index 01f0d2cc1..6600c090d 100644 --- a/trunk/src/com/jpexs/decompiler/flash/types/TEXTRECORD.java +++ b/trunk/src/com/jpexs/decompiler/flash/types/TEXTRECORD.java @@ -16,6 +16,10 @@ */ package com.jpexs.decompiler.flash.types; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import java.util.List; + /** * * @author JPEXS @@ -33,4 +37,12 @@ public class TEXTRECORD { public int yOffset; public int textHeight; public GLYPHENTRY glyphEntries[]; + + public String getText(List tags, FontTag font) { + String ret = ""; + for (GLYPHENTRY ge : glyphEntries) { + ret += font.glyphToChar(tags, ge.glyphIndex); + } + return ret; + } }