tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/image/internal/compress/flate/dict_decoder.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package flate 6 7 // dictDecoder implements the LZ77 sliding dictionary as used in decompression. 8 // LZ77 decompresses data through sequences of two forms of commands: 9 // 10 // - Literal insertions: Runs of one or more symbols are inserted into the data 11 // stream as is. This is accomplished through the writeByte method for a 12 // single symbol, or combinations of writeSlice/writeMark for multiple symbols. 13 // Any valid stream must start with a literal insertion if no preset dictionary 14 // is used. 15 // 16 // - Backward copies: Runs of one or more symbols are copied from previously 17 // emitted data. Backward copies come as the tuple (dist, length) where dist 18 // determines how far back in the stream to copy from and length determines how 19 // many bytes to copy. Note that it is valid for the length to be greater than 20 // the distance. Since LZ77 uses forward copies, that situation is used to 21 // perform a form of run-length encoding on repeated runs of symbols. 22 // The writeCopy and tryWriteCopy are used to implement this command. 23 // 24 // For performance reasons, this implementation performs little to no sanity 25 // checks about the arguments. As such, the invariants documented for each 26 // method call must be respected. 27 type dictDecoder struct { 28 hist []byte // Sliding window history 29 30 // Invariant: 0 <= rdPos <= wrPos <= len(hist) 31 wrPos int // Current output position in buffer 32 rdPos int // Have emitted hist[:rdPos] already 33 full bool // Has a full window length been written yet? 34 } 35 36 // To minimize the memory usage in TinyGo, it is defined as a fixed array 37 // instead of a make(). 38 var ddHistBuf [1 << 15]byte 39 40 // init initializes dictDecoder to have a sliding window dictionary of the given 41 // size. If a preset dict is provided, it will initialize the dictionary with 42 // the contents of dict. 43 func (dd *dictDecoder) init(size int, dict []byte) { 44 *dd = dictDecoder{hist: dd.hist} 45 46 if cap(dd.hist) < size { 47 dd.hist = ddHistBuf[:size] 48 } 49 dd.hist = dd.hist[:size] 50 51 if len(dict) > len(dd.hist) { 52 dict = dict[len(dict)-len(dd.hist):] 53 } 54 dd.wrPos = copy(dd.hist, dict) 55 if dd.wrPos == len(dd.hist) { 56 dd.wrPos = 0 57 dd.full = true 58 } 59 dd.rdPos = dd.wrPos 60 } 61 62 // histSize reports the total amount of historical data in the dictionary. 63 func (dd *dictDecoder) histSize() int { 64 if dd.full { 65 return len(dd.hist) 66 } 67 return dd.wrPos 68 } 69 70 // availRead reports the number of bytes that can be flushed by readFlush. 71 func (dd *dictDecoder) availRead() int { 72 return dd.wrPos - dd.rdPos 73 } 74 75 // availWrite reports the available amount of output buffer space. 76 func (dd *dictDecoder) availWrite() int { 77 return len(dd.hist) - dd.wrPos 78 } 79 80 // writeSlice returns a slice of the available buffer to write data to. 81 // 82 // This invariant will be kept: len(s) <= availWrite() 83 func (dd *dictDecoder) writeSlice() []byte { 84 return dd.hist[dd.wrPos:] 85 } 86 87 // writeMark advances the writer pointer by cnt. 88 // 89 // This invariant must be kept: 0 <= cnt <= availWrite() 90 func (dd *dictDecoder) writeMark(cnt int) { 91 dd.wrPos += cnt 92 } 93 94 // writeByte writes a single byte to the dictionary. 95 // 96 // This invariant must be kept: 0 < availWrite() 97 func (dd *dictDecoder) writeByte(c byte) { 98 dd.hist[dd.wrPos] = c 99 dd.wrPos++ 100 } 101 102 // writeCopy copies a string at a given (dist, length) to the output. 103 // This returns the number of bytes copied and may be less than the requested 104 // length if the available space in the output buffer is too small. 105 // 106 // This invariant must be kept: 0 < dist <= histSize() 107 func (dd *dictDecoder) writeCopy(dist, length int) int { 108 dstBase := dd.wrPos 109 dstPos := dstBase 110 srcPos := dstPos - dist 111 endPos := dstPos + length 112 if endPos > len(dd.hist) { 113 endPos = len(dd.hist) 114 } 115 116 // Copy non-overlapping section after destination position. 117 // 118 // This section is non-overlapping in that the copy length for this section 119 // is always less than or equal to the backwards distance. This can occur 120 // if a distance refers to data that wraps-around in the buffer. 121 // Thus, a backwards copy is performed here; that is, the exact bytes in 122 // the source prior to the copy is placed in the destination. 123 if srcPos < 0 { 124 srcPos += len(dd.hist) 125 dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:]) 126 srcPos = 0 127 } 128 129 // Copy possibly overlapping section before destination position. 130 // 131 // This section can overlap if the copy length for this section is larger 132 // than the backwards distance. This is allowed by LZ77 so that repeated 133 // strings can be succinctly represented using (dist, length) pairs. 134 // Thus, a forwards copy is performed here; that is, the bytes copied is 135 // possibly dependent on the resulting bytes in the destination as the copy 136 // progresses along. This is functionally equivalent to the following: 137 // 138 // for i := 0; i < endPos-dstPos; i++ { 139 // dd.hist[dstPos+i] = dd.hist[srcPos+i] 140 // } 141 // dstPos = endPos 142 // 143 for dstPos < endPos { 144 dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos]) 145 } 146 147 dd.wrPos = dstPos 148 return dstPos - dstBase 149 } 150 151 // tryWriteCopy tries to copy a string at a given (distance, length) to the 152 // output. This specialized version is optimized for short distances. 153 // 154 // This method is designed to be inlined for performance reasons. 155 // 156 // This invariant must be kept: 0 < dist <= histSize() 157 func (dd *dictDecoder) tryWriteCopy(dist, length int) int { 158 dstPos := dd.wrPos 159 endPos := dstPos + length 160 if dstPos < dist || endPos > len(dd.hist) { 161 return 0 162 } 163 dstBase := dstPos 164 srcPos := dstPos - dist 165 166 // Copy possibly overlapping section before destination position. 167 for dstPos < endPos { 168 dstPos += copy(dd.hist[dstPos:endPos], dd.hist[srcPos:dstPos]) 169 } 170 171 dd.wrPos = dstPos 172 return dstPos - dstBase 173 } 174 175 // readFlush returns a slice of the historical buffer that is ready to be 176 // emitted to the user. The data returned by readFlush must be fully consumed 177 // before calling any other dictDecoder methods. 178 func (dd *dictDecoder) readFlush() []byte { 179 toRead := dd.hist[dd.rdPos:dd.wrPos] 180 dd.rdPos = dd.wrPos 181 if dd.wrPos == len(dd.hist) { 182 dd.wrPos, dd.rdPos = 0, 0 183 dd.full = true 184 } 185 return toRead 186 }