@Override
  public RECT getRect(Set<BoundedTag> added) {
    if (rectCache.contains(this)) {
      return rectCache.get(this);
    }
    RECT ret = new RECT(Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE);
    HashMap<Integer, Integer> depthMap = new HashMap<>();
    boolean foundSomething = false;
    for (Tag t : subTags) {
      MATRIX m = null;
      int characterId = -1;
      if (t instanceof PlaceObjectTypeTag) {
        PlaceObjectTypeTag pot = (PlaceObjectTypeTag) t;
        m = pot.getMatrix();
        int charId = pot.getCharacterId();
        if (charId > -1) {
          depthMap.put(pot.getDepth(), charId);
          characterId = charId;
        } else {
          Integer chi = depthMap.get(pot.getDepth());
          if (chi != null) {
            characterId = chi;
          }
        }
      }
      if (characterId == -1) {
        continue;
      }
      Set<Integer> need = new HashSet<>();
      need.add(characterId);
      RECT r = getCharacterBounds(need, added);

      if (m != null) {
        AffineTransform trans = SWF.matrixToTransform(m);

        java.awt.Point topleft = new java.awt.Point();
        trans.transform(new java.awt.Point(r.Xmin, r.Ymin), topleft);
        java.awt.Point topright = new java.awt.Point();
        trans.transform(new java.awt.Point(r.Xmax, r.Ymin), topright);
        java.awt.Point bottomright = new java.awt.Point();
        trans.transform(new java.awt.Point(r.Xmax, r.Ymax), bottomright);
        java.awt.Point bottomleft = new java.awt.Point();
        trans.transform(new java.awt.Point(r.Xmin, r.Ymax), bottomleft);

        r.Xmin =
            (int) Math.min(Math.min(Math.min(topleft.x, topright.x), bottomleft.x), bottomright.x);
        r.Ymin =
            (int) Math.min(Math.min(Math.min(topleft.y, topright.y), bottomleft.y), bottomright.y);
        r.Xmax =
            (int) Math.max(Math.max(Math.max(topleft.x, topright.x), bottomleft.x), bottomright.x);
        r.Ymax =
            (int) Math.max(Math.max(Math.max(topleft.y, topright.y), bottomleft.y), bottomright.y);
      }
      ret.Xmin = Math.min(r.Xmin, ret.Xmin);
      ret.Ymin = Math.min(r.Ymin, ret.Ymin);
      ret.Xmax = Math.max(r.Xmax, ret.Xmax);
      ret.Ymax = Math.max(r.Ymax, ret.Ymax);
      foundSomething = true;
    }
    if (!foundSomething) {
      ret = new RECT();
    }
    rectCache.put(this, ret);
    return ret;
  }
  public void createAndShowTempSwf(TreeItem tagObj) {
    SWF swf;
    try {
      if (tempFile != null) {
        tempFile.delete();
      }

      tempFile = File.createTempFile("ffdec_view_", ".swf");
      tempFile.deleteOnExit();

      Color backgroundColor = View.getSwfBackgroundColor();

      if (tagObj instanceof FontTag) { // Fonts are always black on white
        backgroundColor = View.getDefaultBackgroundColor();
      }

      if (tagObj instanceof Frame) {
        Frame fn = (Frame) tagObj;
        swf = fn.getSwf();
        if (fn.timeline.timelined == swf) {
          for (Tag t : swf.tags) {
            if (t instanceof SetBackgroundColorTag) {
              backgroundColor = ((SetBackgroundColorTag) t).backgroundColor.toColor();
              break;
            }
          }
        }
      } else {
        Tag tag = (Tag) tagObj;
        swf = tag.getSwf();
      }

      int frameCount = 1;
      float frameRate = swf.frameRate;
      HashMap<Integer, VideoFrameTag> videoFrames = new HashMap<>();
      if (tagObj instanceof DefineVideoStreamTag) {
        DefineVideoStreamTag vs = (DefineVideoStreamTag) tagObj;
        SWF.populateVideoFrames(vs.getCharacterId(), swf.tags, videoFrames);
        frameCount = videoFrames.size();
      }

      List<SoundStreamBlockTag> soundFrames = new ArrayList<>();
      if (tagObj instanceof SoundStreamHeadTypeTag) {
        soundFrames = ((SoundStreamHeadTypeTag) tagObj).getBlocks();
        frameCount = soundFrames.size();
      }

      if ((tagObj instanceof DefineMorphShapeTag) || (tagObj instanceof DefineMorphShape2Tag)) {
        frameRate = MainPanel.MORPH_SHAPE_ANIMATION_FRAME_RATE;
        frameCount = (int) (MainPanel.MORPH_SHAPE_ANIMATION_LENGTH * frameRate);
      }

      if (tagObj instanceof DefineSoundTag) {
        frameCount = 1;
      }

      if (tagObj instanceof DefineSpriteTag) {
        frameCount = ((DefineSpriteTag) tagObj).frameCount;
      }

      byte[] data;
      try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        SWFOutputStream sos2 = new SWFOutputStream(baos, SWF.DEFAULT_VERSION);
        RECT outrect = new RECT(swf.displayRect);

        if (tagObj instanceof FontTag) {
          outrect.Xmin = 0;
          outrect.Ymin = 0;
          outrect.Xmax = FontTag.PREVIEWSIZE * 20;
          outrect.Ymax = FontTag.PREVIEWSIZE * 20;
        }
        int width = outrect.getWidth();
        int height = outrect.getHeight();

        sos2.writeRECT(outrect);
        sos2.writeFIXED8(frameRate);
        sos2.writeUI16(frameCount); // framecnt

        /*FileAttributesTag fa = new FileAttributesTag();
        sos2.writeTag(fa);
        */
        new SetBackgroundColorTag(swf, new RGB(backgroundColor)).writeTag(sos2);

        if (tagObj instanceof Frame) {
          Frame fn = (Frame) tagObj;
          Timelined parent = fn.timeline.timelined;
          List<Tag> subs = fn.timeline.tags;
          List<Integer> doneCharacters = new ArrayList<>();
          int frameCnt = 0;
          for (Tag t : subs) {
            if (t instanceof ShowFrameTag) {
              frameCnt++;
              continue;
            }
            if (frameCnt > fn.frame) {
              break;
            }

            if (t instanceof DoActionTag || t instanceof DoInitActionTag) {
              // todo: Maybe DoABC tags should be removed, too
              continue;
            }

            Set<Integer> needed = new HashSet<>();
            t.getNeededCharactersDeep(needed);
            for (int n : needed) {
              if (!doneCharacters.contains(n)) {
                classicTag(swf.getCharacter(n)).writeTag(sos2);
                doneCharacters.add(n);
              }
            }
            if (t instanceof CharacterTag) {
              int characterId = ((CharacterTag) t).getCharacterId();
              if (!doneCharacters.contains(characterId)) {
                doneCharacters.add(((CharacterTag) t).getCharacterId());
              }
            }
            classicTag(t).writeTag(sos2);

            if (parent != null) {
              if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag pot = (PlaceObjectTypeTag) t;
                int chid = pot.getCharacterId();
                int depth = pot.getDepth();
                MATRIX mat = pot.getMatrix();
                if (mat == null) {
                  mat = new MATRIX();
                }
                mat = Helper.deepCopy(mat);
                if (parent instanceof BoundedTag) {
                  RECT r = ((BoundedTag) parent).getRect();
                  mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2;
                  mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2;
                } else {
                  mat.translateX += width / 2;
                  mat.translateY += height / 2;
                }
                new PlaceObject2Tag(
                        swf, false, false, false, false, false, true, false, true, depth, chid, mat,
                        null, 0, null, 0, null)
                    .writeTag(sos2);
              }
            }
          }
          new ShowFrameTag(swf).writeTag(sos2);
        } else {

          boolean isSprite = false;
          if (tagObj instanceof DefineSpriteTag) {
            isSprite = true;
          }
          int chtId = 0;
          if (tagObj instanceof CharacterTag) {
            chtId = ((CharacterTag) tagObj).getCharacterId();
          }

          if (tagObj instanceof DefineBitsTag) {
            JPEGTablesTag jtt = swf.getJtt();
            if (jtt != null) {
              jtt.writeTag(sos2);
            }
          } else if (tagObj instanceof AloneTag) {
          } else {
            Set<Integer> needed = new HashSet<>();
            ((Tag) tagObj).getNeededCharactersDeep(needed);
            for (int n : needed) {
              if (isSprite && chtId == n) {
                continue;
              }

              CharacterTag characterTag = swf.getCharacter(n);
              if (characterTag instanceof DefineBitsTag) {
                JPEGTablesTag jtt = swf.getJtt();
                if (jtt != null) {
                  jtt.writeTag(sos2);
                }
              }

              classicTag(characterTag).writeTag(sos2);
            }
          }

          classicTag((Tag) tagObj).writeTag(sos2);

          MATRIX mat = new MATRIX();
          mat.hasRotate = false;
          mat.hasScale = false;
          mat.translateX = 0;
          mat.translateY = 0;
          if (tagObj instanceof BoundedTag) {
            RECT r = ((BoundedTag) tagObj).getRect();
            mat.translateX = -r.Xmin;
            mat.translateY = -r.Ymin;
            mat.translateX = mat.translateX + width / 2 - r.getWidth() / 2;
            mat.translateY = mat.translateY + height / 2 - r.getHeight() / 2;
          } else {
            mat.translateX = width / 4;
            mat.translateY = height / 4;
          }
          if (tagObj instanceof FontTag) {

            FontTag ft = (FontTag) classicTag((Tag) tagObj);

            int countGlyphsTotal = ft.getGlyphShapeTable().size();
            int countGlyphs =
                Math.min(SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal);
            int fontId = ft.getFontId();
            int cols = (int) Math.ceil(Math.sqrt(countGlyphs));
            int rows = (int) Math.ceil(((float) countGlyphs) / ((float) cols));
            if (rows == 0) {
              rows = 1;
              cols = 1;
            }
            int x = 0;
            int y = 0;
            int firstGlyphIndex = fontPageNum * SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW;
            countGlyphs =
                Math.min(
                    SHAPERECORD.MAX_CHARACTERS_IN_FONT_PREVIEW, countGlyphsTotal - firstGlyphIndex);
            List<SHAPE> shapes = ft.getGlyphShapeTable();
            int maxw = 0;
            for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) {
              RECT b = shapes.get(f).getBounds();
              if (b.Xmin == Integer.MAX_VALUE) {
                continue;
              }
              if (b.Ymin == Integer.MAX_VALUE) {
                continue;
              }
              int w = (int) (b.getWidth() / ft.getDivider());
              if (w > maxw) {
                maxw = w;
              }
              x++;
            }

            x = 0;

            int BORDER = 3 * 20;

            int textHeight = height / rows;

            while (maxw * textHeight / 1024.0 > width / cols - 2 * BORDER) {
              textHeight--;
            }

            MATRIX tmat = new MATRIX();
            for (int f = firstGlyphIndex; f < firstGlyphIndex + countGlyphs; f++) {
              if (x >= cols) {
                x = 0;
                y++;
              }
              List<TEXTRECORD> rec = new ArrayList<>();
              TEXTRECORD tr = new TEXTRECORD();

              RECT b = shapes.get(f).getBounds();
              int xmin = b.Xmin == Integer.MAX_VALUE ? 0 : (int) (b.Xmin / ft.getDivider());
              xmin *= textHeight / 1024.0;
              int ymin = b.Ymin == Integer.MAX_VALUE ? 0 : (int) (b.Ymin / ft.getDivider());
              ymin *= textHeight / 1024.0;
              int w = (int) (b.getWidth() / ft.getDivider());
              w *= textHeight / 1024.0;
              int h = (int) (b.getHeight() / ft.getDivider());
              h *= textHeight / 1024.0;

              tr.fontId = fontId;
              tr.styleFlagsHasFont = true;
              tr.textHeight = textHeight;
              tr.xOffset = -xmin;
              tr.yOffset = 0;
              tr.styleFlagsHasXOffset = true;
              tr.styleFlagsHasYOffset = true;
              tr.glyphEntries = new ArrayList<>(1);
              tr.styleFlagsHasColor = true;
              tr.textColor = new RGB(0, 0, 0);
              GLYPHENTRY ge = new GLYPHENTRY();

              double ga = ft.getGlyphAdvance(f);
              int cw = ga == -1 ? w : (int) (ga / ft.getDivider() * textHeight / 1024.0);

              ge.glyphAdvance = 0;
              ge.glyphIndex = f;
              tr.glyphEntries.add(ge);
              rec.add(tr);

              tmat.translateX = x * width / cols + width / cols / 2 - w / 2;
              tmat.translateY = y * height / rows + height / rows / 2;
              new DefineTextTag(swf, 999 + f, new RECT(0, cw, ymin, ymin + h), new MATRIX(), rec)
                  .writeTag(sos2);
              new PlaceObject2Tag(
                      swf, false, false, false, true, false, true, true, false, 1 + f, 999 + f,
                      tmat, null, 0, null, 0, null)
                  .writeTag(sos2);
              x++;
            }
            new ShowFrameTag(swf).writeTag(sos2);
          } else if ((tagObj instanceof DefineMorphShapeTag)
              || (tagObj instanceof DefineMorphShape2Tag)) {
            new PlaceObject2Tag(
                    swf, false, false, false, true, false, true, true, false, 1, chtId, mat, null,
                    0, null, 0, null)
                .writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);
            for (int ratio = 0; ratio < 65536; ratio += 65536 / frameCount) {
              new PlaceObject2Tag(
                      swf, false, false, false, true, false, true, false, true, 1, chtId, mat, null,
                      ratio, null, 0, null)
                  .writeTag(sos2);
              new ShowFrameTag(swf).writeTag(sos2);
            }
          } else if (tagObj instanceof SoundStreamHeadTypeTag) {
            for (SoundStreamBlockTag blk : soundFrames) {
              blk.writeTag(sos2);
              new ShowFrameTag(swf).writeTag(sos2);
            }
          } else if (tagObj instanceof DefineSoundTag) {
            ExportAssetsTag ea = new ExportAssetsTag(swf);
            DefineSoundTag ds = (DefineSoundTag) tagObj;
            ea.tags.add(ds.soundId);
            ea.names.add("my_define_sound");
            ea.writeTag(sos2);
            List<Action> actions;
            DoActionTag doa;

            doa = new DoActionTag(swf, null);
            actions =
                ASMParser.parse(
                    0,
                    false,
                    "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\"\n"
                        + "Push \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\" 0.0 \"Sound\"\n"
                        + "NewObject\n"
                        + "SetMember\n"
                        + "Push \"my_define_sound\" 1 \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"attachSound\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "Stop",
                    swf.version,
                    false);
            doa.setActions(actions);
            doa.writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);

            actions =
                ASMParser.parse(
                    0,
                    false,
                    "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"start\"\n"
                        + "StopSounds\n"
                        + "Push \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\" 0.0 \"Sound\"\n"
                        + "NewObject\n"
                        + "SetMember\n"
                        + "Push \"my_define_sound\" 1 \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"attachSound\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "Push 9999 0.0 2 \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"start\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "Stop",
                    swf.version,
                    false);
            doa.setActions(actions);
            doa.writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);

            actions =
                ASMParser.parse(
                    0,
                    false,
                    "ConstantPool \"_root\" \"my_sound\" \"Sound\" \"my_define_sound\" \"attachSound\" \"onSoundComplete\" \"start\" \"execParam\"\n"
                        + "StopSounds\n"
                        + "Push \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\" 0.0 \"Sound\"\n"
                        + "NewObject\n"
                        + "SetMember\n"
                        + "Push \"my_define_sound\" 1 \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"attachSound\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "Push \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"onSoundComplete\"\n"
                        + "DefineFunction2 \"\" 0 2 false true true false true false true false false  {\n"
                        + "Push 0.0 register1 \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"start\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "}\n"
                        + "SetMember\n"
                        + "Push \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"execParam\"\n"
                        + "GetMember\n"
                        + "Push 1 \"_root\"\n"
                        + "GetVariable\n"
                        + "Push \"my_sound\"\n"
                        + "GetMember\n"
                        + "Push \"start\"\n"
                        + "CallMethod\n"
                        + "Pop\n"
                        + "Stop",
                    swf.version,
                    false);
            doa.setActions(actions);
            doa.writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);

            actions = ASMParser.parse(0, false, "StopSounds\n" + "Stop", swf.version, false);
            doa.setActions(actions);
            doa.writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);

            new ShowFrameTag(swf).writeTag(sos2);
          } else if (tagObj instanceof DefineVideoStreamTag) {

            new PlaceObject2Tag(
                    swf, false, false, false, false, false, true, true, false, 1, chtId, mat, null,
                    0, null, 0, null)
                .writeTag(sos2);
            List<VideoFrameTag> frs = new ArrayList<>(videoFrames.values());
            Collections.sort(
                frs,
                new Comparator<VideoFrameTag>() {
                  @Override
                  public int compare(VideoFrameTag o1, VideoFrameTag o2) {
                    return o1.frameNum - o2.frameNum;
                  }
                });
            boolean first = true;
            int ratio = 0;
            for (VideoFrameTag f : frs) {
              if (!first) {
                ratio++;
                new PlaceObject2Tag(
                        swf, false, false, false, true, false, false, false, true, 1, 0, null, null,
                        ratio, null, 0, null)
                    .writeTag(sos2);
              }
              f.writeTag(sos2);
              new ShowFrameTag(swf).writeTag(sos2);
              first = false;
            }
          } else if (tagObj instanceof DefineSpriteTag) {
            DefineSpriteTag s = (DefineSpriteTag) tagObj;
            Tag lastTag = null;
            for (Tag t : s.subTags) {
              if (t instanceof EndTag) {
                break;
              } else if (t instanceof PlaceObjectTypeTag) {
                PlaceObjectTypeTag pt = (PlaceObjectTypeTag) t;
                MATRIX m = pt.getMatrix();
                MATRIX m2 = new Matrix(m).preConcatenate(new Matrix(mat)).toMATRIX();
                pt.writeTagWithMatrix(sos2, m2);
                lastTag = t;
              } else {
                t.writeTag(sos2);
                lastTag = t;
              }
            }
            if (!s.subTags.isEmpty() && (lastTag != null) && (!(lastTag instanceof ShowFrameTag))) {
              new ShowFrameTag(swf).writeTag(sos2);
            }
          } else {
            new PlaceObject2Tag(
                    swf, false, false, false, true, false, true, true, false, 1, chtId, mat, null,
                    0, null, 0, null)
                .writeTag(sos2);
            new ShowFrameTag(swf).writeTag(sos2);
          }
        } // not showframe

        new EndTag(swf).writeTag(sos2);
        data = baos.toByteArray();
      }

      try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile))) {
        SWFOutputStream sos = new SWFOutputStream(fos, Math.max(10, swf.version));
        sos.write("FWS".getBytes());
        sos.write(swf.version);
        sos.writeUI32(sos.getPos() + data.length + 4);
        sos.write(data);
        fos.flush();
      }
      if (flashPanel != null) {
        flashPanel.displaySWF(tempFile.getAbsolutePath(), backgroundColor, frameRate);
      }
      showFlashViewerPanel();
    } catch (IOException | ActionParseException ex) {
      Logger.getLogger(PreviewPanel.class.getName()).log(Level.SEVERE, null, ex);
    }
  }