mirror of
https://github.com/UglyToad/PdfPig.git
synced 2025-09-20 20:07:57 +08:00
move classes to new projects
to make the project more useful and expose more usable classes we're rearchitecting in the following way. code used to read fonts from external file formats like truetype, adobe font metrics (afm) and adobe type 1 fonts are moving to a new project which doesn't reference most of the pdf logic. the shared logic is moving to a new flat-structured project called core. this is a sort-of onion type architecture, with core being the... core, fonts being the next layer of the onion, pdfpig itself the next. this will then support additional libraries/projects as outer layers of the onion as well as releasing standalone version of the font library as pdfbox does with fontbox.
This commit is contained in:
144
src/UglyToad.PdfPig.Core/ByteArrayInputBytes.cs
Normal file
144
src/UglyToad.PdfPig.Core/ByteArrayInputBytes.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Input bytes from a byte array.
|
||||
/// </summary>
|
||||
public class ByteArrayInputBytes : IInputBytes
|
||||
{
|
||||
private readonly int upperBound;
|
||||
private readonly byte[] bytes;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ByteArrayInputBytes"/>.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public ByteArrayInputBytes(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
if (bytes is byte[] arr)
|
||||
{
|
||||
this.bytes = arr;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.bytes = bytes.ToArray();
|
||||
}
|
||||
|
||||
upperBound = this.bytes.Length - 1;
|
||||
|
||||
currentOffset = -1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="ByteArrayInputBytes"/>.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public ByteArrayInputBytes(byte[] bytes)
|
||||
{
|
||||
this.bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
|
||||
currentOffset = -1;
|
||||
upperBound = bytes.Length - 1;
|
||||
}
|
||||
|
||||
private int currentOffset;
|
||||
/// <inheritdoc />
|
||||
public long CurrentOffset => currentOffset + 1;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (currentOffset == upperBound)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
currentOffset++;
|
||||
CurrentByte = bytes[currentOffset];
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte CurrentByte { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public long Length => bytes.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte? Peek()
|
||||
{
|
||||
if (currentOffset == upperBound)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return bytes[currentOffset + 1];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAtEnd()
|
||||
{
|
||||
return currentOffset == upperBound;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Seek(long position)
|
||||
{
|
||||
currentOffset = (int)position - 1;
|
||||
CurrentByte = currentOffset < 0 ? (byte)0 : bytes[currentOffset];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Read(byte[] buffer, int? length = null)
|
||||
{
|
||||
var bytesToRead = buffer.Length;
|
||||
if (length.HasValue)
|
||||
{
|
||||
if (length.Value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Cannot use a negative length: {length.Value}.");
|
||||
}
|
||||
|
||||
if (length.Value > bytesToRead)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Cannot read more bytes {length.Value} than there is space in the buffer {buffer.Length}.");
|
||||
}
|
||||
|
||||
bytesToRead = length.Value;
|
||||
}
|
||||
|
||||
if (bytesToRead == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var viableLength = (bytes.Length - currentOffset - 1);
|
||||
var readLength = viableLength < bytesToRead ? viableLength : bytesToRead;
|
||||
var startFrom = currentOffset + 1;
|
||||
|
||||
Array.Copy(bytes, startFrom, buffer, 0, readLength);
|
||||
|
||||
if (readLength > 0)
|
||||
{
|
||||
currentOffset += readLength;
|
||||
CurrentByte = buffer[readLength - 1];
|
||||
}
|
||||
|
||||
return readLength;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
13
src/UglyToad.PdfPig.Core/EmptyArray.cs
Normal file
13
src/UglyToad.PdfPig.Core/EmptyArray.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// NET 4.5 compatible Array.Empty.
|
||||
/// </summary>
|
||||
public static class EmptyArray<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// An empty array.
|
||||
/// </summary>
|
||||
public static T[] Instance { get; } = new T[0];
|
||||
}
|
||||
}
|
13
src/UglyToad.PdfPig.Core/IDeepCloneable.cs
Normal file
13
src/UglyToad.PdfPig.Core/IDeepCloneable.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the type may be cloned making entirely independent copies.
|
||||
/// </summary>
|
||||
public interface IDeepCloneable<out T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Clone the type including all referenced data.
|
||||
/// </summary>
|
||||
T DeepClone();
|
||||
}
|
||||
}
|
54
src/UglyToad.PdfPig.Core/IInputBytes.cs
Normal file
54
src/UglyToad.PdfPig.Core/IInputBytes.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// The input bytes for a PDF document.
|
||||
/// </summary>
|
||||
public interface IInputBytes : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The current offset in bytes.
|
||||
/// </summary>
|
||||
long CurrentOffset { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Moves to the next byte if available.
|
||||
/// </summary>
|
||||
bool MoveNext();
|
||||
|
||||
/// <summary>
|
||||
/// The current byte.
|
||||
/// </summary>
|
||||
byte CurrentByte { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The length of the data in bytes.
|
||||
/// </summary>
|
||||
long Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next byte if available.
|
||||
/// </summary>
|
||||
byte? Peek();
|
||||
|
||||
/// <summary>
|
||||
/// Whether we are at the end of the available data.
|
||||
/// </summary>
|
||||
bool IsAtEnd();
|
||||
|
||||
/// <summary>
|
||||
/// Move to a given position.
|
||||
/// </summary>
|
||||
void Seek(long position);
|
||||
|
||||
/// <summary>
|
||||
/// Fill the buffer with bytes starting from the current position.
|
||||
/// </summary>
|
||||
/// <param name="buffer">A buffer with a length corresponding to the number of bytes to read.</param>
|
||||
/// <param name="length">Optional override for the number of bytes to read.</param>
|
||||
/// <returns>The number of bytes successfully read.</returns>
|
||||
int Read(byte[] buffer, int? length = null);
|
||||
}
|
||||
}
|
15
src/UglyToad.PdfPig.Core/IWriteable.cs
Normal file
15
src/UglyToad.PdfPig.Core/IWriteable.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates that a data structure can be written to an output stream.
|
||||
/// </summary>
|
||||
public interface IWriteable
|
||||
{
|
||||
/// <summary>
|
||||
/// Write the data to the output stream.
|
||||
/// </summary>
|
||||
void Write(Stream stream);
|
||||
}
|
||||
}
|
61
src/UglyToad.PdfPig.Core/OtherEncodings.cs
Normal file
61
src/UglyToad.PdfPig.Core/OtherEncodings.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Convenience access to frequently used encodings.
|
||||
/// </summary>
|
||||
public static class OtherEncodings
|
||||
{
|
||||
/// <summary>
|
||||
/// Latin 1 Encoding: ISO 8859-1 is a single-byte encoding that can represent the first 256 Unicode characters.
|
||||
/// </summary>
|
||||
public static Encoding Iso88591 = Encoding.GetEncoding("ISO-8859-1");
|
||||
|
||||
/// <summary>
|
||||
/// Convert the string to bytes using the ISO 8859-1 encoding.
|
||||
/// </summary>
|
||||
public static byte[] StringAsLatin1Bytes(string s)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Iso88591.GetBytes(s);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the bytes to string using the ISO 8859-1 encoding.
|
||||
/// </summary>
|
||||
public static string BytesAsLatin1String(IReadOnlyList<byte> bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (bytes is byte[] arr)
|
||||
{
|
||||
return BytesAsLatin1String(arr);
|
||||
}
|
||||
|
||||
return BytesAsLatin1String(bytes.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Convert the bytes to string using the ISO 8859-1 encoding.
|
||||
/// </summary>
|
||||
public static string BytesAsLatin1String(byte[] bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Iso88591.GetString(bytes);
|
||||
}
|
||||
}
|
||||
}
|
39
src/UglyToad.PdfPig.Core/PdfDocumentFormatException.cs
Normal file
39
src/UglyToad.PdfPig.Core/PdfDocumentFormatException.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// This exception will be thrown where the contents of the PDF document do not match the specification in such a way that it
|
||||
/// renders the document unreadable.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PdfDocumentFormatException : Exception
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Create a new <see cref="T:UglyToad.PdfPig.Exceptions.PdfDocumentFormatException" />.
|
||||
/// </summary>
|
||||
public PdfDocumentFormatException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PdfDocumentFormatException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public PdfDocumentFormatException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected PdfDocumentFormatException(
|
||||
SerializationInfo info,
|
||||
StreamingContext context) : base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
118
src/UglyToad.PdfPig.Core/PdfPoint.cs
Normal file
118
src/UglyToad.PdfPig.Core/PdfPoint.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// A point in a PDF file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PDF coordinates are defined with the origin at the lower left (0, 0).
|
||||
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
|
||||
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
|
||||
/// </remarks>
|
||||
public struct PdfPoint
|
||||
{
|
||||
/// <summary>
|
||||
/// The origin of the coordinates system.
|
||||
/// </summary>
|
||||
public static PdfPoint Origin { get; } = new PdfPoint(0m, 0m);
|
||||
|
||||
/// <summary>
|
||||
/// The X coordinate for this point. (Horizontal axis).
|
||||
/// </summary>
|
||||
public double X { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Y coordinate of this point. (Vertical axis).
|
||||
/// </summary>
|
||||
public double Y { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfPoint"/> at this position.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public PdfPoint(decimal x, decimal y)
|
||||
{
|
||||
X = (double)x;
|
||||
Y = (double)y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfPoint"/> at this position.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public PdfPoint(int x, int y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfPoint"/> at this position.
|
||||
/// </summary>
|
||||
[DebuggerStepThrough]
|
||||
public PdfPoint(double x, double y)
|
||||
{
|
||||
X = x;
|
||||
Y = y;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfPoint"/> which is the current point moved in the x direction relative to its current position by a value.
|
||||
/// </summary>
|
||||
/// <param name="dx">The distance to move the point in the x direction relative to its current location.</param>
|
||||
/// <returns>A new point shifted on the x axis by the given delta value.</returns>
|
||||
public PdfPoint MoveX(double dx)
|
||||
{
|
||||
return new PdfPoint(X + dx, Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfPoint"/> which is the current point moved in the y direction relative to its current position by a value.
|
||||
/// </summary>
|
||||
/// <param name="dy">The distance to move the point in the y direction relative to its current location.</param>
|
||||
/// <returns>A new point shifted on the y axis by the given delta value.</returns>
|
||||
public PdfPoint MoveY(double dy)
|
||||
{
|
||||
return new PdfPoint(X, Y + dy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfPoint"/> which is the current point moved in the x and y directions relative to its current position by a value.
|
||||
/// </summary>
|
||||
/// <param name="dx">The distance to move the point in the x direction relative to its current location.</param>
|
||||
/// <param name="dy">The distance to move the point in the y direction relative to its current location.</param>
|
||||
/// <returns>A new point shifted on the y axis by the given delta value.</returns>
|
||||
public PdfPoint Translate(double dx, double dy)
|
||||
{
|
||||
return new PdfPoint(X + dx, Y + dy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this <see cref="PdfPoint"/> is equal to a specified <see cref="PdfPoint"/> .
|
||||
/// </summary>
|
||||
/// <param name="obj"></param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is PdfPoint point)
|
||||
{
|
||||
return point.X == X && point.Y == Y;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the hash code for this <see cref="PdfPoint"/>.
|
||||
/// </summary>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (X, Y).GetHashCode();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"(x:{X}, y:{Y})";
|
||||
}
|
||||
}
|
||||
}
|
151
src/UglyToad.PdfPig.Core/PdfRectangle.cs
Normal file
151
src/UglyToad.PdfPig.Core/PdfRectangle.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A rectangle in a PDF file.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// PDF coordinates are defined with the origin at the lower left (0, 0).
|
||||
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
|
||||
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
|
||||
/// </remarks>
|
||||
public struct PdfRectangle
|
||||
{
|
||||
/// <summary>
|
||||
/// Top left point of the rectangle.
|
||||
/// </summary>
|
||||
public PdfPoint TopLeft { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Top right point of the rectangle.
|
||||
/// </summary>
|
||||
public PdfPoint TopRight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bottom right point of the rectangle.
|
||||
/// </summary>
|
||||
public PdfPoint BottomRight { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Bottom left point of the rectangle.
|
||||
/// </summary>
|
||||
public PdfPoint BottomLeft { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Centroid point of the rectangle.
|
||||
/// </summary>
|
||||
public PdfPoint Centroid => new PdfPoint(Left + (Right - Left) / 2, Bottom + (Top - Bottom) / 2);
|
||||
|
||||
/// <summary>
|
||||
/// Width of the rectangle.
|
||||
/// </summary>
|
||||
public double Width => Right - Left;
|
||||
|
||||
/// <summary>
|
||||
/// Height of the rectangle.
|
||||
/// </summary>
|
||||
public double Height => Top - Bottom;
|
||||
|
||||
/// <summary>
|
||||
/// Area of the rectangle.
|
||||
/// </summary>
|
||||
public double Area => Width * Height;
|
||||
|
||||
/// <summary>
|
||||
/// Left.
|
||||
/// </summary>
|
||||
public double Left => TopLeft.X;
|
||||
|
||||
/// <summary>
|
||||
/// Top.
|
||||
/// </summary>
|
||||
public double Top => TopLeft.Y;
|
||||
|
||||
/// <summary>
|
||||
/// Right.
|
||||
/// </summary>
|
||||
public double Right => BottomRight.X;
|
||||
|
||||
/// <summary>
|
||||
/// Bottom.
|
||||
/// </summary>
|
||||
public double Bottom => BottomRight.Y;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfRectangle"/>.
|
||||
/// </summary>
|
||||
public PdfRectangle(PdfPoint point1, PdfPoint point2) : this(point1.X, point1.Y, point2.X, point2.Y) { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfRectangle"/>.
|
||||
/// </summary>
|
||||
public PdfRectangle(short x1, short y1, short x2, short y2) : this((double)x1, y1, x2, y2) { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfRectangle"/>.
|
||||
/// </summary>
|
||||
public PdfRectangle(double x1, double y1, double x2, double y2)
|
||||
{
|
||||
double bottom;
|
||||
double top;
|
||||
|
||||
if (y1 <= y2)
|
||||
{
|
||||
bottom = y1;
|
||||
top = y2;
|
||||
}
|
||||
else
|
||||
{
|
||||
bottom = y2;
|
||||
top = y1;
|
||||
}
|
||||
|
||||
double left;
|
||||
double right;
|
||||
if (x1 <= x2)
|
||||
{
|
||||
left = x1;
|
||||
right = x2;
|
||||
}
|
||||
else
|
||||
{
|
||||
left = x2;
|
||||
right = x1;
|
||||
}
|
||||
|
||||
TopLeft = new PdfPoint(left, top);
|
||||
TopRight = new PdfPoint(right, top);
|
||||
|
||||
BottomLeft = new PdfPoint(left, bottom);
|
||||
BottomRight = new PdfPoint(right, bottom);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="PdfRectangle"/>.
|
||||
/// </summary>
|
||||
public PdfRectangle(PdfPoint topLeft, PdfPoint topRight, PdfPoint bottomLeft, PdfPoint bottomRight)
|
||||
{
|
||||
TopLeft = topLeft;
|
||||
TopRight = topRight;
|
||||
|
||||
BottomLeft = bottomLeft;
|
||||
BottomRight = bottomRight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfRectangle"/> which is the current rectangle moved in the x and y directions relative to its current position by a value.
|
||||
/// </summary>
|
||||
/// <param name="dx">The distance to move the rectangle in the x direction relative to its current location.</param>
|
||||
/// <param name="dy">The distance to move the rectangle in the y direction relative to its current location.</param>
|
||||
/// <returns>A new rectangle shifted on the y axis by the given delta value.</returns>
|
||||
public PdfRectangle Translate(double dx, double dy)
|
||||
{
|
||||
return new PdfRectangle(BottomLeft.Translate(dx, dy), TopRight.Translate(dx, dy));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{TopLeft}, {Width}, {Height}]";
|
||||
}
|
||||
}
|
||||
}
|
324
src/UglyToad.PdfPig.Core/ReadHelper.cs
Normal file
324
src/UglyToad.PdfPig.Core/ReadHelper.cs
Normal file
@@ -0,0 +1,324 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for reading from PDF files.
|
||||
/// </summary>
|
||||
public static class ReadHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// The line-feed '\n' character.
|
||||
/// </summary>
|
||||
public const byte AsciiLineFeed = 10;
|
||||
|
||||
/// <summary>
|
||||
/// The carriage return '\r' character.
|
||||
/// </summary>
|
||||
public const byte AsciiCarriageReturn = 13;
|
||||
|
||||
private static readonly HashSet<int> EndOfNameCharacters = new HashSet<int>
|
||||
{
|
||||
' ',
|
||||
AsciiCarriageReturn,
|
||||
AsciiLineFeed,
|
||||
9,
|
||||
'>',
|
||||
'<',
|
||||
'[',
|
||||
'/',
|
||||
']',
|
||||
')',
|
||||
'(',
|
||||
0,
|
||||
'\f'
|
||||
};
|
||||
|
||||
private static readonly int MaximumNumberStringLength = long.MaxValue.ToString("D").Length;
|
||||
|
||||
/// <summary>
|
||||
/// Read a string from the input until a newline.
|
||||
/// </summary>
|
||||
public static string ReadLine(IInputBytes bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
if (bytes.IsAtEnd())
|
||||
{
|
||||
throw new InvalidOperationException("Error: End-of-File, expected line");
|
||||
}
|
||||
|
||||
var buffer = new StringBuilder(11);
|
||||
|
||||
byte c = 0;
|
||||
while (bytes.MoveNext())
|
||||
{
|
||||
c = bytes.CurrentByte;
|
||||
|
||||
// CR and LF are valid EOLs
|
||||
if (IsEndOfLine(c))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Append((char)c);
|
||||
}
|
||||
|
||||
// CR+LF is also a valid EOL
|
||||
if (IsCarriageReturn(c) && IsLineFeed(bytes.Peek()))
|
||||
{
|
||||
bytes.MoveNext();
|
||||
}
|
||||
|
||||
return buffer.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skip any whitespace characters.
|
||||
/// </summary>
|
||||
public static void SkipSpaces(IInputBytes bytes)
|
||||
{
|
||||
const int commentCharacter = 37;
|
||||
bytes.MoveNext();
|
||||
byte c = bytes.CurrentByte;
|
||||
|
||||
while (IsWhitespace(c) || c == 37)
|
||||
{
|
||||
if (c == commentCharacter)
|
||||
{
|
||||
// skip past the comment section
|
||||
bytes.MoveNext();
|
||||
c = bytes.CurrentByte;
|
||||
while (!IsEndOfLine(c))
|
||||
{
|
||||
bytes.MoveNext();
|
||||
c = bytes.CurrentByte;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bytes.MoveNext();
|
||||
c = bytes.CurrentByte;
|
||||
}
|
||||
}
|
||||
|
||||
if (!bytes.IsAtEnd())
|
||||
{
|
||||
bytes.Seek(bytes.CurrentOffset - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given character value is the end of a PDF Name token.
|
||||
/// </summary>
|
||||
public static bool IsEndOfName(int ch)
|
||||
{
|
||||
return EndOfNameCharacters.Contains(ch);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a character is whitespace or not.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// These values are specified in table 1 (page 12) of ISO 32000-1:2008.
|
||||
/// </remarks>
|
||||
public static bool IsWhitespace(byte c)
|
||||
{
|
||||
return c == 0 || c == 32 || c == AsciiLineFeed || c == AsciiCarriageReturn || c == 9 || c == 12;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the character is an end of line character.
|
||||
/// </summary>
|
||||
public static bool IsEndOfLine(char c) => IsEndOfLine((byte) c);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the character is an end of line character.
|
||||
/// </summary>
|
||||
public static bool IsEndOfLine(byte b)
|
||||
{
|
||||
return IsLineFeed(b) || IsCarriageReturn(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the character is an line feed '\n' character.
|
||||
/// </summary>
|
||||
public static bool IsLineFeed(byte? c)
|
||||
{
|
||||
return AsciiLineFeed == c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the character is a carriage return '\r' character.
|
||||
/// </summary>
|
||||
public static bool IsCarriageReturn(byte c)
|
||||
{
|
||||
return AsciiCarriageReturn == c;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given string is at this position in the input.
|
||||
/// </summary>
|
||||
public static bool IsString(IInputBytes bytes, string s)
|
||||
{
|
||||
bool found = true;
|
||||
|
||||
var startOffset = bytes.CurrentOffset;
|
||||
|
||||
foreach (var c in s)
|
||||
{
|
||||
if (bytes.CurrentByte != c)
|
||||
{
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes.MoveNext();
|
||||
}
|
||||
|
||||
bytes.Seek(startOffset);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a long from the input.
|
||||
/// </summary>
|
||||
public static long ReadLong(IInputBytes bytes)
|
||||
{
|
||||
SkipSpaces(bytes);
|
||||
long retval;
|
||||
|
||||
StringBuilder longBuffer = ReadStringNumber(bytes);
|
||||
|
||||
try
|
||||
{
|
||||
retval = long.Parse(longBuffer.ToString(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (FormatException e)
|
||||
{
|
||||
var bytesToReverse = OtherEncodings.StringAsLatin1Bytes(longBuffer.ToString());
|
||||
bytes.Seek(bytes.CurrentOffset - bytesToReverse.Length);
|
||||
|
||||
throw new InvalidOperationException($"Error: Expected a long type at offset {bytes.CurrentOffset}, instead got \'{longBuffer}\'", e);
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given value is a digit or not.
|
||||
/// </summary>
|
||||
public static bool IsDigit(int c)
|
||||
{
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read an int from the input.
|
||||
/// </summary>
|
||||
public static int ReadInt(IInputBytes bytes)
|
||||
{
|
||||
if (bytes == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(bytes));
|
||||
}
|
||||
|
||||
SkipSpaces(bytes);
|
||||
int result;
|
||||
|
||||
var intBuffer = ReadStringNumber(bytes);
|
||||
|
||||
try
|
||||
{
|
||||
result = int.Parse(intBuffer.ToString(), CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
bytes.Seek(bytes.CurrentOffset - OtherEncodings.StringAsLatin1Bytes(intBuffer.ToString()).Length);
|
||||
|
||||
throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}", e);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given character is a space.
|
||||
/// </summary>
|
||||
public static bool IsSpace(int c)
|
||||
{
|
||||
return c == ' ';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given character value is a valid hex value.
|
||||
/// </summary>
|
||||
public static bool IsHex(byte b) => IsHex((char) b);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given character value is a valid hex value.
|
||||
/// </summary>
|
||||
public static bool IsHex(char ch)
|
||||
{
|
||||
return char.IsDigit(ch) || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the given input bytes are valid UTF8.
|
||||
/// </summary>
|
||||
public static bool IsValidUtf8(byte[] input)
|
||||
{
|
||||
try
|
||||
{
|
||||
var d = Encoding.UTF8.GetDecoder();
|
||||
|
||||
var charLength = d.GetCharCount(input, 0, input.Length);
|
||||
var chars = new char[charLength];
|
||||
d.Convert(input, 0, input.Length, chars, 0, charLength, true, out _, out _, out _);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static StringBuilder ReadStringNumber(IInputBytes reader)
|
||||
{
|
||||
byte lastByte;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
|
||||
while (reader.MoveNext() && (lastByte = reader.CurrentByte) != ' ' &&
|
||||
lastByte != AsciiLineFeed &&
|
||||
lastByte != AsciiCarriageReturn &&
|
||||
lastByte != 60 && //see sourceforge bug 1714707
|
||||
lastByte != '[' && // PDFBOX-1845
|
||||
lastByte != '(' && // PDFBOX-2579
|
||||
lastByte != 0)
|
||||
{
|
||||
buffer.Append((char)lastByte);
|
||||
|
||||
if (buffer.Length > MaximumNumberStringLength)
|
||||
{
|
||||
throw new InvalidOperationException($"Number \'{buffer}\' is getting too long, stop reading at offset {reader.CurrentOffset}");
|
||||
}
|
||||
}
|
||||
|
||||
if (!reader.IsAtEnd())
|
||||
{
|
||||
reader.Seek(reader.CurrentOffset - 1);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
152
src/UglyToad.PdfPig.Core/StreamInputBytes.cs
Normal file
152
src/UglyToad.PdfPig.Core/StreamInputBytes.cs
Normal file
@@ -0,0 +1,152 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Input bytes from a stream.
|
||||
/// </summary>
|
||||
public class StreamInputBytes : IInputBytes
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private readonly bool shouldDispose;
|
||||
|
||||
private bool isAtEnd;
|
||||
|
||||
/// <inheritdoc />
|
||||
public long CurrentOffset => stream.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte CurrentByte { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public long Length => stream.Length;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="StreamInputBytes"/>.
|
||||
/// </summary>
|
||||
/// <param name="stream">The stream to use, should be readable and seekable.</param>
|
||||
/// <param name="shouldDispose">Whether this class should dispose the stream once finished.</param>
|
||||
public StreamInputBytes(Stream stream, bool shouldDispose = true)
|
||||
{
|
||||
if (stream == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
if (!stream.CanRead)
|
||||
{
|
||||
throw new ArgumentException("The provided stream did not support reading.");
|
||||
}
|
||||
|
||||
if (!stream.CanSeek)
|
||||
{
|
||||
throw new ArgumentException("The provided stream did not support seeking.");
|
||||
}
|
||||
|
||||
this.stream = stream;
|
||||
this.shouldDispose = shouldDispose;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool MoveNext()
|
||||
{
|
||||
var b = stream.ReadByte();
|
||||
|
||||
if (b == -1)
|
||||
{
|
||||
isAtEnd = true;
|
||||
CurrentByte = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
CurrentByte = (byte) b;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte? Peek()
|
||||
{
|
||||
var current = CurrentOffset;
|
||||
|
||||
var b = stream.ReadByte();
|
||||
|
||||
stream.Seek(current, SeekOrigin.Begin);
|
||||
|
||||
if (b == -1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return (byte)b;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsAtEnd()
|
||||
{
|
||||
return isAtEnd;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Seek(long position)
|
||||
{
|
||||
isAtEnd = false;
|
||||
|
||||
if (position == 0)
|
||||
{
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
CurrentByte = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Position = position - 1;
|
||||
MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int Read(byte[] buffer, int? length = null)
|
||||
{
|
||||
var bytesToRead = buffer.Length;
|
||||
if (length.HasValue)
|
||||
{
|
||||
if (length.Value < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Cannot use a negative length: {length.Value}.");
|
||||
}
|
||||
|
||||
if (length.Value > bytesToRead)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException($"Cannot read more bytes {length.Value} than there is space in the buffer {buffer.Length}.");
|
||||
}
|
||||
|
||||
bytesToRead = length.Value;
|
||||
}
|
||||
|
||||
if (bytesToRead == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var read = stream.Read(buffer, 0, bytesToRead);
|
||||
if (read > 0)
|
||||
{
|
||||
CurrentByte = buffer[read - 1];
|
||||
}
|
||||
|
||||
isAtEnd = stream.Position == stream.Length;
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
if (shouldDispose)
|
||||
{
|
||||
stream?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
400
src/UglyToad.PdfPig.Core/TransformationMatrix.cs
Normal file
400
src/UglyToad.PdfPig.Core/TransformationMatrix.cs
Normal file
@@ -0,0 +1,400 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
|
||||
/// </summary>
|
||||
public struct TransformationMatrix
|
||||
{
|
||||
/// <summary>
|
||||
/// The default <see cref="TransformationMatrix"/>.
|
||||
/// </summary>
|
||||
public static TransformationMatrix Identity = new TransformationMatrix(1,0,0,
|
||||
0,1,0,
|
||||
0,0,1);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/> with the X and Y translation values set.
|
||||
/// </summary>
|
||||
public static TransformationMatrix GetTranslationMatrix(double x, double y) => new TransformationMatrix(1, 0, 0,
|
||||
0, 1, 0,
|
||||
x, y, 1);
|
||||
|
||||
private readonly double row1;
|
||||
private readonly double row2;
|
||||
private readonly double row3;
|
||||
|
||||
/// <summary>
|
||||
/// The value at (0, 0) - The scale for the X dimension.
|
||||
/// </summary>
|
||||
public readonly double A;
|
||||
/// <summary>
|
||||
/// The value at (0, 1).
|
||||
/// </summary>
|
||||
public readonly double B;
|
||||
/// <summary>
|
||||
/// The value at (1, 0).
|
||||
/// </summary>
|
||||
public readonly double C;
|
||||
/// <summary>
|
||||
/// The value at (1, 1) - The scale for the Y dimension.
|
||||
/// </summary>
|
||||
public readonly double D;
|
||||
/// <summary>
|
||||
/// The value at (2, 0) - translation in X.
|
||||
/// </summary>
|
||||
public readonly double E;
|
||||
/// <summary>
|
||||
/// The value at (2, 1) - translation in Y.
|
||||
/// </summary>
|
||||
public readonly double F;
|
||||
|
||||
/// <summary>
|
||||
/// Get the value at the specific row and column.
|
||||
/// </summary>
|
||||
public double this[int row, int col]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (row >= Rows)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row), $"The transformation matrix only contains {Rows} rows and is zero indexed, you tried to access row {row}.");
|
||||
}
|
||||
|
||||
if (row < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(row), "Cannot access negative rows in a matrix.");
|
||||
}
|
||||
|
||||
if (col >= Columns)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(col), $"The transformation matrix only contains {Columns} columns and is zero indexed, you tried to access column {col}.");
|
||||
}
|
||||
|
||||
if (col < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(col), "Cannot access negative columns in a matrix.");
|
||||
}
|
||||
|
||||
switch (row)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
case 0:
|
||||
return A;
|
||||
case 1:
|
||||
return B;
|
||||
case 2:
|
||||
return row1;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Trying to access {row}, {col} which was not in the value array.");
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
case 0:
|
||||
return C;
|
||||
case 1:
|
||||
return D;
|
||||
case 2:
|
||||
return row2;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Trying to access {row}, {col} which was not in the value array.");
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
switch (col)
|
||||
{
|
||||
case 0:
|
||||
return E;
|
||||
case 1:
|
||||
return F;
|
||||
case 2:
|
||||
return row3;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Trying to access {row}, {col} which was not in the value array.");
|
||||
}
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException($"Trying to access {row}, {col} which was not in the value array.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The number of rows in the matrix.
|
||||
/// </summary>
|
||||
public const int Rows = 3;
|
||||
/// <summary>
|
||||
/// The number of columns in the matrix.
|
||||
/// </summary>
|
||||
public const int Columns = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/>.
|
||||
/// </summary>
|
||||
/// <param name="value">The 9 values of the matrix.</param>
|
||||
public TransformationMatrix(double[] value) : this(value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8])
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/>.
|
||||
/// </summary>
|
||||
public TransformationMatrix(double a, double b, double r1, double c, double d, double r2, double e, double f, double r3)
|
||||
{
|
||||
A = a;
|
||||
B = b;
|
||||
row1 = r1;
|
||||
C = c;
|
||||
D = d;
|
||||
row2 = r2;
|
||||
E = e;
|
||||
F = f;
|
||||
row3 = r3;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform a point using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="original">The original point.</param>
|
||||
/// <returns>A new point which is the result of applying this transformation matrix.</returns>
|
||||
[Pure]
|
||||
public PdfPoint Transform(PdfPoint original)
|
||||
{
|
||||
var x = A * original.X + C * original.Y + E;
|
||||
var y = B * original.X + D * original.Y + F;
|
||||
|
||||
return new PdfPoint(x, y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform an X coordinate using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="x">The X coordinate.</param>
|
||||
/// <returns>The transformed X coordinate.</returns>
|
||||
[Pure]
|
||||
internal double TransformX(double x)
|
||||
{
|
||||
var xt = A * x + C * 0 + E;
|
||||
|
||||
return xt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transform a rectangle using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="original">The original rectangle.</param>
|
||||
/// <returns>A new rectangle which is the result of applying this transformation matrix.</returns>
|
||||
[Pure]
|
||||
public PdfRectangle Transform(PdfRectangle original)
|
||||
{
|
||||
return new PdfRectangle(
|
||||
Transform(original.TopLeft),
|
||||
Transform(original.TopRight),
|
||||
Transform(original.BottomLeft),
|
||||
Transform(original.BottomRight)
|
||||
);
|
||||
}
|
||||
|
||||
[Pure]
|
||||
internal TransformationMatrix Translate(double x, double y)
|
||||
{
|
||||
var a = A;
|
||||
var b = B;
|
||||
var r1 = row1;
|
||||
|
||||
var c = C;
|
||||
var d = D;
|
||||
var r2 = row2;
|
||||
|
||||
var e = (x * A) + (y * C) + E;
|
||||
var f = (x * B) + (y * D) + F;
|
||||
var r3 = (x * row1) + (y * row2) + row3;
|
||||
|
||||
return new TransformationMatrix(a, b, r1,
|
||||
c, d, r2,
|
||||
e, f, r3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/> from the 6 values provided in the default PDF order.
|
||||
/// </summary>
|
||||
public static TransformationMatrix FromValues(double a, double b, double c, double d, double e, double f)
|
||||
=> new TransformationMatrix(a, b, 0, c, d, 0, e, f, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/> from the 4 values provided in the default PDF order.
|
||||
/// </summary>
|
||||
public static TransformationMatrix FromValues(double a, double b, double c, double d)
|
||||
=> new TransformationMatrix(a, b, 0, c, d, 0, 0, 0, 1);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/> from the values.
|
||||
/// </summary>
|
||||
/// <param name="values">Either all 9 values of the matrix, 6 values in the default PDF order or the 4 values of the top left square.</param>
|
||||
/// <returns></returns>
|
||||
public static TransformationMatrix FromArray(decimal[] values)
|
||||
=> FromArray(values.Select(x => (double) x).ToArray());
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TransformationMatrix"/> from the values.
|
||||
/// </summary>
|
||||
/// <param name="values">Either all 9 values of the matrix, 6 values in the default PDF order or the 4 values of the top left square.</param>
|
||||
/// <returns></returns>
|
||||
public static TransformationMatrix FromArray(double[] values)
|
||||
{
|
||||
if (values.Length == 9)
|
||||
{
|
||||
return new TransformationMatrix(values);
|
||||
}
|
||||
|
||||
if (values.Length == 6)
|
||||
{
|
||||
return new TransformationMatrix(values[0], values[1], 0,
|
||||
values[2], values[3], 0,
|
||||
values[4], values[5], 1);
|
||||
}
|
||||
|
||||
if (values.Length == 4)
|
||||
{
|
||||
return new TransformationMatrix(values[0], values[1], 0,
|
||||
values[2], values[3], 0,
|
||||
0, 0, 1);
|
||||
}
|
||||
|
||||
throw new ArgumentException("The array must either define all 9 elements of the matrix or all 6 key elements. Instead array was: " + values);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies one transformation matrix by another without modifying either matrix. Order is: (this * matrix).
|
||||
/// </summary>
|
||||
/// <param name="matrix">The matrix to multiply</param>
|
||||
/// <returns>The resulting matrix.</returns>
|
||||
[Pure]
|
||||
public TransformationMatrix Multiply(TransformationMatrix matrix)
|
||||
{
|
||||
var a = (A * matrix.A) + (B * matrix.C) + (row1 * matrix.E);
|
||||
var b = (A * matrix.B) + (B * matrix.D) + (row1 * matrix.F);
|
||||
var r1 = (A * matrix.row1) + (B * matrix.row2) + (row1 * matrix.row3);
|
||||
|
||||
var c = (C * matrix.A) + (D * matrix.C) + (row2 * matrix.E);
|
||||
var d = (C * matrix.B) + (D * matrix.D) + (row2 * matrix.F);
|
||||
var r2 = (C * matrix.row1) + (D * matrix.row2) + (row2 * matrix.row3);
|
||||
|
||||
var e = (E * matrix.A) + (F * matrix.C) + (row3 * matrix.E);
|
||||
var f = (E * matrix.B) + (F * matrix.D) + (row3 * matrix.F);
|
||||
var r3 = (E * matrix.row1) + (F * matrix.row2) + (row3 * matrix.row3);
|
||||
|
||||
return new TransformationMatrix(a, b, r1,
|
||||
c, d, r2,
|
||||
e, f, r3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies the matrix by a scalar value without modifying this matrix.
|
||||
/// </summary>
|
||||
/// <param name="scalar">The value to multiply.</param>
|
||||
/// <returns>A new matrix which is multiplied by the scalar value.</returns>
|
||||
[Pure]
|
||||
public TransformationMatrix Multiply(double scalar)
|
||||
{
|
||||
return new TransformationMatrix(A * scalar, B * scalar, row1 * scalar,
|
||||
C * scalar, D * scalar, row2 * scalar,
|
||||
E * scalar, F * scalar, row3 * scalar);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the X scaling component of the current matrix.
|
||||
/// </summary>
|
||||
/// <returns>The scaling factor for the x dimension in this matrix.</returns>
|
||||
internal double GetScalingFactorX()
|
||||
{
|
||||
var xScale = A;
|
||||
|
||||
/*
|
||||
* BM: if the trm is rotated, the calculation is a little more complicated
|
||||
*
|
||||
* The rotation matrix multiplied with the scaling matrix is:
|
||||
* ( x 0 0) ( cos sin 0) ( x*cos x*sin 0)
|
||||
* ( 0 y 0) * (-sin cos 0) = (-y*sin y*cos 0)
|
||||
* ( 0 0 1) ( 0 0 1) ( 0 0 1)
|
||||
*
|
||||
* So, if you want to deduce x from the matrix you take
|
||||
* M(0,0) = x*cos and M(0,1) = x*sin and use the theorem of Pythagoras
|
||||
*
|
||||
* sqrt(M(0,0)^2+M(0,1)^2) =
|
||||
* sqrt(x2*cos2+x2*sin2) =
|
||||
* sqrt(x2*(cos2+sin2)) = (here is the trick cos2+sin2 = 1)
|
||||
* sqrt(x2) =
|
||||
* abs(x)
|
||||
*/
|
||||
if (!(B == 0 && C == 0))
|
||||
{
|
||||
xScale = Math.Sqrt(A * A + B * B);
|
||||
}
|
||||
|
||||
return xScale;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (!(obj is TransformationMatrix m))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals(this, m);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether 2 transformation matrices are equal.
|
||||
/// </summary>
|
||||
public static bool Equals(TransformationMatrix a, TransformationMatrix b)
|
||||
{
|
||||
for (var i = 0; i < Rows; i++)
|
||||
{
|
||||
for (var j = 0; j < Columns; j++)
|
||||
{
|
||||
if (a[i, j] != b[i, j])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = 472622392;
|
||||
hashCode = hashCode * -1521134295 + row1.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + row2.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + row3.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + A.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + B.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + C.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + D.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + E.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + F.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{A}, {B}, {row1}\r\n{C}, {D}, {row2}\r\n{E}, {F}, {row3}";
|
||||
}
|
||||
}
|
||||
}
|
17
src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj
Normal file
17
src/UglyToad.PdfPig.Core/UglyToad.PdfPig.Core.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
<DocumentationFile>obj\Debug\netstandard2.0\UglyToad.PdfPig.Core.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net45' OR '$(TargetFramework)'=='net451' OR '$(TargetFramework)'=='net452' OR '$(TargetFramework)'=='net46' OR '$(TargetFramework)'=='net461' OR '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net47'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
118
src/UglyToad.PdfPig.Core/Union.cs
Normal file
118
src/UglyToad.PdfPig.Core/Union.cs
Normal file
@@ -0,0 +1,118 @@
|
||||
// ReSharper disable InconsistentNaming
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
/// <summary>
|
||||
/// Defines a type which is a union of two types.
|
||||
/// </summary>
|
||||
public abstract class Union<A, B>
|
||||
{
|
||||
/// <summary>
|
||||
/// Take an action for the item of the given type.
|
||||
/// </summary>
|
||||
public abstract void Match(Action<A> first, Action<B> second);
|
||||
|
||||
/// <summary>
|
||||
/// Run a func against the item of the given type.
|
||||
/// </summary>
|
||||
public abstract TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second);
|
||||
|
||||
private Union() { }
|
||||
|
||||
/// <summary>
|
||||
/// Create a value of the first type.
|
||||
/// </summary>
|
||||
public static Case1 One(A item)
|
||||
{
|
||||
return new Case1(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a value of the second type.
|
||||
/// </summary>
|
||||
public static Case2 Two(B item)
|
||||
{
|
||||
return new Case2(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type representing items of the first type in a union.
|
||||
/// </summary>
|
||||
public sealed class Case1 : Union<A, B>
|
||||
{
|
||||
/// <summary>
|
||||
/// The item.
|
||||
/// </summary>
|
||||
public readonly A Item;
|
||||
|
||||
/// <summary>
|
||||
/// Create first type.
|
||||
/// </summary>
|
||||
public Case1(A item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DebuggerStepThrough]
|
||||
public override void Match(Action<A> first, Action<B> second)
|
||||
{
|
||||
first(Item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DebuggerStepThrough]
|
||||
public override TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second)
|
||||
{
|
||||
return first(Item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Item?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The type representing items of the second type in a union.
|
||||
/// </summary>
|
||||
public sealed class Case2 : Union<A, B>
|
||||
{
|
||||
/// <summary>
|
||||
/// The item.
|
||||
/// </summary>
|
||||
public readonly B Item;
|
||||
|
||||
/// <summary>
|
||||
/// Create second type.
|
||||
/// </summary>
|
||||
public Case2(B item)
|
||||
{
|
||||
Item = item;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DebuggerStepThrough]
|
||||
public override void Match(Action<A> first, Action<B> second)
|
||||
{
|
||||
second(Item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[DebuggerStepThrough]
|
||||
public override TResult Match<TResult>(Func<A, TResult> first, Func<B, TResult> second)
|
||||
{
|
||||
return second(Item);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return Item?.ToString() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
src/UglyToad.PdfPig.Core/WritingExtensions.cs
Normal file
66
src/UglyToad.PdfPig.Core/WritingExtensions.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
using System.IO;
|
||||
|
||||
/// <summary>
|
||||
/// Handles writing specified data types to an output stream in a valid PDF compliant format.
|
||||
/// </summary>
|
||||
public static class WritingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Write the <see langword="long"/> to the stream as a <see langword="uint"/>.
|
||||
/// </summary>
|
||||
public static void WriteUInt(this Stream stream, long value) => WriteUInt(stream, (uint)value);
|
||||
/// <summary>
|
||||
/// Write the <see langword="uint"/> to the stream as a <see langword="uint"/>.
|
||||
/// </summary>
|
||||
public static void WriteUInt(this Stream stream, uint value)
|
||||
{
|
||||
var buffer = new[]
|
||||
{
|
||||
(byte) (value >> 24),
|
||||
(byte) (value >> 16),
|
||||
(byte) (value >> 8),
|
||||
(byte) value
|
||||
};
|
||||
|
||||
stream.Write(buffer, 0, 4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the <see langword="int"/> to the stream as a <see langword="ushort"/>.
|
||||
/// </summary>
|
||||
public static void WriteUShort(this Stream stream, int value) => WriteUShort(stream, (ushort)value);
|
||||
/// <summary>
|
||||
/// Write the <see langword="ushort"/> to the stream as a <see langword="ushort"/>.
|
||||
/// </summary>
|
||||
public static void WriteUShort(this Stream stream, ushort value)
|
||||
{
|
||||
var buffer = new[]
|
||||
{
|
||||
(byte) (value >> 8),
|
||||
(byte) value
|
||||
};
|
||||
|
||||
stream.Write(buffer, 0, 2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the <see langword="ushort"/> to the stream as a <see langword="short"/>.
|
||||
/// </summary>
|
||||
public static void WriteShort(this Stream stream, ushort value) => WriteShort(stream, (short)value);
|
||||
/// <summary>
|
||||
/// Write the <see langword="short"/> to the stream as a <see langword="short"/>.
|
||||
/// </summary>
|
||||
public static void WriteShort(this Stream stream, short value)
|
||||
{
|
||||
var buffer = new[]
|
||||
{
|
||||
(byte) (value >> 8),
|
||||
(byte) value
|
||||
};
|
||||
|
||||
stream.Write(buffer, 0, 2);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user