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
|
@@ -24,296 +24,508 @@ module Omnizip
|
|
|
24
24
|
module Algorithms
|
|
25
25
|
class Zstandard
|
|
26
26
|
module FSE
|
|
27
|
-
# FSE
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# FSE is a variant of arithmetic coding that uses table-based state transitions.
|
|
27
|
+
# FSE encoder (RFC 8878 §4.1): count normalization, NCount
|
|
28
|
+
# writing, CTable construction, and 2-state interleaved
|
|
29
|
+
# bitstream encoding. Mirrors the C reference FSE library.
|
|
31
30
|
class Encoder
|
|
32
31
|
include Constants
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
RTB_TABLE = [0, 473_195, 504_333, 520_860, 550_000, 700_000,
|
|
34
|
+
750_000, 830_000].freeze
|
|
35
|
+
NOT_YET_ASSIGNED = -2
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
attr_reader :accuracy_log
|
|
37
|
+
SymbolTT = Struct.new(:delta_nb_bits, :delta_find_state)
|
|
39
38
|
|
|
40
|
-
# @return [Integer]
|
|
41
|
-
attr_reader :
|
|
39
|
+
# @return [Array<Integer>] normalized distribution
|
|
40
|
+
attr_reader :distribution
|
|
42
41
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
# @param frequencies [Array<Integer>] Raw symbol frequencies
|
|
46
|
-
# @param max_accuracy_log [Integer] Maximum accuracy log (default 9)
|
|
47
|
-
# @return [Encoder] FSE encoder
|
|
48
|
-
def self.build_from_frequencies(frequencies,
|
|
49
|
-
max_accuracy_log = FSE_MAX_ACCURACY_LOG)
|
|
50
|
-
return nil if frequencies.nil? || frequencies.empty?
|
|
51
|
-
|
|
52
|
-
# Normalize frequencies to table size
|
|
53
|
-
distribution, accuracy_log = normalize_distribution(frequencies,
|
|
54
|
-
max_accuracy_log)
|
|
55
|
-
|
|
56
|
-
new(distribution, accuracy_log)
|
|
57
|
-
end
|
|
42
|
+
# @return [Integer]
|
|
43
|
+
attr_reader :table_log
|
|
58
44
|
|
|
59
|
-
#
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
# @return [Integer]
|
|
46
|
+
attr_reader :max_symbol_value
|
|
47
|
+
|
|
48
|
+
# Normalize a raw histogram to sum to (1 << table_log).
|
|
62
49
|
#
|
|
63
|
-
# @param
|
|
64
|
-
# @param
|
|
65
|
-
# @
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
distribution = normalize_frequencies(frequencies, table_size)
|
|
81
|
-
|
|
82
|
-
# Verify distribution sums to table size
|
|
83
|
-
sum = distribution.sum
|
|
84
|
-
if sum != table_size
|
|
85
|
-
# Adjust to make it sum correctly
|
|
86
|
-
adjust_distribution(distribution, table_size - sum)
|
|
50
|
+
# @param table_log [Integer]
|
|
51
|
+
# @param count [Array<Integer>] raw frequencies
|
|
52
|
+
# @param total [Integer] sum(count)
|
|
53
|
+
# @param max_symbol_value [Integer] highest symbol index
|
|
54
|
+
# @param use_low_prob [Boolean] emit -1 sentinels for rare
|
|
55
|
+
# symbols (the zstd default)
|
|
56
|
+
# @return [Array<Integer>] normalized distribution, or [] for
|
|
57
|
+
# an RLE stream
|
|
58
|
+
def self.normalize_count(table_log, count, total, max_symbol_value,
|
|
59
|
+
use_low_prob: true)
|
|
60
|
+
table_log = FSE_DEFAULT_TABLELOG if table_log.zero?
|
|
61
|
+
|
|
62
|
+
norm = Array.new(max_symbol_value + 1, 0)
|
|
63
|
+
|
|
64
|
+
# RLE: a single symbol accounts for everything.
|
|
65
|
+
(0..max_symbol_value).each do |s|
|
|
66
|
+
return [] if count[s] == total
|
|
87
67
|
end
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
|
|
69
|
+
low_prob_count = use_low_prob ? -1 : 1
|
|
70
|
+
scale = 62 - table_log
|
|
71
|
+
step = (1 << 62) / total
|
|
72
|
+
v_step = 1 << (scale - 20)
|
|
73
|
+
still_to_distribute = 1 << table_log
|
|
74
|
+
low_threshold = total >> table_log
|
|
75
|
+
largest = 0
|
|
76
|
+
largest_p = 0
|
|
77
|
+
|
|
78
|
+
(0..max_symbol_value).each do |s|
|
|
79
|
+
if count[s].zero?
|
|
80
|
+
norm[s] = 0
|
|
81
|
+
next
|
|
82
|
+
end
|
|
91
83
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
84
|
+
c64 = count[s]
|
|
85
|
+
if c64 <= low_threshold
|
|
86
|
+
norm[s] = low_prob_count
|
|
87
|
+
still_to_distribute -= 1
|
|
88
|
+
else
|
|
89
|
+
proba = (c64 * step) >> scale
|
|
90
|
+
if proba < 8
|
|
91
|
+
rest_to_beat = v_step * RTB_TABLE[proba]
|
|
92
|
+
diff = (c64 * step) - (proba << scale)
|
|
93
|
+
proba += 1 if diff > rest_to_beat
|
|
94
|
+
end
|
|
95
|
+
if proba > largest_p
|
|
96
|
+
largest_p = proba
|
|
97
|
+
largest = s
|
|
98
|
+
end
|
|
99
|
+
norm[s] = proba
|
|
100
|
+
still_to_distribute -= proba
|
|
101
|
+
end
|
|
102
|
+
end
|
|
95
103
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
if -still_to_distribute >= norm[largest] / 2
|
|
105
|
+
normalize_m2(norm, table_log, count, total, max_symbol_value,
|
|
106
|
+
low_prob_count)
|
|
107
|
+
else
|
|
108
|
+
norm[largest] += still_to_distribute
|
|
101
109
|
end
|
|
102
|
-
|
|
110
|
+
|
|
111
|
+
norm
|
|
103
112
|
end
|
|
104
113
|
|
|
105
|
-
#
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
# Secondary normalization (C FSE_normalizeM2), used when the
|
|
115
|
+
# primary method's largest-symbol correction would be too big.
|
|
116
|
+
# rubocop:disable Metrics/MethodLength
|
|
117
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
118
|
+
def self.normalize_m2(norm, table_log, count, total, max_symbol_value,
|
|
119
|
+
low_prob_count)
|
|
120
|
+
table_size = 1 << table_log
|
|
121
|
+
low_threshold = total >> table_log
|
|
122
|
+
low_one = (total * 3) >> (table_log + 1)
|
|
123
|
+
distributed = 0
|
|
124
|
+
remaining = total
|
|
125
|
+
|
|
126
|
+
(0..max_symbol_value).each do |s|
|
|
127
|
+
if count[s].zero?
|
|
128
|
+
norm[s] = 0
|
|
129
|
+
elsif count[s] <= low_threshold
|
|
130
|
+
norm[s] = low_prob_count
|
|
131
|
+
distributed += 1
|
|
132
|
+
remaining -= count[s]
|
|
133
|
+
elsif count[s] <= low_one
|
|
134
|
+
norm[s] = 1
|
|
135
|
+
distributed += 1
|
|
136
|
+
remaining -= count[s]
|
|
137
|
+
else
|
|
138
|
+
norm[s] = NOT_YET_ASSIGNED
|
|
139
|
+
end
|
|
140
|
+
end
|
|
109
141
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
next 0 if freq.nil? || freq <= 0
|
|
142
|
+
to_distribute = table_size - distributed
|
|
143
|
+
return if to_distribute.zero?
|
|
113
144
|
|
|
114
|
-
|
|
115
|
-
|
|
145
|
+
if remaining / to_distribute > low_one
|
|
146
|
+
low_one = (remaining * 3) / (to_distribute * 2)
|
|
147
|
+
(0..max_symbol_value).each do |s|
|
|
148
|
+
if norm[s] == NOT_YET_ASSIGNED && count[s] <= low_one
|
|
149
|
+
norm[s] = 1
|
|
150
|
+
distributed += 1
|
|
151
|
+
remaining -= count[s]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
to_distribute = table_size - distributed
|
|
116
155
|
end
|
|
117
|
-
end
|
|
118
156
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
distribution[max_idx] += 1 if max_idx
|
|
157
|
+
if distributed == max_symbol_value + 1
|
|
158
|
+
max_v = 0
|
|
159
|
+
max_c = 0
|
|
160
|
+
(0..max_symbol_value).each do |s|
|
|
161
|
+
if count[s] > max_c
|
|
162
|
+
max_v = s
|
|
163
|
+
max_c = count[s]
|
|
164
|
+
end
|
|
128
165
|
end
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
norm[max_v] += to_distribute
|
|
167
|
+
return
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if remaining.zero?
|
|
171
|
+
idx = 0
|
|
172
|
+
while to_distribute.positive? && idx <= max_symbol_value
|
|
173
|
+
if norm[idx]&.positive?
|
|
174
|
+
norm[idx] += 1
|
|
175
|
+
to_distribute -= 1
|
|
176
|
+
end
|
|
177
|
+
idx = (idx + 1) % (max_symbol_value + 1)
|
|
136
178
|
end
|
|
179
|
+
return
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
v_step_log = 62 - table_log
|
|
183
|
+
mid = (1 << (v_step_log - 1)) - 1
|
|
184
|
+
r_step = (((1 << v_step_log) * to_distribute) + mid) / remaining
|
|
185
|
+
tmp_total = mid
|
|
186
|
+
(0..max_symbol_value).each do |s|
|
|
187
|
+
next unless norm[s] == NOT_YET_ASSIGNED
|
|
188
|
+
|
|
189
|
+
end_v = tmp_total + (count[s] * r_step)
|
|
190
|
+
weight = (end_v >> v_step_log) - (tmp_total >> v_step_log)
|
|
191
|
+
norm[s] = weight
|
|
192
|
+
tmp_total = end_v
|
|
137
193
|
end
|
|
138
194
|
end
|
|
195
|
+
# rubocop:enable Metrics/MethodLength
|
|
139
196
|
|
|
140
|
-
#
|
|
197
|
+
# Choose an accuracy log for the given source size and alphabet.
|
|
141
198
|
#
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
199
|
+
# Direct port of C FSE_optimalTableLog; the branch ladder is
|
|
200
|
+
# inherent to the algorithm.
|
|
201
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
202
|
+
def self.optimal_table_log(max_table_log, src_size, max_symbol_value)
|
|
203
|
+
return FSE_MIN_ACCURACY_LOG if src_size <= 1
|
|
204
|
+
|
|
205
|
+
max_bits_src = (src_size - 1).bit_length - 1 - 2
|
|
206
|
+
max_bits_src = 0 if max_bits_src.negative?
|
|
207
|
+
min_bits_src = src_size.bit_length + 1
|
|
208
|
+
min_bits_sym = [max_symbol_value, 1].max.bit_length + 2
|
|
209
|
+
min_bits = [min_bits_src, min_bits_sym].min
|
|
210
|
+
|
|
211
|
+
table_log = max_table_log
|
|
212
|
+
table_log = FSE_DEFAULT_TABLELOG if table_log.zero?
|
|
213
|
+
table_log = max_bits_src if max_bits_src < table_log
|
|
214
|
+
table_log = min_bits if min_bits > table_log
|
|
215
|
+
table_log.clamp(FSE_MIN_ACCURACY_LOG, FSE_MAX_ACCURACY_LOG)
|
|
216
|
+
end
|
|
148
217
|
|
|
149
|
-
|
|
218
|
+
# Build from raw symbols: normalize + store.
|
|
219
|
+
#
|
|
220
|
+
# @param symbols [Array<Integer>]
|
|
221
|
+
# @param max_symbol_value [Integer]
|
|
222
|
+
# @param max_table_log [Integer]
|
|
223
|
+
# @return [Encoder, nil] nil for an RLE stream (single symbol)
|
|
224
|
+
def self.build_from_symbols(symbols, max_symbol_value,
|
|
225
|
+
max_table_log = FSE_DEFAULT_TABLELOG)
|
|
226
|
+
counts = Array.new(max_symbol_value + 1, 0)
|
|
227
|
+
symbols.each { |s| counts[s] += 1 }
|
|
228
|
+
total = symbols.length
|
|
229
|
+
|
|
230
|
+
actual_max = max_symbol_value
|
|
231
|
+
actual_max -= 1 while actual_max.positive? && counts[actual_max].zero?
|
|
232
|
+
|
|
233
|
+
table_log = optimal_table_log(max_table_log, total, actual_max)
|
|
234
|
+
norm = normalize_count(table_log, counts, total, actual_max,
|
|
235
|
+
use_low_prob: true)
|
|
236
|
+
return nil if norm.empty?
|
|
237
|
+
|
|
238
|
+
new(norm, table_log, actual_max)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def initialize(distribution, table_log, max_symbol_value)
|
|
242
|
+
@distribution = distribution
|
|
243
|
+
@table_log = table_log
|
|
244
|
+
@max_symbol_value = max_symbol_value
|
|
150
245
|
build_encoding_tables
|
|
151
246
|
end
|
|
152
247
|
|
|
153
|
-
#
|
|
248
|
+
# Serialize the table description (NCount) per RFC 8878 §4.1.1.
|
|
154
249
|
#
|
|
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
|
-
|
|
250
|
+
# @return [String]
|
|
251
|
+
# rubocop:disable Metrics/MethodLength
|
|
252
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
253
|
+
def write_ncount
|
|
254
|
+
out = []
|
|
255
|
+
table_size = 1 << @table_log
|
|
256
|
+
bit_stream = 0
|
|
257
|
+
bit_count = 0
|
|
258
|
+
symbol = 0
|
|
259
|
+
alphabet_size = @max_symbol_value + 1
|
|
260
|
+
previous_is_zero = false
|
|
261
|
+
remaining = table_size + 1
|
|
262
|
+
threshold = table_size
|
|
263
|
+
nb_bits = @table_log + 1
|
|
264
|
+
|
|
265
|
+
bit_stream |= (@table_log - FSE_MIN_ACCURACY_LOG) << bit_count
|
|
266
|
+
bit_count += 4
|
|
267
|
+
|
|
268
|
+
while symbol < alphabet_size && remaining > 1
|
|
269
|
+
if previous_is_zero
|
|
270
|
+
start = symbol
|
|
271
|
+
symbol += 1 while symbol < alphabet_size &&
|
|
272
|
+
@distribution[symbol].zero?
|
|
273
|
+
if symbol == alphabet_size
|
|
274
|
+
raise Omnizip::CompressionError, "bad FSE distribution"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
while symbol >= start + 24
|
|
278
|
+
start += 24
|
|
279
|
+
bit_stream |= 0xFFFF << bit_count
|
|
280
|
+
out.push(bit_stream & 0xFF, (bit_stream >> 8) & 0xFF)
|
|
281
|
+
bit_stream >>= 16
|
|
282
|
+
end
|
|
283
|
+
while symbol >= start + 3
|
|
284
|
+
start += 3
|
|
285
|
+
bit_stream |= 3 << bit_count
|
|
286
|
+
bit_count += 2
|
|
287
|
+
end
|
|
288
|
+
bit_stream |= (symbol - start) << bit_count
|
|
289
|
+
bit_count += 2
|
|
290
|
+
if bit_count > 16
|
|
291
|
+
out.push(bit_stream & 0xFF, (bit_stream >> 8) & 0xFF)
|
|
292
|
+
bit_stream >>= 16
|
|
293
|
+
bit_count -= 16
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
count = @distribution[symbol]
|
|
298
|
+
symbol += 1
|
|
299
|
+
max = ((2 * threshold) - 1) - remaining
|
|
300
|
+
remaining -= count.negative? ? -count : count
|
|
301
|
+
count_val = count + 1
|
|
302
|
+
count_val += max if count_val >= threshold
|
|
303
|
+
|
|
304
|
+
bit_stream |= count_val << bit_count
|
|
305
|
+
bit_count += nb_bits
|
|
306
|
+
bit_count -= 1 if count_val < max
|
|
307
|
+
|
|
308
|
+
previous_is_zero = count_val == 1
|
|
309
|
+
raise Omnizip::CompressionError, "FSE NCount remaining < 1" if remaining < 1
|
|
310
|
+
|
|
311
|
+
while remaining < threshold
|
|
312
|
+
nb_bits -= 1
|
|
313
|
+
threshold >>= 1
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if bit_count > 16
|
|
317
|
+
out.push(bit_stream & 0xFF, (bit_stream >> 8) & 0xFF)
|
|
318
|
+
bit_stream >>= 16
|
|
319
|
+
bit_count -= 16
|
|
181
320
|
end
|
|
182
321
|
end
|
|
183
322
|
|
|
184
|
-
|
|
185
|
-
|
|
323
|
+
unless remaining == 1
|
|
324
|
+
raise Omnizip::CompressionError,
|
|
325
|
+
"FSE NCount remaining != 1 (#{remaining})"
|
|
326
|
+
end
|
|
186
327
|
|
|
187
|
-
|
|
188
|
-
|
|
328
|
+
if bit_count.positive?
|
|
329
|
+
n_bytes = (bit_count + 7) / 8
|
|
330
|
+
out.push(*Array.new(n_bytes) { |i| (bit_stream >> (8 * i)) & 0xFF })
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
out.pack("C*")
|
|
189
334
|
end
|
|
335
|
+
# rubocop:enable Metrics/MethodLength
|
|
190
336
|
|
|
191
|
-
#
|
|
337
|
+
# Encode `symbols` into a 2-state interleaved reverse
|
|
338
|
+
# bitstream (C FSE_compress_usingCTable).
|
|
192
339
|
#
|
|
193
|
-
# @
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
340
|
+
# @param symbols [Array<Integer>]
|
|
341
|
+
# @return [String] bitstream bytes ending with the 1-bit mark
|
|
342
|
+
# Direct port of C FSE_compress_usingCTable.
|
|
343
|
+
# rubocop:disable Metrics/AbcSize
|
|
344
|
+
def compress_symbols(symbols)
|
|
345
|
+
return "" if symbols.length <= 2
|
|
346
|
+
|
|
347
|
+
bitc = BitCStream.new
|
|
348
|
+
ip = symbols.length
|
|
349
|
+
|
|
350
|
+
ip -= 1
|
|
351
|
+
if symbols.length.odd?
|
|
352
|
+
s1 = CState.init2(self, symbols[ip])
|
|
353
|
+
ip -= 1
|
|
354
|
+
s2 = CState.init2(self, symbols[ip])
|
|
355
|
+
ip -= 1
|
|
356
|
+
s1.encode(bitc, self, symbols[ip])
|
|
357
|
+
bitc.flush
|
|
358
|
+
else
|
|
359
|
+
s2 = CState.init2(self, symbols[ip])
|
|
360
|
+
ip -= 1
|
|
361
|
+
s1 = CState.init2(self, symbols[ip])
|
|
362
|
+
end
|
|
363
|
+
# rubocop:enable Metrics/AbcSize
|
|
197
364
|
|
|
198
|
-
|
|
365
|
+
while ip.positive?
|
|
366
|
+
ip -= 1
|
|
367
|
+
s2.encode(bitc, self, symbols[ip])
|
|
368
|
+
break if ip.zero?
|
|
199
369
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
370
|
+
ip -= 1
|
|
371
|
+
s1.encode(bitc, self, symbols[ip])
|
|
372
|
+
bitc.flush
|
|
373
|
+
end
|
|
204
374
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
375
|
+
s2.flush(bitc)
|
|
376
|
+
s1.flush(bitc)
|
|
377
|
+
bitc.close
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# Serialize table description + bitstream for `symbols`.
|
|
381
|
+
#
|
|
382
|
+
# @return [String]
|
|
383
|
+
def compress(symbols)
|
|
384
|
+
write_ncount + compress_symbols(symbols)
|
|
385
|
+
end
|
|
209
386
|
|
|
210
|
-
|
|
211
|
-
|
|
387
|
+
# @return [Array<Integer>] state transition table
|
|
388
|
+
attr_reader :state_table
|
|
212
389
|
|
|
213
|
-
|
|
214
|
-
|
|
390
|
+
# @return [Array<SymbolTT>]
|
|
391
|
+
attr_reader :symbol_tt
|
|
215
392
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
393
|
+
private
|
|
394
|
+
|
|
395
|
+
# Direct port of C FSE_buildCTable.
|
|
396
|
+
# rubocop:disable Metrics/AbcSize
|
|
397
|
+
def build_encoding_tables
|
|
398
|
+
table_size = 1 << @table_log
|
|
399
|
+
step = (table_size >> 1) + (table_size >> 3) + 3
|
|
400
|
+
mask = table_size - 1
|
|
401
|
+
max_sv1 = @max_symbol_value + 1
|
|
402
|
+
|
|
403
|
+
table_symbol = Array.new(table_size, 0xFFFF)
|
|
404
|
+
high_threshold = table_size - 1
|
|
405
|
+
|
|
406
|
+
cumul = Array.new(max_sv1 + 1, 0)
|
|
407
|
+
(1..max_sv1).each do |u|
|
|
408
|
+
if @distribution[u - 1] == -1
|
|
409
|
+
cumul[u] = cumul[u - 1] + 1
|
|
410
|
+
table_symbol[high_threshold] = u - 1
|
|
411
|
+
high_threshold -= 1
|
|
412
|
+
else
|
|
413
|
+
cumul[u] = cumul[u - 1] + @distribution[u - 1]
|
|
414
|
+
end
|
|
415
|
+
# rubocop:enable Metrics/AbcSize
|
|
416
|
+
end
|
|
417
|
+
cumul[max_sv1] = table_size + 1
|
|
222
418
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
baseline: 0, # Will be calculated
|
|
227
|
-
}
|
|
419
|
+
position = 0
|
|
420
|
+
@distribution.each_with_index do |freq, symbol|
|
|
421
|
+
next unless freq.positive?
|
|
228
422
|
|
|
423
|
+
freq.times do
|
|
424
|
+
table_symbol[position] = symbol
|
|
229
425
|
position = (position + step) & mask
|
|
426
|
+
position = (position + step) & mask while position > high_threshold
|
|
230
427
|
end
|
|
428
|
+
end
|
|
231
429
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
430
|
+
@state_table = Array.new(table_size, 0)
|
|
431
|
+
table_size.times do |u|
|
|
432
|
+
s = table_symbol[u]
|
|
433
|
+
@state_table[cumul[s]] = table_size + u
|
|
434
|
+
cumul[s] += 1
|
|
236
435
|
end
|
|
237
436
|
|
|
238
|
-
|
|
239
|
-
|
|
437
|
+
@symbol_tt = Array.new(max_sv1) { SymbolTT.new(0, 0) }
|
|
438
|
+
total = 0
|
|
439
|
+
max_sv1.times do |s|
|
|
440
|
+
case @distribution[s]
|
|
441
|
+
when 0
|
|
442
|
+
@symbol_tt[s].delta_nb_bits = ((@table_log + 1) << 16) -
|
|
443
|
+
(1 << @table_log)
|
|
444
|
+
when -1, 1
|
|
445
|
+
@symbol_tt[s].delta_nb_bits = (@table_log << 16) -
|
|
446
|
+
(1 << @table_log)
|
|
447
|
+
@symbol_tt[s].delta_find_state = total - 1
|
|
448
|
+
total += 1
|
|
449
|
+
else
|
|
450
|
+
n = @distribution[s]
|
|
451
|
+
max_bits_out = @table_log - Constants.highbit32(n - 1)
|
|
452
|
+
min_state_plus = n << max_bits_out
|
|
453
|
+
@symbol_tt[s].delta_nb_bits = (max_bits_out << 16) - min_state_plus
|
|
454
|
+
@symbol_tt[s].delta_find_state = total - n
|
|
455
|
+
total += n
|
|
456
|
+
end
|
|
457
|
+
end
|
|
240
458
|
end
|
|
241
459
|
|
|
242
|
-
#
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
460
|
+
# Forward bit writer (C BIT_CStream): accumulates at the low
|
|
461
|
+
# end and flushes whole bytes.
|
|
462
|
+
class BitCStream
|
|
463
|
+
def initialize
|
|
464
|
+
@container = 0
|
|
465
|
+
@bit_pos = 0
|
|
466
|
+
end
|
|
248
467
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
468
|
+
# @param value [Integer] bits in the low end
|
|
469
|
+
# @param nb_bits [Integer] 0..31
|
|
470
|
+
def add_bits(value, nb_bits)
|
|
471
|
+
mask = (1 << nb_bits) - 1
|
|
472
|
+
@container |= (value & mask) << @bit_pos
|
|
473
|
+
@bit_pos += nb_bits
|
|
252
474
|
end
|
|
253
475
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
476
|
+
def flush
|
|
477
|
+
nb_bytes = @bit_pos >> 3
|
|
478
|
+
@bytes ||= []
|
|
479
|
+
nb_bytes.times do |i|
|
|
480
|
+
@bytes << ((@container >> (8 * i)) & 0xFF)
|
|
259
481
|
end
|
|
482
|
+
@container >>= nb_bytes * 8
|
|
483
|
+
@bit_pos &= 7
|
|
260
484
|
end
|
|
261
|
-
end
|
|
262
485
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
# Use lower bits of current state to select state
|
|
272
|
-
((current_state & ((1 << num_bits) - 1)) << (@accuracy_log - num_bits)) |
|
|
273
|
-
(entry[:baseline] >> num_bits)
|
|
274
|
-
else
|
|
275
|
-
entry[:baseline]
|
|
486
|
+
# Add the 1 end-mark bit and flush everything.
|
|
487
|
+
#
|
|
488
|
+
# @return [String]
|
|
489
|
+
def close
|
|
490
|
+
add_bits(1, 1)
|
|
491
|
+
flush
|
|
492
|
+
@bytes << (@container & 0xFF) if @bit_pos.positive?
|
|
493
|
+
@bytes.pack("C*")
|
|
276
494
|
end
|
|
277
495
|
end
|
|
278
496
|
|
|
279
|
-
#
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
bitstream << ((value >> i) & 1)
|
|
283
|
-
end
|
|
284
|
-
end
|
|
497
|
+
# FSE encoder state (value in [table_size, 2*table_size)).
|
|
498
|
+
class CState
|
|
499
|
+
attr_reader :value
|
|
285
500
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
bits = bits.dup
|
|
290
|
-
while bits.length % 8 != 0
|
|
291
|
-
bits << 0
|
|
501
|
+
def initialize(value, state_log)
|
|
502
|
+
@value = value
|
|
503
|
+
@state_log = state_log
|
|
292
504
|
end
|
|
293
505
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
506
|
+
# Initialize to the baseline for the first symbol to encode
|
|
507
|
+
# (the last to decode); C FSE_initCState2.
|
|
508
|
+
# rubocop:disable-next Metrics/AbcSize
|
|
509
|
+
def self.init2(table, symbol)
|
|
510
|
+
s_tt = table.symbol_tt[symbol]
|
|
511
|
+
nb_bits_out = (s_tt.delta_nb_bits + (1 << 15)) >> 16
|
|
512
|
+
value = (nb_bits_out << 16) - s_tt.delta_nb_bits
|
|
513
|
+
idx = (value >> nb_bits_out) + s_tt.delta_find_state
|
|
514
|
+
new(table.state_table[idx], table.table_log)
|
|
301
515
|
end
|
|
302
516
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
517
|
+
def encode(bitc, table, symbol)
|
|
518
|
+
s_tt = table.symbol_tt[symbol]
|
|
519
|
+
nb_bits_out = (@value + s_tt.delta_nb_bits) >> 16
|
|
520
|
+
bitc.add_bits(@value, nb_bits_out)
|
|
521
|
+
idx = (@value >> nb_bits_out) + s_tt.delta_find_state
|
|
522
|
+
@value = table.state_table[idx]
|
|
523
|
+
end
|
|
309
524
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
log += 1
|
|
314
|
-
temp >>= 1
|
|
525
|
+
def flush(bitc)
|
|
526
|
+
bitc.add_bits(@value, @state_log)
|
|
527
|
+
bitc.flush
|
|
315
528
|
end
|
|
316
|
-
log
|
|
317
529
|
end
|
|
318
530
|
end
|
|
319
531
|
end
|