gt:                                 Tag parentTag = tim.getParentTag();
context: /*
   *  Copyright (C) 2010-2021 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.exporters;
  
  import com.jpacker.JPacker;
  import com.jpexs.decompiler.flash.AbortRetryIgnoreHandler;
  import com.jpexs.decompiler.flash.EventListener;
  import com.jpexs.decompiler.flash.RetryTask;
  import com.jpexs.decompiler.flash.SWF;
  import com.jpexs.decompiler.flash.action.parser.ActionParseException;
  import com.jpexs.decompiler.flash.configuration.Configuration;
  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.exporters.modes.FontExportMode;
  import com.jpexs.decompiler.flash.exporters.modes.FrameExportMode;
  import com.jpexs.decompiler.flash.exporters.settings.ButtonExportSettings;
  import com.jpexs.decompiler.flash.exporters.settings.FrameExportSettings;
  import com.jpexs.decompiler.flash.exporters.settings.SpriteExportSettings;
  import com.jpexs.decompiler.flash.exporters.shape.CanvasShapeExporter;
  import com.jpexs.decompiler.flash.helpers.BMPFile;
  import com.jpexs.decompiler.flash.helpers.FontHelper;
  import com.jpexs.decompiler.flash.helpers.ImageHelper;
  import com.jpexs.decompiler.flash.tags.DefineEditTextTag;
  import com.jpexs.decompiler.flash.tags.DefineSpriteTag;
  import com.jpexs.decompiler.flash.tags.DefineText2Tag;
  import com.jpexs.decompiler.flash.tags.DefineTextTag;
  import com.jpexs.decompiler.flash.tags.SetBackgroundColorTag;
  import com.jpexs.decompiler.flash.tags.Tag;
  import com.jpexs.decompiler.flash.tags.base.CharacterTag;
  import com.jpexs.decompiler.flash.tags.base.DrawableTag;
  import com.jpexs.decompiler.flash.tags.base.FontTag;
  import com.jpexs.decompiler.flash.tags.base.RenderContext;
  import com.jpexs.decompiler.flash.tags.base.StaticTextTag;
  import com.jpexs.decompiler.flash.tags.base.TextTag;
  import com.jpexs.decompiler.flash.tags.enums.ImageFormat;
  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.timeline.Timelined;
  import com.jpexs.decompiler.flash.types.ColorTransform;
  import com.jpexs.decompiler.flash.types.DynamicTextGlyphEntry;
  import com.jpexs.decompiler.flash.types.GLYPHENTRY;
  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.TEXTRECORD;
  import com.jpexs.decompiler.flash.types.filters.BEVELFILTER;
  import com.jpexs.decompiler.flash.types.filters.COLORMATRIXFILTER;
  import com.jpexs.decompiler.flash.types.filters.CONVOLUTIONFILTER;
  import com.jpexs.decompiler.flash.types.filters.DROPSHADOWFILTER;
  import com.jpexs.decompiler.flash.types.filters.FILTER;
  import com.jpexs.decompiler.flash.types.filters.GLOWFILTER;
  import com.jpexs.decompiler.flash.types.filters.GRADIENTBEVELFILTER;
  import com.jpexs.decompiler.flash.types.filters.GRADIENTGLOWFILTER;
  import com.jpexs.helpers.Helper;
  import com.jpexs.helpers.Path;
  import com.jpexs.helpers.SerializableImage;
  import com.jpexs.helpers.utf8.Utf8Helper;
  import gnu.jpdf.PDFGraphics;
  import gnu.jpdf.PDFJob;
  import java.awt.AlphaComposite;
  import java.awt.Color;
  import java.awt.Composite;
  import java.awt.Font;
  import java.awt.FontMetrics;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
  import java.awt.GraphicsConfiguration;
  import java.awt.Image;
  import java.awt.Paint;
  import java.awt.Point;
  import java.awt.Rectangle;
  import java.awt.RenderingHints;
  import java.awt.Shape;
  import java.awt.Stroke;
  import java.awt.font.FontRenderContext;
  import java.awt.font.GlyphVector;
  import java.awt.geom.AffineTransform;
  import java.awt.image.BufferedImage;
  import java.awt.image.BufferedImageOp;
  import java.awt.image.ImageObserver;
  import java.awt.image.RenderedImage;
  import java.awt.image.renderable.RenderableImage;
  import java.awt.print.PageFormat;
  import java.awt.print.Paper;
  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.text.AttributedCharacterIterator;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.Stack;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import javax.imageio.stream.FileImageOutputStream;
  import javax.imageio.stream.ImageOutputStream;
  import net.kroo.elliot.GifSequenceWriter;
  import net.weiner.kevin.AnimatedGifEncoder;
  import org.monte.media.VideoFormatKeys;
  import org.monte.media.avi.AVIWriter;
  
  /**
   *
   * @author JPEXS
   */
  public class FrameExporter {
  
      private static final Logger logger = Logger.getLogger(FrameExporter.class.getName());
  
      public List<File> exportButtonFrames(AbortRetryIgnoreHandler handler, String outdir, SWF swf, int containerId, List<Integer> frames, ButtonExportSettings settings, EventListener evl) throws IOException, InterruptedException {
          FrameExportMode fem;
          switch (settings.mode) {
              case BMP:
                  fem = FrameExportMode.BMP;
                  break;
              case PNG:
                  fem = FrameExportMode.PNG;
                  break;
              case SVG:
                  fem = FrameExportMode.SVG;
                  break;
              case SWF:
                  fem = FrameExportMode.SWF;
                  break;
              default:
                  throw new Error("Unsupported button export mode: " + settings.mode);
          }
  
          if (frames == null) {
              frames = new ArrayList<>();
              frames.add(0); // todo: export all frames
          }
  
          FrameExportSettings fes = new FrameExportSettings(fem, settings.zoom);
          return exportFrames(handler, outdir, swf, containerId, frames, fes, evl);
      }
  
      public List<File> exportSpriteFrames(AbortRetryIgnoreHandler handler, String outdir, SWF swf, int containerId, List<Integer> frames, SpriteExportSettings settings, EventListener evl) throws IOException, InterruptedException {
          FrameExportMode fem;
          switch (settings.mode) {
              case PNG:
                  fem = FrameExportMode.PNG;
                  break;
              case GIF:
                  fem = FrameExportMode.GIF;
                  break;
              case AVI:
                  fem = FrameExportMode.AVI;
                  break;
              case SVG:
                  fem = FrameExportMode.SVG;
                  break;
              case CANVAS:
                  fem = FrameExportMode.CANVAS;
                  break;
              case PDF:
                  fem = FrameExportMode.PDF;
                  break;
              case BMP:
                  fem = FrameExportMode.BMP;
                  break;
              case SWF:
                  fem = FrameExportMode.SWF;
                  break;
              default:
                  throw new Error("Unsupported sprite export mode");
          }
  
          FrameExportSettings fes = new FrameExportSettings(fem, settings.zoom);
          return exportFrames(handler, outdir, swf, containerId, frames, fes, evl);
      }
  
      public List<File> exportFrames(AbortRetryIgnoreHandler handler, String outdir, final SWF swf, int containerId, List<Integer> frames, final FrameExportSettings settings, final EventListener evl) throws IOException, InterruptedException {
          final List<File> ret = new ArrayList<>();
          if (swf.getTags().isEmpty()) {
              return ret;
          }
          Timeline tim0;
          String path = "";
          if (containerId == 0) {
              tim0 = swf.getTimeline();
          } else {
              tim0 = ((Timelined) swf.getCharacter(containerId)).getTimeline();
              path = File.separator + Helper.makeFileName(swf.getCharacter(containerId).getExportFileName());
          }
  
          final Timeline tim = tim0;
  
          boolean exportAll = frames == null;
          if (frames == null) {
              frames = new ArrayList<>();
              for (Frame frame : tim.getFrames()) {
                  frames.add(frame.frame);
              }
          }
  
          final File foutdir = new File(outdir + path);
          Path.createDirectorySafe(foutdir);
  
          final List<Integer> fframes = frames;
  
          Color backgroundColor = null;
          SetBackgroundColorTag setBgColorTag = swf.getBackgroundColor();
          if (setBgColorTag != null) {
              backgroundColor = setBgColorTag.backgroundColor.toColor();
          }
  
          if (settings.mode == FrameExportMode.SVG) {
              for (int i = 0; i < frames.size(); i++) {
                  if (evl != null) {
                      Tag parentTag = tim.getParentTag();
                      evl.handleExportingEvent("frame", i + 1, frames.size(), parentTag == null ? "" : parentTag.getName());
                  }
  
                  final int fi = i;
                  final Color fbackgroundColor = null;
                  new RetryTask(() -> {
                      int frame = fframes.get(fi);
                      File f = new File(foutdir + File.separator + (frame + 1) + ".svg");
                      try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) {
                          ExportRectangle rect = new ExportRectangle(tim.displayRect);
                          rect.xMax *= settings.zoom;
                          rect.yMax *= settings.zoom;
                          rect.xMin *= settings.zoom;
                          rect.yMin *= settings.zoom;
                          SVGExporter exporter = new SVGExporter(rect, settings.zoom);
                          if (fbackgroundColor != null) {
                              exporter.setBackGroundColor(fbackgroundColor);
                          }
  
                          tim.toSVG(frame, 0, null, 0, exporter, null, 0);
                          fos.write(Utf8Helper.getBytes(exporter.getSVG()));
                      }
                      ret.add(f);
                  }, handler).run();
  
                  if (evl != null) {
                      Tag parentTag = tim.getParentTag();
                      evl.handleExportedEvent("frame", i + 1, frames.size(), parentTag == null ? "" : parentTag.getName());
                  }
              }
  
              return ret;
          }
  
          if (settings.mode == FrameExportMode.CANVAS) {
              if (evl != null) {
                  Tag parentTag = tim.getParentTag();
                  evl.handleExportingEvent("canvas", 1, 1, parentTag == null ? "" : parentTag.getName());
              }
  
              final Timeline ftim = tim;
              final Color fbackgroundColor = null;
              final SWF fswf = swf;
              new RetryTask(() -> {
                  File fcanvas = new File(foutdir + File.separator + "canvas.js");
                  Helper.saveStream(SWF.class.getClassLoader().getResourceAsStream("com/jpexs/helpers/resource/canvas.js"), fcanvas);
                  ret.add(fcanvas);
  
                  File f = new File(foutdir + File.separator + "frames.js");
                  File fmin = new File(foutdir + File.separator + "frames.min.js");
                  int width = (int) (ftim.displayRect.getWidth() * settings.zoom / SWF.unitDivisor);
                  int height = (int) (ftim.displayRect.getHeight() * settings.zoom / SWF.unitDivisor);
                  try (final OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) {
                      fos.write(Utf8Helper.getBytes("\r\n"));
                      fos.write(Utf8Helper.getBytes("var scalingGrids = {};\r\nvar boundRects = {};\r\n"));
                      Set<Integer> library = new HashSet<>();
                      ftim.getNeededCharacters(fframes, library);
  
                      SWF.libraryToHtmlCanvas(fswf, library, fos);
  
                      String currentName = ftim.id == 0 ? "main" : SWF.getTypePrefix(fswf.getCharacter(ftim.id)) + ftim.id;
  
                      StringBuilder sb = new StringBuilder();
                      sb.append("function ").append(currentName).append("(ctx,ctrans,frame,ratio,time){\r\n");
                      sb.append("\tctx.save();\r\n");
                      sb.append("\tctx.transform(1,0,0,1,").append(-ftim.displayRect.Xmin * settings.zoom / SWF.unitDivisor).append(",").append(-ftim.displayRect.Ymin * settings.zoom / SWF.unitDivisor).append(");\r\n");
                      framesToHtmlCanvas(sb, SWF.unitDivisor / settings.zoom, ftim, fframes, 0, null, 0, ftim.displayRect, null, fbackgroundColor);
                      sb.append("\tctx.restore();\r\n");
                      sb.append("}\r\n\r\n");
  
                      sb.append("var frame = -1;\r\n");
                      sb.append("var time = 0;\r\n");
                      sb.append("var frames = [];\r\n");
                      for (int i : fframes) {
                          sb.append("frames.push(").append(i).append(");\r\n");
                      }
                      sb.append("\r\n");
                      RGB backgroundColor1 = new RGB(255, 255, 255);
                      if (setBgColorTag != null) {
                          backgroundColor1 = setBgColorTag.backgroundColor;
                      }
  
                      sb.append("var backgroundColor = \"").append(backgroundColor1.toHexRGB()).append("\";\r\n");
                      sb.append("var originalWidth = ").append(width).append(";\r\n");
                      sb.append("var originalHeight= ").append(height).append(";\r\n");
                      sb.append("function nextFrame(ctx,ctrans){\r\n");
                      sb.append("\tvar oldframe = frame;\r\n");
                      sb.append("\tframe = (frame+1)%frames.length;\r\n");
                      sb.append("\tif(frame==oldframe){time++;}else{time=0;};\r\n");
                      sb.append("\tdrawFrame();\r\n");
                      sb.append("}\r\n\r\n");
  
                      sb.append("function drawFrame(){\r\n");
                      sb.append("\tctx.fillStyle = backgroundColor;\r\n");
                      sb.append("\tctx.fillRect(0,0,canvas.width,canvas.height);\r\n");
                      sb.append("\tctx.save();\r\n");
                      sb.append("\tctx.transform(canvas.width/originalWidth,0,0,canvas.height/originalHeight,0,0);\r\n");
                      sb.append("\t").append(currentName).append("(ctx,ctrans,frames[frame],0,time);\r\n");
                      sb.append("\tctx.restore();\r\n");
                      sb.append("}\r\n\r\n");
                      if (ftim.swf.frameRate > 0) {
                          sb.append("window.setInterval(function(){nextFrame(ctx,ctrans);},").append((int) (1000.0 / ftim.swf.frameRate)).append(");\r\n");
                      }
                      sb.append("nextFrame(ctx,ctrans);\r\n");
                      fos.write(Utf8Helper.getBytes(sb.toString()));
                  }
  
                  boolean packed = false;
                  if (Configuration.packJavaScripts.get()) {
                      try {
                          JPacker.main(new String[]{"-q", "-b", "62", "-o", fmin.getAbsolutePath(), f.getAbsolutePath()});
                          f.delete();
                          packed = true;
                      } catch (Exception | Error e) { // Something wrong in the packer
                          logger.log(Level.WARNING, "JPacker: Cannot minimize script");
                          f.renameTo(fmin);
                      }
                  } else {
                      f.renameTo(fmin);
                  }
  
                  File fh = new File(foutdir + File.separator + "frames.html");
                  try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(fh)); FileInputStream fis = new FileInputStream(fmin)) {
                      fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlPrefix(width, height)));
                      fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsPrefix()));
                      byte[] buf = new byte[1000];
                      int cnt;
                      while ((cnt = fis.read(buf)) > 0) {
                          fos.write(buf, 0, cnt);
                      }
                      if (packed) {
                          fos.write(Utf8Helper.getBytes(";"));
                      }
                      fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getJsSuffix()));
                      fos.write(Utf8Helper.getBytes(CanvasShapeExporter.getHtmlSuffix()));
                  }
  
                  fmin.delete();
  
                  ret.add(f);
              }, handler).run();
  
              if (evl != null) {
                  Tag parentTag = tim.getParentTag();
                  evl.handleExportedEvent("canvas", 1, 1, parentTag == null ? "" : parentTag.getName());
              }
              return ret;
          }
  
          if (settings.mode == FrameExportMode.SWF) {
              Color fBackgroundColor = backgroundColor;
              if (exportAll) {
                  new RetryTask(() -> {
                      File f = new File(foutdir + File.separator + "frames.swf");
  
                      try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) {
                          try {
                              new PreviewExporter().exportSwf(fos, swf.getCharacter(containerId), fBackgroundColor, 0, false);
                          } catch (ActionParseException ex) {
                              Logger.getLogger(MorphShapeExporter.class.getName()).log(Level.SEVERE, null, ex);
                          }
                      }
  
                      ret.add(f);
                  }, handler).run();
              } else {
                  for (Integer frame : fframes) {
                      new RetryTask(() -> {
                          File f = new File(foutdir + File.separator + (frame + 1) + ".swf");
                          Frame fn = (Frame) tim.getFrame(frame);
  
                          try (OutputStream fos = new BufferedOutputStream(new FileOutputStream(f))) {
                              try {
                                  new PreviewExporter().exportSwf(fos, fn, fBackgroundColor, 0, false);
                              } catch (ActionParseException ex) {
                                  Logger.getLogger(MorphShapeExporter.class.getName()).log(Level.SEVERE, null, ex);
                              }
                          }
  
                          ret.add(f);
                      }, handler).run();
                  }
              }
          }
  
          final Color fbackgroundColor = backgroundColor;
          final boolean fusesTransparency = settings.mode == FrameExportMode.PNG || settings.mode == FrameExportMode.GIF;
          final Iterator<BufferedImage> frameImages = new Iterator<BufferedImage>() {
              private int pos = 0;
  
              @Override
              public boolean hasNext() {
                  return fframes.size() > pos;
              }
  
              @Override
              public void remove() {
                  throw new UnsupportedOperationException();
              }
  
              @Override
              public BufferedImage next() {
                  if (!hasNext()) {
                      return null;
                  }
  
                  Tag parentTag = tim.getParentTag();
                  String tagName = parentTag == null ? "" : parentTag.getName();
  
                  if (evl != null) {
                      evl.handleExportingEvent("frame", pos + 1, fframes.size(), tagName);
                  }
  
                  int fframe = fframes.get(pos++);
                  BufferedImage result = SWF.frameToImageGet(tim, fframe, fframe, null, 0, tim.displayRect, new Matrix(), null, fusesTransparency ? null : fbackgroundColor, settings.zoom).getBufferedImage();
  
                  if (evl != null) {
                      evl.handleExportedEvent("frame", pos, fframes.size(), tagName);
                  }
  
                  return result;
              }
          };
  
          switch (settings.mode) {
              case GIF:
                  new RetryTask(() -> {
                      File f = new File(foutdir + File.separator + "frames.gif");
                      makeGIF(frameImages, swf.frameRate, f, evl);
                      ret.add(f);
                  }, handler).run();
                  break;
              case BMP:
                  for (int i = 0; frameImages.hasNext(); i++) {
                      final int fi = i;
                      new RetryTask(() -> {
                          File f = new File(foutdir + File.separator + (fframes.get(fi) + 1) + ".bmp");
                          BMPFile.saveBitmap(frameImages.next(), f);
                          ret.add(f);
                      }, handler).run();
                  }
                  break;
              case PNG:
                  for (int i = 0; frameImages.hasNext(); i++) {
                      final int fi = i;
                      new RetryTask(() -> {
                          File file = new File(foutdir + File.separator + (fframes.get(fi) + 1) + ".png");
                          ImageHelper.write(frameImages.next(), ImageFormat.PNG, file);
                          ret.add(file);
                      }, handler).run();
                  }
  
                  //ShapeExporterBase.clearCache();
                  break;
              case PDF:
                  if (frameImages.hasNext()) {
                      new RetryTask(() -> {
                          File f = new File(foutdir + File.separator + "frames.pdf");
                          PDFJob job = new PDFJob(new BufferedOutputStream(new FileOutputStream(f)));
                          PageFormat pf = new PageFormat();
                          pf.setOrientation(PageFormat.PORTRAIT);
                          Paper p = new Paper();
                          /*BufferedImage img = frameImages.next();
                          p.setSize(img.getWidth() + 10, img.getHeight() + 10);*/
  
                          int pos = 0;
                          RECT rect = tim.displayRect;
  
                          double w = (rect.getWidth() * settings.zoom / SWF.unitDivisor);
                          double h = (rect.getHeight() * settings.zoom / SWF.unitDivisor);
                          p.setSize(w, h);
                          pf.setPaper(p);
                          double zoom = settings.zoom;
                          Matrix m = new Matrix();
                          m.translate(-rect.Xmin * zoom, -rect.Ymin * zoom);
                          m.scale(zoom);
                          Matrix transformation = m;
                          Map<Integer, Font> existingFonts = new HashMap<>();
  
                          while (pos < fframes.size()) {
  
                              if (evl != null) {
  