mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-30 13:48:51 +08:00
Basic testing for form field models. Useful test functions added.
This commit is contained in:
parent
791e1d2015
commit
11ec4d42e3
@ -43,7 +43,7 @@ type PdfParser struct {
|
|||||||
xrefs XrefTable
|
xrefs XrefTable
|
||||||
objstms ObjectStreams
|
objstms ObjectStreams
|
||||||
trailer *PdfObjectDictionary
|
trailer *PdfObjectDictionary
|
||||||
ObjCache ObjectCache // TODO: Unexport (v3).
|
ObjCache ObjectCache // TODO: Unexport (v3). - May need access from testing.
|
||||||
crypter *PdfCrypt
|
crypter *PdfCrypt
|
||||||
repairsAttempted bool // Avoid multiple attempts for repair.
|
repairsAttempted bool // Avoid multiple attempts for repair.
|
||||||
|
|
||||||
@ -579,6 +579,7 @@ func (parser *PdfParser) ParseDict() (*PdfObjectDictionary, error) {
|
|||||||
common.Log.Trace("Reading PDF Dict!")
|
common.Log.Trace("Reading PDF Dict!")
|
||||||
|
|
||||||
dict := MakeDict()
|
dict := MakeDict()
|
||||||
|
dict.parser = parser
|
||||||
|
|
||||||
// Pass the '<<'
|
// Pass the '<<'
|
||||||
c, _ := parser.reader.ReadByte()
|
c, _ := parser.reader.ReadByte()
|
||||||
@ -1321,7 +1322,9 @@ func (parser *PdfParser) ParseIndirectObject() (PdfObject, error) {
|
|||||||
common.Log.Trace("-Read indirect obj")
|
common.Log.Trace("-Read indirect obj")
|
||||||
bb, err := parser.reader.Peek(20)
|
bb, err := parser.reader.Peek(20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.Log.Debug("ERROR: Fail to read indirect obj")
|
if err != io.EOF {
|
||||||
|
common.Log.Debug("ERROR: Fail to read indirect obj")
|
||||||
|
}
|
||||||
return &indirect, err
|
return &indirect, err
|
||||||
}
|
}
|
||||||
common.Log.Trace("(indirect obj peek \"%s\"", string(bb))
|
common.Log.Trace("(indirect obj peek \"%s\"", string(bb))
|
||||||
@ -1493,6 +1496,7 @@ func (parser *PdfParser) ParseIndirectObject() (PdfObject, error) {
|
|||||||
// TODO: Unexport (v3) or move to test files, if needed by external test cases.
|
// TODO: Unexport (v3) or move to test files, if needed by external test cases.
|
||||||
func NewParserFromString(txt string) *PdfParser {
|
func NewParserFromString(txt string) *PdfParser {
|
||||||
parser := PdfParser{}
|
parser := PdfParser{}
|
||||||
|
parser.ObjCache = ObjectCache{}
|
||||||
buf := []byte(txt)
|
buf := []byte(txt)
|
||||||
|
|
||||||
bufReader := bytes.NewReader(buf)
|
bufReader := bytes.NewReader(buf)
|
||||||
|
@ -54,7 +54,7 @@ func (this *PdfAnnotation) String() string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional elements for mark-up annotations.
|
// PdfAnnotationMarkup represents additional elements for mark-up annotations.
|
||||||
type PdfAnnotationMarkup struct {
|
type PdfAnnotationMarkup struct {
|
||||||
T PdfObject
|
T PdfObject
|
||||||
Popup *PdfAnnotationPopup
|
Popup *PdfAnnotationPopup
|
||||||
@ -68,7 +68,7 @@ type PdfAnnotationMarkup struct {
|
|||||||
ExData PdfObject
|
ExData PdfObject
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subtype: Text
|
// PdfAnnotationText represents a Text annotation.
|
||||||
type PdfAnnotationText struct {
|
type PdfAnnotationText struct {
|
||||||
*PdfAnnotation
|
*PdfAnnotation
|
||||||
*PdfAnnotationMarkup
|
*PdfAnnotationMarkup
|
||||||
|
234
pdf/model/form_test.go
Normal file
234
pdf/model/form_test.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
/*
|
||||||
|
* This file is subject to the terms and conditions defined in
|
||||||
|
* file 'LICENSE.md', which is part of this source code package.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/unidoc/unidoc/common"
|
||||||
|
"github.com/unidoc/unidoc/pdf/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
func compareDictionariesDeep(d1, d2 *core.PdfObjectDictionary) bool {
|
||||||
|
if len(d1.Keys()) != len(d2.Keys()) {
|
||||||
|
common.Log.Debug("Dict entries mismatch (%d != %d)", len(d1.Keys()), len(d2.Keys()))
|
||||||
|
common.Log.Debug("Was '%s' vs '%s'", d1.DefaultWriteString(), d2.DefaultWriteString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range d1.Keys() {
|
||||||
|
if k == "Parent" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
v1 := core.TraceToDirectObject(d1.Get(k))
|
||||||
|
v2 := core.TraceToDirectObject(d2.Get(k))
|
||||||
|
|
||||||
|
if v1 == nil {
|
||||||
|
common.Log.Debug("v1 is nil")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v2 == nil {
|
||||||
|
common.Log.Debug("v2 is nil")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t1 := v1.(type) {
|
||||||
|
case *core.PdfObjectDictionary:
|
||||||
|
t2, ok := v2.(*core.PdfObjectDictionary)
|
||||||
|
if !ok {
|
||||||
|
common.Log.Debug("Type mismatch %T vs %T", v1, v2)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !compareDictionariesDeep(t1, t2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
case *core.PdfObjectArray:
|
||||||
|
t2, ok := v2.(*core.PdfObjectArray)
|
||||||
|
if !ok {
|
||||||
|
common.Log.Debug("v2 not an array")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if t1.Len() != t2.Len() {
|
||||||
|
common.Log.Debug("array length mismatch (%d != %d)", t1.Len(), t2.Len())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < t1.Len(); i++ {
|
||||||
|
v1 := core.TraceToDirectObject(t1.Get(i))
|
||||||
|
v2 := core.TraceToDirectObject(t2.Get(i))
|
||||||
|
if d1, isD1 := v1.(*core.PdfObjectDictionary); isD1 {
|
||||||
|
d2, isD2 := v2.(*core.PdfObjectDictionary)
|
||||||
|
if !isD2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !compareDictionariesDeep(d1, d2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v1.DefaultWriteString() != v2.DefaultWriteString() {
|
||||||
|
common.Log.Debug("Mismatch '%s' != '%s'", v1.DefaultWriteString(), v2.DefaultWriteString())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if v1.String() != v2.String() {
|
||||||
|
common.Log.Debug("key=%s Mismatch! '%s' != '%s'", k, v1.String(), v2.String())
|
||||||
|
common.Log.Debug("For '%T' - '%T'", v1, v2)
|
||||||
|
common.Log.Debug("For '%+v' - '%+v'", v1, v2)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test loading of a basic checkbox field with a merged-in annotation.
|
||||||
|
func TestCheckboxField1(t *testing.T) {
|
||||||
|
rawText := `
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Annot
|
||||||
|
/Subtype /Widget
|
||||||
|
/Rect [100 100 120 120]
|
||||||
|
/FT /Btn
|
||||||
|
/T (Urgent)
|
||||||
|
/V /Yes
|
||||||
|
/AS /Yes
|
||||||
|
/AP <</N <</Yes 2 0 R /Off 3 0 R>> >>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
2 0 obj
|
||||||
|
<</Type /XObject
|
||||||
|
/Subtype /Form
|
||||||
|
/BBox [0 0 20 20]
|
||||||
|
/Resources 20 0 R
|
||||||
|
/Length 44
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
q
|
||||||
|
0 0 1 rg
|
||||||
|
BT
|
||||||
|
/ZaDb 12 Tf
|
||||||
|
0 0 Td
|
||||||
|
(4) Tj
|
||||||
|
ET
|
||||||
|
Q
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
3 0 obj
|
||||||
|
<</Type /XObject
|
||||||
|
/Subtype /Form
|
||||||
|
/BBox [0 0 20 20]
|
||||||
|
/Resources 20 0 R
|
||||||
|
/Length 51
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
q
|
||||||
|
0 0 1 rg
|
||||||
|
BT
|
||||||
|
/ZaDb 12 Tf
|
||||||
|
0 0 Td
|
||||||
|
(8) Tj
|
||||||
|
ET
|
||||||
|
Q
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
|
||||||
|
4 0 obj
|
||||||
|
% Copy of obj 1 except not with merged-in annotation
|
||||||
|
<<
|
||||||
|
/FT /Btn
|
||||||
|
/T (Urgent)
|
||||||
|
/V /Yes
|
||||||
|
/Kids [5 0 R]
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
|
||||||
|
5 0 obj
|
||||||
|
<<
|
||||||
|
/Type /Annot
|
||||||
|
/Subtype /Widget
|
||||||
|
/Rect [100 100 120 120]
|
||||||
|
/AS /Yes
|
||||||
|
/AP <</N <</Yes 2 0 R /Off 3 0 R>> >>
|
||||||
|
/Parent 4 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
`
|
||||||
|
r := NewReaderForText(rawText)
|
||||||
|
|
||||||
|
err := r.ParseIndObjSeries()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed loading indirect object series: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the field from object number 1.
|
||||||
|
obj, err := r.parser.LookupByNumber(1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to parse indirect obj (%s)", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ind, ok := obj.(*core.PdfIndirectObject)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Incorrect type (%T)", obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
field, err := r.newPdfFieldFromIndirectObject(ind, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to load field (%v)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check properties of the field.
|
||||||
|
buttonf, ok := field.GetContext().(*PdfFieldButton)
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("Field content incorrect (%T)", field.GetContext())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if buttonf == nil {
|
||||||
|
t.Fatalf("buttonf is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(field.Kids) > 0 {
|
||||||
|
t.Fatalf("Field should not have kids")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(field.Annotations) != 1 {
|
||||||
|
t.Fatalf("Field should have a single annotation")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Field -> PDF object. Regenerate the field dictionary and see if matches expectations.
|
||||||
|
// Reset the dictionaries for both field and annotation to avoid re-use during re-generation of PDF object.
|
||||||
|
field.container = core.MakeIndirectObject(core.MakeDict())
|
||||||
|
field.Annotations[0].primitive = core.MakeIndirectObject(core.MakeDict())
|
||||||
|
fieldPdfObj := field.ToPdfObject()
|
||||||
|
fieldDict, ok := fieldPdfObj.(*core.PdfIndirectObject).PdfObject.(*core.PdfObjectDictionary)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Type error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the expected field dictionary (output). Slightly different than original as the input had
|
||||||
|
// a merged-in annotation. Our output does not currently merge annotations.
|
||||||
|
obj, err = r.parser.LookupByNumber(4)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expDict, ok := obj.(*core.PdfIndirectObject).PdfObject.(*core.PdfObjectDictionary)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unable to load expected dict")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !compareDictionariesDeep(expDict, fieldDict) {
|
||||||
|
t.Fatalf("Mismatch in expected and actual field dictionaries (deep)")
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
common.SetLogger(common.NewConsoleLogger(common.LogLevelTrace))
|
common.SetLogger(common.NewConsoleLogger(common.LogLevelDebug))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test for an endless recursive loop in
|
// Test for an endless recursive loop in
|
||||||
|
@ -6,19 +6,62 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/unidoc/unidoc/common"
|
||||||
. "github.com/unidoc/unidoc/pdf/core"
|
. "github.com/unidoc/unidoc/pdf/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
// NewReaderForText makes a new PdfReader for an input PDF content string. For use in testing.
|
||||||
func makeReaderForText(txt string) *bufio.Reader {
|
func NewReaderForText(txt string) *PdfReader {
|
||||||
buf := []byte(txt)
|
r := &PdfReader{}
|
||||||
bufReader := bytes.NewReader(buf)
|
r.traversed = map[PdfObject]bool{}
|
||||||
bufferedReader := bufio.NewReader(bufReader)
|
r.modelManager = NewModelManager()
|
||||||
return bufferedReader
|
|
||||||
|
// Create the parser, loads the cross reference table and trailer.
|
||||||
|
parser := NewParserFromString(txt)
|
||||||
|
r.parser = parser
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseIndObjSeries loads a series of indirect objects until it runs into an error.
|
||||||
|
// Fully loads the objects and traverses resolving references to *PdfIndirectObjects.
|
||||||
|
// For use in testing.
|
||||||
|
func (r *PdfReader) ParseIndObjSeries() error {
|
||||||
|
for {
|
||||||
|
obj, err := r.parser.ParseIndirectObject()
|
||||||
|
if err != nil {
|
||||||
|
if err != io.EOF {
|
||||||
|
common.Log.Debug("Error parsing indirect object: %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := obj.(type) {
|
||||||
|
case *PdfObjectStream:
|
||||||
|
r.parser.ObjCache[int(t.ObjectNumber)] = t
|
||||||
|
case *PdfIndirectObject:
|
||||||
|
r.parser.ObjCache[int(t.ObjectNumber)] = t
|
||||||
|
default:
|
||||||
|
common.Log.Debug("Incorrect type for ind obj: %T", obj)
|
||||||
|
return ErrTypeCheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the objects, resolving references to instances to PdfIndirectObject pointers.
|
||||||
|
for _, obj := range r.parser.ObjCache {
|
||||||
|
err := r.traverseObjectData(obj)
|
||||||
|
if err != nil {
|
||||||
|
common.Log.Debug("ERROR: Unable to traverse(%s)", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
// Test PDF date parsing from string.
|
// Test PDF date parsing from string.
|
||||||
func TestDateParse(t *testing.T) {
|
func TestDateParse(t *testing.T) {
|
||||||
@ -221,8 +264,6 @@ func TestPdfPage1(t *testing.T) {
|
|||||||
>>
|
>>
|
||||||
endobj
|
endobj
|
||||||
`
|
`
|
||||||
//parser := PdfParser{}
|
|
||||||
//parser.reader = makeReaderForText(rawText)
|
|
||||||
parser := NewParserFromString(rawText)
|
parser := NewParserFromString(rawText)
|
||||||
|
|
||||||
obj, err := parser.ParseIndirectObject()
|
obj, err := parser.ParseIndirectObject()
|
||||||
|
@ -32,6 +32,7 @@ type PdfReader struct {
|
|||||||
traversed map[PdfObject]bool
|
traversed map[PdfObject]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPdfReader returns a new PdfReader for a specified data stream (ReadSeeker interface).
|
||||||
func NewPdfReader(rs io.ReadSeeker) (*PdfReader, error) {
|
func NewPdfReader(rs io.ReadSeeker) (*PdfReader, error) {
|
||||||
pdfReader := &PdfReader{}
|
pdfReader := &PdfReader{}
|
||||||
pdfReader.traversed = map[PdfObject]bool{}
|
pdfReader.traversed = map[PdfObject]bool{}
|
||||||
@ -140,7 +141,7 @@ func (this *PdfReader) loadStructure() error {
|
|||||||
// Catalog.
|
// Catalog.
|
||||||
root, ok := trailerDict.Get("Root").(*PdfObjectReference)
|
root, ok := trailerDict.Get("Root").(*PdfObjectReference)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Invalid Root (trailer: %s)", *trailerDict)
|
return fmt.Errorf("Invalid Root (trailer: %s)", trailerDict)
|
||||||
}
|
}
|
||||||
oc, err := this.parser.LookupByReference(*root)
|
oc, err := this.parser.LookupByReference(*root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user