| | 1 | | using System; |
| | 2 | |
|
| | 3 | | namespace ICSharpCode.SharpZipLib.Zip.Compression |
| | 4 | | { |
| | 5 | | /// <summary> |
| | 6 | | /// This is the Deflater class. The deflater class compresses input |
| | 7 | | /// with the deflate algorithm described in RFC 1951. It has several |
| | 8 | | /// compression levels and three different strategies described below. |
| | 9 | | /// |
| | 10 | | /// This class is <i>not</i> thread safe. This is inherent in the API, due |
| | 11 | | /// to the split of deflate and setInput. |
| | 12 | | /// |
| | 13 | | /// author of the original java version : Jochen Hoenicke |
| | 14 | | /// </summary> |
| | 15 | | public class Deflater |
| | 16 | | { |
| | 17 | | #region Deflater Documentation |
| | 18 | | /* |
| | 19 | | * The Deflater can do the following state transitions: |
| | 20 | | * |
| | 21 | | * (1) -> INIT_STATE ----> INIT_FINISHING_STATE ---. |
| | 22 | | * / | (2) (5) | |
| | 23 | | * / v (5) | |
| | 24 | | * (3)| SETDICT_STATE ---> SETDICT_FINISHING_STATE |(3) |
| | 25 | | * \ | (3) | ,--------' |
| | 26 | | * | | | (3) / |
| | 27 | | * v v (5) v v |
| | 28 | | * (1) -> BUSY_STATE ----> FINISHING_STATE |
| | 29 | | * | (6) |
| | 30 | | * v |
| | 31 | | * FINISHED_STATE |
| | 32 | | * \_____________________________________/ |
| | 33 | | * | (7) |
| | 34 | | * v |
| | 35 | | * CLOSED_STATE |
| | 36 | | * |
| | 37 | | * (1) If we should produce a header we start in INIT_STATE, otherwise |
| | 38 | | * we start in BUSY_STATE. |
| | 39 | | * (2) A dictionary may be set only when we are in INIT_STATE, then |
| | 40 | | * we change the state as indicated. |
| | 41 | | * (3) Whether a dictionary is set or not, on the first call of deflate |
| | 42 | | * we change to BUSY_STATE. |
| | 43 | | * (4) -- intentionally left blank -- :) |
| | 44 | | * (5) FINISHING_STATE is entered, when flush() is called to indicate that |
| | 45 | | * there is no more INPUT. There are also states indicating, that |
| | 46 | | * the header wasn't written yet. |
| | 47 | | * (6) FINISHED_STATE is entered, when everything has been flushed to the |
| | 48 | | * internal pending output buffer. |
| | 49 | | * (7) At any time (7) |
| | 50 | | * |
| | 51 | | */ |
| | 52 | | #endregion |
| | 53 | | #region Public Constants |
| | 54 | | /// <summary> |
| | 55 | | /// The best and slowest compression level. This tries to find very |
| | 56 | | /// long and distant string repetitions. |
| | 57 | | /// </summary> |
| | 58 | | public const int BEST_COMPRESSION = 9; |
| | 59 | |
|
| | 60 | | /// <summary> |
| | 61 | | /// The worst but fastest compression level. |
| | 62 | | /// </summary> |
| | 63 | | public const int BEST_SPEED = 1; |
| | 64 | |
|
| | 65 | | /// <summary> |
| | 66 | | /// The default compression level. |
| | 67 | | /// </summary> |
| | 68 | | public const int DEFAULT_COMPRESSION = -1; |
| | 69 | |
|
| | 70 | | /// <summary> |
| | 71 | | /// This level won't compress at all but output uncompressed blocks. |
| | 72 | | /// </summary> |
| | 73 | | public const int NO_COMPRESSION = 0; |
| | 74 | |
|
| | 75 | | /// <summary> |
| | 76 | | /// The compression method. This is the only method supported so far. |
| | 77 | | /// There is no need to use this constant at all. |
| | 78 | | /// </summary> |
| | 79 | | public const int DEFLATED = 8; |
| | 80 | | #endregion |
| | 81 | | #region Local Constants |
| | 82 | | private const int IS_SETDICT = 0x01; |
| | 83 | | private const int IS_FLUSHING = 0x04; |
| | 84 | | private const int IS_FINISHING = 0x08; |
| | 85 | |
|
| | 86 | | private const int INIT_STATE = 0x00; |
| | 87 | | private const int SETDICT_STATE = 0x01; |
| | 88 | | // private static int INIT_FINISHING_STATE = 0x08; |
| | 89 | | // private static int SETDICT_FINISHING_STATE = 0x09; |
| | 90 | | private const int BUSY_STATE = 0x10; |
| | 91 | | private const int FLUSHING_STATE = 0x14; |
| | 92 | | private const int FINISHING_STATE = 0x1c; |
| | 93 | | private const int FINISHED_STATE = 0x1e; |
| | 94 | | private const int CLOSED_STATE = 0x7f; |
| | 95 | | #endregion |
| | 96 | | #region Constructors |
| | 97 | | /// <summary> |
| | 98 | | /// Creates a new deflater with default compression level. |
| | 99 | | /// </summary> |
| 4 | 100 | | public Deflater() : this(DEFAULT_COMPRESSION, false) |
| | 101 | | { |
| | 102 | |
|
| 4 | 103 | | } |
| | 104 | |
|
| | 105 | | /// <summary> |
| | 106 | | /// Creates a new deflater with given compression level. |
| | 107 | | /// </summary> |
| | 108 | | /// <param name="level"> |
| | 109 | | /// the compression level, a value between NO_COMPRESSION |
| | 110 | | /// and BEST_COMPRESSION, or DEFAULT_COMPRESSION. |
| | 111 | | /// </param> |
| | 112 | | /// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception> |
| 0 | 113 | | public Deflater(int level) : this(level, false) |
| | 114 | | { |
| | 115 | |
|
| 0 | 116 | | } |
| | 117 | |
|
| | 118 | | /// <summary> |
| | 119 | | /// Creates a new deflater with given compression level. |
| | 120 | | /// </summary> |
| | 121 | | /// <param name="level"> |
| | 122 | | /// the compression level, a value between NO_COMPRESSION |
| | 123 | | /// and BEST_COMPRESSION. |
| | 124 | | /// </param> |
| | 125 | | /// <param name="noZlibHeaderOrFooter"> |
| | 126 | | /// true, if we should suppress the Zlib/RFC1950 header at the |
| | 127 | | /// beginning and the adler checksum at the end of the output. This is |
| | 128 | | /// useful for the GZIP/PKZIP formats. |
| | 129 | | /// </param> |
| | 130 | | /// <exception cref="System.ArgumentOutOfRangeException">if lvl is out of range.</exception> |
| 273 | 131 | | public Deflater(int level, bool noZlibHeaderOrFooter) |
| | 132 | | { |
| 273 | 133 | | if (level == DEFAULT_COMPRESSION) { |
| 100 | 134 | | level = 6; |
| 273 | 135 | | } else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) { |
| 0 | 136 | | throw new ArgumentOutOfRangeException(nameof(level)); |
| | 137 | | } |
| | 138 | |
|
| 273 | 139 | | pending = new DeflaterPending(); |
| 273 | 140 | | engine = new DeflaterEngine(pending); |
| 273 | 141 | | this.noZlibHeaderOrFooter = noZlibHeaderOrFooter; |
| 273 | 142 | | SetStrategy(DeflateStrategy.Default); |
| 273 | 143 | | SetLevel(level); |
| 273 | 144 | | Reset(); |
| 273 | 145 | | } |
| | 146 | | #endregion |
| | 147 | |
|
| | 148 | | /// <summary> |
| | 149 | | /// Resets the deflater. The deflater acts afterwards as if it was |
| | 150 | | /// just created with the same compression level and strategy as it |
| | 151 | | /// had before. |
| | 152 | | /// </summary> |
| | 153 | | public void Reset() |
| | 154 | | { |
| 392 | 155 | | state = (noZlibHeaderOrFooter ? BUSY_STATE : INIT_STATE); |
| 392 | 156 | | totalOut = 0; |
| 392 | 157 | | pending.Reset(); |
| 392 | 158 | | engine.Reset(); |
| 392 | 159 | | } |
| | 160 | |
|
| | 161 | | /// <summary> |
| | 162 | | /// Gets the current adler checksum of the data that was processed so far. |
| | 163 | | /// </summary> |
| | 164 | | public int Adler { |
| | 165 | | get { |
| 0 | 166 | | return engine.Adler; |
| | 167 | | } |
| | 168 | | } |
| | 169 | |
|
| | 170 | | /// <summary> |
| | 171 | | /// Gets the number of input bytes processed so far. |
| | 172 | | /// </summary> |
| | 173 | | public long TotalIn { |
| | 174 | | get { |
| 9 | 175 | | return engine.TotalIn; |
| | 176 | | } |
| | 177 | | } |
| | 178 | |
|
| | 179 | | /// <summary> |
| | 180 | | /// Gets the number of output bytes so far. |
| | 181 | | /// </summary> |
| | 182 | | public long TotalOut { |
| | 183 | | get { |
| 119 | 184 | | return totalOut; |
| | 185 | | } |
| | 186 | | } |
| | 187 | |
|
| | 188 | | /// <summary> |
| | 189 | | /// Flushes the current input block. Further calls to deflate() will |
| | 190 | | /// produce enough output to inflate everything in the current input |
| | 191 | | /// block. This is not part of Sun's JDK so I have made it package |
| | 192 | | /// private. It is used by DeflaterOutputStream to implement |
| | 193 | | /// flush(). |
| | 194 | | /// </summary> |
| | 195 | | public void Flush() |
| | 196 | | { |
| 30 | 197 | | state |= IS_FLUSHING; |
| 30 | 198 | | } |
| | 199 | |
|
| | 200 | | /// <summary> |
| | 201 | | /// Finishes the deflater with the current input block. It is an error |
| | 202 | | /// to give more input after this method was called. This method must |
| | 203 | | /// be called to force all bytes to be flushed. |
| | 204 | | /// </summary> |
| | 205 | | public void Finish() |
| | 206 | | { |
| 325 | 207 | | state |= (IS_FLUSHING | IS_FINISHING); |
| 325 | 208 | | } |
| | 209 | |
|
| | 210 | | /// <summary> |
| | 211 | | /// Returns true if the stream was finished and no more output bytes |
| | 212 | | /// are available. |
| | 213 | | /// </summary> |
| | 214 | | public bool IsFinished { |
| | 215 | | get { |
| 1898 | 216 | | return (state == FINISHED_STATE) && pending.IsFlushed; |
| | 217 | | } |
| | 218 | | } |
| | 219 | |
|
| | 220 | | /// <summary> |
| | 221 | | /// Returns true, if the input buffer is empty. |
| | 222 | | /// You should then call setInput(). |
| | 223 | | /// NOTE: This method can also return true when the stream |
| | 224 | | /// was finished. |
| | 225 | | /// </summary> |
| | 226 | | public bool IsNeedingInput { |
| | 227 | | get { |
| 16258 | 228 | | return engine.NeedsInput(); |
| | 229 | | } |
| | 230 | | } |
| | 231 | |
|
| | 232 | | /// <summary> |
| | 233 | | /// Sets the data which should be compressed next. This should be only |
| | 234 | | /// called when needsInput indicates that more input is needed. |
| | 235 | | /// If you call setInput when needsInput() returns false, the |
| | 236 | | /// previous input that is still pending will be thrown away. |
| | 237 | | /// The given byte array should not be changed, before needsInput() returns |
| | 238 | | /// true again. |
| | 239 | | /// This call is equivalent to <code>setInput(input, 0, input.length)</code>. |
| | 240 | | /// </summary> |
| | 241 | | /// <param name="input"> |
| | 242 | | /// the buffer containing the input data. |
| | 243 | | /// </param> |
| | 244 | | /// <exception cref="System.InvalidOperationException"> |
| | 245 | | /// if the buffer was finished() or ended(). |
| | 246 | | /// </exception> |
| | 247 | | public void SetInput(byte[] input) |
| | 248 | | { |
| 0 | 249 | | SetInput(input, 0, input.Length); |
| 0 | 250 | | } |
| | 251 | |
|
| | 252 | | /// <summary> |
| | 253 | | /// Sets the data which should be compressed next. This should be |
| | 254 | | /// only called when needsInput indicates that more input is needed. |
| | 255 | | /// The given byte array should not be changed, before needsInput() returns |
| | 256 | | /// true again. |
| | 257 | | /// </summary> |
| | 258 | | /// <param name="input"> |
| | 259 | | /// the buffer containing the input data. |
| | 260 | | /// </param> |
| | 261 | | /// <param name="offset"> |
| | 262 | | /// the start of the data. |
| | 263 | | /// </param> |
| | 264 | | /// <param name="count"> |
| | 265 | | /// the number of data bytes of input. |
| | 266 | | /// </param> |
| | 267 | | /// <exception cref="System.InvalidOperationException"> |
| | 268 | | /// if the buffer was Finish()ed or if previous input is still pending. |
| | 269 | | /// </exception> |
| | 270 | | public void SetInput(byte[] input, int offset, int count) |
| | 271 | | { |
| 4479 | 272 | | if ((state & IS_FINISHING) != 0) { |
| 0 | 273 | | throw new InvalidOperationException("Finish() already called"); |
| | 274 | | } |
| 4479 | 275 | | engine.SetInput(input, offset, count); |
| 4479 | 276 | | } |
| | 277 | |
|
| | 278 | | /// <summary> |
| | 279 | | /// Sets the compression level. There is no guarantee of the exact |
| | 280 | | /// position of the change, but if you call this when needsInput is |
| | 281 | | /// true the change of compression level will occur somewhere near |
| | 282 | | /// before the end of the so far given input. |
| | 283 | | /// </summary> |
| | 284 | | /// <param name="level"> |
| | 285 | | /// the new compression level. |
| | 286 | | /// </param> |
| | 287 | | public void SetLevel(int level) |
| | 288 | | { |
| 447 | 289 | | if (level == DEFAULT_COMPRESSION) { |
| 59 | 290 | | level = 6; |
| 447 | 291 | | } else if (level < NO_COMPRESSION || level > BEST_COMPRESSION) { |
| 0 | 292 | | throw new ArgumentOutOfRangeException(nameof(level)); |
| | 293 | | } |
| | 294 | |
|
| 447 | 295 | | if (this.level != level) { |
| 327 | 296 | | this.level = level; |
| 327 | 297 | | engine.SetLevel(level); |
| | 298 | | } |
| 447 | 299 | | } |
| | 300 | |
|
| | 301 | | /// <summary> |
| | 302 | | /// Get current compression level |
| | 303 | | /// </summary> |
| | 304 | | /// <returns>Returns the current compression level</returns> |
| | 305 | | public int GetLevel() |
| | 306 | | { |
| 3 | 307 | | return level; |
| | 308 | | } |
| | 309 | |
|
| | 310 | | /// <summary> |
| | 311 | | /// Sets the compression strategy. Strategy is one of |
| | 312 | | /// DEFAULT_STRATEGY, HUFFMAN_ONLY and FILTERED. For the exact |
| | 313 | | /// position where the strategy is changed, the same as for |
| | 314 | | /// SetLevel() applies. |
| | 315 | | /// </summary> |
| | 316 | | /// <param name="strategy"> |
| | 317 | | /// The new compression strategy. |
| | 318 | | /// </param> |
| | 319 | | public void SetStrategy(DeflateStrategy strategy) |
| | 320 | | { |
| 273 | 321 | | engine.Strategy = strategy; |
| 273 | 322 | | } |
| | 323 | |
|
| | 324 | | /// <summary> |
| | 325 | | /// Deflates the current input block with to the given array. |
| | 326 | | /// </summary> |
| | 327 | | /// <param name="output"> |
| | 328 | | /// The buffer where compressed data is stored |
| | 329 | | /// </param> |
| | 330 | | /// <returns> |
| | 331 | | /// The number of compressed bytes added to the output, or 0 if either |
| | 332 | | /// IsNeedingInput() or IsFinished returns true or length is zero. |
| | 333 | | /// </returns> |
| | 334 | | public int Deflate(byte[] output) |
| | 335 | | { |
| 0 | 336 | | return Deflate(output, 0, output.Length); |
| | 337 | | } |
| | 338 | |
|
| | 339 | | /// <summary> |
| | 340 | | /// Deflates the current input block to the given array. |
| | 341 | | /// </summary> |
| | 342 | | /// <param name="output"> |
| | 343 | | /// Buffer to store the compressed data. |
| | 344 | | /// </param> |
| | 345 | | /// <param name="offset"> |
| | 346 | | /// Offset into the output array. |
| | 347 | | /// </param> |
| | 348 | | /// <param name="length"> |
| | 349 | | /// The maximum number of bytes that may be stored. |
| | 350 | | /// </param> |
| | 351 | | /// <returns> |
| | 352 | | /// The number of compressed bytes added to the output, or 0 if either |
| | 353 | | /// needsInput() or finished() returns true or length is zero. |
| | 354 | | /// </returns> |
| | 355 | | /// <exception cref="System.InvalidOperationException"> |
| | 356 | | /// If Finish() was previously called. |
| | 357 | | /// </exception> |
| | 358 | | /// <exception cref="System.ArgumentOutOfRangeException"> |
| | 359 | | /// If offset or length don't match the array length. |
| | 360 | | /// </exception> |
| | 361 | | public int Deflate(byte[] output, int offset, int length) |
| | 362 | | { |
| 12717 | 363 | | int origLength = length; |
| | 364 | |
|
| 12717 | 365 | | if (state == CLOSED_STATE) { |
| 0 | 366 | | throw new InvalidOperationException("Deflater closed"); |
| | 367 | | } |
| | 368 | |
|
| 12717 | 369 | | if (state < BUSY_STATE) { |
| | 370 | | // output header |
| 14 | 371 | | int header = (DEFLATED + |
| 14 | 372 | | ((DeflaterConstants.MAX_WBITS - 8) << 4)) << 8; |
| 14 | 373 | | int level_flags = (level - 1) >> 1; |
| 14 | 374 | | if (level_flags < 0 || level_flags > 3) { |
| 2 | 375 | | level_flags = 3; |
| | 376 | | } |
| 14 | 377 | | header |= level_flags << 6; |
| 14 | 378 | | if ((state & IS_SETDICT) != 0) { |
| | 379 | | // Dictionary was set |
| 0 | 380 | | header |= DeflaterConstants.PRESET_DICT; |
| | 381 | | } |
| 14 | 382 | | header += 31 - (header % 31); |
| | 383 | |
|
| 14 | 384 | | pending.WriteShortMSB(header); |
| 14 | 385 | | if ((state & IS_SETDICT) != 0) { |
| 0 | 386 | | int chksum = engine.Adler; |
| 0 | 387 | | engine.ResetAdler(); |
| 0 | 388 | | pending.WriteShortMSB(chksum >> 16); |
| 0 | 389 | | pending.WriteShortMSB(chksum & 0xffff); |
| | 390 | | } |
| | 391 | |
|
| 14 | 392 | | state = BUSY_STATE | (state & (IS_FLUSHING | IS_FINISHING)); |
| | 393 | | } |
| | 394 | |
|
| | 395 | | for (;;) { |
| 13248 | 396 | | int count = pending.Flush(output, offset, length); |
| 13248 | 397 | | offset += count; |
| 13248 | 398 | | totalOut += count; |
| 13248 | 399 | | length -= count; |
| | 400 | |
|
| 13248 | 401 | | if (length == 0 || state == FINISHED_STATE) { |
| | 402 | | break; |
| | 403 | | } |
| | 404 | |
|
| 4878 | 405 | | if (!engine.Deflate((state & IS_FLUSHING) != 0, (state & IS_FINISHING) != 0)) { |
| 4652 | 406 | | switch (state) { |
| | 407 | | case BUSY_STATE: |
| | 408 | | // We need more input now |
| 4347 | 409 | | return origLength - length; |
| | 410 | | case FLUSHING_STATE: |
| 0 | 411 | | if (level != NO_COMPRESSION) { |
| | 412 | | /* We have to supply some lookahead. 8 bit lookahead |
| | 413 | | * is needed by the zlib inflater, and we must fill |
| | 414 | | * the next byte, so that all bits are flushed. |
| | 415 | | */ |
| 0 | 416 | | int neededbits = 8 + ((-pending.BitCount) & 7); |
| 0 | 417 | | while (neededbits > 0) { |
| | 418 | | /* write a static tree block consisting solely of |
| | 419 | | * an EOF: |
| | 420 | | */ |
| 0 | 421 | | pending.WriteBits(2, 10); |
| 0 | 422 | | neededbits -= 10; |
| | 423 | | } |
| | 424 | | } |
| 0 | 425 | | state = BUSY_STATE; |
| 0 | 426 | | break; |
| | 427 | | case FINISHING_STATE: |
| 305 | 428 | | pending.AlignToByte(); |
| | 429 | |
|
| | 430 | | // Compressed data is complete. Write footer information if required. |
| 305 | 431 | | if (!noZlibHeaderOrFooter) { |
| 14 | 432 | | int adler = engine.Adler; |
| 14 | 433 | | pending.WriteShortMSB(adler >> 16); |
| 14 | 434 | | pending.WriteShortMSB(adler & 0xffff); |
| | 435 | | } |
| 305 | 436 | | state = FINISHED_STATE; |
| 305 | 437 | | break; |
| | 438 | | } |
| | 439 | | } |
| | 440 | | } |
| 8370 | 441 | | return origLength - length; |
| | 442 | | } |
| | 443 | |
|
| | 444 | | /// <summary> |
| | 445 | | /// Sets the dictionary which should be used in the deflate process. |
| | 446 | | /// This call is equivalent to <code>setDictionary(dict, 0, dict.Length)</code>. |
| | 447 | | /// </summary> |
| | 448 | | /// <param name="dictionary"> |
| | 449 | | /// the dictionary. |
| | 450 | | /// </param> |
| | 451 | | /// <exception cref="System.InvalidOperationException"> |
| | 452 | | /// if SetInput () or Deflate () were already called or another dictionary was already set. |
| | 453 | | /// </exception> |
| | 454 | | public void SetDictionary(byte[] dictionary) |
| | 455 | | { |
| 0 | 456 | | SetDictionary(dictionary, 0, dictionary.Length); |
| 0 | 457 | | } |
| | 458 | |
|
| | 459 | | /// <summary> |
| | 460 | | /// Sets the dictionary which should be used in the deflate process. |
| | 461 | | /// The dictionary is a byte array containing strings that are |
| | 462 | | /// likely to occur in the data which should be compressed. The |
| | 463 | | /// dictionary is not stored in the compressed output, only a |
| | 464 | | /// checksum. To decompress the output you need to supply the same |
| | 465 | | /// dictionary again. |
| | 466 | | /// </summary> |
| | 467 | | /// <param name="dictionary"> |
| | 468 | | /// The dictionary data |
| | 469 | | /// </param> |
| | 470 | | /// <param name="index"> |
| | 471 | | /// The index where dictionary information commences. |
| | 472 | | /// </param> |
| | 473 | | /// <param name="count"> |
| | 474 | | /// The number of bytes in the dictionary. |
| | 475 | | /// </param> |
| | 476 | | /// <exception cref="System.InvalidOperationException"> |
| | 477 | | /// If SetInput () or Deflate() were already called or another dictionary was already set. |
| | 478 | | /// </exception> |
| | 479 | | public void SetDictionary(byte[] dictionary, int index, int count) |
| | 480 | | { |
| 0 | 481 | | if (state != INIT_STATE) { |
| 0 | 482 | | throw new InvalidOperationException(); |
| | 483 | | } |
| | 484 | |
|
| 0 | 485 | | state = SETDICT_STATE; |
| 0 | 486 | | engine.SetDictionary(dictionary, index, count); |
| 0 | 487 | | } |
| | 488 | |
|
| | 489 | | #region Instance Fields |
| | 490 | | /// <summary> |
| | 491 | | /// Compression level. |
| | 492 | | /// </summary> |
| | 493 | | int level; |
| | 494 | |
|
| | 495 | | /// <summary> |
| | 496 | | /// If true no Zlib/RFC1950 headers or footers are generated |
| | 497 | | /// </summary> |
| | 498 | | bool noZlibHeaderOrFooter; |
| | 499 | |
|
| | 500 | | /// <summary> |
| | 501 | | /// The current state. |
| | 502 | | /// </summary> |
| | 503 | | int state; |
| | 504 | |
|
| | 505 | | /// <summary> |
| | 506 | | /// The total bytes of output written. |
| | 507 | | /// </summary> |
| | 508 | | long totalOut; |
| | 509 | |
|
| | 510 | | /// <summary> |
| | 511 | | /// The pending output. |
| | 512 | | /// </summary> |
| | 513 | | DeflaterPending pending; |
| | 514 | |
|
| | 515 | | /// <summary> |
| | 516 | | /// The deflater engine. |
| | 517 | | /// </summary> |
| | 518 | | DeflaterEngine engine; |
| | 519 | | #endregion |
| | 520 | | } |
| | 521 | | } |