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 }