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 }