diff --git a/pkg/api/merge.go b/pkg/api/merge.go index 63b1aa35..36c83ed2 100644 --- a/pkg/api/merge.go +++ b/pkg/api/merge.go @@ -55,6 +55,7 @@ func MergeRaw(rsc []io.ReadSeeker, w io.Writer, conf *model.Configuration) error conf = model.NewDefaultConfiguration() } conf.Cmd = model.MERGECREATE + conf.ValidationMode = model.ValidationRelaxed conf.CreateBookmarks = false ctxDest, _, _, err := readAndValidate(rsc[0], conf, time.Now()) @@ -74,12 +75,6 @@ func MergeRaw(rsc []io.ReadSeeker, w io.Writer, conf *model.Configuration) error return err } - if conf.ValidationMode != model.ValidationNone { - if err = ValidateContext(ctxDest); err != nil { - return err - } - } - return WriteContext(ctxDest, w) } @@ -93,6 +88,7 @@ func Merge(destFile string, inFiles []string, w io.Writer, conf *model.Configura conf = model.NewDefaultConfiguration() } conf.Cmd = model.MERGECREATE + conf.ValidationMode = model.ValidationRelaxed if destFile != "" { conf.Cmd = model.MERGEAPPEND diff --git a/pkg/api/test/bookmark_test.go b/pkg/api/test/bookmark_test.go index 099adfe1..1b74b53a 100644 --- a/pkg/api/test/bookmark_test.go +++ b/pkg/api/test/bookmark_test.go @@ -28,6 +28,26 @@ import ( // Acrobat Reader "Bookmarks" = Mac Preview "Table of Contents". // Mac Preview limitations: does not render color, style, outline tree collapsed by default. +func InactiveTestAddDuplicateBookmarks(t *testing.T) { + msg := "TestAddDuplicateBookmarks" + inFile := filepath.Join(inDir, "CenterOfWhy.pdf") + outFile := filepath.Join("..", "..", "samples", "bookmarks", "bookmarkDuplicates.pdf") + + bms := []pdfcpu.Bookmark{ + {PageFrom: 2, Title: "Duplicate Name"}, + {PageFrom: 3, Title: "Duplicate Name"}, + {PageFrom: 5, Title: "Duplicate Name"}, + } + + replace := true // Replace existing bookmarks. + if err := api.AddBookmarksFile(inFile, outFile, bms, replace, nil); err != nil { + t.Fatalf("%s addBookmarks: %v\n", msg, err) + } + if err := api.ValidateFile(outFile, nil); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } +} + func TestAddSimpleBookmarks(t *testing.T) { msg := "TestAddSimpleBookmarks" inFile := filepath.Join(inDir, "CenterOfWhy.pdf") @@ -52,6 +72,9 @@ func TestAddSimpleBookmarks(t *testing.T) { if err := api.AddBookmarksFile(inFile, outFile, bms, replace, nil); err != nil { t.Fatalf("%s addBookmarks: %v\n", msg, err) } + if err := api.ValidateFile(outFile, nil); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } } func TestAddBookmarkTree2Levels(t *testing.T) { @@ -79,4 +102,7 @@ func TestAddBookmarkTree2Levels(t *testing.T) { if err := api.AddBookmarksFile(inFile, outFile, bms, false, nil); err != nil { t.Fatalf("%s addBookmarks: %v\n", msg, err) } + if err := api.ValidateFile(outFile, nil); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } } diff --git a/pkg/api/test/merge_test.go b/pkg/api/test/merge_test.go index 34ecd14a..6944151b 100644 --- a/pkg/api/test/merge_test.go +++ b/pkg/api/test/merge_test.go @@ -24,7 +24,6 @@ import ( "testing" "github.com/pdfcpu/pdfcpu/pkg/api" - "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model" ) func TestMergeCreateNew(t *testing.T) { @@ -39,10 +38,12 @@ func TestMergeCreateNew(t *testing.T) { // outFile will be overwritten. // Bookmarks for the merged document will be created/preserved per default (see config.yaml) - conf := model.NewDefaultConfiguration() - //conf.CreateBookmarks = false - if err := api.MergeCreateFile(inFiles, outFile, conf); err != nil { + if err := api.MergeCreateFile(inFiles, outFile, nil); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } + + if err := api.ValidateFile(outFile, conf); err != nil { t.Fatalf("%s: %v\n", msg, err) } } @@ -60,15 +61,24 @@ func TestMergeAppendNew(t *testing.T) { // Merge inFiles by concatenation in the order specified and write the result to outFile. // If outFile already exists its content will be preserved and serves as the beginning of the merge result. + + // Bookmarks for the merged document will be created/preserved per default (see config.yaml) + if err := api.MergeAppendFile(inFiles, outFile, nil); err != nil { t.Fatalf("%s: %v\n", msg, err) } + if err := api.ValidateFile(outFile, conf); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } anotherFile := filepath.Join(inDir, "testRot.pdf") err := api.MergeAppendFile([]string{anotherFile}, outFile, nil) if err != nil { t.Fatalf("%s: %v\n", msg, err) } + if err := api.ValidateFile(outFile, conf); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } } func TestMergeToBufNew(t *testing.T) { @@ -90,6 +100,10 @@ func TestMergeToBufNew(t *testing.T) { if err := os.WriteFile(outFile, buf.Bytes(), 0644); err != nil { t.Fatalf("%s: write: %v\n", msg, err) } + + if err := api.ValidateFile(outFile, conf); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } } func TestMergeRaw(t *testing.T) { @@ -124,4 +138,8 @@ func TestMergeRaw(t *testing.T) { if err := os.WriteFile(outFile, buf.Bytes(), 0644); err != nil { t.Fatalf("%s: write: %v\n", msg, err) } + + if err := api.ValidateFile(outFile, conf); err != nil { + t.Fatalf("%s: %v\n", msg, err) + } } diff --git a/pkg/pdfcpu/bookmark.go b/pkg/pdfcpu/bookmark.go index 52d16ac0..f24c3cd9 100644 --- a/pkg/pdfcpu/bookmark.go +++ b/pkg/pdfcpu/bookmark.go @@ -232,7 +232,6 @@ func bmDict(ctx *model.Context, bm Bookmark, parent types.IndirectRef) (types.Di } var o types.Object = *ir - ctx.Names["Dests"].Add(ctx.XRefTable, bm.Title, o) s, err := types.EscapeUTF16String(bm.Title) if err != nil { @@ -240,11 +239,16 @@ func bmDict(ctx *model.Context, bm Bookmark, parent types.IndirectRef) (types.Di } d := types.Dict(map[string]types.Object{ - "Dest": types.StringLiteral(bm.Title), + "Dest": types.NewHexLiteral([]byte(bm.Title)), "Title": types.StringLiteral(*s), "Parent": parent}, ) + m := model.NameMap{bm.Title: []types.Dict{d}} + if err := ctx.Names["Dests"].Add(ctx.XRefTable, bm.Title, o, m, []string{"D", "Dest"}); err != nil { + return nil, err + } + if bm.Color != nil { d["C"] = types.Array{types.Float(bm.Color.R), types.Float(bm.Color.G), types.Float(bm.Color.B)} } @@ -291,7 +295,7 @@ func createOutlineItemDict(ctx *model.Context, bms []Bookmark, parent *types.Ind first = ir } - if bm.Children != nil { + if len(bm.Children) > 0 { first, last, c, visc, err := createOutlineItemDict(ctx, bm.Children, ir, &bm.PageFrom) if err != nil { @@ -339,7 +343,6 @@ func AddBookmarks(ctx *model.Context, bms []Bookmark, replace bool) error { return err } - // TODO Merge with existing outlines. if !replace { if _, ok := rootDict.Find("Outlines"); ok { return errExistingBookmarks diff --git a/pkg/pdfcpu/createAnnotations.go b/pkg/pdfcpu/createAnnotations.go index 7d41dd24..692a4ee7 100644 --- a/pkg/pdfcpu/createAnnotations.go +++ b/pkg/pdfcpu/createAnnotations.go @@ -536,7 +536,13 @@ func createFileAttachmentAnnotation(xRefTable *model.XRefTable, pageIndRef types } fn := filepath.Base(fileName) - fileSpecDict, err := xRefTable.NewFileSpecDict(fn, types.EncodeUTF16String(fn), "attached by pdfcpu", *ir) + + s, err := types.EscapeUTF16String(fn) + if err != nil { + return nil, err + } + + fileSpecDict, err := xRefTable.NewFileSpecDict(fn, *s, "attached by pdfcpu", *ir) if err != nil { return nil, err } @@ -576,7 +582,13 @@ func createFileSpecDict(xRefTable *model.XRefTable, fileName string) (types.Dict return nil, err } fn := filepath.Base(fileName) - return xRefTable.NewFileSpecDict(fn, types.EncodeUTF16String(fn), "attached by pdfcpu", *ir) + + s, err := types.EscapeUTF16String(fn) + if err != nil { + return nil, err + } + + return xRefTable.NewFileSpecDict(fn, *s, "attached by pdfcpu", *ir) } func createSoundObject(xRefTable *model.XRefTable) (*types.IndirectRef, error) { diff --git a/pkg/pdfcpu/createRenditions.go b/pkg/pdfcpu/createRenditions.go index cff7d243..f49a9d2f 100644 --- a/pkg/pdfcpu/createRenditions.go +++ b/pkg/pdfcpu/createRenditions.go @@ -31,7 +31,7 @@ func createMHBEDict() *types.Dict { "U": types.StringLiteral("vnd.adobe.swname:ADBE_Acrobat"), "L": types.NewIntegerArray(0), "H": types.NewIntegerArray(), - "OS": types.NewStringArray(), + "OS": types.NewStringLiteralArray(), }, ) @@ -59,7 +59,7 @@ func createMHBEDict() *types.Dict { }, ), "P": types.NewNameArray("1.3"), - "L": types.NewStringArray("en-US"), + "L": types.NewStringLiteralArray("en-US"), }, ) @@ -77,7 +77,7 @@ func createMediaPlayersDict() *types.Dict { "U": types.StringLiteral("vnd.adobe.swname:ADBE_Acrobat"), "L": types.NewIntegerArray(0), "H": types.NewIntegerArray(), - "OS": types.NewStringArray(), + "OS": types.NewStringLiteralArray(), }, ) @@ -163,7 +163,7 @@ func createMediaClipDataDict(xRefTable *model.XRefTable) (*types.IndirectRef, er //"CT": StringLiteral("audio/mp4"), //"CT": StringLiteral("video/mp4"), "P": mediaPermissionsDict, - "Alt": types.NewStringArray("en-US", "My vacation", "de", "Mein Urlaub", "", "My vacation"), + "Alt": types.NewStringLiteralArray("en-US", "My vacation", "de", "Mein Urlaub", "", "My vacation"), "PL": *mediaPlayersDict, "MH": mhbe, "BE": mhbe, @@ -234,7 +234,7 @@ func createFloatingWindowsParamsDict() *types.Dict { "T": types.Boolean(true), "UC": types.Boolean(true), "R": types.Integer(0), - "TT": types.NewStringArray("en-US", "Special title", "de", "Spezieller Titel", "default title"), + "TT": types.NewStringLiteralArray("en-US", "Special title", "de", "Spezieller Titel", "default title"), }, ) @@ -299,7 +299,7 @@ func createSectionMediaRendition(mediaClipDataDict *types.IndirectRef) *types.Di "S": types.Name("MCS"), // media clip section "N": types.StringLiteral("Sample movie"), "D": *mediaClipDataDict, - "Alt": types.NewStringArray("en-US", "My vacation", "de", "Mein Urlaub", "", "default vacation"), + "Alt": types.NewStringLiteralArray("en-US", "My vacation", "de", "Mein Urlaub", "", "default vacation"), "MH": *mhbe, "BE": *mhbe, }, diff --git a/pkg/pdfcpu/createTestPDF.go b/pkg/pdfcpu/createTestPDF.go index e4668331..846e41e2 100644 --- a/pkg/pdfcpu/createTestPDF.go +++ b/pkg/pdfcpu/createTestPDF.go @@ -1724,7 +1724,7 @@ func createResetButton(xRefTable *model.XRefTable, pageAnnots *types.Array) (*ty map[string]types.Object{ "Type": types.Name("Action"), "S": types.Name("ResetForm"), - "Fields": types.NewStringArray("inputField"), + "Fields": types.NewStringLiteralArray("inputField"), "Flags": types.Integer(0), }, ) @@ -1774,7 +1774,7 @@ func createSubmitButton(xRefTable *model.XRefTable, pageAnnots *types.Array) (*t "Type": types.Name("Action"), "S": types.Name("SubmitForm"), "F": urlSpec, - "Fields": types.NewStringArray("inputField"), + "Fields": types.NewStringLiteralArray("inputField"), "Flags": types.Integer(0), }, ) diff --git a/pkg/pdfcpu/form/form.go b/pkg/pdfcpu/form/form.go index 30bc7e81..a414b85b 100644 --- a/pkg/pdfcpu/form/form.go +++ b/pkg/pdfcpu/form/form.go @@ -127,7 +127,11 @@ func fullyQualifiedFieldName(xRefTable *model.XRefTable, indRef types.IndirectRe thisID := indRef.ObjectNumber.String() thisName := "" - if s := d.StringOrHexLiteralEntry("T"); s != nil { + s, err := d.StringOrHexLiteralEntry("T") + if err != nil { + return false, err + } + if s != nil { thisName = *s } @@ -933,7 +937,11 @@ func annotIndRefSameLevel(xRefTable *model.XRefTable, fields types.Array, fieldI if indRef.ObjectNumber.String() == fieldIDOrName { return &indRef, nil } - if id := d.StringOrHexLiteralEntry("T"); id != nil && *id == fieldIDOrName { + id, err := d.StringOrHexLiteralEntry("T") + if err != nil { + return nil, err + } + if id != nil && *id == fieldIDOrName { return &indRef, nil } } @@ -968,7 +976,11 @@ func annotIndRefForField(xRefTable *model.XRefTable, fields types.Array, fieldID if indRef.ObjectNumber.String() == partialName { return annotIndRefForField(xRefTable, kids, fieldIDOrName[len(partialName)+1:]) } - if id := d.StringOrHexLiteralEntry("T"); id != nil { + id, err := d.StringOrHexLiteralEntry("T") + if err != nil { + return nil, err + } + if id != nil { if *id == partialName { return annotIndRefForField(xRefTable, kids, fieldIDOrName[len(partialName)+1:]) } diff --git a/pkg/pdfcpu/merge.go b/pkg/pdfcpu/merge.go index 1211c14a..01ef330a 100644 --- a/pkg/pdfcpu/merge.go +++ b/pkg/pdfcpu/merge.go @@ -435,8 +435,8 @@ func mergeNames(ctxSrc, ctxDest *model.Context) error { for id, namesSrc := range ctxSrc.Names { if namesDest, ok := ctxDest.Names[id]; ok { - // Merge src tree into dest tree - if err := namesDest.AddTree(ctxDest.XRefTable, namesSrc); err != nil { + // Merge src tree into dest tree including collision detection. + if err := namesDest.AddTree(ctxDest.XRefTable, namesSrc, ctxSrc.NameRefs[id], []string{"D", "Dest"}); err != nil { return err } continue diff --git a/pkg/pdfcpu/model/attach.go b/pkg/pdfcpu/model/attach.go index b2285a97..64033143 100644 --- a/pkg/pdfcpu/model/attach.go +++ b/pkg/pdfcpu/model/attach.go @@ -69,17 +69,15 @@ func decodeFileSpecStreamDict(sd *types.StreamDict, id string) error { func fileSpecStreamFileName(xRefTable *XRefTable, d types.Dict) (string, error) { o, found := d.Find("UF") if found { - fileName, err := xRefTable.DereferenceStringOrHexLiteral(o, V10, nil) - return fileName, err + return xRefTable.DereferenceStringOrHexLiteral(o, V10, nil) } o, found = d.Find("F") - if !found { - return "", errors.New("") + if found { + return xRefTable.DereferenceStringOrHexLiteral(o, V10, nil) } - fileName, err := xRefTable.DereferenceStringOrHexLiteral(o, V10, nil) - return fileName, err + return "", errors.New("fileSpecStream missing \"UF\",\"F\"") } func fileSpecStreamDict(xRefTable *XRefTable, d types.Dict) (*types.StreamDict, error) { @@ -105,7 +103,7 @@ func fileSpecStreamDict(xRefTable *XRefTable, d types.Dict) (*types.StreamDict, } // NewFileSpectDictForAttachment returns a fileSpecDict for a. -func (xRefTable *XRefTable) NewFileSpecDictForAttachment(a Attachment) (*types.IndirectRef, error) { +func (xRefTable *XRefTable) NewFileSpecDictForAttachment(a Attachment) (types.Dict, error) { modTime := time.Now() if a.ModTime != nil { modTime = *a.ModTime @@ -117,18 +115,7 @@ func (xRefTable *XRefTable) NewFileSpecDictForAttachment(a Attachment) (*types.I // TODO insert (escaped) reverse solidus before solidus between file name components. - // TODO Migrate to UTF-8 for both F and UF - s, err := types.EscapeUTF16String(a.ID) - if err != nil { - return nil, err - } - - d, err := xRefTable.NewFileSpecDict(a.ID, *s, a.Desc, *sd) - if err != nil { - return nil, err - } - - return xRefTable.IndRefForNewObject(d) + return xRefTable.NewFileSpecDict(a.ID, a.ID, a.Desc, *sd) } func fileSpecStreamDictInfo(xRefTable *XRefTable, id string, o types.Object, decode bool) (*types.StreamDict, string, string, *time.Time, error) { @@ -218,13 +205,19 @@ func (ctx *Context) AddAttachment(a Attachment, useCollection bool) error { } } - ir, err := xRefTable.NewFileSpecDictForAttachment(a) + d, err := xRefTable.NewFileSpecDictForAttachment(a) if err != nil { return err } - var o types.Object = *ir - return xRefTable.Names["EmbeddedFiles"].Add(xRefTable, types.EncodeUTF16String(a.ID), o) + ir, err := xRefTable.IndRefForNewObject(d) + if err != nil { + return err + } + + m := NameMap{a.ID: []types.Dict{d}} + + return xRefTable.Names["EmbeddedFiles"].Add(xRefTable, a.ID, *ir, m, []string{"F", "UF"}) } var errContentMatch = errors.New("name tree content match") diff --git a/pkg/pdfcpu/model/context.go b/pkg/pdfcpu/model/context.go index dcb38291..efc4a4f4 100644 --- a/pkg/pdfcpu/model/context.go +++ b/pkg/pdfcpu/model/context.go @@ -53,7 +53,7 @@ func NewContext(rs io.ReadSeeker, conf *Configuration) (*Context, error) { ctx := &Context{ conf, - newXRefTable(conf.ValidationMode, conf.ValidateLinks), + newXRefTable(conf), rdCtx, newOptimizationContext(), NewWriteContext(conf.Eol), diff --git a/pkg/pdfcpu/model/nameTree.go b/pkg/pdfcpu/model/nameTree.go index 96ef7402..6b0c62ce 100644 --- a/pkg/pdfcpu/model/nameTree.go +++ b/pkg/pdfcpu/model/nameTree.go @@ -22,8 +22,11 @@ import ( "github.com/pdfcpu/pdfcpu/pkg/log" "github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types" + "github.com/pkg/errors" ) +var errNameTreeDuplicateKey = errors.New("pdfcpu: name: duplicate key") + const maxEntries = 3 // Node is an opinionated implementation of the PDF name tree. @@ -122,8 +125,70 @@ func (n *Node) AppendToNames(k string, v types.Object) { } } +type NameMap map[string][]types.Dict + +func (m NameMap) Add(k string, d types.Dict) { + dd, ok := m[k] + if !ok { + m[k] = []types.Dict{d} + return + } + m[k] = append(dd, d) +} + +func (n *Node) insertIntoLeaf(k string, v types.Object, m NameMap) error { + log.Debug.Printf("Insert k:%s in the middle\n", k) + for i, e := range n.Names { + if keyLess(e.k, k) { + continue + } + if e.k == k { + return errNameTreeDuplicateKey + } + // Insert entry(k,v) at i + n.Names = append(n.Names, entry{}) + copy(n.Names[i+1:], n.Names[i:]) + n.Names[i] = entry{k, v} + return nil + } + log.Debug.Printf("Insert k:%s at end\n", k) + n.Kmax = k + n.Names = append(n.Names, entry{k, v}) + return nil +} + +func updateNameRef(d types.Dict, keys []string, nameOld, nameNew string) error { + for _, k := range keys { + s, err := d.StringOrHexLiteralEntry(k) + if err != nil { + return err + } + if s != nil { + if *s != nameOld { + return errors.Errorf("invalid Name ref detected for: %s", nameOld) + } + d[k] = types.NewHexLiteral([]byte(nameNew)) + } + } + return nil +} + +func updateNameRefDicts(dd []types.Dict, nameRefDictKeys []string, nameOld, nameNew string) error { + // eg. + // "Dests": "D", "Dest" []string{"D", "Dest"} + // "EmbeddedFiles": F", "UF" []string{"F", "UF"} + + for _, d := range dd { + if err := updateNameRef(d, nameRefDictKeys, nameOld, nameNew); err != nil { + return err + } + } + + return nil +} + // HandleLeaf processes a leaf node. -func (n *Node) HandleLeaf(xRefTable *XRefTable, k string, v types.Object) error { +func (n *Node) HandleLeaf(xRefTable *XRefTable, k string, v types.Object, m NameMap, nameRefDictKeys []string) error { // A leaf node contains up to maxEntries names. // Any number of entries greater than maxEntries will be delegated to kid nodes. @@ -139,46 +204,42 @@ func (n *Node) HandleLeaf(xRefTable *XRefTable, k string, v types.Object) error log.Debug.Printf("kmin=%s kmax=%s\n", n.Kmin, n.Kmax) if keyLess(k, n.Kmin) { - // Insert (k,v) at the beginning. + // Prepend (k,v). log.Debug.Printf("Insert k:%s at beginning\n", k) n.Kmin = k n.Names = append(n.Names, entry{}) copy(n.Names[1:], n.Names[0:]) n.Names[0] = entry{k, v} } else if keyLess(n.Kmax, k) { - // Insert (k,v) at the end. + // Append (k,v). log.Debug.Printf("Insert k:%s at end\n", k) n.Kmax = k n.Names = append(n.Names, entry{k, v}) } else { - // Insert (k,v) somewhere in the middle. - log.Debug.Printf("Insert k:%s in the middle\n", k) - for i, e := range n.Names { - - if keyLess(e.k, k) { - continue - } - - // Adding an already existing key updates its value. - if e.k == k { - - // Free up all objs referred to by old values. - if xRefTable != nil { - err := xRefTable.DeleteObjectGraph(n.Names[i].v) - if err != nil { - return err - } - } - - n.Names[i].v = v + // Insert (k,v) while ensuring unique k. + var err error + kOrig := k + for first := true; first || err == errNameTreeDuplicateKey; first = false { + err = n.insertIntoLeaf(k, v, m) + if err == nil { break } - - // Insert entry(k,v) at i - n.Names = append(n.Names, entry{}) - copy(n.Names[i+1:], n.Names[i:]) - n.Names[i] = entry{k, v} - break + if len(m) == 0 { + return err + } + if err != errNameTreeDuplicateKey { + return err + } + kNew := k + "\x01" + dd, ok := m[kOrig] + if !ok { + return nil + //return errors.Errorf("unreferenced Name detected for: %s", k) + } + if err := updateNameRefDicts(dd, nameRefDictKeys, k, kNew); err != nil { + return err + } + k = kNew } } @@ -208,7 +269,7 @@ func (n *Node) HandleLeaf(xRefTable *XRefTable, k string, v types.Object) error } // Add adds an entry to a name tree. -func (n *Node) Add(xRefTable *XRefTable, k string, v types.Object) error { +func (n *Node) Add(xRefTable *XRefTable, k string, v types.Object, m NameMap, nameRefDictKeys []string) error { //fmt.Printf("Add: %s %v\n", k, v) @@ -222,7 +283,7 @@ func (n *Node) Add(xRefTable *XRefTable, k string, v types.Object) error { } if n.leaf() { - return n.HandleLeaf(xRefTable, k, v) + return n.HandleLeaf(xRefTable, k, v, m, nameRefDictKeys) } if k == n.Kmin || k == n.Kmax { @@ -238,21 +299,21 @@ func (n *Node) Add(xRefTable *XRefTable, k string, v types.Object) error { // For intermediary nodes we delegate to the corresponding subtree. for _, a := range n.Kids { if keyLess(k, a.Kmin) || a.withinLimits(k) { - return a.Add(xRefTable, k, v) + return a.Add(xRefTable, k, v, m, nameRefDictKeys) } } // Insert k into last (right most) subtree. last := n.Kids[len(n.Kids)-1] - return last.Add(xRefTable, k, v) + return last.Add(xRefTable, k, v, m, nameRefDictKeys) } // AddTree adds a name tree to a name tree. -func (n *Node) AddTree(xRefTable *XRefTable, tree *Node) error { +func (n *Node) AddTree(xRefTable *XRefTable, tree *Node, m NameMap, nameRefDictKeys []string) error { if !tree.leaf() { for _, v := range tree.Kids { - if err := n.AddTree(xRefTable, v); err != nil { + if err := n.AddTree(xRefTable, v, m, nameRefDictKeys); err != nil { return err } } @@ -260,7 +321,7 @@ func (n *Node) AddTree(xRefTable *XRefTable, tree *Node) error { } for _, e := range tree.Names { - if err := n.Add(xRefTable, e.k, e.v); err != nil { + if err := n.Add(xRefTable, e.k, e.v, m, nameRefDictKeys); err != nil { return err } } @@ -403,7 +464,7 @@ func (n *Node) removeFromKids(xRefTable *XRefTable, k string) (ok bool, err erro if len(n.Kids) == 1 { - // If only one kid remains we can merge it with its parent. + // If a single kid remains we can merge it with its parent. // By doing this we get rid of a redundant intermediary node. log.Debug.Println("removeFromKids: only 1 kid") diff --git a/pkg/pdfcpu/model/nameTree_test.go b/pkg/pdfcpu/model/nameTree_test.go index 5b7e34e2..b2033157 100644 --- a/pkg/pdfcpu/model/nameTree_test.go +++ b/pkg/pdfcpu/model/nameTree_test.go @@ -27,7 +27,7 @@ func checkAddResult(t *testing.T, r *Node, exp string, root bool) { l := r.String() if l != exp { - t.Fatalf("Add b: %s != %s", l, exp) + t.Fatalf("Add: %s != %s", l, exp) } if root { @@ -75,7 +75,7 @@ func checkRemoveResult(t *testing.T, r *Node, k string, empty, ok bool, exp stri func buildNameTree(t *testing.T, r *Node) { - r.Add(nil, "b", types.StringLiteral("bv")) + r.Add(nil, "b", types.StringLiteral("bv"), nil, nil) checkAddResult(t, r, "[(b,(bv)){b,b}]", true) _, ok, _ := r.Remove(nil, "x") @@ -83,7 +83,7 @@ func buildNameTree(t *testing.T, r *Node) { t.Fatal("should not be able to Remove x") } - r.Add(nil, "f", types.StringLiteral("fv")) + r.Add(nil, "f", types.StringLiteral("fv"), nil, nil) checkAddResult(t, r, "[(b,(bv))(f,(fv)){b,f}]", true) _, ok, _ = r.Remove(nil, "c") @@ -91,7 +91,7 @@ func buildNameTree(t *testing.T, r *Node) { t.Fatal("should not be able to Remove c") } - r.Add(nil, "d", types.StringLiteral("dv")) + r.Add(nil, "d", types.StringLiteral("dv"), nil, nil) checkAddResult(t, r, "[(b,(bv))(d,(dv))(f,(fv)){b,f}]", true) _, ok = r.Value("c") @@ -99,16 +99,16 @@ func buildNameTree(t *testing.T, r *Node) { t.Fatal("should not find Value for c") } - r.Add(nil, "h", types.StringLiteral("hv")) + r.Add(nil, "h", types.StringLiteral("hv"), nil, nil) checkAddResult(t, r, "{b,h},[(b,(bv))(d,(dv)){b,d}],[(f,(fv))(h,(hv)){f,h}]", false) - r.Add(nil, "a", types.StringLiteral("av")) + r.Add(nil, "a", types.StringLiteral("av"), nil, nil) checkAddResult(t, r, "{a,h},[(a,(av))(b,(bv))(d,(dv)){a,d}],[(f,(fv))(h,(hv)){f,h}]", false) - r.Add(nil, "i", types.StringLiteral("iv")) + r.Add(nil, "i", types.StringLiteral("iv"), nil, nil) checkAddResult(t, r, "{a,i},[(a,(av))(b,(bv))(d,(dv)){a,d}],[(f,(fv))(h,(hv))(i,(iv)){f,i}]", false) - r.Add(nil, "c", types.StringLiteral("cv")) + r.Add(nil, "c", types.StringLiteral("cv"), nil, nil) checkAddResult(t, r, "{a,i},{a,d},[(a,(av))(b,(bv)){a,b}],[(c,(cv))(d,(dv)){c,d}],[(f,(fv))(h,(hv))(i,(iv)){f,i}]", false) } @@ -170,11 +170,8 @@ func destroyNameTree(t *testing.T, r *Node) { t.Fatal("should not find Value for x") } - r.Add(nil, "c", types.StringLiteral("cvv")) - l := r.String() - exp := "[(c,(cvv))(d,(dv)){c,d}]" - if l != exp { - t.Fatalf("update c: %s != %s", l, exp) + if err := r.Add(nil, "c", types.StringLiteral("cvv"), nil, nil); err == nil { + t.Fatalf("update c:should trigger DuplicateKeyException") } empty, ok, _ = r.Remove(nil, "c") @@ -190,8 +187,8 @@ func destroyNameTree(t *testing.T, r *Node) { if !empty { t.Fatal("r should be empty after removing f") } - l = r.String() - exp = "[{,}]" + l := r.String() + exp := "[{,}]" if l != exp { t.Fatalf("Remove d: %s != %s", l, exp) } diff --git a/pkg/pdfcpu/model/xreftable.go b/pkg/pdfcpu/model/xreftable.go index bf3f3255..1245cc0f 100644 --- a/pkg/pdfcpu/model/xreftable.go +++ b/pkg/pdfcpu/model/xreftable.go @@ -103,6 +103,7 @@ type XRefTable struct { Root *types.IndirectRef // Pointer to catalog (reference to root object). RootDict types.Dict // Catalog Names map[string]*Node // Cache for name trees as found in catalog. + NameRefs map[string]NameMap // Name refs for merging only Encrypt *types.IndirectRef // Encrypt dict. E *Enc EncKey []byte // Encrypt key. @@ -149,6 +150,7 @@ type XRefTable struct { // Validation CurPage int // current page during validation CurObj int // current object during validation, the last dereferenced object + Conf *Configuration // current command being executed ValidationMode int // see Configuration ValidateLinks bool // check for broken links in LinkAnnotations/URIDicts. Valid bool // true means successful validated against ISO 32000. @@ -166,19 +168,21 @@ type XRefTable struct { } // NewXRefTable creates a new XRefTable. -func newXRefTable(validationMode int, validateLinks bool) (xRefTable *XRefTable) { +func newXRefTable(conf *Configuration) (xRefTable *XRefTable) { return &XRefTable{ Table: map[int]*XRefTableEntry{}, Names: map[string]*Node{}, + NameRefs: map[string]NameMap{}, Properties: map[string]string{}, LinearizationObjs: types.IntSet{}, PageAnnots: map[int]PgAnnots{}, PageThumbs: map[int]types.IndirectRef{}, Stats: NewPDFStats(), - ValidationMode: validationMode, - ValidateLinks: validateLinks, + ValidationMode: conf.ValidationMode, + ValidateLinks: conf.ValidateLinks, URIs: map[int]map[string]string{}, UsedGIDs: map[string]map[uint16]bool{}, + Conf: conf, } } @@ -223,6 +227,15 @@ func (xRefTable *XRefTable) ValidateVersion(element string, sinceVersion Version return nil } +func (xRefTable *XRefTable) currentCommand() CommandMode { + return xRefTable.Conf.Cmd +} + +func (xRefTable *XRefTable) IsMerging() bool { + cmd := xRefTable.currentCommand() + return cmd == MERGECREATE || cmd == MERGEAPPEND +} + // EnsureVersionForWriting sets the version to the highest supported PDF Version 1.7. // This is necessary to allow validation after adding features not supported // by the original version of a document as during watermarking. @@ -626,7 +639,9 @@ func (xRefTable *XRefTable) NewFileSpecDict(f, uf, desc string, indRefStreamDict efDict.Insert("UF", indRefStreamDict) d.Insert("EF", efDict) - d.InsertString("Desc", desc) + if desc != "" { + d.InsertString("Desc", desc) + } // CI, optional, collection item dict, since V1.7 // a corresponding collection schema dict in a collection. @@ -1175,7 +1190,7 @@ func (xRefTable *XRefTable) bindNameTreeNode(name string, n *Node, root bool) er } if !root { - dict.Update("Limits", types.NewStringArray(n.Kmin, n.Kmax)) + dict.Update("Limits", types.NewHexLiteralArray(n.Kmin, n.Kmax)) } else { dict.Delete("Limits") } @@ -1183,7 +1198,7 @@ func (xRefTable *XRefTable) bindNameTreeNode(name string, n *Node, root bool) er if n.leaf() { a := types.Array{} for _, e := range n.Names { - a = append(a, types.StringLiteral(e.k)) + a = append(a, types.NewHexLiteral([]byte(e.k))) a = append(a, e.v) } dict.Update("Names", a) @@ -1301,13 +1316,12 @@ func (xRefTable *XRefTable) NamesDict() (types.Dict, error) { o, found := d.Find("Names") if !found { dict := types.NewDict() - ir, err := xRefTable.IndRefForNewObject(dict) if err != nil { return nil, err } d["Names"] = *ir - return d, nil + return dict, nil } return xRefTable.DereferenceDict(o) @@ -1739,7 +1753,7 @@ func consolidateResources(consolidateRes bool, xRefTable *XRefTable, pageDict, r // Compare required resouces (prn) with available resources (pAttrs.resources). // Remove any resource that's not required. // Return an error for any required resource missing. - // TODO Calculate and acumulate resources required by content streams of any present form or type 3 fonts. + // TODO Calculate and accumulate resources required by content streams of any present form or type 3 fonts. return consolidateResourceDict(resDict, prn, page) } @@ -2397,3 +2411,13 @@ func (xRefTable *XRefTable) HasUsedGIDs(fontName string) bool { usedGIDs, ok := xRefTable.UsedGIDs[fontName] return ok && len(usedGIDs) > 0 } + +func (xRefTable *XRefTable) NameRef(nameType string) NameMap { + nm, ok := xRefTable.NameRefs[nameType] + if !ok { + nm = NameMap{} + xRefTable.NameRefs[nameType] = nm + return nm + } + return nm +} diff --git a/pkg/pdfcpu/primitives/listBox.go b/pkg/pdfcpu/primitives/listBox.go index 7bcfc571..05bb6248 100644 --- a/pkg/pdfcpu/primitives/listBox.go +++ b/pkg/pdfcpu/primitives/listBox.go @@ -686,7 +686,7 @@ func (lb *ListBox) prepareDict(fonts model.FontMap) (types.Dict, error) { opt := types.Array{} for _, s := range lb.Options { - s1, err := types.Escape(types.EncodeUTF16String(s)) + s1, err := types.EscapeUTF16String(s) if err != nil { return nil, err } diff --git a/pkg/pdfcpu/read.go b/pkg/pdfcpu/read.go index 8c9a59ea..669f08b0 100644 --- a/pkg/pdfcpu/read.go +++ b/pkg/pdfcpu/read.go @@ -1456,7 +1456,7 @@ func nextStreamOffset(line string, streamInd int) (off int) { off = streamInd + len("stream") // Skip optional blanks. - // TODO Should be skip optional whitespace instead? + // TODO Should we skip optional whitespace instead? for ; line[off] == 0x20; off++ { } diff --git a/pkg/pdfcpu/types/array.go b/pkg/pdfcpu/types/array.go index 5ec61286..79320e42 100644 --- a/pkg/pdfcpu/types/array.go +++ b/pkg/pdfcpu/types/array.go @@ -27,8 +27,8 @@ import ( // Array represents a PDF array object. type Array []Object -// NewStringArray returns a PDFArray with StringLiteral entries. -func NewStringArray(sVars ...string) Array { +// NewStringLiteralArray returns a PDFArray with StringLiteral entries. +func NewStringLiteralArray(sVars ...string) Array { a := Array{} @@ -39,6 +39,18 @@ func NewStringArray(sVars ...string) Array { return a } +// NewHexLiteralArray returns a PDFArray with HexLiteralLiteral entries. +func NewHexLiteralArray(sVars ...string) Array { + + a := Array{} + + for _, s := range sVars { + a = append(a, NewHexLiteral([]byte(s))) + } + + return a +} + // NewNameArray returns a PDFArray with Name entries. func NewNameArray(sVars ...string) Array { diff --git a/pkg/pdfcpu/types/dict.go b/pkg/pdfcpu/types/dict.go index 9af5060c..6657d1b2 100644 --- a/pkg/pdfcpu/types/dict.go +++ b/pkg/pdfcpu/types/dict.go @@ -326,32 +326,11 @@ func (d Dict) HexLiteralEntry(key string) *HexLiteral { return nil } -func (d Dict) StringOrHexLiteralEntry(key string) *string { - - value, found := d.Find(key) - if !found { - return nil - } - - sl, ok := value.(StringLiteral) - if ok { - s, err := StringLiteralToString(sl) - if err != nil { - return nil - } - return &s +func (d Dict) StringOrHexLiteralEntry(key string) (*string, error) { + if obj, ok := d.Find(key); ok { + return StringOrHexLiteral(obj) } - - hl, ok := value.(HexLiteral) - if ok { - s, err := HexLiteralToString(hl) - if err != nil { - return nil - } - return &s - } - - return nil + return nil, nil } // Length returns a *int64 for entry with key "Length". diff --git a/pkg/pdfcpu/types/types.go b/pkg/pdfcpu/types/types.go index ed05e206..2dca87ed 100644 --- a/pkg/pdfcpu/types/types.go +++ b/pkg/pdfcpu/types/types.go @@ -513,11 +513,7 @@ func (hexliteral HexLiteral) Value() string { // Bytes returns the byte representation. func (hexliteral HexLiteral) Bytes() ([]byte, error) { - b, err := hex.DecodeString(hexliteral.Value()) - if err != nil { - return nil, err - } - return b, err + return hex.DecodeString(hexliteral.Value()) } /////////////////////////////////////////////////////////////////////////////////// diff --git a/pkg/pdfcpu/types/utf16.go b/pkg/pdfcpu/types/utf16.go index 3f289e0d..a69b51f1 100644 --- a/pkg/pdfcpu/types/utf16.go +++ b/pkg/pdfcpu/types/utf16.go @@ -47,6 +47,7 @@ func IsUTF16BE(b []byte) bool { } func decodeUTF16String(b []byte) (string, error) { + // Convert UTF-16 to UTF-8 // We only accept big endian byte order. if !IsUTF16BE(b) { log.Debug.Printf("decodeUTF16String: not UTF16BE: %s\n", hex.Dump(b)) @@ -127,34 +128,37 @@ func StringLiteralToString(sl StringLiteral) (string, error) { if err != nil { return "", err } - - s1 := string(bb) - - // Check for Big Endian UTF-16. - if IsStringUTF16BE(s1) { - return DecodeUTF16String(s1) + if IsUTF16BE(bb) { + return decodeUTF16String(bb) } - // if no acceptable UTF16 encoding found, ensure utf8 encoding. - if !utf8.ValidString(s1) { - s1 = CP1252ToUTF8(s1) + s := string(bb) + if !utf8.ValidString(s) { + s = CP1252ToUTF8(s) } - return s1, nil + return s, nil } // HexLiteralToString returns a possibly UTF16 encoded string for a hex string. func HexLiteralToString(hl HexLiteral) (string, error) { - // Get corresponding byte slice. - b, err := hex.DecodeString(hl.Value()) + bb, err := hl.Bytes() if err != nil { return "", err } - - // Check for Big Endian UTF-16. - if IsUTF16BE(b) { - return decodeUTF16String(b) + if IsUTF16BE(bb) { + return decodeUTF16String(bb) } + return string(bb), nil +} - // if no acceptable UTF16 encoding found, just return decoded hexstring. - return string(b), nil +func StringOrHexLiteral(obj Object) (*string, error) { + if sl, ok := obj.(StringLiteral); ok { + s, err := StringLiteralToString(sl) + return &s, err + } + if hl, ok := obj.(HexLiteral); ok { + s, err := HexLiteralToString(hl) + return &s, err + } + return nil, errors.New("pdfcpu: expected StringLiteral or HexLiteral") } diff --git a/pkg/pdfcpu/validate/action.go b/pkg/pdfcpu/validate/action.go index e8dead7f..276911ab 100644 --- a/pkg/pdfcpu/validate/action.go +++ b/pkg/pdfcpu/validate/action.go @@ -33,7 +33,7 @@ func validateGoToActionDict(xRefTable *model.XRefTable, d types.Dict, dictName s } // D, required, name, byte string or array - return validateDestinationEntry(xRefTable, d, dictName, "D", required, model.V10) + return validateActionDestinationEntry(xRefTable, d, dictName, "D", required, model.V10) } func validateGoToRActionDict(xRefTable *model.XRefTable, d types.Dict, dictName string) error { @@ -47,7 +47,7 @@ func validateGoToRActionDict(xRefTable *model.XRefTable, d types.Dict, dictName } // D, required, name, byte string or array - err = validateDestinationEntry(xRefTable, d, dictName, "D", REQUIRED, model.V10) + err = validateActionDestinationEntry(xRefTable, d, dictName, "D", REQUIRED, model.V10) if err != nil { return err } @@ -108,7 +108,7 @@ func validateGoToEActionDict(xRefTable *model.XRefTable, d types.Dict, dictName } // D, required, name, byte string or array - err = validateDestinationEntry(xRefTable, d, dictName, "D", REQUIRED, model.V10) + err = validateActionDestinationEntry(xRefTable, d, dictName, "D", REQUIRED, model.V10) if err != nil { return err } diff --git a/pkg/pdfcpu/validate/annotation.go b/pkg/pdfcpu/validate/annotation.go index ac337107..bd2e629f 100644 --- a/pkg/pdfcpu/validate/annotation.go +++ b/pkg/pdfcpu/validate/annotation.go @@ -250,7 +250,7 @@ func validateAnnotationDictText(xRefTable *model.XRefTable, d types.Dict, dictNa } // State, optional, text string, since V1.5 - validate := func(s string) bool { return types.MemberOf(s, []string{"None", "Unmarked"}) } + validate := func(s string) bool { return types.MemberOf(s, []string{"None", "Unmarked", "Completed"}) } state, err := validateStringEntry(xRefTable, d, dictName, "State", OPTIONAL, model.V15, validate) if err != nil { return err @@ -280,7 +280,17 @@ func validateActionOrDestination(xRefTable *model.XRefTable, d types.Dict, dictN return err } - return validateDestination(xRefTable, obj) + name, err := validateDestination(xRefTable, obj, false) + if err != nil { + return err + } + + if len(name) > 0 && xRefTable.IsMerging() { + nm := xRefTable.NameRef("Dests") + nm.Add(name, d) + } + + return nil } func validateURIActionDictEntry(xRefTable *model.XRefTable, d types.Dict, dictName, entryName string, required bool, sinceVersion model.Version) error { diff --git a/pkg/pdfcpu/validate/destination.go b/pkg/pdfcpu/validate/destination.go index 6c2b6996..0fd3f0d2 100644 --- a/pkg/pdfcpu/validate/destination.go +++ b/pkg/pdfcpu/validate/destination.go @@ -129,25 +129,28 @@ func validateDestinationDict(xRefTable *model.XRefTable, d types.Dict) error { return validateDestinationArray(xRefTable, a) } -func validateDestination(xRefTable *model.XRefTable, o types.Object) error { +func validateDestination(xRefTable *model.XRefTable, o types.Object, forAction bool) (string, error) { o, err := xRefTable.Dereference(o) if err != nil || o == nil { - return err + return "", err } switch o := o.(type) { case types.Name: - // no further processing. + return o.Value(), nil case types.StringLiteral: - // no further processing. + return types.StringLiteralToString(o) case types.HexLiteral: - // no further processing. + return types.HexLiteralToString(o) case types.Dict: + if forAction { + return "", errors.New("pdfcpu: validateDestination: unsupported PDF object") + } err = validateDestinationDict(xRefTable, o) case types.Array: @@ -158,10 +161,10 @@ func validateDestination(xRefTable *model.XRefTable, o types.Object) error { } - return err + return "", err } -func validateDestinationEntry(xRefTable *model.XRefTable, d types.Dict, dictName string, entryName string, required bool, sinceVersion model.Version) error { +func validateActionDestinationEntry(xRefTable *model.XRefTable, d types.Dict, dictName string, entryName string, required bool, sinceVersion model.Version) error { // see 12.3.2 @@ -170,5 +173,15 @@ func validateDestinationEntry(xRefTable *model.XRefTable, d types.Dict, dictName return err } - return validateDestination(xRefTable, o) + name, err := validateDestination(xRefTable, o, true) + if err != nil { + return err + } + + if len(name) > 0 && xRefTable.IsMerging() { + nm := xRefTable.NameRef("Dests") + nm.Add(name, d) + } + + return nil } diff --git a/pkg/pdfcpu/validate/info.go b/pkg/pdfcpu/validate/info.go index 1180dcb1..07770afe 100644 --- a/pkg/pdfcpu/validate/info.go +++ b/pkg/pdfcpu/validate/info.go @@ -122,6 +122,9 @@ func validateDocInfoDictEntry(xRefTable *model.XRefTable, k string, v types.Obje // date, optional case "CreationDate": xRefTable.CreationDate, err = validateInfoDictDate(xRefTable, v) + if err != nil && xRefTable.ValidationMode == model.ValidationRelaxed { + err = nil + } // date, required if PieceInfo is present in document catalog. case "ModDate": diff --git a/pkg/pdfcpu/validate/nameTree.go b/pkg/pdfcpu/validate/nameTree.go index cd19dbff..dc622dbf 100644 --- a/pkg/pdfcpu/validate/nameTree.go +++ b/pkg/pdfcpu/validate/nameTree.go @@ -30,7 +30,8 @@ func validateDestsNameTreeValue(xRefTable *model.XRefTable, o types.Object, sinc return err } - return validateDestination(xRefTable, o) + _, err = validateDestination(xRefTable, o, false) + return err } func validateAPNameTreeValue(xRefTable *model.XRefTable, o types.Object, sinceVersion model.Version) error { @@ -549,7 +550,6 @@ func validateIDTreeValue(xRefTable *model.XRefTable, o types.Object, sinceVersio func validateNameTreeValue(name string, xRefTable *model.XRefTable, o types.Object) (err error) { - // TODO // The values associated with the keys may be objects of any type. // Stream objects shall be specified by indirect object references. // Dictionary, array, and string objects should be specified by indirect object references. @@ -579,7 +579,7 @@ func validateNameTreeValue(name string, xRefTable *model.XRefTable, o types.Obje return errors.Errorf("pdfcpu: validateNameTreeDictNamesEntry: unknown dict name: %s", name) } -func validateNameTreeDictNamesEntry(xRefTable *model.XRefTable, d types.Dict, name string, node *model.Node) (firstKey, lastKey string, err error) { +func validateNameTreeDictNamesEntry(xRefTable *model.XRefTable, d types.Dict, name string, node *model.Node) (string, string, error) { //fmt.Printf("validateNameTreeDictNamesEntry begin %s\n", d) @@ -602,28 +602,26 @@ func validateNameTreeDictNamesEntry(xRefTable *model.XRefTable, d types.Dict, na return "", "", errors.Errorf("pdfcpu: validateNameTreeDictNamesEntry: Names array entry length needs to be even, length=%d\n", len(a)) } - var key string + var key, firstKey, lastKey string + for i := 0; i < len(a); i++ { o := a[i] if i%2 == 0 { + // TODO Do we really need to process indRefs here? o, err = xRefTable.Dereference(o) if err != nil { return "", "", err } - s, ok := o.(types.StringLiteral) - if !ok { - s, ok := o.(types.HexLiteral) - if !ok { - return "", "", errors.Errorf("pdfcpu: validateNameTreeDictNamesEntry: corrupt key <%v>\n", o) - } - key = s.Value() - } else { - key = s.Value() + k, err := types.StringOrHexLiteral(o) + if err != nil { + return "", "", err } + key = *k + if firstKey == "" { firstKey = key } @@ -658,31 +656,22 @@ func validateNameTreeDictLimitsEntry(xRefTable *model.XRefTable, d types.Dict, f if err != nil { return err } - fk, ok := o.(types.StringLiteral) - if !ok { - fk, ok1 := o.(types.HexLiteral) - if !ok1 { - return errors.Errorf("pdfcpu: validateNameTreeDictLimitsEntry: expected type \"string\", but encountered %T", o) - } - fkv = fk.Value() - } else { - fkv = fk.Value() - } - o, err = xRefTable.Dereference(a[1]) + s, err := types.StringOrHexLiteral(o) if err != nil { return err } - lk, ok := o.(types.StringLiteral) - if !ok { - lk, ok1 := a[1].(types.HexLiteral) - if !ok1 { - return errors.Errorf("pdfcpu: validateNameTreeDictLimitsEntry: expected type \"string\", but encountered %T", o) - } - lkv = lk.Value() - } else { - lkv = lk.Value() + fkv = *s + + if o, err = xRefTable.Dereference(a[1]); err != nil { + return err + } + + s, err = types.StringOrHexLiteral(o) + if err != nil { + return err } + lkv = *s if firstKey < fkv || lastKey > lkv { return errors.Errorf("pdfcpu: validateNameTreeDictLimitsEntry: leaf node corrupted (firstKey: %s vs %s) (lastKey: %s vs %s)\n", firstKey, fkv, lastKey, lkv) diff --git a/pkg/pdfcpu/validate/outlineTree.go b/pkg/pdfcpu/validate/outlineTree.go index 2299458f..0db74652 100644 --- a/pkg/pdfcpu/validate/outlineTree.go +++ b/pkg/pdfcpu/validate/outlineTree.go @@ -227,10 +227,7 @@ func validateInvisibleOutlineCount(xRefTable *model.XRefTable, total, visible in if xRefTable.ValidationMode == model.ValidationStrict && *count == 0 { return errors.New("pdfcpu: validateOutlines: corrupted, root \"Count\" shall be omitted if there are no open outline items") } - if xRefTable.ValidationMode == model.ValidationStrict && *count != total { - return errors.Errorf("pdfcpu: validateOutlines: corrupted, root \"Count\" = %d, expected to be %d", *count, total) - } - if xRefTable.ValidationMode == model.ValidationRelaxed && *count != total && *count != -total { + if xRefTable.ValidationMode == model.ValidationStrict && *count != total && *count != -total { return errors.Errorf("pdfcpu: validateOutlines: corrupted, root \"Count\" = %d, expected to be %d", *count, total) } } @@ -255,7 +252,7 @@ func validateOutlines(xRefTable *model.XRefTable, rootDict types.Dict, required // => 12.3.3 Document Outline - ir, err := validateIndRefEntry(xRefTable, rootDict, "rootDict", "Outlines", OPTIONAL, sinceVersion) + ir, err := validateIndRefEntry(xRefTable, rootDict, "rootDict", "Outlines", required, sinceVersion) if err != nil || ir == nil { return err } diff --git a/pkg/pdfcpu/validate/structTree.go b/pkg/pdfcpu/validate/structTree.go index 5f50c8b7..febd3c8b 100644 --- a/pkg/pdfcpu/validate/structTree.go +++ b/pkg/pdfcpu/validate/structTree.go @@ -60,19 +60,6 @@ func validateMarkedContentReferenceDict(xRefTable *model.XRefTable, d types.Dict err = errors.Errorf("pdfcpu: validateMarkedContentReferenceDict: missing entry \"MCID\".") } - // if o, found := d.Find("MCID"); !found { - // // TODO FIX! - // } else { - // o, err := xRefTable.Dereference(o) - // if err != nil { - // return err - // } - - // if o == nil { - // return errors.Errorf("validateMarkedContentReferenceDict: missing entry \"MCID\".") - // } - // } - return err } diff --git a/pkg/pdfcpu/validate/xReftable.go b/pkg/pdfcpu/validate/xReftable.go index 3cec3f32..c9890a02 100644 --- a/pkg/pdfcpu/validate/xReftable.go +++ b/pkg/pdfcpu/validate/xReftable.go @@ -173,8 +173,7 @@ func validateNamedDestinations(xRefTable *model.XRefTable, rootDict types.Dict, } for _, o := range d { - err = validateDestination(xRefTable, o) - if err != nil { + if _, err = validateDestination(xRefTable, o, false); err != nil { return err } }