Skip to content

Commit

Permalink
Combo field appearance (unidoc#370)
Browse files Browse the repository at this point in the history
* Fix combo field appearances not being shown

* Fix V object type for choice and button fields

* Refactor form fill for combo and checkbox fields

* Add fill test case for text, combo and checkbox fields

* Prevent panic when flattening forms using a nil appearance generator
  • Loading branch information
adrg authored Jun 10, 2020
1 parent 6cb58f6 commit 99ef1b8
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 23 deletions.
15 changes: 9 additions & 6 deletions annotator/field_appearance.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
43 changes: 43 additions & 0 deletions fjson/fielddata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
94 changes: 94 additions & 0 deletions fjson/testdata/mixedfields.json
Original file line number Diff line number Diff line change
@@ -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"
}
]
Binary file added fjson/testdata/mixedfields.pdf
Binary file not shown.
7 changes: 4 additions & 3 deletions model/flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
46 changes: 32 additions & 14 deletions model/form.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
}
}

0 comments on commit 99ef1b8

Please sign in to comment.