iPDFdev Tips & Tricks for PDF development

IFXPDFFactory – part 3 – PDF functions

September 03rd, 2012

I have finished implementing support for PDF functions. Usually they are a niche feature in PDF libraries and you might wonder why did I choose to implement them in such an early stage of the development. The answer is they are heavily used for defining separation colorspaces (does Pantone colors ring a bell?), PDF smooth shadings (gradients) and for other features.

Functions in PDF represent static, self-contained numerical transformations. In general, a function can take any number (m) of input values and produce any number (n) of output values. In PDF functions, all the input values and all the output values are numbers, and functions have no side effects.

Each function definition includes a domain, the set of legal values for the input. Some types of functions also define a range, the set of legal values for the output. Input values passed to the function are clipped to the domain, and output values produced by the function are clipped to the range.

The following function types are supported in PDF:

  • sampled functions (type 0)
  • exponential interpolation functions (type 2)
  • stitching functions (type 3)
  • PostScript calculator functions (type 4)

The type value is the value of the /FunctionType key in function dictionary.

The base class for all PDF functions is IFXPDFFunction class. It includes properties that are common to all functions: domain and range.

Sampled functions (Type 0)

Sampled functions use a table of sample values to define the function. Various techniques are used to interpolate values between the sample values. The samples are organized as an m-dimensional table in which each entry has n components.

The IFXPDFSampledFunction class is used to represent sampled functions. Creating a sampled function looks like this:

    IFXPDFSampledFunction* sampledFunction = [[IFXPDFSampledFunction alloc] init];
    double domain[] = { -1, 1, -1, 1 };
    sampledFunction.domain = [IFXPDFNumberArray arrayWithDoubles: domain length: 4];
    double size[] = { 21, 31 };
    sampledFunction.size = [IFXPDFNumberArray arrayWithDoubles: size length: 2];
    double encode[] = { 0, 20, 0, 30 };
    sampledFunction.encode = [IFXPDFNumberArray arrayWithDoubles: encode length: 4];
    sampledFunction.bitsPerSample = 4;
    double range[] = { -1, 1 };
    sampledFunction.range = [IFXPDFNumberArray arrayWithDoubles: range length: 2];
    double decode[] = { -1, 1 };
    sampledFunction.decode = [IFXPDFNumberArray arrayWithDoubles: decode length: 2];
    char samples[326];
    sampledFunction.samples = [NSData dataWithBytes: samples length: 326];

The function takes 2 arguments, x and y, both in the domain [-1.0 1.0]. Each pair in the domain array defines a domain for a function argument. The function returns a single value z in the range [-1.0 1.0]. The x argument is linearly transformed by the Encode property to the domain [0 20] and the y argument to the domain [0 30]. Using bilinear interpolation between sample points, the function computes a value for z, which (because BitsPerSample is 4) will be in the range [0 15], and the decoding transforms z to a number in the range [−1.0 1.0] for the result. The size of the samples array is 31 rows by 21 samples/row. At 4 bits per sample they take 326 bytes.

Exponential interpolation functions (Type 2)

Type 2 functions define an exponential interpolation of one input value and n output values.

The IFXPDFExponentialFunction class is used to represent type 2 functions. Creating an exponential function looks like this:

    IFXPDFExponentialFunction* exponentialFunction = [[IFXPDFExponentialFunction alloc] init];
    double domain[] = { 0, 1 };
    exponentialFunction.domain = [IFXPDFNumberArray arrayWithDoubles: domain length: 2];
    double range[] = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 };
    exponentialFunction.range = [IFXPDFNumberArray arrayWithDoubles: range length: 8];
    double c0[] = { 0, 0, 0, 0 };
    exponentialFunction.c0 = [IFXPDFNumberArray arrayWithDoubles: c0 length: 4];
    double c1[] = { 0, 0, 0, 0 };
    exponentialFunction.c1 = [IFXPDFNumberArray arrayWithDoubles: c1 length: 4];
    exponentialFunction.exponent = 1;

The function takes an argument x in the domain [0 1] and returns 4 values, each one in the range [0 1]. The C0 array defines the function value for x = 0 and the C1 array defines the function value for x = 1. The interpolation exponent is used for computing the output values like this: yj = C0j + x^exponent × (C1j − C0j ) where 0 ≤ j < n.

Stitching functions (Type 3)

Type 3 functions define a “stitching” of the subdomains of several 1-input functions to produce a single new 1-input function. Since the resulting stitching function is a 1-input function, the domain is given by a two-element array, [Domain0 Domain1].

The IFXPDFStitchingFunction class is used to represent type 3 functions. Creating a stitching function looks like this:

    IFXPDFStitchingFunction* stitchingFunction = [[IFXPDFStitchingFunction alloc] init];
    double domain[] = { 0, 1 };
    stitchingFunction.domain = [IFXPDFNumberArray arrayWithDoubles: domain length: 2];
    double range[] = { 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 };
    stitchingFunction.range = [IFXPDFNumberArray arrayWithDoubles: range length: 8];
    double bounds[] = { 0.5 };
    stitchingFunction.bounds = [IFXPDFNumberArray arrayWithDoubles: bounds length: 1];
    [stitchingFunction.functions addFunction: exponentialFunction];
    [stitchingFunction.functions addFunction: exponentialFunction2];

The functions property stores the collections of functions that define the stitching function. The bounds property is an array of k − 1 numbers (where k is the number of functions in functions collection) that, in combination with Domain, define the intervals to which each function from the functions array applies. Bounds elements must be in order of increasing value and each value must be within the domain defined by domain.
Domain0 < Bounds 0 < Bounds 1 <… ... < Bounds k – 2 < Domain1

PostScript calculator functions (Type 4)

A PostScript calculator function is represented as a stream containing code written in a small subset of the PostScript language. These functions offer greater flexibility in defining functions compared to sampled and exponential functions.

The IFXPDFPostScriptFunction class is used to represent type 4 functions. Creating a PostScript function looks like this:

    IFXPDFPostScriptFunction* postScriptFunction = [[IFXPDFPostScriptFunction alloc] init];
    double domain[] = { -1, 1, -1, 1 };
    postScriptFunction.domain = [IFXPDFNumberArray arrayWithDoubles: domain length: 4];
    double range[] = { -1, 1 };
    postScriptFunction.range = [IFXPDFNumberArray arrayWithDoubles: range length: 2];
    char* postScriptCode = "{ 360 mul sin 2 div exch 360 mul sin 2 div add }";
    postScriptFunction.postScript = [NSData dataWithBytes: postScriptCode length: sizeof(postScriptCode)];

The function arguments represent the initial operand stack, the items remaining on the operand stack after execution of the function are the output values.

The PDF ISO standard (PDF 32000-1:2008) presents the PDF functions in chapter 7.10. You can find there much more details and how to use them more effective.

Comments (7) Trackbacks (0)
  1. Hi,

    This sounds great. Any new regarding when your lib will be available?

    Thanks

  2. Will IFXPDFFactory convert from iWork/Office formats to PDF? If not, can you suggest a good way to do this on an iOS device?

  3. You’re doing a great job.

    how to add a URI annotation for a PDF file?
    thanks.


Cancel reply

No trackbacks yet.