omnizip 0.3.13 → 0.3.15
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/algorithm.rb +36 -1
- data/lib/omnizip/algorithms/bzip2.rb +1 -3
- data/lib/omnizip/algorithms/deflate.rb +1 -3
- data/lib/omnizip/algorithms/deflate64.rb +2 -2
- data/lib/omnizip/algorithms/lzma/optimal_encoder.rb +7 -8
- data/lib/omnizip/algorithms/lzma.rb +1 -1
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +5 -1
- data/lib/omnizip/algorithms/lzma2.rb +4 -12
- data/lib/omnizip/algorithms/zstandard/constants.rb +38 -25
- data/lib/omnizip/algorithms/zstandard/decoder.rb +101 -136
- data/lib/omnizip/algorithms/zstandard/encoder.rb +81 -138
- data/lib/omnizip/algorithms/zstandard/frame/block.rb +17 -0
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +42 -29
- data/lib/omnizip/algorithms/zstandard/fse/bitstream.rb +114 -117
- data/lib/omnizip/algorithms/zstandard/fse/encoder.rb +434 -222
- data/lib/omnizip/algorithms/zstandard/fse/interleaved.rb +92 -0
- data/lib/omnizip/algorithms/zstandard/fse/table.rb +98 -176
- data/lib/omnizip/algorithms/zstandard/fse/table_description.rb +203 -0
- data/lib/omnizip/algorithms/zstandard/fse.rb +21 -2
- data/lib/omnizip/algorithms/zstandard/huffman.rb +195 -188
- data/lib/omnizip/algorithms/zstandard/huffman_encoder.rb +232 -255
- data/lib/omnizip/algorithms/zstandard/literals.rb +170 -103
- data/lib/omnizip/algorithms/zstandard/literals_encoder.rb +47 -199
- data/lib/omnizip/algorithms/zstandard/sequences.rb +260 -252
- data/lib/omnizip/algorithms/zstandard/xxhash.rb +159 -0
- data/lib/omnizip/algorithms/zstandard.rb +20 -24
- data/lib/omnizip/algorithms.rb +14 -1
- data/lib/omnizip/buffer.rb +1 -2
- data/lib/omnizip/commands/archive_list_command.rb +9 -7
- data/lib/omnizip/convenience.rb +1 -2
- data/lib/omnizip/entry.rb +44 -0
- data/lib/omnizip/extraction/selective_extractor.rb +2 -10
- data/lib/omnizip/filter_pipeline.rb +1 -1
- data/lib/omnizip/filter_registry.rb +18 -16
- data/lib/omnizip/filters/bcj2.rb +1 -1
- data/lib/omnizip/filters/bcj_arm.rb +1 -1
- data/lib/omnizip/filters/bcj_arm64.rb +1 -1
- data/lib/omnizip/filters/bcj_ia64.rb +1 -1
- data/lib/omnizip/filters/bcj_ppc.rb +1 -1
- data/lib/omnizip/filters/bcj_sparc.rb +1 -1
- data/lib/omnizip/filters/bcj_x86.rb +1 -1
- data/lib/omnizip/filters.rb +0 -2
- data/lib/omnizip/formats/bzip2_file.rb +0 -7
- data/lib/omnizip/formats/cpio/entry.rb +6 -0
- data/lib/omnizip/formats/cpio.rb +0 -6
- data/lib/omnizip/formats/gzip.rb +0 -7
- data/lib/omnizip/formats/iso/directory_record.rb +7 -0
- data/lib/omnizip/formats/iso.rb +0 -5
- data/lib/omnizip/formats/lzip.rb +0 -7
- data/lib/omnizip/formats/lzma_alone.rb +0 -6
- data/lib/omnizip/formats/msi/entry.rb +6 -0
- data/lib/omnizip/formats/msi.rb +0 -9
- data/lib/omnizip/formats/ole/dirent.rb +6 -0
- data/lib/omnizip/formats/ole.rb +0 -9
- data/lib/omnizip/formats/rar.rb +0 -6
- data/lib/omnizip/formats/rar3/reader.rb +7 -0
- data/lib/omnizip/formats/rar5/reader.rb +7 -0
- data/lib/omnizip/formats/rpm/entry.rb +6 -0
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +7 -0
- data/lib/omnizip/formats/seven_zip.rb +0 -6
- data/lib/omnizip/formats/tar/entry.rb +6 -0
- data/lib/omnizip/formats/tar.rb +2 -10
- data/lib/omnizip/formats/xar/entry.rb +6 -0
- data/lib/omnizip/formats/xar.rb +0 -6
- data/lib/omnizip/formats/zip.rb +0 -6
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +33 -31
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +177 -163
- data/lib/omnizip/implementations.rb +18 -4
- data/lib/omnizip/io/source.rb +3 -0
- data/lib/omnizip/metadata/entry_metadata.rb +7 -0
- data/lib/omnizip/parallel/engine.rb +48 -0
- data/lib/omnizip/parallel/parallel_compressor.rb +5 -16
- data/lib/omnizip/parallel/parallel_extractor.rb +5 -16
- data/lib/omnizip/parallel.rb +1 -0
- data/lib/omnizip/profile/profile_registry.rb +1 -2
- data/lib/omnizip/registry.rb +0 -1
- data/lib/omnizip/rubyzip_compat.rb +0 -1
- data/lib/omnizip/temp/temp_file.rb +1 -2
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/entry.rb +7 -0
- data/lib/omnizip/zip/file.rb +4 -8
- data/lib/omnizip.rb +1 -1
- metadata +7 -6
- data/lib/omnizip/filters/filter_base.rb +0 -6
- data/lib/omnizip/filters/registration.rb +0 -22
- data/lib/omnizip/filters/registry.rb +0 -111
- data/lib/omnizip/format_registry.rb +0 -100
|
@@ -23,312 +23,289 @@
|
|
|
23
23
|
module Omnizip
|
|
24
24
|
module Algorithms
|
|
25
25
|
class Zstandard
|
|
26
|
-
# Huffman
|
|
26
|
+
# Huffman encoder for Zstandard literals (RFC 8878 §4.2).
|
|
27
27
|
#
|
|
28
|
-
#
|
|
29
|
-
|
|
28
|
+
# Builds a length-limited Huffman code from per-byte frequencies,
|
|
29
|
+
# emits the weight table (direct or FSE-compressed), and codes
|
|
30
|
+
# the literals into 1 or 4 reverse bitstreams.
|
|
31
|
+
module HuffmanEncoder
|
|
30
32
|
include Constants
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
attr_reader :code_lengths
|
|
34
|
+
module_function
|
|
34
35
|
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
# @return [Integer] Maximum code length
|
|
39
|
-
attr_reader :max_bits
|
|
40
|
-
|
|
41
|
-
# Build Huffman encoder from symbol frequencies
|
|
36
|
+
# Build 256 weights (0 for absent symbols) from the literal
|
|
37
|
+
# bytes, with code lengths capped at HUFFMAN_MAX_BITS.
|
|
42
38
|
#
|
|
43
|
-
# @param
|
|
44
|
-
# @
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
39
|
+
# @param literals [String]
|
|
40
|
+
# @return [Array<Integer>]
|
|
41
|
+
def build_weights(literals)
|
|
42
|
+
counts = Array.new(256, 0)
|
|
43
|
+
literals.each_byte { |b| counts[b] += 1 }
|
|
44
|
+
|
|
45
|
+
present = (0..255).select { |b| counts[b].positive? }
|
|
46
|
+
if present.length < 2
|
|
47
|
+
# A Huffman table needs at least 2 symbols; pad with
|
|
48
|
+
# symbol 0 (or 1) so the real symbol keeps a real code.
|
|
49
|
+
weights = Array.new(256, 0)
|
|
50
|
+
sym = present.first || 0
|
|
51
|
+
weights[sym] = 1
|
|
52
|
+
other = sym.zero? ? 1 : 0
|
|
53
|
+
weights[other] = 1
|
|
54
|
+
return weights
|
|
55
|
+
end
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
freqs = present.map { |b| counts[b] }
|
|
58
|
+
lengths = huffman_lengths(freqs)
|
|
59
|
+
lengths = limit_lengths(lengths, HUFFMAN_MAX_BITS, freqs)
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
max_len = lengths.max
|
|
62
|
+
weights = Array.new(256, 0)
|
|
63
|
+
present.each_with_index do |byte, i|
|
|
64
|
+
weights[byte] = max_len - lengths[i] + 1
|
|
65
|
+
end
|
|
66
|
+
weights
|
|
60
67
|
end
|
|
61
68
|
|
|
62
|
-
#
|
|
69
|
+
# Standard Huffman code lengths via smallest-pair merging.
|
|
63
70
|
#
|
|
64
|
-
# @param
|
|
65
|
-
# @
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
# @param freqs [Array<Integer>]
|
|
72
|
+
# @return [Array<Integer>] code length per symbol
|
|
73
|
+
def huffman_lengths(freqs)
|
|
74
|
+
nodes = freqs.map { |f| { freq: f, parent: -1 } }
|
|
75
|
+
|
|
76
|
+
while nodes.count { |n| n[:parent] == -1 } > 1
|
|
77
|
+
a = -1
|
|
78
|
+
b = -1
|
|
79
|
+
nodes.each_with_index do |n, i|
|
|
80
|
+
next unless n[:parent] == -1
|
|
81
|
+
|
|
82
|
+
if a == -1 || n[:freq] < nodes[a][:freq]
|
|
83
|
+
b = a
|
|
84
|
+
a = i
|
|
85
|
+
elsif b == -1 || n[:freq] < nodes[b][:freq]
|
|
86
|
+
b = i
|
|
87
|
+
end
|
|
88
|
+
end
|
|
76
89
|
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
nodes[a][:parent] = nodes.length
|
|
91
|
+
nodes[b][:parent] = nodes.length
|
|
92
|
+
nodes << { freq: nodes[a][:freq] + nodes[b][:freq], parent: -1 }
|
|
93
|
+
end
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
lengths = Array.new(freqs.length, 0)
|
|
96
|
+
freqs.each_index do |i|
|
|
97
|
+
len = 0
|
|
98
|
+
cur = i
|
|
99
|
+
while nodes[cur][:parent] != -1
|
|
100
|
+
cur = nodes[cur][:parent]
|
|
101
|
+
len += 1
|
|
102
|
+
end
|
|
103
|
+
lengths[i] = [len, 255].min
|
|
104
|
+
end
|
|
105
|
+
lengths
|
|
106
|
+
end
|
|
82
107
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
# Cap code lengths at max_len while preserving the Kraft
|
|
109
|
+
# inequality: shorten the longest codes and lengthen the
|
|
110
|
+
# shortest until the sum of 2^-len is <= 1.
|
|
111
|
+
#
|
|
112
|
+
# @param lengths [Array<Integer>] mutated in place
|
|
113
|
+
# @param max_len [Integer]
|
|
114
|
+
# @param freqs [Array<Integer>]
|
|
115
|
+
# @return [Array<Integer>]
|
|
116
|
+
def limit_lengths(lengths, max_len, freqs)
|
|
117
|
+
loop do
|
|
118
|
+
cur_max = lengths.max || 0
|
|
119
|
+
break if cur_max <= max_len
|
|
120
|
+
|
|
121
|
+
longest = nil
|
|
122
|
+
lengths.each_with_index do |l, i|
|
|
123
|
+
next unless l == cur_max
|
|
124
|
+
next if longest && freqs[i] >= freqs[longest]
|
|
125
|
+
|
|
126
|
+
longest = i
|
|
127
|
+
end
|
|
86
128
|
|
|
87
|
-
|
|
88
|
-
|
|
129
|
+
shortest = nil
|
|
130
|
+
lengths.each_with_index do |l, i|
|
|
131
|
+
next unless l.positive? && l < max_len
|
|
132
|
+
next if shortest && freqs[i] <= freqs[shortest]
|
|
89
133
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return if symbols_with_freq.empty?
|
|
134
|
+
shortest = i
|
|
135
|
+
end
|
|
93
136
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
137
|
+
if longest && shortest
|
|
138
|
+
lengths[longest] -= 1
|
|
139
|
+
lengths[shortest] += 1
|
|
140
|
+
else
|
|
141
|
+
lengths.map! { |l| [l, max_len].min }
|
|
142
|
+
break
|
|
143
|
+
end
|
|
97
144
|
end
|
|
98
145
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
nodes.sort_by! { |n| n[:freq] }
|
|
146
|
+
loop do
|
|
147
|
+
kraft = lengths.sum { |l| l.positive? ? 2.0**-l : 0.0 }
|
|
148
|
+
break if kraft <= 1.0 + 1e-10
|
|
103
149
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
150
|
+
min_idx = nil
|
|
151
|
+
lengths.each_with_index do |l, i|
|
|
152
|
+
next unless l.positive? && l < max_len
|
|
153
|
+
next if min_idx && lengths[i] >= lengths[min_idx]
|
|
107
154
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
left: left,
|
|
112
|
-
right: right,
|
|
113
|
-
depth: [left[:depth], right[:depth]].max + 1,
|
|
114
|
-
}
|
|
155
|
+
min_idx = i
|
|
156
|
+
end
|
|
157
|
+
break if min_idx.nil?
|
|
115
158
|
|
|
116
|
-
|
|
159
|
+
lengths[min_idx] += 1
|
|
117
160
|
end
|
|
118
161
|
|
|
119
|
-
|
|
120
|
-
if nodes.length == 1
|
|
121
|
-
assign_lengths(nodes[0], 0, code_lengths, max_bits)
|
|
122
|
-
elsif symbols_with_freq.length == 1
|
|
123
|
-
# Single symbol
|
|
124
|
-
code_lengths[symbols_with_freq[0][1]] = 1
|
|
125
|
-
end
|
|
162
|
+
lengths
|
|
126
163
|
end
|
|
127
164
|
|
|
128
|
-
#
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
165
|
+
# Serialize the weight table. Direct 4-bit weights when the
|
|
166
|
+
# alphabet fits, FSE-compressed otherwise.
|
|
167
|
+
#
|
|
168
|
+
# @param weights [Array<Integer>]
|
|
169
|
+
# @return [String]
|
|
170
|
+
def encode_weights(weights)
|
|
171
|
+
max_symbol = weights.rindex(&:positive?)
|
|
172
|
+
raise Omnizip::CompressionError, "no present Huffman weights" if max_symbol.nil?
|
|
173
|
+
|
|
174
|
+
if max_symbol <= 128
|
|
175
|
+
encode_weights_direct(weights, max_symbol)
|
|
137
176
|
else
|
|
138
|
-
|
|
139
|
-
assign_lengths(node[:left], depth + 1, code_lengths, max_bits)
|
|
140
|
-
assign_lengths(node[:right], depth + 1, code_lengths, max_bits)
|
|
177
|
+
encode_weights_fse(weights, max_symbol)
|
|
141
178
|
end
|
|
142
179
|
end
|
|
143
180
|
|
|
144
|
-
#
|
|
145
|
-
#
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
return code_lengths if max_length <= max_bits
|
|
157
|
-
|
|
158
|
-
# Limit using a simple approach: cap at max_bits and adjust
|
|
159
|
-
# This is a simplified implementation
|
|
160
|
-
lengths = code_lengths.map { |l| [l, max_bits].min }
|
|
161
|
-
|
|
162
|
-
# Ensure Kraft inequality is satisfied
|
|
163
|
-
# Sum of 2^(-length) must be <= 1
|
|
164
|
-
kraft_sum = lengths.sum { |l| l.positive? ? 1 << (max_bits - l) : 0 }
|
|
165
|
-
max_kraft = 1 << max_bits
|
|
166
|
-
|
|
167
|
-
if kraft_sum > max_kraft
|
|
168
|
-
# Need to increase some lengths
|
|
169
|
-
# This is simplified - a proper implementation would use package-merge
|
|
170
|
-
lengths = redistribute_lengths(lengths, max_bits)
|
|
181
|
+
# Direct encoding: header byte 127 + o_size, then two 4-bit
|
|
182
|
+
# weights per byte. The last present weight is implied on
|
|
183
|
+
# decode and is dropped here.
|
|
184
|
+
def encode_weights_direct(weights, max_symbol)
|
|
185
|
+
o_size = max_symbol
|
|
186
|
+
i_size = 127 + o_size
|
|
187
|
+
out = [i_size].pack("C")
|
|
188
|
+
|
|
189
|
+
(0...o_size).step(2) do |n|
|
|
190
|
+
high = weights[n] & 0x0F
|
|
191
|
+
low = n + 1 < o_size ? weights[n + 1] & 0x0F : 0
|
|
192
|
+
out << ((high << 4) | low).chr
|
|
171
193
|
end
|
|
172
194
|
|
|
173
|
-
|
|
195
|
+
out
|
|
174
196
|
end
|
|
175
197
|
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
# FSE-compressed weights (RFC 8878 §4.2.1.2): payload-size
|
|
199
|
+
# header byte, NCount, then the 2-state bitstream.
|
|
200
|
+
def encode_weights_fse(weights, max_symbol)
|
|
201
|
+
o_size = max_symbol
|
|
202
|
+
symbols = weights.first(o_size)
|
|
181
203
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def self.build_canonical_codes(code_lengths)
|
|
187
|
-
codes = {}
|
|
188
|
-
return codes if code_lengths.nil? || code_lengths.empty?
|
|
189
|
-
|
|
190
|
-
max_length = code_lengths.compact.max || 0
|
|
191
|
-
return codes if max_length.zero?
|
|
192
|
-
|
|
193
|
-
# Count symbols at each length
|
|
194
|
-
bl_count = Array.new(max_length + 1, 0)
|
|
195
|
-
code_lengths.each do |length|
|
|
196
|
-
bl_count[length] += 1 if length&.positive?
|
|
204
|
+
distinct = symbols.uniq.length
|
|
205
|
+
if distinct <= 1
|
|
206
|
+
raise Omnizip::CompressionError,
|
|
207
|
+
"uniform Huffman weights give no compression"
|
|
197
208
|
end
|
|
198
209
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
code = ((code + bl_count[bits - 1]) << 1)
|
|
204
|
-
next_code[bits] = code
|
|
210
|
+
encoder = FSE::Encoder.build_from_symbols(symbols, 11, 6)
|
|
211
|
+
if encoder.nil?
|
|
212
|
+
raise Omnizip::CompressionError,
|
|
213
|
+
"uniform Huffman weights give no compression"
|
|
205
214
|
end
|
|
206
215
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
codes[symbol] = next_code[length]
|
|
212
|
-
next_code[length] += 1
|
|
216
|
+
payload = encoder.compress(symbols)
|
|
217
|
+
if payload.bytesize >= 128
|
|
218
|
+
raise Omnizip::CompressionError,
|
|
219
|
+
"FSE weight payload exceeds the 127-byte header limit"
|
|
213
220
|
end
|
|
214
221
|
|
|
215
|
-
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# Initialize Huffman encoder
|
|
219
|
-
#
|
|
220
|
-
# @param code_lengths [Array<Integer>] Code lengths
|
|
221
|
-
# @param codes [Hash<Integer, Integer>] Symbol to code mapping
|
|
222
|
-
# @param max_bits [Integer] Maximum code length
|
|
223
|
-
def initialize(code_lengths, codes, max_bits)
|
|
224
|
-
@code_lengths = code_lengths
|
|
225
|
-
@codes = codes
|
|
226
|
-
@max_bits = max_bits
|
|
227
|
-
|
|
228
|
-
# Build reverse lookup for encoding
|
|
229
|
-
@symbol_code = {}
|
|
230
|
-
@symbol_length = {}
|
|
231
|
-
|
|
232
|
-
codes.each do |symbol, code|
|
|
233
|
-
@symbol_code[symbol] = code
|
|
234
|
-
@symbol_length[symbol] = code_lengths[symbol]
|
|
235
|
-
end
|
|
222
|
+
[payload.bytesize].pack("C") + payload
|
|
236
223
|
end
|
|
237
224
|
|
|
238
|
-
# Encode
|
|
225
|
+
# Encode literals as a Compressed_Literals_Block section:
|
|
226
|
+
# header + weights + coded stream(s).
|
|
239
227
|
#
|
|
240
|
-
# @param
|
|
241
|
-
# @return [String]
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
228
|
+
# @param literals [String]
|
|
229
|
+
# @return [String]
|
|
230
|
+
# rubocop:disable Metrics/MethodLength
|
|
231
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
232
|
+
def encode_literals(literals)
|
|
233
|
+
weights = build_weights(literals)
|
|
234
|
+
# The wire format drops the last present weight (it is implied
|
|
235
|
+
# by the Kraft inequality on decode), so the coding table must
|
|
236
|
+
# be rebuilt exactly the way the decoder rebuilds it.
|
|
237
|
+
max_symbol = weights.rindex(&:positive?)
|
|
238
|
+
wire_weights = weights.first(max_symbol)
|
|
239
|
+
full_weights = wire_weights +
|
|
240
|
+
[HuffmanTableReader.implied_last_weight(wire_weights)]
|
|
241
|
+
table = Huffman.from_weights(full_weights)
|
|
242
|
+
encode_table = table.encode_table
|
|
243
|
+
|
|
244
|
+
weights_wire = encode_weights(weights)
|
|
245
|
+
lit_size = literals.bytesize
|
|
246
|
+
|
|
247
|
+
# Single stream (3-byte header) when both sizes fit 10 bits.
|
|
248
|
+
if lit_size < 1024
|
|
249
|
+
coded = encode_huffman_stream(encode_table, literals)
|
|
250
|
+
lit_c_size = weights_wire.bytesize + coded.bytesize
|
|
251
|
+
if lit_c_size < 1024
|
|
252
|
+
header = LITERALS_BLOCK_COMPRESSED |
|
|
253
|
+
(lit_size << 4) | (lit_c_size << 14)
|
|
254
|
+
return [header].pack("V")[0, 3] + weights_wire + coded
|
|
257
255
|
end
|
|
258
256
|
end
|
|
259
257
|
|
|
260
|
-
#
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
#
|
|
268
|
-
# @return [String] Encoded Huffman table description
|
|
269
|
-
def encode_table_description
|
|
270
|
-
# Convert code lengths to weights
|
|
271
|
-
# Weight = max_bits - code_length + 1 (for non-zero lengths)
|
|
272
|
-
weights = @code_lengths.map do |length|
|
|
273
|
-
next 0 if length.nil? || length.zero?
|
|
274
|
-
|
|
275
|
-
@max_bits - length + 1
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
encode_weights_fse(weights)
|
|
279
|
-
end
|
|
280
|
-
|
|
281
|
-
private
|
|
282
|
-
|
|
283
|
-
# Encode weights using FSE compression
|
|
284
|
-
def encode_weights_fse(weights)
|
|
285
|
-
# Count non-zero weights
|
|
286
|
-
num_weights = weights.count(&:positive?)
|
|
287
|
-
|
|
288
|
-
if num_weights.zero?
|
|
289
|
-
# No symbols - empty table
|
|
290
|
-
return "\x00"
|
|
258
|
+
# 4 streams with a jump table.
|
|
259
|
+
segment_size = (lit_size + 3) / 4
|
|
260
|
+
segments = Array.new(4) do |i|
|
|
261
|
+
encode_huffman_stream(
|
|
262
|
+
encode_table,
|
|
263
|
+
literals.byteslice(segment_size * i, segment_size),
|
|
264
|
+
)
|
|
291
265
|
end
|
|
292
266
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
267
|
+
lit_c_size = weights_wire.bytesize + 6 + segments.sum(&:bytesize)
|
|
268
|
+
|
|
269
|
+
out = if lit_size < 1024 && lit_c_size < 1024
|
|
270
|
+
header = LITERALS_BLOCK_COMPRESSED | (0b01 << 2) |
|
|
271
|
+
(lit_size << 4) | (lit_c_size << 14)
|
|
272
|
+
[header].pack("V")[0, 3]
|
|
273
|
+
elsif lit_size < 16_384 && lit_c_size < 16_384
|
|
274
|
+
header = LITERALS_BLOCK_COMPRESSED | (0b10 << 2) |
|
|
275
|
+
(lit_size << 4) | (lit_c_size << 18)
|
|
276
|
+
[header].pack("V")[0, 4]
|
|
277
|
+
elsif lit_size < 262_144 && lit_c_size < 262_144
|
|
278
|
+
low = LITERALS_BLOCK_COMPRESSED | (0b11 << 2) |
|
|
279
|
+
(lit_size << 4) | ((lit_c_size & 0x3FF) << 22)
|
|
280
|
+
[low].pack("V") + [(lit_c_size >> 10) & 0xFF].pack("C")
|
|
281
|
+
else
|
|
282
|
+
raise Omnizip::CompressionError,
|
|
283
|
+
"literals section exceeds the 18-bit header limits"
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
out + weights_wire +
|
|
287
|
+
segments.first(3).map { |s| [s.bytesize].pack("v") }.join +
|
|
288
|
+
segments.join
|
|
312
289
|
end
|
|
290
|
+
# rubocop:enable Metrics/MethodLength
|
|
313
291
|
|
|
314
|
-
#
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
292
|
+
# Code literals into one reverse bitstream (C BIT_CStream
|
|
293
|
+
# direction): the encoder writes the last symbol first so the
|
|
294
|
+
# reverse reader recovers symbols in order.
|
|
295
|
+
#
|
|
296
|
+
# @param encode_table [Array<Array(Integer, Integer)>]
|
|
297
|
+
# @param literals [String]
|
|
298
|
+
# @return [String]
|
|
299
|
+
def encode_huffman_stream(encode_table, literals)
|
|
300
|
+
bitc = FSE::Encoder::BitCStream.new
|
|
301
|
+
(literals.bytesize - 1).downto(0) do |i|
|
|
302
|
+
code, len = encode_table[literals.getbyte(i)]
|
|
303
|
+
next if len.zero?
|
|
304
|
+
|
|
305
|
+
bitc.add_bits(code, len)
|
|
306
|
+
bitc.flush
|
|
329
307
|
end
|
|
330
|
-
|
|
331
|
-
bytes.pack("C*")
|
|
308
|
+
bitc.close
|
|
332
309
|
end
|
|
333
310
|
end
|
|
334
311
|
end
|