1353 lines
51 KiB
Odin
1353 lines
51 KiB
Odin
package pdf
|
|
|
|
import "core:mem/virtual"
|
|
import "core:slice"
|
|
import "core:testing"
|
|
|
|
// The predictor vectors below are hand-computed from RFC 2083 section 6 and
|
|
// TIFF 6.0 section 14 and are written out byte by byte in the test that uses
|
|
// them, so a change of behaviour has to be argued with rather than re-blessed.
|
|
// The compressed vectors come from zlib via CPython, and the LZW one is the
|
|
// published TIFF 6.0 example.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Vectors
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(private = "file")
|
|
V_CHAIN_PLAIN :: "Hello, filter chain!"
|
|
|
|
// zlib.compress(V_CHAIN_PLAIN)
|
|
@(private = "file")
|
|
v_chain_flate := [?]byte{
|
|
0x78, 0xda, 0xf3, 0x48, 0xcd, 0xc9, 0xc9, 0xd7, 0x51, 0x48, 0xcb, 0xcc, 0x29, 0x49, 0x2d, 0x52,
|
|
0x48, 0xce, 0x48, 0xcc, 0xcc, 0x53, 0x04, 0x00, 0x4a, 0x94, 0x07, 0x0b,
|
|
}
|
|
|
|
// the same bytes in ASCII85: "GhVa[c,n)Z;+SmS.7l*h8BscMbYnWE8sBPg~>"
|
|
@(private = "file")
|
|
v_chain_a85 := [?]byte{
|
|
0x47, 0x68, 0x56, 0x61, 0x5b, 0x63, 0x2c, 0x6e, 0x29, 0x5a, 0x3b, 0x2b, 0x53, 0x6d, 0x53, 0x2e,
|
|
0x37, 0x6c, 0x2a, 0x68, 0x38, 0x42, 0x73, 0x63, 0x4d, 0x62, 0x59, 0x6e, 0x57, 0x45, 0x38, 0x73,
|
|
0x42, 0x50, 0x67, 0x7e, 0x3e,
|
|
}
|
|
|
|
// The shape an xref stream has: three four-byte rows, PNG Up-filtered, then
|
|
// deflated. The encoded rows are {2,1,0,0,0}, {2,0,0,0,1}, {2,0,0,0,1} and
|
|
// they reconstruct to V_PRED_PAYLOAD.
|
|
@(private = "file")
|
|
v_pred_flate := [?]byte{
|
|
0x78, 0xda, 0x63, 0x62, 0x64, 0x60, 0x60, 0x60, 0x02, 0x62, 0x46, 0x30, 0x01, 0x00, 0x00, 0x60,
|
|
0x00, 0x0a,
|
|
}
|
|
|
|
@(private = "file")
|
|
v_pred_payload := [?]byte{1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 2}
|
|
|
|
// TIFF 6.0 section 13: "-----A---B" packed at 9 bits.
|
|
@(private = "file")
|
|
v_lzw_tiff := [?]byte{0x80, 0x0b, 0x60, 0x50, 0x22, 0x0c, 0x0c, 0x85, 0x01}
|
|
|
|
@(private = "file")
|
|
V_LZW_PLAIN :: "-----A---B"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Fixtures
|
|
//
|
|
// A Document built by hand: filters need nothing but the arena and the repair
|
|
// list, and going through open_bytes would drag in the whole file structure.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Package-private rather than file-private: the codec test files measure how
|
|
// much of this arena a decode costs, and that rule belongs to apply_filter
|
|
// rather than to any one codec, so they should not each grow a Document of
|
|
// their own.
|
|
@(private)
|
|
filter_test_doc :: proc() -> ^Document {
|
|
doc := new(Document)
|
|
doc.backing = context.allocator
|
|
if err := virtual.arena_init_growing(&doc.arena); err != nil {
|
|
free(doc)
|
|
return nil
|
|
}
|
|
doc.allocator = virtual.arena_allocator(&doc.arena)
|
|
doc.repairs = make([dynamic]Repair, doc.allocator)
|
|
return doc
|
|
}
|
|
|
|
@(private)
|
|
filter_test_doc_free :: proc(doc: ^Document) {
|
|
if doc == nil {
|
|
return
|
|
}
|
|
virtual.arena_destroy(&doc.arena)
|
|
free(doc, doc.backing)
|
|
}
|
|
|
|
@(private = "file")
|
|
ft_names :: proc(doc: ^Document, names: []string) -> ^Array {
|
|
a := new_array(doc.allocator)
|
|
for n in names {
|
|
array_push(a, Name(n))
|
|
}
|
|
return a
|
|
}
|
|
|
|
// ft_parms builds a parameter dictionary from alternating key/value pairs,
|
|
// where every value is an integer -- which is all the predictor and
|
|
// /EarlyChange need.
|
|
@(private = "file")
|
|
ft_parms :: proc(doc: ^Document, keys: []string, values: []i64) -> ^Dict {
|
|
d := new_dict(doc.allocator)
|
|
for k, i in keys {
|
|
if i < len(values) {
|
|
dict_set(d, k, values[i])
|
|
}
|
|
}
|
|
return d
|
|
}
|
|
|
|
@(private = "file")
|
|
ft_stream :: proc(doc: ^Document, raw: []byte, filter: Object, parms: Object,
|
|
filter_key := "Filter", parms_key := "DecodeParms") -> ^Stream {
|
|
d := new_dict(doc.allocator)
|
|
if filter != nil {
|
|
dict_set(d, filter_key, filter)
|
|
}
|
|
if parms != nil {
|
|
dict_set(d, parms_key, parms)
|
|
}
|
|
return new_stream(d, raw, doc.allocator)
|
|
}
|
|
|
|
@(private = "file")
|
|
ft_repairs :: proc(doc: ^Document, kind: Repair_Kind) -> int {
|
|
n := 0
|
|
for r in doc.repairs {
|
|
if r.kind == kind {
|
|
n += 1
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Filter names
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_filter_kind_accepts_full_names_and_abbreviations :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
name: string,
|
|
kind: Filter_Kind,
|
|
}
|
|
cases := [?]Case{
|
|
{"ASCIIHexDecode", .Ascii_Hex},
|
|
{"AHx", .Ascii_Hex},
|
|
{"ASCII85Decode", .Ascii85},
|
|
{"A85", .Ascii85},
|
|
{"LZWDecode", .Lzw},
|
|
{"LZW", .Lzw},
|
|
{"FlateDecode", .Flate},
|
|
{"Fl", .Flate},
|
|
{"RunLengthDecode", .Run_Length},
|
|
{"RL", .Run_Length},
|
|
{"CCITTFaxDecode", .Ccitt},
|
|
{"CCF", .Ccitt},
|
|
{"DCTDecode", .Dct},
|
|
{"DCT", .Dct},
|
|
{"JPXDecode", .Jpx},
|
|
{"JBIG2Decode", .Jbig2},
|
|
{"Crypt", .Crypt},
|
|
{"", .Unknown},
|
|
{"Flate", .Unknown},
|
|
{"flatedecode", .Unknown},
|
|
{"NotAFilter", .Unknown},
|
|
}
|
|
for c in cases {
|
|
testing.expectf(t, filter_kind(c.name) == c.kind,
|
|
"filter_kind(%q) = %v, want %v", c.name, filter_kind(c.name), c.kind)
|
|
}
|
|
// The abbreviation is always reported back in full, so the image layer has
|
|
// one spelling to compare against.
|
|
testing.expect_value(t, filter_name(filter_kind("DCT"), "DCT"), "DCTDecode")
|
|
testing.expect_value(t, filter_name(.Unknown, "Whatever"), "Whatever")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// PNG predictors -- RFC 2083, 6.2 to 6.6
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// One row per filter type, each hand-reconstructed above:
|
|
//
|
|
// None 10 20 30
|
|
// Sub 1 1+2=3 3+3=6
|
|
// Up 5+1 5+3 5+6
|
|
// Average 1+(0+6)/2 1+(4+8)/2 1+(7+11)/2
|
|
// Paeth 1+b(4) 2+b(7) 3+b(10)
|
|
@(test)
|
|
test_predictor_png_every_filter :: proc(t: ^testing.T) {
|
|
data := [?]byte{
|
|
0, 10, 20, 30,
|
|
1, 1, 2, 3,
|
|
2, 5, 5, 5,
|
|
3, 1, 1, 1,
|
|
4, 1, 2, 3,
|
|
}
|
|
want := [?]byte{
|
|
10, 20, 30,
|
|
1, 3, 6,
|
|
6, 8, 11,
|
|
4, 7, 10,
|
|
5, 9, 13,
|
|
}
|
|
out, err := apply_predictor(data[:], 12, 1, 8, 3)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// Every /Predictor from 10 to 15 decodes identically: the value records the
|
|
// widest filter the encoder was allowed to pick, and the row tag is what
|
|
// actually selects one.
|
|
@(test)
|
|
test_predictor_png_values_10_to_15_agree :: proc(t: ^testing.T) {
|
|
data := [?]byte{1, 1, 2, 3, 2, 5, 5, 5}
|
|
want := [?]byte{1, 3, 6, 6, 8, 11}
|
|
for p in 10 ..= 15 {
|
|
out, err := apply_predictor(data[:], p, 1, 8, 3)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err))
|
|
testing.expectf(t, slice.equal(out, want[:]), "predictor %d: got %v", p, out)
|
|
}
|
|
}
|
|
|
|
// On the first row the filters read zeroes for the line above, so Up is a
|
|
// no-op, Average halves the left neighbour and Paeth degenerates to Sub.
|
|
@(test)
|
|
test_predictor_png_first_row_reads_zeroes :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
tag: byte,
|
|
want: [2]byte,
|
|
}
|
|
cases := [?]Case{
|
|
{0, {5, 3}},
|
|
{1, {5, 8}},
|
|
{2, {5, 3}},
|
|
{3, {5, 5}},
|
|
{4, {5, 8}},
|
|
}
|
|
for c in cases {
|
|
data := [?]byte{c.tag, 5, 3}
|
|
want := c.want
|
|
out, err := apply_predictor(data[:], 15, 1, 8, 2)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "tag %d: %s", c.tag, error_string(err))
|
|
testing.expectf(t, slice.equal(out, want[:]), "tag %d: got %v, want %v",
|
|
c.tag, out, want)
|
|
}
|
|
}
|
|
|
|
// bpp is the byte distance to the pixel on the left, so Sub over three
|
|
// 8-bit colours adds the value three bytes back, not one.
|
|
@(test)
|
|
test_predictor_png_multibyte_pixel :: proc(t: ^testing.T) {
|
|
data := [?]byte{1, 10, 20, 30, 1, 2, 3}
|
|
want := [?]byte{10, 20, 30, 11, 22, 33}
|
|
out, err := apply_predictor(data[:], 12, 3, 8, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// bpp has a floor of 1: four 1-bit colours are half a byte per pixel, and Sub
|
|
// still reaches one byte back.
|
|
@(test)
|
|
test_predictor_png_sub_byte_pixel :: proc(t: ^testing.T) {
|
|
data := [?]byte{1, 0x11, 0x22}
|
|
want := [?]byte{0x11, 0x33}
|
|
out, err := apply_predictor(data[:], 12, 4, 1, 4)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// Arithmetic is modulo 256 in both directions (RFC 2083, 6.2).
|
|
@(test)
|
|
test_predictor_png_wraps_at_256 :: proc(t: ^testing.T) {
|
|
data := [?]byte{1, 200, 100, 2, 200}
|
|
// 200; 200+100 = 300 mod 256 = 44; then Up: 200+200 = 400 mod 256 = 144.
|
|
want := [?]byte{200, 44, 144, 44}
|
|
out, err := apply_predictor(data[:], 12, 1, 8, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// A tag no encoder emits: one byte lost from the front of the payload shifts
|
|
// every tag, and reading the row unfiltered keeps the rest of the stream in
|
|
// step instead of losing all of it. The row after it still decodes against
|
|
// the row the unknown tag produced.
|
|
@(test)
|
|
test_predictor_png_unknown_row_tag_is_none :: proc(t: ^testing.T) {
|
|
data := [?]byte{9, 7, 7, 2, 1, 2}
|
|
want := [?]byte{7, 7, 8, 9}
|
|
out, err := apply_predictor(data[:], 12, 1, 8, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
@(test)
|
|
test_predictor_paeth_matches_the_reference :: proc(t: ^testing.T) {
|
|
reference :: proc(a, b, c: int) -> int {
|
|
p := a + b - c
|
|
pa, pb, pc := abs(p - a), abs(p - b), abs(p - c)
|
|
if pa <= pb && pa <= pc {
|
|
return a
|
|
} else if pb <= pc {
|
|
return b
|
|
}
|
|
return c
|
|
}
|
|
for a := 0; a < 256; a += 7 {
|
|
for b := 0; b < 256; b += 5 {
|
|
for c := 0; c < 256; c += 3 {
|
|
testing.expectf(t, predictor_paeth(a, b, c) == reference(a, b, c),
|
|
"paeth(%d,%d,%d) = %d, want %d", a, b, c,
|
|
predictor_paeth(a, b, c), reference(a, b, c))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// TIFF predictor -- TIFF 6.0, section 14
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_predictor_tiff_8_bit :: proc(t: ^testing.T) {
|
|
data := [?]byte{10, 5, 5, 1, 1, 1}
|
|
want := [?]byte{10, 15, 20, 1, 2, 3}
|
|
out, err := apply_predictor(data[:], 2, 1, 8, 3)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// Differencing is per colour, so with three colours each component is the
|
|
// difference from the same component of the pixel to its left.
|
|
@(test)
|
|
test_predictor_tiff_8_bit_three_colours :: proc(t: ^testing.T) {
|
|
data := [?]byte{10, 20, 30, 1, 2, 3}
|
|
want := [?]byte{10, 20, 30, 11, 22, 33}
|
|
out, err := apply_predictor(data[:], 2, 3, 8, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
// Four 4-bit components in two bytes: 1 2 3 4 accumulates to 1 3 6 10.
|
|
@(test)
|
|
test_predictor_tiff_4_bit :: proc(t: ^testing.T) {
|
|
data := [?]byte{0x12, 0x34}
|
|
want := [?]byte{0x13, 0x6a}
|
|
out, err := apply_predictor(data[:], 2, 1, 4, 4)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want)
|
|
}
|
|
|
|
// The same two bytes read as two pixels of two 4-bit colours: component 2 adds
|
|
// component 0 and component 3 adds component 1.
|
|
@(test)
|
|
test_predictor_tiff_4_bit_two_colours :: proc(t: ^testing.T) {
|
|
data := [?]byte{0x12, 0x34}
|
|
want := [?]byte{0x12, 0x46}
|
|
out, err := apply_predictor(data[:], 2, 2, 4, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want)
|
|
}
|
|
|
|
// Four 2-bit components in one byte: 1 2 3 1 accumulates to 1 3 2 3, each
|
|
// sum taken modulo 4.
|
|
@(test)
|
|
test_predictor_tiff_2_bit :: proc(t: ^testing.T) {
|
|
data := [?]byte{0b01_10_11_01}
|
|
want := [?]byte{0b01_11_10_11}
|
|
out, err := apply_predictor(data[:], 2, 1, 2, 4)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %b, want %b", out, want)
|
|
}
|
|
|
|
// 1-bit components add modulo 2, which is an exclusive or along the row.
|
|
@(test)
|
|
test_predictor_tiff_1_bit :: proc(t: ^testing.T) {
|
|
data := [?]byte{0xaa}
|
|
want := [?]byte{0xcc}
|
|
out, err := apply_predictor(data[:], 2, 1, 1, 8)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want)
|
|
}
|
|
|
|
// 16-bit components are big-endian and wrap at 65536, not at 256.
|
|
@(test)
|
|
test_predictor_tiff_16_bit :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
data: [4]byte,
|
|
want: [4]byte,
|
|
}
|
|
cases := [?]Case{
|
|
{{0x00, 0x10, 0x00, 0x20}, {0x00, 0x10, 0x00, 0x30}},
|
|
{{0xff, 0xff, 0x00, 0x02}, {0xff, 0xff, 0x00, 0x01}},
|
|
}
|
|
for c in cases {
|
|
input := c.data
|
|
want := c.want
|
|
out, err := apply_predictor(input[:], 2, 1, 16, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want)
|
|
}
|
|
}
|
|
|
|
// The padding bits at the end of a row are not components and must be left
|
|
// alone: 3 columns of 4 bits occupy a byte and a half.
|
|
@(test)
|
|
test_predictor_tiff_ignores_row_padding :: proc(t: ^testing.T) {
|
|
data := [?]byte{0x12, 0x3f}
|
|
// components 1, 2, 3 -> 1, 3, 6; the low nibble of byte 1 is padding.
|
|
want := [?]byte{0x13, 0x6f}
|
|
out, err := apply_predictor(data[:], 2, 1, 4, 3)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %x, want %x", out, want)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Predictor edge cases
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// A payload that stops mid-row is padded, not dropped. A truncated xref stream
|
|
// still describes the objects it got to.
|
|
@(test)
|
|
test_predictor_truncated_final_row_is_padded :: proc(t: ^testing.T) {
|
|
png := [?]byte{0, 10, 20, 30, 2, 1}
|
|
png_want := [?]byte{10, 20, 30, 11, 20, 30}
|
|
out, err := apply_predictor(png[:], 12, 1, 8, 3)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, png_want[:]), "png: got %v, want %v", out, png_want)
|
|
|
|
tiff := [?]byte{10, 5, 5, 1, 1}
|
|
tiff_want := [?]byte{10, 15, 20, 1, 2, 2}
|
|
out2, err2 := apply_predictor(tiff[:], 2, 1, 8, 3)
|
|
defer delete(out2)
|
|
testing.expect_value(t, err2, Error.None)
|
|
testing.expectf(t, slice.equal(out2, tiff_want[:]), "tiff: got %v, want %v", out2, tiff_want)
|
|
}
|
|
|
|
// A row consisting of nothing but its filter tag still produces a row.
|
|
@(test)
|
|
test_predictor_final_row_is_only_a_tag :: proc(t: ^testing.T) {
|
|
data := [?]byte{0, 10, 20, 1}
|
|
want := [?]byte{10, 20, 0, 0}
|
|
out, err := apply_predictor(data[:], 12, 1, 8, 2)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, want[:]), "got %v, want %v", out, want)
|
|
}
|
|
|
|
@(test)
|
|
test_predictor_none_copies :: proc(t: ^testing.T) {
|
|
data := [?]byte{1, 2, 3, 4}
|
|
for p in 0 ..= 1 {
|
|
out, err := apply_predictor(data[:], p, 1, 8, 4)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err))
|
|
testing.expectf(t, slice.equal(out, data[:]), "predictor %d changed the data", p)
|
|
testing.expectf(t, raw_data(out) != raw_data(data[:]),
|
|
"predictor %d aliased its input", p)
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
test_predictor_empty_input :: proc(t: ^testing.T) {
|
|
for p in ([?]int{1, 2, 12}) {
|
|
out, err := apply_predictor(nil, p, 1, 8, 4)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "predictor %d: %s", p, error_string(err))
|
|
testing.expectf(t, len(out) == 0, "predictor %d invented %d bytes", p, len(out))
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
test_predictor_rejects_absurd_parameters :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
label: string,
|
|
predictor, colors, bpc, columns: int,
|
|
}
|
|
cases := [?]Case{
|
|
{"predictor 3 is not defined", 3, 1, 8, 4},
|
|
{"predictor 9 is not defined", 9, 1, 8, 4},
|
|
{"predictor 16 is past PNG", 16, 1, 8, 4},
|
|
{"negative predictor", -1, 1, 8, 4},
|
|
{"zero colours", 12, 0, 8, 4},
|
|
{"negative colours", 12, -3, 8, 4},
|
|
{"more colours than DeviceN", 12, 33, 8, 4},
|
|
{"3 bits per component", 12, 1, 3, 4},
|
|
{"zero bits per component", 12, 1, 0, 4},
|
|
{"32 bits per component", 12, 1, 32, 4},
|
|
{"negative bits per component", 12, 1, -8, 4},
|
|
{"zero columns", 12, 1, 8, 0},
|
|
{"negative columns", 12, 1, 8, -4},
|
|
{"absurd columns", 12, 1, 8, 1 << 30},
|
|
{"absurd row", 2, 32, 16, 1 << 20},
|
|
}
|
|
data := [?]byte{0, 1, 2, 3, 4, 5, 6, 7}
|
|
for c in cases {
|
|
out, err := apply_predictor(data[:], c.predictor, c.colors, c.bpc, c.columns)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .Bad_Predictor, "%s: got %s", c.label, error_string(err))
|
|
testing.expectf(t, out == nil, "%s: returned %d bytes as well", c.label, len(out))
|
|
}
|
|
}
|
|
|
|
// Padding the final row is the only way the output can exceed the input, so
|
|
// the ceiling on an allocation the file controls is one row -- and a row is
|
|
// itself capped, which is what MAX_PREDICTOR_ROW_BYTES is for.
|
|
@(test)
|
|
test_predictor_output_is_bounded_by_one_row :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
predictor, colors, bpc, columns: int,
|
|
row_bytes: int,
|
|
}
|
|
cases := [?]Case{
|
|
{15, 32, 16, 1 << 15, 32 * 16 * (1 << 15) / 8},
|
|
{12, 1, 8, 1 << 20, 1 << 20},
|
|
{2, 4, 8, 1000, 4000},
|
|
{12, 1, 1, 7, 1},
|
|
}
|
|
data := make([]byte, 4096)
|
|
defer delete(data)
|
|
for c in cases {
|
|
out, err := apply_predictor(data, c.predictor, c.colors, c.bpc, c.columns)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "%v: %s", c, error_string(err))
|
|
testing.expectf(t, len(out) <= len(data) + c.row_bytes,
|
|
"%v: %d bytes out of %d in, more than one row of slack",
|
|
c, len(out), len(data))
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// apply_filter
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_apply_filter_byte_filters :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
name: string,
|
|
raw: string,
|
|
want: string,
|
|
}
|
|
cases := [?]Case{
|
|
{"ASCIIHexDecode", "48656C6C6F>", "Hello"},
|
|
{"AHx", "48656C6C6F>", "Hello"},
|
|
{"ASCII85Decode", "87cURD]j7BEbo7~>", "Hello world"},
|
|
{"A85", "87cURD]j7BEbo7~>", "Hello world"},
|
|
{"RunLengthDecode", "\x02abc\xfeZ\x80", "abcZZZ"},
|
|
{"RL", "\x02abc\xfeZ\x80", "abcZZZ"},
|
|
}
|
|
for c in cases {
|
|
out, err := apply_filter(nil, c.name, transmute([]byte)c.raw, nil)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "%s: %s", c.name, error_string(err))
|
|
testing.expectf(t, string(out) == c.want, "%s: got %q, want %q",
|
|
c.name, string(out), c.want)
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
test_apply_filter_flate :: proc(t: ^testing.T) {
|
|
out, err := apply_filter(nil, "Fl", v_chain_flate[:], nil)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(out), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
@(test)
|
|
test_apply_filter_lzw_honours_early_change :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
for value in ([?]i64{0, 1}) {
|
|
parms := ft_parms(doc, {"EarlyChange"}, {value})
|
|
out, err := apply_filter(doc, "LZW", v_lzw_tiff[:], parms)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "EarlyChange %d: %s", value, error_string(err))
|
|
testing.expectf(t, string(out) == V_LZW_PLAIN,
|
|
"EarlyChange %d: got %q", value, string(out))
|
|
}
|
|
// No parameters at all means /EarlyChange 1.
|
|
out, err := apply_filter(nil, "LZWDecode", v_lzw_tiff[:], nil)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(out), V_LZW_PLAIN)
|
|
}
|
|
|
|
@(test)
|
|
test_apply_filter_runs_the_predictor :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4})
|
|
out, err := apply_filter(doc, "FlateDecode", v_pred_flate[:], parms)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, v_pred_payload[:]), "got %v, want %v", out, v_pred_payload)
|
|
}
|
|
|
|
// LZW with the TIFF predictor, end to end: "-----A---B" read as two rows of
|
|
// five 8-bit components, each accumulating along its row modulo 256.
|
|
@(test)
|
|
test_stream_data_lzw_with_the_tiff_predictor :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"Predictor", "Colors", "BitsPerComponent", "Columns"}, {2, 1, 8, 5})
|
|
s := ft_stream(doc, v_lzw_tiff[:], Name("LZWDecode"), parms)
|
|
got, err := stream_data(doc, s)
|
|
want := [?]byte{45, 90, 135, 180, 225, 65, 110, 155, 200, 10}
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(got, want[:]), "got %v, want %v", got, want)
|
|
}
|
|
|
|
// A predictor whose parameters cannot describe any image fails the filter
|
|
// rather than handing back bytes that only look like samples.
|
|
@(test)
|
|
test_apply_filter_bad_predictor_fails :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"Predictor", "Columns"}, {7, 4})
|
|
out, err := apply_filter(doc, "FlateDecode", v_pred_flate[:], parms)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.Bad_Predictor)
|
|
testing.expect(t, out == nil, "bytes returned alongside a predictor failure")
|
|
}
|
|
|
|
// The pass-through errors hand the input back untouched, which is what lets
|
|
// stream_image_data give a JPX codestream to the image layer.
|
|
@(test)
|
|
test_apply_filter_pass_through_errors :: proc(t: ^testing.T) {
|
|
Case :: struct {
|
|
name: string,
|
|
err: Error,
|
|
}
|
|
cases := [?]Case{
|
|
{"DCTDecode", .Unsupported_Image},
|
|
{"DCT", .Unsupported_Image},
|
|
{"JBIG2Decode", .Unsupported_Image},
|
|
{"JPXDecode", .Unsupported_Jpx},
|
|
{"NoSuchThing", .Unknown_Filter},
|
|
{"", .Unknown_Filter},
|
|
}
|
|
data := [?]byte{1, 2, 3, 4}
|
|
for c in cases {
|
|
out, err := apply_filter(nil, c.name, data[:], nil)
|
|
testing.expectf(t, err == c.err, "%s: got %s", c.name, error_string(err))
|
|
testing.expectf(t, raw_data(out) == raw_data(data[:]),
|
|
"%s: did not hand back the input", c.name)
|
|
}
|
|
}
|
|
|
|
// /Crypt is a marker: the bytes were decrypted at parse time.
|
|
@(test)
|
|
test_apply_filter_crypt_is_identity :: proc(t: ^testing.T) {
|
|
data := [?]byte{9, 8, 7}
|
|
out, err := apply_filter(nil, "Crypt", data[:], nil)
|
|
defer delete(out)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(out, data[:]), "got %v", out)
|
|
testing.expect(t, raw_data(out) != raw_data(data[:]), "Crypt aliased its input")
|
|
}
|
|
|
|
@(test)
|
|
test_apply_filter_ccitt_decodes_rows :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
// Group 4, one row of 8 columns, all white: horizontal mode (001), a white
|
|
// run of 8 (10011) and a black run of 0 (0000110111), zero padded.
|
|
encoded := [?]byte{0b0011_0011, 0b0000_1101, 0b1100_0000}
|
|
|
|
// With /BlackIs1 false -- the default -- a white pixel is a 1 bit.
|
|
parms := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 8, 1})
|
|
out, err := apply_filter(doc, "CCF", encoded[:], parms)
|
|
defer delete(out)
|
|
testing.expectf(t, err == .None, "%s", error_string(err))
|
|
testing.expectf(t, len(out) == 1 && out[0] == 0xff, "got %x", out)
|
|
|
|
// /BlackIs1 exchanges them.
|
|
inverted := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 8, 1})
|
|
dict_set(inverted, "BlackIs1", true)
|
|
out2, err2 := apply_filter(doc, "CCITTFaxDecode", encoded[:], inverted)
|
|
defer delete(out2)
|
|
testing.expectf(t, err2 == .None, "%s", error_string(err2))
|
|
testing.expectf(t, len(out2) == 1 && out2[0] == 0x00, "got %x", out2)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// stream_data: the chain
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_stream_data_two_filter_chain :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "FlateDecode"}), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
testing.expect_value(t, s.state, Stream_Decode_State.Decoded)
|
|
testing.expect_value(t, len(doc.repairs), 0)
|
|
}
|
|
|
|
@(test)
|
|
test_stream_data_abbreviated_names :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
// Inline images abbreviate the keys as well as the filter names
|
|
// (ISO 32000-2, table 91).
|
|
@(test)
|
|
test_stream_data_inline_image_keys :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4})
|
|
s := ft_stream(doc, v_pred_flate[:], Name("Fl"), parms, "F", "DP")
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v", got)
|
|
}
|
|
|
|
// A single name is as legal as a one-element array.
|
|
@(test)
|
|
test_stream_data_single_name_filter :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], Name("FlateDecode"), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
// /DecodeParms is positional, and a null stands for "this filter has none".
|
|
@(test)
|
|
test_stream_data_decode_parms_array_with_a_null :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := new_array(doc.allocator)
|
|
array_push(parms, nil)
|
|
array_push(parms, ft_parms(doc, {"Predictor", "Columns"}, {12, 4}))
|
|
|
|
// ASCII85 armour over the flate stream that needs the predictor.
|
|
armoured := [?]byte{
|
|
0x47, 0x68, 0x51, 0x59, 0x3a, 0x41, 0x37, 0x2f, 0x65, 0x51, 0x21, 0x62, 0x2b, 0x56, 0x51,
|
|
0x21, 0x3c, 0x3c, 0x2b, 0x2d, 0x21, 0x22, 0x26, 0x7e, 0x3e,
|
|
}
|
|
s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "Fl"}), parms)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v, want %v", got, v_pred_payload)
|
|
testing.expect_value(t, len(doc.repairs), 0)
|
|
}
|
|
|
|
// A short /DecodeParms array is padded with nulls rather than treated as a
|
|
// disagreement worth failing over.
|
|
@(test)
|
|
test_stream_data_short_decode_parms_array :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := new_array(doc.allocator)
|
|
array_push(parms, nil)
|
|
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), parms)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
// A bare dictionary against a chain is malformed, but it can only have been
|
|
// meant for the filter that reads parameters -- here the flate, not the
|
|
// ASCII85 armour in front of it.
|
|
@(test)
|
|
test_stream_data_single_parms_dict_for_a_chain :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"Predictor", "Columns"}, {12, 4})
|
|
armoured := [?]byte{
|
|
0x47, 0x68, 0x51, 0x59, 0x3a, 0x41, 0x37, 0x2f, 0x65, 0x51, 0x21, 0x62, 0x2b, 0x56, 0x51,
|
|
0x21, 0x3c, 0x3c, 0x2b, 0x2d, 0x21, 0x22, 0x26, 0x7e, 0x3e,
|
|
}
|
|
s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "Fl"}), parms)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expectf(t, slice.equal(got, v_pred_payload[:]), "got %v, want %v", got, v_pred_payload)
|
|
}
|
|
|
|
// A stray null inside /Filter loses that entry, not the stream.
|
|
@(test)
|
|
test_stream_data_null_inside_the_filter_array :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
filters := new_array(doc.allocator)
|
|
array_push(filters, nil)
|
|
array_push(filters, Name("FlateDecode"))
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], filters, nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// stream_data: degrading
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_stream_data_unknown_filter_degrades :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], Name("SuperZipDecode"), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, got == nil, "an unknown filter produced bytes")
|
|
testing.expect_value(t, s.state, Stream_Decode_State.Failed)
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
testing.expect(t, doc.is_damaged, "an unknown filter did not mark the document damaged")
|
|
|
|
// And the failure is cached too: asking again is free and silent.
|
|
again, err2 := stream_data(doc, s)
|
|
testing.expect_value(t, err2, Error.None)
|
|
testing.expect(t, again == nil, "second call produced bytes")
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
}
|
|
|
|
// An unknown filter anywhere in the chain empties the stream: the bytes that
|
|
// did decode are still encoded as far as the next stage is concerned, and
|
|
// handing a content parser half-encoded data is worse than handing it nothing.
|
|
@(test)
|
|
test_stream_data_unknown_filter_late_in_the_chain :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "SuperZipDecode"}), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, got == nil, "produced bytes past an unknown filter")
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
}
|
|
|
|
// JPX and JBIG2 belong to the image layer. Reaching them through stream_data
|
|
// is a caller mistake, not a document defect worth an error.
|
|
@(test)
|
|
test_stream_data_image_filters_degrade :: proc(t: ^testing.T) {
|
|
for name in ([?]string{"DCTDecode", "JPXDecode", "JBIG2Decode"}) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], Name(name), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expectf(t, err == .None, "%s: %s", name, error_string(err))
|
|
testing.expectf(t, got == nil, "%s: produced bytes", name)
|
|
testing.expectf(t, ft_repairs(doc, .Bad_Filter_Data) == 1, "%s: no repair recorded", name)
|
|
}
|
|
}
|
|
|
|
@(test)
|
|
test_stream_data_broken_flate_degrades :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
junk := [?]byte{0xff, 0xff, 0xff, 0xff}
|
|
s := ft_stream(doc, junk[:], Name("FlateDecode"), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, got == nil, "produced bytes from junk")
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
}
|
|
|
|
// A stream truncated part-way keeps what inflated. Half a content stream draws
|
|
// half a page, which beats a blank one.
|
|
@(test)
|
|
test_stream_data_truncated_flate_keeps_what_it_got :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:len(v_chain_flate) - 6], Name("FlateDecode"), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, len(got) > 0, "a truncated stream decoded to nothing")
|
|
plain := V_CHAIN_PLAIN
|
|
testing.expectf(t, len(got) <= len(plain) && plain[:len(got)] == string(got),
|
|
"recovered prefix disagrees: %q", string(got))
|
|
}
|
|
|
|
// A chain this long is not a document.
|
|
@(test)
|
|
test_stream_data_absurdly_long_chain :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
filters := new_array(doc.allocator)
|
|
for _ in 0 ..< MAX_FILTER_CHAIN + 1 {
|
|
array_push(filters, Name("FlateDecode"))
|
|
}
|
|
s := ft_stream(doc, v_chain_flate[:], filters, nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, got == nil, "a 17-filter chain produced bytes")
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// stream_data: caching and borrowing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_stream_data_caches_the_decode :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"A85", "Fl"}), nil)
|
|
first, err1 := stream_data(doc, s)
|
|
second, err2 := stream_data(doc, s)
|
|
testing.expect_value(t, err1, Error.None)
|
|
testing.expect_value(t, err2, Error.None)
|
|
testing.expect_value(t, string(first), V_CHAIN_PLAIN)
|
|
// Same pointer, not merely the same bytes: a second decode would land
|
|
// somewhere else in the arena.
|
|
testing.expect(t, raw_data(first) == raw_data(second), "the decode was not cached")
|
|
testing.expect(t, raw_data(s.decoded) == raw_data(first), "the cache holds something else")
|
|
}
|
|
|
|
// A stream with no filters is its own decoded form; copying every content
|
|
// stream into the arena would double the memory a document costs for nothing.
|
|
@(test)
|
|
test_stream_data_without_filters_borrows_the_raw_bytes :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
raw := [?]byte{'B', 'T', ' ', 'E', 'T'}
|
|
s := ft_stream(doc, raw[:], nil, nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect(t, raw_data(got) == raw_data(raw[:]), "the raw bytes were copied")
|
|
testing.expect_value(t, s.state, Stream_Decode_State.Decoded)
|
|
}
|
|
|
|
// An empty payload is empty whatever the chain claims. Running flate over it
|
|
// would manufacture a repair for a stream that is legitimately empty.
|
|
@(test)
|
|
test_stream_data_empty_payload_is_not_a_repair :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, nil, Name("FlateDecode"), nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, len(got), 0)
|
|
testing.expect_value(t, s.state, Stream_Decode_State.Decoded)
|
|
testing.expect_value(t, len(doc.repairs), 0)
|
|
}
|
|
|
|
@(test)
|
|
test_stream_data_nil_arguments :: proc(t: ^testing.T) {
|
|
got, err := stream_data(nil, nil)
|
|
testing.expect_value(t, err, Error.Invalid_Argument)
|
|
testing.expect(t, got == nil, "bytes from a nil stream")
|
|
|
|
// A nil Document is legal: the standalone object parser has none. The
|
|
// decoded bytes then come from the context allocator and are the caller's.
|
|
d := new_dict(context.allocator)
|
|
dict_set(d, "Filter", Name("FlateDecode"))
|
|
defer {
|
|
delete(d^)
|
|
free(d)
|
|
}
|
|
s := new_stream(d, v_chain_flate[:], context.allocator)
|
|
defer free(s)
|
|
got2, err2 := stream_data(nil, s)
|
|
defer delete(got2)
|
|
testing.expect_value(t, err2, Error.None)
|
|
testing.expect_value(t, string(got2), V_CHAIN_PLAIN)
|
|
|
|
testing.expect(t, stream_data_raw(nil, nil) == nil, "raw bytes from a nil stream")
|
|
testing.expect(t, raw_data(stream_data_raw(nil, s)) == raw_data(v_chain_flate[:]),
|
|
"stream_data_raw did not hand back the stored bytes")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// stream_image_data
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@(test)
|
|
test_stream_image_data_stops_at_the_image_filter :: proc(t: ^testing.T) {
|
|
names := [?]string{"DCTDecode", "DCT", "JPXDecode", "JBIG2Decode", "CCITTFaxDecode", "CCF"}
|
|
for name in names {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
payload := [?]byte{0xff, 0xd8, 0xff, 0xe0}
|
|
s := ft_stream(doc, payload[:], Name(name), nil)
|
|
data, filter, parms, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expectf(t, err == .None, "%s: %s", name, error_string(err))
|
|
testing.expectf(t, slice.equal(data, payload[:]), "%s: payload changed", name)
|
|
testing.expectf(t, filter == filter_name(filter_kind(name), name),
|
|
"%s: reported %q", name, filter)
|
|
testing.expectf(t, parms == nil, "%s: invented parameters", name)
|
|
// Caller-owned, so never the stream's own bytes.
|
|
testing.expectf(t, raw_data(data) != raw_data(payload[:]),
|
|
"%s: aliased the raw bytes", name)
|
|
}
|
|
}
|
|
|
|
// The armour comes off; the image codec's own bytes are handed over intact.
|
|
@(test)
|
|
test_stream_image_data_applies_the_byte_filters_first :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
// ASCII85 for the four bytes ff d8 ff e0.
|
|
armoured := [?]byte{'s', '4', 'I', 'A', '0', '~', '>'}
|
|
s := ft_stream(doc, armoured[:], ft_names(doc, {"A85", "DCTDecode"}), nil)
|
|
data, filter, _, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, filter, "DCTDecode")
|
|
want := [?]byte{0xff, 0xd8, 0xff, 0xe0}
|
|
testing.expectf(t, slice.equal(data, want[:]), "got %x, want %x", data, want)
|
|
}
|
|
|
|
// The parameters that go with the image filter are handed over, since the
|
|
// image layer is the one that has to act on them.
|
|
@(test)
|
|
test_stream_image_data_returns_the_image_parameters :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
parms := ft_parms(doc, {"K", "Columns", "Rows"}, {-1, 16, 4})
|
|
payload := [?]byte{0x00, 0x01}
|
|
s := ft_stream(doc, payload[:], Name("CCF"), parms)
|
|
data, filter, got_parms, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, filter, "CCITTFaxDecode")
|
|
testing.expect(t, got_parms == parms, "the parameter dictionary did not come back")
|
|
|
|
p := filter_ccitt_params(doc, got_parms)
|
|
testing.expect_value(t, p.k, -1)
|
|
testing.expect_value(t, p.columns, 16)
|
|
testing.expect_value(t, p.rows, 4)
|
|
testing.expect_value(t, p.end_of_block, true)
|
|
}
|
|
|
|
// No image filter at all: the samples are already bytes.
|
|
@(test)
|
|
test_stream_image_data_without_an_image_filter :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], Name("FlateDecode"), nil)
|
|
data, filter, parms, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, filter, "")
|
|
testing.expect(t, parms == nil, "invented parameters")
|
|
testing.expect_value(t, string(data), V_CHAIN_PLAIN)
|
|
}
|
|
|
|
// An unfiltered image: still caller-owned memory, so the caller can free it
|
|
// without knowing how it got there.
|
|
@(test)
|
|
test_stream_image_data_unfiltered :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
payload := [?]byte{1, 2, 3, 4, 5}
|
|
s := ft_stream(doc, payload[:], nil, nil)
|
|
data, filter, _, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, filter, "")
|
|
testing.expectf(t, slice.equal(data, payload[:]), "got %v", data)
|
|
testing.expect(t, raw_data(data) != raw_data(payload[:]), "aliased the raw bytes")
|
|
}
|
|
|
|
@(test)
|
|
test_stream_image_data_broken_chain :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
s := ft_stream(doc, v_chain_flate[:], ft_names(doc, {"SuperZipDecode", "DCTDecode"}), nil)
|
|
data, filter, _, err := stream_image_data(doc, s)
|
|
defer delete(data)
|
|
testing.expect_value(t, err, Error.Unknown_Filter)
|
|
testing.expect(t, data == nil, "bytes past an unknown filter")
|
|
testing.expect_value(t, filter, "")
|
|
testing.expect_value(t, ft_repairs(doc, .Bad_Filter_Data), 1)
|
|
}
|
|
|
|
@(test)
|
|
test_stream_image_data_nil_stream :: proc(t: ^testing.T) {
|
|
data, filter, parms, err := stream_image_data(nil, nil)
|
|
testing.expect_value(t, err, Error.Invalid_Argument)
|
|
testing.expect(t, data == nil && parms == nil && filter == "", "something came back")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Malformed input
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// Every filter, over payloads of every shape, with parameters that make no
|
|
// sense: nothing here may panic, hang or leak.
|
|
@(test)
|
|
test_filters_never_crash_on_nonsense :: proc(t: ^testing.T) {
|
|
names := [?]string{
|
|
"FlateDecode", "LZWDecode", "ASCII85Decode", "ASCIIHexDecode", "RunLengthDecode",
|
|
"CCITTFaxDecode", "DCTDecode", "JPXDecode", "JBIG2Decode", "Crypt", "Nonsense", "",
|
|
}
|
|
payloads := [?][]byte{
|
|
nil,
|
|
{0x00},
|
|
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
{'~', '>'},
|
|
{'>', '>', '>'},
|
|
{0x78, 0xda},
|
|
v_chain_flate[:],
|
|
v_chain_a85[:],
|
|
v_lzw_tiff[:],
|
|
}
|
|
parm_sets := [?][2]i64{{1, 1}, {2, 0}, {12, 4}, {15, 1 << 40}, {-1, -1}, {99, 7}}
|
|
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
for name in names {
|
|
for payload in payloads {
|
|
for set in parm_sets {
|
|
parms := ft_parms(doc,
|
|
{"Predictor", "Columns", "Colors", "BitsPerComponent", "K", "Rows"},
|
|
{set[0], set[1], set[0], set[1], set[0], set[1]})
|
|
s := ft_stream(doc, payload, Name(name), parms)
|
|
data, derr := stream_data(doc, s)
|
|
testing.expectf(t, derr == .None || derr == .Invalid_Argument,
|
|
"%s: stream_data returned %s", name, error_string(derr))
|
|
_ = data
|
|
|
|
s2 := ft_stream(doc, payload, Name(name), parms)
|
|
img, _, _, _ := stream_image_data(doc, s2)
|
|
delete(img)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// A /Filter that is not a name or an array of names is not a filter.
|
|
@(test)
|
|
test_stream_data_nonsense_filter_entry :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
raw := [?]byte{'x', 'y', 'z'}
|
|
entries := [?]Object{i64(42), f64(1.5), true, String(transmute([]byte)string("Fl"))}
|
|
for entry in entries {
|
|
s := ft_stream(doc, raw[:], entry, nil)
|
|
got, err := stream_data(doc, s)
|
|
testing.expectf(t, err == .None, "%v: %s", entry, error_string(err))
|
|
testing.expectf(t, slice.equal(got, raw[:]), "%v: data changed", entry)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Allocator discipline
|
|
//
|
|
// Two rules, both invisible from the outside and both easy to break again.
|
|
// A filter chain must free its intermediates -- with no Document there is
|
|
// nobody else to. And a codec's speculation must not reach the Document arena,
|
|
// where `delete` is a silent no-op and nothing is reclaimed before `close`.
|
|
// The tests below are the only thing standing between either rule and a
|
|
// regression, so they measure rather than inspect: the tracking allocator the
|
|
// test runner installs catches the first, `document_memory_used` the second.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// "Hi" run through ASCIIHexDecode twice and three times over. Decoding applies
|
|
// the chain in the order /Filter lists it (ISO 32000-2, 7.4.1), so each pass
|
|
// peels one layer of hex off and every pass but the last leaves a buffer
|
|
// behind that only `stream_data` can free.
|
|
@(private = "file")
|
|
V_HEX_TWICE :: "34383639"
|
|
@(private = "file")
|
|
V_HEX_THRICE :: "3334333833363339"
|
|
|
|
@(test)
|
|
test_stream_data_nil_document_frees_the_intermediates :: proc(t: ^testing.T) {
|
|
cases := [?]string{V_HEX_TWICE, V_HEX_THRICE}
|
|
for raw, i in cases {
|
|
depth := i + 2
|
|
|
|
// A nil Document means no arena, so the chain runs on the context
|
|
// allocator and every buffer it produces is somebody's to free.
|
|
d := new_dict(context.allocator)
|
|
a := new_array(context.allocator)
|
|
for _ in 0 ..< depth {
|
|
array_push(a, Name("ASCIIHexDecode"))
|
|
}
|
|
dict_set(d, "Filter", a)
|
|
s := new_stream(d, transmute([]byte)raw, context.allocator)
|
|
|
|
got, err := stream_data(nil, s)
|
|
testing.expectf(t, err == .None, "%d filters: %s", depth, error_string(err))
|
|
testing.expectf(t, string(got) == "Hi", "%d filters: got %q", depth, string(got))
|
|
testing.expectf(t, s.state == .Decoded, "%d filters: state %v", depth, s.state)
|
|
|
|
// Only the last stage is reachable here; if the earlier ones were not
|
|
// freed as the chain advanced, nothing can free them now and the
|
|
// tracking allocator reports the leak.
|
|
delete(got)
|
|
delete(a.items)
|
|
free(a)
|
|
delete(d^)
|
|
free(d)
|
|
free(s)
|
|
}
|
|
}
|
|
|
|
// The same chain over a real Document: the intermediates must not merely be
|
|
// freed, they must never have been taken from the arena in the first place,
|
|
// because an arena cannot give them back.
|
|
@(test)
|
|
test_stream_data_leaves_only_the_last_stage_in_the_arena :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
// /ASCII85Decode over /FlateDecode: the armour comes off into a buffer
|
|
// that is 28 bytes of deflate, which inflates into the 20 bytes that are
|
|
// kept. Only the 20 may be charged to the arena.
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "FlateDecode"}), nil)
|
|
before := document_memory_used(doc)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(got), V_CHAIN_PLAIN)
|
|
|
|
growth := document_memory_used(doc) - before
|
|
testing.expectf(t, growth <= len(got) + FT_ARENA_SLACK,
|
|
"the arena grew by %d bytes for a %d byte stream", growth, len(got))
|
|
|
|
// Decoding twice must not decode twice, let alone allocate twice.
|
|
settled := document_memory_used(doc)
|
|
again, err2 := stream_data(doc, s)
|
|
testing.expect_value(t, err2, Error.None)
|
|
testing.expect(t, raw_data(again) == raw_data(got), "the cache was not used")
|
|
testing.expect_value(t, document_memory_used(doc), settled)
|
|
}
|
|
|
|
// An arena rounds each allocation up to its alignment, so an exact byte count
|
|
// would be a test of core:mem/virtual rather than of this file. The point is
|
|
// that growth tracks the output and not the number of buffers it took to
|
|
// produce it, and one alignment quantum of slack does not blunt that.
|
|
@(private)
|
|
FT_ARENA_SLACK :: 64
|
|
|
|
// apply_filter is where a codec's guesswork stops. Every rejected attempt and
|
|
// every superseded growth buffer belongs to the scratch allocator; exactly one
|
|
// buffer, at its final length, is charged to the caller's.
|
|
@(test)
|
|
test_apply_filter_charges_the_arena_for_one_buffer :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
before := document_memory_used(doc)
|
|
out, err := apply_filter(doc, "FlateDecode", v_chain_flate[:], nil, doc.allocator)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, string(out), V_CHAIN_PLAIN)
|
|
|
|
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))
|
|
}
|
|
|
|
// A filter that fails hands back nil or the input untouched, and either way
|
|
// must not have spent a byte of the arena on the attempt.
|
|
@(test)
|
|
test_apply_filter_failure_costs_the_arena_nothing :: proc(t: ^testing.T) {
|
|
broken := [?]byte{0x78, 0x9c, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07}
|
|
cases := [?]struct{name: string, data: []byte, want: Error}{
|
|
{"FlateDecode", broken[:], .Bad_Flate_Data},
|
|
{"DCTDecode", broken[:], .Unsupported_Image},
|
|
{"JPXDecode", broken[:], .Unsupported_Jpx},
|
|
{"NoSuchThing", broken[:], .Unknown_Filter},
|
|
}
|
|
for c in cases {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
before := document_memory_used(doc)
|
|
out, err := apply_filter(doc, c.name, c.data, nil, doc.allocator)
|
|
testing.expectf(t, err == c.want, "%s: %s", c.name, error_string(err))
|
|
testing.expectf(t, document_memory_used(doc) == before,
|
|
"%s: %d bytes stranded in the arena by a filter that returned an error",
|
|
c.name, document_memory_used(doc) - before)
|
|
// The pass-through errors hand back the caller's own bytes, which
|
|
// means the result is never something to free.
|
|
if err != .Bad_Flate_Data {
|
|
testing.expectf(t, raw_data(out) == raw_data(c.data),
|
|
"%s: the input was copied on an error path", c.name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The chain runs on scratch even when it is one filter long, so a filter that
|
|
// fails half way through a chain leaves nothing behind either.
|
|
@(test)
|
|
test_stream_data_failed_chain_costs_the_arena_nothing :: proc(t: ^testing.T) {
|
|
doc := filter_test_doc()
|
|
defer filter_test_doc_free(doc)
|
|
|
|
// A85 decodes to eight bytes that JPXDecode will refuse, and refusing is
|
|
// what ends the chain.
|
|
s := ft_stream(doc, v_chain_a85[:], ft_names(doc, {"ASCII85Decode", "JPXDecode"}), nil)
|
|
before := document_memory_used(doc)
|
|
got, err := stream_data(doc, s)
|
|
testing.expect_value(t, err, Error.None)
|
|
testing.expect_value(t, len(got), 0)
|
|
testing.expect_value(t, s.state, Stream_Decode_State.Failed)
|
|
testing.expectf(t, ft_repairs(doc, .Bad_Filter_Data) == 1, "no repair was recorded")
|
|
|
|
growth := document_memory_used(doc) - before
|
|
// The repair itself is charged to the arena, which is the only thing that
|
|
// should be.
|
|
testing.expectf(t, growth <= size_of(Repair) * 8 + FT_ARENA_SLACK,
|
|
"the arena grew by %d bytes for a chain that decoded to nothing", growth)
|
|
}
|