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  }