add operation processing for text state operations, support for matrix multiplication with tests and td operation support

This commit is contained in:
Eliot Jones
2017-11-29 22:55:53 +00:00
parent 157030ea31
commit ab5377884f
14 changed files with 263 additions and 11 deletions

View File

@@ -0,0 +1,90 @@
namespace UglyToad.Pdf.Tests.Core
{
using System.Collections.Generic;
using Pdf.Core;
using Xunit;
public class TransformationMatrixTests
{
public static IEnumerable<object[]> MultiplicationData => new[]
{
new object[]
{
new decimal[]
{
1, 0, 0,
0, 1, 0,
0, 0, 1
},
new decimal[]
{
1, 0, 0,
0, 1, 0,
0, 0, 1
},
new decimal[]
{
1, 0, 0,
0, 1, 0,
0, 0, 1
}
},
new object[]
{
new decimal[]
{
65, 9, 3,
5, 2, 7,
11, 1, 6
},
new decimal[]
{
1, 2, 3,
4, 5, 6,
7, 8, 9
},
new decimal[]
{
122, 199, 276,
62, 76, 90,
57, 75, 93
}
},
new object[]
{
new decimal[]
{
3, 5, 7,
11, 13, -3,
17, -6, -9
},
new decimal[]
{
5, 4, 3,
3, 7, 12,
1, 0, 6
},
new decimal[]
{
37, 47, 111,
91, 135, 171,
58, 26, -75
}
}
};
[Theory]
[MemberData(nameof(MultiplicationData))]
public void MultipliesCorrectly(decimal[] a, decimal[] b, decimal[] expected)
{
var matrixA = TransformationMatrix.FromArray(a);
var matrixB = TransformationMatrix.FromArray(b);
var expectedMatrix = TransformationMatrix.FromArray(expected);
var result = matrixA.Multiply(matrixB);
Assert.Equal(expectedMatrix, result);
}
}
}

View File

@@ -41,7 +41,7 @@ ET";
Assert.Equal(BeginText.Value, result.GraphicsStateOperations[0]);
var font = Assert.IsType<SetFontSize>(result.GraphicsStateOperations[1]);
var font = Assert.IsType<SetFontAndSize>(result.GraphicsStateOperations[1]);
Assert.Equal(CosName.Create("F13"), font.Font);
Assert.Equal(48, font.Size);

View File

@@ -8,7 +8,7 @@
/// </summary>
internal struct TransformationMatrix
{
public static TransformationMatrix Default = new TransformationMatrix(new decimal[]
public static TransformationMatrix Identity = new TransformationMatrix(new decimal[]
{
1,0,0,
0,1,0,
@@ -24,6 +24,44 @@
public decimal E => value[6];
public decimal F => value[7];
public decimal this[int row, int col]
{
get
{
if (row >= Rows)
{
throw new ArgumentOutOfRangeException($"The transformation matrix only contains {Rows} rows and is zero indexed, you tried to access row {row}.");
}
if (row < 0)
{
throw new ArgumentOutOfRangeException("Cannot access negative rows in a matrix.");
}
if (col >= Columns)
{
throw new ArgumentOutOfRangeException($"The transformation matrix only contains {Columns} columns and is zero indexed, you tried to access column {col}.");
}
if (col < 0)
{
throw new ArgumentOutOfRangeException("Cannot access negative columns in a matrix.");
}
var resultIndex = row * Rows + col;
if (resultIndex > value.Length - 1)
{
throw new ArgumentOutOfRangeException($"Trying to access {row}, {col} mapped to the index {resultIndex} which was not in the value array.");
}
return value[resultIndex];
}
}
public const int Rows = 3;
public const int Columns = 3;
public TransformationMatrix(decimal[] value)
{
if (value == null)
@@ -47,7 +85,7 @@
return new PdfPoint(x, y);
}
public static TransformationMatrix FromArray(decimal[] values)
public static TransformationMatrix FromArray(params decimal[] values)
{
if (values.Length == 9)
{
@@ -67,6 +105,52 @@
throw new ArgumentException("The array must either define all 9 elements of the matrix or all 6 key elements. Instead array was: " + values);
}
public TransformationMatrix Multiply(TransformationMatrix matrix)
{
var result = new decimal[9];
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
var index = (i * Rows) + j;
for (int x = 0; x < Rows; x++)
{
result[index] += this[i, x] * matrix[x, j];
}
}
}
return new TransformationMatrix(result);
}
public override bool Equals(object obj)
{
if (!(obj is TransformationMatrix m))
{
return false;
}
return Equals(this, m);
}
public static bool Equals(TransformationMatrix a, TransformationMatrix b)
{
for (int i = 0; i < Rows; i++)
{
for (int j = 0; j < Columns; j++)
{
if (a[i, j] != b[i, j])
{
return false;
}
}
}
return true;
}
public override string ToString()
{
return $"{A}, {B}, 0\r\n{C}, {D}, 0\r\n{E}, {F}, 1";

View File

@@ -61,7 +61,7 @@
/// <summary>
/// Maps positions from user coordinates to device coordinates.
/// </summary>
public TransformationMatrix CurrentTransformationMatrix { get; set; } = TransformationMatrix.Default;
public TransformationMatrix CurrentTransformationMatrix { get; set; } = TransformationMatrix.Identity;
#region Device Dependent

View File

@@ -16,8 +16,8 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.TextMatrices.TextMatrix = TransformationMatrix.Default;
operationContext.TextMatrices.TextLineMatrix = TransformationMatrix.Default;
operationContext.TextMatrices.TextMatrix = TransformationMatrix.Identity;
operationContext.TextMatrices.TextLineMatrix = TransformationMatrix.Identity;
}
public override string ToString()

View File

@@ -16,8 +16,8 @@
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
operationContext.TextMatrices.TextMatrix = TransformationMatrix.Default;
operationContext.TextMatrices.TextLineMatrix = TransformationMatrix.Default;
operationContext.TextMatrices.TextMatrix = TransformationMatrix.Identity;
operationContext.TextMatrices.TextLineMatrix = TransformationMatrix.Identity;
}
public override string ToString()

View File

@@ -1,5 +1,8 @@
namespace UglyToad.Pdf.Graphics.Operations.TextPositioning
{
using Content;
using Core;
internal class MoveToNextLineWithOffset : IGraphicsStateOperation
{
public const string Symbol = "Td";
@@ -16,6 +19,18 @@
Ty = ty;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentTextLineMatrix = operationContext.TextMatrices.TextLineMatrix;
var matrix = TransformationMatrix.FromArray(1, 0, 0, 1, Tx, Ty);
var transformed = matrix.Multiply(currentTextLineMatrix);
operationContext.TextMatrices.TextLineMatrix = transformed;
operationContext.TextMatrices.TextMatrix = transformed;
}
public override string ToString()
{
return $"{Tx} {Ty} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetCharacterSpacing : IGraphicsStateOperation
{
public const string Symbol = "Tc";
@@ -13,6 +15,13 @@
Spacing = spacing;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.CharacterSpacing = Spacing;
}
public override string ToString()
{
return $"{Spacing} {Symbol}";

View File

@@ -1,8 +1,9 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
using Cos;
internal class SetFontSize : IGraphicsStateOperation
internal class SetFontAndSize : IGraphicsStateOperation
{
public const string Symbol = "Tf";
@@ -12,12 +13,20 @@
public decimal Size { get; }
public SetFontSize(CosName font, decimal size)
public SetFontAndSize(CosName font, decimal size)
{
Font = font;
Size = size;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.FontSize = Size;
currentState.FontState.FontName = Font;
}
public override string ToString()
{
return $"{Font} {Size} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetHorizontalScaling : IGraphicsStateOperation
{
public const string Symbol = "Tz";
@@ -13,6 +15,13 @@
Scale = scale;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.HorizontalScaling = Scale;
}
public override string ToString()
{
return $"{Scale} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetTextLeading : IGraphicsStateOperation
{
public const string Symbol = "TL";
@@ -13,6 +15,13 @@
Leading = leading;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.Leading = Leading;
}
public override string ToString()
{
return $"{Leading} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetTextRenderingMode : IGraphicsStateOperation
{
public const string Symbol = "Tr";
@@ -13,6 +15,13 @@
Mode = (RenderingMode)mode;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.RenderingMode = Mode;
}
public override string ToString()
{
return $"{Mode} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetTextRise : IGraphicsStateOperation
{
public const string Symbol = "Ts";
@@ -13,6 +15,13 @@
Rise = rise;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.Rise = Rise;
}
public override string ToString()
{
return $"{Rise} {Symbol}";

View File

@@ -1,5 +1,7 @@
namespace UglyToad.Pdf.Graphics.Operations.TextState
{
using Content;
internal class SetWordSpacing : IGraphicsStateOperation
{
public const string Symbol = "Tw";
@@ -13,6 +15,13 @@
Spacing = spacing;
}
public void Run(IOperationContext operationContext, IResourceStore resourceStore)
{
var currentState = operationContext.GetCurrentState();
currentState.FontState.WordSpacing = Spacing;
}
public override string ToString()
{
return $"{Spacing} {Symbol}";