summaryrefslogtreecommitdiff
path: root/dviware/dvisvgm/src/ZLibOutputStream.hpp
blob: a995dbf01e997a71c3d702d2496a8c5570792f57 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*************************************************************************
** ZLibOutputStream.hpp                                                 **
**                                                                      **
** This file is part of dvisvgm -- a fast DVI to SVG converter          **
** Copyright (C) 2005-2021 Martin Gieseking <martin.gieseking@uos.de>   **
**                                                                      **
** This program is free software; you can redistribute it and/or        **
** modify it under the terms of the GNU General Public License as       **
** published by the Free Software Foundation; either version 3 of       **
** the License, or (at your option) any later version.                  **
**                                                                      **
** This program is distributed in the hope that it will be useful, but  **
** WITHOUT ANY WARRANTY; without even the implied warranty of           **
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the         **
** GNU General Public License for more details.                         **
**                                                                      **
** You should have received a copy of the GNU General Public License    **
** along with this program; if not, see <http://www.gnu.org/licenses/>. **
*************************************************************************/

#ifndef ZLIBOUTPUTSTREAM_HPP
#define ZLIBOUTPUTSTREAM_HPP

#include <fstream>
#include <ostream>
#include <vector>
#include <zlib.h>
#include "MessageException.hpp"

#ifdef _WIN32
#  include <fcntl.h>
#  include <io.h>
#endif

struct ZLibException : public MessageException {
	explicit ZLibException (const std::string &msg) : MessageException(msg) {}
};

enum ZLibCompressionFormat {ZLIB_DEFLATE=0, ZLIB_GZIP=16};

class ZLibOutputBuffer : public std::streambuf {
	public:
		ZLibOutputBuffer () =default;

		ZLibOutputBuffer (std::streambuf *sbuf, ZLibCompressionFormat format, int zipLevel) {
			open(sbuf, format, zipLevel);
		}

		~ZLibOutputBuffer () override {
			close();
		}

		/** Opens the buffer for writing.
		 *  @param[in] sink stream buffer taking the compressed data
		 *  @param[in] format compression format (deflate or gzip)
		 *  @param[in] zipLevel compression level (1-9)
		 *  @return true if buffer is ready for writing */
		bool open (std::streambuf *sink, ZLibCompressionFormat format, int zipLevel) {
			if (sink) {
				_inbuf.reserve(4096);
				_zbuf.resize(4096);
				_zstream.zalloc = Z_NULL;
				_zstream.zfree = Z_NULL;
				_zstream.opaque = Z_NULL;
				zipLevel = std::max(1, std::min(9, zipLevel));
				if (deflateInit2(&_zstream, zipLevel, Z_DEFLATED, 15+format, 8, Z_DEFAULT_STRATEGY) != Z_OK)
					throw ZLibException("failed to initialize deflate compression");
				_sink = sink;
				_opened = true;
			}
			return _opened;
		}

		/** Flushes the remaining data, finishes the compression process, and
		 *  closes the buffer so that further output doesn't reach the sink. */
		void close () {
			close(true);
		}

		int_type overflow (int_type c) override {
			if (c == traits_type::eof()) {
				close();
			}
			else {
				if (_inbuf.size() == _inbuf.capacity())
					flush(Z_NO_FLUSH);
				_inbuf.push_back(static_cast<unsigned char>(c));
			}
			return c;
		}

		int sync () override {
			flush(Z_NO_FLUSH);
			return 0;
		}

	protected:
		/** Compresses the chunk of data present in the input buffer
		 *  and writes it to the assigned output stream.
		 *  @param[in] flushmode flush mode of deflate function (Z_NO_FLUSH or Z_FINISH)
		 *  @throws ZLibException if compression failed */
		void flush (int flushmode) {
			if (_opened) {
				_zstream.avail_in = static_cast<uInt>(_inbuf.size());
				_zstream.next_in = _inbuf.data();
				do {
					_zstream.avail_out = static_cast<uInt>(_zbuf.size());
					_zstream.next_out = _zbuf.data();
					int ret = deflate(&_zstream, flushmode);
					if (ret == Z_STREAM_ERROR) {
						close(false);
						throw ZLibException("stream error during data compression");
					}
					auto have = _zbuf.size()-_zstream.avail_out;
					_sink->sputn(reinterpret_cast<char*>(_zbuf.data()), have);
				} while (_zstream.avail_out == 0);
			}
			_inbuf.clear();
		}

		/** Closes the buffer so that further output doesn't reach the sink.
		 *  @param[in] finish if true, flushes the remaining data and finishes the compression process */
		void close (bool finish) {
			if (_opened) {
				if (finish)
					flush(Z_FINISH);
				deflateEnd(&_zstream);
				_sink = nullptr;
				_opened = false;
			}
		}

	private:
		z_stream _zstream;
		std::streambuf *_sink = nullptr;  ///< target buffer where the compressded data is flushed to
		std::vector<Bytef> _inbuf;  ///< buffer holding a chunk of data to be compressed
		std::vector<Bytef> _zbuf;   ///< buffer holding a chunk of compressed data
		bool _opened = false;       ///< true if ready to process the incoming data correctly
};


class ZLibOutputStream : private ZLibOutputBuffer, public std::ostream {
	public:
		ZLibOutputStream () : std::ostream(this) {}

		ZLibOutputStream (std::ostream &os, ZLibCompressionFormat format, int zipLevel)
			: ZLibOutputBuffer(os.rdbuf(), format, zipLevel), std::ostream(this) {}

		~ZLibOutputStream () override {close();}

		bool open (std::ostream &os, ZLibCompressionFormat format, int zipLevel) {
			ZLibOutputBuffer::close();
			return ZLibOutputBuffer::open(os.rdbuf(), format, zipLevel);
		}

		void close () {
			ZLibOutputBuffer::close();
		}
};


class ZLibOutputFileStream : public ZLibOutputStream {
	public:
		ZLibOutputFileStream (const std::string &fname, ZLibCompressionFormat format, int zipLevel)
			: _ofs(fname, std::ios::binary)
		{
			if (_ofs) {
				if (_ofs.rdbuf())
					open(_ofs, format, zipLevel);
				else
					_ofs.close();
			}
		}

		~ZLibOutputFileStream () override {close();}

	private:
		std::ofstream _ofs;
};

#endif