diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java index f0fbb842d..6a700163b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont2Tag.java @@ -1,549 +1,585 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.FontHelper; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.KERNINGRECORD; -import com.jpexs.decompiler.flash.types.LANGCODE; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.awt.Font; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) -public class DefineFont2Tag extends FontTag { - - public static final int ID = 48; - - public static final String NAME = "DefineFont2"; - - @SWFType(BasicType.UI16) - public int fontID; - - public boolean fontFlagsHasLayout; - - public boolean fontFlagsShiftJIS; - - public boolean fontFlagsSmallText; - - public boolean fontFlagsANSI; - - public boolean fontFlagsWideOffsets; - - public boolean fontFlagsWideCodes; - - public boolean fontFlagsItalic; - - public boolean fontFlagsBold; - - public LANGCODE languageCode; - - public String fontName; - - public List glyphShapeTable; - - @SWFType(value = BasicType.UI8, alternateValue = BasicType.UI16, alternateCondition = "fontFlagsWideCodes") - public List codeTable; - - @SWFType(BasicType.UI16) - @Conditional("fontFlagsHasLayout") - public int fontAscent; - - @SWFType(BasicType.UI16) - @Conditional("fontFlagsHasLayout") - public int fontDescent; - - @SWFType(BasicType.SI16) - @Conditional("fontFlagsHasLayout") - public int fontLeading; - - @SWFType(BasicType.SI16) - @Conditional("fontFlagsHasLayout") - public List fontAdvanceTable; - - @Conditional("fontFlagsHasLayout") - public List fontBoundsTable; - - @Conditional("fontFlagsHasLayout") - public List fontKerningTable; - - /** - * Constructor - * - * @param swf - */ - public DefineFont2Tag(SWF swf) { - super(swf, ID, NAME, null); - fontID = swf.getNextCharacterId(); - languageCode = new LANGCODE(); - fontName = "New font"; - glyphShapeTable = new ArrayList<>(); - codeTable = new ArrayList<>(); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineFont2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - fontID = sis.readUI16("fontId"); - fontFlagsHasLayout = sis.readUB(1, "fontFlagsHasLayout") == 1; - fontFlagsShiftJIS = sis.readUB(1, "fontFlagsShiftJIS") == 1; - fontFlagsSmallText = sis.readUB(1, "fontFlagsSmallText") == 1; - fontFlagsANSI = sis.readUB(1, "fontFlagsANSI") == 1; - fontFlagsWideOffsets = sis.readUB(1, "fontFlagsWideOffsets") == 1; - fontFlagsWideCodes = sis.readUB(1, "fontFlagsWideCodes") == 1; - fontFlagsItalic = sis.readUB(1, "fontFlagsItalic") == 1; - fontFlagsBold = sis.readUB(1, "fontFlagsBold") == 1; - languageCode = sis.readLANGCODE("languageCode"); - if (swf.version >= 6) { - fontName = sis.readNetString("fontName", Utf8Helper.charset); - } else { - fontName = sis.readNetString("fontName"); - } - int numGlyphs = sis.readUI16("numGlyphs"); - long[] offsetTable = new long[numGlyphs]; - long pos = sis.getPos(); - for (int i = 0; i < numGlyphs; i++) { //offsetTable - if (fontFlagsWideOffsets) { - offsetTable[i] = sis.readUI32("offset"); - } else { - offsetTable[i] = sis.readUI16("offset"); - } - } - if (numGlyphs > 0) { //codeTableOffset - if (fontFlagsWideOffsets) { - sis.readUI32("codeTableOffset"); - } else { - sis.readUI16("codeTableOffset"); - } - } - - glyphShapeTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - sis.seek(pos + offsetTable[i]); - glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); - } - - codeTable = new ArrayList<>(); //[numGlyphs]; - for (int i = 0; i < numGlyphs; i++) { - if (fontFlagsWideCodes) { - codeTable.add(sis.readUI16("code")); - } else { - codeTable.add(sis.readUI8("code")); - } - } - - if (fontFlagsHasLayout) { - fontAscent = sis.readUI16("fontAscent"); - fontDescent = sis.readUI16("fontDescent"); - fontLeading = sis.readSI16("fontLeading"); - fontAdvanceTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - fontAdvanceTable.add(sis.readSI16("fontAdvance")); - } - fontBoundsTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - fontBoundsTable.add(sis.readRECT("rect")); - } - int kerningCount = sis.readUI16("kerningCount"); - fontKerningTable = new ArrayList<>(); - for (int i = 0; i < kerningCount; i++) { - fontKerningTable.add(sis.readKERNINGRECORD(fontFlagsWideCodes, "record")); - } - } - } - - private void checkWideParameters() { - int numGlyphs = glyphShapeTable.size(); - - if (!fontFlagsWideOffsets) { - ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); - SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); - for (int i = 0; i < numGlyphs; i++) { - long offset = ((glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + sos3.getPos()); - if (offset > 0xffff) { - fontFlagsWideOffsets = true; - checkWideParameters(); - return; - } - try { - sos3.writeSHAPE(glyphShapeTable.get(i), 1); - } catch (IOException ex) { - //should not happen - return; - } - } - byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); - - if (numGlyphs > 0) { - long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; - if (offset > 0xffff) { - fontFlagsWideOffsets = true; - checkWideParameters(); - return; - } - } - } - - if (!fontFlagsWideCodes) { - for (int i = 0; i < numGlyphs; i++) { - long code = codeTable.get(i); - if (code > 0xffff) { - fontFlagsWideCodes = true; - } - } - } - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - checkWideParameters(); - sos.writeUI16(fontID); - sos.writeUB(1, fontFlagsHasLayout ? 1 : 0); - sos.writeUB(1, fontFlagsShiftJIS ? 1 : 0); - sos.writeUB(1, fontFlagsSmallText ? 1 : 0); - sos.writeUB(1, fontFlagsANSI ? 1 : 0); - sos.writeUB(1, fontFlagsWideOffsets ? 1 : 0); - sos.writeUB(1, fontFlagsWideCodes ? 1 : 0); - sos.writeUB(1, fontFlagsItalic ? 1 : 0); - sos.writeUB(1, fontFlagsBold ? 1 : 0); - sos.writeLANGCODE(languageCode); - if (swf.version >= 6) { - sos.writeNetString(fontName, Utf8Helper.charset); - } else { - sos.writeNetString(fontName); - } - int numGlyphs = glyphShapeTable.size(); - sos.writeUI16(numGlyphs); - - List offsetTable = new ArrayList<>(); - ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); - - SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); - for (int i = 0; i < numGlyphs; i++) { - offsetTable.add((glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + sos3.getPos()); - sos3.writeSHAPE(glyphShapeTable.get(i), 1); - } - byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); - for (Long offset : offsetTable) { - if (fontFlagsWideOffsets) { - sos.writeUI32(offset); - } else { - sos.writeUI16((int) (long) offset); - } - } - if (numGlyphs > 0) { - long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; - if (fontFlagsWideOffsets) { - sos.writeUI32(offset); - } else { - sos.writeUI16((int) offset); - } - sos.write(baGlyphShapes); - - for (int i = 0; i < numGlyphs; i++) { - if (fontFlagsWideCodes) { - sos.writeUI16(codeTable.get(i)); - } else { - sos.writeUI8(codeTable.get(i)); - } - } - } - if (fontFlagsHasLayout) { - sos.writeSI16(fontAscent); - sos.writeSI16(fontDescent); - sos.writeSI16(fontLeading); - for (int i = 0; i < numGlyphs; i++) { - sos.writeSI16(fontAdvanceTable.get(i)); - } - for (int i = 0; i < numGlyphs; i++) { - sos.writeRECT(fontBoundsTable.get(i)); - } - sos.writeUI16(fontKerningTable.size()); - for (int k = 0; k < fontKerningTable.size(); k++) { - sos.writeKERNINGRECORD(fontKerningTable.get(k), fontFlagsWideCodes); - } - } - } - - @Override - public boolean isSmall() { - return fontFlagsSmallText; - } - - @Override - public int getGlyphWidth(int glyphIndex) { - return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); - } - - @Override - public RECT getGlyphBounds(int glyphIndex) { - if (fontFlagsHasLayout) { - return fontBoundsTable.get(glyphIndex); - } - return super.getGlyphBounds(glyphIndex); - } - - @Override - public double getGlyphAdvance(int glyphIndex) { - if (fontFlagsHasLayout) { - return fontAdvanceTable.get(glyphIndex); - } else { - return -1; - } - } - - @Override - public List getGlyphShapeTable() { - return glyphShapeTable; - } - - @Override - public int getCharacterId() { - return fontID; - } - - @Override - public void setCharacterId(int characterId) { - this.fontID = characterId; - } - - @Override - public char glyphToChar(int glyphIndex) { - return (char) (int) codeTable.get(glyphIndex); - } - - @Override - public int charToGlyph(char c) { - return codeTable.indexOf((int) c); - } - - @Override - public String getFontNameIntag() { - String ret = fontName; - if (ret.contains("" + (char) 0)) { - ret = ret.substring(0, ret.indexOf(0)); - } - return ret; - } - - @Override - public boolean isBold() { - return fontFlagsBold; - } - - @Override - public boolean isItalic() { - return fontFlagsItalic; - } - - @Override - public boolean isSmallEditable() { - return true; - } - - @Override - public boolean isBoldEditable() { - return true; - } - - @Override - public boolean isItalicEditable() { - return true; - } - - @Override - public void setSmall(boolean value) { - fontFlagsSmallText = value; - } - - @Override - public void setBold(boolean value) { - fontFlagsBold = value; - } - - @Override - public void setItalic(boolean value) { - fontFlagsItalic = value; - } - - @Override - public double getDivider() { - return 1; - } - - @Override - public int getAscent() { - if (fontFlagsHasLayout) { - return fontAscent; - } - return -1; - } - - @Override - public int getDescent() { - if (fontFlagsHasLayout) { - return fontDescent; - } - return -1; - } - - @Override - public int getLeading() { - if (fontFlagsHasLayout) { - return fontLeading; - } - return -1; - } - - @Override - public void addCharacter(char character, Font font) { - int fontStyle = getFontStyle(); - - SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); - - int code = (int) character; - int pos = -1; - boolean exists = false; - for (int i = 0; i < codeTable.size(); i++) { - if (codeTable.get(i) >= code) { - if (codeTable.get(i) == code) { - exists = true; - } - pos = i; - break; - } - } - if (pos == -1) { - pos = codeTable.size(); - } - - if (!exists) { - shiftGlyphIndices(fontID, pos); - glyphShapeTable.add(pos, shp); - codeTable.add(pos, (int) character); - } else { - glyphShapeTable.set(pos, shp); - } - - if (fontFlagsHasLayout) { - Font fnt = new Font(fontName, fontStyle, 1024); - if (!exists) { - fontBoundsTable.add(pos, shp.getBounds()); - fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); - } else { - fontBoundsTable.set(pos, shp.getBounds()); - fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); - } - } - - checkWideParameters(); - setModified(true); - } - - @Override - public void setAdvanceValues(Font font) { - boolean hasLayout = fontFlagsHasLayout; - fontFlagsHasLayout = true; - fontAdvanceTable = new ArrayList<>(); - if (!hasLayout) { - fontBoundsTable = new ArrayList<>(); - fontKerningTable = new ArrayList<>(); - } - - for (Integer character : codeTable) { - char ch = (char) (int) character; - SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch); - fontBoundsTable.add(shp.getBounds()); - int fontStyle = getFontStyle(); - Font fnt = new Font(font.getFontName(), fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k - fontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, ch))); - } - } - - @Override - public int getCharacterCount() { - return codeTable.size(); - } - - @Override - public String getCharacters() { - StringBuilder ret = new StringBuilder(codeTable.size()); - for (int i : codeTable) { - ret.append((char) i); - } - return ret.toString(); - } - - @Override - public boolean hasLayout() { - return fontFlagsHasLayout; - } - - @Override - public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { - char c1 = glyphToChar(glyphIndex); - char c2 = glyphToChar(nextGlyphIndex); - return getCharKerningAdjustment(c1, c2); - } - - @Override - public int getCharKerningAdjustment(char c1, char c2) { - int kerningAdjustment = 0; - for (KERNINGRECORD ker : fontKerningTable) { - if (ker.fontKerningCode1 == c1 && ker.fontKerningCode2 == c2) { - kerningAdjustment = ker.fontKerningAdjustment; - break; - } - } - return kerningAdjustment; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.helpers.FontHelper; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.KERNINGRECORD; +import com.jpexs.decompiler.flash.types.LANGCODE; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.awt.Font; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class DefineFont2Tag extends FontTag { + + public static final int ID = 48; + + public static final String NAME = "DefineFont2"; + + @SWFType(BasicType.UI16) + public int fontID; + + public boolean fontFlagsHasLayout; + + public boolean fontFlagsShiftJIS; + + public boolean fontFlagsSmallText; + + public boolean fontFlagsANSI; + + public boolean fontFlagsWideOffsets; + + public boolean fontFlagsWideCodes; + + public boolean fontFlagsItalic; + + public boolean fontFlagsBold; + + public LANGCODE languageCode; + + public String fontName; + + public List glyphShapeTable; + + @SWFType(value = BasicType.UI8, alternateValue = BasicType.UI16, alternateCondition = "fontFlagsWideCodes") + public List codeTable; + + @SWFType(BasicType.UI16) + @Conditional("fontFlagsHasLayout") + public int fontAscent; + + @SWFType(BasicType.UI16) + @Conditional("fontFlagsHasLayout") + public int fontDescent; + + @SWFType(BasicType.SI16) + @Conditional("fontFlagsHasLayout") + public int fontLeading; + + @SWFType(BasicType.SI16) + @Conditional("fontFlagsHasLayout") + public List fontAdvanceTable; + + @Conditional("fontFlagsHasLayout") + public List fontBoundsTable; + + @Conditional("fontFlagsHasLayout") + public List fontKerningTable; + + /** + * Constructor + * + * @param swf + */ + public DefineFont2Tag(SWF swf) { + super(swf, ID, NAME, null); + fontID = swf.getNextCharacterId(); + languageCode = new LANGCODE(); + fontName = "New font"; + glyphShapeTable = new ArrayList<>(); + codeTable = new ArrayList<>(); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineFont2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + fontID = sis.readUI16("fontId"); + fontFlagsHasLayout = sis.readUB(1, "fontFlagsHasLayout") == 1; + fontFlagsShiftJIS = sis.readUB(1, "fontFlagsShiftJIS") == 1; + fontFlagsSmallText = sis.readUB(1, "fontFlagsSmallText") == 1; + fontFlagsANSI = sis.readUB(1, "fontFlagsANSI") == 1; + fontFlagsWideOffsets = sis.readUB(1, "fontFlagsWideOffsets") == 1; + fontFlagsWideCodes = sis.readUB(1, "fontFlagsWideCodes") == 1; + fontFlagsItalic = sis.readUB(1, "fontFlagsItalic") == 1; + fontFlagsBold = sis.readUB(1, "fontFlagsBold") == 1; + languageCode = sis.readLANGCODE("languageCode"); + if (swf.version >= 6) { + fontName = sis.readNetString("fontName", Utf8Helper.charset); + } else { + fontName = sis.readNetString("fontName"); + } + int numGlyphs = sis.readUI16("numGlyphs"); + long[] offsetTable = new long[numGlyphs]; + long pos = sis.getPos(); + for (int i = 0; i < numGlyphs; i++) { //offsetTable + if (fontFlagsWideOffsets) { + offsetTable[i] = sis.readUI32("offset"); + } else { + offsetTable[i] = sis.readUI16("offset"); + } + } + if (numGlyphs > 0) { //codeTableOffset + if (fontFlagsWideOffsets) { + sis.readUI32("codeTableOffset"); + } else { + sis.readUI16("codeTableOffset"); + } + } + + glyphShapeTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + sis.seek(pos + offsetTable[i]); + glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); + } + + codeTable = new ArrayList<>(); //[numGlyphs]; + for (int i = 0; i < numGlyphs; i++) { + if (fontFlagsWideCodes) { + codeTable.add(sis.readUI16("code")); + } else { + codeTable.add(sis.readUI8("code")); + } + } + + if (fontFlagsHasLayout) { + fontAscent = sis.readUI16("fontAscent"); + fontDescent = sis.readUI16("fontDescent"); + fontLeading = sis.readSI16("fontLeading"); + fontAdvanceTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + fontAdvanceTable.add(sis.readSI16("fontAdvance")); + } + fontBoundsTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + fontBoundsTable.add(sis.readRECT("rect")); + } + int kerningCount = sis.readUI16("kerningCount"); + fontKerningTable = new ArrayList<>(); + for (int i = 0; i < kerningCount; i++) { + fontKerningTable.add(sis.readKERNINGRECORD(fontFlagsWideCodes, "record")); + } + } + } + + private void checkWideParameters() { + int numGlyphs = glyphShapeTable.size(); + + if (!fontFlagsWideOffsets) { + ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); + SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); + for (int i = 0; i < numGlyphs; i++) { + long offset = ((glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + sos3.getPos()); + if (offset > 0xffff) { + fontFlagsWideOffsets = true; + checkWideParameters(); + return; + } + try { + sos3.writeSHAPE(glyphShapeTable.get(i), 1); + } catch (IOException ex) { + //should not happen + return; + } + } + byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); + + if (numGlyphs > 0) { + long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; + if (offset > 0xffff) { + fontFlagsWideOffsets = true; + checkWideParameters(); + return; + } + } + } + + if (!fontFlagsWideCodes) { + for (int i = 0; i < numGlyphs; i++) { + long code = codeTable.get(i); + if (code > 0xffff) { + fontFlagsWideCodes = true; + } + } + } + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + checkWideParameters(); + sos.writeUI16(fontID); + sos.writeUB(1, fontFlagsHasLayout ? 1 : 0); + sos.writeUB(1, fontFlagsShiftJIS ? 1 : 0); + sos.writeUB(1, fontFlagsSmallText ? 1 : 0); + sos.writeUB(1, fontFlagsANSI ? 1 : 0); + sos.writeUB(1, fontFlagsWideOffsets ? 1 : 0); + sos.writeUB(1, fontFlagsWideCodes ? 1 : 0); + sos.writeUB(1, fontFlagsItalic ? 1 : 0); + sos.writeUB(1, fontFlagsBold ? 1 : 0); + sos.writeLANGCODE(languageCode); + if (swf.version >= 6) { + sos.writeNetString(fontName, Utf8Helper.charset); + } else { + sos.writeNetString(fontName); + } + int numGlyphs = glyphShapeTable.size(); + sos.writeUI16(numGlyphs); + + List offsetTable = new ArrayList<>(); + ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); + + SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); + for (int i = 0; i < numGlyphs; i++) { + offsetTable.add((glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + sos3.getPos()); + sos3.writeSHAPE(glyphShapeTable.get(i), 1); + } + byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); + for (Long offset : offsetTable) { + if (fontFlagsWideOffsets) { + sos.writeUI32(offset); + } else { + sos.writeUI16((int) (long) offset); + } + } + if (numGlyphs > 0) { + long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; + if (fontFlagsWideOffsets) { + sos.writeUI32(offset); + } else { + sos.writeUI16((int) offset); + } + sos.write(baGlyphShapes); + + for (int i = 0; i < numGlyphs; i++) { + if (fontFlagsWideCodes) { + sos.writeUI16(codeTable.get(i)); + } else { + sos.writeUI8(codeTable.get(i)); + } + } + } + if (fontFlagsHasLayout) { + sos.writeSI16(fontAscent); + sos.writeSI16(fontDescent); + sos.writeSI16(fontLeading); + for (int i = 0; i < numGlyphs; i++) { + sos.writeSI16(fontAdvanceTable.get(i)); + } + for (int i = 0; i < numGlyphs; i++) { + sos.writeRECT(fontBoundsTable.get(i)); + } + sos.writeUI16(fontKerningTable.size()); + for (int k = 0; k < fontKerningTable.size(); k++) { + sos.writeKERNINGRECORD(fontKerningTable.get(k), fontFlagsWideCodes); + } + } + } + + @Override + public boolean isSmall() { + return fontFlagsSmallText; + } + + @Override + public int getGlyphWidth(int glyphIndex) { + return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); + } + + @Override + public RECT getGlyphBounds(int glyphIndex) { + if (fontFlagsHasLayout) { + return fontBoundsTable.get(glyphIndex); + } + return super.getGlyphBounds(glyphIndex); + } + + @Override + public double getGlyphAdvance(int glyphIndex) { + if (fontFlagsHasLayout) { + return fontAdvanceTable.get(glyphIndex); + } else { + return -1; + } + } + + @Override + public List getGlyphShapeTable() { + return glyphShapeTable; + } + + @Override + public int getCharacterId() { + return fontID; + } + + @Override + public void setCharacterId(int characterId) { + this.fontID = characterId; + } + + @Override + public char glyphToChar(int glyphIndex) { + return (char) (int) codeTable.get(glyphIndex); + } + + @Override + public int charToGlyph(char c) { + return codeTable.indexOf((int) c); + } + + @Override + public String getFontNameIntag() { + String ret = fontName; + if (ret.contains("" + (char) 0)) { + ret = ret.substring(0, ret.indexOf(0)); + } + return ret; + } + + @Override + public boolean isBold() { + return fontFlagsBold; + } + + @Override + public boolean isItalic() { + return fontFlagsItalic; + } + + @Override + public boolean isSmallEditable() { + return true; + } + + @Override + public boolean isBoldEditable() { + return true; + } + + @Override + public boolean isItalicEditable() { + return true; + } + + @Override + public void setSmall(boolean value) { + fontFlagsSmallText = value; + } + + @Override + public void setBold(boolean value) { + fontFlagsBold = value; + } + + @Override + public void setItalic(boolean value) { + fontFlagsItalic = value; + } + + @Override + public double getDivider() { + return 1; + } + + @Override + public int getAscent() { + if (fontFlagsHasLayout) { + return fontAscent; + } + return -1; + } + + @Override + public int getDescent() { + if (fontFlagsHasLayout) { + return fontDescent; + } + return -1; + } + + @Override + public int getLeading() { + if (fontFlagsHasLayout) { + return fontLeading; + } + return -1; + } + + @Override + public void addCharacter(char character, Font font) { + int fontStyle = getFontStyle(); + + SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); + + int code = (int) character; + int pos = -1; + boolean exists = false; + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + exists = true; + } + pos = i; + break; + } + } + if (pos == -1) { + pos = codeTable.size(); + } + + if (!exists) { + shiftGlyphIndices(fontID, pos, true); + glyphShapeTable.add(pos, shp); + codeTable.add(pos, (int) character); + } else { + glyphShapeTable.set(pos, shp); + } + + if (fontFlagsHasLayout) { + Font fnt = new Font(fontName, fontStyle, 1024); + if (!exists) { + fontBoundsTable.add(pos, shp.getBounds()); + fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); + } else { + fontBoundsTable.set(pos, shp.getBounds()); + fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); + } + } + + checkWideParameters(); + setModified(true); + getSwf().clearImageCache(); + } + + @Override + public boolean removeCharacter(char character) { + int code = (int) character; + int pos = -1; + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + pos = i; + break; + } + + return false; + } + } + + if (pos == -1) { + return false; + } + + glyphShapeTable.remove(pos); + codeTable.remove(pos); + + if (fontFlagsHasLayout) { + fontBoundsTable.remove(pos); + fontAdvanceTable.remove(pos); + } + + shiftGlyphIndices(fontID, pos + 1, false); + + checkWideParameters(); + setModified(true); + getSwf().clearImageCache(); + return true; + } + + @Override + public void setAdvanceValues(Font font) { + boolean hasLayout = fontFlagsHasLayout; + fontFlagsHasLayout = true; + fontAdvanceTable = new ArrayList<>(); + if (!hasLayout) { + fontBoundsTable = new ArrayList<>(); + fontKerningTable = new ArrayList<>(); + } + + for (Integer character : codeTable) { + char ch = (char) (int) character; + SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch); + fontBoundsTable.add(shp.getBounds()); + int fontStyle = getFontStyle(); + Font fnt = new Font(font.getFontName(), fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k + fontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, ch))); + } + } + + @Override + public int getCharacterCount() { + return codeTable.size(); + } + + @Override + public String getCharacters() { + StringBuilder ret = new StringBuilder(codeTable.size()); + for (int i : codeTable) { + ret.append((char) i); + } + return ret.toString(); + } + + @Override + public boolean hasLayout() { + return fontFlagsHasLayout; + } + + @Override + public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { + char c1 = glyphToChar(glyphIndex); + char c2 = glyphToChar(nextGlyphIndex); + return getCharKerningAdjustment(c1, c2); + } + + @Override + public int getCharKerningAdjustment(char c1, char c2) { + int kerningAdjustment = 0; + for (KERNINGRECORD ker : fontKerningTable) { + if (ker.fontKerningCode1 == c1 && ker.fontKerningCode2 == c2) { + kerningAdjustment = ker.fontKerningAdjustment; + break; + } + } + return kerningAdjustment; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java index 3ab20da72..195b40409 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFont3Tag.java @@ -1,553 +1,601 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.helpers.FontHelper; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.KERNINGRECORD; -import com.jpexs.decompiler.flash.types.LANGCODE; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.awt.Font; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 8) -public class DefineFont3Tag extends FontTag { - - public static final int ID = 75; - - public static final String NAME = "DefineFont3"; - - @SWFType(BasicType.UI16) - public int fontID; - - public boolean fontFlagsHasLayout; - - public boolean fontFlagsShiftJIS; - - public boolean fontFlagsSmallText; - - public boolean fontFlagsANSI; - - public boolean fontFlagsWideOffsets; - - public boolean fontFlagsWideCodes; - - public boolean fontFlagsItalic; - - public boolean fontFlagsBold; - - public LANGCODE languageCode; - - public String fontName; - - public List glyphShapeTable; - - @SWFType(value = BasicType.UI8, alternateValue = BasicType.UI16, alternateCondition = "fontFlagsWideCodes") - public List codeTable; - - @SWFType(BasicType.UI16) - @Conditional("fontFlagsHasLayout") - public int fontAscent; - - @SWFType(BasicType.UI16) - @Conditional("fontFlagsHasLayout") - public int fontDescent; - - @SWFType(BasicType.SI16) - @Conditional("fontFlagsHasLayout") - public int fontLeading; - - @SWFType(BasicType.SI16) - @Conditional("fontFlagsHasLayout") - public List fontAdvanceTable; - - @Conditional("fontFlagsHasLayout") - public List fontBoundsTable; - - @Conditional("fontFlagsHasLayout") - public List fontKerningTable; - - /** - * Constructor - * - * @param swf - */ - public DefineFont3Tag(SWF swf) { - super(swf, ID, NAME, null); - fontID = swf.getNextCharacterId(); - languageCode = new LANGCODE(); - fontName = "New font"; - glyphShapeTable = new ArrayList<>(); - codeTable = new ArrayList<>(); - } - - public DefineFont3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - fontID = sis.readUI16("fontId"); - fontFlagsHasLayout = sis.readUB(1, "fontFlagsHasLayout") == 1; - fontFlagsShiftJIS = sis.readUB(1, "fontFlagsShiftJIS") == 1; - fontFlagsSmallText = sis.readUB(1, "fontFlagsSmallText") == 1; - fontFlagsANSI = sis.readUB(1, "fontFlagsANSI") == 1; - fontFlagsWideOffsets = sis.readUB(1, "fontFlagsWideOffsets") == 1; - fontFlagsWideCodes = sis.readUB(1, "fontFlagsWideCodes") == 1; - fontFlagsItalic = sis.readUB(1, "fontFlagsItalic") == 1; - fontFlagsBold = sis.readUB(1, "fontFlagsBold") == 1; - languageCode = sis.readLANGCODE("languageCode"); - if (swf.version >= 6) { - fontName = sis.readNetString("fontName", Utf8Helper.charset); - } else { - fontName = sis.readNetString("fontName"); - } - int numGlyphs = sis.readUI16("numGlyphs"); - long[] offsetTable = new long[numGlyphs]; - long pos = sis.getPos(); - for (int i = 0; i < numGlyphs; i++) { //offsetTable - if (fontFlagsWideOffsets) { - offsetTable[i] = sis.readUI32("offset"); - } else { - offsetTable[i] = sis.readUI16("offset"); - } - } - if (numGlyphs > 0) { - if (fontFlagsWideOffsets) { - sis.readUI32("codeTableOffset"); //codeTableOffset - } else { - sis.readUI16("codeTableOffset"); //codeTableOffset - } - } - glyphShapeTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - sis.seek(pos + offsetTable[i]); - glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); - } - codeTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - if (fontFlagsWideCodes) { - codeTable.add(sis.readUI16("code")); - } else { - codeTable.add(sis.readUI8("code")); - } - } - if (fontFlagsHasLayout) { - fontAscent = sis.readUI16("fontAscent"); - fontDescent = sis.readUI16("fontDescent"); - fontLeading = sis.readSI16("fontLeading"); - fontAdvanceTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - fontAdvanceTable.add(sis.readSI16("fontAdvance")); - } - fontBoundsTable = new ArrayList<>(); - for (int i = 0; i < numGlyphs; i++) { - fontBoundsTable.add(sis.readRECT("rect")); - } - int kerningCount = sis.readUI16("kerningCount"); - fontKerningTable = new ArrayList<>(); - for (int i = 0; i < kerningCount; i++) { - fontKerningTable.add(sis.readKERNINGRECORD(fontFlagsWideCodes, "record")); - } - } - } - - private void checkWideParameters() { - int numGlyphs = glyphShapeTable.size(); - - if (!fontFlagsWideOffsets) { - ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); - SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); - for (int i = 0; i < numGlyphs; i++) { - long offset = (glyphShapeTable.size()) * 2 + sos3.getPos(); - if (offset > 0xffff) { - fontFlagsWideOffsets = true; - checkWideParameters(); - return; - } - try { - sos3.writeSHAPE(glyphShapeTable.get(i), 1); - } catch (IOException ex) { - //should not happen - return; - } - } - byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); - - if (numGlyphs > 0) { - long maxOffset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * 2 + baGlyphShapes.length; - if (maxOffset > 0xffff) { - fontFlagsWideOffsets = true; - checkWideParameters(); - return; - } - } - } - - if (!fontFlagsWideCodes) { - for (int i = 0; i < numGlyphs; i++) { - long code = codeTable.get(i); - if (code > 0xffff) { - fontFlagsWideCodes = true; - } - } - } - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - checkWideParameters(); - List offsetTable = new ArrayList<>(); - ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); - SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); - int numGlyphs = glyphShapeTable.size(); - for (int i = 0; i < numGlyphs; i++) { - offsetTable.add(sos3.getPos()); - sos3.writeSHAPE(glyphShapeTable.get(i), 1); - } - byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); - - sos.writeUI16(fontID); - sos.writeUB(1, fontFlagsHasLayout ? 1 : 0); - sos.writeUB(1, fontFlagsShiftJIS ? 1 : 0); - sos.writeUB(1, fontFlagsSmallText ? 1 : 0); - sos.writeUB(1, fontFlagsANSI ? 1 : 0); - sos.writeUB(1, fontFlagsWideOffsets ? 1 : 0); - sos.writeUB(1, fontFlagsWideCodes ? 1 : 0); - sos.writeUB(1, fontFlagsItalic ? 1 : 0); - sos.writeUB(1, fontFlagsBold ? 1 : 0); - sos.writeLANGCODE(languageCode); - if (swf.version >= 6) { - sos.writeNetString(fontName, Utf8Helper.charset); - } else { - sos.writeNetString(fontName); - } - sos.writeUI16(numGlyphs); - - for (long offset : offsetTable) { - long offset2 = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + offset; - if (fontFlagsWideOffsets) { - sos.writeUI32(offset2); - } else { - sos.writeUI16((int) offset2); - } - } - if (numGlyphs > 0) { - long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; - if (fontFlagsWideOffsets) { - sos.writeUI32(offset); - } else { - sos.writeUI16((int) offset); - } - sos.write(baGlyphShapes); - - for (int i = 0; i < numGlyphs; i++) { - if (fontFlagsWideCodes) { - sos.writeUI16(codeTable.get(i)); - } else { - sos.writeUI8(codeTable.get(i)); - } - } - } - if (fontFlagsHasLayout) { - sos.writeSI16(fontAscent); - sos.writeSI16(fontDescent); - sos.writeSI16(fontLeading); - for (int i = 0; i < numGlyphs; i++) { - sos.writeSI16(fontAdvanceTable.get(i)); - } - for (int i = 0; i < numGlyphs; i++) { - sos.writeRECT(fontBoundsTable.get(i)); - } - sos.writeUI16(fontKerningTable.size()); - for (int k = 0; k < fontKerningTable.size(); k++) { - sos.writeKERNINGRECORD(fontKerningTable.get(k), fontFlagsWideCodes); - } - } - } - - @Override - public boolean isSmall() { - return fontFlagsSmallText; - } - - @Override - public int getGlyphWidth(int glyphIndex) { - return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); - } - - @Override - public double getGlyphAdvance(int glyphIndex) { - if (fontFlagsHasLayout && glyphIndex != -1) { - return fontAdvanceTable.get(glyphIndex); - } else { - return -1; - } - } - - @Override - public char glyphToChar(int glyphIndex) { - return (char) (int) codeTable.get(glyphIndex); - } - - @Override - public int charToGlyph(char c) { - return codeTable.indexOf((int) c); - } - - @Override - public List getGlyphShapeTable() { - return glyphShapeTable; - } - - @Override - public int getCharacterId() { - return fontID; - } - - @Override - public void setCharacterId(int characterId) { - this.fontID = characterId; - } - - @Override - public String getFontNameIntag() { - String ret = fontName; - if (ret.contains("" + (char) 0)) { - ret = ret.substring(0, ret.indexOf(0)); - } - return ret; - } - - @Override - public boolean isBold() { - return fontFlagsBold; - } - - @Override - public boolean isItalic() { - return fontFlagsItalic; - } - - @Override - public boolean isSmallEditable() { - return true; - } - - @Override - public boolean isBoldEditable() { - return true; - } - - @Override - public boolean isItalicEditable() { - return true; - } - - @Override - public void setSmall(boolean value) { - fontFlagsSmallText = value; - } - - @Override - public void setBold(boolean value) { - fontFlagsBold = value; - } - - @Override - public void setItalic(boolean value) { - fontFlagsItalic = value; - } - - @Override - public double getDivider() { - return 20; - } - - @Override - public int getAscent() { - if (fontFlagsHasLayout) { - return fontAscent; - } - return -1; - } - - @Override - public int getDescent() { - if (fontFlagsHasLayout) { - return fontDescent; - } - return -1; - } - - @Override - public int getLeading() { - if (fontFlagsHasLayout) { - return fontLeading; - } - return -1; - } - - @Override - public void addCharacter(char character, Font font) { - - //Font Align Zones will be removed as adding new character zones is not supported:-( - for (int i = 0; i < swf.getTags().size(); i++) { - Tag t = swf.getTags().get(i); - if (t instanceof DefineFontAlignZonesTag) { - DefineFontAlignZonesTag fa = (DefineFontAlignZonesTag) t; - if (fa.fontID == fontID) { - swf.removeTag(t); - i--; - } - } - } - int fontStyle = getFontStyle(); - SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); - int code = (int) character; - int pos = -1; - boolean exists = false; - for (int i = 0; i < codeTable.size(); i++) { - if (codeTable.get(i) >= code) { - if (codeTable.get(i) == code) { - exists = true; - } - pos = i; - break; - } - } - if (pos == -1) { - pos = codeTable.size(); - } - - if (!exists) { - shiftGlyphIndices(fontID, pos); - glyphShapeTable.add(pos, shp); - codeTable.add(pos, (int) character); - } else { - glyphShapeTable.set(pos, shp); - } - if (fontFlagsHasLayout) { - - Font fnt = new Font(fontName, fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k - if (!exists) { - fontBoundsTable.add(pos, shp.getBounds()); - fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); - } else { - fontBoundsTable.set(pos, shp.getBounds()); - fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); - } - } - - checkWideParameters(); - setModified(true); - } - - @Override - public void setAdvanceValues(Font font) { - boolean hasLayout = fontFlagsHasLayout; - fontFlagsHasLayout = true; - fontAdvanceTable = new ArrayList<>(); - if (!hasLayout) { - fontBoundsTable = new ArrayList<>(); - fontKerningTable = new ArrayList<>(); - } - - for (Integer character : codeTable) { - char ch = (char) (int) character; - SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch); - fontBoundsTable.add(shp.getBounds()); - int fontStyle = getFontStyle(); - Font fnt = new Font(font.getFontName(), fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k - fontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, ch))); - } - } - - @Override - public int getCharacterCount() { - return codeTable.size(); - } - - @Override - public String getCharacters() { - StringBuilder ret = new StringBuilder(codeTable.size()); - for (int i : codeTable) { - ret.append((char) i); - } - return ret.toString(); - } - - @Override - public boolean hasLayout() { - return fontFlagsHasLayout; - } - - @Override - public RECT getGlyphBounds(int glyphIndex) { - if (fontFlagsHasLayout) { - return fontBoundsTable.get(glyphIndex); - } - return super.getGlyphBounds(glyphIndex); - } - - @Override - public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { - if (glyphIndex == -1 || nextGlyphIndex == -1) { - return 0; - } - char c1 = glyphToChar(glyphIndex); - char c2 = glyphToChar(nextGlyphIndex); - return getCharKerningAdjustment(c1, c2); - } - - @Override - public int getCharKerningAdjustment(char c1, char c2) { - int kerningAdjustment = 0; - for (KERNINGRECORD ker : fontKerningTable) { - if (ker.fontKerningCode1 == c1 && ker.fontKerningCode2 == c2) { - kerningAdjustment = ker.fontKerningAdjustment; - break; - } - } - return kerningAdjustment; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.helpers.FontHelper; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.KERNINGRECORD; +import com.jpexs.decompiler.flash.types.LANGCODE; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.awt.Font; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 8) +public class DefineFont3Tag extends FontTag { + + public static final int ID = 75; + + public static final String NAME = "DefineFont3"; + + @SWFType(BasicType.UI16) + public int fontID; + + public boolean fontFlagsHasLayout; + + public boolean fontFlagsShiftJIS; + + public boolean fontFlagsSmallText; + + public boolean fontFlagsANSI; + + public boolean fontFlagsWideOffsets; + + public boolean fontFlagsWideCodes; + + public boolean fontFlagsItalic; + + public boolean fontFlagsBold; + + public LANGCODE languageCode; + + public String fontName; + + public List glyphShapeTable; + + @SWFType(value = BasicType.UI8, alternateValue = BasicType.UI16, alternateCondition = "fontFlagsWideCodes") + public List codeTable; + + @SWFType(BasicType.UI16) + @Conditional("fontFlagsHasLayout") + public int fontAscent; + + @SWFType(BasicType.UI16) + @Conditional("fontFlagsHasLayout") + public int fontDescent; + + @SWFType(BasicType.SI16) + @Conditional("fontFlagsHasLayout") + public int fontLeading; + + @SWFType(BasicType.SI16) + @Conditional("fontFlagsHasLayout") + public List fontAdvanceTable; + + @Conditional("fontFlagsHasLayout") + public List fontBoundsTable; + + @Conditional("fontFlagsHasLayout") + public List fontKerningTable; + + /** + * Constructor + * + * @param swf + */ + public DefineFont3Tag(SWF swf) { + super(swf, ID, NAME, null); + fontID = swf.getNextCharacterId(); + languageCode = new LANGCODE(); + fontName = "New font"; + glyphShapeTable = new ArrayList<>(); + codeTable = new ArrayList<>(); + } + + public DefineFont3Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + fontID = sis.readUI16("fontId"); + fontFlagsHasLayout = sis.readUB(1, "fontFlagsHasLayout") == 1; + fontFlagsShiftJIS = sis.readUB(1, "fontFlagsShiftJIS") == 1; + fontFlagsSmallText = sis.readUB(1, "fontFlagsSmallText") == 1; + fontFlagsANSI = sis.readUB(1, "fontFlagsANSI") == 1; + fontFlagsWideOffsets = sis.readUB(1, "fontFlagsWideOffsets") == 1; + fontFlagsWideCodes = sis.readUB(1, "fontFlagsWideCodes") == 1; + fontFlagsItalic = sis.readUB(1, "fontFlagsItalic") == 1; + fontFlagsBold = sis.readUB(1, "fontFlagsBold") == 1; + languageCode = sis.readLANGCODE("languageCode"); + if (swf.version >= 6) { + fontName = sis.readNetString("fontName", Utf8Helper.charset); + } else { + fontName = sis.readNetString("fontName"); + } + int numGlyphs = sis.readUI16("numGlyphs"); + long[] offsetTable = new long[numGlyphs]; + long pos = sis.getPos(); + for (int i = 0; i < numGlyphs; i++) { //offsetTable + if (fontFlagsWideOffsets) { + offsetTable[i] = sis.readUI32("offset"); + } else { + offsetTable[i] = sis.readUI16("offset"); + } + } + if (numGlyphs > 0) { + if (fontFlagsWideOffsets) { + sis.readUI32("codeTableOffset"); //codeTableOffset + } else { + sis.readUI16("codeTableOffset"); //codeTableOffset + } + } + glyphShapeTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + sis.seek(pos + offsetTable[i]); + glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); + } + codeTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + if (fontFlagsWideCodes) { + codeTable.add(sis.readUI16("code")); + } else { + codeTable.add(sis.readUI8("code")); + } + } + if (fontFlagsHasLayout) { + fontAscent = sis.readUI16("fontAscent"); + fontDescent = sis.readUI16("fontDescent"); + fontLeading = sis.readSI16("fontLeading"); + fontAdvanceTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + fontAdvanceTable.add(sis.readSI16("fontAdvance")); + } + fontBoundsTable = new ArrayList<>(); + for (int i = 0; i < numGlyphs; i++) { + fontBoundsTable.add(sis.readRECT("rect")); + } + int kerningCount = sis.readUI16("kerningCount"); + fontKerningTable = new ArrayList<>(); + for (int i = 0; i < kerningCount; i++) { + fontKerningTable.add(sis.readKERNINGRECORD(fontFlagsWideCodes, "record")); + } + } + } + + private void checkWideParameters() { + int numGlyphs = glyphShapeTable.size(); + + if (!fontFlagsWideOffsets) { + ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); + SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); + for (int i = 0; i < numGlyphs; i++) { + long offset = (glyphShapeTable.size()) * 2 + sos3.getPos(); + if (offset > 0xffff) { + fontFlagsWideOffsets = true; + checkWideParameters(); + return; + } + try { + sos3.writeSHAPE(glyphShapeTable.get(i), 1); + } catch (IOException ex) { + //should not happen + return; + } + } + byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); + + if (numGlyphs > 0) { + long maxOffset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * 2 + baGlyphShapes.length; + if (maxOffset > 0xffff) { + fontFlagsWideOffsets = true; + checkWideParameters(); + return; + } + } + } + + if (!fontFlagsWideCodes) { + for (int i = 0; i < numGlyphs; i++) { + long code = codeTable.get(i); + if (code > 0xffff) { + fontFlagsWideCodes = true; + } + } + } + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + checkWideParameters(); + List offsetTable = new ArrayList<>(); + ByteArrayOutputStream baosGlyphShapes = new ByteArrayOutputStream(); + SWFOutputStream sos3 = new SWFOutputStream(baosGlyphShapes, getVersion()); + int numGlyphs = glyphShapeTable.size(); + for (int i = 0; i < numGlyphs; i++) { + offsetTable.add(sos3.getPos()); + sos3.writeSHAPE(glyphShapeTable.get(i), 1); + } + byte[] baGlyphShapes = baosGlyphShapes.toByteArray(); + + sos.writeUI16(fontID); + sos.writeUB(1, fontFlagsHasLayout ? 1 : 0); + sos.writeUB(1, fontFlagsShiftJIS ? 1 : 0); + sos.writeUB(1, fontFlagsSmallText ? 1 : 0); + sos.writeUB(1, fontFlagsANSI ? 1 : 0); + sos.writeUB(1, fontFlagsWideOffsets ? 1 : 0); + sos.writeUB(1, fontFlagsWideCodes ? 1 : 0); + sos.writeUB(1, fontFlagsItalic ? 1 : 0); + sos.writeUB(1, fontFlagsBold ? 1 : 0); + sos.writeLANGCODE(languageCode); + if (swf.version >= 6) { + sos.writeNetString(fontName, Utf8Helper.charset); + } else { + sos.writeNetString(fontName); + } + sos.writeUI16(numGlyphs); + + for (long offset : offsetTable) { + long offset2 = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + offset; + if (fontFlagsWideOffsets) { + sos.writeUI32(offset2); + } else { + sos.writeUI16((int) offset2); + } + } + if (numGlyphs > 0) { + long offset = (glyphShapeTable.size() + 1/*CodeTableOffset*/) * (fontFlagsWideOffsets ? 4 : 2) + baGlyphShapes.length; + if (fontFlagsWideOffsets) { + sos.writeUI32(offset); + } else { + sos.writeUI16((int) offset); + } + sos.write(baGlyphShapes); + + for (int i = 0; i < numGlyphs; i++) { + if (fontFlagsWideCodes) { + sos.writeUI16(codeTable.get(i)); + } else { + sos.writeUI8(codeTable.get(i)); + } + } + } + if (fontFlagsHasLayout) { + sos.writeSI16(fontAscent); + sos.writeSI16(fontDescent); + sos.writeSI16(fontLeading); + for (int i = 0; i < numGlyphs; i++) { + sos.writeSI16(fontAdvanceTable.get(i)); + } + for (int i = 0; i < numGlyphs; i++) { + sos.writeRECT(fontBoundsTable.get(i)); + } + sos.writeUI16(fontKerningTable.size()); + for (int k = 0; k < fontKerningTable.size(); k++) { + sos.writeKERNINGRECORD(fontKerningTable.get(k), fontFlagsWideCodes); + } + } + } + + @Override + public boolean isSmall() { + return fontFlagsSmallText; + } + + @Override + public int getGlyphWidth(int glyphIndex) { + return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); + } + + @Override + public double getGlyphAdvance(int glyphIndex) { + if (fontFlagsHasLayout && glyphIndex != -1) { + return fontAdvanceTable.get(glyphIndex); + } else { + return -1; + } + } + + @Override + public char glyphToChar(int glyphIndex) { + return (char) (int) codeTable.get(glyphIndex); + } + + @Override + public int charToGlyph(char c) { + return codeTable.indexOf((int) c); + } + + @Override + public List getGlyphShapeTable() { + return glyphShapeTable; + } + + @Override + public int getCharacterId() { + return fontID; + } + + @Override + public void setCharacterId(int characterId) { + this.fontID = characterId; + } + + @Override + public String getFontNameIntag() { + String ret = fontName; + if (ret.contains("" + (char) 0)) { + ret = ret.substring(0, ret.indexOf(0)); + } + return ret; + } + + @Override + public boolean isBold() { + return fontFlagsBold; + } + + @Override + public boolean isItalic() { + return fontFlagsItalic; + } + + @Override + public boolean isSmallEditable() { + return true; + } + + @Override + public boolean isBoldEditable() { + return true; + } + + @Override + public boolean isItalicEditable() { + return true; + } + + @Override + public void setSmall(boolean value) { + fontFlagsSmallText = value; + } + + @Override + public void setBold(boolean value) { + fontFlagsBold = value; + } + + @Override + public void setItalic(boolean value) { + fontFlagsItalic = value; + } + + @Override + public double getDivider() { + return 20; + } + + @Override + public int getAscent() { + if (fontFlagsHasLayout) { + return fontAscent; + } + return -1; + } + + @Override + public int getDescent() { + if (fontFlagsHasLayout) { + return fontDescent; + } + return -1; + } + + @Override + public int getLeading() { + if (fontFlagsHasLayout) { + return fontLeading; + } + return -1; + } + + @Override + public void addCharacter(char character, Font font) { + + //Font Align Zones will be removed as adding new character zones is not supported:-( + for (int i = 0; i < swf.getTags().size(); i++) { + Tag t = swf.getTags().get(i); + if (t instanceof DefineFontAlignZonesTag) { + DefineFontAlignZonesTag fa = (DefineFontAlignZonesTag) t; + if (fa.fontID == fontID) { + swf.removeTag(t); + i--; + } + } + } + int fontStyle = getFontStyle(); + SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); + int code = (int) character; + int pos = -1; + boolean exists = false; + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + exists = true; + } + pos = i; + break; + } + } + if (pos == -1) { + pos = codeTable.size(); + } + + if (!exists) { + shiftGlyphIndices(fontID, pos, true); + glyphShapeTable.add(pos, shp); + codeTable.add(pos, (int) character); + } else { + glyphShapeTable.set(pos, shp); + } + if (fontFlagsHasLayout) { + + Font fnt = new Font(fontName, fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k + if (!exists) { + fontBoundsTable.add(pos, shp.getBounds()); + fontAdvanceTable.add(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); + } else { + fontBoundsTable.set(pos, shp.getBounds()); + fontAdvanceTable.set(pos, (int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, character))); + } + } + + checkWideParameters(); + setModified(true); + getSwf().clearImageCache(); + } + + @Override + public boolean removeCharacter(char character) { + + //Font Align Zones will be removed as removing character zones is not supported:-( + for (int i = 0; i < swf.getTags().size(); i++) { + Tag t = swf.getTags().get(i); + if (t instanceof DefineFontAlignZonesTag) { + DefineFontAlignZonesTag fa = (DefineFontAlignZonesTag) t; + if (fa.fontID == fontID) { + swf.removeTag(t); + i--; + } + } + } + + int code = (int) character; + int pos = -1; + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + pos = i; + break; + } + + return false; + } + } + if (pos == -1) { + return false; + } + + glyphShapeTable.remove(pos); + codeTable.remove(pos); + + if (fontFlagsHasLayout) { + fontBoundsTable.remove(pos); + fontAdvanceTable.remove(pos); + } + + shiftGlyphIndices(fontID, pos + 1, false); + + checkWideParameters(); + setModified(true); + getSwf().clearImageCache(); + return true; + } + + @Override + public void setAdvanceValues(Font font) { + boolean hasLayout = fontFlagsHasLayout; + fontFlagsHasLayout = true; + fontAdvanceTable = new ArrayList<>(); + if (!hasLayout) { + fontBoundsTable = new ArrayList<>(); + fontKerningTable = new ArrayList<>(); + } + + for (Integer character : codeTable) { + char ch = (char) (int) character; + SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), ch); + fontBoundsTable.add(shp.getBounds()); + int fontStyle = getFontStyle(); + Font fnt = new Font(font.getFontName(), fontStyle, 1024); // Not multiplied with divider as it causes problems to create font with height around 20k + fontAdvanceTable.add((int) getDivider() * Math.round(FontHelper.getFontAdvance(fnt, ch))); + } + } + + @Override + public int getCharacterCount() { + return codeTable.size(); + } + + @Override + public String getCharacters() { + StringBuilder ret = new StringBuilder(codeTable.size()); + for (int i : codeTable) { + ret.append((char) i); + } + return ret.toString(); + } + + @Override + public boolean hasLayout() { + return fontFlagsHasLayout; + } + + @Override + public RECT getGlyphBounds(int glyphIndex) { + if (fontFlagsHasLayout) { + return fontBoundsTable.get(glyphIndex); + } + return super.getGlyphBounds(glyphIndex); + } + + @Override + public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { + if (glyphIndex == -1 || nextGlyphIndex == -1) { + return 0; + } + char c1 = glyphToChar(glyphIndex); + char c2 = glyphToChar(nextGlyphIndex); + return getCharKerningAdjustment(c1, c2); + } + + @Override + public int getCharKerningAdjustment(char c1, char c2) { + int kerningAdjustment = 0; + for (KERNINGRECORD ker : fontKerningTable) { + if (ker.fontKerningCode1 == c1 && ker.fontKerningCode2 == c2) { + kerningAdjustment = ker.fontKerningAdjustment; + break; + } + } + return kerningAdjustment; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfo2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfo2Tag.java index 2d5b730e6..10a79b9c9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfo2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfo2Tag.java @@ -145,11 +145,17 @@ public class DefineFontInfo2Tag extends FontInfoTag { } @Override - public void addCharacter(int index, int character) { + public void addFontCharacter(int index, int character) { codeTable.add(index, character); setModified(true); } + @Override + public void removeFontCharacter(int index) { + codeTable.remove(index); + setModified(true); + } + @Override public String getFontName() { return fontName; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfoTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfoTag.java index a5d0158b6..456785a12 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfoTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontInfoTag.java @@ -146,11 +146,17 @@ public class DefineFontInfoTag extends FontInfoTag { } @Override - public void addCharacter(int index, int character) { + public void addFontCharacter(int index, int character) { codeTable.add(index, character); setModified(true); } + @Override + public void removeFontCharacter(int index) { + codeTable.remove(index); + setModified(true); + } + @Override public String getFontName() { return fontName; diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java index 740db9c07..154b0320f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineFontTag.java @@ -1,346 +1,382 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.FontInfoTag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.helpers.ByteArrayRange; -import java.awt.Font; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 1) -public class DefineFontTag extends FontTag { - - public static final int ID = 10; - - public static final String NAME = "DefineFont"; - - @SWFType(BasicType.UI16) - public int fontId; - - public List glyphShapeTable; - - @Internal - private FontInfoTag fontInfoTag = null; - - /** - * Constructor - * - * @param swf - */ - public DefineFontTag(SWF swf) { - super(swf, ID, NAME, null); - fontId = swf.getNextCharacterId(); - glyphShapeTable = new ArrayList<>(); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineFontTag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - fontId = sis.readUI16("fontId"); - glyphShapeTable = new ArrayList<>(); - - if (sis.available() > 0) { - long pos = sis.getPos(); - int firstOffset = sis.readUI16("firstOffset"); - int nGlyphs = firstOffset / 2; - - long[] offsetTable = new long[nGlyphs]; - offsetTable[0] = firstOffset; - for (int i = 1; i < nGlyphs; i++) { - offsetTable[i] = sis.readUI16("offset"); - } - for (int i = 0; i < nGlyphs; i++) { - sis.seek(pos + offsetTable[i]); - glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); - } - } - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(fontId); - ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); - List offsetTable = new ArrayList<>(); - SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion()); - for (SHAPE shape : glyphShapeTable) { - offsetTable.add(glyphShapeTable.size() * 2 + (int) sos2.getPos()); - sos2.writeSHAPE(shape, 1); - } - for (int offset : offsetTable) { - sos.writeUI16(offset); - } - sos.write(baos2.toByteArray()); - } - - @Override - public boolean isSmall() { - return false; - } - - @Override - public double getGlyphAdvance(int glyphIndex) { - return -1; - } - - @Override - public int getGlyphWidth(int glyphIndex) { - return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); - } - - private void ensureFontInfo() { - if (fontInfoTag == null) { - List characterIdTags = swf.getCharacterIdTags(fontId); - if (characterIdTags != null) { - for (CharacterIdTag t : characterIdTags) { - if (t instanceof FontInfoTag) { - if (((FontInfoTag) t).fontID == fontId) { - fontInfoTag = (FontInfoTag) t; - break; - } - } - } - } - } - } - - @Override - public char glyphToChar(int glyphIndex) { - ensureFontInfo(); - if (fontInfoTag != null) { - return (char) (int) fontInfoTag.getCodeTable().get(glyphIndex); - } else { - return '?'; - } - } - - @Override - public int charToGlyph(char c) { - ensureFontInfo(); - if (fontInfoTag != null) { - return fontInfoTag.getCodeTable().indexOf((int) c); - } - return -1; - - } - - @Override - public List getGlyphShapeTable() { - return glyphShapeTable; - } - - @Override - public int getCharacterId() { - return fontId; - } - - @Override - public void setCharacterId(int characterId) { - this.fontId = characterId; - } - - @Override - public String getFontNameIntag() { - ensureFontInfo(); - if (fontInfoTag != null) { - return fontInfoTag.getFontName(); - } - return null; - } - - @Override - public boolean isBold() { - if (fontInfoTag != null) { - return fontInfoTag.getFontFlagsBold(); - } - return false; - } - - @Override - public boolean isItalic() { - if (fontInfoTag != null) { - return fontInfoTag.getFontFlagsItalic(); - } - return false; - } - - @Override - public boolean isSmallEditable() { - return false; - } - - @Override - public boolean isBoldEditable() { - return fontInfoTag != null; - } - - @Override - public boolean isItalicEditable() { - return fontInfoTag != null; - } - - @Override - public void setSmall(boolean value) { - } - - @Override - public void setBold(boolean value) { - if (fontInfoTag != null) { - fontInfoTag.setFontFlagsBold(value); - } - } - - @Override - public void setItalic(boolean value) { - if (fontInfoTag != null) { - fontInfoTag.setFontFlagsItalic(value); - } - } - - @Override - public int getAscent() { - return -1; - } - - @Override - public int getDescent() { - return -1; - } - - @Override - public int getLeading() { - return -1; - } - - @Override - public double getDivider() { - return 1; - } - - @Override - public void addCharacter(char character, Font font) { - SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); - ensureFontInfo(); - int code = (int) character; - int pos = -1; - boolean exists = false; - if (fontInfoTag != null) { - List codeTable = fontInfoTag.getCodeTable(); - for (int i = 0; i < codeTable.size(); i++) { - if (codeTable.get(i) >= code) { - if (codeTable.get(i) == code) { - exists = true; - } - - pos = i; - break; - } - } - - if (pos == -1) { - pos = codeTable.size(); - } - } else { - pos = 0; - } - - if (!exists) { - shiftGlyphIndices(fontId, pos); - glyphShapeTable.add(pos, shp); - if (fontInfoTag != null) { - fontInfoTag.addCharacter(pos, (int) character); - } - } else { - glyphShapeTable.set(pos, shp); - } - - setModified(true); - } - - @Override - public void setAdvanceValues(Font font) { - throw new UnsupportedOperationException("Setting the advance values for DefineFontTag is not supported."); - } - - @Override - public int getCharacterCount() { - ensureFontInfo(); - if (fontInfoTag != null) { - List codeTable = fontInfoTag.getCodeTable(); - return codeTable.size(); - } - return 0; - } - - @Override - public String getCharacters() { - ensureFontInfo(); - if (fontInfoTag != null) { - List codeTable = fontInfoTag.getCodeTable(); - StringBuilder ret = new StringBuilder(codeTable.size()); - for (int i : codeTable) { - ret.append((char) i); - } - return ret.toString(); - } - return ""; - } - - @Override - public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { - return 0; - } - - @Override - public int getCharKerningAdjustment(char c1, char c2) { - return 0; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.FontInfoTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.helpers.ByteArrayRange; +import java.awt.Font; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 1) +public class DefineFontTag extends FontTag { + + public static final int ID = 10; + + public static final String NAME = "DefineFont"; + + @SWFType(BasicType.UI16) + public int fontId; + + public List glyphShapeTable; + + @Internal + private FontInfoTag fontInfoTag = null; + + /** + * Constructor + * + * @param swf + */ + public DefineFontTag(SWF swf) { + super(swf, ID, NAME, null); + fontId = swf.getNextCharacterId(); + glyphShapeTable = new ArrayList<>(); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineFontTag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + fontId = sis.readUI16("fontId"); + glyphShapeTable = new ArrayList<>(); + + if (sis.available() > 0) { + long pos = sis.getPos(); + int firstOffset = sis.readUI16("firstOffset"); + int nGlyphs = firstOffset / 2; + + long[] offsetTable = new long[nGlyphs]; + offsetTable[0] = firstOffset; + for (int i = 1; i < nGlyphs; i++) { + offsetTable[i] = sis.readUI16("offset"); + } + for (int i = 0; i < nGlyphs; i++) { + sis.seek(pos + offsetTable[i]); + glyphShapeTable.add(sis.readSHAPE(1, false, "shape")); + } + } + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(fontId); + ByteArrayOutputStream baos2 = new ByteArrayOutputStream(); + List offsetTable = new ArrayList<>(); + SWFOutputStream sos2 = new SWFOutputStream(baos2, getVersion()); + for (SHAPE shape : glyphShapeTable) { + offsetTable.add(glyphShapeTable.size() * 2 + (int) sos2.getPos()); + sos2.writeSHAPE(shape, 1); + } + for (int offset : offsetTable) { + sos.writeUI16(offset); + } + sos.write(baos2.toByteArray()); + } + + @Override + public boolean isSmall() { + return false; + } + + @Override + public double getGlyphAdvance(int glyphIndex) { + return -1; + } + + @Override + public int getGlyphWidth(int glyphIndex) { + return glyphShapeTable.get(glyphIndex).getBounds().getWidth(); + } + + private void ensureFontInfo() { + if (fontInfoTag == null) { + List characterIdTags = swf.getCharacterIdTags(fontId); + if (characterIdTags != null) { + for (CharacterIdTag t : characterIdTags) { + if (t instanceof FontInfoTag) { + if (((FontInfoTag) t).fontID == fontId) { + fontInfoTag = (FontInfoTag) t; + break; + } + } + } + } + } + } + + @Override + public char glyphToChar(int glyphIndex) { + ensureFontInfo(); + if (fontInfoTag != null) { + return (char) (int) fontInfoTag.getCodeTable().get(glyphIndex); + } else { + return '?'; + } + } + + @Override + public int charToGlyph(char c) { + ensureFontInfo(); + if (fontInfoTag != null) { + return fontInfoTag.getCodeTable().indexOf((int) c); + } + return -1; + + } + + @Override + public List getGlyphShapeTable() { + return glyphShapeTable; + } + + @Override + public int getCharacterId() { + return fontId; + } + + @Override + public void setCharacterId(int characterId) { + this.fontId = characterId; + } + + @Override + public String getFontNameIntag() { + ensureFontInfo(); + if (fontInfoTag != null) { + return fontInfoTag.getFontName(); + } + return null; + } + + @Override + public boolean isBold() { + if (fontInfoTag != null) { + return fontInfoTag.getFontFlagsBold(); + } + return false; + } + + @Override + public boolean isItalic() { + if (fontInfoTag != null) { + return fontInfoTag.getFontFlagsItalic(); + } + return false; + } + + @Override + public boolean isSmallEditable() { + return false; + } + + @Override + public boolean isBoldEditable() { + return fontInfoTag != null; + } + + @Override + public boolean isItalicEditable() { + return fontInfoTag != null; + } + + @Override + public void setSmall(boolean value) { + } + + @Override + public void setBold(boolean value) { + if (fontInfoTag != null) { + fontInfoTag.setFontFlagsBold(value); + } + } + + @Override + public void setItalic(boolean value) { + if (fontInfoTag != null) { + fontInfoTag.setFontFlagsItalic(value); + } + } + + @Override + public int getAscent() { + return -1; + } + + @Override + public int getDescent() { + return -1; + } + + @Override + public int getLeading() { + return -1; + } + + @Override + public double getDivider() { + return 1; + } + + @Override + public void addCharacter(char character, Font font) { + SHAPE shp = SHAPERECORD.fontCharacterToSHAPE(font, (int) Math.round(getDivider() * 1024), character); + ensureFontInfo(); + int code = (int) character; + int pos = -1; + boolean exists = false; + if (fontInfoTag != null) { + List codeTable = fontInfoTag.getCodeTable(); + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + exists = true; + } + + pos = i; + break; + } + } + + if (pos == -1) { + pos = codeTable.size(); + } + } else { + pos = 0; + } + + if (!exists) { + shiftGlyphIndices(fontId, pos, true); + glyphShapeTable.add(pos, shp); + if (fontInfoTag != null) { + fontInfoTag.addFontCharacter(pos, (int) character); + } + } else { + glyphShapeTable.set(pos, shp); + } + + setModified(true); + getSwf().clearImageCache(); + } + + @Override + public boolean removeCharacter(char character) { + ensureFontInfo(); + if (fontInfoTag == null) { + return false; + } + + int code = (int) character; + int pos = -1; + List codeTable = fontInfoTag.getCodeTable(); + for (int i = 0; i < codeTable.size(); i++) { + if (codeTable.get(i) >= code) { + if (codeTable.get(i) == code) { + pos = i; + break; + } + + return false; + } + } + + if (pos == -1) { + return false; + } + + glyphShapeTable.remove(pos); + fontInfoTag.removeFontCharacter(pos); + + shiftGlyphIndices(fontId, pos + 1, false); + + setModified(true); + getSwf().clearImageCache(); + return true; + } + + @Override + public void setAdvanceValues(Font font) { + throw new UnsupportedOperationException("Setting the advance values for DefineFontTag is not supported."); + } + + @Override + public int getCharacterCount() { + ensureFontInfo(); + if (fontInfoTag != null) { + List codeTable = fontInfoTag.getCodeTable(); + return codeTable.size(); + } + return 0; + } + + @Override + public String getCharacters() { + ensureFontInfo(); + if (fontInfoTag != null) { + List codeTable = fontInfoTag.getCodeTable(); + StringBuilder ret = new StringBuilder(codeTable.size()); + for (int i : codeTable) { + ret.append((char) i); + } + return ret.toString(); + } + return ""; + } + + @Override + public int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex) { + return 0; + } + + @Override + public int getCharKerningAdjustment(char c1, char c2) { + return 0; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java index ea797bc95..c028aa7e9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/SoundStreamHead2Tag.java @@ -1,259 +1,261 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; -import com.jpexs.decompiler.flash.timeline.Timeline; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.Reserved; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; -import com.jpexs.decompiler.flash.types.sound.SoundFormat; -import com.jpexs.helpers.ByteArrayRange; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) -public class SoundStreamHead2Tag extends Tag implements SoundStreamHeadTypeTag { - - public static final int ID = 45; - - public static final String NAME = "SoundStreamHead2"; - - @Reserved - @SWFType(value = BasicType.UB, count = 4) - public int reserved; - - @SWFType(value = BasicType.UB, count = 2) - public int playBackSoundRate; - - public boolean playBackSoundSize; - - public boolean playBackSoundType; - - @SWFType(value = BasicType.UB, count = 4) - public int streamSoundCompression; - - @SWFType(value = BasicType.UB, count = 2) - public int streamSoundRate; - - public boolean streamSoundSize; - - public boolean streamSoundType; - - @SWFType(BasicType.UI16) - public int streamSoundSampleCount; - - @SWFType(BasicType.SI16) - @Conditional(value = "streamSoundCompression", options = {2}) - public int latencySeek; - - @Internal - private int virtualCharacterId = 0; - - /** - * Constructor - * - * @param swf - */ - public SoundStreamHead2Tag(SWF swf) { - super(swf, ID, NAME, null); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public SoundStreamHead2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { - super(sis.getSwf(), ID, NAME, data); - readData(sis, data, 0, false, false, false); - } - - @Override - public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { - reserved = (int) sis.readUB(4, "reserved"); - playBackSoundRate = (int) sis.readUB(2, "playBackSoundRate"); - playBackSoundSize = sis.readUB(1, "playBackSoundSize") == 1; - playBackSoundType = sis.readUB(1, "playBackSoundType") == 1; - streamSoundCompression = (int) sis.readUB(4, "streamSoundCompression"); - streamSoundRate = (int) sis.readUB(2, "streamSoundRate"); - streamSoundSize = sis.readUB(1, "streamSoundSize") == 1; - streamSoundType = sis.readUB(1, "streamSoundType") == 1; - streamSoundSampleCount = sis.readUI16("streamSoundSampleCount"); - if (streamSoundCompression == 2) { - latencySeek = sis.readSI16("latencySeek"); - } - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUB(4, reserved); - sos.writeUB(2, playBackSoundRate); - sos.writeUB(1, playBackSoundSize ? 1 : 0); - sos.writeUB(1, playBackSoundType ? 1 : 0); - sos.writeUB(4, streamSoundCompression); - sos.writeUB(2, streamSoundRate); - sos.writeUB(1, streamSoundSize ? 1 : 0); - sos.writeUB(1, streamSoundType ? 1 : 0); - sos.writeUI16(streamSoundSampleCount); - if (streamSoundCompression == 2) { - sos.writeSI16(latencySeek); - } - } - - @Override - public int getCharacterId() { - return virtualCharacterId; - } - - @Override - public void setCharacterId(int characterId) { - virtualCharacterId = characterId; - } - - @Override - public SoundExportFormat getExportFormat() { - if (streamSoundCompression == SoundFormat.FORMAT_MP3) { - return SoundExportFormat.MP3; - } - if (streamSoundCompression == SoundFormat.FORMAT_ADPCM) { - return SoundExportFormat.WAV; - } - if (streamSoundCompression == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (streamSoundCompression == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER || streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER16KHZ || streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER8KHZ) { - return SoundExportFormat.WAV; - } - return SoundExportFormat.FLV; - } - - @Override - public long getSoundSampleCount() { - return streamSoundSampleCount; - } - - @Override - public void setVirtualCharacterId(int ch) { - virtualCharacterId = ch; - } - - @Override - public int getSoundFormatId() { - return streamSoundCompression; - } - - @Override - public int getSoundRate() { - return streamSoundRate; - } - - @Override - public boolean getSoundSize() { - return streamSoundSize; - } - - @Override - public boolean getSoundType() { - return streamSoundType; - } - - @Override - public List getBlocks() { - Timeline timeline = swf.getTimeline(); - List ret = timeline.getSoundStreamBlocks(this); - return ret; - - } - - @Override - public boolean importSupported() { - return false; - } - - @Override - public boolean setSound(InputStream is, int newSoundFormat) { - return false; - } - - @Override - public List getRawSoundData() { - List ret = new ArrayList<>(); - List blocks = getBlocks(); - for (SoundStreamBlockTag block : blocks) { - ByteArrayRange data = block.streamSoundData; - if (streamSoundCompression == SoundFormat.FORMAT_MP3) { - ret.add(data.getSubRange(4, data.getLength() - 4)); - } else { - ret.add(data); - } - } - return ret; - } - - @Override - public long getTotalSoundSampleCount() { - return getBlocks().size() * streamSoundSampleCount; - } - - @Override - public SoundFormat getSoundFormat() { - final int[] rateMap = {5512, 11025, 22050, 44100}; - return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); - } - - @Override - public String getCharacterExportFileName() { - String exportName = swf.getExportName(getCharacterId()); - return getCharacterId() + (exportName != null ? "_" + exportName : ""); - } - - @Override - public void getTagInfo(TagInfo tagInfo) { - super.getTagInfo(tagInfo); - SoundFormat soundFormat = getSoundFormat(); - tagInfo.addInfo("general", "codecName", soundFormat.getFormatName()); - tagInfo.addInfo("general", "exportFormat", soundFormat.getNativeExportFormat()); - tagInfo.addInfo("general", "samplingRate", soundFormat.samplingRate); - tagInfo.addInfo("general", "stereo", soundFormat.stereo); - tagInfo.addInfo("general", "sampleCount", streamSoundSampleCount); - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.Reserved; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class SoundStreamHead2Tag extends Tag implements SoundStreamHeadTypeTag { + + public static final int ID = 45; + + public static final String NAME = "SoundStreamHead2"; + + @Reserved + @SWFType(value = BasicType.UB, count = 4) + public int reserved; + + @SWFType(value = BasicType.UB, count = 2) + public int playBackSoundRate; + + public boolean playBackSoundSize; + + public boolean playBackSoundType; + + @SWFType(value = BasicType.UB, count = 4) + public int streamSoundCompression; + + @SWFType(value = BasicType.UB, count = 2) + public int streamSoundRate; + + public boolean streamSoundSize; + + public boolean streamSoundType; + + @SWFType(BasicType.UI16) + public int streamSoundSampleCount; + + @SWFType(BasicType.SI16) + @Conditional(value = "streamSoundCompression", options = {2}) + public int latencySeek; + + @Internal + private int virtualCharacterId = 0; + + /** + * Constructor + * + * @param swf + */ + public SoundStreamHead2Tag(SWF swf) { + super(swf, ID, NAME, null); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public SoundStreamHead2Tag(SWFInputStream sis, ByteArrayRange data) throws IOException { + super(sis.getSwf(), ID, NAME, data); + readData(sis, data, 0, false, false, false); + } + + @Override + public final void readData(SWFInputStream sis, ByteArrayRange data, int level, boolean parallel, boolean skipUnusualTags, boolean lazy) throws IOException { + reserved = (int) sis.readUB(4, "reserved"); + playBackSoundRate = (int) sis.readUB(2, "playBackSoundRate"); + playBackSoundSize = sis.readUB(1, "playBackSoundSize") == 1; + playBackSoundType = sis.readUB(1, "playBackSoundType") == 1; + streamSoundCompression = (int) sis.readUB(4, "streamSoundCompression"); + streamSoundRate = (int) sis.readUB(2, "streamSoundRate"); + streamSoundSize = sis.readUB(1, "streamSoundSize") == 1; + streamSoundType = sis.readUB(1, "streamSoundType") == 1; + streamSoundSampleCount = sis.readUI16("streamSoundSampleCount"); + if (streamSoundCompression == 2) { + latencySeek = sis.readSI16("latencySeek"); + } + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUB(4, reserved); + sos.writeUB(2, playBackSoundRate); + sos.writeUB(1, playBackSoundSize ? 1 : 0); + sos.writeUB(1, playBackSoundType ? 1 : 0); + sos.writeUB(4, streamSoundCompression); + sos.writeUB(2, streamSoundRate); + sos.writeUB(1, streamSoundSize ? 1 : 0); + sos.writeUB(1, streamSoundType ? 1 : 0); + sos.writeUI16(streamSoundSampleCount); + if (streamSoundCompression == 2) { + sos.writeSI16(latencySeek); + } + } + + @Override + public int getCharacterId() { + return virtualCharacterId; + } + + @Override + public void setCharacterId(int characterId) { + virtualCharacterId = characterId; + } + + @Override + public SoundExportFormat getExportFormat() { + if (streamSoundCompression == SoundFormat.FORMAT_MP3) { + return SoundExportFormat.MP3; + } + if (streamSoundCompression == SoundFormat.FORMAT_ADPCM) { + return SoundExportFormat.WAV; + } + if (streamSoundCompression == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (streamSoundCompression == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER || streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER16KHZ || streamSoundCompression == SoundFormat.FORMAT_NELLYMOSER8KHZ) { + return SoundExportFormat.WAV; + } + return SoundExportFormat.FLV; + } + + @Override + public long getSoundSampleCount() { + return streamSoundSampleCount; + } + + @Override + public void setVirtualCharacterId(int ch) { + virtualCharacterId = ch; + } + + @Override + public int getSoundFormatId() { + return streamSoundCompression; + } + + @Override + public int getSoundRate() { + return streamSoundRate; + } + + @Override + public boolean getSoundSize() { + return streamSoundSize; + } + + @Override + public boolean getSoundType() { + return streamSoundType; + } + + @Override + public List getBlocks() { + Timeline timeline = swf.getTimeline(); + List ret = timeline.getSoundStreamBlocks(this); + return ret; + + } + + @Override + public boolean importSupported() { + return false; + } + + @Override + public boolean setSound(InputStream is, int newSoundFormat) { + return false; + } + + @Override + public List getRawSoundData() { + List ret = new ArrayList<>(); + List blocks = getBlocks(); + if (blocks != null) { + for (SoundStreamBlockTag block : blocks) { + ByteArrayRange data = block.streamSoundData; + if (streamSoundCompression == SoundFormat.FORMAT_MP3) { + ret.add(data.getSubRange(4, data.getLength() - 4)); + } else { + ret.add(data); + } + } + } + return ret; + } + + @Override + public long getTotalSoundSampleCount() { + return getBlocks().size() * streamSoundSampleCount; + } + + @Override + public SoundFormat getSoundFormat() { + final int[] rateMap = {5512, 11025, 22050, 44100}; + return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); + } + + @Override + public String getCharacterExportFileName() { + String exportName = swf.getExportName(getCharacterId()); + return getCharacterId() + (exportName != null ? "_" + exportName : ""); + } + + @Override + public void getTagInfo(TagInfo tagInfo) { + super.getTagInfo(tagInfo); + SoundFormat soundFormat = getSoundFormat(); + tagInfo.addInfo("general", "codecName", soundFormat.getFormatName()); + tagInfo.addInfo("general", "exportFormat", soundFormat.getNativeExportFormat()); + tagInfo.addInfo("general", "samplingRate", soundFormat.samplingRate); + tagInfo.addInfo("general", "stereo", soundFormat.stereo); + tagInfo.addInfo("general", "sampleCount", streamSoundSampleCount); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontInfoTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontInfoTag.java index 5662d20ba..950a4754c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontInfoTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontInfoTag.java @@ -1,62 +1,64 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.helpers.ByteArrayRange; -import java.util.List; - -/** - * - * @author JPEXS - */ -public abstract class FontInfoTag extends Tag implements CharacterIdTag { - - @SWFType(BasicType.UI16) - public int fontID; - - public FontInfoTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract List getCodeTable(); - - public abstract void addCharacter(int index, int character); - - @Override - public int getCharacterId() { - return fontID; - } - - @Override - public void setCharacterId(int characterId) { - this.fontID = characterId; - } - - public abstract String getFontName(); - - public abstract boolean getFontFlagsBold(); - - public abstract void setFontFlagsBold(boolean value); - - public abstract boolean getFontFlagsItalic(); - - public abstract void setFontFlagsItalic(boolean value); -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.ByteArrayRange; +import java.util.List; + +/** + * + * @author JPEXS + */ +public abstract class FontInfoTag extends Tag implements CharacterIdTag { + + @SWFType(BasicType.UI16) + public int fontID; + + public FontInfoTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract List getCodeTable(); + + public abstract void addFontCharacter(int index, int character); + + public abstract void removeFontCharacter(int index); + + @Override + public int getCharacterId() { + return fontID; + } + + @Override + public void setCharacterId(int characterId) { + this.fontID = characterId; + } + + public abstract String getFontName(); + + public abstract boolean getFontFlagsBold(); + + public abstract void setFontFlagsBold(boolean value); + + public abstract boolean getFontFlagsItalic(); + + public abstract void setFontFlagsItalic(boolean value); +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java index 7c8880648..ed2234cad 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/base/FontTag.java @@ -1,411 +1,417 @@ -/* - * Copyright (C) 2010-2016 JPEXS, All rights reserved. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3.0 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. - */ -package com.jpexs.decompiler.flash.tags.base; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.configuration.SwfSpecificConfiguration; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; -import com.jpexs.decompiler.flash.helpers.FontHelper; -import com.jpexs.decompiler.flash.tags.DefineFontNameTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.GLYPHENTRY; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.SHAPE; -import com.jpexs.decompiler.flash.types.TEXTRECORD; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.awt.Font; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.font.FontRenderContext; -import java.awt.font.GlyphMetrics; -import java.awt.font.GlyphVector; -import java.awt.geom.Area; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * - * @author JPEXS - */ -public abstract class FontTag extends DrawableTag implements AloneTag { - - public static final int PREVIEWSIZE = 500; - - public FontTag(SWF swf, int id, String name, ByteArrayRange data) { - super(swf, id, name, data); - } - - public abstract List getGlyphShapeTable(); - - public abstract void addCharacter(char character, Font font); - - public abstract void setAdvanceValues(Font font); - - public abstract char glyphToChar(int glyphIndex); - - public abstract int charToGlyph(char c); - - public abstract double getGlyphAdvance(int glyphIndex); - - public abstract int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex); - - public abstract int getCharKerningAdjustment(char c1, char c2); - - public abstract int getGlyphWidth(int glyphIndex); - - public abstract String getFontNameIntag(); - - public abstract boolean isSmall(); - - public abstract boolean isBold(); - - public abstract boolean isItalic(); - - public abstract boolean isSmallEditable(); - - public abstract boolean isBoldEditable(); - - public abstract boolean isItalicEditable(); - - public abstract void setSmall(boolean value); - - public abstract void setBold(boolean value); - - public abstract void setItalic(boolean value); - - public abstract double getDivider(); - - public abstract int getAscent(); - - public abstract int getDescent(); - - public abstract int getLeading(); - - public String getFontName() { - DefineFontNameTag fontNameTag = getFontNameTag(); - if (fontNameTag == null) { - return getFontNameIntag(); - } - return fontNameTag.fontName; - } - - public String getFontCopyright() { - DefineFontNameTag fontNameTag = getFontNameTag(); - if (fontNameTag == null) { - return ""; - } - return fontNameTag.fontCopyright; - } - - public static Map> installedFontsByFamily; - - public static Map installedFontsByName; - - public static String defaultFontName; - - static { - reload(); - } - - public int getFontId() { - return getCharacterId(); - } - - public boolean hasLayout() { - return false; - } - - public boolean containsChar(char character) { - return charToGlyph(character) > -1; - } - - public int getFontStyle() { - int fontStyle = 0; - if (isBold()) { - fontStyle |= Font.BOLD; - } - if (isItalic()) { - fontStyle |= Font.ITALIC; - } - return fontStyle; - } - - public abstract int getCharacterCount(); - - public abstract String getCharacters(); - - @Override - public String getName() { - String nameAppend = ""; - if (exportName != null) { - nameAppend = ": " + exportName; - } - if (className != null) { - nameAppend = ": " + className; - } - String fontName = getFontNameIntag(); - if (fontName != null) { - nameAppend = ": " + fontName; - } - return tagName + " (" + getCharacterId() + nameAppend + ")"; - } - - @Override - public String getExportFileName() { - String result = super.getExportFileName(); - String fontName = getFontNameIntag(); - if (fontName != null) { - fontName = fontName.replace(" ", "_"); - } - return result + (fontName != null ? "_" + fontName : ""); - } - - public String getSystemFontName() { - int fontId = getFontId(); - String selectedFont = swf.sourceFontNamesMap.get(fontId); - if (selectedFont == null) { - SwfSpecificConfiguration swfConf = Configuration.getSwfSpecificConfiguration(swf.getShortFileName()); - String key = fontId + "_" + getFontNameIntag(); - if (swfConf != null) { - selectedFont = swfConf.fontPairingMap.get(key); - } - } - - if (selectedFont == null) { - selectedFont = Configuration.getFontToNameMap().get(getFontNameIntag()); - } - - if (selectedFont != null && FontTag.installedFontsByName.containsKey(selectedFont)) { - return selectedFont; - } - - // findInstalledFontName always returns an available font name - return FontTag.findInstalledFontName(getFontName()); - } - - public Font getSystemFont() { - return FontTag.installedFontsByName.get(getSystemFontName()); - } - - protected void shiftGlyphIndices(int fontId, int startIndex) { - for (Tag t : swf.getTags()) { - List textRecords = null; - if (t instanceof StaticTextTag) { - textRecords = ((StaticTextTag) t).textRecords; - } - - if (textRecords != null) { - int curFontId = 0; - for (TEXTRECORD tr : textRecords) { - if (tr.styleFlagsHasFont) { - curFontId = tr.fontId; - } - - if (curFontId != fontId) { - continue; - } - - for (GLYPHENTRY en : tr.glyphEntries) { - if (en == null) { //Currently edited - continue; - } - if (en.glyphIndex >= startIndex) { - en.glyphIndex++; - } - } - - t.setModified(true); - } - } - } - } - - public static float getSystemFontAdvance(String fontName, int fontStyle, int fontSize, Character character, Character nextCharacter) { - return getSystemFontAdvance(new Font(fontName, fontStyle, fontSize), character, nextCharacter); - } - - public static float getSystemFontAdvance(Font aFont, Character character, Character nextCharacter) { - String chars = "" + character + (nextCharacter == null ? "" : nextCharacter); - GlyphVector gv = aFont.layoutGlyphVector(new FontRenderContext(aFont.getTransform(), true, true), chars.toCharArray(), 0, chars.length(), Font.LAYOUT_LEFT_TO_RIGHT); - GlyphMetrics gm = gv.getGlyphMetrics(0); - return gm.getAdvanceX(); - } - - public static void reload() { - installedFontsByFamily = FontHelper.getInstalledFonts(); - installedFontsByName = new HashMap<>(); - - for (String fam : installedFontsByFamily.keySet()) { - for (String nam : installedFontsByFamily.get(fam).keySet()) { - installedFontsByName.put(nam, installedFontsByFamily.get(fam).get(nam)); - } - } - - if (installedFontsByFamily.containsKey("Times New Roman")) { - defaultFontName = "Times New Roman"; - } else if (installedFontsByFamily.containsKey("Arial")) { - defaultFontName = "Arial"; - } else { - defaultFontName = installedFontsByFamily.keySet().iterator().next(); - } - } - - public static String getFontNameWithFallback(String fontName) { - if (installedFontsByFamily.containsKey(fontName)) { - return fontName; - } - if (installedFontsByFamily.containsKey("Times New Roman")) { - return "Times New Roman"; - } - if (installedFontsByFamily.containsKey("Arial")) { - return "Arial"; - } - - //First font - return installedFontsByFamily.keySet().iterator().next(); - } - - public static String isFontFamilyInstalled(String fontFamily) { - if (installedFontsByFamily.containsKey(fontFamily)) { - return fontFamily; - } - if (fontFamily.contains("_")) { - String beforeUnderscore = fontFamily.substring(0, fontFamily.indexOf('_')); - if (installedFontsByFamily.containsKey(beforeUnderscore)) { - return beforeUnderscore; - } - } - return null; - } - - public static String findInstalledFontName(String fontName) { - if (installedFontsByName.containsKey(fontName)) { - return fontName; - } - if (fontName != null && fontName.contains("_")) { - String beforeUnderscore = fontName.substring(0, fontName.indexOf('_')); - if (installedFontsByName.containsKey(beforeUnderscore)) { - return beforeUnderscore; - } - } - return defaultFontName; - } - - @Override - public int getUsedParameters() { - return PARAMETER_FRAME; - } - - @Override - public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) { - RECT r = getRect(); - return new Area(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); - } - - @Override - public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { - SHAPERECORD.shapeListToImage(swf, getGlyphShapeTable(), image, frame, Color.black, colorTransform); - } - - @Override - public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { - } - - @Override - public void toHtmlCanvas(StringBuilder result, double unitDivisor) { - List shapes = getGlyphShapeTable(); - result.append("\tdefaultFill = textColor;\r\n"); - result.append("\tswitch(ch){\r\n"); - for (int i = 0; i < shapes.size(); i++) { - char c = glyphToChar(i); - String cs = "" + c; - cs = cs.replace("\\", "\\\\").replace("\"", "\\\""); - result.append("\t\tcase \"").append(cs).append("\":\r\n"); - CanvasShapeExporter exporter = new CanvasShapeExporter(null, unitDivisor, swf, shapes.get(i), null, 0, 0); - exporter.export(); - result.append("\t\t").append(exporter.getShapeData().replaceAll("\r\n", "\r\n\t\t")); - result.append("\tbreak;\r\n"); - } - result.append("\t}\r\n"); - } - - @Override - public int getNumFrames() { - int frameCount = (getGlyphShapeTable().size() - 1) / SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW + 1; - if (frameCount < 1) { - frameCount = 1; - } - return frameCount; - } - - @Override - public boolean isSingleFrame() { - return true; - } - - @Override - public RECT getRect() { - return getRect(null); // parameter not used - } - - @Override - public RECT getRect(Set added) { - return new RECT(0, (int) (PREVIEWSIZE * SWF.unitDivisor), 0, (int) (PREVIEWSIZE * SWF.unitDivisor)); - } - - @Override - public String getCharacterExportFileName() { - return super.getCharacterExportFileName() + "_" + getFontNameIntag(); - } - - public DefineFontNameTag getFontNameTag() { - for (Tag t : swf.getTags()) { - if (t instanceof DefineFontNameTag) { - DefineFontNameTag dfn = (DefineFontNameTag) t; - if (dfn.fontId == getFontId()) { - return dfn; - } - } - } - return null; - } - - public String getCopyright() { - DefineFontNameTag dfn = getFontNameTag(); - if (dfn == null) { - return null; - } - return dfn.fontCopyright; - } - - public RECT getGlyphBounds(int glyphIndex) { - return getGlyphShapeTable().get(glyphIndex).getBounds(); - } - - public FontTag toClassicFont() { - return this; - } -} +/* + * Copyright (C) 2010-2016 JPEXS, All rights reserved. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. + */ +package com.jpexs.decompiler.flash.tags.base; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.configuration.SwfSpecificConfiguration; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter; +import com.jpexs.decompiler.flash.helpers.FontHelper; +import com.jpexs.decompiler.flash.tags.DefineFontNameTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.GLYPHENTRY; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.SHAPE; +import com.jpexs.decompiler.flash.types.TEXTRECORD; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.Font; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.font.FontRenderContext; +import java.awt.font.GlyphMetrics; +import java.awt.font.GlyphVector; +import java.awt.geom.Area; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * @author JPEXS + */ +public abstract class FontTag extends DrawableTag implements AloneTag { + + public static final int PREVIEWSIZE = 500; + + public FontTag(SWF swf, int id, String name, ByteArrayRange data) { + super(swf, id, name, data); + } + + public abstract List getGlyphShapeTable(); + + public abstract void addCharacter(char character, Font font); + + public abstract boolean removeCharacter(char character); + + public abstract void setAdvanceValues(Font font); + + public abstract char glyphToChar(int glyphIndex); + + public abstract int charToGlyph(char c); + + public abstract double getGlyphAdvance(int glyphIndex); + + public abstract int getGlyphKerningAdjustment(int glyphIndex, int nextGlyphIndex); + + public abstract int getCharKerningAdjustment(char c1, char c2); + + public abstract int getGlyphWidth(int glyphIndex); + + public abstract String getFontNameIntag(); + + public abstract boolean isSmall(); + + public abstract boolean isBold(); + + public abstract boolean isItalic(); + + public abstract boolean isSmallEditable(); + + public abstract boolean isBoldEditable(); + + public abstract boolean isItalicEditable(); + + public abstract void setSmall(boolean value); + + public abstract void setBold(boolean value); + + public abstract void setItalic(boolean value); + + public abstract double getDivider(); + + public abstract int getAscent(); + + public abstract int getDescent(); + + public abstract int getLeading(); + + public String getFontName() { + DefineFontNameTag fontNameTag = getFontNameTag(); + if (fontNameTag == null) { + return getFontNameIntag(); + } + return fontNameTag.fontName; + } + + public String getFontCopyright() { + DefineFontNameTag fontNameTag = getFontNameTag(); + if (fontNameTag == null) { + return ""; + } + return fontNameTag.fontCopyright; + } + + public static Map> installedFontsByFamily; + + public static Map installedFontsByName; + + public static String defaultFontName; + + static { + reload(); + } + + public int getFontId() { + return getCharacterId(); + } + + public boolean hasLayout() { + return false; + } + + public boolean containsChar(char character) { + return charToGlyph(character) > -1; + } + + public int getFontStyle() { + int fontStyle = 0; + if (isBold()) { + fontStyle |= Font.BOLD; + } + if (isItalic()) { + fontStyle |= Font.ITALIC; + } + return fontStyle; + } + + public abstract int getCharacterCount(); + + public abstract String getCharacters(); + + @Override + public String getName() { + String nameAppend = ""; + if (exportName != null) { + nameAppend = ": " + exportName; + } + if (className != null) { + nameAppend = ": " + className; + } + String fontName = getFontNameIntag(); + if (fontName != null) { + nameAppend = ": " + fontName; + } + return tagName + " (" + getCharacterId() + nameAppend + ")"; + } + + @Override + public String getExportFileName() { + String result = super.getExportFileName(); + String fontName = getFontNameIntag(); + if (fontName != null) { + fontName = fontName.replace(" ", "_"); + } + return result + (fontName != null ? "_" + fontName : ""); + } + + public String getSystemFontName() { + int fontId = getFontId(); + String selectedFont = swf.sourceFontNamesMap.get(fontId); + if (selectedFont == null) { + SwfSpecificConfiguration swfConf = Configuration.getSwfSpecificConfiguration(swf.getShortFileName()); + String key = fontId + "_" + getFontNameIntag(); + if (swfConf != null) { + selectedFont = swfConf.fontPairingMap.get(key); + } + } + + if (selectedFont == null) { + selectedFont = Configuration.getFontToNameMap().get(getFontNameIntag()); + } + + if (selectedFont != null && FontTag.installedFontsByName.containsKey(selectedFont)) { + return selectedFont; + } + + // findInstalledFontName always returns an available font name + return FontTag.findInstalledFontName(getFontName()); + } + + public Font getSystemFont() { + return FontTag.installedFontsByName.get(getSystemFontName()); + } + + protected void shiftGlyphIndices(int fontId, int startIndex, boolean increment) { + for (Tag t : swf.getTags()) { + List textRecords = null; + if (t instanceof StaticTextTag) { + textRecords = ((StaticTextTag) t).textRecords; + } + + if (textRecords != null) { + int curFontId = 0; + for (TEXTRECORD tr : textRecords) { + if (tr.styleFlagsHasFont) { + curFontId = tr.fontId; + } + + if (curFontId != fontId) { + continue; + } + + for (GLYPHENTRY en : tr.glyphEntries) { + if (en == null) { //Currently edited + continue; + } + if (en.glyphIndex >= startIndex) { + if (increment) { + en.glyphIndex++; + } else { + en.glyphIndex--; + } + } + } + + t.setModified(true); + } + } + } + } + + public static float getSystemFontAdvance(String fontName, int fontStyle, int fontSize, Character character, Character nextCharacter) { + return getSystemFontAdvance(new Font(fontName, fontStyle, fontSize), character, nextCharacter); + } + + public static float getSystemFontAdvance(Font aFont, Character character, Character nextCharacter) { + String chars = "" + character + (nextCharacter == null ? "" : nextCharacter); + GlyphVector gv = aFont.layoutGlyphVector(new FontRenderContext(aFont.getTransform(), true, true), chars.toCharArray(), 0, chars.length(), Font.LAYOUT_LEFT_TO_RIGHT); + GlyphMetrics gm = gv.getGlyphMetrics(0); + return gm.getAdvanceX(); + } + + public static void reload() { + installedFontsByFamily = FontHelper.getInstalledFonts(); + installedFontsByName = new HashMap<>(); + + for (String fam : installedFontsByFamily.keySet()) { + for (String nam : installedFontsByFamily.get(fam).keySet()) { + installedFontsByName.put(nam, installedFontsByFamily.get(fam).get(nam)); + } + } + + if (installedFontsByFamily.containsKey("Times New Roman")) { + defaultFontName = "Times New Roman"; + } else if (installedFontsByFamily.containsKey("Arial")) { + defaultFontName = "Arial"; + } else { + defaultFontName = installedFontsByFamily.keySet().iterator().next(); + } + } + + public static String getFontNameWithFallback(String fontName) { + if (installedFontsByFamily.containsKey(fontName)) { + return fontName; + } + if (installedFontsByFamily.containsKey("Times New Roman")) { + return "Times New Roman"; + } + if (installedFontsByFamily.containsKey("Arial")) { + return "Arial"; + } + + //First font + return installedFontsByFamily.keySet().iterator().next(); + } + + public static String isFontFamilyInstalled(String fontFamily) { + if (installedFontsByFamily.containsKey(fontFamily)) { + return fontFamily; + } + if (fontFamily.contains("_")) { + String beforeUnderscore = fontFamily.substring(0, fontFamily.indexOf('_')); + if (installedFontsByFamily.containsKey(beforeUnderscore)) { + return beforeUnderscore; + } + } + return null; + } + + public static String findInstalledFontName(String fontName) { + if (installedFontsByName.containsKey(fontName)) { + return fontName; + } + if (fontName != null && fontName.contains("_")) { + String beforeUnderscore = fontName.substring(0, fontName.indexOf('_')); + if (installedFontsByName.containsKey(beforeUnderscore)) { + return beforeUnderscore; + } + } + return defaultFontName; + } + + @Override + public int getUsedParameters() { + return PARAMETER_FRAME; + } + + @Override + public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, boolean stroked) { + RECT r = getRect(); + return new Area(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); + } + + @Override + public void toImage(int frame, int time, int ratio, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { + SHAPERECORD.shapeListToImage(swf, getGlyphShapeTable(), image, frame, Color.black, colorTransform); + } + + @Override + public void toSVG(SVGExporter exporter, int ratio, ColorTransform colorTransform, int level) { + } + + @Override + public void toHtmlCanvas(StringBuilder result, double unitDivisor) { + List shapes = getGlyphShapeTable(); + result.append("\tdefaultFill = textColor;\r\n"); + result.append("\tswitch(ch){\r\n"); + for (int i = 0; i < shapes.size(); i++) { + char c = glyphToChar(i); + String cs = "" + c; + cs = cs.replace("\\", "\\\\").replace("\"", "\\\""); + result.append("\t\tcase \"").append(cs).append("\":\r\n"); + CanvasShapeExporter exporter = new CanvasShapeExporter(null, unitDivisor, swf, shapes.get(i), null, 0, 0); + exporter.export(); + result.append("\t\t").append(exporter.getShapeData().replaceAll("\r\n", "\r\n\t\t")); + result.append("\tbreak;\r\n"); + } + result.append("\t}\r\n"); + } + + @Override + public int getNumFrames() { + int frameCount = (getGlyphShapeTable().size() - 1) / SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW + 1; + if (frameCount < 1) { + frameCount = 1; + } + return frameCount; + } + + @Override + public boolean isSingleFrame() { + return true; + } + + @Override + public RECT getRect() { + return getRect(null); // parameter not used + } + + @Override + public RECT getRect(Set added) { + return new RECT(0, (int) (PREVIEWSIZE * SWF.unitDivisor), 0, (int) (PREVIEWSIZE * SWF.unitDivisor)); + } + + @Override + public String getCharacterExportFileName() { + return super.getCharacterExportFileName() + "_" + getFontNameIntag(); + } + + public DefineFontNameTag getFontNameTag() { + for (Tag t : swf.getTags()) { + if (t instanceof DefineFontNameTag) { + DefineFontNameTag dfn = (DefineFontNameTag) t; + if (dfn.fontId == getFontId()) { + return dfn; + } + } + } + return null; + } + + public String getCopyright() { + DefineFontNameTag dfn = getFontNameTag(); + if (dfn == null) { + return null; + } + return dfn.fontCopyright; + } + + public RECT getGlyphBounds(int glyphIndex) { + return getGlyphShapeTable().get(glyphIndex).getBounds(); + } + + public FontTag toClassicFont() { + return this; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java index f9054611a..609f1be69 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/gfx/DefineCompactedFont.java @@ -179,7 +179,7 @@ public final class DefineCompactedFont extends FontTag { } if (!exists) { - shiftGlyphIndices(fontId, pos); + shiftGlyphIndices(fontId, pos, true); } Font fnt = cfont.deriveFont(fontStyle, Math.round(font.nominalSize * d)); @@ -198,6 +198,37 @@ public final class DefineCompactedFont extends FontTag { getSwf().clearImageCache(); } + @Override + public boolean removeCharacter(char character) { + FontType font = fonts.get(0); + + int code = (int) character; + int pos = -1; + for (int i = 0; i < font.glyphInfo.size(); i++) { + if (font.glyphInfo.get(i).glyphCode >= code) { + if (font.glyphInfo.get(i).glyphCode == code) { + pos = i; + break; + } + + return false; + } + } + + if (pos == -1) { + return false; + } + + font.glyphInfo.remove(pos); + font.glyphs.remove(pos); + shapeCache.remove(pos); + shiftGlyphIndices(fontId, pos + 1, false); + + setModified(true); + getSwf().clearImageCache(); + return true; + } + @Override public void setAdvanceValues(Font font) { throw new UnsupportedOperationException("Setting the advance values for DefineCompactedFont is not supported."); diff --git a/src/com/jpexs/decompiler/flash/gui/FontPanel.java b/src/com/jpexs/decompiler/flash/gui/FontPanel.java index b0fd01d30..c87b36bde 100644 --- a/src/com/jpexs/decompiler/flash/gui/FontPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/FontPanel.java @@ -1,687 +1,719 @@ -/* - * Copyright (C) 2010-2016 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.gui; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.gui.helpers.TableLayoutHelper; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.treeitems.TreeItem; -import com.jpexs.helpers.Helper; -import java.awt.BorderLayout; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Font; -import java.awt.FontFormatException; -import java.awt.Rectangle; -import java.awt.event.ActionEvent; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Locale; -import java.util.Set; -import java.util.TreeSet; -import java.util.Vector; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.BorderFactory; -import javax.swing.ComboBoxModel; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JFileChooser; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; -import javax.swing.JTextField; -import javax.swing.ScrollPaneConstants; -import javax.swing.filechooser.FileFilter; -import layout.TableLayout; - -/** - * - * @author JPEXS - */ -public class FontPanel extends JPanel { - - private final MainPanel mainPanel; - - private FontTag fontTag; - - /** - * Creates new form FontPanel - * - * @param mainPanel Main panel - */ - public FontPanel(MainPanel mainPanel) { - this.mainPanel = mainPanel; - initComponents(); - } - - public FontTag getFontTag() { - return fontTag; - } - - public void clear() { - fontTag = null; - } - - public static ComboBoxModel getFamilyModel() { - Set famSet = new TreeSet<>(); - for (Font f : FontTag.installedFontsByName.values()) { - famSet.add(new FontFamily(f)); - } - return new DefaultComboBoxModel<>(new Vector<>(famSet)); - } - - public static ComboBoxModel getFaceModel(FontFamily family) { - - Set faceSet = new TreeSet<>(); - for (Font f : FontTag.installedFontsByFamily.get(family.familyEn).values()) { - faceSet.add(new FontFace(f)); - } - - return new DefaultComboBoxModel<>(new Vector<>(faceSet)); - } - - private void setEditable(boolean editable) { - if (editable) { - buttonEdit.setVisible(false); - buttonSave.setVisible(true); - buttonCancel.setVisible(true); - if (fontTag.isBoldEditable()) { - fontIsBoldCheckBox.setEnabled(true); - } - if (fontTag.isItalicEditable()) { - fontIsItalicCheckBox.setEnabled(true); - } - } else { - buttonEdit.setVisible(true); - buttonSave.setVisible(false); - buttonCancel.setVisible(false); - fontIsBoldCheckBox.setEnabled(false); - fontIsItalicCheckBox.setEnabled(false); - } - } - - private String translate(String key) { - return mainPanel.translate(key); - } - - private void fontAddChars(FontTag ft, Set selChars, Font font) { - FontTag f = (FontTag) mainPanel.tagTree.getCurrentTreeItem(); - String oldchars = f.getCharacters(); - for (int ic : selChars) { - char c = (char) ic; - if (oldchars.indexOf((int) c) == -1) { - font = font.deriveFont(f.getFontStyle(), 1024); - if (!font.canDisplay(c)) { - String msg = translate("error.font.nocharacter").replace("%char%", "" + c); - Logger.getLogger(FontPanel.class.getName()).log(Level.SEVERE, msg); - View.showMessageDialog(null, msg, translate("error"), JOptionPane.ERROR_MESSAGE); - return; - } - } - } - - String[] yesno = new String[]{translate("button.yes"), translate("button.no"), translate("button.yes.all"), translate("button.no.all")}; - boolean yestoall = false; - boolean notoall = false; - boolean replaced = false; - for (int ic : selChars) { - char c = (char) ic; - if (oldchars.indexOf((int) c) > -1) { - int opt = -1; - if (!(yestoall || notoall)) { - opt = View.showOptionDialog(null, translate("message.font.add.exists").replace("%char%", "" + c), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, yesno, translate("button.yes")); - if (opt == 2) { - yestoall = true; - } - if (opt == 3) { - notoall = true; - } - } - - if (yestoall) { - opt = 0; // yes - } else if (notoall) { - opt = 1; // no - } - - if (opt == 1) { - continue; - } - - replaced = true; - } - - f.addCharacter(c, font); - oldchars += c; - } - - if (replaced) { - if (View.showConfirmDialog(null, translate("message.font.replace.updateTexts"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.YES_OPTION) { - int fontId = ft.getFontId(); - SWF swf = ft.getSwf(); - for (Tag tag : swf.getTags()) { - if (tag instanceof TextTag) { - TextTag textTag = (TextTag) tag; - if (textTag.getFontIds().contains(fontId)) { - String text = textTag.getFormattedText(true).text; - mainPanel.saveText(textTag, text, null, null); - } - } - } - } - } - - ft.setModified(true); - ft.getSwf().clearImageCache(); - } - - public void showFontTag(FontTag ft) { - SWF swf = ft.getSwf(); - fontTag = ft; - fontNameIntagLabel.setText(ft.getFontNameIntag()); - fontNameTextArea.setText(ft.getFontName()); - fontCopyrightTextArea.setText(ft.getFontCopyright()); - - fontIsBoldCheckBox.setSelected(ft.isBold()); - fontIsItalicCheckBox.setSelected(ft.isItalic()); - fontDescentLabel.setText(ft.getDescent() == -1 ? translate("value.unknown") : Integer.toString(ft.getDescent())); - fontAscentLabel.setText(ft.getAscent() == -1 ? translate("value.unknown") : Integer.toString(ft.getAscent())); - fontLeadingLabel.setText(ft.getLeading() == -1 ? translate("value.unknown") : Integer.toString(ft.getLeading())); - String chars = ft.getCharacters(); - fontCharactersTextArea.setText(chars); - fontCharactersScrollPane.getVerticalScrollBar().scrollRectToVisible(new Rectangle(0, 0, 1, 1)); - setAllowSave(false); - - Font selFont = ft.getSystemFont(); - fontFamilyNameSelection.setSelectedItem(new FontFamily(selFont)); - fontFaceSelection.setSelectedItem(new FontFace(selFont)); - - setAllowSave(true); - setEditable(false); - boolean readOnly = ((Tag) ft).isReadOnly(); - if (readOnly) { - addCharsPanel.setVisible(false); - buttonEdit.setVisible(false); - } - } - - private void initComponents() { - - contentScrollPane = new JScrollPane(); - addCharsPanel = new JPanel(); - fontParamsPanel = new JPanel(); - fontNameIntagLabel = new JLabel(); - JScrollPane fontDisplayNameScrollPane = new JScrollPane(); - fontNameTextArea = new JTextArea(); - JLabel jLabel3 = new JLabel(); - JScrollPane fontCopyrightScrollPane = new JScrollPane(); - fontCopyrightTextArea = new JTextArea(); - JLabel jLabel4 = new JLabel(); - fontIsBoldCheckBox = new JCheckBox(); - JLabel jLabel5 = new JLabel(); - fontIsItalicCheckBox = new JCheckBox(); - JLabel jLabel6 = new JLabel(); - fontAscentLabel = new JLabel(); - JLabel jLabel7 = new JLabel(); - fontDescentLabel = new JLabel(); - JLabel jLabel8 = new JLabel(); - fontLeadingLabel = new JLabel(); - JLabel jLabel9 = new JLabel(); - fontCharactersScrollPane = new JScrollPane(); - fontCharactersTextArea = new JTextArea(); - JLabel fontCharsAddLabel = new JLabel(); - fontAddCharactersField = new JTextField(); - fontAddCharsButton = new JButton(); - fontSourceLabel = new JLabel(); - fontFamilyNameSelection = new JComboBox<>(); - fontFaceSelection = new JComboBox<>(); - fontEmbedButton = new JButton(); - buttonEdit = new JButton(); - buttonSave = new JButton(); - buttonCancel = new JButton(); - buttonPreviewFont = new JButton(); - buttonSetAdvanceValues = new JButton(); - addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent evt) { - formComponentResized(evt); - } - }); - - contentPanel = new JPanel(); - contentScrollPane.setBorder(null); - contentScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); - - TableLayout tlFontParamsPanel; - fontParamsPanel.setLayout(tlFontParamsPanel = new TableLayout(new double[][]{ - {TableLayout.PREFERRED, TableLayout.FILL}, - {TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.FILL,} - })); - - JLabel fontNameIntagLabLabel = new JLabel(); - fontNameIntagLabLabel.setText(AppStrings.translate("font.name.intag")); - fontParamsPanel.add(fontNameIntagLabLabel, "0,0,R"); - - fontNameIntagLabel.setText(AppStrings.translate("value.unknown")); - fontNameIntagLabel.setMinimumSize(new Dimension(100, fontNameIntagLabel.getMinimumSize().height)); - fontNameIntagLabel.setPreferredSize(new Dimension(250, fontNameIntagLabel.getPreferredSize().height)); - fontParamsPanel.add(fontNameIntagLabel, "1,0"); - - JLabel fontNameNameLabLabel = new JLabel(); - fontNameNameLabLabel.setText(AppStrings.translate("font.name")); - fontParamsPanel.add(fontNameNameLabLabel, "0,1,R"); - - fontDisplayNameScrollPane.setBorder(null); - fontDisplayNameScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - fontDisplayNameScrollPane.setHorizontalScrollBar(null); - - fontNameTextArea.setEditable(false); - fontNameTextArea.setColumns(20); - fontNameTextArea.setFont(new JLabel().getFont()); - fontNameTextArea.setLineWrap(true); - fontNameTextArea.setText(AppStrings.translate("value.unknown")); - fontNameTextArea.setWrapStyleWord(true); - fontNameTextArea.setMinimumSize(new Dimension(100, fontNameTextArea.getMinimumSize().height)); - fontNameTextArea.setOpaque(false); - fontDisplayNameScrollPane.setViewportView(fontNameTextArea); - - fontParamsPanel.add(fontDisplayNameScrollPane, "1,1"); - - jLabel3.setText(AppStrings.translate("fontName.copyright")); - fontParamsPanel.add(jLabel3, "0,2,R"); - - fontCopyrightScrollPane.setBorder(null); - fontCopyrightScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - fontCopyrightScrollPane.setHorizontalScrollBar(null); - - fontCopyrightTextArea.setEditable(false); - fontCopyrightTextArea.setColumns(20); - fontCopyrightTextArea.setFont(new JLabel().getFont()); - fontCopyrightTextArea.setLineWrap(true); - fontCopyrightTextArea.setText(AppStrings.translate("value.unknown")); - fontCopyrightTextArea.setWrapStyleWord(true); - fontCopyrightTextArea.setMinimumSize(new Dimension(100, fontCopyrightTextArea.getMinimumSize().height)); - fontCopyrightTextArea.setOpaque(false); - fontCopyrightScrollPane.setViewportView(fontCopyrightTextArea); - - fontParamsPanel.add(fontCopyrightScrollPane, "1,2"); - - jLabel4.setText(AppStrings.translate("font.isbold")); - fontParamsPanel.add(jLabel4, "0,3,R"); - - fontIsBoldCheckBox.setEnabled(false); - - fontParamsPanel.add(fontIsBoldCheckBox, "1,3"); - - jLabel5.setText(AppStrings.translate("font.isitalic")); - - fontParamsPanel.add(jLabel5, "0,4,R"); - - fontIsItalicCheckBox.setEnabled(false); - fontParamsPanel.add(fontIsItalicCheckBox, "1,4"); - - jLabel6.setText(AppStrings.translate("font.ascent")); - fontParamsPanel.add(jLabel6, "0,5,R"); - - fontAscentLabel.setText(AppStrings.translate("value.unknown")); - fontParamsPanel.add(fontAscentLabel, "1,5"); - - jLabel7.setText(AppStrings.translate("font.descent")); - fontParamsPanel.add(jLabel7, "0,6,R"); - - fontDescentLabel.setText(AppStrings.translate("value.unknown")); - fontParamsPanel.add(fontDescentLabel, "1,6"); - - jLabel8.setText(AppStrings.translate("font.leading")); - fontParamsPanel.add(jLabel8, "0,7,R"); - - fontLeadingLabel.setText(AppStrings.translate("value.unknown")); - fontParamsPanel.add(fontLeadingLabel, "1,7"); - - jLabel9.setText(AppStrings.translate("font.characters")); - fontParamsPanel.add(jLabel9, "0,8,R,T"); - - fontCharactersScrollPane.setBorder(null); - fontCharactersScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); - fontCharactersScrollPane.setHorizontalScrollBar(null); - fontCharactersScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - - fontCharactersTextArea.setEditable(false); - fontCharactersTextArea.setColumns(20); - fontCharactersTextArea.setFont(new JLabel().getFont()); - fontCharactersTextArea.setLineWrap(true); - fontCharactersTextArea.setWrapStyleWord(true); - fontCharactersTextArea.setMinimumSize(new Dimension(100, fontCharactersTextArea.getMinimumSize().height)); - fontCharactersTextArea.setOpaque(false); - fontCharactersScrollPane.setViewportView(fontCharactersTextArea); - fontParamsPanel.add(fontCharactersScrollPane, "1,8"); - - fontCharsAddLabel.setText(AppStrings.translate("font.characters.add")); - - fontAddCharsButton.setText(AppStrings.translate("button.ok")); - fontAddCharsButton.addActionListener(this::fontAddCharsButtonActionPerformed); - - fontSourceLabel.setText(AppStrings.translate("font.source")); - - fontFamilyNameSelection.setPreferredSize(new Dimension(100, fontFamilyNameSelection.getMinimumSize().height)); - fontFamilyNameSelection.setModel(getFamilyModel()); - fontFamilyNameSelection.setSelectedItem(FontTag.defaultFontName); - fontFaceSelection.setModel(getFaceModel((FontFamily) fontFamilyNameSelection.getSelectedItem())); - fontFamilyNameSelection.addItemListener(new ItemListener() { - @Override - public void itemStateChanged(ItemEvent evt) { - fontFamilySelectionItemStateChanged(); - } - }); - - fontFaceSelection.addItemListener(new ItemListener() { - - @Override - public void itemStateChanged(ItemEvent evt) { - fontFaceSelectionItemStateChanged(); - } - }); - - fontEmbedButton.setText(AppStrings.translate("button.font.embed")); - fontEmbedButton.addActionListener(this::fontEmbedButtonActionPerformed); - - buttonEdit.setIcon(View.getIcon("edit16")); - buttonEdit.setText(AppStrings.translate("button.edit")); - buttonEdit.addActionListener(this::buttonEditActionPerformed); - - buttonSave.setIcon(View.getIcon("save16")); - buttonSave.setText(AppStrings.translate("button.save")); - buttonSave.addActionListener(this::buttonSaveActionPerformed); - - buttonCancel.setIcon(View.getIcon("cancel16")); - buttonCancel.setText(AppStrings.translate("button.cancel")); - buttonCancel.addActionListener(this::buttonCancelActionPerformed); - - buttonPreviewFont.setText(AppStrings.translate("button.preview")); - buttonPreviewFont.addActionListener(this::buttonPreviewFontActionPerformed); - - buttonSetAdvanceValues.setText(AppStrings.translate("button.setAdvanceValues")); - buttonSetAdvanceValues.addActionListener(this::buttonSetAdvanceValuesActionPerformed); - - TableLayout tlAddCharsPanel; - addCharsPanel.setLayout(tlAddCharsPanel = new TableLayout(new double[][]{ - {TableLayout.PREFERRED, TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED}, - {TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED} - })); - addCharsPanel.setBorder(BorderFactory.createRaisedBevelBorder()); - - addCharsPanel.add(fontCharsAddLabel, "0,0,R"); - addCharsPanel.add(fontAddCharactersField, "1,0,2,0"); - addCharsPanel.add(fontAddCharsButton, "3,0"); - addCharsPanel.add(fontEmbedButton, "4,0"); - - addCharsPanel.add(fontSourceLabel, "0,1,R"); - addCharsPanel.add(fontFamilyNameSelection, "1,1"); - addCharsPanel.add(fontFaceSelection, "2,1"); - addCharsPanel.add(buttonPreviewFont, "3,1"); - addCharsPanel.add(buttonSetAdvanceValues, "4,1"); - - JPanel buttonsPanel = new JPanel(new FlowLayout()); - buttonsPanel.add(buttonEdit); - buttonsPanel.add(buttonSave); - buttonsPanel.add(buttonCancel); - - TableLayout tlAll; - contentPanel.setLayout(tlAll = new TableLayout(new double[][]{ - {TableLayout.FILL}, - {TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED} - })); - - contentPanel.add(fontParamsPanel, "0,0"); - contentPanel.add(buttonsPanel, "0,1"); - contentPanel.add(addCharsPanel, "0,2"); - contentScrollPane.setViewportView(contentPanel); - - setLayout(new BorderLayout()); - add(contentScrollPane, BorderLayout.CENTER); - - TableLayoutHelper.addTableSpaces(tlAddCharsPanel, 10); - TableLayoutHelper.addTableSpaces(tlFontParamsPanel, 10); - TableLayoutHelper.addTableSpaces(tlAll, 10); - - addComponentListener(new ComponentAdapter() { - - @Override - public void componentResized(ComponentEvent e) { - contentPanel.setPreferredSize(new Dimension(Math.max(getSize().width, addCharsPanel.getPreferredSize().width + 20), getSize().height)); - contentPanel.revalidate(); - } - - }); - } - - private void fontAddCharsButtonActionPerformed(ActionEvent evt) { - String newchars = fontAddCharactersField.getText(); - - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item instanceof FontTag) { - FontTag ft = (FontTag) item; - Set selChars = new TreeSet<>(); - for (int c = 0; c < newchars.length(); c++) { - selChars.add(newchars.codePointAt(c)); - } - - if (!selChars.isEmpty()) { - fontAddChars(ft, selChars, ((FontFace) fontFaceSelection.getSelectedItem()).font); - fontAddCharactersField.setText(""); - mainPanel.reload(true); - } - } - } - - private void fontEmbedButtonActionPerformed(ActionEvent evt) { - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item instanceof FontTag) { - FontTag ft = (FontTag) item; - FontEmbedDialog fed = new FontEmbedDialog((FontFace) fontFaceSelection.getSelectedItem(), fontAddCharactersField.getText()); - if (fed.showDialog() == AppDialog.OK_OPTION) { - Set selChars = fed.getSelectedChars(); - if (!selChars.isEmpty()) { - Font selFont = fed.getSelectedFont(); - fontFamilyNameSelection.setSelectedItem(new FontFamily(selFont)); - fontFaceSelection.setSelectedItem(new FontFace(selFont)); - fontAddChars(ft, selChars, selFont); - fontAddCharactersField.setText(""); - mainPanel.reload(true); - } - } - } - } - - private boolean allowSave = true; - - private synchronized void setAllowSave(boolean v) { - allowSave = v; - } - - private synchronized void savePair() { - if (!allowSave) { - return; - } - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item instanceof FontTag) { - FontTag f = (FontTag) item; - SWF swf = f.getSwf(); - String selectedName = ((FontFace) fontFaceSelection.getSelectedItem()).font.getFontName(Locale.ENGLISH); - swf.sourceFontNamesMap.put(f.getFontId(), selectedName); - Configuration.addFontPair(swf.getShortFileName(), f.getFontId(), f.getFontNameIntag(), selectedName); - } - } - - private void fontFamilySelectionItemStateChanged() { - - savePair(); - fontFaceSelection.setModel(getFaceModel((FontFamily) fontFamilyNameSelection.getSelectedItem())); - } - - private void fontFaceSelectionItemStateChanged() { - savePair(); - } - - private void buttonEditActionPerformed(ActionEvent evt) { - setEditable(true); - } - - private void buttonSaveActionPerformed(ActionEvent evt) { - if (fontTag.isBoldEditable()) { - fontTag.setBold(fontIsBoldCheckBox.isSelected()); - } - if (fontTag.isItalicEditable()) { - fontTag.setItalic(fontIsItalicCheckBox.isSelected()); - } - setEditable(false); - } - - private void buttonCancelActionPerformed(ActionEvent evt) { - showFontTag(fontTag); - setEditable(false); - } - - private void buttonPreviewFontActionPerformed(ActionEvent evt) { - new FontPreviewDialog(null, true, ((FontFace) fontFaceSelection.getSelectedItem()).font).setVisible(true); - } - - private void buttonSetAdvanceValuesActionPerformed(ActionEvent evt) { - fontTag.setAdvanceValues(((FontFace) fontFaceSelection.getSelectedItem()).font); - } - - private void formComponentResized(ComponentEvent evt) { - fontParamsPanel.updateUI(); - } - - private void importTTFButtonActionPerformed(ActionEvent evt) { - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item instanceof FontTag) { - FontTag ft = (FontTag) item; - - JFileChooser fc = new JFileChooser(); - fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); - FileFilter ttfFilter = new FileFilter() { - @Override - public boolean accept(File f) { - return (f.getName().toLowerCase().endsWith(".ttf")) || (f.isDirectory()); - } - - @Override - public String getDescription() { - return "TTF files"; - } - }; - fc.setFileFilter(ttfFilter); - - fc.setAcceptAllFileFilterUsed(false); - JFrame fr = new JFrame(); - View.setWindowIcon(fr); - int returnVal = fc.showOpenDialog(fr); - if (returnVal == JFileChooser.APPROVE_OPTION) { - Configuration.lastOpenDir.set(Helper.fixDialogFile(fc.getSelectedFile()).getParentFile().getAbsolutePath()); - File selfile = Helper.fixDialogFile(fc.getSelectedFile()); - Set selChars = new HashSet<>(); - try { - Font f = Font.createFont(Font.TRUETYPE_FONT, selfile); - int required[] = new int[]{0x0001, 0x0000, 0x000D, 0x0020}; - loopi: - for (char i = 0; i < Character.MAX_VALUE; i++) { - for (int r : required) { - if (r == i) { - continue loopi; - } - } - if (f.canDisplay((int) i)) { - selChars.add((int) i); - } - } - fontAddChars(ft, selChars, f); - mainPanel.reload(true); - } catch (FontFormatException ex) { - JOptionPane.showMessageDialog(mainPanel, "Invalid TTF font"); - } catch (IOException ex) { - Logger.getLogger(FontPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - } - - private JButton buttonCancel; - - private JButton buttonEdit; - - private JButton buttonPreviewFont; - - private JButton buttonSetAdvanceValues; - - private JButton buttonSave; - - private JTextField fontAddCharactersField; - - private JButton fontAddCharsButton; - - private JLabel fontAscentLabel; - - private JScrollPane fontCharactersScrollPane; - - private JTextArea fontCharactersTextArea; - - private JTextArea fontCopyrightTextArea; - - private JLabel fontDescentLabel; - - private JTextArea fontNameTextArea; - - private JButton fontEmbedButton; - - private JCheckBox fontIsBoldCheckBox; - - private JCheckBox fontIsItalicCheckBox; - - private JLabel fontLeadingLabel; - - private JLabel fontNameIntagLabel; - - private JComboBox fontFamilyNameSelection; - - private JComboBox fontFaceSelection; - - private JLabel fontSourceLabel; - - private JPanel fontParamsPanel; - - private JPanel addCharsPanel; - - private JPanel contentPanel; - - private JScrollPane contentScrollPane; -} +/* + * Copyright (C) 2010-2016 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.gui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.gui.helpers.TableLayoutHelper; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +import com.jpexs.helpers.Helper; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.FontFormatException; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.BorderFactory; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JFileChooser; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; +import javax.swing.filechooser.FileFilter; +import layout.TableLayout; + +/** + * + * @author JPEXS + */ +public class FontPanel extends JPanel { + + private final MainPanel mainPanel; + + private FontTag fontTag; + + /** + * Creates new form FontPanel + * + * @param mainPanel Main panel + */ + public FontPanel(MainPanel mainPanel) { + this.mainPanel = mainPanel; + initComponents(); + } + + public FontTag getFontTag() { + return fontTag; + } + + public void clear() { + fontTag = null; + } + + public static ComboBoxModel getFamilyModel() { + Set famSet = new TreeSet<>(); + for (Font f : FontTag.installedFontsByName.values()) { + famSet.add(new FontFamily(f)); + } + return new DefaultComboBoxModel<>(new Vector<>(famSet)); + } + + public static ComboBoxModel getFaceModel(FontFamily family) { + + Set faceSet = new TreeSet<>(); + for (Font f : FontTag.installedFontsByFamily.get(family.familyEn).values()) { + faceSet.add(new FontFace(f)); + } + + return new DefaultComboBoxModel<>(new Vector<>(faceSet)); + } + + private void setEditable(boolean editable) { + if (editable) { + buttonEdit.setVisible(false); + buttonSave.setVisible(true); + buttonCancel.setVisible(true); + if (fontTag.isBoldEditable()) { + fontIsBoldCheckBox.setEnabled(true); + } + if (fontTag.isItalicEditable()) { + fontIsItalicCheckBox.setEnabled(true); + } + } else { + buttonEdit.setVisible(true); + buttonSave.setVisible(false); + buttonCancel.setVisible(false); + fontIsBoldCheckBox.setEnabled(false); + fontIsItalicCheckBox.setEnabled(false); + } + } + + private String translate(String key) { + return mainPanel.translate(key); + } + + private void fontAddChars(FontTag ft, Set selChars, Font font) { + FontTag f = (FontTag) mainPanel.tagTree.getCurrentTreeItem(); + String oldchars = f.getCharacters(); + for (int ic : selChars) { + char c = (char) ic; + if (oldchars.indexOf((int) c) == -1) { + font = font.deriveFont(f.getFontStyle(), 1024); + if (!font.canDisplay(c)) { + String msg = translate("error.font.nocharacter").replace("%char%", "" + c); + Logger.getLogger(FontPanel.class.getName()).log(Level.SEVERE, msg); + View.showMessageDialog(null, msg, translate("error"), JOptionPane.ERROR_MESSAGE); + return; + } + } + } + + String[] yesno = new String[]{translate("button.yes"), translate("button.no"), translate("button.yes.all"), translate("button.no.all")}; + boolean yestoall = false; + boolean notoall = false; + boolean replaced = false; + for (int ic : selChars) { + char c = (char) ic; + if (oldchars.indexOf((int) c) > -1) { + int opt = -1; + if (!(yestoall || notoall)) { + opt = View.showOptionDialog(null, translate("message.font.add.exists").replace("%char%", "" + c), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, yesno, translate("button.yes")); + if (opt == 2) { + yestoall = true; + } + if (opt == 3) { + notoall = true; + } + } + + if (yestoall) { + opt = 0; // yes + } else if (notoall) { + opt = 1; // no + } + + if (opt == 1) { + continue; + } + + replaced = true; + } + + f.addCharacter(c, font); + oldchars += c; + } + + if (replaced) { + if (View.showConfirmDialog(null, translate("message.font.replace.updateTexts"), translate("message.warning"), JOptionPane.YES_NO_OPTION, JOptionPane.INFORMATION_MESSAGE) == JOptionPane.YES_OPTION) { + int fontId = ft.getFontId(); + SWF swf = ft.getSwf(); + for (Tag tag : swf.getTags()) { + if (tag instanceof TextTag) { + TextTag textTag = (TextTag) tag; + if (textTag.getFontIds().contains(fontId)) { + String text = textTag.getFormattedText(true).text; + mainPanel.saveText(textTag, text, null, null); + } + } + } + } + } + } + + private void fontRemoveChars(FontTag ft, Set selChars) { + FontTag f = (FontTag) mainPanel.tagTree.getCurrentTreeItem(); + + for (int ic : selChars) { + char c = (char) ic; + f.removeCharacter(c); + } + } + + public void showFontTag(FontTag ft) { + SWF swf = ft.getSwf(); + fontTag = ft; + fontNameIntagLabel.setText(ft.getFontNameIntag()); + fontNameTextArea.setText(ft.getFontName()); + fontCopyrightTextArea.setText(ft.getFontCopyright()); + + fontIsBoldCheckBox.setSelected(ft.isBold()); + fontIsItalicCheckBox.setSelected(ft.isItalic()); + fontDescentLabel.setText(ft.getDescent() == -1 ? translate("value.unknown") : Integer.toString(ft.getDescent())); + fontAscentLabel.setText(ft.getAscent() == -1 ? translate("value.unknown") : Integer.toString(ft.getAscent())); + fontLeadingLabel.setText(ft.getLeading() == -1 ? translate("value.unknown") : Integer.toString(ft.getLeading())); + String chars = ft.getCharacters(); + fontCharactersTextArea.setText(chars); + fontCharactersScrollPane.getVerticalScrollBar().scrollRectToVisible(new Rectangle(0, 0, 1, 1)); + setAllowSave(false); + + Font selFont = ft.getSystemFont(); + fontFamilyNameSelection.setSelectedItem(new FontFamily(selFont)); + fontFaceSelection.setSelectedItem(new FontFace(selFont)); + + setAllowSave(true); + setEditable(false); + boolean readOnly = ((Tag) ft).isReadOnly(); + if (readOnly) { + addCharsPanel.setVisible(false); + buttonEdit.setVisible(false); + } + } + + private void initComponents() { + + contentScrollPane = new JScrollPane(); + addCharsPanel = new JPanel(); + fontParamsPanel = new JPanel(); + fontNameIntagLabel = new JLabel(); + JScrollPane fontDisplayNameScrollPane = new JScrollPane(); + fontNameTextArea = new JTextArea(); + JLabel jLabel3 = new JLabel(); + JScrollPane fontCopyrightScrollPane = new JScrollPane(); + fontCopyrightTextArea = new JTextArea(); + JLabel jLabel4 = new JLabel(); + fontIsBoldCheckBox = new JCheckBox(); + JLabel jLabel5 = new JLabel(); + fontIsItalicCheckBox = new JCheckBox(); + JLabel jLabel6 = new JLabel(); + fontAscentLabel = new JLabel(); + JLabel jLabel7 = new JLabel(); + fontDescentLabel = new JLabel(); + JLabel jLabel8 = new JLabel(); + fontLeadingLabel = new JLabel(); + JLabel jLabel9 = new JLabel(); + fontCharactersScrollPane = new JScrollPane(); + fontCharactersTextArea = new JTextArea(); + JLabel fontCharsAddLabel = new JLabel(); + fontAddCharactersField = new JTextField(); + fontAddCharsButton = new JButton(); + fontRemoveCharsButton = new JButton(); + fontSourceLabel = new JLabel(); + fontFamilyNameSelection = new JComboBox<>(); + fontFaceSelection = new JComboBox<>(); + fontEmbedButton = new JButton(); + buttonEdit = new JButton(); + buttonSave = new JButton(); + buttonCancel = new JButton(); + buttonPreviewFont = new JButton(); + buttonSetAdvanceValues = new JButton(); + addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent evt) { + formComponentResized(evt); + } + }); + + contentPanel = new JPanel(); + contentScrollPane.setBorder(null); + contentScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER); + + TableLayout tlFontParamsPanel; + fontParamsPanel.setLayout(tlFontParamsPanel = new TableLayout(new double[][]{ + {TableLayout.PREFERRED, TableLayout.FILL}, + {TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.FILL,} + })); + + JLabel fontNameIntagLabLabel = new JLabel(); + fontNameIntagLabLabel.setText(AppStrings.translate("font.name.intag")); + fontParamsPanel.add(fontNameIntagLabLabel, "0,0,R"); + + fontNameIntagLabel.setText(AppStrings.translate("value.unknown")); + fontNameIntagLabel.setMinimumSize(new Dimension(100, fontNameIntagLabel.getMinimumSize().height)); + fontNameIntagLabel.setPreferredSize(new Dimension(250, fontNameIntagLabel.getPreferredSize().height)); + fontParamsPanel.add(fontNameIntagLabel, "1,0"); + + JLabel fontNameNameLabLabel = new JLabel(); + fontNameNameLabLabel.setText(AppStrings.translate("font.name")); + fontParamsPanel.add(fontNameNameLabLabel, "0,1,R"); + + fontDisplayNameScrollPane.setBorder(null); + fontDisplayNameScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + fontDisplayNameScrollPane.setHorizontalScrollBar(null); + + fontNameTextArea.setEditable(false); + fontNameTextArea.setColumns(20); + fontNameTextArea.setFont(new JLabel().getFont()); + fontNameTextArea.setLineWrap(true); + fontNameTextArea.setText(AppStrings.translate("value.unknown")); + fontNameTextArea.setWrapStyleWord(true); + fontNameTextArea.setMinimumSize(new Dimension(100, fontNameTextArea.getMinimumSize().height)); + fontNameTextArea.setOpaque(false); + fontDisplayNameScrollPane.setViewportView(fontNameTextArea); + + fontParamsPanel.add(fontDisplayNameScrollPane, "1,1"); + + jLabel3.setText(AppStrings.translate("fontName.copyright")); + fontParamsPanel.add(jLabel3, "0,2,R"); + + fontCopyrightScrollPane.setBorder(null); + fontCopyrightScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + fontCopyrightScrollPane.setHorizontalScrollBar(null); + + fontCopyrightTextArea.setEditable(false); + fontCopyrightTextArea.setColumns(20); + fontCopyrightTextArea.setFont(new JLabel().getFont()); + fontCopyrightTextArea.setLineWrap(true); + fontCopyrightTextArea.setText(AppStrings.translate("value.unknown")); + fontCopyrightTextArea.setWrapStyleWord(true); + fontCopyrightTextArea.setMinimumSize(new Dimension(100, fontCopyrightTextArea.getMinimumSize().height)); + fontCopyrightTextArea.setOpaque(false); + fontCopyrightScrollPane.setViewportView(fontCopyrightTextArea); + + fontParamsPanel.add(fontCopyrightScrollPane, "1,2"); + + jLabel4.setText(AppStrings.translate("font.isbold")); + fontParamsPanel.add(jLabel4, "0,3,R"); + + fontIsBoldCheckBox.setEnabled(false); + + fontParamsPanel.add(fontIsBoldCheckBox, "1,3"); + + jLabel5.setText(AppStrings.translate("font.isitalic")); + + fontParamsPanel.add(jLabel5, "0,4,R"); + + fontIsItalicCheckBox.setEnabled(false); + fontParamsPanel.add(fontIsItalicCheckBox, "1,4"); + + jLabel6.setText(AppStrings.translate("font.ascent")); + fontParamsPanel.add(jLabel6, "0,5,R"); + + fontAscentLabel.setText(AppStrings.translate("value.unknown")); + fontParamsPanel.add(fontAscentLabel, "1,5"); + + jLabel7.setText(AppStrings.translate("font.descent")); + fontParamsPanel.add(jLabel7, "0,6,R"); + + fontDescentLabel.setText(AppStrings.translate("value.unknown")); + fontParamsPanel.add(fontDescentLabel, "1,6"); + + jLabel8.setText(AppStrings.translate("font.leading")); + fontParamsPanel.add(jLabel8, "0,7,R"); + + fontLeadingLabel.setText(AppStrings.translate("value.unknown")); + fontParamsPanel.add(fontLeadingLabel, "1,7"); + + jLabel9.setText(AppStrings.translate("font.characters")); + fontParamsPanel.add(jLabel9, "0,8,R,T"); + + fontCharactersScrollPane.setBorder(null); + fontCharactersScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + fontCharactersScrollPane.setHorizontalScrollBar(null); + fontCharactersScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + + fontCharactersTextArea.setEditable(false); + fontCharactersTextArea.setColumns(20); + fontCharactersTextArea.setFont(new JLabel().getFont()); + fontCharactersTextArea.setLineWrap(true); + fontCharactersTextArea.setWrapStyleWord(true); + fontCharactersTextArea.setMinimumSize(new Dimension(100, fontCharactersTextArea.getMinimumSize().height)); + fontCharactersTextArea.setOpaque(false); + fontCharactersScrollPane.setViewportView(fontCharactersTextArea); + fontParamsPanel.add(fontCharactersScrollPane, "1,8"); + + fontCharsAddLabel.setText(AppStrings.translate("font.characters.add")); + + fontAddCharsButton.setText(AppStrings.translate("button.ok")); + fontAddCharsButton.addActionListener(this::fontAddCharsButtonActionPerformed); + + fontRemoveCharsButton.setText(AppStrings.translate("button.remove")); + fontRemoveCharsButton.addActionListener(this::fontRemoveCharsButtonActionPerformed); + + fontSourceLabel.setText(AppStrings.translate("font.source")); + + fontFamilyNameSelection.setPreferredSize(new Dimension(100, fontFamilyNameSelection.getMinimumSize().height)); + fontFamilyNameSelection.setModel(getFamilyModel()); + fontFamilyNameSelection.setSelectedItem(FontTag.defaultFontName); + fontFaceSelection.setModel(getFaceModel((FontFamily) fontFamilyNameSelection.getSelectedItem())); + fontFamilyNameSelection.addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent evt) { + fontFamilySelectionItemStateChanged(); + } + }); + + fontFaceSelection.addItemListener(new ItemListener() { + + @Override + public void itemStateChanged(ItemEvent evt) { + fontFaceSelectionItemStateChanged(); + } + }); + + fontEmbedButton.setText(AppStrings.translate("button.font.embed")); + fontEmbedButton.addActionListener(this::fontEmbedButtonActionPerformed); + + buttonEdit.setIcon(View.getIcon("edit16")); + buttonEdit.setText(AppStrings.translate("button.edit")); + buttonEdit.addActionListener(this::buttonEditActionPerformed); + + buttonSave.setIcon(View.getIcon("save16")); + buttonSave.setText(AppStrings.translate("button.save")); + buttonSave.addActionListener(this::buttonSaveActionPerformed); + + buttonCancel.setIcon(View.getIcon("cancel16")); + buttonCancel.setText(AppStrings.translate("button.cancel")); + buttonCancel.addActionListener(this::buttonCancelActionPerformed); + + buttonPreviewFont.setText(AppStrings.translate("button.preview")); + buttonPreviewFont.addActionListener(this::buttonPreviewFontActionPerformed); + + buttonSetAdvanceValues.setText(AppStrings.translate("button.setAdvanceValues")); + buttonSetAdvanceValues.addActionListener(this::buttonSetAdvanceValuesActionPerformed); + + TableLayout tlAddCharsPanel; + addCharsPanel.setLayout(tlAddCharsPanel = new TableLayout(new double[][]{ + {TableLayout.PREFERRED, TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED}, + {TableLayout.PREFERRED, TableLayout.PREFERRED, TableLayout.PREFERRED} + })); + addCharsPanel.setBorder(BorderFactory.createRaisedBevelBorder()); + + addCharsPanel.add(fontCharsAddLabel, "0,0,R"); + addCharsPanel.add(fontAddCharactersField, "1,0,2,0"); + addCharsPanel.add(fontAddCharsButton, "3,0"); + //addCharsPanel.add(fontRemoveCharsButton, "3,0"); + addCharsPanel.add(fontEmbedButton, "4,0"); + + addCharsPanel.add(fontSourceLabel, "0,1,R"); + addCharsPanel.add(fontFamilyNameSelection, "1,1"); + addCharsPanel.add(fontFaceSelection, "2,1"); + addCharsPanel.add(buttonPreviewFont, "3,1"); + addCharsPanel.add(buttonSetAdvanceValues, "4,1"); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + buttonsPanel.add(buttonEdit); + buttonsPanel.add(buttonSave); + buttonsPanel.add(buttonCancel); + + TableLayout tlAll; + contentPanel.setLayout(tlAll = new TableLayout(new double[][]{ + {TableLayout.FILL}, + {TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED} + })); + + contentPanel.add(fontParamsPanel, "0,0"); + contentPanel.add(buttonsPanel, "0,1"); + contentPanel.add(addCharsPanel, "0,2"); + contentScrollPane.setViewportView(contentPanel); + + setLayout(new BorderLayout()); + add(contentScrollPane, BorderLayout.CENTER); + + TableLayoutHelper.addTableSpaces(tlAddCharsPanel, 10); + TableLayoutHelper.addTableSpaces(tlFontParamsPanel, 10); + TableLayoutHelper.addTableSpaces(tlAll, 10); + + addComponentListener(new ComponentAdapter() { + + @Override + public void componentResized(ComponentEvent e) { + contentPanel.setPreferredSize(new Dimension(Math.max(getSize().width, addCharsPanel.getPreferredSize().width + 20), getSize().height)); + contentPanel.revalidate(); + } + + }); + } + + private void fontAddCharsButtonActionPerformed(ActionEvent evt) { + String newchars = fontAddCharactersField.getText(); + + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item instanceof FontTag) { + FontTag ft = (FontTag) item; + Set selChars = new TreeSet<>(); + for (int c = 0; c < newchars.length(); c++) { + selChars.add(newchars.codePointAt(c)); + } + + if (!selChars.isEmpty()) { + fontAddChars(ft, selChars, ((FontFace) fontFaceSelection.getSelectedItem()).font); + fontAddCharactersField.setText(""); + mainPanel.repaintTree(); + } + } + } + + private void fontRemoveCharsButtonActionPerformed(ActionEvent evt) { + String newchars = fontAddCharactersField.getText(); + + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item instanceof FontTag) { + FontTag ft = (FontTag) item; + Set selChars = new TreeSet<>(); + for (int c = 0; c < newchars.length(); c++) { + selChars.add(newchars.codePointAt(c)); + } + + if (!selChars.isEmpty()) { + fontRemoveChars(ft, selChars); + fontAddCharactersField.setText(""); + mainPanel.repaintTree(); + } + } + } + + private void fontEmbedButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item instanceof FontTag) { + FontTag ft = (FontTag) item; + FontEmbedDialog fed = new FontEmbedDialog((FontFace) fontFaceSelection.getSelectedItem(), fontAddCharactersField.getText()); + if (fed.showDialog() == AppDialog.OK_OPTION) { + Set selChars = fed.getSelectedChars(); + if (!selChars.isEmpty()) { + Font selFont = fed.getSelectedFont(); + fontFamilyNameSelection.setSelectedItem(new FontFamily(selFont)); + fontFaceSelection.setSelectedItem(new FontFace(selFont)); + fontAddChars(ft, selChars, selFont); + fontAddCharactersField.setText(""); + mainPanel.reload(true); + } + } + } + } + + private boolean allowSave = true; + + private synchronized void setAllowSave(boolean v) { + allowSave = v; + } + + private synchronized void savePair() { + if (!allowSave) { + return; + } + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item instanceof FontTag) { + FontTag f = (FontTag) item; + SWF swf = f.getSwf(); + String selectedName = ((FontFace) fontFaceSelection.getSelectedItem()).font.getFontName(Locale.ENGLISH); + swf.sourceFontNamesMap.put(f.getFontId(), selectedName); + Configuration.addFontPair(swf.getShortFileName(), f.getFontId(), f.getFontNameIntag(), selectedName); + } + } + + private void fontFamilySelectionItemStateChanged() { + + savePair(); + fontFaceSelection.setModel(getFaceModel((FontFamily) fontFamilyNameSelection.getSelectedItem())); + } + + private void fontFaceSelectionItemStateChanged() { + savePair(); + } + + private void buttonEditActionPerformed(ActionEvent evt) { + setEditable(true); + } + + private void buttonSaveActionPerformed(ActionEvent evt) { + if (fontTag.isBoldEditable()) { + fontTag.setBold(fontIsBoldCheckBox.isSelected()); + } + if (fontTag.isItalicEditable()) { + fontTag.setItalic(fontIsItalicCheckBox.isSelected()); + } + setEditable(false); + } + + private void buttonCancelActionPerformed(ActionEvent evt) { + showFontTag(fontTag); + setEditable(false); + } + + private void buttonPreviewFontActionPerformed(ActionEvent evt) { + new FontPreviewDialog(null, true, ((FontFace) fontFaceSelection.getSelectedItem()).font).setVisible(true); + } + + private void buttonSetAdvanceValuesActionPerformed(ActionEvent evt) { + fontTag.setAdvanceValues(((FontFace) fontFaceSelection.getSelectedItem()).font); + } + + private void formComponentResized(ComponentEvent evt) { + fontParamsPanel.updateUI(); + } + + private void importTTFButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item instanceof FontTag) { + FontTag ft = (FontTag) item; + + JFileChooser fc = new JFileChooser(); + fc.setCurrentDirectory(new File(Configuration.lastOpenDir.get())); + FileFilter ttfFilter = new FileFilter() { + @Override + public boolean accept(File f) { + return (f.getName().toLowerCase().endsWith(".ttf")) || (f.isDirectory()); + } + + @Override + public String getDescription() { + return "TTF files"; + } + }; + fc.setFileFilter(ttfFilter); + + fc.setAcceptAllFileFilterUsed(false); + JFrame fr = new JFrame(); + View.setWindowIcon(fr); + int returnVal = fc.showOpenDialog(fr); + if (returnVal == JFileChooser.APPROVE_OPTION) { + Configuration.lastOpenDir.set(Helper.fixDialogFile(fc.getSelectedFile()).getParentFile().getAbsolutePath()); + File selfile = Helper.fixDialogFile(fc.getSelectedFile()); + Set selChars = new HashSet<>(); + try { + Font f = Font.createFont(Font.TRUETYPE_FONT, selfile); + int required[] = new int[]{0x0001, 0x0000, 0x000D, 0x0020}; + loopi: + for (char i = 0; i < Character.MAX_VALUE; i++) { + for (int r : required) { + if (r == i) { + continue loopi; + } + } + if (f.canDisplay((int) i)) { + selChars.add((int) i); + } + } + fontAddChars(ft, selChars, f); + mainPanel.reload(true); + } catch (FontFormatException ex) { + JOptionPane.showMessageDialog(mainPanel, "Invalid TTF font"); + } catch (IOException ex) { + Logger.getLogger(FontPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + } + + private JButton buttonCancel; + + private JButton buttonEdit; + + private JButton buttonPreviewFont; + + private JButton buttonSetAdvanceValues; + + private JButton buttonSave; + + private JTextField fontAddCharactersField; + + private JButton fontAddCharsButton; + + private JButton fontRemoveCharsButton; + + private JLabel fontAscentLabel; + + private JScrollPane fontCharactersScrollPane; + + private JTextArea fontCharactersTextArea; + + private JTextArea fontCopyrightTextArea; + + private JLabel fontDescentLabel; + + private JTextArea fontNameTextArea; + + private JButton fontEmbedButton; + + private JCheckBox fontIsBoldCheckBox; + + private JCheckBox fontIsItalicCheckBox; + + private JLabel fontLeadingLabel; + + private JLabel fontNameIntagLabel; + + private JComboBox fontFamilyNameSelection; + + private JComboBox fontFaceSelection; + + private JLabel fontSourceLabel; + + private JPanel fontParamsPanel; + + private JPanel addCharsPanel; + + private JPanel contentPanel; + + private JScrollPane contentScrollPane; +} diff --git a/src/com/jpexs/decompiler/flash/gui/MainPanel.java b/src/com/jpexs/decompiler/flash/gui/MainPanel.java index 53e043e7c..236131421 100644 --- a/src/com/jpexs/decompiler/flash/gui/MainPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/MainPanel.java @@ -212,6 +212,7 @@ import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; +import javax.swing.ScrollPaneConstants; import javax.swing.SwingConstants; import javax.swing.UIManager; import javax.swing.event.DocumentEvent; diff --git a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java index 6bc02d348..2c9fc24ba 100644 --- a/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java +++ b/src/com/jpexs/decompiler/flash/gui/PreviewPanel.java @@ -1,1261 +1,1266 @@ -/* - * Copyright (C) 2010-2016 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.gui; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.parser.ActionParseException; -import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; -import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; -import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; -import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; -import com.jpexs.decompiler.flash.gui.player.MediaDisplay; -import com.jpexs.decompiler.flash.gui.player.PlayerControls; -import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; -import com.jpexs.decompiler.flash.tags.DefineBitsTag; -import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; -import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag; -import com.jpexs.decompiler.flash.tags.DefineSoundTag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.DefineTextTag; -import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.DoInitActionTag; -import com.jpexs.decompiler.flash.tags.EndTag; -import com.jpexs.decompiler.flash.tags.ExportAssetsTag; -import com.jpexs.decompiler.flash.tags.FileAttributesTag; -import com.jpexs.decompiler.flash.tags.JPEGTablesTag; -import com.jpexs.decompiler.flash.tags.MetadataTag; -import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; -import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; -import com.jpexs.decompiler.flash.tags.ShowFrameTag; -import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.VideoFrameTag; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.FontTag; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.tags.base.RemoveTag; -import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.timeline.Frame; -import com.jpexs.decompiler.flash.timeline.TagScript; -import com.jpexs.decompiler.flash.timeline.Timelined; -import com.jpexs.decompiler.flash.treeitems.TreeItem; -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.SHAPE; -import com.jpexs.decompiler.flash.types.TEXTRECORD; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.helpers.SerializableImage; -import java.awt.BorderLayout; -import java.awt.CardLayout; -import java.awt.Color; -import java.awt.Component; -import java.awt.Font; -import java.awt.Insets; -import java.awt.event.ActionEvent; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.StringReader; -import java.io.StringWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.JSplitPane; -import javax.swing.SwingConstants; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Source; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; - -/** - * - * @author JPEXS - */ -public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel { - - private static final String FLASH_VIEWER_CARD = "FLASHVIEWER"; - - private static final String DRAW_PREVIEW_CARD = "DRAWPREVIEW"; - - private static final String GENERIC_TAG_CARD = "GENERICTAG"; - - private static final String BINARY_TAG_CARD = "BINARYTAG"; - - private static final String METADATA_TAG_CARD = "METADATATAG"; - - private static final String EMPTY_CARD = "EMPTY"; - - private static final String CARDTEXTPANEL = "Text card"; - - private static final String CARDFONTPANEL = "Font card"; - - private final MainPanel mainPanel; - - private final JPanel viewerCards; - - private final FlashPlayerPanel flashPanel; - - private File tempFile; - - private ImagePanel imagePanel; - - private PlayerControls imagePlayControls; - - private MediaDisplay media; - - private BinaryPanel binaryPanel; - - private LineMarkedEditorPane metadataEditor; - - private GenericTagPanel genericTagPanel; - - private JPanel displayWithPreview; - - // Image tag buttons - private JButton replaceImageButton; - - private JButton replaceImageAlphaButton; - - private JButton prevFontsButton; - - private JButton nextFontsButton; - - // Binary tag buttons - private JButton replaceBinaryButton; - - // Metadata editor buttons - private JButton metadataEditButton; - - private JButton metadataSaveButton; - - private JButton metadataCancelButton; - - // Generic tag buttons - private JButton genericEditButton; - - private JButton genericSaveButton; - - private JButton genericCancelButton; - - private JPanel parametersPanel; - - private FontPanel fontPanel; - - private int fontPageNum; - - private TextPanel textPanel; - - private MetadataTag metadataTag; - - private boolean readOnly = false; - - public void setReadOnly(boolean readOnly) { - this.readOnly = readOnly; - if (readOnly) { - parametersPanel.setVisible(false); - } - } - - public PreviewPanel(MainPanel mainPanel, FlashPlayerPanel flashPanel) { - super(JSplitPane.HORIZONTAL_SPLIT, Configuration.guiPreviewSplitPaneDividerLocationPercent); - this.mainPanel = mainPanel; - this.flashPanel = flashPanel; - - viewerCards = new JPanel(); - viewerCards.setLayout(new CardLayout()); - - viewerCards.add(createFlashPlayerPanel(flashPanel), FLASH_VIEWER_CARD); - viewerCards.add(createImagesCard(), DRAW_PREVIEW_CARD); - viewerCards.add(createBinaryCard(), BINARY_TAG_CARD); - viewerCards.add(createMetadataCard(), METADATA_TAG_CARD); - viewerCards.add(createGenericTagCard(), GENERIC_TAG_CARD); - viewerCards.add(createEmptyCard(), EMPTY_CARD); - setLeftComponent(viewerCards); - - createParametersPanel(); - - showCardLeft(FLASH_VIEWER_CARD); - } - - private JPanel createEmptyCard() { - JPanel ret = new JPanel(); - ret.add(new JLabel("-")); - return ret; - } - - private void createParametersPanel() { - displayWithPreview = new JPanel(new CardLayout()); - - textPanel = new TextPanel(mainPanel); - displayWithPreview.add(textPanel, CARDTEXTPANEL); - - fontPanel = new FontPanel(mainPanel); - displayWithPreview.add(fontPanel, CARDFONTPANEL); - - JLabel paramsLabel = new HeaderLabel(mainPanel.translate("parameters")); - paramsLabel.setHorizontalAlignment(SwingConstants.CENTER); - //paramsLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - - parametersPanel = new JPanel(new BorderLayout()); - parametersPanel.add(paramsLabel, BorderLayout.NORTH); - parametersPanel.add(displayWithPreview, BorderLayout.CENTER); - setRightComponent(parametersPanel); - } - - private JPanel createImageButtonsPanel() { - replaceImageButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("replaceimage16")); - replaceImageButton.setMargin(new Insets(3, 3, 3, 10)); - replaceImageButton.addActionListener(mainPanel::replaceButtonActionPerformed); - replaceImageButton.setVisible(false); - - replaceImageAlphaButton = new JButton(mainPanel.translate("button.replaceAlphaChannel"), View.getIcon("replacealpha16")); - replaceImageAlphaButton.setMargin(new Insets(3, 3, 3, 10)); - replaceImageAlphaButton.addActionListener(mainPanel::replaceAlphaButtonActionPerformed); - replaceImageAlphaButton.setVisible(false); - - prevFontsButton = new JButton(mainPanel.translate("button.prev"), View.getIcon("prev16")); - prevFontsButton.setMargin(new Insets(3, 3, 3, 10)); - prevFontsButton.addActionListener(this::prevFontsButtonActionPerformed); - prevFontsButton.setVisible(false); - - nextFontsButton = new JButton(mainPanel.translate("button.next"), View.getIcon("next16")); - nextFontsButton.setMargin(new Insets(3, 3, 3, 10)); - nextFontsButton.addActionListener(this::nextFontsButtonActionPerformed); - nextFontsButton.setVisible(false); - - ButtonsPanel imageButtonsPanel = new ButtonsPanel(); - imageButtonsPanel.add(replaceImageButton); - imageButtonsPanel.add(replaceImageAlphaButton); - imageButtonsPanel.add(prevFontsButton); - imageButtonsPanel.add(nextFontsButton); - return imageButtonsPanel; - } - - private JPanel createBinaryButtonsPanel() { - replaceBinaryButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("edit16")); - replaceBinaryButton.setMargin(new Insets(3, 3, 3, 10)); - replaceBinaryButton.addActionListener(mainPanel::replaceButtonActionPerformed); - - ButtonsPanel binaryButtonsPanel = new ButtonsPanel(); - binaryButtonsPanel.add(replaceBinaryButton); - return binaryButtonsPanel; - } - - private JPanel createGenericTagButtonsPanel() { - genericEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); - genericEditButton.setMargin(new Insets(3, 3, 3, 10)); - genericEditButton.addActionListener(this::editGenericTagButtonActionPerformed); - genericSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); - genericSaveButton.setMargin(new Insets(3, 3, 3, 10)); - genericSaveButton.addActionListener(this::saveGenericTagButtonActionPerformed); - genericSaveButton.setVisible(false); - genericCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); - genericCancelButton.setMargin(new Insets(3, 3, 3, 10)); - genericCancelButton.addActionListener(this::cancelGenericTagButtonActionPerformed); - genericCancelButton.setVisible(false); - - ButtonsPanel genericTagButtonsPanel = new ButtonsPanel(); - genericTagButtonsPanel.add(genericEditButton); - genericTagButtonsPanel.add(genericSaveButton); - genericTagButtonsPanel.add(genericCancelButton); - return genericTagButtonsPanel; - } - - private JPanel createMetadataButtonsPanel() { - metadataEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); - metadataEditButton.setMargin(new Insets(3, 3, 3, 10)); - metadataEditButton.addActionListener(this::editMetadataButtonActionPerformed); - metadataSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); - metadataSaveButton.setMargin(new Insets(3, 3, 3, 10)); - metadataSaveButton.addActionListener(this::saveMetadataButtonActionPerformed); - metadataSaveButton.setVisible(false); - metadataCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); - metadataCancelButton.setMargin(new Insets(3, 3, 3, 10)); - metadataCancelButton.addActionListener(this::cancelMetadataButtonActionPerformed); - metadataCancelButton.setVisible(false); - - ButtonsPanel metadataTagButtonsPanel = new ButtonsPanel(); - metadataTagButtonsPanel.add(metadataEditButton); - metadataTagButtonsPanel.add(metadataSaveButton); - metadataTagButtonsPanel.add(metadataCancelButton); - return metadataTagButtonsPanel; - } - - private JPanel createFlashPlayerPanel(FlashPlayerPanel flashPanel) { - JPanel pan = new JPanel(new BorderLayout()); - JLabel prevLabel = new HeaderLabel(mainPanel.translate("swfpreview")); - prevLabel.setHorizontalAlignment(SwingConstants.CENTER); - //prevLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - - pan.add(prevLabel, BorderLayout.NORTH); - - Component leftComponent; - if (flashPanel != null) { - JPanel flashPlayPanel = new JPanel(new BorderLayout()); - flashPlayPanel.add(flashPanel, BorderLayout.CENTER); - - /*JPanel bottomPanel = new JPanel(new BorderLayout()); - JPanel buttonsPanel = new JPanel(new FlowLayout()); - JButton selectColorButton = new JButton(View.getIcon("color16")); - selectColorButton.addActionListener(mainPanel::selectBkColor); - selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint")); - buttonsPanel.add(selectColorButton); - bottomPanel.add(buttonsPanel, BorderLayout.EAST); - - flashPlayPanel.add(bottomPanel, BorderLayout.SOUTH);*/ - JPanel flashPlayPanel2 = new JPanel(new BorderLayout()); - flashPlayPanel2.add(flashPlayPanel, BorderLayout.CENTER); - flashPlayPanel2.add(new PlayerControls(mainPanel, flashPanel), BorderLayout.SOUTH); - leftComponent = flashPlayPanel2; - } else { - JPanel swtPanel = new JPanel(new BorderLayout()); - swtPanel.add(new JLabel("
" + mainPanel.translate("notavailonthisplatform") + "
", JLabel.CENTER), BorderLayout.CENTER); - swtPanel.setBackground(View.getDefaultBackgroundColor()); - leftComponent = swtPanel; - } - - pan.add(leftComponent, BorderLayout.CENTER); - return pan; - } - - private JPanel createImagesCard() { - JPanel shapesCard = new JPanel(new BorderLayout()); - JPanel previewPanel = new JPanel(new BorderLayout()); - - JPanel previewCnt = new JPanel(new BorderLayout()); - imagePanel = new ImagePanel(); - imagePanel.setLoop(Configuration.loopMedia.get()); - previewCnt.add(imagePanel, BorderLayout.CENTER); - previewCnt.add(imagePlayControls = new PlayerControls(mainPanel, imagePanel), BorderLayout.SOUTH); - imagePlayControls.setMedia(imagePanel); - previewPanel.add(previewCnt, BorderLayout.CENTER); - JLabel prevIntLabel = new HeaderLabel(mainPanel.translate("swfpreview.internal")); - prevIntLabel.setHorizontalAlignment(SwingConstants.CENTER); - //prevIntLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); - previewPanel.add(prevIntLabel, BorderLayout.NORTH); - - shapesCard.add(previewPanel, BorderLayout.CENTER); - - shapesCard.add(createImageButtonsPanel(), BorderLayout.SOUTH); - return shapesCard; - } - - private JPanel createMetadataCard() { - JPanel metadataCard = new JPanel(new BorderLayout()); - metadataEditor = new LineMarkedEditorPane(); - metadataCard.add(new JScrollPane(metadataEditor), BorderLayout.CENTER); - //metadataEditor.setContentType("text/xml"); - metadataEditor.setEditable(false); - - metadataEditor.setFont(new Font("Monospaced", Font.PLAIN, metadataEditor.getFont().getSize())); - metadataEditor.changeContentType("text/xml"); - metadataEditor.addTextChangedListener(this::metadataTextChanged); - - metadataCard.add(createMetadataButtonsPanel(), BorderLayout.SOUTH); - return metadataCard; - } - - private boolean isMetadataModified() { - return metadataSaveButton.isVisible() && metadataSaveButton.isEnabled(); - } - - private void setMetadataModified(boolean value) { - metadataSaveButton.setEnabled(value); - metadataCancelButton.setEnabled(value); - } - - private void metadataTextChanged() { - setMetadataModified(true); - } - - private void updateMetadataButtonsVisibility() { - boolean edit = metadataEditor.isEditable(); - boolean editorMode = Configuration.editorMode.get(); - metadataEditButton.setVisible(!readOnly && !edit); - metadataSaveButton.setVisible(!readOnly && edit); - boolean metadataModified = isMetadataModified(); - metadataCancelButton.setVisible(!readOnly && edit); - metadataCancelButton.setEnabled(metadataModified || !editorMode); - } - - private JPanel createBinaryCard() { - JPanel binaryCard = new JPanel(new BorderLayout()); - binaryPanel = new BinaryPanel(mainPanel); - binaryCard.add(binaryPanel, BorderLayout.CENTER); - binaryCard.add(createBinaryButtonsPanel(), BorderLayout.SOUTH); - return binaryCard; - } - - private JPanel createGenericTagCard() { - JPanel genericTagCard = new JPanel(new BorderLayout()); - genericTagPanel = new GenericTagTreePanel(mainPanel); - genericTagCard.add(genericTagPanel, BorderLayout.CENTER); - genericTagCard.add(createGenericTagButtonsPanel(), BorderLayout.SOUTH); - return genericTagCard; - } - - private void showCardLeft(String card) { - CardLayout cl = (CardLayout) (viewerCards.getLayout()); - cl.show(viewerCards, card); - } - - private void showCardRight(String card) { - CardLayout cl = (CardLayout) (displayWithPreview.getLayout()); - cl.show(displayWithPreview, card); - } - - public TextPanel getTextPanel() { - return textPanel; - } - - public void setParametersPanelVisible(boolean show) { - parametersPanel.setVisible(show); - } - - public void showFlashViewerPanel() { - parametersPanel.setVisible(false); - showCardLeft(FLASH_VIEWER_CARD); - } - - public void showImagePanel(Timelined timelined, SWF swf, int frame) { - showCardLeft(DRAW_PREVIEW_CARD); - parametersPanel.setVisible(false); - imagePlayControls.setMedia(imagePanel); - imagePanel.setTimelined(timelined, swf, frame); - } - - public void showImagePanel(SerializableImage image) { - showCardLeft(DRAW_PREVIEW_CARD); - parametersPanel.setVisible(false); - imagePlayControls.setMedia(imagePanel); - imagePanel.setImage(image); - } - - public void showTextComparePanel(TextTag textTag, TextTag newTextTag) { - imagePanel.setText(textTag, newTextTag); - } - - public void setMedia(MediaDisplay media) { - this.media = media; - imagePlayControls.setMedia(media); - } - - public void showFontPanel(FontTag fontTag) { - fontPageNum = 0; - showFontPage(fontTag); - - showCardRight(CARDFONTPANEL); - if (!readOnly) { - parametersPanel.setVisible(true); - } - fontPanel.showFontTag(fontTag); - - int pageCount = getFontPageCount(fontTag); - if (pageCount > 1) { - prevFontsButton.setVisible(true); - nextFontsButton.setVisible(true); - } - } - - private void showFontPage(FontTag fontTag) { - if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { - showImagePanel(MainPanel.makeTimelined(fontTag), fontTag.getSwf(), fontPageNum); - } - } - - public static int getFontPageCount(FontTag fontTag) { - int pageCount = (fontTag.getGlyphShapeTable().size() - 1) / SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW + 1; - if (pageCount < 1) { - pageCount = 1; - } - return pageCount; - } - - public void showEmpty() { - showCardLeft(EMPTY_CARD); - } - - public void showTextPanel(TextTag textTag) { - if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { - showImagePanel(MainPanel.makeTimelined(textTag), textTag.getSwf(), 0); - } - - showCardRight(CARDTEXTPANEL); - if (!readOnly) { - parametersPanel.setVisible(true); - } - textPanel.setText(textTag); - } - - public void focusTextPanel() { - textPanel.focusTextValue(); - } - - public void clear() { - imagePanel.clearAll(); - if (media != null) { - try { - media.close(); - } catch (IOException ex) { - // ignore - } - } - - binaryPanel.setBinaryData(null); - genericTagPanel.clear(); - fontPanel.clear(); - } - - public void closeTag() { - textPanel.closeTag(); - } - - public static String formatMetadata(String input, int indent) { - input = input.replace("> <", "><"); - try { - Source xmlInput = new StreamSource(new StringReader(input)); - StringWriter stringWriter = new StringWriter(); - StreamResult xmlOutput = new StreamResult(stringWriter); - StringWriter sw = new StringWriter(); - xmlOutput.setWriter(sw); - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - transformerFactory.setAttribute("indent-number", indent); - Transformer transformer = transformerFactory.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent); - transformer.transform(xmlInput, xmlOutput); - - return xmlOutput.getWriter().toString(); - } catch (IllegalArgumentException | TransformerException e) { - return input; - } - } - - public void showMetaDataPanel(MetadataTag metadataTag) { - showCardLeft(METADATA_TAG_CARD); - this.metadataTag = metadataTag; - metadataEditor.setEditable(!readOnly && !metadataTag.isReadOnly() && Configuration.editorMode.get()); - metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4)); - setMetadataModified(false); - updateMetadataButtonsVisibility(); - parametersPanel.setVisible(false); - } - - public void showBinaryPanel(DefineBinaryDataTag binaryDataTag) { - showCardLeft(BINARY_TAG_CARD); - binaryPanel.setBinaryData(binaryDataTag); - parametersPanel.setVisible(false); - } - - public void showGenericTagPanel(Tag tag) { - showCardLeft(GENERIC_TAG_CARD); - genericEditButton.setVisible(!tag.isReadOnly()); - genericSaveButton.setVisible(false); - genericCancelButton.setVisible(false); - genericTagPanel.setEditMode(false, tag); - parametersPanel.setVisible(false); - } - - public void setImageReplaceButtonVisible(boolean show, boolean showAlpha) { - if (readOnly) { - show = false; - showAlpha = false; - } - replaceImageButton.setVisible(show); - replaceImageAlphaButton.setVisible(showAlpha); - prevFontsButton.setVisible(false); - nextFontsButton.setVisible(false); - } - - private static Tag classicTag(Tag t) { - if (t instanceof DefineCompactedFont) { - return ((DefineCompactedFont) t).toClassicFont(); - } - return t; - } - - private static void writeTag(Tag t, SWFOutputStream sos) throws IOException { - t = classicTag(t); - - t.writeTag(sos); - if (t instanceof CharacterIdTag) { - List chIdTags = t.getSwf().getCharacterIdTags(((CharacterIdTag) t).getCharacterId()); - if (chIdTags != null) { - for (CharacterIdTag chIdTag : chIdTags) { - if (!(chIdTag instanceof PlaceObjectTypeTag || chIdTag instanceof RemoveTag)) { - ((Tag) chIdTag).writeTag(sos); - } - } - } - } - } - - public void createAndShowTempSwf(TreeItem treeItem) { - SWF swf = null; - try { - if (tempFile != null) { - tempFile.delete(); - } - - tempFile = File.createTempFile("ffdec_view_", ".swf"); - tempFile.deleteOnExit(); - - Color backgroundColor = View.getSwfBackgroundColor(); - - if (treeItem instanceof Tag) { - Tag tag = (Tag) treeItem; - swf = tag.getSwf(); - if (tag instanceof FontTag) { //Fonts are always black on white - backgroundColor = View.getDefaultBackgroundColor(); - } - } else if (treeItem instanceof Frame) { - Frame fn = (Frame) treeItem; - swf = fn.getSwf(); - if (fn.timeline.timelined == swf) { - SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); - if (setBgColorTag != null) { - backgroundColor = setBgColorTag.backgroundColor.toColor(); - } - } - } - - int frameCount = 1; - float frameRate = swf.frameRate; - HashMap videoFrames = new HashMap<>(); - if (treeItem instanceof DefineVideoStreamTag) { - DefineVideoStreamTag vs = (DefineVideoStreamTag) treeItem; - SWF.populateVideoFrames(vs.getCharacterId(), swf.getTags(), videoFrames); - frameCount = videoFrames.size(); - } - - List soundFrames = new ArrayList<>(); - if (treeItem instanceof SoundStreamHeadTypeTag) { - soundFrames = ((SoundStreamHeadTypeTag) treeItem).getBlocks(); - frameCount = soundFrames.size(); - } - - if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) { - frameRate = MainPanel.MORPH_SHAPE_ANIMATION_FRAME_RATE; - frameCount = (int) (MainPanel.MORPH_SHAPE_ANIMATION_LENGTH * frameRate); - } - - if (treeItem instanceof DefineSoundTag) { - frameCount = 1; - } - - if (treeItem instanceof DefineSpriteTag) { - frameCount = ((DefineSpriteTag) treeItem).frameCount; - } - - byte[] data; - try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - SWFOutputStream sos2 = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); - RECT outrect = new RECT(swf.displayRect); - - RECT treeItemBounds = null; - if (treeItem instanceof FontTag) { - outrect.Xmin = 0; - outrect.Ymin = 0; - outrect.Xmax = FontTag.PREVIEWSIZE * 20; - outrect.Ymax = FontTag.PREVIEWSIZE * 20; - } else if (treeItem instanceof BoundedTag) { - treeItemBounds = ((BoundedTag) treeItem).getRect(); - } else if (treeItem instanceof Frame) { - treeItemBounds = ((Frame) treeItem).timeline.timelined.getRect(); - } - - if (treeItemBounds != null) { - if (outrect.getWidth() < treeItemBounds.getWidth()) { - outrect.Xmax += treeItemBounds.getWidth() - outrect.getWidth(); - } - - if (outrect.getHeight() < treeItemBounds.getHeight()) { - outrect.Ymax += treeItemBounds.getHeight() - outrect.getHeight(); - } - } - - int width = outrect.getWidth(); - int height = outrect.getHeight(); - - sos2.writeRECT(outrect); - sos2.writeFIXED8(frameRate); - sos2.writeUI16(frameCount); //framecnt - - FileAttributesTag fa = swf.getFileAttributes(); - if (fa != null) { - fa.writeTag(sos2); - } - - SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); - if (setBgColorTag == null) { - setBgColorTag = new SetBackgroundColorTag(swf, new RGB(backgroundColor)); - } - - setBgColorTag.writeTag(sos2); - - if (treeItem instanceof Frame) { - Frame fn = (Frame) treeItem; - Timelined parent = fn.timeline.timelined; - List doneCharacters = new ArrayList<>(); - for (Tag t : parent.getTags()) { - if (t instanceof FileAttributesTag || t instanceof SetBackgroundColorTag) { - continue; - } - - if (t instanceof DoActionTag || t instanceof DoInitActionTag) { - // todo: Maybe DoABC tags should be removed, too - continue; - } - - Set needed = new HashSet<>(); - t.getNeededCharactersDeep(needed); - for (int n : needed) { - if (!doneCharacters.contains(n)) { - writeTag(swf.getCharacter(n), sos2); - doneCharacters.add(n); - } - } - - if (t instanceof ShowFrameTag || t instanceof PlaceObjectTypeTag || t instanceof RemoveTag) { - continue; - } - - if (t instanceof CharacterTag) { - int characterId = ((CharacterTag) t).getCharacterId(); - if (!doneCharacters.contains(characterId)) { - doneCharacters.add(((CharacterTag) t).getCharacterId()); - } - } - - writeTag(t, sos2); - } - - RECT r = parent.getRect(); - for (Map.Entry value : fn.layers.entrySet()) { - PlaceObjectTypeTag pot = value.getValue().toPlaceObjectTag(value.getKey()); - MATRIX mat = new MATRIX(pot.getMatrix()); - mat.translateX += width / 2 - r.getWidth() / 2; - mat.translateY += height / 2 - r.getHeight() / 2; - pot.setMatrix(mat); - pot.writeTag(sos2); - } - - new ShowFrameTag(swf).writeTag(sos2); - } else { - boolean isSprite = false; - if (treeItem instanceof DefineSpriteTag) { - isSprite = true; - } - int chtId = 0; - if (treeItem instanceof CharacterTag) { - chtId = ((CharacterTag) treeItem).getCharacterId(); - } - - if (treeItem instanceof DefineBitsTag) { - JPEGTablesTag jtt = swf.getJtt(); - if (jtt != null) { - jtt.writeTag(sos2); - } - } else if (treeItem instanceof AloneTag) { - } else { - Set needed = new HashSet<>(); - ((Tag) treeItem).getNeededCharactersDeep(needed); - for (int n : needed) { - if (isSprite && chtId == n) { - continue; - } - - CharacterTag characterTag = swf.getCharacter(n); - if (characterTag instanceof DefineBitsTag) { - JPEGTablesTag jtt = swf.getJtt(); - if (jtt != null) { - jtt.writeTag(sos2); - } - } - - writeTag(characterTag, sos2); - } - } - - writeTag((Tag) treeItem, sos2); - - MATRIX mat = new MATRIX(); - mat.hasRotate = false; - mat.hasScale = false; - mat.translateX = 0; - mat.translateY = 0; - if (treeItem instanceof BoundedTag) { - RECT r = ((BoundedTag) treeItem).getRect(); - mat.translateX = -r.Xmin; - mat.translateY = -r.Ymin; - mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2; - mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2; - } else { - mat.translateX = width / 4; - mat.translateY = height / 4; - } - if (treeItem instanceof FontTag) { - FontTag ft = (FontTag) classicTag((Tag) treeItem); - - int countGlyphsTotal = ft.getGlyphShapeTable().size(); - int countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal); - int fontId = ft.getFontId(); - int cols = (int) Math.ceil(Math.sqrt(countGlyphs)); - int rows = (int) Math.ceil(((float) countGlyphs) / ((float) cols)); - if (rows == 0) { - rows = 1; - cols = 1; - } - int x = 0; - int y = 0; - int firstGlyphIndex = fontPageNum * SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW; - countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal - firstGlyphIndex); - List shapes = ft.getGlyphShapeTable(); - int maxw = 0; - for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) { - RECT b = shapes.get(f).getBounds(); - if (b.Xmin == Integer.MAX_VALUE) { - continue; - } - if (b.Ymin == Integer.MAX_VALUE) { - continue; - } - int w = (int) (b.getWidth() / ft.getDivider()); - if (w > maxw) { - maxw = w; - } - x++; - } - - x = 0; - - int BORDER = 3 * 20; - - int textHeight = height / rows; - - while (maxw * textHeight / 1024.0 > width / cols - 2 * BORDER) { - textHeight--; - } - - MATRIX tmat = new MATRIX(); - for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) { - if (x >= cols) { - x = 0; - y++; - } - List rec = new ArrayList<>(); - TEXTRECORD tr = new TEXTRECORD(); - - RECT b = shapes.get(f).getBounds(); - 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()); - ymin *= textHeight / 1024.0; - int w = (int) (b.getWidth() / ft.getDivider()); - w *= textHeight / 1024.0; - int h = (int) (b.getHeight() / ft.getDivider()); - h *= textHeight / 1024.0; - - tr.fontId = fontId; - tr.styleFlagsHasFont = true; - tr.textHeight = textHeight; - tr.xOffset = -xmin; - tr.yOffset = 0; - tr.styleFlagsHasXOffset = true; - tr.styleFlagsHasYOffset = true; - tr.glyphEntries = new ArrayList<>(1); - tr.styleFlagsHasColor = true; - tr.textColor = new RGB(0, 0, 0); - GLYPHENTRY ge = new GLYPHENTRY(); - - double ga = ft.getGlyphAdvance(f); - int cw = ga == -1 ? w : (int) (ga / ft.getDivider() * textHeight / 1024.0); - - ge.glyphAdvance = 0; - ge.glyphIndex = f; - tr.glyphEntries.add(ge); - rec.add(tr); - - tmat.translateX = x * width / cols + width / cols / 2 - w / 2; - tmat.translateY = y * height / rows + height / rows / 2; - new DefineTextTag(swf, 999 + f, new RECT(0, cw, ymin, ymin + h), new MATRIX(), rec).writeTag(sos2); - new PlaceObject2Tag(swf, false, 1 + f, 999 + f, tmat, null, 0, null, -1, null).writeTag(sos2); - x++; - } - new ShowFrameTag(swf).writeTag(sos2); - } else if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) { - new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - for (int ratio = 0; ratio < 65536; ratio += 65536 / frameCount) { - new PlaceObject2Tag(swf, true, 1, chtId, mat, null, ratio, null, -1, null).writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - } - } else if (treeItem instanceof SoundStreamHeadTypeTag) { - for (SoundStreamBlockTag blk : soundFrames) { - blk.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - } - } else if (treeItem instanceof DefineSoundTag) { - ExportAssetsTag ea = new ExportAssetsTag(swf); - DefineSoundTag ds = (DefineSoundTag) treeItem; - ea.tags.add(ds.soundId); - ea.names.add("my_define_sound"); - ea.writeTag(sos2); - List actions; - DoActionTag doa; - - doa = new DoActionTag(swf, null); - actions = ASMParser.parse(0, false, - "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\"\n" - + "Push \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\" 0.0 \"Sound\"\n" - + "NewObject\n" - + "SetMember\n" - + "Push \"my_define_sound\" 1 \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"attachSound\"\n" - + "CallMethod\n" - + "Pop\n" - + "Stop", swf.version, false); - doa.setActions(actions); - doa.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - - actions = ASMParser.parse(0, false, - "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"start\"\n" - + "StopSounds\n" - + "Push \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\" 0.0 \"Sound\"\n" - + "NewObject\n" - + "SetMember\n" - + "Push \"my_define_sound\" 1 \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"attachSound\"\n" - + "CallMethod\n" - + "Pop\n" - + "Push 9999 0.0 2 \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"start\"\n" - + "CallMethod\n" - + "Pop\n" - + "Stop", swf.version, false); - doa.setActions(actions); - doa.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - - actions = ASMParser.parse(0, false, - "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"onSoundComplete\" \"start\" \"execParam\"\n" - + "StopSounds\n" - + "Push \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\" 0.0 \"Sound\"\n" - + "NewObject\n" - + "SetMember\n" - + "Push \"my_define_sound\" 1 \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"attachSound\"\n" - + "CallMethod\n" - + "Pop\n" - + "Push \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"onSoundComplete\"\n" - + "DefineFunction2 \"\" 0 2 false true true false true false true false false {\n" - + "Push 0.0 register1 \"my_sound\"\n" - + "GetMember\n" - + "Push \"start\"\n" - + "CallMethod\n" - + "Pop\n" - + "}\n" - + "SetMember\n" - + "Push \"_root\"\n" - + "GetVariable\n" - + "Push \"execParam\"\n" - + "GetMember\n" - + "Push 1 \"_root\"\n" - + "GetVariable\n" - + "Push \"my_sound\"\n" - + "GetMember\n" - + "Push \"start\"\n" - + "CallMethod\n" - + "Pop\n" - + "Stop", swf.version, false); - doa.setActions(actions); - doa.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - - actions = ASMParser.parse(0, false, - "StopSounds\n" - + "Stop", swf.version, false); - doa.setActions(actions); - doa.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - - new ShowFrameTag(swf).writeTag(sos2); - } else if (treeItem instanceof DefineVideoStreamTag) { - - new PlaceObject2Tag(swf, false, 1, chtId, mat, null, -1, null, -1, null).writeTag(sos2); - List frs = new ArrayList<>(videoFrames.values()); - Collections.sort(frs, new Comparator() { - @Override - public int compare(VideoFrameTag o1, VideoFrameTag o2) { - return o1.frameNum - o2.frameNum; - } - }); - boolean first = true; - int ratio = 0; - for (VideoFrameTag f : frs) { - if (!first) { - ratio++; - new PlaceObject2Tag(swf, true, 1, 0, null, null, ratio, null, -1, null).writeTag(sos2); - } - f.writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - first = false; - } - } else if (treeItem instanceof DefineSpriteTag) { - DefineSpriteTag s = (DefineSpriteTag) treeItem; - Tag lastTag = null; - for (Tag t : s.getTags()) { - if (t instanceof EndTag) { - break; - } else if (t instanceof PlaceObjectTypeTag) { - PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; - MATRIX m = pt.getMatrix(); - MATRIX m2 = new Matrix(m).preConcatenate(new Matrix(mat)).toMATRIX(); - pt.writeTagWithMatrix(sos2, m2); - lastTag = t; - } else { - t.writeTag(sos2); - lastTag = t; - } - } - if (!s.getTags().isEmpty() && (lastTag != null) && (!(lastTag instanceof ShowFrameTag))) { - new ShowFrameTag(swf).writeTag(sos2); - } - } else { - new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2); - new ShowFrameTag(swf).writeTag(sos2); - } - - } // not showframe - - new EndTag(swf).writeTag(sos2); - data = baos.toByteArray(); - } - - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { - SWFOutputStream sos = new SWFOutputStream(fos, Math.max(10, swf.version)); - sos.write("FWS".getBytes()); - sos.write(swf.version); - sos.writeUI32(sos.getPos() + data.length + 4); - sos.write(data); - fos.flush(); - } - if (flashPanel != null) { - flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, frameRate); - } - showFlashViewerPanel(); - } catch (IOException | ActionParseException ex) { - Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, null, ex); - } - } - - public void showSwf(SWF swf) { - Color backgroundColor = View.getDefaultBackgroundColor(); - SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); - if (setBgColorTag != null) { - backgroundColor = setBgColorTag.backgroundColor.toColor(); - } - - if (tempFile != null) { - tempFile.delete(); - } - try { - tempFile = File.createTempFile("ffdec_view_", ".swf"); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { - swf.saveTo(fos); - } - //Inject Loader - if (swf.isAS3() && Configuration.autoOpenLoadedSWFs.get() && !Configuration.internalFlashViewer.get() && !DebuggerTools.hasDebugger(swf)) { - SWF instrSWF; - try (InputStream fis = new BufferedInputStream(new FileInputStream(tempFile))) { - instrSWF = new SWF(fis, false, false); - } - - DebuggerTools.switchDebugger(instrSWF); - DebuggerTools.injectDebugLoader(instrSWF); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { - instrSWF.saveTo(fos); - } - } - flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, swf.frameRate); - } catch (IOException iex) { - Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, "Cannot create tempfile", iex); - } catch (InterruptedException ex) { - - } - } - - private void editMetadataButtonActionPerformed(ActionEvent evt) { - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item == null) { - return; - } - - if (item instanceof MetadataTag) { - metadataEditor.setEditable(true); - updateMetadataButtonsVisibility(); - } - } - - private void saveMetadataButtonActionPerformed(ActionEvent evt) { - metadataTag.xmlMetadata = metadataEditor.getText().replaceAll(">\r?\n<", "> <"); - metadataTag.setModified(true); - metadataEditor.setEditable(Configuration.editorMode.get()); - setMetadataModified(false); - updateMetadataButtonsVisibility(); - mainPanel.repaintTree(); - } - - private void cancelMetadataButtonActionPerformed(ActionEvent evt) { - metadataEditor.setEditable(false); - metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4)); - metadataEditor.setEditable(Configuration.editorMode.get()); - setMetadataModified(false); - updateMetadataButtonsVisibility(); - } - - private void editGenericTagButtonActionPerformed(ActionEvent evt) { - TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); - if (item == null) { - return; - } - - if (item instanceof TagScript) { - item = ((TagScript) item).getTag(); - } - - if (item instanceof Tag) { - genericEditButton.setVisible(false); - genericSaveButton.setVisible(true); - genericCancelButton.setVisible(true); - genericTagPanel.setEditMode(true, (Tag) item); - } - } - - private void saveGenericTagButtonActionPerformed(ActionEvent evt) { - genericTagPanel.save(); - Tag tag = genericTagPanel.getTag(); - SWF swf = tag.getSwf(); - swf.clearImageCache(); - swf.updateCharacters(); - tag.getTimelined().resetTimeline(); - swf.assignClassesToSymbols(); - swf.assignExportNamesToSymbols(); - mainPanel.refreshTree(swf); - mainPanel.setTagTreeSelectedNode(tag); - genericEditButton.setVisible(true); - genericSaveButton.setVisible(false); - genericCancelButton.setVisible(false); - genericTagPanel.setEditMode(false, null); - } - - private void cancelGenericTagButtonActionPerformed(ActionEvent evt) { - genericEditButton.setVisible(true); - genericSaveButton.setVisible(false); - genericCancelButton.setVisible(false); - genericTagPanel.setEditMode(false, null); - } - - private void prevFontsButtonActionPerformed(ActionEvent evt) { - FontTag fontTag = fontPanel.getFontTag(); - int pageCount = getFontPageCount(fontTag); - fontPageNum = (fontPageNum + pageCount - 1) % pageCount; - if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { - imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0); - } - } - - private void nextFontsButtonActionPerformed(ActionEvent evt) { - FontTag fontTag = fontPanel.getFontTag(); - int pageCount = getFontPageCount(fontTag); - fontPageNum = (fontPageNum + 1) % pageCount; - if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { - imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0); - } - } - - @Override - public boolean tryAutoSave() { - // todo: implement - return textPanel.tryAutoSave() && false; - } - - @Override - public boolean isEditing() { - return textPanel.isEditing() - || (genericSaveButton.isVisible() && genericSaveButton.isEnabled()) - || (metadataSaveButton.isVisible() && metadataSaveButton.isEnabled()); - } -} +/* + * Copyright (C) 2010-2016 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.gui; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.parser.ActionParseException; +import com.jpexs.decompiler.flash.action.parser.pcode.ASMParser; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.gui.controls.JPersistentSplitPane; +import com.jpexs.decompiler.flash.gui.debugger.DebuggerTools; +import com.jpexs.decompiler.flash.gui.editor.LineMarkedEditorPane; +import com.jpexs.decompiler.flash.gui.player.FlashPlayerPanel; +import com.jpexs.decompiler.flash.gui.player.MediaDisplay; +import com.jpexs.decompiler.flash.gui.player.PlayerControls; +import com.jpexs.decompiler.flash.tags.DefineBinaryDataTag; +import com.jpexs.decompiler.flash.tags.DefineBitsTag; +import com.jpexs.decompiler.flash.tags.DefineMorphShape2Tag; +import com.jpexs.decompiler.flash.tags.DefineMorphShapeTag; +import com.jpexs.decompiler.flash.tags.DefineSoundTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DefineTextTag; +import com.jpexs.decompiler.flash.tags.DefineVideoStreamTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.EndTag; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.FileAttributesTag; +import com.jpexs.decompiler.flash.tags.JPEGTablesTag; +import com.jpexs.decompiler.flash.tags.MetadataTag; +import com.jpexs.decompiler.flash.tags.PlaceObject2Tag; +import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag; +import com.jpexs.decompiler.flash.tags.ShowFrameTag; +import com.jpexs.decompiler.flash.tags.SoundStreamBlockTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.VideoFrameTag; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.FontTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.tags.gfx.DefineCompactedFont; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.TagScript; +import com.jpexs.decompiler.flash.timeline.Timelined; +import com.jpexs.decompiler.flash.treeitems.TreeItem; +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.SHAPE; +import com.jpexs.decompiler.flash.types.TEXTRECORD; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.helpers.SerializableImage; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Font; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.SwingConstants; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * + * @author JPEXS + */ +public class PreviewPanel extends JPersistentSplitPane implements TagEditorPanel { + + private static final String FLASH_VIEWER_CARD = "FLASHVIEWER"; + + private static final String DRAW_PREVIEW_CARD = "DRAWPREVIEW"; + + private static final String GENERIC_TAG_CARD = "GENERICTAG"; + + private static final String BINARY_TAG_CARD = "BINARYTAG"; + + private static final String METADATA_TAG_CARD = "METADATATAG"; + + private static final String EMPTY_CARD = "EMPTY"; + + private static final String CARDTEXTPANEL = "Text card"; + + private static final String CARDFONTPANEL = "Font card"; + + private final MainPanel mainPanel; + + private final JPanel viewerCards; + + private final FlashPlayerPanel flashPanel; + + private File tempFile; + + private ImagePanel imagePanel; + + private PlayerControls imagePlayControls; + + private MediaDisplay media; + + private BinaryPanel binaryPanel; + + private LineMarkedEditorPane metadataEditor; + + private GenericTagPanel genericTagPanel; + + private JPanel displayWithPreview; + + // Image tag buttons + private JButton replaceImageButton; + + private JButton replaceImageAlphaButton; + + private JButton prevFontsButton; + + private JButton nextFontsButton; + + // Binary tag buttons + private JButton replaceBinaryButton; + + // Metadata editor buttons + private JButton metadataEditButton; + + private JButton metadataSaveButton; + + private JButton metadataCancelButton; + + // Generic tag buttons + private JButton genericEditButton; + + private JButton genericSaveButton; + + private JButton genericCancelButton; + + private JPanel parametersPanel; + + private FontPanel fontPanel; + + private int fontPageNum; + + private TextPanel textPanel; + + private MetadataTag metadataTag; + + private boolean readOnly = false; + + private final int dividerSize; + + public void setReadOnly(boolean readOnly) { + this.readOnly = readOnly; + setDividerSize(this.readOnly ? 0 : dividerSize); + if (readOnly) { + parametersPanel.setVisible(false); + } + } + + public PreviewPanel(MainPanel mainPanel, FlashPlayerPanel flashPanel) { + super(JSplitPane.HORIZONTAL_SPLIT, Configuration.guiPreviewSplitPaneDividerLocationPercent); + this.mainPanel = mainPanel; + this.flashPanel = flashPanel; + + viewerCards = new JPanel(); + viewerCards.setLayout(new CardLayout()); + + viewerCards.add(createFlashPlayerPanel(flashPanel), FLASH_VIEWER_CARD); + viewerCards.add(createImagesCard(), DRAW_PREVIEW_CARD); + viewerCards.add(createBinaryCard(), BINARY_TAG_CARD); + viewerCards.add(createMetadataCard(), METADATA_TAG_CARD); + viewerCards.add(createGenericTagCard(), GENERIC_TAG_CARD); + viewerCards.add(createEmptyCard(), EMPTY_CARD); + setLeftComponent(viewerCards); + + createParametersPanel(); + + showCardLeft(FLASH_VIEWER_CARD); + + dividerSize = getDividerSize(); + } + + private JPanel createEmptyCard() { + JPanel ret = new JPanel(); + ret.add(new JLabel("-")); + return ret; + } + + private void createParametersPanel() { + displayWithPreview = new JPanel(new CardLayout()); + + textPanel = new TextPanel(mainPanel); + displayWithPreview.add(textPanel, CARDTEXTPANEL); + + fontPanel = new FontPanel(mainPanel); + displayWithPreview.add(fontPanel, CARDFONTPANEL); + + JLabel paramsLabel = new HeaderLabel(mainPanel.translate("parameters")); + paramsLabel.setHorizontalAlignment(SwingConstants.CENTER); + //paramsLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + + parametersPanel = new JPanel(new BorderLayout()); + parametersPanel.add(paramsLabel, BorderLayout.NORTH); + parametersPanel.add(displayWithPreview, BorderLayout.CENTER); + setRightComponent(parametersPanel); + } + + private JPanel createImageButtonsPanel() { + replaceImageButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("replaceimage16")); + replaceImageButton.setMargin(new Insets(3, 3, 3, 10)); + replaceImageButton.addActionListener(mainPanel::replaceButtonActionPerformed); + replaceImageButton.setVisible(false); + + replaceImageAlphaButton = new JButton(mainPanel.translate("button.replaceAlphaChannel"), View.getIcon("replacealpha16")); + replaceImageAlphaButton.setMargin(new Insets(3, 3, 3, 10)); + replaceImageAlphaButton.addActionListener(mainPanel::replaceAlphaButtonActionPerformed); + replaceImageAlphaButton.setVisible(false); + + prevFontsButton = new JButton(mainPanel.translate("button.prev"), View.getIcon("prev16")); + prevFontsButton.setMargin(new Insets(3, 3, 3, 10)); + prevFontsButton.addActionListener(this::prevFontsButtonActionPerformed); + prevFontsButton.setVisible(false); + + nextFontsButton = new JButton(mainPanel.translate("button.next"), View.getIcon("next16")); + nextFontsButton.setMargin(new Insets(3, 3, 3, 10)); + nextFontsButton.addActionListener(this::nextFontsButtonActionPerformed); + nextFontsButton.setVisible(false); + + ButtonsPanel imageButtonsPanel = new ButtonsPanel(); + imageButtonsPanel.add(replaceImageButton); + imageButtonsPanel.add(replaceImageAlphaButton); + imageButtonsPanel.add(prevFontsButton); + imageButtonsPanel.add(nextFontsButton); + return imageButtonsPanel; + } + + private JPanel createBinaryButtonsPanel() { + replaceBinaryButton = new JButton(mainPanel.translate("button.replace"), View.getIcon("edit16")); + replaceBinaryButton.setMargin(new Insets(3, 3, 3, 10)); + replaceBinaryButton.addActionListener(mainPanel::replaceButtonActionPerformed); + + ButtonsPanel binaryButtonsPanel = new ButtonsPanel(); + binaryButtonsPanel.add(replaceBinaryButton); + return binaryButtonsPanel; + } + + private JPanel createGenericTagButtonsPanel() { + genericEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); + genericEditButton.setMargin(new Insets(3, 3, 3, 10)); + genericEditButton.addActionListener(this::editGenericTagButtonActionPerformed); + genericSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); + genericSaveButton.setMargin(new Insets(3, 3, 3, 10)); + genericSaveButton.addActionListener(this::saveGenericTagButtonActionPerformed); + genericSaveButton.setVisible(false); + genericCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); + genericCancelButton.setMargin(new Insets(3, 3, 3, 10)); + genericCancelButton.addActionListener(this::cancelGenericTagButtonActionPerformed); + genericCancelButton.setVisible(false); + + ButtonsPanel genericTagButtonsPanel = new ButtonsPanel(); + genericTagButtonsPanel.add(genericEditButton); + genericTagButtonsPanel.add(genericSaveButton); + genericTagButtonsPanel.add(genericCancelButton); + return genericTagButtonsPanel; + } + + private JPanel createMetadataButtonsPanel() { + metadataEditButton = new JButton(mainPanel.translate("button.edit"), View.getIcon("edit16")); + metadataEditButton.setMargin(new Insets(3, 3, 3, 10)); + metadataEditButton.addActionListener(this::editMetadataButtonActionPerformed); + metadataSaveButton = new JButton(mainPanel.translate("button.save"), View.getIcon("save16")); + metadataSaveButton.setMargin(new Insets(3, 3, 3, 10)); + metadataSaveButton.addActionListener(this::saveMetadataButtonActionPerformed); + metadataSaveButton.setVisible(false); + metadataCancelButton = new JButton(mainPanel.translate("button.cancel"), View.getIcon("cancel16")); + metadataCancelButton.setMargin(new Insets(3, 3, 3, 10)); + metadataCancelButton.addActionListener(this::cancelMetadataButtonActionPerformed); + metadataCancelButton.setVisible(false); + + ButtonsPanel metadataTagButtonsPanel = new ButtonsPanel(); + metadataTagButtonsPanel.add(metadataEditButton); + metadataTagButtonsPanel.add(metadataSaveButton); + metadataTagButtonsPanel.add(metadataCancelButton); + return metadataTagButtonsPanel; + } + + private JPanel createFlashPlayerPanel(FlashPlayerPanel flashPanel) { + JPanel pan = new JPanel(new BorderLayout()); + JLabel prevLabel = new HeaderLabel(mainPanel.translate("swfpreview")); + prevLabel.setHorizontalAlignment(SwingConstants.CENTER); + //prevLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + + pan.add(prevLabel, BorderLayout.NORTH); + + Component leftComponent; + if (flashPanel != null) { + JPanel flashPlayPanel = new JPanel(new BorderLayout()); + flashPlayPanel.add(flashPanel, BorderLayout.CENTER); + + /*JPanel bottomPanel = new JPanel(new BorderLayout()); + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton selectColorButton = new JButton(View.getIcon("color16")); + selectColorButton.addActionListener(mainPanel::selectBkColor); + selectColorButton.setToolTipText(AppStrings.translate("button.selectbkcolor.hint")); + buttonsPanel.add(selectColorButton); + bottomPanel.add(buttonsPanel, BorderLayout.EAST); + + flashPlayPanel.add(bottomPanel, BorderLayout.SOUTH);*/ + JPanel flashPlayPanel2 = new JPanel(new BorderLayout()); + flashPlayPanel2.add(flashPlayPanel, BorderLayout.CENTER); + flashPlayPanel2.add(new PlayerControls(mainPanel, flashPanel), BorderLayout.SOUTH); + leftComponent = flashPlayPanel2; + } else { + JPanel swtPanel = new JPanel(new BorderLayout()); + swtPanel.add(new JLabel("
" + mainPanel.translate("notavailonthisplatform") + "
", JLabel.CENTER), BorderLayout.CENTER); + swtPanel.setBackground(View.getDefaultBackgroundColor()); + leftComponent = swtPanel; + } + + pan.add(leftComponent, BorderLayout.CENTER); + return pan; + } + + private JPanel createImagesCard() { + JPanel shapesCard = new JPanel(new BorderLayout()); + JPanel previewPanel = new JPanel(new BorderLayout()); + + JPanel previewCnt = new JPanel(new BorderLayout()); + imagePanel = new ImagePanel(); + imagePanel.setLoop(Configuration.loopMedia.get()); + previewCnt.add(imagePanel, BorderLayout.CENTER); + previewCnt.add(imagePlayControls = new PlayerControls(mainPanel, imagePanel), BorderLayout.SOUTH); + imagePlayControls.setMedia(imagePanel); + previewPanel.add(previewCnt, BorderLayout.CENTER); + JLabel prevIntLabel = new HeaderLabel(mainPanel.translate("swfpreview.internal")); + prevIntLabel.setHorizontalAlignment(SwingConstants.CENTER); + //prevIntLabel.setBorder(new BevelBorder(BevelBorder.RAISED)); + previewPanel.add(prevIntLabel, BorderLayout.NORTH); + + shapesCard.add(previewPanel, BorderLayout.CENTER); + + shapesCard.add(createImageButtonsPanel(), BorderLayout.SOUTH); + return shapesCard; + } + + private JPanel createMetadataCard() { + JPanel metadataCard = new JPanel(new BorderLayout()); + metadataEditor = new LineMarkedEditorPane(); + metadataCard.add(new JScrollPane(metadataEditor), BorderLayout.CENTER); + //metadataEditor.setContentType("text/xml"); + metadataEditor.setEditable(false); + + metadataEditor.setFont(new Font("Monospaced", Font.PLAIN, metadataEditor.getFont().getSize())); + metadataEditor.changeContentType("text/xml"); + metadataEditor.addTextChangedListener(this::metadataTextChanged); + + metadataCard.add(createMetadataButtonsPanel(), BorderLayout.SOUTH); + return metadataCard; + } + + private boolean isMetadataModified() { + return metadataSaveButton.isVisible() && metadataSaveButton.isEnabled(); + } + + private void setMetadataModified(boolean value) { + metadataSaveButton.setEnabled(value); + metadataCancelButton.setEnabled(value); + } + + private void metadataTextChanged() { + setMetadataModified(true); + } + + private void updateMetadataButtonsVisibility() { + boolean edit = metadataEditor.isEditable(); + boolean editorMode = Configuration.editorMode.get(); + metadataEditButton.setVisible(!readOnly && !edit); + metadataSaveButton.setVisible(!readOnly && edit); + boolean metadataModified = isMetadataModified(); + metadataCancelButton.setVisible(!readOnly && edit); + metadataCancelButton.setEnabled(metadataModified || !editorMode); + } + + private JPanel createBinaryCard() { + JPanel binaryCard = new JPanel(new BorderLayout()); + binaryPanel = new BinaryPanel(mainPanel); + binaryCard.add(binaryPanel, BorderLayout.CENTER); + binaryCard.add(createBinaryButtonsPanel(), BorderLayout.SOUTH); + return binaryCard; + } + + private JPanel createGenericTagCard() { + JPanel genericTagCard = new JPanel(new BorderLayout()); + genericTagPanel = new GenericTagTreePanel(mainPanel); + genericTagCard.add(genericTagPanel, BorderLayout.CENTER); + genericTagCard.add(createGenericTagButtonsPanel(), BorderLayout.SOUTH); + return genericTagCard; + } + + private void showCardLeft(String card) { + CardLayout cl = (CardLayout) (viewerCards.getLayout()); + cl.show(viewerCards, card); + } + + private void showCardRight(String card) { + CardLayout cl = (CardLayout) (displayWithPreview.getLayout()); + cl.show(displayWithPreview, card); + } + + public TextPanel getTextPanel() { + return textPanel; + } + + public void setParametersPanelVisible(boolean show) { + parametersPanel.setVisible(show); + } + + public void showFlashViewerPanel() { + parametersPanel.setVisible(false); + showCardLeft(FLASH_VIEWER_CARD); + } + + public void showImagePanel(Timelined timelined, SWF swf, int frame) { + showCardLeft(DRAW_PREVIEW_CARD); + parametersPanel.setVisible(false); + imagePlayControls.setMedia(imagePanel); + imagePanel.setTimelined(timelined, swf, frame); + } + + public void showImagePanel(SerializableImage image) { + showCardLeft(DRAW_PREVIEW_CARD); + parametersPanel.setVisible(false); + imagePlayControls.setMedia(imagePanel); + imagePanel.setImage(image); + } + + public void showTextComparePanel(TextTag textTag, TextTag newTextTag) { + imagePanel.setText(textTag, newTextTag); + } + + public void setMedia(MediaDisplay media) { + this.media = media; + imagePlayControls.setMedia(media); + } + + public void showFontPanel(FontTag fontTag) { + fontPageNum = 0; + showFontPage(fontTag); + + showCardRight(CARDFONTPANEL); + if (!readOnly) { + parametersPanel.setVisible(true); + } + fontPanel.showFontTag(fontTag); + + int pageCount = getFontPageCount(fontTag); + if (pageCount > 1) { + prevFontsButton.setVisible(true); + nextFontsButton.setVisible(true); + } + } + + private void showFontPage(FontTag fontTag) { + if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { + showImagePanel(MainPanel.makeTimelined(fontTag), fontTag.getSwf(), fontPageNum); + } + } + + public static int getFontPageCount(FontTag fontTag) { + int pageCount = (fontTag.getGlyphShapeTable().size() - 1) / SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW + 1; + if (pageCount < 1) { + pageCount = 1; + } + return pageCount; + } + + public void showEmpty() { + showCardLeft(EMPTY_CARD); + } + + public void showTextPanel(TextTag textTag) { + if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { + showImagePanel(MainPanel.makeTimelined(textTag), textTag.getSwf(), 0); + } + + showCardRight(CARDTEXTPANEL); + if (!readOnly) { + parametersPanel.setVisible(true); + } + textPanel.setText(textTag); + } + + public void focusTextPanel() { + textPanel.focusTextValue(); + } + + public void clear() { + imagePanel.clearAll(); + if (media != null) { + try { + media.close(); + } catch (IOException ex) { + // ignore + } + } + + binaryPanel.setBinaryData(null); + genericTagPanel.clear(); + fontPanel.clear(); + } + + public void closeTag() { + textPanel.closeTag(); + } + + public static String formatMetadata(String input, int indent) { + input = input.replace("> <", "><"); + try { + Source xmlInput = new StreamSource(new StringReader(input)); + StringWriter stringWriter = new StringWriter(); + StreamResult xmlOutput = new StreamResult(stringWriter); + StringWriter sw = new StringWriter(); + xmlOutput.setWriter(sw); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + transformerFactory.setAttribute("indent-number", indent); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "" + indent); + transformer.transform(xmlInput, xmlOutput); + + return xmlOutput.getWriter().toString(); + } catch (IllegalArgumentException | TransformerException e) { + return input; + } + } + + public void showMetaDataPanel(MetadataTag metadataTag) { + showCardLeft(METADATA_TAG_CARD); + this.metadataTag = metadataTag; + metadataEditor.setEditable(!readOnly && !metadataTag.isReadOnly() && Configuration.editorMode.get()); + metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4)); + setMetadataModified(false); + updateMetadataButtonsVisibility(); + parametersPanel.setVisible(false); + } + + public void showBinaryPanel(DefineBinaryDataTag binaryDataTag) { + showCardLeft(BINARY_TAG_CARD); + binaryPanel.setBinaryData(binaryDataTag); + parametersPanel.setVisible(false); + } + + public void showGenericTagPanel(Tag tag) { + showCardLeft(GENERIC_TAG_CARD); + genericEditButton.setVisible(!tag.isReadOnly()); + genericSaveButton.setVisible(false); + genericCancelButton.setVisible(false); + genericTagPanel.setEditMode(false, tag); + parametersPanel.setVisible(false); + } + + public void setImageReplaceButtonVisible(boolean show, boolean showAlpha) { + if (readOnly) { + show = false; + showAlpha = false; + } + replaceImageButton.setVisible(show); + replaceImageAlphaButton.setVisible(showAlpha); + prevFontsButton.setVisible(false); + nextFontsButton.setVisible(false); + } + + private static Tag classicTag(Tag t) { + if (t instanceof DefineCompactedFont) { + return ((DefineCompactedFont) t).toClassicFont(); + } + return t; + } + + private static void writeTag(Tag t, SWFOutputStream sos) throws IOException { + t = classicTag(t); + + t.writeTag(sos); + if (t instanceof CharacterIdTag) { + List chIdTags = t.getSwf().getCharacterIdTags(((CharacterIdTag) t).getCharacterId()); + if (chIdTags != null) { + for (CharacterIdTag chIdTag : chIdTags) { + if (!(chIdTag instanceof PlaceObjectTypeTag || chIdTag instanceof RemoveTag)) { + ((Tag) chIdTag).writeTag(sos); + } + } + } + } + } + + public void createAndShowTempSwf(TreeItem treeItem) { + SWF swf = null; + try { + if (tempFile != null) { + tempFile.delete(); + } + + tempFile = File.createTempFile("ffdec_view_", ".swf"); + tempFile.deleteOnExit(); + + Color backgroundColor = View.getSwfBackgroundColor(); + + if (treeItem instanceof Tag) { + Tag tag = (Tag) treeItem; + swf = tag.getSwf(); + if (tag instanceof FontTag) { //Fonts are always black on white + backgroundColor = View.getDefaultBackgroundColor(); + } + } else if (treeItem instanceof Frame) { + Frame fn = (Frame) treeItem; + swf = fn.getSwf(); + if (fn.timeline.timelined == swf) { + SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); + if (setBgColorTag != null) { + backgroundColor = setBgColorTag.backgroundColor.toColor(); + } + } + } + + int frameCount = 1; + float frameRate = swf.frameRate; + HashMap videoFrames = new HashMap<>(); + if (treeItem instanceof DefineVideoStreamTag) { + DefineVideoStreamTag vs = (DefineVideoStreamTag) treeItem; + SWF.populateVideoFrames(vs.getCharacterId(), swf.getTags(), videoFrames); + frameCount = videoFrames.size(); + } + + List soundFrames = new ArrayList<>(); + if (treeItem instanceof SoundStreamHeadTypeTag) { + soundFrames = ((SoundStreamHeadTypeTag) treeItem).getBlocks(); + frameCount = soundFrames.size(); + } + + if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) { + frameRate = MainPanel.MORPH_SHAPE_ANIMATION_FRAME_RATE; + frameCount = (int) (MainPanel.MORPH_SHAPE_ANIMATION_LENGTH * frameRate); + } + + if (treeItem instanceof DefineSoundTag) { + frameCount = 1; + } + + if (treeItem instanceof DefineSpriteTag) { + frameCount = ((DefineSpriteTag) treeItem).frameCount; + } + + byte[] data; + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + SWFOutputStream sos2 = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); + RECT outrect = new RECT(swf.displayRect); + + RECT treeItemBounds = null; + if (treeItem instanceof FontTag) { + outrect.Xmin = 0; + outrect.Ymin = 0; + outrect.Xmax = FontTag.PREVIEWSIZE * 20; + outrect.Ymax = FontTag.PREVIEWSIZE * 20; + } else if (treeItem instanceof BoundedTag) { + treeItemBounds = ((BoundedTag) treeItem).getRect(); + } else if (treeItem instanceof Frame) { + treeItemBounds = ((Frame) treeItem).timeline.timelined.getRect(); + } + + if (treeItemBounds != null) { + if (outrect.getWidth() < treeItemBounds.getWidth()) { + outrect.Xmax += treeItemBounds.getWidth() - outrect.getWidth(); + } + + if (outrect.getHeight() < treeItemBounds.getHeight()) { + outrect.Ymax += treeItemBounds.getHeight() - outrect.getHeight(); + } + } + + int width = outrect.getWidth(); + int height = outrect.getHeight(); + + sos2.writeRECT(outrect); + sos2.writeFIXED8(frameRate); + sos2.writeUI16(frameCount); //framecnt + + FileAttributesTag fa = swf.getFileAttributes(); + if (fa != null) { + fa.writeTag(sos2); + } + + SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); + if (setBgColorTag == null) { + setBgColorTag = new SetBackgroundColorTag(swf, new RGB(backgroundColor)); + } + + setBgColorTag.writeTag(sos2); + + if (treeItem instanceof Frame) { + Frame fn = (Frame) treeItem; + Timelined parent = fn.timeline.timelined; + List doneCharacters = new ArrayList<>(); + for (Tag t : parent.getTags()) { + if (t instanceof FileAttributesTag || t instanceof SetBackgroundColorTag) { + continue; + } + + if (t instanceof DoActionTag || t instanceof DoInitActionTag) { + // todo: Maybe DoABC tags should be removed, too + continue; + } + + Set needed = new HashSet<>(); + t.getNeededCharactersDeep(needed); + for (int n : needed) { + if (!doneCharacters.contains(n)) { + writeTag(swf.getCharacter(n), sos2); + doneCharacters.add(n); + } + } + + if (t instanceof ShowFrameTag || t instanceof PlaceObjectTypeTag || t instanceof RemoveTag) { + continue; + } + + if (t instanceof CharacterTag) { + int characterId = ((CharacterTag) t).getCharacterId(); + if (!doneCharacters.contains(characterId)) { + doneCharacters.add(((CharacterTag) t).getCharacterId()); + } + } + + writeTag(t, sos2); + } + + RECT r = parent.getRect(); + for (Map.Entry value : fn.layers.entrySet()) { + PlaceObjectTypeTag pot = value.getValue().toPlaceObjectTag(value.getKey()); + MATRIX mat = new MATRIX(pot.getMatrix()); + mat.translateX += width / 2 - r.getWidth() / 2; + mat.translateY += height / 2 - r.getHeight() / 2; + pot.setMatrix(mat); + pot.writeTag(sos2); + } + + new ShowFrameTag(swf).writeTag(sos2); + } else { + boolean isSprite = false; + if (treeItem instanceof DefineSpriteTag) { + isSprite = true; + } + int chtId = 0; + if (treeItem instanceof CharacterTag) { + chtId = ((CharacterTag) treeItem).getCharacterId(); + } + + if (treeItem instanceof DefineBitsTag) { + JPEGTablesTag jtt = swf.getJtt(); + if (jtt != null) { + jtt.writeTag(sos2); + } + } else if (treeItem instanceof AloneTag) { + } else { + Set needed = new HashSet<>(); + ((Tag) treeItem).getNeededCharactersDeep(needed); + for (int n : needed) { + if (isSprite && chtId == n) { + continue; + } + + CharacterTag characterTag = swf.getCharacter(n); + if (characterTag instanceof DefineBitsTag) { + JPEGTablesTag jtt = swf.getJtt(); + if (jtt != null) { + jtt.writeTag(sos2); + } + } + + writeTag(characterTag, sos2); + } + } + + writeTag((Tag) treeItem, sos2); + + MATRIX mat = new MATRIX(); + mat.hasRotate = false; + mat.hasScale = false; + mat.translateX = 0; + mat.translateY = 0; + if (treeItem instanceof BoundedTag) { + RECT r = ((BoundedTag) treeItem).getRect(); + mat.translateX = -r.Xmin; + mat.translateY = -r.Ymin; + mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2; + mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2; + } else { + mat.translateX = width / 4; + mat.translateY = height / 4; + } + if (treeItem instanceof FontTag) { + FontTag ft = (FontTag) classicTag((Tag) treeItem); + + int countGlyphsTotal = ft.getGlyphShapeTable().size(); + int countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal); + int fontId = ft.getFontId(); + int cols = (int) Math.ceil(Math.sqrt(countGlyphs)); + int rows = (int) Math.ceil(((float) countGlyphs) / ((float) cols)); + if (rows == 0) { + rows = 1; + cols = 1; + } + int x = 0; + int y = 0; + int firstGlyphIndex = fontPageNum * SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW; + countGlyphs = Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal - firstGlyphIndex); + List shapes = ft.getGlyphShapeTable(); + int maxw = 0; + for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) { + RECT b = shapes.get(f).getBounds(); + if (b.Xmin == Integer.MAX_VALUE) { + continue; + } + if (b.Ymin == Integer.MAX_VALUE) { + continue; + } + int w = (int) (b.getWidth() / ft.getDivider()); + if (w > maxw) { + maxw = w; + } + x++; + } + + x = 0; + + int BORDER = 3 * 20; + + int textHeight = height / rows; + + while (maxw * textHeight / 1024.0 > width / cols - 2 * BORDER) { + textHeight--; + } + + MATRIX tmat = new MATRIX(); + for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) { + if (x >= cols) { + x = 0; + y++; + } + List rec = new ArrayList<>(); + TEXTRECORD tr = new TEXTRECORD(); + + RECT b = shapes.get(f).getBounds(); + 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()); + ymin *= textHeight / 1024.0; + int w = (int) (b.getWidth() / ft.getDivider()); + w *= textHeight / 1024.0; + int h = (int) (b.getHeight() / ft.getDivider()); + h *= textHeight / 1024.0; + + tr.fontId = fontId; + tr.styleFlagsHasFont = true; + tr.textHeight = textHeight; + tr.xOffset = -xmin; + tr.yOffset = 0; + tr.styleFlagsHasXOffset = true; + tr.styleFlagsHasYOffset = true; + tr.glyphEntries = new ArrayList<>(1); + tr.styleFlagsHasColor = true; + tr.textColor = new RGB(0, 0, 0); + GLYPHENTRY ge = new GLYPHENTRY(); + + double ga = ft.getGlyphAdvance(f); + int cw = ga == -1 ? w : (int) (ga / ft.getDivider() * textHeight / 1024.0); + + ge.glyphAdvance = 0; + ge.glyphIndex = f; + tr.glyphEntries.add(ge); + rec.add(tr); + + tmat.translateX = x * width / cols + width / cols / 2 - w / 2; + tmat.translateY = y * height / rows + height / rows / 2; + new DefineTextTag(swf, 999 + f, new RECT(0, cw, ymin, ymin + h), new MATRIX(), rec).writeTag(sos2); + new PlaceObject2Tag(swf, false, 1 + f, 999 + f, tmat, null, 0, null, -1, null).writeTag(sos2); + x++; + } + new ShowFrameTag(swf).writeTag(sos2); + } else if ((treeItem instanceof DefineMorphShapeTag) || (treeItem instanceof DefineMorphShape2Tag)) { + new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + for (int ratio = 0; ratio < 65536; ratio += 65536 / frameCount) { + new PlaceObject2Tag(swf, true, 1, chtId, mat, null, ratio, null, -1, null).writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + } + } else if (treeItem instanceof SoundStreamHeadTypeTag) { + for (SoundStreamBlockTag blk : soundFrames) { + blk.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + } + } else if (treeItem instanceof DefineSoundTag) { + ExportAssetsTag ea = new ExportAssetsTag(swf); + DefineSoundTag ds = (DefineSoundTag) treeItem; + ea.tags.add(ds.soundId); + ea.names.add("my_define_sound"); + ea.writeTag(sos2); + List actions; + DoActionTag doa; + + doa = new DoActionTag(swf, null); + actions = ASMParser.parse(0, false, + "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\"\n" + + "Push \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\" 0.0 \"Sound\"\n" + + "NewObject\n" + + "SetMember\n" + + "Push \"my_define_sound\" 1 \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"attachSound\"\n" + + "CallMethod\n" + + "Pop\n" + + "Stop", swf.version, false); + doa.setActions(actions); + doa.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + + actions = ASMParser.parse(0, false, + "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"start\"\n" + + "StopSounds\n" + + "Push \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\" 0.0 \"Sound\"\n" + + "NewObject\n" + + "SetMember\n" + + "Push \"my_define_sound\" 1 \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"attachSound\"\n" + + "CallMethod\n" + + "Pop\n" + + "Push 9999 0.0 2 \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"start\"\n" + + "CallMethod\n" + + "Pop\n" + + "Stop", swf.version, false); + doa.setActions(actions); + doa.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + + actions = ASMParser.parse(0, false, + "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"onSoundComplete\" \"start\" \"execParam\"\n" + + "StopSounds\n" + + "Push \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\" 0.0 \"Sound\"\n" + + "NewObject\n" + + "SetMember\n" + + "Push \"my_define_sound\" 1 \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"attachSound\"\n" + + "CallMethod\n" + + "Pop\n" + + "Push \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"onSoundComplete\"\n" + + "DefineFunction2 \"\" 0 2 false true true false true false true false false {\n" + + "Push 0.0 register1 \"my_sound\"\n" + + "GetMember\n" + + "Push \"start\"\n" + + "CallMethod\n" + + "Pop\n" + + "}\n" + + "SetMember\n" + + "Push \"_root\"\n" + + "GetVariable\n" + + "Push \"execParam\"\n" + + "GetMember\n" + + "Push 1 \"_root\"\n" + + "GetVariable\n" + + "Push \"my_sound\"\n" + + "GetMember\n" + + "Push \"start\"\n" + + "CallMethod\n" + + "Pop\n" + + "Stop", swf.version, false); + doa.setActions(actions); + doa.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + + actions = ASMParser.parse(0, false, + "StopSounds\n" + + "Stop", swf.version, false); + doa.setActions(actions); + doa.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + + new ShowFrameTag(swf).writeTag(sos2); + } else if (treeItem instanceof DefineVideoStreamTag) { + + new PlaceObject2Tag(swf, false, 1, chtId, mat, null, -1, null, -1, null).writeTag(sos2); + List frs = new ArrayList<>(videoFrames.values()); + Collections.sort(frs, new Comparator() { + @Override + public int compare(VideoFrameTag o1, VideoFrameTag o2) { + return o1.frameNum - o2.frameNum; + } + }); + boolean first = true; + int ratio = 0; + for (VideoFrameTag f : frs) { + if (!first) { + ratio++; + new PlaceObject2Tag(swf, true, 1, 0, null, null, ratio, null, -1, null).writeTag(sos2); + } + f.writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + first = false; + } + } else if (treeItem instanceof DefineSpriteTag) { + DefineSpriteTag s = (DefineSpriteTag) treeItem; + Tag lastTag = null; + for (Tag t : s.getTags()) { + if (t instanceof EndTag) { + break; + } else if (t instanceof PlaceObjectTypeTag) { + PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t; + MATRIX m = pt.getMatrix(); + MATRIX m2 = new Matrix(m).preConcatenate(new Matrix(mat)).toMATRIX(); + pt.writeTagWithMatrix(sos2, m2); + lastTag = t; + } else { + t.writeTag(sos2); + lastTag = t; + } + } + if (!s.getTags().isEmpty() && (lastTag != null) && (!(lastTag instanceof ShowFrameTag))) { + new ShowFrameTag(swf).writeTag(sos2); + } + } else { + new PlaceObject2Tag(swf, false, 1, chtId, mat, null, 0, null, -1, null).writeTag(sos2); + new ShowFrameTag(swf).writeTag(sos2); + } + + } // not showframe + + new EndTag(swf).writeTag(sos2); + data = baos.toByteArray(); + } + + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { + SWFOutputStream sos = new SWFOutputStream(fos, Math.max(10, swf.version)); + sos.write("FWS".getBytes()); + sos.write(swf.version); + sos.writeUI32(sos.getPos() + data.length + 4); + sos.write(data); + fos.flush(); + } + if (flashPanel != null) { + flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, frameRate); + } + showFlashViewerPanel(); + } catch (IOException | ActionParseException ex) { + Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, null, ex); + } + } + + public void showSwf(SWF swf) { + Color backgroundColor = View.getDefaultBackgroundColor(); + SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor(); + if (setBgColorTag != null) { + backgroundColor = setBgColorTag.backgroundColor.toColor(); + } + + if (tempFile != null) { + tempFile.delete(); + } + try { + tempFile = File.createTempFile("ffdec_view_", ".swf"); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { + swf.saveTo(fos); + } + //Inject Loader + if (swf.isAS3() && Configuration.autoOpenLoadedSWFs.get() && !Configuration.internalFlashViewer.get() && !DebuggerTools.hasDebugger(swf)) { + SWF instrSWF; + try (InputStream fis = new BufferedInputStream(new FileInputStream(tempFile))) { + instrSWF = new SWF(fis, false, false); + } + + DebuggerTools.switchDebugger(instrSWF); + DebuggerTools.injectDebugLoader(instrSWF); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) { + instrSWF.saveTo(fos); + } + } + flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, swf.frameRate); + } catch (IOException iex) { + Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, "Cannot create tempfile", iex); + } catch (InterruptedException ex) { + + } + } + + private void editMetadataButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item == null) { + return; + } + + if (item instanceof MetadataTag) { + metadataEditor.setEditable(true); + updateMetadataButtonsVisibility(); + } + } + + private void saveMetadataButtonActionPerformed(ActionEvent evt) { + metadataTag.xmlMetadata = metadataEditor.getText().replaceAll(">\r?\n<", "> <"); + metadataTag.setModified(true); + metadataEditor.setEditable(Configuration.editorMode.get()); + setMetadataModified(false); + updateMetadataButtonsVisibility(); + mainPanel.repaintTree(); + } + + private void cancelMetadataButtonActionPerformed(ActionEvent evt) { + metadataEditor.setEditable(false); + metadataEditor.setText(formatMetadata(metadataTag.xmlMetadata, 4)); + metadataEditor.setEditable(Configuration.editorMode.get()); + setMetadataModified(false); + updateMetadataButtonsVisibility(); + } + + private void editGenericTagButtonActionPerformed(ActionEvent evt) { + TreeItem item = mainPanel.tagTree.getCurrentTreeItem(); + if (item == null) { + return; + } + + if (item instanceof TagScript) { + item = ((TagScript) item).getTag(); + } + + if (item instanceof Tag) { + genericEditButton.setVisible(false); + genericSaveButton.setVisible(true); + genericCancelButton.setVisible(true); + genericTagPanel.setEditMode(true, (Tag) item); + } + } + + private void saveGenericTagButtonActionPerformed(ActionEvent evt) { + genericTagPanel.save(); + Tag tag = genericTagPanel.getTag(); + SWF swf = tag.getSwf(); + swf.clearImageCache(); + swf.updateCharacters(); + tag.getTimelined().resetTimeline(); + swf.assignClassesToSymbols(); + swf.assignExportNamesToSymbols(); + mainPanel.refreshTree(swf); + mainPanel.setTagTreeSelectedNode(tag); + genericEditButton.setVisible(true); + genericSaveButton.setVisible(false); + genericCancelButton.setVisible(false); + genericTagPanel.setEditMode(false, null); + } + + private void cancelGenericTagButtonActionPerformed(ActionEvent evt) { + genericEditButton.setVisible(true); + genericSaveButton.setVisible(false); + genericCancelButton.setVisible(false); + genericTagPanel.setEditMode(false, null); + } + + private void prevFontsButtonActionPerformed(ActionEvent evt) { + FontTag fontTag = fontPanel.getFontTag(); + int pageCount = getFontPageCount(fontTag); + fontPageNum = (fontPageNum + pageCount - 1) % pageCount; + if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { + imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0); + } + } + + private void nextFontsButtonActionPerformed(ActionEvent evt) { + FontTag fontTag = fontPanel.getFontTag(); + int pageCount = getFontPageCount(fontTag); + fontPageNum = (fontPageNum + 1) % pageCount; + if (mainPanel.isInternalFlashViewerSelected() /*|| ft instanceof GFxDefineCompactedFont*/) { + imagePanel.setTimelined(MainPanel.makeTimelined(fontTag, fontPageNum), fontTag.getSwf(), 0); + } + } + + @Override + public boolean tryAutoSave() { + // todo: implement + return textPanel.tryAutoSave() && false; + } + + @Override + public boolean isEditing() { + return textPanel.isEditing() + || (genericSaveButton.isVisible() && genericSaveButton.isEnabled()) + || (metadataSaveButton.isVisible() && metadataSaveButton.isEnabled()); + } +} diff --git a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties index 0875f7dc7..4a52a29bb 100644 --- a/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties +++ b/src/com/jpexs/decompiler/flash/gui/locales/MainFrame.properties @@ -1,721 +1,722 @@ -# Copyright (C) 2010-2016 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 . - -menu.file = File -menu.file.open = Open... -menu.file.save = Save -menu.file.saveas = Save as... -menu.file.export.fla = Export to FLA -menu.file.export.all = Export all parts -menu.file.export.selection = Export selection -menu.file.exit = Exit - -menu.tools = Tools -menu.tools.searchas = Search All ActionScript... -menu.tools.proxy = Proxy -menu.tools.deobfuscation = Deobfuscation -menu.tools.deobfuscation.pcode = P-code deobfuscation... -menu.tools.deobfuscation.globalrename = Globally rename identifier -menu.tools.deobfuscation.renameinvalid = Rename invalid identifiers -menu.tools.gotoDocumentClass = Go to document class - -menu.settings = Settings -menu.settings.autodeobfuscation = Automatic deobfuscation -menu.settings.internalflashviewer = Use own Flash viewer -menu.settings.parallelspeedup = Parallel SpeedUp -menu.settings.disabledecompilation = Disable decompilation (Disassemble only) -menu.settings.addtocontextmenu = Add FFDec to SWF files context menu -menu.settings.language = Change language -menu.settings.cacheOnDisk = Use caching on disk -menu.settings.gotoMainClassOnStartup = Highlight document class on startup - -menu.help = Help -menu.help.checkupdates = Check for updates... -menu.help.helpus = Help us! -menu.help.homepage = Visit homepage -menu.help.about = About... - -contextmenu.remove = Remove - -button.save = Save -button.edit = Edit -button.cancel = Cancel -button.replace = Replace... - -notavailonthisplatform = Preview of this object is not available on this platform (Windows only). - -swfpreview = SWF preview -swfpreview.internal = SWF preview (Internal viewer) - -parameters = Parameters - -rename.enternew = Enter new name: - -rename.finished.identifier = Identifier renamed. -rename.finished.multiname = %count% multiname(s) renamed. - -node.texts = texts -node.images = images -node.movies = movies -node.sounds = sounds -node.binaryData = binaryData -node.fonts = fonts -node.sprites = sprites -node.shapes = shapes -node.morphshapes = morphshapes -node.buttons = buttons -node.frames = frames -node.scripts = scripts - -message.warning = Warning -message.confirm.experimental = Following procedure can damage SWF file which can be then unplayable.\r\nUSE IT ON YOUR OWN RISK. Do you want to continue? -message.confirm.parallel = Parallelism can speed up loading and decompilation but uses more memory. -message.confirm.on = Do you want to turn this ON? -message.confirm.off = Do you want to turn this OFF? -message.confirm = Confirm - -message.confirm.autodeobfuscate = 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. - -message.parallel = Parallelism -message.trait.saved = Trait successfully saved - -message.constant.new.string = String "%value%" is not present in constants table. Do you want to add it? -message.constant.new.string.title = Add String -message.constant.new.integer = Integer value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.integer.title = Add Integer -message.constant.new.unsignedinteger = Unsigned integer value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.unsignedinteger.title = Add Unsigned integer -message.constant.new.double = Double value "%value%" is not present in constants table. Do you want to add it? -message.constant.new.double.title = Add Double - -work.buffering = Buffering -work.waitingfordissasembly = Waiting for disassembly -work.gettinghilights = Getting highlights -work.disassembling = Disassembling -work.exporting = Exporting -work.searching = Searching -work.renaming = Renaming -work.exporting.fla = Exporting FLA -work.renaming.identifiers = Renaming identifiers -work.deobfuscating = Deobfuscating -work.decompiling = Decompiling -work.gettingvariables = Getting variables -work.reading.swf = Reading SWF -work.creatingwindow = Creating window -work.buildingscripttree = Building script tree - -work.deobfuscating.complete = Deobfuscation complete - -message.search.notfound = String "%searchtext%" not found. -message.search.notfound.title = Not found - -message.rename.notfound.multiname = No multiname found under cursor -message.rename.notfound.identifier = No identifier found under cursor -message.rename.notfound.title = Not found -message.rename.renamed = Identifiers renamed: %count% - -filter.images = Images (%extensions%) -filter.fla = %version% Document (*.fla) -filter.xfl = %version% Uncompressed Document (*.xfl) -filter.swf = SWF files (*.swf) - -error = Error -error.image.invalid = Invalid image. - -error.text.invalid = Invalid text: %text% on line %line% -error.file.save = Cannot save file -error.file.write = Cannot write to the file -error.export = Error during export - -export.select.directory = Select directory to export -export.finishedin = Exported in %time% - -update.check.title = Update check -update.check.nonewversion = No new version available. - -message.helpus = Please visit\r\n%url%\r\nfor details. -message.homepage = Visit homepage at: \r\n%url% - -proxy = Proxy -proxy.start = Start proxy -proxy.stop = Stop proxy -proxy.show = Show proxy -exit = Exit - -panel.disassembled = P-code source -panel.decompiled = ActionScript source - -search.info = Search for "%text%": -search.script = Script - -constants = Constants -traits = Traits - -pleasewait = Please wait - -abc.detail.methodtrait = Method/Getter/Setter Trait -abc.detail.unsupported = - -abc.detail.slotconsttrait = Slot/Const Trait -abc.detail.traitname = Name: - -abc.detail.body.params.maxstack = Max stack: -abc.detail.body.params.localregcount = Local registers count: -abc.detail.body.params.minscope = Minimum scope depth: -abc.detail.body.params.maxscope = Maximum scope depth: -abc.detail.body.params.autofill = Auto fill on code save (GLOBAL SETTING) -abc.detail.body.params.autofill.experimental = ...EXPERIMENTAL - -abc.detail.methodinfo.methodindex = Method Index: -abc.detail.methodinfo.parameters = Parameters: -abc.detail.methodinfo.returnvalue = Return value type: - -error.methodinfo.params = MethodInfo Params Error -error.methodinfo.returnvalue = MethodInfo Return value type Error - -abc.detail.methodinfo = MethodInfo -abc.detail.body.code = MethodBody Code -abc.detail.body.params = MethodBody params - -abc.detail.slotconst.typevalue = Type and Value: - -error.slotconst.typevalue = SlotConst type value Error - -message.autofill.failed = Cannot get code stats for automatic body params.\r\nUncheck autofill to avoid this message. -info.selecttrait = Select class and click a trait in Actionscript source to edit it. - -button.viewgraph = View Graph -button.viewhex = View Hex - -abc.traitslist.instanceinitializer = instance initializer -abc.traitslist.classinitializer = class initializer - -action.edit.experimental = (Experimental) - -message.action.saved = Code successfully saved - -error.action.save = %error% on line %line% - -message.confirm.remove = Are you sure you want to remove %item%\n and all objects which depend on it? - -#after version 1.6.5u1: - -button.ok = OK -button.cancel = Cancel - -font.name = Font name: -font.isbold = Is bold: -font.isitalic = Is italic: -font.ascent = Ascent: -font.descent = Descent: -font.leading = Leading: -font.characters = Characters: -font.characters.add = Add characters: -value.unknown = ? - -yes = yes -no = no - -errors.present = There are ERRORS in the log. Click to view. -errors.none = There are no errors in the log. - -#after version 1.6.6: - -dialog.message.title = Message -dialog.select.title = Select an Option - -button.yes = Yes -button.no = No - -FileChooser.openButtonText = Open -FileChooser.openButtonToolTipText = Open -FileChooser.lookInLabelText = Look in: -FileChooser.acceptAllFileFilterText = All Files -FileChooser.filesOfTypeLabelText = Files of type: -FileChooser.fileNameLabelText = File name: -FileChooser.listViewButtonToolTipText = List -FileChooser.listViewButtonAccessibleName = List -FileChooser.detailsViewButtonToolTipText = Details -FileChooser.detailsViewButtonAccessibleName = Details -FileChooser.upFolderToolTipText = Up One Level -FileChooser.upFolderAccessibleName = Up One Level -FileChooser.homeFolderToolTipText = Home -FileChooser.homeFolderAccessibleName = Home -FileChooser.fileNameHeaderText = Name -FileChooser.fileSizeHeaderText = Size -FileChooser.fileTypeHeaderText = Type -FileChooser.fileDateHeaderText = Date -FileChooser.fileAttrHeaderText = Attributes -FileChooser.openDialogTitleText = Open -FileChooser.directoryDescriptionText = Directory -FileChooser.directoryOpenButtonText = Open -FileChooser.directoryOpenButtonToolTipText = Open selected directory -FileChooser.fileDescriptionText = Generic File -FileChooser.helpButtonText = Help -FileChooser.helpButtonToolTipText = FileChooser help -FileChooser.newFolderAccessibleName = New Folder -FileChooser.newFolderErrorText = Error creating new folder -FileChooser.newFolderToolTipText = Create New Folder -FileChooser.other.newFolder = NewFolder -FileChooser.other.newFolder.subsequent = NewFolder.{0} -FileChooser.win32.newFolder = New Folder -FileChooser.win32.newFolder.subsequent = New Folder ({0}) -FileChooser.saveButtonText = Save -FileChooser.saveButtonToolTipText = Save selected file -FileChooser.saveDialogTitleText = Save -FileChooser.saveInLabelText = Save in: -FileChooser.updateButtonText = Update -FileChooser.updateButtonToolTipText = Update directory listing - -#after version 1.6.6u2: - -FileChooser.detailsViewActionLabel.textAndMnemonic = Details -FileChooser.detailsViewButtonToolTip.textAndMnemonic = Details -FileChooser.fileAttrHeader.textAndMnemonic = Attributes -FileChooser.fileDateHeader.textAndMnemonic = Modified -FileChooser.fileNameHeader.textAndMnemonic = Name -FileChooser.fileNameLabel.textAndMnemonic = File name: -FileChooser.fileSizeHeader.textAndMnemonic = Size -FileChooser.fileTypeHeader.textAndMnemonic = Type -FileChooser.filesOfTypeLabel.textAndMnemonic = Files of type: -FileChooser.folderNameLabel.textAndMnemonic = Folder name: -FileChooser.homeFolderToolTip.textAndMnemonic = Home -FileChooser.listViewActionLabel.textAndMnemonic = List -FileChooser.listViewButtonToolTip.textAndMnemonic = List -FileChooser.lookInLabel.textAndMnemonic = Look in: -FileChooser.newFolderActionLabel.textAndMnemonic = New Folder -FileChooser.newFolderToolTip.textAndMnemonic = Create New Folder -FileChooser.refreshActionLabel.textAndMnemonic = Refresh -FileChooser.saveInLabel.textAndMnemonic = Save in: -FileChooser.upFolderToolTip.textAndMnemonic = Up One Level -FileChooser.viewMenuButtonAccessibleName = View Menu -FileChooser.viewMenuButtonToolTipText = View Menu -FileChooser.viewMenuLabel.textAndMnemonic = View -FileChooser.newFolderActionLabelText = New Folder -FileChooser.listViewActionLabelText = List -FileChooser.detailsViewActionLabelText = Details -FileChooser.refreshActionLabelText = Refresh -FileChooser.sortMenuLabelText = Arrange Icons By -FileChooser.viewMenuLabelText = View -FileChooser.fileSizeKiloBytes = {0} KB -FileChooser.fileSizeMegaBytes = {0} MB -FileChooser.fileSizeGigaBytes = {0} GB -FileChooser.folderNameLabelText = Folder name: - -error.occured = Error occurred: %error% -button.abort = Abort -button.retry = Retry -button.ignore = Ignore - -font.source = Source Font: - -#after version 1.6.7: - -menu.export = Export -menu.general = General -menu.language = Language - -startup.welcometo = Welcome to -startup.selectopen = Click Open icon on the top panel or drag SWF file to this window to start. - -error.font.nocharacter = Selected source font does not contain character "%char%". - -warning.initializers = Static fields and consts are often initialized in initializers.\nEditing value here is usually not enough! - -#after version 1.7.0u1: - -menu.tools.searchMemory = Search SWFs in memory -menu.file.reload = Reload -message.confirm.reload = This action cancels all unsaved changes and reloads the SWF file again.\nDo you want to continue? - -dialog.selectbkcolor.title = Select background color for SWF display -button.selectbkcolor.hint = Select background color - -ColorChooser.okText = OK -ColorChooser.cancelText = Cancel -ColorChooser.resetText = Reset -ColorChooser.previewText = Preview -ColorChooser.swatchesNameText = Swatches -ColorChooser.swatchesRecentText = Recent: -ColorChooser.sampleText = Sample Text Sample Text - -#after version 1.7.1: - -preview.play = Play -preview.pause = Pause -preview.stop = Stop - -message.confirm.removemultiple = Are you sure you want to remove %count% items\n and all objects which depend on it? - -menu.tools.searchCache = Search browsers cache - -#after version 1.7.2u2 - -error.trait.exists = Trait with name "%name%" already exists. -button.addtrait = Add trait -button.font.embed = Embed... -button.yes.all = Yes to all -button.no.all = No to all -message.font.add.exists = Character %char% already exists in the font tag.\nDo you want to replace it? - -filter.gfx = ScaleForm GFx files (*.gfx) -filter.supported = All supported filetypes -work.canceled = Canceled -work.restoringControlFlow = Restoring control flow -menu.advancedsettings.advancedsettings = Advanced Settings -menu.recentFiles = Recent files - -#after version 1.7.4 -work.restoringControlFlow.complete = Control flow restored -message.confirm.recentFileNotFound = File not found. Do you want to remove it from the recent file list? -contextmenu.closeSwf = Close SWF -menu.settings.autoRenameIdentifiers = Auto rename identifiers -menu.file.saveasexe = Save as Exe... -filter.exe = Executable files (*.exe) - -#after version 1.8.0 -font.updateTexts = Update texts - -#after version 1.8.0u1 -menu.file.close = Close -menu.file.closeAll = Close all -menu.tools.otherTools = Other -menu.tools.otherTools.clearRecentFiles = Clear recent files -fontName.name = Font display name: -fontName.copyright = Font copyright: -button.preview = Preview -button.reset = Reset -errors.info = There are INFORMATIONS in the log. Click to view. -errors.warning = There are WARNINGS in the log. Click to view. - -decompilationError = Decompilation error - -disassemblingProgress.toString = toString -disassemblingProgress.reading = Reading -disassemblingProgress.deobfuscating = Deobfuscating - -contextmenu.moveTag = Move tag to - -filter.swc = SWC component files (*.swc) -filter.zip = ZIP compressed files (*.zip) -filter.binary = Binary search - all files (*.*) - -open.error = Error -open.error.fileNotFound = File not found -open.error.cannotOpen = Cannot open file - -node.others = others - -#after version 1.8.1 -menu.tools.search = Text Search - -#after version 1.8.1u1 -menu.tools.timeline = Timeline - -dialog.selectcolor.title = Select color -button.selectcolor.hint = Click to select color - -#default item name, will be used in following sentences -generictag.array.item = item -generictag.array.insertbeginning = Insert %item% at the beginning -generictag.array.insertbefore = Insert %item% before -generictag.array.remove = Remove %item% -generictag.array.insertafter = Insert %item% after -generictag.array.insertend = Insert %item% at the end - -#after version 2.0.0 -contextmenu.expandAll = Expand all - -filter.sounds = Supported sound formats (*.wav, *.mp3) -filter.sounds.wav = Wave file format (*.wav) -filter.sounds.mp3 = MP3 compressed format (*.mp3) - -error.sound.invalid = Invalid sound. - -button.prev = Previous -button.next = Next - -#after version 2.1.0 -message.action.playerglobal.title = PlayerGlobal library needed -message.action.playerglobal.needed = For ActionScript 3 direct editation, a library called "PlayerGlobal.swc" needs to be downloaded from Adobe homepage.\r\n%adobehomepage%\r\nPress OK to go to the download page. -message.action.playerglobal.place = Download the library called PlayerGlobal(.swc), and place it to directory\r\n%libpath%\r\n Press OK to continue. - -message.confirm.experimental.function = This function is EXPERIMENTAL. It means that you should not trust the results and the SWF file can be disfunctional after saving. -message.confirm.donotshowagain = Do not show again - -menu.import = Import -menu.file.import.text = Import text -import.select.directory = Select directory to import -error.text.import = Error during text import. Do you want to continue? - -#after version 2.1.1 -contextmenu.removeWithDependencies = Remove with dependencies - -abc.action.find-usages = Find usages -abc.action.find-declaration = Find declaration - -contextmenu.rawEdit = Raw edit -contextmenu.jumpToCharacter = Jump to character - -menu.settings.dumpView = Dump view - -menu.view = View -menu.file.view.resources = Resources -menu.file.view.hex = Hex dump - -node.header = header - -header.signature = Signature: -header.compression = Compression: -header.compression.lzma = LZMA -header.compression.zlib = ZLIB -header.compression.none = No compression -header.version = SWF Version: -header.gfx = GFX: -header.filesize = File size: -header.framerate = Frame rate: -header.framecount = Frame count: -header.displayrect = Display rect: -header.displayrect.value.twips = %xmin%,%ymin% => %xmax%,%ymax% twips -header.displayrect.value.pixels = %xmin%,%ymin% => %xmax%,%ymax% pixels - -#after version 2.1.2 -contextmenu.saveToFile = Save to File -contextmenu.parseActions = Parse actions -contextmenu.parseABC = Parse ABC -contextmenu.parseInstructions = Parse AVM2 Instructions - -#after version 2.1.3 -menu.deobfuscation = Deobfuscation -menu.file.deobfuscation.old = Old style -menu.file.deobfuscation.new = New style - -#after version 2.1.4 -contextmenu.openswfinside = Open SWF inside -binarydata.swfInside = It looks like there is SWF inside this binary data tag. Click here to load it as subtree. - -#after version 3.0.0 -button.zoomin.hint = Zoom in -button.zoomout.hint = Zoom out -button.zoomfit.hint = Zoom to fit -button.zoomnone.hint = Zoom to 1:1 -button.snapshot.hint = Take snapshot into clipboard - -editorTruncateWarning = Text truncated at position %chars% in debug mode. - -#Font name which is presented in the SWF Font tag -font.name.intag = Font name in tag: - -menu.debugger = Debugger -menu.debugger.switch = Debugger -menu.debugger.replacetrace = Replace trace calls -menu.debugger.showlog = Show Log - -message.debugger = This SWF Debugger can only be used to print messages to log window, browser console or alerts.\r\nIt is NOT designed for features like step code, breakpoints etc. - -contextmenu.addTag = Add tag - -deobfuscation.comment.tryenable = Tip: You can try enabling "Automatic deobfuscation" in Settings -deobfuscation.comment.failed = Deobfuscation is activated but decompilation still failed. If the file is NOT obfuscated, disable "Automatic deobfuscation" for better results. - -#after version 4.0.2 -preview.nextframe = Next frame -preview.prevframe = Previous frame -preview.gotoframe = Goto frame... - -preview.gotoframe.dialog.title = Goto frame -preview.gotoframe.dialog.message = Enter frame number (%min% - %max%) -preview.gotoframe.dialog.frame.error = Invalid frame number. It must be number between %min% and %max%. - -error.text.invalid.continue = Invalid text: %text% on line %line%. Do you want to continue? - -#after version 4.0.5 -contextmenu.copyTag = Copy tag to -fit = fit -button.setAdvanceValues = Set advance values - -menu.tools.replace = Text Replace - -message.confirm.close = There are unsaved changes. Do you really want to close {swfName}? -message.confirm.closeAll = There are unsaved changes. Do you really want to close all SWFs? - -contextmenu.exportJavaSource = Export Java Source -contextmenu.exportSwfXml = Export SWF as XML -contextmenu.importSwfXml = Import SWF XML - -filter.xml = XML - -#after version 4.1.0 -contextmenu.undo = Undo - -text.align.left = Left align -text.align.right = Right align -text.align.center = Center align -text.align.justify = Justify align - -text.undo = Undo changes - -menu.file.import.xml = Import SWF XML -menu.file.export.xml = Export SWF XML - -#after version 4.1.1 -text.align.translatex.decrease = Decrease TranslateX -text.align.translatex.increase = Increase TranslateX -selectPreviousTag = Select previous tag -selectNextTag = Select next tag -button.ignoreAll = Ignore All -menu.file.import.symbolClass = Import Symbol-Class -text.toggleCase = Toggle case - -#after version 5.0.2 -preview.loop = Loop -menu.file.import.script = Import script -contextmenu.copyTagWithDependencies = Copy tag with dependencies to -button.replaceWithTag = Replace with other character tag -button.resolveConstants = Resolve constants - -#after version 5.1.0 -button.viewConstants = View Constants -work.exported = Exported -button.replaceAlphaChannel = Replace alpha channel... - -tagInfo.header.name = Name -tagInfo.header.value = Value -tagInfo.tagType = Tag Type -tagInfo.characterId = Character Id -tagInfo.offset = Offset -tagInfo.length = Length -tagInfo.bounds = Bounds -tagInfo.width = Width -tagInfo.height = Height -tagInfo.neededCharacters = Needed Characters - -button.viewhexpcode = View Hex with instructions -taginfo.header = Basic tag info - -tagInfo.dependentCharacters = Dependent Characters - -#after version 5.3.0 -header.uncompressed = Uncompressed -header.warning.unsupportedGfxCompression = GFX supports only uncompressed or Zlib compressed content. -header.warning.minimumZlibVersion = Zlib compression needs SWF version 6 or greater. -header.warning.minimumLzmaVersion = LZMA compression needs SWF version 13 or greater. - -tagInfo.codecName = Codec Name -tagInfo.exportFormat = Export Format -tagInfo.samplingRate = Sampling Rate -tagInfo.stereo = Stereo -tagInfo.sampleCount = Sample Count - -filter.dmg = Mac Executable files (*.dmg) -filter.linuxExe = Linux Executable files - -import.script.result = %count% scripts imported. -import.script.as12warning = Import script can import only AS1/2 scripts. - -error.constantPoolTooBig = Constant pool is too big. index=%index%, size=%size% -error.image.alpha.invalid = Invalid alpha channel data. - -#after version 6.0.2 -contextmenu.saveUncompressedToFile = Save to Uncompressed File -abc.traitslist.scriptinitializer = script initializer -menu.settings.autoOpenLoadedSWFs = Open loaded SWFs while playing - -#after version 6.1.1 -menu.file.start = Start -menu.file.start.run = Run -menu.file.start.stop = Stop -menu.file.start.debug = Debug -menu.debugging = Debugging -menu.debugging.debug = Debug -menu.debugging.debug.stop = Stop -menu.debugging.debug.pause = Pause -menu.debugging.debug.stepOver = Step over -menu.debugging.debug.stepInto = Step into -menu.debugging.debug.stepOut = Step out -menu.debugging.debug.continue = Continue -menu.debugging.debug.stack = Stack... -menu.debugging.debug.watch = New watch... - -message.playerpath.notset = Flash Player projector not found. Please configure its path in Advanced Settings / Paths (1). -message.playerpath.debug.notset = Flash Player projector content debugger not found. Please configure its path in Advanced Settings / Paths (2). -message.playerpath.lib.notset = PlayerGlobal (.SWC) not found. Please configure its path in Advanced Settings / Paths (3). - -debugpanel.header = Debugging - -variables.header.registers = Registers -variables.header.locals = Locals -variables.header.arguments = Arguments -variables.header.scopeChain = Scope chain -variables.column.name = Name -variables.column.type = Type -variables.column.value = Value - -callStack.header = Call stack -callStack.header.file = File -callStack.header.line = Line - -stack.header = Stack -stack.header.item = Item - -constantpool.header = Constant pool -constantpool.header.id = Id -constantpool.header.value = Value - -work.running = Running -work.debugging = Debugging -work.debugging.instrumenting = Preparing SWF for debugging -work.breakat = Break at\u0020 -work.halted = Debugging started, execution halted. Add breakpoints and click Continue (F5) to resume running. - -debuglog.header = Log -debuglog.button.clear = Clear - -#after 7.0.1 -work.debugging.wait = Waiting for Flash debug projector to connect - -error.debug.listen = Cannot listen on port %port%. There might be other flash debugger running. - -debug.break.reason.unknown = (Unknown) -debug.break.reason.breakpoint = (Breakpoint) -debug.break.reason.watch = (Watch) -debug.break.reason.fault = (Fault) -debug.break.reason.stopRequest = (Stop request) -debug.break.reason.step = (Step) -debug.break.reason.halt = (Halt) -debug.break.reason.scriptLoaded = (Script loaded) - -menu.file.start.debugpcode = Debug P-code - -#after 7.1.2 -button.replaceNoFill = Replace - Update bounds... -message.warning.svgImportExperimental = Not all SVG features are supported. Please check the log after import. - -message.imported.swf = The SWF file uses assets from an imported SWF file:\n%url%\nDo you want the assets to be loaded from that URL? -message.imported.swf.manually = Cannot load imported SWF\n%url%\nThe file or URL does not exist.\nDo you want to select local file? - -message.warning.hexViewNotUpToDate = Hex View is not up-to-date. Please save and reload the file to update Hex View. -message.font.replace.updateTexts = Some characters were replaced. Do you want to update the existing texts? - -menu.settings.simplifyExpressions = Simplify expressions - -#after 8.0.1 -menu.recentFiles.empty = Recent file list is empty -message.warning.outOfMemory32BitJre = OutOfMemory error occurred. You are running 32bit Java on 64bit system. Please use 64bit Java. - -menu.file.reloadAll = Reload all -message.confirm.reloadAll = This action cancels all unsaved changes in all SWF files and reloads whole application again.\nDo you want to continue? -export.script.singleFilePallelModeWarning = Single file script export is not supported with enabled parallel speedup - -button.showOriginalBytesInPcodeHex = Show original bytes +# Copyright (C) 2010-2016 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 . + +menu.file = File +menu.file.open = Open... +menu.file.save = Save +menu.file.saveas = Save as... +menu.file.export.fla = Export to FLA +menu.file.export.all = Export all parts +menu.file.export.selection = Export selection +menu.file.exit = Exit + +menu.tools = Tools +menu.tools.searchas = Search All ActionScript... +menu.tools.proxy = Proxy +menu.tools.deobfuscation = Deobfuscation +menu.tools.deobfuscation.pcode = P-code deobfuscation... +menu.tools.deobfuscation.globalrename = Globally rename identifier +menu.tools.deobfuscation.renameinvalid = Rename invalid identifiers +menu.tools.gotoDocumentClass = Go to document class + +menu.settings = Settings +menu.settings.autodeobfuscation = Automatic deobfuscation +menu.settings.internalflashviewer = Use own Flash viewer +menu.settings.parallelspeedup = Parallel SpeedUp +menu.settings.disabledecompilation = Disable decompilation (Disassemble only) +menu.settings.addtocontextmenu = Add FFDec to SWF files context menu +menu.settings.language = Change language +menu.settings.cacheOnDisk = Use caching on disk +menu.settings.gotoMainClassOnStartup = Highlight document class on startup + +menu.help = Help +menu.help.checkupdates = Check for updates... +menu.help.helpus = Help us! +menu.help.homepage = Visit homepage +menu.help.about = About... + +contextmenu.remove = Remove + +button.save = Save +button.edit = Edit +button.cancel = Cancel +button.replace = Replace... + +notavailonthisplatform = Preview of this object is not available on this platform (Windows only). + +swfpreview = SWF preview +swfpreview.internal = SWF preview (Internal viewer) + +parameters = Parameters + +rename.enternew = Enter new name: + +rename.finished.identifier = Identifier renamed. +rename.finished.multiname = %count% multiname(s) renamed. + +node.texts = texts +node.images = images +node.movies = movies +node.sounds = sounds +node.binaryData = binaryData +node.fonts = fonts +node.sprites = sprites +node.shapes = shapes +node.morphshapes = morphshapes +node.buttons = buttons +node.frames = frames +node.scripts = scripts + +message.warning = Warning +message.confirm.experimental = Following procedure can damage SWF file which can be then unplayable.\r\nUSE IT ON YOUR OWN RISK. Do you want to continue? +message.confirm.parallel = Parallelism can speed up loading and decompilation but uses more memory. +message.confirm.on = Do you want to turn this ON? +message.confirm.off = Do you want to turn this OFF? +message.confirm = Confirm + +message.confirm.autodeobfuscate = 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. + +message.parallel = Parallelism +message.trait.saved = Trait successfully saved + +message.constant.new.string = String "%value%" is not present in constants table. Do you want to add it? +message.constant.new.string.title = Add String +message.constant.new.integer = Integer value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.integer.title = Add Integer +message.constant.new.unsignedinteger = Unsigned integer value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.unsignedinteger.title = Add Unsigned integer +message.constant.new.double = Double value "%value%" is not present in constants table. Do you want to add it? +message.constant.new.double.title = Add Double + +work.buffering = Buffering +work.waitingfordissasembly = Waiting for disassembly +work.gettinghilights = Getting highlights +work.disassembling = Disassembling +work.exporting = Exporting +work.searching = Searching +work.renaming = Renaming +work.exporting.fla = Exporting FLA +work.renaming.identifiers = Renaming identifiers +work.deobfuscating = Deobfuscating +work.decompiling = Decompiling +work.gettingvariables = Getting variables +work.reading.swf = Reading SWF +work.creatingwindow = Creating window +work.buildingscripttree = Building script tree + +work.deobfuscating.complete = Deobfuscation complete + +message.search.notfound = String "%searchtext%" not found. +message.search.notfound.title = Not found + +message.rename.notfound.multiname = No multiname found under cursor +message.rename.notfound.identifier = No identifier found under cursor +message.rename.notfound.title = Not found +message.rename.renamed = Identifiers renamed: %count% + +filter.images = Images (%extensions%) +filter.fla = %version% Document (*.fla) +filter.xfl = %version% Uncompressed Document (*.xfl) +filter.swf = SWF files (*.swf) + +error = Error +error.image.invalid = Invalid image. + +error.text.invalid = Invalid text: %text% on line %line% +error.file.save = Cannot save file +error.file.write = Cannot write to the file +error.export = Error during export + +export.select.directory = Select directory to export +export.finishedin = Exported in %time% + +update.check.title = Update check +update.check.nonewversion = No new version available. + +message.helpus = Please visit\r\n%url%\r\nfor details. +message.homepage = Visit homepage at: \r\n%url% + +proxy = Proxy +proxy.start = Start proxy +proxy.stop = Stop proxy +proxy.show = Show proxy +exit = Exit + +panel.disassembled = P-code source +panel.decompiled = ActionScript source + +search.info = Search for "%text%": +search.script = Script + +constants = Constants +traits = Traits + +pleasewait = Please wait + +abc.detail.methodtrait = Method/Getter/Setter Trait +abc.detail.unsupported = - +abc.detail.slotconsttrait = Slot/Const Trait +abc.detail.traitname = Name: + +abc.detail.body.params.maxstack = Max stack: +abc.detail.body.params.localregcount = Local registers count: +abc.detail.body.params.minscope = Minimum scope depth: +abc.detail.body.params.maxscope = Maximum scope depth: +abc.detail.body.params.autofill = Auto fill on code save (GLOBAL SETTING) +abc.detail.body.params.autofill.experimental = ...EXPERIMENTAL + +abc.detail.methodinfo.methodindex = Method Index: +abc.detail.methodinfo.parameters = Parameters: +abc.detail.methodinfo.returnvalue = Return value type: + +error.methodinfo.params = MethodInfo Params Error +error.methodinfo.returnvalue = MethodInfo Return value type Error + +abc.detail.methodinfo = MethodInfo +abc.detail.body.code = MethodBody Code +abc.detail.body.params = MethodBody params + +abc.detail.slotconst.typevalue = Type and Value: + +error.slotconst.typevalue = SlotConst type value Error + +message.autofill.failed = Cannot get code stats for automatic body params.\r\nUncheck autofill to avoid this message. +info.selecttrait = Select class and click a trait in Actionscript source to edit it. + +button.viewgraph = View Graph +button.viewhex = View Hex + +abc.traitslist.instanceinitializer = instance initializer +abc.traitslist.classinitializer = class initializer + +action.edit.experimental = (Experimental) + +message.action.saved = Code successfully saved + +error.action.save = %error% on line %line% + +message.confirm.remove = Are you sure you want to remove %item%\n and all objects which depend on it? + +#after version 1.6.5u1: + +button.ok = OK +button.cancel = Cancel + +font.name = Font name: +font.isbold = Is bold: +font.isitalic = Is italic: +font.ascent = Ascent: +font.descent = Descent: +font.leading = Leading: +font.characters = Characters: +font.characters.add = Add characters: +value.unknown = ? + +yes = yes +no = no + +errors.present = There are ERRORS in the log. Click to view. +errors.none = There are no errors in the log. + +#after version 1.6.6: + +dialog.message.title = Message +dialog.select.title = Select an Option + +button.yes = Yes +button.no = No + +FileChooser.openButtonText = Open +FileChooser.openButtonToolTipText = Open +FileChooser.lookInLabelText = Look in: +FileChooser.acceptAllFileFilterText = All Files +FileChooser.filesOfTypeLabelText = Files of type: +FileChooser.fileNameLabelText = File name: +FileChooser.listViewButtonToolTipText = List +FileChooser.listViewButtonAccessibleName = List +FileChooser.detailsViewButtonToolTipText = Details +FileChooser.detailsViewButtonAccessibleName = Details +FileChooser.upFolderToolTipText = Up One Level +FileChooser.upFolderAccessibleName = Up One Level +FileChooser.homeFolderToolTipText = Home +FileChooser.homeFolderAccessibleName = Home +FileChooser.fileNameHeaderText = Name +FileChooser.fileSizeHeaderText = Size +FileChooser.fileTypeHeaderText = Type +FileChooser.fileDateHeaderText = Date +FileChooser.fileAttrHeaderText = Attributes +FileChooser.openDialogTitleText = Open +FileChooser.directoryDescriptionText = Directory +FileChooser.directoryOpenButtonText = Open +FileChooser.directoryOpenButtonToolTipText = Open selected directory +FileChooser.fileDescriptionText = Generic File +FileChooser.helpButtonText = Help +FileChooser.helpButtonToolTipText = FileChooser help +FileChooser.newFolderAccessibleName = New Folder +FileChooser.newFolderErrorText = Error creating new folder +FileChooser.newFolderToolTipText = Create New Folder +FileChooser.other.newFolder = NewFolder +FileChooser.other.newFolder.subsequent = NewFolder.{0} +FileChooser.win32.newFolder = New Folder +FileChooser.win32.newFolder.subsequent = New Folder ({0}) +FileChooser.saveButtonText = Save +FileChooser.saveButtonToolTipText = Save selected file +FileChooser.saveDialogTitleText = Save +FileChooser.saveInLabelText = Save in: +FileChooser.updateButtonText = Update +FileChooser.updateButtonToolTipText = Update directory listing + +#after version 1.6.6u2: + +FileChooser.detailsViewActionLabel.textAndMnemonic = Details +FileChooser.detailsViewButtonToolTip.textAndMnemonic = Details +FileChooser.fileAttrHeader.textAndMnemonic = Attributes +FileChooser.fileDateHeader.textAndMnemonic = Modified +FileChooser.fileNameHeader.textAndMnemonic = Name +FileChooser.fileNameLabel.textAndMnemonic = File name: +FileChooser.fileSizeHeader.textAndMnemonic = Size +FileChooser.fileTypeHeader.textAndMnemonic = Type +FileChooser.filesOfTypeLabel.textAndMnemonic = Files of type: +FileChooser.folderNameLabel.textAndMnemonic = Folder name: +FileChooser.homeFolderToolTip.textAndMnemonic = Home +FileChooser.listViewActionLabel.textAndMnemonic = List +FileChooser.listViewButtonToolTip.textAndMnemonic = List +FileChooser.lookInLabel.textAndMnemonic = Look in: +FileChooser.newFolderActionLabel.textAndMnemonic = New Folder +FileChooser.newFolderToolTip.textAndMnemonic = Create New Folder +FileChooser.refreshActionLabel.textAndMnemonic = Refresh +FileChooser.saveInLabel.textAndMnemonic = Save in: +FileChooser.upFolderToolTip.textAndMnemonic = Up One Level +FileChooser.viewMenuButtonAccessibleName = View Menu +FileChooser.viewMenuButtonToolTipText = View Menu +FileChooser.viewMenuLabel.textAndMnemonic = View +FileChooser.newFolderActionLabelText = New Folder +FileChooser.listViewActionLabelText = List +FileChooser.detailsViewActionLabelText = Details +FileChooser.refreshActionLabelText = Refresh +FileChooser.sortMenuLabelText = Arrange Icons By +FileChooser.viewMenuLabelText = View +FileChooser.fileSizeKiloBytes = {0} KB +FileChooser.fileSizeMegaBytes = {0} MB +FileChooser.fileSizeGigaBytes = {0} GB +FileChooser.folderNameLabelText = Folder name: + +error.occured = Error occurred: %error% +button.abort = Abort +button.retry = Retry +button.ignore = Ignore + +font.source = Source Font: + +#after version 1.6.7: + +menu.export = Export +menu.general = General +menu.language = Language + +startup.welcometo = Welcome to +startup.selectopen = Click Open icon on the top panel or drag SWF file to this window to start. + +error.font.nocharacter = Selected source font does not contain character "%char%". + +warning.initializers = Static fields and consts are often initialized in initializers.\nEditing value here is usually not enough! + +#after version 1.7.0u1: + +menu.tools.searchMemory = Search SWFs in memory +menu.file.reload = Reload +message.confirm.reload = This action cancels all unsaved changes and reloads the SWF file again.\nDo you want to continue? + +dialog.selectbkcolor.title = Select background color for SWF display +button.selectbkcolor.hint = Select background color + +ColorChooser.okText = OK +ColorChooser.cancelText = Cancel +ColorChooser.resetText = Reset +ColorChooser.previewText = Preview +ColorChooser.swatchesNameText = Swatches +ColorChooser.swatchesRecentText = Recent: +ColorChooser.sampleText = Sample Text Sample Text + +#after version 1.7.1: + +preview.play = Play +preview.pause = Pause +preview.stop = Stop + +message.confirm.removemultiple = Are you sure you want to remove %count% items\n and all objects which depend on it? + +menu.tools.searchCache = Search browsers cache + +#after version 1.7.2u2 + +error.trait.exists = Trait with name "%name%" already exists. +button.addtrait = Add trait +button.font.embed = Embed... +button.yes.all = Yes to all +button.no.all = No to all +message.font.add.exists = Character %char% already exists in the font tag.\nDo you want to replace it? + +filter.gfx = ScaleForm GFx files (*.gfx) +filter.supported = All supported filetypes +work.canceled = Canceled +work.restoringControlFlow = Restoring control flow +menu.advancedsettings.advancedsettings = Advanced Settings +menu.recentFiles = Recent files + +#after version 1.7.4 +work.restoringControlFlow.complete = Control flow restored +message.confirm.recentFileNotFound = File not found. Do you want to remove it from the recent file list? +contextmenu.closeSwf = Close SWF +menu.settings.autoRenameIdentifiers = Auto rename identifiers +menu.file.saveasexe = Save as Exe... +filter.exe = Executable files (*.exe) + +#after version 1.8.0 +font.updateTexts = Update texts + +#after version 1.8.0u1 +menu.file.close = Close +menu.file.closeAll = Close all +menu.tools.otherTools = Other +menu.tools.otherTools.clearRecentFiles = Clear recent files +fontName.name = Font display name: +fontName.copyright = Font copyright: +button.preview = Preview +button.reset = Reset +errors.info = There are INFORMATIONS in the log. Click to view. +errors.warning = There are WARNINGS in the log. Click to view. + +decompilationError = Decompilation error + +disassemblingProgress.toString = toString +disassemblingProgress.reading = Reading +disassemblingProgress.deobfuscating = Deobfuscating + +contextmenu.moveTag = Move tag to + +filter.swc = SWC component files (*.swc) +filter.zip = ZIP compressed files (*.zip) +filter.binary = Binary search - all files (*.*) + +open.error = Error +open.error.fileNotFound = File not found +open.error.cannotOpen = Cannot open file + +node.others = others + +#after version 1.8.1 +menu.tools.search = Text Search + +#after version 1.8.1u1 +menu.tools.timeline = Timeline + +dialog.selectcolor.title = Select color +button.selectcolor.hint = Click to select color + +#default item name, will be used in following sentences +generictag.array.item = item +generictag.array.insertbeginning = Insert %item% at the beginning +generictag.array.insertbefore = Insert %item% before +generictag.array.remove = Remove %item% +generictag.array.insertafter = Insert %item% after +generictag.array.insertend = Insert %item% at the end + +#after version 2.0.0 +contextmenu.expandAll = Expand all + +filter.sounds = Supported sound formats (*.wav, *.mp3) +filter.sounds.wav = Wave file format (*.wav) +filter.sounds.mp3 = MP3 compressed format (*.mp3) + +error.sound.invalid = Invalid sound. + +button.prev = Previous +button.next = Next + +#after version 2.1.0 +message.action.playerglobal.title = PlayerGlobal library needed +message.action.playerglobal.needed = For ActionScript 3 direct editation, a library called "PlayerGlobal.swc" needs to be downloaded from Adobe homepage.\r\n%adobehomepage%\r\nPress OK to go to the download page. +message.action.playerglobal.place = Download the library called PlayerGlobal(.swc), and place it to directory\r\n%libpath%\r\n Press OK to continue. + +message.confirm.experimental.function = This function is EXPERIMENTAL. It means that you should not trust the results and the SWF file can be disfunctional after saving. +message.confirm.donotshowagain = Do not show again + +menu.import = Import +menu.file.import.text = Import text +import.select.directory = Select directory to import +error.text.import = Error during text import. Do you want to continue? + +#after version 2.1.1 +contextmenu.removeWithDependencies = Remove with dependencies + +abc.action.find-usages = Find usages +abc.action.find-declaration = Find declaration + +contextmenu.rawEdit = Raw edit +contextmenu.jumpToCharacter = Jump to character + +menu.settings.dumpView = Dump view + +menu.view = View +menu.file.view.resources = Resources +menu.file.view.hex = Hex dump + +node.header = header + +header.signature = Signature: +header.compression = Compression: +header.compression.lzma = LZMA +header.compression.zlib = ZLIB +header.compression.none = No compression +header.version = SWF Version: +header.gfx = GFX: +header.filesize = File size: +header.framerate = Frame rate: +header.framecount = Frame count: +header.displayrect = Display rect: +header.displayrect.value.twips = %xmin%,%ymin% => %xmax%,%ymax% twips +header.displayrect.value.pixels = %xmin%,%ymin% => %xmax%,%ymax% pixels + +#after version 2.1.2 +contextmenu.saveToFile = Save to File +contextmenu.parseActions = Parse actions +contextmenu.parseABC = Parse ABC +contextmenu.parseInstructions = Parse AVM2 Instructions + +#after version 2.1.3 +menu.deobfuscation = Deobfuscation +menu.file.deobfuscation.old = Old style +menu.file.deobfuscation.new = New style + +#after version 2.1.4 +contextmenu.openswfinside = Open SWF inside +binarydata.swfInside = It looks like there is SWF inside this binary data tag. Click here to load it as subtree. + +#after version 3.0.0 +button.zoomin.hint = Zoom in +button.zoomout.hint = Zoom out +button.zoomfit.hint = Zoom to fit +button.zoomnone.hint = Zoom to 1:1 +button.snapshot.hint = Take snapshot into clipboard + +editorTruncateWarning = Text truncated at position %chars% in debug mode. + +#Font name which is presented in the SWF Font tag +font.name.intag = Font name in tag: + +menu.debugger = Debugger +menu.debugger.switch = Debugger +menu.debugger.replacetrace = Replace trace calls +menu.debugger.showlog = Show Log + +message.debugger = This SWF Debugger can only be used to print messages to log window, browser console or alerts.\r\nIt is NOT designed for features like step code, breakpoints etc. + +contextmenu.addTag = Add tag + +deobfuscation.comment.tryenable = Tip: You can try enabling "Automatic deobfuscation" in Settings +deobfuscation.comment.failed = Deobfuscation is activated but decompilation still failed. If the file is NOT obfuscated, disable "Automatic deobfuscation" for better results. + +#after version 4.0.2 +preview.nextframe = Next frame +preview.prevframe = Previous frame +preview.gotoframe = Goto frame... + +preview.gotoframe.dialog.title = Goto frame +preview.gotoframe.dialog.message = Enter frame number (%min% - %max%) +preview.gotoframe.dialog.frame.error = Invalid frame number. It must be number between %min% and %max%. + +error.text.invalid.continue = Invalid text: %text% on line %line%. Do you want to continue? + +#after version 4.0.5 +contextmenu.copyTag = Copy tag to +fit = fit +button.setAdvanceValues = Set advance values + +menu.tools.replace = Text Replace + +message.confirm.close = There are unsaved changes. Do you really want to close {swfName}? +message.confirm.closeAll = There are unsaved changes. Do you really want to close all SWFs? + +contextmenu.exportJavaSource = Export Java Source +contextmenu.exportSwfXml = Export SWF as XML +contextmenu.importSwfXml = Import SWF XML + +filter.xml = XML + +#after version 4.1.0 +contextmenu.undo = Undo + +text.align.left = Left align +text.align.right = Right align +text.align.center = Center align +text.align.justify = Justify align + +text.undo = Undo changes + +menu.file.import.xml = Import SWF XML +menu.file.export.xml = Export SWF XML + +#after version 4.1.1 +text.align.translatex.decrease = Decrease TranslateX +text.align.translatex.increase = Increase TranslateX +selectPreviousTag = Select previous tag +selectNextTag = Select next tag +button.ignoreAll = Ignore All +menu.file.import.symbolClass = Import Symbol-Class +text.toggleCase = Toggle case + +#after version 5.0.2 +preview.loop = Loop +menu.file.import.script = Import script +contextmenu.copyTagWithDependencies = Copy tag with dependencies to +button.replaceWithTag = Replace with other character tag +button.resolveConstants = Resolve constants + +#after version 5.1.0 +button.viewConstants = View Constants +work.exported = Exported +button.replaceAlphaChannel = Replace alpha channel... + +tagInfo.header.name = Name +tagInfo.header.value = Value +tagInfo.tagType = Tag Type +tagInfo.characterId = Character Id +tagInfo.offset = Offset +tagInfo.length = Length +tagInfo.bounds = Bounds +tagInfo.width = Width +tagInfo.height = Height +tagInfo.neededCharacters = Needed Characters + +button.viewhexpcode = View Hex with instructions +taginfo.header = Basic tag info + +tagInfo.dependentCharacters = Dependent Characters + +#after version 5.3.0 +header.uncompressed = Uncompressed +header.warning.unsupportedGfxCompression = GFX supports only uncompressed or Zlib compressed content. +header.warning.minimumZlibVersion = Zlib compression needs SWF version 6 or greater. +header.warning.minimumLzmaVersion = LZMA compression needs SWF version 13 or greater. + +tagInfo.codecName = Codec Name +tagInfo.exportFormat = Export Format +tagInfo.samplingRate = Sampling Rate +tagInfo.stereo = Stereo +tagInfo.sampleCount = Sample Count + +filter.dmg = Mac Executable files (*.dmg) +filter.linuxExe = Linux Executable files + +import.script.result = %count% scripts imported. +import.script.as12warning = Import script can import only AS1/2 scripts. + +error.constantPoolTooBig = Constant pool is too big. index=%index%, size=%size% +error.image.alpha.invalid = Invalid alpha channel data. + +#after version 6.0.2 +contextmenu.saveUncompressedToFile = Save to Uncompressed File +abc.traitslist.scriptinitializer = script initializer +menu.settings.autoOpenLoadedSWFs = Open loaded SWFs while playing + +#after version 6.1.1 +menu.file.start = Start +menu.file.start.run = Run +menu.file.start.stop = Stop +menu.file.start.debug = Debug +menu.debugging = Debugging +menu.debugging.debug = Debug +menu.debugging.debug.stop = Stop +menu.debugging.debug.pause = Pause +menu.debugging.debug.stepOver = Step over +menu.debugging.debug.stepInto = Step into +menu.debugging.debug.stepOut = Step out +menu.debugging.debug.continue = Continue +menu.debugging.debug.stack = Stack... +menu.debugging.debug.watch = New watch... + +message.playerpath.notset = Flash Player projector not found. Please configure its path in Advanced Settings / Paths (1). +message.playerpath.debug.notset = Flash Player projector content debugger not found. Please configure its path in Advanced Settings / Paths (2). +message.playerpath.lib.notset = PlayerGlobal (.SWC) not found. Please configure its path in Advanced Settings / Paths (3). + +debugpanel.header = Debugging + +variables.header.registers = Registers +variables.header.locals = Locals +variables.header.arguments = Arguments +variables.header.scopeChain = Scope chain +variables.column.name = Name +variables.column.type = Type +variables.column.value = Value + +callStack.header = Call stack +callStack.header.file = File +callStack.header.line = Line + +stack.header = Stack +stack.header.item = Item + +constantpool.header = Constant pool +constantpool.header.id = Id +constantpool.header.value = Value + +work.running = Running +work.debugging = Debugging +work.debugging.instrumenting = Preparing SWF for debugging +work.breakat = Break at\u0020 +work.halted = Debugging started, execution halted. Add breakpoints and click Continue (F5) to resume running. + +debuglog.header = Log +debuglog.button.clear = Clear + +#after 7.0.1 +work.debugging.wait = Waiting for Flash debug projector to connect + +error.debug.listen = Cannot listen on port %port%. There might be other flash debugger running. + +debug.break.reason.unknown = (Unknown) +debug.break.reason.breakpoint = (Breakpoint) +debug.break.reason.watch = (Watch) +debug.break.reason.fault = (Fault) +debug.break.reason.stopRequest = (Stop request) +debug.break.reason.step = (Step) +debug.break.reason.halt = (Halt) +debug.break.reason.scriptLoaded = (Script loaded) + +menu.file.start.debugpcode = Debug P-code + +#after 7.1.2 +button.replaceNoFill = Replace - Update bounds... +message.warning.svgImportExperimental = Not all SVG features are supported. Please check the log after import. + +message.imported.swf = The SWF file uses assets from an imported SWF file:\n%url%\nDo you want the assets to be loaded from that URL? +message.imported.swf.manually = Cannot load imported SWF\n%url%\nThe file or URL does not exist.\nDo you want to select local file? + +message.warning.hexViewNotUpToDate = Hex View is not up-to-date. Please save and reload the file to update Hex View. +message.font.replace.updateTexts = Some characters were replaced. Do you want to update the existing texts? + +menu.settings.simplifyExpressions = Simplify expressions + +#after 8.0.1 +menu.recentFiles.empty = Recent file list is empty +message.warning.outOfMemory32BitJre = OutOfMemory error occurred. You are running 32bit Java on 64bit system. Please use 64bit Java. + +menu.file.reloadAll = Reload all +message.confirm.reloadAll = This action cancels all unsaved changes in all SWF files and reloads whole application again.\nDo you want to continue? +export.script.singleFilePallelModeWarning = Single file script export is not supported with enabled parallel speedup + +button.showOriginalBytesInPcodeHex = Show original bytes +button.remove = Remove