Files

1054 lines
41 KiB
Odin
Raw Permalink Normal View History

2026-09-25 23:32:29 +02:00
package pdf
import "core:slice"
import "core:testing"
// Test vectors come from two places.
//
// The Group 3 and Group 4 streams named G3_* and G4_* were produced by
// libtiff (through Pillow), so they are an independent encoder's idea of what
// these images look like; decoding them back to the source pixels checks the
// code tables and the 2-D coder against something that is not this file.
// The MH_* and MR_* streams are hand-assembled bit by bit from the code words
// in ITU-T T.4 tables 1-4, which is the only way to pin down EOL, fill,
// byte alignment, the /K > 0 tag bit and the malformed cases.
// G4, 8 columns x 12 rows. Between them the rows use every 2-D mode:
// pass, horizontal, V0, VR1-VR3 and VL1-VL3, and end with an EOFB.
@(private="file", rodata)
G4_FULL := [?]byte{
0x93, 0x51, 0x59, 0x43, 0x94, 0x38, 0x4A, 0xAE, 0xC4, 0x38, 0x2A, 0x3D, 0x8C, 0xDB, 0x07, 0x09,
0x46, 0x00, 0x20, 0x02,
}
@(private="file", rodata)
G4_FULL_OUT := [?]byte{
0xFF, 0x00, 0xAA, 0x33, 0xFC, 0xE0, 0xCF, 0xFF, 0xF0, 0xFE, 0xF9, 0xFF,
}
// The same image with /BlackIs1 true: every bit inverted.
@(private="file", rodata)
G4_FULL_OUT_INVERTED := [?]byte{
0x00, 0xFF, 0x55, 0xCC, 0x03, 0x1F, 0x30, 0x00, 0x0F, 0x01, 0x06, 0x00,
}
// 200 columns x 5 rows: runs long enough to need make-up codes.
@(private="file", rodata)
G4_WIDE := [?]byte{
0x93, 0x50, 0xC9, 0x14, 0xEC, 0x54, 0x0F, 0x0D, 0x42, 0x6A, 0x07, 0x91, 0x4B, 0x03, 0xC8, 0x4C,
0x9C, 0x37, 0x00, 0x10, 0x01,
}
@(private="file", rodata)
G3_WIDE := [?]byte{
0x00, 0x15, 0xE6, 0x00, 0x26, 0xA1, 0x92, 0x28, 0x00, 0xEC, 0x54, 0x0F, 0x0D, 0x40, 0x01, 0x35,
0x03, 0xC9, 0x2C, 0x0F, 0x20, 0x01, 0x5E, 0x60,
}
@(private="file", rodata)
WIDE_OUT := [?]byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
}
// Group 3 1-D of the first four rows of G4_FULL, EOL-separated.
@(private="file", rodata)
G3_SMALL := [?]byte{
0x00, 0x19, 0x80, 0x09, 0xA8, 0xA0, 0x02, 0x3A, 0x1D, 0x0E, 0x87, 0x40, 0x02, 0x6B, 0xBE, 0xE0,
}
@(private="file", rodata)
SMALL_OUT := [?]byte{
0xFF, 0x00, 0xAA, 0x33,
}
// pass mode: the reference black run ends before the coding line changes.
@(private="file", rodata)
G4_PASS := [?]byte{
0x2F, 0xC6, 0x00, 0x20, 0x02,
}
@(private="file", rodata)
G4_PASS_OUT := [?]byte{
0xCF, 0xFF,
}
// VL1 then VR1.
@(private="file", rodata)
G4_VR1 := [?]byte{
0x2E, 0xE9, 0xC0, 0x04, 0x00, 0x40,
}
@(private="file", rodata)
G4_VR1_OUT := [?]byte{
0xC3, 0x81,
}
// VR2 then VL2.
@(private="file", rodata)
G4_VL2 := [?]byte{
0x23, 0x94, 0x30, 0xA0, 0x02, 0x00, 0x20,
}
@(private="file", rodata)
G4_VL2_OUT := [?]byte{
0x81, 0xE7,
}
// VL2, VL3.
@(private="file", rodata)
G4_VL3 := [?]byte{
0x0A, 0x0A, 0x00, 0x20, 0x02,
}
@(private="file", rodata)
G4_VL3_OUT := [?]byte{
0xFC, 0xE0,
}
// One 1-D row, eight white pixels, no EOL and no padding to speak of.
@(private="file", rodata)
MH_WHITE_ROW := [?]byte{
0x98,
}
@(private="file", rodata)
MH_WHITE_ROW_OUT := [?]byte{
0xFF,
}
// A black row still opens with a zero-length white run.
@(private="file", rodata)
MH_BLACK_ROW := [?]byte{
0x35, 0x14,
}
@(private="file", rodata)
MH_BLACK_ROW_OUT := [?]byte{
0x00,
}
// Alternating pixels: eight runs of one.
@(private="file", rodata)
MH_ALT_ROW := [?]byte{
0x1D, 0x0E, 0x87, 0x43, 0xA0,
}
@(private="file", rodata)
MH_ALT_ROW_OUT := [?]byte{
0xAA,
}
// EOLs with fill bits in front of them.
@(private="file", rodata)
MH_EOL_FILL := [?]byte{
0x00, 0x01, 0x98, 0x00, 0x26, 0xA2, 0x80,
}
@(private="file", rodata)
MH_EOL_FILL_OUT := [?]byte{
0xFF, 0x00,
}
// Two rows then a six-EOL return-to-control.
@(private="file", rodata)
MH_RTC := [?]byte{
0x00, 0x19, 0x80, 0x09, 0xA8, 0xA0, 0x02, 0x00, 0x20, 0x02, 0x00, 0x20, 0x02, 0x00, 0x20,
}
@(private="file", rodata)
MH_RTC_OUT := [?]byte{
0xFF, 0x00,
}
// A 100-pixel run in an 8-pixel row: truncated, not an error.
@(private="file", rodata)
MH_OVERLONG := [?]byte{
0xD8, 0xA8,
}
@(private="file", rodata)
MH_OVERLONG_OUT := [?]byte{
0xFF,
}
// A row that ends after three white pixels; the colour it changed to runs on.
@(private="file", rodata)
MH_SHORT := [?]byte{
0x80,
}
@(private="file", rodata)
MH_SHORT_OUT := [?]byte{
0xE0,
}
// 2000 white pixels: exercises the extended make-up codes (>= 1792).
@(private="file", rodata)
MH_WIDE_WHITE := [?]byte{
0x01, 0x2A, 0x80,
}
@(private="file", rodata)
MH_WIDE_WHITE_OUT := [250]byte{0 ..= 249 = 0xFF}
// 2000 black pixels: the extended make-up codes are shared by both colours.
@(private="file", rodata)
MH_WIDE_BLACK := [?]byte{
0x35, 0x01, 0x20, 0x5C,
}
@(private="file", rodata)
MH_WIDE_BLACK_OUT := [250]byte{0 ..= 249 = 0x00}
// /K > 0: EOL, 1-D tag, row; EOL, 2-D tag, horizontal-mode row.
@(private="file", rodata)
MR_MIXED := [?]byte{
0x00, 0x1C, 0xC0, 0x04, 0x4D, 0x45,
}
@(private="file", rodata)
MR_MIXED_OUT := [?]byte{
0xFF, 0x00,
}
// /K > 0 with a V0 row referring back to a 1-D row.
@(private="file", rodata)
MR_MIXED3 := [?]byte{
0x00, 0x1C, 0xC0, 0x05, 0x00, 0x11, 0x35, 0x14,
}
@(private="file", rodata)
MR_MIXED3_OUT := [?]byte{
0xFF, 0xFF, 0x00,
}
// /EncodedByteAlign: the same two rows, each starting on a byte boundary.
@(private="file", rodata)
MH_BYTE_ALIGN := [?]byte{
0x98, 0x35, 0x14,
}
@(private="file", rodata)
MH_BYTE_ALIGN_OUT := [?]byte{
0xFF, 0x00,
}
// A page at the standard fax width, 1728 x 6, encoded by libtiff both
// ways. Solid rows and 900-pixel runs put the make-up codes, pass mode
// and the long-run paths to work at a size a real document uses.
@(private="file", rodata)
PAGE_G4 := [?]byte{
0x93, 0x50, 0x32, 0x86, 0xF2, 0x07, 0x26, 0xCD, 0x4B, 0x2D, 0x3B, 0x02, 0x60, 0x16, 0x19, 0x2D,
0x01, 0xE6, 0x76, 0x96, 0x07, 0xE6, 0x4A, 0x41, 0x9C, 0xB7, 0x02, 0x0C, 0x8C, 0xC8, 0x60, 0x18,
0x20, 0x60, 0x5C, 0xA7, 0x22, 0xDC, 0x5C, 0x32, 0x11, 0x32, 0x02, 0xC3, 0x31, 0x44, 0x4A, 0xA0,
0x1E, 0x08, 0x24, 0xC0, 0x2C, 0x06, 0x40, 0xF0, 0xD0, 0xBF, 0x89, 0x04, 0xE0, 0x42, 0x49, 0x86,
0xC3, 0x21, 0x20, 0x3C, 0x30, 0x76, 0x14, 0x06, 0x00, 0x04, 0x00, 0x40,
}
@(private="file", rodata)
PAGE_G3 := [?]byte{
0x00, 0x14, 0xD9, 0xA8, 0x00, 0x9A, 0x81, 0x94, 0x37, 0x00, 0x13, 0x50, 0x39, 0x36, 0x6A, 0x58,
0x00, 0xB4, 0xEC, 0x09, 0x80, 0x58, 0x00, 0x32, 0x5A, 0x03, 0xCF, 0x69, 0x60, 0x7F, 0x25, 0x20,
0xCE, 0xDC, 0x08, 0x32, 0x39, 0x0C, 0x03, 0x00, 0x60, 0x5D, 0x39, 0x37, 0x17, 0x0C, 0x84, 0x00,
0x26, 0x40, 0x59, 0x31, 0x4D, 0x85, 0xC0, 0xF0, 0x4D, 0x8D, 0x82, 0xC2, 0xE7, 0x03, 0xC3, 0x42,
0xFE, 0x05, 0x9D, 0x92, 0x06, 0xC9, 0x09, 0x01, 0xE1, 0x8D, 0x85, 0x01, 0x80,
}
@(private="file", rodata)
PAGE_OUT := [?]byte{
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00,
0x7F, 0xFF, 0xE0, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x80, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xF3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x00,
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00,
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
@(private="file")
expect_decode :: proc(t: ^testing.T, name: string, data: []byte, p: Ccitt_Params, want: []byte) {
got, err := ccitt_decode(data, p)
defer delete(got)
testing.expectf(t, err == .None, "%s: %v", name, error_string(err))
testing.expectf(t, slice.equal(got, want), "%s: got % 02X, want % 02X", name, got, want)
}
// ---------------------------------------------------------------------------
// Bit reader
// ---------------------------------------------------------------------------
@(test)
test_ccitt_bits_peek :: proc(t: ^testing.T) {
data := [?]byte{0b1011_0010, 0b0100_1111, 0b1000_0001}
b := ccitt_bits_make(data[:])
testing.expect_value(t, ccitt_bits_left(&b), 24)
testing.expect_value(t, ccitt_bits_peek(&b, 1), u32(1))
testing.expect_value(t, ccitt_bits_peek(&b, 4), u32(0b1011))
testing.expect_value(t, ccitt_bits_peek(&b, 8), u32(0b1011_0010))
testing.expect_value(t, ccitt_bits_peek(&b, 16), u32(0b1011_0010_0100_1111))
testing.expect_value(t, ccitt_bits_peek(&b, 0), u32(0))
// A peek that straddles two bytes, from an unaligned position.
ccitt_bits_skip(&b, 5)
testing.expect_value(t, ccitt_bits_peek(&b, 6), u32(0b010_010))
testing.expect_value(t, ccitt_bits_left(&b), 19)
// Past the end reads as zero bits, and skipping past the end clamps.
ccitt_bits_skip(&b, 18)
testing.expect_value(t, ccitt_bits_left(&b), 1)
testing.expect_value(t, ccitt_bits_peek(&b, 8), u32(0b1000_0000))
ccitt_bits_skip(&b, 1000)
testing.expect_value(t, ccitt_bits_left(&b), 0)
testing.expect_value(t, ccitt_bits_peek(&b, 12), u32(0))
}
@(test)
test_ccitt_bits_align :: proc(t: ^testing.T) {
data := [?]byte{0xFF, 0xFF, 0xFF}
b := ccitt_bits_make(data[:])
ccitt_bits_align(&b)
testing.expect_value(t, b.pos, 0) // already aligned: no movement
ccitt_bits_skip(&b, 1)
ccitt_bits_align(&b)
testing.expect_value(t, b.pos, 8)
ccitt_bits_skip(&b, 7)
ccitt_bits_align(&b)
testing.expect_value(t, b.pos, 16)
ccitt_bits_skip(&b, 7)
ccitt_bits_align(&b)
testing.expect_value(t, b.pos, 24) // clamped to the end, not past it
}
// ---------------------------------------------------------------------------
// Code tables
// ---------------------------------------------------------------------------
@(test)
test_ccitt_tables_are_prefix_free :: proc(t: ^testing.T) {
// The decoder walks the tables in length order and takes the first match,
// which is only the right answer because no code is a prefix of another.
// It also relies on no code having eight leading zeros, so that fill bits
// and EOLs can never be confused with data.
check :: proc(t: ^testing.T, table: []Ccitt_Code, name: string) {
for a, i in table {
testing.expectf(t, a.bits >= 2 && a.bits <= 13, "%s[%d]: bad length %d", name, i, a.bits)
testing.expectf(t, u32(a.code) < 1 << uint(a.bits), "%s[%d]: code wider than its length", name, i)
if a.bits >= 8 {
lead := u32(a.code) >> uint(int(a.bits) - 8)
testing.expectf(t, lead != 0, "%s[%d]: eight leading zeros collide with EOL", name, i)
}
if i > 0 {
prev := table[i - 1]
ordered := prev.bits < a.bits || (prev.bits == a.bits && prev.code < a.code)
testing.expectf(t, ordered, "%s[%d]: table is not sorted by (bits, code)", name, i)
}
for b, j in table {
if i == j || b.bits < a.bits {
continue
}
shifted := u32(b.code) >> uint(int(b.bits) - int(a.bits))
testing.expectf(t, !(b.bits > a.bits && shifted == u32(a.code)),
"%s: run %d is a prefix of run %d", name, a.run, b.run)
testing.expectf(t, !(b.bits == a.bits && b.code == a.code),
"%s: runs %d and %d share a code", name, a.run, b.run)
}
}
}
check(t, CCITT_WHITE[:], "white")
check(t, CCITT_BLACK[:], "black")
}
@(test)
test_ccitt_tables_are_complete :: proc(t: ^testing.T) {
// Terminating codes 0-63, make-up codes 64-1728 in steps of 64, and the
// extended make-up codes 1792-2560, in both tables.
check :: proc(t: ^testing.T, table: []Ccitt_Code, name: string) {
seen: map[int]bool
defer delete(seen)
for e in table {
seen[int(e.run)] = true
}
for r in 0 ..< 64 {
testing.expectf(t, seen[r], "%s: terminating run %d missing", name, r)
}
for r := 64; r <= 2560; r += 64 {
if r > 1728 && r < 1792 {
continue
}
testing.expectf(t, seen[r], "%s: make-up run %d missing", name, r)
}
testing.expectf(t, len(table) == 104, "%s: %d codes, want 104", name, len(table))
}
check(t, CCITT_WHITE[:], "white")
check(t, CCITT_BLACK[:], "black")
}
@(test)
test_ccitt_lookup :: proc(t: ^testing.T) {
Case :: struct {
bits: []byte,
avail: int, // bits of `bits` that are really there; 0 means all
black: bool,
run: int,
used: int,
ok: bool,
}
cases := []Case{
// Terminating codes, shortest and longest of each colour.
{{0b0111_0000}, 0, false, 2, 4, true}, // white 2: 0111
{{0b0011_0101}, 0, false, 0, 8, true}, // white 0: 00110101
{{0b1100_0000}, 0, true, 2, 2, true}, // black 2: 11
{{0b0000_1101, 0b1100_0000}, 0, true, 0, 10, true}, // black 0: 0000110111
// Make-up codes.
{{0b1101_1000}, 0, false, 64, 5, true}, // white 64: 11011
{{0b0100_1101, 0b1000_0000}, 0, false, 1728, 9, true}, // white 1728: 010011011
{{0b0000_0011, 0b1100_0000}, 0, true, 64, 10, true}, // black 64: 0000001111
// Extended make-up codes, shared by both colours.
{{0b0000_0001, 0b0000_0000}, 0, false, 1792, 11, true},
{{0b0000_0001, 0b0000_0000}, 0, true, 1792, 11, true},
{{0b0000_0001, 0b1111_0000}, 0, false, 2560, 12, true},
{{0b0000_0001, 0b1111_0000}, 0, true, 2560, 12, true},
// No code is all zeros, so fill and EOL never look like a run.
{{0x00, 0x00}, 0, false, 0, 0, false},
{{0x00, 0x00}, 0, true, 0, 0, false},
// Codes the zero padding would otherwise complete: only the first
// four bits of white 0 (00110101) and of black 0 (0000110111) are
// really present, and neither may be invented.
{{0b0011_0101}, 4, false, 0, 0, false},
{{0b0000_1101, 0b1100_0000}, 4, true, 0, 0, false},
}
for c, i in cases {
b := ccitt_bits_make(c.bits)
if c.avail > 0 {
b.end = c.avail
}
table := c.black ? CCITT_BLACK[:] : CCITT_WHITE[:]
run, ok := ccitt_lookup(&b, table)
testing.expectf(t, ok == c.ok, "case %d: ok %v, want %v", i, ok, c.ok)
if c.ok {
testing.expectf(t, run == c.run, "case %d: run %d, want %d", i, run, c.run)
testing.expectf(t, b.pos == c.used, "case %d: used %d bits, want %d", i, b.pos, c.used)
}
}
}
@(test)
test_ccitt_run_chains_makeups :: proc(t: ^testing.T) {
// white 1728 (010011011) + white 63 (00110100) = 1791, the largest run
// T.4 can code without the extended table.
data := [?]byte{0b0100_1101, 0b1001_1010, 0b0000_0000}
b := ccitt_bits_make(data[:])
run, ok := ccitt_run(&b, false, 4096)
testing.expect(t, ok)
testing.expect_value(t, run, 1791)
testing.expect_value(t, b.pos, 17)
// The same run clamped by the row width.
b = ccitt_bits_make(data[:])
run, ok = ccitt_run(&b, false, 100)
testing.expect(t, ok)
testing.expect_value(t, run, 100)
}
// ---------------------------------------------------------------------------
// 2-D mode codes and EOL
// ---------------------------------------------------------------------------
@(test)
test_ccitt_mode :: proc(t: ^testing.T) {
Case :: struct {
byte: byte,
mode: Ccitt_Mode,
used: int,
}
cases := []Case{
{0b1000_0000, .V0, 1},
{0b0110_0000, .VR1, 3},
{0b0100_0000, .VL1, 3},
{0b0010_0000, .Horizontal, 3},
{0b0001_0000, .Pass, 4},
{0b0000_1100, .VR2, 6},
{0b0000_1000, .VL2, 6},
{0b0000_0110, .VR3, 7},
{0b0000_0100, .VL3, 7},
{0b0000_0010, .Extension, 7},
}
for c, i in cases {
data := [?]byte{c.byte, 0xFF}
b := ccitt_bits_make(data[:])
mode, ok := ccitt_mode(&b)
testing.expectf(t, ok, "case %d: not decoded", i)
testing.expectf(t, mode == c.mode, "case %d: %v, want %v", i, mode, c.mode)
testing.expectf(t, b.pos == c.used, "case %d: used %d bits, want %d", i, b.pos, c.used)
}
// Seven zero bits is not a mode; it is an EOL or garbage.
zeros := [?]byte{0x00, 0x00}
b := ccitt_bits_make(zeros[:])
_, ok := ccitt_mode(&b)
testing.expect(t, !ok)
testing.expect_value(t, b.pos, 0)
// A mode the zero padding would complete is refused.
short := [?]byte{0b0100_0000}
b = ccitt_bits_make(short[:])
b.end = 2
_, ok = ccitt_mode(&b)
testing.expect(t, !ok)
}
@(test)
test_ccitt_try_eol :: proc(t: ^testing.T) {
// A bare EOL.
eol := [?]byte{0b0000_0000, 0b0001_0000}
b := ccitt_bits_make(eol[:])
testing.expect(t, ccitt_try_eol(&b))
testing.expect_value(t, b.pos, 12)
// Fill bits in front of it are absorbed.
filled := [?]byte{0x00, 0x00, 0b0000_0001, 0b0000_0000}
b = ccitt_bits_make(filled[:])
testing.expect(t, ccitt_try_eol(&b))
testing.expect_value(t, b.pos, 24)
// A run code is not an EOL, and the reader must not have moved.
code := [?]byte{0b0000_0100, 0b0000_0000} // white 45: 00000100
b = ccitt_bits_make(code[:])
testing.expect(t, !ccitt_try_eol(&b))
testing.expect_value(t, b.pos, 0)
// Trailing zero fill at the end of the data is not an EOL either: there
// is no terminating one bit, so the position is restored.
tail := [?]byte{0x00, 0x00, 0x00}
b = ccitt_bits_make(tail[:])
testing.expect(t, !ccitt_try_eol(&b))
testing.expect_value(t, b.pos, 0)
}
// ---------------------------------------------------------------------------
// Painting
// ---------------------------------------------------------------------------
@(test)
test_ccitt_paint :: proc(t: ^testing.T) {
row := [3]byte{}
for i in 0 ..< 3 {
row[i] = 0xFF
}
ccitt_paint(row[:], 3, 5, false)
testing.expect_value(t, row[0], byte(0b1110_0111))
ccitt_paint(row[:], 8, 24, false)
testing.expect_value(t, row[1], byte(0x00))
testing.expect_value(t, row[2], byte(0x00))
ccitt_paint(row[:], 12, 20, true)
testing.expect_value(t, row[1], byte(0b0000_1111))
testing.expect_value(t, row[2], byte(0b1111_0000))
// Out-of-range spans are clipped rather than trusted.
ccitt_paint(row[:], -100, 100, true)
testing.expect_value(t, row[0], byte(0xFF))
testing.expect_value(t, row[2], byte(0xFF))
ccitt_paint(row[:], 5, 5, false)
testing.expect_value(t, row[0], byte(0xFF))
}
// ---------------------------------------------------------------------------
// Whole images
// ---------------------------------------------------------------------------
@(test)
test_ccitt_g4 :: proc(t: ^testing.T) {
Case :: struct {
name: string,
data: []byte,
columns: int,
rows: int,
want: []byte,
}
cases := []Case{
{"full", G4_FULL[:], 8, 12, G4_FULL_OUT[:]},
{"full, rows unknown", G4_FULL[:], 8, 0, G4_FULL_OUT[:]},
{"pass mode", G4_PASS[:], 8, 2, G4_PASS_OUT[:]},
{"vr1", G4_VR1[:], 8, 2, G4_VR1_OUT[:]},
{"vl2", G4_VL2[:], 8, 2, G4_VL2_OUT[:]},
{"vl3", G4_VL3[:], 8, 2, G4_VL3_OUT[:]},
{"wide, make-up codes", G4_WIDE[:], 200, 5, WIDE_OUT[:]},
{"wide, rows unknown", G4_WIDE[:], 200, 0, WIDE_OUT[:]},
}
for c in cases {
p := ccitt_default_params()
p.k = -1
p.columns = c.columns
p.rows = c.rows
expect_decode(t, c.name, c.data, p, c.want)
}
}
@(test)
test_ccitt_g3_1d :: proc(t: ^testing.T) {
Case :: struct {
name: string,
data: []byte,
columns: int,
rows: int,
want: []byte,
}
cases := []Case{
{"white row", MH_WHITE_ROW[:], 8, 1, MH_WHITE_ROW_OUT[:]},
{"black row", MH_BLACK_ROW[:], 8, 1, MH_BLACK_ROW_OUT[:]},
{"alternating pixels", MH_ALT_ROW[:], 8, 1, MH_ALT_ROW_OUT[:]},
{"eol and fill", MH_EOL_FILL[:], 8, 0, MH_EOL_FILL_OUT[:]},
{"return to control", MH_RTC[:], 8, 0, MH_RTC_OUT[:]},
{"libtiff group 3", G3_SMALL[:], 8, 0, SMALL_OUT[:]},
{"libtiff group 3, wide", G3_WIDE[:], 200, 0, WIDE_OUT[:]},
{"extended make-up, white", MH_WIDE_WHITE[:], 2000, 1, MH_WIDE_WHITE_OUT[:]},
{"extended make-up, black", MH_WIDE_BLACK[:], 2000, 1, MH_WIDE_BLACK_OUT[:]},
}
for c in cases {
p := ccitt_default_params()
p.k = 0
p.columns = c.columns
p.rows = c.rows
expect_decode(t, c.name, c.data, p, c.want)
}
}
@(test)
test_ccitt_fax_page :: proc(t: ^testing.T) {
// The same page encoded two different ways by libtiff must come back out
// of the two decoding paths identically. A mistake in one run-length
// table shows up here even when the 2-D coder is stepping over it.
p := ccitt_default_params()
p.columns = 1728
p.rows = 6
p.k = -1
expect_decode(t, "1728-wide page, group 4", PAGE_G4[:], p, PAGE_OUT[:])
p.k = 0
expect_decode(t, "1728-wide page, group 3", PAGE_G3[:], p, PAGE_OUT[:])
// /Rows absent: the EOFB and RTC that libtiff appends have to end it in
// the right place on their own.
p.rows = 0
p.k = -1
expect_decode(t, "group 4, rows unknown", PAGE_G4[:], p, PAGE_OUT[:])
}
@(test)
test_ccitt_max_changing_elements :: proc(t: ^testing.T) {
// 1728 alternating pixels is the most changing elements a standard fax
// row can hold: 1728 of them, against scratch lines sized for the row.
// Anything that miscounted those lines overruns here.
data: [dynamic]byte
defer delete(data)
acc: u32 = 0
n_bits := 0
put :: proc(data: ^[dynamic]byte, acc: ^u32, n_bits: ^int, code: u32, n: int) {
acc^ = acc^ << uint(n) | code
n_bits^ += n
for n_bits^ >= 8 {
append(data, byte(acc^ >> uint(n_bits^ - 8)))
n_bits^ -= 8
}
}
for _ in 0 ..< 864 {
put(&data, &acc, &n_bits, 0b000111, 6) // white 1
put(&data, &acc, &n_bits, 0b010, 3) // black 1
}
if n_bits > 0 {
append(&data, byte(acc << uint(8 - n_bits)))
}
p := ccitt_default_params()
p.k = 0
p.columns = 1728
p.rows = 1
got, err := ccitt_decode(data[:], p)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, len(got), 216)
for b, i in got {
testing.expectf(t, b == 0xAA, "byte %d is %02X, want AA", i, b)
}
}
@(test)
test_ccitt_mixed_k :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.k = 4
p.columns = 8
expect_decode(t, "1-D then 2-D", MR_MIXED[:], p, MR_MIXED_OUT[:])
expect_decode(t, "1-D, V0, horizontal", MR_MIXED3[:], p, MR_MIXED3_OUT[:])
}
@(test)
test_ccitt_byte_align :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.k = 0
p.columns = 8
p.byte_align = true
expect_decode(t, "byte aligned rows", MH_BYTE_ALIGN[:], p, MH_BYTE_ALIGN_OUT[:])
// Without the flag the second row is read starting at bit 5, where the
// padding of the first row is, so it decodes to something else entirely.
// The point is only that it still terminates and returns rows.
p.byte_align = false
got, err := ccitt_decode(MH_BYTE_ALIGN[:], p)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect(t, len(got) > 0)
}
@(test)
test_ccitt_black_is_1 :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.k = -1
p.columns = 8
p.rows = 12
p.black_is_1 = true
expect_decode(t, "black is 1", G4_FULL[:], p, G4_FULL_OUT_INVERTED[:])
}
@(test)
test_ccitt_default_columns :: proc(t: ^testing.T) {
// /Columns absent means 1728, so one all-white row is 216 bytes.
// 1728 = 1728 make-up + 0 terminating.
data := [?]byte{0b0100_1101, 0b1001_1010, 0b1000_0000}
p := ccitt_default_params()
p.columns = 0
p.rows = 1
got, err := ccitt_decode(data[:], p)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, len(got), 216)
for b, i in got {
testing.expectf(t, b == 0xFF, "byte %d is %02X, want FF", i, b)
}
}
// ---------------------------------------------------------------------------
// Tolerances
// ---------------------------------------------------------------------------
@(test)
test_ccitt_row_length_tolerance :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.k = 0
p.columns = 8
// A run longer than the row is clipped and the row is still produced.
expect_decode(t, "over-long run", MH_OVERLONG[:], p, MH_OVERLONG_OUT[:])
// A row whose data ends early keeps the colour it had reached.
expect_decode(t, "short row", MH_SHORT[:], p, MH_SHORT_OUT[:])
}
@(test)
test_ccitt_rows_limit :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.k = -1
p.columns = 8
// Fewer rows than the data holds: decoding stops at /Rows.
p.rows = 3
expect_decode(t, "rows = 3", G4_FULL[:], p, G4_FULL_OUT[:3])
// More rows than the data holds: the rows that decoded are returned
// rather than an error, and the caller can tell from the length.
p.rows = 500
got, err := ccitt_decode(G4_FULL[:], p)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, len(got), 12)
// An absurd /Rows is a clamp, not an allocation.
p.rows = 1 << 40
got2, err2 := ccitt_decode(G4_FULL[:], p)
defer delete(got2)
testing.expect_value(t, err2, Error.None)
testing.expect_value(t, len(got2), 12)
}
@(test)
test_ccitt_absurd_columns :: proc(t: ^testing.T) {
// /Columns is attacker-controlled: it must be clamped before it sizes
// either the scratch lines or the output.
for columns in ([?]int{-1, 0, 1, 1 << 30, max(int)}) {
p := ccitt_default_params()
p.k = -1
p.columns = columns
p.rows = 1
got, err := ccitt_decode(G4_FULL[:], p)
defer delete(got)
testing.expectf(t, err == .None || err == .Bad_Ccitt_Data,
"columns %d: %v", columns, error_string(err))
testing.expectf(t, len(got) <= MAX_CCITT_BYTES, "columns %d: %d bytes", columns, len(got))
}
}
@(test)
test_ccitt_no_data :: proc(t: ^testing.T) {
p := ccitt_default_params()
p.columns = 8
// Nothing at all.
got, err := ccitt_decode(nil, p)
testing.expect_value(t, err, Error.Bad_Ccitt_Data)
testing.expect_value(t, len(got), 0)
// All zeros: no run code is all zeros, so not one row can be made.
zeros := [16]byte{}
got2, err2 := ccitt_decode(zeros[:], p)
defer delete(got2)
testing.expect_value(t, err2, Error.Bad_Ccitt_Data)
// The same in Group 4, where seven zero bits is not a mode code.
p.k = -1
got3, err3 := ccitt_decode(zeros[:], p)
defer delete(got3)
testing.expect_value(t, err3, Error.Bad_Ccitt_Data)
}
@(test)
test_ccitt_truncated :: proc(t: ^testing.T) {
// Every prefix of a good image must decode to some prefix of the rows,
// never hang and never return more than the whole image.
for n in 0 ..= len(G4_FULL) {
p := ccitt_default_params()
p.k = -1
p.columns = 8
p.rows = 12
got, err := ccitt_decode(G4_FULL[:n], p)
defer delete(got)
testing.expectf(t, err == .None || err == .Bad_Ccitt_Data, "prefix %d: %v", n, error_string(err))
testing.expectf(t, len(got) <= 12, "prefix %d: %d rows", n, len(got))
// Whatever survived is a prefix of the real image, except possibly
// the last row, which may have been cut off mid-way.
whole := max(len(got) - 1, 0)
testing.expectf(t, slice.equal(got[:whole], G4_FULL_OUT[:whole]),
"prefix %d: % 02X", n, got)
}
}
@(test)
test_ccitt_bit_flips :: proc(t: ^testing.T) {
// Corrupt data must still terminate, must not read out of bounds and must
// not hand back more rows than were asked for. What it decodes to is not
// interesting; that it always answers is.
for k in ([?]int{-1, 0, 4}) {
for i in 0 ..< len(G4_FULL) * 8 {
data: [len(G4_FULL)]byte
copy(data[:], G4_FULL[:])
data[i / 8] ~= 1 << uint(7 - i % 8)
p := ccitt_default_params()
p.k = k
p.columns = 8
p.rows = 12
p.byte_align = i % 3 == 0
got, err := ccitt_decode(data[:], p)
defer delete(got)
testing.expectf(t, err == .None || err == .Bad_Ccitt_Data, "k %d bit %d: %v", k, i, error_string(err))
testing.expectf(t, len(got) <= 12, "k %d bit %d: %d bytes", k, i, len(got))
}
}
}
@(test)
test_ccitt_hostile_streams :: proc(t: ^testing.T) {
Case :: struct {
name: string,
data: []byte,
k: int,
}
// 0xFF is V0 eight times over, 0x11 is pass mode, 0x24 is horizontal
// mode with the shortest black codes, 0x08 is VL2/VL3 territory. Each of
// these repeated is a stream that only a fuel counter stops.
all_v0 := [64]byte{}
all_pass := [64]byte{}
all_h := [64]byte{}
all_vl := [64]byte{}
for i in 0 ..< 64 {
all_v0[i] = 0xFF
all_pass[i] = 0x11
all_h[i] = 0x24
all_vl[i] = 0x08
}
cases := []Case{
{"all V0", all_v0[:], -1},
{"all pass", all_pass[:], -1},
{"all horizontal", all_h[:], -1},
{"all VL", all_vl[:], -1},
{"all V0 as 1-D", all_v0[:], 0},
{"all pass as mixed", all_pass[:], 4},
}
for c in cases {
for columns in ([?]int{1, 8, 1728}) {
p := ccitt_default_params()
p.k = c.k
p.columns = columns
p.rows = 0
got, err := ccitt_decode(c.data, p)
defer delete(got)
testing.expectf(t, err == .None || err == .Bad_Ccitt_Data,
"%s at %d columns: %v", c.name, columns, error_string(err))
row_bytes := (columns + 7) / 8
testing.expectf(t, len(got) <= len(c.data) * 8 * row_bytes,
"%s at %d columns: %d bytes from %d", c.name, columns, len(got), len(c.data))
}
}
}
@(test)
test_ccitt_extension_mode_stops :: proc(t: ^testing.T) {
// 0000001 is the 2-D extension escape (uncompressed mode). No PDF
// producer emits it, and guessing at what follows would desynchronise
// the whole image, so the row it appears in ends the image.
// Here: one V0 row, then an extension.
data := [?]byte{0b1000_0001, 0b0000_0000}
p := ccitt_default_params()
p.k = -1
p.columns = 8
got, err := ccitt_decode(data[:], p)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect_value(t, len(got), 1)
testing.expect_value(t, got[0], byte(0xFF))
}
@(test)
test_ccitt_allocator_is_honoured :: proc(t: ^testing.T) {
// The result must come from the allocator the caller named and not from
// context.allocator. filters.odin names a scratch allocator it then frees,
// so a result that quietly came from somewhere else would be a leak.
got, err := ccitt_decode(G4_FULL[:], {k = -1, columns = 8, rows = 12, end_of_block = true},
context.allocator)
defer delete(got)
testing.expect_value(t, err, Error.None)
testing.expect(t, slice.equal(got, G4_FULL_OUT[:]))
}
// /Rows is a claim, not a measurement: ccitt_decode reserves a buffer for the
// rows the dictionary promises and then decodes the rows the data actually
// holds. A file that promises twenty thousand and delivers twelve would leave
// the difference in the Document arena for good, since an arena cannot take a
// buffer back and its `delete` is a silent no-op. apply_filter is where that
// is prevented -- the decode runs against scratch and only the finished rows
// are copied across -- so the test sits on the boundary rather than on
// ccitt_decode, which is entitled to over-reserve as long as it does so with
// memory somebody can free.
@(test)
test_ccitt_row_buffer_stays_out_of_the_document_arena :: proc(t: ^testing.T) {
doc := filter_test_doc()
defer filter_test_doc_free(doc)
// The G4 image padded with trailing zeroes, so that the row cap the
// decoder derives from the data length does not clamp /Rows back down.
// The EOFB inside the image still ends it after twelve rows.
data := make([]byte, 4096)
defer delete(data)
copy(data, G4_FULL[:])
parms := new_dict(doc.allocator)
dict_set(parms, "K", i64(-1))
dict_set(parms, "Columns", i64(8))
dict_set(parms, "Rows", i64(20000))
before := document_memory_used(doc)
out, err := apply_filter(doc, "CCITTFaxDecode", data, parms, doc.allocator)
testing.expect_value(t, err, Error.None)
testing.expect(t, slice.equal(out, G4_FULL_OUT[:]), "the image did not decode")
growth := document_memory_used(doc) - before
testing.expectf(t, growth <= len(out) + FT_ARENA_SLACK,
"the arena grew by %d bytes for %d bytes of output", growth, len(out))
}