(*******************************************************************
 *
 *  TTFile.Pas                                                1.2
 *
 *    File I/O Component (specification)
 *
 *  Copyright 1996 David Turner, Robert Wilhelm and Werner Lemberg
 *
 *  This file is part of the FreeType project, and may only be used
 *  modified and distributed under the terms of the FreeType project
 *  license, LICENSE.TXT. By continuing to use, modify or distribute
 *  this file you indicate that you have read the license and
 *  understand and accept it fully.
 *
 *  NOTES :
 *
 *
 *  Changes from 1.1 to 1.2 :
 *
 *  - Changes the stream operations semantics. See changes.txt
 *
 *  - stream records are now allocated on demand in the heap
 *
 *  - introduced the 'frame cache' to avoid Allocating/Freeing
 *    each frame, even tiny ones..
 *
 *  - support for thread-safety and re-entrancy
 *
 *    ( re-entrancy is there for information only.. )
 *
 *  Changes from 1.0 to 1.1 :
 *
 *  - defined the type TT_Stream for file handles
 *  - renamed ( and cleaned ) the API.
 *
 *  - caching and memory-mapped files use the same API :
 *
 *      TT_Access_Frame to notify
 *
 *  - only the interface was really rewritten. This component still
 *    only supports one opened file at a time.
 *
 ******************************************************************)

Unit TTFile;

interface

{$I TTCONFIG.INC}

uses FreeType,
     TTTypes,
     TTError;

  function  TTFile_Init : TError;
  procedure TTFile_Done;

 (*********************************************************************)
 (*                                                                   *)
 (*  Stream Functions                                                 *)
 (*                                                                   *)
 (*********************************************************************)

 function  TT_Open_Stream( name       : String;
                           var stream : TT_Stream ) : TError;
 (* Open a file and return a stream handle for it               *)
 (* should only be used for a new typeface object's main stream *)

 procedure TT_Close_Stream( var stream : TT_Stream );
 (* closes, then discards a stream, when it becomes unuseful *)
 (* should only be used for a typeface object's main stream  *)

 function  TT_Use_Stream( org_stream : TT_Stream;
                          var stream : TT_Stream ) : TError;
 (* notices the component that we're going to use the file   *)
 (* opened in 'org_stream', and report errors to the 'error' *)
 (* variable. the 'stream' variable is untouched, except in  *)
 (* re-entrant buids.                                        *)

 (* in re-entrant builds, the original file handle is duplicated *)
 (* to a new stream which reference is passed to the 'stream'    *)
 (* variable.. thus, each thread can have its own file cursor to *)
 (* access the same file concurrently..                          *)

 procedure TT_Flush_Stream( stream : TT_Stream );
 (* closes a stream's font handle. This is useful to save      *)
 (* system resources.                                          *)

 procedure TT_Done_Stream( stream : TT_Stream );
 (* notice the file component that we don't need to perform    *)
 (* file ops on the stream 'stream' anymore..                  *)
 (*                                                            *)
 (* in re-entrant builds, should also discard the stream       *)

 (*********************************************************************)
 (*                                                                   *)
 (*  File Functions                                                   *)
 (*                                                                   *)
 (*    the following functions perform file operations on the         *)
 (*    currently 'used' stream. In thread-safe builds, only one       *)
 (*    stream can be used at a time. Synchronisation is performed     *)
 (*    through the Use_Stream/Done_Stream functions                   *)
 (*                                                                   *)
 (*  Note:                                                            *)
 (*    re-entrant versions of these functions are only available      *)
 (*    in the C source tree. There, a macro is used to add a 'stream' *)
 (*    parameter to each of these routines..                          *)
 (*                                                                   *)
 (*********************************************************************)

 function TT_Read_File( var ABuff; ACount : Int ) : TError;
 (* Read a chunk of bytes directly from the file *)

 function TT_Seek_File( APos : LongInt ) : TError;
 (* Seek a new file position *)

 function TT_Skip_File( ADist : LongInt ) : TError;
 (* Skip to a new file position *)

 function TT_Read_At_File( APos : Long; var ABuff; ACount : Int ) : TError;
 (* Seek and read a chunk of bytes *)

 function TT_File_Size : Longint;

 function TT_File_Pos  : Longint;

 function TT_Stream_Size( stream : TT_Stream ) : longint;

 (*********************************************************************)
 (*                                                                   *)
 (*  Frame Functions                                                  *)
 (*                                                                   *)
 (*********************************************************************)

 function TT_Access_Frame( aSize : Int ) : TError;
 (* Access the next aSize bytes *)

 function TT_Check_And_Access_Frame( aSize : Int ) : TError;
 (* Access the next min(aSize,file_size-file_pos) bytes *)

 function TT_Forget_Frame :  TError;
 (* Forget the previously cached frame *)

 (* The four following functions should only be used after a *)
 (* TT_Access_Frame and before a TT_Forget_Frame             *)

 (* They do not provide error handling, intentionnaly, and are much faster *)
 (* moreover, they could be converted to MACROS in the C version           *)

 function GET_Byte   : Byte;
 function GET_Char   : ShortInt;
 function GET_Short  : Short;
 function GET_UShort : UShort;
 function GET_Long   : Long;
 function GET_ULong  : ULong;
 function GET_Tag4   : ULong;

implementation

uses
  TTMemory,

  (* THREADS: TTMutex, *)

  TTLists;

const
  frame_cache_size = 2048;
  (* we allocate a single block where we'll place all of our frames *)
  (* instead of allocating an new block on each access. Note that   *)
  (* frames that are bigger than this constant are effectively      *)
  (* allocated in the heap..                                        *)

type
  PString = ^string;
  PFile   = ^FILE;
  PError  = ^TT_Error;

  PStream_Rec = ^TStream_Rec;
  TStream_Rec = record
                  name  : PString;  (* file pathname                     *)
                  open  : Boolean;  (* is the stream currently opened    *)
                  font  : PFILE;    (* file handle for opened stream     *)
                  base  : Longint;  (* base offset for embedding         *)
                  size  : Longint;  (* size of font in resource          *)
                  posit : Longint;  (* current offset for closed streams *)
                end;

var
  (* THREADS: File_Mutex : TMutex *)

  font_file  : PFile;
  cur_stream : PStream_Rec;

  current_frame : PByte;
  frame_cursor  : Longint;
  frame_size    : LongInt;

  dummy_error : TT_Error;

  frame_cache : PByte;

  function  TT_File_Size : Longint;
  begin
    TT_File_Size := FileSize( font_file^ );
  end;

  function TT_File_Pos : Longint;
  begin
    TT_File_Pos := FilePos( font_file^ );
  end;

  function TT_Stream_Size( stream : TT_Stream ) : longint;
  var
    rec : PStream_Rec;
  begin
    rec := PStream_Rec(stream);
    if rec = nil then
      TT_Stream_Size := 0
    else
      TT_Stream_Size := rec^.size;
  end;

(*******************************************************************
 *
 *  Function    :  TTFile_Init
 *
 *  Description :  Init the file component
 *
 *                 - create a file mutex for thread-safe builds
 *
 ******************************************************************)

 function TTFile_Init : TError;
 begin
   (* empty current file *)
   font_file  := nil;
   cur_stream := nil;

   (* empty frame *)
   current_frame := nil;
   frame_cursor  := 0;
   frame_size    := 0;

   (* create frame cache *)
   GetMem( frame_cache, frame_cache_size );

   TTFile_Init := Success;
 end;

(*******************************************************************
 *
 *  Function    :  TTFile_Done
 *
 *  Description :  Finalize the file component
 *
 *                 - destroys the file mutex for thread-safe builds
 *
 ******************************************************************)

 procedure TTFile_Done;
 begin
   (* empty current file *)
   font_file  := nil;
   cur_stream := nil;

   (* empty frame *)
   current_frame := nil;
   frame_cursor  := 0;
   frame_size    := 0;
 end;

(*******************************************************************
 *
 *  Function    :  Stream_New
 *
 *  Description :  allocates a new stream record
 *
 *  Input  :  stream  : the target stream variable
 *
 *  Output :  True on sucess.
 *
 ******************************************************************)

 function Stream_New( pathname   : string;
                      var stream : PStream_Rec ) : TError;
 var
   font : PFile;
   name : PString;
   len  : Integer;
 label
   Fail_Memory;
 begin
   name   := nil;
   font   := nil;
   stream := nil;
   len    := length(pathname)+1;

   (* allocate a new stream_rec in the heap *)
   if Alloc( pointer(stream), sizeof(TStream_Rec) ) or
      Alloc( pointer(font),   sizeof(FILE)        ) or
      Alloc( pointer(name),   len                 ) then
     goto Fail_Memory;

   move( pathname, name^, len );

   stream^.font  := font;
   stream^.name  := name;
   stream^.open  := false;
   stream^.base  := 0;
   stream^.size  := 0;
   stream^.posit := 0;

   Stream_New := Success;
   exit;

 Fail_Memory:
   Free( pointer(name)   );
   Free( pointer(font)   );
   Free( pointer(stream) );
   Stream_New := Failure;
 end;

(*******************************************************************
 *
 *  Function    :  Stream_Activate
 *
 *  Description :  activates a stream, if it needs it
 *
 *  Input  :  stream  : the target stream variable
 *
 *  Output :  Error condition
 *
 ******************************************************************)

 function Stream_Activate( stream : PStream_Rec ) : TError;
 var
   old_filemode : Long;
 begin
   Stream_Activate := Failure;
   if stream = nil then exit;

   with stream^ do
   begin
     Stream_Activate := Success;
     if open then exit;

     old_filemode    := System.FileMode;
     System.FileMode := 0;
     (* read-only mode *)

     Assign( font^, name^ );
     {$I-}
     Reset( font^, 1 );
     {$I+}

     System.FileMode := old_filemode;

     if IOResult <> 0 then
     begin
       error := TT_Err_Could_Not_Open_File;
       Stream_Activate := Failure;
       exit;
     end;

     open := true;
     base := 0;
     if size = -1 then size := FileSize(font^);

     if posit <> 0 then
       Seek( font^, posit );
   end;
 end;

(*******************************************************************
 *
 *  Function    :  Stream_Deactivate
 *
 *  Description :  closes an active stream
 *
 *  Input  :  stream  : the target stream variable
 *
 *  Output :  Error condition
 *
 ******************************************************************)

 function Stream_Deactivate( stream : PStream_Rec ) : TError;
 begin
   Stream_Deactivate := Failure;
   if stream = nil then exit;

   Stream_Deactivate := Success;
   if not stream^.open then exit;

   stream^.posit := FilePos( stream^.font^ );
   close( stream^.font^ );
   stream^.open := false;
 end;

(*******************************************************************
 *
 *  Function    :  Stream_Done
 *
 *  Description :  frees an active stream_rec
 *
 *  Input  :  stream  : the target stream variable
 *
 *  Output :  True on sucess.
 *
 *  Notes  : 'stream' is set to nil on exit..
 *
 ******************************************************************)

 function Stream_Done( var stream : PStream_Rec ) : TError;
 begin
   Stream_Deactivate( stream );

   Free( pointer(stream^.name) );
   Free( pointer(stream^.font) );
   Free( pointer(stream) );

   Stream_Done := Success;
 end;

(*******************************************************************
 *
 *  Function    :  TT_Open_Stream
 *
 *  Description :  opens the font file in a new stream
 *
 *  Input  :  stream  : target stream variable
 *            name    : file pathname
 *            error   : the variable that will be used to
 *                      report stream errors
 *
 *  Output :  True on sucess.
 *
 ******************************************************************)

 function TT_Open_Stream( name       : String;
                          var stream : TT_Stream ) : TError;
 var
   rec  : PStream_Rec;
   font : PFile;

   old_filemode : Long;
 begin
   TT_Open_Stream := Failure;

   if Stream_New( name, rec ) or
      Stream_Activate( rec )  then
     begin
       stream.z := nil;
       exit;
     end;

   cur_stream := rec;
   font_file  := rec^.font;
   stream     := TT_Stream(rec);

   TT_Open_Stream := Success;
 end;

(*******************************************************************
 *
 *  Function    : TT_Close_Stream
 *
 *  Description : Closes the font file and releases memory buffer
 *
 *  Input  :  None
 *
 *  Output :  True ( always )
 *
 ******************************************************************)

 procedure TT_Close_Stream( var stream : TT_Stream );
 begin
   if stream.z = nil then exit;

   Stream_Done( PStream_Rec(stream) );
   font_file  := nil;
   cur_stream := nil;
   stream.z   := nil;
 end;

(*******************************************************************
 *
 *  Function    : TT_Use_Stream
 *
 *  Description : Acquire the file mutex (blocking call)
 *
 *  Input  :  org_stream : original stream to use
 *            stream     : duplicate stream (in re-entrant builds)
 *                         set to 'org_stream' otherwise
 *            error      : error report variable
 *
 *  Output :  True on success. False on failure
 *
 ******************************************************************)

 function  TT_Use_Stream( org_stream : TT_Stream;
                          var stream : TT_Stream ) : TError;
 var
   rec : PStream_Rec;
 begin
   TT_Use_Stream := Failure;

   stream := org_stream;
   if org_stream.z = nil then exit;

   rec := PStream_Rec(stream);
   Stream_Activate(rec);
   cur_stream := rec;
   font_file  := rec^.font;

   TT_Use_Stream := Success;
 end;

(*******************************************************************
 *
 *  Function    : TT_Flush_Stream
 *
 *  Description : closes a stream
 *
 *  Input  :  stream : the stream
 *
 *  Output :  True on success. False on failure
 *
 ******************************************************************)

 procedure TT_Flush_Stream( stream : TT_Stream );
 begin
   if stream.Z <> nil then
     Stream_Deactivate( PStream_Rec(stream.z) );
 end;

(*******************************************************************
 *
 *  Function    : TT_Done_Stream
 *
 *  Description : Release the file mutex on a stream
 *
 *  Input  :  stream : the stream
 *
 *  Output :  True on success. False on failure
 *
 ******************************************************************)

 procedure TT_Done_Stream( stream : TT_Stream );
 begin
   if stream.z <> cur_stream then exit;
   cur_stream := nil;
   font_file  := nil;
 end;

(*******************************************************************
 *
 *  Function    : TT_Seek_File
 *
 *  Description : Seek the file cursor to a different position
 *
 *  Input  :  APos     new position on file
 *
 *  Output :  True on success. False if out of range
 *
 *  Notes  :  Does not set the error variable
 *
 ******************************************************************)

function TT_Seek_File( APos : LongInt ) : TError;
begin
  {$I-}
  Seek( Font_File^, APos );
  {$I+}
  if IOResult <> 0 then
    begin
      error        := TT_Err_Invalid_File_Offset;
      TT_Seek_File := Failure;
      exit;
    end;

  TT_Seek_File := Success;
end;

(*******************************************************************
 *
 *  Function    : TT_Skip_File
 *
 *  Description : Skip forward the file cursor
 *
 *  Input  :  ADist    number of bytes to skip
 *
 *  Output :  see Seek_Font_File
 *
 ******************************************************************)

function TT_Skip_File( ADist : LongInt ) : TError;
begin
  TT_Skip_File := TT_Seek_File( FilePos(Font_File^)+ADist );
end;

(*******************************************************************
 *
 *  Function    : TT_Read_File
 *
 *  Description : Reads a chunk of the file and copy it to memory
 *
 *  Input  :  ABuff     target buffer
 *            ACount    length in bytes to read
 *
 *  Output :  True if success. False if out of range
 *
 *  Notes  :  Current version prints an error message even if the
 *            debug state isn't on.
 *
 ******************************************************************)

function TT_Read_File( var ABuff; ACount : Int ) : TError;
begin
  TT_Read_File := Failure;
  {$I-}
  BlockRead( Font_File^, ABuff, ACount );
  {$I+}

  if IOResult <> 0 then
    begin
      error := TT_Err_Invalid_File_Read;
      exit;
    end;

  TT_Read_File := Success;
end;

(*******************************************************************
 *
 *  Function    : TT_Read_At_File
 *
 *  Description : Read file at a specified position
 *
 *  Input  :  APos     position to seek to before read
 *            ABuff    target buffer
 *            ACount   number of bytes to read
 *
 *  Output :  True on success. False if error.
 *
 *  Notes  :  prints an error message if seek failed.
 *
 ******************************************************************)

function TT_Read_At_File( APos : Long; var ABuff; ACount : Int ) : TError;
begin
  TT_Read_At_File := Failure;

  if TT_Seek_File( APos ) or
     TT_Read_File( ABuff, ACount ) then exit;

  TT_Read_At_File := Success;
end;

(*******************************************************************
 *
 *  Function    :  TT_Access_Frame
 *
 *  Description :  Notifies the component that we're going to read
 *                 aSize bytes from the current file position.
 *                 This function should load/cache/map these bytes
 *                 so that they will be addressed by the GET_xxx
 *                 functions easily.
 *
 *  Input  :  aSize   number of bytes to access.
 *
 *  Output :  True on success. False on failure
 *
 *            The function fails is the byte range is not within the
 *            the file, or if there is not enough memory to cache
 *            the bytes properly ( which usually means that aSize is
 *            too big in both cases ).
 *
 *            It will also fail if you make two consecutive calls
 *            to TT_Access_Frame, without a TT_Forget_Frame between
 *            them.
 *
 ******************************************************************)

 function TT_Access_Frame( aSize : Int ) : TError;
 var
   readBytes : Longint;
 begin
   TT_Access_Frame := Failure;

   if current_frame <> nil then
   begin
     error := TT_Err_Nested_Frame_Access;
     exit;
   end;
   (* We already are accessing one frame *)

   if aSize > frame_cache_size then
     GetMem( current_frame, aSize )
   else
     current_frame := frame_cache;

   if TT_Read_File( current_frame^, aSize ) then
   begin
     if aSize > frame_cache_size then
       FreeMem( current_frame, aSize );
     exit;
   end;

   frame_size   := aSize;
   frame_cursor := 0;

   TT_Access_Frame := Success;
 end;

(*******************************************************************
 *
 *  Function    :  TT_Check_And_Access_Frame
 *
 *  Description :  Notifies the component that we're going to read
 *                 aSize bytes from the current file position.
 *                 This function should load/cache/map these bytes
 *                 so that they will be addressed by the GET_xxx
 *                 functions easily.
 *
 *  Input  :  aSize   number of bytes to access.
 *
 *  Output :  True on success. False on failure
 *
 *            The function fails is the byte range is not within the
 *            the file, or if there is not enough memory to cache
 *            the bytes properly ( which usually means that aSize is
 *            too big in both cases ).
 *
 *            It will also fail if you make two consecutive calls
 *            to TT_Access_Frame, without a TT_Forget_Frame between
 *            them.
 *
 *
 * NOTE :  The only difference with TT_Access_Frame is that we check
 *         that the frame is within the current file.  We otherwise
 *         truncate it..
 *
 ******************************************************************)

 function TT_Check_And_Access_Frame( aSize : Int ) : TError;
 var
   readBytes : Longint;
 begin
   TT_Check_And_Access_Frame := Failure;

   if current_frame <> nil then
   begin
     error := TT_Err_Nested_Frame_Access;
     exit;
   end;
   (* We already are accessing one frame *)

   readBytes := TT_File_Size - TT_File_Pos;
   if aSize > readBytes then aSize := readBytes;

   if aSize > frame_cache_size then
     GetMem( current_frame, aSize )
   else
     current_frame := frame_cache;

   if TT_Read_File( current_frame^, aSize ) then
   begin
     if aSize > frame_cache_size then
       FreeMem( current_frame, aSize );
     exit;
   end;

   frame_size   := aSize;
   frame_cursor := 0;

   TT_Check_And_Access_Frame := Success;
 end;

(*******************************************************************
 *
 *  Function    :  TT_Forget_Frame
 *
 *  Description :  Releases a cached frame after reading
 *
 *  Input  :  None
 *
 *  Output :  True on success. False on failure
 *
 ******************************************************************)

 function TT_Forget_Frame : TError;
 begin
   TT_Forget_Frame := Failure;

   if current_frame = nil then exit;

   if frame_size > frame_cache_size then
     FreeMem( current_frame, frame_size );

   frame_size    := 0;
   current_frame := nil;
   frame_cursor  := 0;
 end;

(*******************************************************************
 *
 *  Function    :  GET_Byte
 *
 *  Description :  Extracts a byte from the current file frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted Byte.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_Byte : Byte;
 begin
   GET_Byte := current_frame^[frame_cursor];
   inc( frame_cursor );
 end;

(*******************************************************************
 *
 *  Function    :  GET_Char
 *
 *  Description :  Extracts a signed byte from the current file frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted char.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_Char : ShortInt;
 begin
   GET_Char := ShortInt( current_frame^[frame_cursor] );
   inc( frame_cursor );
 end;

(*******************************************************************
 *
 *  Function    :  GET_Short
 *
 *  Description :  Extracts a short from the current file frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted short.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_Short : Short;
 begin
   GET_Short := (Short(current_frame^[ frame_cursor ]) shl 8) or
                 Short(current_frame^[frame_cursor+1]);
   inc( frame_cursor, 2 );
 end;

(*******************************************************************
 *
 *  Function    :  GET_UShort
 *
 *  Description :  Extracts an unsigned  short from the frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted ushort.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_UShort : UShort;
 begin
   GET_UShort := (UShort(current_frame^[ frame_cursor ]) shl 8) or
                  UShort(current_frame^[frame_cursor+1]);
   inc( frame_cursor, 2 );
 end;

(*******************************************************************
 *
 *  Function    :  GET_Long
 *
 *  Description :  Extracts a long from the frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted long.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_Long : Long;
 begin
   GET_Long := (Long(current_frame^[ frame_cursor ]) shl 24) or
               (Long(current_frame^[frame_cursor+1]) shl 16) or
               (Long(current_frame^[frame_cursor+2]) shl 8 ) or
               (Long(current_frame^[frame_cursor+3])       );
   inc( frame_cursor, 4 );
 end;

(*******************************************************************
 *
 *  Function    :  GET_ULong
 *
 *  Description :  Extracts an unsigned long from the frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted ulong.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_ULong : ULong;
 begin
   GET_ULong := (ULong(current_frame^[ frame_cursor ]) shl 24) or
                (ULong(current_frame^[frame_cursor+1]) shl 16) or
                (ULong(current_frame^[frame_cursor+2]) shl 8 ) or
                (ULong(current_frame^[frame_cursor+3])       );
   inc( frame_cursor, 4 );
 end;

(*******************************************************************
 *
 *  Function    :  GET_Tag4
 *
 *  Description :  Extracts a Tag from the frame
 *
 *  Input  :  None
 *
 *  Output :  Extracted 4 byte Tag.
 *
 *  NOTES : We consider that the programmer is intelligent enough
 *          not to try to get a byte that is out of the frame. Hence,
 *          we provide no bounds check here. (A misbehaving client
 *          could easily page fault using this call).
 *
 ******************************************************************)

 function GET_Tag4 : ULong;
 var
   C : array[0..3] of Byte;
 begin
   move ( current_frame^[frame_cursor], c, 4 );
   inc( frame_cursor, 4 );

   GET_Tag4 := ULong(C);
end;

end.
