github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/page.go (about) 1 /* 2 * This file is subject to the terms and conditions defined in 3 * file 'LICENSE.md', which is part of this source code package. 4 */ 5 6 // 7 // Allow higher level manipulation of PDF files and pages. 8 // This can be continuously expanded to support more and more features. 9 // Generic handling can be done by defining elements as PdfObject which 10 // can later be replaced and fully defined. 11 // 12 13 package model 14 15 import ( 16 "errors" 17 "fmt" 18 "strings" 19 20 "github.com/unidoc/unidoc/common" 21 . "github.com/unidoc/unidoc/pdf/core" 22 ) 23 24 // PDF page object (7.7.3.3 - Table 30). 25 type PdfPage struct { 26 Parent PdfObject 27 LastModified *PdfDate 28 Resources *PdfPageResources 29 CropBox *PdfRectangle 30 MediaBox *PdfRectangle 31 BleedBox *PdfRectangle 32 TrimBox *PdfRectangle 33 ArtBox *PdfRectangle 34 BoxColorInfo PdfObject 35 Contents PdfObject 36 Rotate *int64 37 Group PdfObject 38 Thumb PdfObject 39 B PdfObject 40 Dur PdfObject 41 Trans PdfObject 42 AA PdfObject 43 Metadata PdfObject 44 PieceInfo PdfObject 45 StructParents PdfObject 46 ID PdfObject 47 PZ PdfObject 48 SeparationInfo PdfObject 49 Tabs PdfObject 50 TemplateInstantiated PdfObject 51 PresSteps PdfObject 52 UserUnit PdfObject 53 VP PdfObject 54 55 Annotations []*PdfAnnotation 56 57 // Primitive container. 58 pageDict *PdfObjectDictionary 59 primitive *PdfIndirectObject 60 } 61 62 func NewPdfPage() *PdfPage { 63 page := PdfPage{} 64 page.pageDict = MakeDict() 65 66 container := PdfIndirectObject{} 67 container.PdfObject = page.pageDict 68 page.primitive = &container 69 70 return &page 71 } 72 73 func (this *PdfPage) setContainer(container *PdfIndirectObject) { 74 container.PdfObject = this.pageDict 75 this.primitive = container 76 } 77 78 func (this *PdfPage) Duplicate() *PdfPage { 79 var dup PdfPage 80 dup = *this 81 dup.pageDict = MakeDict() 82 dup.primitive = MakeIndirectObject(dup.pageDict) 83 84 return &dup 85 } 86 87 // Build a PdfPage based on the underlying dictionary. 88 // Used in loading existing PDF files. 89 // Note that a new container is created (indirect object). 90 func (reader *PdfReader) newPdfPageFromDict(p *PdfObjectDictionary) (*PdfPage, error) { 91 page := NewPdfPage() 92 page.pageDict = p //XXX? 93 94 d := *p 95 96 pType, ok := d.Get("Type").(*PdfObjectName) 97 if !ok { 98 return nil, errors.New("Missing/Invalid Page dictionary Type") 99 } 100 if *pType != "Page" { 101 return nil, errors.New("Page dictionary Type != Page") 102 } 103 104 if obj := d.Get("Parent"); obj != nil { 105 page.Parent = obj 106 } 107 108 if obj := d.Get("LastModified"); obj != nil { 109 var err error 110 obj, err = reader.traceToObject(obj) 111 if err != nil { 112 return nil, err 113 } 114 strObj, ok := TraceToDirectObject(obj).(*PdfObjectString) 115 if !ok { 116 return nil, errors.New("Page dictionary LastModified != string") 117 } 118 lastmod, err := NewPdfDate(string(*strObj)) 119 if err != nil { 120 return nil, err 121 } 122 page.LastModified = &lastmod 123 } 124 125 if obj := d.Get("Resources"); obj != nil { 126 var err error 127 obj, err = reader.traceToObject(obj) 128 if err != nil { 129 return nil, err 130 } 131 132 dict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary) 133 if !ok { 134 return nil, fmt.Errorf("Invalid resource dictionary (%T)", obj) 135 } 136 137 page.Resources, err = NewPdfPageResourcesFromDict(dict) 138 if err != nil { 139 return nil, err 140 } 141 } else { 142 // If Resources not explicitly defined, look up the tree (Parent objects) using 143 // the getResources() function. Resources should always be accessible. 144 resources, err := page.getResources() 145 if err != nil { 146 return nil, err 147 } 148 if resources == nil { 149 resources = NewPdfPageResources() 150 } 151 page.Resources = resources 152 } 153 154 if obj := d.Get("MediaBox"); obj != nil { 155 var err error 156 obj, err = reader.traceToObject(obj) 157 if err != nil { 158 return nil, err 159 } 160 boxArr, ok := TraceToDirectObject(obj).(*PdfObjectArray) 161 if !ok { 162 return nil, errors.New("Page MediaBox not an array") 163 } 164 page.MediaBox, err = NewPdfRectangle(*boxArr) 165 if err != nil { 166 return nil, err 167 } 168 } 169 if obj := d.Get("CropBox"); obj != nil { 170 var err error 171 obj, err = reader.traceToObject(obj) 172 if err != nil { 173 return nil, err 174 } 175 boxArr, ok := TraceToDirectObject(obj).(*PdfObjectArray) 176 if !ok { 177 return nil, errors.New("Page CropBox not an array") 178 } 179 page.CropBox, err = NewPdfRectangle(*boxArr) 180 if err != nil { 181 return nil, err 182 } 183 } 184 if obj := d.Get("BleedBox"); obj != nil { 185 var err error 186 obj, err = reader.traceToObject(obj) 187 if err != nil { 188 return nil, err 189 } 190 boxArr, ok := TraceToDirectObject(obj).(*PdfObjectArray) 191 if !ok { 192 return nil, errors.New("Page BleedBox not an array") 193 } 194 page.BleedBox, err = NewPdfRectangle(*boxArr) 195 if err != nil { 196 return nil, err 197 } 198 } 199 if obj := d.Get("TrimBox"); obj != nil { 200 var err error 201 obj, err = reader.traceToObject(obj) 202 if err != nil { 203 return nil, err 204 } 205 boxArr, ok := TraceToDirectObject(obj).(*PdfObjectArray) 206 if !ok { 207 return nil, errors.New("Page TrimBox not an array") 208 } 209 page.TrimBox, err = NewPdfRectangle(*boxArr) 210 if err != nil { 211 return nil, err 212 } 213 } 214 if obj := d.Get("ArtBox"); obj != nil { 215 var err error 216 obj, err = reader.traceToObject(obj) 217 if err != nil { 218 return nil, err 219 } 220 boxArr, ok := TraceToDirectObject(obj).(*PdfObjectArray) 221 if !ok { 222 return nil, errors.New("Page ArtBox not an array") 223 } 224 page.ArtBox, err = NewPdfRectangle(*boxArr) 225 if err != nil { 226 return nil, err 227 } 228 } 229 if obj := d.Get("BoxColorInfo"); obj != nil { 230 page.BoxColorInfo = obj 231 } 232 if obj := d.Get("Contents"); obj != nil { 233 page.Contents = obj 234 } 235 if obj := d.Get("Rotate"); obj != nil { 236 var err error 237 obj, err = reader.traceToObject(obj) 238 if err != nil { 239 return nil, err 240 } 241 iObj, ok := TraceToDirectObject(obj).(*PdfObjectInteger) 242 if !ok { 243 return nil, errors.New("Invalid Page Rotate object") 244 } 245 iVal := int64(*iObj) 246 page.Rotate = &iVal 247 } 248 if obj := d.Get("Group"); obj != nil { 249 page.Group = obj 250 } 251 if obj := d.Get("Thumb"); obj != nil { 252 page.Thumb = obj 253 } 254 if obj := d.Get("B"); obj != nil { 255 page.B = obj 256 } 257 if obj := d.Get("Dur"); obj != nil { 258 page.Dur = obj 259 } 260 if obj := d.Get("Trans"); obj != nil { 261 page.Trans = obj 262 } 263 //if obj := d.Get("Annots"); obj != nil { 264 // page.Annots = obj 265 //} 266 if obj := d.Get("AA"); obj != nil { 267 page.AA = obj 268 } 269 if obj := d.Get("Metadata"); obj != nil { 270 page.Metadata = obj 271 } 272 if obj := d.Get("PieceInfo"); obj != nil { 273 page.PieceInfo = obj 274 } 275 if obj := d.Get("StructParents"); obj != nil { 276 page.StructParents = obj 277 } 278 if obj := d.Get("ID"); obj != nil { 279 page.ID = obj 280 } 281 if obj := d.Get("PZ"); obj != nil { 282 page.PZ = obj 283 } 284 if obj := d.Get("SeparationInfo"); obj != nil { 285 page.SeparationInfo = obj 286 } 287 if obj := d.Get("Tabs"); obj != nil { 288 page.Tabs = obj 289 } 290 if obj := d.Get("TemplateInstantiated"); obj != nil { 291 page.TemplateInstantiated = obj 292 } 293 if obj := d.Get("PresSteps"); obj != nil { 294 page.PresSteps = obj 295 } 296 if obj := d.Get("UserUnit"); obj != nil { 297 page.UserUnit = obj 298 } 299 if obj := d.Get("VP"); obj != nil { 300 page.VP = obj 301 } 302 303 var err error 304 page.Annotations, err = reader.LoadAnnotations(&d) 305 if err != nil { 306 return nil, err 307 } 308 309 return page, nil 310 } 311 312 func (reader *PdfReader) LoadAnnotations(d *PdfObjectDictionary) ([]*PdfAnnotation, error) { 313 annotsObj := d.Get("Annots") 314 if annotsObj == nil { 315 return nil, nil 316 } 317 318 var err error 319 annotsObj, err = reader.traceToObject(annotsObj) 320 if err != nil { 321 return nil, err 322 } 323 annotsArr, ok := TraceToDirectObject(annotsObj).(*PdfObjectArray) 324 if !ok { 325 return nil, fmt.Errorf("Annots not an array") 326 } 327 328 annotations := []*PdfAnnotation{} 329 for _, obj := range *annotsArr { 330 obj, err = reader.traceToObject(obj) 331 if err != nil { 332 return nil, err 333 } 334 335 // Technically all annotation dictionaries should be inside indirect objects. 336 // In reality, sometimes the annotation dictionary is inline within the Annots array. 337 if _, isNull := obj.(*PdfObjectNull); isNull { 338 // Can safely ignore. 339 continue 340 } 341 342 annotDict, isDict := obj.(*PdfObjectDictionary) 343 indirectObj, isIndirect := obj.(*PdfIndirectObject) 344 if isDict { 345 // Create a container; indirect object; around the dictionary. 346 indirectObj = &PdfIndirectObject{} 347 indirectObj.PdfObject = annotDict 348 } else { 349 if !isIndirect { 350 return nil, fmt.Errorf("Annotation not in an indirect object") 351 } 352 } 353 354 annot, err := reader.newPdfAnnotationFromIndirectObject(indirectObj) 355 if err != nil { 356 return nil, err 357 } 358 annotations = append(annotations, annot) 359 } 360 361 return annotations, nil 362 } 363 364 // Get the inheritable media box value, either from the page 365 // or a higher up page/pages struct. 366 func (this *PdfPage) GetMediaBox() (*PdfRectangle, error) { 367 if this.MediaBox != nil { 368 return this.MediaBox, nil 369 } 370 371 node := this.Parent 372 for node != nil { 373 dictObj, ok := node.(*PdfIndirectObject) 374 if !ok { 375 return nil, errors.New("Invalid parent object") 376 } 377 378 dict, ok := dictObj.PdfObject.(*PdfObjectDictionary) 379 if !ok { 380 return nil, errors.New("Invalid parent objects dictionary") 381 } 382 383 if obj := dict.Get("MediaBox"); obj != nil { 384 arr, ok := obj.(*PdfObjectArray) 385 if !ok { 386 return nil, errors.New("Invalid media box") 387 } 388 rect, err := NewPdfRectangle(*arr) 389 390 if err != nil { 391 return nil, err 392 } 393 394 return rect, nil 395 } 396 397 node = dict.Get("Parent") 398 } 399 400 return nil, errors.New("Media box not defined") 401 } 402 403 // Get the inheritable resources, either from the page or or a higher up page/pages struct. 404 func (this *PdfPage) getResources() (*PdfPageResources, error) { 405 if this.Resources != nil { 406 return this.Resources, nil 407 } 408 409 node := this.Parent 410 for node != nil { 411 dictObj, ok := node.(*PdfIndirectObject) 412 if !ok { 413 return nil, errors.New("Invalid parent object") 414 } 415 416 dict, ok := dictObj.PdfObject.(*PdfObjectDictionary) 417 if !ok { 418 return nil, errors.New("Invalid parent objects dictionary") 419 } 420 421 if obj := dict.Get("Resources"); obj != nil { 422 prDict, ok := TraceToDirectObject(obj).(*PdfObjectDictionary) 423 if !ok { 424 return nil, errors.New("Invalid resource dict!") 425 } 426 resources, err := NewPdfPageResourcesFromDict(prDict) 427 428 if err != nil { 429 return nil, err 430 } 431 432 return resources, nil 433 } 434 435 // Keep moving up the tree... 436 node = dict.Get("Parent") 437 } 438 439 // No resources defined... 440 return nil, nil 441 } 442 443 // Convert the Page to a PDF object dictionary. 444 func (this *PdfPage) GetPageDict() *PdfObjectDictionary { 445 p := this.pageDict 446 p.Set("Type", MakeName("Page")) 447 p.Set("Parent", this.Parent) 448 449 if this.LastModified != nil { 450 p.Set("LastModified", this.LastModified.ToPdfObject()) 451 } 452 if this.Resources != nil { 453 p.Set("Resources", this.Resources.ToPdfObject()) 454 } 455 if this.CropBox != nil { 456 p.Set("CropBox", this.CropBox.ToPdfObject()) 457 } 458 if this.MediaBox != nil { 459 p.Set("MediaBox", this.MediaBox.ToPdfObject()) 460 } 461 if this.BleedBox != nil { 462 p.Set("BleedBox", this.BleedBox.ToPdfObject()) 463 } 464 if this.TrimBox != nil { 465 p.Set("TrimBox", this.TrimBox.ToPdfObject()) 466 } 467 if this.ArtBox != nil { 468 p.Set("ArtBox", this.ArtBox.ToPdfObject()) 469 } 470 p.SetIfNotNil("BoxColorInfo", this.BoxColorInfo) 471 p.SetIfNotNil("Contents", this.Contents) 472 473 if this.Rotate != nil { 474 p.Set("Rotate", MakeInteger(*this.Rotate)) 475 } 476 477 p.SetIfNotNil("Group", this.Group) 478 p.SetIfNotNil("Thumb", this.Thumb) 479 p.SetIfNotNil("B", this.B) 480 p.SetIfNotNil("Dur", this.Dur) 481 p.SetIfNotNil("Trans", this.Trans) 482 p.SetIfNotNil("AA", this.AA) 483 p.SetIfNotNil("Metadata", this.Metadata) 484 p.SetIfNotNil("PieceInfo", this.PieceInfo) 485 p.SetIfNotNil("StructParents", this.StructParents) 486 p.SetIfNotNil("ID", this.ID) 487 p.SetIfNotNil("PZ", this.PZ) 488 p.SetIfNotNil("SeparationInfo", this.SeparationInfo) 489 p.SetIfNotNil("Tabs", this.Tabs) 490 p.SetIfNotNil("TemplateInstantiated", this.TemplateInstantiated) 491 p.SetIfNotNil("PresSteps", this.PresSteps) 492 p.SetIfNotNil("UserUnit", this.UserUnit) 493 p.SetIfNotNil("VP", this.VP) 494 495 if this.Annotations != nil { 496 arr := PdfObjectArray{} 497 for _, annot := range this.Annotations { 498 if subannot := annot.GetContext(); subannot != nil { 499 arr = append(arr, subannot.ToPdfObject()) 500 } else { 501 // Generic annotation dict (without subtype). 502 arr = append(arr, annot.ToPdfObject()) 503 } 504 } 505 p.Set("Annots", &arr) 506 } 507 508 return p 509 } 510 511 // Get the page object as an indirect objects. Wraps the Page 512 // dictionary into an indirect object. 513 func (this *PdfPage) GetPageAsIndirectObject() *PdfIndirectObject { 514 return this.primitive 515 } 516 517 func (this *PdfPage) GetContainingPdfObject() PdfObject { 518 return this.primitive 519 } 520 521 func (this *PdfPage) ToPdfObject() PdfObject { 522 container := this.primitive 523 this.GetPageDict() // update. 524 return container 525 } 526 527 // Add an image to the XObject resources. 528 func (this *PdfPage) AddImageResource(name PdfObjectName, ximg *XObjectImage) error { 529 var xresDict *PdfObjectDictionary 530 if this.Resources.XObject == nil { 531 xresDict = MakeDict() 532 this.Resources.XObject = xresDict 533 } else { 534 var ok bool 535 xresDict, ok = (this.Resources.XObject).(*PdfObjectDictionary) 536 if !ok { 537 return errors.New("Invalid xres dict type") 538 } 539 540 } 541 // Make a stream object container. 542 xresDict.Set(name, ximg.ToPdfObject()) 543 544 return nil 545 } 546 547 // Check if has XObject resource by name. 548 func (this *PdfPage) HasXObjectByName(name PdfObjectName) bool { 549 xresDict, has := this.Resources.XObject.(*PdfObjectDictionary) 550 if !has { 551 return false 552 } 553 554 if obj := xresDict.Get(name); obj != nil { 555 return true 556 } else { 557 return false 558 } 559 } 560 561 // Get XObject by name. 562 func (this *PdfPage) GetXObjectByName(name PdfObjectName) (PdfObject, bool) { 563 xresDict, has := this.Resources.XObject.(*PdfObjectDictionary) 564 if !has { 565 return nil, false 566 } 567 568 if obj := xresDict.Get(name); obj != nil { 569 return obj, true 570 } else { 571 return nil, false 572 } 573 } 574 575 // Check if has font resource by name. 576 func (this *PdfPage) HasFontByName(name PdfObjectName) bool { 577 fontDict, has := this.Resources.Font.(*PdfObjectDictionary) 578 if !has { 579 return false 580 } 581 582 if obj := fontDict.Get(name); obj != nil { 583 return true 584 } else { 585 return false 586 } 587 } 588 589 // Check if ExtGState name is available. 590 func (this *PdfPage) HasExtGState(name PdfObjectName) bool { 591 if this.Resources == nil { 592 return false 593 } 594 595 if this.Resources.ExtGState == nil { 596 return false 597 } 598 599 egsDict, ok := TraceToDirectObject(this.Resources.ExtGState).(*PdfObjectDictionary) 600 if !ok { 601 common.Log.Debug("Expected ExtGState dictionary is not a dictionary: %v", TraceToDirectObject(this.Resources.ExtGState)) 602 return false 603 } 604 605 // Update the dictionary. 606 obj := egsDict.Get(name) 607 has := obj != nil 608 609 return has 610 } 611 612 // Add a graphics state to the XObject resources. 613 func (this *PdfPage) AddExtGState(name PdfObjectName, egs *PdfObjectDictionary) error { 614 if this.Resources == nil { 615 //this.Resources = &PdfPageResources{} 616 this.Resources = NewPdfPageResources() 617 } 618 619 if this.Resources.ExtGState == nil { 620 this.Resources.ExtGState = MakeDict() 621 } 622 623 egsDict, ok := TraceToDirectObject(this.Resources.ExtGState).(*PdfObjectDictionary) 624 if !ok { 625 common.Log.Debug("Expected ExtGState dictionary is not a dictionary: %v", TraceToDirectObject(this.Resources.ExtGState)) 626 return errors.New("Type check error") 627 } 628 629 egsDict.Set(name, egs) 630 return nil 631 } 632 633 // Add a font dictionary to the Font resources. 634 func (this *PdfPage) AddFont(name PdfObjectName, font PdfObject) error { 635 if this.Resources == nil { 636 this.Resources = NewPdfPageResources() 637 } 638 639 if this.Resources.Font == nil { 640 this.Resources.Font = MakeDict() 641 } 642 643 fontDict, ok := TraceToDirectObject(this.Resources.Font).(*PdfObjectDictionary) 644 if !ok { 645 common.Log.Debug("Expected font dictionary is not a dictionary: %v", TraceToDirectObject(this.Resources.Font)) 646 return errors.New("Type check error") 647 } 648 649 // Update the dictionary. 650 fontDict.Set(name, font) 651 652 return nil 653 } 654 655 type WatermarkImageOptions struct { 656 Alpha float64 657 FitToWidth bool 658 PreserveAspectRatio bool 659 } 660 661 // Add a watermark to the page. 662 func (this *PdfPage) AddWatermarkImage(ximg *XObjectImage, opt WatermarkImageOptions) error { 663 // Page dimensions. 664 bbox, err := this.GetMediaBox() 665 if err != nil { 666 return err 667 } 668 pWidth := bbox.Urx - bbox.Llx 669 pHeight := bbox.Ury - bbox.Lly 670 671 wWidth := float64(*ximg.Width) 672 xOffset := (float64(pWidth) - float64(wWidth)) / 2 673 if opt.FitToWidth { 674 wWidth = pWidth 675 xOffset = 0 676 } 677 wHeight := pHeight 678 yOffset := float64(0) 679 if opt.PreserveAspectRatio { 680 wHeight = wWidth * float64(*ximg.Height) / float64(*ximg.Width) 681 yOffset = (pHeight - wHeight) / 2 682 } 683 684 if this.Resources == nil { 685 this.Resources = NewPdfPageResources() 686 } 687 688 // Find available image name for this page. 689 i := 0 690 imgName := PdfObjectName(fmt.Sprintf("Imw%d", i)) 691 for this.Resources.HasXObjectByName(imgName) { 692 i++ 693 imgName = PdfObjectName(fmt.Sprintf("Imw%d", i)) 694 } 695 696 err = this.AddImageResource(imgName, ximg) 697 if err != nil { 698 return err 699 } 700 701 i = 0 702 gsName := PdfObjectName(fmt.Sprintf("GS%d", i)) 703 for this.HasExtGState(gsName) { 704 i++ 705 gsName = PdfObjectName(fmt.Sprintf("GS%d", i)) 706 } 707 gs0 := MakeDict() 708 gs0.Set("BM", MakeName("Normal")) 709 gs0.Set("CA", MakeFloat(opt.Alpha)) 710 gs0.Set("ca", MakeFloat(opt.Alpha)) 711 err = this.AddExtGState(gsName, gs0) 712 if err != nil { 713 return err 714 } 715 716 contentStr := fmt.Sprintf("q\n"+ 717 "/%s gs\n"+ 718 "%.0f 0 0 %.0f %.4f %.4f cm\n"+ 719 "/%s Do\n"+ 720 "Q", gsName, wWidth, wHeight, xOffset, yOffset, imgName) 721 this.AddContentStreamByString(contentStr) 722 723 return nil 724 } 725 726 // Add content stream by string. Puts the content string into a stream 727 // object and points the content stream towards it. 728 func (this *PdfPage) AddContentStreamByString(contentStr string) { 729 stream := PdfObjectStream{} 730 731 sDict := MakeDict() 732 stream.PdfObjectDictionary = sDict 733 734 sDict.Set("Length", MakeInteger(int64(len(contentStr)))) 735 stream.Stream = []byte(contentStr) 736 737 if this.Contents == nil { 738 // If not set, place it directly. 739 this.Contents = &stream 740 } else if contArray, isArray := TraceToDirectObject(this.Contents).(*PdfObjectArray); isArray { 741 // If an array of content streams, append it. 742 *contArray = append(*contArray, &stream) 743 } else { 744 // Only 1 element in place. Wrap inside a new array and add the new one. 745 contArray := PdfObjectArray{} 746 contArray = append(contArray, this.Contents) 747 contArray = append(contArray, &stream) 748 this.Contents = &contArray 749 } 750 } 751 752 // Set the content streams based on a string array. Will make 1 object stream 753 // for each string and reference from the page Contents. Each stream will be 754 // encoded using the encoding specified by the StreamEncoder, if empty, will 755 // use identity encoding (raw data). 756 func (this *PdfPage) SetContentStreams(cStreams []string, encoder StreamEncoder) error { 757 if len(cStreams) == 0 { 758 this.Contents = nil 759 return nil 760 } 761 762 // If encoding is not set, use default raw encoder. 763 if encoder == nil { 764 encoder = NewRawEncoder() 765 } 766 767 streamObjs := []*PdfObjectStream{} 768 for _, cStream := range cStreams { 769 stream := &PdfObjectStream{} 770 771 // Make a new stream dict based on the encoding parameters. 772 sDict := encoder.MakeStreamDict() 773 774 encoded, err := encoder.EncodeBytes([]byte(cStream)) 775 if err != nil { 776 return err 777 } 778 779 sDict.Set("Length", MakeInteger(int64(len(encoded)))) 780 781 stream.PdfObjectDictionary = sDict 782 stream.Stream = []byte(encoded) 783 784 streamObjs = append(streamObjs, stream) 785 } 786 787 // Set the page contents. 788 // Point directly to the object stream if only one, or embed in an array. 789 if len(streamObjs) == 1 { 790 this.Contents = streamObjs[0] 791 } else { 792 contArray := PdfObjectArray{} 793 for _, streamObj := range streamObjs { 794 contArray = append(contArray, streamObj) 795 } 796 this.Contents = &contArray 797 } 798 799 return nil 800 } 801 802 func getContentStreamAsString(cstreamObj PdfObject) (string, error) { 803 if cstream, ok := TraceToDirectObject(cstreamObj).(*PdfObjectString); ok { 804 return string(*cstream), nil 805 } 806 807 if cstream, ok := TraceToDirectObject(cstreamObj).(*PdfObjectStream); ok { 808 buf, err := DecodeStream(cstream) 809 if err != nil { 810 return "", err 811 } 812 813 return string(buf), nil 814 } 815 return "", fmt.Errorf("Invalid content stream object holder (%T)", TraceToDirectObject(cstreamObj)) 816 } 817 818 // Get Content Stream as an array of strings. 819 func (this *PdfPage) GetContentStreams() ([]string, error) { 820 if this.Contents == nil { 821 return nil, nil 822 } 823 824 contents := TraceToDirectObject(this.Contents) 825 if contArray, isArray := contents.(*PdfObjectArray); isArray { 826 // If an array of content streams, append it. 827 cstreams := []string{} 828 for _, cstreamObj := range *contArray { 829 cstreamStr, err := getContentStreamAsString(cstreamObj) 830 if err != nil { 831 return nil, err 832 } 833 cstreams = append(cstreams, cstreamStr) 834 } 835 return cstreams, nil 836 } else { 837 // Only 1 element in place. Wrap inside a new array and add the new one. 838 cstreamStr, err := getContentStreamAsString(contents) 839 if err != nil { 840 return nil, err 841 } 842 cstreams := []string{cstreamStr} 843 return cstreams, nil 844 } 845 } 846 847 // Get all the content streams for a page as one string. 848 func (this *PdfPage) GetAllContentStreams() (string, error) { 849 cstreams, err := this.GetContentStreams() 850 if err != nil { 851 return "", err 852 } 853 return strings.Join(cstreams, " "), nil 854 } 855 856 // Needs to have matching name and colorspace map entry. The Names define the order. 857 type PdfPageResourcesColorspaces struct { 858 Names []string 859 Colorspaces map[string]PdfColorspace 860 861 container *PdfIndirectObject 862 } 863 864 func NewPdfPageResourcesColorspaces() *PdfPageResourcesColorspaces { 865 colorspaces := &PdfPageResourcesColorspaces{} 866 colorspaces.Names = []string{} 867 colorspaces.Colorspaces = map[string]PdfColorspace{} 868 colorspaces.container = &PdfIndirectObject{} 869 return colorspaces 870 } 871 872 // Set the colorspace corresponding to key. Add to Names if not set. 873 func (this *PdfPageResourcesColorspaces) Set(key PdfObjectName, val PdfColorspace) { 874 if _, has := this.Colorspaces[string(key)]; !has { 875 this.Names = append(this.Names, string(key)) 876 } 877 this.Colorspaces[string(key)] = val 878 } 879 880 func newPdfPageResourcesColorspacesFromPdfObject(obj PdfObject) (*PdfPageResourcesColorspaces, error) { 881 colorspaces := &PdfPageResourcesColorspaces{} 882 883 if indObj, isIndirect := obj.(*PdfIndirectObject); isIndirect { 884 colorspaces.container = indObj 885 obj = indObj.PdfObject 886 } 887 888 dict, ok := obj.(*PdfObjectDictionary) 889 if !ok { 890 return nil, errors.New("CS attribute type error") 891 } 892 893 colorspaces.Names = []string{} 894 colorspaces.Colorspaces = map[string]PdfColorspace{} 895 896 for _, csName := range dict.Keys() { 897 csObj := dict.Get(csName) 898 colorspaces.Names = append(colorspaces.Names, string(csName)) 899 cs, err := NewPdfColorspaceFromPdfObject(csObj) 900 if err != nil { 901 return nil, err 902 } 903 colorspaces.Colorspaces[string(csName)] = cs 904 } 905 906 return colorspaces, nil 907 } 908 909 func (this *PdfPageResourcesColorspaces) ToPdfObject() PdfObject { 910 dict := MakeDict() 911 for _, csName := range this.Names { 912 dict.Set(PdfObjectName(csName), this.Colorspaces[csName].ToPdfObject()) 913 } 914 915 if this.container != nil { 916 this.container.PdfObject = dict 917 return this.container 918 } 919 920 return dict 921 }