mirror of
https://github.com/vishapoberon/compiler.git
synced 2026-04-06 01:42:24 +00:00
1492 lines
62 KiB
Modula-2
1492 lines
62 KiB
Modula-2
(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich.
|
|
Refer to the "General ETH Oberon System Source License" contract available at: http://www.oberon.ethz.ch/ *)
|
|
|
|
MODULE ethZlibDeflate; (** Stefan Walthert **)
|
|
|
|
(**
|
|
Compression of byte streams with deflate algorithm
|
|
**)
|
|
|
|
(*
|
|
01.04.2001 - fixed bug in Deflate (condition before 4th RETURN statement:
|
|
.. & (flush # BufError) THEN .. instead of .. & (flush # Finish) THEN ..
|
|
*)
|
|
|
|
IMPORT
|
|
SYSTEM, Zlib := ethZlib, ZlibBuffers := ethZlibBuffers;
|
|
|
|
CONST
|
|
(** Result codes for compression/decompression functions **)
|
|
Ok* = Zlib.Ok; StreamEnd* = Zlib.StreamEnd; (** regular termination **)
|
|
StreamError* = Zlib.StreamError;DataError* = Zlib.DataError; MemError* = Zlib.MemError; BufError* = Zlib.BufError; (** errors **)
|
|
|
|
(** Flush values **)
|
|
NoFlush* = Zlib.NoFlush; PartialFlush = Zlib.PartialFlush; SyncFlush* = Zlib.SyncFlush; FullFlush* = Zlib.FullFlush; Finish* = Zlib.Finish;
|
|
|
|
(** compression levels **)
|
|
DefaultCompression* = Zlib.DefaultCompression; NoCompression* = Zlib.NoCompression;
|
|
BestSpeed* = Zlib.BestSpeed; BestCompression* = Zlib.BestCompression;
|
|
|
|
(** compression strategies **)
|
|
DefaultStrategy* = Zlib.DefaultStrategy; Filtered* = Zlib.Filtered; HuffmanOnly* = Zlib.HuffmanOnly;
|
|
|
|
(** data type **)
|
|
Binary* = Zlib.Binary; Ascii* = Zlib.Ascii; Unknown* = Zlib.Unknown;
|
|
|
|
(* stream states *)
|
|
InitState = 1; BusyState = 2; FinishState = 3;
|
|
|
|
(* block state *)
|
|
NeedMore = 1; BlockDone = 2; FinishStarted = 3; FinishDone = 4;
|
|
|
|
StoredBlock = 0; StaticTrees = 1; DynamicTrees = 2; (* block types *)
|
|
Deflated = 8; (* compression method (by coincidence the only one supported..) *)
|
|
PresetDict = 20H; (* flag indicating use of a preset dictionary *)
|
|
|
|
(* Huffman trees *)
|
|
LengthCodes = 29; Literals = 256; LitLenCodes = Literals + 1 + LengthCodes; DistCodes = 30; BitCodes = 19;
|
|
HeapSize = 2 * LitLenCodes + 1; MaxBits = 15; MaxBitLenBits = 7; DistCodeLen = 512; EndBlock = 256; BitBufSize = 16;
|
|
Rep3To6 = 16; RepZero3To10 = 17; RepZero11To138 = 18;
|
|
|
|
(* window and matches *)
|
|
WindowBits = 15; WindowSize = ASH(1, WindowBits); (* always use 32k buffer *)
|
|
MinMatch = 3; MaxMatch = 258;
|
|
MinLookAhead = MinMatch + MaxMatch + 1; MaxDist = WindowSize - MinLookAhead;
|
|
TooFar = 4096; (* matches of length MinMatch are discarded if their distance exceeds this *)
|
|
MemLevel = 8; (* constant memory level *)
|
|
HashBits = MemLevel + 7; HashSize = ASH(1, HashBits); (* implies constant number of hash bits *)
|
|
HashShift = (HashBits + (MinMatch - 1)) DIV MinMatch; (* MinMatch bytes should have effect on hash code *)
|
|
LitBufSize = ASH(1, MemLevel + 6); (* number of elements in literal/distance buffers *)
|
|
PendingBufSize = ASH(LitBufSize, 2); (* use 64k pending buffer *)
|
|
|
|
TYPE
|
|
(* Huffman trees *)
|
|
Node = RECORD
|
|
freqOrCode: INTEGER; (* frequency count / bit string *)
|
|
dadOrLen: INTEGER (* father node on Huffman tree / length of bit string *)
|
|
END;
|
|
Nodes = POINTER TO ARRAY OF Node;
|
|
|
|
Bits = POINTER TO ARRAY OF INTEGER;
|
|
|
|
StaticTree = RECORD
|
|
node: Nodes;
|
|
bits: Bits; (* extra bits for each code *)
|
|
base: INTEGER; (* base index for Bits *)
|
|
elems: INTEGER; (* max number of elements in the tree *)
|
|
maxLength: INTEGER (* max bit length for the codes *)
|
|
END;
|
|
|
|
Tree = RECORD
|
|
node: Nodes; (* dynamic tree *)
|
|
maxCode: INTEGER; (* largest code with non-zero frequency *)
|
|
static: StaticTree (* corresponding static tree *)
|
|
END;
|
|
|
|
Window = ARRAY 2 * WindowSize OF CHAR; (* double size to keep full dictionary at all times; input is read into upper half *)
|
|
|
|
PendingBuffer = RECORD
|
|
buf: POINTER TO ARRAY PendingBufSize OF CHAR; (* memory for pending buffer *)
|
|
beg: LONGINT; (* next pending byte to write to output buffer *)
|
|
end: LONGINT (* next pending byte in pending buffer *)
|
|
END;
|
|
|
|
(** deflate stream **)
|
|
Stream* = RECORD
|
|
in*, out*: ZlibBuffers.Buffer;
|
|
res-: LONGINT; (** result of last operation **)
|
|
level-: SHORTINT; (** compression level **)
|
|
strategy-: SHORTINT; (**compression strategy **)
|
|
dataType-: SHORTINT; (** Unknown, Binary or Ascii **)
|
|
wrapper-: BOOLEAN; (** if set, zlib header and checksum are generated **)
|
|
open-: BOOLEAN; (** if set, stream is initialized **)
|
|
trailerDone: BOOLEAN; (* if set, the zlib trailer has already been generated *)
|
|
lastFlush: SHORTINT; (* flush operation of the previous deflate call *)
|
|
status: SHORTINT; (* current stream state *)
|
|
adler: LONGINT; (* Adler32 checksum *)
|
|
|
|
window: POINTER TO Window; (* memory for sliding window *)
|
|
block: LONGINT; (* position in window where current block starts (negative if window moved) *)
|
|
hash: LONGINT; (* hash index of string to insert *)
|
|
prev: POINTER TO ARRAY WindowSize OF LONGINT; (* link to older string with same hash code (for last 32k strings) *)
|
|
head: POINTER TO ARRAY HashSize OF LONGINT; (* heads of hash chains for every window position *)
|
|
string: LONGINT; (* start of string to insert *)
|
|
lookAhead: LONGINT; (* number of valid bytes ahead in window *)
|
|
match: LONGINT; (* start of match string *)
|
|
matchLen: LONGINT; (* length of best match *)
|
|
prevMatch: LONGINT; (* start of previous match *)
|
|
prevLen: LONGINT; (* length of best match at previous step *)
|
|
prevAvail: BOOLEAN; (* set if previous match exists *)
|
|
pend: PendingBuffer;
|
|
|
|
(* trees *)
|
|
ltree, dtree, btree: Tree; (* trees for literals/lengths, distances and bit lengths *)
|
|
lnode, dnode, bnode: Nodes; (* corresponding nodes *)
|
|
bitLenCount: ARRAY MaxBits + 1 OF INTEGER; (* number of codes at each bit length for optimal tree *)
|
|
heap: ARRAY HeapSize OF INTEGER; (* heap used to build Huffman tree *)
|
|
heapLen: INTEGER; (* number of elements in the heap *)
|
|
heapMax: INTEGER; (* heap element of largest frequency *)
|
|
depth: ARRAY HeapSize OF INTEGER; (* depth of each subtree for deciding between trees of equal frequency *)
|
|
lbuf: POINTER TO ARRAY LitBufSize OF CHAR; (* buffer for literals/lengths *)
|
|
dbuf: POINTER TO ARRAY LitBufSize OF INTEGER; (* buffer for distances *)
|
|
lastLit: LONGINT; (* running index in lbuf *)
|
|
buf: LONGINT; (* bit buffer *)
|
|
bits: INTEGER; (* number of valid bits in bit buffer *)
|
|
lastEobLen: INTEGER; (* bit length of End Of Block code for last block *)
|
|
optLen: LONGINT; (* bit length of current block with optimal trees *)
|
|
staticLen: LONGINT; (* bit length of current block with static trees *)
|
|
END;
|
|
|
|
Compressor = PROCEDURE (VAR s: Stream; flush: SHORTINT): SHORTINT;
|
|
|
|
VAR
|
|
ExtraLenBits, ExtraDistBits, ExtraBitBits: Bits;
|
|
LTree, DTree, BTree: StaticTree;
|
|
BaseLength: ARRAY LengthCodes OF INTEGER;
|
|
BaseDist: ARRAY DistCodes OF INTEGER;
|
|
LengthCode: ARRAY MaxMatch - MinMatch + 1 OF CHAR;
|
|
DistCode: ARRAY DistCodeLen OF CHAR;
|
|
BitOrder: ARRAY BitCodes OF SHORTINT;
|
|
ConfigTable: ARRAY 10 OF RECORD
|
|
GoodLen: INTEGER; (* reduce lazy search above this match length *)
|
|
MaxLazy: INTEGER; (* do not perform lazy search above this match length *)
|
|
NiceLen: INTEGER; (* quit search above this match length *)
|
|
MaxChain: INTEGER; (* maximal number of hash entries considered *)
|
|
Compress: Compressor; (* block compress procedure *)
|
|
END;
|
|
|
|
|
|
(* Put a byte c in the pending buffer *)
|
|
PROCEDURE PutChar(VAR pend: PendingBuffer; c: CHAR);
|
|
BEGIN
|
|
pend.buf[pend.end] := c;
|
|
INC(pend.end)
|
|
END PutChar;
|
|
|
|
(* Put the 16 LSB of b in LSB order in the pending buffer *)
|
|
PROCEDURE Put16BitsLSB(VAR pend: PendingBuffer; b: LONGINT);
|
|
BEGIN
|
|
PutChar(pend, CHR(b MOD 100H));
|
|
PutChar(pend, CHR((b DIV 100H) MOD 100H))
|
|
END Put16BitsLSB;
|
|
|
|
(* Put the 16 LSB of b in MSB order in the pending buffer *)
|
|
PROCEDURE Put16BitsMSB(VAR pend: PendingBuffer; b: LONGINT);
|
|
BEGIN
|
|
PutChar(pend, CHR((b DIV 100H) MOD 100H));
|
|
PutChar(pend, CHR(b MOD 100H))
|
|
END Put16BitsMSB;
|
|
|
|
(* Put the 32 LSB of b in MSB order in the pending buffer *)
|
|
PROCEDURE Put32BitsMSB(VAR pend: PendingBuffer; b: LONGINT);
|
|
BEGIN
|
|
Put16BitsMSB(pend, (b DIV 10000H) MOD 10000H);
|
|
Put16BitsMSB(pend, b MOD 10000H)
|
|
END Put32BitsMSB;
|
|
|
|
(* Reverse the first len bits of a code, using straightforward code *)
|
|
PROCEDURE ReverseBits(code, len: INTEGER): INTEGER;
|
|
VAR
|
|
res: INTEGER;
|
|
BEGIN
|
|
res := 0;
|
|
REPEAT
|
|
res := res * 2; INC(res, code MOD 2);
|
|
code := code DIV 2; DEC(len)
|
|
UNTIL len = 0;
|
|
RETURN res
|
|
END ReverseBits;
|
|
|
|
(* Send a value on a given number of bits *)
|
|
PROCEDURE SendBits(VAR stream: Stream; val: LONGINT; len: INTEGER);
|
|
BEGIN
|
|
INC(stream.buf, ASH(val, stream.bits)); INC(stream.bits, len);
|
|
IF stream.bits > BitBufSize THEN
|
|
Put16BitsLSB(stream.pend, stream.buf);
|
|
stream.buf := SYSTEM.LSH(stream.buf, -BitBufSize); DEC(stream.bits, BitBufSize)
|
|
END
|
|
END SendBits;
|
|
|
|
(* Send a code of the given node. c and node must not have side effects *)
|
|
PROCEDURE SendCode(VAR stream: Stream; VAR node: Node);
|
|
BEGIN
|
|
SendBits(stream, node.freqOrCode, node.dadOrLen)
|
|
END SendCode;
|
|
|
|
(* Flush the bit buffer, keeping at most 7 bits in it *)
|
|
PROCEDURE FlushBits(VAR stream: Stream);
|
|
BEGIN
|
|
IF stream.bits = BitBufSize THEN
|
|
Put16BitsLSB(stream.pend, stream.buf);
|
|
stream.buf := 0; stream.bits := 0
|
|
ELSIF stream.bits >= 8 THEN
|
|
PutChar(stream.pend, CHR(stream.buf));
|
|
stream.buf := SYSTEM.LSH(stream.buf, -8); DEC(stream.bits, 8)
|
|
END
|
|
END FlushBits;
|
|
|
|
(* Flush as much pending output as possible. *)
|
|
PROCEDURE FlushPending(VAR pend: PendingBuffer; VAR out: ZlibBuffers.Buffer);
|
|
VAR
|
|
len: LONGINT;
|
|
BEGIN
|
|
len := pend.end - pend.beg;
|
|
IF len > out.avail THEN len := out.avail END;
|
|
IF len > 0 THEN
|
|
ZlibBuffers.WriteBytes(out, pend.buf^, pend.beg, len);
|
|
INC(pend.beg, len);
|
|
IF pend.beg = pend.end THEN
|
|
pend.beg := 0; pend.end := 0
|
|
END
|
|
END
|
|
END FlushPending;
|
|
|
|
(* Flush the bit buffer and align the output on a byte boundary *)
|
|
PROCEDURE WindupBits(VAR stream: Stream);
|
|
BEGIN
|
|
IF stream.bits > 8 THEN
|
|
Put16BitsLSB(stream.pend, stream.buf)
|
|
ELSIF stream.bits > 0 THEN
|
|
PutChar(stream.pend, CHR(stream.buf))
|
|
END;
|
|
stream.buf := 0; stream.bits := 0
|
|
END WindupBits;
|
|
|
|
(* Set data type to ASCII or Binary, using a crude heuristic: Binary if more than 20% of the bytes are <= 6 or >= 128, ASCII otherwise *)
|
|
PROCEDURE SetDataType(VAR stream: Stream);
|
|
VAR
|
|
n, ascii, bin: LONGINT;
|
|
BEGIN
|
|
WHILE n < 7 DO INC(bin, LONG(stream.lnode[n].freqOrCode)); INC(n) END;
|
|
WHILE n < 128 DO INC(ascii, LONG(stream.lnode[n].freqOrCode)); INC(n) END;
|
|
WHILE n < Literals DO INC(bin, LONG(stream.lnode[n].freqOrCode)); INC(n) END;
|
|
IF (4 * bin) > ascii THEN stream.dataType := Binary ELSE stream.dataType := Ascii END
|
|
END SetDataType;
|
|
|
|
(* Generate the codes for a given tree and bit counts (which need not to be optimal) *)
|
|
PROCEDURE GenCodes(VAR node: Nodes; maxCode: INTEGER; VAR count: ARRAY OF INTEGER);
|
|
VAR
|
|
nextCode: ARRAY MaxBits + 1 OF INTEGER; (* next code value for each bit length *)
|
|
code, bits, n, len : INTEGER;
|
|
BEGIN
|
|
code := 0;
|
|
FOR bits := 1 TO MaxBits DO
|
|
code := SHORT(ASH(code + count[bits - 1], 1));
|
|
nextCode[bits] := code
|
|
END;
|
|
ASSERT(code + count[MaxBits] - 1 = ASH(1, MaxBits) - 1, 110); (* inconsistent bit counts *)
|
|
FOR n := 0 TO maxCode DO
|
|
len := node[n].dadOrLen;
|
|
IF len # 0 THEN
|
|
node[n].freqOrCode := ReverseBits(nextCode[len], len); INC(nextCode[len])
|
|
END
|
|
END
|
|
END GenCodes;
|
|
|
|
(* Compute optimal bit lengths for a tree and update the total bit length for the current block *)
|
|
PROCEDURE GenBitLen(VAR stream: Stream; VAR tree: Tree);
|
|
VAR
|
|
node: Nodes;
|
|
stree: StaticTree;
|
|
bits, h, n, m, overflow, xbits : INTEGER;
|
|
freq: LONGINT;
|
|
BEGIN
|
|
node := tree.node;
|
|
stree := tree.static;
|
|
overflow := 0;
|
|
FOR bits := 0 TO MaxBits DO stream.bitLenCount[bits] := 0 END;
|
|
(* compute optimal bit lengths *)
|
|
node[stream.heap[stream.heapMax]].dadOrLen := 0; (* root of heap *)
|
|
FOR h := stream.heapMax + 1 TO HeapSize - 1 DO
|
|
n := stream.heap[h];
|
|
bits := node[node[n].dadOrLen].dadOrLen + 1;
|
|
IF bits > stree.maxLength THEN
|
|
bits := stree.maxLength; INC(overflow)
|
|
END;
|
|
node[n].dadOrLen := bits; (* replace dad with len information *)
|
|
IF n <= tree.maxCode THEN (* leaf node *)
|
|
INC(stream.bitLenCount[bits]);
|
|
IF n >= stree.base THEN xbits := stree.bits[n - stree.base] ELSE xbits := 0 END;
|
|
freq := node[n].freqOrCode;
|
|
INC(stream.optLen, freq * (bits + xbits));
|
|
IF stree.node # NIL THEN INC(stream.staticLen, freq * (stree.node[n].dadOrLen + xbits)) END
|
|
END
|
|
END;
|
|
|
|
IF overflow # 0 THEN
|
|
(* find first bit length which could increase *)
|
|
REPEAT
|
|
bits := stree.maxLength - 1;
|
|
WHILE stream.bitLenCount[bits] = 0 DO DEC(bits) END;
|
|
DEC(stream.bitLenCount[bits]); (* move one leaf down the tree *)
|
|
INC(stream.bitLenCount[bits + 1], 2); (* move one overflow item as its brother *)
|
|
DEC(stream.bitLenCount[stree.maxLength]); DEC(overflow, 2)
|
|
UNTIL overflow <= 0;
|
|
|
|
(* recompute all bit lengths, scanning in increasing frequency *)
|
|
bits := stree.maxLength;
|
|
WHILE bits > 0 DO
|
|
n := stream.bitLenCount[bits];
|
|
WHILE n # 0 DO
|
|
DEC(h); m := stream.heap[h];
|
|
IF m <= tree.maxCode THEN
|
|
IF node[m].dadOrLen # bits THEN
|
|
INC(stream.optLen, (bits - node[m].dadOrLen) * LONG(node[m].freqOrCode));
|
|
node[m].dadOrLen := bits
|
|
END;
|
|
DEC(n)
|
|
END
|
|
END;
|
|
DEC(bits)
|
|
END
|
|
END
|
|
END GenBitLen;
|
|
|
|
(* Restore heap property by moving down the tree starting at node k, exchanging a node with smallest child if necessary,
|
|
stopping when heap property is re-established (each father smaller than its two children *)
|
|
PROCEDURE Sift(VAR stream: Stream; VAR node: Nodes; k: INTEGER);
|
|
VAR
|
|
v, i: INTEGER;
|
|
|
|
(* Compare subtrees, using tree depth as tie breaker when subtrees have equal frequency -> minimizes worst case length *)
|
|
PROCEDURE Smaller(n, m: INTEGER): BOOLEAN;
|
|
BEGIN
|
|
RETURN (node[n].freqOrCode < node[m].freqOrCode) OR
|
|
((node[n].freqOrCode = node[m].freqOrCode) & (stream.depth[n] <= stream.depth[m]))
|
|
END Smaller;
|
|
|
|
BEGIN
|
|
v := stream.heap[k];
|
|
i := k * 2; (* left child of k *)
|
|
WHILE (i <= stream.heapLen) DO
|
|
IF (i < stream.heapLen) & Smaller(stream.heap[i + 1], stream.heap[i]) THEN INC(i) END; (* i: smallest child *)
|
|
IF Smaller(v, stream.heap[i]) THEN
|
|
stream.heap[k] := v; RETURN
|
|
ELSE
|
|
stream.heap[k] := stream.heap[i]; k := i; (* exchange v with smallest child *)
|
|
i := i * 2 (* set j to the left child of k *)
|
|
END
|
|
END;
|
|
stream.heap[k] := v
|
|
END Sift;
|
|
|
|
(* Construct one Huffman tree and assign the code bit strings and lengths. Update the total bit length for the current block.
|
|
IN assertion: field freqOrCode is set for all tree elements
|
|
OUT assertions: the fields dadOrLen and freqOrCode are set to the optimal bit length and corresponding code.
|
|
The stream.optLen is updated; stream.staticLen is also updated if snode is not null. The field maxCode is set. *)
|
|
PROCEDURE BuildTree(VAR stream: Stream; VAR tree: Tree);
|
|
VAR
|
|
node: Nodes;
|
|
stree: StaticTree;
|
|
n, m, maxCode, next: INTEGER;
|
|
BEGIN
|
|
node := tree.node; stree := tree.static; maxCode := -1;
|
|
|
|
(* construct initial heap *)
|
|
stream.heapLen := 0; stream.heapMax := HeapSize;
|
|
FOR n := 0 TO stree.elems - 1 DO
|
|
IF node[n].freqOrCode # 0 THEN
|
|
maxCode := n;
|
|
INC(stream.heapLen); stream.heap[stream.heapLen] := n;
|
|
stream.depth[n] := 0
|
|
ELSE
|
|
node[n].dadOrLen := 0
|
|
END
|
|
END;
|
|
|
|
(* force at least two codes of non zero frequency in order to be compliant with pkzip format *)
|
|
WHILE stream.heapLen < 2 DO
|
|
INC(stream.heapLen);
|
|
IF maxCode < 2 THEN INC(maxCode); n := maxCode ELSE n := 0 END;
|
|
stream.heap[stream.heapLen] := n;
|
|
node[n].freqOrCode := 1;
|
|
stream.depth[n] := 0;
|
|
DEC(stream.optLen);
|
|
IF stree.node # NIL THEN DEC(stream.staticLen, LONG(stree.node[n].dadOrLen)) END; (* n IN {0, 1}, thus no extra bits *)
|
|
END;
|
|
tree.maxCode := maxCode;
|
|
|
|
(* build heap *)
|
|
FOR n := stream.heapLen DIV 2 TO 1 BY -1 DO
|
|
Sift(stream, node, n)
|
|
END;
|
|
|
|
(* construct Huffman tree by repeatedly combining the least two frequent nodes *)
|
|
next := stree.elems;
|
|
REPEAT
|
|
n := stream.heap[1];
|
|
stream.heap[1] := stream.heap[stream.heapLen];
|
|
DEC(stream.heapLen);
|
|
Sift(stream, node, 1);
|
|
m := stream.heap[1]; (* n: node of least frequency; m: node of next least frequency *)
|
|
DEC(stream.heapMax); stream.heap[stream.heapMax] := n; (* keep the nodes sorted by frequency *)
|
|
DEC(stream.heapMax); stream.heap[stream.heapMax] := m;
|
|
node[next].freqOrCode := node[n].freqOrCode + node[m].freqOrCode; (* create a new father of n and m *)
|
|
IF stream.depth[n] > stream.depth[m] THEN stream.depth[next] := stream.depth[n] + 1
|
|
ELSE stream.depth[next] := stream.depth[m] + 1
|
|
END;
|
|
node[n].dadOrLen := next; node[m].dadOrLen := next;
|
|
(* and insert the new node in the heap *)
|
|
stream.heap[1] := next; INC(next);
|
|
Sift(stream, node, 1);
|
|
UNTIL stream.heapLen < 2;
|
|
DEC(stream.heapMax); stream.heap[stream.heapMax] := stream.heap[1];
|
|
(* field freqOrCode and dadOrLen are set -> generate bit lengths *)
|
|
GenBitLen(stream, tree);
|
|
(* field dadOrLen is set -> generate bit codes *)
|
|
GenCodes(node, maxCode, stream.bitLenCount)
|
|
END BuildTree;
|
|
|
|
(* Scan a literal or distance tree to determine the frequencies of the codes in the bit length tree. *)
|
|
PROCEDURE ScanTree(VAR stream: Stream; node: Nodes; max: INTEGER);
|
|
VAR
|
|
n, prevLen, curLen, nextLen, count, maxCount, minCount: INTEGER;
|
|
BEGIN
|
|
prevLen := -1; nextLen := node[0].dadOrLen; count := 0;
|
|
IF nextLen = 0 THEN maxCount := 138; minCount := 3
|
|
ELSE maxCount := 7; minCount := 4
|
|
END;
|
|
node[max + 1].dadOrLen := MAX(INTEGER); (* sentinel *)
|
|
FOR n := 0 TO max DO
|
|
curLen := nextLen; nextLen := node[n + 1].dadOrLen;
|
|
INC(count);
|
|
IF (count >= maxCount) OR (curLen # nextLen) THEN
|
|
IF count < minCount THEN
|
|
INC(stream.bnode[curLen].freqOrCode, count);
|
|
ELSIF curLen # 0 THEN
|
|
IF curLen # prevLen THEN INC(stream.bnode[curLen].freqOrCode) END;
|
|
INC(stream.bnode[Rep3To6].freqOrCode)
|
|
ELSIF count <= 10 THEN
|
|
INC(stream.bnode[RepZero3To10].freqOrCode)
|
|
ELSE
|
|
INC(stream.bnode[RepZero11To138].freqOrCode)
|
|
END;
|
|
count := 0; prevLen := curLen;
|
|
IF nextLen = 0 THEN maxCount := 138; minCount := 3
|
|
ELSIF curLen = nextLen THEN maxCount := 6; minCount := 3
|
|
ELSE maxCount := 7; minCount := 4
|
|
END
|
|
END
|
|
END
|
|
END ScanTree;
|
|
|
|
(* Construct the Huffman tree for the bit lengths and return the index in BitOrder of the last bit length code to send. *)
|
|
PROCEDURE BuildBitLenTree(VAR stream: Stream): INTEGER;
|
|
VAR
|
|
max: INTEGER; (* index of last bit length code of non zero frequency *)
|
|
BEGIN
|
|
(* determine the bit length frequencies for literal and distance trees *)
|
|
ScanTree(stream, stream.ltree.node, stream.ltree.maxCode);
|
|
ScanTree(stream, stream.dtree.node, stream.dtree.maxCode);
|
|
BuildTree(stream, stream.btree); (* build bit length tree *)
|
|
(* stream.optLen now includes the length of the tree representations, except the lengths of the bit lengths codes
|
|
and the 5 + 5 + 4 bits for the count *)
|
|
(* determine the number of bit length codes to send; the pkzip format requires that at least 4 bit length codes to be sent *)
|
|
max := BitCodes - 1;
|
|
WHILE (max >= 3) & (stream.bnode[BitOrder[max]].dadOrLen = 0) DO DEC(max) END;
|
|
(* update stream.optLen to include the bit length tree and counts *)
|
|
INC(stream.optLen, LONG(3 * (max + 1) + 5 + 5 + 4));
|
|
RETURN max
|
|
END BuildBitLenTree;
|
|
|
|
(* Send a literal or distance tree in compressed form, using the codes in stream.bnode.
|
|
tree: the tree to be scanned; max: its largest code of non zero frequency *)
|
|
PROCEDURE SendTree(VAR stream: Stream; node: Nodes; max: INTEGER);
|
|
VAR
|
|
n, prevLen, curLen, nextLen, count, maxCount, minCount: INTEGER;
|
|
BEGIN
|
|
prevLen := -1; nextLen := node[0].dadOrLen; count := 0;
|
|
IF nextLen = 0 THEN maxCount := 138; minCount := 3
|
|
ELSE maxCount := 7; minCount := 4 END;
|
|
node[max + 1].dadOrLen := MAX(INTEGER); (* sentinel *)
|
|
FOR n := 0 TO max DO
|
|
curLen := nextLen; nextLen := node[n + 1].dadOrLen;
|
|
INC(count);
|
|
IF (count >= maxCount) OR (curLen # nextLen) THEN
|
|
IF count < minCount THEN
|
|
REPEAT
|
|
SendCode(stream, stream.bnode[curLen]);
|
|
DEC(count)
|
|
UNTIL count = 0
|
|
ELSIF curLen # 0 THEN
|
|
IF curLen # prevLen THEN
|
|
SendCode(stream, stream.bnode[curLen]); DEC(count)
|
|
END;
|
|
ASSERT((3 <= count) & (count <= 6), 110);
|
|
SendCode(stream, stream.bnode[Rep3To6]); SendBits(stream, count - 3, 2)
|
|
ELSIF count <= 10 THEN
|
|
SendCode(stream, stream.bnode[RepZero3To10]); SendBits(stream, count - 3, 3)
|
|
ELSE
|
|
SendCode(stream, stream.bnode[RepZero11To138]); SendBits(stream, count - 11, 7)
|
|
END;
|
|
count := 0; prevLen := curLen;
|
|
IF nextLen = 0 THEN maxCount := 138; minCount := 3
|
|
ELSIF curLen = nextLen THEN maxCount := 6; minCount := 3
|
|
ELSE maxCount := 7; minCount := 4
|
|
END
|
|
END
|
|
END
|
|
END SendTree;
|
|
|
|
(* Send the header for a block using dynamic Huffman trees: the counts, the lengths of the bit length codes, the literal tree
|
|
and the distance tree.
|
|
lcodes, dcodes, blcodes: number of codes for each tree *)
|
|
PROCEDURE SendAllTrees(VAR stream: Stream; lcodes, dcodes, blcodes: INTEGER);
|
|
VAR
|
|
rank: INTEGER; (* index in BitOrder *)
|
|
BEGIN
|
|
ASSERT((lcodes >= 257) & (dcodes >= 1) & (blcodes >= 4), 100); (* not enough codes *)
|
|
ASSERT((lcodes <= LitLenCodes) & (dcodes <= DistCodes) & (blcodes <= BitCodes), 101); (* too many codes *)
|
|
SendBits(stream, lcodes - 257, 5); SendBits(stream, dcodes - 1, 5); SendBits(stream, blcodes - 4, 4);
|
|
FOR rank := 0 TO blcodes - 1 DO
|
|
SendBits(stream, stream.bnode[BitOrder[rank]].dadOrLen, 3)
|
|
END;
|
|
SendTree(stream, stream.lnode, lcodes - 1); (* literal tree *)
|
|
SendTree(stream, stream.dnode, dcodes - 1) (* distance tree *)
|
|
END SendAllTrees;
|
|
|
|
(* Initialize the various constant tables *)
|
|
PROCEDURE InitStaticTrees();
|
|
VAR
|
|
n, code: LONGINT;
|
|
length, dist: INTEGER;
|
|
count: ARRAY MaxBits + 1 OF INTEGER; (* number of codes at each bit length for an optimal tree *)
|
|
BEGIN
|
|
NEW(ExtraLenBits, LengthCodes);
|
|
FOR n := 0 TO 3 DO ExtraLenBits[n] := 0 END;
|
|
FOR n := 4 TO LengthCodes - 2 DO ExtraLenBits[n] := SHORT((n - 4) DIV 4) END;
|
|
ExtraLenBits[LengthCodes - 1] := 0;
|
|
|
|
NEW(ExtraDistBits, DistCodes);
|
|
FOR n := 0 TO 1 DO ExtraDistBits[n] := 0 END;
|
|
FOR n := 2 TO DistCodes - 1 DO ExtraDistBits[n] := SHORT((n - 2) DIV 2) END;
|
|
|
|
NEW(ExtraBitBits, BitCodes);
|
|
FOR n := 0 TO BitCodes - 4 DO ExtraBitBits[n] := 0 END;
|
|
ExtraBitBits[BitCodes - 3] := 2; ExtraBitBits[BitCodes - 2] := 3; ExtraBitBits[BitCodes - 1] := 7;
|
|
|
|
BitOrder[0] := 16; BitOrder[1] := 17; BitOrder[2] := 18; BitOrder[3] := 0; BitOrder[4] := 8; BitOrder[5] := 7; BitOrder[6] := 9;
|
|
BitOrder[7] := 6; BitOrder[8] := 10; BitOrder[9] := 5; BitOrder[10] := 11; BitOrder[11] := 4; BitOrder[12] := 12; BitOrder[13] := 3;
|
|
BitOrder[14] := 13; BitOrder[15] := 2; BitOrder[16] := 14; BitOrder[17] := 1; BitOrder[18] := 15;
|
|
|
|
(* initialize the mapping length (0..255) -> length code (0..28) *)
|
|
length := 0;
|
|
FOR code := 0 TO LengthCodes - 2 DO
|
|
BaseLength[code] := length;
|
|
FOR n := 0 TO ASH(1, ExtraLenBits[code]) - 1 DO
|
|
LengthCode[length] := CHR(code); INC(length)
|
|
END
|
|
END;
|
|
ASSERT(length = 256, 110);
|
|
(* Note that length code 255 (match length 258) can be represented in two different ways: code 284 + 5 bits or code 285,
|
|
so we overwrite LengthCode[255] to use the best encoding: *)
|
|
LengthCode[length - 1] := CHR(code);
|
|
|
|
(* initialize the mapping dist (0..32K) -> dist code (0..29) *)
|
|
dist := 0;
|
|
FOR code := 0 TO 15 DO
|
|
BaseDist[code] := dist;
|
|
FOR n := 0 TO ASH(1, ExtraDistBits[code]) - 1 DO
|
|
DistCode[dist] := CHR(code); INC(dist)
|
|
END
|
|
END;
|
|
ASSERT(dist = 256, 111);
|
|
|
|
dist := SHORT(ASH(dist, -7)); (* from now on, all distances are divided by 128 *)
|
|
FOR code := 16 TO DistCodes - 1 DO
|
|
BaseDist[code] := SHORT(ASH(dist, 7));
|
|
FOR n := 0 TO ASH(1, ExtraDistBits[code] - 7) - 1 DO
|
|
DistCode[256 + dist] := CHR(code); INC(dist)
|
|
END
|
|
END;
|
|
ASSERT(dist = 256, 112);
|
|
|
|
(* construct the codes of the static literal tree *)
|
|
NEW(LTree.node, LitLenCodes + 2);
|
|
LTree.bits := ExtraLenBits; LTree.base := Literals + 1; LTree.elems := LitLenCodes; LTree.maxLength := MaxBits;
|
|
FOR n := 0 TO MaxBits DO count[n] := 0 END;
|
|
FOR n := 0 TO 143 DO LTree.node[n].dadOrLen := 8 END; INC(count[8], 143 - (-1));
|
|
FOR n := 144 TO 255 DO LTree.node[n].dadOrLen := 9 END; INC(count[9], 255 - 143);
|
|
FOR n := 256 TO 279 DO LTree.node[n].dadOrLen := 7 END; INC(count[7], 279 - 255);
|
|
FOR n := 280 TO 287 DO LTree.node[n].dadOrLen := 8 END; INC(count[8], 287 - 279);
|
|
(* codes 286 and 287 do not exist, but we must include them in the tree construction to get a canonical Huffman tree
|
|
(longest code all ones) *)
|
|
GenCodes(LTree.node, LitLenCodes + 1, count);
|
|
|
|
(* construct the codes of the static distance tree (trivial) *)
|
|
NEW(DTree.node, DistCodes);
|
|
DTree.bits := ExtraDistBits; DTree.base := 0; DTree.elems := DistCodes; DTree.maxLength := MaxBits;
|
|
FOR n := 0 TO DistCodes - 1 DO
|
|
DTree.node[n].dadOrLen := 5;
|
|
DTree.node[n].freqOrCode := ReverseBits(SHORT(n), 5)
|
|
END;
|
|
|
|
BTree.node := NIL;
|
|
BTree.bits := ExtraBitBits; BTree.base := 0; BTree.elems := BitCodes; BTree.maxLength := MaxBitLenBits;
|
|
END InitStaticTrees;
|
|
|
|
(* Initialize a new block *)
|
|
PROCEDURE InitBlock(VAR stream: Stream);
|
|
VAR
|
|
n: LONGINT; (* iterates over tree elements *)
|
|
BEGIN
|
|
FOR n := 0 TO LitLenCodes - 1 DO stream.lnode[n].freqOrCode := 0 END;
|
|
FOR n := 0 TO DistCodes - 1 DO stream.dnode[n].freqOrCode := 0 END;
|
|
FOR n := 0 TO BitCodes - 1 DO stream.bnode[n].freqOrCode := 0 END;
|
|
stream.lnode[EndBlock].freqOrCode := 1;
|
|
stream.optLen := 0; stream.staticLen := 0;
|
|
stream.lastLit := 0
|
|
END InitBlock;
|
|
|
|
(* Initialize the tree data structures for a new zlib stream *)
|
|
PROCEDURE InitTrees(VAR stream: Stream);
|
|
BEGIN
|
|
NEW(stream.lnode, HeapSize); NEW(stream.dnode, 2 * DistCodes + 1); NEW(stream.bnode, 2 * BitCodes + 1);
|
|
stream.ltree.node := stream.lnode; stream.dtree.node := stream.dnode; stream.btree.node := stream.bnode;
|
|
stream.ltree.static := LTree; stream.dtree.static := DTree; stream.btree.static := BTree;
|
|
stream.buf := 0; stream.bits := 0; stream.lastEobLen := 8; (* enough lookahead for inflate *)
|
|
InitBlock(stream)
|
|
END InitTrees;
|
|
|
|
PROCEDURE FreeTrees(VAR stream: Stream);
|
|
BEGIN
|
|
stream.lnode := NIL; stream.dnode := NIL; stream.bnode := NIL
|
|
END FreeTrees;
|
|
|
|
(* Send one empty static block to give enough lookahead for inflate. This takes 10 bits, of which 7 may remain in the bit buffer.
|
|
The current inflate code requires 9 bits of lookahead. If the last two codes for the previous block (real code plus end of block)
|
|
were coded on 5 bits or less, inflate may have only 5 + 3 bits of lookahead to decode the las real code.
|
|
In this case we send two empty static blocks instead of one. (There are no problems if the previous block is stored or fixed.)
|
|
To simplify the code, we assume the worst case of last real code encoded on one bit only *)
|
|
PROCEDURE AlignTrees(VAR stream: Stream);
|
|
BEGIN
|
|
SendBits(stream, SHORT(ASH(StaticTrees, 1)), 3);
|
|
SendCode(stream, LTree.node[EndBlock]);
|
|
FlushBits(stream);
|
|
(* Of the 10 bits for the empty block, we have already sent (10 - stream.bits) bits. The lookahead for the last real code
|
|
(before end of block of the previous block) was thus at least one plus the length of the end of block what we have
|
|
just sent of the empty static block. *)
|
|
IF (1 + stream.lastEobLen + 10 - stream.bits) < 9 THEN
|
|
SendBits(stream, SHORT(ASH(StaticTrees, 1)), 3);
|
|
SendCode(stream, LTree.node[EndBlock]);
|
|
FlushBits(stream)
|
|
END;
|
|
stream.lastEobLen := 7
|
|
END AlignTrees;
|
|
|
|
(* Copy a stored block, storing first the length and its one's complement if requested *)
|
|
PROCEDURE CopyBlock(VAR stream: Stream; VAR buf: ARRAY OF CHAR; offset, len: LONGINT; header: BOOLEAN);
|
|
VAR
|
|
BEGIN
|
|
WindupBits(stream); (* align on byte boundary *)
|
|
stream.lastEobLen := 8; (* enough lookahead for inflate *)
|
|
IF header THEN
|
|
Put16BitsLSB(stream.pend, len); (* LEN *)
|
|
Put16BitsLSB(stream.pend, -(len + 1)); (* NLEN (1's complement of LEN) *)
|
|
END;
|
|
WHILE len > 0 DO
|
|
PutChar(stream.pend, buf[offset]);
|
|
INC(offset); DEC(len)
|
|
END
|
|
END CopyBlock;
|
|
|
|
(* Send a stored block *)
|
|
PROCEDURE StoreBlock(VAR stream: Stream; VAR buf: ARRAY OF CHAR; offset, len: LONGINT; eof: BOOLEAN);
|
|
VAR
|
|
value: LONGINT;
|
|
BEGIN
|
|
value := ASH(StoredBlock, 1);
|
|
IF eof THEN INC(value) END;
|
|
SendBits(stream, value, 3); (* send block type *)
|
|
CopyBlock(stream, buf, offset, len, TRUE); (* with header *)
|
|
END StoreBlock;
|
|
|
|
(* Send the block data compressed using the given Huffman trees *)
|
|
PROCEDURE CompressBlock(VAR stream: Stream; lnode, dnode: Nodes);
|
|
VAR
|
|
dist: INTEGER; (* distance of matched string *)
|
|
lc: INTEGER; (* match length or unmatched char (if dist = 0) *)
|
|
code: INTEGER; (* the code to send *)
|
|
extra: INTEGER; (* number of extra bits to send *)
|
|
lx: LONGINT; (* running index in lbuf and dbuf *)
|
|
BEGIN
|
|
IF stream.lastLit # 0 THEN
|
|
lx := 0;
|
|
REPEAT
|
|
dist := stream.dbuf[lx];
|
|
lc := ORD(stream.lbuf[lx]);
|
|
INC(lx);
|
|
IF dist = 0 THEN
|
|
SendCode(stream, lnode[lc]); (* send a literal byte *)
|
|
ELSE (* lc is (match length - MinMatch) *)
|
|
code := ORD(LengthCode[lc]);
|
|
SendCode(stream, lnode[code + Literals + 1]); (* send length code *)
|
|
extra := ExtraLenBits[code];
|
|
IF extra # 0 THEN
|
|
DEC(lc, BaseLength[code]);
|
|
SendBits(stream, lc, extra)
|
|
END;
|
|
DEC(dist); (* dist is now (match distance - 1) *)
|
|
IF dist < 256 THEN code := ORD(DistCode[dist]);
|
|
ELSE code := ORD(DistCode[256 + ASH(dist, -7)])
|
|
END;
|
|
ASSERT(code < DistCodes, 110); (* bad DistCode *)
|
|
SendCode(stream, dnode[code]);
|
|
extra := ExtraDistBits[code];
|
|
IF extra # 0 THEN
|
|
DEC(dist, BaseDist[code]);
|
|
SendBits(stream, dist, extra) (* send extra distance bits *)
|
|
END
|
|
END (* literal or match pair? *)
|
|
(* no need to check for overlay consistency since we don't overlay *)
|
|
UNTIL lx = stream.lastLit
|
|
END;
|
|
SendCode(stream, lnode[EndBlock]);
|
|
stream.lastEobLen := lnode[EndBlock].dadOrLen
|
|
END CompressBlock;
|
|
|
|
(* Flush the current block, with given end-of-file flag, determine the best encoding for the current block:
|
|
dynamic trees, static trees or store, and output the encoded block to the zip file.
|
|
buf: input block, or NULL if too old;
|
|
pos, len: position in and length of input block;
|
|
eof: true if this is the last block for a file;
|
|
IN assertion: stream.string is set to the end of the current match *)
|
|
PROCEDURE FlushBlock(VAR stream: Stream; VAR buf: ARRAY OF CHAR; pos, len: LONGINT; eof: BOOLEAN);
|
|
VAR
|
|
max: INTEGER; (* index of last bit length code of non zero freqency *)
|
|
optLen, staticLen: LONGINT; (* optLen and staticLen in bytes *)
|
|
value: LONGINT;
|
|
BEGIN
|
|
IF stream.level > 0 THEN (* build a Huffman tree unless a stored block is forced *)
|
|
IF stream.dataType = Unknown THEN SetDataType(stream) END; (* check if the file is ascii or binary *)
|
|
BuildTree(stream, stream.ltree); (* construct the literal .. *)
|
|
BuildTree(stream, stream.dtree); (* .. and the distance tree *)
|
|
(* at this point, stream.optLen and stream.staticLen are the total bit lengths of the compressed block data,
|
|
excluding tree representations *)
|
|
max := BuildBitLenTree(stream); (* build bit length tree for the above tow trees, get the index of the last bit length code *)
|
|
optLen := (stream.optLen + 3 + 7) DIV 8;
|
|
staticLen := (stream.staticLen + 3 + 7) DIV 8;
|
|
IF staticLen < optLen THEN optLen := staticLen END;
|
|
ELSE
|
|
ASSERT(pos >= 0, 110); (* lost buf *)
|
|
optLen := len + 5;
|
|
staticLen := optLen
|
|
END;
|
|
IF len + 4 <= optLen THEN (* 4: two words for the lengths *)
|
|
ASSERT(pos >= 0, 111); (* see explanation in trees.c, LitBufSize <= WindowSize avoids lost block *)
|
|
StoreBlock(stream, buf, pos, len, eof);
|
|
ELSIF staticLen = optLen THEN
|
|
value := ASH(StaticTrees, 1);
|
|
IF eof THEN INC(value) END;
|
|
SendBits(stream, value, 3);
|
|
CompressBlock(stream, LTree.node, DTree.node)
|
|
ELSE
|
|
value := ASH(DynamicTrees, 1);
|
|
IF eof THEN INC(value) END;
|
|
SendBits(stream, value, 3);
|
|
SendAllTrees(stream, stream.ltree.maxCode + 1, stream.dtree.maxCode + 1, max + 1);
|
|
CompressBlock(stream, stream.lnode, stream.dnode);
|
|
END;
|
|
InitBlock(stream);
|
|
IF eof THEN
|
|
WindupBits(stream)
|
|
END
|
|
END FlushBlock;
|
|
|
|
(* Put a literal in the literal buffer (stream.lbuf) *)
|
|
PROCEDURE TallyLit(VAR stream: Stream; ch: CHAR): BOOLEAN;
|
|
BEGIN
|
|
stream.lbuf[stream.lastLit] := ch;
|
|
stream.dbuf[stream.lastLit] := 0;
|
|
INC(stream.lastLit);
|
|
INC(stream.lnode[ORD(ch)].freqOrCode);
|
|
RETURN (stream.lastLit = LitBufSize - 1)
|
|
END TallyLit;
|
|
|
|
(* Put a distance/length pair in the distance and the length buffer (stream.dbuf, stream.lbuf) *)
|
|
PROCEDURE TallyDistLen(VAR stream: Stream; dist, len: INTEGER): BOOLEAN;
|
|
BEGIN
|
|
ASSERT(len < 256, 99);
|
|
stream.lbuf[stream.lastLit] := CHR(len);
|
|
stream.dbuf[stream.lastLit] := dist;
|
|
INC(stream.lastLit);
|
|
DEC(dist);
|
|
INC(stream.lnode[ORD(LengthCode[len]) + Literals + 1].freqOrCode);
|
|
IF dist < 256 THEN dist := ORD(DistCode[dist])
|
|
ELSE dist := ORD(DistCode[256 + ASH(dist, -7)])
|
|
END;
|
|
INC(stream.dnode[dist].freqOrCode);
|
|
RETURN (stream.lastLit = LitBufSize - 1)
|
|
END TallyDistLen;
|
|
|
|
(*---Matches---*)
|
|
|
|
PROCEDURE ClearHash(VAR stream: Stream);
|
|
VAR
|
|
i: LONGINT;
|
|
BEGIN
|
|
FOR i := 0 TO HashSize - 1 DO
|
|
stream.head[i] := 0
|
|
END
|
|
END ClearHash;
|
|
|
|
(* Update a hash value with the given input byte
|
|
IN assertion: all calls are made with consecutive input characters, so that a running hash key can be computed
|
|
from the previous key instead of complete recalculation each time *)
|
|
PROCEDURE UpdateHash(VAR h: LONGINT; ch: CHAR);
|
|
BEGIN
|
|
h := SYSTEM.VAL(LONGINT, SYSTEM.VAL(SET, ASH(h, HashShift)) / SYSTEM.VAL(SET, LONG(ORD(ch)))) MOD HashSize
|
|
END UpdateHash;
|
|
|
|
(* Insert string starting at position pos in the dictionary and set head to the previous head of the hash chain
|
|
(the most recent string with the same hash key). Return the previous length of the hash chain.
|
|
IN assertion: all calls are made with consecutive input characters and the first MinMatch bytes at pos are valid
|
|
(except for the last MinMatch - 1 bytes of the input file *)
|
|
PROCEDURE InsertString(VAR stream: Stream; pos: LONGINT; VAR head: LONGINT);
|
|
BEGIN
|
|
UpdateHash(stream.hash, stream.window[pos + MinMatch - 1]);
|
|
head := stream.head[stream.hash];
|
|
stream.prev[pos MOD WindowSize] := head;
|
|
stream.head[stream.hash] := pos
|
|
END InsertString;
|
|
|
|
(* initialize the "longes match" routines for a new zlib stream *)
|
|
PROCEDURE InitMatches(VAR stream: Stream);
|
|
BEGIN
|
|
ClearHash(stream);
|
|
stream.string := 0; stream.block := 0; stream.lookAhead := 0;
|
|
stream.matchLen := MinMatch - 1; stream.prevLen := MinMatch - 1;
|
|
stream.prevAvail := FALSE; stream.hash := 0;
|
|
END InitMatches;
|
|
|
|
(* Set stream.match to the longest match starting at the given string and return its length.
|
|
Matches shorter or equal to stream.prevLen are discarded, in which case the result is equal to stream.prevLen
|
|
and stream.match is garbage.
|
|
IN assertion: cur is the head of the hash chain for the current string (stream.string) and its distance is <= MaxDist,
|
|
and stream.prevLen >= 1.
|
|
OUT assertion: the match length is not greater than stream.lookAhead. *)
|
|
PROCEDURE LongestMatch(VAR stream: Stream; cur: LONGINT): LONGINT;
|
|
VAR
|
|
chainLen: LONGINT; (* max hash chain length *)
|
|
scan: LONGINT; (* current string *)
|
|
match: LONGINT; (* matched string *)
|
|
len: LONGINT; (* length of current match *)
|
|
bestLen: LONGINT; (* best match so far *)
|
|
niceLen: LONGINT; (* stop if match long enough *)
|
|
limit: LONGINT; (* stop when cur becomes <= limit *)
|
|
strend: LONGINT;
|
|
scanEnd1, scanEnd: CHAR;
|
|
BEGIN
|
|
bestLen := stream.prevLen;
|
|
IF bestLen >= ConfigTable[stream.level].GoodLen THEN
|
|
chainLen := ConfigTable[stream.level].MaxChain DIV 4 (* do not waste too much time if match is already good enough *)
|
|
ELSE
|
|
chainLen := ConfigTable[stream.level].MaxChain;
|
|
END;
|
|
IF ConfigTable[stream.level].NiceLen > stream.lookAhead THEN (* do not look for matches beyond the end of the input *)
|
|
niceLen := stream.lookAhead
|
|
ELSE
|
|
niceLen := ConfigTable[stream.level].NiceLen
|
|
END;
|
|
scan := stream.string;
|
|
IF scan > MaxDist THEN limit := scan - MaxDist ELSE limit := 0 END;
|
|
strend := scan + MaxMatch;
|
|
scanEnd1 := stream.window[scan + bestLen - 1];
|
|
scanEnd := stream.window[scan + bestLen];
|
|
ASSERT(scan <= 2 * WindowSize - MinLookAhead, 110); (* need lookahead *)
|
|
len := -1;
|
|
REPEAT
|
|
ASSERT(cur < stream.string, 111); (* no future *)
|
|
match := cur;
|
|
|
|
(* skip to next match if match length cannot increase or match lengtch < 2 *)
|
|
IF (stream.window[match + bestLen] = scanEnd) & (stream.window[match + bestLen - 1] = scanEnd1) &
|
|
(stream.window[match] = stream.window[scan]) & (stream.window[match + 1] = stream.window[scan + 1]) THEN
|
|
(* The check at match + bestLen - 1 can be removed because it will be made again later (this heuristic is not always a win).
|
|
It is not necessary to compare match + 2 and scan + 2 since they are always equal when the other bytes match,
|
|
given that the hash keys are equal and that HashBits >= 8 *)
|
|
INC(scan, 2); INC(match, 2);
|
|
ASSERT(stream.window[match] = stream.window[scan], 112); (* must be equal as well because hash values coincide *)
|
|
REPEAT
|
|
INC(match); INC(scan)
|
|
UNTIL (stream.window[match] # stream.window[scan]) OR (scan >= strend);
|
|
ASSERT(scan <= 2 * WindowSize - 1, 113); (* wild scan *)
|
|
len := MaxMatch - (strend - scan);
|
|
scan := strend - MaxMatch;
|
|
IF len > bestLen THEN
|
|
stream.match := cur;
|
|
bestLen := len;
|
|
scanEnd1 := stream.window[scan + bestLen - 1];
|
|
scanEnd := stream.window[scan + bestLen]
|
|
END
|
|
END;
|
|
cur := stream.prev[cur MOD WindowSize];
|
|
DEC(chainLen)
|
|
UNTIL (len >= niceLen) OR (cur <= limit) OR (chainLen = 0);
|
|
IF bestLen > MaxMatch THEN bestLen := MaxMatch END; (* neu *)
|
|
IF bestLen <= stream.lookAhead THEN
|
|
RETURN bestLen
|
|
ELSE
|
|
RETURN stream.lookAhead
|
|
END
|
|
END LongestMatch;
|
|
|
|
(* Check that the match at stream.match is indeed a match *)
|
|
PROCEDURE CheckMatch(VAR stream: Stream; start, match, len: LONGINT);
|
|
BEGIN
|
|
WHILE len # 0 DO
|
|
ASSERT(stream.window[match] = stream.window[start]);
|
|
INC(match); INC(start); DEC(len)
|
|
END
|
|
END CheckMatch;
|
|
|
|
(* Fill window when lookahead becomes insufficient.
|
|
Updates stream.string and stream.lookAhead *)
|
|
PROCEDURE FillWindow(VAR stream: Stream);
|
|
VAR
|
|
n, len: LONGINT;
|
|
more: LONGINT; (* amount of free space at the end of the window *)
|
|
BEGIN
|
|
more := 2 * WindowSize - (stream.lookAhead + stream.string);
|
|
REPEAT
|
|
IF stream.string >= WindowSize + MaxDist THEN
|
|
(* lower half is no longer available for matches -> slide window *)
|
|
SYSTEM.MOVE(SYSTEM.ADR(stream.window[WindowSize]), SYSTEM.ADR(stream.window[0]), WindowSize);
|
|
DEC(stream.match, WindowSize); DEC(stream.string, WindowSize); DEC(stream.block, WindowSize);
|
|
(* slide hash table *)
|
|
n := HashSize;
|
|
REPEAT
|
|
DEC(n);
|
|
IF stream.head[n] >= WindowSize THEN
|
|
DEC(stream.head[n], WindowSize)
|
|
ELSE
|
|
stream.head[n] := 0
|
|
END
|
|
UNTIL n = 0;
|
|
n := WindowSize;
|
|
REPEAT
|
|
DEC(n);
|
|
IF stream.prev[n] >= WindowSize THEN
|
|
DEC(stream.prev[n], WindowSize)
|
|
ELSE
|
|
stream.prev[n] := 0
|
|
END
|
|
UNTIL n = 0;
|
|
INC(more, WindowSize)
|
|
END;
|
|
len := stream.in.avail;
|
|
IF len = 0 THEN RETURN END;
|
|
ASSERT(more >= 2, 110);
|
|
IF len > more THEN len := more END;
|
|
ZlibBuffers.ReadBytes(stream.in, stream.window^, stream.string + stream.lookAhead, len);
|
|
IF stream.wrapper THEN
|
|
stream.adler := Zlib.Adler32(stream.adler, stream.window^, stream.string + stream.lookAhead, len);
|
|
END;
|
|
INC(stream.lookAhead, len); DEC(more, len);
|
|
(* initialize hash value now there is some input *)
|
|
IF stream.lookAhead >= MinMatch THEN
|
|
stream.hash := LONG(stream.window[stream.string]);
|
|
UpdateHash(stream.hash, stream.window[stream.string + 1]);
|
|
END
|
|
(* if the whole input has less than MinMatch bytes, stream.hash is garbage,
|
|
but this is not important since only literal bytes will be emitted *)
|
|
UNTIL (stream.lookAhead >= MinLookAhead) OR (stream.in.avail = 0)
|
|
END FillWindow;
|
|
|
|
(*---Compressor Methods---*)
|
|
|
|
(* store without compression as much as possible from the input stream, return the current block state.
|
|
This function does not insert new strings in the dictionary since uncompressible data is probably not useful. *)
|
|
PROCEDURE CompressStored(VAR stream: Stream; flush: SHORTINT): SHORTINT;
|
|
CONST
|
|
MaxBlockSize = PendingBufSize - 5; (* header for stored block takes 5 bytes *)
|
|
BEGIN
|
|
(* MaxBlockSize is the minimum of the maximal block size of 0FFFFH and the size of the pending buffer minus 5 bytes for
|
|
the block header. For MemLevel <= 8, PendingBufSize - 5 < 0FFFFH! *)
|
|
ASSERT(PendingBufSize - 5 < 0FFFFH, 110);
|
|
LOOP
|
|
(* fill the window as much as possible *)
|
|
IF stream.lookAhead <= 1 THEN
|
|
ASSERT((stream.string < (WindowSize + MaxDist)) OR (stream.block >= WindowSize), 111); (* slide too late *)
|
|
FillWindow(stream);
|
|
IF stream.lookAhead = 0 THEN
|
|
IF flush = NoFlush THEN RETURN NeedMore
|
|
ELSE EXIT (* flush the current block *)
|
|
END
|
|
END
|
|
END;
|
|
ASSERT(stream.block >= 0, 112); (* block gone *)
|
|
INC(stream.string, stream.lookAhead);
|
|
stream.lookAhead := 0;
|
|
|
|
(* zlib flushes the block if the pending buffer will be full. With MemLevel = 8 and WindowBits = 15 this is impossible
|
|
since MaxBlockSize is almost twice the window size *)
|
|
ASSERT(stream.string < stream.block + MaxBlockSize, 113);
|
|
|
|
(* flush if we may have to slide, otherwise stream.block may become negative and the data will be lost *)
|
|
IF (stream.string - stream.block) >= MaxDist THEN
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, FALSE);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF stream.out.avail = 0 THEN RETURN NeedMore
|
|
END
|
|
END
|
|
END;
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, flush = Finish);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF (stream.out.avail = 0) & (flush = Finish) THEN RETURN FinishStarted
|
|
ELSIF stream.out.avail = 0 THEN RETURN NeedMore
|
|
ELSIF flush = Finish THEN RETURN FinishDone
|
|
ELSE RETURN BlockDone
|
|
END
|
|
END CompressStored;
|
|
|
|
(* Compress without lazy matches.
|
|
This function inserts new strings in the dictionary only for unmatched strings or for short matches. *)
|
|
PROCEDURE CompressFast(VAR stream: Stream; flush: SHORTINT): SHORTINT;
|
|
VAR
|
|
head: LONGINT; (* head of the hash chain *)
|
|
mustFlush: BOOLEAN; (* set if current block must be flushed *)
|
|
BEGIN
|
|
head := 0;
|
|
LOOP
|
|
(* make sure that we always have enough lookahead, except at the end of the input file.
|
|
We need MaxMatch bytes for the next match, plus MinMatch bytes to insert the string following the next match *)
|
|
IF stream.lookAhead < MinLookAhead THEN
|
|
FillWindow(stream);
|
|
IF (stream.lookAhead < MinLookAhead) & (flush = NoFlush) THEN RETURN NeedMore
|
|
ELSIF stream.lookAhead = 0 THEN EXIT (* flush the current block *)
|
|
END
|
|
END;
|
|
(* Insert the string window[stream.string .. stream.string + 2] in the dictionary,
|
|
and set stream.hash to the head of the hash chain *)
|
|
IF stream.lookAhead >= MinMatch THEN
|
|
InsertString(stream, stream.string, head)
|
|
END;
|
|
(* Find the longest match, discarding those <= prevLen. At this point we have always matchLen < MinMatch *)
|
|
IF (head # 0) & ((stream.string - head) <= MaxDist) THEN
|
|
IF stream.strategy # HuffmanOnly THEN (* avoid matches with string at index 0, in particular with itself *)
|
|
stream.matchLen := LongestMatch(stream, head) (* LongestMatch sets match *)
|
|
END
|
|
END;
|
|
IF stream.matchLen >= MinMatch THEN
|
|
CheckMatch(stream, stream.string, stream.match, stream.matchLen);
|
|
mustFlush := TallyDistLen(stream, SHORT(stream.string - stream.match), SHORT(stream.matchLen - MinMatch));
|
|
DEC(stream.lookAhead, stream.matchLen);
|
|
(* Insert new strings in the hash table only if the match length is not too large.
|
|
This saves time but degrades compression *)
|
|
IF (stream.matchLen <= ConfigTable[stream.level].MaxLazy) & (stream.lookAhead >= MinMatch) THEN
|
|
DEC(stream.matchLen); (* string at stream.string is already in hash table *)
|
|
REPEAT
|
|
INC(stream.string);
|
|
InsertString(stream, stream.string, head);
|
|
(* stream.string never exceeds WindowSize - MaxMatch, so there are always MinMatch bytes ahead *)
|
|
DEC(stream.matchLen)
|
|
UNTIL stream.matchLen = 0;
|
|
INC(stream.string);
|
|
ELSE
|
|
INC(stream.string, stream.matchLen);
|
|
stream.matchLen := 0;
|
|
stream.hash := ORD(stream.window[stream.string]);
|
|
UpdateHash(stream.hash, stream.window[stream.string + 1])
|
|
(* If stream.lookAhead < MinMatch, stream.hash is garbage,
|
|
but it does not matter since it will recomputed at next Deflate call *)
|
|
END
|
|
ELSE (* no match, output a literal byte *)
|
|
mustFlush := TallyLit(stream, stream.window[stream.string]);
|
|
DEC(stream.lookAhead);
|
|
INC(stream.string)
|
|
END;
|
|
IF mustFlush THEN
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, FALSE);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF stream.out.avail = 0 THEN RETURN NeedMore
|
|
END
|
|
END
|
|
END;
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, flush = Finish);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF (stream.out.avail = 0) & (flush = Finish) THEN RETURN FinishStarted
|
|
ELSIF stream.out.avail = 0 THEN RETURN NeedMore
|
|
ELSIF flush = Finish THEN RETURN FinishDone
|
|
ELSE RETURN BlockDone
|
|
END
|
|
END CompressFast;
|
|
|
|
(* Same as above, but achieves a better compression. We use lazy evaluation for matches:
|
|
a match is finally adopted only if there is no better match at the next window position *)
|
|
PROCEDURE CompressSlow(VAR stream: Stream; flush: SHORTINT): SHORTINT;
|
|
VAR
|
|
head: LONGINT; (* head of the hash chain *)
|
|
maxIns: LONGINT;
|
|
mustFlush: BOOLEAN; (* set if current block must be flushed *)
|
|
BEGIN
|
|
head := 0;
|
|
LOOP
|
|
(* make sure that we always have enough lookahead, except at the end of the input file.
|
|
We need MaxMatch bytes for the next match, plus MinMatch bytes to insert the string following the next match *)
|
|
IF stream.lookAhead < MinLookAhead THEN
|
|
FillWindow(stream);
|
|
IF (stream.lookAhead < MinLookAhead) & (flush = NoFlush) THEN RETURN NeedMore
|
|
ELSIF stream.lookAhead = 0 THEN EXIT
|
|
END
|
|
END;
|
|
(* Insert the string window[stream.string .. stream.string + 2] in the dictionary,
|
|
and set stream.hash to the head of the hash chain *)
|
|
IF stream.lookAhead >= MinMatch THEN
|
|
InsertString(stream, stream.string, head);
|
|
END;
|
|
(* Find the longest match, discarding those <= stream.prevLen *)
|
|
stream.prevLen := stream.matchLen;
|
|
stream.prevMatch := stream.match;
|
|
stream.matchLen := MinMatch - 1;
|
|
IF (head # 0) & (stream.prevLen < ConfigTable[stream.level].MaxLazy) & (stream.string - head <= MaxDist) THEN
|
|
(* avoid matches with string at index 0, in particular with itself *)
|
|
IF stream.strategy # HuffmanOnly THEN
|
|
stream.matchLen := LongestMatch(stream, head); (* LongestMatch sets stream.match *)
|
|
END;
|
|
IF (stream.matchLen <= 5) &
|
|
((stream.strategy = Filtered) OR ((stream.matchLen = MinMatch) & ((stream.string - stream.match) > TooFar))) THEN
|
|
(* If stream.prevMatch is also MinMatch, stream.match is garbage but we will ignore the current match anyway *)
|
|
stream.matchLen := MinMatch - 1
|
|
END
|
|
END;
|
|
|
|
(* If there was a match at the previous step and the current match is not better, output the previous match: *)
|
|
IF (stream.prevLen >= MinMatch) & (stream.matchLen <= stream.prevLen) THEN
|
|
maxIns := stream.string + stream.lookAhead - MinMatch; (* do not insert strings in hash table beyond this *)
|
|
CheckMatch(stream, stream.string - 1, stream.prevMatch, stream.prevLen);
|
|
mustFlush := TallyDistLen(stream, SHORT(stream.string - 1 - stream.prevMatch), SHORT(stream.prevLen - MinMatch));
|
|
|
|
(* Insert in hash table all strings up to the end of the match. stream.string - 1 and stream.string are already inserted.
|
|
If there is not enough stream.lookAhead, the last two strings are not inserted in the hash table. *)
|
|
DEC(stream.lookAhead, stream.prevLen - 1);
|
|
DEC(stream.prevLen, 2);
|
|
REPEAT
|
|
INC(stream.string);
|
|
IF stream.string <= maxIns THEN
|
|
InsertString(stream, stream.string, head)
|
|
END;
|
|
DEC(stream.prevLen);
|
|
UNTIL stream.prevLen = 0;
|
|
stream.prevAvail := FALSE;
|
|
stream.matchLen := MinMatch - 1;
|
|
INC(stream.string);
|
|
IF mustFlush THEN
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, FALSE);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF stream.out.avail = 0 THEN RETURN NeedMore
|
|
END
|
|
END
|
|
ELSIF stream.prevAvail THEN
|
|
(* If there was no match at the previous position, output a single literal. If there was a match but the current
|
|
match is longer, truncate the previous match to a single literal. *)
|
|
mustFlush := TallyLit(stream, stream.window[stream.string - 1]);
|
|
IF mustFlush THEN
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, FALSE);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out)
|
|
END;
|
|
INC(stream.string);
|
|
DEC(stream.lookAhead);
|
|
IF stream.out.avail = 0 THEN RETURN NeedMore
|
|
END
|
|
ELSE
|
|
(* There is no previous match to compare with, wait for the next step to decide *)
|
|
stream.prevAvail := TRUE;
|
|
INC(stream.string);
|
|
DEC(stream.lookAhead)
|
|
END
|
|
END;
|
|
ASSERT(flush # NoFlush, 110);
|
|
IF stream.prevAvail THEN
|
|
mustFlush := TallyLit(stream, stream.window[stream.string - 1]);
|
|
stream.prevAvail := FALSE
|
|
END;
|
|
FlushBlock(stream, stream.window^, stream.block, stream.string - stream.block, flush = Finish);
|
|
stream.block := stream.string;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF (stream.out.avail = 0) & (flush = Finish) THEN RETURN FinishStarted
|
|
ELSIF stream.out.avail = 0 THEN RETURN NeedMore
|
|
ELSIF flush = Finish THEN RETURN FinishDone
|
|
ELSE RETURN BlockDone
|
|
END
|
|
END CompressSlow;
|
|
|
|
(**---Streams---**)
|
|
|
|
(** reset stream **)
|
|
PROCEDURE Reset*(VAR stream: Stream);
|
|
BEGIN
|
|
IF ~stream.open THEN
|
|
stream.res := StreamError;
|
|
ELSE
|
|
ZlibBuffers.Reset(stream.in); ZlibBuffers.Reset(stream.out);
|
|
stream.dataType := Unknown;
|
|
stream.pend.beg := 0; stream.pend.end := 0;
|
|
stream.trailerDone := FALSE;
|
|
IF stream.wrapper THEN
|
|
stream.status := InitState
|
|
ELSE
|
|
stream.status := BusyState
|
|
END;
|
|
stream.adler := 1;
|
|
stream.lastFlush := NoFlush;
|
|
InitTrees(stream);
|
|
InitMatches(stream);
|
|
stream.res := Ok;
|
|
END
|
|
END Reset;
|
|
|
|
(** close deflate stream **)
|
|
PROCEDURE Close*(VAR stream: Stream);
|
|
BEGIN
|
|
IF stream.open THEN
|
|
stream.window := NIL; stream.prev := NIL; stream.head := NIL;
|
|
stream.pend.buf := NIL; stream.lbuf := NIL; stream.dbuf := NIL;
|
|
FreeTrees(stream);
|
|
stream.open := FALSE; stream.res := Ok
|
|
ELSE
|
|
stream.res := StreamError
|
|
END
|
|
END Close;
|
|
|
|
(** initialize deflate stream with compression level and strategy; if wrapper is not set, no header and checksum are generated **)
|
|
PROCEDURE Open*(VAR stream: Stream; level, strategy: SHORTINT; wrapper: BOOLEAN);
|
|
BEGIN
|
|
IF level = DefaultCompression THEN level := 6 END;
|
|
IF (0 <= level) & (level <= 9) & (DefaultStrategy <= strategy) & (strategy <= HuffmanOnly) THEN
|
|
NEW(stream.window); NEW(stream.prev); NEW(stream.head);
|
|
(* zlib overlays pend.buf, lbuf and dbuf. Since memory usage should not be a very big problem and dbuf stores integers
|
|
instead of bytes they are allocated as seperate memory chunks here *)
|
|
NEW(stream.pend.buf); NEW(stream.lbuf); NEW(stream.dbuf);
|
|
IF (stream.window # NIL) & (stream.prev # NIL) & (stream.head # NIL)
|
|
& (stream.pend.buf # NIL) & (stream.lbuf # NIL) & (stream.dbuf # NIL) THEN
|
|
stream.level := level; stream.strategy := strategy; stream.wrapper := wrapper; stream.open := TRUE;
|
|
Reset(stream)
|
|
ELSE
|
|
stream.open := FALSE;
|
|
Close(stream);
|
|
stream.res := MemError
|
|
END
|
|
ELSE
|
|
stream.open := FALSE;
|
|
stream.res := StreamError
|
|
END
|
|
END Open;
|
|
|
|
(** initializes the compression dictionary from the given byte sequence without producing any compressed output.
|
|
Must be called immediately after Open or Reset before any call of Deflate **)
|
|
PROCEDURE SetDictionary*(VAR stream: Stream; VAR dict: ARRAY OF CHAR; len: LONGINT);
|
|
VAR
|
|
offset, i, head: LONGINT;
|
|
BEGIN
|
|
IF ~stream.open OR (stream.status # InitState) THEN
|
|
stream.res := StreamError;
|
|
RETURN
|
|
END;
|
|
stream.adler := Zlib.Adler32(stream.adler, dict, 0, len);
|
|
IF len >= MinMatch THEN
|
|
IF len > MaxDist THEN
|
|
offset := len - MaxDist; (* use the tail of the dictionary *)
|
|
len := MaxDist
|
|
ELSE
|
|
offset := 0
|
|
END;
|
|
SYSTEM.MOVE(SYSTEM.ADR(dict[offset]), SYSTEM.ADR(stream.window[0]), len);
|
|
stream.string := len; stream.block := len;
|
|
(* insert all strings in the hash table (except for the last two bytes). stream.lookAhead stays zero,
|
|
so stream.hash will be recomputed at the next call of FillWindow *)
|
|
stream.hash := ORD(stream.window[0]);
|
|
UpdateHash(stream.hash, stream.window[1]);
|
|
FOR i := 0 TO (len - MinMatch) DO
|
|
InsertString(stream, i, head)
|
|
END
|
|
END;
|
|
stream.res := Ok
|
|
END SetDictionary;
|
|
|
|
|
|
(** Deflate compresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full;
|
|
the flush parameter decides if and how blocks are terminated **)
|
|
PROCEDURE Deflate*(VAR stream: Stream; flush: SHORTINT);
|
|
VAR
|
|
lastFlush, bstate: SHORTINT;
|
|
header: LONGINT;
|
|
buf: ARRAY 1 OF CHAR;
|
|
BEGIN
|
|
IF ~stream.open OR (flush < NoFlush) OR (flush > Finish) OR ((stream.status = FinishState) & (flush # Finish)) THEN
|
|
stream.res := StreamError;
|
|
RETURN
|
|
END;
|
|
IF stream.out.avail = 0 THEN
|
|
stream.res := BufError;
|
|
RETURN
|
|
END;
|
|
lastFlush := stream.lastFlush; stream.lastFlush := flush;
|
|
|
|
(* write zlib header *)
|
|
IF stream.status = InitState THEN
|
|
header := (((WindowBits - 8) * 10H) + Deflated) * 100H; (* CMF: 7 - 4: CINFO (compression info (=window size - 8)), 3 - 0: CM (compression method) *)
|
|
(* FLG: flags *) (* FLG.FLEVEL: compression level *)
|
|
IF stream.level >= 7 THEN INC(header, 0C0H) (* maximum compression, slowest algorithm *)
|
|
ELSIF stream.level >= 5 THEN INC(header, 80H) (* default algorithm *)
|
|
ELSIF stream.level >= 3 THEN INC(header, 40H) (* fast algorithm *)
|
|
END; (* ELSE fastest algorithm *)
|
|
IF stream.string # 0 THEN
|
|
INC(header, PresetDict) (* FLG.FDICT: preset dictionary *)
|
|
END;
|
|
INC(header, 31 - (header MOD 31)); (* FLG.FCHECK: check bits for CMF and FLG *)
|
|
stream.status := BusyState;
|
|
Put16BitsMSB(stream.pend, header);
|
|
IF stream.string # 0 THEN (* DICT: the adler32 checksum of the preset dictionary *)
|
|
Put32BitsMSB(stream.pend, stream.adler)
|
|
END;
|
|
stream.adler := 1;
|
|
END;
|
|
|
|
(* flush as much pending output as possible *)
|
|
IF stream.pend.end # 0 THEN
|
|
FlushPending(stream.pend, stream.out);
|
|
IF stream.out.avail = 0 THEN
|
|
(* Since stream.out.avail is 0, Deflate will be called again with more output space,
|
|
but possibly with both stream.pend.end and stream.in.avail equal to zero. There won't be anything to do,
|
|
but this is not an error situation so make sure we return Ok instead of BufError at next call of Deflate *)
|
|
stream.lastFlush := -1;
|
|
stream.res := Ok;
|
|
RETURN
|
|
END
|
|
|
|
(* make sure there is something to do and avoid duplicate consecutive flushes. For repeated and useless calls with Finish,
|
|
we keep returning StreamEnd instead of BufError *)
|
|
ELSIF (stream.in.avail = 0) & (flush <= lastFlush) & (flush # Finish) THEN
|
|
stream.res := BufError;
|
|
RETURN
|
|
END;
|
|
|
|
(* user must not provide more input after the first Finish *)
|
|
IF (stream.status = Finish) & (stream.in.avail # 0) THEN
|
|
stream.res := BufError;
|
|
RETURN
|
|
END;
|
|
|
|
(* start a new block or continue the current one *)
|
|
IF (stream.in.avail # 0) OR (stream.lookAhead # 0) OR ((flush # NoFlush) & (stream.status # FinishState)) THEN
|
|
bstate := ConfigTable[stream.level].Compress(stream, flush);
|
|
IF bstate IN {FinishStarted, FinishDone} THEN
|
|
stream.status := FinishState
|
|
END;
|
|
IF bstate IN {NeedMore, FinishStarted} THEN
|
|
IF stream.out.avail = 0 THEN
|
|
stream.lastFlush := -1
|
|
END;
|
|
stream.res := Ok; (* avoid BufError in next call, see above *)
|
|
RETURN
|
|
(* if (flush # NoFlush) & (out.avail = 0), the next call of Deflate should use the same flush parameter
|
|
to make sure that the flush is complete. So we dont't have to output an empty block here, this will be done at next call.
|
|
This also ensures that for a very small output buffer, we emit at most one empty block. *)
|
|
ELSIF bstate = BlockDone THEN
|
|
IF flush = PartialFlush THEN
|
|
AlignTrees(stream)
|
|
ELSE (* FullFlush or SyncFlush *)
|
|
StoreBlock(stream, buf, 0, 0, FALSE); (* for a full flush, this empty block will be recognized as a special marker by Inflate.Sync *)
|
|
IF flush = FullFlush THEN
|
|
ClearHash(stream) (* forget about all hash chains *)
|
|
END
|
|
END;
|
|
FlushPending(stream.pend, stream.out);
|
|
IF stream.out.avail = 0 THEN
|
|
stream.lastFlush := -1; (* avoid BufError at next call, see above *)
|
|
stream.res := Ok;
|
|
RETURN
|
|
END
|
|
END
|
|
END;
|
|
ASSERT(stream.out.avail > 0, 111);
|
|
|
|
IF flush # Finish THEN
|
|
stream.res := Ok
|
|
ELSIF ~stream.wrapper OR stream.trailerDone THEN
|
|
stream.res := StreamEnd
|
|
ELSE (* write the zlib trailer (adler32) *)
|
|
Put32BitsMSB(stream.pend, stream.adler);
|
|
FlushPending(stream.pend, stream.out); (* if stream.out.avail is zero, the application will call deflate again *)
|
|
stream.trailerDone := TRUE; (* write the trailer only once *)
|
|
IF stream.pend.end = 0 THEN (* flushed everything left *)
|
|
stream.res := StreamEnd
|
|
ELSE
|
|
stream.res := Ok
|
|
END
|
|
END
|
|
END Deflate;
|
|
|
|
|
|
(** change deflate parameters within the stream. If the compression level is changed, the input available so far
|
|
is compressed with the old level (and may be flushed); the new level will take effect only at the next call of Deflate **)
|
|
PROCEDURE SetParams*(VAR stream: Stream; level, strategy: SHORTINT);
|
|
BEGIN
|
|
IF level = DefaultCompression THEN
|
|
level := 6
|
|
END;
|
|
IF ~stream.open OR (level < 0) OR (9 < level) OR (strategy < DefaultStrategy) OR (HuffmanOnly < strategy) THEN
|
|
stream.res := StreamError
|
|
ELSE
|
|
IF (ConfigTable[level].Compress # ConfigTable[stream.level].Compress) & (stream.in.totalIn # 0) THEN
|
|
Deflate(stream, PartialFlush)
|
|
END;
|
|
stream.level := level;
|
|
stream.strategy := strategy
|
|
END
|
|
END SetParams;
|
|
|
|
(** compress complete stream and return output length in len **)
|
|
PROCEDURE Compress* (VAR src, dst: ARRAY OF CHAR; srcoffset, srclen, dstoffset, dstlen: LONGINT; level, strategy: SHORTINT; VAR len: LONGINT; VAR res: LONGINT);
|
|
VAR s: Stream;
|
|
BEGIN
|
|
ZlibBuffers.Init(s.in, src, srcoffset, srclen, srclen);
|
|
ZlibBuffers.Init(s.out, dst, dstoffset, dstlen, dstlen);
|
|
Open(s, level, strategy, TRUE);
|
|
IF s.res = Ok THEN
|
|
Deflate(s, Finish);
|
|
IF s.res = StreamEnd THEN
|
|
len := s.out.totalOut;
|
|
Close(s);
|
|
res := s.res
|
|
ELSE
|
|
res := s.res;
|
|
IF res = Ok THEN res := BufError END;
|
|
Close(s)
|
|
END
|
|
ELSE
|
|
res := s.res
|
|
END
|
|
END Compress;
|
|
|
|
|
|
BEGIN
|
|
InitStaticTrees();
|
|
ConfigTable[0].GoodLen := 0; ConfigTable[0].MaxLazy := 0; ConfigTable[0].NiceLen := 0;
|
|
ConfigTable[0].MaxChain := 0; ConfigTable[0].Compress := CompressStored; (* store only *)
|
|
ConfigTable[1].GoodLen := 4; ConfigTable[1].MaxLazy := 4; ConfigTable[1].NiceLen := 8;
|
|
ConfigTable[1].MaxChain := 4; ConfigTable[1].Compress := CompressFast; (*maximum speed, no lazy matches *)
|
|
ConfigTable[2].GoodLen := 4; ConfigTable[2].MaxLazy := 5; ConfigTable[2].NiceLen := 16;
|
|
ConfigTable[2].MaxChain := 8; ConfigTable[2].Compress := CompressFast;
|
|
ConfigTable[3].GoodLen := 4; ConfigTable[3].MaxLazy := 6; ConfigTable[3].NiceLen := 32;
|
|
ConfigTable[3].MaxChain := 32; ConfigTable[3].Compress := CompressFast;
|
|
ConfigTable[4].GoodLen := 4; ConfigTable[4].MaxLazy := 4; ConfigTable[4].NiceLen := 16;
|
|
ConfigTable[4].MaxChain := 16; ConfigTable[4].Compress := CompressSlow; (* lazy matches *)
|
|
ConfigTable[5].GoodLen := 8; ConfigTable[5].MaxLazy := 16; ConfigTable[5].NiceLen := 32;
|
|
ConfigTable[5].MaxChain := 32; ConfigTable[5].Compress := CompressSlow;
|
|
ConfigTable[6].GoodLen := 8; ConfigTable[6].MaxLazy := 16; ConfigTable[6].NiceLen := 128;
|
|
ConfigTable[6].MaxChain := 128; ConfigTable[6].Compress := CompressSlow;
|
|
ConfigTable[7].GoodLen := 8; ConfigTable[7].MaxLazy := 32; ConfigTable[7].NiceLen := 128;
|
|
ConfigTable[7].MaxChain := 256; ConfigTable[7].Compress := CompressSlow;
|
|
ConfigTable[8].GoodLen := 32; ConfigTable[8].MaxLazy := 128; ConfigTable[8].NiceLen := 258;
|
|
ConfigTable[8].MaxChain := 1024; ConfigTable[8].Compress := CompressSlow;
|
|
ConfigTable[9].GoodLen := 32; ConfigTable[9].MaxLazy := 128; ConfigTable[9].NiceLen := 258;
|
|
ConfigTable[9].MaxChain := 4096; ConfigTable[9].Compress := CompressSlow; (* maximum compression *)
|
|
END ethZlibDeflate.
|