(* 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.