unit dswf_primitives;
      // Basic constants and decoders for .swf data structures.

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

interface

uses
  dbitmem;

// Tags
const
  {$IFDEF ENDIAN_LITTLE}
  MASK_ALPHA  = $ff000000;
  MASK_RGB    = $00ffffff;
  {$ELSE}
  MASK_ALPHA  = $000000ff;
  MASK_RGB    = $ffffff00;
  {$ENDIF}

  SWF_COMPRESS_NO     = 0;
  SWF_COMPRESS_ZLIB   = 1;
  SWF_COMPRESS_LZMA   = 2;

  // FILLSTYLE types
  SWF_FILLSTYLE_SOLID                     = $00; // solid
  SWF_FILLSTYLE_LINEAR_GRADIENT           = $10; // gradient
  SWF_FILLSTYLE_RADIAL_GRADIENT           = $12; // gradient
  SWF_FILLSTYLE_FOCAL_GRADIENT            = $13; // gradient
  SWF_FILLSTYLE_REPEAT_BITMAP             = $40; // bitmap
  SWF_FILLSTYLE_CLIPPED_BITMAP            = $41; // bitmap
  SWF_FILLSTYLE_NONSMOOTH_REPEAT_BITMAP   = $42; // bitmap
  SWF_FILLSTYLE_NONSMOOTH_CLIPPED_BITMAP  = $43; // bitmap

  // FOCALGRADIENT SpreadMode
  SWF_FOCALGRADIENT_PAD                   = 0;
  SWF_FOCALGRADIENT_REFLECT               = 1;
  SWF_FOCALGRADIENT_REPEAT                = 2;

  // FOCALGRADIENT InterpolationMode
  SWF_FOCALGRADIENT_NORMAL                = 0;
  SWF_FOCALGRADIENT_RGB                   = 1;

  // StartCapStyle
  SWF_CAP_ROUND       = 0;
  SWF_CAP_NO          = 1;
  SWF_CAP_SQUARE      = 2;
  // JoinStyle
  SWF_JOIN_ROUND      = 0;
  SWF_JOIN_BEVEL      = 1;
  SWF_JOIN_MITER      = 2;

  // Filters
  SWF_FILTER_DROPSHADOW       = 0;
  SWF_FILTER_BLUR             = 1;
  SWF_FILTER_GLOW             = 2;
  SWF_FILTER_BEVEL            = 3;
  SWF_FILTER_GRADIENTGLOW     = 4;
  SWF_FILTER_CONVOLUTION      = 5;
  SWF_FILTER_COLORMATRIX      = 6;
  SWF_FILTER_GRADIENTBEVEL    = 7;

  // Blend modes
  SWF_BLENDMODE_NORMAL        = 1;
  SWF_BLENDMODE_LAYER         = 2;
  SWF_BLENDMODE_MULTIPLY      = 3;
  SWF_BLENDMODE_SCREEN        = 4;
  SWF_BLENDMODE_LIGHTEN       = 5;
  SWF_BLENDMODE_DARKEN        = 6;
  SWF_BLENDMODE_DIFFERENCE    = 7;
  SWF_BLENDMODE_ADD           = 8;
  SWF_BLENDMODE_SUBTRACT      = 9;
  SWF_BLENDMODE_INVERT        = 10;
  SWF_BLENDMODE_ALPHA         = 11;
  SWF_BLENDMODE_ERASE         = 12;
  SWF_BLENDMODE_OVERLAY       = 13;
  SWF_BLENDMODE_HARDLIGHT     = 14;

  // Button states
  SWF_BUTTON_UP               = 0;
  SWF_BUTTON_OVER             = 1;
  SWF_BUTTON_DOWN             = 2;
  SWF_BUTTON_HIT              = 3;

  // Display list
  TAG_PlaceObject                   = 4;
  TAG_PlaceObject2                  = 26;
  TAG_PlaceObject3                  = 70;
  TAG_RemoveObject                  = 5;
  TAG_RemoveObject2                 = 28;
  TAG_ShowFrame                     = 1;
  // Control Tags
  TAG_SetBackgroundColor            = 9;
  TAG_FrameLabel                    = 43;
  TAG_Protect                       = 24;
  TAG_End                           = 0;
  TAG_ExportAssets                  = 56;
  TAG_ImportAssets                  = 57;
  TAG_EnableDebugger                = 58;
  TAG_EnableDebugger2               = 64;
  TAG_ScriptLimits                  = 65;
  TAG_SetTabIndex                   = 66;
  TAG_FileAttributes                = 69;
  TAG_ImportAssets2                 = 71;
  TAG_SymbolClass                   = 76;
  TAG_Metadata                      = 77;
  TAG_DefineScalingGrid             = 78;
  TAG_DefineSceneAndFrameLabelData  = 86;
  // Actions
  TAG_DoAction                      = 12;
  TAG_DoInitAction                  = 59;
  TAG_DoABC                         = 82;
  // Shapes
  TAG_DefineShape                   = 2;
  TAG_DefineShape2                  = 22;
  TAG_DefineShape3                  = 32;
  TAG_DefineShape4                  = 83;
  // Gradients
  TAG_DefineBits                    = 6;
  TAG_JPEGTables                    = 8;
  TAG_DefineBitsJPEG2               = 21;
  TAG_DefineBitsJPEG3               = 35;
  TAG_DefineBitsLossless            = 20;
  TAG_DefineBitsLossless2           = 36;
  TAG_DefineBitsJPEG4               = 90;
  // Shape morphings
  TAG_DefineMorphShape              = 46;
  TAG_DefineMorphShape2             = 84;
  // Fonts and Text
  TAG_DefineFont                    = 10;
  TAG_DefineFontInfo                = 13;
  TAG_DefineFontInfo2               = 62;
  TAG_DefineFont2                   = 48;
  TAG_DefineFont3                   = 75;
  TAG_DefineFontAlignZones          = 73;
  TAG_DefineFontName                = 88;
  TAG_DefineText                    = 11;
  TAG_DefineText2                   = 33;
  TAG_DefineEditText                = 37;
  TAG_CSMTextSettings               = 74;
  TAG_DefineFont4                   = 91;
  // Sounds
  TAG_DefineSound                   = 14;
  TAG_StartSound                    = 15;
  TAG_StartSound2                   = 89;
  TAG_SoundStreamHead               = 18;
  TAG_SoundStreamHead2              = 45;
  TAG_SoundStreamBlock              = 19;
  // Buttons
  TAG_DefineButton                  = 7;
  TAG_DefineButton2                 = 34;
  TAG_DefineButtonCxform            = 23;
  TAG_DefineButtonSound             = 17;
  // Sprites
  TAG_DefineSprite                  = 39;
  // Video
  TAG_DefineVideoStream             = 60;
  TAG_VideoFrame                    = 61;
  // Metadata
  TAG_EnableTelemetry               = 93;
  TAG_DefineBinaryData              = 87;

type
TSwfFIXED = Cardinal;
TSwfFIXED8 = UInt16;

TSwfRECT = packed record
  Xmin: UInt64;
  Xmax: UInt64;
  Ymin: UInt64;
  Ymax: UInt64;
end;

TSwfMATRIX = array[0 .. 6 - 1] of Double;
      // Double can precisely store any 16.16 fixed-point value.
      // To apply a matrix:
      //    X' = X * M[0] + Y * M[1] + M[2]
      //    Y' = X * M[3] + Y * M[4] + M[5]
      // So:
      //    ScaleX=M[0], RotateSkew1=M[1], TranslateX=M[2]
      //    RotateSkew0=M[3], ScaleY=M[4], TranslateY=M[5]
      // As OpenGL matrix:
      //   M'[ 0] M'[ 4] M'[ 8] M'[12]     M[0] M[1] 0 M[2]
      //   M'[ 1] M'[ 5] M'[ 9] M'[13]  =  M[3] M[4] 0 M[5]
      //   M'[ 2] M'[ 6] M'[10] M'[14]       0    0  1   0
      //   M'[ 3] M'[ 7] M'[11] M'[15]       0    0  0   1

TSwfCXFORM = record
  HasAddTerms: Boolean;
  HasMultTerms: Boolean;
  HasAlpha: Boolean; // True for XCFORMWITHALPHA, False for CXFORM
  Add: array[0 .. 4 - 1] of Int16;
  Mult: array[0 .. 4 - 1] of Int16;
end;

PSwfGRADRECORD = ^TSwfGRADRECORD;
TSwfGRADRECORD = record
  Ratio: Byte;
  Color: Cardinal;
end;

TSwfGRADIENT = record
  SpreadMode: Byte;
  InterpolationMode: Byte;
  NumGradients: Byte;
  NeedMemory: SizeUInt;
  Memory: Pointer;
  GradientRecords: PSwfGRADRECORD;
end;

TSwfFOCALGRADIENT = record
  SpreadMode: Byte;
  InterpolationMode: Byte;
  NumGradients: Byte; // 1 to 15
  NeedMemory: SizeUInt;
  Memory: Pointer;
  GradientRecords: PSwfGRADRECORD;
  FocalPoint: TSwfFIXED8;
end;

PSwfFILLSTYLE = ^TSwfFILLSTYLE;
TSwfFILLSTYLE = record
  _Type: PtrUInt; // SWF_FILLSTYLE_*
  NeedMemory: SizeUInt; // Number of bytes to be allocated after SwfFILLSTYLE_Begin
  Memory: Pointer;
case Byte of
  0: (Color: Cardinal); // for solid fill
  1: ( // for gradient fills
    GradientMatrix: TSwfMATRIX;
    case Byte of
      0: (Gradient: TSwfGRADIENT); // for linear and radial
      1: (Focal: TSwfFOCALGRADIENT); // for focal
  );
  2: ( // for bitmaps
    BitmapId: Cardinal;
    BitmapMatrix: TSwfMATRIX
  );
end;

// use TSwfLINESTYLE2 instread of TSwfLINESTYLE
//PSwfLINESTYLE = ^TSwfLINESTYLE;
//TSwfLINESTYLE = record
//  Width: UInt16;
//  Color: Cardinal;
//end;

PSwfLINESTYLE2 = ^TSwfLINESTYLE2;
TSwfLINESTYLE2 = record
  Width: UInt16;
  Flags: array[0 .. 1] of Byte; // only for DefineShape4
  MiterLimitFactor: TSwfFIXED8; // only for DefineShape4
  Color: Cardinal;
  NeedMemory: SizeUInt;
  Memory: Pointer;
  FillType: TSwfFILLSTYLE;
  function StartCapStyle: Byte; inline;
  function JoinStyle: Byte; inline;
  function HasFillFlag: Boolean; inline;
  function NoHScaleFlag: Boolean; inline;
  function NoVScaleFlag: Boolean; inline;
  function PixelHintingFlag: Boolean; inline;
  function NoClose: Boolean; inline;
  function EndCapStyle: Byte; inline;
end;

PSwfStyleChangeRecord = ^TSwfStyleChangeRecord;
TSwfStyleChangeRecord = record
  Flags: Byte;
  MoveDeltaX: LongInt;
  MoveDeltaY: LongInt;
  FillStyle0: Cardinal;
  FillStyle1: Cardinal;
  LineStyle: Cardinal;
  function StateNewStyles: Boolean; inline;
  function StateLineStyle: Boolean; inline;
  function StateFillStyle1: Boolean; inline;
  function StateFillStyle0: Boolean; inline;
  function StateMoveTo: Boolean; inline;
end;

PSwfEdgeRecord = ^TSwfEdgeRecord;
TSwfEdgeRecord = record
  Curved: Boolean;
case Byte of
  0: (
      DeltaX: LongInt;
      DeltaY: LongInt;
    );
  1: (
      AnchorDeltaX: LongInt;
      AnchorDeltaY: LongInt;
      ControlDeltaX: LongInt;
      ControlDeltaY: LongInt;
    );
end;

PSwfPlaceObject2 = ^TSwfPlaceObject2;
TSwfPlaceObject2 = record
  Flags: Byte;
  Depth: UInt16;
  CharacterId: UInt16;
  Matrix: TSwfMATRIX;
  ColorTransform: TSwfCXFORM;
  Ratio: UInt16;
  Name: PAnsiChar;
  NameEnd: PAnsiChar;
  ClipDepth: UInt16;
  function PlaceFlagHasClipActions: Boolean; inline;
  function PlaceFlagHasClipDepth: Boolean; inline;
  function PlaceFlagHasName: Boolean; inline;
  function PlaceFlagHasRatio: Boolean; inline;
  function PlaceFlagHasColorTransform: Boolean; inline;
  function PlaceFlagHasMatrix: Boolean; inline;
  function PlaceFlagHasCharacter: Boolean; inline;
  function PlaceFlagHasMove: Boolean; inline;
end;

TSwfRemoveObject = record
  CharacterId: UInt16; // 0 for RemoveObject2
  Depth: UInt16;
end;

TSwfHeader = record
  Size: PtrUInt; // size of whole uncompressed size
  Compression: Byte; // see SWF_COMPRESS_*
  Version: Byte;
end;

TSwfDROPSHADOWFILTER = record
  DropShadowColor: UInt32;
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Angle: TSwfFIXED;
  Distance: TSwfFIXED;
  Strength: TSwfFIXED8;
  Flags: Byte;
  function InnerShadow: Boolean; inline;
  function Knockout: Boolean; inline;
  function CompositeSource: Boolean; inline;
  function Passes: Byte; inline;
end;

TSwfBLURFILTER = record
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Flags: Byte;
  function Passes: Byte; inline;
end;

TSwfGLOWFILTER = record
  GlowColor: UInt32;
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Strength: TSwfFIXED8;
  Flags: Byte;
  function InnerGlow: Boolean; inline;
  function Knockout: Boolean; inline;
  function CompositeSource: Boolean; inline;
  function Passes: Byte; inline;
end;

TSwfBEVELFILTER = record
  ShadowColor: UInt32;
  HighlightColor: UInt32;
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Angle: TSwfFIXED;
  Distance: TSwfFIXED;
  Strength: TSwfFIXED8;
  Flags: Byte;
  function InnerShadow: Boolean; inline;
  function Knockout: Boolean; inline;
  function CompositeSource: Boolean; inline;
  function OnTop: Boolean; inline;
  function Passes: Byte; inline;
end;

TSwfGRADIENTGLOWFILTER = record
  NumColors: Byte;
  GradientColors: PUInt32;
  GradientRatio: PUInt8;
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Angle: TSwfFIXED;
  Distance: TSwfFIXED;
  Strength: TSwfFIXED8;
  Flags: Byte;
  function InnerShadow: Boolean; inline;
  function Knockout: Boolean; inline;
  function CompositeSource: Boolean; inline;
  function OnTop: Boolean; inline;
  function Passes: Byte; inline;
end;

TSwfCONVOLUTIONFILTER = record
  MatrixX: Byte;
  MatrixY: Byte;
  Divisor: Single;
  Bias: Single;
  Matrix: PSingle;
  DefaultColor: Cardinal;
  Flags: Byte;
  function Clamp: Boolean; inline;
  function PreserveAlpha: Boolean; inline;
end;

TSwfCOLORMATRIXFILTER = record
  Matrix: array[0 .. 20 - 1] of Single;
end;

TSwfGRADIENTBEVELFILTER = record
  NumColors: Byte;
  GradientColors: PUInt32;
  GradientRatio: PUInt8;
  BlurX: TSwfFIXED;
  BlurY: TSwfFIXED;
  Angle: TSwfFIXED;
  Distance: TSwfFIXED;
  Strength: TSwfFIXED8;
  Flags: Byte;
  function InnerShadow: Boolean; inline;
  function Knockout: Boolean; inline;
  function CompositeSource: Boolean; inline;
  function OnTop: Boolean; inline;
  function Passes: Byte; inline;
end;

PSwfFILTER = ^TSwfFILTER;
TSwfFILTER = record
case FilterID: Byte of
  SWF_FILTER_DROPSHADOW: (DropShadowFilter: TSwfDROPSHADOWFILTER);
  SWF_FILTER_BLUR: (BlurFilter: TSwfBLURFILTER);
  SWF_FILTER_GLOW: (GlowFilter: TSwfGLOWFILTER);
  SWF_FILTER_BEVEL: (BevelFilter: TSwfBEVELFILTER);
  SWF_FILTER_GRADIENTGLOW: (GradientGlowFilter: TSwfGRADIENTGLOWFILTER);
  SWF_FILTER_CONVOLUTION: (ConvolutionFilter: TSwfCONVOLUTIONFILTER);
  SWF_FILTER_COLORMATRIX: (ColorMatrixFilter: TSwfCOLORMATRIXFILTER);
  SWF_FILTER_GRADIENTBEVEL: (GradientBevelFilter: TSwfGRADIENTBEVELFILTER);
end;

PSwfButtonRecord = ^TSwfButtonRecord;
TSwfButtonRecord = record
  Flags: Byte;
  CharacterId: UInt16;
  PlaceDepth: UInt16;
  PlaceMatrix: TSwfMATRIX;
  ColorTransform: TSwfCXFORM;
  NumberOfFilters: Byte;
  FilterList: PSwfFILTER;
  BlendMode: Byte;
  function ButtonHasBlendMode: Boolean; inline;
  function ButtonHasFilterList: Boolean; inline;
  function ButtonStateHitTest: Boolean; inline;
  function ButtonStateDown: Boolean; inline;
  function ButtonStateOver: Boolean; inline;
  function ButtonStateUp: Boolean; inline;
end;

function SwfFLOAT16(Data: Pointer): Single; inline;
      // Reads 2-byte floating-point number;

function SwfFIXED(Data: Pointer): UInt32; inline;
      // Reads 4 bytes of 16.16 fixed-point number

function SwfFIXED8(Data: Pointer): UInt16; inline;
      // Reads 2 bytes of 8.8 fixed-point number

function SwfFIXEDToDouble(Fixed: PtrUInt): Double; inline;
function SwfFIXEDToSingle(Fixed: PtrUInt): Single; inline;
function SwfFIXED8ToDouble(Fixed: PtrUInt): Double; inline;
function SwfFIXED8ToSingle(Fixed: PtrUInt): Single; inline;
      // Hint: any 8.8 fixed-point number can be represented as Single
      //   any 16.16 fixed-point number can be represented as Double

function SwfCountExtended(Data, DataEnd: Pointer;
                          out Count: UInt16;
                          out NextData: Pointer): Boolean;

function SwfEncodedU32(Data: Pointer;
                       DataEnd: Pointer;
                       out NextData: Pointer): UInt32;
      // Unsafe for now!

function SwfSTRING(Data, DataEnd: Pointer;
                   out StringEnd: Pointer): Boolean; inline;
      // Tries to find end of string. Returns True and StringEnd that is
      //      Data<=StringEnd<DataEnd   and   PAnsiChar(StringEnd)^=#0
      // returns False if there is no such StringEnd.

function SwfRECT(Data: Pointer;
                 DataEnd: Pointer;
                 out RECT: TSwfRECT;
                 out NextData: Pointer): Boolean;

function SwfMATRIX(Data: Pointer;
                   DataEnd: Pointer;
                   out Matrix: TSwfMATRIX;
                   out NextData: Pointer): Boolean;

function SwfCXFORM(Data: Pointer;
                   DataEnd: Pointer;
                   out Cxform: TSwfCXFORM;
                   out NextData: Pointer): Boolean;

function SwfCXFORMWITHALPHA(Data: Pointer;
                   DataEnd: Pointer;
                   out Cxform: TSwfCXFORM;
                   out NextData: Pointer): Boolean;

function SwfGRADRECORD(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out GradRecord: TSwfGRADRECORD;
                   out NextData: Pointer): Boolean;

function SwfGRADIENT_Begin(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out Gradient: TSwfGRADIENT;
                   out NextData: Pointer): Boolean; inline;
function SwfGRADIENT_End(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   var Gradient: TSwfGRADIENT;
                   out NextData: Pointer): Boolean;
      // 1. Call SwfGRADIENT_Begin
      // 2. Allocate Gradient.NeedMemory bytes
      // 3. Call SwfGRADIENT_End

function SwfFOCALGRADIENT_Begin(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out FocalGradient: TSwfFOCALGRADIENT;
                   out NextData: Pointer): Boolean; inline;
function SwfFOCALGRADIENT_End(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   var FocalGradient: TSwfFOCALGRADIENT;
                   out NextData: Pointer): Boolean; inline;
      // 1. Call SwfFOCALGRADIENT_Begin
      // 2. Allocate FocalGradient.NeedMemory bytes
      // 3. Call SwfFOCALGRADIENT_End

function SwfFILLSTYLE_Begin(Data, DataEnd: Pointer;
                            Shape: PtrUInt;
                            out FillStyle: TSwfFILLSTYLE;
                            out NextData: Pointer): Boolean;
function SwfFILLSTYLE_End(Data, DataEnd: Pointer;
                          Shape: PtrUInt;
                          var FillStyle: TSwfFILLSTYLE;
                          out NextData: Pointer): Boolean;
      // 1. Call SwfFILLSTYLE_Begin.
      // 2. Allocate FillStyle.NeedMemory bytes to FillStyle.Memory
      // 3. Call SwfFILLSTYLE_End
      // ... dont forget to deallocate FillStyle.Memory

function SwfLINESTYLE(Data: Pointer;
                      DataEnd: Pointer;
                      Shape: PtrUInt;
                      out LineStyle: TSwfLINESTYLE2;
                      out NextData: Pointer): Boolean;

function SwfLINESTYLE2_Begin(Data: Pointer;
                       DataEnd: Pointer;
                       out LineStyle: TSwfLINESTYLE2;
                       out NextData: Pointer): Boolean; inline;
function SwfLINESTYLE2_End(Data: Pointer;
                       DataEnd: Pointer;
                       var LineStyle: TSwfLINESTYLE2;
                       out NextData: Pointer): Boolean; inline;

function SwfStyleChangeRecord(Data, DataEnd: Pointer;
                        Bits: PtrInt;
                        FillBits, LineBits: Cardinal;
                        out StyleChangeRecord: TSwfStyleChangeRecord;
                        out NextData: Pointer;
                        out NextBits: PtrInt): Boolean;
      // Does not read FillStyles, LineStyles, NumFillBits and NumLineBits

function SwfEdgeRecord(Data, DataEnd: Pointer;
                       Bits: PtrInt;
                       out EdgeRecord: TSwfEdgeRecord;
                       out NextData: Pointer;
                       out NextBits: PtrInt): Boolean;

function SwfTag(Data, DataEnd: Pointer;
                out TagBody: Pointer;
                out NextData: Pointer): LongInt;
      // Retrurns Tag ID. Returns -1 if data is corrupted.

function SwfPlaceObject2(Data, DataEnd: Pointer;
                         out PlaceObject2: TSwfPlaceObject2;
                         out NextData: Pointer): Boolean;

function SwfRemoveObject(Data, DataEnd: Pointer;
                         out RemoveObject: TSwfRemoveObject;
                         out NextData: Pointer): Boolean;
function SwfRemoveObject2(Data, DataEnd: Pointer;
                          out RemoveObject: TSwfRemoveObject;
                          out NextData: Pointer): Boolean;

function SwfFILTER(Data, DataEnd: Pointer;
                   out Filter: TSwfFILTER;
                   out NextData: Pointer): Boolean;

function SwfButtonRecord_Begin(Data, DataEnd: Pointer;
                               Version: PtrInt;
                               out ButtonRecord: TSwfButtonRecord;
                               out NextData: Pointer): Boolean;
      // Does not read filter list and BlendMode!
function SwfButtonRecord_End(Data, DataEnd: Pointer;
                             Version: PtrInt;
                             var ButtonRecord: TSwfButtonRecord;
                             out NextData: Pointer): Boolean;

function SwfHeader(Data, DataEnd: Pointer;
                   out Header: TSwfHeader;
                   out NextData: Pointer): Boolean;
      // Tries to detect SWF file and retrive header data.

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;

function TSwfLINESTYLE2.StartCapStyle: Byte;
begin
  Result := Flags[0] shr 6;
end;

function TSwfLINESTYLE2.JoinStyle: Byte;
begin
  Result := (Flags[0] shr 4) and $3;
end;

function TSwfLINESTYLE2.HasFillFlag: Boolean;
begin
  Result := Flags[0] and %00001000 <> 0;
end;

function TSwfLINESTYLE2.NoHScaleFlag: Boolean;
begin
  Result := Flags[0] and %00000100 <> 0;
end;

function TSwfLINESTYLE2.NoVScaleFlag: Boolean;
begin
  Result := Flags[0] and %00000010 <> 0;
end;

function TSwfLINESTYLE2.PixelHintingFlag: Boolean;
begin
  Result := Flags[0] and %00000001 <> 0;
end;

function TSwfLINESTYLE2.NoClose: Boolean;
begin
  Result := Flags[1] and %00000100 <> 0;
end;

function TSwfLINESTYLE2.EndCapStyle: Byte;
begin
  Result := Flags[1] and %00000011;
end;

function TSwfStyleChangeRecord.StateNewStyles: Boolean;
begin
  Result := Flags and %010000 <> 0;
end;

function TSwfStyleChangeRecord.StateLineStyle: Boolean;
begin
  Result := Flags and %001000 <> 0;
end;

function TSwfStyleChangeRecord.StateFillStyle1: Boolean;
begin
  Result := Flags and %000100 <> 0;
end;

function TSwfStyleChangeRecord.StateFillStyle0: Boolean;
begin
  Result := Flags and %000010 <> 0;
end;

function TSwfStyleChangeRecord.StateMoveTo: Boolean;
begin
  Result := Flags and %000001 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasClipActions: Boolean; inline;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasClipDepth: Boolean; inline;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasName: Boolean; inline;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasRatio: Boolean; inline;
begin
  Result := Flags and %00010000 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasColorTransform: Boolean; inline;
begin
  Result := Flags and %00001000 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasMatrix: Boolean; inline;
begin
  Result := Flags and %00000100 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasCharacter: Boolean; inline;
begin
  Result := Flags and %00000010 <> 0;
end;

function TSwfPlaceObject2.PlaceFlagHasMove: Boolean; inline;
begin
  Result := Flags and %00000001 <> 0;
end;

function TSwfDROPSHADOWFILTER.InnerShadow: Boolean;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfDROPSHADOWFILTER.Knockout: Boolean;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfDROPSHADOWFILTER.CompositeSource: Boolean;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfDROPSHADOWFILTER.Passes: Byte;
begin
  Result := Flags and %00011111;
end;

function TSwfBLURFILTER.Passes: Byte; inline;
begin
  Result := Flags shr 3;
end;

function TSwfGLOWFILTER.InnerGlow: Boolean;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfGLOWFILTER.Knockout: Boolean;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfGLOWFILTER.CompositeSource: Boolean;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfGLOWFILTER.Passes: Byte;
begin
  Result := Flags and %00011111;
end;

function TSwfBEVELFILTER.InnerShadow: Boolean;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfBEVELFILTER.Knockout: Boolean;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfBEVELFILTER.CompositeSource: Boolean;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfBEVELFILTER.OnTop: Boolean;
begin
  Result := Flags and %00010000 <> 0;
end;

function TSwfBEVELFILTER.Passes: Byte;
begin
  Result := Flags and %00001111;
end;

function TSwfGRADIENTGLOWFILTER.InnerShadow: Boolean; inline;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfGRADIENTGLOWFILTER.Knockout: Boolean; inline;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfGRADIENTGLOWFILTER.CompositeSource: Boolean; inline;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfGRADIENTGLOWFILTER.OnTop: Boolean; inline;
begin
  Result := Flags and %00010000 <> 0;
end;

function TSwfGRADIENTGLOWFILTER.Passes: Byte; inline;
begin
  Result := Flags and %00001111;
end;

function TSwfCONVOLUTIONFILTER.Clamp: Boolean; inline;
begin
  Result := Flags and %00000010 <> 0;
end;

function TSwfCONVOLUTIONFILTER.PreserveAlpha: Boolean; inline;
begin
  Result := Flags and %00000001 <> 0;
end;

function TSwfGRADIENTBEVELFILTER.InnerShadow: Boolean; inline;
begin
  Result := Flags and %10000000 <> 0;
end;

function TSwfGRADIENTBEVELFILTER.Knockout: Boolean; inline;
begin
  Result := Flags and %01000000 <> 0;
end;

function TSwfGRADIENTBEVELFILTER.CompositeSource: Boolean; inline;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfGRADIENTBEVELFILTER.OnTop: Boolean; inline;
begin
  Result := Flags and %00010000 <> 0;
end;

function TSwfGRADIENTBEVELFILTER.Passes: Byte; inline;
begin
  Result := Flags and %00001111;
end;

function TSwfButtonRecord.ButtonHasBlendMode: Boolean;
begin
  Result := Flags and %00100000 <> 0;
end;

function TSwfButtonRecord.ButtonHasFilterList: Boolean;
begin
  Result := Flags and %00010000 <> 0;
end;

function TSwfButtonRecord.ButtonStateHitTest: Boolean;
begin
  Result := Flags and %00001000 <> 0;
end;

function TSwfButtonRecord.ButtonStateDown: Boolean;
begin
  Result := Flags and %00000100 <> 0;
end;

function TSwfButtonRecord.ButtonStateOver: Boolean;
begin
  Result := Flags and %00000010 <> 0;
end;

function TSwfButtonRecord.ButtonStateUp: Boolean;
begin
  Result := Flags and %00000001 <> 0;
end;

function SwfFLOAT16(Data: Pointer): Single;
begin
  // TODO
  Exit(0);
end;

function SwfFIXED(Data: Pointer): UInt32;
begin
  Exit(UInt32(Data^));
end;

function SwfFIXED8(Data: Pointer): UInt16;
begin
  Exit(UInt16(Data^));
end;

function SwfFIXEDToDouble(Fixed: PtrUInt): Double;
begin
  Exit(Fixed / (1 shl 16));
end;

function SwfFIXEDToSingle(Fixed: PtrUInt): Single;
begin
  Exit(Fixed / (1 shl 16));
end;

function SwfFIXED8ToDouble(Fixed: PtrUInt): Double;
begin
  Exit(Fixed / (1 shl 8));
end;

function SwfFIXED8ToSingle(Fixed: PtrUInt): Single;
begin
  Exit(Fixed / (1 shl 8));
end;

function SwfCountExtended(Data, DataEnd: Pointer;
                          out Count: UInt16;
                          out NextData: Pointer): Boolean;
begin
  // TODO check limits
  if PByte(Data)^ = $ff then begin
    Count := AsUInt16(Data + 1);
    NextData := Data + 3;
  end else begin
    Count := PByte(Data)^;
    NextData := Data + 1;
  end;
  Exit(True);
end;

function SwfEncodedU32(Data: Pointer;
                       DataEnd: Pointer;
                       out NextData: Pointer): UInt32;
begin
  Result := PByte(Data)[0];
  if Result and $00000080 = 0 then begin
    NextData := Data + 1;
    Exit;
  end;
  Result := (Result and $0000007f) or (PByte(Data)[1] shl 7);
  if Result and $00004000 = 0 then begin
    NextData := Data + 2;
    Exit;
  end;
  Result := (Result and $00003fff) or (PByte(Data)[2] shl 14);
  if Result and $00200000 = 0 then begin
    NextData := Data + 3;
    Exit;
  end;
  Result := (Result and $001fffff) or (PByte(Data)[3] shl 21);
  if Result and $10000000 = 0 then begin
    NextData := Data + 4;
    Exit;
  end;
  Result := (Result and $0fffffff) or (PByte(Data)[4] shl 28);
  NextData := Data + 5;
end;

function SwfSTRING(Data, DataEnd: Pointer;
                   out StringEnd: Pointer): Boolean;
var
  I: SizeInt;
begin
  I := IndexByte(Data^, DataEnd - Data, 0);
  if I < 0 then begin
    Exit(False);
  end else
    StringEnd := Data + I;
  Exit(True);
end;

function SwfRECT(Data: Pointer;
                 DataEnd: Pointer;
                 out RECT: TSwfRECT;
                 out NextData: Pointer): Boolean;
var
  NBits: PtrInt;
  Head: Byte;
  Bits: PtrInt;
begin
  NBits := ReadBits(Data, 8, 5, Data, Bits);
  RECT.Xmin := ReadBits(Data, Bits, NBits, Data, Bits);
  RECT.Xmax := ReadBits(Data, Bits, NBits, Data, Bits);
  RECT.Ymin := ReadBits(Data, Bits, NBits, Data, Bits);
  RECT.Ymax := ReadBits(Data, Bits, NBits, Data, Bits);
  NextData := AlignBits(Data, Bits);
  Exit(NextData <= DataEnd);
end;

function SwfMATRIX(Data: Pointer;
                   DataEnd: Pointer;
                   out Matrix: TSwfMATRIX;
                   out NextData: Pointer): Boolean;
var
  Bits: PtrInt;
  Size: PtrInt;
begin
  if ReadBit(Data, 8, Data, Bits) <> 0 then begin // HasScale
    Size := ReadBits(Data, Bits, 5, Data, Bits);
    Matrix[0] := ReadBits(Data, Bits, Size, Data, Bits) / (1 shl 16);
    Matrix[4] := ReadBits(Data, Bits, Size, Data, Bits) / (1 shl 16);
  end else begin
    Matrix[0] := 1;
    Matrix[4] := 1;
  end;
  if ReadBit(Data, Bits, Data, Bits) <> 0 then begin // HasRotate
    Size := ReadBits(Data, Bits, 5, Data, Bits);
    Matrix[3] := ReadBits(Data, Bits, Size, Data, Bits) / (1 shl 16);
    Matrix[1] := ReadBits(Data, Bits, Size, Data, Bits) / (1 shl 16);
  end else begin
    Matrix[3] := 0;
    Matrix[1] := 0;
  end;
  Size := ReadBits(Data, Bits, 5, Data, Bits); // Translate
  Matrix[2] := ReadSBits(Data, Bits, Size, Data, Bits) / 20;
  Matrix[5] := ReadSBits(Data, Bits, Size, Data, Bits) / 20;
  Result := (Data < DataEnd) or ((Data = DataEnd) and (Bits = 8));
  Data := AlignBits(Data, Bits);
  NextData := Data;
  Exit(Data <= DataEnd);
end;

function SwfCXFORM(Data: Pointer;
                   DataEnd: Pointer;
                   out Cxform: TSwfCXFORM;
                   out NextData: Pointer): Boolean;
var
  Nbits: LongInt;
  Bits: PtrInt;
begin
  if Data >= DataEnd then // we need at least one byte
    Exit(False);
  Cxform.HasAddTerms  := (PByte(Data)^ and (1 shl 7)) <> 0;
  Cxform.HasMultTerms := (PByte(Data)^ and (1 shl 6)) <> 0;
  Cxform.HasAlpha     := False;
  Nbits := (PByte(Data)^ shr 2) and ((1 shl 4) - 1);
  // Check full length
  if Data + (6 + (Ord(Cxform.HasAddTerms) * 3 + Ord(Cxform.HasMultTerms) * 3) * Nbits + 7) div 8 > DataEnd then
    Exit(False);
  // We are safe now
  if Cxform.HasAddTerms then begin
    // TODO Read signed ints
    Cxform.Add[0]  := ReadBits(Data,    2, Nbits, Data, Bits);
    Cxform.Add[1]  := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Add[2]  := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Add[3]  := ReadBits(Data, Bits, Nbits, Data, Bits);
  end else begin
    Bits := 2;
    Cxform.Add[0]  := 0;
    Cxform.Add[1]  := 0;
    Cxform.Add[2]  := 0;
    Cxform.Add[3]  := 0;
  end;
  if Cxform.HasMultTerms then begin
    Cxform.Mult[0] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[1] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[2] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[3] := 256;
  end else begin
    Cxform.Mult[0]  := 256;
    Cxform.Mult[1]  := 256;
    Cxform.Mult[2]  := 256;
    Cxform.Mult[3] := 256;
  end;
  if Bits > 0 then begin
    NextData := Data + 1;
  end else
    NextData := Data;
  Exit(True);
end;

function SwfCXFORMWITHALPHA(Data: Pointer;
                   DataEnd: Pointer;
                   out Cxform: TSwfCXFORM;
                   out NextData: Pointer): Boolean;
var
  Nbits: LongInt;
  Bits: PtrInt;
begin
  if Data >= DataEnd then // we need at least one byte
    Exit(False);
  Cxform.HasAddTerms  := (PByte(Data)^ and (1 shl 7)) <> 0;
  Cxform.HasMultTerms := (PByte(Data)^ and (1 shl 6)) <> 0;
  Cxform.HasAlpha     := True;
  Nbits := (PByte(Data)^ shr 2) and ((1 shl 4) - 1);
  // Check full length
  if Data + (6 + (Ord(Cxform.HasAddTerms) * 4 + Ord(Cxform.HasMultTerms) * 4) * Nbits + 7) div 8 > DataEnd then
    Exit(False);
  // We are safe now
  if Cxform.HasAddTerms then begin
    // TODO Read signed ints
    Cxform.Add[0]  := ReadBits(Data,    2, Nbits, Data, Bits);
    Cxform.Add[1]  := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Add[2]  := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Add[3]  := ReadBits(Data, Bits, Nbits, Data, Bits);
  end else begin
    Bits := 2;
    Cxform.Add[0]  := 0;
    Cxform.Add[1]  := 0;
    Cxform.Add[2]  := 0;
    Cxform.Add[3]  := 0;
  end;
  if Cxform.HasMultTerms then begin
    Cxform.Mult[0] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[1] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[2] := ReadBits(Data, Bits, Nbits, Data, Bits);
    Cxform.Mult[3] := ReadBits(Data, Bits, Nbits, Data, Bits);
  end else begin
    Cxform.Mult[0]  := 256;
    Cxform.Mult[1]  := 256;
    Cxform.Mult[2]  := 256;
    Cxform.Mult[3]  := 256;
  end;
  if Bits > 0 then begin
    NextData := Data + 1;
  end else
    NextData := Data;
  Exit(True);
end;

function SwfGRADRECORD(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out GradRecord: TSwfGRADRECORD;
                   out NextData: Pointer): Boolean;
begin
  case Shape of
    1, 2: begin
      if DataEnd - Data < 4 then
        Exit(False);
      GradRecord.Ratio := PByte(Data)^;
      GradRecord.Color := PCardinal(Data + 1)^ or MASK_ALPHA;
      NextData := Data + 4;
    end;
    3, 4: begin
      if DataEnd - Data < 5 then
        Exit(False);
      GradRecord.Ratio := PByte(Data)^;
      GradRecord.Color := PCardinal(Data + 1)^;
      NextData := Data + 5;
    end;
  else
    Exit(False);
  end;
  Exit(True);
end;

function SwfGRADIENT_Internal(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out Gradient: TSwfGRADIENT;
                   out GradientRecords: Pointer;
                   out NextData: Pointer): Boolean;
begin
  if Data >= DataEnd then
    Exit(False);
  Gradient.SpreadMode := PByte(Data)^ shr 6;
  Gradient.InterpolationMode := (PByte(Data)^ shr 4) and $3;
  Gradient.NumGradients := (PByte(Data)^ shr 0) and $f;
  GradientRecords := Data + 1;
  if Shape < 3 then begin
    Inc(Data, 1 + 4 * Gradient.NumGradients);
  end else
    Inc(Data, 1 + 5 * Gradient.NumGradients);
  if Data > DataEnd then
    Exit(False);
  NextData := Data;
  Exit(True);
end;

function SwfFOCALGRADIENT_Internal(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out FocalGradient: TSwfFOCALGRADIENT;
                   out GradientRecords: Pointer;
                   out NextData: Pointer): Boolean;
begin
  // FOCALGRADIENT can be declared only for DefineShape4
  if (Data >= DataEnd) or (Shape <> 4) then
    Exit(False);
  FocalGradient.SpreadMode := PByte(Data)^ shr 6;
  FocalGradient.InterpolationMode := (PByte(Data)^ shr 4) and $3;
  FocalGradient.NumGradients := (PByte(Data)^ shr 0) and $f;
  GradientRecords := Data + 1;
  NextData := Data + 1;
  Inc(Data, 1 + 5 * FocalGradient.NumGradients);
  if Data + 2 > DataEnd then
    Exit(False);
  FocalGradient.FocalPoint := SwfFIXED8(Data);
  FocalGradient.GradientRecords := nil;
  Exit(True);
end;

function SwfGRADIENT_Begin(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out Gradient: TSwfGRADIENT;
                   out NextData: Pointer): Boolean;
begin
  if not SwfGRADIENT_Internal(Data, DataEnd, Shape, Gradient, Data, NextData) then
    Exit(False);
  if Shape < 3 then begin
    Gradient.NeedMemory := 4 * Gradient.NumGradients;
  end else
    Gradient.NeedMemory := 5 * Gradient.NumGradients;
  Exit(True);
end;

function SwfGRADIENT_End(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   var Gradient: TSwfGRADIENT;
                   out NextData: Pointer): Boolean;
var
  I: PtrInt;
begin
  Gradient.GradientRecords := Gradient.Memory;
  if Gradient.Memory = nil then
    Exit(False);
  for I := 0 to Gradient.NumGradients - 1 do begin
    if not SwfGRADRECORD(Data, DataEnd, Shape, Gradient.GradientRecords[I], Data) then
      Exit(False);
  end;
  NextData := Data;
  Exit(True);
end;

function SwfFOCALGRADIENT_Begin(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   out FocalGradient: TSwfFOCALGRADIENT;
                   out NextData: Pointer): Boolean;
begin
  if not SwfFOCALGRADIENT_Internal(Data, DataEnd, Shape, FocalGradient, Data, NextData) then
    Exit(False);
  FocalGradient.NeedMemory := 5 * FocalGradient.NumGradients;
  Data := NextData;
  Exit(True);
end;

function SwfFOCALGRADIENT_End(Data: Pointer;
                   DataEnd: Pointer;
                   Shape: PtrUInt;
                   var FocalGradient: TSwfFOCALGRADIENT;
                   out NextData: Pointer): Boolean; inline;
var
  I: PtrInt;
begin
  FocalGradient.GradientRecords := FocalGradient.Memory;
  if FocalGradient.GradientRecords = nil then
    Exit(False);
  for I := 0 to FocalGradient.NumGradients - 1 do begin
    if not SwfGRADRECORD(Data, DataEnd, Shape, FocalGradient.GradientRecords[I], Data) then
      Exit(False);
  end;
  NextData := Data + 2; // <- skip FocalPoint
  Exit(True);
end;

function SwfFILLSTYLE_Begin(Data, DataEnd: Pointer;
                            Shape: PtrUInt;
                            out FillStyle: TSwfFILLSTYLE;
                            out NextData: Pointer): Boolean;
begin
  FillStyle.NeedMemory := 0; // prevent consequences of possible bug on client side
  if Data >= DataEnd then
    Exit(False);
  FillStyle._Type := PByte(Data)^;
  case PByte(Data)^ of
  SWF_FILLSTYLE_SOLID: begin // 0x00 solid fill
      if Shape < 3 then begin
        if Data + 4 > DataEnd then
          Exit(False);
        FillStyle.Color := PCardinal(Data + 1)^ or MASK_ALPHA;
        Inc(Data, 4);
      end else begin
        if Data + 5 > DataEnd then
          Exit(False);
        FillStyle.Color := PCardinal(Data + 1)^;
        Inc(Data, 5);
      end;
    end;
  SWF_FILLSTYLE_LINEAR_GRADIENT,
  SWF_FILLSTYLE_RADIAL_GRADIENT: begin
      Inc(Data);
      if not SwfMATRIX(Data, DataEnd, FillStyle.GradientMatrix, Data) then
        Exit(False);
      if not SwfGRADIENT_Begin(Data, DataEnd, Shape, FillStyle.Gradient, Data) then
        Exit(False);
      FillStyle.NeedMemory := FillStyle.Gradient.NeedMemory;
    end;
  SWF_FILLSTYLE_FOCAL_GRADIENT: begin
      Inc(Data);
      if not SwfMATRIX(Data, DataEnd, FillStyle.GradientMatrix, Data) then
        Exit(False);
      if not SwfFOCALGRADIENT_Begin(Data, DataEnd, Shape, FillStyle.Focal, Data) then
        Exit(False);
      FillStyle.NeedMemory := FillStyle.Focal.NeedMemory;
    end;
  SWF_FILLSTYLE_REPEAT_BITMAP,
  SWF_FILLSTYLE_CLIPPED_BITMAP,
  SWF_FILLSTYLE_NONSMOOTH_REPEAT_BITMAP,
  SWF_FILLSTYLE_NONSMOOTH_CLIPPED_BITMAP: begin
      // TODO check memory limit here
      Inc(Data);
      FillStyle.BitmapId := AsUInt16(Data);
      Inc(Data, 2);
      if not SwfMATRIX(Data, DataEnd, FillStyle.BitmapMatrix, Data) then
        Exit(False);
    end;
  else
    Exit(False);
  end;
  NextData := Data;
  Exit(True);
end;

function SwfFILLSTYLE_End(Data, DataEnd: Pointer;
                          Shape: PtrUInt;
                          var FillStyle: TSwfFILLSTYLE;
                          out NextData: Pointer): Boolean;
begin
  case FillStyle._Type of
  SWF_FILLSTYLE_LINEAR_GRADIENT,
  SWF_FILLSTYLE_RADIAL_GRADIENT: begin
      FillStyle.Gradient.Memory := FillStyle.Memory;
      if not SwfGRADIENT_End(Data, DataEnd, Shape, FillStyle.Gradient, Data) then
        Exit(False);
    end;
  SWF_FILLSTYLE_FOCAL_GRADIENT: begin
      FillStyle.Focal.Memory := FillStyle.Memory;
      if not SwfFOCALGRADIENT_End(Data, DataEnd, Shape, FillStyle.Focal, Data) then
        Exit(False);
    end;
  end;
  NextData := Data;
  Exit(True);
end;

function SwfLINESTYLE(Data: Pointer;
                      DataEnd: Pointer;
                      Shape: PtrUInt;
                      out LineStyle: TSwfLINESTYLE2;
                      out NextData: Pointer): Boolean;
begin
  LineStyle.Width := AsUInt16(Data);
  if Shape = 3 then begin
    LineStyle.Color := PUInt32(Data + 2)^;
    NextData := Data + 6;
  end else begin
    LineStyle.Color := PUInt32(Data + 2)^ or MASK_ALPHA;
    NextData := Data + 5;
  end;
  LineStyle.NeedMemory := 0;
  LineStyle.Memory := nil;
  Exit(True);
end;

function SwfLINESTYLE2_Internal(Data: Pointer;
                                DataEnd: Pointer;
                                out LineStyle: TSwfLINESTYLE2;
                                out NextData: Pointer): Boolean;
begin
  if Data + 4 > DataEnd then
    Exit(False);
  LineStyle.Width := AsUInt16(Data);
  Inc(Data, 2);
  LineStyle.Flags[0] := PByte(Data)^;
  LineStyle.Flags[1] := PByte(Data + 1)^;
  Inc(Data, 2);
  if LineStyle.JoinStyle = SWF_JOIN_MITER then begin
    if Data + 2 > DataEnd then
      Exit(False);
    LineStyle.MiterLimitFactor := SwfFIXED8(Data);
    Inc(Data, 2);
  end;
  if LineStyle.HasFillFlag then begin
    // Here we should read FillType, but it needs memory allocation, so it
    // should be done later by caller.
    LineStyle.Color := $ffffffff;
  end else begin
    if Data + 4 > DataEnd then
      Exit(False);
    LineStyle.Color := Cardinal(Data^);
    Inc(Data, 4);
  end;
  NextData := Data;
  Exit(True);
end;

function SwfLINESTYLE2_Begin(Data: Pointer;
                       DataEnd: Pointer;
                       out LineStyle: TSwfLINESTYLE2;
                       out NextData: Pointer): Boolean; inline;
begin
  LineStyle.NeedMemory := 0;
  if not SwfLINESTYLE2_Internal(Data, DataEnd, LineStyle, NextData) then
    Exit(False);
  if LineStyle.HasFillFlag then begin
    if not SwfFILLSTYLE_Begin(NextData, DataEnd, 4, LineStyle.FillType, NextData) then
      Exit(False);
    LineStyle.NeedMemory := LineStyle.FillType.NeedMemory;
  end;
  Exit(True);
end;

function SwfLINESTYLE2_End(Data: Pointer;
                       DataEnd: Pointer;
                       var LineStyle: TSwfLINESTYLE2;
                       out NextData: Pointer): Boolean; inline;
begin
  if LineStyle.HasFillFlag then begin
    LineStyle.FillType.Memory := LineStyle.Memory;
    if not SwfFILLSTYLE_End(NextData, DataEnd, 4, LineStyle.FillType, NextData) then
      Exit(False);
  end;
  Exit(True);
end;

function SwfStyleChangeRecord(Data, DataEnd: Pointer;
                        Bits: PtrInt;
                        FillBits, LineBits: Cardinal;
                        out StyleChangeRecord: TSwfStyleChangeRecord;
                        out NextData: Pointer;
                        out NextBits: PtrInt): Boolean;
var
  MoveBits: Cardinal;
  TempData: Pointer;
  TempBits: PtrInt;
begin
  Result := False;// make compiler happy
  // TODO check limits
  StyleChangeRecord.Flags := ReadBits(Data, Bits, 5, Data, Bits);
  if StyleChangeRecord.StateMoveTo then begin
    MoveBits := ReadBits(Data, Bits, 5, Data, Bits);
    StyleChangeRecord.MoveDeltaX := ReadSBits(Data, Bits, MoveBits, Data, Bits);
    StyleChangeRecord.MoveDeltaY := ReadSBits(Data, Bits, MoveBits, Data, Bits);
  end else begin
    StyleChangeRecord.MoveDeltaX := 0;
    StyleChangeRecord.MoveDeltaY := 0;
  end;
  if StyleChangeRecord.StateFillStyle0 then begin
    StyleChangeRecord.FillStyle0 := ReadBits(Data, Bits, FillBits, Data, Bits);
  end;
  if StyleChangeRecord.StateFillStyle1 then begin
    StyleChangeRecord.FillStyle1 := ReadBits(Data, Bits, FillBits, Data, Bits);
  end;
  if StyleChangeRecord.StateLineStyle then begin
    StyleChangeRecord.LineStyle := ReadBits(Data, Bits, LineBits, Data, Bits);
  end;
  NextData := Data;
  NextBits := Bits;
  Exit(True);
end;

function SwfEdgeRecord(Data, DataEnd: Pointer;
                       Bits: PtrInt;
                       out EdgeRecord: TSwfEdgeRecord;
                       out NextData: Pointer;
                       out NextBits: PtrInt): Boolean;
var
  NumBits: PtrInt;
begin
  EdgeRecord.Curved := ReadBit(Data, Bits, Data, Bits) = 0; // Straight
  NumBits := ReadBits(Data, Bits, 4, Data, Bits) + 2;
  if EdgeRecord.Curved then begin
    EdgeRecord.ControlDeltaX := ReadSBits(Data, Bits, NumBits, Data, Bits);
    EdgeRecord.ControlDeltaY := ReadSBits(Data, Bits, NumBits, Data, Bits);
    EdgeRecord.AnchorDeltaX  := EdgeRecord.ControlDeltaX + ReadSBits(Data, Bits, NumBits, Data, Bits);
    EdgeRecord.AnchorDeltaY  := EdgeRecord.ControlDeltaY + ReadSBits(Data, Bits, NumBits, Data, Bits);
  end else begin
    if ReadBit(Data, Bits, Data, Bits) <> 0 then begin // general line
      EdgeRecord.DeltaX := ReadSBits(Data, Bits, NumBits, Data, Bits);
      EdgeRecord.DeltaY := ReadSBits(Data, Bits, NumBits, Data, Bits);
    end else begin // vert/horz line
      if ReadBit(Data, Bits, Data, Bits) <> 0 then begin // vertical line
        EdgeRecord.DeltaX := 0;
        EdgeRecord.DeltaY := ReadSBits(Data, Bits, NumBits, Data, Bits);
      end else begin // horizontal line
        EdgeRecord.DeltaX := ReadSBits(Data, Bits, NumBits, Data, Bits);
        EdgeRecord.DeltaY := 0;
      end;
    end;
  end;
  NextData := Data;
  NextBits := Bits;
  Exit(True);
end;

function _TagEnd(Data: Pointer; Len: UInt16; out TagBody: Pointer): Pointer; inline;
begin
  if Len = $3f then begin
    TagBody := Data + 6;
    Exit(Data + 6 + PUInt32(Data + 2)^);
  end else begin
    TagBody := Data + 2;
    Exit(Data + 2 + Len);
  end;
end;

function SwfTag(Data, DataEnd: Pointer;
                out TagBody: Pointer;
                out NextData: Pointer): LongInt;
begin
  NextData := _TagEnd(Data, AsUInt16(Data) and $3f, TagBody);
  if NextData > DataEnd then begin
    Exit(-1);
  end else
    Exit(AsUInt16(Data) shr 6);
end;

function SwfPlaceObject2(Data, DataEnd: Pointer;
                         out PlaceObject2: TSwfPlaceObject2;
                         out NextData: Pointer): Boolean;
begin
  if DataEnd - Data < 3 then
    Exit(False);
  PlaceObject2.Flags := PByte(Data)^;
  PlaceObject2.Depth := AsUInt16(Data + 1);
  Inc(Data, 3);
  if PlaceObject2.PlaceFlagHasCharacter then begin
    PlaceObject2.CharacterId := AsUInt16(Data);
    Inc(Data, 2);
  end;
  if PlaceObject2.PlaceFlagHasMatrix then begin
    if not SwfMATRIX(Data, DataEnd, PlaceObject2.Matrix, Data) then
      Exit(False);
  end;
  if PlaceObject2.PlaceFlagHasColorTransform then begin
    if not SwfCXFORMWITHALPHA(Data, DataEnd, PlaceObject2.ColorTransform, Data) then
      Exit(False);
  end;
  if PlaceObject2.PlaceFlagHasRatio then begin
    PlaceObject2.Ratio := AsUInt16(Data);
    Inc(Data, 2);
  end;
  if PlaceObject2.PlaceFlagHasName then begin
    PlaceObject2.Name := Data;
    if not SwfSTRING(Data, DataEnd, Data) then
      Exit(False);
    PlaceObject2.NameEnd := Data;
  end;
  if PlaceObject2.PlaceFlagHasClipDepth then begin
    PlaceObject2.ClipDepth := AsUInt16(Data);
    Inc(Data, 2);
  end;
  Exit(True);
end;

function SwfRemoveObject(Data, DataEnd: Pointer;
                         out RemoveObject: TSwfRemoveObject;
                         out NextData: Pointer): Boolean;
begin
  if DataEnd - Data < 4 then
    Exit(False);
  RemoveObject.CharacterId := AsUInt16(Data);
  RemoveObject.Depth := AsUInt16(Data + 2);
  NextData := Data + 4;
  Exit(True);
end;

function SwfRemoveObject2(Data, DataEnd: Pointer;
                          out RemoveObject: TSwfRemoveObject;
                          out NextData: Pointer): Boolean;
begin
  if DataEnd - Data < 2 then
    Exit(False);
  RemoveObject.CharacterId := 0;
  RemoveObject.Depth := AsUInt16(Data);
  NextData := Data + 2;
  Exit(True);
end;

function SwfFILTER(Data, DataEnd: Pointer;
                   out Filter: TSwfFILTER;
                   out NextData: Pointer): Boolean;
begin
  // TODO check limits
  Filter.FilterID := PByte(Data)^;
  Inc(Data);
  case Filter.FilterID of
  //SWF_FILTER_DROPSHADOW:    Exit(SwfDROPSHADOWFILTER(Data, DataEnd, Filter.DropShadowFilter, NextData);
  //SWF_FILTER_BLUR:          Exit(SwfBLURFILTER(Data, DataEnd, Filter.BlurFilter, NextData);
  //SWF_FILTER_GLOW:          Exit(SwfGLOWFILTER(Data, DataEnd, Filter.GlowFilter, NextData);
  //SWF_FILTER_BEVEL:         Exit(SwfBEVELFILTER(Data, DataEnd, Filter.BevelFilter, NextData);
  //SWF_FILTER_GRADIENTGLOW:  Exit(SwfGRADIENTGLOWFILTER(Data, DataEnd, Filter.GradientGlowFilter, NextData);
  //SWF_FILTER_CONVOLUTION:   Exit(SwfCONVOLUTIONFILTER(Data, DataEnd, Filter.ConvolutionFilter, NextData);
  //SWF_FILTER_COLORMATRIX:   Exit(SwfCONVOLUTIONFILTER(Data, DataEnd, Filter.ColorMatrixFilter, NextData);
  //SWF_FILTER_GRADIENTBEVEL: Exit(SwfGRADIENTBEVEL(Data, DataEnd, Filter.GradientBevelFilter, NextData));
  SWF_FILTER_DROPSHADOW: begin
      Filter.DropShadowFilter.DropShadowColor := UInt32(Data^); Inc(Data, 4);
      Filter.DropShadowFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.DropShadowFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.DropShadowFilter.Angle := SwfFIXED(Data); Inc(Data, 4);
      Filter.DropShadowFilter.Distance := SwfFIXED(Data); Inc(Data, 4);
      Filter.DropShadowFilter.Strength := SwfFIXED8(Data); Inc(Data, 2);
      Filter.DropShadowFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_BLUR: begin
      Filter.BlurFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.BlurFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.BlurFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_GLOW: begin
      Filter.GlowFilter.GlowColor := UInt32(Data^); Inc(Data, 4);
      Filter.GlowFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.GlowFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.GlowFilter.Strength := SwfFIXED8(Data); Inc(Data, 2);
      Filter.GlowFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_BEVEL: begin
      Filter.BevelFilter.ShadowColor := UInt32(Data^); Inc(Data, 4);
      Filter.BevelFilter.HighlightColor := UInt32(Data^); Inc(Data, 4);
      Filter.BevelFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.BevelFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.BevelFilter.Angle := SwfFIXED(Data); Inc(Data, 4);
      Filter.BevelFilter.Distance := SwfFIXED(Data); Inc(Data, 4);
      Filter.BevelFilter.Strength := SwfFIXED8(Data); Inc(Data, 2);
      Filter.BevelFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_GRADIENTGLOW: begin
      Filter.GradientGlowFilter.NumColors := PByte(Data)^; Inc(Data, 1);
      Filter.GradientGlowFilter.GradientColors := Data; Inc(Data, Filter.GradientGlowFilter.NumColors * SizeOf(UInt32));
      Filter.GradientGlowFilter.GradientRatio := Data; Inc(Data, Filter.GradientGlowFilter.NumColors * SizeOf(Byte));
      Filter.GradientGlowFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientGlowFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientGlowFilter.Angle := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientGlowFilter.Distance := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientGlowFilter.Strength := SwfFIXED8(Data); Inc(Data, 2);
      Filter.GradientGlowFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_CONVOLUTION: begin
      Filter.ConvolutionFilter.MatrixX := PByte(Data)^; Inc(Data, 1);
      Filter.ConvolutionFilter.MatrixY := PByte(Data)^; Inc(Data, 1);
      Filter.ConvolutionFilter.Divisor := PSingle(Data)^; Inc(Data, SizeOf(Single));
      Filter.ConvolutionFilter.Bias := PSingle(Data)^; Inc(Data, SizeOf(Single));
      Filter.ConvolutionFilter.Matrix := Data; Inc(Data, (Filter.ConvolutionFilter.MatrixX * Filter.ConvolutionFilter.MatrixY) * SizeOf(Single));
      Filter.ConvolutionFilter.DefaultColor := UInt32(Data^); Inc(Data, SizeOf(UInt32));
      Filter.ConvolutionFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_COLORMATRIX: begin
      Move(Data^, Filter.ColorMatrixFilter.Matrix[0], 20 * SizeOf(Single)); Inc(Data, 20 * SizeOf(Single));
      NextData := Data;
      Exit(True);
    end;
  SWF_FILTER_GRADIENTBEVEL: begin
      Filter.GradientBevelFilter.NumColors := PByte(Data)^; Inc(Data, 1);
      Filter.GradientBevelFilter.GradientColors := Data; Inc(Data, Filter.GradientGlowFilter.NumColors * SizeOf(UInt32));
      Filter.GradientBevelFilter.GradientRatio := Data; Inc(Data, Filter.GradientGlowFilter.NumColors * SizeOf(Byte));
      Filter.GradientBevelFilter.BlurX := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientBevelFilter.BlurY := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientBevelFilter.Angle := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientBevelFilter.Distance := SwfFIXED(Data); Inc(Data, 4);
      Filter.GradientBevelFilter.Strength := SwfFIXED8(Data); Inc(Data, 2);
      Filter.GradientBevelFilter.Flags := PByte(Data)^; Inc(Data, 1);
      NextData := Data;
      Exit(True);
    end;
  else
    Exit(False);
  end;
end;

function SwfButtonRecord_Begin(Data, DataEnd: Pointer;
                               Version: PtrInt;
                               out ButtonRecord: TSwfButtonRecord;
                               out NextData: Pointer): Boolean;
begin
  // TODO check limits
  ButtonRecord.Flags := PByte(Data)^;
  ButtonRecord.CharacterId := AsUInt16(Data + 1);
  ButtonRecord.PlaceDepth := AsUInt16(Data + 3);
  if not SwfMATRIX(Data + 5, DataEnd, ButtonRecord.PlaceMatrix, Data) then
    Exit(False);
  if Version = 2 then begin
    if not SwfCXFORMWITHALPHA(Data, DataEnd, ButtonRecord.ColorTransform, Data) then
      Exit(False);
  end;
  ButtonRecord.NumberOfFilters := 0;
  ButtonRecord.FilterList := nil;
  ButtonRecord.BlendMode := SWF_BLENDMODE_NORMAL;
  NextData := Data;
  Exit(True);
end;

function SwfButtonRecord_End(Data, DataEnd: Pointer;
                             Version: PtrInt;
                             var ButtonRecord: TSwfButtonRecord;
                             out NextData: Pointer): Boolean;
begin
  // TODO check limits
  if (Version = 2) and ButtonRecord.ButtonHasBlendMode then begin
    ButtonRecord.BlendMode := PByte(Data)^;
    if ButtonRecord.BlendMode = 0 then
      ButtonRecord.BlendMode := SWF_BLENDMODE_NORMAL; // 0 and 1 are the same
    NextData := Data + 1;
    Exit(ButtonRecord.BlendMode <= 14);
  end else begin
    NextData := Data;
    Exit(True);
  end;
end;

function SwfHeader(Data, DataEnd: Pointer;
                   out Header: TSwfHeader;
                   out NextData: Pointer): Boolean;
begin
  if DataEnd - Data < 8 then
    Exit(False); // no data
  if (PAnsiChar(Data)[1] <> 'W') or
     (PAnsiChar(Data)[2] <> 'S') then
    Exit(False); // bad signature
  Header.Version := PByte(Data)[3];
  //if Header.Version > 19 then
  //  Exit(False); // too high version
  Header.Size := UInt32((Data + 4)^);
  case PAnsiChar(Data)[0] of
    'F': begin // uncompressed
      Header.Compression := SWF_COMPRESS_NO;
      //if Header.Size <> DataEnd - Data then
      //  Exit(False); // bad size
    end;
    'C': begin
      Header.Compression := SWF_COMPRESS_ZLIB;
    end;
    'Z': begin
      Header.Compression := SWF_COMPRESS_LZMA;
    end;
  else
    Exit(False); // bad signature
  end;
  NextData := Data + 8;
  Exit(True);
end;

end.
