diff --git a/annotator/field_appearance.go b/annotator/field_appearance.go index 633a10567..fdacaaf66 100644 --- a/annotator/field_appearance.go +++ b/annotator/field_appearance.go @@ -821,18 +821,21 @@ func genFieldComboboxAppearance(form *model.PdfAcroForm, wa *model.PdfAnnotation } } + // See section 12.7.4.4 "Choice Fields" (pp. 444-446 PDF32000_2008). dchoiceapp := core.MakeDict() for _, optObj := range fch.Opt.Elements() { + if optArr, ok := core.GetArray(optObj); ok && optArr.Len() == 2 { + optObj = optArr.Get(1) + } + var optstr string if opt, ok := core.GetString(optObj); ok { + optstr = opt.Decoded() + } else if opt, ok := core.GetName(optObj); ok { optstr = opt.String() } else { - if opt, ok := core.GetName(optObj); ok { - optstr = opt.String() - } else { - common.Log.Debug("ERROR: Opt not a name/string - %T", optObj) - return nil, errors.New("not a name/string") - } + common.Log.Debug("ERROR: Opt not a name/string - %T", optObj) + return nil, errors.New("not a name/string") } if len(optstr) > 0 { diff --git a/fjson/fielddata_test.go b/fjson/fielddata_test.go index e8d034af8..bb7551cdb 100644 --- a/fjson/fielddata_test.go +++ b/fjson/fielddata_test.go @@ -148,6 +148,7 @@ func TestJSONExtractAndFill(t *testing.T) { fieldDataExp, err := LoadFromJSONFile("./testdata/advancedform.json") require.NoError(t, err) jsonDataExp, err := fieldDataExp.JSON() + require.NoError(t, err) // Check templates for equality. require.Equal(t, jsonDataExp, jsonData) @@ -184,6 +185,7 @@ func TestJSONExtractAndFill(t *testing.T) { fieldDataExp, err = LoadFromJSON(bytes.NewReader(jsonBytes)) require.NoError(t, err) jsonDataExp, err = fieldDataExp.JSON() + require.NoError(t, err) // Fill test PDF form fields and write to buffer. f, err := os.Open(inputFilePath) @@ -212,6 +214,47 @@ func TestJSONExtractAndFill(t *testing.T) { fieldData, err = LoadFromPDF(bytes.NewReader(buf.Bytes())) require.NoError(t, err) jsonData, err = fieldData.JSON() + require.NoError(t, err) + + // Check field data for equality. + require.Equal(t, jsonDataExp, jsonData) +} + +func TestJSONFillAndExtract(t *testing.T) { + // Read JSON fill data. + fieldDataExp, err := LoadFromJSONFile("./testdata/mixedfields.json") + require.NoError(t, err) + jsonDataExp, err := fieldDataExp.JSON() + require.NoError(t, err) + + // Fill test PDF form fields and write to buffer. + f, err := os.Open("./testdata/mixedfields.pdf") + require.NoError(t, err) + defer f.Close() + + reader, err := model.NewPdfReader(f) + require.NoError(t, err) + + err = reader.AcroForm.Fill(fieldDataExp) + require.NoError(t, err) + + var buf bytes.Buffer + writer := model.NewPdfWriter() + for i := range reader.PageList { + err := writer.AddPage(reader.PageList[i]) + require.NoError(t, err) + } + + err = writer.SetForms(reader.AcroForm) + require.NoError(t, err) + err = writer.Write(&buf) + require.NoError(t, err) + + // Load field data from buffer. + fieldData, err := LoadFromPDF(bytes.NewReader(buf.Bytes())) + require.NoError(t, err) + jsonData, err := fieldData.JSON() + require.NoError(t, err) // Check field data for equality. require.Equal(t, jsonDataExp, jsonData) diff --git a/fjson/testdata/mixedfields.json b/fjson/testdata/mixedfields.json new file mode 100644 index 000000000..3ee55d320 --- /dev/null +++ b/fjson/testdata/mixedfields.json @@ -0,0 +1,94 @@ +[ + { + "name": "Given Name Text Box", + "value": "Jane" + }, + { + "name": "Family Name Text Box", + "value": "Doe" + }, + { + "name": "House nr Text Box", + "value": "100" + }, + { + "name": "Address 2 Text Box", + "value": "Generic Avenue" + }, + { + "name": "Postcode Text Box", + "value": "11122" + }, + { + "name": "Country Combo Box", + "value": "France" + }, + { + "name": "Height Formatted Field", + "value": "175" + }, + { + "name": "City Text Box", + "value": "Paris" + }, + { + "name": "Driving License Check Box", + "value": "Yes", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Favourite Colour List Box", + "value": "Yellow" + }, + { + "name": "Language 1 Check Box", + "value": "Yes", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Language 2 Check Box", + "value": "Off", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Language 3 Check Box", + "value": "Yes", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Language 4 Check Box", + "value": "Off", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Language 5 Check Box", + "value": "Yes", + "options": [ + "Yes", + "Off" + ] + }, + { + "name": "Gender List Box", + "value": "Woman" + }, + { + "name": "Address 1 Text Box", + "value": "Generic Street" + } +] diff --git a/fjson/testdata/mixedfields.pdf b/fjson/testdata/mixedfields.pdf new file mode 100644 index 000000000..72d0d21d3 Binary files /dev/null and b/fjson/testdata/mixedfields.pdf differ diff --git a/model/flatten.go b/model/flatten.go index b0937252b..332e9ae82 100644 --- a/model/flatten.go +++ b/model/flatten.go @@ -85,9 +85,10 @@ func (r *PdfReader) FlattenFields(allannots bool, appgen FieldAppearanceGenerato var annots []*PdfAnnotation // Wrap the content streams. - err := appgen.WrapContentStream(page) - if err != nil { - return err + if appgen != nil { + if err := appgen.WrapContentStream(page); err != nil { + return err + } } annotations, err := page.GetAnnotations() diff --git a/model/form.go b/model/form.go index 39b3d9231..798802663 100644 --- a/model/form.go +++ b/model/form.go @@ -317,26 +317,36 @@ func fillFieldValue(f *PdfField, val core.PdfObject) error { default: common.Log.Debug("ERROR: Unsupported text field V type: %T (%#v)", t, t) } - case *PdfFieldButton, *PdfFieldChoice: - switch t := val.(type) { + case *PdfFieldButton: + // See section 12.7.4.2.3 "Check Boxes" (pp. 440-441 PDF32000_2008). + switch val.(type) { case *core.PdfObjectName: - if len(t.String()) == 0 { - return nil + if len(val.String()) > 0 { + f.V = val + setFieldAnnotAS(f, val) } - for _, wa := range f.Annotations { - wa.AS = val + case *core.PdfObjectString: + if len(val.String()) > 0 { + f.V = core.MakeName(val.String()) + setFieldAnnotAS(f, f.V) } + default: + common.Log.Debug("ERROR: UNEXPECTED %s -> %v", f.PartialName(), val) f.V = val - case *core.PdfObjectString: - if len(t.String()) == 0 { - return nil + } + case *PdfFieldChoice: + // See section 12.7.4.4 "Choice Fields" (pp. 444-446 PDF32000_2008). + switch val.(type) { + case *core.PdfObjectName: + if len(val.String()) > 0 { + f.V = core.MakeString(val.String()) + setFieldAnnotAS(f, val) } - common.Log.Debug("Unexpected string for button/choice field. Converting to name: '%s'", t.String()) - name := core.MakeName(t.String()) - for _, wa := range f.Annotations { - wa.AS = name + case *core.PdfObjectString: + if len(val.String()) > 0 { + f.V = val + setFieldAnnotAS(f, core.MakeName(val.String())) } - f.V = name default: common.Log.Debug("ERROR: UNEXPECTED %s -> %v", f.PartialName(), val) f.V = val @@ -347,3 +357,11 @@ func fillFieldValue(f *PdfField, val core.PdfObject) error { return nil } + +// setFieldAnnotAS sets the appearance stream of the field annotations to `val`. +func setFieldAnnotAS(f *PdfField, val core.PdfObject) { + for _, wa := range f.Annotations { + wa.AS = val + wa.ToPdfObject() + } +}