(*
From: IAN HUNTER
Subj: LZW Compression Unit
*)

Unit IHLZW;
  {- Unit to handle data compression }
Interface
Const
  StackOverFlow = 1;
  DeniedWrite = 2;
Type
  GetCharFunc = Function (Var Ch : Char) : Boolean;
  PutCharProc = Procedure (Ch : Char);
  LZW = Object
          GetChar : GetCharFunc;
          PutChar : PutCharProc;
          LastError : Word;
          Constructor Init;
          Function Get_Hash_Code (PrevC, FollC : Integer) : Integer;
          Procedure Make_Table_Entry (PrevC, FollC: Integer);
          Procedure Initialize_String_Table;
          Procedure Initialize;
          Function Lookup_String (PrevC, FollC : Integer) : Integer;
          Procedure Get_Char (Var C : Integer);
          Procedure Put_Char (C : Integer);
          Procedure Compress;
          Procedure Decompress;
          End;

Implementation
Const
  MaxTab   = 4095;
  No_Prev  = $7FFF;
  EOF_Char = -2;
  End_List = -1;
  Empty    = -3;

Type
  AnyStr = String;
  String_Table_Entry = Record
    Used : Boolean;
    PrevChar : Integer;
    FollChar : Integer;
    Next : Integer;
    End;

Var
  String_Table : Array [0..MaxTab] Of String_Table_Entry;
  Table_Used     : Integer;
  Output_Code    : Integer;
  Input_Code     : Integer;
  If_Compressing : Boolean;

Constructor LZW.Init;
Begin
  LastError := 0;
End;

Function LZW.Get_Hash_Code (PrevC, FollC : Integer) : Integer;
Var
  Index  : Integer;
  Index2 : Integer;
Begin
  Index := ((PrevC SHL 5) XOR FollC) AND MaxTab;
  If (Not String_Table [Index].Used)
    Then
      Get_Hash_Code := Index
    Else
      Begin
        While (String_Table[Index].Next <> End_List) Do
          Index := String_Table[Index].Next;
        Index2 := (Index + 101) And MaxTab;
        While (String_Table[Index2].Used) Do
          Index2 := Succ (Index2) AND MaxTab;
        String_Table[Index].Next := Index2;
        Get_Hash_Code := Index2;
      End;
End;

Procedure LZW.Make_Table_Entry (PrevC, FollC: Integer);
Begin
  If (Table_Used <= MaxTab )
    Then
      Begin
         With String_Table [Get_Hash_Code (PrevC , FollC)] Do
           Begin
             Used     := True;
             Next     := End_List;
             PrevChar := PrevC;
             FollChar := FollC;
           End;
         Inc (Table_Used);
(*
         IF ( Table_Used > ( MaxTab + 1 ) ) THEN
            BEGIN
               WRITELN('Hash table full.');
            END;
*)
      End;
End;

Procedure LZW.Initialize_String_Table;
Var
  I : Integer;
Begin
  Table_Used := 0;
  For I := 0 to MaxTab Do
    With String_Table[I] Do
      Begin
        PrevChar := No_Prev;
        FollChar := No_Prev;
        Next := -1;
        Used := False;
      End;
  For I := 0 to 255 Do
    Make_Table_Entry (No_Prev, I);
End;

Procedure LZW.Initialize;
Begin
  Output_Code := Empty;
  Input_Code := Empty;
  Initialize_String_Table;
End;

Function LZW.Lookup_String (PrevC, FollC: Integer) : Integer;
Var
  Index  : Integer;
  Index2 : Integer;
  Found  : Boolean;
Begin
  Index := ((PrevC Shl 5) Xor FollC) And MaxTab;
  Lookup_String := End_List;
  Repeat
    Found := (String_Table[Index].PrevChar = PrevC) And
             (String_Table[Index].FollChar = FollC);
    If (Not Found)
      Then
        Index := String_Table [Index].Next;
  Until Found Or (Index = End_List);
  If Found
    Then
      Lookup_String := Index;
End;

Procedure LZW.Get_Char (Var C : Integer);
Var
  Ch : Char;
Begin
  If Not GetChar (Ch)
    Then
      C := EOF_Char
    Else
      C := Ord (Ch);
End;

Procedure LZW.Put_Char (C : Integer);
Var
  Ch : Char;
Begin
  Ch := Chr (C);
  PutChar (Ch);
End;

Procedure LZW.Compress;
  Procedure Put_Code (Hash_Code : Integer);
  Begin
    If (Output_Code = Empty)
      Then
        Begin
          Put_Char ((Hash_Code Shr 4) And $FF);
          Output_Code := Hash_Code And $0F;
        End
      Else
        Begin
          Put_Char (((Output_Code Shl 4) And $FF0) +
                   ((Hash_Code Shr 8) And $00F));
          Put_Char (Hash_Code And $FF);
          Output_Code := Empty;
        End;
  End;


  Procedure Do_Compression;
  Var
    C : Integer;
    WC : Integer;
    W : Integer;
  Begin
    Get_Char (C);
    W := Lookup_String (No_Prev, C);
    Get_Char (C);
    While (C <> EOF_Char) Do
      Begin
        WC := Lookup_String (W, C);
        If (WC = End_List)
          Then
            Begin
              Make_Table_Entry (W, C );
              Put_Code (W);
              W := Lookup_String (No_Prev, C);
            End
          Else
            W := WC;
        Get_Char( C );
      End;
    Put_Code (W);
  End;

Begin
  If_Compressing := True;
  Initialize;
  Do_Compression;
End;

Procedure LZW.Decompress;
Const
  MaxStack = 4096;
Var
  Stack : Array [1..MaxStack] Of Integer;
  Stack_Pointer : Integer;

  Procedure Push (C : Integer);
  Begin
    Inc (Stack_Pointer);
    Stack [Stack_Pointer] := C;
    If (Stack_Pointer >= MaxStack)
      Then
        Begin
          LastError := 1;
          Exit;
        End;
  End;

  Procedure Pop (Var C : Integer);
  Begin;
    If (Stack_Pointer > 0)
      Then
        Begin
          C := Stack [Stack_Pointer];
          Dec (Stack_Pointer);
        End
      Else
        C := Empty;
  End;

  Procedure Get_Code (Var Hash_Code : Integer);
  Var
    Local_Buf : Integer;
  Begin
    If (Input_Code = Empty)
      Then
        Begin
          Get_Char (Local_Buf);
          If (Local_Buf = EOF_Char)
            Then
              Begin
                Hash_Code := EOF_Char;
                Exit;
              End;
          Get_Char (Input_Code);
          If (Input_Code = EOF_Char)
            Then
              Begin
                Hash_Code := EOF_Char;
                Exit;
              End;
          Hash_Code := ((Local_Buf Shl 4) And $FF0) +
                       ((Input_Code Shr 4) And $00F);
          Input_Code := Input_Code And $0F;
        End
      Else
        Begin
          Get_Char (Local_Buf);
          If (Local_Buf = EOF_Char)
            Then
              Begin
                Hash_Code := EOF_Char;
                Exit;
              End;
          Hash_Code := Local_Buf + ((Input_Code Shl 8) And $F00);
          Input_Code := Empty;
        End;
  End;

  Procedure Do_Decompression;
  Var
    C : Integer;
    Code : Integer;
    Old_Code : Integer;
    Fin_Char : Integer;
    In_Code : Integer;
    Last_Char : Integer;
    Unknown : Boolean;
    Temp_C : Integer;
  Begin
    Stack_Pointer := 0;
    Unknown := False;
    Get_Code (Old_Code);
    Code := Old_Code;
    C := String_Table[Code].FollChar;
    Put_Char (C);
    Fin_Char := C;
    Get_Code (In_Code);
    While (In_Code <> EOF_Char) Do
      Begin
        Code := In_Code;
        If (Not String_Table [Code].Used)
          Then
            Begin
              Last_Char := Fin_Char;
              Code := Old_Code;
              Unknown := TRUE;
            End;
        While (String_Table [Code].PrevChar <> No_Prev) Do
          With String_Table[Code] Do
            Begin
              Push (FollChar);
              If (LastError <> 0)
                Then
                  Exit;
              Code := PrevChar;
            End;
        Fin_Char := String_Table [Code].FollChar;
        Put_Char (Fin_Char);
        Pop (Temp_C);
        While (Temp_C <> Empty) Do
          Begin
            Put_Char (Temp_C);
            Pop (Temp_C);
          End;
        If Unknown
          Then
            Begin
              Fin_Char := Last_Char;
              Put_Char (Fin_Char);
              Unknown := FALSE;
            End;
        Make_Table_Entry (Old_Code, Fin_Char);
        Old_Code := In_Code;
        Get_Code( In_Code );
      End;
  End;

Begin
  If_Compressing := False;
  Initialize;
  Do_Decompression;
End;

End.

(* *****************************     TEST PROGRAM    ****************** *)

Program LZWTest;
{ program to demo/test the LZW object }
Uses
  IHLZW;  { Only needs this }
Var
  C : LZW; { The Star of the Show; the Compression Object }

{$F+} Function GetTheChar (Var Ch : Char) : Boolean; {$F-}
{ Make your GetChar routine's declaration look exactly like this }

Begin
  If Not Eof (Input) { End of Input? }
    Then
      Begin
        Read (Input, Ch); { Then read one character into Ch and ... }
        GetTheChar := True; { ... Return True }
      End
    Else
      GetTheChar := False; { Otherwise return False }
End;

{$F+} Procedure PutTheChar (Ch : Char); {$F-}
{ Make your PutChar routine's declaration look exactly like this }

Begin
  Write (Output, Ch); { Write Ch to Output file }
End;

Begin
  { Open data files }
  Assign (Input, ''); { Standard Input; requires redirection to be useful }
  Assign (Output, ''); { Standard Output; requires redirection to be useful }
  Reset (Input);
  Rewrite (Output);
  { Can't fail yet -- maybe a descendant could, though... }
  If not C.Init
    Then
      Halt;
  { Assign I/O routines }
  C.GetChar := GetTheChar; { Set LZW's GetChar to routine GetTheChar }
  C.PutChar := PutTheChar; { Set LZW's PutChar to routine PutTheChar }
  { are we compressing or decompressing? }
  If (ParamCount = 0)
    Then
      C.Compress { compress }
    Else
      C.Decompress; { decompress }
  { All Done! }
End.

