You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

462 lines
13 KiB

  1. {*******************************************************}
  2. { }
  3. { Delphi Supplemental Components }
  4. { ZLIB Data Compression Interface Unit }
  5. { }
  6. { Copyright (c) 1997 Borland International }
  7. { Copyright (c) 1998 Jacques Nomssi Nzali }
  8. { Copyright (c) 2006 Graeme Geldenhuys }
  9. { }
  10. {*******************************************************}
  11. unit dzlib;
  12. {$WARNINGS OFF}
  13. {$HINTS OFF}
  14. {$MODE OBJFPC}{$H+}
  15. { At least FPC 2.0.2 is required }
  16. {$if defined(ver1) or (defined(ver2_0) and (fpc_patch<2))}
  17. {$fatal Lazarus requires at least FPC 2.0.2}
  18. {$ELSEIF (defined(ver2_0) and (fpc_patch=2))}
  19. {$DEFINE FPC202}
  20. {$ELSE}
  21. {$DEFINE FPC202OrAbove}
  22. {$ENDIF}
  23. interface
  24. uses
  25. zbase, Sysutils, Classes;
  26. type
  27. { Internal structure. Ignore. }
  28. TZStreamRec = z_stream;
  29. const
  30. FBufSize = 8192;
  31. type
  32. { Abstract ancestor class }
  33. TCustomZlibStream = class(TStream)
  34. private
  35. FStrm: TStream;
  36. FStrmPos: Integer;
  37. FOnProgress: TNotifyEvent;
  38. FZRec: TZStreamRec;
  39. FBuffer: array [0..FBufSize-1] of Char;
  40. protected
  41. procedure Progress(Sender: TObject); dynamic;
  42. property OnProgress: TNotifyEvent read FOnProgress write FOnProgress;
  43. constructor Create(Strm: TStream);
  44. end;
  45. { TCompressionStream compresses data on the fly as data is written to it, and
  46. stores the compressed data to another stream.
  47. TCompressionStream is write-only and strictly sequential. Reading from the
  48. stream will raise an exception. Using Seek to move the stream pointer
  49. will raise an exception.
  50. Output data is cached internally, written to the output stream only when
  51. the internal output buffer is full. All pending output data is flushed
  52. when the stream is destroyed.
  53. The Position property returns the number of uncompressed bytes of
  54. data that have been written to the stream so far.
  55. CompressionRate returns the on-the-fly percentage by which the original
  56. data has been compressed: (1 - (CompressedBytes / UncompressedBytes)) * 100
  57. If raw data size = 100 and compressed data size = 25, the CompressionRate
  58. is 75%
  59. The OnProgress event is called each time the output buffer is filled and
  60. written to the output stream. This is useful for updating a progress
  61. indicator when you are writing a large chunk of data to the compression
  62. stream in a single call.}
  63. TCompressionLevel = (clNone, clFastest, clDefault, clMax);
  64. TCompressionStream = class(TCustomZlibStream)
  65. private
  66. function GetCompressionRate: Single;
  67. public
  68. constructor Create(CompressionLevel: TCompressionLevel; Dest: TStream);
  69. destructor Destroy; override;
  70. function Read(var Buffer; Count: Longint): Longint; override;
  71. function Write(const Buffer; Count: Longint): Longint; override;
  72. function Seek(Offset: Longint; Origin: Word): Longint; override;
  73. property CompressionRate: Single read GetCompressionRate;
  74. property OnProgress;
  75. end;
  76. { TDecompressionStream decompresses data on the fly as data is read from it.
  77. Compressed data comes from a separate source stream. TDecompressionStream
  78. is read-only and unidirectional; you can seek forward in the stream, but not
  79. backwards. The special case of setting the stream position to zero is
  80. allowed. Seeking forward decompresses data until the requested position in
  81. the uncompressed data has been reached. Seeking backwards, seeking relative
  82. to the end of the stream, requesting the size of the stream, and writing to
  83. the stream will raise an exception.
  84. The Position property returns the number of bytes of uncompressed data that
  85. have been read from the stream so far.
  86. The OnProgress event is called each time the internal input buffer of
  87. compressed data is exhausted and the next block is read from the input stream.
  88. This is useful for updating a progress indicator when you are reading a
  89. large chunk of data from the decompression stream in a single call.}
  90. TDecompressionStream = class(TCustomZlibStream)
  91. public
  92. constructor Create(Source: TStream);
  93. destructor Destroy; override;
  94. function Read(var Buffer; Count: Longint): Longint; override;
  95. function Write(const Buffer; Count: Longint): Longint; override;
  96. function Seek(Offset: Longint; Origin: Word): Longint; override;
  97. property OnProgress;
  98. end;
  99. { CompressBuf compresses data, buffer to buffer, in one call.
  100. In: InBuf = ptr to compressed data
  101. InBytes = number of bytes in InBuf
  102. Out: OutBuf = ptr to newly allocated buffer containing decompressed data
  103. OutBytes = number of bytes in OutBuf }
  104. procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
  105. out OutBuf: Pointer; out OutBytes: Integer);
  106. { DecompressBuf decompresses data, buffer to buffer, in one call.
  107. In: InBuf = ptr to compressed data
  108. InBytes = number of bytes in InBuf
  109. OutEstimate = zero, or est. size of the decompressed data
  110. Out: OutBuf = ptr to newly allocated buffer containing decompressed data
  111. OutBytes = number of bytes in OutBuf }
  112. procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
  113. OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
  114. type
  115. EZlibError = class(Exception);
  116. ECompressionError = class(EZlibError);
  117. EDecompressionError = class(EZlibError);
  118. implementation
  119. uses
  120. {$ifdef fpc202}
  121. zutil,
  122. {$endif}
  123. zDeflate, zInflate;
  124. function zlibAllocMem(AppData: Pointer; Items, Size: Cardinal): Pointer;
  125. begin
  126. GetMem(Result, Items*Size);
  127. end;
  128. procedure zlibFreeMem(AppData, Block: Pointer);
  129. begin
  130. FreeMem(Block);
  131. end;
  132. function zlibCheck(code: Integer): Integer;
  133. begin
  134. Result := code;
  135. if code < 0 then
  136. raise EZlibError.Create('error'); {!!}
  137. end;
  138. function CCheck(code: Integer): Integer;
  139. begin
  140. Result := code;
  141. if code < 0 then
  142. raise ECompressionError.Create('error'); {!!}
  143. end;
  144. function DCheck(code: Integer): Integer;
  145. begin
  146. Result := code;
  147. if code < 0 then
  148. raise EDecompressionError.Create('error'); {!!}
  149. end;
  150. procedure CompressBuf(const InBuf: Pointer; InBytes: Integer;
  151. out OutBuf: Pointer; out OutBytes: Integer);
  152. var
  153. strm: TZStreamRec;
  154. P: Pointer;
  155. begin
  156. FillChar(strm, sizeof(strm), 0);
  157. {$ifdef fpc202}
  158. strm.zalloc := @zlibAllocMem;
  159. strm.zfree := @zlibFreeMem;
  160. {$endif}
  161. OutBytes := ((InBytes + (InBytes div 10) + 12) + 255) and not 255;
  162. GetMem(OutBuf, OutBytes);
  163. try
  164. strm.next_in := InBuf;
  165. strm.avail_in := InBytes;
  166. strm.next_out := OutBuf;
  167. strm.avail_out := OutBytes;
  168. CCheck(deflateInit_(@strm, Z_BEST_COMPRESSION, zlib_version, sizeof(strm)));
  169. try
  170. while deflate(strm, Z_FINISH) <> Z_STREAM_END do
  171. begin
  172. P := OutBuf;
  173. Inc(OutBytes, 256);
  174. ReallocMem(OutBuf, OutBytes);
  175. strm.next_out := {$ifdef fpc202}PBytef{$else}PByte{$endif}(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P)));
  176. strm.avail_out := 256;
  177. end;
  178. finally
  179. CCheck(deflateEnd(strm));
  180. end;
  181. ReallocMem(OutBuf, strm.total_out);
  182. OutBytes := strm.total_out;
  183. except
  184. zlibFreeMem(NIL, OutBuf);
  185. raise
  186. end;
  187. end;
  188. procedure DecompressBuf(const InBuf: Pointer; InBytes: Integer;
  189. OutEstimate: Integer; out OutBuf: Pointer; out OutBytes: Integer);
  190. var
  191. strm: TZStreamRec;
  192. P: Pointer;
  193. BufInc: Integer;
  194. begin
  195. FillChar(strm, sizeof(strm), 0);
  196. {$ifdef fpc202}
  197. strm.zalloc := @zlibAllocMem;
  198. strm.zfree := @zlibFreeMem;
  199. {$endif}
  200. BufInc := (InBytes + 255) and not 255;
  201. if OutEstimate = 0 then
  202. OutBytes := BufInc
  203. else
  204. OutBytes := OutEstimate;
  205. GetMem(OutBuf, OutBytes);
  206. try
  207. strm.next_in := InBuf;
  208. strm.avail_in := InBytes;
  209. strm.next_out := OutBuf;
  210. strm.avail_out := OutBytes;
  211. DCheck(inflateInit_(@strm, zlib_version, sizeof(strm)));
  212. try
  213. while inflate(strm, Z_FINISH) <> Z_STREAM_END do
  214. begin
  215. P := OutBuf;
  216. Inc(OutBytes, BufInc);
  217. ReallocMem(OutBuf, OutBytes);
  218. strm.next_out := {$ifdef fpc202}pBytef{$else}PByte{$endif}(Integer(OutBuf) + (Integer(strm.next_out) - Integer(P)));
  219. strm.avail_out := BufInc;
  220. end;
  221. finally
  222. DCheck(inflateEnd(strm));
  223. end;
  224. ReallocMem(OutBuf, strm.total_out);
  225. OutBytes := strm.total_out;
  226. except
  227. zlibFreeMem(NIL, OutBuf);
  228. raise
  229. end;
  230. end;
  231. { TCustomZlibStream }
  232. constructor TCustomZLibStream.Create(Strm: TStream);
  233. begin
  234. inherited Create;
  235. FStrm := Strm;
  236. FStrmPos := Strm.Position;
  237. {$ifdef fpc202}
  238. FZRec.zalloc := @zlibAllocMem;
  239. FZRec.zfree := @zlibFreeMem;
  240. {$endif}
  241. end;
  242. procedure TCustomZLibStream.Progress(Sender: TObject);
  243. begin
  244. if Assigned(FOnProgress) then FOnProgress(Sender);
  245. end;
  246. { TCompressionStream }
  247. constructor TCompressionStream.Create(CompressionLevel: TCompressionLevel;
  248. Dest: TStream);
  249. const
  250. Levels: array [TCompressionLevel] of ShortInt =
  251. (Z_NO_COMPRESSION, Z_BEST_SPEED, Z_DEFAULT_COMPRESSION, Z_BEST_COMPRESSION);
  252. begin
  253. inherited Create(Dest);
  254. FZRec.next_out := PByte(@FBuffer);
  255. FZRec.avail_out := sizeof(FBuffer);
  256. CCheck(deflateInit_(@FZRec, Levels[CompressionLevel], zlib_version, sizeof(FZRec)));
  257. end;
  258. destructor TCompressionStream.Destroy;
  259. begin
  260. FZRec.next_in := nil;
  261. FZRec.avail_in := 0;
  262. try
  263. if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
  264. while (CCheck(deflate(FZRec, Z_FINISH)) <> Z_STREAM_END)
  265. and (FZRec.avail_out = 0) do
  266. begin
  267. FStrm.WriteBuffer(FBuffer, sizeof(FBuffer));
  268. FZRec.next_out := PByte(@FBuffer);
  269. FZRec.avail_out := sizeof(FBuffer);
  270. end;
  271. if FZRec.avail_out < sizeof(FBuffer) then
  272. FStrm.WriteBuffer(FBuffer, sizeof(FBuffer) - FZRec.avail_out);
  273. finally
  274. deflateEnd(FZRec);
  275. end;
  276. inherited Destroy;
  277. end;
  278. function TCompressionStream.Read(var Buffer; Count: Longint): Longint;
  279. begin
  280. raise ECompressionError.Create('Invalid stream operation');
  281. end;
  282. function TCompressionStream.Write(const Buffer; Count: Longint): Longint;
  283. begin
  284. FZRec.next_in := @Buffer;
  285. FZRec.avail_in := Count;
  286. if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
  287. while (FZRec.avail_in > 0) do
  288. begin
  289. CCheck(deflate(FZRec, 0));
  290. if FZRec.avail_out = 0 then
  291. begin
  292. FStrm.WriteBuffer(FBuffer, sizeof(FBuffer));
  293. FZRec.next_out := PByte(@FBuffer);
  294. FZRec.avail_out := sizeof(FBuffer);
  295. FStrmPos := FStrm.Position;
  296. Progress(Self);
  297. end;
  298. end;
  299. Result := Count;
  300. end;
  301. function TCompressionStream.Seek(Offset: Longint; Origin: Word): Longint;
  302. begin
  303. if (Offset = 0) and (Origin = soFromCurrent) then
  304. Result := FZRec.total_in
  305. else
  306. raise ECompressionError.Create('Invalid stream operation');
  307. end;
  308. function TCompressionStream.GetCompressionRate: Single;
  309. begin
  310. if FZRec.total_in = 0 then
  311. Result := 0
  312. else
  313. Result := (1.0 - (FZRec.total_out / FZRec.total_in)) * 100.0;
  314. end;
  315. { TDecompressionStream }
  316. constructor TDecompressionStream.Create(Source: TStream);
  317. begin
  318. inherited Create(Source);
  319. FZRec.next_in := PByte(@FBuffer);
  320. FZRec.avail_in := 0;
  321. DCheck(inflateInit_(@FZRec, zlib_version, sizeof(FZRec)));
  322. end;
  323. destructor TDecompressionStream.Destroy;
  324. begin
  325. inflateEnd(FZRec);
  326. inherited Destroy;
  327. end;
  328. function TDecompressionStream.Read(var Buffer; Count: Longint): Longint;
  329. begin
  330. FZRec.next_out := @Buffer;
  331. FZRec.avail_out := Count;
  332. if FStrm.Position <> FStrmPos then FStrm.Position := FStrmPos;
  333. while (FZRec.avail_out > 0) do
  334. begin
  335. if FZRec.avail_in = 0 then
  336. begin
  337. FZRec.avail_in := FStrm.Read(FBuffer, sizeof(FBuffer));
  338. if FZRec.avail_in = 0 then
  339. begin
  340. Result := Count - FZRec.avail_out;
  341. Exit;
  342. end;
  343. FZRec.next_in := PByte(@FBuffer);
  344. FStrmPos := FStrm.Position;
  345. Progress(Self);
  346. end;
  347. CCheck(inflate(FZRec, 0));
  348. end;
  349. Result := Count;
  350. end;
  351. function TDecompressionStream.Write(const Buffer; Count: Longint): Longint;
  352. begin
  353. raise EDecompressionError.Create('Invalid stream operation');
  354. end;
  355. function TDecompressionStream.Seek(Offset: Longint; Origin: Word): Longint;
  356. var
  357. I: Integer;
  358. Buf: array [0..4095] of Char;
  359. begin
  360. if (Offset = 0) and (Origin = soFromBeginning) then
  361. begin
  362. DCheck(inflateReset(FZRec));
  363. FZRec.next_in := PByte(@FBuffer);
  364. FZRec.avail_in := 0;
  365. FStrm.Position := 0;
  366. FStrmPos := 0;
  367. end
  368. else if ((Offset >= 0) and (Origin = soFromCurrent)) or
  369. (((Offset - FZRec.total_out) > 0) and (Origin = soFromBeginning)) then
  370. begin
  371. if Origin = soFromBeginning then Dec(Offset, FZRec.total_out);
  372. if Offset > 0 then
  373. begin
  374. for I := 1 to Offset div sizeof(Buf) do
  375. ReadBuffer(Buf, sizeof(Buf));
  376. ReadBuffer(Buf, Offset mod sizeof(Buf));
  377. end;
  378. end
  379. else
  380. raise EDecompressionError.Create('Invalid stream operation');
  381. Result := FZRec.total_out;
  382. end;
  383. end.