github.com/unidoc/unidoc@v2.2.0+incompatible/pdf/model/functions.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 "math" 11 12 "github.com/unidoc/unidoc/common" 13 . "github.com/unidoc/unidoc/pdf/core" 14 "github.com/unidoc/unidoc/pdf/model/sampling" 15 "github.com/unidoc/unidoc/pdf/ps" 16 ) 17 18 type PdfValue interface{} 19 20 type PdfFunction interface { 21 Evaluate([]float64) ([]float64, error) 22 ToPdfObject() PdfObject 23 } 24 25 // In PDF: A function object may be a dictionary or a stream, depending on the type of function. 26 // - Stream: Type 0, Type 4 27 // - Dictionary: Type 2, Type 3. 28 29 // Loads a PDF Function from a PdfObject (can be either stream or dictionary). 30 func newPdfFunctionFromPdfObject(obj PdfObject) (PdfFunction, error) { 31 if stream, is := obj.(*PdfObjectStream); is { 32 dict := stream.PdfObjectDictionary 33 34 ftype, ok := dict.Get("FunctionType").(*PdfObjectInteger) 35 if !ok { 36 common.Log.Error("FunctionType number missing") 37 return nil, errors.New("Invalid parameter or missing") 38 } 39 40 if *ftype == 0 { 41 return newPdfFunctionType0FromStream(stream) 42 } else if *ftype == 4 { 43 return newPdfFunctionType4FromStream(stream) 44 } else { 45 return nil, errors.New("Invalid function type") 46 } 47 } else if indObj, is := obj.(*PdfIndirectObject); is { 48 // Indirect object containing a dictionary. 49 // The indirect object is the container (which is tracked). 50 dict, ok := indObj.PdfObject.(*PdfObjectDictionary) 51 if !ok { 52 common.Log.Error("Function Indirect object not containing dictionary") 53 return nil, errors.New("Invalid parameter or missing") 54 } 55 56 ftype, ok := dict.Get("FunctionType").(*PdfObjectInteger) 57 if !ok { 58 common.Log.Error("FunctionType number missing") 59 return nil, errors.New("Invalid parameter or missing") 60 } 61 62 if *ftype == 2 { 63 return newPdfFunctionType2FromPdfObject(indObj) 64 } else if *ftype == 3 { 65 return newPdfFunctionType3FromPdfObject(indObj) 66 } else { 67 return nil, errors.New("Invalid function type") 68 } 69 } else if dict, is := obj.(*PdfObjectDictionary); is { 70 ftype, ok := dict.Get("FunctionType").(*PdfObjectInteger) 71 if !ok { 72 common.Log.Error("FunctionType number missing") 73 return nil, errors.New("Invalid parameter or missing") 74 } 75 76 if *ftype == 2 { 77 return newPdfFunctionType2FromPdfObject(dict) 78 } else if *ftype == 3 { 79 return newPdfFunctionType3FromPdfObject(dict) 80 } else { 81 return nil, errors.New("Invalid function type") 82 } 83 } else { 84 common.Log.Debug("Function Type error: %#v", obj) 85 return nil, errors.New("Type error") 86 } 87 } 88 89 // Simple linear interpolation from the PDF manual. 90 func interpolate(x, xmin, xmax, ymin, ymax float64) float64 { 91 if math.Abs(xmax-xmin) < 0.000001 { 92 return ymin 93 } 94 95 y := ymin + (x-xmin)*(ymax-ymin)/(xmax-xmin) 96 return y 97 } 98 99 // 100 // Type 0 functions use a sequence of sample values (contained in a stream) to provide an approximation 101 // for functions whose domains and ranges are bounded. The samples are organized as an m-dimensional 102 // table in which each entry has n components 103 // 104 type PdfFunctionType0 struct { 105 Domain []float64 // required; 2*m length; where m is the number of input values 106 Range []float64 // required (type 0); 2*n length; where n is the number of output values 107 108 NumInputs int 109 NumOutputs int 110 111 Size []int 112 BitsPerSample int 113 Order int // Values 1 or 3 (linear or cubic spline interpolation) 114 Encode []float64 115 Decode []float64 116 117 rawData []byte 118 data []uint32 119 120 container *PdfObjectStream 121 } 122 123 // Construct the PDF function object from a stream object (typically loaded from a PDF file). 124 func newPdfFunctionType0FromStream(stream *PdfObjectStream) (*PdfFunctionType0, error) { 125 fun := &PdfFunctionType0{} 126 127 fun.container = stream 128 129 dict := stream.PdfObjectDictionary 130 131 // Domain 132 array, has := TraceToDirectObject(dict.Get("Domain")).(*PdfObjectArray) 133 if !has { 134 common.Log.Error("Domain not specified") 135 return nil, errors.New("Required attribute missing or invalid") 136 } 137 if len(*array) < 0 || len(*array)%2 != 0 { 138 common.Log.Error("Domain invalid") 139 return nil, errors.New("Invalid domain range") 140 } 141 fun.NumInputs = len(*array) / 2 142 domain, err := array.ToFloat64Array() 143 if err != nil { 144 return nil, err 145 } 146 fun.Domain = domain 147 148 // Range 149 array, has = TraceToDirectObject(dict.Get("Range")).(*PdfObjectArray) 150 if !has { 151 common.Log.Error("Range not specified") 152 return nil, errors.New("Required attribute missing or invalid") 153 } 154 if len(*array) < 0 || len(*array)%2 != 0 { 155 return nil, errors.New("Invalid range") 156 } 157 fun.NumOutputs = len(*array) / 2 158 rang, err := array.ToFloat64Array() 159 if err != nil { 160 return nil, err 161 } 162 fun.Range = rang 163 164 // Number of samples in each input dimension 165 array, has = TraceToDirectObject(dict.Get("Size")).(*PdfObjectArray) 166 if !has { 167 common.Log.Error("Size not specified") 168 return nil, errors.New("Required attribute missing or invalid") 169 } 170 tablesize, err := array.ToIntegerArray() 171 if err != nil { 172 return nil, err 173 } 174 if len(tablesize) != fun.NumInputs { 175 common.Log.Error("Table size not matching number of inputs") 176 return nil, errors.New("Range check") 177 } 178 fun.Size = tablesize 179 180 // BitsPerSample 181 bps, has := TraceToDirectObject(dict.Get("BitsPerSample")).(*PdfObjectInteger) 182 if !has { 183 common.Log.Error("BitsPerSample not specified") 184 return nil, errors.New("Required attribute missing or invalid") 185 } 186 if *bps != 1 && *bps != 2 && *bps != 4 && *bps != 8 && *bps != 12 && *bps != 16 && *bps != 24 && *bps != 32 { 187 common.Log.Error("Bits per sample outside range (%d)", *bps) 188 return nil, errors.New("Range check") 189 } 190 fun.BitsPerSample = int(*bps) 191 192 fun.Order = 1 193 order, has := TraceToDirectObject(dict.Get("Order")).(*PdfObjectInteger) 194 if has { 195 if *order != 1 && *order != 3 { 196 common.Log.Error("Invalid order (%d)", *order) 197 return nil, errors.New("Range check") 198 } 199 fun.Order = int(*order) 200 } 201 202 // Encode: is a 2*m array specifying the linear mapping of input values into the domain of the function's 203 // sample table. 204 array, has = TraceToDirectObject(dict.Get("Encode")).(*PdfObjectArray) 205 if has { 206 encode, err := array.ToFloat64Array() 207 if err != nil { 208 return nil, err 209 } 210 fun.Encode = encode 211 } 212 213 // Decode 214 array, has = TraceToDirectObject(dict.Get("Decode")).(*PdfObjectArray) 215 if has { 216 decode, err := array.ToFloat64Array() 217 if err != nil { 218 return nil, err 219 } 220 fun.Decode = decode 221 } 222 223 data, err := DecodeStream(stream) 224 if err != nil { 225 return nil, err 226 } 227 fun.rawData = data 228 229 return fun, nil 230 } 231 232 func (this *PdfFunctionType0) ToPdfObject() PdfObject { 233 container := this.container 234 if container != nil { 235 this.container = &PdfObjectStream{} 236 } 237 238 dict := MakeDict() 239 dict.Set("FunctionType", MakeInteger(0)) 240 241 // Domain (required). 242 domainArray := &PdfObjectArray{} 243 for _, val := range this.Domain { 244 domainArray.Append(MakeFloat(val)) 245 } 246 dict.Set("Domain", domainArray) 247 248 // Range (required). 249 rangeArray := &PdfObjectArray{} 250 for _, val := range this.Range { 251 rangeArray.Append(MakeFloat(val)) 252 } 253 dict.Set("Range", rangeArray) 254 255 // Size (required). 256 sizeArray := &PdfObjectArray{} 257 for _, val := range this.Size { 258 sizeArray.Append(MakeInteger(int64(val))) 259 } 260 dict.Set("Size", sizeArray) 261 262 dict.Set("BitsPerSample", MakeInteger(int64(this.BitsPerSample))) 263 264 if this.Order != 1 { 265 dict.Set("Order", MakeInteger(int64(this.Order))) 266 } 267 268 // TODO: Encode. 269 // Either here, or automatically later on when writing out. 270 dict.Set("Length", MakeInteger(int64(len(this.rawData)))) 271 container.Stream = this.rawData 272 273 container.PdfObjectDictionary = dict 274 return container 275 } 276 277 func (this *PdfFunctionType0) Evaluate(x []float64) ([]float64, error) { 278 if len(x) != this.NumInputs { 279 common.Log.Error("Number of inputs not matching what is needed") 280 return nil, errors.New("Range check error") 281 } 282 283 if this.data == nil { 284 // Process the samples if not already done. 285 err := this.processSamples() 286 if err != nil { 287 return nil, err 288 } 289 } 290 291 // Fall back to default Encode/Decode params if not set. 292 encode := this.Encode 293 if encode == nil { 294 encode = []float64{} 295 for i := 0; i < len(this.Size); i++ { 296 encode = append(encode, 0) 297 encode = append(encode, float64(this.Size[i]-1)) 298 } 299 } 300 decode := this.Decode 301 if decode == nil { 302 decode = this.Range 303 } 304 305 indices := []int{} 306 // Start with nearest neighbour interpolation. 307 for i := 0; i < len(x); i++ { 308 xi := x[i] 309 310 xip := math.Min(math.Max(xi, this.Domain[2*i]), this.Domain[2*i+1]) 311 312 ei := interpolate(xip, this.Domain[2*i], this.Domain[2*i+1], encode[2*i], encode[2*i+1]) 313 eip := math.Min(math.Max(ei, 0), float64(this.Size[i])) 314 // eip represents coordinate into the data table. 315 // At this point it is real values. 316 317 // Interpolation shall be used to to determine output values 318 // from the nearest surrounding values in the sample table. 319 320 // Initial implementation is simply nearest neighbour. 321 // Then will add the linear and possibly bicubic/spline. 322 index := int(math.Floor(eip + 0.5)) 323 if index < 0 { 324 index = 0 325 } else if index > this.Size[i] { 326 index = this.Size[i] - 1 327 } 328 indices = append(indices, index) 329 330 } 331 332 // Calculate the index 333 m := indices[0] 334 for i := 1; i < this.NumInputs; i++ { 335 add := indices[i] 336 for j := 0; j < i; j++ { 337 add *= this.Size[j] 338 } 339 m += add 340 } 341 m *= this.NumOutputs 342 343 // Output values. 344 outputs := []float64{} 345 for j := 0; j < this.NumOutputs; j++ { 346 rj := this.data[m+j] 347 rjp := interpolate(float64(rj), 0, math.Pow(2, float64(this.BitsPerSample)), decode[2*j], decode[2*j+1]) 348 yj := math.Min(math.Max(rjp, this.Range[2*j]), this.Range[2*j+1]) 349 outputs = append(outputs, yj) 350 } 351 352 return outputs, nil 353 } 354 355 // Convert raw data to data table. The maximum supported BitsPerSample is 32, so we store the resulting data 356 // in a uint32 array. This is somewhat wasteful in the case of a small BitsPerSample, but these tables are 357 // presumably not huge at any rate. 358 func (this *PdfFunctionType0) processSamples() error { 359 data := sampling.ResampleBytes(this.rawData, this.BitsPerSample) 360 this.data = data 361 362 return nil 363 } 364 365 // 366 // Type 2 functions define an exponential interpolation of one input value and n 367 // output values: 368 // f(x) = y_0, ..., y_(n-1) 369 // y_j = C0_j + x^N * (C1_j - C0_j); for 0 <= j < n 370 // When N=1 ; linear interpolation between C0 and C1. 371 // 372 type PdfFunctionType2 struct { 373 Domain []float64 374 Range []float64 375 376 C0 []float64 377 C1 []float64 378 N float64 379 380 container *PdfIndirectObject 381 } 382 383 // Can be either indirect object or dictionary. If indirect, then must be holding a dictionary, 384 // i.e. acting as a container. When converting back to pdf object, will use the container provided. 385 386 func newPdfFunctionType2FromPdfObject(obj PdfObject) (*PdfFunctionType2, error) { 387 fun := &PdfFunctionType2{} 388 389 var dict *PdfObjectDictionary 390 if indObj, is := obj.(*PdfIndirectObject); is { 391 d, ok := indObj.PdfObject.(*PdfObjectDictionary) 392 if !ok { 393 return nil, errors.New("Type check error") 394 } 395 fun.container = indObj 396 dict = d 397 } else if d, is := obj.(*PdfObjectDictionary); is { 398 dict = d 399 } else { 400 return nil, errors.New("Type check error") 401 } 402 403 common.Log.Trace("FUNC2: %s", dict.String()) 404 405 // Domain 406 array, has := TraceToDirectObject(dict.Get("Domain")).(*PdfObjectArray) 407 if !has { 408 common.Log.Error("Domain not specified") 409 return nil, errors.New("Required attribute missing or invalid") 410 } 411 if len(*array) < 0 || len(*array)%2 != 0 { 412 common.Log.Error("Domain range invalid") 413 return nil, errors.New("Invalid domain range") 414 } 415 domain, err := array.ToFloat64Array() 416 if err != nil { 417 return nil, err 418 } 419 fun.Domain = domain 420 421 // Range 422 array, has = TraceToDirectObject(dict.Get("Range")).(*PdfObjectArray) 423 if has { 424 if len(*array) < 0 || len(*array)%2 != 0 { 425 return nil, errors.New("Invalid range") 426 } 427 428 rang, err := array.ToFloat64Array() 429 if err != nil { 430 return nil, err 431 } 432 fun.Range = rang 433 } 434 435 // C0. 436 array, has = TraceToDirectObject(dict.Get("C0")).(*PdfObjectArray) 437 if has { 438 c0, err := array.ToFloat64Array() 439 if err != nil { 440 return nil, err 441 } 442 fun.C0 = c0 443 } 444 445 // C1. 446 array, has = TraceToDirectObject(dict.Get("C1")).(*PdfObjectArray) 447 if has { 448 c1, err := array.ToFloat64Array() 449 if err != nil { 450 return nil, err 451 } 452 fun.C1 = c1 453 } 454 455 if len(fun.C0) != len(fun.C1) { 456 common.Log.Error("C0 and C1 not matching") 457 return nil, errors.New("Range check") 458 } 459 460 // Exponent. 461 N, err := getNumberAsFloat(TraceToDirectObject(dict.Get("N"))) 462 if err != nil { 463 common.Log.Error("N missing or invalid, dict: %s", dict.String()) 464 return nil, err 465 } 466 fun.N = N 467 468 return fun, nil 469 } 470 471 func (this *PdfFunctionType2) ToPdfObject() PdfObject { 472 dict := MakeDict() 473 474 dict.Set("FunctionType", MakeInteger(2)) 475 476 // Domain (required). 477 domainArray := &PdfObjectArray{} 478 for _, val := range this.Domain { 479 domainArray.Append(MakeFloat(val)) 480 } 481 dict.Set("Domain", domainArray) 482 483 // Range (required). 484 if this.Range != nil { 485 rangeArray := &PdfObjectArray{} 486 for _, val := range this.Range { 487 rangeArray.Append(MakeFloat(val)) 488 } 489 dict.Set("Range", rangeArray) 490 } 491 492 // C0. 493 if this.C0 != nil { 494 c0Array := &PdfObjectArray{} 495 for _, val := range this.C0 { 496 c0Array.Append(MakeFloat(val)) 497 } 498 dict.Set("C0", c0Array) 499 } 500 501 // C1. 502 if this.C1 != nil { 503 c1Array := &PdfObjectArray{} 504 for _, val := range this.C1 { 505 c1Array.Append(MakeFloat(val)) 506 } 507 dict.Set("C1", c1Array) 508 } 509 510 // exponent 511 dict.Set("N", MakeFloat(this.N)) 512 513 // Wrap in a container if we have one already specified. 514 if this.container != nil { 515 this.container.PdfObject = dict 516 return this.container 517 } else { 518 return dict 519 } 520 521 } 522 523 func (this *PdfFunctionType2) Evaluate(x []float64) ([]float64, error) { 524 if len(x) != 1 { 525 common.Log.Error("Only one input allowed") 526 return nil, errors.New("Range check") 527 } 528 529 // Prepare. 530 c0 := []float64{0.0} 531 if this.C0 != nil { 532 c0 = this.C0 533 } 534 c1 := []float64{1.0} 535 if this.C1 != nil { 536 c1 = this.C1 537 } 538 539 y := []float64{} 540 for i := 0; i < len(c0); i++ { 541 yi := c0[i] + math.Pow(x[0], this.N)*(c1[i]-c0[i]) 542 y = append(y, yi) 543 } 544 545 return y, nil 546 } 547 548 // 549 // Type 3 functions define stitching of the subdomains of serveral 1-input functions to produce 550 // a single new 1-input function. 551 // 552 type PdfFunctionType3 struct { 553 Domain []float64 554 Range []float64 555 556 Functions []PdfFunction // k-1 input functions 557 Bounds []float64 // k-1 numbers; defines the intervals where each function applies 558 Encode []float64 // Array of 2k numbers.. 559 560 container *PdfIndirectObject 561 } 562 563 func (this *PdfFunctionType3) Evaluate(x []float64) ([]float64, error) { 564 if len(x) != 1 { 565 common.Log.Error("Only one input allowed") 566 return nil, errors.New("Range check") 567 } 568 569 // Determine which function to use 570 571 // Encode 572 573 return nil, errors.New("Not implemented yet") 574 } 575 576 func newPdfFunctionType3FromPdfObject(obj PdfObject) (*PdfFunctionType3, error) { 577 fun := &PdfFunctionType3{} 578 579 var dict *PdfObjectDictionary 580 if indObj, is := obj.(*PdfIndirectObject); is { 581 d, ok := indObj.PdfObject.(*PdfObjectDictionary) 582 if !ok { 583 return nil, errors.New("Type check error") 584 } 585 fun.container = indObj 586 dict = d 587 } else if d, is := obj.(*PdfObjectDictionary); is { 588 dict = d 589 } else { 590 return nil, errors.New("Type check error") 591 } 592 593 // Domain 594 array, has := TraceToDirectObject(dict.Get("Domain")).(*PdfObjectArray) 595 if !has { 596 common.Log.Error("Domain not specified") 597 return nil, errors.New("Required attribute missing or invalid") 598 } 599 if len(*array) != 2 { 600 common.Log.Error("Domain invalid") 601 return nil, errors.New("Invalid domain range") 602 } 603 domain, err := array.ToFloat64Array() 604 if err != nil { 605 return nil, err 606 } 607 fun.Domain = domain 608 609 // Range 610 array, has = TraceToDirectObject(dict.Get("Range")).(*PdfObjectArray) 611 if has { 612 if len(*array) < 0 || len(*array)%2 != 0 { 613 return nil, errors.New("Invalid range") 614 } 615 rang, err := array.ToFloat64Array() 616 if err != nil { 617 return nil, err 618 } 619 fun.Range = rang 620 } 621 622 // Functions. 623 array, has = TraceToDirectObject(dict.Get("Functions")).(*PdfObjectArray) 624 if !has { 625 common.Log.Error("Functions not specified") 626 return nil, errors.New("Required attribute missing or invalid") 627 } 628 fun.Functions = []PdfFunction{} 629 for _, obj := range *array { 630 subf, err := newPdfFunctionFromPdfObject(obj) 631 if err != nil { 632 return nil, err 633 } 634 fun.Functions = append(fun.Functions, subf) 635 } 636 637 // Bounds 638 array, has = TraceToDirectObject(dict.Get("Bounds")).(*PdfObjectArray) 639 if !has { 640 common.Log.Error("Bounds not specified") 641 return nil, errors.New("Required attribute missing or invalid") 642 } 643 bounds, err := array.ToFloat64Array() 644 if err != nil { 645 return nil, err 646 } 647 fun.Bounds = bounds 648 if len(fun.Bounds) != len(fun.Functions)-1 { 649 common.Log.Error("Bounds (%d) and num functions (%d) not matching", len(fun.Bounds), len(fun.Functions)) 650 return nil, errors.New("Range check") 651 } 652 653 // Encode. 654 array, has = TraceToDirectObject(dict.Get("Encode")).(*PdfObjectArray) 655 if !has { 656 common.Log.Error("Encode not specified") 657 return nil, errors.New("Required attribute missing or invalid") 658 } 659 encode, err := array.ToFloat64Array() 660 if err != nil { 661 return nil, err 662 } 663 fun.Encode = encode 664 if len(fun.Encode) != 2*len(fun.Functions) { 665 common.Log.Error("Len encode (%d) and num functions (%d) not matching up", len(fun.Encode), len(fun.Functions)) 666 return nil, errors.New("Range check") 667 } 668 669 return fun, nil 670 } 671 672 func (this *PdfFunctionType3) ToPdfObject() PdfObject { 673 dict := MakeDict() 674 675 dict.Set("FunctionType", MakeInteger(3)) 676 677 // Domain (required). 678 domainArray := &PdfObjectArray{} 679 for _, val := range this.Domain { 680 domainArray.Append(MakeFloat(val)) 681 } 682 dict.Set("Domain", domainArray) 683 684 // Range (required). 685 if this.Range != nil { 686 rangeArray := &PdfObjectArray{} 687 for _, val := range this.Range { 688 rangeArray.Append(MakeFloat(val)) 689 } 690 dict.Set("Range", rangeArray) 691 } 692 693 // Functions 694 if this.Functions != nil { 695 fArray := &PdfObjectArray{} 696 for _, fun := range this.Functions { 697 fArray.Append(fun.ToPdfObject()) 698 } 699 dict.Set("Functions", fArray) 700 } 701 702 // Bounds. 703 if this.Bounds != nil { 704 bArray := &PdfObjectArray{} 705 for _, val := range this.Bounds { 706 bArray.Append(MakeFloat(val)) 707 } 708 dict.Set("Bounds", bArray) 709 } 710 711 // Encode. 712 if this.Encode != nil { 713 eArray := &PdfObjectArray{} 714 for _, val := range this.Encode { 715 eArray.Append(MakeFloat(val)) 716 } 717 dict.Set("Encode", eArray) 718 } 719 720 // Wrap in a container if we have one already specified. 721 if this.container != nil { 722 this.container.PdfObject = dict 723 return this.container 724 } else { 725 return dict 726 } 727 } 728 729 // 730 // Type 4. Postscript calculator functions. 731 // 732 type PdfFunctionType4 struct { 733 Domain []float64 734 Range []float64 735 Program *ps.PSProgram 736 737 executor *ps.PSExecutor 738 decodedData []byte 739 740 container *PdfObjectStream 741 } 742 743 // Input [x1 x2 x3] 744 func (this *PdfFunctionType4) Evaluate(xVec []float64) ([]float64, error) { 745 if this.executor == nil { 746 this.executor = ps.NewPSExecutor(this.Program) 747 } 748 749 inputs := []ps.PSObject{} 750 for _, val := range xVec { 751 inputs = append(inputs, ps.MakeReal(val)) 752 } 753 754 outputs, err := this.executor.Execute(inputs) 755 if err != nil { 756 return nil, err 757 } 758 759 // After execution the outputs are on the stack [y1 ... yM] 760 // Convert to floats. 761 yVec, err := ps.PSObjectArrayToFloat64Array(outputs) 762 if err != nil { 763 return nil, err 764 } 765 766 return yVec, nil 767 } 768 769 // Load a type 4 function from a PDF stream object. 770 func newPdfFunctionType4FromStream(stream *PdfObjectStream) (*PdfFunctionType4, error) { 771 fun := &PdfFunctionType4{} 772 773 fun.container = stream 774 775 dict := stream.PdfObjectDictionary 776 777 // Domain 778 array, has := TraceToDirectObject(dict.Get("Domain")).(*PdfObjectArray) 779 if !has { 780 common.Log.Error("Domain not specified") 781 return nil, errors.New("Required attribute missing or invalid") 782 } 783 if len(*array)%2 != 0 { 784 common.Log.Error("Domain invalid") 785 return nil, errors.New("Invalid domain range") 786 } 787 domain, err := array.ToFloat64Array() 788 if err != nil { 789 return nil, err 790 } 791 fun.Domain = domain 792 793 // Range 794 array, has = TraceToDirectObject(dict.Get("Range")).(*PdfObjectArray) 795 if has { 796 if len(*array) < 0 || len(*array)%2 != 0 { 797 return nil, errors.New("Invalid range") 798 } 799 rang, err := array.ToFloat64Array() 800 if err != nil { 801 return nil, err 802 } 803 fun.Range = rang 804 } 805 806 // Program. Decode the program and parse the PS code. 807 decoded, err := DecodeStream(stream) 808 if err != nil { 809 return nil, err 810 } 811 fun.decodedData = decoded 812 813 psParser := ps.NewPSParser([]byte(decoded)) 814 prog, err := psParser.Parse() 815 if err != nil { 816 return nil, err 817 } 818 fun.Program = prog 819 820 return fun, nil 821 } 822 823 func (this *PdfFunctionType4) ToPdfObject() PdfObject { 824 container := this.container 825 if container == nil { 826 this.container = &PdfObjectStream{} 827 container = this.container 828 } 829 830 dict := MakeDict() 831 dict.Set("FunctionType", MakeInteger(4)) 832 833 // Domain (required). 834 domainArray := &PdfObjectArray{} 835 for _, val := range this.Domain { 836 domainArray.Append(MakeFloat(val)) 837 } 838 dict.Set("Domain", domainArray) 839 840 // Range (required). 841 rangeArray := &PdfObjectArray{} 842 for _, val := range this.Range { 843 rangeArray.Append(MakeFloat(val)) 844 } 845 dict.Set("Range", rangeArray) 846 847 if this.decodedData == nil && this.Program != nil { 848 // Update data. This is used for created functions (not parsed ones). 849 this.decodedData = []byte(this.Program.String()) 850 } 851 852 // TODO: Encode. 853 // Either here, or automatically later on when writing out. 854 dict.Set("Length", MakeInteger(int64(len(this.decodedData)))) 855 856 container.Stream = this.decodedData 857 container.PdfObjectDictionary = dict 858 859 return container 860 }