package pdf import "core:mem/virtual" import "core:testing" // --------------------------------------------------------------------------- // Coercions // --------------------------------------------------------------------------- @(test) test_to_int_accepts_reals :: proc(t: ^testing.T) { // Files write "1.0" where an integer is required often enough that // refusing it would reject working documents. v, ok := to_int(f64(1.0)) testing.expect(t, ok, "a real coerces to an integer") testing.expect_value(t, v, i64(1)) v2, ok2 := to_int(i64(-7)) testing.expect(t, ok2, "") testing.expect_value(t, v2, i64(-7)) // Truncation, not rounding -- an index of 2.9 is element 2. v3, _ := to_int(f64(2.9)) testing.expect_value(t, v3, i64(2)) // A real too large for i64 must be refused, not wrapped. _, ok4 := to_int(f64(1e300)) testing.expect(t, !ok4, "a real beyond i64 range is not an integer") _, ok5 := to_int(f64(-1e300)) testing.expect(t, !ok5, "") _, ok6 := to_int(nil) testing.expect(t, !ok6, "null is not an integer") _, ok7 := to_int(true) testing.expect(t, !ok7, "a boolean is not an integer") } @(test) test_to_number_and_real :: proc(t: ^testing.T) { v, ok := to_number(i64(3)) testing.expect(t, ok, "an integer is a number") testing.expect_value(t, v, 3.0) v2, ok2 := to_number(f64(2.5)) testing.expect(t, ok2, "") testing.expect_value(t, v2, 2.5) _, ok3 := to_number(nil) testing.expect(t, !ok3, "") // to_real is strict: an integer object is not a real object. _, ok4 := to_real(i64(3)) testing.expect(t, !ok4, "to_real does not coerce") } @(test) test_to_name_bytes_and_ref :: proc(t: ^testing.T) { n, ok := to_name(Name("Page")) testing.expect(t, ok, "") testing.expect_value(t, n, "Page") b, ok2 := to_bytes(String(transmute([]byte)string("hi"))) testing.expect(t, ok2, "") testing.expect_value(t, string(b), "hi") r, ok3 := to_ref(Ref{12, 0}) testing.expect(t, ok3, "") testing.expect_value(t, r, Ref{12, 0}) testing.expect(t, name_is(Name("Catalog"), "Catalog"), "") testing.expect(t, !name_is(Name("Catalog"), "Page"), "") testing.expect(t, !name_is(String(transmute([]byte)string("Catalog")), "Catalog"), "a string is not a name, even with the same bytes") testing.expect(t, !name_is(nil, ""), "null is not the empty name") } @(test) test_object_kind :: proc(t: ^testing.T) { testing.expect_value(t, object_kind(nil), "null") testing.expect_value(t, object_kind(true), "boolean") testing.expect_value(t, object_kind(i64(1)), "integer") testing.expect_value(t, object_kind(f64(1)), "real") testing.expect_value(t, object_kind(Name("x")), "name") testing.expect_value(t, object_kind(Ref{1, 0}), "reference") testing.expect(t, is_null(nil), "") testing.expect(t, !is_null(i64(0)), "the integer zero is not null") } // A stream where a dictionary is wanted hands back the stream's dictionary: // /Resources, /Group and annotation appearances are all reached through entries // that are sometimes one and sometimes the other. @(test) test_to_dict_accepts_a_stream :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) d := new_dict(a) dict_set(d, "Type", Name("XObject")) s := new_stream(d, nil, a) got, ok := to_dict(s) testing.expect(t, ok, "a stream is usable as a dictionary") testing.expect_value(t, got, d) _, ok2 := to_stream(d) testing.expect(t, !ok2, "but a dictionary is not a stream") // A stream with no dictionary must not report success with a nil result. bare := new_stream(nil, nil, a) _, ok3 := to_dict(bare) testing.expect(t, !ok3, "a stream without a dictionary is not a dictionary") } // --------------------------------------------------------------------------- // Dictionary access // --------------------------------------------------------------------------- @(test) test_dict_accessors_and_defaults :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) d := new_dict(a) dict_set(d, "Count", i64(5)) dict_set(d, "Size", f64(2.5)) dict_set(d, "Type", Name("Pages")) dict_set(d, "Flag", true) dict_set(d, "Ref", Ref{9, 0}) testing.expect_value(t, dict_int(nil, d, "Count"), i64(5)) testing.expect_value(t, dict_num(nil, d, "Size"), 2.5) testing.expect_value(t, dict_num(nil, d, "Count"), 5.0) testing.expect_value(t, dict_name(nil, d, "Type"), "Pages") testing.expect_value(t, dict_type(nil, d), "Pages") testing.expect(t, dict_bool(nil, d, "Flag"), "") // A missing key takes the default, and so does a key of the wrong type -- // a viewer substitutes rather than failing. testing.expect_value(t, dict_int(nil, d, "Absent", 42), i64(42)) testing.expect_value(t, dict_int(nil, d, "Type", 42), i64(42)) testing.expect_value(t, dict_name(nil, d, "Count", "fallback"), "fallback") testing.expect_value(t, dict_bool(nil, d, "Absent", true), true) testing.expect(t, dict_has(d, "Count"), "") testing.expect(t, !dict_has(d, "Nope"), "") testing.expect(t, !dict_has(nil, "Count"), "a nil dictionary has nothing") // dict_ref must not resolve: /Parent is followed for identity, and // resolving it is exactly what makes a cyclic walk. r, ok := dict_ref(d, "Ref") testing.expect(t, ok, "") testing.expect_value(t, r, Ref{9, 0}) _, ok2 := dict_ref(d, "Count") testing.expect(t, !ok2, "a direct value is not a reference") // Every accessor must survive a nil dictionary. testing.expect_value(t, dict_int(nil, nil, "Count", 1), i64(1)) testing.expect(t, dict_get(nil, nil, "Count") == nil, "") testing.expect(t, dict_dict(nil, nil, "x") == nil, "") testing.expect(t, dict_array(nil, nil, "x") == nil, "") testing.expect(t, dict_stream(nil, nil, "x") == nil, "") dict_set(nil, "x", i64(1)) // must not crash } // Inline images abbreviate their keys, and both spellings are legal. @(test) test_dict_get_alt :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) d := new_dict(a) dict_set(d, "W", i64(16)) v, ok := to_int(dict_get_alt(nil, d, "Width", "W")) testing.expect(t, ok, "the abbreviation is found when the full name is absent") testing.expect_value(t, v, i64(16)) dict_set(d, "Width", i64(32)) v2, _ := to_int(dict_get_alt(nil, d, "Width", "W")) testing.expect_value(t, v2, i64(32), ) } // --------------------------------------------------------------------------- // Arrays // --------------------------------------------------------------------------- @(test) test_array_accessors :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) arr := new_array(a) array_push(arr, i64(10)) array_push(arr, f64(2.5)) array_push(arr, Name("Foo")) testing.expect_value(t, array_len(arr), 3) testing.expect_value(t, array_int(nil, arr, 0), i64(10)) testing.expect_value(t, array_num(nil, arr, 1), 2.5) testing.expect_value(t, array_name(nil, arr, 2), "Foo") // Out of range in either direction reads as the default, never a panic. testing.expect_value(t, array_int(nil, arr, 99, -1), i64(-1)) testing.expect_value(t, array_int(nil, arr, -1, -1), i64(-1)) testing.expect(t, array_get_raw(arr, 99) == nil, "") testing.expect_value(t, array_len(nil), 0) testing.expect(t, array_get_raw(nil, 0) == nil, "") array_push(nil, i64(1)) // must not crash } // A stray null in a /Widths or /Decode array reads as 0, which is what a // viewer substitutes -- dropping the entry would shift every later index. @(test) test_array_numbers :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) arr := new_array(a) array_push(arr, i64(1)) array_push(arr, nil) array_push(arr, f64(3.5)) array_push(arr, Name("junk")) nums := array_numbers(nil, arr, a) testing.expect_value(t, len(nums), 4) testing.expect_value(t, nums[0], 1.0) testing.expect_value(t, nums[1], 0.0) testing.expect_value(t, nums[2], 3.5) testing.expect_value(t, nums[3], 0.0) testing.expect_value(t, len(array_numbers(nil, nil, a)), 0) } // --------------------------------------------------------------------------- // Names and numbers // --------------------------------------------------------------------------- @(test) test_decode_name_escapes :: proc(t: ^testing.T) { // The common case allocates nothing and returns the input unchanged. plain := decode_name_escapes("Plain") testing.expect_value(t, plain, "Plain") check :: proc(t: ^testing.T, input, want: string) { got := decode_name_escapes(input) testing.expect_value(t, got, want) if raw_data(got) != raw_data(input) { delete(got) } } check(t, "Adobe#20Green", "Adobe Green") check(t, "#41#42", "AB") check(t, "a#2Fb", "a/b") check(t, "lower#7ecase", "lower~case") // A '#' that is not followed by two hex digits is a broken name, not a // reason to drop bytes. check(t, "bad#zz", "bad#zz") check(t, "trailing#", "trailing#") check(t, "short#4", "short#4") check(t, "", "") } @(test) test_parse_number :: proc(t: ^testing.T) { expect_int :: proc(t: ^testing.T, s: string, want: i64) { o, ok := parse_number(s) testing.expect(t, ok, "should parse") v, is_int := o.(i64) testing.expect(t, is_int, "should be an integer object") testing.expect_value(t, v, want) } expect_real :: proc(t: ^testing.T, s: string, want: f64) { o, ok := parse_number(s) testing.expect(t, ok, "should parse") v, is_real := o.(f64) testing.expect(t, is_real, "should be a real object") testing.expect_value(t, v, want) } expect_int(t, "0", 0) expect_int(t, "42", 42) expect_int(t, "-17", -17) expect_int(t, "+6", 6) expect_real(t, "3.5", 3.5) expect_real(t, "-2.25", -2.25) // ".5" and "4." are both legal PDF and both rejected by strict parsers. expect_real(t, ".5", 0.5) expect_real(t, "4.", 4.0) expect_real(t, "-.5", -0.5) // Garbage yields 0, the value a viewer substitutes, and reports failure so // a caller that cares can tell. _, ok := parse_number("") testing.expect(t, !ok, "the empty string is not a number") _, ok2 := parse_number("--5") testing.expect(t, !ok2, "a doubled sign is not a number") o, _ := parse_number("junk") v, _ := to_number(o) testing.expect_value(t, v, 0.0) } @(test) test_new_object_constructors :: proc(t: ^testing.T) { arena: virtual.Arena _ = virtual.arena_init_growing(&arena) defer virtual.arena_destroy(&arena) a := virtual.arena_allocator(&arena) d := new_dict(a) testing.expect(t, d != nil, "") testing.expect_value(t, len(d^), 0) arr := new_array(a) testing.expect(t, arr != nil, "") testing.expect_value(t, array_len(arr), 0) payload := []byte{1, 2, 3} s := new_stream(d, payload, a) testing.expect(t, s != nil, "") testing.expect_value(t, s.state, Stream_Decode_State.Pending) testing.expect_value(t, len(s.raw), 3) testing.expect_value(t, s.dict, d) }