omnizip 0.3.18 → 0.3.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +18 -489
- data/lib/omnizip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: affda0906d52a418254670adb02e96312d6e268d1df4aafbba575ade503edfbe
|
|
4
|
+
data.tar.gz: ae4e84af21221350bf4d67a1b295546674527e091b500a715cb96f8547870990
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 91044c5b1067662c5177e2b8ca262ad0434e4a4cf94e9ea321251723499188fc4b57b3793e2ed88ce51ea406cdb1bfb6d31921ef3b08a7baaf4fd6e62102dc5b
|
|
7
|
+
data.tar.gz: b6614848a2eb450b748bca1d3d297b7bcd5751607124687237c75ebdeeb43b033ffe6d9af49af457e94aa1cace5ab615c3fe6d04f6a5bb3206a863249225ead6
|
|
@@ -20,506 +20,35 @@
|
|
|
20
20
|
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
21
21
|
# DEALINGS IN THE SOFTWARE.
|
|
22
22
|
|
|
23
|
-
require "stringio"
|
|
24
|
-
|
|
25
23
|
module Omnizip
|
|
26
24
|
module Implementations
|
|
27
25
|
module SevenZip
|
|
28
26
|
module LZMA2
|
|
29
|
-
# 7-Zip
|
|
30
|
-
#
|
|
31
|
-
# This encoder produces LZMA2 compressed data compatible with 7-Zip format.
|
|
32
|
-
# It uses the same LZMA encoding logic as XZ Utils, but with 7-Zip
|
|
33
|
-
# format requirements (no EOS marker, no padding).
|
|
27
|
+
# 7-Zip LZMA2 encoder.
|
|
34
28
|
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# -
|
|
38
|
-
#
|
|
29
|
+
# The LZMA2 stream itself is format-identical to the raw LZMA2
|
|
30
|
+
# layer of the XZ Utils encoder (chunks + 0x00 end marker); the
|
|
31
|
+
# 7-Zip container carries the method properties, so no property
|
|
32
|
+
# byte is written into the data stream (standalone: false).
|
|
39
33
|
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
MAX_COMPRESSED_CHUNK = 64 * 1024 # 64KB
|
|
54
|
-
|
|
55
|
-
# Encoding constants
|
|
56
|
-
UINT32_MAX = 0xFFFFFFFF
|
|
57
|
-
REPS = 4
|
|
58
|
-
MATCH_LEN_MIN = 2
|
|
59
|
-
|
|
60
|
-
attr_reader :dict_size, :lc, :lp, :pb, :standalone
|
|
61
|
-
|
|
62
|
-
# Initialize 7-Zip SDK LZMA2 encoder
|
|
63
|
-
#
|
|
64
|
-
# @param dict_size [Integer] Dictionary size (must be power of 2)
|
|
65
|
-
# @param lc [Integer] Literal context bits (0-8)
|
|
66
|
-
# @param lp [Integer] Literal position bits (0-4)
|
|
67
|
-
# @param pb [Integer] Position bits (0-4)
|
|
68
|
-
# @param standalone [Boolean] Include property byte (false for 7-Zip)
|
|
69
|
-
def initialize(dict_size:, lc: 3, lp: 0, pb: 2, standalone: false)
|
|
70
|
-
super
|
|
71
|
-
|
|
72
|
-
# Initialize shared state across all chunks
|
|
73
|
-
# Using XZ Utils components (tested and working)
|
|
74
|
-
|
|
75
|
-
@dictionary = Omnizip::Algorithms::LZMA::Dictionary.new(dict_size)
|
|
76
|
-
@state = Omnizip::Algorithms::LZMA::LZMAState.new(0)
|
|
77
|
-
@models = Omnizip::Algorithms::LZMA::XzProbabilityModels.new(lc,
|
|
78
|
-
lp, pb)
|
|
79
|
-
@match_finder = Omnizip::Algorithms::LZMA::MatchFinder.new(@dictionary)
|
|
80
|
-
@optimal = Omnizip::Algorithms::LZMA::OptimalEncoder.new(mode: :fast)
|
|
81
|
-
|
|
82
|
-
# Track previous byte for literal context
|
|
83
|
-
@prev_byte = 0
|
|
84
|
-
|
|
85
|
-
# First chunk always resets dictionary (7-Zip compatibility)
|
|
86
|
-
@need_dictionary_reset = true
|
|
87
|
-
@need_state_reset = false
|
|
88
|
-
@need_properties = true
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
# Encode data with LZMA2 compression
|
|
92
|
-
#
|
|
93
|
-
# @param data [String] Input data to compress
|
|
94
|
-
# @return [String] LZMA2 compressed data (7-Zip format)
|
|
95
|
-
def encode(data)
|
|
96
|
-
return "" if data.empty?
|
|
97
|
-
|
|
98
|
-
output = StringIO.new
|
|
99
|
-
output.set_encoding(Encoding::BINARY)
|
|
100
|
-
|
|
101
|
-
# Write property byte if standalone mode
|
|
102
|
-
if @standalone
|
|
103
|
-
prop_byte = encode_dict_size(@dict_size)
|
|
104
|
-
output.putc(prop_byte)
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Reset match finder state for each encoding session
|
|
108
|
-
@match_finder.reset
|
|
109
|
-
|
|
110
|
-
# Process in chunks
|
|
111
|
-
input = StringIO.new(data)
|
|
112
|
-
input.set_encoding(Encoding::BINARY)
|
|
113
|
-
|
|
114
|
-
while !input.eof?
|
|
115
|
-
chunk_data = input.read(MAX_UNCOMPRESSED_CHUNK)
|
|
116
|
-
break if chunk_data.nil? || chunk_data.empty?
|
|
117
|
-
|
|
118
|
-
chunk = encode_chunk(chunk_data)
|
|
119
|
-
output.write(chunk)
|
|
120
|
-
|
|
121
|
-
# Update reset flags for next chunk
|
|
122
|
-
@need_dictionary_reset = false
|
|
123
|
-
@need_state_reset = false
|
|
124
|
-
@need_properties = false
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
# End of stream marker (0x00)
|
|
128
|
-
output.write(Omnizip::Algorithms::LZMA2::LZMA2Chunk.end_chunk.to_bytes)
|
|
129
|
-
|
|
130
|
-
output.string
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Get implementation identifier
|
|
34
|
+
# This is a thin subclass rather than a copy: the XZ Utils
|
|
35
|
+
# encoder carries the fixes for global pos_state across
|
|
36
|
+
# chunks, announced state resets, the 64 KiB compressed-chunk
|
|
37
|
+
# cap (16-bit size field) and the range-encoder symbol-queue
|
|
38
|
+
# drain, which a duplicated implementation silently missed.
|
|
39
|
+
class Encoder < Implementations::XZUtils::LZMA2::Encoder
|
|
40
|
+
# 7-Zip callers pass the same option keys; only the property
|
|
41
|
+
# byte default differs (the container describes the coder).
|
|
42
|
+
def initialize(options = {})
|
|
43
|
+
super({ standalone: false }.merge(options))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Get implementation identifier.
|
|
134
47
|
#
|
|
135
48
|
# @return [Symbol] :seven_zip_sdk
|
|
136
49
|
def implementation_name
|
|
137
50
|
:seven_zip_sdk
|
|
138
51
|
end
|
|
139
|
-
|
|
140
|
-
private
|
|
141
|
-
|
|
142
|
-
# Encode a single chunk with LZMA2 compression
|
|
143
|
-
#
|
|
144
|
-
# Uses XZ Utils encoding logic (tested and compatible)
|
|
145
|
-
def encode_chunk(uncompressed_data)
|
|
146
|
-
compressed = try_compress(uncompressed_data)
|
|
147
|
-
|
|
148
|
-
# Decide: compressed vs uncompressed
|
|
149
|
-
# Use compressed if it's actually smaller
|
|
150
|
-
if compressed.bytesize >= uncompressed_data.bytesize
|
|
151
|
-
# Use uncompressed chunk
|
|
152
|
-
chunk = Omnizip::Algorithms::LZMA2::LZMA2Chunk.new(
|
|
153
|
-
chunk_type: :uncompressed,
|
|
154
|
-
uncompressed_data: uncompressed_data,
|
|
155
|
-
compressed_data: "",
|
|
156
|
-
need_dict_reset: @need_dictionary_reset,
|
|
157
|
-
need_state_reset: false,
|
|
158
|
-
need_props: false,
|
|
159
|
-
)
|
|
160
|
-
# After uncompressed chunk, next chunk needs state reset
|
|
161
|
-
@need_state_reset = true
|
|
162
|
-
else
|
|
163
|
-
# Use compressed chunk
|
|
164
|
-
chunk_properties = (((@pb * 5) + @lp) * 9) + @lc
|
|
165
|
-
chunk = Omnizip::Algorithms::LZMA2::LZMA2Chunk.new(
|
|
166
|
-
chunk_type: :compressed,
|
|
167
|
-
uncompressed_data: uncompressed_data,
|
|
168
|
-
compressed_data: compressed,
|
|
169
|
-
compressed_size: compressed.bytesize,
|
|
170
|
-
properties: chunk_properties,
|
|
171
|
-
need_dict_reset: @need_dictionary_reset,
|
|
172
|
-
need_state_reset: @need_state_reset,
|
|
173
|
-
need_props: true,
|
|
174
|
-
)
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Update dictionary with the chunk data
|
|
178
|
-
@dictionary.append(uncompressed_data)
|
|
179
|
-
|
|
180
|
-
# Update prev_byte for next chunk
|
|
181
|
-
if uncompressed_data.bytesize.positive?
|
|
182
|
-
@prev_byte = uncompressed_data.getbyte(uncompressed_data.bytesize - 1)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
chunk.to_bytes
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Try to compress data using LZMA
|
|
189
|
-
#
|
|
190
|
-
# Uses XZ Utils encoding components (tested and working)
|
|
191
|
-
def try_compress(data)
|
|
192
|
-
# Create output buffer
|
|
193
|
-
output_buffer = StringIO.new
|
|
194
|
-
output_buffer.set_encoding(Encoding::BINARY)
|
|
195
|
-
|
|
196
|
-
# Create range encoder
|
|
197
|
-
encoder = Omnizip::Algorithms::LZMA::XzRangeEncoder.new(output_buffer)
|
|
198
|
-
|
|
199
|
-
# Feed all data to match finder first
|
|
200
|
-
@match_finder.feed(data)
|
|
201
|
-
|
|
202
|
-
# Initialize hash table
|
|
203
|
-
match_len_max = 2
|
|
204
|
-
end_pos = [
|
|
205
|
-
@dictionary.buffer.bytesize + data.bytesize - match_len_max, 0
|
|
206
|
-
].max
|
|
207
|
-
@match_finder.skip(end_pos)
|
|
208
|
-
|
|
209
|
-
# Position in match finder's buffer for encoding
|
|
210
|
-
start_pos = @dictionary.buffer.bytesize
|
|
211
|
-
@current_start_pos = start_pos
|
|
212
|
-
|
|
213
|
-
pos = 0
|
|
214
|
-
while pos < data.bytesize
|
|
215
|
-
# Encode queued symbols if buffer getting full
|
|
216
|
-
if encoder.count > 20
|
|
217
|
-
encode_queued_symbols(encoder, output_buffer)
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
# Find matches at current position
|
|
221
|
-
match_pos = start_pos + pos
|
|
222
|
-
@match_finder.find_matches(match_pos)
|
|
223
|
-
|
|
224
|
-
# Get optimal encoding choice
|
|
225
|
-
distance, length = @optimal.find_optimal(
|
|
226
|
-
match_pos,
|
|
227
|
-
@match_finder,
|
|
228
|
-
@state,
|
|
229
|
-
@state.reps,
|
|
230
|
-
@models,
|
|
231
|
-
)
|
|
232
|
-
|
|
233
|
-
# Encode based on choice
|
|
234
|
-
if distance == UINT32_MAX || length == 1
|
|
235
|
-
encode_literal(data.getbyte(pos), encoder, pos)
|
|
236
|
-
pos += 1
|
|
237
|
-
elsif distance < REPS
|
|
238
|
-
encode_repeated_match(distance, length, encoder, pos, match_pos)
|
|
239
|
-
pos += length
|
|
240
|
-
else
|
|
241
|
-
actual_distance = distance - REPS
|
|
242
|
-
encode_match(actual_distance, length, encoder, pos, match_pos,
|
|
243
|
-
data)
|
|
244
|
-
pos += length
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# Flush encoder
|
|
249
|
-
encode_queued_symbols(encoder, output_buffer)
|
|
250
|
-
encoder.queue_flush
|
|
251
|
-
encode_queued_symbols(encoder, output_buffer)
|
|
252
|
-
|
|
253
|
-
output_buffer.string
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# Encode queued symbols to output
|
|
257
|
-
def encode_queued_symbols(encoder, output)
|
|
258
|
-
return if encoder.none?
|
|
259
|
-
|
|
260
|
-
temp_buffer = "\0" * 10000
|
|
261
|
-
out_pos = Omnizip::Algorithms::LZMA::IntRef.new(0)
|
|
262
|
-
|
|
263
|
-
size_before = output.size
|
|
264
|
-
|
|
265
|
-
encoder.encode_symbols(temp_buffer, out_pos, 10000)
|
|
266
|
-
|
|
267
|
-
if out_pos.value.positive?
|
|
268
|
-
output.write(StringCompat.byteslice(temp_buffer, 0,
|
|
269
|
-
out_pos.value))
|
|
270
|
-
end
|
|
271
|
-
|
|
272
|
-
output.size - size_before
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
# Compatibility helper for Ruby 3.0-3.1
|
|
276
|
-
module StringCompat
|
|
277
|
-
if "".respond_to?(:byteslice)
|
|
278
|
-
def self.byteslice(string, start, length)
|
|
279
|
-
string.byteslice(start, length)
|
|
280
|
-
end
|
|
281
|
-
else
|
|
282
|
-
def self.byteslice(string, start, length)
|
|
283
|
-
string.bytes[start, length]&.pack("C*") || ""
|
|
284
|
-
end
|
|
285
|
-
end
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
# Encode literal byte
|
|
289
|
-
def encode_literal(symbol, encoder, pos)
|
|
290
|
-
pos_state = pos & ((1 << @pb) - 1)
|
|
291
|
-
|
|
292
|
-
prob_is_match = @models.is_match[@state.value][pos_state]
|
|
293
|
-
encoder.queue_bit(prob_is_match, 0)
|
|
294
|
-
|
|
295
|
-
literal_offset = get_literal_state(pos, @prev_byte)
|
|
296
|
-
use_matched = @state.use_matched_literal?
|
|
297
|
-
|
|
298
|
-
@state.update_literal!
|
|
299
|
-
|
|
300
|
-
if use_matched
|
|
301
|
-
match_pos = @current_start_pos + pos
|
|
302
|
-
match_byte_pos = match_pos - @state.reps[0] - 1
|
|
303
|
-
match_byte = @match_finder.buffer.getbyte(match_byte_pos) if match_byte_pos >= 0 && match_byte_pos < @match_finder.buffer.bytesize
|
|
304
|
-
|
|
305
|
-
if match_byte.nil?
|
|
306
|
-
encode_normal_literal(literal_offset, symbol, encoder)
|
|
307
|
-
else
|
|
308
|
-
encode_matched_literal(literal_offset, match_byte, symbol,
|
|
309
|
-
encoder)
|
|
310
|
-
end
|
|
311
|
-
else
|
|
312
|
-
encode_normal_literal(literal_offset, symbol, encoder)
|
|
313
|
-
end
|
|
314
|
-
|
|
315
|
-
@prev_byte = symbol
|
|
316
|
-
end
|
|
317
|
-
|
|
318
|
-
# Encode normal match
|
|
319
|
-
def encode_match(distance, length, encoder, pos, match_pos,
|
|
320
|
-
_input_data)
|
|
321
|
-
pos_state = pos & ((1 << @pb) - 1)
|
|
322
|
-
|
|
323
|
-
prob_is_match = @models.is_match[@state.value][pos_state]
|
|
324
|
-
encoder.queue_bit(prob_is_match, 1)
|
|
325
|
-
|
|
326
|
-
prob_is_rep = @models.is_rep[@state.value]
|
|
327
|
-
encoder.queue_bit(prob_is_rep, 0)
|
|
328
|
-
|
|
329
|
-
# Reps are stored 0-based (distance - 1), matching the decoder's
|
|
330
|
-
# rep0 convention.
|
|
331
|
-
@state.update_match!(distance - 1)
|
|
332
|
-
|
|
333
|
-
encode_match_length(length, pos_state, encoder)
|
|
334
|
-
encode_distance(distance, length, encoder)
|
|
335
|
-
|
|
336
|
-
last_byte_pos = match_pos - distance + length - 1
|
|
337
|
-
@prev_byte = @match_finder.buffer.getbyte(last_byte_pos) if last_byte_pos >= 0 && last_byte_pos < @match_finder.buffer.bytesize
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
# Encode repeated match
|
|
341
|
-
def encode_repeated_match(rep, length, encoder, pos, match_pos)
|
|
342
|
-
pos_state = pos & ((1 << @pb) - 1)
|
|
343
|
-
|
|
344
|
-
prob_is_match = @models.is_match[@state.value][pos_state]
|
|
345
|
-
encoder.queue_bit(prob_is_match, 1)
|
|
346
|
-
|
|
347
|
-
prob_is_rep = @models.is_rep[@state.value]
|
|
348
|
-
encoder.queue_bit(prob_is_rep, 1)
|
|
349
|
-
|
|
350
|
-
prob_is_rep0 = @models.is_rep0[@state.value]
|
|
351
|
-
if rep.zero?
|
|
352
|
-
encoder.queue_bit(prob_is_rep0, 0)
|
|
353
|
-
|
|
354
|
-
prob_is_rep0_long = @models.is_rep0_long[@state.value][pos_state]
|
|
355
|
-
encoder.queue_bit(prob_is_rep0_long, length == 1 ? 0 : 1)
|
|
356
|
-
else
|
|
357
|
-
encoder.queue_bit(prob_is_rep0, 1)
|
|
358
|
-
|
|
359
|
-
prob_is_rep1 = @models.is_rep1[@state.value]
|
|
360
|
-
if rep == 1
|
|
361
|
-
encoder.queue_bit(prob_is_rep1, 0)
|
|
362
|
-
else
|
|
363
|
-
encoder.queue_bit(prob_is_rep1, 1)
|
|
364
|
-
|
|
365
|
-
prob_is_rep2 = @models.is_rep2[@state.value]
|
|
366
|
-
encoder.queue_bit(prob_is_rep2, rep - 2)
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
# XZ Utils rep_match: capture the selected distance BEFORE
|
|
370
|
-
# rotating, then shift reps down.
|
|
371
|
-
distance = @state.reps[rep]
|
|
372
|
-
rep.downto(1) { |i| @state.reps[i] = @state.reps[i - 1] }
|
|
373
|
-
@state.reps[0] = distance
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
if length == 1
|
|
377
|
-
@state.update_short_rep!
|
|
378
|
-
else
|
|
379
|
-
# Rep matches use their own length models (decoder's
|
|
380
|
-
# rep_length_coder).
|
|
381
|
-
encode_match_length(length, pos_state, encoder,
|
|
382
|
-
@models.rep_len_encoder)
|
|
383
|
-
@state.update_long_rep!
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
# Last byte of the match region in the stream
|
|
387
|
-
last_byte_pos = match_pos + length - 1
|
|
388
|
-
@prev_byte = @match_finder.buffer.getbyte(last_byte_pos) if last_byte_pos >= 0 && last_byte_pos < @match_finder.buffer.bytesize
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
def get_literal_state(pos, prev_byte)
|
|
392
|
-
literal_mask = (0x100 << @lp) - (0x100 >> @lc)
|
|
393
|
-
# Factor of 3: each literal context owns a 0x300-sized subcoder
|
|
394
|
-
# (XZ Utils literal_subcoder macro); must match the decoder's
|
|
395
|
-
# base_offset = 3 * (lit_state << lc).
|
|
396
|
-
3 * ((((pos << 8) + prev_byte) & literal_mask) << @lc)
|
|
397
|
-
end
|
|
398
|
-
|
|
399
|
-
def encode_normal_literal(literal_offset, symbol, encoder)
|
|
400
|
-
context = 1
|
|
401
|
-
8.downto(1) do |i|
|
|
402
|
-
bit = (symbol >> (i - 1)) & 1
|
|
403
|
-
encoder.queue_bit(@models.literal[literal_offset + context], bit)
|
|
404
|
-
context = (context << 1) | bit
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def encode_matched_literal(literal_offset, match_byte, symbol,
|
|
409
|
-
encoder)
|
|
410
|
-
offset = 0x100
|
|
411
|
-
symbol += 0x100
|
|
412
|
-
|
|
413
|
-
while symbol < 0x10000
|
|
414
|
-
match_byte <<= 1
|
|
415
|
-
match_bit = match_byte & offset
|
|
416
|
-
subcoder_index = offset + match_bit + (symbol >> 8)
|
|
417
|
-
bit = (symbol >> 7) & 1
|
|
418
|
-
|
|
419
|
-
encoder.queue_bit(
|
|
420
|
-
@models.literal[literal_offset + subcoder_index], bit
|
|
421
|
-
)
|
|
422
|
-
|
|
423
|
-
symbol <<= 1
|
|
424
|
-
offset &= ~(match_byte ^ symbol)
|
|
425
|
-
end
|
|
426
|
-
end
|
|
427
|
-
|
|
428
|
-
# len_encoder selects the length model set: match_len_encoder for
|
|
429
|
-
# normal matches, rep_len_encoder for repeated matches.
|
|
430
|
-
def encode_match_length(length, pos_state, encoder,
|
|
431
|
-
len_encoder = @models.match_len_encoder)
|
|
432
|
-
len = length - 2
|
|
433
|
-
|
|
434
|
-
if len < 8
|
|
435
|
-
encoder.queue_bit(len_encoder.choice, 0)
|
|
436
|
-
encode_bittree(len_encoder.low[pos_state], 3, len, encoder)
|
|
437
|
-
elsif len < 16
|
|
438
|
-
encoder.queue_bit(len_encoder.choice, 1)
|
|
439
|
-
encoder.queue_bit(len_encoder.choice2, 0)
|
|
440
|
-
encode_bittree(len_encoder.mid[pos_state], 3, len - 8, encoder)
|
|
441
|
-
else
|
|
442
|
-
encoder.queue_bit(len_encoder.choice, 1)
|
|
443
|
-
encoder.queue_bit(len_encoder.choice2, 1)
|
|
444
|
-
encode_bittree(len_encoder.high, 8, len - 16, encoder)
|
|
445
|
-
end
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
def encode_distance(distance, length, encoder)
|
|
449
|
-
# get_dist_slot expects a 0-based distance (xz fastpos.h:
|
|
450
|
-
# dist 4 -> slot 4, dist 5 -> slot 4).
|
|
451
|
-
dist0 = distance - 1
|
|
452
|
-
dist_slot = get_dist_slot(dist0)
|
|
453
|
-
len_state = [length - 2, 3].min
|
|
454
|
-
|
|
455
|
-
encode_bittree(@models.dist_slot[len_state], 6, dist_slot, encoder)
|
|
456
|
-
|
|
457
|
-
if dist_slot >= 4
|
|
458
|
-
footer_bits = (dist_slot >> 1) - 1
|
|
459
|
-
base = (2 | (dist_slot & 1)) << footer_bits
|
|
460
|
-
dist_reduced = dist0 - base
|
|
461
|
-
|
|
462
|
-
if dist_slot < 14
|
|
463
|
-
encode_bittree_reverse(@models.dist_special, dist_reduced,
|
|
464
|
-
footer_bits, base - dist_slot - 1, encoder)
|
|
465
|
-
else
|
|
466
|
-
direct_bits = footer_bits - 4
|
|
467
|
-
encoder.queue_direct_bits(dist_reduced >> 4, direct_bits)
|
|
468
|
-
align_mask = (1 << 4) - 1
|
|
469
|
-
encode_bittree_reverse(@models.dist_align,
|
|
470
|
-
dist_reduced & align_mask, 4, 0, encoder)
|
|
471
|
-
end
|
|
472
|
-
end
|
|
473
|
-
end
|
|
474
|
-
|
|
475
|
-
def encode_bittree(probs, num_bits, value, encoder)
|
|
476
|
-
context = 1
|
|
477
|
-
num_bits.downto(1) do |i|
|
|
478
|
-
bit = (value >> (i - 1)) & 1
|
|
479
|
-
encoder.queue_bit(probs[context], bit)
|
|
480
|
-
context = (context << 1) | bit
|
|
481
|
-
end
|
|
482
|
-
end
|
|
483
|
-
|
|
484
|
-
def encode_bittree_reverse(probs, value, num_bits, offset, encoder)
|
|
485
|
-
context = 1
|
|
486
|
-
num_bits.times do |i|
|
|
487
|
-
bit = (value >> i) & 1
|
|
488
|
-
encoder.queue_bit(probs[offset + context], bit)
|
|
489
|
-
context = (context << 1) | bit
|
|
490
|
-
end
|
|
491
|
-
end
|
|
492
|
-
|
|
493
|
-
def get_dist_slot(distance)
|
|
494
|
-
if distance < 4
|
|
495
|
-
distance
|
|
496
|
-
else
|
|
497
|
-
slot = 0
|
|
498
|
-
dist = distance
|
|
499
|
-
while dist > 3
|
|
500
|
-
dist >>= 1
|
|
501
|
-
slot += 2
|
|
502
|
-
end
|
|
503
|
-
slot + dist
|
|
504
|
-
end
|
|
505
|
-
end
|
|
506
|
-
|
|
507
|
-
def encode_dict_size(dict_size)
|
|
508
|
-
d = [dict_size, DICT_SIZE_MIN].max
|
|
509
|
-
|
|
510
|
-
log2_size = 0
|
|
511
|
-
temp = d
|
|
512
|
-
while temp > 1
|
|
513
|
-
log2_size += 1
|
|
514
|
-
temp >>= 1
|
|
515
|
-
end
|
|
516
|
-
|
|
517
|
-
if d == (1 << log2_size)
|
|
518
|
-
[(log2_size - 12) * 2, 40].min
|
|
519
|
-
else
|
|
520
|
-
[((log2_size - 12) * 2) + 1, 40].min
|
|
521
|
-
end
|
|
522
|
-
end
|
|
523
52
|
end
|
|
524
53
|
end
|
|
525
54
|
end
|
data/lib/omnizip/version.rb
CHANGED