github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/xobject.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  package model
     7  
     8  import (
     9  	"errors"
    10  
    11  	"github.com/unidoc/unidoc/common"
    12  	. "github.com/unidoc/unidoc/pdf/core"
    13  )
    14  
    15  // XObjectForm (Table 95 in 8.10.2).
    16  type XObjectForm struct {
    17  	Filter StreamEncoder
    18  
    19  	FormType      PdfObject
    20  	BBox          PdfObject
    21  	Matrix        PdfObject
    22  	Resources     *PdfPageResources
    23  	Group         PdfObject
    24  	Ref           PdfObject
    25  	MetaData      PdfObject
    26  	PieceInfo     PdfObject
    27  	LastModified  PdfObject
    28  	StructParent  PdfObject
    29  	StructParents PdfObject
    30  	OPI           PdfObject
    31  	OC            PdfObject
    32  	Name          PdfObject
    33  
    34  	// Stream data.
    35  	Stream []byte
    36  	// Primitive
    37  	primitive *PdfObjectStream
    38  }
    39  
    40  var ErrTypeCheck = errors.New("Type check error")
    41  
    42  // Create a brand new XObject Form. Creates a new underlying PDF object stream primitive.
    43  func NewXObjectForm() *XObjectForm {
    44  	xobj := &XObjectForm{}
    45  	stream := &PdfObjectStream{}
    46  	stream.PdfObjectDictionary = MakeDict()
    47  	xobj.primitive = stream
    48  	return xobj
    49  }
    50  
    51  // Build the Form XObject from a stream object.
    52  // XXX: Should this be exposed? Consider different access points.
    53  func NewXObjectFormFromStream(stream *PdfObjectStream) (*XObjectForm, error) {
    54  	form := &XObjectForm{}
    55  	form.primitive = stream
    56  
    57  	dict := *(stream.PdfObjectDictionary)
    58  
    59  	encoder, err := NewEncoderFromStream(stream)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	form.Filter = encoder
    64  
    65  	if obj := dict.Get("Subtype"); obj != nil {
    66  		name, ok := obj.(*PdfObjectName)
    67  		if !ok {
    68  			return nil, errors.New("Type error")
    69  		}
    70  		if *name != "Form" {
    71  			common.Log.Debug("Invalid form subtype")
    72  			return nil, errors.New("Invalid form subtype")
    73  		}
    74  	}
    75  
    76  	if obj := dict.Get("FormType"); obj != nil {
    77  		form.FormType = obj
    78  	}
    79  	if obj := dict.Get("BBox"); obj != nil {
    80  		form.BBox = obj
    81  	}
    82  	if obj := dict.Get("Matrix"); obj != nil {
    83  		form.Matrix = obj
    84  	}
    85  	if obj := dict.Get("Resources"); obj != nil {
    86  		obj = TraceToDirectObject(obj)
    87  		d, ok := obj.(*PdfObjectDictionary)
    88  		if !ok {
    89  			common.Log.Debug("Invalid XObject Form Resources object, pointing to non-dictionary")
    90  			return nil, errors.New("Type check error")
    91  		}
    92  		res, err := NewPdfPageResourcesFromDict(d)
    93  		if err != nil {
    94  			common.Log.Debug("Failed getting form resources")
    95  			return nil, err
    96  		}
    97  		form.Resources = res
    98  		common.Log.Trace("Form resources: %#v", form.Resources)
    99  	}
   100  
   101  	form.Group = dict.Get("Group")
   102  	form.Ref = dict.Get("Ref")
   103  	form.MetaData = dict.Get("MetaData")
   104  	form.PieceInfo = dict.Get("PieceInfo")
   105  	form.LastModified = dict.Get("LastModified")
   106  	form.StructParent = dict.Get("StructParent")
   107  	form.StructParents = dict.Get("StructParents")
   108  	form.OPI = dict.Get("OPI")
   109  	form.OC = dict.Get("OC")
   110  	form.Name = dict.Get("Name")
   111  
   112  	form.Stream = stream.Stream
   113  
   114  	return form, nil
   115  }
   116  
   117  func (xform *XObjectForm) GetContainingPdfObject() PdfObject {
   118  	return xform.primitive
   119  }
   120  
   121  func (xform *XObjectForm) GetContentStream() ([]byte, error) {
   122  	decoded, err := DecodeStream(xform.primitive)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	return decoded, nil
   128  }
   129  
   130  // Update the content stream with specified encoding.  If encoding is null, will use the xform.Filter object
   131  // or Raw encoding if not set.
   132  func (xform *XObjectForm) SetContentStream(content []byte, encoder StreamEncoder) error {
   133  	encoded := content
   134  
   135  	if encoder == nil {
   136  		if xform.Filter != nil {
   137  			encoder = xform.Filter
   138  		} else {
   139  			encoder = NewRawEncoder()
   140  		}
   141  	}
   142  
   143  	enc, err := encoder.EncodeBytes(encoded)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	encoded = enc
   148  
   149  	xform.Stream = encoded
   150  
   151  	return nil
   152  }
   153  
   154  // Return a stream object.
   155  func (xform *XObjectForm) ToPdfObject() PdfObject {
   156  	stream := xform.primitive
   157  
   158  	dict := stream.PdfObjectDictionary
   159  	if xform.Filter != nil {
   160  		// Pre-populate the stream dictionary with the encoding related fields.
   161  		dict = xform.Filter.MakeStreamDict()
   162  		stream.PdfObjectDictionary = dict
   163  	}
   164  	dict.Set("Type", MakeName("XObject"))
   165  	dict.Set("Subtype", MakeName("Form"))
   166  
   167  	dict.SetIfNotNil("FormType", xform.FormType)
   168  	dict.SetIfNotNil("BBox", xform.BBox)
   169  	dict.SetIfNotNil("Matrix", xform.Matrix)
   170  	if xform.Resources != nil {
   171  		dict.SetIfNotNil("Resources", xform.Resources.ToPdfObject())
   172  	}
   173  	dict.SetIfNotNil("Group", xform.Group)
   174  	dict.SetIfNotNil("Ref", xform.Ref)
   175  	dict.SetIfNotNil("MetaData", xform.MetaData)
   176  	dict.SetIfNotNil("PieceInfo", xform.PieceInfo)
   177  	dict.SetIfNotNil("LastModified", xform.LastModified)
   178  	dict.SetIfNotNil("StructParent", xform.StructParent)
   179  	dict.SetIfNotNil("StructParents", xform.StructParents)
   180  	dict.SetIfNotNil("OPI", xform.OPI)
   181  	dict.SetIfNotNil("OC", xform.OC)
   182  	dict.SetIfNotNil("Name", xform.Name)
   183  
   184  	dict.Set("Length", MakeInteger(int64(len(xform.Stream))))
   185  	stream.Stream = xform.Stream
   186  
   187  	return stream
   188  }
   189  
   190  // XObjectImage (Table 89 in 8.9.5.1).
   191  // Implements PdfModel interface.
   192  type XObjectImage struct {
   193  	//ColorSpace       PdfObject
   194  	Width            *int64
   195  	Height           *int64
   196  	ColorSpace       PdfColorspace
   197  	BitsPerComponent *int64
   198  	Filter           StreamEncoder
   199  
   200  	Intent       PdfObject
   201  	ImageMask    PdfObject
   202  	Mask         PdfObject
   203  	Matte        PdfObject
   204  	Decode       PdfObject
   205  	Interpolate  PdfObject
   206  	Alternatives PdfObject
   207  	SMask        PdfObject
   208  	SMaskInData  PdfObject
   209  	Name         PdfObject // Obsolete. Currently read if available and write if available. Not setting on new created files.
   210  	StructParent PdfObject
   211  	ID           PdfObject
   212  	OPI          PdfObject
   213  	Metadata     PdfObject
   214  	OC           PdfObject
   215  	Stream       []byte
   216  	// Primitive
   217  	primitive *PdfObjectStream
   218  }
   219  
   220  func NewXObjectImage() *XObjectImage {
   221  	xobj := &XObjectImage{}
   222  	stream := &PdfObjectStream{}
   223  	stream.PdfObjectDictionary = MakeDict()
   224  	xobj.primitive = stream
   225  	return xobj
   226  }
   227  
   228  // Creates a new XObject Image from an image object with default options.
   229  // If encoder is nil, uses raw encoding (none).
   230  func NewXObjectImageFromImage(img *Image, cs PdfColorspace, encoder StreamEncoder) (*XObjectImage, error) {
   231  	xobj := NewXObjectImage()
   232  	return UpdateXObjectImageFromImage(xobj, img, cs, encoder)
   233  }
   234  
   235  // UpdateXObjectImageFromImage creates a new XObject Image from an Image object `img` and default
   236  //  masks from xobjIn.
   237  // The default masks are overriden if img.hasAlpha
   238  // If `encoder` is nil, uses raw encoding (none).
   239  func UpdateXObjectImageFromImage(xobjIn *XObjectImage, img *Image, cs PdfColorspace,
   240  	encoder StreamEncoder) (*XObjectImage, error) {
   241  	xobj := NewXObjectImage()
   242  
   243  	if encoder == nil {
   244  		encoder = NewRawEncoder()
   245  	}
   246  
   247  	encoded, err := encoder.EncodeBytes(img.Data)
   248  	if err != nil {
   249  		common.Log.Debug("Error with encoding: %v", err)
   250  		return nil, err
   251  	}
   252  
   253  	xobj.Filter = encoder
   254  	xobj.Stream = encoded
   255  
   256  	// Width and height.
   257  	imWidth := img.Width
   258  	imHeight := img.Height
   259  	xobj.Width = &imWidth
   260  	xobj.Height = &imHeight
   261  
   262  	// Bits.
   263  	xobj.BitsPerComponent = &img.BitsPerComponent
   264  
   265  	// Guess colorspace if not explicitly set.
   266  	if cs == nil {
   267  		if img.ColorComponents == 1 {
   268  			xobj.ColorSpace = NewPdfColorspaceDeviceGray()
   269  		} else if img.ColorComponents == 3 {
   270  			xobj.ColorSpace = NewPdfColorspaceDeviceRGB()
   271  		} else if img.ColorComponents == 4 {
   272  			xobj.ColorSpace = NewPdfColorspaceDeviceCMYK()
   273  		} else {
   274  			return nil, errors.New("Colorspace undefined")
   275  		}
   276  	} else {
   277  		xobj.ColorSpace = cs
   278  	}
   279  
   280  	if img.hasAlpha {
   281  		// Add the alpha channel information as a stencil mask (SMask).
   282  		// Has same width and height as original and stored in same
   283  		// bits per component (1 component, hence the DeviceGray channel).
   284  		smask := NewXObjectImage()
   285  		smask.Filter = encoder
   286  		encoded, err := encoder.EncodeBytes(img.alphaData)
   287  		if err != nil {
   288  			common.Log.Debug("Error with encoding: %v", err)
   289  			return nil, err
   290  		}
   291  		smask.Stream = encoded
   292  		smask.BitsPerComponent = &img.BitsPerComponent
   293  		smask.Width = &img.Width
   294  		smask.Height = &img.Height
   295  		smask.ColorSpace = NewPdfColorspaceDeviceGray()
   296  		xobj.SMask = smask.ToPdfObject()
   297  	} else {
   298  		xobj.SMask = xobjIn.SMask
   299  		xobj.ImageMask = xobjIn.ImageMask
   300  		if xobj.ColorSpace.GetNumComponents() == 1 {
   301  			smaskMatteToGray(xobj)
   302  		}
   303  	}
   304  
   305  	return xobj, nil
   306  }
   307  
   308  // smaskMatteToGray converts to gray the Matte value in the SMask image referenced by `xobj` (if
   309  // there is one)
   310  func smaskMatteToGray(xobj *XObjectImage) error {
   311  	if xobj.SMask == nil {
   312  		return nil
   313  	}
   314  	stream, ok := xobj.SMask.(*PdfObjectStream)
   315  	if !ok {
   316  		common.Log.Debug("SMask is not *PdfObjectStream")
   317  		return ErrTypeCheck
   318  	}
   319  	dict := stream.PdfObjectDictionary
   320  	matte := dict.Get("Matte")
   321  	if matte == nil {
   322  		return nil
   323  	}
   324  
   325  	gray, err := toGray(matte.(*PdfObjectArray))
   326  	if err != nil {
   327  		return err
   328  	}
   329  	grayMatte := MakeArrayFromFloats([]float64{gray})
   330  	dict.SetIfNotNil("Matte", grayMatte)
   331  	return nil
   332  }
   333  
   334  // toGray converts a 1, 3 or 4 dimensional color `matte` to gray
   335  // If `matte` is not a 1, 3 or 4 dimensional color then an error is returned
   336  func toGray(matte *PdfObjectArray) (float64, error) {
   337  	colors, err := matte.ToFloat64Array()
   338  	if err != nil {
   339  		common.Log.Debug("Bad Matte array: matte=%s err=%v", matte, err)
   340  	}
   341  	switch len(colors) {
   342  	case 1:
   343  		return colors[0], nil
   344  	case 3:
   345  		cs := PdfColorspaceDeviceRGB{}
   346  		rgbColor, err := cs.ColorFromFloats(colors)
   347  		if err != nil {
   348  			return 0.0, err
   349  		}
   350  		return rgbColor.(*PdfColorDeviceRGB).ToGray().Val(), nil
   351  
   352  	case 4:
   353  		cs := PdfColorspaceDeviceCMYK{}
   354  		cmykColor, err := cs.ColorFromFloats(colors)
   355  		if err != nil {
   356  			return 0.0, err
   357  		}
   358  		rgbColor, err := cs.ColorToRGB(cmykColor.(*PdfColorDeviceCMYK))
   359  		if err != nil {
   360  			return 0.0, err
   361  		}
   362  		return rgbColor.(*PdfColorDeviceRGB).ToGray().Val(), nil
   363  	}
   364  	err = errors.New("Bad Matte color")
   365  	common.Log.Error("toGray: matte=%s err=%v", matte, err)
   366  	return 0.0, err
   367  }
   368  
   369  // Build the image xobject from a stream object.
   370  // An image dictionary is the dictionary portion of a stream object representing an image XObject.
   371  func NewXObjectImageFromStream(stream *PdfObjectStream) (*XObjectImage, error) {
   372  	img := &XObjectImage{}
   373  	img.primitive = stream
   374  
   375  	dict := *(stream.PdfObjectDictionary)
   376  
   377  	encoder, err := NewEncoderFromStream(stream)
   378  	if err != nil {
   379  		return nil, err
   380  	}
   381  	img.Filter = encoder
   382  
   383  	if obj := TraceToDirectObject(dict.Get("Width")); obj != nil {
   384  		iObj, ok := obj.(*PdfObjectInteger)
   385  		if !ok {
   386  			return nil, errors.New("Invalid image width object")
   387  		}
   388  		iVal := int64(*iObj)
   389  		img.Width = &iVal
   390  	} else {
   391  		return nil, errors.New("Width missing")
   392  	}
   393  
   394  	if obj := TraceToDirectObject(dict.Get("Height")); obj != nil {
   395  		iObj, ok := obj.(*PdfObjectInteger)
   396  		if !ok {
   397  			return nil, errors.New("Invalid image height object")
   398  		}
   399  		iVal := int64(*iObj)
   400  		img.Height = &iVal
   401  	} else {
   402  		return nil, errors.New("Height missing")
   403  	}
   404  
   405  	if obj := TraceToDirectObject(dict.Get("ColorSpace")); obj != nil {
   406  		cs, err := NewPdfColorspaceFromPdfObject(obj)
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  		img.ColorSpace = cs
   411  	} else {
   412  		// If not specified, assume gray..
   413  		common.Log.Debug("XObject Image colorspace not specified - assuming 1 color component")
   414  		img.ColorSpace = NewPdfColorspaceDeviceGray()
   415  	}
   416  
   417  	if obj := TraceToDirectObject(dict.Get("BitsPerComponent")); obj != nil {
   418  		iObj, ok := obj.(*PdfObjectInteger)
   419  		if !ok {
   420  			return nil, errors.New("Invalid image height object")
   421  		}
   422  		iVal := int64(*iObj)
   423  		img.BitsPerComponent = &iVal
   424  	}
   425  
   426  	img.Intent = dict.Get("Intent")
   427  	img.ImageMask = dict.Get("ImageMask")
   428  	img.Mask = dict.Get("Mask")
   429  	img.Decode = dict.Get("Decode")
   430  	img.Interpolate = dict.Get("Interpolate")
   431  	img.Alternatives = dict.Get("Alternatives")
   432  	img.SMask = dict.Get("SMask")
   433  	img.SMaskInData = dict.Get("SMaskInData")
   434  	img.Matte = dict.Get("Matte")
   435  	img.Name = dict.Get("Name")
   436  	img.StructParent = dict.Get("StructParent")
   437  	img.ID = dict.Get("ID")
   438  	img.OPI = dict.Get("OPI")
   439  	img.Metadata = dict.Get("Metadata")
   440  	img.OC = dict.Get("OC")
   441  
   442  	img.Stream = stream.Stream
   443  
   444  	return img, nil
   445  }
   446  
   447  // Update XObject Image with new image data.
   448  func (ximg *XObjectImage) SetImage(img *Image, cs PdfColorspace) error {
   449  	encoded, err := ximg.Filter.EncodeBytes(img.Data)
   450  	if err != nil {
   451  		return err
   452  	}
   453  
   454  	ximg.Stream = encoded
   455  
   456  	// Width, height and bits.
   457  	ximg.Width = &img.Width
   458  	ximg.Height = &img.Height
   459  	ximg.BitsPerComponent = &img.BitsPerComponent
   460  
   461  	// Guess colorspace if not explicitly set.
   462  	if cs == nil {
   463  		if img.ColorComponents == 1 {
   464  			ximg.ColorSpace = NewPdfColorspaceDeviceGray()
   465  		} else if img.ColorComponents == 3 {
   466  			ximg.ColorSpace = NewPdfColorspaceDeviceRGB()
   467  		} else if img.ColorComponents == 4 {
   468  			ximg.ColorSpace = NewPdfColorspaceDeviceCMYK()
   469  		} else {
   470  			return errors.New("Colorspace undefined")
   471  		}
   472  	} else {
   473  		ximg.ColorSpace = cs
   474  	}
   475  
   476  	return nil
   477  }
   478  
   479  // Set compression filter.  Decodes with current filter sets and encodes the data with the new filter.
   480  func (ximg *XObjectImage) SetFilter(encoder StreamEncoder) error {
   481  	encoded := ximg.Stream
   482  	decoded, err := ximg.Filter.DecodeBytes(encoded)
   483  	if err != nil {
   484  		return err
   485  	}
   486  
   487  	ximg.Filter = encoder
   488  	encoded, err = encoder.EncodeBytes(decoded)
   489  	if err != nil {
   490  		return err
   491  	}
   492  
   493  	ximg.Stream = encoded
   494  	return nil
   495  }
   496  
   497  // This will convert to an Image which can be transformed or saved out.
   498  // The image data is decoded and the Image returned.
   499  func (ximg *XObjectImage) ToImage() (*Image, error) {
   500  	image := &Image{}
   501  
   502  	if ximg.Height == nil {
   503  		return nil, errors.New("Height attribute missing")
   504  	}
   505  	image.Height = *ximg.Height
   506  
   507  	if ximg.Width == nil {
   508  		return nil, errors.New("Width attribute missing")
   509  	}
   510  	image.Width = *ximg.Width
   511  
   512  	if ximg.BitsPerComponent == nil {
   513  		return nil, errors.New("Bits per component missing")
   514  	}
   515  	image.BitsPerComponent = *ximg.BitsPerComponent
   516  
   517  	image.ColorComponents = ximg.ColorSpace.GetNumComponents()
   518  
   519  	decoded, err := DecodeStream(ximg.primitive)
   520  	if err != nil {
   521  		return nil, err
   522  	}
   523  	image.Data = decoded
   524  
   525  	if ximg.Decode != nil {
   526  		darr, ok := ximg.Decode.(*PdfObjectArray)
   527  		if !ok {
   528  			common.Log.Debug("Invalid Decode object")
   529  			return nil, errors.New("Invalid type")
   530  		}
   531  		decode, err := darr.ToFloat64Array()
   532  		if err != nil {
   533  			return nil, err
   534  		}
   535  		image.decode = decode
   536  	}
   537  
   538  	return image, nil
   539  }
   540  
   541  func (ximg *XObjectImage) GetContainingPdfObject() PdfObject {
   542  	return ximg.primitive
   543  }
   544  
   545  // Return a stream object.
   546  func (ximg *XObjectImage) ToPdfObject() PdfObject {
   547  	stream := ximg.primitive
   548  
   549  	dict := stream.PdfObjectDictionary
   550  	if ximg.Filter != nil {
   551  		//dict.Set("Filter", ximg.Filter)
   552  		// Pre-populate the stream dictionary with the
   553  		// encoding related fields.
   554  		dict = ximg.Filter.MakeStreamDict()
   555  		stream.PdfObjectDictionary = dict
   556  	}
   557  	dict.Set("Type", MakeName("XObject"))
   558  	dict.Set("Subtype", MakeName("Image"))
   559  	dict.Set("Width", MakeInteger(*(ximg.Width)))
   560  	dict.Set("Height", MakeInteger(*(ximg.Height)))
   561  
   562  	if ximg.BitsPerComponent != nil {
   563  		dict.Set("BitsPerComponent", MakeInteger(*(ximg.BitsPerComponent)))
   564  	}
   565  
   566  	if ximg.ColorSpace != nil {
   567  		dict.SetIfNotNil("ColorSpace", ximg.ColorSpace.ToPdfObject())
   568  	}
   569  	dict.SetIfNotNil("Intent", ximg.Intent)
   570  	dict.SetIfNotNil("ImageMask", ximg.ImageMask)
   571  	dict.SetIfNotNil("Mask", ximg.Mask)
   572  	dict.SetIfNotNil("Decode", ximg.Decode)
   573  	dict.SetIfNotNil("Interpolate", ximg.Interpolate)
   574  	dict.SetIfNotNil("Alternatives", ximg.Alternatives)
   575  	dict.SetIfNotNil("SMask", ximg.SMask)
   576  	dict.SetIfNotNil("SMaskInData", ximg.SMaskInData)
   577  	dict.SetIfNotNil("Matte", ximg.Matte)
   578  	dict.SetIfNotNil("Name", ximg.Name)
   579  	dict.SetIfNotNil("StructParent", ximg.StructParent)
   580  	dict.SetIfNotNil("ID", ximg.ID)
   581  	dict.SetIfNotNil("OPI", ximg.OPI)
   582  	dict.SetIfNotNil("Metadata", ximg.Metadata)
   583  	dict.SetIfNotNil("OC", ximg.OC)
   584  
   585  	dict.Set("Length", MakeInteger(int64(len(ximg.Stream))))
   586  	stream.Stream = ximg.Stream
   587  
   588  	return stream
   589  }