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  }