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  }