diff --git a/git_eol_normalization.sh b/git_eol_normalization.sh old mode 100644 new mode 100755 diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ApplicationInfo.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ApplicationInfo.java index ab6da28bc..bfaf4d1a3 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ApplicationInfo.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/ApplicationInfo.java @@ -1,132 +1,132 @@ -/* - * 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; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Properties; - -/** - * - * @author JPEXS - */ -public class ApplicationInfo { - - public static final String APPLICATION_NAME = "JPEXS Free Flash Decompiler"; - - public static final String SHORT_APPLICATION_NAME = "FFDec"; - - public static final String VENDOR = "JPEXS"; - - public static String libraryVersion = ""; - - public static String version = ""; - - public static String revision = ""; - - public static int version_major = 4; - - public static int version_minor = 0; - - public static int version_release = 0; - - public static int version_build = 0; - - public static boolean nightly = false; - - public static String applicationVerName; - - public static String shortApplicationVerName; - - public static final String PROJECT_PAGE = "https://www.free-decompiler.com/flash"; - - /** - * URL for checking new updates - */ - public static String updateCheckUrl = "https://www.free-decompiler.com/flash/update/check/?currentVersion=¤tRevision=¤tVersionMajor=¤tVersionMinor=¤tVersionRelease=¤tVersionBuild=¤tNightly="; - - /** - * URL for doing update - */ - public static String updateUrl = "https://www.free-decompiler.com/flash/update/update/?currentVersion=¤tRevision=¤tVersionMajor=¤tVersionMinor=¤tVersionRelease=¤tVersionBuild=¤tNightly="; - - static { - loadProperties(); - loadLibraryVersion(); - } - - private static void loadLibraryVersion() { - Properties prop = new Properties(); - try { - prop.load(SWF.class.getResourceAsStream("/project.properties")); - String version = prop.getProperty("version"); - int version_build = Integer.parseInt(prop.getProperty("version.build")); - boolean nightly = prop.getProperty("nightly").equals("true"); - if (nightly) { - version = version + " nightly build " + version_build; - } - - libraryVersion = version; - } catch (IOException | NullPointerException | NumberFormatException ex) { - // ignore - libraryVersion = "unknown"; - } - } - - private static void loadProperties() { - Properties prop = new Properties(); - try { - prop.load(ApplicationInfo.class.getResourceAsStream("/project.properties")); - version = prop.getProperty("version"); - revision = prop.getProperty("build"); - version_major = Integer.parseInt(prop.getProperty("version.major")); - version_minor = Integer.parseInt(prop.getProperty("version.minor")); - version_release = Integer.parseInt(prop.getProperty("version.release")); - version_build = Integer.parseInt(prop.getProperty("version.build")); - nightly = prop.getProperty("nightly").equals("true"); - if (nightly) { - version = version + " nightly build " + version_build; - } - } catch (IOException | NullPointerException | NumberFormatException ex) { - // ignore - version = "unknown"; - } - try { - updateCheckUrl = updateCheckUrl - .replace("", URLEncoder.encode(revision, "UTF-8")) - .replace("", URLEncoder.encode(version, "UTF-8")) - .replace("", "" + version_major) - .replace("", "" + version_minor) - .replace("", "" + version_release) - .replace("", "" + version_build) - .replace("", nightly ? "1" : "0"); - updateUrl = updateUrl - .replace("", URLEncoder.encode(revision, "UTF-8")) - .replace("", URLEncoder.encode(version, "UTF-8")) - .replace("", "" + version_major) - .replace("", "" + version_minor) - .replace("", "" + version_release) - .replace("", "" + version_build) - .replace("", nightly ? "1" : "0"); - } catch (UnsupportedEncodingException e) { - - } - applicationVerName = APPLICATION_NAME + " v." + version; - shortApplicationVerName = SHORT_APPLICATION_NAME + " v." + version; - } -} +/* + * 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; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Properties; + +/** + * + * @author JPEXS + */ +public class ApplicationInfo { + + public static final String APPLICATION_NAME = "JPEXS Free Flash Decompiler"; + + public static final String SHORT_APPLICATION_NAME = "FFDec"; + + public static final String VENDOR = "JPEXS"; + + public static String libraryVersion = ""; + + public static String version = ""; + + public static String revision = ""; + + public static int version_major = 4; + + public static int version_minor = 0; + + public static int version_release = 0; + + public static int version_build = 0; + + public static boolean nightly = false; + + public static String applicationVerName; + + public static String shortApplicationVerName; + + public static final String PROJECT_PAGE = "https://www.free-decompiler.com/flash"; + + /** + * URL for checking new updates + */ + public static String updateCheckUrl = "https://www.free-decompiler.com/flash/update/check/?currentVersion=¤tRevision=¤tVersionMajor=¤tVersionMinor=¤tVersionRelease=¤tVersionBuild=¤tNightly="; + + /** + * URL for doing update + */ + public static String updateUrl = "https://www.free-decompiler.com/flash/update/update/?currentVersion=¤tRevision=¤tVersionMajor=¤tVersionMinor=¤tVersionRelease=¤tVersionBuild=¤tNightly="; + + static { + loadProperties(); + loadLibraryVersion(); + } + + private static void loadLibraryVersion() { + Properties prop = new Properties(); + try { + prop.load(SWF.class.getResourceAsStream("/project.properties")); + String version = prop.getProperty("version"); + int version_build = Integer.parseInt(prop.getProperty("version.build")); + boolean nightly = prop.getProperty("nightly").equals("true"); + if (nightly) { + version = version + " nightly build " + version_build; + } + + libraryVersion = version; + } catch (IOException | NullPointerException | NumberFormatException ex) { + // ignore + libraryVersion = "unknown"; + } + } + + private static void loadProperties() { + Properties prop = new Properties(); + try { + prop.load(ApplicationInfo.class.getResourceAsStream("/project.properties")); + version = prop.getProperty("version"); + revision = prop.getProperty("build"); + version_major = Integer.parseInt(prop.getProperty("version.major")); + version_minor = Integer.parseInt(prop.getProperty("version.minor")); + version_release = Integer.parseInt(prop.getProperty("version.release")); + version_build = Integer.parseInt(prop.getProperty("version.build")); + nightly = prop.getProperty("nightly").equals("true"); + if (nightly) { + version = version + " nightly build " + version_build; + } + } catch (IOException | NullPointerException | NumberFormatException ex) { + // ignore + version = "unknown"; + } + try { + updateCheckUrl = updateCheckUrl + .replace("", URLEncoder.encode(revision, "UTF-8")) + .replace("", URLEncoder.encode(version, "UTF-8")) + .replace("", "" + version_major) + .replace("", "" + version_minor) + .replace("", "" + version_release) + .replace("", "" + version_build) + .replace("", nightly ? "1" : "0"); + updateUrl = updateUrl + .replace("", URLEncoder.encode(revision, "UTF-8")) + .replace("", URLEncoder.encode(version, "UTF-8")) + .replace("", "" + version_major) + .replace("", "" + version_minor) + .replace("", "" + version_release) + .replace("", "" + version_build) + .replace("", nightly ? "1" : "0"); + } catch (UnsupportedEncodingException e) { + + } + applicationVerName = APPLICATION_NAME + " v." + version; + shortApplicationVerName = SHORT_APPLICATION_NAME + " v." + version; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java index 8de309061..f4bc95c08 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/ABCInputStream.java @@ -1,543 +1,543 @@ -/* - * 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.abc; - -import com.jpexs.decompiler.flash.EndOfStreamException; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.abc.types.Decimal; -import com.jpexs.decompiler.flash.abc.types.Float4; -import com.jpexs.decompiler.flash.abc.types.InstanceInfo; -import com.jpexs.decompiler.flash.abc.types.MethodInfo; -import com.jpexs.decompiler.flash.abc.types.Multiname; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.abc.types.ValueKind; -import com.jpexs.decompiler.flash.abc.types.traits.Trait; -import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; -import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; -import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; -import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; -import com.jpexs.decompiler.flash.abc.types.traits.Traits; -import com.jpexs.decompiler.flash.dumpview.DumpInfo; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.helpers.MemoryInputStream; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -/** - * - * @author JPEXS - */ -public class ABCInputStream implements AutoCloseable { - - private static final int CLASS_PROTECTED_NS = 8; - - private static final int ATTR_METADATA = 4; - - private final MemoryInputStream is; - - private ByteArrayOutputStream bufferOs = null; - - public static final boolean DEBUG_READ = false; - - public DumpInfo dumpInfo; - - private byte[] stringDataBuffer = new byte[256]; - - public void startBuffer() { - if (bufferOs == null) { - bufferOs = new ByteArrayOutputStream(); - } else { - bufferOs.reset(); - } - } - - public byte[] stopBuffer() { - if (bufferOs == null) { - return SWFInputStream.BYTE_ARRAY_EMPTY; - } - byte[] ret = bufferOs.toByteArray(); - bufferOs.reset(); - return ret; - } - - public ABCInputStream(MemoryInputStream is) { - this.is = is; - } - - /** - * Sets position in bytes in the stream - * - * @param pos Number of bytes - * @throws java.io.IOException - */ - public void seek(long pos) throws IOException { - is.seek(pos); - } - - public DumpInfo newDumpLevel(String name, String type) { - return newDumpLevel(name, type, DumpInfoSpecialType.NONE); - } - - public DumpInfo newDumpLevel(String name, String type, DumpInfoSpecialType specialType) { - if (dumpInfo != null) { - long startByte = is.getPos(); - DumpInfo di = specialType == DumpInfoSpecialType.NONE - ? new DumpInfo(name, type, null, startByte, 0, 0, 0) - : new DumpInfoSpecial(name, type, null, startByte, 0, 0, 0, specialType); - di.parent = dumpInfo; - dumpInfo.getChildInfos().add(di); - dumpInfo = di; - } - - return dumpInfo; - } - - public void endDumpLevel() { - endDumpLevel(null); - } - - public void endDumpLevel(Object value) { - if (dumpInfo != null) { - dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte; - dumpInfo.previewValue = value; - dumpInfo = dumpInfo.parent; - } - } - - public void endDumpLevelUntil(DumpInfo di) { - if (di != null) { - while (dumpInfo != null && dumpInfo != di) { - endDumpLevel(); - } - } - } - - private int readInternal() throws IOException { - int i = is.read(); - if (i == -1) { - throw new EndOfStreamException(); - } - if (DEBUG_READ) { - System.out.println("Read:0x" + Integer.toHexString(i)); - } - if (bufferOs != null) { - if (i != -1) { - bufferOs.write(i); - } - } - return i; - } - - public int read(String name) throws IOException { - newDumpLevel(name, "byte"); - int ret = readInternal(); - endDumpLevel(ret); - return ret; - } - - private int read(byte[] b) throws IOException { - int currBytesRead = is.read(b); - if (DEBUG_READ) { - StringBuilder sb = new StringBuilder("Read["); - sb.append(currBytesRead); - sb.append('/'); - sb.append(b.length); - sb.append("]: "); - for (int jj = 0; jj < currBytesRead; jj++) { - sb.append("0x"); - sb.append(Integer.toHexString(b[jj])); - sb.append(' '); - } - System.out.println(sb.toString()); - } - if (bufferOs != null) { - if (currBytesRead > 0) { - bufferOs.write(b, 0, currBytesRead); - } - } - return currBytesRead; - } - - public int readU8(String name) throws IOException { - newDumpLevel(name, "U8"); - int ret = readInternal(); - endDumpLevel(ret); - return ret; - } - - private long readU32Internal() throws IOException { - int i; - long ret = 0; - int bytePos = 0; - int byteCount = 0; - boolean nextByte; - do { - i = readInternal(); - nextByte = (i >> 7) == 1; - i &= 0x7f; - ret += (((long) i) << bytePos); - byteCount++; - bytePos += 7; - } while (nextByte && byteCount < 5); - return ret; - } - - public long readU32(String name) throws IOException { - newDumpLevel(name, "U32"); - long ret = readU32Internal(); - endDumpLevel(ret); - return ret; - } - - private int readU30Internal() throws IOException { - long u32 = readU32Internal(); - //no bits above bit 30 - return (int) (u32 & 0x3FFFFFFF); - } - - public int readU30(String name) throws IOException { - newDumpLevel(name, "U30"); - int ret = readU30Internal(); - endDumpLevel(ret); - return ret; - } - - public int readS24(String name) throws IOException { - newDumpLevel(name, "S24"); - int ret = (readInternal()) + (readInternal() << 8) + (readInternal() << 16); - - if ((ret >> 23) == 1) { - ret |= 0xff000000; - } - - endDumpLevel(ret); - return ret; - } - - public int readU16(String name) throws IOException { - newDumpLevel(name, "U16"); - int ret = (readInternal()) + (readInternal() << 8); - endDumpLevel(ret); - return ret; - } - - public long readS32(String name) throws IOException { - int i; - long ret = 0; - int bytePos = 0; - int byteCount = 0; - boolean nextByte; - newDumpLevel(name, "S32"); - do { - i = readInternal(); - nextByte = (i >> 7) == 1; - i &= 0x7f; - ret += (i << bytePos); - byteCount++; - bytePos += 7; - if (bytePos == 35) { - if ((ret >> 31) == 1) { - ret = -(ret & 0x7fffffff); - } - break; - } - } while (nextByte && byteCount < 5); - endDumpLevel(ret); - return ret; - } - - public int available() throws IOException { - return is.available(); - } - - private long readLong() throws IOException { - safeRead(8, stringDataBuffer); - byte[] readBuffer = stringDataBuffer; - return (((long) readBuffer[7] << 56) - + ((long) (readBuffer[6] & 255) << 48) - + ((long) (readBuffer[5] & 255) << 40) - + ((long) (readBuffer[4] & 255) << 32) - + ((long) (readBuffer[3] & 255) << 24) - + ((readBuffer[2] & 255) << 16) - + ((readBuffer[1] & 255) << 8) - + ((readBuffer[0] & 255))); - } - - public double readDouble(String name) throws IOException { - newDumpLevel(name, "Double"); - long el = readLong(); - double ret = Double.longBitsToDouble(el); - endDumpLevel(ret); - return ret; - } - - private void safeRead(int count, byte[] data) throws IOException { - for (int i = 0; i < count; i++) { - data[i] = (byte) readInternal(); - } - } - - public Namespace readNamespace(String name) throws IOException { - newDumpLevel(name, "Namespace"); - int kind = read("kind"); - int name_index = 0; - for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { - if (Namespace.nameSpaceKinds[k] == kind) { - name_index = readU30("name_index"); - break; - } - } - endDumpLevel(); - return new Namespace(kind, name_index); - } - - public Multiname readMultiname(String name) throws IOException { - int kind = readU8("kind"); - Multiname result = null; - - newDumpLevel(name, "Multiname"); - if ((kind == Multiname.QNAME) || (kind == Multiname.QNAMEA)) { - int namespace_index = readU30("namespace_index"); - int name_index = readU30("name_index"); - result = Multiname.createQName(kind == Multiname.QNAMEA, name_index, namespace_index); - } else if ((kind == Multiname.RTQNAME) || (kind == Multiname.RTQNAMEA)) { - int name_index = readU30("name_index"); - result = Multiname.createRTQName(kind == Multiname.RTQNAMEA, name_index); - } else if ((kind == Multiname.RTQNAMEL) || (kind == Multiname.RTQNAMELA)) { - result = Multiname.createRTQNameL(kind == Multiname.RTQNAMELA); - } else if ((kind == Multiname.MULTINAME) || (kind == Multiname.MULTINAMEA)) { - int name_index = readU30("name_index"); - int namespace_set_index = readU30("namespace_set_index"); - result = Multiname.createMultiname(kind == Multiname.MULTINAMEA, name_index, namespace_set_index); - } else if ((kind == Multiname.MULTINAMEL) || (kind == Multiname.MULTINAMELA)) { - int namespace_set_index = readU30("namespace_set_index"); - result = Multiname.createMultinameL(kind == Multiname.MULTINAMELA, namespace_set_index); - } else if (kind == Multiname.TYPENAME) { - int qname_index = readU30("qname_index"); // Multiname index!!! - int paramsLength = readU30("paramsLength"); - int[] params = new int[paramsLength]; - for (int i = 0; i < paramsLength; i++) { - params[i] = readU30("param"); // multiname indices! - } - result = Multiname.createTypeName(qname_index, params); - } else { - throw new IOException("Unknown kind of Multiname:0x" + Integer.toHexString(kind)); - } - - endDumpLevel(); - return result; - } - - public MethodInfo readMethodInfo(String name) throws IOException { - newDumpLevel(name, "method_info"); - int param_count = readU30("param_count"); - int ret_type = readU30("ret_type"); - int[] param_types = new int[param_count]; - for (int i = 0; i < param_count; i++) { - param_types[i] = readU30("param_type"); - } - int name_index = readU30("name_index"); - int flags = read("flags"); - - // 1=need_arguments, 2=need_activation, 4=need_rest 8=has_optional (16=ignore_rest, 32=explicit,) 64=setsdxns, 128=has_paramnames - ValueKind[] optional = new ValueKind[0]; - if ((flags & 8) == 8) { // if has_optional - int optional_count = readU30("optional_count"); - optional = new ValueKind[optional_count]; - for (int i = 0; i < optional_count; i++) { - optional[i] = new ValueKind(readU30("value_index"), read("value_kind")); - } - } - - int[] param_names = new int[param_count]; - if ((flags & 128) == 128) { // if has_paramnames - for (int i = 0; i < param_count; i++) { - param_names[i] = readU30("param_name"); - } - } - - endDumpLevel(); - return new MethodInfo(param_types, ret_type, name_index, flags, optional, param_names); - } - - public Trait readTrait(String name) throws IOException { - newDumpLevel(name, "Trait"); - long pos = getPosition(); - startBuffer(); - int name_index = readU30("name_index"); - int kind = read("kind"); - int kindType = 0xf & kind; - int kindFlags = kind >> 4; - Trait trait; - - switch (kindType) { - case 0: // slot - case 6: // const - TraitSlotConst t1 = new TraitSlotConst(); - t1.slot_id = readU30("slot_id"); - t1.type_index = readU30("type_index"); - t1.value_index = readU30("value_index"); - if (t1.value_index != 0) { - t1.value_kind = read("value_kind"); - } - trait = t1; - break; - case 1: // method - case 2: // getter - case 3: // setter - TraitMethodGetterSetter t2 = new TraitMethodGetterSetter(); - t2.disp_id = readU30("disp_id"); - t2.method_info = readU30("method_info"); - trait = t2; - break; - case 4: // class - TraitClass t3 = new TraitClass(); - t3.slot_id = readU30("slot_id"); - t3.class_info = readU30("class_info"); - trait = t3; - break; - case 5: // function - TraitFunction t4 = new TraitFunction(); - t4.slot_id = readU30("slot_id"); - t4.method_info = readU30("method_info"); - trait = t4; - break; - default: - throw new IOException("Unknown trait kind:" + kind); - } - trait.fileOffset = pos; - trait.kindType = kindType; - trait.kindFlags = kindFlags; - trait.name_index = name_index; - if ((kindFlags & ATTR_METADATA) != 0) { - int metadata_count = readU30("metadata_count"); - trait.metadata = new int[metadata_count]; - for (int i = 0; i < metadata_count; i++) { - trait.metadata[i] = readU30("metadata"); - } - } - trait.bytes = stopBuffer(); - endDumpLevel(); - return trait; - } - - public Traits readTraits(String name) throws IOException { - newDumpLevel(name, "Traits"); - int count = readU30("count"); - Traits traits = new Traits(count); - for (int i = 0; i < count; i++) { - traits.traits.add(readTrait("trait")); - } - endDumpLevel(); - return traits; - } - - private byte[] readBytesInternal(int count) throws IOException { - byte[] ret = new byte[count]; - for (int i = 0; i < count; i++) { - ret[i] = (byte) readInternal(); - } - return ret; - } - - public byte[] readBytes(int count, String name, DumpInfoSpecialType specialType) throws IOException { - newDumpLevel(name, "Bytes", specialType); - byte[] ret = readBytesInternal(count); - endDumpLevel(); - return ret; - } - - public Decimal readDecimal(String name) throws IOException { - newDumpLevel(name, "Decimal"); - byte[] data = readBytesInternal(16); - endDumpLevel(); - return new Decimal(data); - } - - public Float readFloat(String name) throws IOException { - newDumpLevel(name, "Float"); - int intBits = (readInternal()) + (readInternal() << 8); - float ret = Float.intBitsToFloat(intBits); - endDumpLevel(ret); - return ret; - } - - public Float4 readFloat4(String name) throws IOException { - newDumpLevel(name, "Float4"); - float f1 = readFloat("value1"); - float f2 = readFloat("value2"); - float f3 = readFloat("value3"); - float f4 = readFloat("value4"); - Float4 ret = new Float4(f1, f2, f3, f4); - endDumpLevel(ret); - return ret; - } - - public InstanceInfo readInstanceInfo(String name) throws IOException { - newDumpLevel(name, "instance_info"); - InstanceInfo ret = new InstanceInfo(null); // do not create Traits in constructor - ret.name_index = readU30("name_index"); - ret.super_index = readU30("super_index"); - ret.flags = readInternal(); - if ((ret.flags & CLASS_PROTECTED_NS) != 0) { - ret.protectedNS = readU30("protectedNS"); - } - int interfaces_count = readU30("interfaces_count"); - ret.interfaces = new int[interfaces_count]; - for (int i = 0; i < interfaces_count; i++) { - ret.interfaces[i] = readU30("interface"); - } - ret.iinit_index = readU30("iinit_index"); - ret.instance_traits = readTraits("instance_traits"); - endDumpLevel(); - return ret; - } - - public String readString(String name) throws IOException { - newDumpLevel(name, "String"); - int length = readU30Internal(); - - // avoid creating new byte array every time - if (stringDataBuffer.length < length) { - int newLength = stringDataBuffer.length * 2; - while (newLength < length) { - newLength *= 2; - } - - stringDataBuffer = new byte[newLength]; - } - - safeRead(length, stringDataBuffer); - String r = new String(stringDataBuffer, 0, length, Utf8Helper.charset); - endDumpLevel(r); - return r; - } - - - /*public void markStart(){ - bytesRead=0; - }*/ - public long getPosition() { - return is.getPos(); - } - - @Override - public void close() { - } -} +/* + * 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.abc; + +import com.jpexs.decompiler.flash.EndOfStreamException; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.abc.types.Decimal; +import com.jpexs.decompiler.flash.abc.types.Float4; +import com.jpexs.decompiler.flash.abc.types.InstanceInfo; +import com.jpexs.decompiler.flash.abc.types.MethodInfo; +import com.jpexs.decompiler.flash.abc.types.Multiname; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.abc.types.ValueKind; +import com.jpexs.decompiler.flash.abc.types.traits.Trait; +import com.jpexs.decompiler.flash.abc.types.traits.TraitClass; +import com.jpexs.decompiler.flash.abc.types.traits.TraitFunction; +import com.jpexs.decompiler.flash.abc.types.traits.TraitMethodGetterSetter; +import com.jpexs.decompiler.flash.abc.types.traits.TraitSlotConst; +import com.jpexs.decompiler.flash.abc.types.traits.Traits; +import com.jpexs.decompiler.flash.dumpview.DumpInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecial; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.helpers.MemoryInputStream; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +public class ABCInputStream implements AutoCloseable { + + private static final int CLASS_PROTECTED_NS = 8; + + private static final int ATTR_METADATA = 4; + + private final MemoryInputStream is; + + private ByteArrayOutputStream bufferOs = null; + + public static final boolean DEBUG_READ = false; + + public DumpInfo dumpInfo; + + private byte[] stringDataBuffer = new byte[256]; + + public void startBuffer() { + if (bufferOs == null) { + bufferOs = new ByteArrayOutputStream(); + } else { + bufferOs.reset(); + } + } + + public byte[] stopBuffer() { + if (bufferOs == null) { + return SWFInputStream.BYTE_ARRAY_EMPTY; + } + byte[] ret = bufferOs.toByteArray(); + bufferOs.reset(); + return ret; + } + + public ABCInputStream(MemoryInputStream is) { + this.is = is; + } + + /** + * Sets position in bytes in the stream + * + * @param pos Number of bytes + * @throws java.io.IOException + */ + public void seek(long pos) throws IOException { + is.seek(pos); + } + + public DumpInfo newDumpLevel(String name, String type) { + return newDumpLevel(name, type, DumpInfoSpecialType.NONE); + } + + public DumpInfo newDumpLevel(String name, String type, DumpInfoSpecialType specialType) { + if (dumpInfo != null) { + long startByte = is.getPos(); + DumpInfo di = specialType == DumpInfoSpecialType.NONE + ? new DumpInfo(name, type, null, startByte, 0, 0, 0) + : new DumpInfoSpecial(name, type, null, startByte, 0, 0, 0, specialType); + di.parent = dumpInfo; + dumpInfo.getChildInfos().add(di); + dumpInfo = di; + } + + return dumpInfo; + } + + public void endDumpLevel() { + endDumpLevel(null); + } + + public void endDumpLevel(Object value) { + if (dumpInfo != null) { + dumpInfo.lengthBytes = is.getPos() - dumpInfo.startByte; + dumpInfo.previewValue = value; + dumpInfo = dumpInfo.parent; + } + } + + public void endDumpLevelUntil(DumpInfo di) { + if (di != null) { + while (dumpInfo != null && dumpInfo != di) { + endDumpLevel(); + } + } + } + + private int readInternal() throws IOException { + int i = is.read(); + if (i == -1) { + throw new EndOfStreamException(); + } + if (DEBUG_READ) { + System.out.println("Read:0x" + Integer.toHexString(i)); + } + if (bufferOs != null) { + if (i != -1) { + bufferOs.write(i); + } + } + return i; + } + + public int read(String name) throws IOException { + newDumpLevel(name, "byte"); + int ret = readInternal(); + endDumpLevel(ret); + return ret; + } + + private int read(byte[] b) throws IOException { + int currBytesRead = is.read(b); + if (DEBUG_READ) { + StringBuilder sb = new StringBuilder("Read["); + sb.append(currBytesRead); + sb.append('/'); + sb.append(b.length); + sb.append("]: "); + for (int jj = 0; jj < currBytesRead; jj++) { + sb.append("0x"); + sb.append(Integer.toHexString(b[jj])); + sb.append(' '); + } + System.out.println(sb.toString()); + } + if (bufferOs != null) { + if (currBytesRead > 0) { + bufferOs.write(b, 0, currBytesRead); + } + } + return currBytesRead; + } + + public int readU8(String name) throws IOException { + newDumpLevel(name, "U8"); + int ret = readInternal(); + endDumpLevel(ret); + return ret; + } + + private long readU32Internal() throws IOException { + int i; + long ret = 0; + int bytePos = 0; + int byteCount = 0; + boolean nextByte; + do { + i = readInternal(); + nextByte = (i >> 7) == 1; + i &= 0x7f; + ret += (((long) i) << bytePos); + byteCount++; + bytePos += 7; + } while (nextByte && byteCount < 5); + return ret; + } + + public long readU32(String name) throws IOException { + newDumpLevel(name, "U32"); + long ret = readU32Internal(); + endDumpLevel(ret); + return ret; + } + + private int readU30Internal() throws IOException { + long u32 = readU32Internal(); + //no bits above bit 30 + return (int) (u32 & 0x3FFFFFFF); + } + + public int readU30(String name) throws IOException { + newDumpLevel(name, "U30"); + int ret = readU30Internal(); + endDumpLevel(ret); + return ret; + } + + public int readS24(String name) throws IOException { + newDumpLevel(name, "S24"); + int ret = (readInternal()) + (readInternal() << 8) + (readInternal() << 16); + + if ((ret >> 23) == 1) { + ret |= 0xff000000; + } + + endDumpLevel(ret); + return ret; + } + + public int readU16(String name) throws IOException { + newDumpLevel(name, "U16"); + int ret = (readInternal()) + (readInternal() << 8); + endDumpLevel(ret); + return ret; + } + + public long readS32(String name) throws IOException { + int i; + long ret = 0; + int bytePos = 0; + int byteCount = 0; + boolean nextByte; + newDumpLevel(name, "S32"); + do { + i = readInternal(); + nextByte = (i >> 7) == 1; + i &= 0x7f; + ret += (i << bytePos); + byteCount++; + bytePos += 7; + if (bytePos == 35) { + if ((ret >> 31) == 1) { + ret = -(ret & 0x7fffffff); + } + break; + } + } while (nextByte && byteCount < 5); + endDumpLevel(ret); + return ret; + } + + public int available() throws IOException { + return is.available(); + } + + private long readLong() throws IOException { + safeRead(8, stringDataBuffer); + byte[] readBuffer = stringDataBuffer; + return (((long) readBuffer[7] << 56) + + ((long) (readBuffer[6] & 255) << 48) + + ((long) (readBuffer[5] & 255) << 40) + + ((long) (readBuffer[4] & 255) << 32) + + ((long) (readBuffer[3] & 255) << 24) + + ((readBuffer[2] & 255) << 16) + + ((readBuffer[1] & 255) << 8) + + ((readBuffer[0] & 255))); + } + + public double readDouble(String name) throws IOException { + newDumpLevel(name, "Double"); + long el = readLong(); + double ret = Double.longBitsToDouble(el); + endDumpLevel(ret); + return ret; + } + + private void safeRead(int count, byte[] data) throws IOException { + for (int i = 0; i < count; i++) { + data[i] = (byte) readInternal(); + } + } + + public Namespace readNamespace(String name) throws IOException { + newDumpLevel(name, "Namespace"); + int kind = read("kind"); + int name_index = 0; + for (int k = 0; k < Namespace.nameSpaceKinds.length; k++) { + if (Namespace.nameSpaceKinds[k] == kind) { + name_index = readU30("name_index"); + break; + } + } + endDumpLevel(); + return new Namespace(kind, name_index); + } + + public Multiname readMultiname(String name) throws IOException { + int kind = readU8("kind"); + Multiname result = null; + + newDumpLevel(name, "Multiname"); + if ((kind == Multiname.QNAME) || (kind == Multiname.QNAMEA)) { + int namespace_index = readU30("namespace_index"); + int name_index = readU30("name_index"); + result = Multiname.createQName(kind == Multiname.QNAMEA, name_index, namespace_index); + } else if ((kind == Multiname.RTQNAME) || (kind == Multiname.RTQNAMEA)) { + int name_index = readU30("name_index"); + result = Multiname.createRTQName(kind == Multiname.RTQNAMEA, name_index); + } else if ((kind == Multiname.RTQNAMEL) || (kind == Multiname.RTQNAMELA)) { + result = Multiname.createRTQNameL(kind == Multiname.RTQNAMELA); + } else if ((kind == Multiname.MULTINAME) || (kind == Multiname.MULTINAMEA)) { + int name_index = readU30("name_index"); + int namespace_set_index = readU30("namespace_set_index"); + result = Multiname.createMultiname(kind == Multiname.MULTINAMEA, name_index, namespace_set_index); + } else if ((kind == Multiname.MULTINAMEL) || (kind == Multiname.MULTINAMELA)) { + int namespace_set_index = readU30("namespace_set_index"); + result = Multiname.createMultinameL(kind == Multiname.MULTINAMELA, namespace_set_index); + } else if (kind == Multiname.TYPENAME) { + int qname_index = readU30("qname_index"); // Multiname index!!! + int paramsLength = readU30("paramsLength"); + int[] params = new int[paramsLength]; + for (int i = 0; i < paramsLength; i++) { + params[i] = readU30("param"); // multiname indices! + } + result = Multiname.createTypeName(qname_index, params); + } else { + throw new IOException("Unknown kind of Multiname:0x" + Integer.toHexString(kind)); + } + + endDumpLevel(); + return result; + } + + public MethodInfo readMethodInfo(String name) throws IOException { + newDumpLevel(name, "method_info"); + int param_count = readU30("param_count"); + int ret_type = readU30("ret_type"); + int[] param_types = new int[param_count]; + for (int i = 0; i < param_count; i++) { + param_types[i] = readU30("param_type"); + } + int name_index = readU30("name_index"); + int flags = read("flags"); + + // 1=need_arguments, 2=need_activation, 4=need_rest 8=has_optional (16=ignore_rest, 32=explicit,) 64=setsdxns, 128=has_paramnames + ValueKind[] optional = new ValueKind[0]; + if ((flags & 8) == 8) { // if has_optional + int optional_count = readU30("optional_count"); + optional = new ValueKind[optional_count]; + for (int i = 0; i < optional_count; i++) { + optional[i] = new ValueKind(readU30("value_index"), read("value_kind")); + } + } + + int[] param_names = new int[param_count]; + if ((flags & 128) == 128) { // if has_paramnames + for (int i = 0; i < param_count; i++) { + param_names[i] = readU30("param_name"); + } + } + + endDumpLevel(); + return new MethodInfo(param_types, ret_type, name_index, flags, optional, param_names); + } + + public Trait readTrait(String name) throws IOException { + newDumpLevel(name, "Trait"); + long pos = getPosition(); + startBuffer(); + int name_index = readU30("name_index"); + int kind = read("kind"); + int kindType = 0xf & kind; + int kindFlags = kind >> 4; + Trait trait; + + switch (kindType) { + case 0: // slot + case 6: // const + TraitSlotConst t1 = new TraitSlotConst(); + t1.slot_id = readU30("slot_id"); + t1.type_index = readU30("type_index"); + t1.value_index = readU30("value_index"); + if (t1.value_index != 0) { + t1.value_kind = read("value_kind"); + } + trait = t1; + break; + case 1: // method + case 2: // getter + case 3: // setter + TraitMethodGetterSetter t2 = new TraitMethodGetterSetter(); + t2.disp_id = readU30("disp_id"); + t2.method_info = readU30("method_info"); + trait = t2; + break; + case 4: // class + TraitClass t3 = new TraitClass(); + t3.slot_id = readU30("slot_id"); + t3.class_info = readU30("class_info"); + trait = t3; + break; + case 5: // function + TraitFunction t4 = new TraitFunction(); + t4.slot_id = readU30("slot_id"); + t4.method_info = readU30("method_info"); + trait = t4; + break; + default: + throw new IOException("Unknown trait kind:" + kind); + } + trait.fileOffset = pos; + trait.kindType = kindType; + trait.kindFlags = kindFlags; + trait.name_index = name_index; + if ((kindFlags & ATTR_METADATA) != 0) { + int metadata_count = readU30("metadata_count"); + trait.metadata = new int[metadata_count]; + for (int i = 0; i < metadata_count; i++) { + trait.metadata[i] = readU30("metadata"); + } + } + trait.bytes = stopBuffer(); + endDumpLevel(); + return trait; + } + + public Traits readTraits(String name) throws IOException { + newDumpLevel(name, "Traits"); + int count = readU30("count"); + Traits traits = new Traits(count); + for (int i = 0; i < count; i++) { + traits.traits.add(readTrait("trait")); + } + endDumpLevel(); + return traits; + } + + private byte[] readBytesInternal(int count) throws IOException { + byte[] ret = new byte[count]; + for (int i = 0; i < count; i++) { + ret[i] = (byte) readInternal(); + } + return ret; + } + + public byte[] readBytes(int count, String name, DumpInfoSpecialType specialType) throws IOException { + newDumpLevel(name, "Bytes", specialType); + byte[] ret = readBytesInternal(count); + endDumpLevel(); + return ret; + } + + public Decimal readDecimal(String name) throws IOException { + newDumpLevel(name, "Decimal"); + byte[] data = readBytesInternal(16); + endDumpLevel(); + return new Decimal(data); + } + + public Float readFloat(String name) throws IOException { + newDumpLevel(name, "Float"); + int intBits = (readInternal()) + (readInternal() << 8); + float ret = Float.intBitsToFloat(intBits); + endDumpLevel(ret); + return ret; + } + + public Float4 readFloat4(String name) throws IOException { + newDumpLevel(name, "Float4"); + float f1 = readFloat("value1"); + float f2 = readFloat("value2"); + float f3 = readFloat("value3"); + float f4 = readFloat("value4"); + Float4 ret = new Float4(f1, f2, f3, f4); + endDumpLevel(ret); + return ret; + } + + public InstanceInfo readInstanceInfo(String name) throws IOException { + newDumpLevel(name, "instance_info"); + InstanceInfo ret = new InstanceInfo(null); // do not create Traits in constructor + ret.name_index = readU30("name_index"); + ret.super_index = readU30("super_index"); + ret.flags = readInternal(); + if ((ret.flags & CLASS_PROTECTED_NS) != 0) { + ret.protectedNS = readU30("protectedNS"); + } + int interfaces_count = readU30("interfaces_count"); + ret.interfaces = new int[interfaces_count]; + for (int i = 0; i < interfaces_count; i++) { + ret.interfaces[i] = readU30("interface"); + } + ret.iinit_index = readU30("iinit_index"); + ret.instance_traits = readTraits("instance_traits"); + endDumpLevel(); + return ret; + } + + public String readString(String name) throws IOException { + newDumpLevel(name, "String"); + int length = readU30Internal(); + + // avoid creating new byte array every time + if (stringDataBuffer.length < length) { + int newLength = stringDataBuffer.length * 2; + while (newLength < length) { + newLength *= 2; + } + + stringDataBuffer = new byte[newLength]; + } + + safeRead(length, stringDataBuffer); + String r = new String(stringDataBuffer, 0, length, Utf8Helper.charset); + endDumpLevel(r); + return r; + } + + + /*public void markStart(){ + bytesRead=0; + }*/ + public long getPosition() { + return is.getPos(); + } + + @Override + public void close() { + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/ActionScript3Parser.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/ActionScript3Parser.java index 6267744f4..677c0d48a 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/ActionScript3Parser.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/avm2/parser/script/ActionScript3Parser.java @@ -1,2624 +1,2624 @@ -/* - * 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.abc.avm2.parser.script; - -import com.jpexs.decompiler.flash.SWC; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SourceGeneratorLocalData; -import com.jpexs.decompiler.flash.abc.ABC; -import com.jpexs.decompiler.flash.abc.avm2.model.ApplyTypeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.BooleanAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.CoerceAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ConstructSuperAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.DefaultXMLNamespace; -import com.jpexs.decompiler.flash.abc.avm2.model.EscapeXAttrAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.EscapeXElemAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.FloatValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.GetDescendantsAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.GetPropertyAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.HasNextAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.InAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.InitVectorAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NameValuePair; -import com.jpexs.decompiler.flash.abc.avm2.model.NanAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NewActivationAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NewArrayAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NewObjectAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NextNameAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.PostDecrementAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.PostIncrementAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.RegExpAvm2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ReturnValueAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.ThrowAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.UndefinedAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ExceptionAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForInAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.AddAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.AsTypeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitAndAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitOrAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitXorAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.DeletePropertyAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.DivideAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.EqAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.GeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.GtAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.InstanceOfAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.IsTypeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.LShiftAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.LeAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.LtAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.ModuloAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.MultiplyAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.NegAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.NeqAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.PreDecrementAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.PreIncrementAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.RShiftAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictEqAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictNeqAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.SubtractAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.TypeOfAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.model.operations.URShiftAVM2Item; -import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; -import com.jpexs.decompiler.flash.abc.types.Namespace; -import com.jpexs.decompiler.flash.action.swf4.ActionIf; -import com.jpexs.decompiler.flash.configuration.Configuration; -import com.jpexs.decompiler.flash.tags.ABCContainerTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.graph.CompilationException; -import com.jpexs.decompiler.graph.DottedChain; -import com.jpexs.decompiler.graph.GraphTargetItem; -import com.jpexs.decompiler.graph.Loop; -import com.jpexs.decompiler.graph.TypeItem; -import com.jpexs.decompiler.graph.model.AndItem; -import com.jpexs.decompiler.graph.model.BlockItem; -import com.jpexs.decompiler.graph.model.BreakItem; -import com.jpexs.decompiler.graph.model.CommaExpressionItem; -import com.jpexs.decompiler.graph.model.ContinueItem; -import com.jpexs.decompiler.graph.model.DefaultItem; -import com.jpexs.decompiler.graph.model.DoWhileItem; -import com.jpexs.decompiler.graph.model.DuplicateItem; -import com.jpexs.decompiler.graph.model.ForItem; -import com.jpexs.decompiler.graph.model.IfItem; -import com.jpexs.decompiler.graph.model.NotItem; -import com.jpexs.decompiler.graph.model.OrItem; -import com.jpexs.decompiler.graph.model.ParenthesisItem; -import com.jpexs.decompiler.graph.model.PopItem; -import com.jpexs.decompiler.graph.model.PushItem; -import com.jpexs.decompiler.graph.model.SwitchItem; -import com.jpexs.decompiler.graph.model.TernarOpItem; -import com.jpexs.decompiler.graph.model.UnboundedTypeItem; -import com.jpexs.decompiler.graph.model.WhileItem; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.utf8.Utf8Helper; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -public class ActionScript3Parser { - - private long uniqLast = 0; - - private final boolean debugMode = false; - - private static final String AS3_NAMESPACE = "http://adobe.com/AS3/2006/builtin"; - - private final AbcIndexing abcIndex; - -// private final AbcIndexing otherABCs; - //private static final List playerABCs = new ArrayList<>(); - private static AbcIndexing playerGlobalAbcIndex; - - private long uniqId() { - uniqLast++; - return uniqLast; - } - - private List commands(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, List variables) throws IOException, AVM2ParseException { - List ret = new ArrayList<>(); - if (debugMode) { - System.out.println("commands:"); - } - GraphTargetItem cmd; - while ((cmd = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)) != null) { - ret.add(cmd); - } - if (debugMode) { - System.out.println("/commands"); - } - return ret; - } - - private GraphTargetItem type(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, List variables) throws IOException, AVM2ParseException { - ParsedSymbol s = lex(); - if (s.type == SymbolType.MULTIPLY) { - return new UnboundedTypeItem(); - } else if (s.type == SymbolType.VOID) { - return new TypeItem(DottedChain.VOID); - } else { - lexer.pushback(s); - } - - GraphTargetItem t = name(allOpenedNamespaces, thisType, pkg, needsActivation, true, openedNamespaces, null, false, false, variables, importedClasses); - t = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, t, new HashMap<>(), false, false, variables); - return t; - } - - private GraphTargetItem memberOrCall(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem newcmds, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - if (debugMode) { - System.out.println("memberOrCall:"); - } - ParsedSymbol s = lex(); - GraphTargetItem ret = newcmds; - while (s.isType(SymbolType.DOT, SymbolType.PARENT_OPEN, SymbolType.BRACKET_OPEN, SymbolType.TYPENAME, SymbolType.FILTER, SymbolType.DESCENDANTS)) { - switch (s.type) { - case BRACKET_OPEN: - case DOT: - case TYPENAME: - lexer.pushback(s); - ret = member(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); - break; - case FILTER: - needsActivation.setVal(true); - ret = new XMLFilterAVM2Item(ret, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, inMethod, variables), openedNamespaces); - expectedType(SymbolType.PARENT_CLOSE); - break; - case PARENT_OPEN: - ret = new CallAVM2Item(openedNamespaces, lexer.yyline(), ret, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); - break; - case DESCENDANTS: - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); - ret = new GetDescendantsAVM2Item(ret, s.type == SymbolType.MULTIPLY ? null : s.value.toString(), openedNamespaces); - break; - - } - s = lex(); - } - if (s.type == SymbolType.INCREMENT) { - if (!isNameOrProp(ret)) { - throw new AVM2ParseException("Invalid assignment", lexer.yyline()); - } - ret = new PostIncrementAVM2Item(null, null, ret); - s = lex(); - - } else if (s.type == SymbolType.DECREMENT) { - if (!isNameOrProp(ret)) { - throw new AVM2ParseException("Invalid assignment", lexer.yyline()); - } - ret = new PostDecrementAVM2Item(null, null, ret); - s = lex(); - } - - lexer.pushback(s); - - if (debugMode) { - System.out.println("/memberOrCall"); - } - return ret; - } - - private GraphTargetItem applyType(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem obj, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - GraphTargetItem ret = obj; - ParsedSymbol s = lex(); - if (s.type == SymbolType.TYPENAME) { - List params = new ArrayList<>(); - do { - s = lex(); - if (s.isType(SymbolType.MULTIPLY)) { - params.add(new NullAVM2Item(null, null)); - } else { - lexer.pushback(s); - params.add(expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); - } - s = lex(); - } while (s.type == SymbolType.COMMA); - if (s.type == SymbolType.USHIFT_RIGHT) { - s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.GREATER_THAN); - lexer.pushback(s); - lexer.pushback(s); - } - if (s.type == SymbolType.SHIFT_RIGHT) { - s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.GREATER_THAN); - lexer.pushback(s); - } - expected(s, lexer.yyline(), SymbolType.GREATER_THAN); - ret = new ApplyTypeAVM2Item(null, null, ret, params); - } else { - lexer.pushback(s); - } - return ret; - } - - private GraphTargetItem member(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem obj, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - if (debugMode) { - System.out.println("member:"); - } - GraphTargetItem ret = obj; - ParsedSymbol s = lex(); - while (s.isType(SymbolType.DOT, SymbolType.BRACKET_OPEN, SymbolType.TYPENAME)) { - ParsedSymbol s2 = lex(); - boolean attr = false; - if (s.type == SymbolType.DOT) { - if (s2.type == SymbolType.ATTRIBUTE) { - attr = true; - } else { - lexer.pushback(s2); - } - - } else { - lexer.pushback(s2); - } - if (s.type == SymbolType.TYPENAME) { - lexer.pushback(s); - ret = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); - s = lex(); - } else if (s.type == SymbolType.BRACKET_OPEN) { - GraphTargetItem index = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.BRACKET_CLOSE); - ret = new IndexAVM2Item(attr, ret, index, null, openedNamespaces); - s = lex(); - } else { - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); - String propName = s.value.toString(); //Can be * - GraphTargetItem propItem = null; - s = lex(); - GraphTargetItem ns = null; - if (s.type == SymbolType.NAMESPACE_OP) { - ns = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, false, null, lexer.yyline(), new DottedChain(new String[]{propName}, "" /*FIXME ???*/), null, openedNamespaces); - variables.add((UnresolvedAVM2Item) ns); - s = lex(); - if (s.type == SymbolType.BRACKET_OPEN) { - propItem = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.BRACKET_CLOSE); - propName = null; - } else { - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - propName = s.value.toString(); - propItem = null; - } - } else { - lexer.pushback(s); - } - if (ns != null) { - ret = new NamespacedAVM2Item(ns, propName, propItem, ret, attr, openedNamespaces, null); - } else { - ret = new PropertyAVM2Item(ret, (attr ? "@" : "") + propName, abcIndex, openedNamespaces, new ArrayList<>()); - } - s = lex(); - } - } - lexer.pushback(s); - - if (debugMode) { - System.out.println("/member"); - } - return ret; - } - - private GraphTargetItem name(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, boolean typeOnly, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables, List importedClasses) throws IOException, AVM2ParseException { - ParsedSymbol s = lex(); - DottedChain name = new DottedChain(new String[]{}, ""); - String name2 = ""; - if (s.type == SymbolType.ATTRIBUTE) { - name2 += "@"; - s = lex(); - } - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); - name2 += s.value.toString(); - s = lex(); - boolean attrBracket = false; - - name = name.addWithSuffix(name2); - while (s.isType(SymbolType.DOT)) { - //name += s.value.toString(); //. or :: - s = lex(); - name2 = ""; - if (s.type == SymbolType.ATTRIBUTE) { - name2 += "@"; - s = lex(); - if (s.type == SymbolType.MULTIPLY) { - name2 += s.value.toString(); - } else if (s.group == SymbolGroup.IDENTIFIER) { - name2 += s.value.toString(); - } else { - if (s.type != SymbolType.BRACKET_OPEN) { - throw new AVM2ParseException("Attribute identifier or bracket expected", lexer.yyline()); - } - attrBracket = true; - continue; - } - } else { - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.NAMESPACE, SymbolType.MULTIPLY); - name2 += s.value.toString(); - } - name = name.addWithSuffix(name2); - s = lex(); - } - String nsname = null; - String nsprop = null; - GraphTargetItem nspropItem = null; - if (s.type == SymbolType.NAMESPACE_OP) { - nsname = name.getLast(); - s = lex(); - if (s.group == SymbolGroup.IDENTIFIER) { - nsprop = s.value.toString(); - } else if (s.type == SymbolType.BRACKET_OPEN) { - nspropItem = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.BRACKET_CLOSE); - } - name = name.getWithoutLast(); - s = lex(); - } - - GraphTargetItem ret = null; - if (!name.isEmpty()) { - UnresolvedAVM2Item unr = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, typeOnly, null, lexer.yyline(), name, null, openedNamespaces); - //unr.setIndex(index); - variables.add(unr); - ret = unr; - } - if (nsname != null) { - boolean attr = nsname.startsWith("@"); - if (attr) { - nsname = nsname.substring(1); - } - UnresolvedAVM2Item ns = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, typeOnly, null, lexer.yyline(), new DottedChain(new String[]{nsname}, ""), null, openedNamespaces); - variables.add(ns); - ret = new NamespacedAVM2Item(ns, nsprop, nspropItem, ret, attr, openedNamespaces, null); - } - if (s.type == SymbolType.BRACKET_OPEN) { - lexer.pushback(s); - if (attrBracket) { - lexer.pushback(new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.ATTRIBUTE, "@")); - lexer.pushback(new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.DOT, ".")); - } - ret = member(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); - } else { - lexer.pushback(s); - } - return ret; - } - - private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, AVM2ParseException { - boolean found = false; - for (Object t : expected) { - if (symb.type == t) { - found = true; - } - if (symb.group == t) { - found = true; - } - } - if (!found) { - String expStr = ""; - boolean first = true; - for (Object e : expected) { - if (!first) { - expStr += " or "; - } - expStr += e; - first = false; - } - throw new AVM2ParseException("" + expStr + " expected but " + symb.type + " found", line); - } - } - - private ParsedSymbol expectedType(Object... type) throws IOException, AVM2ParseException { - ParsedSymbol symb = lex(); - expected(symb, lexer.yyline(), type); - return symb; - } - - private ParsedSymbol lex() throws IOException, AVM2ParseException { - ParsedSymbol ret = lexer.lex(); - if (debugMode) { - System.out.println(ret); - } - return ret; - } - - private List call(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - List ret = new ArrayList<>(); - //expected(SymbolType.PARENT_OPEN); //MUST BE HANDLED BY CALLER - ParsedSymbol s = lex(); - while (s.type != SymbolType.PARENT_CLOSE) { - if (s.type != SymbolType.COMMA) { - lexer.pushback(s); - } - ret.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - s = lex(); - expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); - } - return ret; - } - - private MethodAVM2Item method(List> allOpenedNamespaces, boolean outsidePackage, boolean isPrivate, List>> metadata, NamespaceItem pkg, boolean isInterface, String customAccess, Reference needsActivation, List importedClasses, boolean override, boolean isFinal, TypeItem thisType, List openedNamespaces, boolean isStatic, String functionName, boolean isMethod, List variables) throws IOException, AVM2ParseException { - FunctionAVM2Item f = function(allOpenedNamespaces, metadata, pkg, isInterface, needsActivation, importedClasses, thisType, openedNamespaces, functionName, isMethod, variables); - return new MethodAVM2Item(allOpenedNamespaces, outsidePackage, isPrivate, f.metadata, f.pkg, f.isInterface, customAccess, f.needsActivation, f.hasRest, f.line, override, isFinal, isStatic, functionName, f.paramTypes, f.paramNames, f.paramValues, f.body, f.subvariables, f.retType); - } - - private FunctionAVM2Item function(List> allOpenedNamespaces, List>> metadata, NamespaceItem pkg, boolean isInterface, Reference needsActivation, List importedClasses, TypeItem thisType, List openedNamespaces, String functionName, boolean isMethod, List variables) throws IOException, AVM2ParseException { - - openedNamespaces = new ArrayList<>(openedNamespaces); //local copy - allOpenedNamespaces.add(openedNamespaces); - int line = lexer.yyline(); - ParsedSymbol s; - expectedType(SymbolType.PARENT_OPEN); - s = lex(); - List paramNames = new ArrayList<>(); - List paramTypes = new ArrayList<>(); - List paramValues = new ArrayList<>(); - boolean hasRest = false; - while (s.type != SymbolType.PARENT_CLOSE) { - if (s.type != SymbolType.COMMA) { - lexer.pushback(s); - } - s = lex(); - if (s.type == SymbolType.REST) { - hasRest = true; - s = lex(); - } - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - - paramNames.add(s.value.toString()); - s = lex(); - if (!hasRest) { - if (s.type == SymbolType.COLON) { - paramTypes.add(type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables)); - s = lex(); - } else { - paramTypes.add(new UnboundedTypeItem()); - } - if (s.type == SymbolType.ASSIGN) { - paramValues.add(expression(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, null, isMethod, isMethod, isMethod, variables)); - s = lex(); - } else if (!paramValues.isEmpty()) { - throw new AVM2ParseException("Some of parameters do not have default values", lexer.yyline()); - } - } - - if (!s.isType(SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { - expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); - } - if (hasRest) { - expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE); - } - } - s = lex(); - GraphTargetItem retType; - if (s.type == SymbolType.COLON) { - retType = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - } else { - retType = new UnboundedTypeItem(); - lexer.pushback(s); - } - List body = null; - List subvariables = new ArrayList<>(); - subvariables.add(new NameAVM2Item(thisType, lexer.yyline(), "this", null, true, openedNamespaces)); - for (int i = 0; i < paramNames.size() - (hasRest ? 1 : 0); i++) { - subvariables.add(new NameAVM2Item(paramTypes.get(i), lexer.yyline(), paramNames.get(i), null, true, openedNamespaces)); - } - if (hasRest) { - subvariables.add(new NameAVM2Item(TypeItem.UNBOUNDED, lexer.yyline(), paramNames.get(paramNames.size() - 1), null, true, openedNamespaces)); - } - subvariables.add(new NameAVM2Item(thisType, lexer.yyline(), "arguments", null, true, openedNamespaces)); - int parCnt = subvariables.size(); - Reference needsActivation2 = new Reference<>(false); - if (!isInterface) { - expectedType(SymbolType.CURLY_OPEN); - body = commands(allOpenedNamespaces, thisType, pkg, needsActivation2, importedClasses, openedNamespaces, new Stack<>(), new HashMap<>(), new HashMap<>(), true, isMethod, 0, subvariables); - expectedType(SymbolType.CURLY_CLOSE); - } else { - expectedType(SymbolType.SEMICOLON); - } - - for (int i = 0; i < parCnt; i++) { - subvariables.remove(0); - } - return new FunctionAVM2Item(metadata, pkg, isInterface, needsActivation2.getVal(), hasRest, line, functionName, paramTypes, paramNames, paramValues, body, subvariables, retType); - } - - private List>> parseMetadata() throws IOException, AVM2ParseException { - List>> metadata = new ArrayList<>(); - ParsedSymbol s = lex(); - while (s.isType(SymbolType.BRACKET_OPEN)) { - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String name = s.value.toString(); - Map.Entry> en = new AbstractMap.SimpleEntry<>(name, new HashMap()); - s = lex(); - if (s.isType(SymbolType.PARENT_OPEN)) { - s = lex(); - if (s.isType(SymbolGroup.STRING)) { - en.getValue().put("", s.value.toString()); - s = lex(); - } else { - lexer.pushback(s); - do { - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String key = s.value.toString(); - expectedType(SymbolType.ASSIGN); - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.STRING); - String value = s.value.toString(); - en.getValue().put(key, value); - s = lex(); - } while (s.isType(SymbolType.COMMA)); - } - expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE); - s = lex(); - } - metadata.add(en); - expected(s, lexer.yyline(), SymbolType.BRACKET_CLOSE); - s = lex(); - } - lexer.pushback(s); - return metadata; - } - - private void classTraits(List> allOpenedNamespaces, boolean outsidePackage, List cinitVariables, Reference cinitNeedsActivation, List cinit, List importedClasses, List openedNamespaces, NamespaceItem pkg, String classNameStr, boolean isInterface, List traits, List iinitVariables, Reference iinitNeedsActivation, Reference iinit) throws AVM2ParseException, IOException, CompilationException { - - NamespaceItem publicNs = new NamespaceItem("", Namespace.KIND_PACKAGE); - NamespaceItem privateNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_PRIVATE); - NamespaceItem protectedNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_PROTECTED); - NamespaceItem staticProtectedNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_STATIC_PROTECTED); - NamespaceItem packageInternalNs = new NamespaceItem(pkg.name, Namespace.KIND_PACKAGE_INTERNAL); - - openedNamespaces = new ArrayList<>(openedNamespaces); - allOpenedNamespaces.add(openedNamespaces); - for (List ln : allOpenedNamespaces) { - if (!ln.contains(publicNs)) { - ln.add(publicNs); - } - } - openedNamespaces.add(privateNs); - openedNamespaces.add(protectedNs); - openedNamespaces.add(staticProtectedNs); - - looptraits: - while (true) { - TypeItem thisType = new TypeItem(pkg.name.addWithSuffix(classNameStr)); - boolean isGetter = false; - boolean isSetter = false; - boolean isOverride = false; - boolean isStatic = false; - boolean isFinal = false; - boolean isPrivate = false; - - String customNs = null; - NamespaceItem namespace = null; - ParsedSymbol s = lex(); - //static class initializer - if (s.type == SymbolType.CURLY_OPEN) { - cinit.addAll(commands(allOpenedNamespaces, thisType, pkg, cinitNeedsActivation, importedClasses, openedNamespaces, new Stack<>(), new HashMap<>(), new HashMap<>(), true, false, 0, cinitVariables)); - expectedType(SymbolType.CURLY_CLOSE); - } else { - lexer.pushback(s); - } - List>> metadata = parseMetadata(); - s = lex(); - - while (s.isType(SymbolType.STATIC, SymbolType.PUBLIC, SymbolType.PRIVATE, SymbolType.PROTECTED, SymbolType.OVERRIDE, SymbolType.FINAL, SymbolType.DYNAMIC, SymbolGroup.IDENTIFIER)) { - if (s.type == SymbolType.FINAL) { - if (isFinal) { - throw new AVM2ParseException("Only one final keyword allowed", lexer.yyline()); - } - isFinal = true; - } else if (s.type == SymbolType.OVERRIDE) { - if (isOverride) { - throw new AVM2ParseException("Only one override keyword allowed", lexer.yyline()); - } - isOverride = true; - } else if (s.type == SymbolType.STATIC) { - if (isInterface) { - throw new AVM2ParseException("Interface cannot have static traits", lexer.yyline()); - } - if (classNameStr == null) { - throw new AVM2ParseException("No static keyword allowed here", lexer.yyline()); - } - if (isStatic) { - throw new AVM2ParseException("Only one static keyword allowed", lexer.yyline()); - } - isStatic = true; - } else if (s.type == SymbolType.NAMESPACE) { - break; - } else if (s.type == SymbolType.NATIVE) { - throw new AVM2ParseException("Cannot compile native code", lexer.yyline()); - } else if (s.group == SymbolGroup.IDENTIFIER) { - customNs = s.value.toString(); - } else if (namespace != null) { - throw new AVM2ParseException("Only one access identifier allowed", lexer.yyline()); - } - switch (s.type) { - case PUBLIC: - namespace = publicNs; - if (isInterface) { - throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); - } - break; - case PRIVATE: - isPrivate = true; - namespace = privateNs; - if (isInterface) { - throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); - } - break; - case PROTECTED: - namespace = protectedNs; - if (isInterface) { - throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); - } - break; - } - s = lex(); - } - if (namespace == null && customNs == null) { - namespace = packageInternalNs; - } - if (namespace == protectedNs && isStatic) { - namespace = staticProtectedNs; - } - if (namespace == null && customNs != null) { - //Special: it will be resolved later: - namespace = new NamespaceItem(customNs, Namespace.KIND_NAMESPACE); - } - - switch (s.type) { - case FUNCTION: - s = lex(); - if (s.type == SymbolType.GET) { - isGetter = true; - s = lex(); - } else if (s.type == SymbolType.SET) { - isSetter = true; - s = lex(); - } - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.PARENT_OPEN); - String fname = null; - - //fix for methods with name "get" or "set" - they are not getters/setters! - if (s.isType(SymbolType.PARENT_OPEN)) { - lexer.pushback(s); - if (isGetter) { - fname = "get"; - isGetter = false; - } else if (isSetter) { - fname = "set"; - isSetter = false; - } else { - throw new AVM2ParseException("Missing method name", lexer.yyline()); - } - } else { - fname = s.value.toString(); - } - if (fname.equals(classNameStr)) { //constructor - if (isStatic) { - throw new AVM2ParseException("Constructor cannot be static", lexer.yyline()); - } - if (isOverride) { - throw new AVM2ParseException("Override flag not allowed for constructor", lexer.yyline()); - } - if (isFinal) { - throw new AVM2ParseException("Final flag not allowed for constructor", lexer.yyline()); - } - if (isInterface) { - throw new AVM2ParseException("Interface cannot have constructor", lexer.yyline()); - } - iinit.setVal(method(allOpenedNamespaces, outsidePackage, isPrivate, metadata, pkg, false, customNs, iinitNeedsActivation, importedClasses, false, false, thisType, openedNamespaces, false, "", true, iinitVariables)); - } else { - GraphTargetItem t; - if (classNameStr == null) { - isStatic = true; - } - { - MethodAVM2Item ft = method(allOpenedNamespaces, outsidePackage, isPrivate, metadata, namespace, isInterface, customNs, new Reference<>(false), importedClasses, isOverride, isFinal, thisType, openedNamespaces, isStatic, fname, true, new ArrayList<>()); - - if (isGetter) { - if (!ft.paramTypes.isEmpty()) { - throw new AVM2ParseException("Getter can't have any parameters", lexer.yyline()); - } - } - - if (isSetter) { - if (ft.paramTypes.size() != 1) { - throw new AVM2ParseException("Getter must have exactly one parameter", lexer.yyline()); - } - } - - if (isStatic && isInterface) { - if (isInterface) { - throw new AVM2ParseException("Interface cannot have static fields", lexer.yyline()); - } - } - if (isGetter) { - GetterAVM2Item g = new GetterAVM2Item(allOpenedNamespaces, outsidePackage, ft.isPrivate(), ft.metadata, ft.pkg, isInterface, customNs, ft.needsActivation, ft.hasRest, ft.line, ft.isOverride(), ft.isFinal(), isStatic, ft.functionName, ft.paramTypes, ft.paramNames, ft.paramValues, ft.body, ft.subvariables, ft.retType); - t = g; - } else if (isSetter) { - SetterAVM2Item st = new SetterAVM2Item(allOpenedNamespaces, outsidePackage, ft.isPrivate(), ft.metadata, ft.pkg, isInterface, customNs, ft.needsActivation, ft.hasRest, ft.line, ft.isOverride(), ft.isFinal(), isStatic, ft.functionName, ft.paramTypes, ft.paramNames, ft.paramValues, ft.body, ft.subvariables, ft.retType); - t = st; - } else { - t = ft; - } - - } - //NOTE: Looks like TraitFunction does not work in FlashPlayer - use MethodTrait instead - /*else { - t = function(metadata, pkg, isInterface, new Reference(false), importedClasses, namespace, thisType, openedNamespaces, fname, false, new ArrayList()); - }*/ - - traits.add(t); - } - //} - break; - case NAMESPACE: - if (isInterface) { - throw new AVM2ParseException("Interface cannot have namespace fields", lexer.yyline()); - } - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String nname = s.value.toString(); - String nval; - s = lex(); - - if (s.type == SymbolType.ASSIGN) { - s = lex(); - expected(s, lexer.yyline(), SymbolType.STRING); - nval = s.value.toString(); - s = lex(); - } else { - nval = (pkg.name.toRawString() + ":" + classNameStr) + "/" + nname; - } - if (s.type != SymbolType.SEMICOLON) { - lexer.pushback(s); - } - - ConstAVM2Item ns = new ConstAVM2Item(metadata, namespace, customNs, true, nname, new TypeItem(DottedChain.NAMESPACE), new StringAVM2Item(null, null, nval), lexer.yyline()); - traits.add(ns); - break; - case CONST: - case VAR: - boolean isConst = s.type == SymbolType.CONST; - if (isOverride) { - throw new AVM2ParseException("Override flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); - } - if (isFinal) { - throw new AVM2ParseException("Final flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); - } - if (isInterface) { - throw new AVM2ParseException("Interface cannot have variable/const fields", lexer.yyline()); - } - - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String vcname = s.value.toString(); - s = lex(); - GraphTargetItem type; - if (s.type == SymbolType.COLON) { - type = type(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); - s = lex(); - } else { - type = TypeItem.UNBOUNDED; - } - - GraphTargetItem value = null; - - if (s.type == SymbolType.ASSIGN) { - value = expression(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, new HashMap<>(), false, false, true, isStatic || isConst ? cinitVariables : iinitVariables); - s = lex(); - } - GraphTargetItem tar; - if (isConst) { - tar = new ConstAVM2Item(metadata, namespace, customNs, isStatic, vcname, type, value, lexer.yyline()); - } else { - tar = new SlotAVM2Item(metadata, namespace, customNs, isStatic, vcname, type, value, lexer.yyline()); - } - traits.add(tar); - if (s.type != SymbolType.SEMICOLON) { - lexer.pushback(s); - } - break; - default: - lexer.pushback(s); - break looptraits; - } - } - } - - private void scriptTraits(List> allOpenedNamespaces, int scriptIndex, String scriptName, List traits) throws AVM2ParseException, IOException, CompilationException { - - while (scriptTraitsBlock(allOpenedNamespaces, scriptIndex, scriptName, traits)) { - //empty - } - } - - private boolean scriptTraitsBlock(List> allOpenedNamespaces, int scriptIndex, String scriptName, List traits) throws AVM2ParseException, IOException, CompilationException { - ParsedSymbol s; - boolean inPackage = false; - s = lex(); - List sinitVariables = new ArrayList<>(); - NamespaceItem publicNs; - NamespaceItem packageInternalNs; - if (s.type == SymbolType.PACKAGE) { - DottedChain pkgName = DottedChain.TOPLEVEL; - s = lex(); - if (s.type != SymbolType.CURLY_OPEN) { - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - pkgName = pkgName.addWithSuffix(s.value.toString()); - s = lex(); - } - while (s.type == SymbolType.DOT) { - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - pkgName = pkgName.addWithSuffix(s.value.toString()); - s = lex(); - } - expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); - publicNs = new NamespaceItem(pkgName, Namespace.KIND_PACKAGE); - packageInternalNs = new NamespaceItem(pkgName, Namespace.KIND_PACKAGE_INTERNAL); - s = lex(); - inPackage = true; - } else { - publicNs = null; - packageInternalNs = new NamespaceItem(scriptName + "$" + scriptIndex, Namespace.KIND_PRIVATE); - } - lexer.pushback(s); - - List openedNamespaces = new ArrayList<>(); - allOpenedNamespaces.add(openedNamespaces); - NamespaceItem emptyNs = new NamespaceItem("", Namespace.KIND_PACKAGE); - openedNamespaces.add(emptyNs); - NamespaceItem as3Ns = new NamespaceItem(AS3_NAMESPACE, Namespace.KIND_NAMESPACE); - as3Ns.forceResolve(abcIndex); - openedNamespaces.add(as3Ns); - - for (List ln : allOpenedNamespaces) { - if (publicNs != null && !ln.contains(publicNs)) { - ln.add(publicNs); - } - if (!ln.contains(packageInternalNs)) { - ln.add(packageInternalNs); - } - } - - List importedClasses = parseImportsUsages(openedNamespaces); - - boolean isEmpty = true; - - looptrait: - while (true) { - List>> metadata = parseMetadata(); - s = lex(); - boolean isFinal = false; - boolean isDynamic = false; - boolean isPublic = false; - NamespaceItem ns = packageInternalNs; - while (s.isType(SymbolType.FINAL, SymbolType.DYNAMIC, SymbolType.PUBLIC)) { - if (s.type == SymbolType.FINAL) { - if (isFinal) { - throw new AVM2ParseException("Only one final keyword allowed", lexer.yyline()); - } - isFinal = true; - } - if (s.type == SymbolType.PUBLIC) { - if (!inPackage) { - throw new AVM2ParseException("public only allowed inside package", lexer.yyline()); - - } - if (isPublic) { - throw new AVM2ParseException("Only one public keyword allowed", lexer.yyline()); - } - isPublic = true; - ns = publicNs; - } - if (s.type == SymbolType.DYNAMIC) { - if (isDynamic) { - throw new AVM2ParseException("Only one dynamic keyword allowed", lexer.yyline()); - } - isDynamic = true; - } - s = lex(); - } - - switch (s.type) { - case CLASS: - case INTERFACE: - isEmpty = false; - List subOpenedNamespaces = new ArrayList<>(openedNamespaces); - boolean isInterface = false; - if (s.type == SymbolType.INTERFACE) { - isInterface = true; - } - GraphTargetItem extendsTypeStr = null; - List interfaces = new ArrayList<>(); - String subNameStr; - - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - subNameStr = s.value.toString(); - s = lex(); - if (!isInterface) { - - if (s.type == SymbolType.EXTENDS) { - extendsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); - s = lex(); - } - if (s.type == SymbolType.IMPLEMENTS) { - do { - GraphTargetItem implementsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); - interfaces.add(implementsTypeStr); - s = lex(); - } while (s.type == SymbolType.COMMA); - } - expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); - } else { - if (s.type == SymbolType.EXTENDS) { - do { - GraphTargetItem intExtendsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); - interfaces.add(intExtendsTypeStr); - s = lex(); - } while (s.type == SymbolType.COMMA); - } - expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); - } - - if (extendsTypeStr != null) { - List indices = new ArrayList<>(); - List names = new ArrayList<>(); - List namespaces = new ArrayList<>(); - //FIXME for Private classes in script (?) - AVM2SourceGenerator.parentNamesAddNames(abcIndex, AVM2SourceGenerator.resolveType(new SourceGeneratorLocalData(new HashMap<>(), 0, false, 0), ((TypeItem) ((UnresolvedAVM2Item) extendsTypeStr).resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>())), abcIndex), indices, names, namespaces); - for (int i = 0; i < names.size(); i++) { - if (namespaces.get(i) == null || namespaces.get(i).isEmpty()) { - continue; - } - subOpenedNamespaces.add(new NamespaceItem(namespaces.get(i) + ":" + names.get(i), Namespace.KIND_STATIC_PROTECTED)); - } - } - - List cinit = new ArrayList<>(); - Reference cinitNeedsActivation = new Reference<>(false); - - Reference iinit = new Reference<>(null); - List cinitVariables = new ArrayList<>(); - List iinitVariables = new ArrayList<>(); - Reference iinitNeedsActivation = new Reference<>(false); - - List subTraits = new ArrayList<>(); - - classTraits(allOpenedNamespaces, !inPackage, cinitVariables, cinitNeedsActivation, cinit, importedClasses, subOpenedNamespaces, ns, subNameStr, isInterface, subTraits, iinitVariables, iinitNeedsActivation, iinit); - - if (isInterface) { - traits.add(new InterfaceAVM2Item(metadata, importedClasses, ns, subOpenedNamespaces, isFinal, subNameStr, interfaces, subTraits)); - } else { - traits.add(new ClassAVM2Item(metadata, importedClasses, ns, subOpenedNamespaces, isFinal, isDynamic, subNameStr, extendsTypeStr, interfaces, cinit, cinitNeedsActivation.getVal(), cinitVariables, iinit.getVal(), iinitVariables, subTraits, iinitNeedsActivation.getVal())); - } - - expectedType(SymbolType.CURLY_CLOSE); - break; - case FUNCTION: - isEmpty = false; - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String fname = s.value.toString(); - - traits.add(method(allOpenedNamespaces, !inPackage, false, metadata, ns, false, null, new Reference<>(false), importedClasses, false, isFinal, null, openedNamespaces, true, fname, true, new ArrayList<>())); - break; - case CONST: - case VAR: - isEmpty = false; - boolean isConst = s.type == SymbolType.CONST; - if (isFinal) { - throw new AVM2ParseException("Final flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); - } - - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String vcname = s.value.toString(); - s = lex(); - GraphTargetItem type; - if (s.type == SymbolType.COLON) { - type = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); - s = lex(); - } else { - type = TypeItem.UNBOUNDED; - } - - GraphTargetItem value = null; - - if (s.type == SymbolType.ASSIGN) { - value = expression(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new HashMap<>(), false, false, true, sinitVariables); - s = lex(); - } - GraphTargetItem tar; - if (isConst) { - tar = new ConstAVM2Item(metadata, ns, null, false, vcname, type, value, lexer.yyline()); - } else { - tar = new SlotAVM2Item(metadata, ns, null, false, vcname, type, value, lexer.yyline()); - } - traits.add(tar); - if (s.type != SymbolType.SEMICOLON) { - lexer.pushback(s); - } - break; - case NAMESPACE: - isEmpty = false; - if (isFinal) { - throw new AVM2ParseException("Final flag not allowed for namespaces", lexer.yyline()); - } - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String nname = s.value.toString(); - String nval; - s = lex(); - - if (s.type == SymbolType.ASSIGN) { - s = lex(); - expected(s, lexer.yyline(), SymbolType.STRING); - nval = s.value.toString(); - s = lex(); - } else { - nval = ns + "/" + nname; - } - if (s.type != SymbolType.SEMICOLON) { - lexer.pushback(s); - } - - traits.add(new ConstAVM2Item(metadata, ns, null, true, nname, new TypeItem(DottedChain.NAMESPACE), new StringAVM2Item(null, null, nval), lexer.yyline())); - break; - default: - lexer.pushback(s); - break looptrait; - } - - } - if (inPackage) { - expectedType(SymbolType.CURLY_CLOSE); - } - return !isEmpty; - } - - private GraphTargetItem expressionCommands(ParsedSymbol s, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, List variables) throws IOException, AVM2ParseException { - GraphTargetItem ret = null; - switch (s.type) { - /*case INT: - expectedType(SymbolType.PARENT_OPEN); - ret = new ToIntegerAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.PARENT_CLOSE); - break; - case NUMBER_OP: - s = lex(); - if (s.type == SymbolType.DOT) { - VariableAVM2Item vi = new VariableAVM2Item(s.value.toString(), null, false); - variables.add(vi); - ret = memberOrCall(allOpenedNamespaces, thisType,vi, registerVars, inFunction, inMethod, variables); - } else { - expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); - ret = new ToNumberAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.PARENT_CLOSE); - } - break; - case STRING_OP: - ParsedSymbol sop = s; - s = lex(); - if (s.type == SymbolType.DOT) { - lexer.pushback(s); - VariableAVM2Item vi2 = new VariableAVM2Item(sop.value.toString(), null, false); - variables.add(vi2); - ret = memberOrCall(allOpenedNamespaces, thisType,vi2, registerVars, inFunction, inMethod, variables); - } else { - expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); - ret = new ToStringAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.PARENT_CLOSE); - ret = memberOrCall(allOpenedNamespaces, thisType,ret, registerVars, inFunction, inMethod, variables); - } - break;*/ - default: - return null; - } - //return ret; - } - - private GraphTargetItem add(Object a) { - if (a instanceof List) { - List l = (List) a; - if (l.isEmpty()) { - return null; - } - GraphTargetItem o = add(l.get(0)); - for (int i = 1; i < l.size(); i++) { - o = add(o, l.get(i)); - } - return o; - } - if (a instanceof StringBuilder) { - if (((StringBuilder) a).length() == 0) { - return null; - } - GraphTargetItem ret = new StringAVM2Item(null, null, a.toString()); - ((StringBuilder) a).setLength(0); - return ret; - } - if (a instanceof String) { - return new StringAVM2Item(null, null, (String) a); - } - if (a instanceof GraphTargetItem) { - return (GraphTargetItem) a; - } - return null; - } - - private GraphTargetItem add(Object a, Object b) { - GraphTargetItem ta = add(a); - GraphTargetItem tb = add(b); - if (ta == null && tb == null) { - return null; - } - if (ta == null) { - return tb; - } - if (tb == null) { - return ta; - } - return new AddAVM2Item(null, null, ta, tb); - } - - private void addS(List rets, StringBuilder sb) { - if (sb.length() > 0) { - if (!rets.isEmpty() && (rets.get(rets.size() - 1) instanceof StringAVM2Item)) { - StringAVM2Item stringItem = ((StringAVM2Item) rets.get(rets.size() - 1)); - stringItem.setValue(stringItem.getValue() + sb.toString()); - } else { - rets.add(new StringAVM2Item(null, null, sb.toString())); - } - sb.setLength(0); - } - } - - private List xmltag(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference usesVars, List openedTags, Reference closedVarTags, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - ParsedSymbol s; - List rets = new ArrayList<>(); - //GraphTargetItem ret = null; - StringBuilder sb = new StringBuilder(); - loop: - do { - s = lex(); - List sub = new ArrayList<>(); - Reference subclose = new Reference<>(0); - Reference subusesvars = new Reference<>(false); - switch (s.type) { - case XML_ATTRNAMEVAR_BEGIN: //add - usesVars.setVal(true); - addS(rets, sb); - rets.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.CURLY_CLOSE); - expectedType(SymbolType.ASSIGN); - sb.append("="); - lexer.yybegin(ActionScriptLexer.XMLOPENTAGATTRIB); - break; - case XML_ATTRVALVAR_BEGIN: //esc_xattr - usesVars.setVal(true); - sb.append("\""); - addS(rets, sb); - rets.add(new EscapeXAttrAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); - sb.append("\""); - expectedType(SymbolType.CURLY_CLOSE); - lexer.yybegin(ActionScriptLexer.XMLOPENTAG); - break; - case XML_INSTRATTRNAMEVAR_BEGIN: //add - usesVars.setVal(true); - addS(rets, sb); - rets.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.CURLY_CLOSE); - expectedType(SymbolType.ASSIGN); - sb.append("="); - lexer.yybegin(ActionScriptLexer.XMLOPENTAGATTRIB); - break; - case XML_INSTRATTRVALVAR_BEGIN: //esc_xattr - usesVars.setVal(true); - sb.append("\""); - addS(rets, sb); - rets.add(new EscapeXAttrAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); - sb.append("\""); - expectedType(SymbolType.CURLY_CLOSE); - lexer.yybegin(ActionScriptLexer.XMLOPENTAG); - break; - case XML_VAR_BEGIN: //esc_xelem - usesVars.setVal(true); - addS(rets, sb); - rets.add(new EscapeXElemAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); - expectedType(SymbolType.CURLY_CLOSE); - lexer.yybegin(ActionScriptLexer.XML); - break; - case XML_FINISHVARTAG_BEGIN: //add - usesVars.setVal(true); - closedVarTags.setVal(closedVarTags.getVal() + 1); - sb.append(""); - addS(rets, sb); - lexer.yybegin(ActionScriptLexer.XML); - break; - case XML_STARTVARTAG_BEGIN: //add - //openedTags.add("*"); - - //ret = add(ret, ); - GraphTargetItem ex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.CURLY_CLOSE); - lexer.yybegin(ActionScriptLexer.XMLOPENTAG); - sub.add("*"); - sb.append("<"); - addS(rets, sb); - rets.add(ex); - rets.addAll(xmltag(allOpenedNamespaces, thisType, pkg, subusesvars, sub, subclose, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); - closedVarTags.setVal(subclose.getVal() + subclose.getVal()); - break; - case XML_INSTRVARTAG_BEGIN: //add - usesVars.setVal(true); - addS(rets, sb); - sb.append(" st = xmltag(allOpenedNamespaces, thisType, pkg, subusesvars, sub, closedVarTags, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); - sb.append(s.value.toString()); - addS(rets, sb); - rets.addAll(st); - closedVarTags.setVal(subclose.getVal() + subclose.getVal()); - break; - /*case XML_STARTTAG_END: - sb.append(s.value.toString()); - ret = addstr(ret,sb); - break;*/ - case XML_FINISHTAG: - String tname = s.value.toString().substring(2, s.value.toString().length() - 1).trim(); - if (openedTags.contains(tname)) { - openedTags.remove(tname); - } else if (openedTags.contains("*")) { - openedTags.remove("*"); - } else { - throw new AVM2ParseException("XML : Closing unopened tag", lexer.yyline()); - } - sb.append(s.value.toString()); - break; - case XML_STARTFINISHTAG_END: - openedTags.remove(openedTags.size() - 1); //close last tag - sb.append(s.value.toString()); - break; - case EOF: - throw new AVM2ParseException("End of file before XML finish", lexer.yyline()); - default: - sb.append(s.value.toString()); - break; - } - } while (!(openedTags.isEmpty() || closedVarTags.getVal() >= openedTags.size())); - addS(rets, sb); - return rets; - } - - private GraphTargetItem xml(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - List openedTags = new ArrayList<>(); - int closedVarTags = 0; - - GraphTargetItem ret = add(xmltag(allOpenedNamespaces, thisType, pkg, new Reference<>(false), openedTags, new Reference<>(closedVarTags), needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); - ret = new XMLAVM2Item(ret); - lexer.yybegin(ActionScriptLexer.YYINITIAL); - //TODO: Order of additions as in official compiler - return ret; - } - - private GraphTargetItem command(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, boolean mustBeCommand, List variables) throws IOException, AVM2ParseException { - LexBufferer buf = new LexBufferer(); - lexer.addListener(buf); - GraphTargetItem ret = null; - if (debugMode) { - System.out.println("command:"); - } - ParsedSymbol s = lex(); - if (s.type == SymbolType.EOF) { - return null; - } - String loopLabel = null; - - if (s.group == SymbolGroup.IDENTIFIER) { - ParsedSymbol sc = lex(); - if (sc.type == SymbolType.COLON) { - loopLabel = s.value.toString(); - s = lex(); - } else { - lexer.pushback(sc); - } - } - - if (s.type == SymbolType.DEFAULT) { - ParsedSymbol sx = lex(); - if (sx.group != SymbolGroup.IDENTIFIER) { - lexer.pushback(sx); - } else if (!sx.value.equals("xml")) { - lexer.pushback(sx); - } else { - expectedType(SymbolType.NAMESPACE); - expectedType(SymbolType.ASSIGN); - GraphTargetItem ns = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - ret = new DefaultXMLNamespace(null, null, ns); - //TODO: use dxns for attribute namespaces instead of dxnslate - } - } - if (ret == null) { - switch (s.type) { - case USE: - expectedType(SymbolType.NAMESPACE); - GraphTargetItem ns = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - openedNamespaces.add(new NamespaceItem(ns.toString(), Namespace.KIND_PACKAGE /*FIXME?*/)); - break; - case WITH: - needsActivation.setVal(true); - expectedType(SymbolType.PARENT_OPEN); - GraphTargetItem wvar = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//(name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables)); - if (!isNameOrProp(wvar)) { - throw new AVM2ParseException("Not a property or name", lexer.yyline()); - } - expectedType(SymbolType.PARENT_CLOSE); - expectedType(SymbolType.CURLY_OPEN); - List withVars = new ArrayList<>(); - List wcmd = commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, withVars); - variables.addAll(withVars); - for (AssignableAVM2Item a : withVars) { - if (a instanceof UnresolvedAVM2Item) { - UnresolvedAVM2Item ua = (UnresolvedAVM2Item) a; - ua.scopeStack.add(0, wvar); - } - } - expectedType(SymbolType.CURLY_CLOSE); - ret = new WithAVM2Item(null, null, wvar, wcmd); - ((WithAVM2Item) ret).subvariables = withVars; - break; - /*case DELETE: - GraphTargetItem varDel = expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); - if(!isNameOrProp(varDel)){ - throw new ParseException("Not a property or name", lexer.yyline()); - } - if (varDel instanceof GetPropertyAVM2Item) { - GetPropertyAVM2Item gm = (GetPropertyAVM2Item) varDel; - ret = new DeletePropertyAVM2Item(null, null, gm.object, gm.propertyName); - } else if (varDel instanceof NameAVM2Item) { - variables.remove(varDel); - ret = new DeletePropertyAVM2Item(null, null, null, (NameAVM2Item) varDel); - } else { - throw new ParseException("Not a property", lexer.yyline()); - } - break;*/ - case FUNCTION: - s = lexer.lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - needsActivation.setVal(true); - ret = (function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, s.value.toString(), false, variables)); - break; - case VAR: - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - String varIdentifier = s.value.toString(); - s = lex(); - GraphTargetItem type; - if (s.type == SymbolType.COLON) { - type = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - s = lex(); - } else { - type = new UnboundedTypeItem(); - } - - if (s.type == SymbolType.ASSIGN) { - GraphTargetItem varval = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - ret = new NameAVM2Item(type, lexer.yyline(), varIdentifier, varval, true, openedNamespaces); - variables.add((NameAVM2Item) ret); - } else { - ret = new NameAVM2Item(type, lexer.yyline(), varIdentifier, null, true, openedNamespaces); - variables.add((NameAVM2Item) ret); - lexer.pushback(s); - } - break; - case CURLY_OPEN: - ret = new BlockItem(null, null, commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); - expectedType(SymbolType.CURLY_CLOSE); - break; - /*case INCREMENT: //preincrement - case DECREMENT: //predecrement - GraphTargetItem varincdec = expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); - if(!isNameOrProp(varincdec)){ - throw new ParseException("Not a property or name", lexer.yyline()); - } - if (s.type == SymbolType.INCREMENT) { - ret = new PreIncrementAVM2Item(null, null, varincdec); - } else if (s.type == SymbolType.DECREMENT) { - ret = new PreDecrementAVM2Item(null, null, varincdec); - } - break;*/ - case SUPER: //constructor call - ParsedSymbol ss2 = lex(); - if (ss2.type == SymbolType.PARENT_OPEN) { - List args = call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); - ret = new ConstructSuperAVM2Item(null, null, new LocalRegAVM2Item(null, null, 0, null), args); - } else {//no costructor call, but it could be calling parent methods... => handle in expression - lexer.pushback(ss2); - lexer.pushback(s); - } - break; - case IF: - expectedType(SymbolType.PARENT_OPEN); - GraphTargetItem ifExpr = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.PARENT_CLOSE); - GraphTargetItem onTrue = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); - List onTrueList = new ArrayList<>(); - onTrueList.add(onTrue); - s = lex(); - List onFalseList = null; - if (s.type == SymbolType.ELSE) { - onFalseList = new ArrayList<>(); - onFalseList.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); - } else { - lexer.pushback(s); - } - ret = new IfItem(null, null, ifExpr, onTrueList, onFalseList); - break; - case WHILE: - expectedType(SymbolType.PARENT_OPEN); - List whileExpr = new ArrayList<>(); - whileExpr.add(commaExpression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); - expectedType(SymbolType.PARENT_CLOSE); - List whileBody = new ArrayList<>(); - Loop wloop = new Loop(uniqId(), null, null); - if (loopLabel != null) { - loopLabels.put(wloop, loopLabel); - } - loops.push(wloop); - whileBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); - ret = new WhileItem(null, null, wloop, whileExpr, whileBody); - break; - case DO: - List doBody = new ArrayList<>(); - Loop dloop = new Loop(uniqId(), null, null); - loops.push(dloop); - if (loopLabel != null) { - loopLabels.put(dloop, loopLabel); - } - doBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); - expectedType(SymbolType.WHILE); - expectedType(SymbolType.PARENT_OPEN); - List doExpr = new ArrayList<>(); - doExpr.add(commaExpression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); - expectedType(SymbolType.PARENT_CLOSE); - ret = new DoWhileItem(null, null, dloop, doBody, doExpr); - break; - case FOR: - s = lex(); - boolean forin = false; - boolean each = false; - GraphTargetItem collection = null; - if (s.type == SymbolType.EACH) { - each = true; - forin = true; - s = lex(); - } - expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); - GraphTargetItem firstCommand = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, false, variables); - if (firstCommand instanceof NameAVM2Item) { - NameAVM2Item nai = (NameAVM2Item) firstCommand; - if (nai.isDefinition() && nai.getAssignedValue() == null) { //Declared value in for..in - firstCommand = expression1(allOpenedNamespaces, firstCommand, GraphTargetItem.NOPRECEDENCE, thisType, pkg, needsActivation, importedClasses, openedNamespaces, true, registerVars, inFunction, inMethod, true, variables); - } - } - InAVM2Item inexpr = null; - if (firstCommand instanceof InAVM2Item) { - forin = true; - inexpr = (InAVM2Item) firstCommand; - } else if (forin) { - throw new AVM2ParseException("In expression required", lexer.yyline()); - } - - Loop floop = new Loop(uniqId(), null, null); - loops.push(floop); - if (loopLabel != null) { - loopLabels.put(floop, loopLabel); - } - List forFinalCommands = new ArrayList<>(); - GraphTargetItem forExpr = null; - List forFirstCommands = new ArrayList<>(); - if (!forin) { - s = lex(); - if (firstCommand != null) { //can be empty command - forFirstCommands.add(firstCommand); - } - while (s.isType(SymbolType.COMMA)) { - forFirstCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, false, variables)); - s = lex(); - } - lexer.pushback(s); - //GraphTargetItem firstCommand = command(thisType,pkg,needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); - forExpr = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.SEMICOLON); - GraphTargetItem fcom = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); - if (fcom != null) { - forFinalCommands.add(fcom); - } - } - expectedType(SymbolType.PARENT_CLOSE); - List forBody = new ArrayList<>(); - forBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forin ? forinlevel + 1 : forinlevel, true, variables)); - if (forin) { - if (each) { - ret = new ForEachInAVM2Item(null, null, floop, inexpr, forBody); - } else { - - ret = new ForInAVM2Item(null, null, floop, inexpr, forBody); - } - } else { - ret = new ForItem(null, null, floop, forFirstCommands, forExpr, forFinalCommands, forBody); - } - break; - case SWITCH: - Loop sloop = new Loop(-uniqId(), null, null); //negative id marks switch = no continue - loops.push(sloop); - if (loopLabel != null) { - loopLabels.put(sloop, loopLabel); - } - expectedType(SymbolType.PARENT_OPEN); - GraphTargetItem switchExpr = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.PARENT_CLOSE); - expectedType(SymbolType.CURLY_OPEN); - s = lex(); - //ret.addAll(switchExpr); - int exprReg = 0; - for (int i = 0; i < 256; i++) { - if (!registerVars.containsValue(i)) { - registerVars.put("__switch" + uniqId(), i); - exprReg = i; - break; - } - } - List> caseIfs = new ArrayList<>(); - List> caseCmds = new ArrayList<>(); - List caseExprsAll = new ArrayList<>(); - List valueMapping = new ArrayList<>(); - int pos = 0; - while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { - while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { - GraphTargetItem curCaseExpr = s.type == SymbolType.DEFAULT ? new DefaultItem() : expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.COLON); - s = lex(); - caseExprsAll.add(curCaseExpr); - valueMapping.add(pos); - } - pos++; - lexer.pushback(s); - List caseCmd = commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables); - caseCmds.add(caseCmd); - s = lex(); - } - expected(s, lexer.yyline(), SymbolType.CURLY_CLOSE); - ret = new SwitchItem(null, null, sloop, switchExpr, caseExprsAll, caseCmds, valueMapping); - break; - case BREAK: - s = lex(); - long bloopId = 0; - if (loops.isEmpty()) { - throw new AVM2ParseException("No loop to break", lexer.yyline()); - } - if (s.group == SymbolGroup.IDENTIFIER) { - String breakLabel = s.value.toString(); - for (Loop l : loops) { - if (breakLabel.equals(loopLabels.get(l))) { - bloopId = l.id; - break; - } - } - if (bloopId == 0) { - throw new AVM2ParseException("Identifier of loop expected", lexer.yyline()); - } - } else { - lexer.pushback(s); - bloopId = loops.peek().id; - } - ret = new BreakItem(null, null, bloopId); - break; - case CONTINUE: - s = lex(); - long cloopId = 0; - if (loops.isEmpty()) { - throw new AVM2ParseException("No loop to continue", lexer.yyline()); - } - if (s.group == SymbolGroup.IDENTIFIER) { - String continueLabel = s.value.toString(); - for (Loop l : loops) { - if (l.id < 0) { //negative id marks switch => no continue - continue; - } - if (continueLabel.equals(loopLabels.get(l))) { - cloopId = l.id; - break; - } - } - if (cloopId == -1) { - throw new AVM2ParseException("Identifier of loop expected", lexer.yyline()); - } - } else { - lexer.pushback(s); - for (int i = loops.size() - 1; i >= 0; i--) { - if (loops.get(i).id >= 0) {//no switches - cloopId = loops.get(i).id; - break; - } - } - if (cloopId <= 0) { - throw new AVM2ParseException("No loop to continue", lexer.yyline()); - } - } - //TODO: handle switch - ret = new ContinueItem(null, null, cloopId); - break; - case RETURN: - GraphTargetItem retexpr = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, true, registerVars, inFunction, inMethod, true, variables); - if (retexpr == null) { - ret = new ReturnVoidAVM2Item(null, null); - } else { - ret = new ReturnValueAVM2Item(null, null, retexpr); - } - break; - case TRY: - needsActivation.setVal(true); - List tryCommands = new ArrayList<>(); - tryCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); - s = lex(); - boolean found = false; - List> catchCommands = new ArrayList<>(); - List catchExceptions = new ArrayList<>(); - int varCnt = variables.size(); - List> catchesVars = new ArrayList<>(); - while (s.type == SymbolType.CATCH) { - expectedType(SymbolType.PARENT_OPEN); - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); - - String enamestr = s.value.toString(); - expectedType(SymbolType.COLON); - GraphTargetItem etype = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - NameAVM2Item e = new NameAVM2Item(etype, lexer.yyline(), enamestr, new ExceptionAVM2Item(null)/*?*/, true/*?*/, openedNamespaces); - variables.add(e); - catchExceptions.add(e); - e.setSlotNumber(1); - e.setSlotScope(Integer.MAX_VALUE); //will be changed later - expectedType(SymbolType.PARENT_CLOSE); - List cc = new ArrayList<>(); - List catchVars = new ArrayList<>(); - cc.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, catchVars)); - catchesVars.add(catchVars); - variables.addAll(catchVars); - - for (AssignableAVM2Item a : catchVars) { - if (a instanceof UnresolvedAVM2Item) { - UnresolvedAVM2Item ui = (UnresolvedAVM2Item) a; - if (ui.getVariableName().equals(DottedChain.parseWithSuffix(e.getVariableName()))) { - try { - ui.resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), variables); - } catch (CompilationException ex) { - // ignore - } - ui.setSlotNumber(e.getSlotNumber()); - ui.setSlotScope(e.getSlotScope()); - } - - } - } - - catchCommands.add(cc); - s = lex(); - found = true; - } - //TODO: - for (int i = varCnt; i < variables.size(); i++) { - AssignableAVM2Item av = variables.get(i); - if (av instanceof UnresolvedAVM2Item) { - UnresolvedAVM2Item ui = (UnresolvedAVM2Item) av; - for (NameAVM2Item e : catchExceptions) { - if (ui.getVariableName().equals(DottedChain.parseWithSuffix(e.getVariableName()))) { - try { - ui.resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), variables); - } catch (CompilationException ex) { - // ignore - } - ui.setSlotNumber(e.getSlotNumber()); - ui.setSlotScope(e.getSlotScope()); - } - } - } - } - - List finallyCommands = null; - if (s.type == SymbolType.FINALLY) { - finallyCommands = new ArrayList<>(); - finallyCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); - found = true; - s = lex(); - } - if (!found) { - expected(s, lexer.yyline(), SymbolType.CATCH, SymbolType.FINALLY); - } - lexer.pushback(s); - TryAVM2Item tai = new TryAVM2Item(tryCommands, null, catchCommands, finallyCommands, ""); - tai.catchVariables = catchesVars; - tai.catchExceptions2 = catchExceptions; - ret = tai; - break; - case THROW: - ret = new ThrowAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - break; - default: - GraphTargetItem valcmd = expressionCommands(s, registerVars, inFunction, inMethod, forinlevel, variables); - if (valcmd != null) { - ret = valcmd; - break; - } - if (s.type == SymbolType.SEMICOLON) { - return null; - } - lexer.pushback(s); - ret = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - if (debugMode) { - System.out.println("/command"); - } - } - } - if (debugMode) { - System.out.println("/command"); - } - lexer.removeListener(buf); - if (ret == null) { //can be popped expression - buf.pushAllBack(lexer); - ret = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - } - s = lex(); - if ((s != null) && (s.type != SymbolType.SEMICOLON)) { - lexer.pushback(s); - } - - return ret; - - } - - private GraphTargetItem expression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { - return expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, allowRemainder, variables); - } - - /*private GraphTargetItem expressionRemainder(TypeItem thisType, String pkg, Reference needsActivation, List openedNamespaces, GraphTargetItem expr, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables, List importedClasses) throws IOException, AVM2ParseException { - GraphTargetItem ret = null; - ParsedSymbol s = lex(); - - ret = fixPrecedence(ret); - return ret; - }*/ - private boolean isNameOrProp(GraphTargetItem item) { - if (item instanceof UnresolvedAVM2Item) { - return true; //we don't know yet - } - if (item instanceof NameAVM2Item) { - return true; - } - if (item instanceof PropertyAVM2Item) { - return true; - } - return (item instanceof IndexAVM2Item); - } - - private boolean isType(GraphTargetItem item) { - if (item == null) { - return false; - } - while (item instanceof GetPropertyAVM2Item) { - item = ((GetPropertyAVM2Item) item).object; - } - return (item instanceof NameAVM2Item); - } - - private int brackets(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, List ret, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { - ParsedSymbol s = lex(); - int arrCnt = 0; - if (s.type == SymbolType.BRACKET_OPEN) { - s = lex(); - - while (s.type != SymbolType.BRACKET_CLOSE) { - if (s.type != SymbolType.COMMA) { - lexer.pushback(s); - } - arrCnt++; - ret.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - s = lex(); - if (!s.isType(SymbolType.COMMA, SymbolType.BRACKET_CLOSE)) { - expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.BRACKET_CLOSE); - } - } - } else { - lexer.pushback(s); - return -1; - } - return arrCnt; - } - - private GraphTargetItem commaExpression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forInLevel, List variables) throws IOException, AVM2ParseException { - GraphTargetItem cmd = null; - List expr = new ArrayList<>(); - ParsedSymbol s; - do { - cmd = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forInLevel, false, variables); - if (cmd != null) { - expr.add(cmd); - } - s = lex(); - } while (s.type == SymbolType.COMMA && cmd != null); - lexer.pushback(s); - if (cmd == null) { - expr.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - } else if (!cmd.hasReturnValue()) { - throw new AVM2ParseException("Expression expected", lexer.yyline()); - } - return new CommaExpressionItem(null, null, expr); - } - - private GraphTargetItem expression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { - GraphTargetItem prim = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); - if (prim == null) { - return null; - } - return expression1(allOpenedNamespaces, prim, GraphTargetItem.NOPRECEDENCE, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); - } - - /** - * Lexer can return XML opentags instead of greater. In expression, we need - * greater sign only - * - * @param symb - */ - private void xmlToLowerThanFix(ParsedSymbol symb) { - if (symb.isType(SymbolType.XML_STARTVARTAG_BEGIN, SymbolType.XML_STARTTAG_BEGIN)) { - lexer.yypushbackstr(symb.value.toString().substring(1)); //parse again as LOWER_THAN - String pb = symb.value.toString().substring(1); - symb.type = SymbolType.LOWER_THAN; - symb.group = SymbolGroup.OPERATOR; - symb.value = "<"; - if (pb.charAt(0) == '=') { - symb.type = SymbolType.LOWER_EQUAL; - symb.value = "<="; - pb = pb.substring(1); - } - lexer.yypushbackstr(pb); //parse again as LOWER_THAN - } - } - - private void regexpToDivideFix(ParsedSymbol symb) { - if (symb.isType(SymbolType.REGEXP)) { - String pb = symb.value.toString().substring(1); - symb.type = SymbolType.DIVIDE; - symb.group = SymbolGroup.OPERATOR; - symb.value = "/"; - if (pb.charAt(0) == '=') { - symb.type = SymbolType.ASSIGN_DIVIDE; - symb.value = "/="; - pb = pb.substring(1); - } - lexer.yypushbackstr(pb); //parse again as DIVIDE - - } - } - - private ParsedSymbol peekExprToken() throws IOException, AVM2ParseException { - ParsedSymbol lookahead = lex(); - xmlToLowerThanFix(lookahead); - regexpToDivideFix(lookahead); - - lexer.pushback(lookahead); - return lookahead; - } - - private GraphTargetItem expression1(List> allOpenedNamespaces, GraphTargetItem lhs, int min_precedence, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { - if (debugMode) { - System.out.println("expression1:"); - } - ParsedSymbol lookahead = peekExprToken(); - - ParsedSymbol op; - GraphTargetItem rhs; - GraphTargetItem mhs = null; - - //Note: algorithm from http://en.wikipedia.org/wiki/Operator-precedence_parser - //with relation operators reversed as we have precedence in reverse order - while (lookahead.type.isBinary() && lookahead.type.getPrecedence() <= /* >= on wiki */ min_precedence) { - op = lookahead; - lex(); - - //Note: Handle ternar operator as Binary - //http://stackoverflow.com/questions/13681293/how-can-i-incorporate-ternary-operators-into-a-precedence-climbing-algorithm - if (op.type == SymbolType.TERNAR) { - if (debugMode) { - System.out.println("ternar-middle:"); - } - mhs = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); - expectedType(SymbolType.COLON); - if (debugMode) { - System.out.println("/ternar-middle"); - } - } - - rhs = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); - if (rhs == null) { - lexer.pushback(op); - break; - } - - lookahead = peekExprToken(); - while ((lookahead.type.isBinary() && lookahead.type.getPrecedence() < /* > on wiki */ op.type.getPrecedence()) - || (lookahead.type.isRightAssociative() && lookahead.type.getPrecedence() == op.type.getPrecedence())) { - rhs = expression1(allOpenedNamespaces, rhs, lookahead.type.getPrecedence(), thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); - lookahead = peekExprToken(); - } - - switch (op.type) { - case AS: - //GraphTargetItem type = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - - lhs = new AsTypeAVM2Item(null, null, lhs, rhs); //??? - allowRemainder = false; - break; - - case IN: - lhs = new InAVM2Item(null, null, lhs, rhs); - break; - - case TERNAR: //??? - lhs = new TernarOpItem(null, null, lhs, mhs, rhs); - break; - case SHIFT_LEFT: - lhs = new LShiftAVM2Item(null, null, lhs, rhs); - break; - case SHIFT_RIGHT: - lhs = new RShiftAVM2Item(null, null, lhs, rhs); - break; - case USHIFT_RIGHT: - lhs = new URShiftAVM2Item(null, null, lhs, rhs); - break; - case BITAND: - lhs = new BitAndAVM2Item(null, null, lhs, rhs); - break; - case BITOR: - lhs = new BitOrAVM2Item(null, null, lhs, rhs); - break; - case DIVIDE: - lhs = new DivideAVM2Item(null, null, lhs, rhs); - break; - case MODULO: - lhs = new ModuloAVM2Item(null, null, lhs, rhs); - break; - case EQUALS: - lhs = new EqAVM2Item(null, null, lhs, rhs); - break; - case STRICT_EQUALS: - lhs = new StrictEqAVM2Item(null, null, lhs, rhs); - break; - case NOT_EQUAL: - lhs = new NeqAVM2Item(null, null, lhs, rhs); - break; - case STRICT_NOT_EQUAL: - lhs = new StrictNeqAVM2Item(null, null, lhs, rhs); - break; - case LOWER_THAN: - lhs = new LtAVM2Item(null, null, lhs, rhs); - break; - case LOWER_EQUAL: - lhs = new LeAVM2Item(null, null, lhs, rhs); - break; - case GREATER_THAN: - lhs = new GtAVM2Item(null, null, lhs, rhs); - break; - case GREATER_EQUAL: - lhs = new GeAVM2Item(null, null, lhs, rhs); - break; - case AND: - lhs = new AndItem(null, null, lhs, rhs); - break; - case OR: - lhs = new OrItem(null, null, lhs, rhs); - break; - case MINUS: - lhs = new SubtractAVM2Item(null, null, lhs, rhs); - break; - case MULTIPLY: - lhs = new MultiplyAVM2Item(null, null, lhs, rhs); - break; - case PLUS: - lhs = new AddAVM2Item(null, null, lhs, rhs); - break; - case XOR: - lhs = new BitXorAVM2Item(null, null, lhs, rhs); - break; - case INSTANCEOF: - lhs = new InstanceOfAVM2Item(null, null, lhs, rhs); - break; - case IS: - GraphTargetItem istype = rhs;//type(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, variables); - lhs = new IsTypeAVM2Item(null, null, lhs, istype); - break; - case ASSIGN: - case ASSIGN_BITAND: - case ASSIGN_BITOR: - case ASSIGN_DIVIDE: - case ASSIGN_MINUS: - case ASSIGN_MODULO: - case ASSIGN_MULTIPLY: - case ASSIGN_PLUS: - case ASSIGN_SHIFT_LEFT: - case ASSIGN_SHIFT_RIGHT: - case ASSIGN_USHIFT_RIGHT: - case ASSIGN_XOR: - GraphTargetItem assigned = rhs; - switch (op.type) { - case ASSIGN: - //assigned = assigned; - break; - case ASSIGN_BITAND: - assigned = new BitAndAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_BITOR: - assigned = new BitOrAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_DIVIDE: - assigned = new DivideAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_MINUS: - assigned = new SubtractAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_MODULO: - assigned = new ModuloAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_MULTIPLY: - assigned = new MultiplyAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_PLUS: - assigned = new AddAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_SHIFT_LEFT: - assigned = new LShiftAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_SHIFT_RIGHT: - assigned = new RShiftAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_USHIFT_RIGHT: - assigned = new URShiftAVM2Item(null, null, lhs, assigned); - break; - case ASSIGN_XOR: - assigned = new BitXorAVM2Item(null, null, lhs, assigned); - break; - } - - if (!(lhs instanceof AssignableAVM2Item)) { - throw new AVM2ParseException("Invalid assignment", lexer.yyline()); - } - AssignableAVM2Item as = ((AssignableAVM2Item) lhs).copy(); - if ((as instanceof UnresolvedAVM2Item) || (as instanceof NameAVM2Item)) { - variables.add(as); - } - as.setAssignedValue(assigned); - if (lhs instanceof NameAVM2Item) { - ((NameAVM2Item) lhs).setDefinition(false); - } - lhs = as; - break; - case DESCENDANTS: - expected(lookahead, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); - lookahead = lex(); - lhs = new GetDescendantsAVM2Item(lhs, lookahead.type == SymbolType.MULTIPLY ? null : lookahead.value.toString(), openedNamespaces); - allowRemainder = true; - break; - } - } - if (lhs instanceof ParenthesisItem) { - GraphTargetItem coerced = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); - if (coerced != null && isType(((ParenthesisItem) lhs).value)) { - lhs = new CoerceAVM2Item(null, null, ((ParenthesisItem) lhs).value, coerced); - } - } - - if (debugMode) { - System.out.println("/expression1"); - } - return lhs; - } - - private GraphTargetItem expressionPrimary(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { - if (debugMode) { - System.out.println("primary:"); - } - GraphTargetItem ret = null; - ParsedSymbol s = lex(); - boolean allowMemberOrCall = false; - switch (s.type) { - case PREPROCESSOR: - expectedType(SymbolType.PARENT_OPEN); - switch ("" + s.value) { - //AS3 - case "hasnext": - GraphTargetItem hnIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.COMMA); - GraphTargetItem hnObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - ret = new HasNextAVM2Item(null, null, hnIndex, hnObject); - break; - case "newactivation": - ret = new NewActivationAVM2Item(null, null); - break; - case "nextname": - GraphTargetItem nnIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.COMMA); - GraphTargetItem nnObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - - ret = new NextNameAVM2Item(null, null, nnIndex, nnObject); - allowMemberOrCall = true; - break; - case "nextvalue": - GraphTargetItem nvIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - expectedType(SymbolType.COMMA); - GraphTargetItem nvObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - - ret = new NextNameAVM2Item(null, null, nvIndex, nvObject); - allowMemberOrCall = true; - break; - //Both ASs - case "dup": - ret = new DuplicateItem(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - break; - case "push": - ret = new PushItem(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - break; - case "pop": - ret = new PopItem(null, null); - break; - case "goto": //TODO - case "multiname": - throw new AVM2ParseException("Compiling §§" + s.value + " is not available, sorry", lexer.yyline()); - default: - throw new AVM2ParseException("Unknown preprocessor instruction: §§" + s.value, lexer.yyline()); - } - expectedType(SymbolType.PARENT_CLOSE); - break; - case REGEXP: - String p = (String) s.value; - p = p.substring(1); - int spos = p.lastIndexOf('/'); - String mod = p.substring(spos + 1); - p = p.substring(0, spos); - p = p.replace("\\/", "/"); - ret = new RegExpAvm2Item(p, mod, null, null); - allowMemberOrCall = true; - - break; - case XML_STARTTAG_BEGIN: - lexer.pushback(s); - ret = xml(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); - break; - case STRING: - ret = new StringAVM2Item(null, null, s.value.toString()); - allowMemberOrCall = true; - break; - case NEGATE: - ret = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables); - ret = new NegAVM2Item(null, null, ret); - - break; - case MINUS: - s = lex(); - if (s.isType(SymbolType.DOUBLE)) { - ret = new FloatValueAVM2Item(null, null, -(Double) s.value); - - } else if (s.isType(SymbolType.INTEGER)) { - ret = new IntegerValueAVM2Item(null, null, -(Long) s.value); - - } else { - lexer.pushback(s); - GraphTargetItem num = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, true, variables); - if (num instanceof IntegerValueAVM2Item) { - ((IntegerValueAVM2Item) num).value = -((IntegerValueAVM2Item) num).value; - ret = num; - } else if (num instanceof FloatValueAVM2Item) { - Double d = ((FloatValueAVM2Item) num).value; - if (d.isInfinite()) { - ((FloatValueAVM2Item) num).value = Double.NEGATIVE_INFINITY; - } else { - ((FloatValueAVM2Item) num).value = -d; - } - ret = (num); - } else { - ret = (new SubtractAVM2Item(null, null, new IntegerValueAVM2Item(null, null, 0L), num)); - } - } - break; - case TYPEOF: - ret = new TypeOfAVM2Item(null, null, expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); - break; - case TRUE: - ret = new BooleanAVM2Item(null, null, true); - - break; - case NULL: - ret = new NullAVM2Item(null, null); - - break; - case UNDEFINED: - ret = new UndefinedAVM2Item(null, null); - break; - case FALSE: - ret = new BooleanAVM2Item(null, null, false); - - break; - case CURLY_OPEN: //Object literal - s = lex(); - List nvs = new ArrayList<>(); - - while (s.type != SymbolType.CURLY_CLOSE) { - if (s.type != SymbolType.COMMA) { - lexer.pushback(s); - } - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.STRING, SymbolType.INTEGER, SymbolType.DOUBLE); - - GraphTargetItem n = new StringAVM2Item(null, null, s.value.toString()); -//expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); - expectedType(SymbolType.COLON); - GraphTargetItem v = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); - - NameValuePair nv = new NameValuePair(n, v); - nvs.add(nv); - s = lex(); - if (!s.isType(SymbolType.COMMA, SymbolType.CURLY_CLOSE)) { - expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.CURLY_CLOSE); - } - } - ret = new NewObjectAVM2Item(null, null, nvs); - allowMemberOrCall = true; - break; - case BRACKET_OPEN: //Array literal or just brackets - lexer.pushback(s); - List inBrackets = new ArrayList<>(); - int arrCnt = brackets(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, inBrackets, registerVars, inFunction, inMethod, variables); - ret = new NewArrayAVM2Item(null, null, inBrackets); - allowMemberOrCall = true; - - break; - case FUNCTION: - s = lexer.lex(); - String fname = ""; - if (s.isType(SymbolGroup.IDENTIFIER)) { - fname = s.value.toString(); - } else { - lexer.pushback(s); - } - needsActivation.setVal(true); - ret = function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, fname, false, variables); - allowMemberOrCall = true; - break; - case NAN: - ret = new NanAVM2Item(null, null); - - break; - case INFINITY: - ret = new FloatValueAVM2Item(null, null, Double.POSITIVE_INFINITY); - - break; - case INTEGER: - ret = new IntegerValueAVM2Item(null, null, (Long) s.value); - - break; - case DOUBLE: - ret = new FloatValueAVM2Item(null, null, (Double) s.value); - - break; - case DELETE: - GraphTargetItem varDel = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); - if (!isNameOrProp(varDel)) { - throw new AVM2ParseException("Not a property or name", lexer.yyline()); - } - ret = new DeletePropertyAVM2Item(varDel, lexer.yyline()); - break; - case INCREMENT: - case DECREMENT: //preincrement - GraphTargetItem varincdec = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false/*?*/, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); - if (!isNameOrProp(varincdec)) { - throw new AVM2ParseException("Not a property or name", lexer.yyline()); - } - if (s.type == SymbolType.INCREMENT) { - ret = new PreIncrementAVM2Item(null, null, varincdec); - } - if (s.type == SymbolType.DECREMENT) { - ret = new PreDecrementAVM2Item(null, null, varincdec); - } - - break; - case NOT: - ret = new NotItem(null, null, expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); - - break; - case PARENT_OPEN: - ret = new ParenthesisItem(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); - expectedType(SymbolType.PARENT_CLOSE); - if (ret.value == null) { - throw new AVM2ParseException("Expression in parenthesis expected", lexer.yyline()); - } - allowMemberOrCall = true; - break; - case NEW: - s = lex(); - if (s.type == SymbolType.XML_STARTTAG_BEGIN) { - lexer.yypushbackstr(s.value.toString().substring(1), ActionScriptLexer.YYINITIAL); - s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.LOWER_THAN); - } - if (s.type == SymbolType.FUNCTION) { - s = lexer.lex(); - String ffname = ""; - if (s.isType(SymbolGroup.IDENTIFIER)) { - ffname = s.value.toString(); - } else { - lexer.pushback(s); - } - needsActivation.setVal(true); - ret = function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, ffname, false, variables); - } else if (s.type == SymbolType.LOWER_THAN) { - GraphTargetItem subtype = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); - expectedType(SymbolType.GREATER_THAN); - s = lex(); - expected(s, lexer.yyline(), SymbolType.BRACKET_OPEN); - lexer.pushback(s); - List params = new ArrayList<>(); - brackets(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, params, registerVars, inFunction, inMethod, variables); - ret = new InitVectorAVM2Item(subtype, params, openedNamespaces); - } else if (s.type == SymbolType.PARENT_OPEN) { - GraphTargetItem newvar = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); - newvar = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, newvar, registerVars, inFunction, inMethod, variables); - expectedType(SymbolType.PARENT_CLOSE); - expectedType(SymbolType.PARENT_OPEN); - ret = new ConstructSomethingAVM2Item(lexer.yyline(), openedNamespaces, newvar, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); - - } else { - lexer.pushback(s); - GraphTargetItem newvar = name(allOpenedNamespaces, thisType, pkg, needsActivation, false /*?*/, openedNamespaces, registerVars, inFunction, inMethod, variables, importedClasses); - newvar = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, newvar, registerVars, inFunction, inMethod, variables); - expectedType(SymbolType.PARENT_OPEN); - ret = new ConstructSomethingAVM2Item(lexer.yyline(), openedNamespaces, newvar, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); - } - allowMemberOrCall = true; - break; - case IDENTIFIER: - case THIS: - case SUPER: - case ATTRIBUTE: - lexer.pushback(s); - ret = name(allOpenedNamespaces, thisType, pkg, needsActivation, false, openedNamespaces, registerVars, inFunction, inMethod, variables, importedClasses); - allowMemberOrCall = true; - - //var = memberOrCall(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, var, registerVars, inFunction, inMethod, variables); - //ret = var; - break; - default: - GraphTargetItem excmd = expressionCommands(s, registerVars, inFunction, inMethod, -1, variables); - if (excmd != null) { - //? - ret = excmd; - allowMemberOrCall = true; //? - break; - } - lexer.pushback(s); - } - if (allowMemberOrCall && ret != null) { - ret = memberOrCall(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); - } - if (debugMode) { - System.out.println("/primary"); - } - return ret; - } - - private ActionScriptLexer lexer = null; - - private List constantPool; - - private List parseImportsUsages(List openedNamespaces) throws IOException, AVM2ParseException { - - ParsedSymbol s; - List importedClasses = new ArrayList<>(); - - s = lex(); - while (s.isType(SymbolType.IMPORT, SymbolType.USE)) { - boolean all = false; - boolean isUse = s.type == SymbolType.USE; - if (isUse) { - expectedType(SymbolType.NAMESPACE); - } - s = lex(); - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - DottedChain fullName = new DottedChain(new String[]{}, ""); - fullName = fullName.addWithSuffix(s.value.toString()); - s = lex(); - boolean isStar = false; - while (s.type == SymbolType.DOT) { - - s = lex(); - if (s.type == SymbolType.MULTIPLY && !isUse) { - isStar = true; - s = lex(); - break; - } - expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); - fullName = fullName.addWithSuffix(s.value.toString()); - s = lex(); - } - - if (isStar) { - openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_PACKAGE)); - } else if (isUse) { - //Note: in this case, fullName attribute will be changed to real NS insude NamespaceItem - openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_NAMESPACE)); - } else { - importedClasses.add(fullName); - } - - expected(s, lexer.yyline(), SymbolType.SEMICOLON); - s = lex(); - } - lexer.pushback(s); - return importedClasses; - } - - private List parseScript(List> allOpenedNamespaces, int scriptIndex, String fileName) throws IOException, AVM2ParseException, CompilationException { - - //int scriptPrivateNs; - if (fileName.contains("/")) { - fileName = fileName.substring(fileName.lastIndexOf('/') + 1); - } - if (fileName.contains("\\")) { - fileName = fileName.substring(fileName.lastIndexOf('\\') + 1); - } - List items = new ArrayList<>(); - scriptTraits(allOpenedNamespaces, scriptIndex, fileName, items); - return items; - } - - public List scriptTraitsFromString(List> allOpenedNamespaces, String str, String fileName, int scriptIndex) throws AVM2ParseException, IOException, CompilationException { - lexer = new ActionScriptLexer(str); - - List ret = parseScript(allOpenedNamespaces, scriptIndex, fileName); - if (lexer.lex().type != SymbolType.EOF) { - throw new AVM2ParseException("Parsing finisned before end of the file", lexer.yyline()); - } - return ret; - } - - public void addScriptFromTree(List> allOpenedNamespaces, List items, boolean documentClass, int classPos) throws AVM2ParseException, CompilationException { - AVM2SourceGenerator gen = new AVM2SourceGenerator(abcIndex); - SourceGeneratorLocalData localData = new SourceGeneratorLocalData( - new HashMap<>(), 0, Boolean.FALSE, 0); - localData.documentClass = documentClass; - abcIndex.getSelectedAbc().script_info.add(gen.generateScriptInfo(allOpenedNamespaces, localData, items, classPos)); - } - - public void addScript(String s, boolean documentClass, String fileName, int classPos, int scriptIndex) throws AVM2ParseException, IOException, CompilationException { - List> allOpenedNamespaces = new ArrayList<>(); - List traits = scriptTraitsFromString(allOpenedNamespaces, s, fileName, scriptIndex); - addScriptFromTree(allOpenedNamespaces, traits, documentClass, classPos); - } - - public ActionScript3Parser(ABC abc, List otherAbcs) throws IOException, InterruptedException { - initPlayer(); - - abcIndex = new AbcIndexing(playerGlobalAbcIndex); - for (ABC a : otherAbcs) { - abcIndex.addAbc(a); - } - - abcIndex.addAbc(abc); - } - - private static void initPlayer() throws IOException, InterruptedException { - if (playerGlobalAbcIndex == null) { - if (Configuration.getPlayerSWC() == null) { - throw new IOException("Player SWC library not found, please place it to " + Configuration.getFlashLibPath()); - } - - SWC swc = new SWC(new FileInputStream(Configuration.getPlayerSWC())); - SWF swf = new SWF(swc.getSWF("library.swf"), true); - playerGlobalAbcIndex = new AbcIndexing(swf); - } - } - - public static void compile(String src, ABC abc, List otherABCs, boolean documentClass, String fileName, int classPos, int scriptIndex) throws AVM2ParseException, IOException, InterruptedException, CompilationException { - //List parABCs = new ArrayList<>(); - initPlayer(); - ActionScript3Parser parser = new ActionScript3Parser(abc, otherABCs); - boolean success = false; - ABC originalAbc = ((ABCContainerTag) ((Tag) abc.parentTag).cloneTag()).getABC(); - try { - parser.addScript(src, documentClass, fileName, classPos, scriptIndex); - success = true; - } finally { - if (!success) { - // restore original constant pool and other lists - abc.constants = originalAbc.constants; - abc.method_info = originalAbc.method_info; - abc.metadata_info = originalAbc.metadata_info; - abc.instance_info = originalAbc.instance_info; - abc.class_info = originalAbc.class_info; - abc.script_info = originalAbc.script_info; - abc.bodies = originalAbc.bodies; - abc.getMethodIndexing(); - } - } - } - - public static void compile(SWF swf, String src, String dst, int classPos, int scriptIndex) { - System.err.println("WARNING: AS3 compiler is not finished yet. This is only used for debuggging!"); - try { - initPlayer(); - ABC abc = new ABC(null); - ActionScript3Parser parser = new ActionScript3Parser(abc, new ArrayList<>()); - parser.addScript(new String(Helper.readFile(src), Utf8Helper.charset), true, src, classPos, scriptIndex); - try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(dst)))) { - abc.saveToStream(fos); - - } - } catch (Exception ex) { - Logger.getLogger(ActionScript3Parser.class - .getName()).log(Level.SEVERE, null, ex); - } - System.exit(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.abc.avm2.parser.script; + +import com.jpexs.decompiler.flash.SWC; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SourceGeneratorLocalData; +import com.jpexs.decompiler.flash.abc.ABC; +import com.jpexs.decompiler.flash.abc.avm2.model.ApplyTypeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.BooleanAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.CoerceAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ConstructSuperAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.DefaultXMLNamespace; +import com.jpexs.decompiler.flash.abc.avm2.model.EscapeXAttrAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.EscapeXElemAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.FloatValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.GetDescendantsAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.GetPropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.HasNextAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.InAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.InitVectorAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.IntegerValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.LocalRegAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NameValuePair; +import com.jpexs.decompiler.flash.abc.avm2.model.NanAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NewActivationAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NewArrayAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NewObjectAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NextNameAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.NullAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.PostDecrementAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.PostIncrementAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.RegExpAvm2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ReturnValueAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ReturnVoidAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.StringAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.ThrowAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.UndefinedAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.WithAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ExceptionAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForEachInAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.ForInAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.clauses.TryAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.AddAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.AsTypeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitAndAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitOrAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.BitXorAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.DeletePropertyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.DivideAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.EqAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.GeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.GtAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.InstanceOfAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.IsTypeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.LShiftAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.LeAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.LtAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.ModuloAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.MultiplyAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.NegAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.NeqAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.PreDecrementAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.PreIncrementAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.RShiftAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictEqAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.StrictNeqAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.SubtractAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.TypeOfAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.model.operations.URShiftAVM2Item; +import com.jpexs.decompiler.flash.abc.avm2.parser.AVM2ParseException; +import com.jpexs.decompiler.flash.abc.types.Namespace; +import com.jpexs.decompiler.flash.action.swf4.ActionIf; +import com.jpexs.decompiler.flash.configuration.Configuration; +import com.jpexs.decompiler.flash.tags.ABCContainerTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.graph.CompilationException; +import com.jpexs.decompiler.graph.DottedChain; +import com.jpexs.decompiler.graph.GraphTargetItem; +import com.jpexs.decompiler.graph.Loop; +import com.jpexs.decompiler.graph.TypeItem; +import com.jpexs.decompiler.graph.model.AndItem; +import com.jpexs.decompiler.graph.model.BlockItem; +import com.jpexs.decompiler.graph.model.BreakItem; +import com.jpexs.decompiler.graph.model.CommaExpressionItem; +import com.jpexs.decompiler.graph.model.ContinueItem; +import com.jpexs.decompiler.graph.model.DefaultItem; +import com.jpexs.decompiler.graph.model.DoWhileItem; +import com.jpexs.decompiler.graph.model.DuplicateItem; +import com.jpexs.decompiler.graph.model.ForItem; +import com.jpexs.decompiler.graph.model.IfItem; +import com.jpexs.decompiler.graph.model.NotItem; +import com.jpexs.decompiler.graph.model.OrItem; +import com.jpexs.decompiler.graph.model.ParenthesisItem; +import com.jpexs.decompiler.graph.model.PopItem; +import com.jpexs.decompiler.graph.model.PushItem; +import com.jpexs.decompiler.graph.model.SwitchItem; +import com.jpexs.decompiler.graph.model.TernarOpItem; +import com.jpexs.decompiler.graph.model.UnboundedTypeItem; +import com.jpexs.decompiler.graph.model.WhileItem; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.utf8.Utf8Helper; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +public class ActionScript3Parser { + + private long uniqLast = 0; + + private final boolean debugMode = false; + + private static final String AS3_NAMESPACE = "http://adobe.com/AS3/2006/builtin"; + + private final AbcIndexing abcIndex; + +// private final AbcIndexing otherABCs; + //private static final List playerABCs = new ArrayList<>(); + private static AbcIndexing playerGlobalAbcIndex; + + private long uniqId() { + uniqLast++; + return uniqLast; + } + + private List commands(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, List variables) throws IOException, AVM2ParseException { + List ret = new ArrayList<>(); + if (debugMode) { + System.out.println("commands:"); + } + GraphTargetItem cmd; + while ((cmd = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)) != null) { + ret.add(cmd); + } + if (debugMode) { + System.out.println("/commands"); + } + return ret; + } + + private GraphTargetItem type(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, List variables) throws IOException, AVM2ParseException { + ParsedSymbol s = lex(); + if (s.type == SymbolType.MULTIPLY) { + return new UnboundedTypeItem(); + } else if (s.type == SymbolType.VOID) { + return new TypeItem(DottedChain.VOID); + } else { + lexer.pushback(s); + } + + GraphTargetItem t = name(allOpenedNamespaces, thisType, pkg, needsActivation, true, openedNamespaces, null, false, false, variables, importedClasses); + t = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, t, new HashMap<>(), false, false, variables); + return t; + } + + private GraphTargetItem memberOrCall(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem newcmds, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + if (debugMode) { + System.out.println("memberOrCall:"); + } + ParsedSymbol s = lex(); + GraphTargetItem ret = newcmds; + while (s.isType(SymbolType.DOT, SymbolType.PARENT_OPEN, SymbolType.BRACKET_OPEN, SymbolType.TYPENAME, SymbolType.FILTER, SymbolType.DESCENDANTS)) { + switch (s.type) { + case BRACKET_OPEN: + case DOT: + case TYPENAME: + lexer.pushback(s); + ret = member(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); + break; + case FILTER: + needsActivation.setVal(true); + ret = new XMLFilterAVM2Item(ret, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, inMethod, variables), openedNamespaces); + expectedType(SymbolType.PARENT_CLOSE); + break; + case PARENT_OPEN: + ret = new CallAVM2Item(openedNamespaces, lexer.yyline(), ret, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); + break; + case DESCENDANTS: + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); + ret = new GetDescendantsAVM2Item(ret, s.type == SymbolType.MULTIPLY ? null : s.value.toString(), openedNamespaces); + break; + + } + s = lex(); + } + if (s.type == SymbolType.INCREMENT) { + if (!isNameOrProp(ret)) { + throw new AVM2ParseException("Invalid assignment", lexer.yyline()); + } + ret = new PostIncrementAVM2Item(null, null, ret); + s = lex(); + + } else if (s.type == SymbolType.DECREMENT) { + if (!isNameOrProp(ret)) { + throw new AVM2ParseException("Invalid assignment", lexer.yyline()); + } + ret = new PostDecrementAVM2Item(null, null, ret); + s = lex(); + } + + lexer.pushback(s); + + if (debugMode) { + System.out.println("/memberOrCall"); + } + return ret; + } + + private GraphTargetItem applyType(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem obj, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + GraphTargetItem ret = obj; + ParsedSymbol s = lex(); + if (s.type == SymbolType.TYPENAME) { + List params = new ArrayList<>(); + do { + s = lex(); + if (s.isType(SymbolType.MULTIPLY)) { + params.add(new NullAVM2Item(null, null)); + } else { + lexer.pushback(s); + params.add(expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); + } + s = lex(); + } while (s.type == SymbolType.COMMA); + if (s.type == SymbolType.USHIFT_RIGHT) { + s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.GREATER_THAN); + lexer.pushback(s); + lexer.pushback(s); + } + if (s.type == SymbolType.SHIFT_RIGHT) { + s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.GREATER_THAN); + lexer.pushback(s); + } + expected(s, lexer.yyline(), SymbolType.GREATER_THAN); + ret = new ApplyTypeAVM2Item(null, null, ret, params); + } else { + lexer.pushback(s); + } + return ret; + } + + private GraphTargetItem member(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, GraphTargetItem obj, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + if (debugMode) { + System.out.println("member:"); + } + GraphTargetItem ret = obj; + ParsedSymbol s = lex(); + while (s.isType(SymbolType.DOT, SymbolType.BRACKET_OPEN, SymbolType.TYPENAME)) { + ParsedSymbol s2 = lex(); + boolean attr = false; + if (s.type == SymbolType.DOT) { + if (s2.type == SymbolType.ATTRIBUTE) { + attr = true; + } else { + lexer.pushback(s2); + } + + } else { + lexer.pushback(s2); + } + if (s.type == SymbolType.TYPENAME) { + lexer.pushback(s); + ret = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); + s = lex(); + } else if (s.type == SymbolType.BRACKET_OPEN) { + GraphTargetItem index = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.BRACKET_CLOSE); + ret = new IndexAVM2Item(attr, ret, index, null, openedNamespaces); + s = lex(); + } else { + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); + String propName = s.value.toString(); //Can be * + GraphTargetItem propItem = null; + s = lex(); + GraphTargetItem ns = null; + if (s.type == SymbolType.NAMESPACE_OP) { + ns = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, false, null, lexer.yyline(), new DottedChain(new String[]{propName}, "" /*FIXME ???*/), null, openedNamespaces); + variables.add((UnresolvedAVM2Item) ns); + s = lex(); + if (s.type == SymbolType.BRACKET_OPEN) { + propItem = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.BRACKET_CLOSE); + propName = null; + } else { + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + propName = s.value.toString(); + propItem = null; + } + } else { + lexer.pushback(s); + } + if (ns != null) { + ret = new NamespacedAVM2Item(ns, propName, propItem, ret, attr, openedNamespaces, null); + } else { + ret = new PropertyAVM2Item(ret, (attr ? "@" : "") + propName, abcIndex, openedNamespaces, new ArrayList<>()); + } + s = lex(); + } + } + lexer.pushback(s); + + if (debugMode) { + System.out.println("/member"); + } + return ret; + } + + private GraphTargetItem name(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, boolean typeOnly, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables, List importedClasses) throws IOException, AVM2ParseException { + ParsedSymbol s = lex(); + DottedChain name = new DottedChain(new String[]{}, ""); + String name2 = ""; + if (s.type == SymbolType.ATTRIBUTE) { + name2 += "@"; + s = lex(); + } + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); + name2 += s.value.toString(); + s = lex(); + boolean attrBracket = false; + + name = name.addWithSuffix(name2); + while (s.isType(SymbolType.DOT)) { + //name += s.value.toString(); //. or :: + s = lex(); + name2 = ""; + if (s.type == SymbolType.ATTRIBUTE) { + name2 += "@"; + s = lex(); + if (s.type == SymbolType.MULTIPLY) { + name2 += s.value.toString(); + } else if (s.group == SymbolGroup.IDENTIFIER) { + name2 += s.value.toString(); + } else { + if (s.type != SymbolType.BRACKET_OPEN) { + throw new AVM2ParseException("Attribute identifier or bracket expected", lexer.yyline()); + } + attrBracket = true; + continue; + } + } else { + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.NAMESPACE, SymbolType.MULTIPLY); + name2 += s.value.toString(); + } + name = name.addWithSuffix(name2); + s = lex(); + } + String nsname = null; + String nsprop = null; + GraphTargetItem nspropItem = null; + if (s.type == SymbolType.NAMESPACE_OP) { + nsname = name.getLast(); + s = lex(); + if (s.group == SymbolGroup.IDENTIFIER) { + nsprop = s.value.toString(); + } else if (s.type == SymbolType.BRACKET_OPEN) { + nspropItem = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.BRACKET_CLOSE); + } + name = name.getWithoutLast(); + s = lex(); + } + + GraphTargetItem ret = null; + if (!name.isEmpty()) { + UnresolvedAVM2Item unr = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, typeOnly, null, lexer.yyline(), name, null, openedNamespaces); + //unr.setIndex(index); + variables.add(unr); + ret = unr; + } + if (nsname != null) { + boolean attr = nsname.startsWith("@"); + if (attr) { + nsname = nsname.substring(1); + } + UnresolvedAVM2Item ns = new UnresolvedAVM2Item(new ArrayList<>(), importedClasses, typeOnly, null, lexer.yyline(), new DottedChain(new String[]{nsname}, ""), null, openedNamespaces); + variables.add(ns); + ret = new NamespacedAVM2Item(ns, nsprop, nspropItem, ret, attr, openedNamespaces, null); + } + if (s.type == SymbolType.BRACKET_OPEN) { + lexer.pushback(s); + if (attrBracket) { + lexer.pushback(new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.ATTRIBUTE, "@")); + lexer.pushback(new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.DOT, ".")); + } + ret = member(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); + } else { + lexer.pushback(s); + } + return ret; + } + + private void expected(ParsedSymbol symb, int line, Object... expected) throws IOException, AVM2ParseException { + boolean found = false; + for (Object t : expected) { + if (symb.type == t) { + found = true; + } + if (symb.group == t) { + found = true; + } + } + if (!found) { + String expStr = ""; + boolean first = true; + for (Object e : expected) { + if (!first) { + expStr += " or "; + } + expStr += e; + first = false; + } + throw new AVM2ParseException("" + expStr + " expected but " + symb.type + " found", line); + } + } + + private ParsedSymbol expectedType(Object... type) throws IOException, AVM2ParseException { + ParsedSymbol symb = lex(); + expected(symb, lexer.yyline(), type); + return symb; + } + + private ParsedSymbol lex() throws IOException, AVM2ParseException { + ParsedSymbol ret = lexer.lex(); + if (debugMode) { + System.out.println(ret); + } + return ret; + } + + private List call(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + List ret = new ArrayList<>(); + //expected(SymbolType.PARENT_OPEN); //MUST BE HANDLED BY CALLER + ParsedSymbol s = lex(); + while (s.type != SymbolType.PARENT_CLOSE) { + if (s.type != SymbolType.COMMA) { + lexer.pushback(s); + } + ret.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + s = lex(); + expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); + } + return ret; + } + + private MethodAVM2Item method(List> allOpenedNamespaces, boolean outsidePackage, boolean isPrivate, List>> metadata, NamespaceItem pkg, boolean isInterface, String customAccess, Reference needsActivation, List importedClasses, boolean override, boolean isFinal, TypeItem thisType, List openedNamespaces, boolean isStatic, String functionName, boolean isMethod, List variables) throws IOException, AVM2ParseException { + FunctionAVM2Item f = function(allOpenedNamespaces, metadata, pkg, isInterface, needsActivation, importedClasses, thisType, openedNamespaces, functionName, isMethod, variables); + return new MethodAVM2Item(allOpenedNamespaces, outsidePackage, isPrivate, f.metadata, f.pkg, f.isInterface, customAccess, f.needsActivation, f.hasRest, f.line, override, isFinal, isStatic, functionName, f.paramTypes, f.paramNames, f.paramValues, f.body, f.subvariables, f.retType); + } + + private FunctionAVM2Item function(List> allOpenedNamespaces, List>> metadata, NamespaceItem pkg, boolean isInterface, Reference needsActivation, List importedClasses, TypeItem thisType, List openedNamespaces, String functionName, boolean isMethod, List variables) throws IOException, AVM2ParseException { + + openedNamespaces = new ArrayList<>(openedNamespaces); //local copy + allOpenedNamespaces.add(openedNamespaces); + int line = lexer.yyline(); + ParsedSymbol s; + expectedType(SymbolType.PARENT_OPEN); + s = lex(); + List paramNames = new ArrayList<>(); + List paramTypes = new ArrayList<>(); + List paramValues = new ArrayList<>(); + boolean hasRest = false; + while (s.type != SymbolType.PARENT_CLOSE) { + if (s.type != SymbolType.COMMA) { + lexer.pushback(s); + } + s = lex(); + if (s.type == SymbolType.REST) { + hasRest = true; + s = lex(); + } + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + + paramNames.add(s.value.toString()); + s = lex(); + if (!hasRest) { + if (s.type == SymbolType.COLON) { + paramTypes.add(type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables)); + s = lex(); + } else { + paramTypes.add(new UnboundedTypeItem()); + } + if (s.type == SymbolType.ASSIGN) { + paramValues.add(expression(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, null, isMethod, isMethod, isMethod, variables)); + s = lex(); + } else if (!paramValues.isEmpty()) { + throw new AVM2ParseException("Some of parameters do not have default values", lexer.yyline()); + } + } + + if (!s.isType(SymbolType.COMMA, SymbolType.PARENT_CLOSE)) { + expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.PARENT_CLOSE); + } + if (hasRest) { + expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE); + } + } + s = lex(); + GraphTargetItem retType; + if (s.type == SymbolType.COLON) { + retType = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + } else { + retType = new UnboundedTypeItem(); + lexer.pushback(s); + } + List body = null; + List subvariables = new ArrayList<>(); + subvariables.add(new NameAVM2Item(thisType, lexer.yyline(), "this", null, true, openedNamespaces)); + for (int i = 0; i < paramNames.size() - (hasRest ? 1 : 0); i++) { + subvariables.add(new NameAVM2Item(paramTypes.get(i), lexer.yyline(), paramNames.get(i), null, true, openedNamespaces)); + } + if (hasRest) { + subvariables.add(new NameAVM2Item(TypeItem.UNBOUNDED, lexer.yyline(), paramNames.get(paramNames.size() - 1), null, true, openedNamespaces)); + } + subvariables.add(new NameAVM2Item(thisType, lexer.yyline(), "arguments", null, true, openedNamespaces)); + int parCnt = subvariables.size(); + Reference needsActivation2 = new Reference<>(false); + if (!isInterface) { + expectedType(SymbolType.CURLY_OPEN); + body = commands(allOpenedNamespaces, thisType, pkg, needsActivation2, importedClasses, openedNamespaces, new Stack<>(), new HashMap<>(), new HashMap<>(), true, isMethod, 0, subvariables); + expectedType(SymbolType.CURLY_CLOSE); + } else { + expectedType(SymbolType.SEMICOLON); + } + + for (int i = 0; i < parCnt; i++) { + subvariables.remove(0); + } + return new FunctionAVM2Item(metadata, pkg, isInterface, needsActivation2.getVal(), hasRest, line, functionName, paramTypes, paramNames, paramValues, body, subvariables, retType); + } + + private List>> parseMetadata() throws IOException, AVM2ParseException { + List>> metadata = new ArrayList<>(); + ParsedSymbol s = lex(); + while (s.isType(SymbolType.BRACKET_OPEN)) { + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String name = s.value.toString(); + Map.Entry> en = new AbstractMap.SimpleEntry<>(name, new HashMap()); + s = lex(); + if (s.isType(SymbolType.PARENT_OPEN)) { + s = lex(); + if (s.isType(SymbolGroup.STRING)) { + en.getValue().put("", s.value.toString()); + s = lex(); + } else { + lexer.pushback(s); + do { + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String key = s.value.toString(); + expectedType(SymbolType.ASSIGN); + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.STRING); + String value = s.value.toString(); + en.getValue().put(key, value); + s = lex(); + } while (s.isType(SymbolType.COMMA)); + } + expected(s, lexer.yyline(), SymbolType.PARENT_CLOSE); + s = lex(); + } + metadata.add(en); + expected(s, lexer.yyline(), SymbolType.BRACKET_CLOSE); + s = lex(); + } + lexer.pushback(s); + return metadata; + } + + private void classTraits(List> allOpenedNamespaces, boolean outsidePackage, List cinitVariables, Reference cinitNeedsActivation, List cinit, List importedClasses, List openedNamespaces, NamespaceItem pkg, String classNameStr, boolean isInterface, List traits, List iinitVariables, Reference iinitNeedsActivation, Reference iinit) throws AVM2ParseException, IOException, CompilationException { + + NamespaceItem publicNs = new NamespaceItem("", Namespace.KIND_PACKAGE); + NamespaceItem privateNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_PRIVATE); + NamespaceItem protectedNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_PROTECTED); + NamespaceItem staticProtectedNs = new NamespaceItem(pkg.name.toRawString() + ":" + classNameStr, Namespace.KIND_STATIC_PROTECTED); + NamespaceItem packageInternalNs = new NamespaceItem(pkg.name, Namespace.KIND_PACKAGE_INTERNAL); + + openedNamespaces = new ArrayList<>(openedNamespaces); + allOpenedNamespaces.add(openedNamespaces); + for (List ln : allOpenedNamespaces) { + if (!ln.contains(publicNs)) { + ln.add(publicNs); + } + } + openedNamespaces.add(privateNs); + openedNamespaces.add(protectedNs); + openedNamespaces.add(staticProtectedNs); + + looptraits: + while (true) { + TypeItem thisType = new TypeItem(pkg.name.addWithSuffix(classNameStr)); + boolean isGetter = false; + boolean isSetter = false; + boolean isOverride = false; + boolean isStatic = false; + boolean isFinal = false; + boolean isPrivate = false; + + String customNs = null; + NamespaceItem namespace = null; + ParsedSymbol s = lex(); + //static class initializer + if (s.type == SymbolType.CURLY_OPEN) { + cinit.addAll(commands(allOpenedNamespaces, thisType, pkg, cinitNeedsActivation, importedClasses, openedNamespaces, new Stack<>(), new HashMap<>(), new HashMap<>(), true, false, 0, cinitVariables)); + expectedType(SymbolType.CURLY_CLOSE); + } else { + lexer.pushback(s); + } + List>> metadata = parseMetadata(); + s = lex(); + + while (s.isType(SymbolType.STATIC, SymbolType.PUBLIC, SymbolType.PRIVATE, SymbolType.PROTECTED, SymbolType.OVERRIDE, SymbolType.FINAL, SymbolType.DYNAMIC, SymbolGroup.IDENTIFIER)) { + if (s.type == SymbolType.FINAL) { + if (isFinal) { + throw new AVM2ParseException("Only one final keyword allowed", lexer.yyline()); + } + isFinal = true; + } else if (s.type == SymbolType.OVERRIDE) { + if (isOverride) { + throw new AVM2ParseException("Only one override keyword allowed", lexer.yyline()); + } + isOverride = true; + } else if (s.type == SymbolType.STATIC) { + if (isInterface) { + throw new AVM2ParseException("Interface cannot have static traits", lexer.yyline()); + } + if (classNameStr == null) { + throw new AVM2ParseException("No static keyword allowed here", lexer.yyline()); + } + if (isStatic) { + throw new AVM2ParseException("Only one static keyword allowed", lexer.yyline()); + } + isStatic = true; + } else if (s.type == SymbolType.NAMESPACE) { + break; + } else if (s.type == SymbolType.NATIVE) { + throw new AVM2ParseException("Cannot compile native code", lexer.yyline()); + } else if (s.group == SymbolGroup.IDENTIFIER) { + customNs = s.value.toString(); + } else if (namespace != null) { + throw new AVM2ParseException("Only one access identifier allowed", lexer.yyline()); + } + switch (s.type) { + case PUBLIC: + namespace = publicNs; + if (isInterface) { + throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); + } + break; + case PRIVATE: + isPrivate = true; + namespace = privateNs; + if (isInterface) { + throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); + } + break; + case PROTECTED: + namespace = protectedNs; + if (isInterface) { + throw new AVM2ParseException("Interface cannot have public, private or protected modifier", lexer.yyline()); + } + break; + } + s = lex(); + } + if (namespace == null && customNs == null) { + namespace = packageInternalNs; + } + if (namespace == protectedNs && isStatic) { + namespace = staticProtectedNs; + } + if (namespace == null && customNs != null) { + //Special: it will be resolved later: + namespace = new NamespaceItem(customNs, Namespace.KIND_NAMESPACE); + } + + switch (s.type) { + case FUNCTION: + s = lex(); + if (s.type == SymbolType.GET) { + isGetter = true; + s = lex(); + } else if (s.type == SymbolType.SET) { + isSetter = true; + s = lex(); + } + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.PARENT_OPEN); + String fname = null; + + //fix for methods with name "get" or "set" - they are not getters/setters! + if (s.isType(SymbolType.PARENT_OPEN)) { + lexer.pushback(s); + if (isGetter) { + fname = "get"; + isGetter = false; + } else if (isSetter) { + fname = "set"; + isSetter = false; + } else { + throw new AVM2ParseException("Missing method name", lexer.yyline()); + } + } else { + fname = s.value.toString(); + } + if (fname.equals(classNameStr)) { //constructor + if (isStatic) { + throw new AVM2ParseException("Constructor cannot be static", lexer.yyline()); + } + if (isOverride) { + throw new AVM2ParseException("Override flag not allowed for constructor", lexer.yyline()); + } + if (isFinal) { + throw new AVM2ParseException("Final flag not allowed for constructor", lexer.yyline()); + } + if (isInterface) { + throw new AVM2ParseException("Interface cannot have constructor", lexer.yyline()); + } + iinit.setVal(method(allOpenedNamespaces, outsidePackage, isPrivate, metadata, pkg, false, customNs, iinitNeedsActivation, importedClasses, false, false, thisType, openedNamespaces, false, "", true, iinitVariables)); + } else { + GraphTargetItem t; + if (classNameStr == null) { + isStatic = true; + } + { + MethodAVM2Item ft = method(allOpenedNamespaces, outsidePackage, isPrivate, metadata, namespace, isInterface, customNs, new Reference<>(false), importedClasses, isOverride, isFinal, thisType, openedNamespaces, isStatic, fname, true, new ArrayList<>()); + + if (isGetter) { + if (!ft.paramTypes.isEmpty()) { + throw new AVM2ParseException("Getter can't have any parameters", lexer.yyline()); + } + } + + if (isSetter) { + if (ft.paramTypes.size() != 1) { + throw new AVM2ParseException("Getter must have exactly one parameter", lexer.yyline()); + } + } + + if (isStatic && isInterface) { + if (isInterface) { + throw new AVM2ParseException("Interface cannot have static fields", lexer.yyline()); + } + } + if (isGetter) { + GetterAVM2Item g = new GetterAVM2Item(allOpenedNamespaces, outsidePackage, ft.isPrivate(), ft.metadata, ft.pkg, isInterface, customNs, ft.needsActivation, ft.hasRest, ft.line, ft.isOverride(), ft.isFinal(), isStatic, ft.functionName, ft.paramTypes, ft.paramNames, ft.paramValues, ft.body, ft.subvariables, ft.retType); + t = g; + } else if (isSetter) { + SetterAVM2Item st = new SetterAVM2Item(allOpenedNamespaces, outsidePackage, ft.isPrivate(), ft.metadata, ft.pkg, isInterface, customNs, ft.needsActivation, ft.hasRest, ft.line, ft.isOverride(), ft.isFinal(), isStatic, ft.functionName, ft.paramTypes, ft.paramNames, ft.paramValues, ft.body, ft.subvariables, ft.retType); + t = st; + } else { + t = ft; + } + + } + //NOTE: Looks like TraitFunction does not work in FlashPlayer - use MethodTrait instead + /*else { + t = function(metadata, pkg, isInterface, new Reference(false), importedClasses, namespace, thisType, openedNamespaces, fname, false, new ArrayList()); + }*/ + + traits.add(t); + } + //} + break; + case NAMESPACE: + if (isInterface) { + throw new AVM2ParseException("Interface cannot have namespace fields", lexer.yyline()); + } + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String nname = s.value.toString(); + String nval; + s = lex(); + + if (s.type == SymbolType.ASSIGN) { + s = lex(); + expected(s, lexer.yyline(), SymbolType.STRING); + nval = s.value.toString(); + s = lex(); + } else { + nval = (pkg.name.toRawString() + ":" + classNameStr) + "/" + nname; + } + if (s.type != SymbolType.SEMICOLON) { + lexer.pushback(s); + } + + ConstAVM2Item ns = new ConstAVM2Item(metadata, namespace, customNs, true, nname, new TypeItem(DottedChain.NAMESPACE), new StringAVM2Item(null, null, nval), lexer.yyline()); + traits.add(ns); + break; + case CONST: + case VAR: + boolean isConst = s.type == SymbolType.CONST; + if (isOverride) { + throw new AVM2ParseException("Override flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); + } + if (isFinal) { + throw new AVM2ParseException("Final flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); + } + if (isInterface) { + throw new AVM2ParseException("Interface cannot have variable/const fields", lexer.yyline()); + } + + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String vcname = s.value.toString(); + s = lex(); + GraphTargetItem type; + if (s.type == SymbolType.COLON) { + type = type(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); + s = lex(); + } else { + type = TypeItem.UNBOUNDED; + } + + GraphTargetItem value = null; + + if (s.type == SymbolType.ASSIGN) { + value = expression(allOpenedNamespaces, thisType, pkg, new Reference<>(false), importedClasses, openedNamespaces, new HashMap<>(), false, false, true, isStatic || isConst ? cinitVariables : iinitVariables); + s = lex(); + } + GraphTargetItem tar; + if (isConst) { + tar = new ConstAVM2Item(metadata, namespace, customNs, isStatic, vcname, type, value, lexer.yyline()); + } else { + tar = new SlotAVM2Item(metadata, namespace, customNs, isStatic, vcname, type, value, lexer.yyline()); + } + traits.add(tar); + if (s.type != SymbolType.SEMICOLON) { + lexer.pushback(s); + } + break; + default: + lexer.pushback(s); + break looptraits; + } + } + } + + private void scriptTraits(List> allOpenedNamespaces, int scriptIndex, String scriptName, List traits) throws AVM2ParseException, IOException, CompilationException { + + while (scriptTraitsBlock(allOpenedNamespaces, scriptIndex, scriptName, traits)) { + //empty + } + } + + private boolean scriptTraitsBlock(List> allOpenedNamespaces, int scriptIndex, String scriptName, List traits) throws AVM2ParseException, IOException, CompilationException { + ParsedSymbol s; + boolean inPackage = false; + s = lex(); + List sinitVariables = new ArrayList<>(); + NamespaceItem publicNs; + NamespaceItem packageInternalNs; + if (s.type == SymbolType.PACKAGE) { + DottedChain pkgName = DottedChain.TOPLEVEL; + s = lex(); + if (s.type != SymbolType.CURLY_OPEN) { + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + pkgName = pkgName.addWithSuffix(s.value.toString()); + s = lex(); + } + while (s.type == SymbolType.DOT) { + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + pkgName = pkgName.addWithSuffix(s.value.toString()); + s = lex(); + } + expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); + publicNs = new NamespaceItem(pkgName, Namespace.KIND_PACKAGE); + packageInternalNs = new NamespaceItem(pkgName, Namespace.KIND_PACKAGE_INTERNAL); + s = lex(); + inPackage = true; + } else { + publicNs = null; + packageInternalNs = new NamespaceItem(scriptName + "$" + scriptIndex, Namespace.KIND_PRIVATE); + } + lexer.pushback(s); + + List openedNamespaces = new ArrayList<>(); + allOpenedNamespaces.add(openedNamespaces); + NamespaceItem emptyNs = new NamespaceItem("", Namespace.KIND_PACKAGE); + openedNamespaces.add(emptyNs); + NamespaceItem as3Ns = new NamespaceItem(AS3_NAMESPACE, Namespace.KIND_NAMESPACE); + as3Ns.forceResolve(abcIndex); + openedNamespaces.add(as3Ns); + + for (List ln : allOpenedNamespaces) { + if (publicNs != null && !ln.contains(publicNs)) { + ln.add(publicNs); + } + if (!ln.contains(packageInternalNs)) { + ln.add(packageInternalNs); + } + } + + List importedClasses = parseImportsUsages(openedNamespaces); + + boolean isEmpty = true; + + looptrait: + while (true) { + List>> metadata = parseMetadata(); + s = lex(); + boolean isFinal = false; + boolean isDynamic = false; + boolean isPublic = false; + NamespaceItem ns = packageInternalNs; + while (s.isType(SymbolType.FINAL, SymbolType.DYNAMIC, SymbolType.PUBLIC)) { + if (s.type == SymbolType.FINAL) { + if (isFinal) { + throw new AVM2ParseException("Only one final keyword allowed", lexer.yyline()); + } + isFinal = true; + } + if (s.type == SymbolType.PUBLIC) { + if (!inPackage) { + throw new AVM2ParseException("public only allowed inside package", lexer.yyline()); + + } + if (isPublic) { + throw new AVM2ParseException("Only one public keyword allowed", lexer.yyline()); + } + isPublic = true; + ns = publicNs; + } + if (s.type == SymbolType.DYNAMIC) { + if (isDynamic) { + throw new AVM2ParseException("Only one dynamic keyword allowed", lexer.yyline()); + } + isDynamic = true; + } + s = lex(); + } + + switch (s.type) { + case CLASS: + case INTERFACE: + isEmpty = false; + List subOpenedNamespaces = new ArrayList<>(openedNamespaces); + boolean isInterface = false; + if (s.type == SymbolType.INTERFACE) { + isInterface = true; + } + GraphTargetItem extendsTypeStr = null; + List interfaces = new ArrayList<>(); + String subNameStr; + + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + subNameStr = s.value.toString(); + s = lex(); + if (!isInterface) { + + if (s.type == SymbolType.EXTENDS) { + extendsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); + s = lex(); + } + if (s.type == SymbolType.IMPLEMENTS) { + do { + GraphTargetItem implementsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); + interfaces.add(implementsTypeStr); + s = lex(); + } while (s.type == SymbolType.COMMA); + } + expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); + } else { + if (s.type == SymbolType.EXTENDS) { + do { + GraphTargetItem intExtendsTypeStr = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); + interfaces.add(intExtendsTypeStr); + s = lex(); + } while (s.type == SymbolType.COMMA); + } + expected(s, lexer.yyline(), SymbolType.CURLY_OPEN); + } + + if (extendsTypeStr != null) { + List indices = new ArrayList<>(); + List names = new ArrayList<>(); + List namespaces = new ArrayList<>(); + //FIXME for Private classes in script (?) + AVM2SourceGenerator.parentNamesAddNames(abcIndex, AVM2SourceGenerator.resolveType(new SourceGeneratorLocalData(new HashMap<>(), 0, false, 0), ((TypeItem) ((UnresolvedAVM2Item) extendsTypeStr).resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), new ArrayList<>())), abcIndex), indices, names, namespaces); + for (int i = 0; i < names.size(); i++) { + if (namespaces.get(i) == null || namespaces.get(i).isEmpty()) { + continue; + } + subOpenedNamespaces.add(new NamespaceItem(namespaces.get(i) + ":" + names.get(i), Namespace.KIND_STATIC_PROTECTED)); + } + } + + List cinit = new ArrayList<>(); + Reference cinitNeedsActivation = new Reference<>(false); + + Reference iinit = new Reference<>(null); + List cinitVariables = new ArrayList<>(); + List iinitVariables = new ArrayList<>(); + Reference iinitNeedsActivation = new Reference<>(false); + + List subTraits = new ArrayList<>(); + + classTraits(allOpenedNamespaces, !inPackage, cinitVariables, cinitNeedsActivation, cinit, importedClasses, subOpenedNamespaces, ns, subNameStr, isInterface, subTraits, iinitVariables, iinitNeedsActivation, iinit); + + if (isInterface) { + traits.add(new InterfaceAVM2Item(metadata, importedClasses, ns, subOpenedNamespaces, isFinal, subNameStr, interfaces, subTraits)); + } else { + traits.add(new ClassAVM2Item(metadata, importedClasses, ns, subOpenedNamespaces, isFinal, isDynamic, subNameStr, extendsTypeStr, interfaces, cinit, cinitNeedsActivation.getVal(), cinitVariables, iinit.getVal(), iinitVariables, subTraits, iinitNeedsActivation.getVal())); + } + + expectedType(SymbolType.CURLY_CLOSE); + break; + case FUNCTION: + isEmpty = false; + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String fname = s.value.toString(); + + traits.add(method(allOpenedNamespaces, !inPackage, false, metadata, ns, false, null, new Reference<>(false), importedClasses, false, isFinal, null, openedNamespaces, true, fname, true, new ArrayList<>())); + break; + case CONST: + case VAR: + isEmpty = false; + boolean isConst = s.type == SymbolType.CONST; + if (isFinal) { + throw new AVM2ParseException("Final flag not allowed for " + (isConst ? "consts" : "vars"), lexer.yyline()); + } + + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String vcname = s.value.toString(); + s = lex(); + GraphTargetItem type; + if (s.type == SymbolType.COLON) { + type = type(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new ArrayList<>()); + s = lex(); + } else { + type = TypeItem.UNBOUNDED; + } + + GraphTargetItem value = null; + + if (s.type == SymbolType.ASSIGN) { + value = expression(allOpenedNamespaces, null, ns, new Reference<>(false), importedClasses, openedNamespaces, new HashMap<>(), false, false, true, sinitVariables); + s = lex(); + } + GraphTargetItem tar; + if (isConst) { + tar = new ConstAVM2Item(metadata, ns, null, false, vcname, type, value, lexer.yyline()); + } else { + tar = new SlotAVM2Item(metadata, ns, null, false, vcname, type, value, lexer.yyline()); + } + traits.add(tar); + if (s.type != SymbolType.SEMICOLON) { + lexer.pushback(s); + } + break; + case NAMESPACE: + isEmpty = false; + if (isFinal) { + throw new AVM2ParseException("Final flag not allowed for namespaces", lexer.yyline()); + } + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String nname = s.value.toString(); + String nval; + s = lex(); + + if (s.type == SymbolType.ASSIGN) { + s = lex(); + expected(s, lexer.yyline(), SymbolType.STRING); + nval = s.value.toString(); + s = lex(); + } else { + nval = ns + "/" + nname; + } + if (s.type != SymbolType.SEMICOLON) { + lexer.pushback(s); + } + + traits.add(new ConstAVM2Item(metadata, ns, null, true, nname, new TypeItem(DottedChain.NAMESPACE), new StringAVM2Item(null, null, nval), lexer.yyline())); + break; + default: + lexer.pushback(s); + break looptrait; + } + + } + if (inPackage) { + expectedType(SymbolType.CURLY_CLOSE); + } + return !isEmpty; + } + + private GraphTargetItem expressionCommands(ParsedSymbol s, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, List variables) throws IOException, AVM2ParseException { + GraphTargetItem ret = null; + switch (s.type) { + /*case INT: + expectedType(SymbolType.PARENT_OPEN); + ret = new ToIntegerAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.PARENT_CLOSE); + break; + case NUMBER_OP: + s = lex(); + if (s.type == SymbolType.DOT) { + VariableAVM2Item vi = new VariableAVM2Item(s.value.toString(), null, false); + variables.add(vi); + ret = memberOrCall(allOpenedNamespaces, thisType,vi, registerVars, inFunction, inMethod, variables); + } else { + expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); + ret = new ToNumberAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.PARENT_CLOSE); + } + break; + case STRING_OP: + ParsedSymbol sop = s; + s = lex(); + if (s.type == SymbolType.DOT) { + lexer.pushback(s); + VariableAVM2Item vi2 = new VariableAVM2Item(sop.value.toString(), null, false); + variables.add(vi2); + ret = memberOrCall(allOpenedNamespaces, thisType,vi2, registerVars, inFunction, inMethod, variables); + } else { + expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); + ret = new ToStringAVM2Item(null, null, expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces,openedNamespacesKinds,registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.PARENT_CLOSE); + ret = memberOrCall(allOpenedNamespaces, thisType,ret, registerVars, inFunction, inMethod, variables); + } + break;*/ + default: + return null; + } + //return ret; + } + + private GraphTargetItem add(Object a) { + if (a instanceof List) { + List l = (List) a; + if (l.isEmpty()) { + return null; + } + GraphTargetItem o = add(l.get(0)); + for (int i = 1; i < l.size(); i++) { + o = add(o, l.get(i)); + } + return o; + } + if (a instanceof StringBuilder) { + if (((StringBuilder) a).length() == 0) { + return null; + } + GraphTargetItem ret = new StringAVM2Item(null, null, a.toString()); + ((StringBuilder) a).setLength(0); + return ret; + } + if (a instanceof String) { + return new StringAVM2Item(null, null, (String) a); + } + if (a instanceof GraphTargetItem) { + return (GraphTargetItem) a; + } + return null; + } + + private GraphTargetItem add(Object a, Object b) { + GraphTargetItem ta = add(a); + GraphTargetItem tb = add(b); + if (ta == null && tb == null) { + return null; + } + if (ta == null) { + return tb; + } + if (tb == null) { + return ta; + } + return new AddAVM2Item(null, null, ta, tb); + } + + private void addS(List rets, StringBuilder sb) { + if (sb.length() > 0) { + if (!rets.isEmpty() && (rets.get(rets.size() - 1) instanceof StringAVM2Item)) { + StringAVM2Item stringItem = ((StringAVM2Item) rets.get(rets.size() - 1)); + stringItem.setValue(stringItem.getValue() + sb.toString()); + } else { + rets.add(new StringAVM2Item(null, null, sb.toString())); + } + sb.setLength(0); + } + } + + private List xmltag(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference usesVars, List openedTags, Reference closedVarTags, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + ParsedSymbol s; + List rets = new ArrayList<>(); + //GraphTargetItem ret = null; + StringBuilder sb = new StringBuilder(); + loop: + do { + s = lex(); + List sub = new ArrayList<>(); + Reference subclose = new Reference<>(0); + Reference subusesvars = new Reference<>(false); + switch (s.type) { + case XML_ATTRNAMEVAR_BEGIN: //add + usesVars.setVal(true); + addS(rets, sb); + rets.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.CURLY_CLOSE); + expectedType(SymbolType.ASSIGN); + sb.append("="); + lexer.yybegin(ActionScriptLexer.XMLOPENTAGATTRIB); + break; + case XML_ATTRVALVAR_BEGIN: //esc_xattr + usesVars.setVal(true); + sb.append("\""); + addS(rets, sb); + rets.add(new EscapeXAttrAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); + sb.append("\""); + expectedType(SymbolType.CURLY_CLOSE); + lexer.yybegin(ActionScriptLexer.XMLOPENTAG); + break; + case XML_INSTRATTRNAMEVAR_BEGIN: //add + usesVars.setVal(true); + addS(rets, sb); + rets.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.CURLY_CLOSE); + expectedType(SymbolType.ASSIGN); + sb.append("="); + lexer.yybegin(ActionScriptLexer.XMLOPENTAGATTRIB); + break; + case XML_INSTRATTRVALVAR_BEGIN: //esc_xattr + usesVars.setVal(true); + sb.append("\""); + addS(rets, sb); + rets.add(new EscapeXAttrAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); + sb.append("\""); + expectedType(SymbolType.CURLY_CLOSE); + lexer.yybegin(ActionScriptLexer.XMLOPENTAG); + break; + case XML_VAR_BEGIN: //esc_xelem + usesVars.setVal(true); + addS(rets, sb); + rets.add(new EscapeXElemAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables))); + expectedType(SymbolType.CURLY_CLOSE); + lexer.yybegin(ActionScriptLexer.XML); + break; + case XML_FINISHVARTAG_BEGIN: //add + usesVars.setVal(true); + closedVarTags.setVal(closedVarTags.getVal() + 1); + sb.append(""); + addS(rets, sb); + lexer.yybegin(ActionScriptLexer.XML); + break; + case XML_STARTVARTAG_BEGIN: //add + //openedTags.add("*"); + + //ret = add(ret, ); + GraphTargetItem ex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.CURLY_CLOSE); + lexer.yybegin(ActionScriptLexer.XMLOPENTAG); + sub.add("*"); + sb.append("<"); + addS(rets, sb); + rets.add(ex); + rets.addAll(xmltag(allOpenedNamespaces, thisType, pkg, subusesvars, sub, subclose, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); + closedVarTags.setVal(subclose.getVal() + subclose.getVal()); + break; + case XML_INSTRVARTAG_BEGIN: //add + usesVars.setVal(true); + addS(rets, sb); + sb.append(" st = xmltag(allOpenedNamespaces, thisType, pkg, subusesvars, sub, closedVarTags, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); + sb.append(s.value.toString()); + addS(rets, sb); + rets.addAll(st); + closedVarTags.setVal(subclose.getVal() + subclose.getVal()); + break; + /*case XML_STARTTAG_END: + sb.append(s.value.toString()); + ret = addstr(ret,sb); + break;*/ + case XML_FINISHTAG: + String tname = s.value.toString().substring(2, s.value.toString().length() - 1).trim(); + if (openedTags.contains(tname)) { + openedTags.remove(tname); + } else if (openedTags.contains("*")) { + openedTags.remove("*"); + } else { + throw new AVM2ParseException("XML : Closing unopened tag", lexer.yyline()); + } + sb.append(s.value.toString()); + break; + case XML_STARTFINISHTAG_END: + openedTags.remove(openedTags.size() - 1); //close last tag + sb.append(s.value.toString()); + break; + case EOF: + throw new AVM2ParseException("End of file before XML finish", lexer.yyline()); + default: + sb.append(s.value.toString()); + break; + } + } while (!(openedTags.isEmpty() || closedVarTags.getVal() >= openedTags.size())); + addS(rets, sb); + return rets; + } + + private GraphTargetItem xml(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + List openedTags = new ArrayList<>(); + int closedVarTags = 0; + + GraphTargetItem ret = add(xmltag(allOpenedNamespaces, thisType, pkg, new Reference<>(false), openedTags, new Reference<>(closedVarTags), needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); + ret = new XMLAVM2Item(ret); + lexer.yybegin(ActionScriptLexer.YYINITIAL); + //TODO: Order of additions as in official compiler + return ret; + } + + private GraphTargetItem command(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forinlevel, boolean mustBeCommand, List variables) throws IOException, AVM2ParseException { + LexBufferer buf = new LexBufferer(); + lexer.addListener(buf); + GraphTargetItem ret = null; + if (debugMode) { + System.out.println("command:"); + } + ParsedSymbol s = lex(); + if (s.type == SymbolType.EOF) { + return null; + } + String loopLabel = null; + + if (s.group == SymbolGroup.IDENTIFIER) { + ParsedSymbol sc = lex(); + if (sc.type == SymbolType.COLON) { + loopLabel = s.value.toString(); + s = lex(); + } else { + lexer.pushback(sc); + } + } + + if (s.type == SymbolType.DEFAULT) { + ParsedSymbol sx = lex(); + if (sx.group != SymbolGroup.IDENTIFIER) { + lexer.pushback(sx); + } else if (!sx.value.equals("xml")) { + lexer.pushback(sx); + } else { + expectedType(SymbolType.NAMESPACE); + expectedType(SymbolType.ASSIGN); + GraphTargetItem ns = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + ret = new DefaultXMLNamespace(null, null, ns); + //TODO: use dxns for attribute namespaces instead of dxnslate + } + } + if (ret == null) { + switch (s.type) { + case USE: + expectedType(SymbolType.NAMESPACE); + GraphTargetItem ns = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + openedNamespaces.add(new NamespaceItem(ns.toString(), Namespace.KIND_PACKAGE /*FIXME?*/)); + break; + case WITH: + needsActivation.setVal(true); + expectedType(SymbolType.PARENT_OPEN); + GraphTargetItem wvar = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//(name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables)); + if (!isNameOrProp(wvar)) { + throw new AVM2ParseException("Not a property or name", lexer.yyline()); + } + expectedType(SymbolType.PARENT_CLOSE); + expectedType(SymbolType.CURLY_OPEN); + List withVars = new ArrayList<>(); + List wcmd = commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, withVars); + variables.addAll(withVars); + for (AssignableAVM2Item a : withVars) { + if (a instanceof UnresolvedAVM2Item) { + UnresolvedAVM2Item ua = (UnresolvedAVM2Item) a; + ua.scopeStack.add(0, wvar); + } + } + expectedType(SymbolType.CURLY_CLOSE); + ret = new WithAVM2Item(null, null, wvar, wcmd); + ((WithAVM2Item) ret).subvariables = withVars; + break; + /*case DELETE: + GraphTargetItem varDel = expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); + if(!isNameOrProp(varDel)){ + throw new ParseException("Not a property or name", lexer.yyline()); + } + if (varDel instanceof GetPropertyAVM2Item) { + GetPropertyAVM2Item gm = (GetPropertyAVM2Item) varDel; + ret = new DeletePropertyAVM2Item(null, null, gm.object, gm.propertyName); + } else if (varDel instanceof NameAVM2Item) { + variables.remove(varDel); + ret = new DeletePropertyAVM2Item(null, null, null, (NameAVM2Item) varDel); + } else { + throw new ParseException("Not a property", lexer.yyline()); + } + break;*/ + case FUNCTION: + s = lexer.lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + needsActivation.setVal(true); + ret = (function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, s.value.toString(), false, variables)); + break; + case VAR: + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + String varIdentifier = s.value.toString(); + s = lex(); + GraphTargetItem type; + if (s.type == SymbolType.COLON) { + type = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + s = lex(); + } else { + type = new UnboundedTypeItem(); + } + + if (s.type == SymbolType.ASSIGN) { + GraphTargetItem varval = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + ret = new NameAVM2Item(type, lexer.yyline(), varIdentifier, varval, true, openedNamespaces); + variables.add((NameAVM2Item) ret); + } else { + ret = new NameAVM2Item(type, lexer.yyline(), varIdentifier, null, true, openedNamespaces); + variables.add((NameAVM2Item) ret); + lexer.pushback(s); + } + break; + case CURLY_OPEN: + ret = new BlockItem(null, null, commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); + expectedType(SymbolType.CURLY_CLOSE); + break; + /*case INCREMENT: //preincrement + case DECREMENT: //predecrement + GraphTargetItem varincdec = expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); + if(!isNameOrProp(varincdec)){ + throw new ParseException("Not a property or name", lexer.yyline()); + } + if (s.type == SymbolType.INCREMENT) { + ret = new PreIncrementAVM2Item(null, null, varincdec); + } else if (s.type == SymbolType.DECREMENT) { + ret = new PreDecrementAVM2Item(null, null, varincdec); + } + break;*/ + case SUPER: //constructor call + ParsedSymbol ss2 = lex(); + if (ss2.type == SymbolType.PARENT_OPEN) { + List args = call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); + ret = new ConstructSuperAVM2Item(null, null, new LocalRegAVM2Item(null, null, 0, null), args); + } else {//no costructor call, but it could be calling parent methods... => handle in expression + lexer.pushback(ss2); + lexer.pushback(s); + } + break; + case IF: + expectedType(SymbolType.PARENT_OPEN); + GraphTargetItem ifExpr = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.PARENT_CLOSE); + GraphTargetItem onTrue = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); + List onTrueList = new ArrayList<>(); + onTrueList.add(onTrue); + s = lex(); + List onFalseList = null; + if (s.type == SymbolType.ELSE) { + onFalseList = new ArrayList<>(); + onFalseList.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); + } else { + lexer.pushback(s); + } + ret = new IfItem(null, null, ifExpr, onTrueList, onFalseList); + break; + case WHILE: + expectedType(SymbolType.PARENT_OPEN); + List whileExpr = new ArrayList<>(); + whileExpr.add(commaExpression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); + expectedType(SymbolType.PARENT_CLOSE); + List whileBody = new ArrayList<>(); + Loop wloop = new Loop(uniqId(), null, null); + if (loopLabel != null) { + loopLabels.put(wloop, loopLabel); + } + loops.push(wloop); + whileBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); + ret = new WhileItem(null, null, wloop, whileExpr, whileBody); + break; + case DO: + List doBody = new ArrayList<>(); + Loop dloop = new Loop(uniqId(), null, null); + loops.push(dloop); + if (loopLabel != null) { + loopLabels.put(dloop, loopLabel); + } + doBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); + expectedType(SymbolType.WHILE); + expectedType(SymbolType.PARENT_OPEN); + List doExpr = new ArrayList<>(); + doExpr.add(commaExpression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables)); + expectedType(SymbolType.PARENT_CLOSE); + ret = new DoWhileItem(null, null, dloop, doBody, doExpr); + break; + case FOR: + s = lex(); + boolean forin = false; + boolean each = false; + GraphTargetItem collection = null; + if (s.type == SymbolType.EACH) { + each = true; + forin = true; + s = lex(); + } + expected(s, lexer.yyline(), SymbolType.PARENT_OPEN); + GraphTargetItem firstCommand = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, false, variables); + if (firstCommand instanceof NameAVM2Item) { + NameAVM2Item nai = (NameAVM2Item) firstCommand; + if (nai.isDefinition() && nai.getAssignedValue() == null) { //Declared value in for..in + firstCommand = expression1(allOpenedNamespaces, firstCommand, GraphTargetItem.NOPRECEDENCE, thisType, pkg, needsActivation, importedClasses, openedNamespaces, true, registerVars, inFunction, inMethod, true, variables); + } + } + InAVM2Item inexpr = null; + if (firstCommand instanceof InAVM2Item) { + forin = true; + inexpr = (InAVM2Item) firstCommand; + } else if (forin) { + throw new AVM2ParseException("In expression required", lexer.yyline()); + } + + Loop floop = new Loop(uniqId(), null, null); + loops.push(floop); + if (loopLabel != null) { + loopLabels.put(floop, loopLabel); + } + List forFinalCommands = new ArrayList<>(); + GraphTargetItem forExpr = null; + List forFirstCommands = new ArrayList<>(); + if (!forin) { + s = lex(); + if (firstCommand != null) { //can be empty command + forFirstCommands.add(firstCommand); + } + while (s.isType(SymbolType.COMMA)) { + forFirstCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, false, variables)); + s = lex(); + } + lexer.pushback(s); + //GraphTargetItem firstCommand = command(thisType,pkg,needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); + forExpr = (expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.SEMICOLON); + GraphTargetItem fcom = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables); + if (fcom != null) { + forFinalCommands.add(fcom); + } + } + expectedType(SymbolType.PARENT_CLOSE); + List forBody = new ArrayList<>(); + forBody.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forin ? forinlevel + 1 : forinlevel, true, variables)); + if (forin) { + if (each) { + ret = new ForEachInAVM2Item(null, null, floop, inexpr, forBody); + } else { + + ret = new ForInAVM2Item(null, null, floop, inexpr, forBody); + } + } else { + ret = new ForItem(null, null, floop, forFirstCommands, forExpr, forFinalCommands, forBody); + } + break; + case SWITCH: + Loop sloop = new Loop(-uniqId(), null, null); //negative id marks switch = no continue + loops.push(sloop); + if (loopLabel != null) { + loopLabels.put(sloop, loopLabel); + } + expectedType(SymbolType.PARENT_OPEN); + GraphTargetItem switchExpr = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.PARENT_CLOSE); + expectedType(SymbolType.CURLY_OPEN); + s = lex(); + //ret.addAll(switchExpr); + int exprReg = 0; + for (int i = 0; i < 256; i++) { + if (!registerVars.containsValue(i)) { + registerVars.put("__switch" + uniqId(), i); + exprReg = i; + break; + } + } + List> caseIfs = new ArrayList<>(); + List> caseCmds = new ArrayList<>(); + List caseExprsAll = new ArrayList<>(); + List valueMapping = new ArrayList<>(); + int pos = 0; + while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { + while (s.type == SymbolType.CASE || s.type == SymbolType.DEFAULT) { + GraphTargetItem curCaseExpr = s.type == SymbolType.DEFAULT ? new DefaultItem() : expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.COLON); + s = lex(); + caseExprsAll.add(curCaseExpr); + valueMapping.add(pos); + } + pos++; + lexer.pushback(s); + List caseCmd = commands(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, variables); + caseCmds.add(caseCmd); + s = lex(); + } + expected(s, lexer.yyline(), SymbolType.CURLY_CLOSE); + ret = new SwitchItem(null, null, sloop, switchExpr, caseExprsAll, caseCmds, valueMapping); + break; + case BREAK: + s = lex(); + long bloopId = 0; + if (loops.isEmpty()) { + throw new AVM2ParseException("No loop to break", lexer.yyline()); + } + if (s.group == SymbolGroup.IDENTIFIER) { + String breakLabel = s.value.toString(); + for (Loop l : loops) { + if (breakLabel.equals(loopLabels.get(l))) { + bloopId = l.id; + break; + } + } + if (bloopId == 0) { + throw new AVM2ParseException("Identifier of loop expected", lexer.yyline()); + } + } else { + lexer.pushback(s); + bloopId = loops.peek().id; + } + ret = new BreakItem(null, null, bloopId); + break; + case CONTINUE: + s = lex(); + long cloopId = 0; + if (loops.isEmpty()) { + throw new AVM2ParseException("No loop to continue", lexer.yyline()); + } + if (s.group == SymbolGroup.IDENTIFIER) { + String continueLabel = s.value.toString(); + for (Loop l : loops) { + if (l.id < 0) { //negative id marks switch => no continue + continue; + } + if (continueLabel.equals(loopLabels.get(l))) { + cloopId = l.id; + break; + } + } + if (cloopId == -1) { + throw new AVM2ParseException("Identifier of loop expected", lexer.yyline()); + } + } else { + lexer.pushback(s); + for (int i = loops.size() - 1; i >= 0; i--) { + if (loops.get(i).id >= 0) {//no switches + cloopId = loops.get(i).id; + break; + } + } + if (cloopId <= 0) { + throw new AVM2ParseException("No loop to continue", lexer.yyline()); + } + } + //TODO: handle switch + ret = new ContinueItem(null, null, cloopId); + break; + case RETURN: + GraphTargetItem retexpr = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, true, registerVars, inFunction, inMethod, true, variables); + if (retexpr == null) { + ret = new ReturnVoidAVM2Item(null, null); + } else { + ret = new ReturnValueAVM2Item(null, null, retexpr); + } + break; + case TRY: + needsActivation.setVal(true); + List tryCommands = new ArrayList<>(); + tryCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); + s = lex(); + boolean found = false; + List> catchCommands = new ArrayList<>(); + List catchExceptions = new ArrayList<>(); + int varCnt = variables.size(); + List> catchesVars = new ArrayList<>(); + while (s.type == SymbolType.CATCH) { + expectedType(SymbolType.PARENT_OPEN); + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.THIS, SymbolType.SUPER, SymbolType.STRING_OP); + + String enamestr = s.value.toString(); + expectedType(SymbolType.COLON); + GraphTargetItem etype = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + NameAVM2Item e = new NameAVM2Item(etype, lexer.yyline(), enamestr, new ExceptionAVM2Item(null)/*?*/, true/*?*/, openedNamespaces); + variables.add(e); + catchExceptions.add(e); + e.setSlotNumber(1); + e.setSlotScope(Integer.MAX_VALUE); //will be changed later + expectedType(SymbolType.PARENT_CLOSE); + List cc = new ArrayList<>(); + List catchVars = new ArrayList<>(); + cc.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, catchVars)); + catchesVars.add(catchVars); + variables.addAll(catchVars); + + for (AssignableAVM2Item a : catchVars) { + if (a instanceof UnresolvedAVM2Item) { + UnresolvedAVM2Item ui = (UnresolvedAVM2Item) a; + if (ui.getVariableName().equals(DottedChain.parseWithSuffix(e.getVariableName()))) { + try { + ui.resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), variables); + } catch (CompilationException ex) { + // ignore + } + ui.setSlotNumber(e.getSlotNumber()); + ui.setSlotScope(e.getSlotScope()); + } + + } + } + + catchCommands.add(cc); + s = lex(); + found = true; + } + //TODO: + for (int i = varCnt; i < variables.size(); i++) { + AssignableAVM2Item av = variables.get(i); + if (av instanceof UnresolvedAVM2Item) { + UnresolvedAVM2Item ui = (UnresolvedAVM2Item) av; + for (NameAVM2Item e : catchExceptions) { + if (ui.getVariableName().equals(DottedChain.parseWithSuffix(e.getVariableName()))) { + try { + ui.resolve(null, new ArrayList<>(), new ArrayList<>(), abcIndex, new ArrayList<>(), variables); + } catch (CompilationException ex) { + // ignore + } + ui.setSlotNumber(e.getSlotNumber()); + ui.setSlotScope(e.getSlotScope()); + } + } + } + } + + List finallyCommands = null; + if (s.type == SymbolType.FINALLY) { + finallyCommands = new ArrayList<>(); + finallyCommands.add(command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forinlevel, true, variables)); + found = true; + s = lex(); + } + if (!found) { + expected(s, lexer.yyline(), SymbolType.CATCH, SymbolType.FINALLY); + } + lexer.pushback(s); + TryAVM2Item tai = new TryAVM2Item(tryCommands, null, catchCommands, finallyCommands, ""); + tai.catchVariables = catchesVars; + tai.catchExceptions2 = catchExceptions; + ret = tai; + break; + case THROW: + ret = new ThrowAVM2Item(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + break; + default: + GraphTargetItem valcmd = expressionCommands(s, registerVars, inFunction, inMethod, forinlevel, variables); + if (valcmd != null) { + ret = valcmd; + break; + } + if (s.type == SymbolType.SEMICOLON) { + return null; + } + lexer.pushback(s); + ret = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + if (debugMode) { + System.out.println("/command"); + } + } + } + if (debugMode) { + System.out.println("/command"); + } + lexer.removeListener(buf); + if (ret == null) { //can be popped expression + buf.pushAllBack(lexer); + ret = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + } + s = lex(); + if ((s != null) && (s.type != SymbolType.SEMICOLON)) { + lexer.pushback(s); + } + + return ret; + + } + + private GraphTargetItem expression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { + return expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, allowRemainder, variables); + } + + /*private GraphTargetItem expressionRemainder(TypeItem thisType, String pkg, Reference needsActivation, List openedNamespaces, GraphTargetItem expr, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables, List importedClasses) throws IOException, AVM2ParseException { + GraphTargetItem ret = null; + ParsedSymbol s = lex(); + + ret = fixPrecedence(ret); + return ret; + }*/ + private boolean isNameOrProp(GraphTargetItem item) { + if (item instanceof UnresolvedAVM2Item) { + return true; //we don't know yet + } + if (item instanceof NameAVM2Item) { + return true; + } + if (item instanceof PropertyAVM2Item) { + return true; + } + return (item instanceof IndexAVM2Item); + } + + private boolean isType(GraphTargetItem item) { + if (item == null) { + return false; + } + while (item instanceof GetPropertyAVM2Item) { + item = ((GetPropertyAVM2Item) item).object; + } + return (item instanceof NameAVM2Item); + } + + private int brackets(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, List ret, HashMap registerVars, boolean inFunction, boolean inMethod, List variables) throws IOException, AVM2ParseException { + ParsedSymbol s = lex(); + int arrCnt = 0; + if (s.type == SymbolType.BRACKET_OPEN) { + s = lex(); + + while (s.type != SymbolType.BRACKET_CLOSE) { + if (s.type != SymbolType.COMMA) { + lexer.pushback(s); + } + arrCnt++; + ret.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + s = lex(); + if (!s.isType(SymbolType.COMMA, SymbolType.BRACKET_CLOSE)) { + expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.BRACKET_CLOSE); + } + } + } else { + lexer.pushback(s); + return -1; + } + return arrCnt; + } + + private GraphTargetItem commaExpression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, Stack loops, Map loopLabels, HashMap registerVars, boolean inFunction, boolean inMethod, int forInLevel, List variables) throws IOException, AVM2ParseException { + GraphTargetItem cmd = null; + List expr = new ArrayList<>(); + ParsedSymbol s; + do { + cmd = command(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, loops, loopLabels, registerVars, inFunction, inMethod, forInLevel, false, variables); + if (cmd != null) { + expr.add(cmd); + } + s = lex(); + } while (s.type == SymbolType.COMMA && cmd != null); + lexer.pushback(s); + if (cmd == null) { + expr.add(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + } else if (!cmd.hasReturnValue()) { + throw new AVM2ParseException("Expression expected", lexer.yyline()); + } + return new CommaExpressionItem(null, null, expr); + } + + private GraphTargetItem expression(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { + GraphTargetItem prim = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); + if (prim == null) { + return null; + } + return expression1(allOpenedNamespaces, prim, GraphTargetItem.NOPRECEDENCE, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); + } + + /** + * Lexer can return XML opentags instead of greater. In expression, we need + * greater sign only + * + * @param symb + */ + private void xmlToLowerThanFix(ParsedSymbol symb) { + if (symb.isType(SymbolType.XML_STARTVARTAG_BEGIN, SymbolType.XML_STARTTAG_BEGIN)) { + lexer.yypushbackstr(symb.value.toString().substring(1)); //parse again as LOWER_THAN + String pb = symb.value.toString().substring(1); + symb.type = SymbolType.LOWER_THAN; + symb.group = SymbolGroup.OPERATOR; + symb.value = "<"; + if (pb.charAt(0) == '=') { + symb.type = SymbolType.LOWER_EQUAL; + symb.value = "<="; + pb = pb.substring(1); + } + lexer.yypushbackstr(pb); //parse again as LOWER_THAN + } + } + + private void regexpToDivideFix(ParsedSymbol symb) { + if (symb.isType(SymbolType.REGEXP)) { + String pb = symb.value.toString().substring(1); + symb.type = SymbolType.DIVIDE; + symb.group = SymbolGroup.OPERATOR; + symb.value = "/"; + if (pb.charAt(0) == '=') { + symb.type = SymbolType.ASSIGN_DIVIDE; + symb.value = "/="; + pb = pb.substring(1); + } + lexer.yypushbackstr(pb); //parse again as DIVIDE + + } + } + + private ParsedSymbol peekExprToken() throws IOException, AVM2ParseException { + ParsedSymbol lookahead = lex(); + xmlToLowerThanFix(lookahead); + regexpToDivideFix(lookahead); + + lexer.pushback(lookahead); + return lookahead; + } + + private GraphTargetItem expression1(List> allOpenedNamespaces, GraphTargetItem lhs, int min_precedence, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { + if (debugMode) { + System.out.println("expression1:"); + } + ParsedSymbol lookahead = peekExprToken(); + + ParsedSymbol op; + GraphTargetItem rhs; + GraphTargetItem mhs = null; + + //Note: algorithm from http://en.wikipedia.org/wiki/Operator-precedence_parser + //with relation operators reversed as we have precedence in reverse order + while (lookahead.type.isBinary() && lookahead.type.getPrecedence() <= /* >= on wiki */ min_precedence) { + op = lookahead; + lex(); + + //Note: Handle ternar operator as Binary + //http://stackoverflow.com/questions/13681293/how-can-i-incorporate-ternary-operators-into-a-precedence-climbing-algorithm + if (op.type == SymbolType.TERNAR) { + if (debugMode) { + System.out.println("ternar-middle:"); + } + mhs = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); + expectedType(SymbolType.COLON); + if (debugMode) { + System.out.println("/ternar-middle"); + } + } + + rhs = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); + if (rhs == null) { + lexer.pushback(op); + break; + } + + lookahead = peekExprToken(); + while ((lookahead.type.isBinary() && lookahead.type.getPrecedence() < /* > on wiki */ op.type.getPrecedence()) + || (lookahead.type.isRightAssociative() && lookahead.type.getPrecedence() == op.type.getPrecedence())) { + rhs = expression1(allOpenedNamespaces, rhs, lookahead.type.getPrecedence(), thisType, pkg, needsActivation, importedClasses, openedNamespaces, allowEmpty, registerVars, inFunction, inMethod, allowRemainder, variables); + lookahead = peekExprToken(); + } + + switch (op.type) { + case AS: + //GraphTargetItem type = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + + lhs = new AsTypeAVM2Item(null, null, lhs, rhs); //??? + allowRemainder = false; + break; + + case IN: + lhs = new InAVM2Item(null, null, lhs, rhs); + break; + + case TERNAR: //??? + lhs = new TernarOpItem(null, null, lhs, mhs, rhs); + break; + case SHIFT_LEFT: + lhs = new LShiftAVM2Item(null, null, lhs, rhs); + break; + case SHIFT_RIGHT: + lhs = new RShiftAVM2Item(null, null, lhs, rhs); + break; + case USHIFT_RIGHT: + lhs = new URShiftAVM2Item(null, null, lhs, rhs); + break; + case BITAND: + lhs = new BitAndAVM2Item(null, null, lhs, rhs); + break; + case BITOR: + lhs = new BitOrAVM2Item(null, null, lhs, rhs); + break; + case DIVIDE: + lhs = new DivideAVM2Item(null, null, lhs, rhs); + break; + case MODULO: + lhs = new ModuloAVM2Item(null, null, lhs, rhs); + break; + case EQUALS: + lhs = new EqAVM2Item(null, null, lhs, rhs); + break; + case STRICT_EQUALS: + lhs = new StrictEqAVM2Item(null, null, lhs, rhs); + break; + case NOT_EQUAL: + lhs = new NeqAVM2Item(null, null, lhs, rhs); + break; + case STRICT_NOT_EQUAL: + lhs = new StrictNeqAVM2Item(null, null, lhs, rhs); + break; + case LOWER_THAN: + lhs = new LtAVM2Item(null, null, lhs, rhs); + break; + case LOWER_EQUAL: + lhs = new LeAVM2Item(null, null, lhs, rhs); + break; + case GREATER_THAN: + lhs = new GtAVM2Item(null, null, lhs, rhs); + break; + case GREATER_EQUAL: + lhs = new GeAVM2Item(null, null, lhs, rhs); + break; + case AND: + lhs = new AndItem(null, null, lhs, rhs); + break; + case OR: + lhs = new OrItem(null, null, lhs, rhs); + break; + case MINUS: + lhs = new SubtractAVM2Item(null, null, lhs, rhs); + break; + case MULTIPLY: + lhs = new MultiplyAVM2Item(null, null, lhs, rhs); + break; + case PLUS: + lhs = new AddAVM2Item(null, null, lhs, rhs); + break; + case XOR: + lhs = new BitXorAVM2Item(null, null, lhs, rhs); + break; + case INSTANCEOF: + lhs = new InstanceOfAVM2Item(null, null, lhs, rhs); + break; + case IS: + GraphTargetItem istype = rhs;//type(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, variables); + lhs = new IsTypeAVM2Item(null, null, lhs, istype); + break; + case ASSIGN: + case ASSIGN_BITAND: + case ASSIGN_BITOR: + case ASSIGN_DIVIDE: + case ASSIGN_MINUS: + case ASSIGN_MODULO: + case ASSIGN_MULTIPLY: + case ASSIGN_PLUS: + case ASSIGN_SHIFT_LEFT: + case ASSIGN_SHIFT_RIGHT: + case ASSIGN_USHIFT_RIGHT: + case ASSIGN_XOR: + GraphTargetItem assigned = rhs; + switch (op.type) { + case ASSIGN: + //assigned = assigned; + break; + case ASSIGN_BITAND: + assigned = new BitAndAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_BITOR: + assigned = new BitOrAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_DIVIDE: + assigned = new DivideAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_MINUS: + assigned = new SubtractAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_MODULO: + assigned = new ModuloAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_MULTIPLY: + assigned = new MultiplyAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_PLUS: + assigned = new AddAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_SHIFT_LEFT: + assigned = new LShiftAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_SHIFT_RIGHT: + assigned = new RShiftAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_USHIFT_RIGHT: + assigned = new URShiftAVM2Item(null, null, lhs, assigned); + break; + case ASSIGN_XOR: + assigned = new BitXorAVM2Item(null, null, lhs, assigned); + break; + } + + if (!(lhs instanceof AssignableAVM2Item)) { + throw new AVM2ParseException("Invalid assignment", lexer.yyline()); + } + AssignableAVM2Item as = ((AssignableAVM2Item) lhs).copy(); + if ((as instanceof UnresolvedAVM2Item) || (as instanceof NameAVM2Item)) { + variables.add(as); + } + as.setAssignedValue(assigned); + if (lhs instanceof NameAVM2Item) { + ((NameAVM2Item) lhs).setDefinition(false); + } + lhs = as; + break; + case DESCENDANTS: + expected(lookahead, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.MULTIPLY); + lookahead = lex(); + lhs = new GetDescendantsAVM2Item(lhs, lookahead.type == SymbolType.MULTIPLY ? null : lookahead.value.toString(), openedNamespaces); + allowRemainder = true; + break; + } + } + if (lhs instanceof ParenthesisItem) { + GraphTargetItem coerced = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); + if (coerced != null && isType(((ParenthesisItem) lhs).value)) { + lhs = new CoerceAVM2Item(null, null, ((ParenthesisItem) lhs).value, coerced); + } + } + + if (debugMode) { + System.out.println("/expression1"); + } + return lhs; + } + + private GraphTargetItem expressionPrimary(List> allOpenedNamespaces, TypeItem thisType, NamespaceItem pkg, Reference needsActivation, List importedClasses, List openedNamespaces, boolean allowEmpty, HashMap registerVars, boolean inFunction, boolean inMethod, boolean allowRemainder, List variables) throws IOException, AVM2ParseException { + if (debugMode) { + System.out.println("primary:"); + } + GraphTargetItem ret = null; + ParsedSymbol s = lex(); + boolean allowMemberOrCall = false; + switch (s.type) { + case PREPROCESSOR: + expectedType(SymbolType.PARENT_OPEN); + switch ("" + s.value) { + //AS3 + case "hasnext": + GraphTargetItem hnIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.COMMA); + GraphTargetItem hnObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + ret = new HasNextAVM2Item(null, null, hnIndex, hnObject); + break; + case "newactivation": + ret = new NewActivationAVM2Item(null, null); + break; + case "nextname": + GraphTargetItem nnIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.COMMA); + GraphTargetItem nnObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + + ret = new NextNameAVM2Item(null, null, nnIndex, nnObject); + allowMemberOrCall = true; + break; + case "nextvalue": + GraphTargetItem nvIndex = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + expectedType(SymbolType.COMMA); + GraphTargetItem nvObject = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + + ret = new NextNameAVM2Item(null, null, nvIndex, nvObject); + allowMemberOrCall = true; + break; + //Both ASs + case "dup": + ret = new DuplicateItem(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + break; + case "push": + ret = new PushItem(expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + break; + case "pop": + ret = new PopItem(null, null); + break; + case "goto": //TODO + case "multiname": + throw new AVM2ParseException("Compiling §§" + s.value + " is not available, sorry", lexer.yyline()); + default: + throw new AVM2ParseException("Unknown preprocessor instruction: §§" + s.value, lexer.yyline()); + } + expectedType(SymbolType.PARENT_CLOSE); + break; + case REGEXP: + String p = (String) s.value; + p = p.substring(1); + int spos = p.lastIndexOf('/'); + String mod = p.substring(spos + 1); + p = p.substring(0, spos); + p = p.replace("\\/", "/"); + ret = new RegExpAvm2Item(p, mod, null, null); + allowMemberOrCall = true; + + break; + case XML_STARTTAG_BEGIN: + lexer.pushback(s); + ret = xml(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables); + break; + case STRING: + ret = new StringAVM2Item(null, null, s.value.toString()); + allowMemberOrCall = true; + break; + case NEGATE: + ret = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables); + ret = new NegAVM2Item(null, null, ret); + + break; + case MINUS: + s = lex(); + if (s.isType(SymbolType.DOUBLE)) { + ret = new FloatValueAVM2Item(null, null, -(Double) s.value); + + } else if (s.isType(SymbolType.INTEGER)) { + ret = new IntegerValueAVM2Item(null, null, -(Long) s.value); + + } else { + lexer.pushback(s); + GraphTargetItem num = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, true, variables); + if (num instanceof IntegerValueAVM2Item) { + ((IntegerValueAVM2Item) num).value = -((IntegerValueAVM2Item) num).value; + ret = num; + } else if (num instanceof FloatValueAVM2Item) { + Double d = ((FloatValueAVM2Item) num).value; + if (d.isInfinite()) { + ((FloatValueAVM2Item) num).value = Double.NEGATIVE_INFINITY; + } else { + ((FloatValueAVM2Item) num).value = -d; + } + ret = (num); + } else { + ret = (new SubtractAVM2Item(null, null, new IntegerValueAVM2Item(null, null, 0L), num)); + } + } + break; + case TYPEOF: + ret = new TypeOfAVM2Item(null, null, expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); + break; + case TRUE: + ret = new BooleanAVM2Item(null, null, true); + + break; + case NULL: + ret = new NullAVM2Item(null, null); + + break; + case UNDEFINED: + ret = new UndefinedAVM2Item(null, null); + break; + case FALSE: + ret = new BooleanAVM2Item(null, null, false); + + break; + case CURLY_OPEN: //Object literal + s = lex(); + List nvs = new ArrayList<>(); + + while (s.type != SymbolType.CURLY_CLOSE) { + if (s.type != SymbolType.COMMA) { + lexer.pushback(s); + } + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER, SymbolType.STRING, SymbolType.INTEGER, SymbolType.DOUBLE); + + GraphTargetItem n = new StringAVM2Item(null, null, s.value.toString()); +//expression(allOpenedNamespaces, thisType,pkg,needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); + expectedType(SymbolType.COLON); + GraphTargetItem v = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, allowRemainder, variables); + + NameValuePair nv = new NameValuePair(n, v); + nvs.add(nv); + s = lex(); + if (!s.isType(SymbolType.COMMA, SymbolType.CURLY_CLOSE)) { + expected(s, lexer.yyline(), SymbolType.COMMA, SymbolType.CURLY_CLOSE); + } + } + ret = new NewObjectAVM2Item(null, null, nvs); + allowMemberOrCall = true; + break; + case BRACKET_OPEN: //Array literal or just brackets + lexer.pushback(s); + List inBrackets = new ArrayList<>(); + int arrCnt = brackets(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, inBrackets, registerVars, inFunction, inMethod, variables); + ret = new NewArrayAVM2Item(null, null, inBrackets); + allowMemberOrCall = true; + + break; + case FUNCTION: + s = lexer.lex(); + String fname = ""; + if (s.isType(SymbolGroup.IDENTIFIER)) { + fname = s.value.toString(); + } else { + lexer.pushback(s); + } + needsActivation.setVal(true); + ret = function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, fname, false, variables); + allowMemberOrCall = true; + break; + case NAN: + ret = new NanAVM2Item(null, null); + + break; + case INFINITY: + ret = new FloatValueAVM2Item(null, null, Double.POSITIVE_INFINITY); + + break; + case INTEGER: + ret = new IntegerValueAVM2Item(null, null, (Long) s.value); + + break; + case DOUBLE: + ret = new FloatValueAVM2Item(null, null, (Double) s.value); + + break; + case DELETE: + GraphTargetItem varDel = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, true, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); + if (!isNameOrProp(varDel)) { + throw new AVM2ParseException("Not a property or name", lexer.yyline()); + } + ret = new DeletePropertyAVM2Item(varDel, lexer.yyline()); + break; + case INCREMENT: + case DECREMENT: //preincrement + GraphTargetItem varincdec = expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false/*?*/, variables);//name(allOpenedNamespaces, thisType,false, openedNamespaces, registerVars, inFunction, inMethod, variables); + if (!isNameOrProp(varincdec)) { + throw new AVM2ParseException("Not a property or name", lexer.yyline()); + } + if (s.type == SymbolType.INCREMENT) { + ret = new PreIncrementAVM2Item(null, null, varincdec); + } + if (s.type == SymbolType.DECREMENT) { + ret = new PreDecrementAVM2Item(null, null, varincdec); + } + + break; + case NOT: + ret = new NotItem(null, null, expressionPrimary(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, false, registerVars, inFunction, inMethod, false, variables)); + + break; + case PARENT_OPEN: + ret = new ParenthesisItem(null, null, expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables)); + expectedType(SymbolType.PARENT_CLOSE); + if (ret.value == null) { + throw new AVM2ParseException("Expression in parenthesis expected", lexer.yyline()); + } + allowMemberOrCall = true; + break; + case NEW: + s = lex(); + if (s.type == SymbolType.XML_STARTTAG_BEGIN) { + lexer.yypushbackstr(s.value.toString().substring(1), ActionScriptLexer.YYINITIAL); + s = new ParsedSymbol(SymbolGroup.OPERATOR, SymbolType.LOWER_THAN); + } + if (s.type == SymbolType.FUNCTION) { + s = lexer.lex(); + String ffname = ""; + if (s.isType(SymbolGroup.IDENTIFIER)) { + ffname = s.value.toString(); + } else { + lexer.pushback(s); + } + needsActivation.setVal(true); + ret = function(allOpenedNamespaces, new ArrayList<>(), pkg, false, needsActivation, importedClasses, thisType, openedNamespaces, ffname, false, variables); + } else if (s.type == SymbolType.LOWER_THAN) { + GraphTargetItem subtype = type(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, variables); + expectedType(SymbolType.GREATER_THAN); + s = lex(); + expected(s, lexer.yyline(), SymbolType.BRACKET_OPEN); + lexer.pushback(s); + List params = new ArrayList<>(); + brackets(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, params, registerVars, inFunction, inMethod, variables); + ret = new InitVectorAVM2Item(subtype, params, openedNamespaces); + } else if (s.type == SymbolType.PARENT_OPEN) { + GraphTargetItem newvar = expression(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, true, variables); + newvar = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, newvar, registerVars, inFunction, inMethod, variables); + expectedType(SymbolType.PARENT_CLOSE); + expectedType(SymbolType.PARENT_OPEN); + ret = new ConstructSomethingAVM2Item(lexer.yyline(), openedNamespaces, newvar, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); + + } else { + lexer.pushback(s); + GraphTargetItem newvar = name(allOpenedNamespaces, thisType, pkg, needsActivation, false /*?*/, openedNamespaces, registerVars, inFunction, inMethod, variables, importedClasses); + newvar = applyType(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, newvar, registerVars, inFunction, inMethod, variables); + expectedType(SymbolType.PARENT_OPEN); + ret = new ConstructSomethingAVM2Item(lexer.yyline(), openedNamespaces, newvar, call(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, registerVars, inFunction, inMethod, variables)); + } + allowMemberOrCall = true; + break; + case IDENTIFIER: + case THIS: + case SUPER: + case ATTRIBUTE: + lexer.pushback(s); + ret = name(allOpenedNamespaces, thisType, pkg, needsActivation, false, openedNamespaces, registerVars, inFunction, inMethod, variables, importedClasses); + allowMemberOrCall = true; + + //var = memberOrCall(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, var, registerVars, inFunction, inMethod, variables); + //ret = var; + break; + default: + GraphTargetItem excmd = expressionCommands(s, registerVars, inFunction, inMethod, -1, variables); + if (excmd != null) { + //? + ret = excmd; + allowMemberOrCall = true; //? + break; + } + lexer.pushback(s); + } + if (allowMemberOrCall && ret != null) { + ret = memberOrCall(allOpenedNamespaces, thisType, pkg, needsActivation, importedClasses, openedNamespaces, ret, registerVars, inFunction, inMethod, variables); + } + if (debugMode) { + System.out.println("/primary"); + } + return ret; + } + + private ActionScriptLexer lexer = null; + + private List constantPool; + + private List parseImportsUsages(List openedNamespaces) throws IOException, AVM2ParseException { + + ParsedSymbol s; + List importedClasses = new ArrayList<>(); + + s = lex(); + while (s.isType(SymbolType.IMPORT, SymbolType.USE)) { + boolean all = false; + boolean isUse = s.type == SymbolType.USE; + if (isUse) { + expectedType(SymbolType.NAMESPACE); + } + s = lex(); + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + DottedChain fullName = new DottedChain(new String[]{}, ""); + fullName = fullName.addWithSuffix(s.value.toString()); + s = lex(); + boolean isStar = false; + while (s.type == SymbolType.DOT) { + + s = lex(); + if (s.type == SymbolType.MULTIPLY && !isUse) { + isStar = true; + s = lex(); + break; + } + expected(s, lexer.yyline(), SymbolGroup.IDENTIFIER); + fullName = fullName.addWithSuffix(s.value.toString()); + s = lex(); + } + + if (isStar) { + openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_PACKAGE)); + } else if (isUse) { + //Note: in this case, fullName attribute will be changed to real NS insude NamespaceItem + openedNamespaces.add(new NamespaceItem(fullName, Namespace.KIND_NAMESPACE)); + } else { + importedClasses.add(fullName); + } + + expected(s, lexer.yyline(), SymbolType.SEMICOLON); + s = lex(); + } + lexer.pushback(s); + return importedClasses; + } + + private List parseScript(List> allOpenedNamespaces, int scriptIndex, String fileName) throws IOException, AVM2ParseException, CompilationException { + + //int scriptPrivateNs; + if (fileName.contains("/")) { + fileName = fileName.substring(fileName.lastIndexOf('/') + 1); + } + if (fileName.contains("\\")) { + fileName = fileName.substring(fileName.lastIndexOf('\\') + 1); + } + List items = new ArrayList<>(); + scriptTraits(allOpenedNamespaces, scriptIndex, fileName, items); + return items; + } + + public List scriptTraitsFromString(List> allOpenedNamespaces, String str, String fileName, int scriptIndex) throws AVM2ParseException, IOException, CompilationException { + lexer = new ActionScriptLexer(str); + + List ret = parseScript(allOpenedNamespaces, scriptIndex, fileName); + if (lexer.lex().type != SymbolType.EOF) { + throw new AVM2ParseException("Parsing finisned before end of the file", lexer.yyline()); + } + return ret; + } + + public void addScriptFromTree(List> allOpenedNamespaces, List items, boolean documentClass, int classPos) throws AVM2ParseException, CompilationException { + AVM2SourceGenerator gen = new AVM2SourceGenerator(abcIndex); + SourceGeneratorLocalData localData = new SourceGeneratorLocalData( + new HashMap<>(), 0, Boolean.FALSE, 0); + localData.documentClass = documentClass; + abcIndex.getSelectedAbc().script_info.add(gen.generateScriptInfo(allOpenedNamespaces, localData, items, classPos)); + } + + public void addScript(String s, boolean documentClass, String fileName, int classPos, int scriptIndex) throws AVM2ParseException, IOException, CompilationException { + List> allOpenedNamespaces = new ArrayList<>(); + List traits = scriptTraitsFromString(allOpenedNamespaces, s, fileName, scriptIndex); + addScriptFromTree(allOpenedNamespaces, traits, documentClass, classPos); + } + + public ActionScript3Parser(ABC abc, List otherAbcs) throws IOException, InterruptedException { + initPlayer(); + + abcIndex = new AbcIndexing(playerGlobalAbcIndex); + for (ABC a : otherAbcs) { + abcIndex.addAbc(a); + } + + abcIndex.addAbc(abc); + } + + private static void initPlayer() throws IOException, InterruptedException { + if (playerGlobalAbcIndex == null) { + if (Configuration.getPlayerSWC() == null) { + throw new IOException("Player SWC library not found, please place it to " + Configuration.getFlashLibPath()); + } + + SWC swc = new SWC(new FileInputStream(Configuration.getPlayerSWC())); + SWF swf = new SWF(swc.getSWF("library.swf"), true); + playerGlobalAbcIndex = new AbcIndexing(swf); + } + } + + public static void compile(String src, ABC abc, List otherABCs, boolean documentClass, String fileName, int classPos, int scriptIndex) throws AVM2ParseException, IOException, InterruptedException, CompilationException { + //List parABCs = new ArrayList<>(); + initPlayer(); + ActionScript3Parser parser = new ActionScript3Parser(abc, otherABCs); + boolean success = false; + ABC originalAbc = ((ABCContainerTag) ((Tag) abc.parentTag).cloneTag()).getABC(); + try { + parser.addScript(src, documentClass, fileName, classPos, scriptIndex); + success = true; + } finally { + if (!success) { + // restore original constant pool and other lists + abc.constants = originalAbc.constants; + abc.method_info = originalAbc.method_info; + abc.metadata_info = originalAbc.metadata_info; + abc.instance_info = originalAbc.instance_info; + abc.class_info = originalAbc.class_info; + abc.script_info = originalAbc.script_info; + abc.bodies = originalAbc.bodies; + abc.getMethodIndexing(); + } + } + } + + public static void compile(SWF swf, String src, String dst, int classPos, int scriptIndex) { + System.err.println("WARNING: AS3 compiler is not finished yet. This is only used for debuggging!"); + try { + initPlayer(); + ABC abc = new ABC(null); + ActionScript3Parser parser = new ActionScript3Parser(abc, new ArrayList<>()); + parser.addScript(new String(Helper.readFile(src), Utf8Helper.charset), true, src, classPos, scriptIndex); + try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(new File(dst)))) { + abc.saveToStream(fos); + + } + } catch (Exception ex) { + Logger.getLogger(ActionScript3Parser.class + .getName()).log(Level.SEVERE, null, ex); + } + System.exit(0); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java index 9b825bb63..6d67d38d9 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/abc/types/Float4.java @@ -1,21 +1,21 @@ -package com.jpexs.decompiler.flash.abc.types; - -public class Float4 { - - public float[] values = new float[4]; - - public Float4(float value1, float value2, float value3, float value4) { - this.values = new float[]{value1, value2, value3, value4}; - } - - public Float4(float[] values) { - if (values == null || values.length < 4) { - throw new IllegalArgumentException("Invalid values size"); - } - this.values[0] = values[0]; - this.values[1] = values[1]; - this.values[2] = values[2]; - this.values[3] = values[3]; - } - -} +package com.jpexs.decompiler.flash.abc.types; + +public class Float4 { + + public float[] values = new float[4]; + + public Float4(float value1, float value2, float value3, float value4) { + this.values = new float[]{value1, value2, value3, value4}; + } + + public Float4(float[] values) { + if (values == null || values.length < 4) { + throw new IllegalArgumentException("Invalid values size"); + } + this.values[0] = values[0]; + this.values[1] = values[1]; + this.values[2] = values[2]; + this.values[3] = values[3]; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java index d81467147..d2452942c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/dumpview/DumpInfoSpecialType.java @@ -1,28 +1,28 @@ -/* - * 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.dumpview; - -/** - * - * @author JPEXS - */ -public enum DumpInfoSpecialType { - NONE, ZLIB_DATA, - TAG, - ACTION_BYTES, - ABC_BYTES, ABC_METHOD_BODY, ABC_CODE -} +/* + * 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.dumpview; + +/** + * + * @author JPEXS + */ +public enum DumpInfoSpecialType { + NONE, ZLIB_DATA, + TAG, + ACTION_BYTES, + ABC_BYTES, ABC_METHOD_BODY, ABC_CODE +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java index 2a642a62f..44a33590b 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgImporter.java @@ -1,1666 +1,1666 @@ -/* - * 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.importers.svg; - -import com.jpexs.decompiler.flash.ReadOnlyTagList; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.ShapeExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.Point; -import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; -import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; -import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; -import com.jpexs.decompiler.flash.importers.ShapeImporter; -import com.jpexs.decompiler.flash.tags.DefineShape4Tag; -import com.jpexs.decompiler.flash.tags.ExportAssetsTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.types.FILLSTYLE; -import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; -import com.jpexs.decompiler.flash.types.FOCALGRADIENT; -import com.jpexs.decompiler.flash.types.GRADIENT; -import com.jpexs.decompiler.flash.types.GRADRECORD; -import com.jpexs.decompiler.flash.types.LINESTYLE; -import com.jpexs.decompiler.flash.types.LINESTYLE2; -import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.RGB; -import com.jpexs.decompiler.flash.types.RGBA; -import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; -import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; -import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; -import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.Color; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.StringReader; -import java.net.URL; -import java.util.ArrayList; -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.imageio.ImageIO; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; - -/** - * - * @author JPEXS - */ -public class SvgImporter { - - private final Set shownWarnings = new HashSet<>(); - - ShapeTag shapeTag; - - private Rectangle2D.Double viewBox; - - public Tag importSvg(ShapeTag st, String svgXml) { - return importSvg(st, svgXml, true); - } - - public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { - shapeTag = st; - - SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); - shapes.fillStyles = new FILLSTYLEARRAY(); - shapes.lineStyles = new LINESTYLEARRAY(); - shapes.fillStyles.fillStyles = new FILLSTYLE[0]; - shapes.lineStyles.lineStyles = new LINESTYLE[0]; - - int shapeNum = st.getShapeNum(); - shapes.shapeRecords = new ArrayList<>(); - - Rectangle2D.Double viewBox = null; - try { - DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); - /*docFactory.setValidating(false); - docFactory.setNamespaceAware(true); - docFactory.setFeature("http://xml.org/sax/features/namespaces", false); - docFactory.setFeature("http://xml.org/sax/features/validation", false); - docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);*/ - docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); - DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); - - Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); - Element rootElement = doc.getDocumentElement(); - - Map idMap = new HashMap<>(); - populateIds(rootElement, idMap); - - if (!"svg".equals(rootElement.getTagName())) { - throw new IOException("SVG root element should be 'svg'"); - } - - double width = 800; - double height = 600; - - if (rootElement.hasAttribute("viewBox")) { - String params = rootElement.getAttribute("viewBox"); - String[] args = Matrix.parseSvgNumberList(params); - viewBox = new Rectangle2D.Double(); - if (args.length > 0) { - viewBox.x = parseNumber(args[0]); - } - if (args.length > 1) { - viewBox.y = parseNumber(args[1]); - } - if (args.length > 2) { - viewBox.width = parseNumber(args[2]); - } - if (args.length > 3) { - viewBox.height = parseNumber(args[3]); - } - - width = viewBox.width; - height = viewBox.height; - } - - if (rootElement.hasAttribute("width")) { - width = parseLength(rootElement.getAttribute("width"), width); - } - - if (rootElement.hasAttribute("height")) { - height = parseLength(rootElement.getAttribute("height"), height); - } - - if (viewBox == null) { - viewBox = new Rectangle2D.Double(); - viewBox.width = width; - viewBox.height = height; - } - - this.viewBox = viewBox; - - SvgStyle style = new SvgStyle(this, idMap, rootElement); - Matrix transform = new Matrix(); - processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style); - } catch (SAXException | IOException | ParserConfigurationException ex) { - Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); - } - - shapes.shapeRecords.add(new EndShapeRecord()); - - RECT rect = st.getRect(); - int origXmin = rect.Xmin; - int origYmin = rect.Ymin; - rect.Xmin -= origXmin; - rect.Xmax -= origXmin; - rect.Ymin -= origYmin; - rect.Ymax -= origYmin; - - if (!fill && viewBox != null) { - rect.Xmin = (int) Math.round(viewBox.x * SWF.unitDivisor); - rect.Ymin = (int) Math.round(viewBox.y * SWF.unitDivisor); - rect.Xmax = (int) Math.round((viewBox.x + viewBox.width) * SWF.unitDivisor); - rect.Ymax = (int) Math.round((viewBox.y + viewBox.height) * SWF.unitDivisor); - } - - st.shapes = shapes; - st.setModified(true); - - return (Tag) st; - } - - // Generate id-element map, because getElementById does not work in some cases (namespaces?) - protected void populateIds(Element el, Map out) { - if (el.hasAttribute("id")) { - out.put(el.getAttribute("id"), el); - } - NodeList nodes = el.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - if (nodes.item(i) instanceof Element) { - populateIds((Element) nodes.item(i), out); - } - } - } - - private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { - for (int i = 0; i < element.getChildNodes().getLength(); i++) { - Node childNode = element.getChildNodes().item(i); - if (childNode instanceof Element) { - Element childElement = (Element) childNode; - String tagName = childElement.getTagName(); - SvgStyle newStyle = new SvgStyle(this, idMap, childElement); - Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1); - Matrix m2 = m == null ? transform : transform.concatenate(m); - if ("g".equals(tagName)) { - processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle); - } else if ("path".equals(tagName)) { - processPath(shapeNum, shapes, childElement, m2, newStyle); - } else if ("circle".equals(tagName)) { - processCircle(shapeNum, shapes, childElement, m2, newStyle); - } else if ("ellipse".equals(tagName)) { - processEllipse(shapeNum, shapes, childElement, m2, newStyle); - } else if ("rect".equals(tagName)) { - processRect(shapeNum, shapes, childElement, m2, newStyle); - } else if ("line".equals(tagName)) { - processLine(shapeNum, shapes, childElement, m2, newStyle); - } else if ("polyline".equals(tagName)) { - processPolyline(shapeNum, shapes, childElement, m2, newStyle); - } else if ("polygon".equals(tagName)) { - processPolygon(shapeNum, shapes, childElement, m2, newStyle); - } else if ("defs".equals(tagName) || "title".equals(tagName) || "desc".equals(tagName) - || "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) { - // ignore - } else { - showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); - } - } - } - } - - private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { - Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); - Point prevPoint = new Point(0, 0); - Point startPoint = prevPoint; - double x0 = 0; - double y0 = 0; - - StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); - int fillStyle = scrStyle.fillStyle1; - int lineStyle = scrStyle.lineStyle; - scrStyle.stateFillStyle0 = true; - scrStyle.stateFillStyle1 = true; - scrStyle.stateLineStyle = true; - scrStyle.fillStyle0 = 0; - scrStyle.fillStyle1 = 0; - scrStyle.lineStyle = 0; - - List newRecords = new ArrayList<>(); - - newRecords.add(scrStyle); - - LINESTYLE lineStyleObj = scrStyle.lineStyles.lineStyles.length < 1 ? null : scrStyle.lineStyles.lineStyles[0]; - LINESTYLE2 lineStyle2Obj = null; - if (lineStyleObj instanceof LINESTYLE2) { - lineStyle2Obj = (LINESTYLE2) lineStyleObj; - lineStyle2Obj.noClose = true; - } - - for (PathCommand command : commands) { - double x = x0; - double y = y0; - Point p; - char cmd = Character.toUpperCase(command.command); - switch (cmd) { - case 'M': - StyleChangeRecord scr = new StyleChangeRecord(); - if (fillStyle != 0) { - scr.stateFillStyle1 = true; - scr.fillStyle1 = fillStyle; - } - if (lineStyle != 0) { - scr.lineStyle = lineStyle; - scr.stateLineStyle = true; - } - - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - scr.moveDeltaX = (int) Math.round(p.x); - scr.moveDeltaY = (int) Math.round(p.y); - prevPoint = p; - scr.stateMoveTo = true; - - newRecords.add(scr); - startPoint = p; - break; - case 'Z': - StraightEdgeRecord serz = new StraightEdgeRecord(); - p = startPoint; - serz.deltaX = (int) Math.round(p.x - prevPoint.x); - serz.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serz.generalLineFlag = true; - newRecords.add(serz); - if (lineStyle2Obj != null) { - lineStyle2Obj.noClose = false; - } - break; - case 'L': - StraightEdgeRecord serl = new StraightEdgeRecord(); - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - serl.deltaX = (int) Math.round(p.x - prevPoint.x); - serl.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serl.generalLineFlag = true; - serl.simplify(); - newRecords.add(serl); - break; - case 'H': - StraightEdgeRecord serh = new StraightEdgeRecord(); - x = command.params[0]; - - p = transform2.transform(x, y); - serh.deltaX = (int) Math.round(p.x - prevPoint.x); - prevPoint = p; - newRecords.add(serh); - break; - case 'V': - StraightEdgeRecord serv = new StraightEdgeRecord(); - y = command.params[0]; - - p = transform2.transform(x, y); - serv.deltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - serv.vertLineFlag = true; - newRecords.add(serv); - break; - case 'Q': - CurvedEdgeRecord cer = new CurvedEdgeRecord(); - x = command.params[0]; - y = command.params[1]; - - p = transform2.transform(x, y); - cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); - cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - - x = command.params[2]; - y = command.params[3]; - - p = transform2.transform(x, y); - cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); - cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - newRecords.add(cer); - break; - case 'C': - showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); - - // create at least something... - Point pStart = prevPoint; - Point pControl1; - - x = command.params[0]; - y = command.params[1]; - - pControl1 = transform2.transform(x, y); - - x = command.params[2]; - y = command.params[3]; - - Point pControl2 = transform2.transform(x, y); - - x = command.params[4]; - y = command.params[5]; - - p = transform2.transform(x, y); - - //StraightEdgeRecord serc = new StraightEdgeRecord(); - //serc.generalLineFlag = true; - //serc.deltaX = (int) Math.round(p.x - prevPoint.x); - //serc.deltaY = (int) Math.round(p.y - prevPoint.y); - //newRecords.add(serc); - List quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 1); - for (int i = 2; i < quadCoordinates.size();) { - CurvedEdgeRecord cerc = new CurvedEdgeRecord(); - p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); - cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); - cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - - p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); - cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); - cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); - prevPoint = p; - newRecords.add(cerc); - } - - break; - default: - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - return; - } - - x0 = x; - y0 = y; - } - applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style); - shapes.shapeRecords.addAll(newRecords); - } - - private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String data = childElement.getAttribute("d"); - - char command = 0; - Point startPoint = new Point(0, 0); - Point prevCControlPoint = null; - Point prevQControlPoint = null; - double x0 = 0; - double y0 = 0; - - List pathCommands = new ArrayList<>(); - SvgPathReader pathReader = new SvgPathReader(data); - try { - while (pathReader.hasNext()) { - char newCommand; - if ((newCommand = pathReader.readCommand()) != 0) { - command = newCommand; - } - - boolean isRelative = Character.isLowerCase(command); - - double x = x0; - double y = y0; - - char cmd = Character.toUpperCase(command); - switch (cmd) { - case 'M': - PathCommand scr = new PathCommand(); - scr.command = 'M'; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - scr.params = new double[]{x, y}; - - pathCommands.add(scr); - startPoint = new Point(x, y); - - command = isRelative ? 'l' : 'L'; - break; - case 'Z': - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - x = startPoint.x; - y = startPoint.y; - pathCommands.add(serz); - break; - case 'L': - PathCommand serl = new PathCommand(); - serl.command = 'L'; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - serl.params = new double[]{x, y}; - pathCommands.add(serl); - break; - case 'H': - PathCommand serh = new PathCommand(); - serh.command = 'H'; - x = pathReader.readDouble(); - if (isRelative) { - x += x0; - } - - serh.params = new double[]{x}; - pathCommands.add(serh); - break; - case 'V': - PathCommand serv = new PathCommand(); - serv.command = 'V'; - y = pathReader.readDouble(); - if (isRelative) { - y += y0; - } - - serv.params = new double[]{y}; - pathCommands.add(serv); - break; - case 'Q': - case 'T': - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - - Point pControl; - if (cmd == 'Q') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - pControl = new Point(x, y); - } else if (prevQControlPoint != null) { - pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); - } else { - pControl = new Point(x0, y0); - } - - prevQControlPoint = pControl; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - cer.params = new double[]{pControl.x, pControl.y, x, y}; - pathCommands.add(cer); - break; - case 'C': - case 'S': - showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); - - // create at least something... - Point pControl1; - if (cmd == 'C') { - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - pControl1 = new Point(x, y); - } else if (prevCControlPoint != null) { - pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); - } else { - pControl1 = new Point(x0, y0); - } - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - Point pControl2 = new Point(x, y); - prevCControlPoint = pControl2; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - PathCommand cerc = new PathCommand(); - cerc.command = 'C'; - cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; - pathCommands.add(cerc); - break; - case 'A': - double rx = pathReader.readDouble(); - double ry = pathReader.readDouble(); - double fi = pathReader.readDouble() * Math.PI / 180; - boolean largeFlag = (int) pathReader.readDouble() != 0; - boolean sweepFlag = (int) pathReader.readDouble() != 0; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - if (isRelative) { - x += x0; - y += y0; - } - - if (rx == 0 || ry == 0) { - // straight line to (x, y) - PathCommand sera = new PathCommand(); - sera.command = 'L'; - sera.params = new double[]{x, y}; - pathCommands.add(sera); - } else { - rx = Math.abs(rx); - ry = Math.abs(ry); - - double x1 = x0; - double y1 = y0; - double x2 = x; - double y2 = y; - - double d1 = (x1 - x2) / 2; - double d2 = (y1 - y2) / 2; - double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; - double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; - - // Correction of out-of-range radii - double lambda = x1Comma * x1Comma / (rx * rx) + y1Comma * y1Comma / (ry * ry); - if (lambda > 1) { - double sqrtLambda = Math.sqrt(lambda); - rx = sqrtLambda * rx; - ry = sqrtLambda * ry; - } - - double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); - double cxComma = c * rx * y1Comma / ry; - double cyComma = c * -ry * x1Comma / rx; - - if (largeFlag == sweepFlag) { - cxComma = -cxComma; - cyComma = -cyComma; - } - - double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; - double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; - - double px1 = (x1Comma - cxComma) / rx; - double py1 = (y1Comma - cyComma) / ry; - double theta1 = calcAngle(1, 0, px1, py1); - - double px2 = (-x1Comma - cxComma) / rx; - double py2 = (-y1Comma - cyComma) / ry; - double deltaTheta = calcAngle(px1, py1, px2, py2); - if (sweepFlag) { - if (deltaTheta < 0) { - deltaTheta += 2 * Math.PI; - } - } else if (deltaTheta > 0) { - deltaTheta -= 2 * Math.PI; - } - - double rcp = Math.sqrt(4 - 2 * Math.sqrt(2)); - double delta = Math.signum(deltaTheta) * Math.PI / 4; - - int segmentCount = (int) Math.ceil(deltaTheta / delta); - double theta = theta1; - - PathCommand sera; - for (int i = 0; i < segmentCount - 1; i++) { - theta += delta; - /*sera = new PathCommand(); - sera.command = 'L'; - double x12 = Math.cos(theta) * rx; - double y12 = Math.sin(theta) * ry; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma, cy + y1Comma}; - pathCommands.add(sera);*/ - - sera = new PathCommand(); - sera.command = 'Q'; - double x12 = Math.cos(theta) * rx; - double y12 = Math.sin(theta) * ry; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - - double theta2 = theta - delta / 2; - x12 = Math.cos(theta2) * rx * rcp; - y12 = Math.sin(theta2) * ry * rcp; - double x1Comma2 = Math.cos(fi) * x12 - Math.sin(fi) * y12; - double y1Comma2 = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma2, cy + y1Comma2, cx + x1Comma, cy + y1Comma}; - pathCommands.add(sera); - } - - sera = new PathCommand(); - sera.command = 'Q'; - - theta += delta; - double diff = theta1 + deltaTheta - theta; - diff = -delta - diff; - theta = theta - delta - diff / 2; - - double rcpm = 1 + (rcp - 1) * (diff / delta) * (diff / delta); - double x12 = Math.cos(theta) * rx * rcpm; - double y12 = Math.sin(theta) * ry * rcpm; - x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; - y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; - sera.params = new double[]{cx + x1Comma, cy + y1Comma, x, y}; - pathCommands.add(sera); - /*sera = new PathCommand(); - sera.command = 'L'; - sera.params = new double[]{x, y}; - pathCommands.add(sera);*/ - } - break; - default: - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); - return; - } - - if (cmd != 'C' && cmd != 'S') { - prevCControlPoint = null; - } - - if (cmd != 'Q' && cmd != 'T') { - prevQControlPoint = null; - } - - x0 = x; - y0 = y; - } - } catch (NumberFormatException e) { - // ignore remaining data as specified in SVG Specification F.2 Error processing - } - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private double calcAngle(double ux, double uy, double vx, double vy) { - double lu = Math.sqrt(ux * ux + uy * uy); - double lv = Math.sqrt(ux * ux + uy * uy); - double sign = Math.signum(ux * vy - uy * vx); - if (sign == 0) { - sign = 1; - } - - return sign * Math.acos(ux * vx + uy * vy / (lu * lv)); - } - - private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("cx"); - double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("cy"); - double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("r"); - double r = attr.length() > 0 ? parseLength(attr, viewBox.width/* todo: how much is 100%? */) : 0; - - processEllipse(shapeNum, shapes, transform, style, cx, cy, r, r); - } - - private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("cx"); - double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("cy"); - double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("rx"); - double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("ry"); - double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - processEllipse(shapeNum, shapes, transform, style, cx, cy, rx, ry); - } - - private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, double cx, double cy, double rx, double ry) { - double sqrt2RXHalf = Math.sqrt(2) * rx / 2; - double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; - double sqrt2RYHalf = Math.sqrt(2) * ry / 2; - double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; - - List pathCommands = new ArrayList<>(); - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{cx + rx, cy}; - pathCommands.add(scr); - - double[] points = new double[]{ - rx, -sqrt2Minus1RY, - sqrt2RXHalf, -sqrt2RYHalf, - sqrt2Minus1RX, -ry, - 0, -ry, - -sqrt2Minus1RX, -ry, - -sqrt2RXHalf, -sqrt2RYHalf, - -rx, -sqrt2Minus1RY, - -rx, 0, - -rx, sqrt2Minus1RY, - -sqrt2RXHalf, sqrt2RYHalf, - -sqrt2Minus1RX, ry, - 0, ry, - sqrt2Minus1RX, ry, - sqrt2RXHalf, sqrt2RYHalf, - rx, sqrt2Minus1RY, - rx, 0}; - - for (int i = 0; i < points.length; i += 4) { - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - cer.params = new double[]{cx + points[i], cy + points[i + 1], cx + points[i + 2], cy + points[i + 3]}; - - /*double tetha = 30; - tetha *= Math.PI / 180; - double x1 = points[i]; - double y1 = points[i + 1]; - double x2 = points[i + 2]; - double y2 = points[i + 3]; - - double x1Comma = Math.cos(tetha) * x1 + Math.sin(tetha) * y1; - double y1Comma = -Math.sin(tetha) * x1 + Math.cos(tetha) * y1; - double x2Comma = Math.cos(tetha) * x2 + Math.sin(tetha) * y2; - double y2Comma = -Math.sin(tetha) * x2 + Math.cos(tetha) * y2; - - cer.params = new double[]{cx + x1Comma, cy + y1Comma, cx + x2Comma, cy + y2Comma};*/ - pathCommands.add(cer); - } - - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("x"); - double x = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y"); - double y = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("width"); - double width = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("height"); - double height = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("rx"); - double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("ry"); - double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; - - if (rx == 0 && ry != 0) { - rx = ry; - } else if (rx != 0 && ry == 0) { - ry = rx; - } - - if (rx > width / 2) { - rx = width / 2; - } - - if (ry > height / 2) { - ry = height / 2; - } - - List pathCommands = new ArrayList<>(); - - if (rx > 0 || ry > 0) { - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x + width, y + ry}; - pathCommands.add(scr); - - double sqrt2RXHalf = Math.sqrt(2) * rx / 2; - double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; - double sqrt2RYHalf = Math.sqrt(2) * ry / 2; - double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; - - double[] points = new double[]{ - x + width, y + ry - sqrt2Minus1RY, - x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, - x + width - rx + sqrt2Minus1RX, y, - x + width - rx, y, - x + rx, y, - x + rx - sqrt2Minus1RX, y, - x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, - x, y + ry - sqrt2Minus1RY, - x, y + ry, - x, y + height - ry, - x, y + height - ry + sqrt2Minus1RY, - x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, - x + rx - sqrt2Minus1RX, y + height, - x + rx, y + height, - x + width - rx, y + height, - x + width - rx + sqrt2Minus1RX, y + height, - x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, - x + width, y + height - ry + sqrt2Minus1RY, - x + width, y + height - ry, - x + width, y + ry}; - - for (int i = 0; i < points.length;) { - if (i % 10 == 8) { - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{points[i], points[i + 1]}; - pathCommands.add(cer); - i += 2; - } else { - PathCommand cer = new PathCommand(); - cer.command = 'Q'; - cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; - pathCommands.add(cer); - i += 4; - } - } - } else { - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x, y}; - pathCommands.add(scr); - - double[] points = new double[]{ - x + width, y, - x + width, y + height, - x, y + height, - x, y}; - - for (int i = 0; i < points.length; i += 2) { - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{points[i], points[i + 1]}; - - pathCommands.add(cer); - } - } - - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - String attr = childElement.getAttribute("x1"); - double x1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y1"); - double y1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - attr = childElement.getAttribute("x2"); - double x2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; - - attr = childElement.getAttribute("y2"); - double y2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; - - List pathCommands = new ArrayList<>(); - PathCommand scr = new PathCommand(); - scr.command = 'M'; - scr.params = new double[]{x1, y1}; - pathCommands.add(scr); - - PathCommand cer = new PathCommand(); - cer.command = 'L'; - cer.params = new double[]{x2, y2}; - - pathCommands.add(cer); - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - processPolyline(shapeNum, shapes, childElement, transform, style, false); - } - - private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { - processPolyline(shapeNum, shapes, childElement, transform, style, true); - } - - private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { - String data = childElement.getAttribute("points"); - - char command = 'M'; - double x0 = 0; - double y0 = 0; - - List pathCommands = new ArrayList<>(); - SvgPathReader pathReader = new SvgPathReader(data); - try { - while (pathReader.hasNext()) { - double x = x0; - double y = y0; - - Point p = null; - switch (command) { - case 'M': - PathCommand scr = new PathCommand(); - scr.command = 'M'; - - x = pathReader.readDouble(); - y = pathReader.readDouble(); - scr.params = new double[]{x, y}; - - pathCommands.add(scr); - break; - case 'L': - PathCommand serl = new PathCommand(); - serl.command = 'L'; - x = pathReader.readDouble(); - y = pathReader.readDouble(); - serl.params = new double[]{x, y}; - pathCommands.add(serl); - break; - } - - x0 = x; - y0 = y; - command = 'L'; - } - } catch (NumberFormatException e) { - // ignore remaining data as specified in SVG Specification F.2 Error processing - } - - if (close) { - PathCommand serz = new PathCommand(); - serz.command = 'Z'; - pathCommands.add(serz); - } - - processCommands(shapeNum, shapes, pathCommands, transform, style); - } - - //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file - private static void svgTest(String name) throws IOException, InterruptedException { - if (!new File(name + ".original.svg").exists()) { - URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg"); - byte[] svgData = Helper.readStream(svgUrl.openStream()); - Helper.writeFile(name + ".orig.svg", svgData); - - URL pngUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/png/full-" + name + ".png"); - byte[] pngData = Helper.readStream(pngUrl.openStream()); - Helper.writeFile(name + ".orig.png", pngData); - } - - String svgDataS = Helper.readTextFile(name + ".orig.svg"); - SWF swf = new SWF(); - DefineShape4Tag st = new DefineShape4Tag(swf); - st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS)); - swf.addTag(st); - SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR); - BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null); - List li = new ArrayList<>(); - li.add(st); - ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); - ExportAssetsTag eat = new ExportAssetsTag(swf); - eat.tags.add(st.getCharacterId()); - eat.names.add(name); - swf.addTag(eat); - swf.assignExportNamesToSymbols(); - st.shapeBounds.Xmax = (int) (si.getWidth() * SWF.unitDivisor); - st.shapeBounds.Ymax = (int) (si.getHeight() * SWF.unitDivisor); - new ShapeExporter().exportShapes(null, "./outex/", new ReadOnlyTagList(li), new ShapeExportSettings(ShapeExportMode.SVG, 1), null); - } - - //Test for SVG - public static void main(String[] args) throws IOException, InterruptedException { -// svgTest("animate-elem-02-t"); -// svgTest("animate-elem-03-t"); -// svgTest("animate-elem-04-t"); -// svgTest("animate-elem-05-t"); -// svgTest("animate-elem-06-t"); -// svgTest("animate-elem-07-t"); -// svgTest("animate-elem-08-t"); -// svgTest("animate-elem-09-t"); -// svgTest("animate-elem-10-t"); -// svgTest("animate-elem-11-t"); -// svgTest("animate-elem-12-t"); -// svgTest("animate-elem-13-t"); -// svgTest("animate-elem-14-t"); -// svgTest("animate-elem-15-t"); -// svgTest("animate-elem-17-t"); -// svgTest("animate-elem-19-t"); -// svgTest("animate-elem-20-t"); -// svgTest("animate-elem-21-t"); -// svgTest("animate-elem-22-b"); -// svgTest("animate-elem-23-t"); -// svgTest("animate-elem-24-t"); -// svgTest("animate-elem-25-t"); -// svgTest("animate-elem-26-t"); -// svgTest("animate-elem-27-t"); -// svgTest("animate-elem-28-t"); -// svgTest("animate-elem-29-b"); -// svgTest("animate-elem-30-t"); -// svgTest("animate-elem-31-t"); -// svgTest("animate-elem-32-t"); -// svgTest("animate-elem-33-t"); -// svgTest("animate-elem-34-t"); -// svgTest("animate-elem-36-t"); -// svgTest("animate-elem-37-t"); -// svgTest("animate-elem-39-t"); -// svgTest("animate-elem-40-t"); -// svgTest("animate-elem-41-t"); -// svgTest("animate-elem-44-t"); -// svgTest("animate-elem-46-t"); -// svgTest("animate-elem-52-t"); -// svgTest("animate-elem-60-t"); -// svgTest("animate-elem-61-t"); -// svgTest("animate-elem-62-t"); -// svgTest("animate-elem-63-t"); -// svgTest("animate-elem-64-t"); -// svgTest("animate-elem-65-t"); -// svgTest("animate-elem-66-t"); -// svgTest("animate-elem-67-t"); -// svgTest("animate-elem-68-t"); -// svgTest("animate-elem-69-t"); -// svgTest("animate-elem-70-t"); -// svgTest("animate-elem-77-t"); -// svgTest("animate-elem-78-t"); -// svgTest("animate-elem-80-t"); -// svgTest("animate-elem-81-t"); -// svgTest("animate-elem-82-t"); -// svgTest("animate-elem-83-t"); -// svgTest("animate-elem-84-t"); -// svgTest("animate-elem-85-t"); - svgTest("color-prof-01-f"); - svgTest("color-prop-01-b"); - svgTest("color-prop-02-f"); - svgTest("color-prop-03-t"); - svgTest("coords-coord-01-t"); - svgTest("coords-coord-02-t"); - svgTest("coords-trans-01-b"); - svgTest("coords-trans-02-t"); - svgTest("coords-trans-03-t"); - svgTest("coords-trans-04-t"); - svgTest("coords-trans-05-t"); - svgTest("coords-trans-06-t"); - svgTest("coords-units-01-b"); - svgTest("coords-units-02-b"); - svgTest("coords-units-03-b"); - svgTest("coords-viewattr-01-b"); - svgTest("coords-viewattr-02-b"); - svgTest("coords-viewattr-03-b"); - svgTest("extend-namespace-01-f"); -// svgTest("filters-blend-01-b"); -// svgTest("filters-color-01-b"); -// svgTest("filters-composite-02-b"); -// svgTest("filters-comptran-01-b"); -// svgTest("filters-conv-01-f"); -// svgTest("filters-diffuse-01-f"); -// svgTest("filters-displace-01-f"); -// svgTest("filters-example-01-b"); -// svgTest("filters-felem-01-b"); -// svgTest("filters-gauss-01-b"); -// svgTest("filters-image-01-b"); -// svgTest("filters-light-01-f"); -// svgTest("filters-morph-01-f"); -// svgTest("filters-offset-01-b"); -// svgTest("filters-specular-01-f"); -// svgTest("filters-tile-01-b"); -// svgTest("filters-turb-01-f"); -// svgTest("fonts-desc-02-t"); -// svgTest("fonts-elem-01-t"); -// svgTest("fonts-elem-02-t"); -// svgTest("fonts-elem-03-b"); -// svgTest("fonts-elem-04-b"); -// svgTest("fonts-elem-05-t"); -// svgTest("fonts-elem-06-t"); -// svgTest("fonts-elem-07-b"); -// svgTest("fonts-glyph-02-t"); -// svgTest("fonts-glyph-03-t"); -// svgTest("fonts-glyph-04-t"); -// svgTest("fonts-kern-01-t"); -// svgTest("interact-cursor-01-f"); -// svgTest("interact-dom-01-b"); -// svgTest("interact-events-01-b"); -// svgTest("interact-order-01-b"); -// svgTest("interact-order-02-b"); -// svgTest("interact-order-03-b"); -// svgTest("interact-zoom-01-t"); -// svgTest("linking-a-01-b"); -// svgTest("linking-a-02-b"); -// svgTest("linking-a-03-b"); -// svgTest("linking-a-04-t"); -// svgTest("linking-a-05-t"); -// svgTest("linking-a-07-t"); -// svgTest("linking-uri-01-b"); -// svgTest("linking-uri-02-b"); -// svgTest("linking-uri-03-t"); -// svgTest("masking-intro-01-f"); -// svgTest("masking-mask-01-b"); -// svgTest("masking-opacity-01-b"); -// svgTest("masking-path-01-b"); -// svgTest("masking-path-02-b"); -// svgTest("masking-path-03-b"); -// svgTest("masking-path-04-b"); -// svgTest("masking-path-05-f"); -// svgTest("metadata-example-01-b"); - svgTest("painting-fill-01-t"); - svgTest("painting-fill-02-t"); - svgTest("painting-fill-03-t"); - svgTest("painting-fill-04-t"); - svgTest("painting-fill-05-b"); - svgTest("painting-marker-01-f"); - svgTest("painting-marker-02-f"); - svgTest("painting-marker-03-f"); - svgTest("painting-render-01-b"); - svgTest("painting-stroke-01-t"); - svgTest("painting-stroke-02-t"); - svgTest("painting-stroke-03-t"); - svgTest("painting-stroke-04-t"); - svgTest("painting-stroke-07-t"); - svgTest("paths-data-01-t"); - svgTest("paths-data-02-t"); - svgTest("paths-data-03-f"); - svgTest("paths-data-04-t"); - svgTest("paths-data-05-t"); - svgTest("paths-data-06-t"); - svgTest("paths-data-07-t"); - svgTest("paths-data-08-t"); - svgTest("paths-data-09-t"); - svgTest("paths-data-10-t"); - svgTest("paths-data-12-t"); - svgTest("paths-data-13-t"); - svgTest("paths-data-14-t"); - svgTest("paths-data-15-t"); - svgTest("pservers-grad-01-b"); - svgTest("pservers-grad-02-b"); - svgTest("pservers-grad-03-b"); - svgTest("pservers-grad-04-b"); - svgTest("pservers-grad-05-b"); - svgTest("pservers-grad-06-b"); - svgTest("pservers-grad-07-b"); - svgTest("pservers-grad-08-b"); - svgTest("pservers-grad-09-b"); - svgTest("pservers-grad-10-b"); - svgTest("pservers-grad-11-b"); - svgTest("pservers-grad-12-b"); - svgTest("pservers-grad-13-b"); - svgTest("pservers-grad-14-b"); - svgTest("pservers-grad-15-b"); - svgTest("pservers-grad-16-b"); - svgTest("pservers-grad-17-b"); - svgTest("pservers-grad-18-b"); - svgTest("pservers-grad-19-b"); - svgTest("pservers-pattern-01-b"); - svgTest("render-elems-01-t"); - svgTest("render-elems-02-t"); - svgTest("render-elems-03-t"); - svgTest("render-elems-06-t"); - svgTest("render-elems-07-t"); - svgTest("render-elems-08-t"); - svgTest("render-groups-01-b"); - svgTest("render-groups-03-t"); -// svgTest("script-handle-01-b"); -// svgTest("script-handle-02-b"); -// svgTest("script-handle-03-b"); -// svgTest("script-handle-04-b"); - svgTest("shapes-circle-01-t"); - svgTest("shapes-circle-02-t"); - svgTest("shapes-ellipse-01-t"); - svgTest("shapes-ellipse-02-t"); - svgTest("shapes-intro-01-t"); - svgTest("shapes-line-01-t"); - svgTest("shapes-polygon-01-t"); - svgTest("shapes-polyline-01-t"); - svgTest("shapes-rect-01-t"); - svgTest("shapes-rect-02-t"); -// svgTest("struct-cond-01-t"); -// svgTest("struct-cond-02-t"); -// svgTest("struct-cond-03-t"); -// svgTest("struct-defs-01-t"); -// svgTest("struct-dom-01-b"); -// svgTest("struct-dom-02-b"); -// svgTest("struct-dom-03-b"); -// svgTest("struct-dom-04-b"); -// svgTest("struct-dom-05-b"); -// svgTest("struct-dom-06-b"); -// svgTest("struct-frag-01-t"); -// svgTest("struct-frag-02-t"); -// svgTest("struct-frag-03-t"); -// svgTest("struct-frag-04-t"); -// svgTest("struct-frag-05-t"); -// svgTest("struct-frag-06-t"); -// svgTest("struct-group-01-t"); -// svgTest("struct-group-02-b"); -// svgTest("struct-group-03-t"); -// svgTest("struct-image-01-t"); -// svgTest("struct-image-02-b"); -// svgTest("struct-image-03-t"); -// svgTest("struct-image-04-t"); -// svgTest("struct-image-05-b"); -// svgTest("struct-image-06-t"); -// svgTest("struct-image-07-t"); -// svgTest("struct-image-08-t"); -// svgTest("struct-image-09-t"); -// svgTest("struct-image-10-t"); -// svgTest("struct-symbol-01-b"); -// svgTest("struct-use-01-t"); -// svgTest("struct-use-03-t"); -// svgTest("struct-use-05-b"); -// svgTest("styling-css-01-b"); -// svgTest("styling-css-02-b"); -// svgTest("styling-css-03-b"); -// svgTest("styling-css-04-f"); -// svgTest("styling-css-05-b"); -// svgTest("styling-css-06-b"); -// svgTest("styling-inherit-01-b"); -// svgTest("styling-pres-01-t"); -// svgTest("text-align-01-b"); -// svgTest("text-align-02-b"); -// svgTest("text-align-03-b"); -// svgTest("text-align-04-b"); -// svgTest("text-align-05-b"); -// svgTest("text-align-06-b"); -// svgTest("text-align-08-b"); -// svgTest("text-altglyph-01-b"); -// svgTest("text-deco-01-b"); -// svgTest("text-fonts-01-t"); -// svgTest("text-fonts-02-t"); -// svgTest("text-fonts-03-t"); -// svgTest("text-intro-01-t"); -// svgTest("text-intro-02-b"); -// svgTest("text-intro-03-b"); -// svgTest("text-intro-04-t"); -// svgTest("text-intro-05-t"); -// svgTest("text-path-01-b"); -// svgTest("text-spacing-01-b"); -// svgTest("text-text-01-b"); -// svgTest("text-text-03-b"); -// svgTest("text-text-04-t"); -// svgTest("text-text-05-t"); -// svgTest("text-text-06-t"); -// svgTest("text-text-07-t"); -// svgTest("text-text-08-b"); -// svgTest("text-tref-01-b"); -// svgTest("text-tselect-01-b"); -// svgTest("text-tselect-02-f"); -// svgTest("text-tspan-01-b"); -// svgTest("text-ws-01-t"); -// svgTest("text-ws-02-t"); -// svgTest("types-basicDOM-01-b"); - } - - private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { - if (fill == null || fillStyle == null) { - return; - } - if (fill instanceof SvgGradient) { - SvgGradient gfill = (SvgGradient) fill; - Matrix gradientMatrix = Matrix.parseSvgMatrix(gfill.gradientTransform, SWF.unitDivisor, 1); - gradientMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(gradientMatrix); - fillStyle.gradientMatrix = gradientMatrix.toMATRIX(); - if (fill instanceof SvgLinearGradient) { - SvgLinearGradient lgfill = (SvgLinearGradient) fill; - fillStyle.fillStyleType = FILLSTYLE.LINEAR_GRADIENT; - fillStyle.gradient = new GRADIENT(); - double x1 = parseCoordinate(lgfill.x1, 1/* todo: how much is 100%? */); - double y1 = parseCoordinate(lgfill.y1, 1/* todo: how much is 100%? */); - double x2 = parseCoordinate(lgfill.x2, 1/* todo: how much is 100%? */); - double y2 = parseCoordinate(lgfill.y2, 1/* todo: how much is 100%? */); - - x1 = x1 * SWF.unitDivisor; - y1 = y1 * SWF.unitDivisor; - x2 = x2 * SWF.unitDivisor; - y2 = y2 * SWF.unitDivisor; - - Matrix boundingBoxMatrix = new Matrix(); - if (lgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { - boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; - boundingBoxMatrix.rotateSkew0 = 0; - boundingBoxMatrix.rotateSkew1 = 0; - boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; - boundingBoxMatrix.translateX = bounds.Xmin; - boundingBoxMatrix.translateY = bounds.Ymin; - } - - Matrix xyMatrix = new Matrix(); - xyMatrix.scaleX = x2 - x1; - xyMatrix.rotateSkew0 = y2 - y1; - xyMatrix.rotateSkew1 = -xyMatrix.rotateSkew0; - xyMatrix.scaleY = xyMatrix.scaleX; - - xyMatrix = xyMatrix.preConcatenate(boundingBoxMatrix); - - Matrix zeroStartMatrix = Matrix.getTranslateInstance(0.5, 0); - - Matrix scaleMatrix = Matrix.getScaleInstance(1 / 16384.0 / 2); - Matrix transMatrix = Matrix.getTranslateInstance(x1, y1); - - Matrix tMatrix = new Matrix(); - tMatrix = tMatrix.preConcatenate(scaleMatrix); - tMatrix = tMatrix.preConcatenate(zeroStartMatrix); - tMatrix = tMatrix.preConcatenate(xyMatrix); - - tMatrix = tMatrix.preConcatenate(transMatrix); - Point p1 = tMatrix.transform(new Point(-16384, 0)); - Point p2 = tMatrix.transform(new Point(16384, 0)); - - tMatrix = tMatrix.preConcatenate(new Matrix(fillStyle.gradientMatrix)); - fillStyle.gradientMatrix = tMatrix.toMATRIX(); - } else if (fill instanceof SvgRadialGradient) { - SvgRadialGradient rgfill = (SvgRadialGradient) fill; - double cx = parseCoordinate(rgfill.cx, 1/* todo: how much is 100%? */); - double cy = parseCoordinate(rgfill.cy, 1/* todo: how much is 100%? */); - double r = parseLength(rgfill.r, 1/* todo: how much is 100%? */); - - Matrix boundingBoxMatrix = new Matrix(); - if (rgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { - boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; - boundingBoxMatrix.rotateSkew0 = 0; - boundingBoxMatrix.rotateSkew1 = 0; - boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; - boundingBoxMatrix.translateX = bounds.Xmin; - boundingBoxMatrix.translateY = bounds.Ymin; - } - - fillStyle.gradientMatrix = Matrix.getTranslateInstance(SWF.unitDivisor * cx, SWF.unitDivisor * cy).concatenate(new Matrix(fillStyle.gradientMatrix)).concatenate(Matrix.getScaleInstance(r / 819.2)).preConcatenate(boundingBoxMatrix).toMATRIX(); - - double fx = parseCoordinate(rgfill.fx, 1/* todo: how much is 100%? */); - double fy = parseCoordinate(rgfill.fy, 1/* todo: how much is 100%? */); - if (!rgfill.fx.equals(rgfill.cx) || !rgfill.fy.equals(rgfill.cy)) { - fillStyle.fillStyleType = FILLSTYLE.FOCAL_RADIAL_GRADIENT; - fillStyle.gradient = new FOCALGRADIENT(); - FOCALGRADIENT fg = (FOCALGRADIENT) fillStyle.gradient; - double f = Math.sqrt((fx - cx) * (fx - cx) + (fy - cy) * (fy - cy)) / 819.2; - fg.focalPoint = (float) f; - } else { - fillStyle.fillStyleType = FILLSTYLE.RADIAL_GRADIENT; - fillStyle.gradient = new GRADIENT(); - } - } - switch (gfill.spreadMethod) { - case PAD: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_PAD_MODE; - break; - case REFLECT: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REFLECT_MODE; - break; - case REPEAT: - fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REPEAT_MODE; - break; - } - switch (gfill.interpolation) { - case LINEAR_RGB: - fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_LINEAR_RGB_MODE; - break; - case SRGB: - fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_RGB_MODE; - break; - } - - fillStyle.gradient.gradientRecords = new GRADRECORD[gfill.stops.size()]; - int prevRatio = -1; - for (int i = 0; i < gfill.stops.size(); i++) { - SvgStop stop = gfill.stops.get(i); - Color color = stop.color; - color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) Math.round(color.getAlpha() * style.getOpacity())); - fillStyle.gradient.gradientRecords[i] = new GRADRECORD(); - fillStyle.gradient.gradientRecords[i].inShape3 = shapeNum >= 3; - fillStyle.gradient.gradientRecords[i].color = getRGB(shapeNum, color); - int ratio = Math.max((int) Math.round(stop.offset * 255), prevRatio + 1); - fillStyle.gradient.gradientRecords[i].ratio = ratio; - prevRatio = ratio; - if (prevRatio == 255) { - break; - } - } - } else if (fill instanceof SvgBitmapFill) { - SvgBitmapFill bfill = (SvgBitmapFill) fill; - fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; - fillStyle.bitmapId = bfill.characterId; - Matrix fillMatrix = Matrix.parseSvgMatrix(bfill.patternTransform, SWF.unitDivisor, SWF.unitDivisor); - fillMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(fillMatrix); - fillStyle.bitmapMatrix = fillMatrix.toMATRIX(); - } - } - - private void applyStyleGradients(RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { - SvgFill fill = style.getFillWithOpacity(); - if (fill != null && fill != SvgTransparentFill.INSTANCE) { - applyFillGradients(fill, scr.fillStyles.fillStyles[0], bounds, scr, transform, shapeNum, style); - } - SvgFill strokeFill = style.getStrokeFillWithOpacity(); - if (strokeFill != null) { - if (scr.lineStyles.lineStyles.length > 0 && scr.lineStyles.lineStyles[0] instanceof LINESTYLE2) { - applyFillGradients(strokeFill, ((LINESTYLE2) scr.lineStyles.lineStyles[0]).fillType, bounds, scr, transform, shapeNum, style); - } - } - } - - private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { - StyleChangeRecord scr = new StyleChangeRecord(); - - scr.stateNewStyles = true; - scr.fillStyles = new FILLSTYLEARRAY(); - scr.stateFillStyle1 = true; - scr.stateLineStyle = true; - SvgFill fill = style.getFillWithOpacity(); - if (fill != null && fill != SvgTransparentFill.INSTANCE) { - scr.fillStyles.fillStyles = new FILLSTYLE[1]; - scr.fillStyles.fillStyles[0] = new FILLSTYLE(); - if (fill instanceof SvgColor) { - Color colorFill = fill.toColor(); - scr.fillStyles.fillStyles[0].color = getRGB(shapeNum, colorFill); - scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; - } else if (fill instanceof SvgGradient) { - //...apply in second step - applyStyleGradients - } - - scr.fillStyle1 = 1; - } else { - scr.fillStyles.fillStyles = new FILLSTYLE[0]; - scr.fillStyle1 = 0; - } - - scr.lineStyles = new LINESTYLEARRAY(); - SvgFill strokeFill = style.getStrokeFillWithOpacity(); - if (strokeFill != null && strokeFill != SvgTransparentFill.INSTANCE) { - Color lineColor = strokeFill.toColor(); - - scr.lineStyles.lineStyles = new LINESTYLE[1]; - LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); - lineStyle.color = getRGB(shapeNum, lineColor); - lineStyle.width = (int) Math.round(style.getStrokeWidth() * SWF.unitDivisor); - SvgLineCap lineCap = style.getStrokeLineCap(); - SvgLineJoin lineJoin = style.getStrokeLineJoin(); - if (lineStyle instanceof LINESTYLE2) { - LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; - int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP - : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP - : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; - lineStyle2.startCapStyle = swfCap; - lineStyle2.endCapStyle = swfCap; - if (!(strokeFill instanceof SvgColor)) { - lineStyle2.hasFillFlag = true; - lineStyle2.fillType = new FILLSTYLE(); - //...apply in second step - applyStyleGradients - } // Single color does not need fillType attribute - - int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN - : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN - : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; - lineStyle2.joinStyle = swfJoin; - lineStyle2.miterLimitFactor = (float) style.getStrokeMiterLimit(); - } else { - if (lineCap != SvgLineCap.ROUND) { - showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); - } - if (lineJoin != SvgLineJoin.ROUND) { - showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); - } - } - - scr.lineStyles.lineStyles[0] = lineStyle; - scr.lineStyle = 1; - } else { - scr.lineStyles.lineStyles = new LINESTYLE[0]; - scr.lineStyle = 0; - } - - return scr; - } - - private RGB getRGB(int shapeNum, Color color) { - if (shapeNum < 3 && color.getAlpha() != 0xff) { - showWarning("transparentColorNotSupported", "Transparent color is not supported in shape " + shapeNum); - } - - return shapeNum >= 3 ? new RGBA(color) : new RGB(color); - } - - private double parseCoordinate(String value, double relativeTo) { - return parseLength(value, relativeTo); - } - - private double parseLength(String value, double relativeTo) { - if (value == null) { - throw new NumberFormatException(); - } - - value = value.toLowerCase(); - String unit = null; - if (value.endsWith("em") - || value.endsWith("ex") - || value.endsWith("px") - || value.endsWith("in") - || value.endsWith("cm") - || value.endsWith("mm") - || value.endsWith("pt") - || value.endsWith("pc")) { - unit = value.substring(value.length() - 2); - value = value.substring(0, value.length() - 2); - } else if (value.endsWith("%")) { - unit = "%"; - value = value.substring(0, value.length() - 1); - } - - double result = Double.parseDouble(value); - if (unit != null) { - switch (unit) { - case "em": - case "ex": - // todo: font things - break; - case "in": - result *= getDpi(); - break; - case "pt": - result *= getDpi() / 72; - break; - case "pc": - result *= getDpi() / 6; - break; - case "cm": - result *= getDpi() / 2.54; - break; - case "mm": - result *= getDpi() / 25.4; - break; - case "%": - result = relativeTo * result / 100; - break; - } - } - - return result; - } - - public double parseNumber(String value) { - if (value == null) { - throw new NumberFormatException(); - } - - double result = Double.parseDouble(value); - return result; - } - - public double parseNumberOrPercent(String value) { - if (value == null) { - throw new NumberFormatException(); - } - - boolean percent = value.endsWith("%"); - if (percent) { - value = value.substring(0, value.length() - 1); - } - - double result = Double.parseDouble(value); - if (percent) { - result /= 100; - } - - return result; - } - - private double getDpi() { - return 96; - - } - - class PathCommand { - - public char command; - - public double[] params; - } - - void showWarning(String name, String text) { - if (!shownWarnings.contains(name)) { - Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); - shownWarnings.add(name); - } - } -} +/* + * 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.importers.svg; + +import com.jpexs.decompiler.flash.ReadOnlyTagList; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.ShapeExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; +import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode; +import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings; +import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter; +import com.jpexs.decompiler.flash.importers.ShapeImporter; +import com.jpexs.decompiler.flash.tags.DefineShape4Tag; +import com.jpexs.decompiler.flash.tags.ExportAssetsTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.types.FILLSTYLE; +import com.jpexs.decompiler.flash.types.FILLSTYLEARRAY; +import com.jpexs.decompiler.flash.types.FOCALGRADIENT; +import com.jpexs.decompiler.flash.types.GRADIENT; +import com.jpexs.decompiler.flash.types.GRADRECORD; +import com.jpexs.decompiler.flash.types.LINESTYLE; +import com.jpexs.decompiler.flash.types.LINESTYLE2; +import com.jpexs.decompiler.flash.types.LINESTYLEARRAY; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.RGB; +import com.jpexs.decompiler.flash.types.RGBA; +import com.jpexs.decompiler.flash.types.SHAPEWITHSTYLE; +import com.jpexs.decompiler.flash.types.shaperecords.CurvedEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.EndShapeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.SHAPERECORD; +import com.jpexs.decompiler.flash.types.shaperecords.StraightEdgeRecord; +import com.jpexs.decompiler.flash.types.shaperecords.StyleChangeRecord; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.Color; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.StringReader; +import java.net.URL; +import java.util.ArrayList; +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.imageio.ImageIO; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** + * + * @author JPEXS + */ +public class SvgImporter { + + private final Set shownWarnings = new HashSet<>(); + + ShapeTag shapeTag; + + private Rectangle2D.Double viewBox; + + public Tag importSvg(ShapeTag st, String svgXml) { + return importSvg(st, svgXml, true); + } + + public Tag importSvg(ShapeTag st, String svgXml, boolean fill) { + shapeTag = st; + + SHAPEWITHSTYLE shapes = new SHAPEWITHSTYLE(); + shapes.fillStyles = new FILLSTYLEARRAY(); + shapes.lineStyles = new LINESTYLEARRAY(); + shapes.fillStyles.fillStyles = new FILLSTYLE[0]; + shapes.lineStyles.lineStyles = new LINESTYLE[0]; + + int shapeNum = st.getShapeNum(); + shapes.shapeRecords = new ArrayList<>(); + + Rectangle2D.Double viewBox = null; + try { + DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + /*docFactory.setValidating(false); + docFactory.setNamespaceAware(true); + docFactory.setFeature("http://xml.org/sax/features/namespaces", false); + docFactory.setFeature("http://xml.org/sax/features/validation", false); + docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false);*/ + docFactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + + Document doc = docBuilder.parse(new InputSource(new StringReader(svgXml))); + Element rootElement = doc.getDocumentElement(); + + Map idMap = new HashMap<>(); + populateIds(rootElement, idMap); + + if (!"svg".equals(rootElement.getTagName())) { + throw new IOException("SVG root element should be 'svg'"); + } + + double width = 800; + double height = 600; + + if (rootElement.hasAttribute("viewBox")) { + String params = rootElement.getAttribute("viewBox"); + String[] args = Matrix.parseSvgNumberList(params); + viewBox = new Rectangle2D.Double(); + if (args.length > 0) { + viewBox.x = parseNumber(args[0]); + } + if (args.length > 1) { + viewBox.y = parseNumber(args[1]); + } + if (args.length > 2) { + viewBox.width = parseNumber(args[2]); + } + if (args.length > 3) { + viewBox.height = parseNumber(args[3]); + } + + width = viewBox.width; + height = viewBox.height; + } + + if (rootElement.hasAttribute("width")) { + width = parseLength(rootElement.getAttribute("width"), width); + } + + if (rootElement.hasAttribute("height")) { + height = parseLength(rootElement.getAttribute("height"), height); + } + + if (viewBox == null) { + viewBox = new Rectangle2D.Double(); + viewBox.width = width; + viewBox.height = height; + } + + this.viewBox = viewBox; + + SvgStyle style = new SvgStyle(this, idMap, rootElement); + Matrix transform = new Matrix(); + processSvgObject(idMap, shapeNum, shapes, rootElement, transform, style); + } catch (SAXException | IOException | ParserConfigurationException ex) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.SEVERE, null, ex); + } + + shapes.shapeRecords.add(new EndShapeRecord()); + + RECT rect = st.getRect(); + int origXmin = rect.Xmin; + int origYmin = rect.Ymin; + rect.Xmin -= origXmin; + rect.Xmax -= origXmin; + rect.Ymin -= origYmin; + rect.Ymax -= origYmin; + + if (!fill && viewBox != null) { + rect.Xmin = (int) Math.round(viewBox.x * SWF.unitDivisor); + rect.Ymin = (int) Math.round(viewBox.y * SWF.unitDivisor); + rect.Xmax = (int) Math.round((viewBox.x + viewBox.width) * SWF.unitDivisor); + rect.Ymax = (int) Math.round((viewBox.y + viewBox.height) * SWF.unitDivisor); + } + + st.shapes = shapes; + st.setModified(true); + + return (Tag) st; + } + + // Generate id-element map, because getElementById does not work in some cases (namespaces?) + protected void populateIds(Element el, Map out) { + if (el.hasAttribute("id")) { + out.put(el.getAttribute("id"), el); + } + NodeList nodes = el.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + if (nodes.item(i) instanceof Element) { + populateIds((Element) nodes.item(i), out); + } + } + } + + private void processSvgObject(Map idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) { + for (int i = 0; i < element.getChildNodes().getLength(); i++) { + Node childNode = element.getChildNodes().item(i); + if (childNode instanceof Element) { + Element childElement = (Element) childNode; + String tagName = childElement.getTagName(); + SvgStyle newStyle = new SvgStyle(this, idMap, childElement); + Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1); + Matrix m2 = m == null ? transform : transform.concatenate(m); + if ("g".equals(tagName)) { + processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle); + } else if ("path".equals(tagName)) { + processPath(shapeNum, shapes, childElement, m2, newStyle); + } else if ("circle".equals(tagName)) { + processCircle(shapeNum, shapes, childElement, m2, newStyle); + } else if ("ellipse".equals(tagName)) { + processEllipse(shapeNum, shapes, childElement, m2, newStyle); + } else if ("rect".equals(tagName)) { + processRect(shapeNum, shapes, childElement, m2, newStyle); + } else if ("line".equals(tagName)) { + processLine(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polyline".equals(tagName)) { + processPolyline(shapeNum, shapes, childElement, m2, newStyle); + } else if ("polygon".equals(tagName)) { + processPolygon(shapeNum, shapes, childElement, m2, newStyle); + } else if ("defs".equals(tagName) || "title".equals(tagName) || "desc".equals(tagName) + || "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) { + // ignore + } else { + showWarning(tagName + "tagNotSupported", "The SVG tag '" + tagName + "' is not supported."); + } + } + } + } + + private void processCommands(int shapeNum, SHAPEWITHSTYLE shapes, List commands, Matrix transform, SvgStyle style) { + Matrix transform2 = transform.preConcatenate(Matrix.getScaleInstance(SWF.unitDivisor)); + Point prevPoint = new Point(0, 0); + Point startPoint = prevPoint; + double x0 = 0; + double y0 = 0; + + StyleChangeRecord scrStyle = getStyleChangeRecord(shapeNum, style); + int fillStyle = scrStyle.fillStyle1; + int lineStyle = scrStyle.lineStyle; + scrStyle.stateFillStyle0 = true; + scrStyle.stateFillStyle1 = true; + scrStyle.stateLineStyle = true; + scrStyle.fillStyle0 = 0; + scrStyle.fillStyle1 = 0; + scrStyle.lineStyle = 0; + + List newRecords = new ArrayList<>(); + + newRecords.add(scrStyle); + + LINESTYLE lineStyleObj = scrStyle.lineStyles.lineStyles.length < 1 ? null : scrStyle.lineStyles.lineStyles[0]; + LINESTYLE2 lineStyle2Obj = null; + if (lineStyleObj instanceof LINESTYLE2) { + lineStyle2Obj = (LINESTYLE2) lineStyleObj; + lineStyle2Obj.noClose = true; + } + + for (PathCommand command : commands) { + double x = x0; + double y = y0; + Point p; + char cmd = Character.toUpperCase(command.command); + switch (cmd) { + case 'M': + StyleChangeRecord scr = new StyleChangeRecord(); + if (fillStyle != 0) { + scr.stateFillStyle1 = true; + scr.fillStyle1 = fillStyle; + } + if (lineStyle != 0) { + scr.lineStyle = lineStyle; + scr.stateLineStyle = true; + } + + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + scr.moveDeltaX = (int) Math.round(p.x); + scr.moveDeltaY = (int) Math.round(p.y); + prevPoint = p; + scr.stateMoveTo = true; + + newRecords.add(scr); + startPoint = p; + break; + case 'Z': + StraightEdgeRecord serz = new StraightEdgeRecord(); + p = startPoint; + serz.deltaX = (int) Math.round(p.x - prevPoint.x); + serz.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serz.generalLineFlag = true; + newRecords.add(serz); + if (lineStyle2Obj != null) { + lineStyle2Obj.noClose = false; + } + break; + case 'L': + StraightEdgeRecord serl = new StraightEdgeRecord(); + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + serl.deltaX = (int) Math.round(p.x - prevPoint.x); + serl.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serl.generalLineFlag = true; + serl.simplify(); + newRecords.add(serl); + break; + case 'H': + StraightEdgeRecord serh = new StraightEdgeRecord(); + x = command.params[0]; + + p = transform2.transform(x, y); + serh.deltaX = (int) Math.round(p.x - prevPoint.x); + prevPoint = p; + newRecords.add(serh); + break; + case 'V': + StraightEdgeRecord serv = new StraightEdgeRecord(); + y = command.params[0]; + + p = transform2.transform(x, y); + serv.deltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + serv.vertLineFlag = true; + newRecords.add(serv); + break; + case 'Q': + CurvedEdgeRecord cer = new CurvedEdgeRecord(); + x = command.params[0]; + y = command.params[1]; + + p = transform2.transform(x, y); + cer.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + x = command.params[2]; + y = command.params[3]; + + p = transform2.transform(x, y); + cer.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cer.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + newRecords.add(cer); + break; + case 'C': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pStart = prevPoint; + Point pControl1; + + x = command.params[0]; + y = command.params[1]; + + pControl1 = transform2.transform(x, y); + + x = command.params[2]; + y = command.params[3]; + + Point pControl2 = transform2.transform(x, y); + + x = command.params[4]; + y = command.params[5]; + + p = transform2.transform(x, y); + + //StraightEdgeRecord serc = new StraightEdgeRecord(); + //serc.generalLineFlag = true; + //serc.deltaX = (int) Math.round(p.x - prevPoint.x); + //serc.deltaY = (int) Math.round(p.y - prevPoint.y); + //newRecords.add(serc); + List quadCoordinates = new CubicToQuad().cubicToQuad(pStart.x, pStart.y, pControl1.x, pControl1.y, pControl2.x, pControl2.y, p.x, p.y, 1); + for (int i = 2; i < quadCoordinates.size();) { + CurvedEdgeRecord cerc = new CurvedEdgeRecord(); + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.controlDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.controlDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + + p = new Point(quadCoordinates.get(i++), quadCoordinates.get(i++)); + cerc.anchorDeltaX = (int) Math.round(p.x - prevPoint.x); + cerc.anchorDeltaY = (int) Math.round(p.y - prevPoint.y); + prevPoint = p; + newRecords.add(cerc); + } + + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + x0 = x; + y0 = y; + } + applyStyleGradients(SHAPERECORD.getBounds(newRecords), scrStyle, transform2, shapeNum, style); + shapes.shapeRecords.addAll(newRecords); + } + + private void processPath(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String data = childElement.getAttribute("d"); + + char command = 0; + Point startPoint = new Point(0, 0); + Point prevCControlPoint = null; + Point prevQControlPoint = null; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + try { + while (pathReader.hasNext()) { + char newCommand; + if ((newCommand = pathReader.readCommand()) != 0) { + command = newCommand; + } + + boolean isRelative = Character.isLowerCase(command); + + double x = x0; + double y = y0; + + char cmd = Character.toUpperCase(command); + switch (cmd) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + startPoint = new Point(x, y); + + command = isRelative ? 'l' : 'L'; + break; + case 'Z': + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + x = startPoint.x; + y = startPoint.y; + pathCommands.add(serz); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + case 'H': + PathCommand serh = new PathCommand(); + serh.command = 'H'; + x = pathReader.readDouble(); + if (isRelative) { + x += x0; + } + + serh.params = new double[]{x}; + pathCommands.add(serh); + break; + case 'V': + PathCommand serv = new PathCommand(); + serv.command = 'V'; + y = pathReader.readDouble(); + if (isRelative) { + y += y0; + } + + serv.params = new double[]{y}; + pathCommands.add(serv); + break; + case 'Q': + case 'T': + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + + Point pControl; + if (cmd == 'Q') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl = new Point(x, y); + } else if (prevQControlPoint != null) { + pControl = new Point(2 * x0 - prevQControlPoint.x, 2 * y0 - prevQControlPoint.y); + } else { + pControl = new Point(x0, y0); + } + + prevQControlPoint = pControl; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + cer.params = new double[]{pControl.x, pControl.y, x, y}; + pathCommands.add(cer); + break; + case 'C': + case 'S': + showWarning("cubicCurvesNotSupported", "Cubic curves are not supported by Flash."); + + // create at least something... + Point pControl1; + if (cmd == 'C') { + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + pControl1 = new Point(x, y); + } else if (prevCControlPoint != null) { + pControl1 = new Point(2 * x0 - prevCControlPoint.x, 2 * y0 - prevCControlPoint.y); + } else { + pControl1 = new Point(x0, y0); + } + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + Point pControl2 = new Point(x, y); + prevCControlPoint = pControl2; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + PathCommand cerc = new PathCommand(); + cerc.command = 'C'; + cerc.params = new double[]{pControl1.x, pControl1.y, pControl2.x, pControl2.y, x, y}; + pathCommands.add(cerc); + break; + case 'A': + double rx = pathReader.readDouble(); + double ry = pathReader.readDouble(); + double fi = pathReader.readDouble() * Math.PI / 180; + boolean largeFlag = (int) pathReader.readDouble() != 0; + boolean sweepFlag = (int) pathReader.readDouble() != 0; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + if (isRelative) { + x += x0; + y += y0; + } + + if (rx == 0 || ry == 0) { + // straight line to (x, y) + PathCommand sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera); + } else { + rx = Math.abs(rx); + ry = Math.abs(ry); + + double x1 = x0; + double y1 = y0; + double x2 = x; + double y2 = y; + + double d1 = (x1 - x2) / 2; + double d2 = (y1 - y2) / 2; + double x1Comma = Math.cos(fi) * d1 + Math.sin(fi) * d2; + double y1Comma = -Math.sin(fi) * d1 + Math.cos(fi) * d2; + + // Correction of out-of-range radii + double lambda = x1Comma * x1Comma / (rx * rx) + y1Comma * y1Comma / (ry * ry); + if (lambda > 1) { + double sqrtLambda = Math.sqrt(lambda); + rx = sqrtLambda * rx; + ry = sqrtLambda * ry; + } + + double c = Math.sqrt((rx * rx * ry * ry - rx * rx * y1Comma * y1Comma - ry * ry * x1Comma * x1Comma) / (rx * rx * y1Comma * y1Comma + ry * ry * x1Comma * x1Comma)); + double cxComma = c * rx * y1Comma / ry; + double cyComma = c * -ry * x1Comma / rx; + + if (largeFlag == sweepFlag) { + cxComma = -cxComma; + cyComma = -cyComma; + } + + double cx = Math.cos(fi) * cxComma - Math.sin(fi) * cyComma + (x1 + x2) / 2; + double cy = Math.sin(fi) * cxComma + Math.cos(fi) * cyComma + (y1 + y2) / 2; + + double px1 = (x1Comma - cxComma) / rx; + double py1 = (y1Comma - cyComma) / ry; + double theta1 = calcAngle(1, 0, px1, py1); + + double px2 = (-x1Comma - cxComma) / rx; + double py2 = (-y1Comma - cyComma) / ry; + double deltaTheta = calcAngle(px1, py1, px2, py2); + if (sweepFlag) { + if (deltaTheta < 0) { + deltaTheta += 2 * Math.PI; + } + } else if (deltaTheta > 0) { + deltaTheta -= 2 * Math.PI; + } + + double rcp = Math.sqrt(4 - 2 * Math.sqrt(2)); + double delta = Math.signum(deltaTheta) * Math.PI / 4; + + int segmentCount = (int) Math.ceil(deltaTheta / delta); + double theta = theta1; + + PathCommand sera; + for (int i = 0; i < segmentCount - 1; i++) { + theta += delta; + /*sera = new PathCommand(); + sera.command = 'L'; + double x12 = Math.cos(theta) * rx; + double y12 = Math.sin(theta) * ry; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma, cy + y1Comma}; + pathCommands.add(sera);*/ + + sera = new PathCommand(); + sera.command = 'Q'; + double x12 = Math.cos(theta) * rx; + double y12 = Math.sin(theta) * ry; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + + double theta2 = theta - delta / 2; + x12 = Math.cos(theta2) * rx * rcp; + y12 = Math.sin(theta2) * ry * rcp; + double x1Comma2 = Math.cos(fi) * x12 - Math.sin(fi) * y12; + double y1Comma2 = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma2, cy + y1Comma2, cx + x1Comma, cy + y1Comma}; + pathCommands.add(sera); + } + + sera = new PathCommand(); + sera.command = 'Q'; + + theta += delta; + double diff = theta1 + deltaTheta - theta; + diff = -delta - diff; + theta = theta - delta - diff / 2; + + double rcpm = 1 + (rcp - 1) * (diff / delta) * (diff / delta); + double x12 = Math.cos(theta) * rx * rcpm; + double y12 = Math.sin(theta) * ry * rcpm; + x1Comma = Math.cos(fi) * x12 - Math.sin(fi) * y12; + y1Comma = Math.sin(fi) * x12 + Math.cos(fi) * y12; + sera.params = new double[]{cx + x1Comma, cy + y1Comma, x, y}; + pathCommands.add(sera); + /*sera = new PathCommand(); + sera.command = 'L'; + sera.params = new double[]{x, y}; + pathCommands.add(sera);*/ + } + break; + default: + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, "Unknown command: {0}", command); + return; + } + + if (cmd != 'C' && cmd != 'S') { + prevCControlPoint = null; + } + + if (cmd != 'Q' && cmd != 'T') { + prevQControlPoint = null; + } + + x0 = x; + y0 = y; + } + } catch (NumberFormatException e) { + // ignore remaining data as specified in SVG Specification F.2 Error processing + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private double calcAngle(double ux, double uy, double vx, double vy) { + double lu = Math.sqrt(ux * ux + uy * uy); + double lv = Math.sqrt(ux * ux + uy * uy); + double sign = Math.signum(ux * vy - uy * vx); + if (sign == 0) { + sign = 1; + } + + return sign * Math.acos(ux * vx + uy * vy / (lu * lv)); + } + + private void processCircle(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("r"); + double r = attr.length() > 0 ? parseLength(attr, viewBox.width/* todo: how much is 100%? */) : 0; + + processEllipse(shapeNum, shapes, transform, style, cx, cy, r, r); + } + + private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("cx"); + double cx = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("cy"); + double cy = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + processEllipse(shapeNum, shapes, transform, style, cx, cy, rx, ry); + } + + private void processEllipse(int shapeNum, SHAPEWITHSTYLE shapes, Matrix transform, SvgStyle style, double cx, double cy, double rx, double ry) { + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{cx + rx, cy}; + pathCommands.add(scr); + + double[] points = new double[]{ + rx, -sqrt2Minus1RY, + sqrt2RXHalf, -sqrt2RYHalf, + sqrt2Minus1RX, -ry, + 0, -ry, + -sqrt2Minus1RX, -ry, + -sqrt2RXHalf, -sqrt2RYHalf, + -rx, -sqrt2Minus1RY, + -rx, 0, + -rx, sqrt2Minus1RY, + -sqrt2RXHalf, sqrt2RYHalf, + -sqrt2Minus1RX, ry, + 0, ry, + sqrt2Minus1RX, ry, + sqrt2RXHalf, sqrt2RYHalf, + rx, sqrt2Minus1RY, + rx, 0}; + + for (int i = 0; i < points.length; i += 4) { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{cx + points[i], cy + points[i + 1], cx + points[i + 2], cy + points[i + 3]}; + + /*double tetha = 30; + tetha *= Math.PI / 180; + double x1 = points[i]; + double y1 = points[i + 1]; + double x2 = points[i + 2]; + double y2 = points[i + 3]; + + double x1Comma = Math.cos(tetha) * x1 + Math.sin(tetha) * y1; + double y1Comma = -Math.sin(tetha) * x1 + Math.cos(tetha) * y1; + double x2Comma = Math.cos(tetha) * x2 + Math.sin(tetha) * y2; + double y2Comma = -Math.sin(tetha) * x2 + Math.cos(tetha) * y2; + + cer.params = new double[]{cx + x1Comma, cy + y1Comma, cx + x2Comma, cy + y2Comma};*/ + pathCommands.add(cer); + } + + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processRect(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x"); + double x = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y"); + double y = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("width"); + double width = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("height"); + double height = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("rx"); + double rx = attr.length() > 0 ? parseLength(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("ry"); + double ry = attr.length() > 0 ? parseLength(attr, viewBox.height) : 0; + + if (rx == 0 && ry != 0) { + rx = ry; + } else if (rx != 0 && ry == 0) { + ry = rx; + } + + if (rx > width / 2) { + rx = width / 2; + } + + if (ry > height / 2) { + ry = height / 2; + } + + List pathCommands = new ArrayList<>(); + + if (rx > 0 || ry > 0) { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x + width, y + ry}; + pathCommands.add(scr); + + double sqrt2RXHalf = Math.sqrt(2) * rx / 2; + double sqrt2Minus1RX = (Math.sqrt(2) - 1) * rx; + double sqrt2RYHalf = Math.sqrt(2) * ry / 2; + double sqrt2Minus1RY = (Math.sqrt(2) - 1) * ry; + + double[] points = new double[]{ + x + width, y + ry - sqrt2Minus1RY, + x + width - rx + sqrt2RXHalf, y + ry - sqrt2RYHalf, + x + width - rx + sqrt2Minus1RX, y, + x + width - rx, y, + x + rx, y, + x + rx - sqrt2Minus1RX, y, + x + rx - sqrt2RXHalf, y + ry - sqrt2RYHalf, + x, y + ry - sqrt2Minus1RY, + x, y + ry, + x, y + height - ry, + x, y + height - ry + sqrt2Minus1RY, + x + rx - sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + rx - sqrt2Minus1RX, y + height, + x + rx, y + height, + x + width - rx, y + height, + x + width - rx + sqrt2Minus1RX, y + height, + x + width - rx + sqrt2RXHalf, y + height - ry + sqrt2RYHalf, + x + width, y + height - ry + sqrt2Minus1RY, + x + width, y + height - ry, + x + width, y + ry}; + + for (int i = 0; i < points.length;) { + if (i % 10 == 8) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + pathCommands.add(cer); + i += 2; + } else { + PathCommand cer = new PathCommand(); + cer.command = 'Q'; + cer.params = new double[]{points[i], points[i + 1], points[i + 2], points[i + 3]}; + pathCommands.add(cer); + i += 4; + } + } + } else { + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x, y}; + pathCommands.add(scr); + + double[] points = new double[]{ + x + width, y, + x + width, y + height, + x, y + height, + x, y}; + + for (int i = 0; i < points.length; i += 2) { + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{points[i], points[i + 1]}; + + pathCommands.add(cer); + } + } + + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processLine(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + String attr = childElement.getAttribute("x1"); + double x1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y1"); + double y1 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + attr = childElement.getAttribute("x2"); + double x2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.width) : 0; + + attr = childElement.getAttribute("y2"); + double y2 = attr.length() > 0 ? parseCoordinate(attr, viewBox.height) : 0; + + List pathCommands = new ArrayList<>(); + PathCommand scr = new PathCommand(); + scr.command = 'M'; + scr.params = new double[]{x1, y1}; + pathCommands.add(scr); + + PathCommand cer = new PathCommand(); + cer.command = 'L'; + cer.params = new double[]{x2, y2}; + + pathCommands.add(cer); + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, false); + } + + private void processPolygon(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style) { + processPolyline(shapeNum, shapes, childElement, transform, style, true); + } + + private void processPolyline(int shapeNum, SHAPEWITHSTYLE shapes, Element childElement, Matrix transform, SvgStyle style, boolean close) { + String data = childElement.getAttribute("points"); + + char command = 'M'; + double x0 = 0; + double y0 = 0; + + List pathCommands = new ArrayList<>(); + SvgPathReader pathReader = new SvgPathReader(data); + try { + while (pathReader.hasNext()) { + double x = x0; + double y = y0; + + Point p = null; + switch (command) { + case 'M': + PathCommand scr = new PathCommand(); + scr.command = 'M'; + + x = pathReader.readDouble(); + y = pathReader.readDouble(); + scr.params = new double[]{x, y}; + + pathCommands.add(scr); + break; + case 'L': + PathCommand serl = new PathCommand(); + serl.command = 'L'; + x = pathReader.readDouble(); + y = pathReader.readDouble(); + serl.params = new double[]{x, y}; + pathCommands.add(serl); + break; + } + + x0 = x; + y0 = y; + command = 'L'; + } + } catch (NumberFormatException e) { + // ignore remaining data as specified in SVG Specification F.2 Error processing + } + + if (close) { + PathCommand serz = new PathCommand(); + serz.command = 'Z'; + pathCommands.add(serz); + } + + processCommands(shapeNum, shapes, pathCommands, transform, style); + } + + //Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file + private static void svgTest(String name) throws IOException, InterruptedException { + if (!new File(name + ".original.svg").exists()) { + URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg"); + byte[] svgData = Helper.readStream(svgUrl.openStream()); + Helper.writeFile(name + ".orig.svg", svgData); + + URL pngUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/png/full-" + name + ".png"); + byte[] pngData = Helper.readStream(pngUrl.openStream()); + Helper.writeFile(name + ".orig.png", pngData); + } + + String svgDataS = Helper.readTextFile(name + ".orig.svg"); + SWF swf = new SWF(); + DefineShape4Tag st = new DefineShape4Tag(swf); + st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS)); + swf.addTag(st); + SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR); + BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null); + List li = new ArrayList<>(); + li.add(st); + ImageIO.write(si.getBufferedImage(), "PNG", new File(name + ".imported.png")); + ExportAssetsTag eat = new ExportAssetsTag(swf); + eat.tags.add(st.getCharacterId()); + eat.names.add(name); + swf.addTag(eat); + swf.assignExportNamesToSymbols(); + st.shapeBounds.Xmax = (int) (si.getWidth() * SWF.unitDivisor); + st.shapeBounds.Ymax = (int) (si.getHeight() * SWF.unitDivisor); + new ShapeExporter().exportShapes(null, "./outex/", new ReadOnlyTagList(li), new ShapeExportSettings(ShapeExportMode.SVG, 1), null); + } + + //Test for SVG + public static void main(String[] args) throws IOException, InterruptedException { +// svgTest("animate-elem-02-t"); +// svgTest("animate-elem-03-t"); +// svgTest("animate-elem-04-t"); +// svgTest("animate-elem-05-t"); +// svgTest("animate-elem-06-t"); +// svgTest("animate-elem-07-t"); +// svgTest("animate-elem-08-t"); +// svgTest("animate-elem-09-t"); +// svgTest("animate-elem-10-t"); +// svgTest("animate-elem-11-t"); +// svgTest("animate-elem-12-t"); +// svgTest("animate-elem-13-t"); +// svgTest("animate-elem-14-t"); +// svgTest("animate-elem-15-t"); +// svgTest("animate-elem-17-t"); +// svgTest("animate-elem-19-t"); +// svgTest("animate-elem-20-t"); +// svgTest("animate-elem-21-t"); +// svgTest("animate-elem-22-b"); +// svgTest("animate-elem-23-t"); +// svgTest("animate-elem-24-t"); +// svgTest("animate-elem-25-t"); +// svgTest("animate-elem-26-t"); +// svgTest("animate-elem-27-t"); +// svgTest("animate-elem-28-t"); +// svgTest("animate-elem-29-b"); +// svgTest("animate-elem-30-t"); +// svgTest("animate-elem-31-t"); +// svgTest("animate-elem-32-t"); +// svgTest("animate-elem-33-t"); +// svgTest("animate-elem-34-t"); +// svgTest("animate-elem-36-t"); +// svgTest("animate-elem-37-t"); +// svgTest("animate-elem-39-t"); +// svgTest("animate-elem-40-t"); +// svgTest("animate-elem-41-t"); +// svgTest("animate-elem-44-t"); +// svgTest("animate-elem-46-t"); +// svgTest("animate-elem-52-t"); +// svgTest("animate-elem-60-t"); +// svgTest("animate-elem-61-t"); +// svgTest("animate-elem-62-t"); +// svgTest("animate-elem-63-t"); +// svgTest("animate-elem-64-t"); +// svgTest("animate-elem-65-t"); +// svgTest("animate-elem-66-t"); +// svgTest("animate-elem-67-t"); +// svgTest("animate-elem-68-t"); +// svgTest("animate-elem-69-t"); +// svgTest("animate-elem-70-t"); +// svgTest("animate-elem-77-t"); +// svgTest("animate-elem-78-t"); +// svgTest("animate-elem-80-t"); +// svgTest("animate-elem-81-t"); +// svgTest("animate-elem-82-t"); +// svgTest("animate-elem-83-t"); +// svgTest("animate-elem-84-t"); +// svgTest("animate-elem-85-t"); + svgTest("color-prof-01-f"); + svgTest("color-prop-01-b"); + svgTest("color-prop-02-f"); + svgTest("color-prop-03-t"); + svgTest("coords-coord-01-t"); + svgTest("coords-coord-02-t"); + svgTest("coords-trans-01-b"); + svgTest("coords-trans-02-t"); + svgTest("coords-trans-03-t"); + svgTest("coords-trans-04-t"); + svgTest("coords-trans-05-t"); + svgTest("coords-trans-06-t"); + svgTest("coords-units-01-b"); + svgTest("coords-units-02-b"); + svgTest("coords-units-03-b"); + svgTest("coords-viewattr-01-b"); + svgTest("coords-viewattr-02-b"); + svgTest("coords-viewattr-03-b"); + svgTest("extend-namespace-01-f"); +// svgTest("filters-blend-01-b"); +// svgTest("filters-color-01-b"); +// svgTest("filters-composite-02-b"); +// svgTest("filters-comptran-01-b"); +// svgTest("filters-conv-01-f"); +// svgTest("filters-diffuse-01-f"); +// svgTest("filters-displace-01-f"); +// svgTest("filters-example-01-b"); +// svgTest("filters-felem-01-b"); +// svgTest("filters-gauss-01-b"); +// svgTest("filters-image-01-b"); +// svgTest("filters-light-01-f"); +// svgTest("filters-morph-01-f"); +// svgTest("filters-offset-01-b"); +// svgTest("filters-specular-01-f"); +// svgTest("filters-tile-01-b"); +// svgTest("filters-turb-01-f"); +// svgTest("fonts-desc-02-t"); +// svgTest("fonts-elem-01-t"); +// svgTest("fonts-elem-02-t"); +// svgTest("fonts-elem-03-b"); +// svgTest("fonts-elem-04-b"); +// svgTest("fonts-elem-05-t"); +// svgTest("fonts-elem-06-t"); +// svgTest("fonts-elem-07-b"); +// svgTest("fonts-glyph-02-t"); +// svgTest("fonts-glyph-03-t"); +// svgTest("fonts-glyph-04-t"); +// svgTest("fonts-kern-01-t"); +// svgTest("interact-cursor-01-f"); +// svgTest("interact-dom-01-b"); +// svgTest("interact-events-01-b"); +// svgTest("interact-order-01-b"); +// svgTest("interact-order-02-b"); +// svgTest("interact-order-03-b"); +// svgTest("interact-zoom-01-t"); +// svgTest("linking-a-01-b"); +// svgTest("linking-a-02-b"); +// svgTest("linking-a-03-b"); +// svgTest("linking-a-04-t"); +// svgTest("linking-a-05-t"); +// svgTest("linking-a-07-t"); +// svgTest("linking-uri-01-b"); +// svgTest("linking-uri-02-b"); +// svgTest("linking-uri-03-t"); +// svgTest("masking-intro-01-f"); +// svgTest("masking-mask-01-b"); +// svgTest("masking-opacity-01-b"); +// svgTest("masking-path-01-b"); +// svgTest("masking-path-02-b"); +// svgTest("masking-path-03-b"); +// svgTest("masking-path-04-b"); +// svgTest("masking-path-05-f"); +// svgTest("metadata-example-01-b"); + svgTest("painting-fill-01-t"); + svgTest("painting-fill-02-t"); + svgTest("painting-fill-03-t"); + svgTest("painting-fill-04-t"); + svgTest("painting-fill-05-b"); + svgTest("painting-marker-01-f"); + svgTest("painting-marker-02-f"); + svgTest("painting-marker-03-f"); + svgTest("painting-render-01-b"); + svgTest("painting-stroke-01-t"); + svgTest("painting-stroke-02-t"); + svgTest("painting-stroke-03-t"); + svgTest("painting-stroke-04-t"); + svgTest("painting-stroke-07-t"); + svgTest("paths-data-01-t"); + svgTest("paths-data-02-t"); + svgTest("paths-data-03-f"); + svgTest("paths-data-04-t"); + svgTest("paths-data-05-t"); + svgTest("paths-data-06-t"); + svgTest("paths-data-07-t"); + svgTest("paths-data-08-t"); + svgTest("paths-data-09-t"); + svgTest("paths-data-10-t"); + svgTest("paths-data-12-t"); + svgTest("paths-data-13-t"); + svgTest("paths-data-14-t"); + svgTest("paths-data-15-t"); + svgTest("pservers-grad-01-b"); + svgTest("pservers-grad-02-b"); + svgTest("pservers-grad-03-b"); + svgTest("pservers-grad-04-b"); + svgTest("pservers-grad-05-b"); + svgTest("pservers-grad-06-b"); + svgTest("pservers-grad-07-b"); + svgTest("pservers-grad-08-b"); + svgTest("pservers-grad-09-b"); + svgTest("pservers-grad-10-b"); + svgTest("pservers-grad-11-b"); + svgTest("pservers-grad-12-b"); + svgTest("pservers-grad-13-b"); + svgTest("pservers-grad-14-b"); + svgTest("pservers-grad-15-b"); + svgTest("pservers-grad-16-b"); + svgTest("pservers-grad-17-b"); + svgTest("pservers-grad-18-b"); + svgTest("pservers-grad-19-b"); + svgTest("pservers-pattern-01-b"); + svgTest("render-elems-01-t"); + svgTest("render-elems-02-t"); + svgTest("render-elems-03-t"); + svgTest("render-elems-06-t"); + svgTest("render-elems-07-t"); + svgTest("render-elems-08-t"); + svgTest("render-groups-01-b"); + svgTest("render-groups-03-t"); +// svgTest("script-handle-01-b"); +// svgTest("script-handle-02-b"); +// svgTest("script-handle-03-b"); +// svgTest("script-handle-04-b"); + svgTest("shapes-circle-01-t"); + svgTest("shapes-circle-02-t"); + svgTest("shapes-ellipse-01-t"); + svgTest("shapes-ellipse-02-t"); + svgTest("shapes-intro-01-t"); + svgTest("shapes-line-01-t"); + svgTest("shapes-polygon-01-t"); + svgTest("shapes-polyline-01-t"); + svgTest("shapes-rect-01-t"); + svgTest("shapes-rect-02-t"); +// svgTest("struct-cond-01-t"); +// svgTest("struct-cond-02-t"); +// svgTest("struct-cond-03-t"); +// svgTest("struct-defs-01-t"); +// svgTest("struct-dom-01-b"); +// svgTest("struct-dom-02-b"); +// svgTest("struct-dom-03-b"); +// svgTest("struct-dom-04-b"); +// svgTest("struct-dom-05-b"); +// svgTest("struct-dom-06-b"); +// svgTest("struct-frag-01-t"); +// svgTest("struct-frag-02-t"); +// svgTest("struct-frag-03-t"); +// svgTest("struct-frag-04-t"); +// svgTest("struct-frag-05-t"); +// svgTest("struct-frag-06-t"); +// svgTest("struct-group-01-t"); +// svgTest("struct-group-02-b"); +// svgTest("struct-group-03-t"); +// svgTest("struct-image-01-t"); +// svgTest("struct-image-02-b"); +// svgTest("struct-image-03-t"); +// svgTest("struct-image-04-t"); +// svgTest("struct-image-05-b"); +// svgTest("struct-image-06-t"); +// svgTest("struct-image-07-t"); +// svgTest("struct-image-08-t"); +// svgTest("struct-image-09-t"); +// svgTest("struct-image-10-t"); +// svgTest("struct-symbol-01-b"); +// svgTest("struct-use-01-t"); +// svgTest("struct-use-03-t"); +// svgTest("struct-use-05-b"); +// svgTest("styling-css-01-b"); +// svgTest("styling-css-02-b"); +// svgTest("styling-css-03-b"); +// svgTest("styling-css-04-f"); +// svgTest("styling-css-05-b"); +// svgTest("styling-css-06-b"); +// svgTest("styling-inherit-01-b"); +// svgTest("styling-pres-01-t"); +// svgTest("text-align-01-b"); +// svgTest("text-align-02-b"); +// svgTest("text-align-03-b"); +// svgTest("text-align-04-b"); +// svgTest("text-align-05-b"); +// svgTest("text-align-06-b"); +// svgTest("text-align-08-b"); +// svgTest("text-altglyph-01-b"); +// svgTest("text-deco-01-b"); +// svgTest("text-fonts-01-t"); +// svgTest("text-fonts-02-t"); +// svgTest("text-fonts-03-t"); +// svgTest("text-intro-01-t"); +// svgTest("text-intro-02-b"); +// svgTest("text-intro-03-b"); +// svgTest("text-intro-04-t"); +// svgTest("text-intro-05-t"); +// svgTest("text-path-01-b"); +// svgTest("text-spacing-01-b"); +// svgTest("text-text-01-b"); +// svgTest("text-text-03-b"); +// svgTest("text-text-04-t"); +// svgTest("text-text-05-t"); +// svgTest("text-text-06-t"); +// svgTest("text-text-07-t"); +// svgTest("text-text-08-b"); +// svgTest("text-tref-01-b"); +// svgTest("text-tselect-01-b"); +// svgTest("text-tselect-02-f"); +// svgTest("text-tspan-01-b"); +// svgTest("text-ws-01-t"); +// svgTest("text-ws-02-t"); +// svgTest("types-basicDOM-01-b"); + } + + private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { + if (fill == null || fillStyle == null) { + return; + } + if (fill instanceof SvgGradient) { + SvgGradient gfill = (SvgGradient) fill; + Matrix gradientMatrix = Matrix.parseSvgMatrix(gfill.gradientTransform, SWF.unitDivisor, 1); + gradientMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(gradientMatrix); + fillStyle.gradientMatrix = gradientMatrix.toMATRIX(); + if (fill instanceof SvgLinearGradient) { + SvgLinearGradient lgfill = (SvgLinearGradient) fill; + fillStyle.fillStyleType = FILLSTYLE.LINEAR_GRADIENT; + fillStyle.gradient = new GRADIENT(); + double x1 = parseCoordinate(lgfill.x1, 1/* todo: how much is 100%? */); + double y1 = parseCoordinate(lgfill.y1, 1/* todo: how much is 100%? */); + double x2 = parseCoordinate(lgfill.x2, 1/* todo: how much is 100%? */); + double y2 = parseCoordinate(lgfill.y2, 1/* todo: how much is 100%? */); + + x1 = x1 * SWF.unitDivisor; + y1 = y1 * SWF.unitDivisor; + x2 = x2 * SWF.unitDivisor; + y2 = y2 * SWF.unitDivisor; + + Matrix boundingBoxMatrix = new Matrix(); + if (lgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { + boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; + boundingBoxMatrix.rotateSkew0 = 0; + boundingBoxMatrix.rotateSkew1 = 0; + boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; + boundingBoxMatrix.translateX = bounds.Xmin; + boundingBoxMatrix.translateY = bounds.Ymin; + } + + Matrix xyMatrix = new Matrix(); + xyMatrix.scaleX = x2 - x1; + xyMatrix.rotateSkew0 = y2 - y1; + xyMatrix.rotateSkew1 = -xyMatrix.rotateSkew0; + xyMatrix.scaleY = xyMatrix.scaleX; + + xyMatrix = xyMatrix.preConcatenate(boundingBoxMatrix); + + Matrix zeroStartMatrix = Matrix.getTranslateInstance(0.5, 0); + + Matrix scaleMatrix = Matrix.getScaleInstance(1 / 16384.0 / 2); + Matrix transMatrix = Matrix.getTranslateInstance(x1, y1); + + Matrix tMatrix = new Matrix(); + tMatrix = tMatrix.preConcatenate(scaleMatrix); + tMatrix = tMatrix.preConcatenate(zeroStartMatrix); + tMatrix = tMatrix.preConcatenate(xyMatrix); + + tMatrix = tMatrix.preConcatenate(transMatrix); + Point p1 = tMatrix.transform(new Point(-16384, 0)); + Point p2 = tMatrix.transform(new Point(16384, 0)); + + tMatrix = tMatrix.preConcatenate(new Matrix(fillStyle.gradientMatrix)); + fillStyle.gradientMatrix = tMatrix.toMATRIX(); + } else if (fill instanceof SvgRadialGradient) { + SvgRadialGradient rgfill = (SvgRadialGradient) fill; + double cx = parseCoordinate(rgfill.cx, 1/* todo: how much is 100%? */); + double cy = parseCoordinate(rgfill.cy, 1/* todo: how much is 100%? */); + double r = parseLength(rgfill.r, 1/* todo: how much is 100%? */); + + Matrix boundingBoxMatrix = new Matrix(); + if (rgfill.gradientUnits == SvgGradientUnits.OBJECT_BOUNDING_BOX) { + boundingBoxMatrix.scaleX = (bounds.Xmax - bounds.Xmin) / SWF.unitDivisor; + boundingBoxMatrix.rotateSkew0 = 0; + boundingBoxMatrix.rotateSkew1 = 0; + boundingBoxMatrix.scaleY = (bounds.Ymax - bounds.Ymin) / SWF.unitDivisor; + boundingBoxMatrix.translateX = bounds.Xmin; + boundingBoxMatrix.translateY = bounds.Ymin; + } + + fillStyle.gradientMatrix = Matrix.getTranslateInstance(SWF.unitDivisor * cx, SWF.unitDivisor * cy).concatenate(new Matrix(fillStyle.gradientMatrix)).concatenate(Matrix.getScaleInstance(r / 819.2)).preConcatenate(boundingBoxMatrix).toMATRIX(); + + double fx = parseCoordinate(rgfill.fx, 1/* todo: how much is 100%? */); + double fy = parseCoordinate(rgfill.fy, 1/* todo: how much is 100%? */); + if (!rgfill.fx.equals(rgfill.cx) || !rgfill.fy.equals(rgfill.cy)) { + fillStyle.fillStyleType = FILLSTYLE.FOCAL_RADIAL_GRADIENT; + fillStyle.gradient = new FOCALGRADIENT(); + FOCALGRADIENT fg = (FOCALGRADIENT) fillStyle.gradient; + double f = Math.sqrt((fx - cx) * (fx - cx) + (fy - cy) * (fy - cy)) / 819.2; + fg.focalPoint = (float) f; + } else { + fillStyle.fillStyleType = FILLSTYLE.RADIAL_GRADIENT; + fillStyle.gradient = new GRADIENT(); + } + } + switch (gfill.spreadMethod) { + case PAD: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_PAD_MODE; + break; + case REFLECT: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REFLECT_MODE; + break; + case REPEAT: + fillStyle.gradient.spreadMode = GRADIENT.SPREAD_REPEAT_MODE; + break; + } + switch (gfill.interpolation) { + case LINEAR_RGB: + fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_LINEAR_RGB_MODE; + break; + case SRGB: + fillStyle.gradient.interpolationMode = GRADIENT.INTERPOLATION_RGB_MODE; + break; + } + + fillStyle.gradient.gradientRecords = new GRADRECORD[gfill.stops.size()]; + int prevRatio = -1; + for (int i = 0; i < gfill.stops.size(); i++) { + SvgStop stop = gfill.stops.get(i); + Color color = stop.color; + color = new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) Math.round(color.getAlpha() * style.getOpacity())); + fillStyle.gradient.gradientRecords[i] = new GRADRECORD(); + fillStyle.gradient.gradientRecords[i].inShape3 = shapeNum >= 3; + fillStyle.gradient.gradientRecords[i].color = getRGB(shapeNum, color); + int ratio = Math.max((int) Math.round(stop.offset * 255), prevRatio + 1); + fillStyle.gradient.gradientRecords[i].ratio = ratio; + prevRatio = ratio; + if (prevRatio == 255) { + break; + } + } + } else if (fill instanceof SvgBitmapFill) { + SvgBitmapFill bfill = (SvgBitmapFill) fill; + fillStyle.fillStyleType = FILLSTYLE.REPEATING_BITMAP; + fillStyle.bitmapId = bfill.characterId; + Matrix fillMatrix = Matrix.parseSvgMatrix(bfill.patternTransform, SWF.unitDivisor, SWF.unitDivisor); + fillMatrix = transform.concatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).concatenate(fillMatrix); + fillStyle.bitmapMatrix = fillMatrix.toMATRIX(); + } + } + + private void applyStyleGradients(RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) { + SvgFill fill = style.getFillWithOpacity(); + if (fill != null && fill != SvgTransparentFill.INSTANCE) { + applyFillGradients(fill, scr.fillStyles.fillStyles[0], bounds, scr, transform, shapeNum, style); + } + SvgFill strokeFill = style.getStrokeFillWithOpacity(); + if (strokeFill != null) { + if (scr.lineStyles.lineStyles.length > 0 && scr.lineStyles.lineStyles[0] instanceof LINESTYLE2) { + applyFillGradients(strokeFill, ((LINESTYLE2) scr.lineStyles.lineStyles[0]).fillType, bounds, scr, transform, shapeNum, style); + } + } + } + + private StyleChangeRecord getStyleChangeRecord(int shapeNum, SvgStyle style) { + StyleChangeRecord scr = new StyleChangeRecord(); + + scr.stateNewStyles = true; + scr.fillStyles = new FILLSTYLEARRAY(); + scr.stateFillStyle1 = true; + scr.stateLineStyle = true; + SvgFill fill = style.getFillWithOpacity(); + if (fill != null && fill != SvgTransparentFill.INSTANCE) { + scr.fillStyles.fillStyles = new FILLSTYLE[1]; + scr.fillStyles.fillStyles[0] = new FILLSTYLE(); + if (fill instanceof SvgColor) { + Color colorFill = fill.toColor(); + scr.fillStyles.fillStyles[0].color = getRGB(shapeNum, colorFill); + scr.fillStyles.fillStyles[0].fillStyleType = FILLSTYLE.SOLID; + } else if (fill instanceof SvgGradient) { + //...apply in second step - applyStyleGradients + } + + scr.fillStyle1 = 1; + } else { + scr.fillStyles.fillStyles = new FILLSTYLE[0]; + scr.fillStyle1 = 0; + } + + scr.lineStyles = new LINESTYLEARRAY(); + SvgFill strokeFill = style.getStrokeFillWithOpacity(); + if (strokeFill != null && strokeFill != SvgTransparentFill.INSTANCE) { + Color lineColor = strokeFill.toColor(); + + scr.lineStyles.lineStyles = new LINESTYLE[1]; + LINESTYLE lineStyle = shapeNum <= 3 ? new LINESTYLE() : new LINESTYLE2(); + lineStyle.color = getRGB(shapeNum, lineColor); + lineStyle.width = (int) Math.round(style.getStrokeWidth() * SWF.unitDivisor); + SvgLineCap lineCap = style.getStrokeLineCap(); + SvgLineJoin lineJoin = style.getStrokeLineJoin(); + if (lineStyle instanceof LINESTYLE2) { + LINESTYLE2 lineStyle2 = (LINESTYLE2) lineStyle; + int swfCap = lineCap == SvgLineCap.BUTT ? LINESTYLE2.NO_CAP + : lineCap == SvgLineCap.ROUND ? LINESTYLE2.ROUND_CAP + : lineCap == SvgLineCap.SQUARE ? LINESTYLE2.SQUARE_CAP : 0; + lineStyle2.startCapStyle = swfCap; + lineStyle2.endCapStyle = swfCap; + if (!(strokeFill instanceof SvgColor)) { + lineStyle2.hasFillFlag = true; + lineStyle2.fillType = new FILLSTYLE(); + //...apply in second step - applyStyleGradients + } // Single color does not need fillType attribute + + int swfJoin = lineJoin == SvgLineJoin.MITER ? LINESTYLE2.MITER_JOIN + : lineJoin == SvgLineJoin.ROUND ? LINESTYLE2.ROUND_JOIN + : lineJoin == SvgLineJoin.BEVEL ? LINESTYLE2.BEVEL_JOIN : 0; + lineStyle2.joinStyle = swfJoin; + lineStyle2.miterLimitFactor = (float) style.getStrokeMiterLimit(); + } else { + if (lineCap != SvgLineCap.ROUND) { + showWarning("lineCapNotSupported", "LineCap style not supported in shape " + shapeNum); + } + if (lineJoin != SvgLineJoin.ROUND) { + showWarning("lineJoinNotSupported", "LineJoin style not supported in shape " + shapeNum); + } + } + + scr.lineStyles.lineStyles[0] = lineStyle; + scr.lineStyle = 1; + } else { + scr.lineStyles.lineStyles = new LINESTYLE[0]; + scr.lineStyle = 0; + } + + return scr; + } + + private RGB getRGB(int shapeNum, Color color) { + if (shapeNum < 3 && color.getAlpha() != 0xff) { + showWarning("transparentColorNotSupported", "Transparent color is not supported in shape " + shapeNum); + } + + return shapeNum >= 3 ? new RGBA(color) : new RGB(color); + } + + private double parseCoordinate(String value, double relativeTo) { + return parseLength(value, relativeTo); + } + + private double parseLength(String value, double relativeTo) { + if (value == null) { + throw new NumberFormatException(); + } + + value = value.toLowerCase(); + String unit = null; + if (value.endsWith("em") + || value.endsWith("ex") + || value.endsWith("px") + || value.endsWith("in") + || value.endsWith("cm") + || value.endsWith("mm") + || value.endsWith("pt") + || value.endsWith("pc")) { + unit = value.substring(value.length() - 2); + value = value.substring(0, value.length() - 2); + } else if (value.endsWith("%")) { + unit = "%"; + value = value.substring(0, value.length() - 1); + } + + double result = Double.parseDouble(value); + if (unit != null) { + switch (unit) { + case "em": + case "ex": + // todo: font things + break; + case "in": + result *= getDpi(); + break; + case "pt": + result *= getDpi() / 72; + break; + case "pc": + result *= getDpi() / 6; + break; + case "cm": + result *= getDpi() / 2.54; + break; + case "mm": + result *= getDpi() / 25.4; + break; + case "%": + result = relativeTo * result / 100; + break; + } + } + + return result; + } + + public double parseNumber(String value) { + if (value == null) { + throw new NumberFormatException(); + } + + double result = Double.parseDouble(value); + return result; + } + + public double parseNumberOrPercent(String value) { + if (value == null) { + throw new NumberFormatException(); + } + + boolean percent = value.endsWith("%"); + if (percent) { + value = value.substring(0, value.length() - 1); + } + + double result = Double.parseDouble(value); + if (percent) { + result /= 100; + } + + return result; + } + + private double getDpi() { + return 96; + + } + + class PathCommand { + + public char command; + + public double[] params; + } + + void showWarning(String name, String text) { + if (!shownWarnings.contains(name)) { + Logger.getLogger(ShapeImporter.class.getName()).log(Level.WARNING, text); + shownWarnings.add(name); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java index 5f5a15bd9..8c09d68fd 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/importers/svg/SvgPathReader.java @@ -1,138 +1,138 @@ -/* - * 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.importers.svg; - -/** - * - * @author JPEXS - */ -public class SvgPathReader { - - private final String str; - - private int pos; - - public SvgPathReader(String str) { - this.str = str; - } - - public boolean hasNext() { - return pos < str.length(); - } - - public char peek() { - return str.charAt(pos); - } - - public char readChar() { - char ch = str.charAt(pos); - pos++; - return ch; - } - - public char readCommand() { - if (!hasNext()) { - return 0; - } - - readWhiteSpaces(); - char ch = peek(); - char command = 0; - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { - command = ch; - pos++; - readSeparators(); - } - - return command; - } - - private void digitSequence() { - while (hasNext()) { - char ch = str.charAt(pos); - - if (ch >= '0' && ch <= '9') { - } else { - break; - } - - pos++; - } - } - - public double readDouble() { - int startPos = pos; - - readWhiteSpaces(); - char ch = str.charAt(pos); - if (ch == '-') { - pos++; - } - - digitSequence(); - ch = str.charAt(pos); - if (ch == '.') { - pos++; - digitSequence(); - } - - ch = str.charAt(pos); - if (ch == 'e') { - pos++; - ch = str.charAt(pos); - if (ch == '-') { - pos++; - } - - digitSequence(); - } - - boolean ok = false; - try { - double result = Double.parseDouble(str.substring(startPos, pos)); - readSeparators(); - ok = true; - return result; - } finally { - if (!ok) { - pos = startPos; - } - } - } - - private void readWhiteSpaces() { - while (hasNext()) { - char ch = peek(); - if (ch != ' ' && ch != '\r' && ch != '\n') { - return; - } - - readChar(); - } - } - - private void readSeparators() { - while (hasNext()) { - char ch = peek(); - if (ch != ' ' && ch != ',' && ch != '\r' && ch != '\n') { - return; - } - - readChar(); - } - } -} +/* + * 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.importers.svg; + +/** + * + * @author JPEXS + */ +public class SvgPathReader { + + private final String str; + + private int pos; + + public SvgPathReader(String str) { + this.str = str; + } + + public boolean hasNext() { + return pos < str.length(); + } + + public char peek() { + return str.charAt(pos); + } + + public char readChar() { + char ch = str.charAt(pos); + pos++; + return ch; + } + + public char readCommand() { + if (!hasNext()) { + return 0; + } + + readWhiteSpaces(); + char ch = peek(); + char command = 0; + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) { + command = ch; + pos++; + readSeparators(); + } + + return command; + } + + private void digitSequence() { + while (hasNext()) { + char ch = str.charAt(pos); + + if (ch >= '0' && ch <= '9') { + } else { + break; + } + + pos++; + } + } + + public double readDouble() { + int startPos = pos; + + readWhiteSpaces(); + char ch = str.charAt(pos); + if (ch == '-') { + pos++; + } + + digitSequence(); + ch = str.charAt(pos); + if (ch == '.') { + pos++; + digitSequence(); + } + + ch = str.charAt(pos); + if (ch == 'e') { + pos++; + ch = str.charAt(pos); + if (ch == '-') { + pos++; + } + + digitSequence(); + } + + boolean ok = false; + try { + double result = Double.parseDouble(str.substring(startPos, pos)); + readSeparators(); + ok = true; + return result; + } finally { + if (!ok) { + pos = startPos; + } + } + } + + private void readWhiteSpaces() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } + + private void readSeparators() { + while (hasNext()) { + char ch = peek(); + if (ch != ' ' && ch != ',' && ch != '\r' && ch != '\n') { + return; + } + + readChar(); + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java index 398a7d23b..ca5342415 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG3Tag.java @@ -1,239 +1,239 @@ -/* - * 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.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) //Note: GIF and PNG since version -public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { - - public static final int ID = 35; - - public static final String NAME = "DefineBitsJPEG3"; - - @SWFType(BasicType.UI8) - public ByteArrayRange imageData; - - @SWFType(BasicType.UI8) - public ByteArrayRange bitmapAlphaData; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsJPEG3Tag(SWF swf) { - super(swf, ID, NAME, null); - characterID = swf.getNextCharacterId(); - imageData = new ByteArrayRange(createEmptyImage()); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - public DefineBitsJPEG3Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { - super(swf, ID, NAME, data); - this.characterID = characterID; - this.imageData = new ByteArrayRange(imageData); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineBitsJPEG3Tag(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 { - characterID = sis.readUI16("characterID"); - long alphaDataOffset = sis.readUI32("alphaDataOffset"); - imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); - bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI32(imageData.getLength()); - sos.write(imageData); - sos.write(bitmapAlphaData); - } - - private byte[] createEmptyImage() { - BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); - return bitmapDataOS.toByteArray(); - } - - @Override - public void setImage(byte[] data) throws IOException { - if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { - BufferedImage image = ImageHelper.read(data); - byte[] ba = new byte[image.getWidth() * image.getHeight()]; - for (int i = 0; i < ba.length; i++) { - ba[i] = (byte) 255; - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); - } else { - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); - } - - imageData = new ByteArrayRange(data); - clearCache(); - setModified(true); - } - - public byte[] getImageAlpha() throws IOException { - return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); - } - - public void setImageAlpha(byte[] data) throws IOException { - ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt != ImageFormat.JPEG) { - throw new IOException("Only Jpeg can have alpha channel."); - } - - Dimension dimension = getImageDimension(); - if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { - throw new IOException("Data length must match the size of the image."); - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); - clearCache(); - setModified(true); - } - - @Override - public ImageFormat getImageFormat() { - ImageFormat fmt = getOriginalImageFormat(); - if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { - fmt = ImageFormat.PNG; //transparency - } - return fmt; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageTag.getImageFormat(imageData); - } - - @Override - public InputStream getOriginalImageData() { - if (bitmapAlphaData.getLength() == 0) { // No alpha - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - } - - return null; - } - - @Override - protected SerializableImage getImage() { - try { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - - BufferedImage image = ImageHelper.read(bis); - if (image == null) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); - return null; - } - - SerializableImage img = new SerializableImage(image); - if (bitmapAlphaData.getLength() == 0) { - return img; - } - - byte[] alphaData = getImageAlpha(); - if (alphaData.length == 0) { - return img; - } - - int width = img.getWidth(); - int height = img.getHeight(); - SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); - int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < pixels.length; i++) { - int a = alphaData[i] & 0xff; - pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); - } - - return img2; - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); - } - return null; - } - - @Override - public Dimension getImageDimension() { - if (cachedImage != null) { - return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); - } - - try { - int errorLength = hasErrorHeader(imageData) ? 4 : 0; - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); - return ImageHelper.getDimesion(bis); - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); - } - - return null; - } -} +/* + * 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.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) //Note: GIF and PNG since version +public class DefineBitsJPEG3Tag extends ImageTag implements AloneTag { + + public static final int ID = 35; + + public static final String NAME = "DefineBitsJPEG3"; + + @SWFType(BasicType.UI8) + public ByteArrayRange imageData; + + @SWFType(BasicType.UI8) + public ByteArrayRange bitmapAlphaData; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsJPEG3Tag(SWF swf) { + super(swf, ID, NAME, null); + characterID = swf.getNextCharacterId(); + imageData = new ByteArrayRange(createEmptyImage()); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + public DefineBitsJPEG3Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { + super(swf, ID, NAME, data); + this.characterID = characterID; + this.imageData = new ByteArrayRange(imageData); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineBitsJPEG3Tag(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 { + characterID = sis.readUI16("characterID"); + long alphaDataOffset = sis.readUI32("alphaDataOffset"); + imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); + bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI32(imageData.getLength()); + sos.write(imageData); + sos.write(bitmapAlphaData); + } + + private byte[] createEmptyImage() { + BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); + return bitmapDataOS.toByteArray(); + } + + @Override + public void setImage(byte[] data) throws IOException { + if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { + BufferedImage image = ImageHelper.read(data); + byte[] ba = new byte[image.getWidth() * image.getHeight()]; + for (int i = 0; i < ba.length; i++) { + ba[i] = (byte) 255; + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); + } else { + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); + } + + imageData = new ByteArrayRange(data); + clearCache(); + setModified(true); + } + + public byte[] getImageAlpha() throws IOException { + return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); + } + + public void setImageAlpha(byte[] data) throws IOException { + ImageFormat fmt = ImageTag.getImageFormat(imageData); + if (fmt != ImageFormat.JPEG) { + throw new IOException("Only Jpeg can have alpha channel."); + } + + Dimension dimension = getImageDimension(); + if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { + throw new IOException("Data length must match the size of the image."); + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); + clearCache(); + setModified(true); + } + + @Override + public ImageFormat getImageFormat() { + ImageFormat fmt = getOriginalImageFormat(); + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { + fmt = ImageFormat.PNG; //transparency + } + return fmt; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageTag.getImageFormat(imageData); + } + + @Override + public InputStream getOriginalImageData() { + if (bitmapAlphaData.getLength() == 0) { // No alpha + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + } + + return null; + } + + @Override + protected SerializableImage getImage() { + try { + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + + BufferedImage image = ImageHelper.read(bis); + if (image == null) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); + return null; + } + + SerializableImage img = new SerializableImage(image); + if (bitmapAlphaData.getLength() == 0) { + return img; + } + + byte[] alphaData = getImageAlpha(); + if (alphaData.length == 0) { + return img; + } + + int width = img.getWidth(); + int height = img.getHeight(); + SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); + for (int i = 0; i < pixels.length; i++) { + int a = alphaData[i] & 0xff; + pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); + } + + return img2; + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); + } + return null; + } + + @Override + public Dimension getImageDimension() { + if (cachedImage != null) { + return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); + } + + try { + int errorLength = hasErrorHeader(imageData) ? 4 : 0; + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos() + errorLength, imageData.getLength() - errorLength); + return ImageHelper.getDimesion(bis); + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); + } + + return null; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java index fb4bffde8..c65894a88 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsJPEG4Tag.java @@ -1,239 +1,239 @@ -/* - * 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.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.BufferedImage; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 10) -public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { - - public static final int ID = 90; - - public static final String NAME = "DefineBitsJPEG4"; - - @SWFType(BasicType.UI16) - public int deblockParam; - - @SWFType(BasicType.UI8) - public ByteArrayRange imageData; - - @SWFType(BasicType.UI8) - public ByteArrayRange bitmapAlphaData; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsJPEG4Tag(SWF swf) { - super(swf, ID, NAME, null); - characterID = swf.getNextCharacterId(); - imageData = new ByteArrayRange(createEmptyImage()); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - public DefineBitsJPEG4Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { - super(swf, ID, NAME, data); - this.characterID = characterID; - this.imageData = new ByteArrayRange(imageData); - bitmapAlphaData = ByteArrayRange.EMPTY; - forceWriteAsLong = true; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineBitsJPEG4Tag(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 { - characterID = sis.readUI16("characterID"); - long alphaDataOffset = sis.readUI32("alphaDataOffset"); - deblockParam = sis.readUI16("deblockParam"); - imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); - bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI32(imageData.getLength()); - sos.writeUI16(deblockParam); - sos.write(imageData); - sos.write(bitmapAlphaData); - } - - private byte[] createEmptyImage() { - BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); - return bitmapDataOS.toByteArray(); - } - - @Override - public void setImage(byte[] data) throws IOException { - if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { - BufferedImage image = ImageHelper.read(data); - byte[] ba = new byte[image.getWidth() * image.getHeight()]; - for (int i = 0; i < ba.length; i++) { - ba[i] = (byte) 255; - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); - } else { - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); - } - - imageData = new ByteArrayRange(data); - clearCache(); - setModified(true); - } - - public byte[] getImageAlpha() throws IOException { - return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); - } - - public void setImageAlpha(byte[] data) throws IOException { - ImageFormat fmt = ImageTag.getImageFormat(imageData); - if (fmt != ImageFormat.JPEG) { - throw new IOException("Only Jpeg can have alpha channel."); - } - - Dimension dimension = getImageDimension(); - if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { - throw new IOException("Data length must match the size of the image."); - } - - bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); - clearCache(); - setModified(true); - } - - @Override - public ImageFormat getImageFormat() { - ImageFormat fmt = getOriginalImageFormat(); - if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { - fmt = ImageFormat.PNG; //transparency - } - return fmt; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageTag.getImageFormat(imageData); - } - - @Override - public InputStream getOriginalImageData() { - if (bitmapAlphaData.getLength() == 0) { // No alpha - return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); - } - - return null; - } - - @Override - protected SerializableImage getImage() { - try { - BufferedImage image = ImageHelper.read(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength())); - if (image == null) { - Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); - return null; - } - - SerializableImage img = new SerializableImage(image); - if (bitmapAlphaData.getLength() == 0) { - return img; - } - - byte[] alphaData = getImageAlpha(); - if (alphaData.length == 0) { - return img; - } - - int width = img.getWidth(); - int height = img.getHeight(); - SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); - int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); - for (int i = 0; i < pixels.length; i++) { - int a = alphaData[i] & 0xff; - pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); - } - - return img2; - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); - } - return null; - } - - @Override - public Dimension getImageDimension() { - if (cachedImage != null) { - return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); - } - - try { - ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); - return ImageHelper.getDimesion(bis); - } catch (IOException ex) { - Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); - } - - return null; - } -} +/* + * 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.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 10) +public class DefineBitsJPEG4Tag extends ImageTag implements AloneTag { + + public static final int ID = 90; + + public static final String NAME = "DefineBitsJPEG4"; + + @SWFType(BasicType.UI16) + public int deblockParam; + + @SWFType(BasicType.UI8) + public ByteArrayRange imageData; + + @SWFType(BasicType.UI8) + public ByteArrayRange bitmapAlphaData; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsJPEG4Tag(SWF swf) { + super(swf, ID, NAME, null); + characterID = swf.getNextCharacterId(); + imageData = new ByteArrayRange(createEmptyImage()); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + public DefineBitsJPEG4Tag(SWF swf, ByteArrayRange data, int characterID, byte[] imageData) throws IOException { + super(swf, ID, NAME, data); + this.characterID = characterID; + this.imageData = new ByteArrayRange(imageData); + bitmapAlphaData = ByteArrayRange.EMPTY; + forceWriteAsLong = true; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineBitsJPEG4Tag(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 { + characterID = sis.readUI16("characterID"); + long alphaDataOffset = sis.readUI32("alphaDataOffset"); + deblockParam = sis.readUI16("deblockParam"); + imageData = sis.readByteRangeEx(alphaDataOffset, "imageData"); + bitmapAlphaData = sis.readByteRangeEx(sis.available(), "bitmapAlphaData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI32(imageData.getLength()); + sos.writeUI16(deblockParam); + sos.write(imageData); + sos.write(bitmapAlphaData); + } + + private byte[] createEmptyImage() { + BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE); + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + ImageHelper.write(img, ImageFormat.JPEG, bitmapDataOS); + return bitmapDataOS.toByteArray(); + } + + @Override + public void setImage(byte[] data) throws IOException { + if (ImageTag.getImageFormat(data) == ImageFormat.JPEG) { + BufferedImage image = ImageHelper.read(data); + byte[] ba = new byte[image.getWidth() * image.getHeight()]; + for (int i = 0; i < ba.length; i++) { + ba[i] = (byte) 255; + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(ba)); + } else { + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(new byte[0])); + } + + imageData = new ByteArrayRange(data); + clearCache(); + setModified(true); + } + + public byte[] getImageAlpha() throws IOException { + return SWFInputStream.uncompressByteArray(bitmapAlphaData.getRangeData()); + } + + public void setImageAlpha(byte[] data) throws IOException { + ImageFormat fmt = ImageTag.getImageFormat(imageData); + if (fmt != ImageFormat.JPEG) { + throw new IOException("Only Jpeg can have alpha channel."); + } + + Dimension dimension = getImageDimension(); + if (data == null || data.length != dimension.getWidth() * dimension.getHeight()) { + throw new IOException("Data length must match the size of the image."); + } + + bitmapAlphaData = new ByteArrayRange(SWFOutputStream.compressByteArray(data)); + clearCache(); + setModified(true); + } + + @Override + public ImageFormat getImageFormat() { + ImageFormat fmt = getOriginalImageFormat(); + if (fmt == ImageFormat.JPEG && bitmapAlphaData.getLength() > 0) { + fmt = ImageFormat.PNG; //transparency + } + return fmt; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageTag.getImageFormat(imageData); + } + + @Override + public InputStream getOriginalImageData() { + if (bitmapAlphaData.getLength() == 0) { // No alpha + return new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + } + + return null; + } + + @Override + protected SerializableImage getImage() { + try { + BufferedImage image = ImageHelper.read(new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength())); + if (image == null) { + Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to load image"); + return null; + } + + SerializableImage img = new SerializableImage(image); + if (bitmapAlphaData.getLength() == 0) { + return img; + } + + byte[] alphaData = getImageAlpha(); + if (alphaData.length == 0) { + return img; + } + + int width = img.getWidth(); + int height = img.getHeight(); + SerializableImage img2 = new SerializableImage(width, height, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData(); + int[] pixels2 = ((DataBufferInt) img2.getRaster().getDataBuffer()).getData(); + for (int i = 0; i < pixels.length; i++) { + int a = alphaData[i] & 0xff; + pixels2[i] = (pixels[i] & 0xffffff) | (a << 24); + } + + return img2; + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG4Tag.class.getName()).log(Level.SEVERE, "Failed to get image", ex); + } + return null; + } + + @Override + public Dimension getImageDimension() { + if (cachedImage != null) { + return new Dimension(cachedImage.getWidth(), cachedImage.getHeight()); + } + + try { + ByteArrayInputStream bis = new ByteArrayInputStream(imageData.getArray(), imageData.getPos(), imageData.getLength()); + return ImageHelper.getDimesion(bis); + } catch (IOException ex) { + Logger.getLogger(DefineBitsJPEG3Tag.class.getName()).log(Level.SEVERE, "Failed to get image dimension", ex); + } + + return null; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java index 153a7300f..78a6f16b5 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLossless2Tag.java @@ -1,281 +1,281 @@ -/* - * 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.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA; -import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -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.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 3) -public class DefineBitsLossless2Tag extends ImageTag implements AloneTag { - - public static final int ID = 36; - - public static final String NAME = "DefineBitsLossless2"; - - @SWFType(BasicType.UI8) - public int bitmapFormat; - - @SWFType(BasicType.UI16) - public int bitmapWidth; - - @SWFType(BasicType.UI16) - public int bitmapHeight; - - @SWFType(BasicType.UI8) - @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) - public int bitmapColorTableSize; - - public ByteArrayRange zlibBitmapData; - - public static final int FORMAT_8BIT_COLORMAPPED = 3; - - public static final int FORMAT_32BIT_ARGB = 5; - - @HideInRawEdit - private ALPHACOLORMAPDATA colorMapData; - - @HideInRawEdit - private ALPHABITMAPDATA bitmapData; - - @Internal - private boolean decompressed = false; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsLossless2Tag(SWF swf) { - this(swf, null, swf.getNextCharacterId()); - } - - public DefineBitsLossless2Tag(SWF swf, ByteArrayRange data, int characterID) { - super(swf, ID, NAME, data); - this.characterID = characterID; - bitmapFormat = DefineBitsLossless2Tag.FORMAT_32BIT_ARGB; - bitmapWidth = 1; - bitmapHeight = 1; - zlibBitmapData = new ByteArrayRange(createEmptyImage()); - forceWriteAsLong = true; - } - - public DefineBitsLossless2Tag(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 { - characterID = sis.readUI16("characterID"); - bitmapFormat = sis.readUI8("bitmapFormat"); - bitmapWidth = sis.readUI16("bitmapWidth"); - bitmapHeight = sis.readUI16("bitmapHeight"); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); - } - - zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI8(bitmapFormat); - sos.writeUI16(bitmapWidth); - sos.writeUI16(bitmapHeight); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - sos.writeUI8(bitmapColorTableSize); - } - sos.write(zlibBitmapData); - } - - private byte[] createEmptyImage() { - try { - ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); - bitmapData.bitmapPixelData = new int[]{0xff000000}; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeALPHABITMAPDATA(bitmapData, FORMAT_32BIT_ARGB, 1, 1); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - return zlibOS.toByteArray(); - } catch (IOException ex) { - Logger.getLogger(DefineBitsLossless2Tag.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - @Override - public void setImage(byte[] data) throws IOException { - SerializableImage image = new SerializableImage(ImageHelper.read(data)); - ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); - int width = image.getWidth(); - int height = image.getHeight(); - bitmapData.bitmapPixelData = new int[width * height]; - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - for (int pos = 0; pos < pixels.length; pos++) { - int argb = pixels[pos]; - int a = (argb >> 24) & 0xff; - int r = (argb >> 16) & 0xff; - int g = (argb >> 8) & 0xff; - int b = argb & 0xff; - - r = r * a / 255; - g = g * a / 255; - b = b * a / 255; - - bitmapData.bitmapPixelData[pos] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); - } - - int format = FORMAT_32BIT_ARGB; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeALPHABITMAPDATA(bitmapData, format, width, height); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); - bitmapFormat = format; - bitmapWidth = width; - bitmapHeight = height; - decompressed = false; - clearCache(); - setModified(true); - } - - public ALPHACOLORMAPDATA getColorMapData() { - if (!decompressed) { - uncompressData(); - } - return colorMapData; - } - - public ALPHABITMAPDATA getBitmapData() { - if (!decompressed) { - uncompressData(); - } - return bitmapData; - } - - private void uncompressData() { - try { - byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); - SWFInputStream sis = new SWFInputStream(swf, uncompressedData); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - colorMapData = sis.readALPHACOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); - } else if (bitmapFormat == FORMAT_32BIT_ARGB) { - bitmapData = sis.readALPHABITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); - } - } catch (IOException ex) { - } - decompressed = true; - } - - @Override - public ImageFormat getImageFormat() { - return ImageFormat.PNG; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageFormat.PNG; - } - - @Override - public InputStream getOriginalImageData() { - return null; - } - - @Override - protected SerializableImage getImage() { - SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_ARGB_PRE); - int[] pixels = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); - - ALPHACOLORMAPDATA colorMapData = null; - ALPHABITMAPDATA bitmapData = null; - if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED) { - colorMapData = getColorMapData(); - } - if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB) { - bitmapData = getBitmapData(); - } - int pos32aligned = 0; - int pos = 0; - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = 0; - if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED)) { - int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; - if (colorTableIndex < colorMapData.colorTableRGB.length) { - c = colorMapData.colorTableRGB[colorTableIndex]; - } - } - if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB)) { - c = bitmapData.bitmapPixelData[pos]; - } - - pixels[pos] = c; - pos32aligned++; - pos++; - } - while ((pos32aligned % 4 != 0)) { - pos32aligned++; - } - } - - return bi; - } - - @Override - public Dimension getImageDimension() { - return new Dimension(bitmapWidth, bitmapHeight); - } -} +/* + * 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.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.ALPHABITMAPDATA; +import com.jpexs.decompiler.flash.types.ALPHACOLORMAPDATA; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +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.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 3) +public class DefineBitsLossless2Tag extends ImageTag implements AloneTag { + + public static final int ID = 36; + + public static final String NAME = "DefineBitsLossless2"; + + @SWFType(BasicType.UI8) + public int bitmapFormat; + + @SWFType(BasicType.UI16) + public int bitmapWidth; + + @SWFType(BasicType.UI16) + public int bitmapHeight; + + @SWFType(BasicType.UI8) + @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) + public int bitmapColorTableSize; + + public ByteArrayRange zlibBitmapData; + + public static final int FORMAT_8BIT_COLORMAPPED = 3; + + public static final int FORMAT_32BIT_ARGB = 5; + + @HideInRawEdit + private ALPHACOLORMAPDATA colorMapData; + + @HideInRawEdit + private ALPHABITMAPDATA bitmapData; + + @Internal + private boolean decompressed = false; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsLossless2Tag(SWF swf) { + this(swf, null, swf.getNextCharacterId()); + } + + public DefineBitsLossless2Tag(SWF swf, ByteArrayRange data, int characterID) { + super(swf, ID, NAME, data); + this.characterID = characterID; + bitmapFormat = DefineBitsLossless2Tag.FORMAT_32BIT_ARGB; + bitmapWidth = 1; + bitmapHeight = 1; + zlibBitmapData = new ByteArrayRange(createEmptyImage()); + forceWriteAsLong = true; + } + + public DefineBitsLossless2Tag(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 { + characterID = sis.readUI16("characterID"); + bitmapFormat = sis.readUI8("bitmapFormat"); + bitmapWidth = sis.readUI16("bitmapWidth"); + bitmapHeight = sis.readUI16("bitmapHeight"); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); + } + + zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI8(bitmapFormat); + sos.writeUI16(bitmapWidth); + sos.writeUI16(bitmapHeight); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + sos.writeUI8(bitmapColorTableSize); + } + sos.write(zlibBitmapData); + } + + private byte[] createEmptyImage() { + try { + ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); + bitmapData.bitmapPixelData = new int[]{0xff000000}; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeALPHABITMAPDATA(bitmapData, FORMAT_32BIT_ARGB, 1, 1); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + return zlibOS.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(DefineBitsLossless2Tag.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public void setImage(byte[] data) throws IOException { + SerializableImage image = new SerializableImage(ImageHelper.read(data)); + ALPHABITMAPDATA bitmapData = new ALPHABITMAPDATA(); + int width = image.getWidth(); + int height = image.getHeight(); + bitmapData.bitmapPixelData = new int[width * height]; + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + for (int pos = 0; pos < pixels.length; pos++) { + int argb = pixels[pos]; + int a = (argb >> 24) & 0xff; + int r = (argb >> 16) & 0xff; + int g = (argb >> 8) & 0xff; + int b = argb & 0xff; + + r = r * a / 255; + g = g * a / 255; + b = b * a / 255; + + bitmapData.bitmapPixelData[pos] = ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); + } + + int format = FORMAT_32BIT_ARGB; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeALPHABITMAPDATA(bitmapData, format, width, height); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); + bitmapFormat = format; + bitmapWidth = width; + bitmapHeight = height; + decompressed = false; + clearCache(); + setModified(true); + } + + public ALPHACOLORMAPDATA getColorMapData() { + if (!decompressed) { + uncompressData(); + } + return colorMapData; + } + + public ALPHABITMAPDATA getBitmapData() { + if (!decompressed) { + uncompressData(); + } + return bitmapData; + } + + private void uncompressData() { + try { + byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); + SWFInputStream sis = new SWFInputStream(swf, uncompressedData); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + colorMapData = sis.readALPHACOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); + } else if (bitmapFormat == FORMAT_32BIT_ARGB) { + bitmapData = sis.readALPHABITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); + } + } catch (IOException ex) { + } + decompressed = true; + } + + @Override + public ImageFormat getImageFormat() { + return ImageFormat.PNG; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageFormat.PNG; + } + + @Override + public InputStream getOriginalImageData() { + return null; + } + + @Override + protected SerializableImage getImage() { + SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_ARGB_PRE); + int[] pixels = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData(); + + ALPHACOLORMAPDATA colorMapData = null; + ALPHABITMAPDATA bitmapData = null; + if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED) { + colorMapData = getColorMapData(); + } + if (bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB) { + bitmapData = getBitmapData(); + } + int pos32aligned = 0; + int pos = 0; + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = 0; + if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_8BIT_COLORMAPPED)) { + int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; + if (colorTableIndex < colorMapData.colorTableRGB.length) { + c = colorMapData.colorTableRGB[colorTableIndex]; + } + } + if ((bitmapFormat == DefineBitsLossless2Tag.FORMAT_32BIT_ARGB)) { + c = bitmapData.bitmapPixelData[pos]; + } + + pixels[pos] = c; + pos32aligned++; + pos++; + } + while ((pos32aligned % 4 != 0)) { + pos32aligned++; + } + } + + return bi; + } + + @Override + public Dimension getImageDimension() { + return new Dimension(bitmapWidth, bitmapHeight); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java index fb1abc000..b9b03f25f 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineBitsLosslessTag.java @@ -1,281 +1,281 @@ -/* - * 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.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.helpers.ImageHelper; -import com.jpexs.decompiler.flash.tags.base.AloneTag; -import com.jpexs.decompiler.flash.tags.base.ImageTag; -import com.jpexs.decompiler.flash.tags.enums.ImageFormat; -import com.jpexs.decompiler.flash.types.BITMAPDATA; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.COLORMAPDATA; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -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.helpers.ByteArrayRange; -import com.jpexs.helpers.SerializableImage; -import java.awt.Dimension; -import java.awt.image.DataBufferInt; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 2) -public class DefineBitsLosslessTag extends ImageTag implements AloneTag { - - public static final int ID = 20; - - public static final String NAME = "DefineBitsLossless"; - - @SWFType(BasicType.UI8) - public int bitmapFormat; - - @SWFType(BasicType.UI16) - public int bitmapWidth; - - @SWFType(BasicType.UI16) - public int bitmapHeight; - - @SWFType(BasicType.UI8) - @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) - public int bitmapColorTableSize; - - public ByteArrayRange zlibBitmapData; - - public static final int FORMAT_8BIT_COLORMAPPED = 3; - - public static final int FORMAT_15BIT_RGB = 4; - - public static final int FORMAT_24BIT_RGB = 5; - - @HideInRawEdit - private COLORMAPDATA colorMapData; - - @HideInRawEdit - private BITMAPDATA bitmapData; - - @Internal - private boolean decompressed = false; - - /** - * Constructor - * - * @param swf - */ - public DefineBitsLosslessTag(SWF swf) { - this(swf, null, swf.getNextCharacterId()); - } - - public DefineBitsLosslessTag(SWF swf, ByteArrayRange data, int characterID) { - super(swf, ID, NAME, data); - this.characterID = characterID; - bitmapFormat = DefineBitsLosslessTag.FORMAT_24BIT_RGB; - bitmapWidth = 1; - bitmapHeight = 1; - zlibBitmapData = new ByteArrayRange(createEmptyImage()); - forceWriteAsLong = true; - } - - public DefineBitsLosslessTag(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 { - characterID = sis.readUI16("characterID"); - bitmapFormat = sis.readUI8("bitmapFormat"); - bitmapWidth = sis.readUI16("bitmapWidth"); - bitmapHeight = sis.readUI16("bitmapHeight"); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); - } - - zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterID); - sos.writeUI8(bitmapFormat); - sos.writeUI16(bitmapWidth); - sos.writeUI16(bitmapHeight); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - sos.writeUI8(bitmapColorTableSize); - } - sos.write(zlibBitmapData); - } - - private byte[] createEmptyImage() { - try { - BITMAPDATA bitmapData = new BITMAPDATA(); - bitmapData.bitmapPixelDataPix24 = new int[]{0xff000000}; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeBITMAPDATA(bitmapData, FORMAT_24BIT_RGB, 1, 1); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - return zlibOS.toByteArray(); - } catch (IOException ex) { - Logger.getLogger(DefineBitsLosslessTag.class.getName()).log(Level.SEVERE, null, ex); - } - return null; - } - - @Override - public void setImage(byte[] data) throws IOException { - SerializableImage image = new SerializableImage(ImageHelper.read(data)); - int width = image.getWidth(); - int height = image.getHeight(); - bitmapData = new BITMAPDATA(); - bitmapData.bitmapPixelDataPix24 = new int[width * height]; - int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); - for (int pos = 0; pos < pixels.length; pos++) { - // set the reserved bits to 0xff, because: - // documentation says 0, but image is sometimes broken with 0, so there is 0xff, which works (maybe alpha?) - int argb = pixels[pos] | 0xff000000; - bitmapData.bitmapPixelDataPix24[pos] = argb; - } - - int format = FORMAT_24BIT_RGB; - ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); - sos.writeBITMAPDATA(bitmapData, format, width, height); - ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); - SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); - sos2.writeBytesZlib(bitmapDataOS.toByteArray()); - zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); - bitmapFormat = format; - bitmapWidth = width; - bitmapHeight = height; - decompressed = false; - clearCache(); - setModified(true); - } - - public COLORMAPDATA getColorMapData() { - if (!decompressed) { - uncompressData(); - } - return colorMapData; - } - - public BITMAPDATA getBitmapData() { - if (!decompressed) { - uncompressData(); - } - return bitmapData; - } - - private void uncompressData() { - try { - byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); - SWFInputStream sis = new SWFInputStream(swf, uncompressedData); - if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { - colorMapData = sis.readCOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); - } else if ((bitmapFormat == FORMAT_15BIT_RGB) || (bitmapFormat == FORMAT_24BIT_RGB)) { - bitmapData = sis.readBITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); - } - } catch (IOException ex) { - } - decompressed = true; - } - - @Override - public ImageFormat getImageFormat() { - return ImageFormat.PNG; - } - - @Override - public ImageFormat getOriginalImageFormat() { - return ImageFormat.PNG; - } - - @Override - public InputStream getOriginalImageData() { - return null; - } - - @Override - protected SerializableImage getImage() { - int[] pixels = new int[bitmapWidth * bitmapHeight]; - if (bitmapFormat == DefineBitsLosslessTag.FORMAT_8BIT_COLORMAPPED) { - COLORMAPDATA colorMapData = getColorMapData(); - int pos32aligned = 0; - int pos = 0; - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = 0; - int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; - if (colorTableIndex < colorMapData.colorTableRGB.length) { - c = colorMapData.colorTableRGB[colorTableIndex]; - } - - pixels[pos++] = c; - pos32aligned++; - } - - while ((pos32aligned % 4 != 0)) { - pos32aligned++; - } - } - } else if ((bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) || (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB)) { - BITMAPDATA bitmapData = getBitmapData(); - int pos = 0; - int[] bitmapPixelData = null; - if (bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) { - bitmapPixelData = bitmapData.bitmapPixelDataPix15; - } else if (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB) { - bitmapPixelData = bitmapData.bitmapPixelDataPix24; - } - - for (int y = 0; y < bitmapHeight; y++) { - for (int x = 0; x < bitmapWidth; x++) { - int c = bitmapPixelData[pos] | 0xff000000; - pixels[pos++] = c; - } - } - } - - SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_RGB, pixels); - return bi; - } - - @Override - public Dimension getImageDimension() { - return new Dimension(bitmapWidth, bitmapHeight); - } -} +/* + * 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.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.helpers.ImageHelper; +import com.jpexs.decompiler.flash.tags.base.AloneTag; +import com.jpexs.decompiler.flash.tags.base.ImageTag; +import com.jpexs.decompiler.flash.tags.enums.ImageFormat; +import com.jpexs.decompiler.flash.types.BITMAPDATA; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.COLORMAPDATA; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +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.helpers.ByteArrayRange; +import com.jpexs.helpers.SerializableImage; +import java.awt.Dimension; +import java.awt.image.DataBufferInt; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 2) +public class DefineBitsLosslessTag extends ImageTag implements AloneTag { + + public static final int ID = 20; + + public static final String NAME = "DefineBitsLossless"; + + @SWFType(BasicType.UI8) + public int bitmapFormat; + + @SWFType(BasicType.UI16) + public int bitmapWidth; + + @SWFType(BasicType.UI16) + public int bitmapHeight; + + @SWFType(BasicType.UI8) + @Conditional(value = "bitmapFormat", options = {FORMAT_8BIT_COLORMAPPED}) + public int bitmapColorTableSize; + + public ByteArrayRange zlibBitmapData; + + public static final int FORMAT_8BIT_COLORMAPPED = 3; + + public static final int FORMAT_15BIT_RGB = 4; + + public static final int FORMAT_24BIT_RGB = 5; + + @HideInRawEdit + private COLORMAPDATA colorMapData; + + @HideInRawEdit + private BITMAPDATA bitmapData; + + @Internal + private boolean decompressed = false; + + /** + * Constructor + * + * @param swf + */ + public DefineBitsLosslessTag(SWF swf) { + this(swf, null, swf.getNextCharacterId()); + } + + public DefineBitsLosslessTag(SWF swf, ByteArrayRange data, int characterID) { + super(swf, ID, NAME, data); + this.characterID = characterID; + bitmapFormat = DefineBitsLosslessTag.FORMAT_24BIT_RGB; + bitmapWidth = 1; + bitmapHeight = 1; + zlibBitmapData = new ByteArrayRange(createEmptyImage()); + forceWriteAsLong = true; + } + + public DefineBitsLosslessTag(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 { + characterID = sis.readUI16("characterID"); + bitmapFormat = sis.readUI8("bitmapFormat"); + bitmapWidth = sis.readUI16("bitmapWidth"); + bitmapHeight = sis.readUI16("bitmapHeight"); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + bitmapColorTableSize = sis.readUI8("bitmapColorTableSize"); + } + + zlibBitmapData = sis.readByteRangeEx(sis.available(), "zlibBitmapData", DumpInfoSpecialType.ZLIB_DATA, null); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterID); + sos.writeUI8(bitmapFormat); + sos.writeUI16(bitmapWidth); + sos.writeUI16(bitmapHeight); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + sos.writeUI8(bitmapColorTableSize); + } + sos.write(zlibBitmapData); + } + + private byte[] createEmptyImage() { + try { + BITMAPDATA bitmapData = new BITMAPDATA(); + bitmapData.bitmapPixelDataPix24 = new int[]{0xff000000}; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeBITMAPDATA(bitmapData, FORMAT_24BIT_RGB, 1, 1); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + return zlibOS.toByteArray(); + } catch (IOException ex) { + Logger.getLogger(DefineBitsLosslessTag.class.getName()).log(Level.SEVERE, null, ex); + } + return null; + } + + @Override + public void setImage(byte[] data) throws IOException { + SerializableImage image = new SerializableImage(ImageHelper.read(data)); + int width = image.getWidth(); + int height = image.getHeight(); + bitmapData = new BITMAPDATA(); + bitmapData.bitmapPixelDataPix24 = new int[width * height]; + int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); + for (int pos = 0; pos < pixels.length; pos++) { + // set the reserved bits to 0xff, because: + // documentation says 0, but image is sometimes broken with 0, so there is 0xff, which works (maybe alpha?) + int argb = pixels[pos] | 0xff000000; + bitmapData.bitmapPixelDataPix24[pos] = argb; + } + + int format = FORMAT_24BIT_RGB; + ByteArrayOutputStream bitmapDataOS = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(bitmapDataOS, getVersion()); + sos.writeBITMAPDATA(bitmapData, format, width, height); + ByteArrayOutputStream zlibOS = new ByteArrayOutputStream(); + SWFOutputStream sos2 = new SWFOutputStream(zlibOS, getVersion()); + sos2.writeBytesZlib(bitmapDataOS.toByteArray()); + zlibBitmapData = new ByteArrayRange(zlibOS.toByteArray()); + bitmapFormat = format; + bitmapWidth = width; + bitmapHeight = height; + decompressed = false; + clearCache(); + setModified(true); + } + + public COLORMAPDATA getColorMapData() { + if (!decompressed) { + uncompressData(); + } + return colorMapData; + } + + public BITMAPDATA getBitmapData() { + if (!decompressed) { + uncompressData(); + } + return bitmapData; + } + + private void uncompressData() { + try { + byte[] uncompressedData = SWFInputStream.uncompressByteArray(zlibBitmapData.getArray(), zlibBitmapData.getPos(), zlibBitmapData.getLength()); + SWFInputStream sis = new SWFInputStream(swf, uncompressedData); + if (bitmapFormat == FORMAT_8BIT_COLORMAPPED) { + colorMapData = sis.readCOLORMAPDATA(bitmapColorTableSize, bitmapWidth, bitmapHeight, "colorMapData"); + } else if ((bitmapFormat == FORMAT_15BIT_RGB) || (bitmapFormat == FORMAT_24BIT_RGB)) { + bitmapData = sis.readBITMAPDATA(bitmapFormat, bitmapWidth, bitmapHeight, "bitmapData"); + } + } catch (IOException ex) { + } + decompressed = true; + } + + @Override + public ImageFormat getImageFormat() { + return ImageFormat.PNG; + } + + @Override + public ImageFormat getOriginalImageFormat() { + return ImageFormat.PNG; + } + + @Override + public InputStream getOriginalImageData() { + return null; + } + + @Override + protected SerializableImage getImage() { + int[] pixels = new int[bitmapWidth * bitmapHeight]; + if (bitmapFormat == DefineBitsLosslessTag.FORMAT_8BIT_COLORMAPPED) { + COLORMAPDATA colorMapData = getColorMapData(); + int pos32aligned = 0; + int pos = 0; + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = 0; + int colorTableIndex = colorMapData.colorMapPixelData[pos32aligned] & 0xff; + if (colorTableIndex < colorMapData.colorTableRGB.length) { + c = colorMapData.colorTableRGB[colorTableIndex]; + } + + pixels[pos++] = c; + pos32aligned++; + } + + while ((pos32aligned % 4 != 0)) { + pos32aligned++; + } + } + } else if ((bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) || (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB)) { + BITMAPDATA bitmapData = getBitmapData(); + int pos = 0; + int[] bitmapPixelData = null; + if (bitmapFormat == DefineBitsLosslessTag.FORMAT_15BIT_RGB) { + bitmapPixelData = bitmapData.bitmapPixelDataPix15; + } else if (bitmapFormat == DefineBitsLosslessTag.FORMAT_24BIT_RGB) { + bitmapPixelData = bitmapData.bitmapPixelDataPix24; + } + + for (int y = 0; y < bitmapHeight; y++) { + for (int x = 0; x < bitmapWidth; x++) { + int c = bitmapPixelData[pos] | 0xff000000; + pixels[pos++] = c; + } + } + } + + SerializableImage bi = new SerializableImage(bitmapWidth, bitmapHeight, SerializableImage.TYPE_INT_RGB, pixels); + return bi; + } + + @Override + public Dimension getImageDimension() { + return new Dimension(bitmapWidth, bitmapHeight); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java index d9c92028d..c1e0df0a0 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineButtonTag.java @@ -1,291 +1,291 @@ -/* - * 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.action.Action; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonAction; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.timeline.DepthState; -import com.jpexs.decompiler.flash.timeline.Frame; -import com.jpexs.decompiler.flash.timeline.Timeline; -import com.jpexs.decompiler.flash.types.BUTTONRECORD; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Cache; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Set; - -/** - * Defines a button character - * - * @author JPEXS - */ -@SWFVersion(from = 1) -public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { - - public static final int ID = 7; - - public static final String NAME = "DefineButton"; - - /** - * ID for this character - */ - @SWFType(BasicType.UI16) - public int buttonId; - - /** - * Characters that make up the button - */ - public List characters; - - /** - * Actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Constructor - * - * @param swf - */ - public DefineButtonTag(SWF swf) { - super(swf, ID, NAME, null); - buttonId = swf.getNextCharacterId(); - characters = new ArrayList<>(); - actionBytes = ByteArrayRange.EMPTY; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineButtonTag(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 { - buttonId = sis.readUI16("buttonId"); - characters = sis.readBUTTONRECORDList(false, "characters"); - actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(buttonId); - sos.writeBUTTONRECORDList(characters, false); - sos.write(getActionBytes()); - } - - @Override - public int getCharacterId() { - return buttonId; - } - - @Override - public void setCharacterId(int characterId) { - this.buttonId = characterId; - } - - @Override - public List getRecords() { - return characters; - } - - @Override - public List getSubItems() { - return Arrays.asList(new ButtonAction(this)); - } - - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - } - - public void setModified() { - setModified(true); - } - - @Override - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - boolean modified = false; - for (int i = 0; i < characters.size(); i++) { - BUTTONRECORD character = characters.get(i); - if (character.characterId == oldCharacterId) { - character.characterId = newCharacterId; - modified = true; - } - } - if (modified) { - setModified(true); - } - return modified; - } - - @Override - public boolean removeCharacter(int characterId) { - boolean modified = false; - for (int i = 0; i < characters.size(); i++) { - if (characters.get(i).characterId == characterId) { - characters.remove(i); - modified = true; - i--; - } - } - if (modified) { - setModified(true); - } - return modified; - } - - @Override - public RECT getRect(Set added) { - Cache cache = swf == null ? null : swf.getRectCache(); - RECT ret = cache == null ? null : cache.get(this); - if (ret != null) { - return ret; - } - - RECT rect = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); - for (BUTTONRECORD r : characters) { - CharacterTag ch = swf.getCharacter(r.characterId); - if (ch instanceof BoundedTag) { - BoundedTag bt = (BoundedTag) ch; - if (!added.contains(bt)) { - added.add(bt); - RECT r2 = bt.getRect(added); - added.remove(bt); - MATRIX mat = r.placeMatrix; - if (mat != null) { - r2 = mat.apply(r2); - } - rect.Xmin = Math.min(r2.Xmin, rect.Xmin); - rect.Ymin = Math.min(r2.Ymin, rect.Ymin); - rect.Xmax = Math.max(r2.Xmax, rect.Xmax); - rect.Ymax = Math.max(r2.Ymax, rect.Ymax); - } - } - } - - if (cache != null) { - cache.put(this, rect); - } - - return rect; - } - - @Override - public boolean trackAsMenu() { - return false; - } - - @Override - public int getNumFrames() { - return 1; - } - - @Override - protected void initTimeline(Timeline timeline) { - DefineButtonCxformTag cxformTag = (DefineButtonCxformTag) swf.getCharacterIdTag(buttonId, DefineButtonCxformTag.ID); - ColorTransform clrTrans = cxformTag == null ? null : cxformTag.buttonColorTransform; - int maxDepth = 0; - Frame frameUp = new Frame(timeline, 0); - Frame frameDown = new Frame(timeline, 0); - Frame frameOver = new Frame(timeline, 0); - Frame frameHit = new Frame(timeline, 0); - for (BUTTONRECORD r : this.characters) { - - DepthState layer = new DepthState(swf, null); - layer.colorTransForm = clrTrans; - layer.blendMode = r.blendMode; - layer.filters = r.filterList; - layer.matrix = r.placeMatrix; - layer.characterId = r.characterId; - if (r.placeDepth > maxDepth) { - maxDepth = r.placeDepth; - } - - if (r.buttonStateUp) { - frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, false)); - } - if (r.buttonStateDown) { - frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, false)); - } - if (r.buttonStateOver) { - frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, false)); - } - if (r.buttonStateHitTest) { - frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, false)); - } - - } - - timeline.addFrame(frameUp); - - if (frameOver.layers.isEmpty()) { - frameOver = frameUp; - } - - timeline.addFrame(frameOver); - - if (frameDown.layers.isEmpty()) { - frameDown = frameOver; - } - - timeline.addFrame(frameDown); - - if (frameHit.layers.isEmpty()) { - frameHit = frameUp; - } - - timeline.addFrame(frameHit); - } -} +/* + * 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.action.Action; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonAction; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.timeline.DepthState; +import com.jpexs.decompiler.flash.timeline.Frame; +import com.jpexs.decompiler.flash.timeline.Timeline; +import com.jpexs.decompiler.flash.types.BUTTONRECORD; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Cache; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +/** + * Defines a button character + * + * @author JPEXS + */ +@SWFVersion(from = 1) +public class DefineButtonTag extends ButtonTag implements ASMSourceContainer { + + public static final int ID = 7; + + public static final String NAME = "DefineButton"; + + /** + * ID for this character + */ + @SWFType(BasicType.UI16) + public int buttonId; + + /** + * Characters that make up the button + */ + public List characters; + + /** + * Actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Constructor + * + * @param swf + */ + public DefineButtonTag(SWF swf) { + super(swf, ID, NAME, null); + buttonId = swf.getNextCharacterId(); + characters = new ArrayList<>(); + actionBytes = ByteArrayRange.EMPTY; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineButtonTag(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 { + buttonId = sis.readUI16("buttonId"); + characters = sis.readBUTTONRECORDList(false, "characters"); + actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(buttonId); + sos.writeBUTTONRECORDList(characters, false); + sos.write(getActionBytes()); + } + + @Override + public int getCharacterId() { + return buttonId; + } + + @Override + public void setCharacterId(int characterId) { + this.buttonId = characterId; + } + + @Override + public List getRecords() { + return characters; + } + + @Override + public List getSubItems() { + return Arrays.asList(new ButtonAction(this)); + } + + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + } + + public void setModified() { + setModified(true); + } + + @Override + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + boolean modified = false; + for (int i = 0; i < characters.size(); i++) { + BUTTONRECORD character = characters.get(i); + if (character.characterId == oldCharacterId) { + character.characterId = newCharacterId; + modified = true; + } + } + if (modified) { + setModified(true); + } + return modified; + } + + @Override + public boolean removeCharacter(int characterId) { + boolean modified = false; + for (int i = 0; i < characters.size(); i++) { + if (characters.get(i).characterId == characterId) { + characters.remove(i); + modified = true; + i--; + } + } + if (modified) { + setModified(true); + } + return modified; + } + + @Override + public RECT getRect(Set added) { + Cache cache = swf == null ? null : swf.getRectCache(); + RECT ret = cache == null ? null : cache.get(this); + if (ret != null) { + return ret; + } + + RECT rect = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE); + for (BUTTONRECORD r : characters) { + CharacterTag ch = swf.getCharacter(r.characterId); + if (ch instanceof BoundedTag) { + BoundedTag bt = (BoundedTag) ch; + if (!added.contains(bt)) { + added.add(bt); + RECT r2 = bt.getRect(added); + added.remove(bt); + MATRIX mat = r.placeMatrix; + if (mat != null) { + r2 = mat.apply(r2); + } + rect.Xmin = Math.min(r2.Xmin, rect.Xmin); + rect.Ymin = Math.min(r2.Ymin, rect.Ymin); + rect.Xmax = Math.max(r2.Xmax, rect.Xmax); + rect.Ymax = Math.max(r2.Ymax, rect.Ymax); + } + } + } + + if (cache != null) { + cache.put(this, rect); + } + + return rect; + } + + @Override + public boolean trackAsMenu() { + return false; + } + + @Override + public int getNumFrames() { + return 1; + } + + @Override + protected void initTimeline(Timeline timeline) { + DefineButtonCxformTag cxformTag = (DefineButtonCxformTag) swf.getCharacterIdTag(buttonId, DefineButtonCxformTag.ID); + ColorTransform clrTrans = cxformTag == null ? null : cxformTag.buttonColorTransform; + int maxDepth = 0; + Frame frameUp = new Frame(timeline, 0); + Frame frameDown = new Frame(timeline, 0); + Frame frameOver = new Frame(timeline, 0); + Frame frameHit = new Frame(timeline, 0); + for (BUTTONRECORD r : this.characters) { + + DepthState layer = new DepthState(swf, null); + layer.colorTransForm = clrTrans; + layer.blendMode = r.blendMode; + layer.filters = r.filterList; + layer.matrix = r.placeMatrix; + layer.characterId = r.characterId; + if (r.placeDepth > maxDepth) { + maxDepth = r.placeDepth; + } + + if (r.buttonStateUp) { + frameUp.layers.put(r.placeDepth, new DepthState(layer, frameUp, false)); + } + if (r.buttonStateDown) { + frameDown.layers.put(r.placeDepth, new DepthState(layer, frameDown, false)); + } + if (r.buttonStateOver) { + frameOver.layers.put(r.placeDepth, new DepthState(layer, frameOver, false)); + } + if (r.buttonStateHitTest) { + frameHit.layers.put(r.placeDepth, new DepthState(layer, frameHit, false)); + } + + } + + timeline.addFrame(frameUp); + + if (frameOver.layers.isEmpty()) { + frameOver = frameUp; + } + + timeline.addFrame(frameOver); + + if (frameDown.layers.isEmpty()) { + frameDown = frameOver; + } + + timeline.addFrame(frameDown); + + if (frameHit.layers.isEmpty()) { + frameHit = frameUp; + } + + timeline.addFrame(frameHit); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java index 713a891e7..e1bb15c89 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineScalingGridTag.java @@ -1,229 +1,229 @@ -/* - * 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.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.Point; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import java.awt.Rectangle; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.GeneralPath; -import java.awt.geom.PathIterator; -import java.io.IOException; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 8) -public class DefineScalingGridTag extends Tag implements CharacterIdTag { - - public static final int ID = 78; - - public static final String NAME = "DefineScalingGrid"; - - @SWFType(BasicType.UI16) - public int characterId; - - public RECT splitter; - - /** - * Constructor - * - * @param swf - */ - public DefineScalingGridTag(SWF swf) { - super(swf, ID, NAME, null); - splitter = new RECT(); - } - - public DefineScalingGridTag(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 { - characterId = sis.readUI16("characterId"); - splitter = sis.readRECT("splitter"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(characterId); - sos.writeRECT(splitter); - } - - @Override - public int getCharacterId() { - return characterId; - } - - @Override - public void setCharacterId(int characterId) { - this.characterId = characterId; - } - - private static double roundPixels(double v) { - return v; //Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; - } - - private static double roundPixels20(double v) { - return Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; - } - - private static Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) { - Matrix toOrigin = Matrix.getTranslateInstance(roundPixels(-fromRect.xMin), roundPixels(-fromRect.yMin)); - Matrix scale = new Matrix(); - scale.scaleX = roundPixels(toRect.getWidth()) / roundPixels(fromRect.getWidth()); - scale.scaleY = roundPixels(toRect.getHeight()) / roundPixels(fromRect.getHeight()); - Matrix toDest = Matrix.getTranslateInstance(roundPixels(toRect.xMin), roundPixels(toRect.yMin)); - return toOrigin.preConcatenate(scale).preConcatenate(toDest); - } - - public RECT getRect() { - Shape s = getOutline(0, 0, 0, new RenderContext(), new Matrix(), new Matrix(), true); - if (s == null) { - return null; - } - Rectangle r = s.getBounds(); - return new RECT(r.x, r.x + r.width, r.y, r.y + r.height); - } - - public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, Matrix prevTransform, boolean stroked) { - CharacterTag ct = swf.getCharacter(characterId); - if (ct == null) { - return null; - } - if (!(ct instanceof DrawableTag)) { - return null; - } - double[] coords = new double[6]; - - DrawableTag dt = (DrawableTag) ct; - Shape path = dt.getOutline(frame, time, ratio, renderContext, transformation, stroked); - PathIterator iterator = path.getPathIterator(new AffineTransform()); - GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); - ExportRectangle boundsRect = new ExportRectangle(dt.getRect()); - ExportRectangle scalingGrid = new ExportRectangle(splitter); - - ExportRectangle[] sourceRect = new ExportRectangle[9]; - ExportRectangle[] targetRect = new ExportRectangle[9]; - Matrix[] transforms = new Matrix[9]; - - getSlices(transformation.transform(boundsRect), boundsRect, scalingGrid, sourceRect, targetRect, transforms); - - while (!iterator.isDone()) { - int type = iterator.currentSegment(coords); - for (int i = 0; i < 6; i += 2) { - double x = coords[i]; - double y = coords[i + 1]; - for (int s = 0; s < 9; s++) { - Point p = new Point(x, y); - if (sourceRect[s].contains(p)) { - p = transforms[s].transform(p); - coords[i] = p.x; - coords[i + 1] = p.y; - break; - } - } - } - switch (type) { - case PathIterator.SEG_MOVETO: - gp.moveTo(coords[0], coords[1]); - break; - case PathIterator.SEG_LINETO: - gp.lineTo(coords[0], coords[1]); - break; - case PathIterator.SEG_QUADTO: - gp.quadTo(coords[0], coords[1], coords[2], coords[3]); - break; - case PathIterator.SEG_CUBICTO: - gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); - break; - case PathIterator.SEG_CLOSE: - gp.closePath(); - break; - } - iterator.next(); - } - return gp; - } - - public static void getSlices(ExportRectangle targetBounds, ExportRectangle boundsRect, ExportRectangle scalingGrid, ExportRectangle[] sourceRect, ExportRectangle[] targetRect, Matrix[] transforms) { - - double[] src_x = new double[]{boundsRect.xMin, scalingGrid.xMin, scalingGrid.xMax, boundsRect.xMax}; - double[] dst_x = new double[]{targetBounds.xMin, targetBounds.xMin + scalingGrid.xMin, targetBounds.xMax - (boundsRect.xMax - scalingGrid.xMax), targetBounds.xMax}; - - double[] src_y = new double[]{boundsRect.yMin, scalingGrid.yMin, scalingGrid.yMax, boundsRect.yMax}; - double[] dst_y = new double[]{targetBounds.yMin, targetBounds.yMin + scalingGrid.yMin, targetBounds.yMax - (boundsRect.yMax - scalingGrid.yMax), targetBounds.yMax}; - - int pos = 0; - for (int sy = 0; sy < 3; sy++) { - for (int sx = 0; sx < 3; sx++) { - sourceRect[pos] = new ExportRectangle(src_x[sx], src_y[sy], src_x[sx + 1], src_y[sy + 1]); - targetRect[pos] = new ExportRectangle(dst_x[sx], dst_y[sy], dst_x[sx + 1], dst_y[sy + 1]); - pos++; - } - } - - for (int i = 0; i < targetRect.length; i++) { - - /* sourceRect[i].xMax = roundPixels20(sourceRect[i].xMax); - sourceRect[i].yMax = roundPixels20(sourceRect[i].yMax); - sourceRect[i].xMin = roundPixels20(sourceRect[i].xMin); - sourceRect[i].yMin = roundPixels20(sourceRect[i].yMin); - */ - //System.out.println("source[" + i + "]=" + sourceRect[i]); - //System.out.println("target[" + i + "]=" + targetRect[i]); - - /*targetRect[i].xMax = roundPixels20(targetRect[i].xMax); - targetRect[i].yMax = roundPixels20(targetRect[i].yMax); - targetRect[i].xMin = roundPixels20(targetRect[i].xMin); - targetRect[i].yMin = roundPixels20(targetRect[i].yMin); - */ - transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]); - - targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor); - targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor); - targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor); - targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor); - - //targetRect[i].xMax += maxStroke; - //Round to pixel boundary - } - } -} +/* + * 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.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.Point; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.awt.Rectangle; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.GeneralPath; +import java.awt.geom.PathIterator; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 8) +public class DefineScalingGridTag extends Tag implements CharacterIdTag { + + public static final int ID = 78; + + public static final String NAME = "DefineScalingGrid"; + + @SWFType(BasicType.UI16) + public int characterId; + + public RECT splitter; + + /** + * Constructor + * + * @param swf + */ + public DefineScalingGridTag(SWF swf) { + super(swf, ID, NAME, null); + splitter = new RECT(); + } + + public DefineScalingGridTag(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 { + characterId = sis.readUI16("characterId"); + splitter = sis.readRECT("splitter"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(characterId); + sos.writeRECT(splitter); + } + + @Override + public int getCharacterId() { + return characterId; + } + + @Override + public void setCharacterId(int characterId) { + this.characterId = characterId; + } + + private static double roundPixels(double v) { + return v; //Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; + } + + private static double roundPixels20(double v) { + return Math.rint(v / SWF.unitDivisor) * SWF.unitDivisor; + } + + private static Matrix rectToRectMatrix(ExportRectangle fromRect, ExportRectangle toRect) { + Matrix toOrigin = Matrix.getTranslateInstance(roundPixels(-fromRect.xMin), roundPixels(-fromRect.yMin)); + Matrix scale = new Matrix(); + scale.scaleX = roundPixels(toRect.getWidth()) / roundPixels(fromRect.getWidth()); + scale.scaleY = roundPixels(toRect.getHeight()) / roundPixels(fromRect.getHeight()); + Matrix toDest = Matrix.getTranslateInstance(roundPixels(toRect.xMin), roundPixels(toRect.yMin)); + return toOrigin.preConcatenate(scale).preConcatenate(toDest); + } + + public RECT getRect() { + Shape s = getOutline(0, 0, 0, new RenderContext(), new Matrix(), new Matrix(), true); + if (s == null) { + return null; + } + Rectangle r = s.getBounds(); + return new RECT(r.x, r.x + r.width, r.y, r.y + r.height); + } + + public Shape getOutline(int frame, int time, int ratio, RenderContext renderContext, Matrix transformation, Matrix prevTransform, boolean stroked) { + CharacterTag ct = swf.getCharacter(characterId); + if (ct == null) { + return null; + } + if (!(ct instanceof DrawableTag)) { + return null; + } + double[] coords = new double[6]; + + DrawableTag dt = (DrawableTag) ct; + Shape path = dt.getOutline(frame, time, ratio, renderContext, transformation, stroked); + PathIterator iterator = path.getPathIterator(new AffineTransform()); + GeneralPath gp = new GeneralPath(GeneralPath.WIND_EVEN_ODD); + ExportRectangle boundsRect = new ExportRectangle(dt.getRect()); + ExportRectangle scalingGrid = new ExportRectangle(splitter); + + ExportRectangle[] sourceRect = new ExportRectangle[9]; + ExportRectangle[] targetRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + + getSlices(transformation.transform(boundsRect), boundsRect, scalingGrid, sourceRect, targetRect, transforms); + + while (!iterator.isDone()) { + int type = iterator.currentSegment(coords); + for (int i = 0; i < 6; i += 2) { + double x = coords[i]; + double y = coords[i + 1]; + for (int s = 0; s < 9; s++) { + Point p = new Point(x, y); + if (sourceRect[s].contains(p)) { + p = transforms[s].transform(p); + coords[i] = p.x; + coords[i + 1] = p.y; + break; + } + } + } + switch (type) { + case PathIterator.SEG_MOVETO: + gp.moveTo(coords[0], coords[1]); + break; + case PathIterator.SEG_LINETO: + gp.lineTo(coords[0], coords[1]); + break; + case PathIterator.SEG_QUADTO: + gp.quadTo(coords[0], coords[1], coords[2], coords[3]); + break; + case PathIterator.SEG_CUBICTO: + gp.curveTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]); + break; + case PathIterator.SEG_CLOSE: + gp.closePath(); + break; + } + iterator.next(); + } + return gp; + } + + public static void getSlices(ExportRectangle targetBounds, ExportRectangle boundsRect, ExportRectangle scalingGrid, ExportRectangle[] sourceRect, ExportRectangle[] targetRect, Matrix[] transforms) { + + double[] src_x = new double[]{boundsRect.xMin, scalingGrid.xMin, scalingGrid.xMax, boundsRect.xMax}; + double[] dst_x = new double[]{targetBounds.xMin, targetBounds.xMin + scalingGrid.xMin, targetBounds.xMax - (boundsRect.xMax - scalingGrid.xMax), targetBounds.xMax}; + + double[] src_y = new double[]{boundsRect.yMin, scalingGrid.yMin, scalingGrid.yMax, boundsRect.yMax}; + double[] dst_y = new double[]{targetBounds.yMin, targetBounds.yMin + scalingGrid.yMin, targetBounds.yMax - (boundsRect.yMax - scalingGrid.yMax), targetBounds.yMax}; + + int pos = 0; + for (int sy = 0; sy < 3; sy++) { + for (int sx = 0; sx < 3; sx++) { + sourceRect[pos] = new ExportRectangle(src_x[sx], src_y[sy], src_x[sx + 1], src_y[sy + 1]); + targetRect[pos] = new ExportRectangle(dst_x[sx], dst_y[sy], dst_x[sx + 1], dst_y[sy + 1]); + pos++; + } + } + + for (int i = 0; i < targetRect.length; i++) { + + /* sourceRect[i].xMax = roundPixels20(sourceRect[i].xMax); + sourceRect[i].yMax = roundPixels20(sourceRect[i].yMax); + sourceRect[i].xMin = roundPixels20(sourceRect[i].xMin); + sourceRect[i].yMin = roundPixels20(sourceRect[i].yMin); + */ + //System.out.println("source[" + i + "]=" + sourceRect[i]); + //System.out.println("target[" + i + "]=" + targetRect[i]); + + /*targetRect[i].xMax = roundPixels20(targetRect[i].xMax); + targetRect[i].yMax = roundPixels20(targetRect[i].yMax); + targetRect[i].xMin = roundPixels20(targetRect[i].xMin); + targetRect[i].yMin = roundPixels20(targetRect[i].yMin); + */ + transforms[i] = rectToRectMatrix(sourceRect[i], targetRect[i]); + + targetRect[i].xMax = Math.rint(targetRect[i].xMax / SWF.unitDivisor); + targetRect[i].yMax = Math.rint(targetRect[i].yMax / SWF.unitDivisor); + targetRect[i].xMin = Math.rint(targetRect[i].xMin / SWF.unitDivisor); + targetRect[i].yMin = Math.rint(targetRect[i].yMin / SWF.unitDivisor); + + //targetRect[i].xMax += maxStroke; + //Round to pixel boundary + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java index d598f4246..50f248601 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DefineSoundTag.java @@ -1,371 +1,371 @@ -/* - * 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.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.SoundTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.decompiler.flash.types.sound.MP3FRAME; -import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; -import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; -import com.jpexs.decompiler.flash.types.sound.SoundFormat; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import javax.sound.sampled.AudioFormat; -import javax.sound.sampled.AudioInputStream; -import javax.sound.sampled.AudioSystem; -import javax.sound.sampled.UnsupportedAudioFileException; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 1) -public class DefineSoundTag extends CharacterTag implements SoundTag { - - public static final int ID = 14; - - public static final String NAME = "DefineSound"; - - @SWFType(BasicType.UI16) - public int soundId; - - @SWFType(value = BasicType.UB, count = 4) - public int soundFormat; - - @SWFType(value = BasicType.UB, count = 2) - public int soundRate; - - public boolean soundSize; - - public boolean soundType; - - @SWFType(BasicType.UI32) - public long soundSampleCount; - - public ByteArrayRange soundData; - - /** - * Constructor - * - * @param swf - */ - public DefineSoundTag(SWF swf) { - super(swf, ID, NAME, null); - soundId = swf.getNextCharacterId(); - soundData = ByteArrayRange.EMPTY; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DefineSoundTag(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 { - soundId = sis.readUI16("soundId"); - soundFormat = (int) sis.readUB(4, "soundFormat"); - soundRate = (int) sis.readUB(2, "soundRate"); - soundSize = sis.readUB(1, "soundSize") == 1; - soundType = sis.readUB(1, "soundType") == 1; - soundSampleCount = sis.readUI32("soundSampleCount"); - soundData = sis.readByteRangeEx(sis.available(), "soundData"); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(soundId); - sos.writeUB(4, soundFormat); - sos.writeUB(2, soundRate); - sos.writeUB(1, soundSize ? 1 : 0); - sos.writeUB(1, soundType ? 1 : 0); - sos.writeUI32(soundSampleCount); - sos.write(soundData); - } - - @Override - public int getCharacterId() { - return soundId; - } - - @Override - public void setCharacterId(int characterId) { - this.soundId = characterId; - } - - @Override - public SoundExportFormat getExportFormat() { - if (soundFormat == SoundFormat.FORMAT_MP3) { - return SoundExportFormat.MP3; - } - if (soundFormat == SoundFormat.FORMAT_ADPCM) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { - return SoundExportFormat.WAV; - } - if (soundFormat == SoundFormat.FORMAT_NELLYMOSER || soundFormat == SoundFormat.FORMAT_NELLYMOSER16KHZ || soundFormat == SoundFormat.FORMAT_NELLYMOSER8KHZ) { - return SoundExportFormat.WAV; - } - return SoundExportFormat.FLV; - } - - private void loadID3v2(InputStream in) { - int size = -1; - try { - // Read ID3v2 header (10 bytes). - in.mark(10); - size = readID3v2Header(in); - } catch (IOException e) { - } finally { - try { - // Unread ID3v2 header (10 bytes). - in.reset(); - } catch (IOException e) { - } - } - // Load ID3v2 tags. - try { - if (size > 0) { - byte[] rawid3v2 = new byte[size]; - in.read(rawid3v2, 0, rawid3v2.length); - } - } catch (IOException e) { - } - } - - /** - * Parse ID3v2 tag header to find out size of ID3v2 frames. - * - * @param in MP3 InputStream - * @return size of ID3v2 frames + header - * @throws IOException - * @author JavaZOOM - */ - private int readID3v2Header(InputStream in) throws IOException { - byte[] id3header = new byte[4]; - int size = -10; - in.read(id3header, 0, 3); - // Look for ID3v2 - if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { - in.read(id3header, 0, 3); - int majorVersion = id3header[0]; - int revision = id3header[1]; - in.read(id3header, 0, 4); - size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); - } - return (size + 10); - } - - @Override - public boolean setSound(InputStream is, int newSoundFormat) { - int newSoundRate = -1; - boolean newSoundSize = false; - boolean newSoundType = false; - long newSoundSampleCount = -1; - byte[] newSoundData; - switch (newSoundFormat) { - case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: - try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { - AudioFormat fmt = audioIs.getFormat(); - newSoundType = fmt.getChannels() == 2; - newSoundSize = fmt.getSampleSizeInBits() == 16; - newSoundSampleCount = audioIs.getFrameLength(); - newSoundData = Helper.readStream(audioIs); - newSoundRate = (int) Math.round(fmt.getSampleRate()); - switch (newSoundRate) { - case 5512: - newSoundRate = 0; - break; - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - return false; - } - } catch (UnsupportedAudioFileException | IOException ex) { - return false; - } - break; - case SoundFormat.FORMAT_MP3: - BufferedInputStream bis = new BufferedInputStream(is); - loadID3v2(bis); - byte[] mp3data = Helper.readStream(bis); - - final int ID3_V1_LENTGH = 128; - final int ID3_V1_EXT_LENGTH = 227; - - if (mp3data.length > ID3_V1_LENTGH) { - //ID3v1 - if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); - if (mp3data.length > ID3_V1_EXT_LENGTH) { - //ID3v1 extended - if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { - mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); - } - } - } - } - try { - MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); - if (!snd.frames.isEmpty()) { - MP3FRAME fr = snd.frames.get(0); - newSoundRate = fr.getSamplingRate(); - switch (newSoundRate) { - case 11025: - newSoundRate = 1; - break; - case 22050: - newSoundRate = 2; - break; - case 44100: - newSoundRate = 3; - break; - default: - return false; - } - - newSoundSize = true; - newSoundType = fr.isStereo(); - int len = snd.sampleCount(); - if (fr.isStereo()) { - len = len / 2; - } - - newSoundSampleCount = len; - } - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); - sos.writeSI16(0); //Latency - how to calculate it? - sos.write(mp3data); - newSoundData = baos.toByteArray(); - } catch (IOException ex) { - return false; - } - break; - default: - return false; - } - if (newSoundData != null) { - this.soundSize = newSoundSize; - this.soundRate = newSoundRate; - this.soundSampleCount = newSoundSampleCount; - this.soundData = new ByteArrayRange(newSoundData); - this.soundType = newSoundType; - this.soundFormat = newSoundFormat; - setModified(true); - return true; - } - return false; - - } - - @Override - public boolean importSupported() { - return true; - } - - @Override - public int getSoundRate() { - return soundRate; - } - - @Override - public boolean getSoundType() { - return soundType; - } - - @Override - public List getRawSoundData() { - List ret = new ArrayList<>(); - if (soundFormat == SoundFormat.FORMAT_MP3) { - ret.add(soundData.getSubRange(2, soundData.getLength() - 2)); - return ret; - } - - ret.add(soundData); - return ret; - } - - @Override - public int getSoundFormatId() { - return soundFormat; - } - - @Override - public long getTotalSoundSampleCount() { - return soundSampleCount; - } - - @Override - public boolean getSoundSize() { - return soundSize; - } - - @Override - public SoundFormat getSoundFormat() { - final int[] rateMap = {5512, 11025, 22050, 44100}; - return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); - } - - @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", soundSampleCount); - } -} +/* + * 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.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.SoundTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.decompiler.flash.types.sound.MP3FRAME; +import com.jpexs.decompiler.flash.types.sound.MP3SOUNDDATA; +import com.jpexs.decompiler.flash.types.sound.SoundExportFormat; +import com.jpexs.decompiler.flash.types.sound.SoundFormat; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 1) +public class DefineSoundTag extends CharacterTag implements SoundTag { + + public static final int ID = 14; + + public static final String NAME = "DefineSound"; + + @SWFType(BasicType.UI16) + public int soundId; + + @SWFType(value = BasicType.UB, count = 4) + public int soundFormat; + + @SWFType(value = BasicType.UB, count = 2) + public int soundRate; + + public boolean soundSize; + + public boolean soundType; + + @SWFType(BasicType.UI32) + public long soundSampleCount; + + public ByteArrayRange soundData; + + /** + * Constructor + * + * @param swf + */ + public DefineSoundTag(SWF swf) { + super(swf, ID, NAME, null); + soundId = swf.getNextCharacterId(); + soundData = ByteArrayRange.EMPTY; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DefineSoundTag(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 { + soundId = sis.readUI16("soundId"); + soundFormat = (int) sis.readUB(4, "soundFormat"); + soundRate = (int) sis.readUB(2, "soundRate"); + soundSize = sis.readUB(1, "soundSize") == 1; + soundType = sis.readUB(1, "soundType") == 1; + soundSampleCount = sis.readUI32("soundSampleCount"); + soundData = sis.readByteRangeEx(sis.available(), "soundData"); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(soundId); + sos.writeUB(4, soundFormat); + sos.writeUB(2, soundRate); + sos.writeUB(1, soundSize ? 1 : 0); + sos.writeUB(1, soundType ? 1 : 0); + sos.writeUI32(soundSampleCount); + sos.write(soundData); + } + + @Override + public int getCharacterId() { + return soundId; + } + + @Override + public void setCharacterId(int characterId) { + this.soundId = characterId; + } + + @Override + public SoundExportFormat getExportFormat() { + if (soundFormat == SoundFormat.FORMAT_MP3) { + return SoundExportFormat.MP3; + } + if (soundFormat == SoundFormat.FORMAT_ADPCM) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_UNCOMPRESSED_NATIVE_ENDIAN) { + return SoundExportFormat.WAV; + } + if (soundFormat == SoundFormat.FORMAT_NELLYMOSER || soundFormat == SoundFormat.FORMAT_NELLYMOSER16KHZ || soundFormat == SoundFormat.FORMAT_NELLYMOSER8KHZ) { + return SoundExportFormat.WAV; + } + return SoundExportFormat.FLV; + } + + private void loadID3v2(InputStream in) { + int size = -1; + try { + // Read ID3v2 header (10 bytes). + in.mark(10); + size = readID3v2Header(in); + } catch (IOException e) { + } finally { + try { + // Unread ID3v2 header (10 bytes). + in.reset(); + } catch (IOException e) { + } + } + // Load ID3v2 tags. + try { + if (size > 0) { + byte[] rawid3v2 = new byte[size]; + in.read(rawid3v2, 0, rawid3v2.length); + } + } catch (IOException e) { + } + } + + /** + * Parse ID3v2 tag header to find out size of ID3v2 frames. + * + * @param in MP3 InputStream + * @return size of ID3v2 frames + header + * @throws IOException + * @author JavaZOOM + */ + private int readID3v2Header(InputStream in) throws IOException { + byte[] id3header = new byte[4]; + int size = -10; + in.read(id3header, 0, 3); + // Look for ID3v2 + if ((id3header[0] == 'I') && (id3header[1] == 'D') && (id3header[2] == '3')) { + in.read(id3header, 0, 3); + int majorVersion = id3header[0]; + int revision = id3header[1]; + in.read(id3header, 0, 4); + size = (int) (id3header[0] << 21) + (id3header[1] << 14) + (id3header[2] << 7) + (id3header[3]); + } + return (size + 10); + } + + @Override + public boolean setSound(InputStream is, int newSoundFormat) { + int newSoundRate = -1; + boolean newSoundSize = false; + boolean newSoundType = false; + long newSoundSampleCount = -1; + byte[] newSoundData; + switch (newSoundFormat) { + case SoundFormat.FORMAT_UNCOMPRESSED_LITTLE_ENDIAN: + try (AudioInputStream audioIs = AudioSystem.getAudioInputStream(new BufferedInputStream(is))) { + AudioFormat fmt = audioIs.getFormat(); + newSoundType = fmt.getChannels() == 2; + newSoundSize = fmt.getSampleSizeInBits() == 16; + newSoundSampleCount = audioIs.getFrameLength(); + newSoundData = Helper.readStream(audioIs); + newSoundRate = (int) Math.round(fmt.getSampleRate()); + switch (newSoundRate) { + case 5512: + newSoundRate = 0; + break; + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + return false; + } + } catch (UnsupportedAudioFileException | IOException ex) { + return false; + } + break; + case SoundFormat.FORMAT_MP3: + BufferedInputStream bis = new BufferedInputStream(is); + loadID3v2(bis); + byte[] mp3data = Helper.readStream(bis); + + final int ID3_V1_LENTGH = 128; + final int ID3_V1_EXT_LENGTH = 227; + + if (mp3data.length > ID3_V1_LENTGH) { + //ID3v1 + if (mp3data[mp3data.length - ID3_V1_LENTGH] == 'T' && mp3data[mp3data.length - ID3_V1_LENTGH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_LENTGH + 2] == 'G') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_LENTGH); + if (mp3data.length > ID3_V1_EXT_LENGTH) { + //ID3v1 extended + if (mp3data[mp3data.length - ID3_V1_EXT_LENGTH] == 'T' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 1] == 'A' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 2] == 'G' && mp3data[mp3data.length - ID3_V1_EXT_LENGTH + 3] == '+') { + mp3data = Arrays.copyOf(mp3data, mp3data.length - ID3_V1_EXT_LENGTH); + } + } + } + } + try { + MP3SOUNDDATA snd = new MP3SOUNDDATA(new SWFInputStream(swf, mp3data), true); + if (!snd.frames.isEmpty()) { + MP3FRAME fr = snd.frames.get(0); + newSoundRate = fr.getSamplingRate(); + switch (newSoundRate) { + case 11025: + newSoundRate = 1; + break; + case 22050: + newSoundRate = 2; + break; + case 44100: + newSoundRate = 3; + break; + default: + return false; + } + + newSoundSize = true; + newSoundType = fr.isStereo(); + int len = snd.sampleCount(); + if (fr.isStereo()) { + len = len / 2; + } + + newSoundSampleCount = len; + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + SWFOutputStream sos = new SWFOutputStream(baos, SWF.DEFAULT_VERSION); + sos.writeSI16(0); //Latency - how to calculate it? + sos.write(mp3data); + newSoundData = baos.toByteArray(); + } catch (IOException ex) { + return false; + } + break; + default: + return false; + } + if (newSoundData != null) { + this.soundSize = newSoundSize; + this.soundRate = newSoundRate; + this.soundSampleCount = newSoundSampleCount; + this.soundData = new ByteArrayRange(newSoundData); + this.soundType = newSoundType; + this.soundFormat = newSoundFormat; + setModified(true); + return true; + } + return false; + + } + + @Override + public boolean importSupported() { + return true; + } + + @Override + public int getSoundRate() { + return soundRate; + } + + @Override + public boolean getSoundType() { + return soundType; + } + + @Override + public List getRawSoundData() { + List ret = new ArrayList<>(); + if (soundFormat == SoundFormat.FORMAT_MP3) { + ret.add(soundData.getSubRange(2, soundData.getLength() - 2)); + return ret; + } + + ret.add(soundData); + return ret; + } + + @Override + public int getSoundFormatId() { + return soundFormat; + } + + @Override + public long getTotalSoundSampleCount() { + return soundSampleCount; + } + + @Override + public boolean getSoundSize() { + return soundSize; + } + + @Override + public SoundFormat getSoundFormat() { + final int[] rateMap = {5512, 11025, 22050, 44100}; + return new SoundFormat(getSoundFormatId(), rateMap[getSoundRate()], getSoundType()); + } + + @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", soundSampleCount); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java index c3bbcab53..f4c6fec5c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoABC2Tag.java @@ -1,146 +1,146 @@ -/* - * 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.abc.ABC; -import com.jpexs.decompiler.flash.abc.ABCInputStream; -import com.jpexs.decompiler.flash.abc.types.ScriptInfo; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.SWFField; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.decompiler.flash.types.annotations.SWFVersion; -import com.jpexs.helpers.ByteArrayRange; -import java.io.IOException; - -/** - * Defines a series of ActionScript 3 bytecodes to be executed - * - * @author JPEXS - */ -@SWFVersion(from = 9) -public class DoABC2Tag extends Tag implements ABCContainerTag { - - public static final int ID = 82; - - public static final String NAME = "DoABC2"; - - /** - * ActionScript 3 bytecodes - */ - @HideInRawEdit - @SWFField - private ABC abc; - - /** - * A 32-bit flags value, which may contain the following bits set: - * kDoAbcLazyInitializeFlag = 1: Indicates that the ABC block should not be - * executed immediately, but only parsed. A later finddef may cause its - * scripts to execute. - */ - @SWFType(BasicType.UI32) - public long flags; - - /** - * The name assigned to the bytecode. - */ - public String name; - - /** - * Constructor - * - * @param swf - */ - public DoABC2Tag(SWF swf) { - super(swf, ID, NAME, null); - name = "New DoABC"; - abc = new ABC(this); - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DoABC2Tag(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 { - flags = sis.readUI32("flags"); - name = sis.readString("name"); - - ABCInputStream ais = new ABCInputStream(sis.getBaseStream()); - - // put it to the dumpview: - sis.readByteRangeEx(sis.available(), "abcBytes", DumpInfoSpecialType.ABC_BYTES, null); - abc = new ABC(ais, swf, this); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI32(flags); - sos.writeString(name); - abc.saveToStream(sos); - } - - @Override - public ABC getABC() { - return abc; - } - - @Override - public String getName() { - return super.getName() + " (" + name + ")"; - } - - @Override - public int compareTo(ABCContainerTag o) { - if (o instanceof DoABC2Tag) { - DoABC2Tag n = (DoABC2Tag) o; - int lastCmp = name.compareTo(n.name); - return (lastCmp != 0 ? lastCmp - : name.compareTo(n.name)); - } - return 0; - } - - @Override - public void setModified(boolean value) { - super.setModified(value); - if (value == false && !isModified()) { - ABC abc = getABC(); - for (ScriptInfo si : abc.script_info) { - si.setModified(false); - } - } - } -} +/* + * 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.abc.ABC; +import com.jpexs.decompiler.flash.abc.ABCInputStream; +import com.jpexs.decompiler.flash.abc.types.ScriptInfo; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.SWFField; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.decompiler.flash.types.annotations.SWFVersion; +import com.jpexs.helpers.ByteArrayRange; +import java.io.IOException; + +/** + * Defines a series of ActionScript 3 bytecodes to be executed + * + * @author JPEXS + */ +@SWFVersion(from = 9) +public class DoABC2Tag extends Tag implements ABCContainerTag { + + public static final int ID = 82; + + public static final String NAME = "DoABC2"; + + /** + * ActionScript 3 bytecodes + */ + @HideInRawEdit + @SWFField + private ABC abc; + + /** + * A 32-bit flags value, which may contain the following bits set: + * kDoAbcLazyInitializeFlag = 1: Indicates that the ABC block should not be + * executed immediately, but only parsed. A later finddef may cause its + * scripts to execute. + */ + @SWFType(BasicType.UI32) + public long flags; + + /** + * The name assigned to the bytecode. + */ + public String name; + + /** + * Constructor + * + * @param swf + */ + public DoABC2Tag(SWF swf) { + super(swf, ID, NAME, null); + name = "New DoABC"; + abc = new ABC(this); + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DoABC2Tag(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 { + flags = sis.readUI32("flags"); + name = sis.readString("name"); + + ABCInputStream ais = new ABCInputStream(sis.getBaseStream()); + + // put it to the dumpview: + sis.readByteRangeEx(sis.available(), "abcBytes", DumpInfoSpecialType.ABC_BYTES, null); + abc = new ABC(ais, swf, this); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI32(flags); + sos.writeString(name); + abc.saveToStream(sos); + } + + @Override + public ABC getABC() { + return abc; + } + + @Override + public String getName() { + return super.getName() + " (" + name + ")"; + } + + @Override + public int compareTo(ABCContainerTag o) { + if (o instanceof DoABC2Tag) { + DoABC2Tag n = (DoABC2Tag) o; + int lastCmp = name.compareTo(n.name); + return (lastCmp != 0 ? lastCmp + : name.compareTo(n.name)); + } + return 0; + } + + @Override + public void setModified(boolean value) { + super.setModified(value); + if (value == false && !isModified()) { + ABC abc = getABC(); + for (ScriptInfo si : abc.script_info) { + si.setModified(false); + } + } + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java index 33e47dc58..890b9a70c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/tags/DoInitActionTag.java @@ -1,253 +1,253 @@ -/* - * 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.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.SWFOutputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.types.BasicType; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -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.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * - * @author JPEXS - */ -@SWFVersion(from = 6) -public class DoInitActionTag extends Tag implements CharacterIdTag, ASMSource { - - public static final int ID = 59; - - public static final String NAME = "DoInitAction"; - - /** - * Identifier of Sprite - */ - @SWFType(BasicType.UI16) - public int spriteId = 0; - - /** - * List of actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - @Internal - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - /** - * Constructor - * - * @param swf - */ - public DoInitActionTag(SWF swf) { - super(swf, ID, NAME, null); - actionBytes = ByteArrayRange.EMPTY; - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - /** - * Constructor - * - * @param sis - * @param data - * @throws IOException - */ - public DoInitActionTag(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 { - spriteId = sis.readUI16("spriteId"); - actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); - } - - /** - * Gets data bytes - * - * @param sos SWF output stream - * @throws java.io.IOException - */ - @Override - public void getData(SWFOutputStream sos) throws IOException { - sos.writeUI16(spriteId); - sos.write(getActionBytes()); - //sos.write(Action.actionsToBytes(actions, true, version)); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - setModified(true); - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - @Override - public int getCharacterId() { - return spriteId; - } - - @Override - public void setCharacterId(int characterId) { - this.spriteId = characterId; - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - @Override - public String getExportFileName() { - String expName = swf == null ? "" : swf.getExportName(spriteId); - if (expName == null || expName.isEmpty()) { - return super.getExportFileName(); - } - String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - return pathParts[pathParts.length - 1]; - } - - @Override - public String getName() { - String expName = swf == null ? "" : swf.getExportName(spriteId); - if (expName == null || expName.isEmpty()) { - return super.getName(); - } - String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; - return pathParts[pathParts.length - 1]; - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - return writer; - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - return writer; - } - - @Override - public int getPrefixLineCount() { - return 0; - } - - @Override - public String removePrefixAndSuffix(String source) { - return source; - } - - @Override - public Tag getSourceTag() { - return this; - } - - @Override - public void setSourceTag(Tag t) { - //nothing - } -} +/* + * 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.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.SWFOutputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +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.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author JPEXS + */ +@SWFVersion(from = 6) +public class DoInitActionTag extends Tag implements CharacterIdTag, ASMSource { + + public static final int ID = 59; + + public static final String NAME = "DoInitAction"; + + /** + * Identifier of Sprite + */ + @SWFType(BasicType.UI16) + public int spriteId = 0; + + /** + * List of actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + @Internal + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + /** + * Constructor + * + * @param swf + */ + public DoInitActionTag(SWF swf) { + super(swf, ID, NAME, null); + actionBytes = ByteArrayRange.EMPTY; + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + /** + * Constructor + * + * @param sis + * @param data + * @throws IOException + */ + public DoInitActionTag(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 { + spriteId = sis.readUI16("spriteId"); + actionBytes = sis.readByteRangeEx(sis.available(), "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + /** + * Gets data bytes + * + * @param sos SWF output stream + * @throws java.io.IOException + */ + @Override + public void getData(SWFOutputStream sos) throws IOException { + sos.writeUI16(spriteId); + sos.write(getActionBytes()); + //sos.write(Action.actionsToBytes(actions, true, version)); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + setModified(true); + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + @Override + public int getCharacterId() { + return spriteId; + } + + @Override + public void setCharacterId(int characterId) { + this.spriteId = characterId; + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + @Override + public String getExportFileName() { + String expName = swf == null ? "" : swf.getExportName(spriteId); + if (expName == null || expName.isEmpty()) { + return super.getExportFileName(); + } + String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; + return pathParts[pathParts.length - 1]; + } + + @Override + public String getName() { + String expName = swf == null ? "" : swf.getExportName(spriteId); + if (expName == null || expName.isEmpty()) { + return super.getName(); + } + String[] pathParts = expName.contains(".") ? expName.split("\\.") : new String[]{expName}; + return pathParts[pathParts.length - 1]; + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + return writer; + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + return writer; + } + + @Override + public int getPrefixLineCount() { + return 0; + } + + @Override + public String removePrefixAndSuffix(String source) { + return source; + } + + @Override + public Tag getSourceTag() { + return this; + } + + @Override + public void setSourceTag(Tag t) { + //nothing + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java index 5c7162cf6..f0144e696 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/timeline/Timeline.java @@ -1,1210 +1,1210 @@ -/* - * 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.timeline; - -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.exporters.FrameExporter; -import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; -import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; -import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; -import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; -import com.jpexs.decompiler.flash.tags.DefineSpriteTag; -import com.jpexs.decompiler.flash.tags.DoActionTag; -import com.jpexs.decompiler.flash.tags.DoInitActionTag; -import com.jpexs.decompiler.flash.tags.FrameLabelTag; -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.StartSound2Tag; -import com.jpexs.decompiler.flash.tags.StartSoundTag; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; -import com.jpexs.decompiler.flash.tags.base.BoundedTag; -import com.jpexs.decompiler.flash.tags.base.ButtonTag; -import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; -import com.jpexs.decompiler.flash.tags.base.CharacterTag; -import com.jpexs.decompiler.flash.tags.base.DrawableTag; -import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; -import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; -import com.jpexs.decompiler.flash.tags.base.RemoveTag; -import com.jpexs.decompiler.flash.tags.base.RenderContext; -import com.jpexs.decompiler.flash.tags.base.ShapeTag; -import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; -import com.jpexs.decompiler.flash.tags.base.TextTag; -import com.jpexs.decompiler.flash.types.CLIPACTIONS; -import com.jpexs.decompiler.flash.types.ColorTransform; -import com.jpexs.decompiler.flash.types.MATRIX; -import com.jpexs.decompiler.flash.types.RECT; -import com.jpexs.decompiler.flash.types.filters.BlendComposite; -import com.jpexs.decompiler.flash.types.filters.FILTER; -import com.jpexs.helpers.Helper; -import com.jpexs.helpers.SerializableImage; -import java.awt.AlphaComposite; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.Shape; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.Rectangle2D; -import java.awt.image.BufferedImage; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.Stack; -import org.w3c.dom.Element; - -/** - * - * @author JPEXS - */ -public class Timeline { - - public int id; - - public SWF swf; - - public RECT displayRect; - - public float frameRate; - - public Timelined timelined; - - public int maxDepth; - - public int fontFrameNum = -1; - - private final List frames = new ArrayList<>(); - - private final Map depthMaxFrame = new HashMap<>(); - - private final List asmSources = new ArrayList<>(); - - private final List asmSourceContainers = new ArrayList<>(); - - private final Map actionFrames = new HashMap<>(); - - private final Map> soundStramBlocks = new HashMap<>(); - - private AS2Package as2RootPackage; - - public final List otherTags = new ArrayList<>(); - - private boolean initialized = false; - - private Map labelToFrame = new HashMap<>(); - - private void ensureInitialized() { - if (!initialized) { - initialize(); - initialized = true; - } - } - - public List getFrames() { - ensureInitialized(); - return frames; - } - - public Frame getFrame(int index) { - ensureInitialized(); - if (index >= frames.size()) { - return null; - } - return frames.get(index); - } - - public void addFrame(Frame frame) { - ensureInitialized(); - frames.add(frame); - maxDepth = getMaxDepthInternal(); - calculateMaxDepthFrames(); - } - - public AS2Package getAS2RootPackage() { - ensureInitialized(); - return as2RootPackage; - } - - public Map getDepthMaxFrame() { - ensureInitialized(); - return depthMaxFrame; - } - - public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { - ensureInitialized(); - return soundStramBlocks.get(head); - } - - public Tag getParentTag() { - return timelined instanceof Tag ? (Tag) timelined : null; - } - - public void reset(SWF swf) { - reset(swf, swf, 0, swf.displayRect); - } - - public void reset(SWF swf, Timelined timelined, int id, RECT displayRect) { - initialized = false; - frames.clear(); - depthMaxFrame.clear(); - asmSources.clear(); - asmSourceContainers.clear(); - actionFrames.clear(); - soundStramBlocks.clear(); - otherTags.clear(); - this.id = id; - this.swf = swf; - this.displayRect = displayRect; - this.frameRate = swf.frameRate; - this.timelined = timelined; - as2RootPackage = new AS2Package(null, null, swf); - } - - public final int getMaxDepth() { - ensureInitialized(); - return maxDepth; - } - - private int getMaxDepthInternal() { - int max_depth = 0; - for (Frame f : frames) { - for (int depth : f.layers.keySet()) { - if (depth > max_depth) { - max_depth = depth; - } - int clipDepth = f.layers.get(depth).clipDepth; - if (clipDepth > max_depth) { - max_depth = clipDepth; - } - } - } - return max_depth; - } - - public int getFrameCount() { - ensureInitialized(); - return frames.size(); - } - - public int getRealFrameCount() { - ensureInitialized(); - - int cnt = 1; - for (int i = 1; i < frames.size(); i++) { - if (!frames.get(i).actions.isEmpty()) { - cnt++; - continue; - } - if (frames.get(i).layersChanged) { - cnt++; - } - } - - return cnt; - } - - public int getFrameForAction(ASMSource asm) { - Integer frame = actionFrames.get(asm); - if (frame == null) { - return -1; - } - - return frame; - } - - public Timeline(SWF swf) { - this(swf, swf, 0, swf.displayRect); - } - - public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect) { - this.id = id; - this.swf = swf; - this.displayRect = displayRect; - this.frameRate = swf.frameRate; - this.timelined = timelined; - as2RootPackage = new AS2Package(null, null, swf); - } - - public int getFrameWithLabel(String label) { - if (labelToFrame.containsKey(label)) { - return labelToFrame.get(label); - } - return -1; - } - - private void initialize() { - int frameIdx = 0; - Frame frame = new Frame(this, frameIdx++); - frame.layersChanged = true; - boolean newFrameNeeded = false; - for (Tag t : timelined.getTags()) { - boolean isNested = ShowFrameTag.isNestedTagType(t.getId()); - if (isNested) { - newFrameNeeded = true; - frame.innerTags.add(t); - } - - if (t instanceof ASMSourceContainer) { - ASMSourceContainer asmSourceContainer = (ASMSourceContainer) t; - if (!asmSourceContainer.getSubItems().isEmpty()) { - if (isNested) { - frame.actionContainers.add(asmSourceContainer); - } else { - asmSourceContainers.add(asmSourceContainer); - } - } - } - - if (t instanceof FrameLabelTag) { - newFrameNeeded = true; - frame.label = ((FrameLabelTag) t).getLabelName(); - frame.namedAnchor = ((FrameLabelTag) t).isNamedAnchor(); - labelToFrame.put(frame.label, frames.size()); - } else if (t instanceof StartSoundTag) { - newFrameNeeded = true; - frame.sounds.add(((StartSoundTag) t).soundId); - } else if (t instanceof StartSound2Tag) { - newFrameNeeded = true; - frame.soundClasses.add(((StartSound2Tag) t).soundClassName); - } else if (t instanceof SetBackgroundColorTag) { - newFrameNeeded = true; - frame.backgroundColor = ((SetBackgroundColorTag) t).backgroundColor; - } else if (t instanceof PlaceObjectTypeTag) { - newFrameNeeded = true; - PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; - int depth = po.getDepth(); - DepthState fl = frame.layers.get(depth); - if (fl == null) { - frame.layers.put(depth, fl = new DepthState(swf, frame)); - } - frame.layersChanged = true; - fl.placeObjectTag = po; - fl.minPlaceObjectNum = Math.max(fl.minPlaceObjectNum, po.getPlaceObjectNum()); - int characterId = po.getCharacterId(); - if (characterId != -1) { - fl.characterId = characterId; - } - if (po.flagMove()) { - MATRIX matrix2 = po.getMatrix(); - if (matrix2 != null) { - fl.matrix = matrix2; - } - String instanceName2 = po.getInstanceName(); - if (instanceName2 != null) { - fl.instanceName = instanceName2; - } - ColorTransform colorTransForm2 = po.getColorTransform(); - if (colorTransForm2 != null) { - fl.colorTransForm = colorTransForm2; - } - - CLIPACTIONS clipActions2 = po.getClipActions(); - if (clipActions2 != null) { - fl.clipActions = clipActions2; - } - if (po.cacheAsBitmap()) { - fl.cacheAsBitmap = true; - } - int blendMode2 = po.getBlendMode(); - if (blendMode2 > 0) { - fl.blendMode = blendMode2; - } - List filters2 = po.getFilters(); - if (filters2 != null) { - fl.filters = filters2; - } - int ratio2 = po.getRatio(); - if (ratio2 > -1) { - fl.ratio = ratio2; - } - int clipDepth2 = po.getClipDepth(); - if (clipDepth2 > -1) { - fl.clipDepth = clipDepth2; - } - } else { - fl.matrix = po.getMatrix(); - fl.instanceName = po.getInstanceName(); - fl.colorTransForm = po.getColorTransform(); - fl.cacheAsBitmap = po.cacheAsBitmap(); - fl.blendMode = po.getBlendMode(); - fl.filters = po.getFilters(); - fl.ratio = po.getRatio(); - fl.clipActions = po.getClipActions(); - fl.clipDepth = po.getClipDepth(); - } - fl.key = characterId != -1; - } else if (t instanceof RemoveTag) { - newFrameNeeded = true; - RemoveTag r = (RemoveTag) t; - int depth = r.getDepth(); - frame.layers.remove(depth); - frame.layersChanged = true; - } else if (t instanceof DoActionTag) { - newFrameNeeded = true; - frame.actions.add((DoActionTag) t); - actionFrames.put((DoActionTag) t, frame.frame); - } else if (t instanceof ShowFrameTag) { - frame.showFrameTag = (ShowFrameTag) t; - frames.add(frame); - frame = new Frame(frame, frameIdx++); - newFrameNeeded = false; - } else if (t instanceof ASMSource) { - asmSources.add((ASMSource) t); - } else { - otherTags.add(t); - } - } - if (newFrameNeeded) { - frames.add(frame); - } - - maxDepth = getMaxDepthInternal(); - - detectTweens(); - calculateMaxDepthFrames(); - - createASPackages(); - if (timelined instanceof SWF) { - // popuplate only for main timeline - populateSoundStreamBlocks(0, timelined.getTags()); - } - - initialized = true; - } - - private void detectTweens() { - for (int d = 1; d <= maxDepth; d++) { - int characterId = -1; - int len = 0; - for (int f = 0; f <= frames.size(); f++) { - DepthState ds = f >= frames.size() ? null : frames.get(f).layers.get(d); - - if (ds != null && characterId != -1 && ds.characterId == characterId) { - len++; - } else { - if (characterId != -1) { - int startPos = f - len; - List matrices = new ArrayList<>(len); - for (int k = 0; k < len; k++) { - matrices.add(frames.get(startPos + k).layers.get(d)); - } - - List ranges = TweenDetector.detectRanges(matrices); - for (TweenRange r : ranges) { - for (int t = r.startPosition; t <= r.endPosition; t++) { - DepthState layer = frames.get(startPos + t).layers.get(d); - layer.motionTween = true; - layer.key = false; - } - - frames.get(startPos + r.startPosition).layers.get(d).key = true; - } - } - - len = 1; - } - - characterId = ds == null ? -1 : ds.characterId; - } - } - } - - private void calculateMaxDepthFrames() { - depthMaxFrame.clear(); - for (int d = 1; d <= maxDepth; d++) { - for (int f = frames.size() - 1; f >= 0; f--) { - if (frames.get(f).layers.get(d) != null) { - depthMaxFrame.put(d, f); - break; - } - } - } - } - - private void createASPackages() { - for (ASMSource asm : asmSources) { - if (asm instanceof DoInitActionTag) { - DoInitActionTag initAction = (DoInitActionTag) asm; - String path = swf.getExportName(initAction.spriteId); - path = path != null ? path : "_unk_"; - if (path.isEmpty()) { - path = initAction.getExportFileName(); - } - - String[] pathParts = path.contains(".") ? path.split("\\.") : new String[]{path}; - AS2Package pkg = as2RootPackage; - for (int pos = 0; pos < pathParts.length - 1; pos++) { - String pathPart = pathParts[pos]; - AS2Package subPkg = pkg.subPackages.get(pathPart); - if (subPkg == null) { - subPkg = new AS2Package(pathPart, pkg, swf); - pkg.subPackages.put(pathPart, subPkg); - } - - pkg = subPkg; - } - - pkg.scripts.put(pathParts[pathParts.length - 1], asm); - } - } - } - - private void populateSoundStreamBlocks(int containerId, Iterable tags) { - List blocks = null; - for (Tag t : tags) { - if (t instanceof SoundStreamHeadTypeTag) { - SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; - head.setVirtualCharacterId(containerId); - blocks = new ArrayList<>(); - soundStramBlocks.put(head, blocks); - continue; - } - - if (t instanceof DefineSpriteTag) { - DefineSpriteTag sprite = (DefineSpriteTag) t; - populateSoundStreamBlocks(sprite.getCharacterId(), sprite.getTags()); - } - - if (blocks == null) { - continue; - } - - if (t instanceof SoundStreamBlockTag) { - blocks.add((SoundStreamBlockTag) t); - } - } - } - - public void getNeededCharacters(Set usedCharacters) { - for (int i = 0; i < getFrameCount(); i++) { - getNeededCharacters(i, usedCharacters); - } - } - - public void getNeededCharacters(List frames, Set usedCharacters) { - for (int frame = 0; frame < getFrameCount(); frame++) { - getNeededCharacters(frame, usedCharacters); - } - } - - public void getNeededCharacters(int frame, Set usedCharacters) { - Frame frameObj = getFrame(frame); - for (int depth : frameObj.layers.keySet()) { - DepthState layer = frameObj.layers.get(depth); - if (layer.characterId != -1) { - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - usedCharacters.add(layer.characterId); - swf.getCharacter(layer.characterId).getNeededCharactersDeep(usedCharacters); - } - } - } - - public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { - boolean modified = false; - for (int i = 0; i < timelined.getTags().size(); i++) { - Tag t = timelined.getTags().get(i); - if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == oldCharacterId) { - ((CharacterIdTag) t).setCharacterId(newCharacterId); - ((Tag) t).setModified(true); - modified = true; - } - } - return modified; - } - - public boolean removeCharacter(int characterId) { - boolean modified = false; - for (int i = 0; i < timelined.getTags().size(); i++) { - Tag t = timelined.getTags().get(i); - if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == characterId) { - timelined.removeTag(i); - i--; - modified = true; - } - } - return modified; - } - - public double roundToPixel(double val) { - return Math.rint(val / SWF.unitDivisor) * SWF.unitDivisor; - } - - /*public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { - ExportRectangle scalingGrid = null; - if (timelined instanceof CharacterTag) { - DefineScalingGridTag sgt = ((CharacterTag) timelined).getScalingGridTag(); - if (sgt != null) { - scalingGrid = new ExportRectangle(sgt.splitter); - } - } - - if (scalingGrid == null || transformation.rotateSkew0 != 0 || transformation.rotateSkew1 != 0) { - toImage(frame, time, renderContext, image, isClip, transformation, absoluteTransformation, colorTransform, null); - return; - } - - //9-slice scaling using DefineScalingGrid - Matrix diffTransform = prevTransformation.inverse().preConcatenate(transformation); - transformation = diffTransform; - - Matrix prevScale = new Matrix(); - prevScale.scaleX = prevTransformation.scaleX; - prevScale.scaleY = prevTransformation.scaleY; - - ExportRectangle boundsRect = new ExportRectangle(timelined.getRect()); - - - 0 | 1 | 2 - ------------ - 3 | 4 | 5 - ------------ - 6 | 7 | 8 - - ExportRectangle[] targetRect = new ExportRectangle[9]; - ExportRectangle[] sourceRect = new ExportRectangle[9]; - Matrix[] transforms = new Matrix[9]; - - DefineScalingGridTag.getSlices(transformation, prevScale, boundsRect, scalingGrid, sourceRect, targetRect, transforms); - - for (int i = 0; i < targetRect.length; i++) { - toImage(frame, time, renderContext, image, isClip, transforms[i], absoluteTransformation, colorTransform, targetRect[i]); - } - }*/ - private void drawDrawable(Matrix strokeTransform, DepthState layer, Matrix layerMatrix, Graphics2D g, ColorTransform colorTransForm, int blendMode, List clips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List filters, double unzoom, ColorTransform clrTrans) { - Matrix drawMatrix = new Matrix(); - int drawableFrameCount = drawable.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - - RECT boundRect = drawable.getRect(); - - ExportRectangle rect = new ExportRectangle(boundRect); - Matrix mat = transformation.concatenate(layerMatrix); - rect = mat.transform(rect); - - boolean cacheAsBitmap = layer.cacheAsBitmap() && layer.placeObjectTag != null && drawable.isSingleFrame(); - /* // draw bounds - AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); - g.setTransform(trans); - BoundedTag b = (BoundedTag) drawable; - g.setPaint(new Color(255, 255, 255, 128)); - g.setComposite(BlendComposite.Invert); - g.setStroke(new BasicStroke((int) SWF.unitDivisor)); - RECT r = b.getRect(); - g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); - g.drawString(drawable.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); - g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); - g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); - g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); - g.setComposite(AlphaComposite.Dst);*/ - - SerializableImage img = null; - if (cacheAsBitmap && renderContext.displayObjectCache != null) { - img = renderContext.displayObjectCache.get(layer.placeObjectTag); - } - - int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size(); - int dframe; - if (fontFrameNum != -1) { - dframe = fontFrameNum; - } else { - dframe = time % drawableFrameCount; - } - - if (filters != null && filters.size() > 0) { - // calculate size after applying the filters - double deltaXMax = 0; - double deltaYMax = 0; - for (FILTER filter : filters) { - double x = filter.getDeltaX(); - double y = filter.getDeltaY(); - deltaXMax = Math.max(x, deltaXMax); - deltaYMax = Math.max(y, deltaYMax); - } - rect.xMin -= deltaXMax * unzoom; - rect.xMax += deltaXMax * unzoom; - rect.yMin -= deltaYMax * unzoom; - rect.yMax += deltaYMax * unzoom; - } - - rect.xMin -= unzoom; - rect.yMin -= unzoom; - rect.xMin = Math.max(0, rect.xMin); - rect.yMin = Math.max(0, rect.yMin); - drawMatrix.translate(rect.xMin, rect.yMin); - - if (img == null) { - int newWidth = (int) (rect.getWidth() / unzoom); - int newHeight = (int) (rect.getHeight() / unzoom); - int deltaX = (int) (rect.xMin / unzoom); - int deltaY = (int) (rect.yMin / unzoom); - newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; - newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; - - if (newWidth <= 0 || newHeight <= 0) { - return; - } - - Matrix m = mat.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); - //strokeTransform = strokeTransform.clone(); - //strokeTransform.translate(-rect.xMin, -rect.yMin); - - if (drawable instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (renderContext.cursorPosition != null) { - Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, ratio, renderContext, absMat, true); - if (buttonShape.contains(renderContext.cursorPosition)) { - renderContext.mouseOverButton = (ButtonTag) drawable; - if (renderContext.mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - } - - img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); - img.fillTransparent(); - - drawable.toImage(dframe, time, ratio, renderContext, img, isClip || clipDepth > -1, m, strokeTransform, absMat, clrTrans); - - if (filters != null) { - for (FILTER filter : filters) { - img = filter.apply(img); - } - } - if (blendMode > 1) { - if (colorTransForm != null) { - img = colorTransForm.apply(img); - } - } - - if (cacheAsBitmap && renderContext.displayObjectCache != null) { - renderContext.displayObjectCache.put(layer.placeObjectTag, img); - } - } - - drawMatrix.translateX /= unzoom; - drawMatrix.translateY /= unzoom; - AffineTransform trans = drawMatrix.toTransform(); - - switch (blendMode) { - case 0: - case 1: - g.setComposite(AlphaComposite.SrcOver); - break; - case 2: // Layer - g.setComposite(AlphaComposite.SrcOver); - break; - case 3: - g.setComposite(BlendComposite.Multiply); - break; - case 4: - g.setComposite(BlendComposite.Screen); - break; - case 5: - g.setComposite(BlendComposite.Lighten); - break; - case 6: - g.setComposite(BlendComposite.Darken); - break; - case 7: - g.setComposite(BlendComposite.Difference); - break; - case 8: - g.setComposite(BlendComposite.Add); - break; - case 9: - g.setComposite(BlendComposite.Subtract); - break; - case 10: - g.setComposite(BlendComposite.Invert); - break; - case 11: - g.setComposite(BlendComposite.Alpha); - break; - case 12: - g.setComposite(BlendComposite.Erase); - break; - case 13: - g.setComposite(BlendComposite.Overlay); - break; - case 14: - g.setComposite(BlendComposite.HardLight); - break; - default: // Not implemented - g.setComposite(AlphaComposite.SrcOver); - break; - } - - if (clipDepth > -1) { - BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); - Graphics2D gm = (Graphics2D) mask.getGraphics(); - gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); - gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gm.setComposite(AlphaComposite.Src); - gm.setColor(new Color(0, 0, 0, 0f)); - gm.fillRect(0, 0, image.getWidth(), image.getHeight()); - gm.setTransform(trans); - gm.drawImage(img.getBufferedImage(), 0, 0, null); - Clip clip = new Clip(Helper.imageToShape(mask), clipDepth); // Maybe we can get current outline instead converting from image (?) - clips.add(clip); - } else { - if (renderContext.cursorPosition != null) { - if (drawable instanceof DefineSpriteTag) { - if (renderContext.stateUnderCursor.size() > stateCount) { - renderContext.stateUnderCursor.add(layer); - } - } else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) { - Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat, true); - if (shape.contains(renderContext.cursorPosition)) { - renderContext.stateUnderCursor.add(layer); - } - } - } - - if (renderContext.borderImage != null) { - Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics(); - g2.setPaint(Color.red); - g2.setStroke(new BasicStroke(2)); - Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)), true); - g2.draw(shape); - } - - g.setTransform(trans); - g.drawImage(img.getBufferedImage(), 0, 0, null); - } - } - - public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { - double unzoom = SWF.unitDivisor; - if (getFrameCount() <= frame) { - return; - } - - Frame frameObj = getFrame(frame); - Graphics2D g = (Graphics2D) image.getGraphics(); - Shape prevClip = g.getClip(); - //if (targetRect != null) { - // g.setClip(new Rectangle2D.Double(targetRect.xMin, targetRect.yMin, targetRect.getWidth(), targetRect.getHeight())); - //} - - g.setPaint(frameObj.backgroundColor.toColor()); - g.fill(new Rectangle(image.getWidth(), image.getHeight())); - - g.setTransform(transformation.toTransform()); - List clips = new ArrayList<>(); - - int maxDepth = getMaxDepth(); - int clipCount = 0; - for (int i = 1; i <= maxDepth; i++) { - boolean clipChanged = clipCount != clips.size(); - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth < i) { - clips.remove(c); - clipChanged = true; - } - } - - if (clipChanged) { - if (clips.size() > 0) { - Area clip = null; - for (Clip clip1 : clips) { - Shape shape = clip1.shape; - if (clip == null) { - clip = new Area(shape); - } else { - clip.intersect(new Area(shape)); - } - } - - g.setTransform(new AffineTransform()); - g.setClip(clip); - - // draw clip border - //g.setPaint(Color.red); - //g.setStroke(new BasicStroke(2)); - //g.draw(clip); - } else { - g.setClip(null); - } - - clipCount = clips.size(); - } - - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - - CharacterTag character = swf.getCharacter(layer.characterId); - Matrix layerMatrix = new Matrix(layer.matrix); - Matrix mat = transformation.concatenate(layerMatrix); - Matrix absMat = absoluteTransformation.concatenate(layerMatrix); - - ColorTransform clrTrans = colorTransform; - if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode - clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); - } - - boolean showPlaceholder = false; - if (character instanceof DrawableTag) { - - RECT scalingRect = null; - DefineScalingGridTag sgt = character.getScalingGridTag(); - if (sgt != null) { - scalingRect = sgt.splitter; - } - - if (scalingRect != null) { - ExportRectangle[] sourceRect = new ExportRectangle[9]; - ExportRectangle[] targetRect = new ExportRectangle[9]; - Matrix[] transforms = new Matrix[9]; - //mat => image - //t => - //Matrix tx = transformation.concatenate(layerMatrix); - DrawableTag dr = (DrawableTag) character; - ExportRectangle boundsRect = new ExportRectangle(dr.getRect()); - ExportRectangle targetBoundsRect = layerMatrix.transform(boundsRect); - DefineScalingGridTag.getSlices(targetBoundsRect, boundsRect, new ExportRectangle(scalingRect), sourceRect, targetRect, transforms); - Shape c = g.getClip(); - AffineTransform origTransform = g.getTransform(); - for (int s = 0; s < 9; s++) { - g.setTransform(new AffineTransform()); - ExportRectangle p1 = transformation.transform(targetRect[s]); - g.setClip(c); - - Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); - g.setClip(r); - drawDrawable(strokeTransformation.preConcatenate(layerMatrix), layer, transforms[s], g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); - - } - g.setClip(c); - - /* - for (int s = 0; s < 9; s++) { - g.setTransform(new AffineTransform()); - ExportRectangle p1 = transformation.transform(targetRect[s]); - g.setClip(c); - - Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); - g.setColor(Color.blue); - g.draw(r); - - }*/ - g.setTransform(origTransform); - } else { - drawDrawable(strokeTransformation, layer, layerMatrix, g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); - } - } else if (character instanceof BoundedTag) { - showPlaceholder = true; - } - - if (showPlaceholder) { - AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); - g.setTransform(trans); - BoundedTag b = (BoundedTag) character; - g.setPaint(new Color(255, 255, 255, 128)); - g.setComposite(BlendComposite.Invert); - g.setStroke(new BasicStroke((int) SWF.unitDivisor)); - RECT r = b.getRect(); - g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); - g.drawString(character.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); - g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); - g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); - g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); - g.setComposite(AlphaComposite.Dst); - } - } - - g.setTransform(new AffineTransform()); - g.setClip(prevClip); - } - - public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { - if (getFrameCount() <= frame) { - return; - } - - Frame frameObj = getFrame(frame); - List clips = new ArrayList<>(); - - int maxDepth = getMaxDepth(); - int clipCount = 0; - Element clipGroup = null; - for (int i = 1; i <= maxDepth; i++) { - boolean clipChanged = clipCount != clips.size(); - for (int c = 0; c < clips.size(); c++) { - if (clips.get(c).depth < i) { - clips.remove(c); - clipChanged = true; - } - } - - if (clipChanged) { - if (clipGroup != null) { - exporter.endGroup(); - } - - if (clips.size() > 0) { - String clip = clips.get(clips.size() - 1).shape; // todo: merge clip areas - clipGroup = exporter.createSubGroup(null, null); - clipGroup.setAttribute("clip-path", "url(#" + clip + ")"); - } - - clipCount = clips.size(); - } - - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - - CharacterTag character = swf.getCharacter(layer.characterId); - - ColorTransform clrTrans = colorTransform; - if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode - clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); - } - - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - - String assetName; - Tag drawableTag = (Tag) drawable; - RECT boundRect = drawable.getRect(); - if (exporter.exportedTags.containsKey(drawableTag)) { - assetName = exporter.exportedTags.get(drawableTag); - } else { - assetName = getTagIdPrefix(drawableTag, exporter); - exporter.exportedTags.put(drawableTag, assetName); - exporter.createDefGroup(new ExportRectangle(boundRect), assetName); - drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); - exporter.endGroup(); - } - ExportRectangle rect = new ExportRectangle(boundRect); - - // TODO: if (layer.filters != null) - // TODO: if (layer.blendMode > 1) - if (layer.clipDepth > -1) { - String clipName = exporter.getUniqueId("clipPath"); - exporter.createClipPath(new Matrix(), clipName); - SvgClip clip = new SvgClip(clipName, layer.clipDepth); - clips.add(clip); - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName, layer.instanceName); - exporter.endGroup(); - } else { - Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); - exporter.addUse(mat, boundRect, assetName, layer.instanceName); - } - } - } - - if (clipGroup != null) { - exporter.endGroup(); - } - } - - private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { - if (tag instanceof ShapeTag) { - return exporter.getUniqueId("shape"); - } - if (tag instanceof MorphShapeTag) { - return exporter.getUniqueId("morphshape"); - } - if (tag instanceof DefineSpriteTag) { - return exporter.getUniqueId("sprite"); - } - if (tag instanceof TextTag) { - return exporter.getUniqueId("text"); - } - if (tag instanceof ButtonTag) { - return exporter.getUniqueId("button"); - } - return exporter.getUniqueId("tag"); - } - - public void toHtmlCanvas(StringBuilder result, double unitDivisor, List frames) { - FrameExporter.framesToHtmlCanvas(result, unitDivisor, this, frames, 0, null, 0, displayRect, null, null); - } - - public void getSounds(int frame, int time, ButtonTag mouseOverButton, int mouseButton, List sounds, List soundClasses) { - Frame fr = getFrame(frame); - sounds.addAll(fr.sounds); - soundClasses.addAll(fr.soundClasses); - for (int d = maxDepth; d >= 0; d--) { - DepthState ds = fr.layers.get(d); - if (ds != null) { - CharacterTag c = swf.getCharacter(ds.characterId); - if (c instanceof Timelined) { - int frameCount = ((Timelined) c).getTimeline().frames.size(); - if (frameCount == 0) { - continue; - } - int dframe = time % frameCount; - if (c instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (mouseOverButton == c) { - if (mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - ((Timelined) c).getTimeline().getSounds(dframe, time, mouseOverButton, mouseButton, sounds, soundClasses); - } - } - } - } - - public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation, boolean stroked) { - Frame fr = getFrame(frame); - Area area = new Area(); - Stack clips = new Stack<>(); - for (int d = maxDepth; d >= 0; d--) { - Clip currentClip = null; - for (int i = clips.size() - 1; i >= 0; i--) { - Clip cl = clips.get(i); - if (cl.depth <= d) { - clips.remove(i); - } - } - if (!clips.isEmpty()) { - currentClip = clips.peek(); - } - DepthState layer = fr.layers.get(d); - if (layer == null) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = swf.getCharacter(layer.characterId); - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - Matrix m = transformation.concatenate(new Matrix(layer.matrix)); - - int drawableFrameCount = drawable.getNumFrames(); - if (drawableFrameCount == 0) { - drawableFrameCount = 1; - } - - int dframe = time % drawableFrameCount; - if (character instanceof ButtonTag) { - dframe = ButtonTag.FRAME_UP; - if (renderContext.cursorPosition != null) { - ButtonTag buttonTag = (ButtonTag) character; - Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m, stroked); - if (buttonShape.contains(renderContext.cursorPosition)) { - if (renderContext.mouseButton > 0) { - dframe = ButtonTag.FRAME_DOWN; - } else { - dframe = ButtonTag.FRAME_OVER; - } - } - } - } - - Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m, stroked); - Area addArea = new Area(cshape); - if (currentClip != null) { - Area a = new Area(new Rectangle(displayRect.Xmin, displayRect.Ymin, displayRect.getWidth(), displayRect.getHeight())); - a.subtract(new Area(currentClip.shape)); - addArea.subtract(a); - } - - if (layer.clipDepth > -1) { - Clip clip = new Clip(addArea, layer.clipDepth); - clips.push(clip); - } else { - area.add(addArea); - } - } - } - return area; - } - - public boolean isSingleFrame() { - for (int i = 0; i < getFrameCount(); i++) { - if (!isSingleFrame(i)) { - return false; - } - } - return true; - } - - public boolean isSingleFrame(int frame) { - Frame frameObj = getFrame(frame); - for (int i = 1; i <= maxDepth; i++) { - if (!frameObj.layers.containsKey(i)) { - continue; - } - DepthState layer = frameObj.layers.get(i); - if (!swf.getCharacters().containsKey(layer.characterId)) { - continue; - } - if (!layer.isVisible) { - continue; - } - CharacterTag character = swf.getCharacter(layer.characterId); - if (character instanceof DrawableTag) { - DrawableTag drawable = (DrawableTag) character; - if (!drawable.isSingleFrame()) { - return false; - } - } - } - - return true; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof Timeline) { - Timeline timelineObj = (Timeline) obj; - return timelined.equals(timelineObj.timelined); - } - - return false; - } -} +/* + * 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.timeline; + +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.exporters.FrameExporter; +import com.jpexs.decompiler.flash.exporters.commonshape.ExportRectangle; +import com.jpexs.decompiler.flash.exporters.commonshape.Matrix; +import com.jpexs.decompiler.flash.exporters.commonshape.SVGExporter; +import com.jpexs.decompiler.flash.tags.DefineScalingGridTag; +import com.jpexs.decompiler.flash.tags.DefineSpriteTag; +import com.jpexs.decompiler.flash.tags.DoActionTag; +import com.jpexs.decompiler.flash.tags.DoInitActionTag; +import com.jpexs.decompiler.flash.tags.FrameLabelTag; +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.StartSound2Tag; +import com.jpexs.decompiler.flash.tags.StartSoundTag; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.tags.base.ASMSourceContainer; +import com.jpexs.decompiler.flash.tags.base.BoundedTag; +import com.jpexs.decompiler.flash.tags.base.ButtonTag; +import com.jpexs.decompiler.flash.tags.base.CharacterIdTag; +import com.jpexs.decompiler.flash.tags.base.CharacterTag; +import com.jpexs.decompiler.flash.tags.base.DrawableTag; +import com.jpexs.decompiler.flash.tags.base.MorphShapeTag; +import com.jpexs.decompiler.flash.tags.base.PlaceObjectTypeTag; +import com.jpexs.decompiler.flash.tags.base.RemoveTag; +import com.jpexs.decompiler.flash.tags.base.RenderContext; +import com.jpexs.decompiler.flash.tags.base.ShapeTag; +import com.jpexs.decompiler.flash.tags.base.SoundStreamHeadTypeTag; +import com.jpexs.decompiler.flash.tags.base.TextTag; +import com.jpexs.decompiler.flash.types.CLIPACTIONS; +import com.jpexs.decompiler.flash.types.ColorTransform; +import com.jpexs.decompiler.flash.types.MATRIX; +import com.jpexs.decompiler.flash.types.RECT; +import com.jpexs.decompiler.flash.types.filters.BlendComposite; +import com.jpexs.decompiler.flash.types.filters.FILTER; +import com.jpexs.helpers.Helper; +import com.jpexs.helpers.SerializableImage; +import java.awt.AlphaComposite; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; +import org.w3c.dom.Element; + +/** + * + * @author JPEXS + */ +public class Timeline { + + public int id; + + public SWF swf; + + public RECT displayRect; + + public float frameRate; + + public Timelined timelined; + + public int maxDepth; + + public int fontFrameNum = -1; + + private final List frames = new ArrayList<>(); + + private final Map depthMaxFrame = new HashMap<>(); + + private final List asmSources = new ArrayList<>(); + + private final List asmSourceContainers = new ArrayList<>(); + + private final Map actionFrames = new HashMap<>(); + + private final Map> soundStramBlocks = new HashMap<>(); + + private AS2Package as2RootPackage; + + public final List otherTags = new ArrayList<>(); + + private boolean initialized = false; + + private Map labelToFrame = new HashMap<>(); + + private void ensureInitialized() { + if (!initialized) { + initialize(); + initialized = true; + } + } + + public List getFrames() { + ensureInitialized(); + return frames; + } + + public Frame getFrame(int index) { + ensureInitialized(); + if (index >= frames.size()) { + return null; + } + return frames.get(index); + } + + public void addFrame(Frame frame) { + ensureInitialized(); + frames.add(frame); + maxDepth = getMaxDepthInternal(); + calculateMaxDepthFrames(); + } + + public AS2Package getAS2RootPackage() { + ensureInitialized(); + return as2RootPackage; + } + + public Map getDepthMaxFrame() { + ensureInitialized(); + return depthMaxFrame; + } + + public List getSoundStreamBlocks(SoundStreamHeadTypeTag head) { + ensureInitialized(); + return soundStramBlocks.get(head); + } + + public Tag getParentTag() { + return timelined instanceof Tag ? (Tag) timelined : null; + } + + public void reset(SWF swf) { + reset(swf, swf, 0, swf.displayRect); + } + + public void reset(SWF swf, Timelined timelined, int id, RECT displayRect) { + initialized = false; + frames.clear(); + depthMaxFrame.clear(); + asmSources.clear(); + asmSourceContainers.clear(); + actionFrames.clear(); + soundStramBlocks.clear(); + otherTags.clear(); + this.id = id; + this.swf = swf; + this.displayRect = displayRect; + this.frameRate = swf.frameRate; + this.timelined = timelined; + as2RootPackage = new AS2Package(null, null, swf); + } + + public final int getMaxDepth() { + ensureInitialized(); + return maxDepth; + } + + private int getMaxDepthInternal() { + int max_depth = 0; + for (Frame f : frames) { + for (int depth : f.layers.keySet()) { + if (depth > max_depth) { + max_depth = depth; + } + int clipDepth = f.layers.get(depth).clipDepth; + if (clipDepth > max_depth) { + max_depth = clipDepth; + } + } + } + return max_depth; + } + + public int getFrameCount() { + ensureInitialized(); + return frames.size(); + } + + public int getRealFrameCount() { + ensureInitialized(); + + int cnt = 1; + for (int i = 1; i < frames.size(); i++) { + if (!frames.get(i).actions.isEmpty()) { + cnt++; + continue; + } + if (frames.get(i).layersChanged) { + cnt++; + } + } + + return cnt; + } + + public int getFrameForAction(ASMSource asm) { + Integer frame = actionFrames.get(asm); + if (frame == null) { + return -1; + } + + return frame; + } + + public Timeline(SWF swf) { + this(swf, swf, 0, swf.displayRect); + } + + public Timeline(SWF swf, Timelined timelined, int id, RECT displayRect) { + this.id = id; + this.swf = swf; + this.displayRect = displayRect; + this.frameRate = swf.frameRate; + this.timelined = timelined; + as2RootPackage = new AS2Package(null, null, swf); + } + + public int getFrameWithLabel(String label) { + if (labelToFrame.containsKey(label)) { + return labelToFrame.get(label); + } + return -1; + } + + private void initialize() { + int frameIdx = 0; + Frame frame = new Frame(this, frameIdx++); + frame.layersChanged = true; + boolean newFrameNeeded = false; + for (Tag t : timelined.getTags()) { + boolean isNested = ShowFrameTag.isNestedTagType(t.getId()); + if (isNested) { + newFrameNeeded = true; + frame.innerTags.add(t); + } + + if (t instanceof ASMSourceContainer) { + ASMSourceContainer asmSourceContainer = (ASMSourceContainer) t; + if (!asmSourceContainer.getSubItems().isEmpty()) { + if (isNested) { + frame.actionContainers.add(asmSourceContainer); + } else { + asmSourceContainers.add(asmSourceContainer); + } + } + } + + if (t instanceof FrameLabelTag) { + newFrameNeeded = true; + frame.label = ((FrameLabelTag) t).getLabelName(); + frame.namedAnchor = ((FrameLabelTag) t).isNamedAnchor(); + labelToFrame.put(frame.label, frames.size()); + } else if (t instanceof StartSoundTag) { + newFrameNeeded = true; + frame.sounds.add(((StartSoundTag) t).soundId); + } else if (t instanceof StartSound2Tag) { + newFrameNeeded = true; + frame.soundClasses.add(((StartSound2Tag) t).soundClassName); + } else if (t instanceof SetBackgroundColorTag) { + newFrameNeeded = true; + frame.backgroundColor = ((SetBackgroundColorTag) t).backgroundColor; + } else if (t instanceof PlaceObjectTypeTag) { + newFrameNeeded = true; + PlaceObjectTypeTag po = (PlaceObjectTypeTag) t; + int depth = po.getDepth(); + DepthState fl = frame.layers.get(depth); + if (fl == null) { + frame.layers.put(depth, fl = new DepthState(swf, frame)); + } + frame.layersChanged = true; + fl.placeObjectTag = po; + fl.minPlaceObjectNum = Math.max(fl.minPlaceObjectNum, po.getPlaceObjectNum()); + int characterId = po.getCharacterId(); + if (characterId != -1) { + fl.characterId = characterId; + } + if (po.flagMove()) { + MATRIX matrix2 = po.getMatrix(); + if (matrix2 != null) { + fl.matrix = matrix2; + } + String instanceName2 = po.getInstanceName(); + if (instanceName2 != null) { + fl.instanceName = instanceName2; + } + ColorTransform colorTransForm2 = po.getColorTransform(); + if (colorTransForm2 != null) { + fl.colorTransForm = colorTransForm2; + } + + CLIPACTIONS clipActions2 = po.getClipActions(); + if (clipActions2 != null) { + fl.clipActions = clipActions2; + } + if (po.cacheAsBitmap()) { + fl.cacheAsBitmap = true; + } + int blendMode2 = po.getBlendMode(); + if (blendMode2 > 0) { + fl.blendMode = blendMode2; + } + List filters2 = po.getFilters(); + if (filters2 != null) { + fl.filters = filters2; + } + int ratio2 = po.getRatio(); + if (ratio2 > -1) { + fl.ratio = ratio2; + } + int clipDepth2 = po.getClipDepth(); + if (clipDepth2 > -1) { + fl.clipDepth = clipDepth2; + } + } else { + fl.matrix = po.getMatrix(); + fl.instanceName = po.getInstanceName(); + fl.colorTransForm = po.getColorTransform(); + fl.cacheAsBitmap = po.cacheAsBitmap(); + fl.blendMode = po.getBlendMode(); + fl.filters = po.getFilters(); + fl.ratio = po.getRatio(); + fl.clipActions = po.getClipActions(); + fl.clipDepth = po.getClipDepth(); + } + fl.key = characterId != -1; + } else if (t instanceof RemoveTag) { + newFrameNeeded = true; + RemoveTag r = (RemoveTag) t; + int depth = r.getDepth(); + frame.layers.remove(depth); + frame.layersChanged = true; + } else if (t instanceof DoActionTag) { + newFrameNeeded = true; + frame.actions.add((DoActionTag) t); + actionFrames.put((DoActionTag) t, frame.frame); + } else if (t instanceof ShowFrameTag) { + frame.showFrameTag = (ShowFrameTag) t; + frames.add(frame); + frame = new Frame(frame, frameIdx++); + newFrameNeeded = false; + } else if (t instanceof ASMSource) { + asmSources.add((ASMSource) t); + } else { + otherTags.add(t); + } + } + if (newFrameNeeded) { + frames.add(frame); + } + + maxDepth = getMaxDepthInternal(); + + detectTweens(); + calculateMaxDepthFrames(); + + createASPackages(); + if (timelined instanceof SWF) { + // popuplate only for main timeline + populateSoundStreamBlocks(0, timelined.getTags()); + } + + initialized = true; + } + + private void detectTweens() { + for (int d = 1; d <= maxDepth; d++) { + int characterId = -1; + int len = 0; + for (int f = 0; f <= frames.size(); f++) { + DepthState ds = f >= frames.size() ? null : frames.get(f).layers.get(d); + + if (ds != null && characterId != -1 && ds.characterId == characterId) { + len++; + } else { + if (characterId != -1) { + int startPos = f - len; + List matrices = new ArrayList<>(len); + for (int k = 0; k < len; k++) { + matrices.add(frames.get(startPos + k).layers.get(d)); + } + + List ranges = TweenDetector.detectRanges(matrices); + for (TweenRange r : ranges) { + for (int t = r.startPosition; t <= r.endPosition; t++) { + DepthState layer = frames.get(startPos + t).layers.get(d); + layer.motionTween = true; + layer.key = false; + } + + frames.get(startPos + r.startPosition).layers.get(d).key = true; + } + } + + len = 1; + } + + characterId = ds == null ? -1 : ds.characterId; + } + } + } + + private void calculateMaxDepthFrames() { + depthMaxFrame.clear(); + for (int d = 1; d <= maxDepth; d++) { + for (int f = frames.size() - 1; f >= 0; f--) { + if (frames.get(f).layers.get(d) != null) { + depthMaxFrame.put(d, f); + break; + } + } + } + } + + private void createASPackages() { + for (ASMSource asm : asmSources) { + if (asm instanceof DoInitActionTag) { + DoInitActionTag initAction = (DoInitActionTag) asm; + String path = swf.getExportName(initAction.spriteId); + path = path != null ? path : "_unk_"; + if (path.isEmpty()) { + path = initAction.getExportFileName(); + } + + String[] pathParts = path.contains(".") ? path.split("\\.") : new String[]{path}; + AS2Package pkg = as2RootPackage; + for (int pos = 0; pos < pathParts.length - 1; pos++) { + String pathPart = pathParts[pos]; + AS2Package subPkg = pkg.subPackages.get(pathPart); + if (subPkg == null) { + subPkg = new AS2Package(pathPart, pkg, swf); + pkg.subPackages.put(pathPart, subPkg); + } + + pkg = subPkg; + } + + pkg.scripts.put(pathParts[pathParts.length - 1], asm); + } + } + } + + private void populateSoundStreamBlocks(int containerId, Iterable tags) { + List blocks = null; + for (Tag t : tags) { + if (t instanceof SoundStreamHeadTypeTag) { + SoundStreamHeadTypeTag head = (SoundStreamHeadTypeTag) t; + head.setVirtualCharacterId(containerId); + blocks = new ArrayList<>(); + soundStramBlocks.put(head, blocks); + continue; + } + + if (t instanceof DefineSpriteTag) { + DefineSpriteTag sprite = (DefineSpriteTag) t; + populateSoundStreamBlocks(sprite.getCharacterId(), sprite.getTags()); + } + + if (blocks == null) { + continue; + } + + if (t instanceof SoundStreamBlockTag) { + blocks.add((SoundStreamBlockTag) t); + } + } + } + + public void getNeededCharacters(Set usedCharacters) { + for (int i = 0; i < getFrameCount(); i++) { + getNeededCharacters(i, usedCharacters); + } + } + + public void getNeededCharacters(List frames, Set usedCharacters) { + for (int frame = 0; frame < getFrameCount(); frame++) { + getNeededCharacters(frame, usedCharacters); + } + } + + public void getNeededCharacters(int frame, Set usedCharacters) { + Frame frameObj = getFrame(frame); + for (int depth : frameObj.layers.keySet()) { + DepthState layer = frameObj.layers.get(depth); + if (layer.characterId != -1) { + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + usedCharacters.add(layer.characterId); + swf.getCharacter(layer.characterId).getNeededCharactersDeep(usedCharacters); + } + } + } + + public boolean replaceCharacter(int oldCharacterId, int newCharacterId) { + boolean modified = false; + for (int i = 0; i < timelined.getTags().size(); i++) { + Tag t = timelined.getTags().get(i); + if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == oldCharacterId) { + ((CharacterIdTag) t).setCharacterId(newCharacterId); + ((Tag) t).setModified(true); + modified = true; + } + } + return modified; + } + + public boolean removeCharacter(int characterId) { + boolean modified = false; + for (int i = 0; i < timelined.getTags().size(); i++) { + Tag t = timelined.getTags().get(i); + if (t instanceof CharacterIdTag && ((CharacterIdTag) t).getCharacterId() == characterId) { + timelined.removeTag(i); + i--; + modified = true; + } + } + return modified; + } + + public double roundToPixel(double val) { + return Math.rint(val / SWF.unitDivisor) * SWF.unitDivisor; + } + + /*public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix prevTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { + ExportRectangle scalingGrid = null; + if (timelined instanceof CharacterTag) { + DefineScalingGridTag sgt = ((CharacterTag) timelined).getScalingGridTag(); + if (sgt != null) { + scalingGrid = new ExportRectangle(sgt.splitter); + } + } + + if (scalingGrid == null || transformation.rotateSkew0 != 0 || transformation.rotateSkew1 != 0) { + toImage(frame, time, renderContext, image, isClip, transformation, absoluteTransformation, colorTransform, null); + return; + } + + //9-slice scaling using DefineScalingGrid + Matrix diffTransform = prevTransformation.inverse().preConcatenate(transformation); + transformation = diffTransform; + + Matrix prevScale = new Matrix(); + prevScale.scaleX = prevTransformation.scaleX; + prevScale.scaleY = prevTransformation.scaleY; + + ExportRectangle boundsRect = new ExportRectangle(timelined.getRect()); + + + 0 | 1 | 2 + ------------ + 3 | 4 | 5 + ------------ + 6 | 7 | 8 + + ExportRectangle[] targetRect = new ExportRectangle[9]; + ExportRectangle[] sourceRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + + DefineScalingGridTag.getSlices(transformation, prevScale, boundsRect, scalingGrid, sourceRect, targetRect, transforms); + + for (int i = 0; i < targetRect.length; i++) { + toImage(frame, time, renderContext, image, isClip, transforms[i], absoluteTransformation, colorTransform, targetRect[i]); + } + }*/ + private void drawDrawable(Matrix strokeTransform, DepthState layer, Matrix layerMatrix, Graphics2D g, ColorTransform colorTransForm, int blendMode, List clips, Matrix transformation, boolean isClip, int clipDepth, Matrix absMat, int time, int ratio, RenderContext renderContext, SerializableImage image, DrawableTag drawable, List filters, double unzoom, ColorTransform clrTrans) { + Matrix drawMatrix = new Matrix(); + int drawableFrameCount = drawable.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + RECT boundRect = drawable.getRect(); + + ExportRectangle rect = new ExportRectangle(boundRect); + Matrix mat = transformation.concatenate(layerMatrix); + rect = mat.transform(rect); + + boolean cacheAsBitmap = layer.cacheAsBitmap() && layer.placeObjectTag != null && drawable.isSingleFrame(); + /* // draw bounds + AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); + g.setTransform(trans); + BoundedTag b = (BoundedTag) drawable; + g.setPaint(new Color(255, 255, 255, 128)); + g.setComposite(BlendComposite.Invert); + g.setStroke(new BasicStroke((int) SWF.unitDivisor)); + RECT r = b.getRect(); + g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); + g.drawString(drawable.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); + g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); + g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); + g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); + g.setComposite(AlphaComposite.Dst);*/ + + SerializableImage img = null; + if (cacheAsBitmap && renderContext.displayObjectCache != null) { + img = renderContext.displayObjectCache.get(layer.placeObjectTag); + } + + int stateCount = renderContext.stateUnderCursor == null ? 0 : renderContext.stateUnderCursor.size(); + int dframe; + if (fontFrameNum != -1) { + dframe = fontFrameNum; + } else { + dframe = time % drawableFrameCount; + } + + if (filters != null && filters.size() > 0) { + // calculate size after applying the filters + double deltaXMax = 0; + double deltaYMax = 0; + for (FILTER filter : filters) { + double x = filter.getDeltaX(); + double y = filter.getDeltaY(); + deltaXMax = Math.max(x, deltaXMax); + deltaYMax = Math.max(y, deltaYMax); + } + rect.xMin -= deltaXMax * unzoom; + rect.xMax += deltaXMax * unzoom; + rect.yMin -= deltaYMax * unzoom; + rect.yMax += deltaYMax * unzoom; + } + + rect.xMin -= unzoom; + rect.yMin -= unzoom; + rect.xMin = Math.max(0, rect.xMin); + rect.yMin = Math.max(0, rect.yMin); + drawMatrix.translate(rect.xMin, rect.yMin); + + if (img == null) { + int newWidth = (int) (rect.getWidth() / unzoom); + int newHeight = (int) (rect.getHeight() / unzoom); + int deltaX = (int) (rect.xMin / unzoom); + int deltaY = (int) (rect.yMin / unzoom); + newWidth = Math.min(image.getWidth() - deltaX, newWidth) + 1; + newHeight = Math.min(image.getHeight() - deltaY, newHeight) + 1; + + if (newWidth <= 0 || newHeight <= 0) { + return; + } + + Matrix m = mat.preConcatenate(Matrix.getTranslateInstance(-rect.xMin, -rect.yMin)); + //strokeTransform = strokeTransform.clone(); + //strokeTransform.translate(-rect.xMin, -rect.yMin); + + if (drawable instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (renderContext.cursorPosition != null) { + Shape buttonShape = drawable.getOutline(ButtonTag.FRAME_HITTEST, time, ratio, renderContext, absMat, true); + if (buttonShape.contains(renderContext.cursorPosition)) { + renderContext.mouseOverButton = (ButtonTag) drawable; + if (renderContext.mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + } + + img = new SerializableImage(newWidth, newHeight, SerializableImage.TYPE_INT_ARGB_PRE); + img.fillTransparent(); + + drawable.toImage(dframe, time, ratio, renderContext, img, isClip || clipDepth > -1, m, strokeTransform, absMat, clrTrans); + + if (filters != null) { + for (FILTER filter : filters) { + img = filter.apply(img); + } + } + if (blendMode > 1) { + if (colorTransForm != null) { + img = colorTransForm.apply(img); + } + } + + if (cacheAsBitmap && renderContext.displayObjectCache != null) { + renderContext.displayObjectCache.put(layer.placeObjectTag, img); + } + } + + drawMatrix.translateX /= unzoom; + drawMatrix.translateY /= unzoom; + AffineTransform trans = drawMatrix.toTransform(); + + switch (blendMode) { + case 0: + case 1: + g.setComposite(AlphaComposite.SrcOver); + break; + case 2: // Layer + g.setComposite(AlphaComposite.SrcOver); + break; + case 3: + g.setComposite(BlendComposite.Multiply); + break; + case 4: + g.setComposite(BlendComposite.Screen); + break; + case 5: + g.setComposite(BlendComposite.Lighten); + break; + case 6: + g.setComposite(BlendComposite.Darken); + break; + case 7: + g.setComposite(BlendComposite.Difference); + break; + case 8: + g.setComposite(BlendComposite.Add); + break; + case 9: + g.setComposite(BlendComposite.Subtract); + break; + case 10: + g.setComposite(BlendComposite.Invert); + break; + case 11: + g.setComposite(BlendComposite.Alpha); + break; + case 12: + g.setComposite(BlendComposite.Erase); + break; + case 13: + g.setComposite(BlendComposite.Overlay); + break; + case 14: + g.setComposite(BlendComposite.HardLight); + break; + default: // Not implemented + g.setComposite(AlphaComposite.SrcOver); + break; + } + + if (clipDepth > -1) { + BufferedImage mask = new BufferedImage(image.getWidth(), image.getHeight(), image.getType()); + Graphics2D gm = (Graphics2D) mask.getGraphics(); + gm.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); + gm.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + gm.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + gm.setComposite(AlphaComposite.Src); + gm.setColor(new Color(0, 0, 0, 0f)); + gm.fillRect(0, 0, image.getWidth(), image.getHeight()); + gm.setTransform(trans); + gm.drawImage(img.getBufferedImage(), 0, 0, null); + Clip clip = new Clip(Helper.imageToShape(mask), clipDepth); // Maybe we can get current outline instead converting from image (?) + clips.add(clip); + } else { + if (renderContext.cursorPosition != null) { + if (drawable instanceof DefineSpriteTag) { + if (renderContext.stateUnderCursor.size() > stateCount) { + renderContext.stateUnderCursor.add(layer); + } + } else if (absMat.transform(new ExportRectangle(boundRect)).contains(renderContext.cursorPosition)) { + Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat, true); + if (shape.contains(renderContext.cursorPosition)) { + renderContext.stateUnderCursor.add(layer); + } + } + } + + if (renderContext.borderImage != null) { + Graphics2D g2 = (Graphics2D) renderContext.borderImage.getGraphics(); + g2.setPaint(Color.red); + g2.setStroke(new BasicStroke(2)); + Shape shape = drawable.getOutline(dframe, time, layer.ratio, renderContext, absMat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)), true); + g2.draw(shape); + } + + g.setTransform(trans); + g.drawImage(img.getBufferedImage(), 0, 0, null); + } + } + + public void toImage(int frame, int time, RenderContext renderContext, SerializableImage image, boolean isClip, Matrix transformation, Matrix strokeTransformation, Matrix absoluteTransformation, ColorTransform colorTransform) { + double unzoom = SWF.unitDivisor; + if (getFrameCount() <= frame) { + return; + } + + Frame frameObj = getFrame(frame); + Graphics2D g = (Graphics2D) image.getGraphics(); + Shape prevClip = g.getClip(); + //if (targetRect != null) { + // g.setClip(new Rectangle2D.Double(targetRect.xMin, targetRect.yMin, targetRect.getWidth(), targetRect.getHeight())); + //} + + g.setPaint(frameObj.backgroundColor.toColor()); + g.fill(new Rectangle(image.getWidth(), image.getHeight())); + + g.setTransform(transformation.toTransform()); + List clips = new ArrayList<>(); + + int maxDepth = getMaxDepth(); + int clipCount = 0; + for (int i = 1; i <= maxDepth; i++) { + boolean clipChanged = clipCount != clips.size(); + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth < i) { + clips.remove(c); + clipChanged = true; + } + } + + if (clipChanged) { + if (clips.size() > 0) { + Area clip = null; + for (Clip clip1 : clips) { + Shape shape = clip1.shape; + if (clip == null) { + clip = new Area(shape); + } else { + clip.intersect(new Area(shape)); + } + } + + g.setTransform(new AffineTransform()); + g.setClip(clip); + + // draw clip border + //g.setPaint(Color.red); + //g.setStroke(new BasicStroke(2)); + //g.draw(clip); + } else { + g.setClip(null); + } + + clipCount = clips.size(); + } + + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + + CharacterTag character = swf.getCharacter(layer.characterId); + Matrix layerMatrix = new Matrix(layer.matrix); + Matrix mat = transformation.concatenate(layerMatrix); + Matrix absMat = absoluteTransformation.concatenate(layerMatrix); + + ColorTransform clrTrans = colorTransform; + if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode + clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); + } + + boolean showPlaceholder = false; + if (character instanceof DrawableTag) { + + RECT scalingRect = null; + DefineScalingGridTag sgt = character.getScalingGridTag(); + if (sgt != null) { + scalingRect = sgt.splitter; + } + + if (scalingRect != null) { + ExportRectangle[] sourceRect = new ExportRectangle[9]; + ExportRectangle[] targetRect = new ExportRectangle[9]; + Matrix[] transforms = new Matrix[9]; + //mat => image + //t => + //Matrix tx = transformation.concatenate(layerMatrix); + DrawableTag dr = (DrawableTag) character; + ExportRectangle boundsRect = new ExportRectangle(dr.getRect()); + ExportRectangle targetBoundsRect = layerMatrix.transform(boundsRect); + DefineScalingGridTag.getSlices(targetBoundsRect, boundsRect, new ExportRectangle(scalingRect), sourceRect, targetRect, transforms); + Shape c = g.getClip(); + AffineTransform origTransform = g.getTransform(); + for (int s = 0; s < 9; s++) { + g.setTransform(new AffineTransform()); + ExportRectangle p1 = transformation.transform(targetRect[s]); + g.setClip(c); + + Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); + g.setClip(r); + drawDrawable(strokeTransformation.preConcatenate(layerMatrix), layer, transforms[s], g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); + + } + g.setClip(c); + + /* + for (int s = 0; s < 9; s++) { + g.setTransform(new AffineTransform()); + ExportRectangle p1 = transformation.transform(targetRect[s]); + g.setClip(c); + + Rectangle2D r = new Rectangle2D.Double(p1.xMin, p1.yMin, p1.getWidth(), p1.getHeight()); + g.setColor(Color.blue); + g.draw(r); + + }*/ + g.setTransform(origTransform); + } else { + drawDrawable(strokeTransformation, layer, layerMatrix, g, colorTransform, layer.blendMode, clips, transformation, isClip, layer.clipDepth, absMat, time, layer.ratio, renderContext, image, (DrawableTag) character, layer.filters, unzoom, clrTrans); + } + } else if (character instanceof BoundedTag) { + showPlaceholder = true; + } + + if (showPlaceholder) { + AffineTransform trans = mat.preConcatenate(Matrix.getScaleInstance(1 / SWF.unitDivisor)).toTransform(); + g.setTransform(trans); + BoundedTag b = (BoundedTag) character; + g.setPaint(new Color(255, 255, 255, 128)); + g.setComposite(BlendComposite.Invert); + g.setStroke(new BasicStroke((int) SWF.unitDivisor)); + RECT r = b.getRect(); + g.setFont(g.getFont().deriveFont((float) (12 * SWF.unitDivisor))); + g.drawString(character.toString(), r.Xmin + (int) (3 * SWF.unitDivisor), r.Ymin + (int) (15 * SWF.unitDivisor)); + g.draw(new Rectangle(r.Xmin, r.Ymin, r.getWidth(), r.getHeight())); + g.drawLine(r.Xmin, r.Ymin, r.Xmax, r.Ymax); + g.drawLine(r.Xmax, r.Ymin, r.Xmin, r.Ymax); + g.setComposite(AlphaComposite.Dst); + } + } + + g.setTransform(new AffineTransform()); + g.setClip(prevClip); + } + + public void toSVG(int frame, int time, DepthState stateUnderCursor, int mouseButton, SVGExporter exporter, ColorTransform colorTransform, int level) throws IOException { + if (getFrameCount() <= frame) { + return; + } + + Frame frameObj = getFrame(frame); + List clips = new ArrayList<>(); + + int maxDepth = getMaxDepth(); + int clipCount = 0; + Element clipGroup = null; + for (int i = 1; i <= maxDepth; i++) { + boolean clipChanged = clipCount != clips.size(); + for (int c = 0; c < clips.size(); c++) { + if (clips.get(c).depth < i) { + clips.remove(c); + clipChanged = true; + } + } + + if (clipChanged) { + if (clipGroup != null) { + exporter.endGroup(); + } + + if (clips.size() > 0) { + String clip = clips.get(clips.size() - 1).shape; // todo: merge clip areas + clipGroup = exporter.createSubGroup(null, null); + clipGroup.setAttribute("clip-path", "url(#" + clip + ")"); + } + + clipCount = clips.size(); + } + + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + + CharacterTag character = swf.getCharacter(layer.characterId); + + ColorTransform clrTrans = colorTransform; + if (layer.colorTransForm != null && layer.blendMode <= 1) { // Normal blend mode + clrTrans = clrTrans == null ? layer.colorTransForm : colorTransform.merge(layer.colorTransForm); + } + + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + + String assetName; + Tag drawableTag = (Tag) drawable; + RECT boundRect = drawable.getRect(); + if (exporter.exportedTags.containsKey(drawableTag)) { + assetName = exporter.exportedTags.get(drawableTag); + } else { + assetName = getTagIdPrefix(drawableTag, exporter); + exporter.exportedTags.put(drawableTag, assetName); + exporter.createDefGroup(new ExportRectangle(boundRect), assetName); + drawable.toSVG(exporter, layer.ratio, clrTrans, level + 1); + exporter.endGroup(); + } + ExportRectangle rect = new ExportRectangle(boundRect); + + // TODO: if (layer.filters != null) + // TODO: if (layer.blendMode > 1) + if (layer.clipDepth > -1) { + String clipName = exporter.getUniqueId("clipPath"); + exporter.createClipPath(new Matrix(), clipName); + SvgClip clip = new SvgClip(clipName, layer.clipDepth); + clips.add(clip); + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName, layer.instanceName); + exporter.endGroup(); + } else { + Matrix mat = Matrix.getTranslateInstance(rect.xMin, rect.yMin).preConcatenate(new Matrix(layer.matrix)); + exporter.addUse(mat, boundRect, assetName, layer.instanceName); + } + } + } + + if (clipGroup != null) { + exporter.endGroup(); + } + } + + private static String getTagIdPrefix(Tag tag, SVGExporter exporter) { + if (tag instanceof ShapeTag) { + return exporter.getUniqueId("shape"); + } + if (tag instanceof MorphShapeTag) { + return exporter.getUniqueId("morphshape"); + } + if (tag instanceof DefineSpriteTag) { + return exporter.getUniqueId("sprite"); + } + if (tag instanceof TextTag) { + return exporter.getUniqueId("text"); + } + if (tag instanceof ButtonTag) { + return exporter.getUniqueId("button"); + } + return exporter.getUniqueId("tag"); + } + + public void toHtmlCanvas(StringBuilder result, double unitDivisor, List frames) { + FrameExporter.framesToHtmlCanvas(result, unitDivisor, this, frames, 0, null, 0, displayRect, null, null); + } + + public void getSounds(int frame, int time, ButtonTag mouseOverButton, int mouseButton, List sounds, List soundClasses) { + Frame fr = getFrame(frame); + sounds.addAll(fr.sounds); + soundClasses.addAll(fr.soundClasses); + for (int d = maxDepth; d >= 0; d--) { + DepthState ds = fr.layers.get(d); + if (ds != null) { + CharacterTag c = swf.getCharacter(ds.characterId); + if (c instanceof Timelined) { + int frameCount = ((Timelined) c).getTimeline().frames.size(); + if (frameCount == 0) { + continue; + } + int dframe = time % frameCount; + if (c instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (mouseOverButton == c) { + if (mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + ((Timelined) c).getTimeline().getSounds(dframe, time, mouseOverButton, mouseButton, sounds, soundClasses); + } + } + } + } + + public Shape getOutline(int frame, int time, RenderContext renderContext, Matrix transformation, boolean stroked) { + Frame fr = getFrame(frame); + Area area = new Area(); + Stack clips = new Stack<>(); + for (int d = maxDepth; d >= 0; d--) { + Clip currentClip = null; + for (int i = clips.size() - 1; i >= 0; i--) { + Clip cl = clips.get(i); + if (cl.depth <= d) { + clips.remove(i); + } + } + if (!clips.isEmpty()) { + currentClip = clips.peek(); + } + DepthState layer = fr.layers.get(d); + if (layer == null) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = swf.getCharacter(layer.characterId); + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + Matrix m = transformation.concatenate(new Matrix(layer.matrix)); + + int drawableFrameCount = drawable.getNumFrames(); + if (drawableFrameCount == 0) { + drawableFrameCount = 1; + } + + int dframe = time % drawableFrameCount; + if (character instanceof ButtonTag) { + dframe = ButtonTag.FRAME_UP; + if (renderContext.cursorPosition != null) { + ButtonTag buttonTag = (ButtonTag) character; + Shape buttonShape = buttonTag.getOutline(ButtonTag.FRAME_HITTEST, time, layer.ratio, renderContext, m, stroked); + if (buttonShape.contains(renderContext.cursorPosition)) { + if (renderContext.mouseButton > 0) { + dframe = ButtonTag.FRAME_DOWN; + } else { + dframe = ButtonTag.FRAME_OVER; + } + } + } + } + + Shape cshape = ((DrawableTag) character).getOutline(dframe, time, layer.ratio, renderContext, m, stroked); + Area addArea = new Area(cshape); + if (currentClip != null) { + Area a = new Area(new Rectangle(displayRect.Xmin, displayRect.Ymin, displayRect.getWidth(), displayRect.getHeight())); + a.subtract(new Area(currentClip.shape)); + addArea.subtract(a); + } + + if (layer.clipDepth > -1) { + Clip clip = new Clip(addArea, layer.clipDepth); + clips.push(clip); + } else { + area.add(addArea); + } + } + } + return area; + } + + public boolean isSingleFrame() { + for (int i = 0; i < getFrameCount(); i++) { + if (!isSingleFrame(i)) { + return false; + } + } + return true; + } + + public boolean isSingleFrame(int frame) { + Frame frameObj = getFrame(frame); + for (int i = 1; i <= maxDepth; i++) { + if (!frameObj.layers.containsKey(i)) { + continue; + } + DepthState layer = frameObj.layers.get(i); + if (!swf.getCharacters().containsKey(layer.characterId)) { + continue; + } + if (!layer.isVisible) { + continue; + } + CharacterTag character = swf.getCharacter(layer.characterId); + if (character instanceof DrawableTag) { + DrawableTag drawable = (DrawableTag) character; + if (!drawable.isSingleFrame()) { + return false; + } + } + } + + return true; + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof Timeline) { + Timeline timelineObj = (Timeline) obj; + return timelined.equals(timelineObj.timelined); + } + + return false; + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java index 8b0f06182..9815b305c 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/BUTTONCONDACTION.java @@ -1,345 +1,345 @@ -/* - * 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.types; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.decompiler.flash.types.annotations.SWFType; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Actions to execute at particular button events - * - * @author JPEXS - */ -public class BUTTONCONDACTION implements ASMSource, Serializable { - - private SWF swf; - - private Tag tag; - - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - // Constructor for Generic tag editor. - public BUTTONCONDACTION() { - swf = null; - tag = null; - actionBytes = new ByteArrayRange(SWFInputStream.BYTE_ARRAY_EMPTY); - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - public BUTTONCONDACTION(SWF swf, SWFInputStream sis, Tag tag) throws IOException { - this.swf = swf; - this.tag = tag; - int condActionSize = sis.readUI16("condActionSize"); - isLast = condActionSize <= 0; - condIdleToOverDown = sis.readUB(1, "condIdleToOverDown") == 1; - condOutDownToIdle = sis.readUB(1, "condOutDownToIdle") == 1; - condOutDownToOverDown = sis.readUB(1, "condOutDownToOverDown") == 1; - condOverDownToOutDown = sis.readUB(1, "condOverDownToOutDown") == 1; - condOverDownToOverUp = sis.readUB(1, "condOverDownToOverUp") == 1; - condOverUpToOverDown = sis.readUB(1, "condOverUpToOverDown") == 1; - condOverUpToIddle = sis.readUB(1, "condOverUpToIddle") == 1; - condIdleToOverUp = sis.readUB(1, "condIdleToOverUp") == 1; - condKeyPress = (int) sis.readUB(7, "condKeyPress"); - condOverDownToIdle = sis.readUB(1, "condOverDownToIdle") == 1; - actionBytes = sis.readByteRangeEx(condActionSize <= 0 ? sis.available() : condActionSize - 4, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); - } - - @Override - public SWF getSwf() { - return swf; - } - - /** - * Is this BUTTONCONDACTION last in the list? - */ - @Internal - public boolean isLast; - - /** - * Idle to OverDown - */ - public boolean condIdleToOverDown; - - /** - * OutDown to Idle - */ - public boolean condOutDownToIdle; - - /** - * OutDown to OverDown - */ - public boolean condOutDownToOverDown; - - /** - * OverDown to OutDown - */ - public boolean condOverDownToOutDown; - - /** - * OverDown to OverUp - */ - public boolean condOverDownToOverUp; - - /** - * OverUp to OverDown - */ - public boolean condOverUpToOverDown; - - /** - * OverUp to Idle - */ - public boolean condOverUpToIddle; - - /** - * Idle to OverUp - */ - public boolean condIdleToOverUp; - - /** - * @since SWF 4 key code - */ - @SWFType(value = BasicType.UB, count = 7) - @Conditional(minSwfVersion = 4) - public int condKeyPress; - - /** - * OverDown to Idle - */ - public boolean condOverDownToIdle; - - /** - * Actions to perform in byte array - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Sets actions associated with this object - * - * @param actions Action list - */ - /*public void setActions(List actions) { - this.actions = actions; - }*/ - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return "BUTTONCONDACTION"; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - /** - * Returns actions associated with this object - * - * @return List of actions - * @throws java.lang.InterruptedException - */ - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - if (tag != null) { - tag.setModified(true); - } - } - - @Override - public boolean isModified() { - if (tag != null) { - return tag.isModified(); - } - return false; - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - private String getHeader(boolean asFilename) { - List events = new ArrayList<>(); - if (condOverUpToOverDown) { - events.add("press"); - } - if (condOverDownToOverUp) { - events.add("release"); - } - if (condOutDownToIdle) { - events.add("releaseOutside"); - } - if (condIdleToOverUp) { - events.add("rollOver"); - } - if (condOverUpToIddle) { - events.add("rollOut"); - } - if (condOverDownToOutDown) { - events.add("dragOut"); - } - if (condOutDownToOverDown) { - events.add("dragOver"); - } - if (condKeyPress > 0) { - if (asFilename) { - events.add("keyPress " + Helper.makeFileName(CLIPACTIONRECORD.keyToString(condKeyPress).replace("<", "").replace(">", "")) + ""); - } else { - events.add("keyPress \"" + CLIPACTIONRECORD.keyToString(condKeyPress) + "\""); - } - } - String onStr = ""; - for (int i = 0; i < events.size(); i++) { - if (i > 0) { - onStr += ", "; - } - onStr += events.get(i); - } - return "on(" + onStr + ")"; - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - writer.appendNoHilight(getHeader(false)); - writer.appendNoHilight("{").newLine(); - return writer.indent(); - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - writer.unindent(); - return writer.appendNoHilight("}").newLine(); - } - - @Override - public int getPrefixLineCount() { - return 1; - } - - @Override - public String removePrefixAndSuffix(String source) { - return Helper.unindentRows(1, 1, source); - } - - @Override - public String getExportFileName() { - return getHeader(true); - } - - @Override - public Tag getSourceTag() { - return tag; - } - - @Override - public void setSourceTag(Tag t) { - this.tag = t; - this.swf = t.getSwf(); - } -} +/* + * 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.types; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Actions to execute at particular button events + * + * @author JPEXS + */ +public class BUTTONCONDACTION implements ASMSource, Serializable { + + private SWF swf; + + private Tag tag; + + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + // Constructor for Generic tag editor. + public BUTTONCONDACTION() { + swf = null; + tag = null; + actionBytes = new ByteArrayRange(SWFInputStream.BYTE_ARRAY_EMPTY); + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public BUTTONCONDACTION(SWF swf, SWFInputStream sis, Tag tag) throws IOException { + this.swf = swf; + this.tag = tag; + int condActionSize = sis.readUI16("condActionSize"); + isLast = condActionSize <= 0; + condIdleToOverDown = sis.readUB(1, "condIdleToOverDown") == 1; + condOutDownToIdle = sis.readUB(1, "condOutDownToIdle") == 1; + condOutDownToOverDown = sis.readUB(1, "condOutDownToOverDown") == 1; + condOverDownToOutDown = sis.readUB(1, "condOverDownToOutDown") == 1; + condOverDownToOverUp = sis.readUB(1, "condOverDownToOverUp") == 1; + condOverUpToOverDown = sis.readUB(1, "condOverUpToOverDown") == 1; + condOverUpToIddle = sis.readUB(1, "condOverUpToIddle") == 1; + condIdleToOverUp = sis.readUB(1, "condIdleToOverUp") == 1; + condKeyPress = (int) sis.readUB(7, "condKeyPress"); + condOverDownToIdle = sis.readUB(1, "condOverDownToIdle") == 1; + actionBytes = sis.readByteRangeEx(condActionSize <= 0 ? sis.available() : condActionSize - 4, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + @Override + public SWF getSwf() { + return swf; + } + + /** + * Is this BUTTONCONDACTION last in the list? + */ + @Internal + public boolean isLast; + + /** + * Idle to OverDown + */ + public boolean condIdleToOverDown; + + /** + * OutDown to Idle + */ + public boolean condOutDownToIdle; + + /** + * OutDown to OverDown + */ + public boolean condOutDownToOverDown; + + /** + * OverDown to OutDown + */ + public boolean condOverDownToOutDown; + + /** + * OverDown to OverUp + */ + public boolean condOverDownToOverUp; + + /** + * OverUp to OverDown + */ + public boolean condOverUpToOverDown; + + /** + * OverUp to Idle + */ + public boolean condOverUpToIddle; + + /** + * Idle to OverUp + */ + public boolean condIdleToOverUp; + + /** + * @since SWF 4 key code + */ + @SWFType(value = BasicType.UB, count = 7) + @Conditional(minSwfVersion = 4) + public int condKeyPress; + + /** + * OverDown to Idle + */ + public boolean condOverDownToIdle; + + /** + * Actions to perform in byte array + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Sets actions associated with this object + * + * @param actions Action list + */ + /*public void setActions(List actions) { + this.actions = actions; + }*/ + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return "BUTTONCONDACTION"; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + /** + * Returns actions associated with this object + * + * @return List of actions + * @throws java.lang.InterruptedException + */ + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + if (tag != null) { + tag.setModified(true); + } + } + + @Override + public boolean isModified() { + if (tag != null) { + return tag.isModified(); + } + return false; + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + private String getHeader(boolean asFilename) { + List events = new ArrayList<>(); + if (condOverUpToOverDown) { + events.add("press"); + } + if (condOverDownToOverUp) { + events.add("release"); + } + if (condOutDownToIdle) { + events.add("releaseOutside"); + } + if (condIdleToOverUp) { + events.add("rollOver"); + } + if (condOverUpToIddle) { + events.add("rollOut"); + } + if (condOverDownToOutDown) { + events.add("dragOut"); + } + if (condOutDownToOverDown) { + events.add("dragOver"); + } + if (condKeyPress > 0) { + if (asFilename) { + events.add("keyPress " + Helper.makeFileName(CLIPACTIONRECORD.keyToString(condKeyPress).replace("<", "").replace(">", "")) + ""); + } else { + events.add("keyPress \"" + CLIPACTIONRECORD.keyToString(condKeyPress) + "\""); + } + } + String onStr = ""; + for (int i = 0; i < events.size(); i++) { + if (i > 0) { + onStr += ", "; + } + onStr += events.get(i); + } + return "on(" + onStr + ")"; + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + writer.appendNoHilight(getHeader(false)); + writer.appendNoHilight("{").newLine(); + return writer.indent(); + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + writer.unindent(); + return writer.appendNoHilight("}").newLine(); + } + + @Override + public int getPrefixLineCount() { + return 1; + } + + @Override + public String removePrefixAndSuffix(String source) { + return Helper.unindentRows(1, 1, source); + } + + @Override + public String getExportFileName() { + return getHeader(true); + } + + @Override + public Tag getSourceTag() { + return tag; + } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java index cc57c20ef..2fcfd6740 100644 --- a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/types/CLIPACTIONRECORD.java @@ -1,301 +1,301 @@ -/* - * 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.types; - -import com.jpexs.decompiler.flash.DisassemblyListener; -import com.jpexs.decompiler.flash.SWF; -import com.jpexs.decompiler.flash.SWFInputStream; -import com.jpexs.decompiler.flash.action.Action; -import com.jpexs.decompiler.flash.action.ActionList; -import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; -import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; -import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; -import com.jpexs.decompiler.flash.helpers.GraphTextWriter; -import com.jpexs.decompiler.flash.tags.Tag; -import com.jpexs.decompiler.flash.tags.base.ASMSource; -import com.jpexs.decompiler.flash.types.annotations.Conditional; -import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; -import com.jpexs.decompiler.flash.types.annotations.Internal; -import com.jpexs.helpers.ByteArrayRange; -import com.jpexs.helpers.Helper; -import java.io.IOException; -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Event handler - * - * @author JPEXS - */ -public class CLIPACTIONRECORD implements ASMSource, Serializable { - - private String scriptName = "-"; - - @Override - public String getScriptName() { - return scriptName; - } - - public static String keyToString(int key) { - if ((key < CLIPACTIONRECORD.KEYNAMES.length) && (key > 0) && (CLIPACTIONRECORD.KEYNAMES[key] != null)) { - return CLIPACTIONRECORD.KEYNAMES[key]; - } else { - return "" + (char) key; - } - } - - public static final String[] KEYNAMES = { - null, - "", - "", - "", - "", - "", - "", - null, - "", - null, - null, - null, - null, - "", - "", - "", - "", - "", - "", - "", - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "" - }; - - @Internal - private SWF swf; - - @Internal - private Tag tag; - - // Constructor for Generic tag editor. TODO:Handle this somehow better - public CLIPACTIONRECORD() { - swf = null; - tag = null; - eventFlags = new CLIPEVENTFLAGS(); - actionBytes = ByteArrayRange.EMPTY; - } - - @Override - public void setScriptName(String scriptName) { - this.scriptName = scriptName; - } - - public CLIPACTIONRECORD(SWF swf, SWFInputStream sis, Tag tag) throws IOException { - this.swf = swf; - this.tag = tag; - eventFlags = sis.readCLIPEVENTFLAGS("eventFlags"); - if (eventFlags.isClear()) { - return; - } - long actionRecordSize = sis.readUI32("actionRecordSize"); - if (eventFlags.clipEventKeyPress) { - keyCode = sis.readUI8("keyCode"); - actionRecordSize--; - } - actionBytes = sis.readByteRangeEx(actionRecordSize, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); - } - - @Override - public SWF getSwf() { - return swf; - } - - /** - * Events to which this handler applies - */ - public CLIPEVENTFLAGS eventFlags; - - /** - * If EventFlags contain ClipEventKeyPress: Key code to trap - */ - @Conditional("eventFlags.clipEventKeyPress") - public int keyCode; - - /** - * Actions to perform - */ - @HideInRawEdit - public ByteArrayRange actionBytes; - - /** - * Returns a string representation of the object - * - * @return a string representation of the object. - */ - @Override - public String toString() { - return eventFlags.getHeader(keyCode, false); - } - - /** - * Returns header with events converted to string - * - * @return String representation of events - */ - public String getHeader() { - String ret; - ret = eventFlags.toString(); - if (eventFlags.clipEventKeyPress) { - ret = ret.replace("keyPress", "keyPress<" + keyCode + ">"); - } - return ret; - } - - /** - * Converts actions to ASM source - * - * @param exportMode PCode or hex? - * @param writer - * @param actions - * @return ASM source - * @throws java.lang.InterruptedException - */ - @Override - public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { - if (actions == null) { - actions = getActions(); - } - return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); - } - - /** - * Whether or not this object contains ASM source - * - * @return True when contains - */ - @Override - public boolean containsSource() { - return true; - } - - @Override - public ActionList getActions() throws InterruptedException { - return SWF.getCachedActionList(this, listeners); - } - - @Override - public void setActions(List actions) { - actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); - } - - @Override - public ByteArrayRange getActionBytes() { - return actionBytes; - } - - @Override - public void setActionBytes(byte[] actionBytes) { - this.actionBytes = new ByteArrayRange(actionBytes); - SWF.uncache(this); - } - - @Override - public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { - Action.setConstantPools(this, constantPools, false); - } - - @Override - public void setModified() { - if (tag != null) { - tag.setModified(true); - } - } - - @Override - public boolean isModified() { - if (tag != null) { - return tag.isModified(); - } - return false; - } - - @Override - public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { - return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); - } - - List listeners = new ArrayList<>(); - - @Override - public void addDisassemblyListener(DisassemblyListener listener) { - listeners.add(listener); - } - - @Override - public void removeDisassemblyListener(DisassemblyListener listener) { - listeners.remove(listener); - } - - @Override - public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { - writer.appendNoHilight(eventFlags.getHeader(keyCode, false)); - writer.appendNoHilight("{").newLine(); - return writer.indent(); - } - - @Override - public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { - writer.unindent(); - return writer.appendNoHilight("}").newLine(); - } - - @Override - public int getPrefixLineCount() { - return 1; - } - - @Override - public String removePrefixAndSuffix(String source) { - return Helper.unindentRows(1, 1, source); - } - - @Override - public String getExportFileName() { - return eventFlags.getHeader(keyCode, true); - } - - @Override - public Tag getSourceTag() { - return tag; - } - - @Override - public void setSourceTag(Tag t) { - this.tag = t; - this.swf = t.getSwf(); - } -} +/* + * 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.types; + +import com.jpexs.decompiler.flash.DisassemblyListener; +import com.jpexs.decompiler.flash.SWF; +import com.jpexs.decompiler.flash.SWFInputStream; +import com.jpexs.decompiler.flash.action.Action; +import com.jpexs.decompiler.flash.action.ActionList; +import com.jpexs.decompiler.flash.action.ConstantPoolTooBigException; +import com.jpexs.decompiler.flash.dumpview.DumpInfoSpecialType; +import com.jpexs.decompiler.flash.exporters.modes.ScriptExportMode; +import com.jpexs.decompiler.flash.helpers.GraphTextWriter; +import com.jpexs.decompiler.flash.tags.Tag; +import com.jpexs.decompiler.flash.tags.base.ASMSource; +import com.jpexs.decompiler.flash.types.annotations.Conditional; +import com.jpexs.decompiler.flash.types.annotations.HideInRawEdit; +import com.jpexs.decompiler.flash.types.annotations.Internal; +import com.jpexs.helpers.ByteArrayRange; +import com.jpexs.helpers.Helper; +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Event handler + * + * @author JPEXS + */ +public class CLIPACTIONRECORD implements ASMSource, Serializable { + + private String scriptName = "-"; + + @Override + public String getScriptName() { + return scriptName; + } + + public static String keyToString(int key) { + if ((key < CLIPACTIONRECORD.KEYNAMES.length) && (key > 0) && (CLIPACTIONRECORD.KEYNAMES[key] != null)) { + return CLIPACTIONRECORD.KEYNAMES[key]; + } else { + return "" + (char) key; + } + } + + public static final String[] KEYNAMES = { + null, + "", + "", + "", + "", + "", + "", + null, + "", + null, + null, + null, + null, + "", + "", + "", + "", + "", + "", + "", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "" + }; + + @Internal + private SWF swf; + + @Internal + private Tag tag; + + // Constructor for Generic tag editor. TODO:Handle this somehow better + public CLIPACTIONRECORD() { + swf = null; + tag = null; + eventFlags = new CLIPEVENTFLAGS(); + actionBytes = ByteArrayRange.EMPTY; + } + + @Override + public void setScriptName(String scriptName) { + this.scriptName = scriptName; + } + + public CLIPACTIONRECORD(SWF swf, SWFInputStream sis, Tag tag) throws IOException { + this.swf = swf; + this.tag = tag; + eventFlags = sis.readCLIPEVENTFLAGS("eventFlags"); + if (eventFlags.isClear()) { + return; + } + long actionRecordSize = sis.readUI32("actionRecordSize"); + if (eventFlags.clipEventKeyPress) { + keyCode = sis.readUI8("keyCode"); + actionRecordSize--; + } + actionBytes = sis.readByteRangeEx(actionRecordSize, "actionBytes", DumpInfoSpecialType.ACTION_BYTES, sis.getPos()); + } + + @Override + public SWF getSwf() { + return swf; + } + + /** + * Events to which this handler applies + */ + public CLIPEVENTFLAGS eventFlags; + + /** + * If EventFlags contain ClipEventKeyPress: Key code to trap + */ + @Conditional("eventFlags.clipEventKeyPress") + public int keyCode; + + /** + * Actions to perform + */ + @HideInRawEdit + public ByteArrayRange actionBytes; + + /** + * Returns a string representation of the object + * + * @return a string representation of the object. + */ + @Override + public String toString() { + return eventFlags.getHeader(keyCode, false); + } + + /** + * Returns header with events converted to string + * + * @return String representation of events + */ + public String getHeader() { + String ret; + ret = eventFlags.toString(); + if (eventFlags.clipEventKeyPress) { + ret = ret.replace("keyPress", "keyPress<" + keyCode + ">"); + } + return ret; + } + + /** + * Converts actions to ASM source + * + * @param exportMode PCode or hex? + * @param writer + * @param actions + * @return ASM source + * @throws java.lang.InterruptedException + */ + @Override + public GraphTextWriter getASMSource(ScriptExportMode exportMode, GraphTextWriter writer, ActionList actions) throws InterruptedException { + if (actions == null) { + actions = getActions(); + } + return Action.actionsToString(listeners, 0, actions, swf.version, exportMode, writer); + } + + /** + * Whether or not this object contains ASM source + * + * @return True when contains + */ + @Override + public boolean containsSource() { + return true; + } + + @Override + public ActionList getActions() throws InterruptedException { + return SWF.getCachedActionList(this, listeners); + } + + @Override + public void setActions(List actions) { + actionBytes = Action.actionsToByteArrayRange(actions, true, swf.version); + } + + @Override + public ByteArrayRange getActionBytes() { + return actionBytes; + } + + @Override + public void setActionBytes(byte[] actionBytes) { + this.actionBytes = new ByteArrayRange(actionBytes); + SWF.uncache(this); + } + + @Override + public void setConstantPools(List> constantPools) throws ConstantPoolTooBigException { + Action.setConstantPools(this, constantPools, false); + } + + @Override + public void setModified() { + if (tag != null) { + tag.setModified(true); + } + } + + @Override + public boolean isModified() { + if (tag != null) { + return tag.isModified(); + } + return false; + } + + @Override + public GraphTextWriter getActionBytesAsHex(GraphTextWriter writer) { + return Helper.byteArrayToHexWithHeader(writer, actionBytes.getRangeData()); + } + + List listeners = new ArrayList<>(); + + @Override + public void addDisassemblyListener(DisassemblyListener listener) { + listeners.add(listener); + } + + @Override + public void removeDisassemblyListener(DisassemblyListener listener) { + listeners.remove(listener); + } + + @Override + public GraphTextWriter getActionSourcePrefix(GraphTextWriter writer) { + writer.appendNoHilight(eventFlags.getHeader(keyCode, false)); + writer.appendNoHilight("{").newLine(); + return writer.indent(); + } + + @Override + public GraphTextWriter getActionSourceSuffix(GraphTextWriter writer) { + writer.unindent(); + return writer.appendNoHilight("}").newLine(); + } + + @Override + public int getPrefixLineCount() { + return 1; + } + + @Override + public String removePrefixAndSuffix(String source) { + return Helper.unindentRows(1, 1, source); + } + + @Override + public String getExportFileName() { + return eventFlags.getHeader(keyCode, true); + } + + @Override + public Tag getSourceTag() { + return tag; + } + + @Override + public void setSourceTag(Tag t) { + this.tag = t; + this.swf = t.getSwf(); + } +}