mirror of
https://github.com/unidoc/unipdf.git
synced 2025-04-30 13:48:51 +08:00
V3: Write page annots from loaded PDFs (#452)
* Add Annots when serializing page object * Handle multiple dicts with pending objects per object (writer)
This commit is contained in:
parent
dcc0723e70
commit
359d6965de
@ -306,6 +306,8 @@ type PdfAnnotationWidget struct {
|
|||||||
AA core.PdfObject
|
AA core.PdfObject
|
||||||
BS core.PdfObject
|
BS core.PdfObject
|
||||||
Parent core.PdfObject
|
Parent core.PdfObject
|
||||||
|
|
||||||
|
parent *PdfField
|
||||||
}
|
}
|
||||||
|
|
||||||
// PdfAnnotationWatermark represents Watermark annotations.
|
// PdfAnnotationWatermark represents Watermark annotations.
|
||||||
@ -1796,7 +1798,12 @@ func (widget *PdfAnnotationWidget) ToPdfObject() core.PdfObject {
|
|||||||
d.SetIfNotNil("A", widget.A)
|
d.SetIfNotNil("A", widget.A)
|
||||||
d.SetIfNotNil("AA", widget.AA)
|
d.SetIfNotNil("AA", widget.AA)
|
||||||
d.SetIfNotNil("BS", widget.BS)
|
d.SetIfNotNil("BS", widget.BS)
|
||||||
d.SetIfNotNil("Parent", widget.Parent)
|
|
||||||
|
if widget.parent != nil {
|
||||||
|
d.SetIfNotNil("Parent", widget.parent.GetContainingPdfObject())
|
||||||
|
} else if widget.Parent != nil {
|
||||||
|
d.SetIfNotNil("Parent", widget.Parent)
|
||||||
|
}
|
||||||
|
|
||||||
return container
|
return container
|
||||||
}
|
}
|
||||||
|
@ -547,16 +547,16 @@ func (f *PdfField) SetFlag(flag FieldFlag) {
|
|||||||
|
|
||||||
// newPdfFieldFromIndirectObject load a field from an indirect object containing the field dictionary.
|
// newPdfFieldFromIndirectObject load a field from an indirect object containing the field dictionary.
|
||||||
func (r *PdfReader) newPdfFieldFromIndirectObject(container *core.PdfIndirectObject, parent *PdfField) (*PdfField, error) {
|
func (r *PdfReader) newPdfFieldFromIndirectObject(container *core.PdfIndirectObject, parent *PdfField) (*PdfField, error) {
|
||||||
d, isDict := container.PdfObject.(*core.PdfObjectDictionary)
|
|
||||||
if !isDict {
|
|
||||||
return nil, fmt.Errorf("PdfField indirect object not containing a dictionary")
|
|
||||||
}
|
|
||||||
|
|
||||||
// If already processed and cached - return processed model.
|
// If already processed and cached - return processed model.
|
||||||
if field, cached := r.modelManager.GetModelFromPrimitive(container).(*PdfField); cached {
|
if field, cached := r.modelManager.GetModelFromPrimitive(container).(*PdfField); cached {
|
||||||
return field, nil
|
return field, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d, isDict := core.GetDict(container)
|
||||||
|
if !isDict {
|
||||||
|
return nil, fmt.Errorf("PdfField indirect object not containing a dictionary")
|
||||||
|
}
|
||||||
|
|
||||||
field := NewPdfField()
|
field := NewPdfField()
|
||||||
|
|
||||||
// Field type (required in terminal fields).
|
// Field type (required in terminal fields).
|
||||||
@ -661,7 +661,8 @@ func (r *PdfReader) newPdfFieldFromIndirectObject(container *core.PdfIndirectObj
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("invalid widget annotation")
|
return nil, errors.New("invalid widget annotation")
|
||||||
}
|
}
|
||||||
widget.Parent = field.GetContainingPdfObject()
|
widget.parent = field
|
||||||
|
widget.Parent = field.container
|
||||||
|
|
||||||
field.Annotations = append(field.Annotations, widget)
|
field.Annotations = append(field.Annotations, widget)
|
||||||
|
|
||||||
@ -695,6 +696,7 @@ func (r *PdfReader) newPdfFieldFromIndirectObject(container *core.PdfIndirectObj
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, ErrTypeCheck
|
return nil, ErrTypeCheck
|
||||||
}
|
}
|
||||||
|
wa.parent = field
|
||||||
field.Annotations = append(field.Annotations, wa)
|
field.Annotations = append(field.Annotations, wa)
|
||||||
} else {
|
} else {
|
||||||
childf, err := r.newPdfFieldFromIndirectObject(container, field)
|
childf, err := r.newPdfFieldFromIndirectObject(container, field)
|
||||||
|
@ -495,6 +495,8 @@ func (p *PdfPage) GetPageDict() *core.PdfObjectDictionary {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.Set("Annots", arr)
|
d.Set("Annots", arr)
|
||||||
|
} else if p.Annots != nil {
|
||||||
|
d.SetIfNotNil("Annots", p.Annots)
|
||||||
}
|
}
|
||||||
|
|
||||||
return d
|
return d
|
||||||
|
@ -157,7 +157,7 @@ type PdfWriter struct {
|
|||||||
// for writing.
|
// for writing.
|
||||||
// The map stores the object and the dictionary it is contained in.
|
// The map stores the object and the dictionary it is contained in.
|
||||||
// Only way so we can access the dictionary entry later.
|
// Only way so we can access the dictionary entry later.
|
||||||
pendingObjects map[core.PdfObject]*core.PdfObjectDictionary
|
pendingObjects map[core.PdfObject][]*core.PdfObjectDictionary
|
||||||
|
|
||||||
// Forms.
|
// Forms.
|
||||||
acroForm *PdfAcroForm
|
acroForm *PdfAcroForm
|
||||||
@ -179,7 +179,7 @@ func NewPdfWriter() PdfWriter {
|
|||||||
|
|
||||||
w.objectsMap = map[core.PdfObject]struct{}{}
|
w.objectsMap = map[core.PdfObject]struct{}{}
|
||||||
w.objects = []core.PdfObject{}
|
w.objects = []core.PdfObject{}
|
||||||
w.pendingObjects = map[core.PdfObject]*core.PdfObjectDictionary{}
|
w.pendingObjects = map[core.PdfObject][]*core.PdfObjectDictionary{}
|
||||||
w.traversed = map[core.PdfObject]struct{}{}
|
w.traversed = map[core.PdfObject]struct{}{}
|
||||||
|
|
||||||
// PDF Version. Can be changed if using more advanced features in PDF.
|
// PDF Version. Can be changed if using more advanced features in PDF.
|
||||||
@ -466,8 +466,8 @@ func (w *PdfWriter) addObjects(obj core.PdfObject) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasObj := w.hasObject(v); !hasObj {
|
if hasObj := w.hasObject(v); !hasObj {
|
||||||
common.Log.Debug("Parent obj is missing!! %T %p %v", v, v, v)
|
common.Log.Debug("Parent obj not added yet!! %T %p %v", v, v, v)
|
||||||
w.pendingObjects[v] = dict
|
w.pendingObjects[v] = append(w.pendingObjects[v], dict)
|
||||||
// Although it is missing at this point, it could be added later...
|
// Although it is missing at this point, it could be added later...
|
||||||
}
|
}
|
||||||
// How to handle the parent? Make sure it is present?
|
// How to handle the parent? Make sure it is present?
|
||||||
@ -889,15 +889,17 @@ func (w *PdfWriter) Write(writer io.Writer) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check pending objects prior to write.
|
// Check pending objects prior to write.
|
||||||
for pendingObj, pendingObjDict := range w.pendingObjects {
|
for pendingObj, pendingObjDicts := range w.pendingObjects {
|
||||||
if !w.hasObject(pendingObj) {
|
if !w.hasObject(pendingObj) {
|
||||||
common.Log.Debug("ERROR Pending object %+v %T (%p) never added for writing", pendingObj, pendingObj, pendingObj)
|
common.Log.Debug("WARN Pending object %+v %T (%p) never added for writing", pendingObj, pendingObj, pendingObj)
|
||||||
for _, key := range pendingObjDict.Keys() {
|
for _, pendingObjDict := range pendingObjDicts {
|
||||||
val := pendingObjDict.Get(key)
|
for _, key := range pendingObjDict.Keys() {
|
||||||
if val == pendingObj {
|
val := pendingObjDict.Get(key)
|
||||||
common.Log.Debug("Pending object found! and replaced with null")
|
if val == pendingObj {
|
||||||
pendingObjDict.Set(key, core.MakeNull())
|
common.Log.Debug("Pending object found! and replaced with null")
|
||||||
break
|
pendingObjDict.Set(key, core.MakeNull())
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
pdf/model/writer_test.go
Normal file
102
pdf/model/writer_test.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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 (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/unidoc/unidoc/pdf/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tests loading annotations from file, writing back out and reloading.
|
||||||
|
func TestReadWriteAnnotations(t *testing.T) {
|
||||||
|
f, err := os.Open(`testdata/OoPdfFormExample.pdf`)
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
reader, err := NewPdfReaderLazy(f)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
checkAnnots := func(reader *PdfReader, formExpected bool) {
|
||||||
|
// Check Acroform and fields.
|
||||||
|
if formExpected {
|
||||||
|
require.NotNil(t, reader.AcroForm)
|
||||||
|
fields := reader.AcroForm.AllFields()
|
||||||
|
require.Len(t, fields, 17)
|
||||||
|
require.Nil(t, fields[0].Parent)
|
||||||
|
} else {
|
||||||
|
require.Nil(t, reader.AcroForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check annotations.
|
||||||
|
numPages, err := reader.GetNumPages()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, numPages)
|
||||||
|
|
||||||
|
page, err := reader.GetPage(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, page.Annots)
|
||||||
|
annots, err := page.GetAnnotations()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, annots, 17)
|
||||||
|
|
||||||
|
wa, ok := annots[0].GetContext().(*PdfAnnotationWidget)
|
||||||
|
require.True(t, ok)
|
||||||
|
if formExpected {
|
||||||
|
require.NotNil(t, wa.parent)
|
||||||
|
require.NotNil(t, wa.Parent)
|
||||||
|
} else {
|
||||||
|
require.Nil(t, wa.parent)
|
||||||
|
require.True(t, core.IsNullObject(wa.Parent))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkAnnots(reader, true)
|
||||||
|
|
||||||
|
// Write out and reload. With the AcroForm in place.
|
||||||
|
{
|
||||||
|
w := NewPdfWriter()
|
||||||
|
page, err := reader.GetPage(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = w.AddPage(page)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = w.SetForms(reader.AcroForm)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = w.Write(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bufReader := bytes.NewReader(buf.Bytes())
|
||||||
|
reader, err = NewPdfReaderLazy(bufReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
checkAnnots(reader, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write out and reload without setting the AcroForm.
|
||||||
|
{
|
||||||
|
w := NewPdfWriter()
|
||||||
|
page, err := reader.GetPage(1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = w.AddPage(page)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = w.Write(&buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bufReader := bytes.NewReader(buf.Bytes())
|
||||||
|
reader, err = NewPdfReaderLazy(bufReader)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
checkAnnots(reader, false)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user