unit dflash_holder;

//
//  The unit
//    * loads and validates .swf files
//    * builds static data: the dictionary, meshs for rendering, frame
//      snapshots etc
//

{$MODE FPC}
{$MODESWITCH OUT}
{$MODESWITCH RESULT}
{$MODESWITCH ADVANCEDRECORDS}

interface

uses
  dbitmem,
  dstream,
  dstrnum,
  dansiview,
  dvector2i,
  dvector2f,
  dmatrix4f,
  dcolors,
  dswf_primitives,
  dtriangulation,
  dlinegen,
  deng,
  dengcache,
  ddraw,
  dadler32,
  dzlib_decompress_utils,
  ddynamicarray,
  dflash_coloring,
  dflash_cache,
  dflash_scenebuffer,
  libtess2_tesselator;

const
  FLASH_VERSION     = 1; // used in cache files

type
PFlashCharacter = ^TFlashCharacter;
PFlashControl = ^TFlashControl;
PFlashHolder = ^TFlashHolder;

TPoints = array of TVec2f;

PPath = ^TPath;
TPath = object
  LineWidth: Single;
  Color: Cardinal;
  FPoints: TPoints;
  FTriangles: array of TVec2f;
  FillType: LongInt;
  TrianglesData: PVec2f;
  TrianglesCount: SizeUInt; // number of points
  FSceneBufferChunk: TFlashSceneBuffer.TChunk; // see dflash_scenebuffer
  procedure AddPoint(const V: TVec2f);
  procedure Triangulate;
end;

PShape = ^TShape;
TShape = object
public
  FCharacterId: LongInt;
  FShape: PtrUInt; // 1, 2, 3 or 4
  FShapeBounds: TSwfRECT;
  FEdgeBounds: TSwfRECT;
  FBits: PtrUInt; // Only while loading
  FFillStyles: TDynamicArray; // Only while loading
  FLineStyles: TDynamicArray; // Only while loading
  FNumFillBits: Cardinal; // Only while loading
  FNumLineBits: Cardinal; // Only while loading
  FColoring: TFlashColoring; // Only while loading
  FFinal: array of TPath;
  FSceneBufferChunk: TFlashSceneBuffer.TChunk; // see dflash_scenebuffer
  FMin, FMax, FRepos, FRescale: TVec2f;
  FLastUp, FLastDown: TVec2f;
public
  procedure Init;
  procedure Done;
  function GetLineStyle(LineStyle: Cardinal): PSwfLINESTYLE2;
  function GetFillStyle(FillStyle: Cardinal): PSwfFILLSTYLE;
  procedure ContoursToFinal;
  procedure StartLineCap(const A, B: TVec2f; Width: Single);
  procedure CloseCap(const B: TVec2f; Width: Single);
  procedure AddLineQuad(const Up, Down: TVec2f);
  procedure AddLineRoundJoint(const P, A, B: TVec2f; Width: Single; Iterations: LongInt);
  procedure GenerateLines;
  procedure AddLine(const A, B, C: TVec2f; Curved: Boolean; Style: LongInt);
end;

PButtonAtom = ^TButtonAtom;
TButtonAtom = object
  ButtonRecord: TSwfButtonRecord;
  //C: PFlashCharacter;
end;

PButton = ^TButton;
TButton = object
public
  FButtonId: LongInt;
  FVersion: PtrUInt;
  FFlags: Byte;
  FActionOffset: UInt16;
  FAtoms: array of PButtonAtom;
  FUp: array of PButtonAtom;
  FOver: array of PButtonAtom;
  FDown: array of PButtonAtom;
  FHit: array of PButtonAtom;
  function ReadFilters(Data, DataEnd: Pointer; out N: Byte; out F: PSwfFILTER; out NextData: Pointer): Boolean;
  function ReadButton(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
end;

TFlashCharacter = object
public
  FType: PtrUInt;
  FCharacterId: UInt16;
  // Shape1, Shape2, Shape3, Shape4
  FShape: TShape;
  // Sprite
  FHolder: PFlashHolder;
  // Button, Button2
  FButton: TButton;
  FFrameCount: LongInt;
public
  function ReadButton(Data: Pointer;
                      DataEnd: Pointer;
                      Version: Cardinal): Boolean;
  function IsShape: Boolean; inline;
end;

TFlashControl = object
private
  FTag: Cardinal;
  FColor: Cardinal;
  // PlaceObject
  FPlaceFlags: Byte;
  FCharacterId: LongInt;
  FDepth: LongInt;
public
  PlaceObject2: TSwfPlaceObject2;
  procedure Init(Tag: Cardinal);
  function ReadBackground(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
  property Tag: Cardinal read FTag;
  property Color: Cardinal read FColor;
  property CharacterId: LongInt read FCharacterId;
  property Depth: LongInt read FDepth;
end;

PFlashLabel = ^TFlashLabel;
TFlashLabel = record
  Name: AnsiString;
  Index: PtrUInt;
  AnchorFlag: Boolean;
end;

PFlashDisplayItem = ^TFlashDisplayItem;
TFlashDisplayItem = record
  Place: TSwfPlaceObject2;
  Character: PFlashCharacter; // = GetCharacter(Place.CharacterId)
  // First and last frames in between the CharacterId sustain.
  // Needed for random GoTo from one frame to another to
  // determine when actualy reset an object in display list.
  First, Last: LongInt;
  PrevFrame: PFlashDisplayItem;
end;

PFlashFrame = ^TFlashFrame;
TFlashFrame = record
//  Prepared frame's data.
  // Items are sorted by Depth.
  Count: LongInt;
  DisplayList: PFlashDisplayItem;
end;

TFlashDisplayListBuilder = object
//  Used while loading.
private
  FCurrentFrame: LongInt;
  FDisplayItems: TDynamicArray;
  function FindLeftActual(Item, Limit: PFlashDisplayItem): PFlashDisplayItem;
  function FindRightActual(Item, Limit: PFlashDisplayItem): PFlashDisplayItem;
  function Find(Depth: LongInt): LongInt;
  // Returns index for Depth. Uses binary search.
public
  procedure Init;
  procedure Done;
  function PlaceObject2(const Place: TSwfPlaceObject2): Boolean;
  function RemoveObject(CharacterId, Depth: UInt16): Boolean;
  function RemoveObject2(Depth: UInt16): Boolean;
  procedure ShowFrame(out FlashFrame: TFlashFrame);
  procedure Finalize; // computes Last of all frames
end;

TFlashMovieClip = object
      // 1. a movieclip is a sprite on steroids.
      // 2. a movieclip has a timeline, a sprite does not.  that has major
      //    implications about the properties, methods and events that a
      //    movieclip has and a sprite does not.
      // 3. the movieclip class is dynamic (ie, you can add properties), the
      //    sprite class is not.
end;

TFlashHolder = object
private
  FReady: Boolean;
  FErrorCode: LongInt;
  FErrorMsg: PAnsiChar;
  FSize: TVec2f;
  FFrameRate: Single;
  FFrameDuration: Single;
  FFrameCount: Cardinal;
  FCharacters: array of TFlashCharacter;
  FControls: array of TFlashControl;
  FScenes: array of TFlashLabel;
  FParent: PFlashHolder;
  FFrames: array of TFlashFrame; // array of FFrameCount elements
  FFrameLabels: array of TFlashLabel;
  // Currently building objects
  FCurCharacter: PFlashCharacter;
  FCurShape: PShape;
  FCurShapeVersion: PtrInt;
  FDisplayListBuilder: TFlashDisplayListBuilder;
  FCache: TFlashCache;
  function GetControlCount: Cardinal; inline;
  function Error(Code: LongInt; Msg: PAnsiChar): Boolean;
  function ReadStyles(Data, DataEnd: Pointer;
                      out NextData: Pointer): Boolean;
  //
  // Reads FILLSTYLEARRAY, LINESTYLEARRAY, NumFillBits, NumLineBits
  //

  function ReadShapeRecords(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
  function ReadSHAPEWITHSTYLE(Data, DataEnd: Pointer): Boolean;
  function ReadShape(Data, DataEnd: Pointer; ShapeVersion: LongInt): Boolean;
  function ReadTags(Data, DataEnd: Pointer): Boolean;
  function ReadTagsLoop(Data, DataEnd: Pointer): Boolean;

  procedure BuildCache(const Name: AnsiString; Hash: UInt32);
  //
  //  Call it at the end of load.
  //
public
  procedure Init; overload;
  procedure Init(const D: TData); overload;
  procedure Done; inline;
  procedure Clear; inline;
  function Load(const Name: AnsiString; Data, DataEnd: Pointer): Boolean; inline; overload;
  function Load(const D: TData): Boolean; inline; overload;
  procedure DumpToLog;
  function GetCharacter(CharacterId: PtrUInt): PFlashCharacter;
  function GetControl(Index: PtrUInt): PFlashControl;
  function GetFrameByName(Name: TAnsiView): LongInt;
  function FrameLabelsCount: LongInt; inline;
  function FrameLabels: PFlashLabel; inline;
  procedure LinkToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
  procedure LoadToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
  procedure InvalidateSceneBufferChunks(SceneBuffer: PFlashSceneBuffer);
  property Ready: Boolean read FReady;
  property ErrorCode: LongInt read FErrorCode;
  property ErrorMsg: PAnsiChar read FErrorMsg;
  property Size: TVec2f read FSize;
  property FrameRate: Single read FFrameRate;
  property FrameDuration: Single read FFrameDuration;
  property FrameCount: Cardinal read FFrameCount;
  property ControlCount: Cardinal read GetControlCount;
end;

implementation

function AsUInt16(Data: Pointer): UInt16; inline;
begin
  Result := {$IF Defined(ENDIAN_BIG)}SwapEndian{$ENDIF}(UInt16(Data^));
end;

function AsUInt32(Data: Pointer): UInt32; inline;
begin
  Result := {$IF Defined(ENDIAN_BIG)}SwapEndian{$ENDIF}(UInt32(Data^));
end;

// Triangulation callback
procedure OnTriangle(Path: Pointer; A, B, C: TTriangulator.PPoint);
begin
  SetLength(PPath(Path)^.FTriangles, Length(PPath(Path)^.FTriangles) + 3);
  PPath(Path)^.FTriangles[High(PPath(Path)^.FTriangles) - 2] := Vec2f(A^.X, A^.Y);
  PPath(Path)^.FTriangles[High(PPath(Path)^.FTriangles) - 1] := Vec2f(B^.X, B^.Y);
  PPath(Path)^.FTriangles[High(PPath(Path)^.FTriangles) - 0] := Vec2f(C^.X, C^.Y);
end;

procedure TPath.AddPoint(const V: TVec2f);
begin
  SetLength(FPoints, Length(FPoints) + 1);
  FPoints[High(FPoints)] := V;
end;

procedure TPath.Triangulate;
var
  Triangulator: TTriangulator;
  I: LongInt;
begin
  Triangulator.Init;
  Triangulator.Userdata := @Self;
  Triangulator.OnTriangle := @OnTriangle;
  Triangulator.AddPolygon(@FPoints[0], Length(FPoints));
  if Length(FPoints) < 3 then begin
    Log('WARNING: not enough points');
  end;
  if not Triangulator.Triangulate then begin
    Log('ERROR failed triangulation for points (' + IntToStr(Length(FPoints)) + ')');
    for I := 0 to High(FPoints) do begin
      Log('  ' + FloatToStr(FPoints[I].X, 2) + ' ' + FloatToStr(FPoints[I].Y, 2));
    end;
    Log('  (end of triangulation points)');
  end;
  if Length(FTriangles) = 0 then begin
    Log('WARNING: no triangles');
  end;
  Triangulator.Done;
end;

procedure AddMiddlePoints(var Points: TPoints;
                          const A, B, Control: TVec2f;
                          N: LongInt);
var
  I: LongInt;
  F1, F2: Single;
  T1, T2: TVec2f;
  P1, P2: TVec2f;
begin
  for I := 1 to N - 1 do begin
    F1 := I / N;
    F2 := (I + 1) / N;
    T1 := Vec2f(Sqr(1 - F1) * A.X + 2 * F1 * (1 - F1) * Control.X + Sqr(F1) * B.X,
                Sqr(1 - F1) * A.Y + 2 * F1 * (1 - F1) * Control.Y + Sqr(F1) * B.Y);
    T2 := Vec2f(Sqr(1 - F2) * A.X + 2 * F2 * (1 - F2) * Control.X + Sqr(F2) * B.X,
                Sqr(1 - F2) * A.Y + 2 * F2 * (1 - F2) * Control.Y + Sqr(F2) * B.Y);
    SetLength(Points, Length(Points) + 1);
    Points[High(Points)] := T1;
  end;
end;

procedure TShape.Init;
begin
  daInit(@FFillStyles, SizeOf(TSwfFILLSTYLE), 16);
  daInit(@FLineStyles, SizeOf(TSwfLINESTYLE2), 16);
end;

procedure TShape.Done;
begin
  daDone(@FLineStyles);
  daDone(@FFillStyles);
end;

function TShape.GetLineStyle(LineStyle: Cardinal): PSwfLINESTYLE2;
begin
  if (LineStyle > 0) and (LineStyle <= daLength(@FLineStyles)) then begin
    Exit(FLineStyles[LineStyle - 1]);
  end else
    Exit(nil);
end;

function TShape.GetFillStyle(FillStyle: Cardinal): PSwfFILLSTYLE;
begin
  if (FillStyle > 0) and (FillStyle <= daLength(@FFillStyles)) then begin
    Exit(FFillStyles[FillStyle - 1]);
  end else
    Exit(nil);
end;

function GetElements(Triangles: TPoints): LongInt;
label
  LNext;
var
  Uniq: TPoints;
  I, J: LongInt;
begin
  SetLength(Uniq, Length(Triangles));
  Result := 0;
  for I := 0 to High(Triangles) do begin
    for J := 0 to Result - 1 do begin
      if (Triangles[I].X = Uniq[J].X) and (Triangles[I].Y = Uniq[J].Y) then
        goto LNext;
    end;
    Uniq[Result] := Triangles[I];
    Inc(Result);
  LNext:
  end;
end;

procedure TShape.ContoursToFinal;
var
  I, J, L, Start: LongInt;
  C, S: PFlashContour;
  //Triangulator: TTriangulator;
  tess: PTESStesselator;
  Count: LongInt;
  Indices: PTESSindex;
  Vertices: PTESSreal;
  Time: LongInt;
  E: PEdge;
  EdgesCount: LongInt;
  function AlreadyDone(FillType: LongInt): Boolean;
  var
    K: LongInt;
  begin
    for K := 0 to High(FFinal) do begin
      if FFinal[K].FillType = FillType then
        Exit(True);
    end;
    Exit(False);
  end;
begin
  Log('CHARACTERID = ' + IntToStr(FCharacterId));
  Time := Eng.Time;
  Log('Triangulation started, ' + IntToStr(Length(FColoring.Contours)) + ' contours');
  for I := 0 to High(FColoring.Contours) do begin
    C := @FColoring.Contours[I];
    if not C^.IsClosed then begin
      Log('  Not closed!');
      continue;
    end;
    if AlreadyDone(C^.FillStyle) then
      continue;
    SetLength(FFinal, Length(FFinal) + 1);
    SetLength(FFinal[High(FFinal)].FTriangles, 0);
    //Triangulator.Init;
    //Triangulator.Userdata := @FFinal[High(FFinal)];
    //Triangulator.OnTriangle := @OnTriangle;
    tess := tessNewTess(nil);
    FFinal[High(FFinal)].LineWidth := 0;
    if C^.FillStyle > 0 then
      FFinal[High(FFinal)].Color := GetFillStyle(C^.FillStyle)^.Color;
    FFinal[High(FFinal)].FillType := C^.FillStyle;
    Log('FillStyle = ' + IntToStr(C^.FillStyle));
    for J := I to High(FColoring.Contours) do begin
      S := @FColoring.Contours[J];
      if (S^.FillStyle <> C^.FillStyle) then
        continue;
      //Log('  CONTOUR (' + IntToStr(Length(S^.Edges) - 1) + ' closed=' + IntToStr(Ord(S^.IsClosed)) + '):');
      if not S^.IsClosed then
        continue;
      Start := Length(FFinal[High(FFinal)].FPoints);
      EdgesCount := 0;
      E := S^.First;
      while E^.Next <> nil do begin
        Inc(EdgesCount);
        E := E^.Next;
      end;
      SetLength(FFinal[High(FFinal)].FPoints, Length(FFinal[High(FFinal)].FPoints) + EdgesCount);
      E := S^.First;
      L := 0;
      while E^.Next <> nil do begin
        FFinal[High(FFinal)].FPoints[Start + L] := E^.Point^;
        E := E^.Next;
        Inc(L);
      end;
      tessAddContour(tess, 2, @FFinal[High(FFinal)].FPoints[Start], SizeOf(TVec2f), EdgesCount);
      Log('  +' + IntToStr(EdgesCount) + ' edges');
    end;
    if tessTesselate(tess, TESS_WINDING_POSITIVE, TESS_POLYGONS, 3, 2, nil) then begin
      Count    := tessGetElementCount(tess);
      Indices  := tessGetElements(tess);
      Vertices := tessGetVertices(tess);
      for J := 0 to Count - 1 do begin
        SetLength(FFinal[High(FFinal)].FTriangles, Length(FFinal[High(FFinal)].FTriangles) + 3);
        FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 2] := Vec2f(Vertices[2 * Indices[3 * J + 0]],
                                                                                            Vertices[2 * Indices[3 * J + 0] + 1]);
        FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 1] := Vec2f(Vertices[2 * Indices[3 * J + 1]],
                                                                                            Vertices[2 * Indices[3 * J + 1] + 1]);
        FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 0] := Vec2f(Vertices[2 * Indices[3 * J + 2]],
                                                                                            Vertices[2 * Indices[3 * J + 2] + 1]);
      end;
    end else
      Log('ERROR failed tessTesselate');
    FFinal[High(FFinal)].TrianglesData := @FFinal[High(FFinal)].FTriangles[0];
    FFinal[High(FFinal)].TrianglesCount := Length(FFinal[High(FFinal)].FTriangles);
    tessDeleteTess(tess);
    //if not Triangulator.Triangulate then begin
    //  Log('ERROR failed triangulation for points (' + IntToStr(Length(FFinal[High(FFinal)].FPoints)) + ')');
      //for I := 0 to High(FPoints) do begin
      //  Log('  ' + FloatToStr(FPoints[I].X, 2) + ' ' + FloatToStr(FPoints[I].Y, 2));
      //end;
      //Log('  (end of triangulation points)');
    //end;
    //Triangulator.Done;
    Log('  GENERATED: ' + IntToStr(GetElements(FFinal[High(FFinal)].FTriangles)) + ' elements, ' + IntToStr(Length(FFinal[High(FFinal)].FTriangles) div 3) + ' triangles');
  end;
  //for I := 0 to High(FFinal) do begin
  //  FFinal[I].Triangulate;
  //end;
  Log('Triangulation done, it took ' + IntToStr(Eng.Time - Time) + 'ms');
  GenerateLines;
end;

procedure TShape.StartLineCap(const A, B: TVec2f; Width: Single);
var
  Normal: TVec2f;
begin
  Normal := Mul2f(Norm2f(Vec2f(A.Y - B.Y, B.X - A.X)), Width / 2);
  FLastUp := A - Normal;
  FLastDown := A + Normal;
end;

procedure TShape.CloseCap(const B: TVec2f; Width: Single);
var
  A: TVec2f;
  Normal: TVec2f;
begin
  A := Mul2f(FLastUp + FLastDown, 0.5);
  Normal := Mul2f(Norm2f(Vec2f(A.Y - B.Y, B.X - A.X)), Width / 2);
  AddLineQuad(A - Normal, A + Normal);
end;

procedure TShape.AddLineQuad(const Up, Down: TVec2f);
begin
  SetLength(FFinal[High(FFinal)].FTriangles, Length(FFinal[High(FFinal)].FTriangles) + 6);
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 5] := FLastUp;
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 4] := FLastDown;
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 3] := Down;
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 2] := Down;
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 1] := Up;
  FFinal[High(FFinal)].FTriangles[High(FFinal[High(FFinal)].FTriangles) - 0] := FLastUp;
  FLastUp := Up;
  FLastDown := Down;
end;

procedure TShape.AddLineRoundJoint(const P, A, B: TVec2f; Width: Single; Iterations: LongInt);
var
  T1, T2, N1, N2, N: TVec2f;
  CornerN, RoundN: TVec2f;
  I: LongInt;
  F: Single;
begin
  T1 := Vec2f(A.X - P.X, A.Y - P.Y);
  T2 := Vec2f(B.X - A.X, B.Y - A.Y);
  if Len2f(T1) < 0.000001 then
    Exit;
  if Len2f(T2) < 0.000001 then
    Exit;
  N1 := Norm2f(Vec2f(- T1.Y, T1.X));
  N2 := Norm2f(Vec2f(- T2.Y, T2.X));
  N := Mul2f(Add2f(N1, N2), 0.5);
  if Len2f(N) < 0.000001 then
    Exit;
  if T1.X * T2.Y - T1.Y * T2.X >= 0 then begin
    for I := 0 to Iterations do begin
      F := I / Iterations;
      //AddLineQuad(A - Mul2f(Norm2f(Mul2f(N1, F)) + Norm2f(Mul2f(N2, F)), Width / 2), A + Mul2f(N, Width));
    end;
  end else begin
    for I := 0 to Iterations do begin
      F := I / Iterations;
      //AddLineQuad(A - Mul2f(N, Width / 2), A + Mul2f(Norm2f(Mul2f(N1, F)) + Norm2f(Mul2f(N2, F)), Width / 2));
    end;
  end;
  AddLineQuad(A - Mul2f(Norm2f(N), Width / 2),
              A + Mul2f(Norm2f(N), Width / 2));
end;

function GetNormal(E, EBackup: PEdge): TVec2f;
var
  P, A, B: TVec2f;
  T1, T2, N1, N2, N: TVec2f;
begin
  if E = nil then begin
    if EBackup <> nil then
      Exit(GetNormal(EBackup, nil));
    Exit(Vec2f(0, 0));
  end;
  A := E^.Point^;
  if (E^.Next = nil) and (E^.Prev = nil) then
    Exit(Vec2f(0, 0));
  if E^.Prev <> nil then begin
    P := E^.Prev^.Point^;
  end else
    P := A;
  if E^.Next <> nil then begin
    B := E^.Next^.Point^;
  end else
    B := P;
  if E^.Prev <> nil then begin
    P := E^.Prev^.Point^;
  end else
    P := B;
  T1 := Vec2f(A.X - P.X, A.Y - P.Y);
  T2 := Vec2f(B.X - A.X, B.Y - A.Y);
  N1 := Norm2f(Vec2f(- T1.Y, T1.X));
  N2 := Norm2f(Vec2f(- T2.Y, T2.X));
  Exit(Norm2f(Mul2f(N1 + N2, 0.5)));
end;

procedure TShape.GenerateLines;
var
  I, J: LongInt;
  C: PFlashContour;
  E: PEdge;
  P, A, B: TVec2f;
  Normal: TVec2f;
  LineStyle: PSwfLINESTYLE2;
  Segments: array of TLineSegment;
  Mesh: TLineMesh;
  PrevCount: SizeUInt;
begin
  Segments := nil;
  SetLength(FFinal, Length(FFinal) + 1);
  FFinal[High(FFinal)].Color := RGBA(0, 0, 0, $ff);
  FFinal[High(FFinal)].FillType := 1;
  for I := 0 to High(FColoring.Contours) do begin
    C := @FColoring.Contours[I];
    E := C^.First;
    if (E = nil) or (E^.Next = nil) then
      continue;
    //StartLineCap(E^.Point^, E^.Next^.Point^, 1);
    E := E^.Next;
    while E <> nil do begin
      LineStyle := GetLineStyle(E^.LineStyle);
      if LineStyle = nil then begin
        // TODO Close with cap
        //CloseCap(E^.Point^, 1);
        E := E^.Next;
        // if (E <> nil) and (E^.Next <> nil) then
        //   StartLineCap(E^.Point^, E^.Next^.Point^, 1);
        continue;
      end;
      if (LineStyle^.StartCapStyle <> SWF_CAP_SQUARE) then begin
        Log('SWF WARNING: non-square caps not supported, got ' + IntToStr(LineStyle^.StartCapStyle));
      end;
      if (LineStyle^.EndCapStyle <> SWF_CAP_SQUARE) then begin
        Log('SWF WARNING: non-square caps not supported, got ' + IntToStr(LineStyle^.EndCapStyle));
      end;
      SetLength(Segments, Length(Segments) + 1);
      Segments[High(Segments)].V := E^.Point^;
      Segments[High(Segments)].Width := LineStyle^.Width / 20;
      Segments[High(Segments)].Color := LineStyle^.Color;
      Segments[High(Segments)].JointType := JT_BEVEL; // TODO actual joint type
      if E^.Next = nil then begin
        if C^.IsClosed then begin
          // TODO end cap
        end else begin
          SetLength(Segments, Length(Segments) + 1);
          Segments[High(Segments)] := Segments[0];
          break;
        end;
      end;
      E := E^.Next;
    end;

    // TODO actual mesh with faces
    BuildLineMesh(@Segments[0], Length(Segments), Mesh);
    PrevCount := Length(FFinal[High(FFinal)].FTriangles);
    SetLength(FFinal[High(FFinal)].FTriangles, PrevCount + Mesh.IndicesCount);
    for J := 0 to Mesh.IndicesCount - 1 do begin
      FFinal[High(FFinal)].FTriangles[PrevCount + J] := Mesh.Vertices[Mesh.Indices[J]];
    end;
    SetLength(Segments, 0);
    FreeLineMesh(Mesh);
  end;
  FFinal[High(FFinal)].TrianglesData := @FFinal[High(FFinal)].FTriangles[0];
  FFinal[High(FFinal)].TrianglesCount := Length(FFinal[High(FFinal)].FTriangles);
end;

procedure TShape.AddLine(const A, B, C: TVec2f; Curved: Boolean; Style: LongInt);
begin
  // TODO
end;

function TButton.ReadFilters(Data, DataEnd: Pointer; out N: Byte; out F: PSwfFILTER; out NextData: Pointer): Boolean;
var
  I: PtrInt;
begin
  // TODO check limits
  N := PByte(Data)^;
  Inc(Data);
  F := GetMem(SizeOf(TSwfFILTER) * N);
  Log('Filters ' + IntToStr(N));
  for I := 0 to PtrInt(N) - 1 do begin
    if not SwfFILTER(Data, DataEnd, F[I], Data) then begin
      Log('ERROR bad filter ' + IntToStr(F[I].FilterID));
      Exit(False);
    end;
    Log('FILTER ' + IntToStr(F[I].FilterID));
  end;
  if N <> 0 then begin
    Log('NOTE filters are not supported yet, skipped');
  end;
  NextData := Data;
  Exit(True);
end;

function TButton.ReadButton(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
label
  LActions;
var
  B: PButtonAtom;
begin
  FFlags := PByte(Data)^;
  Inc(Data);
  FActionOffset := AsUInt16(Data);
  Inc(Data, 2);
  while Data < DataEnd do begin // BUTTONRECORDs
    if PByte(Data)^ = 0 then begin // CharacterEndFlag
      if Length(FHit) = 0 then
        Log('WARNING no hit for ' + IntToStr(FButtonId));
      if Length(FDown) = 0 then
        Log('WARNING no down for ' + IntToStr(FButtonId));
      if Length(FOver) = 0 then
        Log('WARNING no over for ' + IntToStr(FButtonId));
      if Length(FUp) = 0 then
        Log('WARNING no up for ' + IntToStr(FButtonId));
      Inc(Data);
      goto LActions;
    end;
    New(B);
    if not SwfButtonRecord_Begin(Data, DataEnd, FVersion, B^.ButtonRecord, Data) then begin
      Log('ERROR bad ButtonRecord');
      Exit(False);
    end;
    if (FVersion = 2) and B^.ButtonRecord.ButtonHasFilterList then begin
      if not ReadFilters(Data, DataEnd, B^.ButtonRecord.NumberOfFilters, B^.ButtonRecord.FilterList, Data) then begin
        Log('ERROR bad filters');
        Exit(False);
      end;
    end;
    if not SwfButtonRecord_End(Data, DataEnd, FVersion, B^.ButtonRecord, Data) then begin
      Log('ERROR bad ButtonRecord ending');
      Exit(False);
    end;
    // TODO fill B^.C
    SetLength(FAtoms, Length(FAtoms) + 1);
    FAtoms[High(FAtoms)] := B;
    if B^.ButtonRecord.ButtonStateHitTest then begin
      SetLength(FHit, Length(FHit) + 1);
      FHit[High(FHit)] := B;
    end;
    if B^.ButtonRecord.ButtonStateDown then begin
      SetLength(FDown, Length(FDown) + 1);
      FDown[High(FDown)] := B;
    end;
    if B^.ButtonRecord.ButtonStateOver then begin
      SetLength(FOver, Length(FOver) + 1);
      FOver[High(FOver)] := B;
    end;
    if B^.ButtonRecord.ButtonStateUp then begin
      SetLength(FUp, Length(FUp) + 1);
      FUp[High(FUp)] := B;
    end;
    //Log('Done ButtonRecord ' + IntToStr(B^.ButtonRecord.CharacterId));
  end;
  Log('ERROR unexpected end of button');

LActions:
  Log('ButtonRecords: ' + IntToStr(Length(FAtoms)) + ' (' + IntToStr(Length(FHit)) + ' ' + IntToStr(Length(FDown)) + ' ' + IntToStr(Length(FOver)) + ' ' + IntToStr(Length(FUp)) + ')');
  if PByte(Data)^ <> 0 then begin
    Log('NOTE Actions are not supported for buttons yet');
  end;
  NextData := Data;
  Exit(True);
end;

function TFlashCharacter.ReadButton(Data: Pointer;
                                    DataEnd: Pointer;
                                    Version: Cardinal): Boolean;
begin
  // TODO check limits
  //FButton.Init;
  FCharacterId := AsUInt16(Data);
  FButton.FButtonId := FCharacterId;
  FButton.FVersion := Version;
  Exit(FButton.ReadButton(Data + 2, DataEnd, Data));
end;

function TFlashCharacter.IsShape: Boolean;
begin
  // All tag IDs are less than 256, so we can safely cast the FType to Byte
  Exit(Byte(FType) in [TAG_DefineShape,
                       TAG_DefineShape2,
                       TAG_DefineShape3,
                       TAG_DefineShape4]);
end;

procedure TFlashControl.Init(Tag: Cardinal);
begin
  FTag := Tag;
end;

function TFlashControl.ReadBackground(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
const
  RGB_MASK: array[0 .. 3] of Byte = ($ff, $ff, $ff, $00);
begin
  FTag := TAG_SetBackgroundColor;
  FColor := UInt32(Data^) and PUInt32(@RGB_MASK[0])^;
  NextData := Data + 3;
  Exit(True);
end;

function TFlashDisplayListBuilder.FindLeftActual(Item, Limit: PFlashDisplayItem): PFlashDisplayItem;
begin
  while Item >= Limit do begin
    if Item^.Place.Depth <> 0 then
      Exit(Item);
    Dec(Item);
  end;
  Exit(nil);
end;

function TFlashDisplayListBuilder.FindRightActual(Item, Limit: PFlashDisplayItem): PFlashDisplayItem;
begin
  while Item > Limit do begin
    if Item^.Place.Depth <> 0 then
      Exit(Item);
    Inc(Item);
  end;
  Exit(nil);
end;

function TFlashDisplayListBuilder.Find(Depth: LongInt): LongInt;
var
  L, R, M: LongInt;
  Item, Actual: PFlashDisplayItem;
begin
  L := 0;
  R := daLength(@FDisplayItems);
  while L < R do begin
    Item := FDisplayItems[L + (R - L) div 2];
    Actual := FindRightActual(Item, FDisplayItems[R]);
    if Actual <> nil then begin
      if Actual^.Place.Depth = Depth then begin
        Exit(Actual - PFlashDisplayItem(FDisplayItems[0]));
      end else if Depth > Actual^.Place.Depth then begin
        L := Actual - PFlashDisplayItem(FDisplayItems[0]) + 1;
        continue;
      end;
    end;
    Actual := FindLeftActual(Item, FDisplayItems[L]);
    if Actual <> nil then begin
      if Actual^.Place.Depth = Depth then begin
        Exit(Actual - PFlashDisplayItem(FDisplayItems[0]));
      end else if Depth < Actual^.Place.Depth then begin
        R := Actual - PFlashDisplayItem(FDisplayItems[0]);
        continue;
      end else begin
        Exit(Actual - PFlashDisplayItem(FDisplayItems[0]) + 1);
      end;
    end else
      Exit(Item - PFlashDisplayItem(FDisplayItems[0]));
  end;
  Exit(L);
end;

procedure TFlashDisplayListBuilder.Init;
begin
  FCurrentFrame := 0;
  daInit(@FDisplayItems, SizeOf(TFlashDisplayItem), 16);
end;

procedure TFlashDisplayListBuilder.Done;
begin
  daDone(@FDisplayItems);
end;

function TFlashDisplayListBuilder.PlaceObject2(const Place: TSwfPlaceObject2): Boolean;
var
  I, J: LongInt;
  Item: PFlashDisplayItem;
  Prev: PFlashDisplayItem;
begin
  I := Find(Place.Depth);
  Item := FDisplayItems[I];
  if (I > daHigh(@FDisplayItems))
  or ((Item^.Place.Depth <> 0) and (Item^.Place.Depth <> Place.Depth)) then begin
    // add new
    if not Place.PlaceFlagHasCharacter then
      Exit(False); // PlaceObject no CharacterId
    daInc(@FDisplayItems, 1);
    for J := daHigh(@FDisplayItems) downto I + 1 do
      PFlashDisplayItem(FDisplayItems[J])^ := PFlashDisplayItem(FDisplayItems[J - 1])^;
    Item := FDisplayItems[I];
    Item^.Place := Place;
    Item^.First := FCurrentFrame;
    Item^.PrevFrame := nil;
  end else if Item^.Place.Depth = 0 then begin // add to existence slot
    if not Place.PlaceFlagHasCharacter then
      Exit(False); // PlaceObject no CharacterId
    Item^.Place := Place;
    Item^.First := FCurrentFrame;
    Item^.PrevFrame := nil;
  end else begin // modify
    if Place.PlaceFlagHasCharacter then begin
      // end of previous character
      Prev := Item^.PrevFrame;
      while Prev <> nil do begin
        Prev^.First := Item^.First;
        Prev^.Last := FCurrentFrame - 1;
        Prev := Prev^.PrevFrame;
      end;
      // set new
      Item^.First := FCurrentFrame;
      Item^.Last := -1;
      Item^.PrevFrame := nil;
      Item^.Place.CharacterId := Place.CharacterId;
    end;
    if Place.PlaceFlagHasClipActions then
      Log('WARNING PlaceFlagHasClipActions not supported');
    if Place.PlaceFlagHasClipDepth then
      Log('WARNING PlaceFlagHasClipDepth not supported');
    if Place.PlaceFlagHasName then
      Log('WARNING PlaceFlagHasName not supported');
    if Place.PlaceFlagHasRatio then
      Log('WARNING PlaceFlagHasRatio not supported');
    if Place.PlaceFlagHasColorTransform then
      Log('WARNING PlaceFlagHasColorTransform not supported');
    if Place.PlaceFlagHasMatrix then begin
      Item^.Place.Matrix := Place.Matrix;
    end;
  end;
  Exit(True);
end;

function TFlashDisplayListBuilder.RemoveObject(CharacterId, Depth: UInt16): Boolean;
var
  I: LongInt;
begin
  I := Find(Depth);
  if (I > daHigh(@FDisplayItems)) or (PFlashDisplayItem(FDisplayItems[I])^.Place.Depth <> Depth) then
    Exit(False);
  if PFlashDisplayItem(FDisplayItems[I])^.Place.CharacterId <> CharacterId then
    Exit(False);
  // Just invalidate Depth
  PFlashDisplayItem(FDisplayItems[I])^.Place.Depth := 0;
  Exit(True);
end;

function TFlashDisplayListBuilder.RemoveObject2(Depth: UInt16): Boolean;
var
  I: LongInt;
begin
  I := Find(Depth);
  if (I > daHigh(@FDisplayItems)) or (PFlashDisplayItem(FDisplayItems[I])^.Place.Depth <> Depth) then
    Exit(False);
  // Just invalidate Depth
  PFlashDisplayItem(FDisplayItems[I])^.Place.Depth := 0;
  Exit(True);
end;

procedure TFlashDisplayListBuilder.ShowFrame(out FlashFrame: TFlashFrame);
var
  I: LongInt;
  Item, ItemEnd: PFlashDisplayItem;
  OutItem: PFlashDisplayItem;
begin
  // 1. compute actual items for the frame in the display list
  FlashFrame.Count := 0;
  Item := FDisplayItems[0];
  ItemEnd := daEnd(@FDisplayItems);
  while Item < ItemEnd do begin
    if Item^.Place.Depth <> 0 then
      Inc(FlashFrame.Count);
    Inc(Item);
  end;
  // 2. copy the actual items and reset first marks
  //    we do not need to sort them, the sort order is maintained in PlaceObject/RemoveObject
  FlashFrame.DisplayList := GetMem(FlashFrame.Count * SizeOf(TFlashDisplayItem));
  Item := FDisplayItems[0];
  ItemEnd := daEnd(@FDisplayItems);
  OutItem := FlashFrame.DisplayList;
  while Item < ItemEnd do begin
    if Item^.Place.Depth <> 0 then begin
      OutItem^ := Item^;
      OutItem^.Last := FCurrentFrame;
      Item^.PrevFrame := OutItem;
      Inc(OutItem);
    end;
    Inc(Item);
  end;
  // 3. increment the frame counter
  Inc(FCurrentFrame);
end;

procedure TFlashDisplayListBuilder.Finalize;
var
  Item, ItemEnd, Prev: PFlashDisplayItem;
begin
  Item := FDisplayItems[0];
  ItemEnd := daEnd(@FDisplayItems);
  while Item < ItemEnd do begin
    Prev := Item^.PrevFrame;
    while Prev <> nil do begin
      Prev^.Last := FCurrentFrame - 1;
      Prev := Prev^.PrevFrame;
    end;
    Inc(Item);
  end;
end;

function TFlashHolder.GetControlCount: Cardinal;
begin
  Result := Length(FControls);
end;

function TFlashHolder.Error(Code: LongInt; Msg: PAnsiChar): Boolean;
begin
  FErrorCode := Code;
  FErrorMsg := Msg;
  Exit(Code = 0);
end;

function TFlashHolder.ReadStyles(Data, DataEnd: Pointer;
                                 out NextData: Pointer): Boolean;
var
  OldCount, Count: UInt16;
  FillArr: PSwfFILLSTYLE;
  LineArr: PSwfLINESTYLE2;
  I: LongInt;
begin
  if not SwfCountExtended(Data, DataEnd, Count, Data) then
    Exit(Error(1, 'bad FillStyleCount'));
  OldCount := daLength(@FCurShape^.FFillStyles);
  daInc(@FCurShape^.FFillStyles, Count);
  FillArr := PSwfFILLSTYLE(FCurShape^.FFillStyles[OldCount]);
  for I := 0 to Count - 1 do begin
    if not SwfFILLSTYLE_Begin(Data, DataEnd, FCurShapeVersion, FillArr[I], Data) then
      Exit(Error(1, 'bad FILLSTYLE'));
    FillArr[I].Memory := GetMem(FillArr[I].NeedMemory); // TODO memory leak
    if not SwfFILLSTYLE_End(Data, DataEnd, FCurShapeVersion, FillArr[I], Data) then
      Exit(Error(1, 'bad FILLSTYLE'));
  end;

  if not SwfCountExtended(Data, DataEnd, Count, Data) then
    Exit(Error(1, 'bad LineStyleCount'));
  OldCount := daLength(@FCurShape^.FLineStyles);
  daInc(@FCurShape^.FLineStyles, Count);
  LineArr := PSwfLINESTYLE2(FCurShape^.FLineStyles[OldCount]);
  for I := 0 to Count - 1 do begin
    if FCurShapeVersion = 4 then begin
      if not SwfLINESTYLE2_Begin(Data, DataEnd, LineArr[I], Data) then
        Exit(Error(1, 'bad LINESTYLE2'));
      LineArr[I].Memory := GetMem(LineArr[I].NeedMemory);
      if not SwfLINESTYLE2_End(Data, DataEnd, LineArr[I], Data) then
        Exit(Error(1, 'bad LINESTYLE2'));
    end else begin
      if not SwfLINESTYLE(Data, DataEnd, FCurShapeVersion, LineArr[I], Data) then
        Exit(Error(1, 'bad LINESTYLE'));
    end;
  end;

  FCurShape^.FNumFillBits := PByte(Data)^ shr 4;
  FCurShape^.FNumLineBits := PByte(Data)^ and $f;
  Inc(Data);

  NextData := Data;
  Exit(True);
end;

function TFlashHolder.ReadShapeRecords(Data, DataEnd: Pointer; out NextData: Pointer): Boolean;
var
  StyleChangeRecord: TSwfStyleChangeRecord;
  _Cur, _Next: TVec2i;
  P: TVec2f;
  EdgeRecord: TSwfEdgeRecord;
  Bits: PtrInt;
  TempData: Pointer;
  TempBits: PtrInt;
  BaseLineStyle, BaseFillStyle0, BaseFillStyle1: LongInt;
  CurLineStyle, CurFillStyle0, CurFillStyle1: LongInt;
  C: PFlashContour;
  S: AnsiString;
  ShapeIndex: LongInt;
begin with FCurShape^ do begin
  Result := False; // make compiler happy
  ShapeIndex := 0;
  BaseLineStyle := 1;
  BaseFillStyle0 := 1;
  BaseFillStyle1 := 1;
  Bits := 8;

  _Cur := Vec2i(0, 0);
  CurFillStyle0 := 0;
  CurFillStyle1 := 0;
  CurLineStyle := 0;
  FColoring.Start;
  while Data < DataEnd do begin
    if ReadBit(Data, Bits, Data, Bits) = 0 then begin // non-edge record
      if not SwfStyleChangeRecord(Data, DataEnd, Bits, FNumFillBits, FNumLineBits, StyleChangeRecord, Data, Bits) then
        Exit(Error(1, 'bad StyleChangeRecord'));
      if StyleChangeRecord.Flags = 0 then begin // EndShapeRecord
        FColoring.Finish;
        ContoursToFinal;
        Exit(True);
      end;
      if StyleChangeRecord.StateLineStyle then begin
        if StyleChangeRecord.LineStyle > 0 then begin
          CurLineStyle := StyleChangeRecord.LineStyle + BaseLineStyle - 1;
        end else
          CurLineStyle := 0;
      end;
      if StyleChangeRecord.StateFillStyle0 then begin
        if StyleChangeRecord.FillStyle0 > 0 then begin
          CurFillStyle0 := StyleChangeRecord.FillStyle0 + BaseFillStyle0 - 1;
        end else
          CurFillStyle0 := 0;
      end;
      if StyleChangeRecord.StateFillStyle1 then begin
        if StyleChangeRecord.FillStyle1 > 0 then begin
          CurFillStyle1 := StyleChangeRecord.FillStyle1 + BaseFillStyle1 - 1;
        end else
          CurFillStyle1 := 0;
      end;
      if StyleChangeRecord.StateMoveTo then begin
        _Next := Vec2i(StyleChangeRecord.MoveDeltaX, StyleChangeRecord.MoveDeltaY);
        P := Vec2f(_Cur.X / 20, _Cur.Y / 20);
        _Cur := _Next;
      end;
      if StyleChangeRecord.StateNewStyles then begin
        Log('StateNewStyles');
        Inc(ShapeIndex);
        CurFillStyle0 := 0;
        CurFillStyle1 := 0;
        CurLineStyle := 0;
        BaseLineStyle := daLength(@FLineStyles) + 1;
        BaseFillStyle0 := daLength(@FFillStyles) + 1;
        BaseFillStyle1 := daLength(@FFillStyles) + 1;
        Data := AlignBits(Data, Bits);
        Bits := 8;
        if not ReadStyles(Data, DataEnd, Data) then
          Exit(False);
      end;
    end else begin
      if not SwfEdgeRecord(Data, DataEnd, Bits, EdgeRecord, Data, Bits) then
        Exit(False);
      _Next := _Cur + Vec2i(EdgeRecord.DeltaX, EdgeRecord.DeltaY);
      P := Vec2f(_Cur.X / 20, _Cur.Y / 20);
      if (CurFillStyle0 <> 0) or (CurFillStyle1 <> 0) then begin
          if CurFillStyle0 <> 0 then begin
            FColoring.AddEdge(Vec2f(_Next.X / 20, _Next.Y / 20),
                              P + Vec2f(EdgeRecord.ControlDeltaX / 20, EdgeRecord.ControlDeltaY / 20),
                              P,
                              EdgeRecord.Curved,
                              CurFillStyle0,
                              CurLineStyle,
                              False,
                              ShapeIndex);
            if CurFillStyle1 = 0 then begin
              // Generate external edge
            end;
          end;
          if CurFillStyle1 <> 0 then begin
            FColoring.AddEdge(P,
                              P + Vec2f(EdgeRecord.ControlDeltaX / 20, EdgeRecord.ControlDeltaY / 20),
                              Vec2f(_Next.X / 20, _Next.Y / 20),
                              EdgeRecord.Curved,
                              CurFillStyle1,
                              CurLineStyle,
                              True,
                              ShapeIndex);
            if CurFillStyle1 = 0 then begin
              // Generate external edge
            end;
          end;
      end;
      if CurLineStyle <> 0 then begin
        AddLine(P,
                P + Vec2f(EdgeRecord.ControlDeltaX / 20, EdgeRecord.ControlDeltaY / 20),
                Vec2f(_Next.X / 20, _Next.Y / 20),
                EdgeRecord.Curved,
                CurLineStyle);
      end;
      //TODO support curved
      //if EdgeRecord.Curved then begin
      //  AddMiddlePoints(FPaths[High(FPaths)].FPoints,
      //                  P,
      //                  P + Vec2f(EdgeRecord.DeltaX / 20, EdgeRecord.DeltaY / 20),
      //                  P + Vec2f(EdgeRecord.ControlDeltaX / 20, EdgeRecord.ControlDeltaY / 20),
      //                  20);
      //end;
      //if (EdgeRecord.DeltaX <> 0) or (EdgeRecord.DeltaY <> 0) then begin
        //SetLength(FPaths[High(FPaths)].FPoints, Length(FPaths[High(FPaths)].FPoints) + 1);
        //FPaths[High(FPaths)].FPoints[High(FPaths[High(FPaths)].FPoints)] := CurrentPos + Vec2f(EdgeRecord.DeltaX / 20, EdgeRecord.DeltaY / 20);
        //CurrentPos := FPaths[High(FPaths)].FPoints[High(FPaths[High(FPaths)].FPoints)];
      //end;
      _Cur := _Next;
    end;
  end;
  Log('WARNING no EndShapeRecord');
  Exit(True);
end end;

function TFlashHolder.ReadSHAPEWITHSTYLE(Data, DataEnd: Pointer): Boolean;
label
  LExit1, LExit2;
begin with FCurShape^ do begin
  Result := False; // make compiler happy
  // TODO check limits

  daSetLength(@FFillStyles, 0);
  daSetLength(@FLineStyles, 0);
  if not ReadStyles(Data, DataEnd, Data) then
    Exit(False);

  FColoring.Init;
  Result := ReadShapeRecords(Data, DataEnd, Data);
  FColoring.Done;
  if Result then
    Exit;

LExit2:
  // TODO FreeMem all
  daDone(@FLineStyles);
  FColoring.Done;
LExit1:
  // TODO FreeMem all
  daDone(@FFillStyles);
  Exit(False);
end end;

function TFlashHolder.ReadShape(Data, DataEnd: Pointer; ShapeVersion: LongInt): Boolean;
var
  CachedCharacter: TFlashCache.PCharacter;
  CachedLayer: TFlashCache.PLayer;
  I: LongInt;
begin
  SetLength(FCharacters, Length(FCharacters) + 1);
  FCharacters[High(FCharacters)].FType := TAG_DefineShape;
  FCharacters[High(FCharacters)].FCharacterId := AsUInt16(Data);
  FCharacters[High(FCharacters)].FShape.FSceneBufferChunk.Invalidate;
  //Log('CharacterId = ' + IntToStr(FCharacters[High(FCharacters)].FCharacterId));
  FCurCharacter := @FCharacters[High(FCharacters)];
  FCurShape := @FCharacters[High(FCharacters)].FShape;
  // TODO check limits
  FCurShape^.Init;
  FCurShape^.FCharacterId := FCurCharacter^.FCharacterId;
  FCurShapeVersion := ShapeVersion;
  if not SwfRECT(Data + 2, DataEnd, FCurShape^.FShapeBounds, Data) then
    Exit(False);
  if ShapeVersion > 3 then begin
    if not SwfRECT(Data, DataEnd, FCurShape^.FEdgeBounds, Data) then
      Exit(False);
    FCurShape^.FBits := PByte(Data)^;
    Inc(Data);
  end else
    FCurShape^.FShapeBounds := FCurShape^.FEdgeBounds;
  if dengHasCacheStorage and FCache.Loaded then begin
    // We try to read the shape from the cache
    CachedCharacter := FCache.FindCharacter(FCurShape^.FCharacterId);
    if (CachedCharacter <> nil) and (FCache.CharacterType(CachedCharacter) = 1) then begin
      SetLength(FCurShape^.FFinal, FCache.ShapeLayersCount(CachedCharacter));
      for I := 0 to High(FCurShape^.FFinal) do begin
        CachedLayer := FCache.ShapeLayer(CachedCharacter, I);
        if FCache.LayerType(CachedLayer) = 1 then begin
          SetLength(FCurShape^.FFinal[I].FTriangles, 0);
          FCurShape^.FFinal[I].Color := FCache.LayerColor(CachedLayer);
          FCurShape^.FFinal[I].TrianglesCount := FCache.LayerCount(CachedLayer);
          FCurShape^.FFinal[I].TrianglesData := FCache.LayerTriangles(CachedLayer);
          Log('LAYER FROM CACHE: #' + HexStr(FCurShape^.FFinal[I].Color, 8) + ' Count=' + IntToStr(FCurShape^.FFinal[I].TrianglesCount) + ' Offset=' + IntToStr(CachedLayer^.Offset));
        end else
          Log('ERROR bad cached layer type');
      end;
    end;
    // Skip reading SHAPEWITHSTYLE
    Exit(True);
  end;
  if not ReadSHAPEWITHSTYLE(Data, DataEnd) then
    Exit(False);
  Exit(True);
end;

function TFlashHolder.ReadTagsLoop(Data, DataEnd: Pointer): Boolean;
var
  TagBody, TagEnd: Pointer;
  I: SizeInt;
  SceneCount: SizeUInt;
  FrameLabelCount: SizeUInt;
  Temp, EOS: Pointer;
  ShapeBounds, EdgeBounds: TSwfRECT;
  PlaceObject2: TSwfPlaceObject2;
  RemoveObject: TSwfRemoveObject;
  NextFrameNum: LongInt;
begin
  NextFrameNum := 0;
  while Data + 2 <= DataEnd do begin
    case SwfTag(Data, DataEnd, TagBody, TagEnd) of
      -1: Exit(Error(1, 'unexpected end'));
      TAG_PlaceObject: begin
        Log('WARNING TAG_PlaceObject is not supported yet');
      end;
      TAG_PlaceObject2: begin
        SetLength(FControls, Length(FControls) + 1);
        FControls[High(FControls)].Init(TAG_PlaceObject2);
        if not SwfPlaceObject2(TagBody, TagEnd, PlaceObject2, TagBody) then
          Exit(Error(1, 'bad PlaceObject2'));
        FControls[High(FControls)].FDepth := PlaceObject2.Depth;
        FControls[High(FControls)].FCharacterId := PlaceObject2.CharacterId;
        FControls[High(FControls)].PlaceObject2 := PlaceObject2;
        if not FDisplayListBuilder.PlaceObject2(PlaceObject2) then
          Exit(Error(1, 'inconsistent PlaceObject2'));
        Log('PlaceObject2 CharacterId=' + IntToStr(PlaceObject2.CharacterId));
      end;
      TAG_PlaceObject3: begin
        Log('WARNING TAG_PlaceObject3 is not supported yet');
      end;
      TAG_RemoveObject: begin
        SetLength(FControls, Length(FControls) + 1);
        FControls[High(FControls)].Init(TAG_RemoveObject);
        if not SwfRemoveObject(TagBody, TagEnd, RemoveObject, TagBody) then
          Exit(Error(1, 'bad RemoveObject'));
        FControls[High(FControls)].FCharacterId := RemoveObject.CharacterId;
        FControls[High(FControls)].FDepth := RemoveObject.Depth;
        if not FDisplayListBuilder.RemoveObject(RemoveObject.CharacterId, RemoveObject.Depth) then
          Exit(Error(1, 'inconsistent RemoveObject2'));
      end;
      TAG_RemoveObject2: begin
        SetLength(FControls, Length(FControls) + 1);
        FControls[High(FControls)].Init(TAG_RemoveObject);
        if not SwfRemoveObject2(TagBody, TagEnd, RemoveObject, TagBody) then
          Exit(Error(1, 'bad RemoveObject2'));
        FControls[High(FControls)].FCharacterId := RemoveObject.CharacterId;
        FControls[High(FControls)].FDepth := RemoveObject.Depth;
        if not FDisplayListBuilder.RemoveObject2(RemoveObject.Depth) then
          Exit(Error(1, 'inconsistent RemoveObject2'));
      end;
      TAG_ShowFrame: begin
        SetLength(FControls, Length(FControls) + 1);
        FControls[High(FControls)].Init(TAG_ShowFrame);
        if NextFrameNum > High(FFrames) then begin
          Log('WARNING: too many ShowFrames');
        end else
          FDisplayListBuilder.ShowFrame(FFrames[NextFrameNum]);
        Inc(NextFrameNum);
      end;
      TAG_SetBackgroundColor: begin
        SetLength(FControls, Length(FControls) + 1);
        if not FControls[High(FControls)].ReadBackground(TagBody, TagEnd, Temp) then begin
          Log('WARNING: bad SetBackgroundColor');
        end;
      end;
      TAG_FrameLabel: begin
        if not SwfSTRING(TagBody, TagEnd, EOS) then begin
          Log('WARNING: could not find end of FrameLabel');
          Data := TagEnd;
          continue;
        end;
        SetLength(FFrameLabels, Length(FFrameLabels) + 1);
        FFrameLabels[High(FFrameLabels)].Index := NextFrameNum;
        SetLength(FFrameLabels[High(FFrameLabels)].Name, EOS - TagBody);
        Move(TagBody^, FFrameLabels[High(FFrameLabels)].Name[1], EOS - TagBody);
        FFrameLabels[High(FFrameLabels)].AnchorFlag := (EOS + 1 < TagEnd) and (PByte(EOS + 1)^ = 1);
        Log('FrameLabel ' + FFrameLabels[High(FFrameLabels)].Name + ' ' + IntToStr(FFrameLabels[High(FFrameLabels)].Index));
      end;
      TAG_Protect: begin
        Log('WARNING TAG_Protect is not supported yet');
      end;
      TAG_End: begin
      end;
      TAG_ExportAssets: begin
        Log('WARNING TAG_ExportAssets is not supported yet');
      end;
      TAG_ImportAssets: begin
        Log('WARNING TAG_ImportAssets is not supported yet');
      end;
      TAG_EnableDebugger: begin
        Log('WARNING TAG_EnableDebugger is not supported yet');
      end;
      TAG_EnableDebugger2: begin
        Log('WARNING TAG_EnableDebugger2 is not supported yet');
      end;
      TAG_ScriptLimits: begin
        Log('WARNING TAG_ScriptLimits is not supported yet');
      end;
      TAG_SetTabIndex: begin
        Log('WARNING TAG_SetTabIndex is not supported yet');
      end;
      TAG_FileAttributes: begin
        Log('INFO FileAttributes:'
          + ' Reserved=' + IntToStr((PByte(TagBody)^ shr 7) and 1)
          + ' UseDirectBlit=' + IntToStr((PByte(TagBody)^ shr 6) and 1)
          + ' UseGPU=' + IntToStr((PByte(TagBody)^ shr 5) and 1)
          + ' HasMetadata=' + IntToStr((PByte(TagBody)^ shr 4) and 1)
          + ' ActionScript3=' + IntToStr((PByte(TagBody)^ shr 3) and 1)
          + ' Reserved=' + IntToStr((PByte(TagBody)^ shr 1) and 3)
          + ' UseNetwork=' + IntToStr((PByte(TagBody)^ shr 0) and 1)
          + ' Reserved=' + IntToStr(PByte(TagBody)[1]) + ' ' + IntToStr(PByte(TagBody)[2]) + ' ' + IntToStr(PByte(TagBody)[3]) + ' ');
      end;
      TAG_ImportAssets2: begin
        Log('WARNING TAG_ImportAssets2 is not supported yet');
      end;
      TAG_SymbolClass: begin
        Log('WARNING TAG_SymbolClass is not supported yet');
      end;
      TAG_Metadata: begin // ignore
        {if PByte(TagBody)[Length - 1] <> 0 then begin
          Log('WARNING: metadata is not null-terminated string');
        end else
          Log('Metadata: ', PAnsiChar(TagBody));}
      end;
      TAG_DefineScalingGrid: begin
        Log('WARNING TAG_DefineScalingGrid is not supported yet');
      end;
      TAG_DefineSceneAndFrameLabelData: begin
        Temp := TagBody;
        SceneCount := SwfEncodedU32(Temp, TagEnd, Temp);
        SetLength(FScenes, SceneCount);
        for I := 0 to High(FScenes) do begin
          FScenes[I].Index := SwfEncodedU32(Temp, TagEnd, Temp);
          if not SwfSTRING(Temp, TagEnd, EOS) then begin
            Log('WARNING: could not find end of name');
            EOS := Temp;
            Data := TagEnd;
            continue;
          end;
          SetString(FScenes[I].Name, Temp, EOS - Temp);
          Temp := EOS + 1;
        end;
        FrameLabelCount := SwfEncodedU32(Temp, TagEnd, Temp);
        SetLength(FFrameLabels, FrameLabelCount);
        for I := 0 to High(FFrameLabels) do begin
          FFrameLabels[I].Index := SwfEncodedU32(Temp, TagEnd, Temp);
          FFrameLabels[I].AnchorFlag := False;
          if not SwfSTRING(Temp, TagEnd, EOS) then begin
            Log('WARNING: could not find end of name');
            Data := TagEnd;
            continue;
          end;
          SetString(FFrameLabels[I].Name, Temp, EOS - Temp);
          Log('FRAME LABEL: "' + FFrameLabels[I].Name + '" -> ' + IntToStr(FFrameLabels[I].Index));
          Temp := EOS + 1;
        end;
      end;
      TAG_DoAction: begin
        Log('WARNING TAG_DoAction is not supported yet');
      end;
      TAG_DoInitAction: begin
        Log('WARNING TAG_DoInitAction is not supported yet');
      end;
      TAG_DoABC: begin
        Log('WARNING TAG_DoABC is not supported yet');
      end;
      TAG_DefineShape: begin
        if not ReadShape(TagBody, TagEnd, 1) then begin
          Log('WARNING: bad DefineShape');
          SetLength(FCharacters, Length(FCharacters) - 1);
        end;
      end;
      TAG_DefineShape2: begin
        if not ReadShape(TagBody, TagEnd, 2) then begin
          Log('WARNING: bad DefineShape2');
          SetLength(FCharacters, Length(FCharacters) - 1);
        end;
      end;
      TAG_DefineShape3: begin
        if not ReadShape(TagBody, TagEnd, 3) then begin
          Log('WARNING: bad DefineShape3');
          SetLength(FCharacters, Length(FCharacters) - 1);
        end;
      end;
      TAG_DefineShape4: begin
        if not ReadShape(TagBody, TagEnd, 4) then begin
          Log('WARNING: bad DefineShape4');
          SetLength(FCharacters, Length(FCharacters) - 1);
        end;
      end;
      TAG_DefineBits: begin
        Log('WARNING TAG_DefineBits is not supported yet');
      end;
      TAG_JPEGTables: begin
        Log('WARNING TAG_JPEGTables is not supported yet');
      end;
      TAG_DefineBitsJPEG2: begin
        Log('WARNING TAG_DefineBitsJPEG2 is not supported yet');
      end;
      TAG_DefineBitsJPEG3: begin
        Log('WARNING TAG_DefineBitsJPEG3 is not supported yet');
      end;
      TAG_DefineBitsLossless: begin
        Log('WARNING TAG_DefineBitsLossless is not supported yet');
      end;
      TAG_DefineBitsLossless2: begin
        Log('WARNING TAG_DefineBitsLossless2 is not supported yet');
      end;
      TAG_DefineBitsJPEG4: begin
        Log('WARNING TAG_DefineBitsJPEG4 is not supported yet');
      end;
      TAG_DefineMorphShape: begin
        Log('WARNING TAG_DefineMorphShape is not supported yet');
      end;
      TAG_DefineMorphShape2: begin
        Log('WARNING TAG_DefineMorphShape2 is not supported yet');
      end;
      TAG_DefineFont: begin
        Log('WARNING TAG_DefineFont is not supported yet');
      end;
      TAG_DefineFontInfo: begin
        Log('WARNING TAG_DefineFontInfo is not supported yet');
      end;
      TAG_DefineFontInfo2: begin
        Log('WARNING TAG_DefineFontInfo2 is not supported yet');
      end;
      TAG_DefineFont2: begin
        Log('WARNING TAG_DefineFont2 is not supported yet');
      end;
      TAG_DefineFont3: begin
        Log('WARNING TAG_DefineFont3 is not supported yet');
      end;
      TAG_DefineFontAlignZones: begin
        Log('WARNING TAG_DefineFontAlignZones is not supported yet');
      end;
      TAG_DefineFontName: begin
        Log('WARNING TAG_DefineFontName is not supported yet');
      end;
      TAG_DefineText: begin
        Log('WARNING TAG_DefineText is not supported yet');
      end;
      TAG_DefineText2: begin
        Log('WARNING TAG_DefineText2 is not supported yet');
      end;
      TAG_DefineEditText: begin
        Log('WARNING TAG_DefineEditText is not supported yet');
      end;
      TAG_CSMTextSettings: begin
        Log('WARNING TAG_CSMTextSettings is not supported yet');
      end;
      TAG_DefineFont4: begin
        Log('WARNING TAG_DefineFont4 is not supported yet');
      end;
      TAG_DefineSound: begin
        Log('WARNING TAG_DefineSound is not supported yet');
      end;
      TAG_StartSound: begin
        Log('WARNING TAG_StartSound is not supported yet');
      end;
      TAG_StartSound2: begin
        Log('WARNING TAG_StartSound2 is not supported yet');
      end;
      TAG_SoundStreamHead: begin
        Log('WARNING TAG_SoundStreamHead is not supported yet');
      end;
      TAG_SoundStreamHead2: begin
        Log('WARNING TAG_SoundStreamHead2 is not supported yet');
      end;
      TAG_SoundStreamBlock: begin
        Log('WARNING TAG_SoundStreamBlock is not supported yet');
      end;
      TAG_DefineButton: begin
        SetLength(FCharacters, Length(FCharacters) + 1);
        FCharacters[High(FCharacters)].FType := TAG_DefineButton;
        FCharacters[High(FCharacters)].FHolder := @Self;
        if not FCharacters[High(FCharacters)].ReadButton(TagBody, TagEnd, 1) then begin
          // TODO free allocated data
          SetLength(FCharacters, Length(FCharacters) - 1);
          Exit(Error(1, 'bad DefineButton2'));
        end;
      end;
      TAG_DefineButton2: begin
        SetLength(FCharacters, Length(FCharacters) + 1);
        FCharacters[High(FCharacters)].FType := TAG_DefineButton;
        FCharacters[High(FCharacters)].FHolder := @Self;
        if not FCharacters[High(FCharacters)].ReadButton(TagBody, TagEnd, 2) then begin
          // TODO free allocated data
          SetLength(FCharacters, Length(FCharacters) - 1);
          Exit(Error(1, 'bad DefineButton2'));
        end;
      end;
      TAG_DefineButtonCxform: begin
        Log('WARNING TAG_DefineButtonCxform is not supported yet');
      end;
      TAG_DefineButtonSound: begin
        Log('WARNING TAG_DefineButtonSound is not supported yet');
      end;
      TAG_DefineSprite: begin
        SetLength(FCharacters, Length(FCharacters) + 1);
        FCharacters[High(FCharacters)].FType := TAG_DefineSprite;
        New(FCharacters[High(FCharacters)].FHolder);
        FCharacters[High(FCharacters)].FHolder^.Init;
        FCharacters[High(FCharacters)].FHolder^.FParent := @Self;
        FCharacters[High(FCharacters)].FHolder^.FFrameDuration := FFrameDuration;
        FCharacters[High(FCharacters)].FCharacterId := AsUInt16(TagBody);
        FCharacters[High(FCharacters)].FFrameCount := AsUInt16(TagBody + 2);
        if FCharacters[High(FCharacters)].FFrameCount <= 0 then
          FCharacters[High(FCharacters)].FFrameCount := 1;
        SetLength(FCharacters[High(FCharacters)].FHolder^.FFrames, FCharacters[High(FCharacters)].FFrameCount);
        Log('DefineSprite: CharacterId=' + IntToStr(FCharacters[High(FCharacters)].FCharacterId) +  ' frames=' + IntToStr(FCharacters[High(FCharacters)].FFrameCount));
        if not FCharacters[High(FCharacters)].FHolder^.ReadTags(TagBody + 4, TagEnd) then begin
          FCharacters[High(FCharacters)].FHolder^.Done;
          Dispose(FCharacters[High(FCharacters)].FHolder);
          SetLength(FCharacters, Length(FCharacters) - 1);
          Exit(Error(1, 'bad DefineSprite'));
        end;
      end;
      TAG_DefineVideoStream: begin
        Log('WARNING TAG_DefineVideoStream is not supported yet');
      end;
      TAG_VideoFrame: begin
        Log('WARNING TAG_VideoFrame is not supported yet');
      end;
      TAG_EnableTelemetry: begin
        Log('WARNING TAG_EnableTelemetry is not supported yet');
      end;
      TAG_DefineBinaryData: begin
        Log('WARNING TAG_DefineBinaryData is not supported yet');
      end;
    else
      Log('Error: unknown tag ' + IntToStr(SwfTag(Data, DataEnd, TagBody, TagEnd)));
    end;
    Data := TagEnd;
  end;
  Exit(True);
end;

procedure TFlashHolder.BuildCache(const Name: AnsiString; Hash: UInt32);
var
  FlashCacheWriter: TFlashCacheWriter;
  Header: TFlashCache.THeader;
  HeaderOffset: UInt64;
  DictItem: TFlashCache.TDictItem;
  Layer: TFlashCache.TLayer;
  I, J: LongInt;
  C: PFlashCharacter;
  S: PShape;
  Off: UInt64;
begin
  if not FCache.Loaded then begin
    FlashCacheWriter.Init(@FCache);
    FlashCacheWriter.Start;

    Header.Version := FLASH_CACHE_VERSION;
    Header.Adler32 := Hash;
    // Only allocate memory for the header, some fields like Dict will be filled later
    HeaderOffset := FlashCacheWriter.AllocData(SizeOf(Header));

    Header.DictCount := Length(FCharacters);
    Header.Dict := FlashCacheWriter.AllocData(Length(FCharacters) * SizeOf(TFlashCache.TDictItem));
    for I := 0 to High(FCharacters) do begin
      C := @FCharacters[I];
      DictItem.CharacterId := C^.FCharacterId;
      if (C^.FType = TAG_DefineShape) or (C^.FType <> TAG_DefineShape2) or (C^.FType <> TAG_DefineShape3) or (C^.FType <> TAG_DefineShape4) then begin
        S := @C^.FShape;
        DictItem.CharacterType := 1;
        DictItem.Count := Length(S^.FFinal);
        DictItem.Offset := FlashCacheWriter.AllocData(Length(S^.FFinal) * SizeOf(TFlashCache.TLayer));
        Off := DictItem.Offset;
        for J := 0 to High(S^.FFinal) do begin
          Layer.LayerType := 1;
          Layer.Color := S^.FFinal[J].Color;
          Layer.Count := S^.FFinal[J].TrianglesCount;
          Layer.Offset := FlashCacheWriter.AppendData(S^.FFinal[J].TrianglesData, Layer.Count * SizeOf(TVec2f));
          FlashCacheWriter.RewriteData(Off + J * SizeOf(TFlashCache.TLayer), @Layer, SizeOf(TFlashCache.TLayer));
          Log('LAYER FROM CACHE: #' + HexStr(Layer.Color, 8) + ' Count=' + IntToStr(Layer.Count) + ' Offset=' + IntToStr(Layer.Offset));
        end;
      end else
        DictItem.CharacterType := 0;
      FlashCacheWriter.RewriteData(Header.Dict + I * SizeOf(TFlashCache.TDictItem), @DictItem, SizeOf(TFlashCache.TDictItem));
    end;

    FlashCacheWriter.RewriteData(HeaderOffset, @Header, SizeOf(Header));

    if FlashCacheWriter.Finish then
      Log('CACHED ' + Name);
    FlashCacheWriter.Done;
  end;
end;

function TFlashHolder.ReadTags(Data, DataEnd: Pointer): Boolean;
var
  F, I: LongInt;
begin
  FDisplayListBuilder.Init;
  Result := ReadTagsLoop(Data, DataEnd);
  FDisplayListBuilder.Finalize;
  FDisplayListBuilder.Done;
  // Just for debug
  Log('DUMP OF FFrames');
  for F := 0 to High(FFrames) do begin
    Log('  FRAME #' + IntToStr(F) + ':');
    for I := 0 to FFrames[F].Count - 1 do begin
      if FFrames[F].DisplayList[I].Place.Depth = 0 then begin
        Log('    corrupted DisplayList item');
        continue;
      end;
      Log('    CharacterId=' + IntToStr(FFrames[F].DisplayList[I].Place.CharacterId)
           + ' Depth=' + IntToStr(FFrames[F].DisplayList[I].Place.Depth)
           + ' First=' + IntToStr(FFrames[F].DisplayList[I].First)
           + ' Last='  + IntToStr(FFrames[F].DisplayList[I].Last));
      if not ((FFrames[F].DisplayList[I].First <= F) and (F <= FFrames[F].DisplayList[I].Last)) then begin
        Log('      WARNING bad borders');
      end;
    end;
  end;
  Log('DUMP OF FFrames DONE');
end;

procedure TFlashHolder.Init;
begin
  FParent := nil;
  FCache.Init;
end;

procedure TFlashHolder.Init(const D: TData);
begin
  Init;
  Load(D);
end;

procedure TFlashHolder.Done;
begin
  Clear;
  FCache.Done;
end;

procedure TFlashHolder.Clear;
begin
  // TODO
end;

function SwfUncompressed(Data, DataEnd: Pointer;
                         var Self: TFlashHolder): Boolean;
var
  FrameSize: TSwfRECT;
begin with Self do begin
  // Header
  if not SwfRECT(Data, DataEnd, FrameSize, Data) then
    Exit(Error(1, 'could not read FrameSize'));
  FSize.X := FrameSize.Xmax / 20;
  FSize.Y := FrameSize.Ymax / 20;
  FFrameRate := SwfFIXED8ToSingle(SwfFIXED8(Data));
  if FFrameRate < 1 / 20 then
    FFrameRate := 1;
  FFrameDuration := 1000 / FFrameRate;
  Inc(Data, 2);
  FFrameCount := AsUInt16(Data);
  if FFrameCount <= 0 then
    FFrameCount := 1;
  SetLength(FFrames, FFrameCount);
  Inc(Data, 2);

  // Tagged data blocks
  Exit(Self.ReadTags(Data, DataEnd));
end end;

function TFlashHolder.Load(const Name: AnsiString; Data, DataEnd: Pointer): Boolean; inline; overload;
var
  Header: TSwfHeader;
  Body: Pointer;
  Uncompressed, UncompressedEnd: Pointer;
  Time, DecompressTime: LongInt;
  Hash: UInt32;
begin
  FReady := False;

  Hash := Adler32(Data, DataEnd);
  if (Name <> '') and dengHasCacheStorage then begin
    if FCache.Load(Name, Hash) then
      Log('Found cache for ' + Name);
  end;

  Time := Eng.Time;
  Log('Started .swf loading');
  if not SwfHeader(Data, DataEnd, Header, Body) then
    Exit(Error(1, 'not SWF'));
  if Header.Version > 19 then
    Exit(Error(1, 'too high version'));

  case Header.Compression of
    SWF_COMPRESS_NO: begin
      if Header.Size <> DataEnd - Data then
        Exit(Error(1, 'bad size'));
      if not SwfUncompressed(Body, DataEnd, Self) then
        Exit(False);
    end;
    SWF_COMPRESS_ZLIB: begin
      DecompressTime := Eng.Time;
      Log('Started zlib decompression');
      Uncompressed := GetMem(Header.Size);
      if Uncompressed = nil then
        Exit(Error(1, 'no space'));
      if not ZLibDecompressAll(Body, DataEnd, Uncompressed, Uncompressed + Header.Size, UncompressedEnd) then
        Exit(Error(1, 'corrupted'));
      Log('Decompression done, it took ' + IntToStr(Eng.Time - DecompressTime) + 'ms');
      if not SwfUncompressed(Uncompressed, UncompressedEnd, Self) then
        Exit(False);
      FreeMem(Uncompressed);
    end;
    SWF_COMPRESS_LZMA:
      Exit(Error(1, 'LZMA underdeveloped')); // LZMA
  else
    Exit(Error(1, 'unknown compression'));
  end;

  Log('Finished .swf loading, it took ' + IntToStr(Eng.Time - Time) + 'ms');
  FReady := True;
  if (Name <> '') and dengHasCacheStorage then begin
    Time := Eng.Time;
    Log('Building cache');
    BuildCache(Name, Hash);
    Log('Finished building cache, it took ' + IntToStr(Eng.Time - Time) + 'ms');
  end;
  Exit(True);
end;

function TFlashHolder.Load(const D: TData): Boolean;
var
  Data: Pointer;
  S: SizeUInt;
begin
  FReady := False;

  if not D.ReadyReadable then begin
    D.Done;
    Exit(Error(1, 'could not open'));
  end;

  if not D.Stream^.ReadToMemory(Data, S) then begin
    D.Done;
    Exit(Error(1, 'could not read'));
  end;

  Result := Load(D.Name, Data, Data + S);

  // TODO keep loaded data for uncompressed .swf data
  FreeMem(Data);
  D.Done;
end;

procedure TFlashHolder.DumpToLog;
begin
end;

function TFlashHolder.GetCharacter(CharacterId: PtrUInt): PFlashCharacter;
var
  I: LongInt;
begin
  for I := 0 to High(FCharacters) do
    if FCharacters[I].FCharacterId = CharacterId then begin
      Exit(@FCharacters[I]);
    end;
  Exit(nil);
end;

function TFlashHolder.GetControl(Index: PtrUInt): PFlashControl;
begin
  Exit(@FControls[Index]);
end;

function TFlashHolder.GetFrameByName(Name: TAnsiView): LongInt;
var
  I: LongInt;
begin
  for I := 0 to High(FFrameLabels) do begin
    if FFrameLabels[I].Name = Name then
      Exit(FFrameLabels[I].Index);
  end;
  Exit(0);
end;

function TFlashHolder.FrameLabelsCount: LongInt; inline;
begin
  Result := Length(FFrameLabels);
end;

function TFlashHolder.FrameLabels: PFlashLabel; inline;
begin
  Result := @FFrameLabels[0];
end;

procedure TFlashHolder.LinkToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
var
  I, J: LongInt;
  Count: PtrUInt;
begin
  for I := 0 to High(FCharacters) do begin
    if not FCharacters[I].IsShape then
      continue;
    if not FCharacters[I].FShape.FSceneBufferChunk.IsEmpty then
      continue; // already uploaded
    Count := 0;
    for J := 0 to High(FCharacters[I].FShape.FFinal) do
      Inc(Count, FCharacters[I].FShape.FFinal[J].TrianglesCount);
    SceneBuffer^.GetChunk(Count, FCharacters[I].FShape.FSceneBufferChunk);
  end;
end;

procedure TFlashHolder.LoadToSceneBuffer(SceneBuffer: PFlashSceneBuffer);
var
  I, J, K: LongInt;
  Shape: PShape;
  Index: PtrUInt;
  Triangles: PVec2f;
  VerticesData: array of TFlashSceneBuffer.TVertexData;
begin
  SetLength(VerticesData, 0);
  for I := 0 to High(FCharacters) do begin
    if not FCharacters[I].IsShape then
      continue;
    if FCharacters[I].FShape.FSceneBufferChunk.IsEmpty then
      continue; // no linked chunk
    Shape := @FCharacters[I].FShape;
    if Shape^.FSceneBufferChunk.Count > Length(VerticesData) then
      SetLength(VerticesData, Shape^.FSceneBufferChunk.Count);
    Index := 0;
    {Shape^.FMin := Vec2f(+1000000, +1000000);
    Shape^.FMax := Vec2f(-1000000, -1000000);
    for J := 0 to High(Shape^.FFinal) do begin
      Triangles := Shape^.FFinal[J].TrianglesData;
      for K := 0 to Shape^.FFinal[J].TrianglesCount - 1 do begin
        if Shape^.FMin.X > Triangles[K].X then
          Shape^.FMin.X := Triangles[K].X;
        if Shape^.FMin.Y > Triangles[K].Y then
          Shape^.FMin.Y := Triangles[K].Y;
        if Shape^.FMax.X < Triangles[K].X then
          Shape^.FMax.X := Triangles[K].X;
        if Shape^.FMax.Y < Triangles[K].Y then
          Shape^.FMax.Y := Triangles[K].Y;
      end;
    end;
    Shape^.FRepos.X := (FMax.X - FMin.X) / 2;
    Shape^.FRepos.Y := (FMax.Y - FMin.Y) / 2;
    Shape^.FRescale.X := (High(Int16) - Low(Int16)) / (FMax.X - FMin.X);
    Shape^.FRescale.Y := (High(Int16) - Low(Int16)) / (FMax.Y - FMin.Y);}
    Shape^.FRepos.X := 0;
    Shape^.FRepos.Y := 0;
    Shape^.FRescale.X := 1 / 20;
    Shape^.FRescale.Y := 1 / 20;
    for J := 0 to High(Shape^.FFinal) do begin
      Shape^.FFinal[J].FSceneBufferChunk.First := Shape^.FSceneBufferChunk.First + Index;
      Shape^.FFinal[J].FSceneBufferChunk.Count := Shape^.FFinal[J].TrianglesCount;
      if Index + Shape^.FFinal[J].TrianglesCount > Length(VerticesData) then
        SetLength(VerticesData, Index + Shape^.FFinal[J].TrianglesCount); // <- should be unreachable...
      Triangles := Shape^.FFinal[J].TrianglesData;
      for K := 0 to Shape^.FFinal[J].TrianglesCount - 1 do begin
        VerticesData[Index + K].X := Round(Triangles[K].X * 20);
        VerticesData[Index + K].Y := Round(Triangles[K].Y * 20);
        // VerticesData[Index + K].Coord := Triangles[K];
        // VerticesData[Index + K].Normal := Vec2f(0, 0);
        // VerticesData[Index + K].Color := Shape^.FFinal[J].Color;
        // VerticesData[Index + K].BgColor := 0;
      end;
      Inc(Index, Shape^.FFinal[J].TrianglesCount);
    end;
    SceneBuffer^.UpdateChunk(Shape^.FSceneBufferChunk, @VerticesData[0]);
  end;
end;

procedure TFlashHolder.InvalidateSceneBufferChunks(SceneBuffer: PFlashSceneBuffer);
var
  I: LongInt;
begin
  for I := 0 to High(FCharacters) do
    FCharacters[I].FShape.FSceneBufferChunk.Invalidate;
end;

end.
