mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-04-05 20:55:01 +08:00
Merge pull request #557 from UglyToad/functions
Implement pdf functions and add type 0, 2 and 4 function tests
This commit is contained in:
commit
52b99b6816
90
src/UglyToad.PdfPig.Core/PdfRange.cs
Normal file
90
src/UglyToad.PdfPig.Core/PdfRange.cs
Normal file
@ -0,0 +1,90 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// This class will be used to signify a range. a(min) <= a* <= a(max)
|
||||
/// </summary>
|
||||
public struct PdfRange
|
||||
{
|
||||
private readonly IReadOnlyList<double> rangeArray;
|
||||
private readonly int startingIndex;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with an initial range of 0..1.
|
||||
/// </summary>
|
||||
public PdfRange()
|
||||
{
|
||||
rangeArray = new double[] { 0.0, 1.0 };
|
||||
startingIndex = 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor assumes a starting index of 0.
|
||||
/// </summary>
|
||||
/// <param name="range">The array that describes the range.</param>
|
||||
public PdfRange(IEnumerable<decimal> range)
|
||||
: this(range.Select(v => (double)v), 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with an index into an array. Because some arrays specify
|
||||
/// multiple ranges ie [0, 1, 0, 2, 2, 3]. It is convenient for this
|
||||
/// class to take an index into an array. So if you want this range to
|
||||
/// represent 0, 2 in the above example then you would say <c>new PDRange(array, 1)</c>.
|
||||
/// </summary>
|
||||
/// <param name="range">The array that describes the index</param>
|
||||
/// <param name="index">The range index into the array for the start of the range.</param>
|
||||
public PdfRange(IEnumerable<decimal> range, int index)
|
||||
: this(range.Select(v => (double)v), index)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor assumes a starting index of 0.
|
||||
/// </summary>
|
||||
/// <param name="range">The array that describes the range.</param>
|
||||
public PdfRange(IEnumerable<double> range)
|
||||
: this(range, 0)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor with an index into an array. Because some arrays specify
|
||||
/// multiple ranges ie [0, 1, 0, 2, 2, 3]. It is convenient for this
|
||||
/// class to take an index into an array. So if you want this range to
|
||||
/// represent 0, 2 in the above example then you would say <c>new PDRange(array, 1)</c>.
|
||||
/// </summary>
|
||||
/// <param name="range">The array that describes the index</param>
|
||||
/// <param name="index">The range index into the array for the start of the range.</param>
|
||||
public PdfRange(IEnumerable<double> range, int index)
|
||||
{
|
||||
rangeArray = range.Select(v => (double)v).ToArray();
|
||||
startingIndex = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The minimum value of the range.
|
||||
/// </summary>
|
||||
public double Min
|
||||
{
|
||||
get
|
||||
{
|
||||
return rangeArray[startingIndex * 2];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum value of the range.
|
||||
/// </summary>
|
||||
public double Max
|
||||
{
|
||||
get
|
||||
{
|
||||
return rangeArray[startingIndex * 2 + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
200
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType0Tests.cs
Normal file
200
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType0Tests.cs
Normal file
@ -0,0 +1,200 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class PdfFunctionType0Tests
|
||||
{
|
||||
private static ArrayToken GetArrayToken(params double[] data)
|
||||
{
|
||||
return new ArrayToken(data.Select(v => new NumericToken((decimal)v)).ToArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TIKA_1228_0()
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(0) },
|
||||
{ NameToken.Domain, GetArrayToken(0, 1) },
|
||||
{ NameToken.Range, GetArrayToken(0, 1, 0, 1, 0, 1, 0, 1) },
|
||||
|
||||
{ NameToken.BitsPerSample, new NumericToken(8) },
|
||||
{ NameToken.Decode, GetArrayToken(0, 1, 0, 1, 0, 1, 0, 1) },
|
||||
{ NameToken.Encode, GetArrayToken(0, 254) },
|
||||
{ NameToken.Size, GetArrayToken(255) }
|
||||
});
|
||||
|
||||
byte[] data = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 4, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 7, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 10, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 13, 0, 0, 0, 14, 0, 0, 0, 15, 0, 0, 0, 16, 0, 0, 0, 17, 0, 0, 0, 18, 0, 0, 0, 19, 0, 0, 0, 20, 0, 0, 0, 21, 0, 0, 0, 22, 0, 0, 0, 23, 0, 0, 0, 24, 0, 0, 0, 25, 0, 0, 0, 26, 0, 0, 0, 27, 0, 0, 0, 28, 0, 0, 0, 29, 0, 0, 0, 30, 0, 0, 0, 31, 0, 0, 0, 32, 0, 0, 0, 33, 0, 0, 0, 34, 0, 0, 0, 35, 0, 0, 0, 36, 0, 0, 0, 37, 0, 0, 0, 38, 0, 0, 0, 39, 0, 0, 0, 40, 0, 0, 0, 41, 0, 0, 0, 42, 0, 0, 0, 43, 0, 0, 0, 44, 0, 0, 0, 45, 0, 0, 0, 46, 0, 0, 0, 47, 0, 0, 0, 48, 0, 0, 0, 49, 0, 0, 0, 50, 0, 0, 0, 51, 0, 0, 0, 52, 0, 0, 0, 53, 0, 0, 0, 54, 0, 0, 0, 55, 0, 0, 0, 56, 0, 0, 0, 57, 0, 0, 0, 58, 0, 0, 0, 59, 0, 0, 0, 60, 0, 0, 0, 61, 0, 0, 0, 62, 0, 0, 0, 63, 0, 0, 0, 64, 0, 0, 0, 65, 0, 0, 0, 66, 0, 0, 0, 67, 0, 0, 0, 68, 0, 0, 0, 69, 0, 0, 0, 70, 0, 0, 0, 71, 0, 0, 0, 72, 0, 0, 0, 73, 0, 0, 0, 74, 0, 0, 0, 75, 0, 0, 0, 76, 0, 0, 0, 77, 0, 0, 0, 78, 0, 0, 0, 79, 0, 0, 0, 80, 0, 0, 0, 81, 0, 0, 0, 82, 0, 0, 0, 83, 0, 0, 0, 84, 0, 0, 0, 85, 0, 0, 0, 86, 0, 0, 0, 87, 0, 0, 0, 88, 0, 0, 0, 89, 0, 0, 0, 90, 0, 0, 0, 91, 0, 0, 0, 92, 0, 0, 0, 93, 0, 0, 0, 94, 0, 0, 0, 95, 0, 0, 0, 96, 0, 0, 0, 97, 0, 0, 0, 98, 0, 0, 0, 99, 0, 0, 0, 100, 0, 0, 0, 101, 0, 0, 0, 102, 0, 0, 0, 103, 0, 0, 0, 104, 0, 0, 0, 105, 0, 0, 0, 106, 0, 0, 0, 107, 0, 0, 0, 108, 0, 0, 0, 109, 0, 0, 0, 110, 0, 0, 0, 111, 0, 0, 0, 112, 0, 0, 0, 113, 0, 0, 0, 114, 0, 0, 0, 115, 0, 0, 0, 116, 0, 0, 0, 117, 0, 0, 0, 118, 0, 0, 0, 119, 0, 0, 0, 120, 0, 0, 0, 121, 0, 0, 0, 122, 0, 0, 0, 123, 0, 0, 0, 124, 0, 0, 0, 125, 0, 0, 0, 126, 0, 0, 0, 128, 0, 0, 0, 129, 0, 0, 0, 130, 0, 0, 0, 131, 0, 0, 0, 132, 0, 0, 0, 133, 0, 0, 0, 134, 0, 0, 0, 135, 0, 0, 0, 136, 0, 0, 0, 137, 0, 0, 0, 138, 0, 0, 0, 139, 0, 0, 0, 140, 0, 0, 0, 141, 0, 0, 0, 142, 0, 0, 0, 143, 0, 0, 0, 144, 0, 0, 0, 145, 0, 0, 0, 146, 0, 0, 0, 147, 0, 0, 0, 148, 0, 0, 0, 149, 0, 0, 0, 150, 0, 0, 0, 151, 0, 0, 0, 152, 0, 0, 0, 153, 0, 0, 0, 154, 0, 0, 0, 155, 0, 0, 0, 156, 0, 0, 0, 157, 0, 0, 0, 158, 0, 0, 0, 159, 0, 0, 0, 160, 0, 0, 0, 161, 0, 0, 0, 162, 0, 0, 0, 163, 0, 0, 0, 164, 0, 0, 0, 165, 0, 0, 0, 166, 0, 0, 0, 167, 0, 0, 0, 168, 0, 0, 0, 169, 0, 0, 0, 170, 0, 0, 0, 171, 0, 0, 0, 172, 0, 0, 0, 173, 0, 0, 0, 174, 0, 0, 0, 175, 0, 0, 0, 176, 0, 0, 0, 177, 0, 0, 0, 178, 0, 0, 0, 179, 0, 0, 0, 180, 0, 0, 0, 181, 0, 0, 0, 182, 0, 0, 0, 183, 0, 0, 0, 184, 0, 0, 0, 185, 0, 0, 0, 186, 0, 0, 0, 187, 0, 0, 0, 188, 0, 0, 0, 189, 0, 0, 0, 190, 0, 0, 0, 191, 0, 0, 0, 192, 0, 0, 0, 193, 0, 0, 0, 194, 0, 0, 0, 195, 0, 0, 0, 196, 0, 0, 0, 197, 0, 0, 0, 198, 0, 0, 0, 199, 0, 0, 0, 200, 0, 0, 0, 201, 0, 0, 0, 202, 0, 0, 0, 203, 0, 0, 0, 204, 0, 0, 0, 205, 0, 0, 0, 206, 0, 0, 0, 207, 0, 0, 0, 208, 0, 0, 0, 209, 0, 0, 0, 210, 0, 0, 0, 211, 0, 0, 0, 212, 0, 0, 0, 213, 0, 0, 0, 214, 0, 0, 0, 215, 0, 0, 0, 216, 0, 0, 0, 217, 0, 0, 0, 218, 0, 0, 0, 219, 0, 0, 0, 220, 0, 0, 0, 221, 0, 0, 0, 222, 0, 0, 0, 223, 0, 0, 0, 224, 0, 0, 0, 225, 0, 0, 0, 226, 0, 0, 0, 227, 0, 0, 0, 228, 0, 0, 0, 229, 0, 0, 0, 230, 0, 0, 0, 231, 0, 0, 0, 232, 0, 0, 0, 233, 0, 0, 0, 234, 0, 0, 0, 235, 0, 0, 0, 236, 0, 0, 0, 237, 0, 0, 0, 238, 0, 0, 0, 239, 0, 0, 0, 240, 0, 0, 0, 241, 0, 0, 0, 242, 0, 0, 0, 243, 0, 0, 0, 244, 0, 0, 0, 245, 0, 0, 0, 246, 0, 0, 0, 247, 0, 0, 0, 248, 0, 0, 0, 249, 0, 0, 0, 250, 0, 0, 0, 251, 0, 0, 0, 252, 0, 0, 0, 253, 0, 0, 0, 254, 0, 0, 0, 255 };
|
||||
|
||||
StreamToken function = new StreamToken(dictionaryToken, data);
|
||||
|
||||
var function0 = new PdfFunctionType0(function);
|
||||
var result = function0.Eval(new double[] { 0 });
|
||||
Assert.Equal(4, result.Length);
|
||||
result = function0.Eval(new double[] { 0.5 });
|
||||
Assert.Equal(4, result.Length);
|
||||
result = function0.Eval(new double[] { 1 });
|
||||
Assert.Equal(4, result.Length);
|
||||
result = function0.Eval(new double[] { 0.2 });
|
||||
Assert.Equal(4, result.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Simple16()
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(0) },
|
||||
{ NameToken.Domain, GetArrayToken(0, 1) },
|
||||
{ NameToken.Range, GetArrayToken(0, 1) },
|
||||
|
||||
{ NameToken.BitsPerSample, new NumericToken(16) },
|
||||
{ NameToken.Size, GetArrayToken(5) }
|
||||
});
|
||||
|
||||
byte[] data = new ushort[] { 0, 8192, 16384, 32768, 65535 }.SelectMany(v => BitConverter.GetBytes(v)).ToArray();
|
||||
|
||||
StreamToken function = new StreamToken(dictionaryToken, data);
|
||||
|
||||
var function0 = new PdfFunctionType0(function);
|
||||
var result = function0.Eval(new double[] { 0.00 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.0, result[0], 3);
|
||||
|
||||
result = function0.Eval(new double[] { 0.25 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.125, result[0], 3);
|
||||
|
||||
result = function0.Eval(new double[] { 0.50 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.25, result[0], 2);
|
||||
|
||||
result = function0.Eval(new double[] { 0.75 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.50, result[0], 2);
|
||||
|
||||
result = function0.Eval(new double[] { 1.0 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(1.00, result[0], 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Simple8()
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(0) },
|
||||
{ NameToken.Domain, GetArrayToken(0, 1) },
|
||||
{ NameToken.Range, GetArrayToken(0, 1) },
|
||||
|
||||
{ NameToken.BitsPerSample, new NumericToken(8) },
|
||||
{ NameToken.Size, GetArrayToken(5) }
|
||||
});
|
||||
|
||||
byte[] data = new byte[] { 0, 32, 64, 128, 255 };
|
||||
|
||||
StreamToken function = new StreamToken(dictionaryToken, data);
|
||||
|
||||
var function0 = new PdfFunctionType0(function);
|
||||
var result = function0.Eval(new double[] { 0.00 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.0, result[0], 3);
|
||||
|
||||
result = function0.Eval(new double[] { 0.25 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.125, result[0], 3);
|
||||
|
||||
result = function0.Eval(new double[] { 0.50 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.25, result[0], 2);
|
||||
|
||||
result = function0.Eval(new double[] { 0.75 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(0.50, result[0], 2);
|
||||
|
||||
result = function0.Eval(new double[] { 1.0 });
|
||||
Assert.Single(result);
|
||||
Assert.Equal(1.00, result[0], 2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RgbColorSpace()
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(0) },
|
||||
{ NameToken.Domain, GetArrayToken(0, 1, 0, 1) },
|
||||
{ NameToken.Range, GetArrayToken(0, 1, 0, 1, 0, 1) },
|
||||
|
||||
{ NameToken.BitsPerSample, new NumericToken(8) },
|
||||
{ NameToken.Size, GetArrayToken(2, 2) }
|
||||
});
|
||||
|
||||
byte[] data = new byte[] { 255, 255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255 };
|
||||
|
||||
StreamToken function = new StreamToken(dictionaryToken, data);
|
||||
|
||||
var function0 = new PdfFunctionType0(function);
|
||||
var result = function0.Eval(new double[] { 0, 0 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 1, 1, 0 }, result); // yellow
|
||||
|
||||
result = function0.Eval(new double[] { 1, 0 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0, 0, 0 }, result); // black
|
||||
|
||||
result = function0.Eval(new double[] { 0, 1 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 1, 0, 0 }, result); // red
|
||||
|
||||
result = function0.Eval(new double[] { 1, 1 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0, 0, 1 }, result); // blue
|
||||
|
||||
result = function0.Eval(new double[] { 0.5, 0.5 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0.5, 0.25, 0.25 }, result); // Mid point
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RedBlueGradient()
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(0) },
|
||||
{ NameToken.Domain, GetArrayToken(0, 1) },
|
||||
{ NameToken.Range, GetArrayToken(0, 1, 0, 1, 0, 1) },
|
||||
|
||||
{ NameToken.BitsPerSample, new NumericToken(8) },
|
||||
{ NameToken.Size, GetArrayToken(2) }
|
||||
});
|
||||
|
||||
byte[] data = new byte[] { 255, 0, 0, 0, 0, 255 };
|
||||
|
||||
StreamToken function = new StreamToken(dictionaryToken, data);
|
||||
|
||||
var function0 = new PdfFunctionType0(function);
|
||||
|
||||
var result = function0.Eval(new double[] { 0 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 1, 0, 0 }, result); // red
|
||||
|
||||
result = function0.Eval(new double[] { 1 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0, 0, 1 }, result); // blue
|
||||
|
||||
result = function0.Eval(new double[] { 0.5 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0.5, 0.0, 0.5 }, result); // Mid point
|
||||
|
||||
result = function0.Eval(new double[] { 0.3333 });
|
||||
Assert.Equal(3, result.Length);
|
||||
Assert.Equal(new double[] { 0.6667, 0.0, 0.3333 }, result); // 1/3 point
|
||||
}
|
||||
}
|
||||
}
|
169
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType2Tests.cs
Normal file
169
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType2Tests.cs
Normal file
@ -0,0 +1,169 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class PdfFunctionType2Tests
|
||||
{
|
||||
private PdfFunctionType2 CreateFunction(double[] domain, double[] range, double[] c0, double[] c1, double n)
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(4) },
|
||||
{ NameToken.Domain, new ArrayToken(domain.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
{ NameToken.Range, new ArrayToken(range.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
|
||||
{ NameToken.C0, new ArrayToken(c0.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
{ NameToken.C1, new ArrayToken(c1.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
{ NameToken.N, new NumericToken((decimal)n) },
|
||||
});
|
||||
|
||||
return new PdfFunctionType2(dictionaryToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Simple()
|
||||
{
|
||||
PdfFunctionType2 function = CreateFunction(
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -1.0, 1.0 },
|
||||
new double[] { 0.0 },
|
||||
new double[] { 1.0 },
|
||||
1);
|
||||
|
||||
Assert.Equal(FunctionTypes.Exponential, function.FunctionType);
|
||||
|
||||
double[] input = new double[] { -0.7 };
|
||||
double[] output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-0.7, output[0], 4);
|
||||
|
||||
input = new double[] { 0.7 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(0.7, output[0], 4);
|
||||
|
||||
input = new double[] { -0.5 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-0.5, output[0], 4);
|
||||
|
||||
input = new double[] { 0.5 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(0.5, output[0], 4);
|
||||
|
||||
input = new double[] { 0 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(0, output[0], 4);
|
||||
|
||||
input = new double[] { 1 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1, output[0], 4);
|
||||
|
||||
input = new double[] { -1 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-1, output[0], 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimpleClip()
|
||||
{
|
||||
PdfFunctionType2 function = CreateFunction(
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -1.0, 1.0 },
|
||||
new double[] { 0.0 },
|
||||
new double[] { 1.0 },
|
||||
1);
|
||||
|
||||
double[] input = new double[] { -15 };
|
||||
double[] output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-1, output[0], 4);
|
||||
|
||||
input = new double[] { 15 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1, output[0], 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void N2()
|
||||
{
|
||||
PdfFunctionType2 function = CreateFunction(
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -10.0, 10.0 },
|
||||
new double[] { 0.0 },
|
||||
new double[] { 1.0 },
|
||||
2);
|
||||
|
||||
double[] input = new double[] { 1.12 };
|
||||
double[] output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1.2544, output[0], 4);
|
||||
|
||||
input = new double[] { -1.35 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1.82250, output[0], 4);
|
||||
|
||||
input = new double[] { 5 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(10, output[0], 4); // clip
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void N3()
|
||||
{
|
||||
PdfFunctionType2 function = CreateFunction(
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -10.0, 10.0 },
|
||||
new double[] { 4.0 },
|
||||
new double[] { 9.53 },
|
||||
3);
|
||||
|
||||
double[] input = new double[] { 1.0 };
|
||||
double[] output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(9.53, output[0], 4);
|
||||
|
||||
input = new double[] { -1.236 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-6.44192, output[0], 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NSqrt()
|
||||
{
|
||||
PdfFunctionType2 function = CreateFunction(
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -10.0, 10.0 },
|
||||
new double[] { 2.589 },
|
||||
new double[] { 10.58 },
|
||||
0.5);
|
||||
|
||||
double[] input = new double[] { 0.5 };
|
||||
double[] output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(8.23949, output[0], 4);
|
||||
|
||||
input = new double[] { 0.78 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.Equal(9.64646, output[0], 4);
|
||||
|
||||
input = new double[] { -0.78 };
|
||||
output = function.Eval(input);
|
||||
Assert.Single(output);
|
||||
Assert.True(double.IsNaN(output[0])); // negative input with sqrt
|
||||
}
|
||||
}
|
||||
}
|
11
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType3Tests.cs
Normal file
11
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType3Tests.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
public class PdfFunctionType3Tests
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
96
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType4Tests.cs
Normal file
96
src/UglyToad.PdfPig.Tests/Functions/PdfFunctionType4Tests.cs
Normal file
@ -0,0 +1,96 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class PdfFunctionType4Tests
|
||||
{
|
||||
private PdfFunctionType4 CreateFunction(string function, double[] domain, double[] range)
|
||||
{
|
||||
DictionaryToken dictionaryToken = new DictionaryToken(new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{ NameToken.FunctionType, new NumericToken(4) },
|
||||
{ NameToken.Domain, new ArrayToken(domain.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
{ NameToken.Range, new ArrayToken(range.Select(v => new NumericToken((decimal)v)).ToArray()) },
|
||||
});
|
||||
|
||||
var data = Encoding.ASCII.GetBytes(function); // OtherEncodings.Iso88591.GetBytes(function);
|
||||
StreamToken stream = new StreamToken(dictionaryToken, data);
|
||||
|
||||
return new PdfFunctionType4(stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the <see cref="PdfFunctionType4"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FunctionSimple()
|
||||
{
|
||||
const string functionText = "{ add }";
|
||||
//Simply adds the two arguments and returns the result
|
||||
|
||||
PdfFunctionType4 function = CreateFunction(functionText,
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -1.0, 1.0 });
|
||||
|
||||
Assert.Equal(FunctionTypes.PostScript, function.FunctionType);
|
||||
|
||||
double[] input = new double[] { 0.8, 0.1 };
|
||||
double[] output = function.Eval(input);
|
||||
|
||||
Assert.Single(output);
|
||||
Assert.Equal(0.9, output[0], 4);
|
||||
|
||||
input = new double[] { 0.8, 0.3 }; //results in 1.1f being outside Range
|
||||
output = function.Eval(input);
|
||||
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1, output[0]);
|
||||
|
||||
input = new double[] { 0.8, 1.2 }; //input argument outside Dimension
|
||||
output = function.Eval(input);
|
||||
|
||||
Assert.Single(output);
|
||||
Assert.Equal(1, output[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks the handling of the argument order for a <see cref="PdfFunctionType4"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FunctionArgumentOrder()
|
||||
{
|
||||
const string functionText = "{ pop }";
|
||||
// pops an argument (2nd) and returns the next argument (1st)
|
||||
|
||||
PdfFunctionType4 function = CreateFunction(functionText,
|
||||
new double[] { -1.0, 1.0, -1.0, 1.0 },
|
||||
new double[] { -1.0, 1.0 });
|
||||
|
||||
double[] input = new double[] { -0.7, 0.0 };
|
||||
double[] output = function.Eval(input);
|
||||
|
||||
Assert.Single(output);
|
||||
Assert.Equal(-0.7, output[0], 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Advanced()
|
||||
{
|
||||
const string functionText = "{ dup 0.0 mul 1 exch sub 2 index 1.0 mul 1 exch sub mul 1 exch sub 3 1 roll dup 0.75 mul 1 exch sub 2 index 0.723 mul 1 exch sub mul 1 exch sub 3 1 roll dup 0.9 mul 1 exch sub 2 index 0.0 mul 1 exch sub mul 1 exch sub 3 1 roll dup 0.0 mul 1 exch sub 2 index 0.02 mul 1 exch sub mul 1 exch sub 3 1 roll pop pop }";
|
||||
|
||||
PdfFunctionType4 function = CreateFunction(functionText,
|
||||
new double[] { 0, 1, 0, 1 },
|
||||
new double[] { 0, 1, 0, 1, 0, 1, 0, 1 });
|
||||
|
||||
double[] input = new double[] { 1.0, 1.0 };
|
||||
double[] output = function.Eval(input);
|
||||
|
||||
Assert.Equal(4, output.Length);
|
||||
}
|
||||
}
|
||||
}
|
482
src/UglyToad.PdfPig.Tests/Functions/Type4/OperatorsTests.cs
Normal file
482
src/UglyToad.PdfPig.Tests/Functions/Type4/OperatorsTests.cs
Normal file
@ -0,0 +1,482 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using UglyToad.PdfPig.Functions.Type4;
|
||||
using Xunit;
|
||||
|
||||
public class OperatorsTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests the "add" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Add()
|
||||
{
|
||||
Type4Tester.Create("5 6 add").Pop(11).IsEmpty();
|
||||
|
||||
Type4Tester.Create("5 0.23 add").Pop(5.23).IsEmpty();
|
||||
|
||||
const int bigValue = int.MaxValue - 2;
|
||||
ExecutionContext context = Type4Tester.Create($"{bigValue} {bigValue} add").ToExecutionContext();
|
||||
double floatResult = Convert.ToDouble(context.Stack.Pop());
|
||||
Assert.Equal((long)2 * (long)int.MaxValue - (long)4, floatResult, 1);
|
||||
|
||||
Assert.Empty(context.Stack);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "abs" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Abs()
|
||||
{
|
||||
Type4Tester.Create("-3 abs 2.1 abs -2.1 abs -7.5 abs")
|
||||
.Pop(7.5).Pop(2.1).Pop(2.1).Pop(3).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "and" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void And()
|
||||
{
|
||||
Type4Tester.Create("true true and true false and")
|
||||
.Pop(false).Pop(true).IsEmpty();
|
||||
|
||||
Type4Tester.Create("99 1 and 52 7 and")
|
||||
.Pop(4).Pop(1).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "atan" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Atan()
|
||||
{
|
||||
Type4Tester.Create("0 1 atan").Pop(0.0).IsEmpty();
|
||||
Type4Tester.Create("1 0 atan").Pop(90.0).IsEmpty();
|
||||
Type4Tester.Create("-100 0 atan").Pop(270.0).IsEmpty();
|
||||
Type4Tester.Create("4 4 atan").Pop(45.0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "ceiling" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ceiling()
|
||||
{
|
||||
Type4Tester.Create("3.2 ceiling -4.8 ceiling 99 ceiling")
|
||||
.Pop(99.0).Pop(-4.0).Pop(4.0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "cos" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Cos()
|
||||
{
|
||||
Type4Tester.Create("0 cos").PopReal(1).IsEmpty();
|
||||
Type4Tester.Create("90 cos").PopReal(0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "cvi" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Cvi()
|
||||
{
|
||||
Type4Tester.Create("-47.8 cvi").Pop(-47).IsEmpty();
|
||||
Type4Tester.Create("520.9 cvi").Pop(520).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "cvr" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Cvr()
|
||||
{
|
||||
Type4Tester.Create("-47.8 cvr").PopReal(-47.8).IsEmpty();
|
||||
Type4Tester.Create("520.9 cvr").PopReal(520.9).IsEmpty();
|
||||
Type4Tester.Create("77 cvr").PopReal(77).IsEmpty();
|
||||
|
||||
//Check that the data types are really right
|
||||
ExecutionContext context = Type4Tester.Create("77 77 cvr").ToExecutionContext();
|
||||
Assert.True(context.Stack.Pop() is double, "Expected a real as the result of 'cvr'");
|
||||
Assert.True(context.Stack.Pop() is int, "Expected an int from an int literal");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "div" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Div()
|
||||
{
|
||||
Type4Tester.Create("3 2 div").PopReal(1.5).IsEmpty();
|
||||
Type4Tester.Create("4 2 div").PopReal(2.0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "exp" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Exp()
|
||||
{
|
||||
Type4Tester.Create("9 0.5 exp").PopReal(3.0).IsEmpty();
|
||||
Type4Tester.Create("-9 -1 exp").PopReal(-0.111111, 0.000001).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "floor" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Floor()
|
||||
{
|
||||
Type4Tester.Create("3.2 floor -4.8 floor 99 floor")
|
||||
.Pop(99.0).Pop(-5.0).Pop(3.0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "div" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IDiv()
|
||||
{
|
||||
Type4Tester.Create("3 2 idiv").Pop(1).IsEmpty();
|
||||
Type4Tester.Create("4 2 idiv").Pop(2).IsEmpty();
|
||||
Type4Tester.Create("-5 2 idiv").Pop(-2).IsEmpty();
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => Type4Tester.Create("4.4 2 idiv"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "ln" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ln()
|
||||
{
|
||||
Type4Tester.Create("10 ln").PopReal(2.30259, 0.00001).IsEmpty();
|
||||
Type4Tester.Create("100 ln").PopReal(4.60517, 0.00001).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "log" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Log()
|
||||
{
|
||||
Type4Tester.Create("10 log").PopReal(1.0).IsEmpty();
|
||||
Type4Tester.Create("100 log").PopReal(2.0).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "mod" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Mod()
|
||||
{
|
||||
Type4Tester.Create("5 3 mod").Pop(2).IsEmpty();
|
||||
Type4Tester.Create("5 2 mod").Pop(1).IsEmpty();
|
||||
Type4Tester.Create("-5 3 mod").Pop(-2).IsEmpty();
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => Type4Tester.Create("4.4 2 mod"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "mul" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Mul()
|
||||
{
|
||||
Type4Tester.Create("1 2 mul").Pop(2).IsEmpty();
|
||||
Type4Tester.Create("1.5 2 mul").PopReal(3.0).IsEmpty();
|
||||
Type4Tester.Create("1.5 2.1 mul").PopReal(3.15, 0.001).IsEmpty();
|
||||
Type4Tester.Create($"{(int.MaxValue - 3)} 2 mul") //int overflow -> real
|
||||
.PopReal(2L * (int.MaxValue - 3), 0.001).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "neg" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Neg()
|
||||
{
|
||||
Type4Tester.Create("4.5 neg").PopReal(-4.5).IsEmpty();
|
||||
Type4Tester.Create("-3 neg").Pop(3).IsEmpty();
|
||||
|
||||
//Border cases
|
||||
Type4Tester.Create((int.MinValue + 1) + " neg").Pop(int.MaxValue).IsEmpty();
|
||||
Type4Tester.Create(int.MinValue + " neg").PopReal(-(double)int.MinValue).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "round" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Round()
|
||||
{
|
||||
Type4Tester.Create("3.2 round").PopReal(3.0).IsEmpty();
|
||||
Type4Tester.Create("6.5 round").PopReal(7.0).IsEmpty();
|
||||
Type4Tester.Create("-4.8 round").PopReal(-5.0).IsEmpty();
|
||||
Type4Tester.Create("-6.5 round").PopReal(-6.0).IsEmpty();
|
||||
Type4Tester.Create("99 round").Pop(99).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "sin" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Sin()
|
||||
{
|
||||
Type4Tester.Create("0 sin").PopReal(0).IsEmpty();
|
||||
Type4Tester.Create("90 sin").PopReal(1).IsEmpty();
|
||||
Type4Tester.Create("-90.0 sin").PopReal(-1).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "sqrt" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Sqrt()
|
||||
{
|
||||
Type4Tester.Create("0 sqrt").PopReal(0).IsEmpty();
|
||||
Type4Tester.Create("1 sqrt").PopReal(1).IsEmpty();
|
||||
Type4Tester.Create("4 sqrt").PopReal(2).IsEmpty();
|
||||
Type4Tester.Create("4.4 sqrt").PopReal(2.097617, 0.000001).IsEmpty();
|
||||
Assert.Throws<ArgumentException>(() => Type4Tester.Create("-4.1 sqrt"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "sub" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Sub()
|
||||
{
|
||||
Type4Tester.Create("5 2 sub -7.5 1 sub").Pop(-8.5f).Pop(3).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "truncate" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Truncate()
|
||||
{
|
||||
Type4Tester.Create("3.2 truncate").PopReal(3.0).IsEmpty();
|
||||
Type4Tester.Create("-4.8 truncate").PopReal(-4.0).IsEmpty();
|
||||
Type4Tester.Create("99 truncate").Pop(99).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "bitshift" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Bitshift()
|
||||
{
|
||||
Type4Tester.Create("7 3 bitshift 142 -3 bitshift")
|
||||
.Pop(17).Pop(56).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "eq" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Eq()
|
||||
{
|
||||
Type4Tester.Create("7 7 eq 7 6 eq 7 -7 eq true true eq false true eq 7.7 7.7 eq")
|
||||
.Pop(true).Pop(false).Pop(true).Pop(false).Pop(false).Pop(true).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "ge" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ge()
|
||||
{
|
||||
Type4Tester.Create("5 7 ge 7 5 ge 7 7 ge -1 2 ge")
|
||||
.Pop(false).Pop(true).Pop(true).Pop(false).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "gt" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Gt()
|
||||
{
|
||||
Type4Tester.Create("5 7 gt 7 5 gt 7 7 gt -1 2 gt")
|
||||
.Pop(false).Pop(false).Pop(true).Pop(false).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "le" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Le()
|
||||
{
|
||||
Type4Tester.Create("5 7 le 7 5 le 7 7 le -1 2 le")
|
||||
.Pop(true).Pop(true).Pop(false).Pop(true).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "lt" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Lt()
|
||||
{
|
||||
Type4Tester.Create("5 7 lt 7 5 lt 7 7 lt -1 2 lt")
|
||||
.Pop(true).Pop(false).Pop(false).Pop(true).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "ne" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Ne()
|
||||
{
|
||||
Type4Tester.Create("7 7 ne 7 6 ne 7 -7 ne true true ne false true ne 7.7 7.7 ne")
|
||||
.Pop(false).Pop(true).Pop(false).Pop(true).Pop(true).Pop(false).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "not" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Not()
|
||||
{
|
||||
Type4Tester.Create("true not false not")
|
||||
.Pop(true).Pop(false).IsEmpty();
|
||||
|
||||
Type4Tester.Create("52 not -37 not")
|
||||
.Pop(37).Pop(-52).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "or" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Or()
|
||||
{
|
||||
Type4Tester.Create("true true or true false or false false or")
|
||||
.Pop(false).Pop(true).Pop(true).IsEmpty();
|
||||
|
||||
Type4Tester.Create("17 5 or 1 1 or")
|
||||
.Pop(1).Pop(21).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "cor" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Xor()
|
||||
{
|
||||
Type4Tester.Create("true true xor true false xor false false xor")
|
||||
.Pop(false).Pop(true).Pop(false).IsEmpty();
|
||||
|
||||
Type4Tester.Create("7 3 xor 12 3 or")
|
||||
.Pop(15).Pop(4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "if" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void If()
|
||||
{
|
||||
Type4Tester.Create("true { 2 1 add } if")
|
||||
.Pop(3).IsEmpty();
|
||||
|
||||
Type4Tester.Create("false { 2 1 add } if")
|
||||
.IsEmpty();
|
||||
|
||||
Assert.Throws<InvalidCastException>(() => Type4Tester.Create("0 { 2 1 add } if"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "ifelse" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IfElse()
|
||||
{
|
||||
Type4Tester.Create("true { 2 1 add } { 2 1 sub } ifelse")
|
||||
.Pop(3).IsEmpty();
|
||||
|
||||
Type4Tester.Create("false { 2 1 add } { 2 1 sub } ifelse")
|
||||
.Pop(1).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "copy" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Copy()
|
||||
{
|
||||
Type4Tester.Create("true 1 2 3 3 copy")
|
||||
.Pop(3).Pop(2).Pop(1)
|
||||
.Pop(3).Pop(2).Pop(1)
|
||||
.Pop(true)
|
||||
.IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "dup" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Dup()
|
||||
{
|
||||
Type4Tester.Create("true 1 2 dup")
|
||||
.Pop(2).Pop(2).Pop(1)
|
||||
.Pop(true)
|
||||
.IsEmpty();
|
||||
Type4Tester.Create("true dup")
|
||||
.Pop(true).Pop(true).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "exch" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Exch()
|
||||
{
|
||||
Type4Tester.Create("true 1 exch")
|
||||
.Pop(true).Pop(1).IsEmpty();
|
||||
Type4Tester.Create("1 2.5 exch")
|
||||
.Pop(1).Pop(2.5).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "index" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Index()
|
||||
{
|
||||
Type4Tester.Create("1 2 3 4 0 index")
|
||||
.Pop(4).Pop(4).Pop(3).Pop(2).Pop(1).IsEmpty();
|
||||
Type4Tester.Create("1 2 3 4 3 index")
|
||||
.Pop(1).Pop(4).Pop(3).Pop(2).Pop(1).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "pop" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Pop()
|
||||
{
|
||||
Type4Tester.Create("1 pop 7 2 pop")
|
||||
.Pop(7).IsEmpty();
|
||||
Type4Tester.Create("1 2 3 pop pop")
|
||||
.Pop(1).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests the "roll" operator.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Roll()
|
||||
{
|
||||
Type4Tester.Create("1 2 3 4 5 5 -2 roll")
|
||||
.Pop(2).Pop(1).Pop(5).Pop(4).Pop(3).IsEmpty();
|
||||
Type4Tester.Create("1 2 3 4 5 5 2 roll")
|
||||
.Pop(3).Pop(2).Pop(1).Pop(5).Pop(4).IsEmpty();
|
||||
Type4Tester.Create("1 2 3 3 0 roll")
|
||||
.Pop(3).Pop(2).Pop(1).IsEmpty();
|
||||
}
|
||||
}
|
||||
}
|
42
src/UglyToad.PdfPig.Tests/Functions/Type4/ParserTests.cs
Normal file
42
src/UglyToad.PdfPig.Tests/Functions/Type4/ParserTests.cs
Normal file
@ -0,0 +1,42 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions.Type4
|
||||
{
|
||||
using Xunit;
|
||||
|
||||
public class ParserTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test the very basics.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ParserBasics()
|
||||
{
|
||||
Type4Tester.Create("3 4 add 2 sub").Pop(5).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test nested blocks.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Nested()
|
||||
{
|
||||
Type4Tester.Create("true { 2 1 add } { 2 1 sub } ifelse")
|
||||
.Pop(3).IsEmpty();
|
||||
|
||||
Type4Tester.Create("{ true }").Pop(true).IsEmpty();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests problematic functions from PDFBOX-804.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Jira804()
|
||||
{
|
||||
//This is an example of a tint to CMYK function
|
||||
//Problems here were:
|
||||
//1. no whitespace between "mul" and "}" (token was detected as "mul}")
|
||||
//2. line breaks cause endless loops
|
||||
Type4Tester.Create("1 {dup dup .72 mul exch 0 exch .38 mul}\n")
|
||||
.Pop(0.38f).Pop(0f).Pop(0.72f).Pop(1.0f).IsEmpty();
|
||||
}
|
||||
}
|
||||
}
|
124
src/UglyToad.PdfPig.Tests/Functions/Type4/Type4Tester.cs
Normal file
124
src/UglyToad.PdfPig.Tests/Functions/Type4/Type4Tester.cs
Normal file
@ -0,0 +1,124 @@
|
||||
namespace UglyToad.PdfPig.Tests.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using UglyToad.PdfPig.Functions.Type4;
|
||||
using Xunit;
|
||||
|
||||
/// <summary>
|
||||
/// Testing helper class for testing type 4 functions from the PDF specification.
|
||||
/// </summary>
|
||||
public sealed class Type4Tester
|
||||
{
|
||||
private readonly ExecutionContext context;
|
||||
|
||||
private Type4Tester(ExecutionContext ctxt)
|
||||
{
|
||||
this.context = ctxt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance for the given type 4 function.
|
||||
/// </summary>
|
||||
/// <param name="text">the text of the type 4 function</param>
|
||||
/// <returns>the tester instance</returns>
|
||||
public static Type4Tester Create(string text)
|
||||
{
|
||||
InstructionSequence instructions = InstructionSequenceBuilder.Parse(text.Trim());
|
||||
|
||||
ExecutionContext context = new ExecutionContext(new Operators());
|
||||
instructions.Execute(context);
|
||||
return new Type4Tester(context);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a bool value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected bool value</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester Pop(bool expected)
|
||||
{
|
||||
bool value = (bool)context.Stack.Pop();
|
||||
Assert.Equal(expected, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a real value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected real value</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester PopReal(double expected)
|
||||
{
|
||||
return PopReal(expected, 0.0000001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a real value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected real value</param>
|
||||
/// <param name="delta">delta the allowed deviation of the value from the expected result</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester PopReal(double expected, double delta)
|
||||
{
|
||||
double value = Convert.ToDouble(context.Stack.Pop());
|
||||
DoubleComparer doubleComparer = new DoubleComparer(delta);
|
||||
Assert.True(doubleComparer.Equals(expected, value));//expected, value, delta);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops an int value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected int value</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester Pop(int expected)
|
||||
{
|
||||
int value = context.PopInt();
|
||||
Assert.Equal(expected, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a numeric value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected numeric value</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester Pop(double expected)
|
||||
{
|
||||
return Pop(expected, 0.0000001);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a numeric value from the stack and checks it against the expected result.
|
||||
/// </summary>
|
||||
/// <param name="expected">the expected numeric value</param>
|
||||
/// <param name="delta">the allowed deviation of the value from the expected result</param>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester Pop(double expected, double delta)
|
||||
{
|
||||
object value = context.PopNumber();
|
||||
DoubleComparer doubleComparer = new DoubleComparer(delta);
|
||||
Assert.True(doubleComparer.Equals(expected, Convert.ToDouble(value)));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that the stack is empty at this point.
|
||||
/// </summary>
|
||||
/// <returns>this instance</returns>
|
||||
public Type4Tester IsEmpty()
|
||||
{
|
||||
Assert.Empty(context.Stack);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the execution context so some custom checks can be performed.
|
||||
/// </summary>
|
||||
/// <returns>the associated execution context</returns>
|
||||
internal ExecutionContext ToExecutionContext()
|
||||
{
|
||||
return this.context;
|
||||
}
|
||||
}
|
||||
}
|
@ -90,6 +90,8 @@
|
||||
"UglyToad.PdfPig.Filters.DefaultFilterProvider",
|
||||
"UglyToad.PdfPig.Filters.IFilter",
|
||||
"UglyToad.PdfPig.Filters.IFilterProvider",
|
||||
"UglyToad.PdfPig.Functions.FunctionTypes",
|
||||
"UglyToad.PdfPig.Functions.PdfFunction",
|
||||
"UglyToad.PdfPig.PdfFonts.DescriptorFontFile",
|
||||
"UglyToad.PdfPig.PdfFonts.FontDescriptor",
|
||||
"UglyToad.PdfPig.PdfFonts.FontDescriptorFlags",
|
||||
|
@ -158,7 +158,7 @@
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
{
|
||||
loadedFonts[reference] = fontFactory.Get(fontObject);
|
||||
}
|
||||
catch
|
||||
@ -168,7 +168,6 @@
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (pair.Value is DictionaryToken fd)
|
||||
{
|
||||
|
274
src/UglyToad.PdfPig/Functions/PdfFunction.cs
Normal file
274
src/UglyToad.PdfPig/Functions/PdfFunction.cs
Normal file
@ -0,0 +1,274 @@
|
||||
namespace UglyToad.PdfPig.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a function in a PDF document.
|
||||
/// </summary>
|
||||
public abstract class PdfFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// The function dictionary.
|
||||
/// </summary>
|
||||
public DictionaryToken FunctionDictionary { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The function stream.
|
||||
/// </summary>
|
||||
public StreamToken FunctionStream { get; }
|
||||
|
||||
private ArrayToken domain;
|
||||
private ArrayToken range;
|
||||
private int numberOfInputValues = -1;
|
||||
private int numberOfOutputValues = -1;
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a function in a PDF document.
|
||||
/// </summary>
|
||||
public PdfFunction(DictionaryToken function)
|
||||
{
|
||||
FunctionDictionary = function;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class represents a function in a PDF document.
|
||||
/// </summary>
|
||||
public PdfFunction(StreamToken function)
|
||||
{
|
||||
FunctionStream = function;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the function type. Possible values are:
|
||||
/// <list type="bullet">
|
||||
/// <item><term>0</term><description>Sampled function</description></item>
|
||||
/// <item><term>2</term><description>Exponential interpolation function</description></item>
|
||||
/// <item><term>3</term><description>Stitching function</description></item>
|
||||
/// <item><term>4</term><description>PostScript calculator function</description></item>
|
||||
/// </list>
|
||||
/// </summary>
|
||||
/// <returns>the function type.</returns>
|
||||
public abstract FunctionTypes FunctionType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the function's dictionary. If <see cref="FunctionDictionary"/> is defined, it will be returned.
|
||||
/// If not, the <see cref="FunctionStream"/>'s StreamDictionary will be returned.
|
||||
/// </summary>
|
||||
public DictionaryToken GetDictionary()
|
||||
{
|
||||
if (FunctionStream != null)
|
||||
{
|
||||
return FunctionStream.StreamDictionary;
|
||||
}
|
||||
else
|
||||
{
|
||||
return FunctionDictionary;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <returns>The number of output parameters that have a range specified.</returns>
|
||||
public int NumberOfOutputParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (numberOfOutputValues == -1)
|
||||
{
|
||||
if (RangeValues == null)
|
||||
{
|
||||
numberOfOutputValues = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
numberOfOutputValues = RangeValues.Length / 2;
|
||||
}
|
||||
}
|
||||
return numberOfOutputValues;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="n">The output parameter number to get the range for.</param>
|
||||
/// <returns>The range for this component.</returns>
|
||||
public PdfRange GetRangeForOutput(int n)
|
||||
{
|
||||
return new PdfRange(RangeValues.Data.OfType<NumericToken>().Select(t => t.Double), n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will get the number of input parameters that
|
||||
/// have a domain specified.
|
||||
/// </summary>
|
||||
/// <returns>The number of input parameters that have a domain specified.</returns>
|
||||
public int NumberOfInputParameters
|
||||
{
|
||||
get
|
||||
{
|
||||
if (numberOfInputValues == -1)
|
||||
{
|
||||
ArrayToken array = GetDomainValues();
|
||||
numberOfInputValues = array.Length / 2;
|
||||
}
|
||||
return numberOfInputValues;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
/// <param name="n">The parameter number to get the domain for.</param>
|
||||
/// <returns>The domain range for this component.</returns>
|
||||
public PdfRange GetDomainForInput(int n)
|
||||
{
|
||||
ArrayToken domainValues = GetDomainValues();
|
||||
return new PdfRange(domainValues.Data.OfType<NumericToken>().Select(t => t.Double), n);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates the function at the given input.
|
||||
/// ReturnValue = f(input)
|
||||
/// </summary>
|
||||
/// <param name="input">The array of input values for the function.
|
||||
/// In many cases will be an array of a single value, but not always.</param>
|
||||
/// <returns>The of outputs the function returns based on those inputs.
|
||||
/// In many cases will be an array of a single value, but not always.</returns>
|
||||
public abstract double[] Eval(double[] input);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all ranges for the output values as <see cref="ArrayToken"/>. Required for type 0 and type 4 functions.
|
||||
/// </summary>
|
||||
/// <returns>the ranges array.</returns>
|
||||
protected virtual ArrayToken RangeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (range == null)
|
||||
{
|
||||
GetDictionary().TryGet(NameToken.Range, out range); // Optionnal
|
||||
}
|
||||
return range;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all domains for the input values as <see cref="ArrayToken"/>. Required for all function types.
|
||||
/// </summary>
|
||||
/// <returns>the domains array.</returns>
|
||||
private ArrayToken GetDomainValues()
|
||||
{
|
||||
if (domain == null)
|
||||
{
|
||||
if (!GetDictionary().TryGet(NameToken.Domain, out ArrayToken domainToken))
|
||||
{
|
||||
throw new ArgumentException("Could not retrieve Domain.");
|
||||
}
|
||||
domain = domainToken;
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clip the given input values to the ranges.
|
||||
/// </summary>
|
||||
/// <param name="inputValues">inputValues the input values</param>
|
||||
/// <returns>the clipped values</returns>
|
||||
protected double[] ClipToRange(double[] inputValues)
|
||||
{
|
||||
ArrayToken rangesArray = RangeValues;
|
||||
double[] result;
|
||||
if (rangesArray != null && rangesArray.Length > 0)
|
||||
{
|
||||
double[] rangeValues = rangesArray.Data.OfType<NumericToken>().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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clip the given input value to the given range.
|
||||
/// </summary>
|
||||
/// <param name="x">x the input value</param>
|
||||
/// <param name="rangeMin">the min value of the range</param>
|
||||
/// <param name="rangeMax">the max value of the range</param>
|
||||
/// <returns>the clipped value</returns>
|
||||
protected static double ClipToRange(double x, double rangeMin, double rangeMax)
|
||||
{
|
||||
if (x < rangeMin)
|
||||
{
|
||||
return rangeMin;
|
||||
}
|
||||
else if (x > rangeMax)
|
||||
{
|
||||
return rangeMax;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given value of x, interpolate calculates the y value
|
||||
/// on the line defined by the two points (xRangeMin, xRangeMax)
|
||||
/// and (yRangeMin, yRangeMax).
|
||||
/// </summary>
|
||||
/// <param name="x">the value to be interpolated value.</param>
|
||||
/// <param name="xRangeMin">the min value of the x range</param>
|
||||
/// <param name="xRangeMax">the max value of the x range</param>
|
||||
/// <param name="yRangeMin">the min value of the y range</param>
|
||||
/// <param name="yRangeMax">the max value of the y range</param>
|
||||
/// <returns>the interpolated y value</returns>
|
||||
protected static double Interpolate(double x, double xRangeMin, double xRangeMax, double yRangeMin, double yRangeMax)
|
||||
{
|
||||
return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin) / (xRangeMax - xRangeMin));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pdf function types.
|
||||
/// </summary>
|
||||
public enum FunctionTypes : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Sampled function.
|
||||
/// </summary>
|
||||
Sampled = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Exponential interpolation function.
|
||||
/// </summary>
|
||||
Exponential = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function.
|
||||
/// </summary>
|
||||
Stitching = 3,
|
||||
|
||||
/// <summary>
|
||||
/// PostScript calculator function.
|
||||
/// </summary>
|
||||
PostScript = 4
|
||||
}
|
||||
}
|
418
src/UglyToad.PdfPig/Functions/PdfFunctionType0.cs
Normal file
418
src/UglyToad.PdfPig/Functions/PdfFunctionType0.cs
Normal file
@ -0,0 +1,418 @@
|
||||
namespace UglyToad.PdfPig.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
internal sealed class PdfFunctionType0 : PdfFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// An array of 2 x m numbers specifying the linear mapping of input values
|
||||
/// into the domain of the function's sample table. Default value: [ 0 (Size0
|
||||
/// - 1) 0 (Size1 - 1) ...].
|
||||
/// </summary>
|
||||
private ArrayToken encode;
|
||||
|
||||
/// <summary>
|
||||
/// An array of 2 x n numbers specifying the linear mapping of sample values
|
||||
/// into the range appropriate for the function's output values. Default
|
||||
/// value: same as the value of Range.
|
||||
/// </summary>
|
||||
private ArrayToken decode;
|
||||
|
||||
/// <summary>
|
||||
/// An array of m positive integers specifying the number of samples in each
|
||||
/// input dimension of the sample table.
|
||||
/// </summary>
|
||||
private ArrayToken size;
|
||||
|
||||
/// <summary>
|
||||
/// The samples of the function.
|
||||
/// </summary>
|
||||
private int[][] samples;
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function
|
||||
/// </summary>
|
||||
internal PdfFunctionType0(DictionaryToken function) : base(function)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function
|
||||
/// </summary>
|
||||
internal PdfFunctionType0(StreamToken function) : base(function)
|
||||
{
|
||||
}
|
||||
|
||||
public override FunctionTypes FunctionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return FunctionTypes.Sampled;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The "Size" entry, which is the number of samples in each input dimension of the sample table.
|
||||
/// </summary>
|
||||
public ArrayToken Size
|
||||
{
|
||||
get
|
||||
{
|
||||
if (size == null && !GetDictionary().TryGet(NameToken.Size, out size))
|
||||
{
|
||||
throw new ArgumentNullException(NameToken.Size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the number of bits that the output value will take up.
|
||||
/// <para>Valid values are 1,2,4,8,12,16,24,32.</para>
|
||||
/// </summary>
|
||||
/// <returns>Number of bits for each output value.</returns>
|
||||
public int BitsPerSample
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!GetDictionary().TryGet<NumericToken>(NameToken.BitsPerSample, out var bps))
|
||||
{
|
||||
throw new ArgumentNullException(NameToken.BitsPerSample);
|
||||
}
|
||||
return bps.Int;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the order of interpolation between samples. Valid values are 1 and 3,
|
||||
/// specifying linear and cubic spline interpolation, respectively. Default
|
||||
/// is 1. See p.170 in PDF spec 1.7.
|
||||
/// </summary>
|
||||
/// <returns>order of interpolation.</returns>
|
||||
public int Order
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!GetDictionary().TryGet<NumericToken>(NameToken.Order, out var order))
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return order.Int;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all encode values as <see cref="ArrayToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>the encode array. </returns>
|
||||
private ArrayToken EncodeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (encode == null)
|
||||
{
|
||||
GetDictionary().TryGet<ArrayToken>(NameToken.Encode, out encode);
|
||||
|
||||
// the default value is [0 (size[0]-1) 0 (size[1]-1) ...]
|
||||
if (encode == null)
|
||||
{
|
||||
var values = new List<NumericToken>();
|
||||
ArrayToken sizeValues = Size;
|
||||
int sizeValuesSize = sizeValues.Length;
|
||||
for (int i = 0; i < sizeValuesSize; i++)
|
||||
{
|
||||
values.Add(new NumericToken(0));
|
||||
values.Add(new NumericToken((sizeValues[i] as NumericToken).Int - 1L));
|
||||
}
|
||||
encode = new ArrayToken(values);
|
||||
}
|
||||
}
|
||||
return encode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all decode values as <see cref="ArrayToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>the decode array.</returns>
|
||||
private ArrayToken DecodeValues
|
||||
{
|
||||
get
|
||||
{
|
||||
if (decode == null)
|
||||
{
|
||||
GetDictionary().TryGet<ArrayToken>(NameToken.Decode, out decode);
|
||||
|
||||
// if decode is null, the default values are the range values
|
||||
if (decode == null)
|
||||
{
|
||||
decode = RangeValues;
|
||||
}
|
||||
}
|
||||
return decode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the encode for the input parameter.
|
||||
/// </summary>
|
||||
/// <param name="paramNum">The function parameter number.</param>
|
||||
/// <returns>The encode parameter range or null if none is set.</returns>
|
||||
public PdfRange? GetEncodeForParameter(int paramNum)
|
||||
{
|
||||
ArrayToken encodeValues = EncodeValues;
|
||||
if (encodeValues != null && encodeValues.Length >= paramNum * 2 + 1)
|
||||
{
|
||||
return new PdfRange(encodeValues.Data.OfType<NumericToken>().Select(t => t.Double), paramNum);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the decode for the input parameter.
|
||||
/// </summary>
|
||||
/// <param name="paramNum">The function parameter number.</param>
|
||||
/// <returns>The decode parameter range or null if none is set.</returns>
|
||||
public PdfRange? GetDecodeForParameter(int paramNum)
|
||||
{
|
||||
ArrayToken decodeValues = DecodeValues;
|
||||
if (decodeValues != null && decodeValues.Length >= paramNum * 2 + 1)
|
||||
{
|
||||
return new PdfRange(decodeValues.Data.OfType<NumericToken>().Select(t => t.Double), paramNum);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inner class do to an interpolation in the Nth dimension by comparing the
|
||||
/// content size of N-1 dimensional objects.This is done with the help of
|
||||
/// recursive calls.
|
||||
/// <para>To understand the algorithm without recursion, see <see href="http://harmoniccode.blogspot.de/2011/04/bilinear-color-interpolation.html"/> for a bilinear interpolation
|
||||
/// and <see href="https://en.wikipedia.org/wiki/Trilinear_interpolation"/> for trilinear interpolation.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal class RInterpol
|
||||
{
|
||||
// coordinate that is to be interpolated
|
||||
private readonly double[] in_;
|
||||
// coordinate of the "ceil" point
|
||||
private readonly int[] inPrev;
|
||||
// coordinate of the "floor" point
|
||||
private readonly int[] inNext;
|
||||
private readonly int numberOfInputValues;
|
||||
private readonly int numberOfOutputValues;
|
||||
private readonly ArrayToken size;
|
||||
|
||||
private readonly int[][] samples;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="input">the input coordinates</param>
|
||||
/// <param name="inputPrev">coordinate of the "ceil" point</param>
|
||||
/// <param name="inputNext">coordinate of the "floor" point</param>
|
||||
/// <param name="numberOfOutputValues"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <param name="samples"></param>
|
||||
internal RInterpol(double[] input, int[] inputPrev, int[] inputNext, int numberOfOutputValues, ArrayToken size, int[][] samples)
|
||||
{
|
||||
in_ = input;
|
||||
inPrev = inputPrev;
|
||||
inNext = inputNext;
|
||||
numberOfInputValues = input.Length;
|
||||
this.numberOfOutputValues = numberOfOutputValues;
|
||||
this.size = size;
|
||||
this.samples = samples;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculate the interpolation.
|
||||
/// </summary>
|
||||
/// <returns>interpolated result sample</returns>
|
||||
internal double[] RInterpolate()
|
||||
{
|
||||
return InternalRInterpol(new int[numberOfInputValues], 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Do a linear interpolation if the two coordinates can be known, or
|
||||
/// call itself recursively twice.
|
||||
/// </summary>
|
||||
/// <param name="coord">partially set coordinate (not set from step
|
||||
/// upwards); gets fully filled in the last call ("leaf"), where it is
|
||||
/// used to get the correct sample</param>
|
||||
/// <param name="step">between 0 (first call) and dimension - 1</param>
|
||||
/// <returns>interpolated result sample</returns>
|
||||
private double[] InternalRInterpol(int[] coord, int step)
|
||||
{
|
||||
double[] resultSample = new double[numberOfOutputValues];
|
||||
if (step == in_.Length - 1)
|
||||
{
|
||||
// leaf
|
||||
if (inPrev[step] == inNext[step])
|
||||
{
|
||||
coord[step] = inPrev[step];
|
||||
int[] tmpSample = samples[CalcSampleIndex(coord)];
|
||||
for (int i = 0; i < numberOfOutputValues; ++i)
|
||||
{
|
||||
resultSample[i] = tmpSample[i];
|
||||
}
|
||||
return resultSample;
|
||||
}
|
||||
coord[step] = inPrev[step];
|
||||
int[] sample1 = samples[CalcSampleIndex(coord)];
|
||||
coord[step] = inNext[step];
|
||||
int[] sample2 = samples[CalcSampleIndex(coord)];
|
||||
for (int i = 0; i < numberOfOutputValues; ++i)
|
||||
{
|
||||
resultSample[i] = Interpolate(in_[step], inPrev[step], inNext[step], sample1[i], sample2[i]);
|
||||
}
|
||||
return resultSample;
|
||||
}
|
||||
else
|
||||
{
|
||||
// branch
|
||||
if (inPrev[step] == inNext[step])
|
||||
{
|
||||
coord[step] = inPrev[step];
|
||||
return InternalRInterpol(coord, step + 1);
|
||||
}
|
||||
coord[step] = inPrev[step];
|
||||
double[] sample1 = InternalRInterpol(coord, step + 1);
|
||||
coord[step] = inNext[step];
|
||||
double[] sample2 = InternalRInterpol(coord, step + 1);
|
||||
for (int i = 0; i < numberOfOutputValues; ++i)
|
||||
{
|
||||
resultSample[i] = Interpolate(in_[step], inPrev[step], inNext[step], sample1[i], sample2[i]);
|
||||
}
|
||||
return resultSample;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// calculate array index (structure described in p.171 PDF spec 1.7) in multiple dimensions.
|
||||
/// </summary>
|
||||
/// <param name="vector">with coordinates</param>
|
||||
/// <returns>index in flat array</returns>
|
||||
private int CalcSampleIndex(int[] vector)
|
||||
{
|
||||
// inspiration: http://stackoverflow.com/a/12113479/535646
|
||||
// but used in reverse
|
||||
double[] sizeValues = size.Data.OfType<NumericToken>().Select(t => t.Double).ToArray();
|
||||
int index = 0;
|
||||
int sizeProduct = 1;
|
||||
int dimension = vector.Length;
|
||||
for (int i = dimension - 2; i >= 0; --i)
|
||||
{
|
||||
sizeProduct = (int)(sizeProduct * sizeValues[i]);
|
||||
}
|
||||
for (int i = dimension - 1; i >= 0; --i)
|
||||
{
|
||||
index += sizeProduct * vector[i];
|
||||
if (i - 1 >= 0)
|
||||
{
|
||||
sizeProduct = (int)(sizeProduct / sizeValues[i - 1]);
|
||||
}
|
||||
}
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all sample values of this function.
|
||||
/// </summary>
|
||||
/// <returns>an array with all samples.</returns>
|
||||
private int[][] GetSamples()
|
||||
{
|
||||
if (samples == null)
|
||||
{
|
||||
int arraySize = 1;
|
||||
int nIn = NumberOfInputParameters;
|
||||
int nOut = NumberOfOutputParameters;
|
||||
ArrayToken sizes = Size;
|
||||
for (int i = 0; i < nIn; i++)
|
||||
{
|
||||
arraySize *= (sizes[i] as NumericToken).Int;
|
||||
}
|
||||
samples = new int[arraySize][];
|
||||
int bitsPerSample = BitsPerSample;
|
||||
|
||||
// PDF spec 1.7 p.171:
|
||||
// Each sample value is represented as a sequence of BitsPerSample bits.
|
||||
// Successive values are adjacent in the bit stream; there is no padding at byte boundaries.
|
||||
var bits = new BitArray(FunctionStream.Data.ToArray());
|
||||
|
||||
System.Diagnostics.Debug.Assert(bits.Length == arraySize * nOut * bitsPerSample);
|
||||
|
||||
for (int i = 0; i < arraySize; i++)
|
||||
{
|
||||
samples[i] = new int[nOut];
|
||||
for (int k = 0; k < nOut; k++)
|
||||
{
|
||||
long accum = 0L;
|
||||
for (int l = bitsPerSample - 1; l >= 0; l--)
|
||||
{
|
||||
accum <<= 1;
|
||||
accum |= bits[i * nOut * bitsPerSample + (k * bitsPerSample) + l] ? (uint)1 : 0;
|
||||
}
|
||||
|
||||
// TODO will this cast work properly for 32 bitsPerSample or should we use long[]?
|
||||
samples[i][k] = (int)accum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
public override double[] Eval(double[] input)
|
||||
{
|
||||
//This involves linear interpolation based on a set of sample points.
|
||||
//Theoretically it's not that difficult ... see section 3.9.1 of the PDF Reference.
|
||||
|
||||
double[] sizeValues = Size.Data.OfType<NumericToken>().Select(t => t.Double).ToArray();
|
||||
int bitsPerSample = BitsPerSample;
|
||||
double maxSample = Math.Pow(2, bitsPerSample) - 1.0;
|
||||
int numberOfInputValues = input.Length;
|
||||
int numberOfOutputValues = NumberOfOutputParameters;
|
||||
|
||||
int[] inputPrev = new int[numberOfInputValues];
|
||||
int[] inputNext = new int[numberOfInputValues];
|
||||
input = input.ToArray(); // PDFBOX-4461
|
||||
|
||||
for (int i = 0; i < numberOfInputValues; i++)
|
||||
{
|
||||
PdfRange domain = GetDomainForInput(i);
|
||||
PdfRange? encodeValues = GetEncodeForParameter(i);
|
||||
input[i] = ClipToRange(input[i], domain.Min, domain.Max);
|
||||
input[i] = Interpolate(input[i], domain.Min, domain.Max,
|
||||
encodeValues.Value.Min, encodeValues.Value.Max);
|
||||
input[i] = ClipToRange(input[i], 0, sizeValues[i] - 1);
|
||||
inputPrev[i] = (int)Math.Floor(input[i]);
|
||||
inputNext[i] = (int)Math.Ceiling(input[i]);
|
||||
}
|
||||
|
||||
double[] outputValues = new RInterpol(input, inputPrev, inputNext, numberOfOutputValues, Size, GetSamples()).RInterpolate();
|
||||
|
||||
for (int i = 0; i < numberOfOutputValues; i++)
|
||||
{
|
||||
PdfRange range = GetRangeForOutput(i);
|
||||
PdfRange? decodeValues = GetDecodeForParameter(i);
|
||||
if (!decodeValues.HasValue)
|
||||
{
|
||||
throw new IOException("Range missing in function /Decode entry");
|
||||
}
|
||||
outputValues[i] = Interpolate(outputValues[i], 0, maxSample, decodeValues.Value.Min, decodeValues.Value.Max);
|
||||
outputValues[i] = ClipToRange(outputValues[i], range.Min, range.Max);
|
||||
}
|
||||
|
||||
return outputValues;
|
||||
}
|
||||
}
|
||||
}
|
138
src/UglyToad.PdfPig/Functions/PdfFunctionType2.cs
Normal file
138
src/UglyToad.PdfPig/Functions/PdfFunctionType2.cs
Normal file
@ -0,0 +1,138 @@
|
||||
namespace UglyToad.PdfPig.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// Exponential interpolation function
|
||||
/// </summary>
|
||||
internal sealed class PdfFunctionType2 : PdfFunction
|
||||
{
|
||||
/// <summary>
|
||||
/// Exponential interpolation function
|
||||
/// </summary>
|
||||
internal PdfFunctionType2(DictionaryToken function) : base(function)
|
||||
{
|
||||
if (GetDictionary().TryGet(NameToken.C0, out ArrayToken array0))
|
||||
{
|
||||
C0 = array0;
|
||||
}
|
||||
else
|
||||
{
|
||||
C0 = new ArrayToken(new List<IToken>());
|
||||
}
|
||||
if (C0.Length == 0)
|
||||
{
|
||||
C0 = new ArrayToken(new List<NumericToken>() { new NumericToken(0) });
|
||||
}
|
||||
|
||||
if (GetDictionary().TryGet(NameToken.C1, out ArrayToken array1))
|
||||
{
|
||||
C1 = array1;
|
||||
}
|
||||
else
|
||||
{
|
||||
C1 = new ArrayToken(new List<IToken>());
|
||||
}
|
||||
if (C0.Length == 0)
|
||||
{
|
||||
C1 = new ArrayToken(new List<NumericToken>() { new NumericToken(1) });
|
||||
}
|
||||
|
||||
if (GetDictionary().TryGet(NameToken.N, out NumericToken exp))
|
||||
{
|
||||
N = exp.Double;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
internal PdfFunctionType2(StreamToken function) : base(function)
|
||||
{
|
||||
if (GetDictionary().TryGet(NameToken.C0, out ArrayToken array0))
|
||||
{
|
||||
C0 = array0;
|
||||
}
|
||||
else
|
||||
{
|
||||
C0 = new ArrayToken(new List<IToken>());
|
||||
}
|
||||
if (C0.Length == 0)
|
||||
{
|
||||
C0 = new ArrayToken(new List<NumericToken>() { new NumericToken(0) });
|
||||
}
|
||||
|
||||
if (GetDictionary().TryGet(NameToken.C1, out ArrayToken array1))
|
||||
{
|
||||
C1 = array1;
|
||||
}
|
||||
else
|
||||
{
|
||||
C1 = new ArrayToken(new List<IToken>());
|
||||
}
|
||||
if (C0.Length == 0)
|
||||
{
|
||||
C1 = new ArrayToken(new List<NumericToken>() { new NumericToken(1) });
|
||||
}
|
||||
|
||||
if (GetDictionary().TryGet(NameToken.N, out NumericToken exp))
|
||||
{
|
||||
N = exp.Double;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public override FunctionTypes FunctionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return FunctionTypes.Exponential;
|
||||
}
|
||||
}
|
||||
|
||||
public override double[] Eval(double[] input)
|
||||
{
|
||||
// exponential interpolation
|
||||
double xToN = Math.Pow(input[0], N); // x^exponent
|
||||
|
||||
double[] result = new double[Math.Min(C0.Length, C1.Length)];
|
||||
for (int j = 0; j < result.Length; j++)
|
||||
{
|
||||
double c0j = ((NumericToken)C0[j]).Double;
|
||||
double c1j = ((NumericToken)C1[j]).Double;
|
||||
result[j] = c0j + xToN * (c1j - c0j);
|
||||
}
|
||||
|
||||
return ClipToRange(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The C0 values of the function, 0 if empty.
|
||||
/// </summary>
|
||||
public ArrayToken C0 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The C1 values of the function, 1 if empty.
|
||||
/// </summary>
|
||||
public ArrayToken C1 { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The exponent of the function.
|
||||
/// </summary>
|
||||
public double N { get; }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "FunctionType2{"
|
||||
+ "C0: " + C0 + " "
|
||||
+ "C1: " + C1 + " "
|
||||
+ "N: " + N + "}";
|
||||
}
|
||||
}
|
||||
}
|
172
src/UglyToad.PdfPig/Functions/PdfFunctionType3.cs
Normal file
172
src/UglyToad.PdfPig/Functions/PdfFunctionType3.cs
Normal file
@ -0,0 +1,172 @@
|
||||
namespace UglyToad.PdfPig.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function
|
||||
/// </summary>
|
||||
internal sealed class PdfFunctionType3 : PdfFunction
|
||||
{
|
||||
private ArrayToken functions;
|
||||
private ArrayToken encode;
|
||||
private ArrayToken bounds;
|
||||
private double[] boundsValues;
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function
|
||||
/// </summary>
|
||||
internal PdfFunctionType3(DictionaryToken function, IReadOnlyList<PdfFunction> functionsArray)
|
||||
: base(function)
|
||||
{
|
||||
if (functionsArray == null || functionsArray.Count == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(functionsArray));
|
||||
}
|
||||
this.FunctionsArray = functionsArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stitching function
|
||||
/// </summary>
|
||||
internal PdfFunctionType3(StreamToken function, IReadOnlyList<PdfFunction> functionsArray)
|
||||
: base(function)
|
||||
{
|
||||
if (functionsArray == null || functionsArray.Count == 0)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(functionsArray));
|
||||
}
|
||||
this.FunctionsArray = functionsArray;
|
||||
}
|
||||
|
||||
public override FunctionTypes FunctionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return FunctionTypes.Stitching;
|
||||
}
|
||||
}
|
||||
|
||||
public override double[] Eval(double[] input)
|
||||
{
|
||||
// This function is known as a "stitching" function. Based on the input, it decides which child function to call.
|
||||
// All functions in the array are 1-value-input functions
|
||||
// See PDF Reference section 3.9.3.
|
||||
PdfFunction function = null;
|
||||
double x = input[0];
|
||||
PdfRange domain = GetDomainForInput(0);
|
||||
// clip input value to domain
|
||||
x = ClipToRange(x, domain.Min, domain.Max);
|
||||
|
||||
if (FunctionsArray.Count == 1)
|
||||
{
|
||||
// This doesn't make sense but it may happen...
|
||||
function = FunctionsArray[0];
|
||||
PdfRange encRange = GetEncodeForParameter(0);
|
||||
x = Interpolate(x, domain.Min, domain.Max, encRange.Min, encRange.Max);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (boundsValues == null)
|
||||
{
|
||||
boundsValues = Bounds.Data.OfType<NumericToken>().Select(t => t.Double).ToArray();
|
||||
}
|
||||
|
||||
int boundsSize = boundsValues.Length;
|
||||
// create a combined array containing the domain and the bounds values
|
||||
// domain.min, bounds[0], bounds[1], ...., bounds[boundsSize-1], domain.max
|
||||
double[] partitionValues = new double[boundsSize + 2];
|
||||
int partitionValuesSize = partitionValues.Length;
|
||||
partitionValues[0] = domain.Min;
|
||||
partitionValues[partitionValuesSize - 1] = domain.Max;
|
||||
Array.Copy(boundsValues, 0, partitionValues, 1, boundsSize); // System.arraycopy(boundsValues, 0, partitionValues, 1, boundsSize);
|
||||
// find the partition
|
||||
for (int i = 0; i < partitionValuesSize - 1; i++)
|
||||
{
|
||||
if (x >= partitionValues[i] &&
|
||||
(x < partitionValues[i + 1] || (i == partitionValuesSize - 2 && x == partitionValues[i + 1])))
|
||||
{
|
||||
function = FunctionsArray[i];
|
||||
PdfRange encRange = GetEncodeForParameter(i);
|
||||
x = Interpolate(x, partitionValues[i], partitionValues[i + 1], encRange.Min, encRange.Max);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (function == null)
|
||||
{
|
||||
throw new IOException("partition not found in type 3 function");
|
||||
}
|
||||
double[] functionValues = new double[] { x };
|
||||
// calculate the output values using the chosen function
|
||||
double[] functionResult = function.Eval(functionValues);
|
||||
// clip to range if available
|
||||
return ClipToRange(functionResult);
|
||||
}
|
||||
|
||||
public IReadOnlyList<PdfFunction> FunctionsArray { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns all functions values as <see cref="ArrayToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>the functions array. </returns>
|
||||
public ArrayToken Functions
|
||||
{
|
||||
get
|
||||
{
|
||||
if (functions == null && !GetDictionary().TryGet<ArrayToken>(NameToken.Functions, out functions))
|
||||
{
|
||||
throw new ArgumentNullException(NameToken.Functions);
|
||||
}
|
||||
return functions;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all bounds values as <see cref="ArrayToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>the bounds array.</returns>
|
||||
public ArrayToken Bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if (bounds == null && !GetDictionary().TryGet<ArrayToken>(NameToken.Bounds, out bounds))
|
||||
{
|
||||
throw new ArgumentNullException(NameToken.Bounds);
|
||||
}
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all encode values as <see cref="ArrayToken"/>.
|
||||
/// </summary>
|
||||
/// <returns>the encode array.</returns>
|
||||
public ArrayToken Encode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (encode == null && !GetDictionary().TryGet<ArrayToken>(NameToken.Encode, out encode))
|
||||
{
|
||||
throw new ArgumentNullException(NameToken.Encode);
|
||||
}
|
||||
return encode;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the encode for the input parameter.
|
||||
/// </summary>
|
||||
/// <param name="n">The function parameter number.</param>
|
||||
/// <returns>The encode parameter range or null if none is set.</returns>
|
||||
private PdfRange GetEncodeForParameter(int n)
|
||||
{
|
||||
ArrayToken encodeValues = Encode;
|
||||
return new PdfRange(encodeValues.Data.OfType<NumericToken>().Select(t => t.Double), n);
|
||||
}
|
||||
}
|
||||
}
|
71
src/UglyToad.PdfPig/Functions/PdfFunctionType4.cs
Normal file
71
src/UglyToad.PdfPig/Functions/PdfFunctionType4.cs
Normal file
@ -0,0 +1,71 @@
|
||||
namespace UglyToad.PdfPig.Functions
|
||||
{
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Functions.Type4;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// PostScript calculator function
|
||||
/// </summary>
|
||||
internal sealed class PdfFunctionType4 : PdfFunction
|
||||
{
|
||||
private readonly Operators operators = new Operators();
|
||||
private readonly InstructionSequence instructions;
|
||||
|
||||
/// <summary>
|
||||
/// PostScript calculator function
|
||||
/// </summary>
|
||||
internal PdfFunctionType4(StreamToken function) : base(function)
|
||||
{
|
||||
byte[] bytes = FunctionStream.Data.ToArray();
|
||||
string str = OtherEncodings.Iso88591.GetString(bytes);
|
||||
this.instructions = InstructionSequenceBuilder.Parse(str);
|
||||
}
|
||||
|
||||
public override FunctionTypes FunctionType
|
||||
{
|
||||
get
|
||||
{
|
||||
return FunctionTypes.PostScript;
|
||||
}
|
||||
}
|
||||
|
||||
public override double[] Eval(double[] input)
|
||||
{
|
||||
//Setup the input values
|
||||
ExecutionContext context = new ExecutionContext(operators);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
PdfRange domain = GetDomainForInput(i);
|
||||
double value = ClipToRange(input[i], domain.Min, domain.Max);
|
||||
context.Stack.Push(value);
|
||||
}
|
||||
|
||||
//Execute the type 4 function.
|
||||
instructions.Execute(context);
|
||||
|
||||
//Extract the output values
|
||||
int numberOfOutputValues = NumberOfOutputParameters;
|
||||
int numberOfActualOutputValues = context.Stack.Count;
|
||||
if (numberOfActualOutputValues < numberOfOutputValues)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("The type 4 function returned "
|
||||
+ numberOfActualOutputValues
|
||||
+ " values but the Range entry indicates that "
|
||||
+ numberOfOutputValues + " values be returned.");
|
||||
}
|
||||
double[] outputValues = new double[numberOfOutputValues];
|
||||
for (int i = numberOfOutputValues - 1; i >= 0; i--)
|
||||
{
|
||||
PdfRange range = GetRangeForOutput(i);
|
||||
outputValues[i] = context.PopReal();
|
||||
outputValues[i] = ClipToRange(outputValues[i], range.Min, range.Max);
|
||||
}
|
||||
|
||||
//Return the resulting array
|
||||
return outputValues;
|
||||
}
|
||||
}
|
||||
}
|
398
src/UglyToad.PdfPig/Functions/Type4/ArithmeticOperators.cs
Normal file
398
src/UglyToad.PdfPig/Functions/Type4/ArithmeticOperators.cs
Normal file
@ -0,0 +1,398 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the arithmetic operators such as "add" and "sub".
|
||||
/// </summary>
|
||||
internal sealed class ArithmeticOperators
|
||||
{
|
||||
private ArithmeticOperators()
|
||||
{
|
||||
// Private constructor.
|
||||
}
|
||||
|
||||
private static double ToRadians(double val)
|
||||
{
|
||||
return (Math.PI / 180.0) * val;
|
||||
}
|
||||
|
||||
private static double ToDegrees(double val)
|
||||
{
|
||||
return (180.0 / Math.PI) * val;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "Abs" operator.
|
||||
/// </summary>
|
||||
internal sealed class Abs : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int numi)
|
||||
{
|
||||
context.Stack.Push(Math.Abs(numi));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(Math.Abs(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "add" operator.
|
||||
/// </summary>
|
||||
internal sealed class Add : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num2 = context.PopNumber();
|
||||
var num1 = context.PopNumber();
|
||||
if (num1 is int num1i && num2 is int num2i)
|
||||
{
|
||||
long sum = (long)num1i + (long)num2i; // Keep both cast here
|
||||
if (sum < int.MinValue || sum > int.MaxValue)
|
||||
{
|
||||
context.Stack.Push((double)sum);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push((int)sum);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double sum = Convert.ToDouble(num1) + Convert.ToDouble(num2);
|
||||
context.Stack.Push(sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "atan" operator.
|
||||
/// </summary>
|
||||
internal sealed class Atan : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double den = context.PopReal();
|
||||
double num = context.PopReal();
|
||||
double atan = Math.Atan2(num, den);
|
||||
atan = ToDegrees(atan) % 360;
|
||||
if (atan < 0)
|
||||
{
|
||||
atan += 360;
|
||||
}
|
||||
context.Stack.Push(atan);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "ceiling" operator.
|
||||
/// </summary>
|
||||
internal sealed class Ceiling : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int numi)
|
||||
{
|
||||
context.Stack.Push(numi);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(Math.Ceiling(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "cos" operator.
|
||||
/// </summary>
|
||||
internal sealed class Cos : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double angle = context.PopReal();
|
||||
double cos = Math.Cos(ToRadians(angle));
|
||||
context.Stack.Push(cos);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "cvi" operator.
|
||||
/// </summary>
|
||||
internal sealed class Cvi : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
context.Stack.Push((int)Math.Truncate(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "cvr" operator.
|
||||
/// </summary>
|
||||
internal sealed class Cvr : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
context.Stack.Push(Convert.ToDouble(num));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "div" operator.
|
||||
/// </summary>
|
||||
internal sealed class Div : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double num2 = Convert.ToDouble(context.PopNumber());
|
||||
double num1 = Convert.ToDouble(context.PopNumber());
|
||||
context.Stack.Push(num1 / num2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "exp" operator.
|
||||
/// </summary>
|
||||
internal sealed class Exp : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double exp = Convert.ToDouble(context.PopNumber());
|
||||
double base_ = Convert.ToDouble(context.PopNumber());
|
||||
double value = Math.Pow(base_, exp);
|
||||
context.Stack.Push(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "floor" operator.
|
||||
/// </summary>
|
||||
internal sealed class Floor : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int numi)
|
||||
{
|
||||
context.Stack.Push(numi);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(Math.Floor(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "idiv" operator.
|
||||
/// </summary>
|
||||
internal sealed class IDiv : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int num2 = context.PopInt();
|
||||
int num1 = context.PopInt();
|
||||
context.Stack.Push(num1 / num2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "ln" operator.
|
||||
/// </summary>
|
||||
internal sealed class Ln : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
context.Stack.Push(Math.Log(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "log" operator.
|
||||
/// </summary>
|
||||
internal sealed class Log : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
context.Stack.Push(Math.Log10(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "mod" operator.
|
||||
/// </summary>
|
||||
internal sealed class Mod : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int int2 = context.PopInt();
|
||||
int int1 = context.PopInt();
|
||||
context.Stack.Push(int1 % int2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "mul" operator.
|
||||
/// </summary>
|
||||
internal sealed class Mul : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num2 = context.PopNumber();
|
||||
var num1 = context.PopNumber();
|
||||
if (num1 is int num1i && num2 is int num2i)
|
||||
{
|
||||
long result = (long)num1i * (long)num2i; // Keep both cast here
|
||||
if (result >= int.MinValue && result <= int.MaxValue)
|
||||
{
|
||||
context.Stack.Push((int)result);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push((double)result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double result = Convert.ToDouble(num1) * Convert.ToDouble(num2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "neg" operator.
|
||||
/// </summary>
|
||||
internal sealed class Neg : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int v)
|
||||
{
|
||||
if (v == int.MinValue)
|
||||
{
|
||||
context.Stack.Push(-Convert.ToDouble(v));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(-v);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(-Convert.ToDouble(num));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "round" operator.
|
||||
/// </summary>
|
||||
internal sealed class Round : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int numi)
|
||||
{
|
||||
context.Stack.Push(numi);
|
||||
}
|
||||
else
|
||||
{
|
||||
double value = Convert.ToDouble(num);
|
||||
// The way java works...
|
||||
double roundedValue = value < 0 ? Math.Round(value) : Math.Round(value, MidpointRounding.AwayFromZero);
|
||||
context.Stack.Push(roundedValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "sin" operator.
|
||||
/// </summary>
|
||||
internal sealed class Sin : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double angle = context.PopReal();
|
||||
double sin = Math.Sin(ToRadians(angle));
|
||||
context.Stack.Push(sin);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "sqrt" operator.
|
||||
/// </summary>
|
||||
internal sealed class Sqrt : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
double num = context.PopReal();
|
||||
if (num < 0)
|
||||
{
|
||||
throw new ArgumentException("argument must be nonnegative");
|
||||
}
|
||||
context.Stack.Push(Math.Sqrt(num));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "sub" operator.
|
||||
/// </summary>
|
||||
internal sealed class Sub : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num2 = context.PopNumber();
|
||||
var num1 = context.PopNumber();
|
||||
if (num1 is int num1i && num2 is int num2i)
|
||||
{
|
||||
long result = (long)num1i - (long)num2i; // Keep both cast here
|
||||
if (result < int.MinValue || result > int.MaxValue)
|
||||
{
|
||||
context.Stack.Push((double)result);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push((int)result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double result = Convert.ToDouble(num1) - Convert.ToDouble(num2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// the "truncate" operator.
|
||||
/// </summary>
|
||||
internal sealed class Truncate : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
var num = context.PopNumber();
|
||||
if (num is int numi)
|
||||
{
|
||||
context.Stack.Push(numi);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(Math.Truncate(Convert.ToDouble(num)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
160
src/UglyToad.PdfPig/Functions/Type4/BitwiseOperators.cs
Normal file
160
src/UglyToad.PdfPig/Functions/Type4/BitwiseOperators.cs
Normal file
@ -0,0 +1,160 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal sealed class BitwiseOperators
|
||||
{
|
||||
private BitwiseOperators()
|
||||
{
|
||||
// Private constructor.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for logical operators.
|
||||
/// </summary>
|
||||
internal abstract class AbstractLogicalOperator : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
object op2 = context.Stack.Pop();
|
||||
object op1 = context.Stack.Pop();
|
||||
if (op1 is bool bool1 && op2 is bool bool2)
|
||||
{
|
||||
bool result = ApplyForBoolean(bool1, bool2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
else if (op1 is int int1 && op2 is int int2)
|
||||
{
|
||||
int result = ApplyForInt(int1, int2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Operands must be bool/bool or int/int");
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract bool ApplyForBoolean(bool bool1, bool bool2);
|
||||
|
||||
protected abstract int ApplyForInt(int int1, int int2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "and" operator.
|
||||
/// </summary>
|
||||
internal sealed class And : AbstractLogicalOperator
|
||||
{
|
||||
protected override bool ApplyForBoolean(bool bool1, bool bool2)
|
||||
{
|
||||
return bool1 && bool2;
|
||||
}
|
||||
|
||||
protected override int ApplyForInt(int int1, int int2)
|
||||
{
|
||||
return int1 & int2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "bitshift" operator.
|
||||
/// </summary>
|
||||
internal sealed class Bitshift : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int shift = Convert.ToInt32(context.Stack.Pop());
|
||||
int int1 = Convert.ToInt32(context.Stack.Pop());
|
||||
if (shift < 0)
|
||||
{
|
||||
int result = int1 >> Math.Abs(shift);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
int result = int1 << shift;
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "false" operator.
|
||||
/// </summary>
|
||||
internal sealed class False : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
context.Stack.Push(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "not" operator.
|
||||
/// </summary>
|
||||
internal sealed class Not : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
object op1 = context.Stack.Pop();
|
||||
if (op1 is bool bool1)
|
||||
{
|
||||
bool result = !bool1;
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
else if (op1 is int int1)
|
||||
{
|
||||
int result = -int1;
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Operand must be bool or int");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "or" operator.
|
||||
/// </summary>
|
||||
internal sealed class Or : AbstractLogicalOperator
|
||||
{
|
||||
protected override bool ApplyForBoolean(bool bool1, bool bool2)
|
||||
{
|
||||
return bool1 || bool2;
|
||||
}
|
||||
|
||||
protected override int ApplyForInt(int int1, int int2)
|
||||
{
|
||||
return int1 | int2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "true" operator.
|
||||
/// </summary>
|
||||
internal sealed class True : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
context.Stack.Push(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "xor" operator.
|
||||
/// </summary>
|
||||
internal sealed class Xor : AbstractLogicalOperator
|
||||
{
|
||||
protected override bool ApplyForBoolean(bool bool1, bool bool2)
|
||||
{
|
||||
return bool1 ^ bool2;
|
||||
}
|
||||
|
||||
protected override int ApplyForInt(int int1, int int2)
|
||||
{
|
||||
return int1 ^ int2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
53
src/UglyToad.PdfPig/Functions/Type4/ConditionalOperators.cs
Normal file
53
src/UglyToad.PdfPig/Functions/Type4/ConditionalOperators.cs
Normal file
@ -0,0 +1,53 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the conditional operators such as "if" and "ifelse".
|
||||
/// </summary>
|
||||
internal sealed class ConditionalOperators
|
||||
{
|
||||
private ConditionalOperators()
|
||||
{
|
||||
// Private constructor.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "if" operator.
|
||||
/// </summary>
|
||||
internal sealed class If : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
InstructionSequence proc = (InstructionSequence)context.Stack.Pop();
|
||||
bool condition = (bool)context.Stack.Pop();
|
||||
if (condition)
|
||||
{
|
||||
proc.Execute(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "ifelse" operator.
|
||||
/// </summary>
|
||||
internal sealed class IfElse : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
InstructionSequence proc2 = (InstructionSequence)context.Stack.Pop();
|
||||
InstructionSequence proc1 = (InstructionSequence)context.Stack.Pop();
|
||||
bool condition = Convert.ToBoolean(context.Stack.Pop());
|
||||
if (condition)
|
||||
{
|
||||
proc1.Execute(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
proc2.Execute(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
src/UglyToad.PdfPig/Functions/Type4/ExecutionContext.cs
Normal file
81
src/UglyToad.PdfPig/Functions/Type4/ExecutionContext.cs
Normal file
@ -0,0 +1,81 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
internal sealed class ExecutionContext
|
||||
{
|
||||
private readonly Operators operators;
|
||||
|
||||
/// <summary>
|
||||
/// The stack used by this execution context.
|
||||
/// </summary>
|
||||
public Stack<object> Stack { get; private set; } = new Stack<object>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new execution context.
|
||||
/// </summary>
|
||||
/// <param name="operatorSet">the operator set</param>
|
||||
public ExecutionContext(Operators operatorSet)
|
||||
{
|
||||
this.operators = operatorSet;
|
||||
}
|
||||
|
||||
internal void AddAllToStack(IEnumerable<object> values)
|
||||
{
|
||||
var valuesList = values.ToList();
|
||||
valuesList.AddRange(Stack);
|
||||
valuesList.Reverse();
|
||||
this.Stack = new Stack<object>(valuesList);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the operator set used by this execution context.
|
||||
/// </summary>
|
||||
/// <returns>the operator set</returns>
|
||||
public Operators GetOperators()
|
||||
{
|
||||
return this.operators;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a number (int or real) from the stack. If it's neither data type, a <see cref="InvalidCastException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <returns>the number</returns>
|
||||
public object PopNumber()
|
||||
{
|
||||
object popped = this.Stack.Pop();
|
||||
if (popped is int || popped is double || popped is float)
|
||||
{
|
||||
return popped;
|
||||
}
|
||||
throw new InvalidCastException("The object popped is neither an integer or a real.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a value of type int from the stack. If the value is not of type int, a <see cref="InvalidCastException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <returns>the int value</returns>
|
||||
public int PopInt()
|
||||
{
|
||||
object popped = Stack.Pop();
|
||||
if (popped is int poppedInt)
|
||||
{
|
||||
return poppedInt;
|
||||
}
|
||||
throw new InvalidCastException("PopInt cannot be done as the value is not integer");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pops a number from the stack and returns it as a real value. If the value is not of a numeric type,
|
||||
/// a <see cref="InvalidCastException"/> is thrown.
|
||||
/// </summary>
|
||||
/// <returns>the real value</returns>
|
||||
public double PopReal()
|
||||
{
|
||||
return Convert.ToDouble(Stack.Pop());
|
||||
}
|
||||
}
|
||||
}
|
90
src/UglyToad.PdfPig/Functions/Type4/InstructionSequence.cs
Normal file
90
src/UglyToad.PdfPig/Functions/Type4/InstructionSequence.cs
Normal file
@ -0,0 +1,90 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
internal sealed class InstructionSequence
|
||||
{
|
||||
private readonly List<object> instructions = new List<object>();
|
||||
|
||||
/// <summary>
|
||||
/// Add a name (ex. an operator)
|
||||
/// </summary>
|
||||
/// <param name="name">the name</param>
|
||||
public void AddName(string name)
|
||||
{
|
||||
this.instructions.Add(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an int value.
|
||||
/// </summary>
|
||||
/// <param name="value">the value</param>
|
||||
public void AddInteger(int value)
|
||||
{
|
||||
this.instructions.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a real value.
|
||||
/// </summary>
|
||||
/// <param name="value">the value</param>
|
||||
public void AddReal(double value)
|
||||
{
|
||||
this.instructions.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a bool value.
|
||||
/// </summary>
|
||||
/// <param name="value">the value</param>
|
||||
public void AddBoolean(bool value)
|
||||
{
|
||||
this.instructions.Add(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a proc (sub-sequence of instructions).
|
||||
/// </summary>
|
||||
/// <param name="child">the child proc</param>
|
||||
public void AddProc(InstructionSequence child)
|
||||
{
|
||||
this.instructions.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the instruction sequence.
|
||||
/// </summary>
|
||||
/// <param name="context">the execution context</param>
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
foreach (object o in instructions)
|
||||
{
|
||||
if (o is string name)
|
||||
{
|
||||
Operator cmd = context.GetOperators().GetOperator(name);
|
||||
if (cmd != null)
|
||||
{
|
||||
cmd.Execute(context);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Unknown operator or name: " + name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Stack.Push(o);
|
||||
}
|
||||
}
|
||||
|
||||
//Handles top-level procs that simply need to be executed
|
||||
while (context.Stack.Any() && context.Stack.Peek() is InstructionSequence)
|
||||
{
|
||||
InstructionSequence nested = (InstructionSequence)context.Stack.Pop();
|
||||
nested.Execute(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Basic parser for Type 4 functions which is used to build up instruction sequences.
|
||||
/// </summary>
|
||||
internal sealed class InstructionSequenceBuilder : Parser.AbstractSyntaxHandler
|
||||
{
|
||||
private readonly InstructionSequence mainSequence = new InstructionSequence();
|
||||
private readonly Stack<InstructionSequence> seqStack = new Stack<InstructionSequence>();
|
||||
|
||||
private InstructionSequenceBuilder()
|
||||
{
|
||||
this.seqStack.Push(this.mainSequence);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the instruction sequence that has been build from the syntactic elements.
|
||||
/// </summary>
|
||||
/// <returns>the instruction sequence</returns>
|
||||
public InstructionSequence GetInstructionSequence()
|
||||
{
|
||||
return this.mainSequence;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the given text into an instruction sequence representing a Type 4 function that can be executed.
|
||||
/// </summary>
|
||||
/// <param name="text">the Type 4 function text</param>
|
||||
/// <returns>the instruction sequence</returns>
|
||||
public static InstructionSequence Parse(string text)
|
||||
{
|
||||
InstructionSequenceBuilder builder = new InstructionSequenceBuilder();
|
||||
Parser.Parse(text, builder);
|
||||
return builder.GetInstructionSequence();
|
||||
}
|
||||
|
||||
private InstructionSequence GetCurrentSequence()
|
||||
{
|
||||
return this.seqStack.Peek();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Token(char[] text)
|
||||
{
|
||||
string val = string.Concat(text);
|
||||
Token(val);
|
||||
}
|
||||
|
||||
public override void Token(string token)
|
||||
{
|
||||
if ("{".Equals(token))
|
||||
{
|
||||
InstructionSequence child = new InstructionSequence();
|
||||
GetCurrentSequence().AddProc(child);
|
||||
this.seqStack.Push(child);
|
||||
}
|
||||
else if ("}".Equals(token))
|
||||
{
|
||||
this.seqStack.Pop();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (int.TryParse(token, out int tokenInt))
|
||||
{
|
||||
GetCurrentSequence().AddInteger(tokenInt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (double.TryParse(token, out double tokenFloat))
|
||||
{
|
||||
GetCurrentSequence().AddReal(tokenFloat);
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO Maybe implement radix numbers, such as 8#1777 or 16#FFFE
|
||||
|
||||
GetCurrentSequence().AddName(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14
src/UglyToad.PdfPig/Functions/Type4/Operator.cs
Normal file
14
src/UglyToad.PdfPig/Functions/Type4/Operator.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for PostScript operators.e
|
||||
/// </summary>
|
||||
internal interface Operator
|
||||
{
|
||||
/// <summary>
|
||||
/// Executes the operator. The method can inspect and manipulate the stack.
|
||||
/// </summary>
|
||||
/// <param name="context">the execution context</param>
|
||||
void Execute(ExecutionContext context);
|
||||
}
|
||||
}
|
124
src/UglyToad.PdfPig/Functions/Type4/Operators.cs
Normal file
124
src/UglyToad.PdfPig/Functions/Type4/Operators.cs
Normal file
@ -0,0 +1,124 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// This class provides all the supported operators.
|
||||
/// </summary>
|
||||
internal sealed class Operators
|
||||
{
|
||||
//Arithmetic operators
|
||||
private static readonly Operator ABS = new ArithmeticOperators.Abs();
|
||||
private static readonly Operator ADD = new ArithmeticOperators.Add();
|
||||
private static readonly Operator ATAN = new ArithmeticOperators.Atan();
|
||||
private static readonly Operator CEILING = new ArithmeticOperators.Ceiling();
|
||||
private static readonly Operator COS = new ArithmeticOperators.Cos();
|
||||
private static readonly Operator CVI = new ArithmeticOperators.Cvi();
|
||||
private static readonly Operator CVR = new ArithmeticOperators.Cvr();
|
||||
private static readonly Operator DIV = new ArithmeticOperators.Div();
|
||||
private static readonly Operator EXP = new ArithmeticOperators.Exp();
|
||||
private static readonly Operator FLOOR = new ArithmeticOperators.Floor();
|
||||
private static readonly Operator IDIV = new ArithmeticOperators.IDiv();
|
||||
private static readonly Operator LN = new ArithmeticOperators.Ln();
|
||||
private static readonly Operator LOG = new ArithmeticOperators.Log();
|
||||
private static readonly Operator MOD = new ArithmeticOperators.Mod();
|
||||
private static readonly Operator MUL = new ArithmeticOperators.Mul();
|
||||
private static readonly Operator NEG = new ArithmeticOperators.Neg();
|
||||
private static readonly Operator ROUND = new ArithmeticOperators.Round();
|
||||
private static readonly Operator SIN = new ArithmeticOperators.Sin();
|
||||
private static readonly Operator SQRT = new ArithmeticOperators.Sqrt();
|
||||
private static readonly Operator SUB = new ArithmeticOperators.Sub();
|
||||
private static readonly Operator TRUNCATE = new ArithmeticOperators.Truncate();
|
||||
|
||||
//Relational, boolean and bitwise operators
|
||||
private static readonly Operator AND = new BitwiseOperators.And();
|
||||
private static readonly Operator BITSHIFT = new BitwiseOperators.Bitshift();
|
||||
private static readonly Operator EQ = new RelationalOperators.Eq();
|
||||
private static readonly Operator FALSE = new BitwiseOperators.False();
|
||||
private static readonly Operator GE = new RelationalOperators.Ge();
|
||||
private static readonly Operator GT = new RelationalOperators.Gt();
|
||||
private static readonly Operator LE = new RelationalOperators.Le();
|
||||
private static readonly Operator LT = new RelationalOperators.Lt();
|
||||
private static readonly Operator NE = new RelationalOperators.Ne();
|
||||
private static readonly Operator NOT = new BitwiseOperators.Not();
|
||||
private static readonly Operator OR = new BitwiseOperators.Or();
|
||||
private static readonly Operator TRUE = new BitwiseOperators.True();
|
||||
private static readonly Operator XOR = new BitwiseOperators.Xor();
|
||||
|
||||
//Conditional operators
|
||||
private static readonly Operator IF = new ConditionalOperators.If();
|
||||
private static readonly Operator IFELSE = new ConditionalOperators.IfElse();
|
||||
|
||||
//Stack operators
|
||||
private static readonly Operator COPY = new StackOperators.Copy();
|
||||
private static readonly Operator DUP = new StackOperators.Dup();
|
||||
private static readonly Operator EXCH = new StackOperators.Exch();
|
||||
private static readonly Operator INDEX = new StackOperators.Index();
|
||||
private static readonly Operator POP = new StackOperators.Pop();
|
||||
private static readonly Operator ROLL = new StackOperators.Roll();
|
||||
|
||||
private readonly Dictionary<string, Operator> operators = new Dictionary<string, Operator>();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new Operators object with the default set of operators.
|
||||
/// </summary>
|
||||
public Operators()
|
||||
{
|
||||
operators.Add("add", ADD);
|
||||
operators.Add("abs", ABS);
|
||||
operators.Add("atan", ATAN);
|
||||
operators.Add("ceiling", CEILING);
|
||||
operators.Add("cos", COS);
|
||||
operators.Add("cvi", CVI);
|
||||
operators.Add("cvr", CVR);
|
||||
operators.Add("div", DIV);
|
||||
operators.Add("exp", EXP);
|
||||
operators.Add("floor", FLOOR);
|
||||
operators.Add("idiv", IDIV);
|
||||
operators.Add("ln", LN);
|
||||
operators.Add("log", LOG);
|
||||
operators.Add("mod", MOD);
|
||||
operators.Add("mul", MUL);
|
||||
operators.Add("neg", NEG);
|
||||
operators.Add("round", ROUND);
|
||||
operators.Add("sin", SIN);
|
||||
operators.Add("sqrt", SQRT);
|
||||
operators.Add("sub", SUB);
|
||||
operators.Add("truncate", TRUNCATE);
|
||||
|
||||
operators.Add("and", AND);
|
||||
operators.Add("bitshift", BITSHIFT);
|
||||
operators.Add("eq", EQ);
|
||||
operators.Add("false", FALSE);
|
||||
operators.Add("ge", GE);
|
||||
operators.Add("gt", GT);
|
||||
operators.Add("le", LE);
|
||||
operators.Add("lt", LT);
|
||||
operators.Add("ne", NE);
|
||||
operators.Add("not", NOT);
|
||||
operators.Add("or", OR);
|
||||
operators.Add("true", TRUE);
|
||||
operators.Add("xor", XOR);
|
||||
|
||||
operators.Add("if", IF);
|
||||
operators.Add("ifelse", IFELSE);
|
||||
|
||||
operators.Add("copy", COPY);
|
||||
operators.Add("dup", DUP);
|
||||
operators.Add("exch", EXCH);
|
||||
operators.Add("index", INDEX);
|
||||
operators.Add("pop", POP);
|
||||
operators.Add("roll", ROLL);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the operator for the given operator name.
|
||||
/// </summary>
|
||||
/// <param name="operatorName">the operator name</param>
|
||||
/// <returns>the operator (or null if there's no such operator</returns>
|
||||
public Operator GetOperator(string operatorName)
|
||||
{
|
||||
return this.operators[operatorName];
|
||||
}
|
||||
}
|
||||
}
|
310
src/UglyToad.PdfPig/Functions/Type4/Parser.cs
Normal file
310
src/UglyToad.PdfPig/Functions/Type4/Parser.cs
Normal file
@ -0,0 +1,310 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Parser for PDF Type 4 functions. This implements a small subset of the PostScript
|
||||
/// language but is no full PostScript interpreter.
|
||||
/// </summary>
|
||||
internal sealed class Parser
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to indicate the parsers current state.
|
||||
/// </summary>
|
||||
internal enum State
|
||||
{
|
||||
NEWLINE, WHITESPACE, COMMENT, TOKEN
|
||||
}
|
||||
|
||||
private Parser()
|
||||
{
|
||||
//nop
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a Type 4 function and sends the syntactic elements to the given syntax handler.
|
||||
/// </summary>
|
||||
/// <param name="input">the text source</param>
|
||||
/// <param name="handler">the syntax handler</param>
|
||||
public static void Parse(string input, SyntaxHandler handler)
|
||||
{
|
||||
Tokenizer tokenizer = new Tokenizer(input, handler);
|
||||
tokenizer.Tokenize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This interface defines all possible syntactic elements of a Type 4 function.
|
||||
/// It is called by the parser as the function is interpreted.
|
||||
/// </summary>
|
||||
public interface SyntaxHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates that a new line starts.
|
||||
/// </summary>
|
||||
/// <param name="text">the new line character (CR, LF, CR/LF or FF)</param>
|
||||
void NewLine(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Called when whitespace characters are encountered.
|
||||
/// </summary>
|
||||
/// <param name="text">the whitespace text</param>
|
||||
void Whitespace(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Called when a token is encountered. No distinction between operators and values is done here.
|
||||
/// </summary>
|
||||
/// <param name="text">the token text</param>
|
||||
void Token(string text);
|
||||
|
||||
/// <summary>
|
||||
/// Called for a comment.
|
||||
/// </summary>
|
||||
/// <param name="text">the comment</param>
|
||||
void Comment(string text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for a <see cref="SyntaxHandler"/>.
|
||||
/// </summary>
|
||||
public abstract class AbstractSyntaxHandler : SyntaxHandler
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public void Comment(string text)
|
||||
{
|
||||
//nop
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void NewLine(string text)
|
||||
{
|
||||
//nop
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Whitespace(string text)
|
||||
{
|
||||
//nop
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract void Token(string text);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tokenizer for Type 4 functions.
|
||||
/// </summary>
|
||||
internal class Tokenizer
|
||||
{
|
||||
private const char NUL = '\u0000'; //NUL
|
||||
private const char EOT = '\u0004'; //END OF TRANSMISSION
|
||||
private const char TAB = '\u0009'; //TAB CHARACTER
|
||||
private const char FF = '\u000C'; //FORM FEED
|
||||
private const char CR = '\r'; //CARRIAGE RETURN
|
||||
private const char LF = '\n'; //LINE FEED
|
||||
private const char SPACE = '\u0020'; //SPACE
|
||||
|
||||
private readonly string input;
|
||||
private int index;
|
||||
private readonly SyntaxHandler handler;
|
||||
private State state = State.WHITESPACE;
|
||||
private readonly StringBuilder buffer = new StringBuilder();
|
||||
|
||||
internal Tokenizer(string text, SyntaxHandler syntaxHandler)
|
||||
{
|
||||
this.input = text;
|
||||
this.handler = syntaxHandler;
|
||||
}
|
||||
|
||||
private bool HasMore()
|
||||
{
|
||||
return index < input.Length;
|
||||
}
|
||||
|
||||
private char CurrentChar()
|
||||
{
|
||||
return input[index];
|
||||
}
|
||||
|
||||
private char NextChar()
|
||||
{
|
||||
index++;
|
||||
if (!HasMore())
|
||||
{
|
||||
return EOT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return CurrentChar();
|
||||
}
|
||||
}
|
||||
|
||||
private char Peek()
|
||||
{
|
||||
if (index < input.Length - 1)
|
||||
{
|
||||
return input[index + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return EOT;
|
||||
}
|
||||
}
|
||||
|
||||
private State NextState()
|
||||
{
|
||||
char ch = CurrentChar();
|
||||
switch (ch)
|
||||
{
|
||||
case CR:
|
||||
case LF:
|
||||
case FF: //FF
|
||||
state = State.NEWLINE;
|
||||
break;
|
||||
case NUL:
|
||||
case TAB:
|
||||
case SPACE:
|
||||
state = State.WHITESPACE;
|
||||
break;
|
||||
case '%':
|
||||
state = State.COMMENT;
|
||||
break;
|
||||
default:
|
||||
state = State.TOKEN;
|
||||
break;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
internal void Tokenize()
|
||||
{
|
||||
while (HasMore())
|
||||
{
|
||||
buffer.Length = 0;
|
||||
NextState();
|
||||
switch (state)
|
||||
{
|
||||
case State.NEWLINE:
|
||||
ScanNewLine();
|
||||
break;
|
||||
case State.WHITESPACE:
|
||||
ScanWhitespace();
|
||||
break;
|
||||
case State.COMMENT:
|
||||
ScanComment();
|
||||
break;
|
||||
default:
|
||||
ScanToken();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScanNewLine()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(state == State.NEWLINE);
|
||||
char ch = CurrentChar();
|
||||
buffer.Append(ch);
|
||||
if (ch == CR && Peek() == LF)
|
||||
{
|
||||
//CRLF is treated as one newline
|
||||
buffer.Append(NextChar());
|
||||
}
|
||||
handler.NewLine(buffer.ToString());
|
||||
NextChar();
|
||||
}
|
||||
|
||||
private void ScanWhitespace()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(state == State.WHITESPACE);
|
||||
buffer.Append(CurrentChar());
|
||||
|
||||
bool loop = true;
|
||||
while (HasMore() && loop)
|
||||
{
|
||||
char ch = NextChar();
|
||||
switch (ch)
|
||||
{
|
||||
case NUL:
|
||||
case TAB:
|
||||
case SPACE:
|
||||
buffer.Append(ch);
|
||||
break;
|
||||
default:
|
||||
loop = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
handler.Whitespace(buffer.ToString());
|
||||
}
|
||||
|
||||
private void ScanComment()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(state == State.COMMENT);
|
||||
buffer.Append(CurrentChar());
|
||||
|
||||
bool loop = true;
|
||||
while (HasMore() && loop)
|
||||
{
|
||||
char ch = NextChar();
|
||||
switch (ch)
|
||||
{
|
||||
case CR:
|
||||
case LF:
|
||||
case FF:
|
||||
loop = false;
|
||||
break;
|
||||
default:
|
||||
buffer.Append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//EOF reached
|
||||
handler.Comment(buffer.ToString());
|
||||
}
|
||||
|
||||
private void ScanToken()
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(state == State.TOKEN);
|
||||
char ch = CurrentChar();
|
||||
buffer.Append(ch);
|
||||
switch (ch)
|
||||
{
|
||||
case '{':
|
||||
case '}':
|
||||
handler.Token(buffer.ToString());
|
||||
NextChar();
|
||||
return;
|
||||
default:
|
||||
//continue
|
||||
break;
|
||||
}
|
||||
|
||||
bool loop = true;
|
||||
while (HasMore() && loop)
|
||||
{
|
||||
ch = NextChar();
|
||||
switch (ch)
|
||||
{
|
||||
case NUL:
|
||||
case TAB:
|
||||
case SPACE:
|
||||
case CR:
|
||||
case LF:
|
||||
case FF:
|
||||
case EOT:
|
||||
case '{':
|
||||
case '}':
|
||||
loop = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
buffer.Append(ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
//EOF reached
|
||||
handler.Token(buffer.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
118
src/UglyToad.PdfPig/Functions/Type4/RelationalOperators.cs
Normal file
118
src/UglyToad.PdfPig/Functions/Type4/RelationalOperators.cs
Normal file
@ -0,0 +1,118 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the relational operators such as "eq" and "le".
|
||||
/// </summary>
|
||||
internal sealed class RelationalOperators
|
||||
{
|
||||
private RelationalOperators()
|
||||
{
|
||||
// Private constructor.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "eq" operator.
|
||||
/// </summary>
|
||||
internal class Eq : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
object op2 = context.Stack.Pop();
|
||||
object op1 = context.Stack.Pop();
|
||||
bool result = IsEqual(op1, op2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
|
||||
protected virtual bool IsEqual(object op1, object op2)
|
||||
{
|
||||
bool result;
|
||||
if (op1 is double num1 && op2 is double num2)
|
||||
{
|
||||
result = num1.Equals(num2);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = op1.Equals(op2);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Abstract base class for number comparison operators.
|
||||
/// </summary>
|
||||
internal abstract class AbstractNumberComparisonOperator : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
object op2 = context.Stack.Pop();
|
||||
object op1 = context.Stack.Pop();
|
||||
double num1 = Convert.ToDouble(op1);
|
||||
double num2 = Convert.ToDouble(op2);
|
||||
bool result = Compare(num1, num2);
|
||||
context.Stack.Push(result);
|
||||
}
|
||||
|
||||
protected abstract bool Compare(double num1, double num2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "ge" operator.
|
||||
/// </summary>
|
||||
internal sealed class Ge : AbstractNumberComparisonOperator
|
||||
{
|
||||
protected override bool Compare(double num1, double num2)
|
||||
{
|
||||
return num1 >= num2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "gt" operator.
|
||||
/// </summary>
|
||||
internal sealed class Gt : AbstractNumberComparisonOperator
|
||||
{
|
||||
protected override bool Compare(double num1, double num2)
|
||||
{
|
||||
return num1 > num2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "le" operator.
|
||||
/// </summary>
|
||||
internal sealed class Le : AbstractNumberComparisonOperator
|
||||
{
|
||||
protected override bool Compare(double num1, double num2)
|
||||
{
|
||||
return num1 <= num2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "lt" operator.
|
||||
/// </summary>
|
||||
internal sealed class Lt : AbstractNumberComparisonOperator
|
||||
{
|
||||
protected override bool Compare(double num1, double num2)
|
||||
{
|
||||
return num1 < num2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "ne" operator.
|
||||
/// </summary>
|
||||
internal sealed class Ne : Eq
|
||||
{
|
||||
protected override bool IsEqual(object op1, object op2)
|
||||
{
|
||||
bool result = base.IsEqual(op1, op2);
|
||||
return !result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
142
src/UglyToad.PdfPig/Functions/Type4/StackOperators.cs
Normal file
142
src/UglyToad.PdfPig/Functions/Type4/StackOperators.cs
Normal file
@ -0,0 +1,142 @@
|
||||
namespace UglyToad.PdfPig.Functions.Type4
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Provides the stack operators such as "Pop" and "dup".
|
||||
/// </summary>
|
||||
internal sealed class StackOperators
|
||||
{
|
||||
private StackOperators()
|
||||
{
|
||||
// Private constructor.
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "copy" operator.
|
||||
/// </summary>
|
||||
internal sealed class Copy : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int n = ((int)context.Stack.Pop());
|
||||
if (n > 0)
|
||||
{
|
||||
int size = context.Stack.Count;
|
||||
// Need to copy to a new list to avoid ConcurrentModificationException
|
||||
List<object> copy = context.Stack.ToList().GetRange(size - n - 1, n);
|
||||
context.AddAllToStack(copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "dup" operator.
|
||||
/// </summary>
|
||||
internal sealed class Dup : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
context.Stack.Push(context.Stack.Peek());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "exch" operator.
|
||||
/// </summary>
|
||||
internal sealed class Exch : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
object any2 = context.Stack.Pop();
|
||||
object any1 = context.Stack.Pop();
|
||||
context.Stack.Push(any2);
|
||||
context.Stack.Push(any1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "index" operator.
|
||||
/// </summary>
|
||||
internal sealed class Index : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int n = Convert.ToInt32(context.Stack.Pop());
|
||||
if (n < 0)
|
||||
{
|
||||
throw new ArgumentException("rangecheck: " + n);
|
||||
}
|
||||
context.Stack.Push(context.Stack.ElementAt(n));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "Pop" operator.
|
||||
/// </summary>
|
||||
internal sealed class Pop : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
context.Stack.Pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements the "roll" operator.
|
||||
/// </summary>
|
||||
internal sealed class Roll : Operator
|
||||
{
|
||||
public void Execute(ExecutionContext context)
|
||||
{
|
||||
int j = (int)context.Stack.Pop();
|
||||
int n = (int)context.Stack.Pop();
|
||||
if (j == 0)
|
||||
{
|
||||
return; //Nothing to do
|
||||
}
|
||||
if (n < 0)
|
||||
{
|
||||
throw new ArgumentException("rangecheck: " + n);
|
||||
}
|
||||
|
||||
var rolled = new List<object>();
|
||||
var moved = new List<object>();
|
||||
if (j < 0)
|
||||
{
|
||||
//negative roll
|
||||
int n1 = n + j;
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
moved.Add(context.Stack.Pop());
|
||||
}
|
||||
for (int i = j; i < 0; i++)
|
||||
{
|
||||
rolled.Add(context.Stack.Pop());
|
||||
}
|
||||
|
||||
context.AddAllToStack(moved);
|
||||
context.AddAllToStack(rolled);
|
||||
}
|
||||
else
|
||||
{
|
||||
//positive roll
|
||||
int n1 = n - j;
|
||||
for (int i = j; i > 0; i--)
|
||||
{
|
||||
rolled.Add(context.Stack.Pop());
|
||||
}
|
||||
for (int i = 0; i < n1; i++)
|
||||
{
|
||||
moved.Add(context.Stack.Pop());
|
||||
}
|
||||
|
||||
context.AddAllToStack(rolled);
|
||||
context.AddAllToStack(moved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Graphics.Colors
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
using UglyToad.PdfPig.Util;
|
||||
using UglyToad.PdfPig.Util.JetBrains.Annotations;
|
||||
|
||||
@ -160,25 +160,25 @@
|
||||
/// which are then rendered with the usual primary or process colorants.
|
||||
/// </summary>
|
||||
public ColorSpaceDetails AlternateColorSpaceDetails { get; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// During subsequent painting operations, an application calls this function to transform a tint value into
|
||||
/// color component values in the alternate color space.
|
||||
/// The function is called with the tint value and must return the corresponding color component values.
|
||||
/// That is, the number of components and the interpretation of their values depend on the <see cref="AlternateColorSpaceDetails"/>.
|
||||
/// </summary>
|
||||
public Union<DictionaryToken, StreamToken> TintFunction { get; }
|
||||
/// </summary>
|
||||
public PdfFunction TintFunction { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="SeparationColorSpaceDetails"/>.
|
||||
/// </summary>
|
||||
public SeparationColorSpaceDetails(NameToken name,
|
||||
ColorSpaceDetails alternateColorSpaceDetails,
|
||||
Union<DictionaryToken, StreamToken> tintFunction)
|
||||
ColorSpaceDetails alternateColorSpaceDetails,
|
||||
PdfFunction tintFunction)
|
||||
: base(ColorSpace.Separation)
|
||||
{
|
||||
Name = name;
|
||||
AlternateColorSpaceDetails = alternateColorSpaceDetails;
|
||||
AlternateColorSpaceDetails = alternateColorSpaceDetails;
|
||||
TintFunction = tintFunction;
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
namespace UglyToad.PdfPig.Util
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content;
|
||||
using Core;
|
||||
using Filters;
|
||||
using Graphics.Colors;
|
||||
using Parser.Parts;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
|
||||
internal static class ColorSpaceMapper
|
||||
{
|
||||
private static bool TryExtendedColorSpaceNameMapping(NameToken name, out ColorSpace result)
|
||||
@ -405,24 +406,24 @@
|
||||
{
|
||||
return UnsupportedColorSpaceDetails.Instance;
|
||||
}
|
||||
|
||||
Union<DictionaryToken, StreamToken> functionTokensUnion;
|
||||
|
||||
PdfFunction function;
|
||||
var func = colorSpaceArray[3];
|
||||
|
||||
if (DirectObjectFinder.TryGet(func, scanner, out DictionaryToken functionDictionary))
|
||||
{
|
||||
functionTokensUnion = Union<DictionaryToken, StreamToken>.One(functionDictionary);
|
||||
{
|
||||
function = PdfFunctionParser.Create(functionDictionary, scanner, filterProvider);
|
||||
}
|
||||
else if (DirectObjectFinder.TryGet(func, scanner, out StreamToken functionStream))
|
||||
{
|
||||
functionTokensUnion = Union<DictionaryToken, StreamToken>.Two(functionStream);
|
||||
{
|
||||
function = PdfFunctionParser.Create(functionStream, scanner, filterProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UnsupportedColorSpaceDetails.Instance;
|
||||
}
|
||||
|
||||
return new SeparationColorSpaceDetails(separationNameToken, alternateColorSpaceDetails, functionTokensUnion);
|
||||
return new SeparationColorSpaceDetails(separationNameToken, alternateColorSpaceDetails, function);
|
||||
}
|
||||
case ColorSpace.DeviceN:
|
||||
return UnsupportedColorSpaceDetails.Instance;
|
||||
|
81
src/UglyToad.PdfPig/Util/PdfFunctionParser.cs
Normal file
81
src/UglyToad.PdfPig/Util/PdfFunctionParser.cs
Normal file
@ -0,0 +1,81 @@
|
||||
namespace UglyToad.PdfPig.Util
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Filters;
|
||||
using UglyToad.PdfPig.Functions;
|
||||
using UglyToad.PdfPig.Parser.Parts;
|
||||
using UglyToad.PdfPig.Tokenization.Scanner;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
internal static class PdfFunctionParser
|
||||
{
|
||||
public static PdfFunction Create(IToken function, IPdfTokenScanner scanner, ILookupFilterProvider filterProvider)
|
||||
{
|
||||
DictionaryToken functionDictionary;
|
||||
StreamToken functionStream = null;
|
||||
|
||||
if (function is StreamToken fs)
|
||||
{
|
||||
functionDictionary = fs.StreamDictionary;
|
||||
functionStream = new StreamToken(fs.StreamDictionary, fs.Decode(filterProvider, scanner));
|
||||
}
|
||||
else if (function is DictionaryToken fd)
|
||||
{
|
||||
functionDictionary = fd;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException(nameof(function));
|
||||
}
|
||||
|
||||
int functionType = (functionDictionary.Data[NameToken.FunctionType] as NumericToken).Int;
|
||||
|
||||
switch (functionType)
|
||||
{
|
||||
case 0:
|
||||
if (functionStream == null)
|
||||
{
|
||||
throw new NotImplementedException("PdfFunctionType0 not stream");
|
||||
}
|
||||
return new PdfFunctionType0(functionStream);
|
||||
|
||||
case 2:
|
||||
return new PdfFunctionType2(functionDictionary);
|
||||
|
||||
case 3:
|
||||
var functions = new List<PdfFunction>();
|
||||
if (functionDictionary.TryGet<ArrayToken>(NameToken.Functions, scanner, out var functionsToken))
|
||||
{
|
||||
foreach (IToken token in functionsToken.Data)
|
||||
{
|
||||
if (DirectObjectFinder.TryGet<StreamToken>(token, scanner, out var strTk))
|
||||
{
|
||||
functions.Add(Create(strTk, scanner, filterProvider));
|
||||
}
|
||||
else if (DirectObjectFinder.TryGet<DictionaryToken>(token, scanner, out var dicTk))
|
||||
{
|
||||
functions.Add(Create(dicTk, scanner, filterProvider));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Could not find function for token '{token}' inside type 3 function.");
|
||||
}
|
||||
}
|
||||
}
|
||||
return new PdfFunctionType3(functionDictionary, functions);
|
||||
|
||||
case 4:
|
||||
if (functionStream == null)
|
||||
{
|
||||
throw new NotImplementedException("PdfFunctionType0 not stream");
|
||||
}
|
||||
return new PdfFunctionType4(functionStream);
|
||||
|
||||
default:
|
||||
throw new IOException("Error: Unknown function type " + functionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user