namespace UglyToad.PdfPig.Functions { using System.Linq; using UglyToad.PdfPig.Core; using UglyToad.PdfPig.Tokens; /// /// This class represents a function in a PDF document. /// public abstract class PdfFunction { /// /// The function dictionary. /// public DictionaryToken? FunctionDictionary { get; } /// /// The function stream. /// public StreamToken? FunctionStream { get; } private int numberOfInputValues = -1; private int numberOfOutputValues = -1; /// /// This class represents a function in a PDF document. /// public PdfFunction(DictionaryToken function, ArrayToken domain, ArrayToken? range) { FunctionDictionary = function; DomainValues = domain; RangeValues = range; } /// /// This class represents a function in a PDF document. /// public PdfFunction(StreamToken function, ArrayToken domain, ArrayToken? range) { FunctionStream = function; DomainValues = domain; RangeValues = range; } /// /// Returns the function type. Possible values are: /// /// 0Sampled function /// 2Exponential interpolation function /// 3Stitching function /// 4PostScript calculator function /// /// /// the function type. public abstract FunctionTypes FunctionType { get; } /// /// Returns the function's dictionary. If is defined, it will be returned. /// If not, the 's StreamDictionary will be returned. /// public DictionaryToken? GetDictionary() { if (FunctionStream != null) { return FunctionStream.StreamDictionary; } else { return FunctionDictionary; } } /// /// This will get the number of output parameters that /// have a range specified. A range for output parameters /// is optional so this may return zero for a function /// that does have output parameters, this will simply return the /// number that have the range specified. /// /// The number of output parameters that have a range specified. public int NumberOfOutputParameters { get { if (numberOfOutputValues == -1) { if (RangeValues is null) { numberOfOutputValues = 0; } else { numberOfOutputValues = RangeValues.Length / 2; } } return numberOfOutputValues; } } /// /// This will get the range for a certain output parameters. This is will never /// return null. If it is not present then the range 0 to 0 will /// be returned. /// /// The output parameter number to get the range for. /// The range for this component. public PdfRange GetRangeForOutput(int n) { return new PdfRange(RangeValues!.Data.OfType().Select(t => t.Double), n); } /// /// This will get the number of input parameters that /// have a domain specified. /// /// The number of input parameters that have a domain specified. public int NumberOfInputParameters { get { if (numberOfInputValues == -1) { ArrayToken array = DomainValues; numberOfInputValues = array.Length / 2; } return numberOfInputValues; } } /// /// This will get the range for a certain input parameter. This is will never /// return null. If it is not present then the range 0 to 0 will /// be returned. /// /// The parameter number to get the domain for. /// The domain range for this component. public PdfRange GetDomainForInput(int n) { ArrayToken domainValues = DomainValues; return new PdfRange(domainValues.Data.OfType().Select(t => t.Double), n); } /// /// Evaluates the function at the given input. /// ReturnValue = f(input) /// /// The array of input values for the function. /// In many cases will be an array of a single value, but not always. /// The of outputs the function returns based on those inputs. /// In many cases will be an array of a single value, but not always. public abstract double[] Eval(params double[] input); /// /// Returns all ranges for the output values as . Required for type 0 and type 4 functions. /// /// the ranges array. protected ArrayToken? RangeValues { get; } /// /// Returns all domains for the input values as . Required for all function types. /// /// the domains array. private ArrayToken DomainValues { get; } /// /// Clip the given input values to the ranges. /// /// inputValues the input values /// the clipped values protected double[] ClipToRange(double[] inputValues) { ArrayToken rangesArray = RangeValues!; double[] result; if (rangesArray != null && rangesArray.Length > 0) { double[] rangeValues = rangesArray.Data.OfType().Select(t => t.Double).ToArray(); int numberOfRanges = rangeValues.Length / 2; result = new double[numberOfRanges]; for (int i = 0; i < numberOfRanges; i++) { int index = i << 1; result[i] = ClipToRange(inputValues[i], rangeValues[index], rangeValues[index + 1]); } } else { result = inputValues; } return result; } /// /// Clip the given input value to the given range. /// /// x the input value /// the min value of the range /// the max value of the range /// the clipped value public static double ClipToRange(double x, double rangeMin, double rangeMax) { if (x < rangeMin) { return rangeMin; } else if (x > rangeMax) { return rangeMax; } return x; } /// /// For a given value of x, interpolate calculates the y value /// on the line defined by the two points (xRangeMin, xRangeMax) /// and (yRangeMin, yRangeMax). /// /// the value to be interpolated value. /// the min value of the x range /// the max value of the x range /// the min value of the y range /// the max value of the y range /// the interpolated y value protected static double Interpolate(double x, double xRangeMin, double xRangeMax, double yRangeMin, double yRangeMax) { return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin) / (xRangeMax - xRangeMin)); } } /// /// Pdf function types. /// public enum FunctionTypes : byte { /// /// Sampled function. /// Sampled = 0, /// /// Exponential interpolation function. /// Exponential = 2, /// /// Stitching function. /// Stitching = 3, /// /// PostScript calculator function. /// PostScript = 4 } }