github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/pattern.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  	"fmt"
    12  
    13  	"github.com/unidoc/unidoc/common"
    14  	. "github.com/unidoc/unidoc/pdf/core"
    15  )
    16  
    17  // A PdfPattern can represent a Pattern, either a tiling pattern or a shading pattern.
    18  // Note that all patterns shall be treated as colours; a Pattern colour space shall be established with the CS or cs
    19  // operator just like other colour spaces, and a particular pattern shall be installed as the current colour with the
    20  // SCN or scn operator.
    21  type PdfPattern struct {
    22  	// Type: Pattern
    23  	PatternType int64
    24  	context     PdfModel // The sub pattern, either PdfTilingPattern (Type 1) or PdfShadingPattern (Type 2).
    25  
    26  	container PdfObject
    27  }
    28  
    29  func (this *PdfPattern) GetContainingPdfObject() PdfObject {
    30  	return this.container
    31  }
    32  
    33  // Context in this case is a reference to the subpattern entry: either PdfTilingPattern or PdfShadingPattern.
    34  func (this *PdfPattern) GetContext() PdfModel {
    35  	return this.context
    36  }
    37  
    38  // Set the sub pattern (context).  Either PdfTilingPattern or PdfShadingPattern.
    39  func (this *PdfPattern) SetContext(ctx PdfModel) {
    40  	this.context = ctx
    41  }
    42  
    43  func (this *PdfPattern) IsTiling() bool {
    44  	return this.PatternType == 1
    45  }
    46  
    47  func (this *PdfPattern) IsShading() bool {
    48  	return this.PatternType == 2
    49  }
    50  
    51  // Check with IsTiling() prior to using this to ensure is a tiling pattern.
    52  func (this *PdfPattern) GetAsTilingPattern() *PdfTilingPattern {
    53  	return this.context.(*PdfTilingPattern)
    54  }
    55  
    56  // Check with IsShading() prior to using this, to ensure is a shading pattern.
    57  func (this *PdfPattern) GetAsShadingPattern() *PdfShadingPattern {
    58  	return this.context.(*PdfShadingPattern)
    59  }
    60  
    61  // A Tiling pattern consists of repetitions of a pattern cell with defined intervals.
    62  // It is a type 1 pattern. (PatternType = 1).
    63  // A tiling pattern is represented by a stream object, where the stream content is
    64  // a content stream that describes the pattern cell.
    65  type PdfTilingPattern struct {
    66  	*PdfPattern
    67  	PaintType  *PdfObjectInteger // Colored or uncolored tiling pattern.
    68  	TilingType *PdfObjectInteger // Constant spacing, no distortion or constant spacing/faster tiling.
    69  	BBox       *PdfRectangle
    70  	XStep      *PdfObjectFloat
    71  	YStep      *PdfObjectFloat
    72  	Resources  *PdfPageResources
    73  	Matrix     *PdfObjectArray // Pattern matrix (6 numbers).
    74  }
    75  
    76  func (this *PdfTilingPattern) IsColored() bool {
    77  	if this.PaintType != nil && *this.PaintType == 1 {
    78  		return true
    79  	} else {
    80  		return false
    81  	}
    82  }
    83  
    84  // GetContentStream returns the pattern cell's content stream
    85  func (this *PdfTilingPattern) GetContentStream() ([]byte, error) {
    86  	decoded, _, err := this.GetContentStreamWithEncoder()
    87  	return decoded, err
    88  }
    89  
    90  // GetContentStreamWithEncoder returns the pattern cell's content stream and its encoder
    91  // TODO (v3): Change GetContentStreamWithEncoder to GetContentStream
    92  func (this *PdfTilingPattern) GetContentStreamWithEncoder() ([]byte, StreamEncoder, error) {
    93  	streamObj, ok := this.container.(*PdfObjectStream)
    94  	if !ok {
    95  		common.Log.Debug("Tiling pattern container not a stream (got %T)", this.container)
    96  		return nil, nil, ErrTypeError
    97  	}
    98  
    99  	decoded, err := DecodeStream(streamObj)
   100  	if err != nil {
   101  		common.Log.Debug("Failed decoding stream, err: %v", err)
   102  		return nil, nil, err
   103  	}
   104  
   105  	encoder, err := NewEncoderFromStream(streamObj)
   106  	if err != nil {
   107  		common.Log.Debug("Failed finding decoding encoder: %v", err)
   108  		return nil, nil, err
   109  	}
   110  
   111  	return decoded, encoder, nil
   112  }
   113  
   114  // Set the pattern cell's content stream.
   115  func (this *PdfTilingPattern) SetContentStream(content []byte, encoder StreamEncoder) error {
   116  	streamObj, ok := this.container.(*PdfObjectStream)
   117  	if !ok {
   118  		common.Log.Debug("Tiling pattern container not a stream (got %T)", this.container)
   119  		return ErrTypeError
   120  	}
   121  
   122  	// If encoding is not set, use raw encoder.
   123  	if encoder == nil {
   124  		encoder = NewRawEncoder()
   125  	}
   126  
   127  	streamDict := streamObj.PdfObjectDictionary
   128  
   129  	// Make a new stream dict based on the encoding parameters.
   130  	encDict := encoder.MakeStreamDict()
   131  	// Merge the encoding dict into the stream dict.
   132  	streamDict.Merge(encDict)
   133  
   134  	encoded, err := encoder.EncodeBytes(content)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	// Update length.
   140  	streamDict.Set("Length", MakeInteger(int64(len(encoded))))
   141  
   142  	streamObj.Stream = []byte(encoded)
   143  
   144  	return nil
   145  }
   146  
   147  // Shading patterns provide a smooth transition between colors across an area to be painted, i.e.
   148  // color(x,y) = f(x,y) at each point.
   149  // It is a type 2 pattern (PatternType = 2).
   150  type PdfShadingPattern struct {
   151  	*PdfPattern
   152  	Shading   *PdfShading
   153  	Matrix    *PdfObjectArray
   154  	ExtGState PdfObject
   155  }
   156  
   157  // Load a pdf pattern from an indirect object. Used in parsing/loading PDFs.
   158  func newPdfPatternFromPdfObject(container PdfObject) (*PdfPattern, error) {
   159  	pattern := &PdfPattern{}
   160  
   161  	var dict *PdfObjectDictionary
   162  	if indObj, is := container.(*PdfIndirectObject); is {
   163  		pattern.container = indObj
   164  		d, ok := indObj.PdfObject.(*PdfObjectDictionary)
   165  		if !ok {
   166  			common.Log.Debug("Pattern indirect object not containing dictionary (got %T)", indObj.PdfObject)
   167  			return nil, ErrTypeError
   168  		}
   169  		dict = d
   170  	} else if streamObj, is := container.(*PdfObjectStream); is {
   171  		pattern.container = streamObj
   172  		dict = streamObj.PdfObjectDictionary
   173  	} else {
   174  		common.Log.Debug("Pattern not an indirect object or stream")
   175  		return nil, ErrTypeError
   176  	}
   177  
   178  	// PatternType.
   179  	obj := dict.Get("PatternType")
   180  	if obj == nil {
   181  		common.Log.Debug("Pdf Pattern not containing PatternType")
   182  		return nil, ErrRequiredAttributeMissing
   183  	}
   184  	patternType, ok := obj.(*PdfObjectInteger)
   185  	if !ok {
   186  		common.Log.Debug("Pattern type not an integer (got %T)", obj)
   187  		return nil, ErrTypeError
   188  	}
   189  	if *patternType != 1 && *patternType != 2 {
   190  		common.Log.Debug("Pattern type != 1/2 (got %d)", *patternType)
   191  		return nil, ErrRangeError
   192  	}
   193  	pattern.PatternType = int64(*patternType)
   194  
   195  	switch *patternType {
   196  	case 1: // Tiling pattern.
   197  		ctx, err := newPdfTilingPatternFromDictionary(dict)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		ctx.PdfPattern = pattern
   202  		pattern.context = ctx
   203  		return pattern, nil
   204  	case 2: // Shading pattern.
   205  		ctx, err := newPdfShadingPatternFromDictionary(dict)
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		ctx.PdfPattern = pattern
   210  		pattern.context = ctx
   211  		return pattern, nil
   212  	}
   213  
   214  	return nil, errors.New("Unknown pattern")
   215  }
   216  
   217  // Load entries specific to a pdf tiling pattern from a dictionary. Used in parsing/loading PDFs.
   218  func newPdfTilingPatternFromDictionary(dict *PdfObjectDictionary) (*PdfTilingPattern, error) {
   219  	pattern := &PdfTilingPattern{}
   220  
   221  	// PaintType (required).
   222  	obj := dict.Get("PaintType")
   223  	if obj == nil {
   224  		common.Log.Debug("PaintType missing")
   225  		return nil, ErrRequiredAttributeMissing
   226  	}
   227  	paintType, ok := obj.(*PdfObjectInteger)
   228  	if !ok {
   229  		common.Log.Debug("PaintType not an integer (got %T)", obj)
   230  		return nil, ErrTypeError
   231  	}
   232  	pattern.PaintType = paintType
   233  
   234  	// TilingType (required).
   235  	obj = dict.Get("TilingType")
   236  	if obj == nil {
   237  		common.Log.Debug("TilingType missing")
   238  		return nil, ErrRequiredAttributeMissing
   239  	}
   240  	tilingType, ok := obj.(*PdfObjectInteger)
   241  	if !ok {
   242  		common.Log.Debug("TilingType not an integer (got %T)", obj)
   243  		return nil, ErrTypeError
   244  	}
   245  	pattern.TilingType = tilingType
   246  
   247  	// BBox (required).
   248  	obj = dict.Get("BBox")
   249  	if obj == nil {
   250  		common.Log.Debug("BBox missing")
   251  		return nil, ErrRequiredAttributeMissing
   252  	}
   253  	obj = TraceToDirectObject(obj)
   254  	arr, ok := obj.(*PdfObjectArray)
   255  	if !ok {
   256  		common.Log.Debug("BBox should be specified by an array (got %T)", obj)
   257  		return nil, ErrTypeError
   258  	}
   259  	rect, err := NewPdfRectangle(*arr)
   260  	if err != nil {
   261  		common.Log.Debug("BBox error: %v", err)
   262  		return nil, err
   263  	}
   264  	pattern.BBox = rect
   265  
   266  	// XStep (required).
   267  	obj = dict.Get("XStep")
   268  	if obj == nil {
   269  		common.Log.Debug("XStep missing")
   270  		return nil, ErrRequiredAttributeMissing
   271  	}
   272  	xStep, err := getNumberAsFloat(obj)
   273  	if err != nil {
   274  		common.Log.Debug("Error getting XStep as float: %v", xStep)
   275  		return nil, err
   276  	}
   277  	pattern.XStep = MakeFloat(xStep)
   278  
   279  	// YStep (required).
   280  	obj = dict.Get("YStep")
   281  	if obj == nil {
   282  		common.Log.Debug("YStep missing")
   283  		return nil, ErrRequiredAttributeMissing
   284  	}
   285  	yStep, err := getNumberAsFloat(obj)
   286  	if err != nil {
   287  		common.Log.Debug("Error getting YStep as float: %v", yStep)
   288  		return nil, err
   289  	}
   290  	pattern.YStep = MakeFloat(yStep)
   291  
   292  	// Resources (required).
   293  	obj = dict.Get("Resources")
   294  	if obj == nil {
   295  		common.Log.Debug("Resources missing")
   296  		return nil, ErrRequiredAttributeMissing
   297  	}
   298  	dict, ok = TraceToDirectObject(obj).(*PdfObjectDictionary)
   299  	if !ok {
   300  		return nil, fmt.Errorf("Invalid resource dictionary (%T)", obj)
   301  	}
   302  	resources, err := NewPdfPageResourcesFromDict(dict)
   303  	if err != nil {
   304  		return nil, err
   305  	}
   306  	pattern.Resources = resources
   307  
   308  	// Matrix (optional).
   309  	if obj := dict.Get("Matrix"); obj != nil {
   310  		arr, ok := obj.(*PdfObjectArray)
   311  		if !ok {
   312  			common.Log.Debug("Matrix not an array (got %T)", obj)
   313  			return nil, ErrTypeError
   314  		}
   315  		pattern.Matrix = arr
   316  	}
   317  
   318  	return pattern, nil
   319  }
   320  
   321  // Load entries specific to a pdf shading pattern from a dictionary. Used in parsing/loading PDFs.
   322  func newPdfShadingPatternFromDictionary(dict *PdfObjectDictionary) (*PdfShadingPattern, error) {
   323  	pattern := &PdfShadingPattern{}
   324  
   325  	// Shading (required).
   326  	obj := dict.Get("Shading")
   327  	if obj == nil {
   328  		common.Log.Debug("Shading missing")
   329  		return nil, ErrRequiredAttributeMissing
   330  	}
   331  	shading, err := newPdfShadingFromPdfObject(obj)
   332  	if err != nil {
   333  		common.Log.Debug("Error loading shading: %v", err)
   334  		return nil, err
   335  	}
   336  	pattern.Shading = shading
   337  
   338  	// Matrix (optional).
   339  	if obj := dict.Get("Matrix"); obj != nil {
   340  		arr, ok := obj.(*PdfObjectArray)
   341  		if !ok {
   342  			common.Log.Debug("Matrix not an array (got %T)", obj)
   343  			return nil, ErrTypeError
   344  		}
   345  		pattern.Matrix = arr
   346  	}
   347  
   348  	// ExtGState (optional).
   349  	if obj := dict.Get("ExtGState"); obj != nil {
   350  		pattern.ExtGState = obj
   351  	}
   352  
   353  	return pattern, nil
   354  }
   355  
   356  /* Conversions to pdf objects. */
   357  
   358  func (this *PdfPattern) getDict() *PdfObjectDictionary {
   359  	if indObj, is := this.container.(*PdfIndirectObject); is {
   360  		dict, ok := indObj.PdfObject.(*PdfObjectDictionary)
   361  		if !ok {
   362  			return nil
   363  		}
   364  		return dict
   365  	} else if streamObj, is := this.container.(*PdfObjectStream); is {
   366  		return streamObj.PdfObjectDictionary
   367  	} else {
   368  		common.Log.Debug("Trying to access pattern dictionary of invalid object type (%T)", this.container)
   369  		return nil
   370  	}
   371  }
   372  
   373  func (this *PdfPattern) ToPdfObject() PdfObject {
   374  	d := this.getDict()
   375  	d.Set("Type", MakeName("Pattern"))
   376  	d.Set("PatternType", MakeInteger(this.PatternType))
   377  
   378  	return this.container
   379  }
   380  
   381  func (this *PdfTilingPattern) ToPdfObject() PdfObject {
   382  	this.PdfPattern.ToPdfObject()
   383  
   384  	d := this.getDict()
   385  	if this.PaintType != nil {
   386  		d.Set("PaintType", this.PaintType)
   387  	}
   388  	if this.TilingType != nil {
   389  		d.Set("TilingType", this.TilingType)
   390  	}
   391  	if this.BBox != nil {
   392  		d.Set("BBox", this.BBox.ToPdfObject())
   393  	}
   394  	if this.XStep != nil {
   395  		d.Set("XStep", this.XStep)
   396  	}
   397  	if this.YStep != nil {
   398  		d.Set("YStep", this.YStep)
   399  	}
   400  	if this.Resources != nil {
   401  		d.Set("Resources", this.Resources.ToPdfObject())
   402  	}
   403  	if this.Matrix != nil {
   404  		d.Set("Matrix", this.Matrix)
   405  	}
   406  
   407  	return this.container
   408  }
   409  
   410  func (this *PdfShadingPattern) ToPdfObject() PdfObject {
   411  	this.PdfPattern.ToPdfObject()
   412  	d := this.getDict()
   413  
   414  	if this.Shading != nil {
   415  		d.Set("Shading", this.Shading.ToPdfObject())
   416  	}
   417  	if this.Matrix != nil {
   418  		d.Set("Matrix", this.Matrix)
   419  	}
   420  	if this.ExtGState != nil {
   421  		d.Set("ExtGState", this.ExtGState)
   422  	}
   423  
   424  	return this.container
   425  }