Compare commits

..

5 Commits

Author SHA1 Message Date
Eliot Jones
487e368e9e Merge pull request #224 from InusualZ/object-copier
Writer: New API to copy token
2020-10-06 14:57:52 +01:00
InusualZ
704c56285c Address CI Errors 2020-10-05 20:16:41 -04:00
InusualZ
a190653683 Use the new IObjectCopier API in the PdfMerger
Added a new PagesCopier, this class would be the one responsible to give a parent to the page tree that are copied from other documents
2020-10-02 21:33:36 -04:00
InusualZ
8f0326a818 Introduce a new API for intercepting token that are being copied
This API would allow us to track any type of token while is/was copied to a stream, so when a similar token come again, we can decide if want to just use the already written token or the new one.

This API would allow us to divide the code for each specific thing that we are trying to avoid having duplicate, while not penalizing performance.

Another plus would be, that since every "deduplicator" code would be behind a class, if a class is causing some performance regression that the user don't want, the user could decide not to add it and the resultant pdf would still be valid
2020-10-02 21:11:46 -04:00
InusualZ
5a98b21c2f Don't brute force search for similar token. since the process is slow
Since this class is the writer of token,  it should never be the one responsible to check if a token is duplicated.
2020-10-02 20:36:31 -04:00
127 changed files with 1449 additions and 4182 deletions

View File

@@ -193,10 +193,10 @@ PdfPig also comes with some tools for document layout analysis such as the Recur
An example of the output of the Recursive XY Cut algorithm viewed in an external viewer such as [LayoutEvalGUI](https://www.primaresearch.org/tools/PerformanceEvaluation) is shown below:
![Output is a single page with two columns and some bulleted lists. The page has been divided into regions bounded in blue and words bounded in red.](https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/Document%20Layout%20Analysis/rxyc%20example.png)
![Output is a single page with two columns and some bulleted lists. The page has been divided into regions bounded in blue and words bounded in red.](https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/Document%20Layout%20Analysis/recursive%20xy%20cut%20example.png)
See the [document layout analysis](https://github.com/UglyToad/PdfPig/wiki/Document-Layout-Analysis) page on the wiki for full details.
## Credit ##
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.

View File

@@ -49,7 +49,6 @@
private double width;
/// <summary>
/// Width of the rectangle.
/// <para>A positive number.</para>
/// </summary>
public double Width
{
@@ -67,7 +66,6 @@
private double height;
/// <summary>
/// Height of the rectangle.
/// <para>A positive number.</para>
/// </summary>
public double Height
{
@@ -199,16 +197,13 @@
var cos = Math.Cos(t);
var sin = Math.Sin(t);
var inverseRotation = new TransformationMatrix(
var inverseRotation = new TransformationMatrix(
cos, -sin, 0,
sin, cos, 0,
0, 0, 1);
// Using Abs as a proxy for Euclidean distance in 1D
// as it might happen that points have negative coordinates.
var bl = inverseRotation.Transform(BottomLeft);
width = Math.Abs(inverseRotation.Transform(BottomRight).X - bl.X);
height = Math.Abs(inverseRotation.Transform(TopLeft).Y - bl.Y);
width = inverseRotation.Transform(BottomRight).X - inverseRotation.Transform(BottomLeft).X;
height = inverseRotation.Transform(TopLeft).Y - inverseRotation.Transform(BottomLeft).Y;
}
/// <inheritdoc />

View File

@@ -160,9 +160,9 @@
throw new ArgumentNullException("LineTo(): currentPosition is null.");
}
}
/// <summary>
/// Add a rectangle following the pdf specification (m, l, l, l, c) path. A new subpath is created.
/// Adds 4 <see cref="Line"/>s forming a rectangle to the path.
/// </summary>
public void Rectangle(double x, double y, double width, double height)
{
@@ -174,6 +174,8 @@
CloseSubpath(); // h
IsDrawnAsRectangle = true;
}
internal void QuadraticCurveTo(double x1, double y1, double x2, double y2) { }
/// <summary>
/// Add a <see cref="BezierCurve"/> to the path.

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>0.1.5-alpha001</Version>
<Version>0.1.3-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>0.1.5-alpha001</Version>
<Version>0.1.3-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -76,13 +76,6 @@
AddAdobeFontMetrics("Times,Italic", "Times-Italic");
AddAdobeFontMetrics("Times,Bold", "Times-Bold");
AddAdobeFontMetrics("Times,BoldItalic", "Times-BoldItalic");
// Additional names for Arial
AddAdobeFontMetrics("ArialMT", "Helvetica");
AddAdobeFontMetrics("Arial-ItalicMT", "Helvetica-Oblique");
AddAdobeFontMetrics("Arial-BoldMT", "Helvetica-Bold");
AddAdobeFontMetrics("Arial-BoldMT,Bold", "Helvetica-Bold");
AddAdobeFontMetrics("Arial-BoldItalicMT", "Helvetica-BoldOblique");
}
private static void AddAdobeFontMetrics(string fontName, Standard14Font? type = null)
@@ -150,7 +143,7 @@
{
return Standard14Cache[BuilderTypesToNames[fontType]];
}
/// <summary>
/// Determines if a font with this name is a standard 14 font.
/// </summary>
@@ -166,7 +159,7 @@
{
return new HashSet<string>(Standard14Names);
}
/// <summary>
/// Get the official Standard 14 name of the actual font which the given font name maps to.
/// </summary>

View File

@@ -22,19 +22,10 @@
}
var strings = new TrueTypeNameRecord[count];
var offset = header.Offset + stringOffset;
for (var i = 0; i < count; i++)
{
strings[i] = GetTrueTypeNameRecord(names[i], data, offset);
}
var nameRecord = names[i];
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
}
private static TrueTypeNameRecord GetTrueTypeNameRecord(NameRecordBuilder nameRecord, TrueTypeDataBytes data, uint offset)
{
try
{
var encoding = OtherEncodings.Iso88591;
switch (nameRecord.PlatformId)
@@ -71,27 +62,16 @@
}
}
var position = offset + nameRecord.Offset;
if (position >= data.Length)
{
return null;
}
var position = header.Offset + stringOffset + nameRecord.Offset;
data.Seek(position);
if (data.TryReadString(nameRecord.Length, encoding, out var str))
{
return nameRecord.ToNameRecord(str);
}
var str = data.ReadString(nameRecord.Length, encoding);
// Damaged font
return null;
}
catch
{
return null;
strings[i] = nameRecord.ToNameRecord(str);
}
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
}
private static string GetName(int nameId, TrueTypeNameRecord[] names)
@@ -104,7 +84,7 @@
{
var name = names[i];
if (name is null || name.NameId != nameId)
if (name.NameId != nameId)
{
continue;
}

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
{
using System;
using System.Diagnostics;
using Tables;
internal static class TableParser
@@ -14,20 +13,6 @@
public static T Parse<T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITrueTypeTable
{
//checksum https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
uint sum = 0;
var nLongs = (table.Length + 3) / 4;
data.Seek(table.Offset);
while (nLongs-- > 0)
{
sum += (uint)data.ReadSignedInt();
}
if (sum != table.CheckSum)
{
Trace.TraceWarning("Table with invalid checksum found in TrueType font file.");
}
if (typeof(T) == typeof(CMapTable))
{
return (T) (object) CMapTableParser.Parse(table, data, register);

View File

@@ -61,7 +61,7 @@
string any = null;
foreach (var nameRecord in NameRecords)
{
if (nameRecord is null || nameRecord.NameId != 6)
if (nameRecord.NameId != 6)
{
continue;
}

View File

@@ -203,10 +203,7 @@
for (var i = 0; i < namesLength; i++)
{
var numberOfCharacters = data.ReadByte();
if (data.TryReadString(numberOfCharacters, Encoding.UTF8, out var str))
{
nameArray[i] = str;
}
nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
}
}

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.Fonts.TrueType
{
using System;
using System.IO;
using System.Text;
using Core;
@@ -80,33 +81,22 @@
/// </summary>
public string ReadTag()
{
if (TryReadString(4, Encoding.UTF8, out var result))
{
return result;
}
throw new InvalidOperationException($"Could not read Tag from TrueType file at {inputBytes.CurrentOffset}.");
return ReadString(4, Encoding.UTF8);
}
/// <summary>
/// Read a <see langword="string"/> of the given number of bytes in length with the specified encoding.
/// </summary>
public bool TryReadString(int bytesToRead, Encoding encoding, out string result)
public string ReadString(int bytesToRead, Encoding encoding)
{
result = null;
if (encoding == null)
{
return false;
throw new ArgumentNullException(nameof(encoding));
}
byte[] data = new byte[bytesToRead];
if (ReadBuffered(data, bytesToRead))
{
result = encoding.GetString(data, 0, data.Length);
return true;
}
return false;
ReadBuffered(data, bytesToRead);
return encoding.GetString(data, 0, data.Length);
}
/// <summary>
@@ -244,15 +234,13 @@
return $"@: {Position} of {inputBytes.Length} bytes.";
}
private bool ReadBuffered(byte[] buffer, int length)
private void ReadBuffered(byte[] buffer, int length)
{
var read = inputBytes.Read(buffer, length);
if (read < length)
{
return false;
throw new EndOfStreamException($"Could not read a buffer of {length} bytes.");
}
return true;
}
}
}

View File

@@ -3,8 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using UglyToad.PdfPig.Core;
using UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction;
/// <summary>
/// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then
@@ -25,13 +23,13 @@
public static bool TakeFromStackBottom { get; } = false;
public static bool ClearsOperandStack { get; } = false;
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
public static void Run(Type1BuildCharContext context)
{
var index = (int)context.Stack.PopTop();
var index = (int) context.Stack.PopTop();
// What it should do
var numberOfArguments = (int)context.Stack.PopTop();
var otherSubroutineArguments = new List<double>(numberOfArguments);
@@ -44,44 +42,17 @@
{
// Other subrs 0-2 implement flex
case FlexEnd:
{
// https://github.com/apache/pdfbox/blob/2c23d8b4e3ad61852f0b6ee2b95b907eefba1fcf/fontbox/src/main/java/org/apache/fontbox/cff/Type1CharString.java#L339
context.IsFlexing = false;
if (context.FlexPoints.Count < 7)
{
throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
}
{
context.IsFlexing = false;
// TODO: I don't really care about flexpoints, but we should probably handle them... one day.
//if (context.FlexPoints.Count < 7)
//{
// throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
//}
// reference point is relative to start point
PdfPoint reference = context.FlexPoints[0];
reference = reference.Translate(context.CurrentPosition.X, context.CurrentPosition.Y);
// first point is relative to reference point
PdfPoint first = context.FlexPoints[1];
first = first.Translate(reference.X, reference.Y);
// make the first point relative to the start point
first = first.Translate(-context.CurrentPosition.X, -context.CurrentPosition.Y);
context.Stack.Push(first.X);
context.Stack.Push(first.Y);
context.Stack.Push(context.FlexPoints[2].X);
context.Stack.Push(context.FlexPoints[2].Y);
context.Stack.Push(context.FlexPoints[3].X);
context.Stack.Push(context.FlexPoints[3].Y);
RelativeRCurveToCommand.Run(context);
context.Stack.Push(context.FlexPoints[4].X);
context.Stack.Push(context.FlexPoints[4].Y);
context.Stack.Push(context.FlexPoints[5].X);
context.Stack.Push(context.FlexPoints[5].Y);
context.Stack.Push(context.FlexPoints[6].X);
context.Stack.Push(context.FlexPoints[6].Y);
RelativeRCurveToCommand.Run(context);
context.ClearFlexPoints();
break;
}
context.ClearFlexPoints();
break;
}
case FlexBegin:
Debug.Assert(otherSubroutineArguments.Count == 0, "Flex begin should have no arguments.");

View File

@@ -4,8 +4,6 @@
/// <summary>
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
/// <para>This establishes the current point for a subsequent relative path building command.
/// The 'setcurrentpoint' command is used only in conjunction with results from 'OtherSubrs' procedures.</para>
/// </summary>
internal static class SetCurrentPointCommand
{
@@ -24,8 +22,8 @@
var x = context.Stack.PopBottom();
var y = context.Stack.PopBottom();
//context.CurrentPosition = new PdfPoint(x, y);
// TODO: need to investigate why odd behavior when the current point is actualy set.
context.CurrentPosition = new PdfPoint(x, y);
context.Stack.Clear();
}
}

View File

@@ -19,19 +19,19 @@
public static void Run(Type1BuildCharContext context)
{
var deltaX = context.Stack.PopBottom();
var x = context.Stack.PopBottom();
var actualX = context.CurrentPosition.X + x;
var y = context.CurrentPosition.Y;
if (context.IsFlexing)
{
// not in the Type 1 spec, but exists in some fonts
context.AddFlexPoint(new PdfPoint(deltaX, 0));
// TODO: flex support
}
else
{
var x = context.CurrentPosition.X + deltaX;
var y = context.CurrentPosition.Y;
context.CurrentPosition = new PdfPoint(x, y);
context.Path.MoveTo(x, y);
context.CurrentPosition = new PdfPoint(actualX, y);
context.Path.MoveTo(actualX, y);
}
context.Stack.Clear();

View File

@@ -30,7 +30,7 @@
if (context.IsFlexing)
{
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
}
else
{

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
{
using Core;
using System;
/// <summary>
/// Vertical move to. Moves relative to the current point.
@@ -24,8 +23,7 @@
if (context.IsFlexing)
{
// not in the Type 1 spec, but exists in some fonts
context.AddFlexPoint(new PdfPoint(0, deltaY));
// TODO: flex commands
}
else
{

View File

@@ -6,7 +6,7 @@
/// Sets left sidebearing and the character width vector.
/// This command also sets the current point to(sbx, sby), but does not place the point in the character path.
/// </summary>
internal static class SbwCommand
internal class SbwCommand
{
public const string Name = "sbw";

View File

@@ -28,7 +28,7 @@
public CharStringStack PostscriptStack { get; } = new CharStringStack();
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
public IReadOnlyList<PdfPoint> FlexPoints { get; }
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
Func<int, PdfSubpath> characterByIndexFactory,
@@ -41,7 +41,7 @@
public void AddFlexPoint(PdfPoint point)
{
FlexPoints.Add(point);
}
public PdfSubpath GetCharacter(int characterCode)
@@ -61,7 +61,7 @@
public void ClearFlexPoints()
{
FlexPoints.Clear();
}
}
}

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>0.1.5-alpha001</Version>
<Version>0.1.3-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -4,9 +4,6 @@
using System.Linq;
using PdfFonts.Cmap;
using PdfPig.Tokens;
using UglyToad.PdfPig.Core;
using UglyToad.PdfPig.PdfFonts.Parser.Parts;
using UglyToad.PdfPig.Tokenization.Scanner;
using Xunit;
public class CodespaceRangeTests
@@ -103,29 +100,6 @@
Assert.True(matches);
}
[Fact]
public void ColorspaceParserError()
{
var parser = new CodespaceRangeParser();
var byteArrayInput = new ByteArrayInputBytes(OtherEncodings.StringAsLatin1Bytes("1 begincodespacerange\nendcodespacerange"));
var tokenScanner = new CoreTokenScanner(byteArrayInput);
Assert.True(tokenScanner.MoveNext());
Assert.True(tokenScanner.CurrentToken is NumericToken);
var numeric = (NumericToken) tokenScanner.CurrentToken;
Assert.True(tokenScanner.MoveNext());
Assert.True(tokenScanner.CurrentToken is OperatorToken);
var opToken = (OperatorToken)tokenScanner.CurrentToken;
Assert.Equal("begincodespacerange", opToken.Data);
var cmapBuilder = new CharacterMapBuilder();
parser.Parse(numeric, tokenScanner, cmapBuilder);
Assert.Empty(cmapBuilder.CodespaceRanges);
}
private static byte[] GetHexBytes(params char[] characters)
{
var token = new HexToken(characters);

View File

@@ -1,22 +0,0 @@
namespace UglyToad.PdfPig.Tests.Fonts
{
using UglyToad.PdfPig.Tests.Dla;
using Xunit;
public class PointSizeTests
{
[Fact]
public void RotatedText()
{
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath("complex rotated")))
{
var page = document.GetPage(1);
foreach (var letter in page.Letters)
{
Assert.Equal(12, letter.PointSize);
}
}
}
}
}

View File

@@ -10,7 +10,7 @@
{
var names = Standard14.GetNames().Count;
Assert.Equal(39, names);
Assert.Equal(34, names);
}
}
}

View File

@@ -210,20 +210,6 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
Assert.Equal(points, glyph.Points.Length);
}
}
[Fact]
public void ParseIssue258CorruptNameTable()
{
var bytes = TrueTypeTestHelper.GetFileBytes("issue-258-corrupt-name-table");
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));
var font = TrueTypeFontParser.Parse(input);
Assert.NotNull(font);
Assert.NotNull(font.TableRegister.NameTable);
Assert.NotEmpty(font.TableRegister.NameTable.NameRecords);
}
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.IO;
using UglyToad.PdfPig.Core;
using Xunit;
namespace UglyToad.PdfPig.Tests.Fonts.Type1
{
public class Type1CharStringParserTests
{
[Fact]
public void CorrectBoundingBoxesFlexPoints()
{
PointComparer pointComparer = new PointComparer(new DoubleComparer(3));
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
var filePath = Path.Combine(documentFolder, "data.pdf");
using (var doc = PdfDocument.Open(filePath))
{
var page = doc.GetPage(1);
var letters = page.Letters;
// check 'm'
var m = letters[0];
Assert.Equal("m", m.Value);
Assert.Equal(new PdfPoint(253.4458, 658.431), m.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.GlyphRectangle.TopRight, pointComparer);
// check 'p'
var p = letters[1];
Assert.Equal("p", p.Value);
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.GlyphRectangle.TopRight, pointComparer);
}
}
}
}

View File

@@ -1682,17 +1682,5 @@
Assert.Equal(expected[2], rectR.TopLeft, PointComparer);
Assert.Equal(expected[3], rectR.BottomLeft, PointComparer);
}
[Fact]
public void Issue261()
{
// Some points have negative coordinates after rotation, resulting in negative Height
PdfRectangle rect = new PdfRectangle(new PdfPoint(401.51, 461.803424),
new PdfPoint(300.17322363281249, 461.803424),
new PdfPoint(401.51, 291.45),
new PdfPoint(300.17322363281249, 291.45));
Assert.True(rect.Height > 0);
}
}
}

View File

@@ -7,8 +7,6 @@
using PdfPig.Tokens;
using PdfPig.Core;
using Tokens;
using UglyToad.PdfPig.Graphics.Core;
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
internal class TestOperationContext : IOperationContext
{
@@ -90,66 +88,6 @@
}
public void MoveTo(double x, double y)
{
BeginSubpath();
var point = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
CurrentPosition = point;
CurrentSubpath.MoveTo(point.X, point.Y);
}
public void BezierCurveTo(double x2, double y2, double x3, double y3)
{
if (CurrentSubpath == null)
{
return;
}
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
CurrentSubpath.BezierCurveTo(CurrentPosition.X, CurrentPosition.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
CurrentPosition = end;
}
public void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
{
if (CurrentSubpath == null)
{
return;
}
var controlPoint1 = CurrentTransformationMatrix.Transform(new PdfPoint(x1, y1));
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
CurrentPosition = end;
}
public void LineTo(double x, double y)
{
if (CurrentSubpath == null)
{
return;
}
var endPoint = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
CurrentPosition = endPoint;
}
public void Rectangle(double x, double y, double width, double height)
{
BeginSubpath();
var lowerLeft = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
var upperRight = CurrentTransformationMatrix.Transform(new PdfPoint(x + width, y + height));
CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
AddCurrentSubpath();
}
public void EndPath()
{
}
@@ -186,91 +124,6 @@
{
}
private void AdjustTextMatrix(double tx, double ty)
{
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
}
public void SetFlatnessTolerance(decimal tolerance)
{
GetCurrentState().Flatness = tolerance;
}
public void SetLineCap(LineCapStyle cap)
{
GetCurrentState().CapStyle = cap;
}
public void SetLineDashPattern(LineDashPattern pattern)
{
GetCurrentState().LineDashPattern = pattern;
}
public void SetLineJoin(LineJoinStyle join)
{
GetCurrentState().JoinStyle = join;
}
public void SetLineWidth(decimal width)
{
GetCurrentState().LineWidth = width;
}
public void SetMiterLimit(decimal limit)
{
GetCurrentState().MiterLimit = limit;
}
public void MoveToNextLineWithOffset()
{
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)GetCurrentState().FontState.Leading);
tdOperation.Run(this);
}
public void SetFontAndSize(NameToken font, double size)
{
var currentState = GetCurrentState();
currentState.FontState.FontSize = size;
currentState.FontState.FontName = font;
}
public void SetHorizontalScaling(double scale)
{
GetCurrentState().FontState.HorizontalScaling = scale;
}
public void SetTextLeading(double leading)
{
GetCurrentState().FontState.Leading = leading;
}
public void SetTextRenderingMode(TextRenderingMode mode)
{
GetCurrentState().FontState.TextRenderingMode = mode;
}
public void SetTextRise(double rise)
{
GetCurrentState().FontState.Rise = rise;
}
public void SetWordSpacing(double spacing)
{
GetCurrentState().FontState.WordSpacing = spacing;
}
public void ModifyCurrentTransformationMatrix(double[] value)
{
var ctm = GetCurrentState().CurrentTransformationMatrix;
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
}
public void SetCharacterSpacing(double spacing)
{
GetCurrentState().FontState.CharacterSpacing = spacing;
}
private class TestFontFactory : IFontFactory
{
public IFont Get(DictionaryToken dictionary)

View File

@@ -1,52 +0,0 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using PdfPig.Tokens;
using System.Collections.Generic;
using Xunit;
public class AdvancedPdfDocumentAccessTests
{
[Fact]
public void ReplacesObjectsFunc()
{
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
using (var document = PdfDocument.Open(path))
{
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
document.Advanced.ReplaceIndirectObject(contents.Data, tk =>
{
var dict = new Dictionary<NameToken, IToken>();
dict[NameToken.Length] = new NumericToken(0);
var replaced = new StreamToken(new DictionaryToken(dict), new List<byte>());
return replaced;
});
var page = document.GetPage(1);
Assert.Empty(page.Letters);
}
}
[Fact]
public void ReplacesObjects()
{
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
using (var document = PdfDocument.Open(path))
{
var dict = new Dictionary<NameToken, IToken>();
dict[NameToken.Length] = new NumericToken(0);
var replacement = new StreamToken(new DictionaryToken(dict), new List<byte>());
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
document.Advanced.ReplaceIndirectObject(contents.Data, replacement);
var page = document.GetPage(1);
Assert.Empty(page.Letters);
}
}
}
}

View File

@@ -1,66 +0,0 @@
using System;
using UglyToad.PdfPig.Tokens;
using Xunit;
namespace UglyToad.PdfPig.Tests.Integration
{
public class OptionalContentTests
{
[Fact]
public void NoMarkedOptionalContent()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("AcroFormsBasicFields.pdf")))
{
var page = document.GetPage(1);
var oc = page.ExperimentalAccess.GetOptionalContents();
Assert.Equal(0, oc.Count);
}
}
[Fact]
public void MarkedOptionalContent()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("odwriteex.pdf")))
{
var page = document.GetPage(1);
var oc = page.ExperimentalAccess.GetOptionalContents();
Assert.Equal(3, oc.Count);
Assert.Contains("0", oc);
Assert.Contains("Dimentions", oc);
Assert.Contains("Text", oc);
Assert.Equal(1, oc["0"].Count);
Assert.Equal(2, oc["Dimentions"].Count);
Assert.Equal(1, oc["Text"].Count);
}
}
[Fact]
public void MarkedOptionalContentRecursion()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Layer pdf - 322_High_Holborn_building_Brochure.pdf")))
{
var page1 = document.GetPage(1);
var oc1 = page1.ExperimentalAccess.GetOptionalContents();
Assert.Equal(16, oc1.Count);
Assert.Contains("NEW ARRANGEMENT", oc1);
var page2 = document.GetPage(2);
var oc2 = page2.ExperimentalAccess.GetOptionalContents();
Assert.Equal(15, oc2.Count);
Assert.DoesNotContain("NEW ARRANGEMENT", oc2);
Assert.Contains("WDL Shell text", oc2);
Assert.Equal(2, oc2["WDL Shell text"].Count);
var page3 = document.GetPage(3);
var oc3 = page3.ExperimentalAccess.GetOptionalContents();
Assert.Equal(15, oc3.Count);
Assert.Contains("WDL Shell text", oc3);
Assert.Equal(2, oc3["WDL Shell text"].Count);
}
}
}
}

View File

@@ -74,8 +74,6 @@
"UglyToad.PdfPig.Content.IPdfImage",
"UglyToad.PdfPig.Content.Letter",
"UglyToad.PdfPig.Content.MarkedContentElement",
"UglyToad.PdfPig.Content.MediaBox",
"UglyToad.PdfPig.Content.OptionalContentGroupElement",
"UglyToad.PdfPig.Content.Page",
"UglyToad.PdfPig.Content.PageRotationDegrees",
"UglyToad.PdfPig.Content.PageSize",
@@ -104,13 +102,6 @@
"UglyToad.PdfPig.Graphics.Colors.GrayColor",
"UglyToad.PdfPig.Graphics.Colors.IColor",
"UglyToad.PdfPig.Graphics.Colors.RGBColor",
"UglyToad.PdfPig.Graphics.Colors.ColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.IndexedColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.SeparationColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.UnsupportedColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Core.LineCapStyle",
"UglyToad.PdfPig.Graphics.Core.LineDashPattern",
"UglyToad.PdfPig.Graphics.Core.LineJoinStyle",
@@ -195,7 +186,6 @@
"UglyToad.PdfPig.Graphics.Operations.TextState.Type3SetGlyphWidth",
"UglyToad.PdfPig.Graphics.Operations.TextState.Type3SetGlyphWidthAndBoundingBox",
"UglyToad.PdfPig.Graphics.TextMatrices",
"UglyToad.PdfPig.Images.ColorSpaceDetailsByteConverter",
"UglyToad.PdfPig.Logging.ILog",
"UglyToad.PdfPig.Outline.Bookmarks",
"UglyToad.PdfPig.Outline.BookmarkNode",
@@ -207,8 +197,6 @@
"UglyToad.PdfPig.ParsingOptions",
"UglyToad.PdfPig.PdfDocument",
"UglyToad.PdfPig.PdfExtensions",
"UglyToad.PdfPig.Rendering.IPageImageRenderer",
"UglyToad.PdfPig.Rendering.PdfRendererImageFormat",
"UglyToad.PdfPig.Structure",
"UglyToad.PdfPig.Util.Adler32Checksum",
"UglyToad.PdfPig.Util.IWordExtractor",
@@ -218,7 +206,6 @@
"UglyToad.PdfPig.Writer.PdfAStandard",
"UglyToad.PdfPig.Writer.PdfDocumentBuilder",
"UglyToad.PdfPig.Writer.PdfMerger",
"UglyToad.PdfPig.Writer.PdfWriterType",
"UglyToad.PdfPig.Writer.PdfPageBuilder",
"UglyToad.PdfPig.Writer.TokenWriter",
"UglyToad.PdfPig.XObjects.XObjectImage"

View File

@@ -45,11 +45,6 @@ namespace UglyToad.PdfPig.Tests.Tokens
return Objects[reference];
}
public void ReplaceToken(IndirectReference reference, IToken token)
{
throw new NotImplementedException();
}
public void Dispose()
{
}

View File

@@ -39,7 +39,6 @@
<ItemGroup>
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
@@ -65,9 +64,6 @@
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Fonts\TrueType\PMingLiU.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View File

@@ -6,7 +6,6 @@
using Integration;
using PdfPig.Core;
using PdfPig.Fonts.Standard14Fonts;
using PdfPig.Tokens;
using PdfPig.Writer;
using Tests.Fonts.TrueType;
using Xunit;
@@ -137,7 +136,7 @@
var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font);
Assert.NotEmpty(page.CurrentStream.Operations);
Assert.NotEmpty(page.Operations);
var b = builder.Build();
@@ -187,7 +186,7 @@
page.AddText("eé", 12, new PdfPoint(30, 520), font);
Assert.NotEmpty(page.CurrentStream.Operations);
Assert.NotEmpty(page.Operations);
var b = builder.Build();
@@ -233,7 +232,7 @@
page.AddText("eé", 12, new PdfPoint(30, 520), font);
Assert.NotEmpty(page.CurrentStream.Operations);
Assert.NotEmpty(page.Operations);
var b = builder.Build();
@@ -280,7 +279,7 @@
var letters = page.AddText("Hello World!", 16, new PdfPoint(30, 520), font);
page.AddText("This is some further text continuing to write", 12, new PdfPoint(30, 500), font);
Assert.NotEmpty(page.CurrentStream.Operations);
Assert.NotEmpty(page.Operations);
var b = builder.Build();
@@ -589,400 +588,6 @@
}
}
[Fact]
public void CanCreateDocumentWithFilledRectangle()
{
var builder = new PdfDocumentBuilder();
var page = builder.AddPage(PageSize.A4);
page.SetTextAndFillColor(255, 0, 0);
page.SetStrokeColor(0, 0, 255);
page.DrawRectangle(new PdfPoint(20, 100), 200, 100, 1.5m, true);
var file = builder.Build();
WriteFile(nameof(CanCreateDocumentWithFilledRectangle), file);
}
[Fact]
public void CanGeneratePageWithMultipleStream()
{
var builder = new PdfDocumentBuilder();
var page = builder.AddPage(PageSize.A4);
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
var font = builder.AddTrueTypeFont(file);
var letters = page.AddText("Hello", 12, new PdfPoint(30, 50), font);
Assert.NotEmpty(page.CurrentStream.Operations);
page.NewContentStreamAfter();
page.AddText("World!", 12, new PdfPoint(50, 50), font);
Assert.NotEmpty(page.CurrentStream.Operations);
var b = builder.Build();
WriteFile(nameof(CanGeneratePageWithMultipleStream), b);
Assert.NotEmpty(b);
using (var document = PdfDocument.Open(b))
{
var page1 = document.GetPage(1);
Assert.Equal("HelloWorld!", page1.Text);
var h = page1.Letters[0];
Assert.Equal("H", h.Value);
Assert.Equal("Andada-Regular", h.FontName);
}
}
[Fact]
public void CanCopyPage()
{
byte[] b;
{
var builder = new PdfDocumentBuilder();
var page1 = builder.AddPage(PageSize.A4);
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
var font = builder.AddTrueTypeFont(file);
page1.AddText("Hello", 12, new PdfPoint(30, 50), font);
Assert.NotEmpty(page1.CurrentStream.Operations);
using (var readDocument = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("bold-italic.pdf")))
{
var rpage = readDocument.GetPage(1);
var page2 = builder.AddPage(PageSize.A4);
page2.CopyFrom(rpage);
}
b = builder.Build();
Assert.NotEmpty(b);
}
WriteFile(nameof(CanCopyPage), b);
using (var document = PdfDocument.Open(b))
{
Assert.Equal( 2, document.NumberOfPages);
var page1 = document.GetPage(1);
Assert.Equal("Hello", page1.Text);
var page2 = document.GetPage(2);
Assert.Equal("Lorem ipsum dolor sit amet, consectetur adipiscing elit. ", page2.Text);
}
}
[Fact]
public void CanAddHelloWorldToSimplePage()
{
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var doc = PdfDocument.Open(path);
var builder = new PdfDocumentBuilder();
var page = builder.AddPage(doc, 1);
page.DrawLine(new PdfPoint(30, 520), new PdfPoint(360, 520));
page.DrawLine(new PdfPoint(360, 520), new PdfPoint(360, 250));
page.SetStrokeColor(250, 132, 131);
page.DrawLine(new PdfPoint(25, 70), new PdfPoint(100, 70), 3);
page.ResetColor();
page.DrawRectangle(new PdfPoint(30, 200), 250, 100, 0.5m);
page.DrawRectangle(new PdfPoint(30, 100), 250, 100, 0.5m);
var file = TrueTypeTestHelper.GetFileBytes("Andada-Regular.ttf");
var font = builder.AddTrueTypeFont(file);
var letters = page.AddText("Hello World!", 12, new PdfPoint(30, 50), font);
Assert.NotEmpty(page.CurrentStream.Operations);
var b = builder.Build();
WriteFile(nameof(CanAddHelloWorldToSimplePage), b);
Assert.NotEmpty(b);
using (var document = PdfDocument.Open(b))
{
var page1 = document.GetPage(1);
Assert.Equal("I am a simple pdf.Hello World!", page1.Text);
var h = page1.Letters[18];
Assert.Equal("H", h.Value);
Assert.Equal("Andada-Regular", h.FontName);
var comparer = new DoubleComparer(0.01);
var pointComparer = new PointComparer(comparer);
for (int i = 0; i < letters.Count; i++)
{
var readerLetter = page1.Letters[i+18];
var writerLetter = letters[i];
Assert.Equal(readerLetter.Value, writerLetter.Value);
Assert.Equal(readerLetter.Location, writerLetter.Location, pointComparer);
Assert.Equal(readerLetter.FontSize, writerLetter.FontSize, comparer);
Assert.Equal(readerLetter.GlyphRectangle.Width, writerLetter.GlyphRectangle.Width, comparer);
Assert.Equal(readerLetter.GlyphRectangle.Height, writerLetter.GlyphRectangle.Height, comparer);
Assert.Equal(readerLetter.GlyphRectangle.BottomLeft, writerLetter.GlyphRectangle.BottomLeft, pointComparer);
}
}
}
[Fact]
public void CanMerge2SimpleDocumentsReversed_Builder()
{
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
using (var docOne = PdfDocument.Open(one))
using (var docTwo = PdfDocument.Open(two))
{
var builder = new PdfDocumentBuilder();
builder.AddPage(docOne, 1);
builder.AddPage(docTwo, 1);
var result = builder.Build();
PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape", false);
}
}
[Fact]
public void CanMerge2SimpleDocuments_Builder()
{
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
using (var docOne = PdfDocument.Open(one))
using (var docTwo = PdfDocument.Open(two))
using (var builder = new PdfDocumentBuilder())
{
builder.AddPage(docOne, 1);
builder.AddPage(docTwo, 1);
var result = builder.Build();
PdfMergerTests.CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.", false);
}
}
[Fact]
public void CanDedupObjectsFromSameDoc_Builder()
{
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
using (var doc = PdfDocument.Open(one))
{
var builder = new PdfDocumentBuilder();
builder.AddPage(doc, 1);
builder.AddPage(doc, 1);
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(2, document.NumberOfPages);
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29,
"Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
}
}
}
[Fact]
public void CanDedupObjectsFromDifferentDoc_HashBuilder()
{
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
using (var doc = PdfDocument.Open(one))
using (var doc2 = PdfDocument.Open(one))
using (var builder = new PdfDocumentBuilder(new MemoryStream(), true, PdfWriterType.ObjectInMemoryDedup))
{
builder.AddPage(doc, 1);
builder.AddPage(doc2, 1);
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(2, document.NumberOfPages);
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29,
"Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
}
}
}
[Fact]
public void CanCreatePageTree()
{
var count = 25 * 25 * 25 + 1;
using (var builder = new PdfDocumentBuilder())
{
for (var i = 0; i < count;i++)
{
builder.AddPage(PageSize.A4);
}
var result = builder.Build();
WriteFile(nameof(CanCreatePageTree), result);
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(count, document.NumberOfPages);
}
}
}
[Fact]
public void CanWriteEmptyContentStream()
{
using (var builder = new PdfDocumentBuilder())
{
builder.AddPage(PageSize.A4);
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(1, document.NumberOfPages);
var pg = document.GetPage(1);
// single empty page should result in single content stream
Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken);
}
}
}
[Fact]
public void CanWriteSingleContentStream()
{
using (var builder = new PdfDocumentBuilder())
{
var pb = builder.AddPage(PageSize.A4);
pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2));
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(1, document.NumberOfPages);
var pg = document.GetPage(1);
// single empty page should result in single content stream
Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken);
}
}
}
[Fact]
public void CanWriteAndIgnoreEmptyContentStream()
{
using (var builder = new PdfDocumentBuilder())
{
var pb = builder.AddPage(PageSize.A4);
pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2));
pb.NewContentStreamAfter();
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(1, document.NumberOfPages);
var pg = document.GetPage(1);
// empty stream should be ignored and resulting single stream should be written
Assert.NotNull(pg.Dictionary.Data[NameToken.Contents] as IndirectReferenceToken);
}
}
}
[Fact]
public void CanWriteMultipleContentStream()
{
using (var builder = new PdfDocumentBuilder())
{
var pb = builder.AddPage(PageSize.A4);
pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2));
pb.NewContentStreamAfter();
pb.DrawLine(new PdfPoint(1, 1), new PdfPoint(2, 2));
var result = builder.Build();
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(1, document.NumberOfPages);
var pg = document.GetPage(1);
// multiple streams should be written to array
var streams = pg.Dictionary.Data[NameToken.Contents] as ArrayToken;
Assert.NotNull(streams);
Assert.Equal(2, streams.Length);
}
}
}
[InlineData("Single Page Simple - from google drive.pdf")]
[InlineData("Old Gutnish Internet Explorer.pdf")]
[InlineData("68-1990-01_A.pdf")]
[InlineData("Multiple Page - from Mortality Statistics.pdf")]
[Theory]
public void CopiedPagesResultInSameData(string name)
{
var docPath = IntegrationHelpers.GetDocumentPath(name);
using (var doc = PdfDocument.Open(docPath, ParsingOptions.LenientParsingOff))
using (var builder = new PdfDocumentBuilder())
{
var count1 = GetCounts(doc);
for (var i = 1; i <= doc.NumberOfPages; i++)
{
builder.AddPage(doc, i);
}
var result = builder.Build();
WriteFile(nameof(CopiedPagesResultInSameData) + "_" + name, result);
using (var doc2 = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
var count2 = GetCounts(doc2);
Assert.Equal(count1.Item1, count2.Item1);
Assert.Equal(count1.Item2, count2.Item2);
}
}
(int, double) GetCounts(PdfDocument toCount)
{
int letters = 0;
double location = 0;
foreach (var page in toCount.GetPages())
{
foreach (var letter in page.Letters)
{
unchecked { letters += 1; }
unchecked {
location += letter.Location.X;
location += letter.Location.Y;
location += letter.Font.Name.Length;
}
}
}
return (letters, location);
}
}
private static void WriteFile(string name, byte[] bytes, string extension = "pdf")
{
try

View File

@@ -2,8 +2,6 @@
{
using Integration;
using PdfPig.Writer;
using System;
using System.Collections.Generic;
using System.IO;
using Xunit;
@@ -16,24 +14,20 @@
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var result = PdfMerger.Merge(one, two);
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.");
}
[Fact]
public void CanMerge2SimpleDocumentsIntoStream()
{
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
using (var outputStream = GetSelfDestructingNewFileStream("merge2"))
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
if (outputStream is null)
{
return;//we can't create a file in this test session
}
Assert.Equal(2, document.NumberOfPages);
PdfMerger.Merge(one, two, outputStream);
CanMerge2SimpleDocumentsAssertions(outputStream, "Write something inInkscape", "I am a simple pdf.");
Assert.Equal(1.5m, document.Version);
var page1 = document.GetPage(1);
Assert.Equal("Write something inInkscape", page1.Text);
var page2 = document.GetPage(2);
Assert.Equal("I am a simple pdf.", page2.Text);
}
}
@@ -44,25 +38,20 @@
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
var result = PdfMerger.Merge(one, two);
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape");
}
internal static void CanMerge2SimpleDocumentsAssertions(Stream stream, string page1Text, string page2Text, bool checkVersion=true)
{
stream.Position = 0;
using (var document = PdfDocument.Open(stream, ParsingOptions.LenientParsingOff))
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(2, document.NumberOfPages);
if (checkVersion)
{
Assert.Equal(1.5m, document.Version);
}
Assert.Equal(1.5m, document.Version);
var page1 = document.GetPage(1);
Assert.Equal(page1Text, page1.Text);
Assert.Equal("I am a simple pdf.", page1.Text);
var page2 = document.GetPage(2);
Assert.Equal(page2Text, page2.Text);
Assert.Equal("Write something inInkscape", page2.Text);
}
}
@@ -100,26 +89,11 @@
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(2, document.NumberOfPages);
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 24,
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count < 24,
"Expected object count to be lower than 24");
}
}
[Fact]
public void DedupsObjectsFromSameDoc()
{
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
var result = PdfMerger.Merge(new List<byte[]> { File.ReadAllBytes(one) }, new List<IReadOnlyList<int>> { new List<int> { 1, 2} });
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(2, document.NumberOfPages);
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29,
"Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
}
}
[Fact]
public void CanMergeWithObjectStream()
{
@@ -141,53 +115,6 @@
}
}
[Fact]
public void CanMergeWithSelection()
{
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
var contents = File.ReadAllBytes(first);
var toCopy = new[] {2, 1, 4, 3, 6, 5};
var result = PdfMerger.Merge(new [] { contents }, new [] { toCopy });
WriteFile(nameof(CanMergeWithSelection), result);
using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff))
using (var merged = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(6, merged.NumberOfPages);
for (var i =1;i<merged.NumberOfPages;i++)
{
Assert.Equal(
existing.GetPage(toCopy[i-1]).Text,
merged.GetPage(i).Text
);
}
}
}
[Fact]
public void CanMergeMultipleWithSelection()
{
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
var second = IntegrationHelpers.GetDocumentPath("Old Gutnish Internet Explorer.pdf");
var result = PdfMerger.Merge(new[] { File.ReadAllBytes(first), File.ReadAllBytes(second) }, new[] { new[] { 2, 1, 4, 3, 6, 5 }, new []{ 3, 2, 1 } });
WriteFile(nameof(CanMergeMultipleWithSelection), result);
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
{
Assert.Equal(9, document.NumberOfPages);
foreach (var page in document.GetPages())
{
Assert.NotNull(page.Text);
}
}
}
private static void WriteFile(string name, byte[] bytes)
{
try
@@ -206,40 +133,5 @@
// ignored.
}
}
private static FileStream GetSelfDestructingNewFileStream(string name)
{
try
{
if (!Directory.Exists("Merger"))
{
Directory.CreateDirectory("Merger");
}
var output = Path.Combine("Merger", $"{name}.pdf");
return File.Create(output, 4096, FileOptions.DeleteOnClose);
}
catch
{
return null;
}
}
[Fact]
public void NoStackoverflow()
{
try
{
var bytes = PdfMerger.Merge(IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf"));
using (var document = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
{
Assert.Equal(45, document.NumberOfPages);
}
}
catch (StackOverflowException)
{
Assert.True(false);
}
}
}
}

View File

@@ -1,30 +0,0 @@
namespace UglyToad.PdfPig.Tests.Writer
{
using Integration;
using PdfPig.Writer;
using System.IO;
using UglyToad.PdfPig.Tokens;
using Xunit;
public class TokenWriterTests
{
[Fact]
public void EscapeSpecialCharacter()
{
using (var memStream = new MemoryStream())
{
TokenWriter.WriteToken(new StringToken("\\"), memStream);
TokenWriter.WriteToken(new StringToken("(Hello)"), memStream);
// Read Test
memStream.Position = 0;
using (var streamReader = new StreamReader(memStream))
{
var line = streamReader.ReadToEnd();
Assert.Equal("(\\\\) (\\(Hello\\)) ", line);
}
}
}
}
}

View File

@@ -8,7 +8,7 @@
internal class NumericTokenizer : ITokenizer
{
private readonly StringBuilder stringBuilder = new StringBuilder();
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
private const byte Zero = 48;
private const byte Nine = 57;
@@ -23,7 +23,7 @@
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.')
{
characters = stringBuilder;
characters = StringBuilderPool.Borrow();
characters.Append((char)currentByte);
}
else
@@ -53,7 +53,7 @@
try
{
var str = characters.ToString();
characters.Clear();
StringBuilderPool.Return(characters);
switch (str)
{

View File

@@ -1,12 +1,11 @@
namespace UglyToad.PdfPig.Tokenization
{
using Core;
using System.Text;
using Tokens;
internal class PlainTokenizer : ITokenizer
{
private readonly StringBuilder stringBuilder = new StringBuilder();
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
public bool ReadsNextByte { get; } = true;
@@ -19,7 +18,7 @@
return false;
}
var builder = stringBuilder;
var builder = StringBuilderPool.Borrow();
builder.Append((char)currentByte);
while (inputBytes.MoveNext())
{
@@ -40,7 +39,7 @@
}
var text = builder.ToString();
builder.Clear();
StringBuilderPool.Return(builder);
switch (text)
{

View File

@@ -15,12 +15,9 @@
private static readonly DictionaryTokenizer DictionaryTokenizer = new DictionaryTokenizer();
private static readonly HexTokenizer HexTokenizer = new HexTokenizer();
private static readonly NameTokenizer NameTokenizer = new NameTokenizer();
// NOTE: these are not thread safe so should not be static. Each instance includes a
// StringBuilder it re-uses.
private readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
private readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
private readonly StringTokenizer StringTokenizer = new StringTokenizer();
private static readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
private static readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
private static readonly StringTokenizer StringTokenizer = new StringTokenizer();
private readonly ScannerScope scope;
private readonly IInputBytes inputBytes;

View File

@@ -6,7 +6,7 @@
internal class StringTokenizer : ITokenizer
{
private readonly StringBuilder stringBuilder = new StringBuilder();
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(16);
public bool ReadsNextByte { get; } = false;
public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token)
@@ -23,7 +23,7 @@
return false;
}
var builder = stringBuilder;
var builder = StringBuilderPool.Borrow();
var numberOfBrackets = 1;
var isEscapeActive = false;
var isLineBreaking = false;
@@ -178,7 +178,7 @@
encodedWith = StringToken.Encoding.Iso88591;
}
builder.Clear();
StringBuilderPool.Return(builder);
token = new StringToken(tokenStr, encodedWith);

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>0.1.5-alpha001</Version>
<Version>0.1.3-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -114,15 +114,6 @@
return new DictionaryToken(result);
}
/// <summary>
/// Create a new <see cref="DictionaryToken"/>.
/// </summary>
/// <param name="data">The data this dictionary will contain.</param>
public static DictionaryToken With(IReadOnlyDictionary<string, IToken> data)
{
return new DictionaryToken(data ?? throw new ArgumentNullException(nameof(data)));
}
/// <inheritdoc />
public bool Equals(IToken obj)

View File

@@ -338,12 +338,10 @@
public static readonly NameToken MarkInfo = new NameToken("MarkInfo");
public static readonly NameToken Mask = new NameToken("Mask");
public static readonly NameToken Matrix = new NameToken("Matrix");
public static readonly NameToken Matte = new NameToken("Matte");
public static readonly NameToken MaxLen = new NameToken("MaxLen");
public static readonly NameToken MaxWidth = new NameToken("MaxWidth");
public static readonly NameToken Mcid = new NameToken("MCID");
public static readonly NameToken Mdp = new NameToken("MDP");
public static readonly NameToken Measure = new NameToken("Measure");
public static readonly NameToken MediaBox = new NameToken("MediaBox");
public static readonly NameToken Metadata = new NameToken("Metadata");
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
@@ -551,12 +549,10 @@
public static readonly NameToken Unix = new NameToken("Unix");
public static readonly NameToken Uri = new NameToken("URI");
public static readonly NameToken Url = new NameToken("URL");
public static readonly NameToken Usage = new NameToken("Usage");
public static readonly NameToken UserUnit = new NameToken("UserUnit");
// V
public static readonly NameToken V = new NameToken("V");
public static readonly NameToken V2 = new NameToken("V2");
public static readonly NameToken VE = new NameToken("VE");
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
public static readonly NameToken Version = new NameToken("Version");
public static readonly NameToken Vertices = new NameToken("Vertices");
@@ -565,7 +561,6 @@
public static readonly NameToken ViewClip = new NameToken("ViewClip");
public static readonly NameToken ViewerPreferences = new NameToken("ViewerPreferences");
public static readonly NameToken Volume = new NameToken("Volume");
public static readonly NameToken Vp = new NameToken("VP");
// W
public static readonly NameToken W = new NameToken("W");
public static readonly NameToken W2 = new NameToken("W2");

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
<LangVersion>latest</LangVersion>
<Version>0.1.5-alpha001</Version>
<Version>0.1.3-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -1,6 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>

View File

@@ -478,17 +478,6 @@
var isChecked = false;
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
{
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.As, tokenScanner, out NameToken appearanceStateName)
&& fieldDictionary.TryGetOptionalTokenDirect(NameToken.Ap, tokenScanner, out DictionaryToken _))
{
// Issue #267 - Use the set appearance instead, this might not work for 3 state checkboxes.
isChecked = !string.Equals(
appearanceStateName.Data,
NameToken.Off,
StringComparison.OrdinalIgnoreCase);
valueToken = appearanceStateName;
return (isChecked, valueToken);
}
valueToken = NameToken.Off;
}
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))

View File

@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Content;
using Core;
using Filters;
using Parser.Parts;
using Tokenization.Scanner;
@@ -83,30 +82,6 @@
return embeddedFiles.Count > 0;
}
/// <summary>
/// Replaces the token in an internal cache that will be returned instead of
/// scanning the source PDF data for future requests.
/// </summary>
/// <param name="reference">The object number for the object to replace.</param>
/// <param name="replacer">Func that takes existing token as input and return new token.</param>
public void ReplaceIndirectObject(IndirectReference reference, Func<IToken, IToken> replacer)
{
var obj = pdfScanner.Get(reference);
var replacement = replacer(obj.Data);
pdfScanner.ReplaceToken(reference, replacement);
}
/// <summary>
/// Replaces the token in an internal cache that will be returned instead of
/// scanning the source PDF data for future requests.
/// </summary>
/// <param name="reference">The object number for the object to replace.</param>
/// <param name="replacement">Replacement token to use.</param>
public void ReplaceIndirectObject(IndirectReference reference, IToken replacement)
{
pdfScanner.ReplaceToken(reference, replacement);
}
private void GuardDisposed()
{
if (isDisposed)

View File

@@ -84,11 +84,6 @@
/// </summary>
bool IsInlineImage { get; }
/// <summary>
/// Full details for the <see cref="ColorSpace"/> with any associated data.
/// </summary>
ColorSpaceDetails ColorSpaceDetails { get; }
/// <summary>
/// Get the decoded bytes of the image if applicable. For JPEG images and some other types the
/// <see cref="RawBytes"/> should be used directly.

View File

@@ -51,9 +51,6 @@
/// <inheritdoc />
public IReadOnlyList<byte> RawBytes { get; }
/// <inheritdoc />
public ColorSpaceDetails ColorSpaceDetails { get; }
/// <summary>
/// Create a new <see cref="InlineImage"/>.
/// </summary>
@@ -64,8 +61,7 @@
IReadOnlyList<decimal> decode,
IReadOnlyList<byte> bytes,
IReadOnlyList<IFilter> filters,
DictionaryToken streamDictionary,
ColorSpaceDetails colorSpaceDetails)
DictionaryToken streamDictionary)
{
Bounds = bounds;
WidthInSamples = widthInSamples;
@@ -78,7 +74,6 @@
Interpolate = interpolate;
RawBytes = bytes;
ColorSpaceDetails = colorSpaceDetails;
var supportsFilters = true;
foreach (var filter in filters)

View File

@@ -68,6 +68,7 @@
/// <summary>
/// The size of the font in points.
/// <para>This is considered experimental because the calculated value is incorrect for some documents at present.</para>
/// </summary>
public double PointSize { get; }

View File

@@ -9,7 +9,7 @@
/// <remarks>
/// See table 3.27 from the PDF specification version 1.7.
/// </remarks>
public class MediaBox
internal class MediaBox
{
///<summary>
/// User space units per inch.
@@ -66,14 +66,8 @@
/// </summary>
public static readonly MediaBox A6 = new MediaBox(new PdfRectangle(0, 0, 105 * PointsPerMm, 148 * PointsPerMm));
/// <summary>
/// A rectangle, expressed in default user space units, that defines the boundaries of the physical medium on which the page shall be displayed or printed.
/// </summary>
public PdfRectangle Bounds { get; }
/// <summary>
/// Create a new <see cref="MediaBox"/>.
/// </summary>
public MediaBox(PdfRectangle? bounds)
{
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));

View File

@@ -1,157 +0,0 @@
using System;
using System.Collections.Generic;
using UglyToad.PdfPig.Tokenization.Scanner;
using UglyToad.PdfPig.Tokens;
namespace UglyToad.PdfPig.Content
{
/// <summary>
/// An optional content group is a dictionary representing a collection of graphics
/// that can be made visible or invisible dynamically by users of viewers applications.
/// </summary>
public class OptionalContentGroupElement
{
/// <summary>
/// The type of PDF object that this dictionary describes.
/// <para>Must be OCG for an optional content group dictionary.</para>
/// </summary>
public string Type { get; }
/// <summary>
/// The name of the optional content group, suitable for presentation in a viewer application's user interface.
/// </summary>
public string Name { get; }
/// <summary>
/// A single name or an array containing any combination of names.
/// <para>Default value is 'View'.</para>
/// </summary>
public IReadOnlyList<string> Intent { get; }
/// <summary>
/// A usage dictionary describing the nature of the content controlled by the group.
/// </summary>
public IReadOnlyDictionary<string, IToken> Usage { get; }
/// <summary>
/// Underlying <see cref="MarkedContentElement"/>.
/// </summary>
public MarkedContentElement MarkedContent { get; }
internal OptionalContentGroupElement(MarkedContentElement markedContentElement, IPdfTokenScanner pdfTokenScanner)
{
MarkedContent = markedContentElement;
// Type - Required
if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out NameToken type))
{
Type = type.Data;
}
else if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out StringToken typeStr))
{
Type = typeStr.Data;
}
else
{
throw new ArgumentException($"Cannot parse optional content's {nameof(Type)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
}
switch (Type)
{
case "OCG": // Optional content group dictionary
// Name - Required
if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out NameToken name))
{
Name = name.Data;
}
else if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out StringToken nameStr))
{
Name = nameStr.Data;
}
else
{
throw new ArgumentException($"Cannot parse optional content's {nameof(Name)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
}
// Intent - Optional
if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out NameToken intentName))
{
Intent = new string[] { intentName.Data };
}
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out StringToken intentStr))
{
Intent = new string[] { intentStr.Data };
}
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out ArrayToken intentArray))
{
List<string> intentList = new List<string>();
foreach (var token in intentArray.Data)
{
if (token is NameToken nameA)
{
intentList.Add(nameA.Data);
}
else if (token is StringToken strA)
{
intentList.Add(strA.Data);
}
else
{
throw new NotImplementedException();
}
}
Intent = intentList;
}
else
{
// Default value is 'View'.
Intent = new string[] { "View" };
}
// Usage - Optional
if (markedContentElement.Properties.TryGet(NameToken.Usage, pdfTokenScanner, out DictionaryToken usage))
{
this.Usage = usage.Data;
}
break;
case "OCMD":
// OCGs - Optional
if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out DictionaryToken ocgsD))
{
// dictionary or array
throw new NotImplementedException($"{NameToken.Ocgs}");
}
else if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out ArrayToken ocgsA))
{
// dictionary or array
throw new NotImplementedException($"{NameToken.Ocgs}");
}
// P - Optional
if (markedContentElement.Properties.TryGet(NameToken.P, pdfTokenScanner, out NameToken p))
{
throw new NotImplementedException($"{NameToken.P}");
}
// VE - Optional
if (markedContentElement.Properties.TryGet(NameToken.VE, pdfTokenScanner, out ArrayToken ve))
{
throw new NotImplementedException($"{NameToken.VE}");
}
break;
default:
throw new ArgumentException($"Unknown Optional Content of type '{Type}' not known.", nameof(Type));
}
}
/// <summary>
/// <inheritdoc/>
/// </summary>
public override string ToString()
{
return $"{Type} - {Name} [{string.Join(",", Intent)}]: {MarkedContent?.ToString()}";
}
}
}

View File

@@ -10,7 +10,6 @@
using Util.JetBrains.Annotations;
using Tokenization.Scanner;
using Graphics;
using System.Linq;
/// <summary>
/// Contains the content and provides access to methods of a single page in the <see cref="PdfDocument"/>.
@@ -18,7 +17,7 @@
public class Page
{
private readonly AnnotationProvider annotationProvider;
internal readonly IPdfTokenScanner pdfScanner;
private readonly IPdfTokenScanner pdfScanner;
private readonly Lazy<string> textLazy;
/// <summary>
@@ -36,10 +35,7 @@
/// </summary>
public CropBox CropBox { get; }
/// <summary>
/// Defines the boundaries of the physical medium on which the page shall be displayed or printed.
/// </summary>
public MediaBox MediaBox { get; }
internal MediaBox MediaBox { get; }
internal PageContent Content { get; }
@@ -194,46 +190,6 @@
{
return annotationProvider.GetAnnotations();
}
/// <summary>
/// Gets any optional content on the page.
/// <para>Does not handle XObjects and annotations for the time being.</para>
/// </summary>
public IReadOnlyDictionary<string, IReadOnlyList<OptionalContentGroupElement>> GetOptionalContents()
{
List<OptionalContentGroupElement> mcesOptional = new List<OptionalContentGroupElement>();
// 4.10.2
// Optional content in content stream
GetOptionalContentsRecursively(page.Content?.GetMarkedContents(), ref mcesOptional);
// Optional content in XObjects and annotations
// TO DO
//var annots = GetAnnotations().ToList();
return mcesOptional.GroupBy(oc => oc.Name).ToDictionary(g => g.Key, g => g.ToList() as IReadOnlyList<OptionalContentGroupElement>);
}
private void GetOptionalContentsRecursively(IReadOnlyList<MarkedContentElement> markedContentElements, ref List<OptionalContentGroupElement> mcesOptional)
{
if (markedContentElements.Count == 0)
{
return;
}
foreach (var mce in markedContentElements)
{
if (mce.Tag == "OC")
{
mcesOptional.Add(new OptionalContentGroupElement(mce, page.pdfScanner));
// we don't recurse
}
else if (mce.Children?.Count > 0)
{
GetOptionalContentsRecursively(mce.Children, ref mcesOptional);
}
}
}
}
}
}

View File

@@ -1,11 +1,11 @@
namespace UglyToad.PdfPig.Content
{
using System;
using System.Collections.Generic;
using Core;
using Filters;
using Graphics;
using Graphics.Operations;
using System;
using System.Collections.Generic;
using Tokenization.Scanner;
using XObjects;

View File

@@ -55,13 +55,6 @@
int offset = 0;
while (offset < rowlength && ((i = input.Read(actline, offset, rowlength - offset)) != -1))
{
if (i == 0)
{
// TODO: #291, this indicates a bug in reading logic.
// This only avoids the infinite loop it does not fix the logic bug.
break;
}
offset += i;
}

View File

@@ -1,184 +0,0 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
using System;
using System.Collections.Generic;
using PdfPig.Core;
using Tokens;
/// <summary>
/// Contains more document-specific information about the <see cref="ColorSpace"/>.
/// </summary>
public abstract class ColorSpaceDetails
{
/// <summary>
/// The type of the ColorSpace.
/// </summary>
public ColorSpace Type { get; }
/// <summary>
/// The underlying type of ColorSpace, usually equal to <see cref="Type"/>
/// unless <see cref="ColorSpace.Indexed"/>.
/// </summary>
public ColorSpace BaseType { get; protected set; }
/// <summary>
/// Create a new <see cref="ColorSpaceDetails"/>.
/// </summary>
protected ColorSpaceDetails(ColorSpace type)
{
Type = type;
BaseType = type;
}
}
/// <summary>
/// A grayscale value is represented by a single number in the range 0.0 to 1.0,
/// where 0.0 corresponds to black, 1.0 to white, and intermediate values to different gray levels.
/// </summary>
public sealed class DeviceGrayColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The single instance of the <see cref="DeviceGrayColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceGrayColorSpaceDetails Instance = new DeviceGrayColorSpaceDetails();
private DeviceGrayColorSpaceDetails() : base(ColorSpace.DeviceGray)
{
}
}
/// <summary>
/// Color values are defined by three components representing the intensities of the additive primary colorants red, green and blue.
/// Each component is specified by a number in the range 0.0 to 1.0, where 0.0 denotes the complete absence of a primary component and 1.0 denotes maximum intensity.
/// </summary>
public sealed class DeviceRgbColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The single instance of the <see cref="DeviceRgbColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceRgbColorSpaceDetails Instance = new DeviceRgbColorSpaceDetails();
private DeviceRgbColorSpaceDetails() : base(ColorSpace.DeviceRGB)
{
}
}
/// <summary>
/// Color values are defined by four components cyan, magenta, yellow and black
/// </summary>
public sealed class DeviceCmykColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The single instance of the <see cref="DeviceCmykColorSpaceDetails"/>.
/// </summary>
public static readonly DeviceCmykColorSpaceDetails Instance = new DeviceCmykColorSpaceDetails();
private DeviceCmykColorSpaceDetails() : base(ColorSpace.DeviceCMYK)
{
}
}
/// <summary>
/// An Indexed color space allows a PDF content stream to use small integers as indices into a color map or color table of arbitrary colors in some other space.
/// A PDF consumer treats each sample value as an index into the color table and uses the color value it finds there.
/// </summary>
public class IndexedColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The base color space in which the values in the color table are to be interpreted.
/// It can be any device or CIE-based color space or(in PDF 1.3) a Separation or DeviceN space,
/// but not a Pattern space or another Indexed space.
/// </summary>
public ColorSpaceDetails BaseColorSpaceDetails { get; }
/// <summary>
/// An integer that specifies the maximum valid index value. Can be no greater than 255.
/// </summary>
public byte HiVal { get; }
/// <summary>
/// Provides the mapping between index values and the corresponding colors in the base color space.
/// </summary>
public IReadOnlyList<byte> ColorTable { get; }
/// <summary>
/// Create a new <see cref="IndexedColorSpaceDetails"/>.
/// </summary>
public IndexedColorSpaceDetails(ColorSpaceDetails baseColorSpaceDetails, byte hiVal, IReadOnlyList<byte> colorTable)
: base(ColorSpace.Indexed)
{
BaseColorSpaceDetails = baseColorSpaceDetails ?? throw new ArgumentNullException(nameof(baseColorSpaceDetails));
HiVal = hiVal;
ColorTable = colorTable;
BaseType = baseColorSpaceDetails.BaseType;
}
}
/// <summary>
/// A Separation color space provides a means for specifying the use of additional colorants or
/// for isolating the control of individual color components of a device color space for a subtractive device.
/// When such a space is the current color space, the current color is a single-component value, called a tint,
/// that controls the application of the given colorant or color components only.
/// </summary>
public class SeparationColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// Specifies the name of the colorant that this Separation color space is intended to represent.
/// </summary>
/// <remarks>
/// <para>
/// The special colorant name All refers collectively to all colorants available on an output device,
/// including those for the standard process colorants.
/// </para>
/// <para>
/// The special colorant name None never produces any visible output.
/// Painting operations in a Separation space with this colorant name have no effect on the current page.
/// </para>
/// </remarks>
public NameToken Name { get; }
/// <summary>
/// If the colorant name associated with a Separation color space does not correspond to a colorant available on the device,
/// the application arranges for subsequent painting operations to be performed in an alternate color space.
/// The intended colors can be approximated by colors in a device or CIE-based color space
/// 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>
/// Create a new <see cref="SeparationColorSpaceDetails"/>.
/// </summary>
public SeparationColorSpaceDetails(NameToken name,
ColorSpaceDetails alternateColorSpaceDetails,
Union<DictionaryToken, StreamToken> tintFunction)
: base(ColorSpace.Separation)
{
Name = name;
AlternateColorSpaceDetails = alternateColorSpaceDetails;
TintFunction = tintFunction;
}
}
/// <summary>
/// A ColorSpace which the PdfPig library does not currently support. Please raise a PR if you need support for this ColorSpace.
/// </summary>
public class UnsupportedColorSpaceDetails : ColorSpaceDetails
{
/// <summary>
/// The single instance of the <see cref="UnsupportedColorSpaceDetails"/>.
/// </summary>
public static readonly UnsupportedColorSpaceDetails Instance = new UnsupportedColorSpaceDetails();
private UnsupportedColorSpaceDetails() : base(ColorSpace.DeviceGray)
{
}
}
}

View File

@@ -1,5 +1,9 @@
namespace UglyToad.PdfPig.Graphics
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Colors;
using Content;
using Core;
@@ -10,13 +14,8 @@
using Parser;
using PdfFonts;
using PdfPig.Core;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Tokenization.Scanner;
using Tokens;
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
using XObjects;
using static PdfPig.Core.PdfSubpath;
@@ -242,7 +241,20 @@
var renderingMatrix =
TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise);
var pointSize = Math.Round(transformationMatrix.Multiply(TextMatrices.TextMatrix).Transform(new PdfRectangle(0, 0, 1, fontSize)).Height, 2);
// TODO: this does not seem correct, produces the correct result for now but we need to revisit.
// see: https://stackoverflow.com/questions/48010235/pdf-specification-get-font-size-in-points
var fontSizeMatrix = transformationMatrix.Multiply(TextMatrices.TextMatrix).Multiply(fontSize);
var pointSize = Math.Round(fontSizeMatrix.A, 2);
// Assume a rotated letter
if (pointSize == 0)
{
pointSize = Math.Round(fontSizeMatrix.B, 2);
}
if (pointSize < 0)
{
pointSize *= -1;
}
while (bytes.MoveNext())
{
@@ -280,7 +292,7 @@
var boundingBox = font.GetBoundingBox(code);
var transformedGlyphBounds = PerformantRectangleTransformer
.Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds);
.Transform(renderingMatrix, textMatrix, transformationMatrix, boundingBox.GlyphBounds);
var transformedPdfBounds = PerformantRectangleTransformer
.Transform(renderingMatrix, textMatrix, transformationMatrix, new PdfRectangle(0, 0, boundingBox.Width, 0));
@@ -561,67 +573,7 @@
ClosePath();
}
public void MoveTo(double x, double y)
{
BeginSubpath();
var point = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
CurrentPosition = point;
CurrentSubpath.MoveTo(point.X, point.Y);
}
public void BezierCurveTo(double x2, double y2, double x3, double y3)
{
if (CurrentSubpath == null)
{
return;
}
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
CurrentSubpath.BezierCurveTo(CurrentPosition.X, CurrentPosition.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
CurrentPosition = end;
}
public void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
{
if (CurrentSubpath == null)
{
return;
}
var controlPoint1 = CurrentTransformationMatrix.Transform(new PdfPoint(x1, y1));
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
CurrentPosition = end;
}
public void LineTo(double x, double y)
{
if (CurrentSubpath == null)
{
return;
}
var endPoint = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
CurrentPosition = endPoint;
}
public void Rectangle(double x, double y, double width, double height)
{
BeginSubpath();
var lowerLeft = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
var upperRight = CurrentTransformationMatrix.Transform(new PdfPoint(x + width, y + height));
CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
AddCurrentSubpath();
}
public void EndPath()
{
if (CurrentPath == null)
@@ -805,10 +757,7 @@
if (markedContentStack.CanPop)
{
var mc = markedContentStack.Pop(pdfScanner);
if (mc != null)
{
markedContents.Add(mc);
}
if (mc != null) markedContents.Add(mc);
}
}
@@ -816,86 +765,9 @@
{
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
}
var newMatrix = matrix.Multiply(TextMatrices.TextMatrix);
public void SetFlatnessTolerance(decimal tolerance)
{
GetCurrentState().Flatness = tolerance;
}
public void SetLineCap(LineCapStyle cap)
{
GetCurrentState().CapStyle = cap;
}
public void SetLineDashPattern(LineDashPattern pattern)
{
GetCurrentState().LineDashPattern = pattern;
}
public void SetLineJoin(LineJoinStyle join)
{
GetCurrentState().JoinStyle = join;
}
public void SetLineWidth(decimal width)
{
GetCurrentState().LineWidth = width;
}
public void SetMiterLimit(decimal limit)
{
GetCurrentState().MiterLimit = limit;
}
public void MoveToNextLineWithOffset()
{
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)GetCurrentState().FontState.Leading);
tdOperation.Run(this);
}
public void SetFontAndSize(NameToken font, double size)
{
var currentState = GetCurrentState();
currentState.FontState.FontSize = size;
currentState.FontState.FontName = font;
}
public void SetHorizontalScaling(double scale)
{
GetCurrentState().FontState.HorizontalScaling = scale;
}
public void SetTextLeading(double leading)
{
GetCurrentState().FontState.Leading = leading;
}
public void SetTextRenderingMode(TextRenderingMode mode)
{
GetCurrentState().FontState.TextRenderingMode = mode;
}
public void SetTextRise(double rise)
{
GetCurrentState().FontState.Rise = rise;
}
public void SetWordSpacing(double spacing)
{
GetCurrentState().FontState.WordSpacing = spacing;
}
public void ModifyCurrentTransformationMatrix(double[] value)
{
var ctm = GetCurrentState().CurrentTransformationMatrix;
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
}
public void SetCharacterSpacing(double spacing)
{
GetCurrentState().FontState.CharacterSpacing = spacing;
TextMatrices.TextMatrix = newMatrix;
}
}
}

View File

@@ -3,13 +3,25 @@
using PdfPig.Core;
using System.Collections.Generic;
using Tokens;
using UglyToad.PdfPig.Graphics.Core;
using Util.JetBrains.Annotations;
/// <summary>
/// The current graphics state context when running a PDF content stream.
/// </summary>
public interface IOperationContext
{
/// <summary>
/// The current subpath being drawn if applicable.
/// </summary>
[CanBeNull]
PdfSubpath CurrentSubpath { get; }
/// <summary>
/// The current path being drawn if applicable.
/// </summary>
[CanBeNull]
PdfPath CurrentPath { get; }
/// <summary>
/// The active colorspaces for this content stream.
/// </summary>
@@ -20,11 +32,22 @@
/// </summary>
PdfPoint CurrentPosition { get; set; }
/// <summary>
/// Get the currently active <see cref="CurrentGraphicsState"/>. States are stored on a stack structure.
/// </summary>
/// <returns>The currently active graphics state.</returns>
CurrentGraphicsState GetCurrentState();
/// <summary>
/// The matrices for the current text state.
/// </summary>
TextMatrices TextMatrices { get; }
/// <summary>
/// The current transformation matrix
/// </summary>
TransformationMatrix CurrentTransformationMatrix { get; }
/// <summary>
/// The number of graphics states on the stack.
/// </summary>
@@ -36,7 +59,7 @@
void PopState();
/// <summary>
/// Saves a copy of the current graphics state on the stack.
/// Saves a copy of the current graphics state on the stack.
/// </summary>
void PushState();
@@ -62,7 +85,7 @@
/// Start a new sub-path.
/// </summary>
void BeginSubpath();
/// <summary>
/// Close the current subpath.
/// </summary>
@@ -93,36 +116,6 @@
/// <param name="close">Whether to also close the path.</param>
void FillStrokePath(FillingRule fillingRule, bool close);
/// <summary>
/// Add a move command to the path.
/// <para>Should implement matrix transformations.</para>
/// </summary>
void MoveTo(double x, double y);
/// <summary>
/// Add a bezier curve to the current subpath.
/// <para>Should implement matrix transformations.</para>
/// </summary>
void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3);
/// <summary>
/// Add a bezier curve to the current subpath.
/// <para>Should implement matrix transformations.</para>
/// </summary>
void BezierCurveTo(double x2, double y2, double x3, double y3);
/// <summary>
/// Add a line command to the subpath.
/// <para>Should implement matrix transformations.</para>
/// </summary>
void LineTo(double x, double y);
/// <summary>
/// Add a rectangle following the pdf specification (m, l, l, l, c) path. A new subpath will be created.
/// <para>Should implement matrix transformations.</para>
/// </summary>
void Rectangle(double x, double y, double width, double height);
/// <summary>
/// End the path object without filling or stroking it. This operator shall be a path-painting no-op,
/// used primarily for the side effect of changing the current clipping path (see 8.5.4, "Clipping Path Operators").
@@ -169,90 +162,5 @@
/// Modify the clipping rule of the current path.
/// </summary>
void ModifyClippingIntersect(FillingRule clippingRule);
/// <summary>
/// Set the flatness tolerance in the graphics state.
/// Flatness is a number in the range 0 to 100; a value of 0 specifies the output devices default flatness tolerance.
/// </summary>
/// <param name="tolerance"></param>
void SetFlatnessTolerance(decimal tolerance);
/// <summary>
/// Set the line cap style in the graphics state.
/// </summary>
void SetLineCap(LineCapStyle cap);
/// <summary>
/// Set the line dash pattern in the graphics state.
/// </summary>
void SetLineDashPattern(LineDashPattern pattern);
/// <summary>
/// Set the line join style in the graphics state.
/// </summary>
void SetLineJoin(LineJoinStyle join);
/// <summary>
/// Set the line width in the graphics state.
/// </summary>
void SetLineWidth(decimal width);
/// <summary>
/// Set the miter limit in the graphics state.
/// </summary>
void SetMiterLimit(decimal limit);
/// <summary>
/// Move to the start of the next line.
/// </summary>
/// <remarks>
/// This performs this operation: 0 -Tl Td
/// The offset is negative leading text (Tl) value, this is incorrect in the specification.
/// </remarks>
void MoveToNextLineWithOffset();
/// <summary>
/// Set the font and the font size.
/// Font is the name of a font resource in the Font subdictionary of the current resource dictionary.
/// Size is a number representing a scale factor.
/// </summary>
void SetFontAndSize(NameToken font, double size);
/// <summary>
/// Set the horizontal scaling.
/// </summary>
/// <param name="scale"></param>
void SetHorizontalScaling(double scale);
/// <summary>
/// Set the text leading.
/// </summary>
void SetTextLeading(double leading);
/// <summary>
/// Set the text rendering mode.
/// </summary>
void SetTextRenderingMode(TextRenderingMode mode);
/// <summary>
/// Set text rise.
/// </summary>
void SetTextRise(double rise);
/// <summary>
/// Sets the word spacing.
/// </summary>
void SetWordSpacing(double spacing);
/// <summary>
/// Modify the current transformation matrix by concatenating the specified matrix.
/// </summary>
void ModifyCurrentTransformationMatrix(double[] value);
/// <summary>
/// Set the character spacing to a number expressed in unscaled text space units.
/// Initial value: 0.
/// </summary>
void SetCharacterSpacing(double spacing);
}
}

View File

@@ -10,7 +10,6 @@
using PdfPig.Core;
using Tokenization.Scanner;
using Tokens;
using Util;
internal class InlineImageBuilder
{
@@ -28,7 +27,35 @@
throw new InvalidOperationException($"Inline image builder not completely defined before calling {nameof(CreateInlineImage)}.");
}
bool TryMapColorSpace(NameToken name, out ColorSpace colorSpaceResult)
{
if (name.TryMapToColorSpace(out colorSpaceResult))
{
return true;
}
if (TryExtendedColorSpaceNameMapping(name, out colorSpaceResult))
{
return true;
}
if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken))
{
return false;
}
if (colorSpaceNamedToken.Name.TryMapToColorSpace(out colorSpaceResult))
{
return true;
}
if (TryExtendedColorSpaceNameMapping(colorSpaceNamedToken.Name, out colorSpaceResult))
{
return true;
}
return false;
}
var bounds = transformationMatrix.Transform(new PdfRectangle(new PdfPoint(1, 1),
new PdfPoint(0, 0)));
@@ -63,7 +90,7 @@
throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for inline image: {colorSpaceArray}.");
}
if (!ColorSpaceMapper.TryMap(firstColorSpaceName, resourceStore, out var colorSpaceMapped))
if (!TryMapColorSpace(firstColorSpaceName, out var colorSpaceMapped))
{
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {firstColorSpaceName}.");
}
@@ -72,7 +99,7 @@
}
else
{
if (!ColorSpaceMapper.TryMap(colorSpaceName, resourceStore, out var colorSpaceMapped))
if (!TryMapColorSpace(colorSpaceName, out var colorSpaceMapped))
{
throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {colorSpaceName}.");
}
@@ -81,15 +108,6 @@
}
}
var imgDic = new DictionaryToken(Properties ?? new Dictionary<NameToken, IToken>());
var details = ColorSpaceDetailsParser.GetColorSpaceDetails(
colorSpace,
imgDic,
tokenScanner,
resourceStore,
filterProvider);
var renderingIntent = GetByKeys<NameToken>(NameToken.Intent, null, false)?.Data?.ToRenderingIntent() ?? defaultRenderingIntent;
var filterNames = new List<NameToken>();
@@ -139,8 +157,30 @@
return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, colorSpace, decode, Bytes,
filters,
streamDictionary,
details);
streamDictionary);
}
private static bool TryExtendedColorSpaceNameMapping(NameToken name, out ColorSpace result)
{
result = ColorSpace.DeviceGray;
switch (name.Data)
{
case "G":
result = ColorSpace.DeviceGray;
return true;
case "RGB":
result = ColorSpace.DeviceRGB;
return true;
case "CMYK":
result = ColorSpace.DeviceCMYK;
return true;
case "I":
result = ColorSpace.Indexed;
return true;
}
return false;
}
// ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local

View File

@@ -35,7 +35,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetFlatnessTolerance(Tolerance);
operationContext.GetCurrentState().Flatness = Tolerance;
}
/// <inheritdoc />

View File

@@ -45,7 +45,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetLineCap(Cap);
operationContext.GetCurrentState().CapStyle = Cap;
}
/// <inheritdoc />

View File

@@ -33,7 +33,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetLineDashPattern(Pattern);
operationContext.GetCurrentState().LineDashPattern = Pattern;
}
/// <inheritdoc />

View File

@@ -40,7 +40,7 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetLineJoin(Join);
operationContext.GetCurrentState().JoinStyle = Join;
}
/// <inheritdoc />

View File

@@ -33,7 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetLineWidth(Width);
var currentState = operationContext.GetCurrentState();
currentState.LineWidth = Width;
}
/// <inheritdoc />

View File

@@ -33,7 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetMiterLimit(Limit);
var currentState = operationContext.GetCurrentState();
currentState.MiterLimit = Limit;
}
/// <inheritdoc />

View File

@@ -20,7 +20,6 @@
/// <summary>
/// Applies the operation to the current context with the provided resources.
/// <para>Matrix transformations should be implemented in <see cref="IOperationContext"/>.</para>
/// </summary>
/// <param name="operationContext"></param>
void Run(IOperationContext operationContext);

View File

@@ -63,7 +63,6 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
// need to implement marked content here
}
/// <inheritdoc />

View File

@@ -71,7 +71,14 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.BezierCurveTo((double)X1, (double)Y1, (double)X2, (double)Y2, (double)X3, (double)Y3);
if (operationContext.CurrentSubpath == null) return;
var controlPoint1 = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X1, Y1));
var controlPoint2 = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X2, Y2));
var end = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X3, Y3));
operationContext.CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y,
controlPoint2.X, controlPoint2.Y, end.X, end.Y);
operationContext.CurrentPosition = end;
}
/// <inheritdoc />

View File

@@ -56,7 +56,12 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.BezierCurveTo((double)X1, (double)Y1, (double)X3, (double)Y3, (double)X3, (double)Y3);
if (operationContext.CurrentSubpath == null) return;
var controlPoint1 = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X1, Y1));
var end = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X3, Y3));
operationContext.CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, end.X, end.Y, end.X, end.Y);
operationContext.CurrentPosition = end;
}
/// <inheritdoc />

View File

@@ -56,7 +56,12 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.Rectangle((double)LowerLeftX, (double)LowerLeftY, (double)Width, (double)Height);
operationContext.BeginSubpath();
var lowerLeft = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(LowerLeftX, LowerLeftY));
var upperRight = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(LowerLeftX + Width, LowerLeftY + Height));
operationContext.CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
operationContext.AddCurrentSubpath();
}
/// <inheritdoc />

View File

@@ -56,7 +56,17 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.BezierCurveTo((double)X2, (double)Y2, (double)X3, (double)Y3);
if (operationContext.CurrentSubpath == null) return;
var controlPoint2 = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X2, Y2));
var end = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X3, Y3));
operationContext.CurrentSubpath.BezierCurveTo(operationContext.CurrentPosition.X,
operationContext.CurrentPosition.Y,
controlPoint2.X,
controlPoint2.Y,
end.X,
end.Y);
operationContext.CurrentPosition = end;
}
/// <inheritdoc />

View File

@@ -41,7 +41,11 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.LineTo((double)X, (double)Y);
if (operationContext.CurrentSubpath == null) return;
var endPoint = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X, Y));
operationContext.CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
operationContext.CurrentPosition = endPoint;
}
/// <inheritdoc />

View File

@@ -41,7 +41,10 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.MoveTo((double)X, (double)Y);
operationContext.BeginSubpath();
var point = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X, Y));
operationContext.CurrentPosition = point;
operationContext.CurrentSubpath.MoveTo(point.X, point.Y);
}
/// <inheritdoc />

View File

@@ -44,7 +44,13 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.ModifyCurrentTransformationMatrix(Array.ConvertAll(Value, x => (double)x));
var newMatrix = TransformationMatrix.FromArray(Value);
var ctm = operationContext.GetCurrentState().CurrentTransformationMatrix;
var newCtm = newMatrix.Multiply(ctm);
operationContext.GetCurrentState().CurrentTransformationMatrix = newCtm;
}
/// <inheritdoc />

View File

@@ -32,7 +32,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.MoveToNextLineWithOffset();
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)operationContext.GetCurrentState().FontState.Leading);
tdOperation.Run(operationContext);
}
/// <inheritdoc />

View File

@@ -34,7 +34,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetCharacterSpacing((double)Spacing);
var currentState = operationContext.GetCurrentState();
currentState.FontState.CharacterSpacing = (double)Spacing;
}
/// <inheritdoc />

View File

@@ -48,7 +48,10 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetFontAndSize(Font, (double)Size);
var currentState = operationContext.GetCurrentState();
currentState.FontState.FontSize = (double)Size;
currentState.FontState.FontName = Font;
}
/// <inheritdoc />

View File

@@ -30,7 +30,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetHorizontalScaling((double)Scale);
var currentState = operationContext.GetCurrentState();
currentState.FontState.HorizontalScaling = (double)Scale;
}
/// <inheritdoc />

View File

@@ -33,7 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetTextLeading((double)Leading);
var currentState = operationContext.GetCurrentState();
currentState.FontState.Leading = (double)Leading;
}
/// <inheritdoc />

View File

@@ -33,7 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetTextRenderingMode(Mode);
var currentState = operationContext.GetCurrentState();
currentState.FontState.TextRenderingMode = Mode;
}
/// <inheritdoc />

View File

@@ -33,7 +33,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetTextRise((double)Rise);
var currentState = operationContext.GetCurrentState();
currentState.FontState.Rise = (double)Rise;
}
/// <inheritdoc />

View File

@@ -34,7 +34,9 @@
/// <inheritdoc />
public void Run(IOperationContext operationContext)
{
operationContext.SetWordSpacing((double)Spacing);
var currentState = operationContext.GetCurrentState();
currentState.FontState.WordSpacing = (double)Spacing;
}
/// <inheritdoc />

View File

@@ -1,99 +0,0 @@
namespace UglyToad.PdfPig.Images
{
using System;
using System.Collections.Generic;
using System.Linq;
using Content;
using Core;
using Graphics.Colors;
/// <summary>
/// Utility for working with the bytes in <see cref="IPdfImage"/>s and converting according to their <see cref="ColorSpaceDetails"/>.s
/// </summary>
public static class ColorSpaceDetailsByteConverter
{
/// <summary>
/// Converts the output bytes (if available) of <see cref="IPdfImage.TryGetBytes"/>
/// to actual pixel values using the <see cref="IPdfImage.ColorSpaceDetails"/>. For most images this doesn't
/// change the data but for <see cref="ColorSpace.Indexed"/> it will convert the bytes which are indexes into the
/// real pixel data into the real pixel data.
/// </summary>
public static byte[] Convert(ColorSpaceDetails details, IReadOnlyList<byte> decoded)
{
if (decoded == null)
{
return EmptyArray<byte>.Instance;
}
if (details == null)
{
return decoded.ToArray();
}
switch (details)
{
case IndexedColorSpaceDetails indexed:
return UnwrapIndexedColorSpaceBytes(indexed, decoded);
}
return decoded.ToArray();
}
private static byte[] UnwrapIndexedColorSpaceBytes(IndexedColorSpaceDetails indexed, IReadOnlyList<byte> input)
{
var multiplier = 1;
Func<byte, IEnumerable<byte>> transformer = null;
switch (indexed.BaseColorSpaceDetails.Type)
{
case ColorSpace.DeviceRGB:
transformer = x =>
{
var r = new byte[3];
for (var i = 0; i < 3; i++)
{
r[i] = indexed.ColorTable[x * 3 + i];
}
return r;
};
multiplier = 3;
break;
case ColorSpace.DeviceCMYK:
transformer = x =>
{
var r = new byte[4];
for (var i = 0; i < 4; i++)
{
r[i] = indexed.ColorTable[x * 4 + i];
}
return r;
};
multiplier = 4;
break;
case ColorSpace.DeviceGray:
transformer = x => new[] { indexed.ColorTable[x] };
multiplier = 1;
break;
}
if (transformer != null)
{
var result = new byte[input.Count * multiplier];
var i = 0;
foreach (var b in input)
{
foreach (var newByte in transformer(b))
{
result[i++] = newByte;
}
}
return result;
}
return input.ToArray();
}
}
}

View File

@@ -9,24 +9,15 @@
{
bytes = null;
var hasValidDetails = image.ColorSpaceDetails != null &&
!(image.ColorSpaceDetails is UnsupportedColorSpaceDetails);
var actualColorSpace = hasValidDetails ? image.ColorSpaceDetails.BaseType : image.ColorSpace;
var isColorSpaceSupported =
actualColorSpace == ColorSpace.DeviceGray || actualColorSpace == ColorSpace.DeviceRGB
|| actualColorSpace == ColorSpace.DeviceCMYK;
var isColorSpaceSupported = image.ColorSpace == ColorSpace.DeviceGray || image.ColorSpace == ColorSpace.DeviceRGB;
if (!isColorSpaceSupported || !image.TryGetBytes(out var bytesPure))
{
return false;
}
bytesPure = ColorSpaceDetailsByteConverter.Convert(image.ColorSpaceDetails, bytesPure);
try
{
var is3Byte = actualColorSpace == ColorSpace.DeviceRGB || actualColorSpace == ColorSpace.DeviceCMYK;
var is3Byte = image.ColorSpace == ColorSpace.DeviceRGB;
var multiplier = is3Byte ? 3 : 1;
var builder = PngBuilder.Create(image.WidthInSamples, image.HeightInSamples, false);
@@ -39,37 +30,18 @@
}
var i = 0;
for (var col = 0; col < image.HeightInSamples; col++)
for (var y = 0; y < image.HeightInSamples; y++)
{
for (var row = 0; row < image.WidthInSamples; row++)
for (var x = 0; x < image.WidthInSamples; x++)
{
if (actualColorSpace == ColorSpace.DeviceCMYK)
if (is3Byte)
{
/*
* Where CMYK in 0..1
* R = 255 × (1-C) × (1-K)
* G = 255 × (1-M) × (1-K)
* B = 255 × (1-Y) × (1-K)
*/
var c = (bytesPure[i++]/255d);
var m = (bytesPure[i++]/255d);
var y = (bytesPure[i++]/255d);
var k = (bytesPure[i++]/255d);
var r = (byte)(255 * (1 - c) * (1 - k));
var g = (byte)(255 * (1 - m) * (1 - k));
var b = (byte)(255 * (1 - y) * (1 - k));
builder.SetPixel(r, g, b, row, col);
}
else if (is3Byte)
{
builder.SetPixel(bytesPure[i++], bytesPure[i++], bytesPure[i++], row, col);
builder.SetPixel(bytesPure[i++], bytesPure[i++], bytesPure[i++], x, y);
}
else
{
var pixel = bytesPure[i++];
builder.SetPixel(pixel, pixel, pixel, row, col);
builder.SetPixel(pixel, pixel, pixel, x, y);
}
}
}

View File

@@ -38,11 +38,6 @@
private static string GetEntryOrDefault(DictionaryToken infoDictionary, NameToken key)
{
if (infoDictionary == null)
{
return null;
}
if (!infoDictionary.TryGet(key, out var value))
{
return null;

View File

@@ -23,8 +23,6 @@
{
return true;
}
var builderOffsets = new Dictionary<IndirectReference, long>();
var bruteForceOffsets = BruteForceSearcher.GetObjectLocations(bytes);
if (bruteForceOffsets.Count > 0)
@@ -79,11 +77,10 @@
foreach (var item in bruteForceOffsets)
{
builderOffsets[item.Key] = item.Value;
//xrefOffset[item.Key] = item.Value;
}
}
actualOffsets = builderOffsets;
}
}
return false;
@@ -134,12 +131,6 @@
try
{
if (offset >= bytes.Length)
{
bytes.Seek(originOffset);
return false;
}
bytes.Seek(offset);
if (ReadHelper.IsWhitespace(bytes.CurrentByte))

View File

@@ -4,6 +4,7 @@
using System.Collections.Generic;
using Core;
using Logging;
using Parts;
using Tokenization.Scanner;
using Tokens;
@@ -29,11 +30,6 @@
return startXRefOffset;
}
if (startXRefOffset >= inputBytes.Length)
{
return CalculateXRefFixedOffset(startXRefOffset, scanner, inputBytes);
}
scanner.Seek(startXRefOffset);
scanner.MoveNext();

View File

@@ -8,7 +8,6 @@
using Filters;
using Geometry;
using Graphics;
using Graphics.Operations;
using Logging;
using Parts;
using Tokenization.Scanner;
@@ -88,18 +87,10 @@
UserSpaceUnit userSpaceUnit = GetUserSpaceUnits(dictionary);
PageContent content;
PageContent content = default(PageContent);
if (!dictionary.TryGet(NameToken.Contents, out var contents))
{
content = new PageContent(EmptyArray<IGraphicsStateOperation>.Instance,
EmptyArray<Letter>.Instance,
EmptyArray<PdfPath>.Instance,
EmptyArray<Union<XObjectContentRecord, InlineImage>>.Instance,
EmptyArray<MarkedContentElement>.Instance,
pdfScanner,
filterProvider,
resourceStore);
// ignored for now, is it possible? check the spec...
}
else if (DirectObjectFinder.TryGet<ArrayToken>(contents, pdfScanner, out var array))

View File

@@ -46,7 +46,7 @@
do
{
if (loopProtection > 10_000_000)
if (loopProtection > 1_000_000)
{
throw new PdfDocumentFormatException("Failed to brute-force search the file due to an infinite loop.");
}

View File

@@ -46,7 +46,7 @@
public static T Get<T>(IndirectReference reference, IPdfTokenScanner scanner) where T : class, IToken
{
var temp = scanner.Get(reference);
if (temp is null || temp.Data is NullToken)
if (temp is null)
{
return null;
}

View File

@@ -16,7 +16,6 @@
private readonly ICidFontProgram fontProgram;
private readonly VerticalWritingMetrics verticalWritingMetrics;
private readonly double? defaultWidth;
private readonly double scale;
public NameToken Type { get; }
@@ -47,13 +46,11 @@
this.fontProgram = fontProgram;
this.verticalWritingMetrics = verticalWritingMetrics;
this.defaultWidth = defaultWidth;
scale = 1 / (double)(fontProgram?.GetFontMatrixMultiplier() ?? 1000);
Type = type;
SubType = subType;
BaseFont = baseFont;
SystemInfo = systemInfo;
var scale = 1 / (double)(fontProgram?.GetFontMatrixMultiplier() ?? 1000);
FontMatrix = TransformationMatrix.FromValues(scale, 0, 0, scale, 0, 0);
Descriptor = descriptor;
Widths = widths;
@@ -99,9 +96,9 @@
if (fontProgram == null)
{
return Descriptor?.BoundingBox ?? new PdfRectangle(0, 0, 1000, 1.0 / scale);
return Descriptor?.BoundingBox ?? new PdfRectangle(0, 0, 1000, 0);
}
if (fontProgram.TryGetBoundingBox(characterIdentifier, out var boundingBox))
{
return boundingBox;
@@ -109,15 +106,15 @@
if (Widths.TryGetValue(characterIdentifier, out var width))
{
return new PdfRectangle(0, 0, width, 1.0 / scale);
return new PdfRectangle(0, 0, width, 0);
}
if (defaultWidth.HasValue)
{
return new PdfRectangle(0, 0, defaultWidth.Value, 1.0 / scale);
return new PdfRectangle(0, 0, defaultWidth.Value, 0);
}
return new PdfRectangle(0, 0, 1000, 1.0 / scale);
return new PdfRectangle(0, 0, 1000, 0);
}
public PdfVector GetPositionVector(int characterIdentifier)

View File

@@ -12,26 +12,20 @@
private static readonly CMapParser CMapParser = new CMapParser();
public static bool TryGet(string name, out CMap result)
public static CMap Get(string name)
{
result = null;
lock (Lock)
{
if (Cache.TryGetValue(name, out result))
if (Cache.TryGetValue(name, out var result))
{
return true;
return result;
}
if (CMapParser.TryParseExternal(name, out result))
{
result = CMapParser.ParseExternal(name);
Cache[name] = result;
Cache[name] = result;
return true;
}
return false;
return result;
}
}

View File

@@ -35,8 +35,10 @@
{
case "usecmap":
{
if (previousToken is NameToken name && TryParseExternal(name.Data, out var external))
if (previousToken is NameToken name)
{
var external = ParseExternal(name.Data);
builder.UseCMap(external);
}
else
@@ -118,10 +120,8 @@
return builder.Build();
}
public bool TryParseExternal(string name, out CMap result)
public CMap ParseExternal(string name)
{
result = null;
var resources = typeof(CMapParser).Assembly.GetManifestResourceNames();
var resource = resources.FirstOrDefault(x =>
@@ -129,28 +129,19 @@
if (resource == null)
{
return false;
throw new InvalidOperationException("Could not find the referenced CMap: " + name);
}
byte[] bytes;
using (var stream = typeof(CMapParser).Assembly.GetManifestResourceStream(resource))
using (var memoryStream = new MemoryStream())
{
if (stream == null)
{
return false;
}
stream.CopyTo(memoryStream);
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
bytes = memoryStream.ToArray();
}
bytes = memoryStream.ToArray();
}
result = Parse(new ByteArrayInputBytes(bytes));
return true;
return Parse(new ByteArrayInputBytes(bytes));
}
}
}

View File

@@ -7,6 +7,7 @@
using PdfPig.Parser.Parts;
using Tokenization.Scanner;
using Tokens;
using Util;
internal class EncodingReader : IEncodingReader
{
@@ -94,17 +95,20 @@
return differences;
}
var currentIndex = -1;
foreach (var differenceEntry in differenceArray.Data)
var activeCode = differenceArray.GetNumeric(0).Int;
for (int i = 1; i < differenceArray.Data.Count; i++)
{
if (differenceEntry is NumericToken number)
var entry = differenceArray.Data[i];
if (entry is NumericToken numeric)
{
currentIndex = number.Int;
activeCode = numeric.Int;
}
else if (differenceEntry is NameToken name)
else if (entry is NameToken name)
{
differences.Add((currentIndex, name.Data));
currentIndex++;
differences.Add((activeCode, name.Data));
activeCode++;
}
else
{

View File

@@ -47,7 +47,7 @@
}
else
{
descendantFontDictionary = (DictionaryToken)descendantObject;
descendantFontDictionary = (DictionaryToken) descendantObject;
}
cidFont = ParseDescendant(descendantFontDictionary);
@@ -73,9 +73,9 @@
toUnicodeCMap = CMapCache.Parse(new ByteArrayInputBytes(decodedUnicodeCMap));
}
}
else if (DirectObjectFinder.TryGet<NameToken>(toUnicodeValue, scanner, out var toUnicodeName)
&& CMapCache.TryGet(toUnicodeName.Data, out toUnicodeCMap))
else if (DirectObjectFinder.TryGet<NameToken>(toUnicodeValue, scanner, out var toUnicodeName))
{
toUnicodeCMap = CMapCache.Get(toUnicodeName.Data);
}
else
{
@@ -107,7 +107,7 @@
{
if (array.Data[0] is IndirectReferenceToken objArr)
{
descendant = objArr;
descendant = objArr;
}
else if (array.Data[0] is DictionaryToken dict)
{
@@ -140,31 +140,30 @@
private CMap ReadEncoding(DictionaryToken dictionary, out bool isCMapPredefined)
{
isCMapPredefined = false;
CMap result;
CMap result = default(CMap);
if (dictionary.TryGet(NameToken.Encoding, scanner, out NameToken encodingName))
if (dictionary.TryGet(NameToken.Encoding, out var value))
{
if (!CMapCache.TryGet(encodingName.Data, out var cmap))
if (value is NameToken encodingName)
{
throw new InvalidOperationException($"Missing CMap named {encodingName.Data}.");
var cmap = CMapCache.Get(encodingName.Data);
result = cmap ?? throw new InvalidOperationException("Missing CMap for " + encodingName.Data);
isCMapPredefined = true;
}
result = cmap ?? throw new InvalidOperationException($"Missing CMap named {encodingName.Data}.");
else if (value is StreamToken stream)
{
var decoded = stream.Decode(filterProvider);
isCMapPredefined = true;
}
else if (dictionary.TryGet(NameToken.Encoding, scanner, out StreamToken stream))
{
var decoded = stream.Decode(filterProvider);
var cmap = CMapCache.Parse(new ByteArrayInputBytes(decoded));
var cmap = CMapCache.Parse(new ByteArrayInputBytes(decoded));
result = cmap ?? throw new InvalidOperationException($"Could not read CMap from stream in the dictionary: {dictionary}");
}
else
{
throw new InvalidOperationException(
$"Could not read the encoding, expected a name or a stream but it was not found in the dictionary: {dictionary}");
result = cmap ?? throw new InvalidOperationException("Could not read CMap for " + dictionary);
}
else
{
throw new InvalidOperationException("Could not read the encoding, expected a name or a stream but got a: " + value.GetType().Name);
}
}
return result;
@@ -213,28 +212,16 @@
}
var fullCmapName = cidFont.SystemInfo.ToString();
var nonUnicodeCMap = CMapCache.Get(fullCmapName);
string registry;
string ordering;
if (CMapCache.TryGet(fullCmapName, out var nonUnicodeCMap))
if (nonUnicodeCMap == null)
{
registry = nonUnicodeCMap.Info.Registry;
ordering = nonUnicodeCMap.Info.Ordering;
}
else
{
registry = cidFont.SystemInfo.Registry;
ordering = cidFont.SystemInfo.Ordering;
}
var unicodeCMapName = $"{registry}-{ordering}-UCS2";
if (!CMapCache.TryGet(unicodeCMapName, out var unicodeCMap))
{
throw new InvalidFontFormatException($"Could not locate CMap by name: {nonUnicodeCMap}.");
return (null, true);
}
return (unicodeCMap, true);
var unicodeCMapName = $"{nonUnicodeCMap.Info.Registry}-{nonUnicodeCMap.Info.Ordering}-UCS2";
return (CMapCache.Get(unicodeCMapName), true);
}
}
}

View File

@@ -23,18 +23,7 @@
for (var i = 0; i < numeric.Int; i++)
{
if (!tokenScanner.MoveNext())
{
throw new InvalidOperationException("Codespace range have reach an unexpected end");
}
if (tokenScanner.CurrentToken is OperatorToken operatorToken && operatorToken.Data == "endcodespacerange")
{
// Don't add this code space range
break;
}
if (!(tokenScanner.CurrentToken is HexToken start))
if (!tokenScanner.MoveNext() || !(tokenScanner.CurrentToken is HexToken start))
{
throw new InvalidOperationException("Codespace range contains an unexpected token: " + tokenScanner.CurrentToken);
}

View File

@@ -1,19 +0,0 @@
using UglyToad.PdfPig.Content;
namespace UglyToad.PdfPig.Rendering
{
/// <summary>
/// Render page as an image.
/// </summary>
public interface IPageImageRenderer
{
/// <summary>
/// Render page as an image.
/// </summary>
/// <param name="page">The pdf page.</param>
/// <param name="scale">The scale to apply to the page (i.e. zoom level).</param>
/// <param name="imageFormat">The output image format, if supported.</param>
/// <returns>The image as a memory stream.</returns>
byte[] Render(Page page, double scale, PdfRendererImageFormat imageFormat);
}
}

View File

@@ -1,33 +0,0 @@
namespace UglyToad.PdfPig.Rendering
{
/// <summary>
/// The output image format of the <see cref="IPageImageRenderer"/>.
/// </summary>
public enum PdfRendererImageFormat : byte
{
/// <summary>
/// Bitmap image format.
/// </summary>
Bmp,
/// <summary>
/// Jpeg/Jpg image format.
/// </summary>
Jpeg,
/// <summary>
/// Png image format.
/// </summary>
Png,
/// <summary>
/// Tiff image format.
/// </summary>
Tiff,
/// <summary>
/// Gif image format.
/// </summary>
Gif
}
}

Some files were not shown because too many files have changed in this diff Show More