diff --git a/object_test.go b/object_test.go new file mode 100644 index 0000000..43fbd5b --- /dev/null +++ b/object_test.go @@ -0,0 +1,123 @@ +package pdfdisassembler + +import ( + "bytes" + "testing" +) + +func TestDictAccessors(t *testing.T) { + data := buildDictPDF(t, []string{ + "<< /Type /Catalog /Pages 2 0 R /IntVal 42 /NegInt -7 /BoolVal true " + + "/StrVal (hi) /NameVal /Foo /ArrVal [ 1 2 3 ] /DictRef 3 0 R >>", + "<< /Type /Pages /Kids [] /Count 0 >>", + "<< /Inner (deep) >>", + }) + r, err := Open(bytes.NewReader(data)) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer r.Close() + cat, err := r.Catalog() + if err != nil { + t.Fatalf("Catalog: %v", err) + } + + if v, ok := cat.Int("IntVal"); !ok || v != 42 { + t.Errorf("Int(IntVal) = %d, %v", v, ok) + } + if v, ok := cat.Int("NegInt"); !ok || v != -7 { + t.Errorf("Int(NegInt) = %d, %v", v, ok) + } + if v, ok := cat.Bool("BoolVal"); !ok || !v { + t.Errorf("Bool(BoolVal) = %v, %v", v, ok) + } + if v, ok := cat.String("StrVal"); !ok || v != "hi" { + t.Errorf("String(StrVal) = %q, %v", v, ok) + } + if v, ok := cat.Bytes("StrVal"); !ok || string(v) != "hi" { + t.Errorf("Bytes(StrVal) = %q, %v", v, ok) + } + if v, ok := cat.Name("NameVal"); !ok || v != "Foo" { + t.Errorf("Name(NameVal) = %q, %v", v, ok) + } + if v, ok := cat.Array("ArrVal"); !ok || len(v) != 3 { + t.Errorf("Array(ArrVal) len = %d, %v", len(v), ok) + } + + // Dict() follows the indirect reference to object 3. + inner, ok := cat.Dict("DictRef") + if !ok { + t.Fatal("Dict(DictRef) not resolved") + } + if s, ok := inner.String("Inner"); !ok || s != "deep" { + t.Errorf("resolved inner String(Inner) = %q, %v", s, ok) + } + + if !cat.Has("IntVal") || cat.Has("Missing") { + t.Error("Has wrong") + } + if cat.Len() < 8 { + t.Errorf("Len = %d, want >= 8", cat.Len()) + } + seen := map[string]bool{} + for _, k := range cat.Keys() { + seen[k] = true + } + for k := range cat.Iter() { + if !seen[k] { + t.Errorf("Iter yielded %q absent from Keys", k) + } + } + if !seen["IntVal"] || !seen["ArrVal"] { + t.Errorf("Keys missing entries: %v", cat.Keys()) + } + + // Type mismatches must report ok=false, not panic or coerce. + if _, ok := cat.Int("StrVal"); ok { + t.Error("Int on a string") + } + if _, ok := cat.Bool("IntVal"); ok { + t.Error("Bool on an int") + } + if _, ok := cat.Name("IntVal"); ok { + t.Error("Name on an int") + } + if _, ok := cat.Array("IntVal"); ok { + t.Error("Array on an int") + } + if _, ok := cat.Dict("IntVal"); ok { + t.Error("Dict on an int") + } + if _, ok := cat.Stream("IntVal"); ok { + t.Error("Stream on an int") + } + if _, ok := cat.String("IntVal"); ok { + t.Error("String on an int") + } + if _, ok := cat.Bytes("IntVal"); ok { + t.Error("Bytes on an int") + } + if _, ok := cat.Int("Missing"); ok { + t.Error("Int on a missing key") + } +} + +// Every accessor must be safe on a nil *Dict (the common "key absent" result). +func TestDictNilReceiver(t *testing.T) { + var d *Dict + if d.Len() != 0 { + t.Error("Len") + } + if d.Has("x") { + t.Error("Has") + } + if d.Keys() != nil { + t.Error("Keys") + } + if _, ok := d.Get("x"); ok { + t.Error("Get") + } + for range d.Iter() { + t.Error("Iter on nil yielded an entry") + } +} diff --git a/reader_test.go b/reader_test.go index b95482f..3639442 100644 --- a/reader_test.go +++ b/reader_test.go @@ -393,3 +393,71 @@ func FuzzOpen(f *testing.F) { } }) } + +func TestResolveHelpers(t *testing.T) { + data := buildDictPDF(t, []string{ + "<< /Type /Catalog /Pages 2 0 R /IntRef 3 0 R /BoolRef 4 0 R /ArrRef 5 0 R >>", + "<< /Type /Pages /Kids [] /Count 0 >>", + "42", + "true", + "[ 1 2 3 ]", + }) + r, err := Open(bytes.NewReader(data)) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer r.Close() + cat, err := r.Catalog() + if err != nil { + t.Fatalf("Catalog: %v", err) + } + intRef, _ := cat.Get("IntRef") + boolRef, _ := cat.Get("BoolRef") + arrRef, _ := cat.Get("ArrRef") + + if v, err := r.ResolveInt(intRef); err != nil || v != 42 { + t.Errorf("ResolveInt = %d, %v", v, err) + } + if v, err := r.ResolveBool(boolRef); err != nil || !v { + t.Errorf("ResolveBool = %v, %v", v, err) + } + if a, err := r.ResolveArray(arrRef); err != nil || len(a) != 3 { + t.Errorf("ResolveArray len = %d, %v", len(a), err) + } + // Type mismatches must error. + if _, err := r.ResolveInt(boolRef); err == nil { + t.Error("ResolveInt on a bool") + } + if _, err := r.ResolveBool(arrRef); err == nil { + t.Error("ResolveBool on an array") + } + if _, err := r.ResolveArray(intRef); err == nil { + t.Error("ResolveArray on an int") + } + + if r.Trailer() == nil { + t.Fatal("nil trailer") + } + if _, ok := r.Trailer().Get("Root"); !ok { + t.Error("trailer missing /Root") + } +} + +// A catalog /Version higher than the header version wins (PDF 32000-1 ยง7.5.5). +func TestVersionCatalogOverride(t *testing.T) { + data := buildDictPDF(t, []string{ + "<< /Type /Catalog /Pages 2 0 R /Version /2.0 >>", + "<< /Type /Pages /Kids [] /Count 0 >>", + }) + r, err := Open(bytes.NewReader(data)) + if err != nil { + t.Fatalf("Open: %v", err) + } + defer r.Close() + if _, err := r.Catalog(); err != nil { // Version() reads the cached catalog + t.Fatalf("Catalog: %v", err) + } + if v := r.Version(); v != "2.0" { + t.Errorf("Version = %q, want 2.0 (catalog override)", v) + } +}