Pure OCaml implementation of the Brotli compression algorithm
at main 1044 lines 38 kB view raw
1(* Brotli compression implementation *) 2(* Supports quality levels 0-11 with context modeling, block splitting, and optimal parsing *) 3 4(* Re-export from LZ77 for backward compatibility *) 5let min_match = Lz77.min_match 6 7(* Number of literal contexts *) 8let num_literal_contexts = 64 9 10(* Insert length code tables *) 11let insert_length_n_bits = [| 12 0; 0; 0; 0; 0; 0; 1; 1; 2; 2; 3; 3; 4; 4; 5; 5; 6; 7; 8; 9; 10; 12; 14; 24 13|] 14 15let insert_length_offset = [| 16 0; 1; 2; 3; 4; 5; 6; 8; 10; 14; 18; 26; 34; 50; 66; 98; 130; 194; 322; 578; 1090; 2114; 6210; 22594 17|] 18 19(* Get insert length code *) 20let get_insert_code length = 21 let rec find i = 22 if i >= 23 then 23 23 else if length < insert_length_offset.(i + 1) then i 24 else find (i + 1) 25 in 26 find 0 27 28(* Get copy length code *) 29let get_copy_code length = 30 let copy_length_offset = [| 31 2; 3; 4; 5; 6; 7; 8; 9; 10; 12; 14; 18; 22; 30; 38; 54; 70; 102; 134; 198; 326; 582; 1094; 2118 32 |] in 33 let rec find i = 34 if i >= 23 then 23 35 else if length < copy_length_offset.(i + 1) then i 36 else find (i + 1) 37 in 38 find 0 39 40(* Command code lookup tables from RFC 7932 *) 41let insert_range_lut = [| 0; 0; 8; 8; 0; 16; 8; 16; 16 |] 42let copy_range_lut = [| 0; 8; 0; 8; 16; 0; 16; 8; 16 |] 43 44(* Build command code from insert_code and copy_code. 45 use_implicit_distance: true ONLY for distance code 0 (last distance) 46 47 Per RFC 7932, command codes have range_idx in bits 7-6: 48 - range_idx 0-1 (cmd_code 0-127): Distance code 0 is IMPLICIT (not read from stream) 49 The decoder automatically uses distance code 0 (last used distance). 50 - range_idx 2-8 (cmd_code 128+): Distance code is EXPLICIT (read from stream) 51 Short codes 0-15 and long codes >= 16 are all written explicitly. 52 53 IMPORTANT: Only dist_code=Some 0 can use implicit distance (range_idx 0-1). 54 For all other short codes (1-15), we must use explicit distance (range_idx >= 2). 55*) 56let get_command_code insert_code copy_code use_implicit_distance = 57 let found = ref None in 58 59 (* Only use range_idx 0-1 for implicit distance code 0 *) 60 if use_implicit_distance then begin 61 for r = 0 to 1 do 62 if !found = None then begin 63 let insert_base = insert_range_lut.(r) in 64 let copy_base = copy_range_lut.(r) in 65 let insert_delta = insert_code - insert_base in 66 let copy_delta = copy_code - copy_base in 67 if insert_delta >= 0 && insert_delta < 8 && 68 copy_delta >= 0 && copy_delta < 8 then begin 69 let cmd_code = (r lsl 6) lor (insert_delta lsl 3) lor copy_delta in 70 found := Some cmd_code 71 end 72 end 73 done 74 end; 75 76 (* Use range_idx 2-8 for explicit distance (including short codes 0-15) *) 77 if !found = None then begin 78 for r = 2 to 8 do 79 if !found = None then begin 80 let adjusted_r = r - 2 in 81 let insert_base = insert_range_lut.(adjusted_r) in 82 let copy_base = copy_range_lut.(adjusted_r) in 83 let insert_delta = insert_code - insert_base in 84 let copy_delta = copy_code - copy_base in 85 if insert_delta >= 0 && insert_delta < 8 && 86 copy_delta >= 0 && copy_delta < 8 then begin 87 let cmd_code = (r lsl 6) lor (insert_delta lsl 3) lor copy_delta in 88 found := Some cmd_code 89 end 90 end 91 done 92 end; 93 94 match !found with 95 | Some cmd_code -> cmd_code 96 | None -> 97 (* Fallback - shouldn't happen if LZ77 limits copy_len properly *) 98 let insert_delta = min insert_code 7 in 99 let copy_delta = min copy_code 7 in 100 (2 lsl 6) lor (insert_delta lsl 3) lor copy_delta 101 102(* Encode window bits *) 103let encode_window_bits bw = 104 Bit_writer.write_bits bw 1 1; 105 Bit_writer.write_bits bw 3 5 (* 22-bit window *) 106 107(* Write empty last block *) 108let write_empty_last_block bw = 109 Bit_writer.write_bits bw 1 1; 110 Bit_writer.write_bits bw 1 1 111 112(* Write meta-block header *) 113let write_meta_block_header bw length is_last is_uncompressed = 114 Bit_writer.write_bits bw 1 (if is_last then 1 else 0); 115 if is_last then 116 Bit_writer.write_bits bw 1 0; (* ISEMPTY = 0 for non-empty last block *) 117 let nibbles = if length - 1 < (1 lsl 16) then 4 else if length - 1 < (1 lsl 20) then 5 else 6 in 118 Bit_writer.write_bits bw 2 (nibbles - 4); 119 for i = 0 to nibbles - 1 do 120 Bit_writer.write_bits bw 4 (((length - 1) lsr (i * 4)) land 0xF) 121 done; 122 if not is_last then 123 Bit_writer.write_bits bw 1 (if is_uncompressed then 1 else 0) 124 125(* Write uncompressed block *) 126let write_uncompressed_block bw src src_pos length = 127 write_meta_block_header bw length false true; 128 Bit_writer.align_to_byte bw; 129 Bit_writer.copy_bytes bw ~src ~src_pos ~len:length 130 131(* Count bits needed to represent values 0 to n-1 (ceiling of log2(n)) *) 132let count_bits n = 133 if n <= 1 then 0 134 else 135 let rec count v b = if v = 0 then b else count (v lsr 1) (b + 1) in 136 count (n - 1) 0 137 138(* Write simple prefix code - 1 to 4 symbols *) 139let write_simple_prefix_code bw symbols alphabet_size = 140 let n = Array.length symbols in 141 Bit_writer.write_bits bw 2 1; (* HSKIP = 1 means simple code *) 142 Bit_writer.write_bits bw 2 (n - 1); (* NSYM - 1 *) 143 let bits = count_bits (alphabet_size - 1) in 144 for i = 0 to n - 1 do 145 Bit_writer.write_bits bw bits symbols.(i) 146 done; 147 if n = 4 then Bit_writer.write_bits bw 1 0 148 149(* Static Huffman code for code lengths *) 150let write_code_length_symbol bw len = 151 match len with 152 | 0 -> Bit_writer.write_bits bw 2 0 153 | 1 -> Bit_writer.write_bits bw 4 7 154 | 2 -> Bit_writer.write_bits bw 3 3 155 | 3 -> Bit_writer.write_bits bw 2 2 156 | 4 -> Bit_writer.write_bits bw 2 1 157 | 5 -> Bit_writer.write_bits bw 4 15 158 | _ -> Bit_writer.write_bits bw 2 0 159 160(* Build valid Huffman code lengths using Kraft inequality *) 161let build_valid_code_lengths freqs max_len = 162 let n = Array.length freqs in 163 let lengths = Array.make n 0 in 164 let symbols = ref [] in 165 for i = n - 1 downto 0 do 166 if freqs.(i) > 0 then 167 symbols := (freqs.(i), i) :: !symbols 168 done; 169 let num_symbols = List.length !symbols in 170 if num_symbols = 0 then lengths 171 else if num_symbols = 1 then begin 172 let (_, sym) = List.hd !symbols in 173 lengths.(sym) <- 1; 174 lengths 175 end 176 else begin 177 let sorted = List.sort (fun (f1, _) (f2, _) -> compare f2 f1) !symbols in 178 let bits_needed = count_bits num_symbols in 179 let base_len = min max_len (max bits_needed 1) in 180 let len_to_use = ref base_len in 181 while (1 lsl !len_to_use) < num_symbols && !len_to_use < max_len do 182 incr len_to_use 183 done; 184 let slots_used = ref num_symbols in 185 let total_slots = 1 lsl !len_to_use in 186 List.iter (fun (_, sym) -> 187 let extra_slots = total_slots - !slots_used in 188 if extra_slots > 0 && !len_to_use > 1 then begin 189 let shorter_len = !len_to_use - 1 in 190 let extra_needed = (1 lsl (!len_to_use - shorter_len)) - 1 in 191 if extra_slots >= extra_needed then begin 192 lengths.(sym) <- shorter_len; 193 slots_used := !slots_used + extra_needed 194 end else 195 lengths.(sym) <- !len_to_use 196 end else 197 lengths.(sym) <- !len_to_use 198 ) sorted; 199 lengths 200 end 201 202(* Build canonical Huffman codes from lengths *) 203let build_codes lengths = 204 let n = Array.length lengths in 205 let codes = Array.make n 0 in 206 let max_len = Array.fold_left max 0 lengths in 207 if max_len = 0 then codes 208 else begin 209 let bl_count = Array.make (max_len + 1) 0 in 210 Array.iter (fun l -> if l > 0 then bl_count.(l) <- bl_count.(l) + 1) lengths; 211 let next_code = Array.make (max_len + 1) 0 in 212 let code = ref 0 in 213 for bits = 1 to max_len do 214 code := (!code + bl_count.(bits - 1)) lsl 1; 215 next_code.(bits) <- !code 216 done; 217 for i = 0 to n - 1 do 218 let len = lengths.(i) in 219 if len > 0 then begin 220 codes.(i) <- next_code.(len); 221 next_code.(len) <- next_code.(len) + 1 222 end 223 done; 224 codes 225 end 226 227(* Reverse bits for canonical Huffman *) 228let reverse_bits v n = 229 let r = ref 0 in 230 let v = ref v in 231 for _ = 0 to n - 1 do 232 r := (!r lsl 1) lor (!v land 1); 233 v := !v lsr 1 234 done; 235 !r 236 237(* Write a Huffman symbol *) 238let write_symbol bw codes lengths sym = 239 let len = lengths.(sym) in 240 if len > 0 then 241 Bit_writer.write_bits bw len (reverse_bits codes.(sym) len) 242 243(* RLE encoding for code lengths *) 244let emit_zeros_rle symbols_ref extras_ref total_ref run_len = 245 if run_len < 3 then begin 246 for _ = 1 to run_len do 247 symbols_ref := 0 :: !symbols_ref; 248 extras_ref := 0 :: !extras_ref; 249 incr total_ref 250 done 251 end else begin 252 let reps = ref (run_len - 3) in 253 let rec build acc_codes acc_extras = 254 let e = !reps land 7 in 255 reps := !reps lsr 3; 256 if !reps = 0 then 257 (17 :: acc_codes, e :: acc_extras) 258 else begin 259 reps := !reps - 1; 260 build (17 :: acc_codes) (e :: acc_extras) 261 end 262 in 263 let (codes, extras) = build [] [] in 264 List.iter2 (fun c e -> 265 symbols_ref := c :: !symbols_ref; 266 extras_ref := e :: !extras_ref 267 ) codes extras; 268 total_ref := !total_ref + run_len 269 end 270 271let emit_nonzero_rle symbols_ref extras_ref total_ref run_len prev_value_ref value = 272 let to_write = ref run_len in 273 if !prev_value_ref <> value then begin 274 symbols_ref := value :: !symbols_ref; 275 extras_ref := 0 :: !extras_ref; 276 prev_value_ref := value; 277 decr to_write; 278 incr total_ref 279 end; 280 if !to_write < 3 then begin 281 for _ = 1 to !to_write do 282 symbols_ref := value :: !symbols_ref; 283 extras_ref := 0 :: !extras_ref 284 done; 285 total_ref := !total_ref + !to_write 286 end else begin 287 let reps = ref (!to_write - 3) in 288 let rec build acc_codes acc_extras = 289 let e = !reps land 3 in 290 reps := !reps lsr 2; 291 if !reps = 0 then 292 (16 :: acc_codes, e :: acc_extras) 293 else begin 294 reps := !reps - 1; 295 build (16 :: acc_codes) (e :: acc_extras) 296 end 297 in 298 let (codes, extras) = build [] [] in 299 List.iter2 (fun c e -> 300 symbols_ref := c :: !symbols_ref; 301 extras_ref := e :: !extras_ref 302 ) codes extras; 303 total_ref := !total_ref + !to_write 304 end 305 306let generate_rle_sequence lengths num_symbols = 307 let symbols = ref [] in 308 let extras = ref [] in 309 let prev_value = ref 8 in 310 let total = ref 0 in 311 let i = ref 0 in 312 while !i < num_symbols do 313 let value = if !i < Array.length lengths then lengths.(!i) else 0 in 314 let run_start = !i in 315 while !i < num_symbols && 316 (if !i < Array.length lengths then lengths.(!i) else 0) = value do 317 incr i 318 done; 319 let run_len = !i - run_start in 320 if value = 0 then 321 emit_zeros_rle symbols extras total run_len 322 else 323 emit_nonzero_rle symbols extras total run_len prev_value value 324 done; 325 let syms = Array.of_list (List.rev !symbols) in 326 let exts = Array.of_list (List.rev !extras) in 327 (syms, exts) 328 329(* Write complex prefix code with RLE encoding *) 330let write_complex_prefix_code bw lengths alphabet_size = 331 let last_nonzero = ref (-1) in 332 for i = 0 to min (alphabet_size - 1) (Array.length lengths - 1) do 333 if lengths.(i) > 0 then last_nonzero := i 334 done; 335 let num_symbols = !last_nonzero + 1 in 336 let (rle_symbols, rle_extra) = generate_rle_sequence lengths num_symbols in 337 let cl_histogram = Array.make Constants.code_length_codes 0 in 338 Array.iter (fun sym -> cl_histogram.(sym) <- cl_histogram.(sym) + 1) rle_symbols; 339 let cl_depths = build_valid_code_lengths cl_histogram Constants.huffman_max_code_length_code_length in 340 let num_codes = ref 0 in 341 for i = 0 to Constants.code_length_codes - 1 do 342 if cl_histogram.(i) > 0 then incr num_codes 343 done; 344 let skip_some = 345 if cl_depths.(Constants.code_length_code_order.(0)) = 0 && 346 cl_depths.(Constants.code_length_code_order.(1)) = 0 then 347 if cl_depths.(Constants.code_length_code_order.(2)) = 0 then 3 348 else 2 349 else 0 350 in 351 let codes_to_store = ref Constants.code_length_codes in 352 if !num_codes > 1 then begin 353 while !codes_to_store > 0 && 354 cl_depths.(Constants.code_length_code_order.(!codes_to_store - 1)) = 0 do 355 decr codes_to_store 356 done 357 end; 358 Bit_writer.write_bits bw 2 skip_some; 359 let space = ref 32 in 360 for i = skip_some to !codes_to_store - 1 do 361 if !space > 0 then begin 362 let idx = Constants.code_length_code_order.(i) in 363 let l = cl_depths.(idx) in 364 write_code_length_symbol bw l; 365 if l <> 0 then 366 space := !space - (32 lsr l) 367 end 368 done; 369 let cl_codes = build_codes cl_depths in 370 for i = 0 to Array.length rle_symbols - 1 do 371 let sym = rle_symbols.(i) in 372 if !num_codes > 1 then 373 write_symbol bw cl_codes cl_depths sym; 374 if sym = 16 then 375 Bit_writer.write_bits bw 2 rle_extra.(i) 376 else if sym = 17 then 377 Bit_writer.write_bits bw 3 rle_extra.(i) 378 done 379 380(* Write Huffman code definition - choose simple or complex *) 381let write_huffman_code bw lengths alphabet_size = 382 let symbols = ref [] in 383 for i = 0 to min (alphabet_size - 1) (Array.length lengths - 1) do 384 if i < Array.length lengths && lengths.(i) > 0 then 385 symbols := (i, lengths.(i)) :: !symbols 386 done; 387 let sorted = List.sort (fun (s1, l1) (s2, l2) -> 388 let c = compare l1 l2 in 389 if c <> 0 then c else compare s1 s2 390 ) !symbols in 391 let symbols = Array.of_list (List.map fst sorted) in 392 let num_symbols = Array.length symbols in 393 if num_symbols = 0 then 394 write_simple_prefix_code bw [|0|] alphabet_size 395 else if num_symbols <= 4 then 396 write_simple_prefix_code bw symbols alphabet_size 397 else 398 write_complex_prefix_code bw lengths alphabet_size 399 400(* Count used symbols in frequency array *) 401let count_used_symbols freqs = 402 let count = ref 0 in 403 Array.iter (fun f -> if f > 0 then incr count) freqs; 404 !count 405 406(* Write context map using RLE and IMTF encoding *) 407(* Encode a variable length uint8 (matches decode_var_len_uint8 in decoder) *) 408let write_var_len_uint8 bw n = 409 if n = 0 then 410 Bit_writer.write_bits bw 1 0 411 else if n = 1 then begin 412 Bit_writer.write_bits bw 1 1; 413 Bit_writer.write_bits bw 3 0 (* nbits = 0 means value 1 *) 414 end else begin 415 Bit_writer.write_bits bw 1 1; 416 (* Find nbits such that (1 << nbits) <= n < (1 << (nbits + 1)) *) 417 let rec find_nbits nb = 418 if n < (1 lsl (nb + 1)) then nb 419 else find_nbits (nb + 1) 420 in 421 let nbits = find_nbits 1 in 422 Bit_writer.write_bits bw 3 nbits; 423 Bit_writer.write_bits bw nbits (n - (1 lsl nbits)) 424 end 425 426let write_context_map bw context_map num_trees = 427 (* Write NTREES - 1 using variable length encoding *) 428 write_var_len_uint8 bw (num_trees - 1); 429 430 if num_trees > 1 then begin 431 (* Write RLEMAX flag: 0 = no RLE *) 432 Bit_writer.write_bits bw 1 0; 433 434 (* With RLEMAX=0, alphabet size is just num_trees, symbols are values directly *) 435 let map_len = Array.length context_map in 436 let freq = Array.make num_trees 0 in 437 for i = 0 to map_len - 1 do 438 freq.(context_map.(i)) <- freq.(context_map.(i)) + 1 439 done; 440 441 (* Build Huffman code for context map values *) 442 let lengths = build_valid_code_lengths freq 15 in 443 let codes = build_codes lengths in 444 445 (* Write the Huffman code for num_trees symbols *) 446 write_huffman_code bw lengths num_trees; 447 448 (* Write the context map values *) 449 let num_symbols = count_used_symbols freq in 450 for i = 0 to map_len - 1 do 451 if num_symbols > 1 then 452 write_symbol bw codes lengths context_map.(i) 453 done; 454 455 (* Write IMTF flag: 0 = no inverse move-to-front *) 456 Bit_writer.write_bits bw 1 0 457 end 458 459(* Copy length extra bits table *) 460let copy_length_n_bits = [| 461 0; 0; 0; 0; 0; 0; 0; 0; 1; 1; 2; 2; 3; 3; 4; 4; 5; 5; 6; 7; 8; 9; 10; 24 462|] 463 464let copy_length_offset = [| 465 2; 3; 4; 5; 6; 7; 8; 9; 10; 12; 14; 18; 22; 30; 38; 54; 70; 102; 134; 198; 326; 582; 1094; 2118 466|] 467 468(* Encode distance for NPOSTFIX=0, NDIRECT=0 *) 469let encode_distance distance = 470 if distance < 1 then 471 (16, 1, 0) 472 else begin 473 let d = distance - 1 in 474 let nbits = ref 1 in 475 let range_start = ref 0 in 476 while d >= !range_start + (1 lsl (!nbits + 1)) && !nbits < 24 do 477 range_start := !range_start + (1 lsl (!nbits + 1)); 478 incr nbits 479 done; 480 let half_size = 1 lsl !nbits in 481 let d_in_range = d - !range_start in 482 let lcode = if d_in_range >= half_size then 1 else 0 in 483 let dc = 2 * (!nbits - 1) + lcode in 484 let code = 16 + dc in 485 let extra = d_in_range - (lcode * half_size) in 486 (code, !nbits, extra) 487 end 488 489(* Quality level for dictionary matching *) 490let current_quality = ref 1 491 492(* Write a compressed block with context modeling for quality >= 5 *) 493let write_compressed_block_with_context bw src _src_pos _src_len is_last context_mode context_map num_lit_trees num_dist_trees dist_context_map commands = 494 let num_distance_codes = 16 + 48 in 495 496 (* Count frequencies for context-aware literal encoding *) 497 let lit_freqs = Array.init num_lit_trees (fun _ -> Array.make 256 0) in 498 let cmd_freq = Array.make 704 0 in 499 (* Distance frequencies per tree *) 500 let dist_freqs = Array.init num_dist_trees (fun _ -> Array.make num_distance_codes 0) in 501 502 (* Track previous bytes for context calculation *) 503 let prev1 = ref 0 in 504 let prev2 = ref 0 in 505 506 (* Helper to get distance code value *) 507 let get_dist_code_val dist_code distance = 508 match dist_code with 509 | Some code -> code 510 | None -> 511 let dist_code_val, _, _ = encode_distance distance in 512 min dist_code_val (num_distance_codes - 1) 513 in 514 515 (* Count literals with context and build command/distance frequencies *) 516 List.iter (fun cmd -> 517 match cmd with 518 | Lz77.Literals { start; len } -> 519 for i = start to start + len - 1 do 520 let c = Char.code (Bytes.get src i) in 521 let ctx_id = Context.get_context context_mode ~prev_byte1:!prev1 ~prev_byte2:!prev2 in 522 let tree_id = context_map.(ctx_id) in 523 lit_freqs.(tree_id).(c) <- lit_freqs.(tree_id).(c) + 1; 524 prev2 := !prev1; 525 prev1 := c 526 done; 527 let insert_code = get_insert_code len in 528 let copy_code = 0 in 529 let cmd_code = get_command_code insert_code copy_code false in 530 cmd_freq.(cmd_code) <- cmd_freq.(cmd_code) + 1; 531 (* Literals command with copy_code=0 has copy_len=2, so dist context = 0 *) 532 let dist_tree = dist_context_map.(0) in 533 dist_freqs.(dist_tree).(0) <- dist_freqs.(dist_tree).(0) + 1 534 | Lz77.InsertCopy { lit_start; lit_len; copy_len; distance; dist_code } -> 535 for i = lit_start to lit_start + lit_len - 1 do 536 let c = Char.code (Bytes.get src i) in 537 let ctx_id = Context.get_context context_mode ~prev_byte1:!prev1 ~prev_byte2:!prev2 in 538 let tree_id = context_map.(ctx_id) in 539 lit_freqs.(tree_id).(c) <- lit_freqs.(tree_id).(c) + 1; 540 prev2 := !prev1; 541 prev1 := c 542 done; 543 let insert_code = get_insert_code lit_len in 544 let copy_code = get_copy_code copy_len in 545 let use_implicit = dist_code = Some 0 in 546 let cmd_code = get_command_code insert_code copy_code use_implicit in 547 let range_idx = cmd_code lsr 6 in 548 cmd_freq.(cmd_code) <- cmd_freq.(cmd_code) + 1; 549 if range_idx >= 2 then begin 550 let dist_ctx = Context.distance_context copy_len in 551 let dist_tree = dist_context_map.(dist_ctx) in 552 let code_val = get_dist_code_val dist_code distance in 553 dist_freqs.(dist_tree).(code_val) <- dist_freqs.(dist_tree).(code_val) + 1 554 end 555 ) commands; 556 557 (* Build Huffman codes for each literal tree *) 558 let lit_lengths_arr = Array.init num_lit_trees (fun i -> 559 build_valid_code_lengths lit_freqs.(i) 15 560 ) in 561 let lit_codes_arr = Array.init num_lit_trees (fun i -> 562 build_codes lit_lengths_arr.(i) 563 ) in 564 let cmd_lengths = build_valid_code_lengths cmd_freq 15 in 565 let cmd_codes = build_codes cmd_lengths in 566 (* Build Huffman codes for each distance tree *) 567 let dist_lengths_arr = Array.init num_dist_trees (fun i -> 568 build_valid_code_lengths dist_freqs.(i) 15 569 ) in 570 let dist_codes_arr = Array.init num_dist_trees (fun i -> 571 build_codes dist_lengths_arr.(i) 572 ) in 573 574 (* Calculate total uncompressed size *) 575 let total_len = List.fold_left (fun acc cmd -> 576 match cmd with 577 | Lz77.Literals { len; _ } -> acc + len 578 | Lz77.InsertCopy { lit_len; copy_len; _ } -> acc + lit_len + copy_len 579 ) 0 commands in 580 581 (* Write meta-block header *) 582 write_meta_block_header bw total_len is_last false; 583 584 (* Block type counts: 1 for each category *) 585 Bit_writer.write_bits bw 1 0; (* NBLTYPESL = 1 *) 586 Bit_writer.write_bits bw 1 0; (* NBLTYPESI = 1 *) 587 Bit_writer.write_bits bw 1 0; (* NBLTYPESD = 1 *) 588 589 (* Distance parameters: NPOSTFIX=0, NDIRECT=0 *) 590 Bit_writer.write_bits bw 2 0; 591 Bit_writer.write_bits bw 4 0; 592 593 (* Context mode for literal block type 0 *) 594 Bit_writer.write_bits bw 2 (Context.int_of_mode context_mode); 595 596 (* Literal context map *) 597 write_context_map bw context_map num_lit_trees; 598 599 (* Distance context map: 4 contexts per block type *) 600 write_context_map bw dist_context_map num_dist_trees; 601 602 (* Write Huffman codes for all literal trees *) 603 for i = 0 to num_lit_trees - 1 do 604 write_huffman_code bw lit_lengths_arr.(i) 256 605 done; 606 write_huffman_code bw cmd_lengths 704; 607 (* Write Huffman codes for all distance trees *) 608 for i = 0 to num_dist_trees - 1 do 609 write_huffman_code bw dist_lengths_arr.(i) num_distance_codes 610 done; 611 612 (* Write commands with context-aware literal and distance encoding *) 613 let num_cmd_symbols = count_used_symbols cmd_freq in 614 prev1 := 0; 615 prev2 := 0; 616 617 List.iter (fun cmd -> 618 match cmd with 619 | Lz77.Literals { start; len } -> 620 let insert_code = get_insert_code len in 621 let copy_code = 0 in 622 let cmd_code = get_command_code insert_code copy_code false in 623 if num_cmd_symbols > 1 then 624 write_symbol bw cmd_codes cmd_lengths cmd_code; 625 if insert_length_n_bits.(insert_code) > 0 then begin 626 let extra = len - insert_length_offset.(insert_code) in 627 Bit_writer.write_bits bw insert_length_n_bits.(insert_code) extra 628 end; 629 for i = start to start + len - 1 do 630 let c = Char.code (Bytes.get src i) in 631 let ctx_id = Context.get_context context_mode ~prev_byte1:!prev1 ~prev_byte2:!prev2 in 632 let tree_id = context_map.(ctx_id) in 633 let num_symbols = count_used_symbols lit_freqs.(tree_id) in 634 if num_symbols > 1 then 635 write_symbol bw lit_codes_arr.(tree_id) lit_lengths_arr.(tree_id) c; 636 prev2 := !prev1; 637 prev1 := c 638 done 639 640 | Lz77.InsertCopy { lit_start; lit_len; copy_len; distance; dist_code } -> 641 let insert_code = get_insert_code lit_len in 642 let copy_code = get_copy_code copy_len in 643 let use_implicit = dist_code = Some 0 in 644 let cmd_code = get_command_code insert_code copy_code use_implicit in 645 let range_idx = cmd_code lsr 6 in 646 if num_cmd_symbols > 1 then 647 write_symbol bw cmd_codes cmd_lengths cmd_code; 648 if insert_length_n_bits.(insert_code) > 0 then begin 649 let extra = lit_len - insert_length_offset.(insert_code) in 650 Bit_writer.write_bits bw insert_length_n_bits.(insert_code) extra 651 end; 652 if copy_length_n_bits.(copy_code) > 0 then begin 653 let extra = copy_len - copy_length_offset.(copy_code) in 654 Bit_writer.write_bits bw copy_length_n_bits.(copy_code) extra 655 end; 656 for i = lit_start to lit_start + lit_len - 1 do 657 let c = Char.code (Bytes.get src i) in 658 let ctx_id = Context.get_context context_mode ~prev_byte1:!prev1 ~prev_byte2:!prev2 in 659 let tree_id = context_map.(ctx_id) in 660 let num_symbols = count_used_symbols lit_freqs.(tree_id) in 661 if num_symbols > 1 then 662 write_symbol bw lit_codes_arr.(tree_id) lit_lengths_arr.(tree_id) c; 663 prev2 := !prev1; 664 prev1 := c 665 done; 666 if range_idx >= 2 then begin 667 let dist_ctx = Context.distance_context copy_len in 668 let dist_tree = dist_context_map.(dist_ctx) in 669 let num_dist_symbols = count_used_symbols dist_freqs.(dist_tree) in 670 match dist_code with 671 | Some code -> 672 if num_dist_symbols > 1 then 673 write_symbol bw dist_codes_arr.(dist_tree) dist_lengths_arr.(dist_tree) code 674 | None -> 675 let dist_code_val, nbits, extra = encode_distance distance in 676 if num_dist_symbols > 1 then 677 write_symbol bw dist_codes_arr.(dist_tree) dist_lengths_arr.(dist_tree) dist_code_val; 678 if nbits > 0 then 679 Bit_writer.write_bits bw nbits extra 680 end 681 ) commands 682 683(* Write a compressed block with LZ77 commands *) 684let write_compressed_block bw src src_pos src_len is_last = 685 (* Dictionary matching provides additional compression for text content *) 686 let use_dict = !current_quality >= 3 in 687 let quality = !current_quality in 688 689 (* Generate commands using LZ77 or optimal parsing *) 690 let commands = 691 if quality >= 10 then 692 (* Use optimal greedy parsing with lazy matching for quality 10-11 *) 693 Optimal.generate_commands ~quality src src_pos src_len 694 else 695 (* Standard LZ77 for lower quality levels *) 696 Lz77.generate_commands ~use_dict ~quality src src_pos src_len 697 in 698 699 (* Use context modeling for quality >= 5 *) 700 if quality >= 5 then begin 701 let context_mode = Block_split.choose_context_mode src src_pos src_len in 702 (* For quality >= 7 with enough data, use multiple literal trees *) 703 let (context_map, num_lit_trees) = 704 if quality >= 7 && src_len >= 1024 then begin 705 let max_trees = if quality >= 9 then 4 else 2 in 706 let (cmap, _histograms, ntrees) = 707 Block_split.build_literal_context_map context_mode src src_pos src_len max_trees 708 in 709 (cmap, ntrees) 710 end else 711 (Array.make 64 0, 1) 712 in 713 (* Distance context map: 4 contexts based on copy_length *) 714 (* For now, use single distance tree (infrastructure ready for multiple) *) 715 let dist_context_map = Array.make 4 0 in 716 let num_dist_trees = 1 in 717 write_compressed_block_with_context bw src src_pos src_len is_last 718 context_mode context_map num_lit_trees num_dist_trees dist_context_map commands 719 end else begin 720 (* Original simple encoding for quality < 5 *) 721 722 (* Count frequencies for all three alphabets *) 723 let lit_freq = Array.make 256 0 in 724 let cmd_freq = Array.make 704 0 in 725 let num_distance_codes = 16 + 48 in 726 let dist_freq = Array.make num_distance_codes 0 in 727 728 (* Count literals and build command/distance frequencies *) 729 List.iter (fun cmd -> 730 match cmd with 731 | Lz77.Literals { start; len } -> 732 for i = start to start + len - 1 do 733 let c = Char.code (Bytes.get src i) in 734 lit_freq.(c) <- lit_freq.(c) + 1 735 done; 736 let insert_code = get_insert_code len in 737 let copy_code = 0 in 738 let cmd_code = get_command_code insert_code copy_code false in 739 cmd_freq.(cmd_code) <- cmd_freq.(cmd_code) + 1; 740 (* range_idx for Literals command with copy_code=0 is >= 2, so we need distance *) 741 dist_freq.(0) <- dist_freq.(0) + 1 742 | Lz77.InsertCopy { lit_start; lit_len; copy_len; distance; dist_code } -> 743 for i = lit_start to lit_start + lit_len - 1 do 744 let c = Char.code (Bytes.get src i) in 745 lit_freq.(c) <- lit_freq.(c) + 1 746 done; 747 let insert_code = get_insert_code lit_len in 748 let copy_code = get_copy_code copy_len in 749 (* Only dist_code=Some 0 can use implicit distance (range_idx 0-1) *) 750 let use_implicit = dist_code = Some 0 in 751 let cmd_code = get_command_code insert_code copy_code use_implicit in 752 let range_idx = cmd_code lsr 6 in 753 cmd_freq.(cmd_code) <- cmd_freq.(cmd_code) + 1; 754 (* Count distance code if range_idx >= 2 (explicit distance) *) 755 if range_idx >= 2 then begin 756 match dist_code with 757 | Some code -> dist_freq.(code) <- dist_freq.(code) + 1 758 | None -> 759 let dist_code_val, _, _ = encode_distance distance in 760 if dist_code_val < num_distance_codes then 761 dist_freq.(dist_code_val) <- dist_freq.(dist_code_val) + 1 762 else 763 dist_freq.(num_distance_codes - 1) <- dist_freq.(num_distance_codes - 1) + 1 764 end 765 (* For range_idx 0-1, distance code 0 is implicit, don't count *) 766 ) commands; 767 768 (* Build Huffman codes *) 769 let lit_lengths = build_valid_code_lengths lit_freq 15 in 770 let lit_codes = build_codes lit_lengths in 771 let cmd_lengths = build_valid_code_lengths cmd_freq 15 in 772 let cmd_codes = build_codes cmd_lengths in 773 let dist_lengths = build_valid_code_lengths dist_freq 15 in 774 let dist_codes = build_codes dist_lengths in 775 776 (* Calculate total uncompressed size for meta-block header *) 777 let total_len = List.fold_left (fun acc cmd -> 778 match cmd with 779 | Lz77.Literals { len; _ } -> acc + len 780 | Lz77.InsertCopy { lit_len; copy_len; _ } -> acc + lit_len + copy_len 781 ) 0 commands in 782 783 (* Write meta-block header *) 784 write_meta_block_header bw total_len is_last false; 785 786 (* Block type counts: 1 for each category *) 787 Bit_writer.write_bits bw 1 0; 788 Bit_writer.write_bits bw 1 0; 789 Bit_writer.write_bits bw 1 0; 790 791 (* Distance parameters: NPOSTFIX=0, NDIRECT=0 *) 792 Bit_writer.write_bits bw 2 0; 793 Bit_writer.write_bits bw 4 0; 794 795 (* Context mode for literal block type 0: LSB6 = 0 *) 796 Bit_writer.write_bits bw 2 0; 797 798 (* Literal context map: NTREESL = 1 tree *) 799 Bit_writer.write_bits bw 1 0; 800 801 (* Distance context map: NTREESD = 1 tree *) 802 Bit_writer.write_bits bw 1 0; 803 804 (* Write Huffman codes *) 805 write_huffman_code bw lit_lengths 256; 806 write_huffman_code bw cmd_lengths 704; 807 write_huffman_code bw dist_lengths num_distance_codes; 808 809 (* Write commands *) 810 let num_lit_symbols = count_used_symbols lit_freq in 811 let num_cmd_symbols = count_used_symbols cmd_freq in 812 let num_dist_symbols = count_used_symbols dist_freq in 813 814 List.iter (fun cmd -> 815 match cmd with 816 | Lz77.Literals { start; len } -> 817 let insert_code = get_insert_code len in 818 let copy_code = 0 in 819 let cmd_code = get_command_code insert_code copy_code false in 820 if num_cmd_symbols > 1 then 821 write_symbol bw cmd_codes cmd_lengths cmd_code; 822 if insert_length_n_bits.(insert_code) > 0 then begin 823 let extra = len - insert_length_offset.(insert_code) in 824 Bit_writer.write_bits bw insert_length_n_bits.(insert_code) extra 825 end; 826 if num_lit_symbols > 1 then begin 827 for i = start to start + len - 1 do 828 let c = Char.code (Bytes.get src i) in 829 write_symbol bw lit_codes lit_lengths c 830 done 831 end 832 833 | Lz77.InsertCopy { lit_start; lit_len; copy_len; distance; dist_code } -> 834 let insert_code = get_insert_code lit_len in 835 let copy_code = get_copy_code copy_len in 836 (* Only dist_code=Some 0 can use implicit distance (range_idx 0-1) *) 837 let use_implicit = dist_code = Some 0 in 838 let cmd_code = get_command_code insert_code copy_code use_implicit in 839 let range_idx = cmd_code lsr 6 in 840 if num_cmd_symbols > 1 then 841 write_symbol bw cmd_codes cmd_lengths cmd_code; 842 if insert_length_n_bits.(insert_code) > 0 then begin 843 let extra = lit_len - insert_length_offset.(insert_code) in 844 Bit_writer.write_bits bw insert_length_n_bits.(insert_code) extra 845 end; 846 if copy_length_n_bits.(copy_code) > 0 then begin 847 let extra = copy_len - copy_length_offset.(copy_code) in 848 Bit_writer.write_bits bw copy_length_n_bits.(copy_code) extra 849 end; 850 if num_lit_symbols > 1 then begin 851 for i = lit_start to lit_start + lit_len - 1 do 852 let c = Char.code (Bytes.get src i) in 853 write_symbol bw lit_codes lit_lengths c 854 done 855 end; 856 (* Write distance code. 857 For range_idx 0-1 (command codes 0-127), the decoder uses implicit distance code 0 858 and does NOT read from the stream. For range_idx >= 2, we must write the distance code. *) 859 if range_idx >= 2 then begin 860 match dist_code with 861 | Some code -> 862 (* Short codes 0-15 - just write the code, no extra bits *) 863 if num_dist_symbols > 1 then 864 write_symbol bw dist_codes dist_lengths code 865 | None -> 866 let dist_code_val, nbits, extra = encode_distance distance in 867 if num_dist_symbols > 1 then 868 write_symbol bw dist_codes dist_lengths dist_code_val; 869 if nbits > 0 then 870 Bit_writer.write_bits bw nbits extra 871 end 872 (* For range_idx 0-1, distance code 0 is implicit, don't write anything *) 873 ) commands 874 end 875 876(* Write a compressed block with only literals *) 877let write_literals_only_block bw src src_pos src_len is_last = 878 write_meta_block_header bw src_len is_last false; 879 Bit_writer.write_bits bw 1 0; 880 Bit_writer.write_bits bw 1 0; 881 Bit_writer.write_bits bw 1 0; 882 Bit_writer.write_bits bw 2 0; 883 Bit_writer.write_bits bw 4 0; 884 Bit_writer.write_bits bw 2 0; 885 Bit_writer.write_bits bw 1 0; 886 Bit_writer.write_bits bw 1 0; 887 888 let lit_freq = Array.make 256 0 in 889 for i = src_pos to src_pos + src_len - 1 do 890 let c = Char.code (Bytes.get src i) in 891 lit_freq.(c) <- lit_freq.(c) + 1 892 done; 893 let num_lit_symbols = count_used_symbols lit_freq in 894 let lit_lengths = build_valid_code_lengths lit_freq 15 in 895 let lit_codes = build_codes lit_lengths in 896 897 let insert_code = get_insert_code src_len in 898 let copy_code = 0 in 899 let cmd_code = get_command_code insert_code copy_code false in 900 let cmd_freq = Array.make 704 0 in 901 cmd_freq.(cmd_code) <- 1; 902 let cmd_lengths = build_valid_code_lengths cmd_freq 15 in 903 904 let num_distance_codes = 16 + 48 in 905 let dist_freq = Array.make num_distance_codes 0 in 906 dist_freq.(0) <- 1; 907 let dist_lengths = build_valid_code_lengths dist_freq 15 in 908 909 write_huffman_code bw lit_lengths 256; 910 write_huffman_code bw cmd_lengths 704; 911 write_huffman_code bw dist_lengths num_distance_codes; 912 913 if insert_length_n_bits.(insert_code) > 0 then begin 914 let extra = src_len - insert_length_offset.(insert_code) in 915 Bit_writer.write_bits bw insert_length_n_bits.(insert_code) extra 916 end; 917 918 if num_lit_symbols > 1 then begin 919 for i = src_pos to src_pos + src_len - 1 do 920 let c = Char.code (Bytes.get src i) in 921 write_symbol bw lit_codes lit_lengths c 922 done 923 end 924 925(* Main compression function *) 926let compress_into ?(quality=1) ~src ~src_pos ~src_len ~dst ~dst_pos () = 927 current_quality := quality; 928 let bw = Bit_writer.create ~dst ~pos:dst_pos ~len:(Bytes.length dst - dst_pos) in 929 encode_window_bits bw; 930 931 if src_len = 0 then begin 932 write_empty_last_block bw; 933 Bit_writer.flush bw - dst_pos 934 end 935 else if quality = 0 || src_len < 16 then begin 936 write_uncompressed_block bw src src_pos src_len; 937 write_empty_last_block bw; 938 Bit_writer.flush bw - dst_pos 939 end 940 else begin 941 try 942 if quality >= 2 && src_len >= min_match then 943 write_compressed_block bw src src_pos src_len true 944 else 945 write_literals_only_block bw src src_pos src_len true; 946 Bit_writer.flush bw - dst_pos 947 with _ -> 948 let bw = Bit_writer.create ~dst ~pos:dst_pos ~len:(Bytes.length dst - dst_pos) in 949 encode_window_bits bw; 950 write_uncompressed_block bw src src_pos src_len; 951 write_empty_last_block bw; 952 Bit_writer.flush bw - dst_pos 953 end 954 955let max_compressed_length input_len = 956 input_len + input_len / 8 + 64 957 958(* Streaming encoder state *) 959type streaming_encoder = { 960 mutable quality : int; 961 mutable dst : bytes; 962 mutable dst_pos : int; 963 mutable header_written : bool; 964 mutable finished : bool; 965} 966 967let create_streaming_encoder ?(quality=1) ~dst ~dst_pos () = 968 { quality; dst; dst_pos; header_written = false; finished = false } 969 970(* Write a chunk of data to the streaming encoder *) 971let streaming_write encoder ~src ~src_pos ~src_len ~is_last = 972 if encoder.finished then 973 invalid_arg "streaming encoder already finished"; 974 975 current_quality := encoder.quality; 976 let bw = Bit_writer.create ~dst:encoder.dst ~pos:encoder.dst_pos 977 ~len:(Bytes.length encoder.dst - encoder.dst_pos) in 978 979 (* Write header on first chunk *) 980 if not encoder.header_written then begin 981 encode_window_bits bw; 982 encoder.header_written <- true 983 end; 984 985 if src_len = 0 then begin 986 if is_last then begin 987 write_empty_last_block bw; 988 encoder.finished <- true 989 end 990 end 991 else if encoder.quality = 0 || src_len < 16 then begin 992 (* For low quality or small blocks, write uncompressed *) 993 if is_last then begin 994 write_uncompressed_block bw src src_pos src_len; 995 write_empty_last_block bw; 996 encoder.finished <- true 997 end else begin 998 (* Non-last uncompressed block *) 999 write_meta_block_header bw src_len false true; 1000 Bit_writer.align_to_byte bw; 1001 Bit_writer.copy_bytes bw ~src ~src_pos ~len:src_len 1002 end 1003 end 1004 else begin 1005 try 1006 if encoder.quality >= 2 && src_len >= min_match then 1007 write_compressed_block bw src src_pos src_len is_last 1008 else 1009 write_literals_only_block bw src src_pos src_len is_last; 1010 if is_last then encoder.finished <- true 1011 with _ -> 1012 (* Fallback to uncompressed *) 1013 if is_last then begin 1014 write_uncompressed_block bw src src_pos src_len; 1015 write_empty_last_block bw; 1016 encoder.finished <- true 1017 end else begin 1018 write_meta_block_header bw src_len false true; 1019 Bit_writer.align_to_byte bw; 1020 Bit_writer.copy_bytes bw ~src ~src_pos ~len:src_len 1021 end 1022 end; 1023 1024 let written = Bit_writer.flush bw - encoder.dst_pos in 1025 encoder.dst_pos <- encoder.dst_pos + written; 1026 written 1027 1028let streaming_finish encoder = 1029 if not encoder.finished then begin 1030 let result = streaming_write encoder ~src:(Bytes.create 0) ~src_pos:0 ~src_len:0 ~is_last:true in 1031 encoder.finished <- true; 1032 result 1033 end else 0 1034 1035let streaming_bytes_written encoder = 1036 encoder.dst_pos 1037 1038(* Re-export command type for Debug module *) 1039type command = Lz77.command = 1040 | InsertCopy of { lit_start: int; lit_len: int; copy_len: int; distance: int; dist_code: int option } 1041 | Literals of { start: int; len: int } 1042 1043let generate_commands src src_pos src_len = 1044 Lz77.generate_commands src src_pos src_len