Skip to content

Commit 68d2f39

Browse files
committed
Fix #628, #924
1 parent 649f511 commit 68d2f39

23 files changed

+1004
-415
lines changed

pkg/api/test/annotation_test.go

Lines changed: 385 additions & 59 deletions
Large diffs are not rendered by default.

pkg/pdfcpu/annotation.go

Lines changed: 138 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"strings"
2424

2525
"github.com/pdfcpu/pdfcpu/pkg/log"
26+
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/draw"
2627
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/model"
2728
"github.com/pdfcpu/pdfcpu/pkg/pdfcpu/types"
2829
"github.com/pkg/errors"
@@ -122,12 +123,16 @@ func findAnnotByObjNr(objNr int, annots types.Array) (int, error) {
122123
return -1, nil
123124
}
124125

125-
func createAnnot(ctx *model.Context, ar model.AnnotationRenderer, pageIndRef *types.IndirectRef) (*types.IndirectRef, error) {
126-
d, err := ar.RenderDict(ctx.XRefTable, *pageIndRef)
126+
func createAnnot(ctx *model.Context, ar model.AnnotationRenderer, pageIndRef *types.IndirectRef) (*types.IndirectRef, types.Dict, error) {
127+
d, err := ar.RenderDict(ctx.XRefTable, pageIndRef)
127128
if err != nil {
128-
return nil, err
129+
return nil, nil, err
130+
}
131+
indRef, err := ctx.IndRefForNewObject(d)
132+
if err != nil {
133+
return nil, nil, err
129134
}
130-
return ctx.IndRefForNewObject(d)
135+
return indRef, d, nil
131136
}
132137

133138
// Annotation returns an annotation renderer.
@@ -147,11 +152,13 @@ func Annotation(xRefTable *model.XRefTable, d types.Dict) (model.AnnotationRende
147152
return nil, err
148153
}
149154

150-
bb, err := d.StringEntryBytes("Contents")
151-
if err != nil {
152-
return nil, err
155+
contents := ""
156+
if c, ok := d["Contents"]; ok {
157+
contents, err = xRefTable.DereferenceStringOrHexLiteral(c, model.V10, nil)
158+
if err != nil {
159+
return nil, err
160+
}
153161
}
154-
contents := string(bb)
155162

156163
var nm string
157164
s := d.StringEntry("NM") // This is what pdfcpu refers to as the annotation id.
@@ -170,7 +177,8 @@ func Annotation(xRefTable *model.XRefTable, d types.Dict) (model.AnnotationRende
170177
switch *subtype {
171178

172179
case "Text":
173-
ann = model.NewTextAnnotation(*r, contents, nm, "", f, nil, nil, "", "", true, "")
180+
popupIndRef := d.IndirectRefEntry("Popup")
181+
ann = model.NewTextAnnotation(*r, contents, nm, "", f, nil, "", popupIndRef, nil, "", "", true, "")
174182

175183
case "Link":
176184
var uri string
@@ -190,16 +198,17 @@ func Annotation(xRefTable *model.XRefTable, d types.Dict) (model.AnnotationRende
190198
}
191199
}
192200
dest := (*model.Destination)(nil) // will not collect link dest during validation.
193-
ann = model.NewLinkAnnotation(*r, nil, dest, uri, nm, f, 0, model.BSSolid, nil, false)
201+
ann = model.NewLinkAnnotation(*r, contents, nm, "", f, nil, dest, uri, nil, false, 0, model.BSSolid)
194202

195203
case "Popup":
196204
parentIndRef := d.IndirectRefEntry("Parent")
197-
ann = model.NewPopupAnnotation(*r, nil, contents, nm, f, nil, parentIndRef)
205+
ann = model.NewPopupAnnotation(*r, contents, nm, "", f, nil, parentIndRef, false)
198206

199207
// TODO handle remaining annotation types.
200208

201209
default:
202-
ann = model.NewAnnotationForRawType(*subtype, *r, contents, nil, nm, f, nil)
210+
ann = model.NewAnnotationForRawType(*subtype, *r, contents, nm, "", f, nil)
211+
203212
}
204213

205214
return ann, nil
@@ -234,6 +243,46 @@ func AnnotationsForSelectedPages(ctx *model.Context, selectedPages types.IntSet)
234243
return m
235244
}
236245

246+
func prepareHeader(horSep *[]int, maxLen *AnnotListMaxLengths) string {
247+
s := " Obj# "
248+
if maxLen.ObjNr > 4 {
249+
s += strings.Repeat(" ", maxLen.ObjNr-4)
250+
*horSep = append(*horSep, 10+maxLen.ObjNr-4)
251+
} else {
252+
*horSep = append(*horSep, 10)
253+
}
254+
255+
s += draw.VBar + " Id "
256+
if maxLen.ID > 2 {
257+
s += strings.Repeat(" ", maxLen.ID-2)
258+
*horSep = append(*horSep, 4+maxLen.ID-2)
259+
} else {
260+
*horSep = append(*horSep, 4)
261+
}
262+
263+
s += draw.VBar + " Rect "
264+
if maxLen.Rect > 4 {
265+
s += strings.Repeat(" ", maxLen.Rect-4)
266+
*horSep = append(*horSep, 6+maxLen.Rect-4)
267+
} else {
268+
*horSep = append(*horSep, 6)
269+
}
270+
271+
s += draw.VBar + " Content"
272+
if maxLen.Content > 7 {
273+
s += strings.Repeat(" ", maxLen.Rect-7)
274+
*horSep = append(*horSep, 8+maxLen.Rect-7)
275+
} else {
276+
*horSep = append(*horSep, 8)
277+
}
278+
279+
return s
280+
}
281+
282+
type AnnotListMaxLengths struct {
283+
ObjNr, ID, Rect, Content int
284+
}
285+
237286
// ListAnnotations returns a formatted list of annotations.
238287
func ListAnnotations(annots map[int]model.PgAnnots) (int, []string, error) {
239288
var (
@@ -262,37 +311,62 @@ func ListAnnotations(annots map[int]model.PgAnnots) (int, []string, error) {
262311

263312
for _, annType := range annTypes {
264313
annots := pageAnnots[model.AnnotTypes[annType]]
265-
var (
266-
maxLenRect int
267-
maxLenContent int
268-
)
269-
maxLenID := 2
314+
315+
var maxLen AnnotListMaxLengths
316+
maxLen.ID = 2
317+
maxLen.Content = len("Content")
318+
270319
var objNrs []int
271320
for objNr, ann := range annots.Map {
272321
objNrs = append(objNrs, objNr)
273-
if len(ann.RectString()) > maxLenRect {
274-
maxLenRect = len(ann.RectString())
322+
s := strconv.Itoa(objNr)
323+
if len(s) > maxLen.ObjNr {
324+
maxLen.ObjNr = len(s)
325+
}
326+
if len(ann.RectString()) > maxLen.Rect {
327+
maxLen.Rect = len(ann.RectString())
275328
}
276-
if len(ann.ID()) > maxLenID {
277-
maxLenID = len(ann.ID())
329+
if len(ann.ID()) > maxLen.ID {
330+
maxLen.ID = len(ann.ID())
278331
}
279-
if len(ann.ContentString()) > maxLenContent {
280-
maxLenContent = len(ann.ContentString())
332+
if len(ann.ContentString()) > maxLen.Content {
333+
maxLen.Content = len(ann.ContentString())
281334
}
282335
}
283336
sort.Ints(objNrs)
284337
ss = append(ss, "")
285338
ss = append(ss, fmt.Sprintf(" %s:", annType))
286-
s1 := (" obj# ")
287-
s2 := fmt.Sprintf("%%%ds", maxLenRect)
288-
s3 := fmt.Sprintf("%%%ds", maxLenID)
289-
s4 := fmt.Sprintf("%%%ds", maxLenContent)
290-
s := fmt.Sprintf(s1+s2+" "+s3+" "+s4, "rect", "id", "content")
291-
ss = append(ss, s)
292-
ss = append(ss, " "+strings.Repeat("=", len(s)-4))
339+
340+
horSep := []int{}
341+
342+
// Render header.
343+
ss = append(ss, prepareHeader(&horSep, &maxLen))
344+
345+
// Render separator.
346+
ss = append(ss, draw.HorSepLine(horSep))
347+
348+
// Render content.
293349
for _, objNr := range objNrs {
294350
ann := annots.Map[objNr]
295-
ss = append(ss, fmt.Sprintf(" %5d "+s2+" "+s3+" "+s4, objNr, ann.RectString(), ann.ID(), ann.ContentString()))
351+
352+
s := strconv.Itoa(objNr)
353+
fill1 := strings.Repeat(" ", maxLen.ObjNr-len(s))
354+
if maxLen.ObjNr < 4 {
355+
fill1 += strings.Repeat(" ", 4-maxLen.ObjNr)
356+
}
357+
358+
s = ann.ID()
359+
fill2 := strings.Repeat(" ", maxLen.ID-len(s))
360+
if maxLen.ID < 2 {
361+
fill2 += strings.Repeat(" ", 2-maxLen.ID)
362+
}
363+
364+
s = ann.RectString()
365+
fill3 := strings.Repeat(" ", maxLen.Rect-len(s))
366+
367+
ss = append(ss, fmt.Sprintf(" %s%d %s %s%s %s %s%s %s %s",
368+
fill1, objNr, draw.VBar, fill2, ann.ID(), draw.VBar, fill3, ann.RectString(), draw.VBar, ann.ContentString()))
369+
296370
j++
297371
}
298372
}
@@ -308,22 +382,22 @@ func addAnnotationToDirectObj(
308382
pageDict types.Dict,
309383
pageNr int,
310384
ar model.AnnotationRenderer,
311-
incr bool) (bool, error) {
385+
incr bool) error {
312386

313387
i, err := findAnnotByID(ctx, ar.ID(), annots)
314388
if err != nil {
315-
return false, err
389+
return err
316390
}
317391
if i >= 0 {
318-
return false, errors.Errorf("page %d: duplicate annotation with id:%s\n", pageNr, ar.ID())
392+
return errors.Errorf("page %d: duplicate annotation with id:%s\n", pageNr, ar.ID())
319393
}
320394
pageDict.Update("Annots", append(annots, *annotIndRef))
321395
if incr {
322396
// Mark page dict obj for incremental writing.
323397
ctx.Write.IncrementWithObjNr(pageDictIndRef.ObjectNumber.Value())
324398
}
325399
ctx.EnsureVersionForWriting()
326-
return true, nil
400+
return nil
327401
}
328402

329403
// AddAnnotation adds ar to pageDict.
@@ -333,18 +407,18 @@ func AddAnnotation(
333407
pageDict types.Dict,
334408
pageNr int,
335409
ar model.AnnotationRenderer,
336-
incr bool) (bool, error) {
410+
incr bool) (*types.IndirectRef, types.Dict, error) {
337411

338412
// Create xreftable entry for annotation.
339-
annotIndRef, err := createAnnot(ctx, ar, pageDictIndRef)
413+
annotIndRef, d, err := createAnnot(ctx, ar, pageDictIndRef)
340414
if err != nil {
341-
return false, err
415+
return nil, nil, err
342416
}
343417

344418
// Add annotation to xreftable page annotation cache.
345419
err = addAnnotationToCache(ctx, ar, pageNr, annotIndRef.ObjectNumber.Value())
346420
if err != nil {
347-
return false, err
421+
return nil, nil, err
348422
}
349423

350424
if incr {
@@ -360,33 +434,33 @@ func AddAnnotation(
360434
ctx.Write.IncrementWithObjNr(pageDictIndRef.ObjectNumber.Value())
361435
}
362436
ctx.EnsureVersionForWriting()
363-
return true, nil
437+
return annotIndRef, d, nil
364438
}
365439

366440
ir, ok := obj.(types.IndirectRef)
367441
if !ok {
368-
return addAnnotationToDirectObj(ctx, obj.(types.Array), annotIndRef, pageDictIndRef, pageDict, pageNr, ar, incr)
442+
return annotIndRef, d, addAnnotationToDirectObj(ctx, obj.(types.Array), annotIndRef, pageDictIndRef, pageDict, pageNr, ar, incr)
369443
}
370444

371445
// Annots array is an IndirectReference.
372446

373447
o, err := ctx.Dereference(ir)
374448
if err != nil || o == nil {
375-
return false, err
449+
return nil, nil, err
376450
}
377451

378452
annots, _ := o.(types.Array)
379453
i, err := findAnnotByID(ctx, ar.ID(), annots)
380454
if err != nil {
381-
return false, err
455+
return nil, nil, err
382456
}
383457
if i >= 0 {
384-
return false, errors.Errorf("page %d: duplicate annotation with id:%s\n", pageNr, ar.ID())
458+
return nil, nil, errors.Errorf("page %d: duplicate annotation with id:%s\n", pageNr, ar.ID())
385459
}
386460

387461
entry, ok := ctx.FindTableEntryForIndRef(&ir)
388462
if !ok {
389-
return false, errors.Errorf("page %d: can't dereference Annots indirect reference(obj#:%d)\n", pageNr, ir.ObjectNumber)
463+
return nil, nil, errors.Errorf("page %d: can't dereference Annots indirect reference(obj#:%d)\n", pageNr, ir.ObjectNumber)
390464
}
391465
entry.Object = append(annots, *annotIndRef)
392466
if incr {
@@ -395,7 +469,21 @@ func AddAnnotation(
395469
}
396470

397471
ctx.EnsureVersionForWriting()
398-
return true, nil
472+
return annotIndRef, d, nil
473+
}
474+
475+
func AddAnnotationToPage(ctx *model.Context, pageNr int, ar model.AnnotationRenderer, incr bool) (*types.IndirectRef, types.Dict, error) {
476+
pageDictIndRef, err := ctx.PageDictIndRef(pageNr)
477+
if err != nil {
478+
return nil, nil, err
479+
}
480+
481+
d, err := ctx.DereferenceDict(*pageDictIndRef)
482+
if err != nil {
483+
return nil, nil, err
484+
}
485+
486+
return AddAnnotation(ctx, pageDictIndRef, d, pageNr, ar, incr)
399487
}
400488

401489
// AddAnnotations adds ar to selected pages.
@@ -424,11 +512,11 @@ func AddAnnotations(ctx *model.Context, selectedPages types.IntSet, ar model.Ann
424512
return false, err
425513
}
426514

427-
added, err := AddAnnotation(ctx, pageDictIndRef, d, k, ar, incr)
515+
indRef, _, err := AddAnnotation(ctx, pageDictIndRef, d, k, ar, incr)
428516
if err != nil {
429517
return false, err
430518
}
431-
if added {
519+
if indRef != nil {
432520
ok = true
433521
}
434522
}
@@ -460,11 +548,11 @@ func AddAnnotationsMap(ctx *model.Context, m map[int][]model.AnnotationRenderer,
460548
}
461549

462550
for _, annot := range annots {
463-
added, err := AddAnnotation(ctx, pageDictIndRef, d, i, annot, incr)
551+
indRef, _, err := AddAnnotation(ctx, pageDictIndRef, d, i, annot, incr)
464552
if err != nil {
465553
return false, err
466554
}
467-
if added {
555+
if indRef != nil {
468556
ok = true
469557
}
470558
}

pkg/pdfcpu/color/color.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
Red = SimpleColor{1, 0, 0}
3737
Green = SimpleColor{0, 1, 0}
3838
Blue = SimpleColor{0, 0, 1}
39+
Yellow = SimpleColor{.5, .5, 0}
3940
)
4041

4142
var ErrInvalidColor = errors.New("pdfcpu: invalid color constant")

pkg/pdfcpu/create/create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ func CreatePage(
332332
}
333333

334334
for _, la := range p.LinkAnnots {
335-
d, err := la.RenderDict(xRefTable, *pageDictIndRef)
335+
d, err := la.RenderDict(xRefTable, pageDictIndRef)
336336
if err != nil {
337337
return nil, nil, &json.UnsupportedTypeError{}
338338
}
@@ -382,7 +382,7 @@ func UpdatePage(xRefTable *model.XRefTable, dIndRef types.IndirectRef, d, res ty
382382
}
383383

384384
for _, la := range p.LinkAnnots {
385-
d, err := la.RenderDict(xRefTable, dIndRef)
385+
d, err := la.RenderDict(xRefTable, &dIndRef)
386386
if err != nil {
387387
return err
388388
}

0 commit comments

Comments
 (0)