mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
24 Commits
object-cop
...
v0.1.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
237fd96f9e | ||
|
|
6e69160b4e | ||
|
|
2b60474e11 | ||
|
|
cbd6eebecc | ||
|
|
7fdc8e2e86 | ||
|
|
52eb935182 | ||
|
|
54f7f7a84a | ||
|
|
69e23be450 | ||
|
|
3c510c62f2 | ||
|
|
68e487d5ff | ||
|
|
95fa002b4a | ||
|
|
4d86dc399e | ||
|
|
910e22997c | ||
|
|
eb85f67b18 | ||
|
|
14465f0198 | ||
|
|
d07baa97d5 | ||
|
|
ccd7d81ae3 | ||
|
|
fa5e37dc8c | ||
|
|
09a85d0497 | ||
|
|
d8e0263ec7 | ||
|
|
ad0fb4ec5b | ||
|
|
d959a30bf0 | ||
|
|
61d3e94a95 | ||
|
|
cd9ac6ac6c |
@@ -160,9 +160,9 @@
|
||||
throw new ArgumentNullException("LineTo(): currentPosition is null.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds 4 <see cref="Line"/>s forming a rectangle to the path.
|
||||
/// Add a rectangle following the pdf specification (m, l, l, l, c) path. A new subpath is created.
|
||||
/// </summary>
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
@@ -174,8 +174,6 @@
|
||||
CloseSubpath(); // h
|
||||
IsDrawnAsRectangle = true;
|
||||
}
|
||||
|
||||
internal void QuadraticCurveTo(double x1, double y1, double x2, double y2) { }
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="BezierCurve"/> to the path.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -76,6 +76,12 @@
|
||||
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-BoldItalicMT", "Helvetica-BoldOblique");
|
||||
}
|
||||
|
||||
private static void AddAdobeFontMetrics(string fontName, Standard14Font? type = null)
|
||||
@@ -143,7 +149,7 @@
|
||||
{
|
||||
return Standard14Cache[BuilderTypesToNames[fontType]];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a font with this name is a standard 14 font.
|
||||
/// </summary>
|
||||
@@ -159,7 +165,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>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
22
src/UglyToad.PdfPig.Tests/Fonts/PointSizeTests.cs
Normal file
22
src/UglyToad.PdfPig.Tests/Fonts/PointSizeTests.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
var names = Standard14.GetNames().Count;
|
||||
|
||||
Assert.Equal(34, names);
|
||||
Assert.Equal(38, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
using PdfPig.Tokens;
|
||||
using PdfPig.Core;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
|
||||
|
||||
internal class TestOperationContext : IOperationContext
|
||||
{
|
||||
@@ -88,6 +90,66 @@
|
||||
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
}
|
||||
@@ -124,6 +186,91 @@
|
||||
{
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -74,6 +74,7 @@
|
||||
"UglyToad.PdfPig.Content.IPdfImage",
|
||||
"UglyToad.PdfPig.Content.Letter",
|
||||
"UglyToad.PdfPig.Content.MarkedContentElement",
|
||||
"UglyToad.PdfPig.Content.MediaBox",
|
||||
"UglyToad.PdfPig.Content.Page",
|
||||
"UglyToad.PdfPig.Content.PageRotationDegrees",
|
||||
"UglyToad.PdfPig.Content.PageSize",
|
||||
@@ -197,6 +198,8 @@
|
||||
"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",
|
||||
|
||||
@@ -588,6 +588,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes, string extension = "pdf")
|
||||
{
|
||||
try
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -338,6 +338,7 @@
|
||||
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");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -68,7 +68,6 @@
|
||||
|
||||
/// <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; }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/// <remarks>
|
||||
/// See table 3.27 from the PDF specification version 1.7.
|
||||
/// </remarks>
|
||||
internal class MediaBox
|
||||
public class MediaBox
|
||||
{
|
||||
///<summary>
|
||||
/// User space units per inch.
|
||||
@@ -66,8 +66,14 @@
|
||||
/// </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));
|
||||
|
||||
@@ -35,7 +35,10 @@
|
||||
/// </summary>
|
||||
public CropBox CropBox { get; }
|
||||
|
||||
internal MediaBox MediaBox { get; }
|
||||
/// <summary>
|
||||
/// Defines the boundaries of the physical medium on which the page shall be displayed or printed.
|
||||
/// </summary>
|
||||
public MediaBox MediaBox { get; }
|
||||
|
||||
internal PageContent Content { get; }
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
namespace UglyToad.PdfPig.Graphics
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Colors;
|
||||
using Content;
|
||||
using Core;
|
||||
@@ -14,8 +10,13 @@
|
||||
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;
|
||||
|
||||
@@ -241,20 +242,7 @@
|
||||
var renderingMatrix =
|
||||
TransformationMatrix.FromValues(fontSize * horizontalScaling, 0, 0, fontSize, 0, rise);
|
||||
|
||||
// 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;
|
||||
}
|
||||
var pointSize = Math.Round(transformationMatrix.Multiply(TextMatrices.TextMatrix).Transform(new PdfRectangle(0, 0, 1, fontSize)).Height, 2);
|
||||
|
||||
while (bytes.MoveNext())
|
||||
{
|
||||
@@ -292,7 +280,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));
|
||||
@@ -573,7 +561,67 @@
|
||||
|
||||
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)
|
||||
@@ -757,7 +805,10 @@
|
||||
if (markedContentStack.CanPop)
|
||||
{
|
||||
var mc = markedContentStack.Pop(pdfScanner);
|
||||
if (mc != null) markedContents.Add(mc);
|
||||
if (mc != null)
|
||||
{
|
||||
markedContents.Add(mc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -765,9 +816,86 @@
|
||||
{
|
||||
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
||||
|
||||
var newMatrix = matrix.Multiply(TextMatrices.TextMatrix);
|
||||
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
|
||||
}
|
||||
|
||||
TextMatrices.TextMatrix = newMatrix;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,25 +3,13 @@
|
||||
using PdfPig.Core;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
using Util.JetBrains.Annotations;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
|
||||
/// <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>
|
||||
@@ -32,22 +20,11 @@
|
||||
/// </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>
|
||||
@@ -59,7 +36,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();
|
||||
|
||||
@@ -85,7 +62,7 @@
|
||||
/// Start a new sub-path.
|
||||
/// </summary>
|
||||
void BeginSubpath();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Close the current subpath.
|
||||
/// </summary>
|
||||
@@ -116,6 +93,36 @@
|
||||
/// <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").
|
||||
@@ -162,5 +169,90 @@
|
||||
/// 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 device’s 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);
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.GetCurrentState().Flatness = Tolerance;
|
||||
operationContext.SetFlatnessTolerance(Tolerance);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.GetCurrentState().CapStyle = Cap;
|
||||
operationContext.SetLineCap(Cap);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.GetCurrentState().LineDashPattern = Pattern;
|
||||
operationContext.SetLineDashPattern(Pattern);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.GetCurrentState().JoinStyle = Join;
|
||||
operationContext.SetLineJoin(Join);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.LineWidth = Width;
|
||||
operationContext.SetLineWidth(Width);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.MiterLimit = Limit;
|
||||
operationContext.SetMiterLimit(Limit);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
/// <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);
|
||||
|
||||
@@ -71,14 +71,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
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;
|
||||
operationContext.BezierCurveTo((double)X1, (double)Y1, (double)X2, (double)Y2, (double)X3, (double)Y3);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -56,12 +56,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
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;
|
||||
operationContext.BezierCurveTo((double)X1, (double)Y1, (double)X3, (double)Y3, (double)X3, (double)Y3);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -56,12 +56,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
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();
|
||||
operationContext.Rectangle((double)LowerLeftX, (double)LowerLeftY, (double)Width, (double)Height);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -56,17 +56,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
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;
|
||||
operationContext.BezierCurveTo((double)X2, (double)Y2, (double)X3, (double)Y3);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -41,11 +41,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
if (operationContext.CurrentSubpath == null) return;
|
||||
|
||||
var endPoint = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X, Y));
|
||||
operationContext.CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
|
||||
operationContext.CurrentPosition = endPoint;
|
||||
operationContext.LineTo((double)X, (double)Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -41,10 +41,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.BeginSubpath();
|
||||
var point = operationContext.CurrentTransformationMatrix.Transform(new PdfPoint(X, Y));
|
||||
operationContext.CurrentPosition = point;
|
||||
operationContext.CurrentSubpath.MoveTo(point.X, point.Y);
|
||||
operationContext.MoveTo((double)X, (double)Y);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -44,13 +44,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var newMatrix = TransformationMatrix.FromArray(Value);
|
||||
|
||||
var ctm = operationContext.GetCurrentState().CurrentTransformationMatrix;
|
||||
|
||||
var newCtm = newMatrix.Multiply(ctm);
|
||||
|
||||
operationContext.GetCurrentState().CurrentTransformationMatrix = newCtm;
|
||||
operationContext.ModifyCurrentTransformationMatrix(Array.ConvertAll(Value, x => (double)x));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -32,9 +32,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)operationContext.GetCurrentState().FontState.Leading);
|
||||
|
||||
tdOperation.Run(operationContext);
|
||||
operationContext.MoveToNextLineWithOffset();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.CharacterSpacing = (double)Spacing;
|
||||
operationContext.SetCharacterSpacing((double)Spacing);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -48,10 +48,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.FontSize = (double)Size;
|
||||
currentState.FontState.FontName = Font;
|
||||
operationContext.SetFontAndSize(Font, (double)Size);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -30,9 +30,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.HorizontalScaling = (double)Scale;
|
||||
operationContext.SetHorizontalScaling((double)Scale);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.Leading = (double)Leading;
|
||||
operationContext.SetTextLeading((double)Leading);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.TextRenderingMode = Mode;
|
||||
operationContext.SetTextRenderingMode(Mode);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,9 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.Rise = (double)Rise;
|
||||
operationContext.SetTextRise((double)Rise);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -34,9 +34,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.WordSpacing = (double)Spacing;
|
||||
operationContext.SetWordSpacing((double)Spacing);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
|
||||
do
|
||||
{
|
||||
if (loopProtection > 1_000_000)
|
||||
if (loopProtection > 10_000_000)
|
||||
{
|
||||
throw new PdfDocumentFormatException("Failed to brute-force search the file due to an infinite loop.");
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
using PdfPig.Parser.Parts;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using Util;
|
||||
|
||||
internal class EncodingReader : IEncodingReader
|
||||
{
|
||||
@@ -95,20 +94,17 @@
|
||||
return differences;
|
||||
}
|
||||
|
||||
var activeCode = differenceArray.GetNumeric(0).Int;
|
||||
|
||||
for (int i = 1; i < differenceArray.Data.Count; i++)
|
||||
var currentIndex = -1;
|
||||
foreach (var differenceEntry in differenceArray.Data)
|
||||
{
|
||||
var entry = differenceArray.Data[i];
|
||||
|
||||
if (entry is NumericToken numeric)
|
||||
if (differenceEntry is NumericToken number)
|
||||
{
|
||||
activeCode = numeric.Int;
|
||||
currentIndex = number.Int;
|
||||
}
|
||||
else if (entry is NameToken name)
|
||||
else if (differenceEntry is NameToken name)
|
||||
{
|
||||
differences.Add((activeCode, name.Data));
|
||||
activeCode++;
|
||||
differences.Add((currentIndex, name.Data));
|
||||
currentIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
descendantFontDictionary = (DictionaryToken) descendantObject;
|
||||
descendantFontDictionary = (DictionaryToken)descendantObject;
|
||||
}
|
||||
|
||||
cidFont = ParseDescendant(descendantFontDictionary);
|
||||
@@ -107,7 +107,7 @@
|
||||
{
|
||||
if (array.Data[0] is IndirectReferenceToken objArr)
|
||||
{
|
||||
descendant = objArr;
|
||||
descendant = objArr;
|
||||
}
|
||||
else if (array.Data[0] is DictionaryToken dict)
|
||||
{
|
||||
@@ -140,30 +140,28 @@
|
||||
private CMap ReadEncoding(DictionaryToken dictionary, out bool isCMapPredefined)
|
||||
{
|
||||
isCMapPredefined = false;
|
||||
CMap result = default(CMap);
|
||||
CMap result;
|
||||
|
||||
if (dictionary.TryGet(NameToken.Encoding, out var value))
|
||||
if (dictionary.TryGet(NameToken.Encoding, scanner, out NameToken encodingName))
|
||||
{
|
||||
if (value is NameToken encodingName)
|
||||
{
|
||||
var cmap = CMapCache.Get(encodingName.Data);
|
||||
var cmap = CMapCache.Get(encodingName.Data);
|
||||
|
||||
result = cmap ?? throw new InvalidOperationException("Missing CMap for " + encodingName.Data);
|
||||
result = cmap ?? throw new InvalidOperationException($"Missing CMap named {encodingName.Data}.");
|
||||
|
||||
isCMapPredefined = true;
|
||||
}
|
||||
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 for " + dictionary);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Could not read the encoding, expected a name or a stream but got a: " + value.GetType().Name);
|
||||
}
|
||||
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}");
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
19
src/UglyToad.PdfPig/Rendering/IPageImageRenderer.cs
Normal file
19
src/UglyToad.PdfPig/Rendering/IPageImageRenderer.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
33
src/UglyToad.PdfPig/Rendering/PdfRendererImageFormat.cs
Normal file
33
src/UglyToad.PdfPig/Rendering/PdfRendererImageFormat.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -800,6 +800,13 @@
|
||||
|
||||
var token = scanner.CurrentToken;
|
||||
|
||||
if (token.Equals(OperatorToken.EndObject))
|
||||
{
|
||||
scanner.MoveNext();
|
||||
|
||||
token = scanner.CurrentToken;
|
||||
}
|
||||
|
||||
results.Add(new ObjectToken(offset, new IndirectReference(obj.Item1, 0), token));
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Writer.Copier
|
||||
{
|
||||
using System;
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// An interface for copying token
|
||||
/// </summary>
|
||||
internal interface IObjectCopier
|
||||
{
|
||||
/// <summary>
|
||||
/// Copy the token to the destination stream
|
||||
/// </summary>
|
||||
/// <param name="sourceToken">Token to copy</param>
|
||||
/// <param name="tokenScanner">Function to resolve indirect reference identified in the token to copy</param>
|
||||
/// <returns></returns>
|
||||
public IToken CopyObject(IToken sourceToken, Func<IndirectReferenceToken, IToken> tokenScanner);
|
||||
|
||||
/// <summary>
|
||||
/// Clear the references of the previously copied object
|
||||
/// </summary>
|
||||
public void ClearReference();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Writer.Copier
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tokens;
|
||||
using Writer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class MultiCopier : ObjectCopier
|
||||
{
|
||||
private readonly List<IObjectCopier> copiers;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public MultiCopier(PdfStreamWriter destinationStream) : base(destinationStream)
|
||||
{
|
||||
copiers = new List<IObjectCopier>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="copier"></param>
|
||||
public void AddCopier(IObjectCopier copier)
|
||||
{
|
||||
copiers.Add(copier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="copier"></param>
|
||||
/// <returns></returns>
|
||||
public bool RemoveCopier(IObjectCopier copier)
|
||||
{
|
||||
return copiers.Remove(copier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IReadOnlyList<IObjectCopier> GetCopiers()
|
||||
{
|
||||
return copiers;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IToken CopyObject(IToken sourceToken, Func<IndirectReferenceToken, IToken> tokenScanner)
|
||||
{
|
||||
// We give the token to the child copiers, to see if they have a better way of copying the token
|
||||
foreach (var copier in copiers)
|
||||
{
|
||||
var newToken = copier.CopyObject(sourceToken, tokenScanner);
|
||||
if (newToken != null)
|
||||
{
|
||||
return newToken;
|
||||
}
|
||||
}
|
||||
|
||||
// If the token did not found a suitable copier, let just do a simple copy of the token
|
||||
return base.CopyObject(sourceToken, tokenScanner);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void ClearReference()
|
||||
{
|
||||
foreach (var copier in copiers)
|
||||
{
|
||||
copier.ClearReference();
|
||||
}
|
||||
|
||||
base.ClearReference();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,167 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Writer.Copier
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using PdfPig;
|
||||
using Tokenization.Scanner;
|
||||
using Tokens;
|
||||
using Writer;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class ObjectCopier : IObjectCopier
|
||||
{
|
||||
private readonly PdfStreamWriter pdfStream;
|
||||
|
||||
private readonly Dictionary<IndirectReferenceToken, IndirectReferenceToken> newReferenceMap;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ObjectCopier(PdfStreamWriter destinationStream)
|
||||
{
|
||||
pdfStream = destinationStream ?? throw new ArgumentNullException(nameof(destinationStream));
|
||||
newReferenceMap = new Dictionary<IndirectReferenceToken, IndirectReferenceToken>();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IToken CopyObject(IToken sourceToken, PdfDocument sourceDocument)
|
||||
{
|
||||
IToken tokenScanner(IndirectReferenceToken referenceToken)
|
||||
{
|
||||
var objToken = sourceDocument.Structure.GetObject(referenceToken.Data);
|
||||
return objToken.Data;
|
||||
}
|
||||
|
||||
return CopyObject(sourceToken, tokenScanner);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IToken CopyObject(IToken sourceToken, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
IToken tokenGetter(IndirectReferenceToken referenceToken)
|
||||
{
|
||||
var objToken = tokenScanner.Get(referenceToken.Data);
|
||||
return objToken.Data;
|
||||
}
|
||||
|
||||
return CopyObject(sourceToken, tokenGetter);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IToken CopyObject(IToken sourceToken, Func<IndirectReferenceToken, IToken> tokenScanner)
|
||||
{
|
||||
// This token need to be deep copied, because they could contain reference. So we have to update them.
|
||||
switch (sourceToken)
|
||||
{
|
||||
case DictionaryToken dictionaryToken:
|
||||
{
|
||||
var newContent = new Dictionary<NameToken, IToken>();
|
||||
foreach (var setPair in dictionaryToken.Data)
|
||||
{
|
||||
var name = setPair.Key;
|
||||
var token = setPair.Value;
|
||||
|
||||
newContent.Add(NameToken.Create(name), CopyObject(token, tokenScanner));
|
||||
}
|
||||
|
||||
return new DictionaryToken(newContent);
|
||||
}
|
||||
case ArrayToken arrayToken:
|
||||
{
|
||||
var newArray = new List<IToken>(arrayToken.Length);
|
||||
foreach (var token in arrayToken.Data)
|
||||
{
|
||||
newArray.Add(CopyObject(token, tokenScanner));
|
||||
}
|
||||
|
||||
return new ArrayToken(newArray);
|
||||
}
|
||||
case IndirectReferenceToken referenceToken:
|
||||
{
|
||||
if (TryGetNewReference(referenceToken, out var newReferenceToken))
|
||||
{
|
||||
return newReferenceToken;
|
||||
}
|
||||
|
||||
var referencedToken = tokenScanner(referenceToken);
|
||||
var newReferencedToken = CopyObject(referencedToken, tokenScanner);
|
||||
|
||||
var newToken = WriteToken(newReferencedToken);
|
||||
SetNewReference(referenceToken, newToken);
|
||||
return newToken;
|
||||
}
|
||||
|
||||
case StreamToken streamToken:
|
||||
{
|
||||
var properties = CopyObject(streamToken.StreamDictionary, tokenScanner);
|
||||
var bytes = streamToken.Data;
|
||||
return new StreamToken(properties as DictionaryToken, bytes);
|
||||
}
|
||||
|
||||
case ObjectToken _:
|
||||
{
|
||||
throw new NotSupportedException("Copying a Object Token is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
return sourceToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="sourceReferenceToken"></param>
|
||||
/// <param name="newReferenceToken"></param>
|
||||
/// <returns></returns>
|
||||
public virtual bool TryGetNewReference(IndirectReferenceToken sourceReferenceToken, out IndirectReferenceToken newReferenceToken)
|
||||
{
|
||||
newReferenceToken = default;
|
||||
foreach (var referenceSet in newReferenceMap)
|
||||
{
|
||||
if (!referenceSet.Key.Equals(sourceReferenceToken))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newReferenceToken = referenceSet.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual void ClearReference()
|
||||
{
|
||||
newReferenceMap.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="oldToken"></param>
|
||||
/// <param name="newToken"></param>
|
||||
public void SetNewReference(IndirectReferenceToken oldToken, IndirectReferenceToken newToken)
|
||||
{
|
||||
newReferenceMap.Add(oldToken, newToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int ReserveTokenNumber()
|
||||
{
|
||||
return pdfStream.ReserveNumber();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="reservedNumber"></param>
|
||||
/// <returns></returns>
|
||||
public IndirectReferenceToken WriteToken(IToken token, int? reservedNumber = null)
|
||||
{
|
||||
return pdfStream.WriteToken(token, reservedNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Writer.Copier.Page
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Core;
|
||||
using Tokens;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class PagesCopier : IObjectCopier
|
||||
{
|
||||
private readonly ObjectCopier copier;
|
||||
|
||||
private readonly IndirectReferenceToken rootPagesReferenceToken;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public PagesCopier(ObjectCopier mainCopier, IndirectReferenceToken rootPagesToken = null)
|
||||
{
|
||||
copier = mainCopier;
|
||||
rootPagesReferenceToken = rootPagesToken;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public IToken CopyObject(IToken sourceToken, Func<IndirectReferenceToken, IToken> tokenScanner)
|
||||
{
|
||||
if (!(sourceToken is IndirectReferenceToken sourceReferenceToken))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if this token haven't been copied before
|
||||
if (copier.TryGetNewReference(sourceReferenceToken, out var newReferenceToken))
|
||||
{
|
||||
return newReferenceToken;
|
||||
}
|
||||
|
||||
// Make sure that we are copying a DictionaryToken
|
||||
var token = tokenScanner(sourceReferenceToken);
|
||||
if (!(token is DictionaryToken dictionaryToken))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Make sure we are copying a `/Pages` Dictionary
|
||||
if (!dictionaryToken.TryGet(NameToken.Type, out var nameTypeToken) || !nameTypeToken.Equals(NameToken.Pages))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// We have to reserve the reference before hand, because if we don't, we would fall in a loop.
|
||||
// The child `/Page` have a reference to the parent
|
||||
var tokenNumber = copier.ReserveTokenNumber();
|
||||
copier.SetNewReference(sourceReferenceToken, new IndirectReferenceToken(new IndirectReference(tokenNumber, 0)));
|
||||
|
||||
// If `/Pages` is not the root page node, copy the token normally
|
||||
// We are testing for one:
|
||||
// * If @rootPagesReferenceToken is null, just do a normal copy of the tree
|
||||
// * If the tree have a Parent NameToken, it means the tree is not a root tree so we don't have to assign him
|
||||
// a new parent
|
||||
if (rootPagesReferenceToken == null || dictionaryToken.TryGet(NameToken.Parent, out IndirectReferenceToken _))
|
||||
{
|
||||
return copier.WriteToken(copier.CopyObject(dictionaryToken, tokenScanner), tokenNumber);
|
||||
}
|
||||
|
||||
// Since the tree is a root tree, it means that the tree comes from another document, we have to make sure
|
||||
// that the new tree is a child of the new root tree, this we do by adding a Parent NameToken to the tree,
|
||||
// that point to @rootPagesReferenceToken
|
||||
return CopyPagesTree(dictionaryToken, tokenNumber, tokenScanner);
|
||||
}
|
||||
|
||||
private IndirectReferenceToken CopyPagesTree(DictionaryToken pagesDictionary, int reservedNumber, Func<IndirectReferenceToken, IToken> tokenScanner)
|
||||
{
|
||||
Debug.Assert(rootPagesReferenceToken != null);
|
||||
|
||||
var newContent = new Dictionary<NameToken, IToken>()
|
||||
{
|
||||
{NameToken.Parent, rootPagesReferenceToken}
|
||||
};
|
||||
|
||||
foreach (var dataSet in pagesDictionary.Data)
|
||||
{
|
||||
newContent.Add(NameToken.Create(dataSet.Key), copier.CopyObject(dataSet.Value, tokenScanner));
|
||||
}
|
||||
|
||||
var newPagesTree = new DictionaryToken(newContent);
|
||||
|
||||
return copier.WriteToken(newPagesTree, reservedNumber);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void ClearReference()
|
||||
{
|
||||
// Nothing to do
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Writer.Copier
|
||||
{
|
||||
using System;
|
||||
using Tokens;
|
||||
|
||||
internal static class TokenHelper
|
||||
{
|
||||
// This is to avoid infinite loop in production. Although, it should never happen
|
||||
const int MAX_ITERATIONS = 10;
|
||||
|
||||
public static T GetTokenAs<T>(IToken token, Func<IndirectReferenceToken, IToken> lookupFunc) where T : IToken
|
||||
{
|
||||
var iterations = 0;
|
||||
|
||||
var original = token;
|
||||
while (iterations++ < MAX_ITERATIONS)
|
||||
{
|
||||
switch (token)
|
||||
{
|
||||
case T result:
|
||||
return result;
|
||||
case IndirectReferenceToken tokenReference:
|
||||
token = lookupFunc(tokenReference);
|
||||
continue;
|
||||
case ObjectToken tokenObject:
|
||||
token = tokenObject.Data;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unable to extract a {typeof(T)} token from {original}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using Content;
|
||||
using Copier;
|
||||
using Copier.Page;
|
||||
using Core;
|
||||
using CrossReference;
|
||||
using Encryption;
|
||||
@@ -150,36 +149,31 @@
|
||||
private class DocumentMerger
|
||||
{
|
||||
private const decimal DefaultVersion = 1.2m;
|
||||
|
||||
private readonly PdfStreamWriter context;
|
||||
private readonly List<IndirectReferenceToken> pagesTokenReferences;
|
||||
|
||||
private readonly PdfStreamWriter context = new PdfStreamWriter();
|
||||
private readonly List<IndirectReferenceToken> pagesTokenReferences = new List<IndirectReferenceToken>();
|
||||
private readonly IndirectReferenceToken rootPagesReference;
|
||||
private readonly MultiCopier copier;
|
||||
|
||||
private decimal currentVersion = DefaultVersion;
|
||||
private int pageCount = 0;
|
||||
|
||||
private readonly Dictionary<IndirectReferenceToken, IndirectReferenceToken> referencesFromDocument =
|
||||
new Dictionary<IndirectReferenceToken, IndirectReferenceToken>();
|
||||
|
||||
public DocumentMerger()
|
||||
{
|
||||
context = new PdfStreamWriter();
|
||||
pagesTokenReferences = new List<IndirectReferenceToken>();
|
||||
|
||||
rootPagesReference = context.ReserveNumberToken();
|
||||
|
||||
copier = new MultiCopier(context);
|
||||
copier.AddCopier(new PagesCopier(copier, rootPagesReference));
|
||||
}
|
||||
|
||||
|
||||
public void AppendDocument(Catalog documentCatalog, decimal version, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
currentVersion = Math.Max(version, currentVersion);
|
||||
|
||||
var copiedPages = copier.CopyObject(new IndirectReferenceToken(documentCatalog.PageTree.Reference), tokenScanner) as IndirectReferenceToken;
|
||||
pagesTokenReferences.Add(copiedPages);
|
||||
var (pagesReference, count) = CopyPagesTree(documentCatalog.PageTree, rootPagesReference, tokenScanner);
|
||||
pageCount += count;
|
||||
pagesTokenReferences.Add(pagesReference);
|
||||
|
||||
pageCount += documentCatalog.PagesDictionary.Get<NumericToken>(NameToken.Count, tokenScanner).Int;
|
||||
|
||||
copier.ClearReference();
|
||||
referencesFromDocument.Clear();
|
||||
}
|
||||
|
||||
public byte[] Build()
|
||||
@@ -196,7 +190,7 @@
|
||||
{ NameToken.Count, new NumericToken(pageCount) }
|
||||
});
|
||||
|
||||
var pagesRef = context.WriteToken(pagesDictionary, (int)rootPagesReference.Data.ObjectNumber);
|
||||
var pagesRef = context.WriteToken( pagesDictionary, (int)rootPagesReference.Data.ObjectNumber);
|
||||
|
||||
var catalog = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
@@ -205,9 +199,9 @@
|
||||
});
|
||||
|
||||
var catalogRef = context.WriteToken(catalog);
|
||||
|
||||
|
||||
context.Flush(currentVersion, catalogRef);
|
||||
|
||||
|
||||
var bytes = context.ToArray();
|
||||
|
||||
Close();
|
||||
@@ -215,10 +209,165 @@
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private void Close()
|
||||
public void Close()
|
||||
{
|
||||
context.Dispose();
|
||||
}
|
||||
|
||||
private (IndirectReferenceToken, int) CopyPagesTree(PageTreeNode treeNode, IndirectReferenceToken treeParentReference, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
Debug.Assert(!treeNode.IsPage);
|
||||
|
||||
var currentNodeReference = context.ReserveNumberToken();
|
||||
|
||||
var pageReferences = new List<IndirectReferenceToken>();
|
||||
var nodeCount = 0;
|
||||
foreach (var pageNode in treeNode.Children)
|
||||
{
|
||||
IndirectReferenceToken newEntry;
|
||||
if (!pageNode.IsPage)
|
||||
{
|
||||
var count = 0;
|
||||
(newEntry, count) = CopyPagesTree(pageNode, currentNodeReference, tokenScanner);
|
||||
nodeCount += count;
|
||||
}
|
||||
else
|
||||
{
|
||||
newEntry = CopyPageNode(pageNode, currentNodeReference, tokenScanner);
|
||||
++nodeCount;
|
||||
}
|
||||
|
||||
pageReferences.Add(newEntry);
|
||||
}
|
||||
|
||||
var newPagesNode = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.Type, NameToken.Pages },
|
||||
{ NameToken.Kids, new ArrayToken(pageReferences) },
|
||||
{ NameToken.Count, new NumericToken(nodeCount) },
|
||||
{ NameToken.Parent, treeParentReference }
|
||||
};
|
||||
|
||||
foreach (var pair in treeNode.NodeDictionary.Data)
|
||||
{
|
||||
if (IgnoreKeyForPagesNode(pair))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
newPagesNode[NameToken.Create(pair.Key)] = CopyToken(pair.Value, tokenScanner);
|
||||
}
|
||||
|
||||
var pagesDictionary = new DictionaryToken(newPagesNode);
|
||||
|
||||
return (context.WriteToken(pagesDictionary, (int)currentNodeReference.Data.ObjectNumber), nodeCount);
|
||||
}
|
||||
|
||||
private IndirectReferenceToken CopyPageNode(PageTreeNode pageNode, IndirectReferenceToken parentPagesObject, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
Debug.Assert(pageNode.IsPage);
|
||||
|
||||
var pageDictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{NameToken.Parent, parentPagesObject},
|
||||
};
|
||||
|
||||
foreach (var setPair in pageNode.NodeDictionary.Data)
|
||||
{
|
||||
var name = setPair.Key;
|
||||
var token = setPair.Value;
|
||||
|
||||
if (name == NameToken.Parent)
|
||||
{
|
||||
// Skip Parent token, since we have to reassign it
|
||||
continue;
|
||||
}
|
||||
|
||||
pageDictionary.Add(NameToken.Create(name), CopyToken(token, tokenScanner));
|
||||
}
|
||||
|
||||
return context.WriteToken(new DictionaryToken(pageDictionary));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The purpose of this method is to resolve indirect reference. That mean copy the reference's content to the new document's stream
|
||||
/// and replace the indirect reference with the correct/new one
|
||||
/// </summary>
|
||||
/// <param name="tokenToCopy">Token to inspect for reference</param>
|
||||
/// <param name="tokenScanner">scanner get the content from the original document</param>
|
||||
/// <returns>A reference of the token that was copied. With all the reference updated</returns>
|
||||
private IToken CopyToken(IToken tokenToCopy, IPdfTokenScanner tokenScanner)
|
||||
{
|
||||
// This token need to be deep copied, because they could contain reference. So we have to update them.
|
||||
switch (tokenToCopy)
|
||||
{
|
||||
case DictionaryToken dictionaryToken:
|
||||
{
|
||||
var newContent = new Dictionary<NameToken, IToken>();
|
||||
foreach (var setPair in dictionaryToken.Data)
|
||||
{
|
||||
var name = setPair.Key;
|
||||
var token = setPair.Value;
|
||||
newContent.Add(NameToken.Create(name), CopyToken(token, tokenScanner));
|
||||
}
|
||||
|
||||
return new DictionaryToken(newContent);
|
||||
}
|
||||
case ArrayToken arrayToken:
|
||||
{
|
||||
var newArray = new List<IToken>(arrayToken.Length);
|
||||
foreach (var token in arrayToken.Data)
|
||||
{
|
||||
newArray.Add(CopyToken(token, tokenScanner));
|
||||
}
|
||||
|
||||
return new ArrayToken(newArray);
|
||||
}
|
||||
case IndirectReferenceToken referenceToken:
|
||||
{
|
||||
if (referencesFromDocument.TryGetValue(referenceToken, out var newReferenceToken))
|
||||
{
|
||||
return newReferenceToken;
|
||||
}
|
||||
|
||||
var tokenObject = DirectObjectFinder.Get<IToken>(referenceToken.Data, tokenScanner);
|
||||
|
||||
Debug.Assert(!(tokenObject is IndirectReferenceToken));
|
||||
|
||||
var newToken = CopyToken(tokenObject, tokenScanner);
|
||||
newReferenceToken = context.WriteToken(newToken);
|
||||
|
||||
referencesFromDocument.Add(referenceToken, newReferenceToken);
|
||||
|
||||
return newReferenceToken;
|
||||
}
|
||||
case StreamToken streamToken:
|
||||
{
|
||||
var properties = CopyToken(streamToken.StreamDictionary, tokenScanner) as DictionaryToken;
|
||||
Debug.Assert(properties != null);
|
||||
|
||||
var bytes = streamToken.Data;
|
||||
return new StreamToken(properties, bytes);
|
||||
}
|
||||
|
||||
case ObjectToken _:
|
||||
{
|
||||
// Since we don't write token directly to the stream.
|
||||
// We can't know the offset. Therefore the token would be invalid
|
||||
throw new NotSupportedException("Copying a Object token is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
return tokenToCopy;
|
||||
}
|
||||
|
||||
private static bool IgnoreKeyForPagesNode(KeyValuePair<string, IToken> token)
|
||||
{
|
||||
return string.Equals(token.Key, NameToken.Type.Data, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(token.Key, NameToken.Kids.Data, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(token.Key, NameToken.Count.Data, StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(token.Key, NameToken.Parent.Data, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,8 @@
|
||||
/// <param name="width">The width of the rectangle.</param>
|
||||
/// <param name="height">The height of the rectangle.</param>
|
||||
/// <param name="lineWidth">The width of the line border of the rectangle.</param>
|
||||
public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1)
|
||||
/// <param name="fill">Whether to fill with the color set by <see cref="SetTextAndFillColor"/>.</param>
|
||||
public void DrawRectangle(PdfPoint position, decimal width, decimal height, decimal lineWidth = 1, bool fill = false)
|
||||
{
|
||||
if (lineWidth != 1)
|
||||
{
|
||||
@@ -99,7 +100,15 @@
|
||||
}
|
||||
|
||||
operations.Add(new AppendRectangle((decimal)position.X, (decimal)position.Y, width, height));
|
||||
operations.Add(StrokePath.Value);
|
||||
|
||||
if (fill)
|
||||
{
|
||||
operations.Add(FillPathEvenOddRuleAndStroke.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
operations.Add(StrokePath.Value);
|
||||
}
|
||||
|
||||
if (lineWidth != 1)
|
||||
{
|
||||
|
||||
@@ -9,8 +9,7 @@
|
||||
using Tokens;
|
||||
|
||||
/// <summary>
|
||||
/// This class would lazily flush all token.
|
||||
/// Allowing us to make changes to references without need to rewrite the whole stream
|
||||
/// This class would lazily flush all token. Allowing us to make changes to references without need to rewrite the whole stream
|
||||
/// </summary>
|
||||
internal class PdfStreamWriter : IDisposable
|
||||
{
|
||||
@@ -18,34 +17,20 @@
|
||||
|
||||
private readonly Dictionary<IndirectReferenceToken, IToken> tokenReferences = new Dictionary<IndirectReferenceToken, IToken>();
|
||||
|
||||
private int currentNumber = 1;
|
||||
public int CurrentNumber { get; private set; } = 1;
|
||||
|
||||
private Stream stream;
|
||||
public Stream Stream { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag to set whether or not we want to dispose the stream
|
||||
/// </summary>
|
||||
public bool DisposeStream { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a PdfStreamWriter with a memory stream
|
||||
/// </summary>
|
||||
public PdfStreamWriter() : this(new MemoryStream()) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a PdfStreamWriter
|
||||
/// </summary>
|
||||
public PdfStreamWriter(Stream baseStream, bool disposeStream = true)
|
||||
{
|
||||
stream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||
Stream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
|
||||
DisposeStream = disposeStream;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flush the document with all the token that we have accumulated
|
||||
/// </summary>
|
||||
/// <param name="version">Pdf Version that we are targeting</param>
|
||||
/// <param name="catalogReference">Catalog's indirect reference token to which the token are related</param>
|
||||
public void Flush(decimal version, IndirectReferenceToken catalogReference)
|
||||
{
|
||||
if (catalogReference == null)
|
||||
@@ -53,14 +38,14 @@
|
||||
throw new ArgumentNullException(nameof(catalogReference));
|
||||
}
|
||||
|
||||
WriteString($"%PDF-{version.ToString("0.0", CultureInfo.InvariantCulture)}", stream);
|
||||
WriteString($"%PDF-{version.ToString("0.0", CultureInfo.InvariantCulture)}", Stream);
|
||||
|
||||
stream.WriteText("%");
|
||||
stream.WriteByte(169);
|
||||
stream.WriteByte(205);
|
||||
stream.WriteByte(196);
|
||||
stream.WriteByte(210);
|
||||
stream.WriteNewLine();
|
||||
Stream.WriteText("%");
|
||||
Stream.WriteByte(169);
|
||||
Stream.WriteByte(205);
|
||||
Stream.WriteByte(196);
|
||||
Stream.WriteByte(210);
|
||||
Stream.WriteNewLine();
|
||||
|
||||
var offsets = new Dictionary<IndirectReference, long>();
|
||||
ObjectToken catalogToken = null;
|
||||
@@ -68,10 +53,10 @@
|
||||
{
|
||||
var referenceToken = pair.Key;
|
||||
var token = pair.Value;
|
||||
var offset = stream.Position;
|
||||
var offset = Stream.Position;
|
||||
var obj = new ObjectToken(offset, referenceToken.Data, token);
|
||||
|
||||
TokenWriter.WriteToken(obj, stream);
|
||||
TokenWriter.WriteToken(obj, Stream);
|
||||
|
||||
offsets.Add(referenceToken.Data, offset);
|
||||
|
||||
@@ -87,105 +72,75 @@
|
||||
}
|
||||
|
||||
// TODO: Support document information
|
||||
TokenWriter.WriteCrossReferenceTable(offsets, catalogToken, stream, null);
|
||||
TokenWriter.WriteCrossReferenceTable(offsets, catalogToken, Stream, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Push a new token to be written
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
/// <param name="reservedNumber"></param>
|
||||
/// <returns></returns>
|
||||
public IndirectReferenceToken WriteToken(IToken token, int? reservedNumber = null)
|
||||
{
|
||||
if (reservedNumber.HasValue)
|
||||
// if you can't consider deduplicating the token.
|
||||
// It must be because it's referenced by his child element, so you must have reserved a number before hand
|
||||
// Example /Pages Obj
|
||||
var canBeDuplicated = !reservedNumber.HasValue;
|
||||
if (!canBeDuplicated)
|
||||
{
|
||||
if (!reservedNumbers.Remove(reservedNumber.Value))
|
||||
{
|
||||
throw new InvalidOperationException("You can't reuse a reserved number");
|
||||
}
|
||||
|
||||
// When we end up writing this token, all of his child would already have been added and checked for duplicate
|
||||
return AddToken(token, reservedNumber.Value);
|
||||
}
|
||||
|
||||
return AddToken(token, currentNumber++);
|
||||
var reference = FindToken(token);
|
||||
if (reference == null)
|
||||
{
|
||||
return AddToken(token, CurrentNumber++);
|
||||
}
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a token based on his indirect reference
|
||||
/// </summary>
|
||||
/// <param name="referenceToken"></param>
|
||||
/// <returns></returns>
|
||||
public IToken GetToken(IndirectReferenceToken referenceToken)
|
||||
{
|
||||
return tokenReferences.TryGetValue(referenceToken, out var token) ? token : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replace a token base on his indirect reference
|
||||
/// </summary>
|
||||
/// <param name="referenceToken"></param>
|
||||
/// <param name="newToken"></param>
|
||||
public void ReplaceToken(IndirectReferenceToken referenceToken, IToken newToken)
|
||||
{
|
||||
tokenReferences[referenceToken] = newToken;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserve a number for a token
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public int ReserveNumber()
|
||||
{
|
||||
var reserved = currentNumber;
|
||||
var reserved = CurrentNumber;
|
||||
reservedNumbers.Add(reserved);
|
||||
currentNumber++;
|
||||
CurrentNumber++;
|
||||
return reserved;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserve a number and create a token with it
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public IndirectReferenceToken ReserveNumberToken()
|
||||
{
|
||||
return new IndirectReferenceToken(new IndirectReference(ReserveNumber(), 0));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the bytes that have been flushed to the stream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public byte[] ToArray()
|
||||
{
|
||||
var currentPosition = stream.Position;
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
var currentPosition = Stream.Position;
|
||||
Stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
var bytes = new byte[stream.Length];
|
||||
var bytes = new byte[Stream.Length];
|
||||
|
||||
if (stream.Read(bytes, 0, bytes.Length) != bytes.Length)
|
||||
if (Stream.Read(bytes, 0, bytes.Length) != bytes.Length)
|
||||
{
|
||||
throw new IOException("Unable to read all the bytes from stream");
|
||||
throw new Exception("Unable to read all the bytes from stream");
|
||||
}
|
||||
|
||||
stream.Seek(currentPosition, SeekOrigin.Begin);
|
||||
Stream.Seek(currentPosition, SeekOrigin.Begin);
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dispose the stream if the PdfStreamWriter#DisposeStream flag is set
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
if (!DisposeStream)
|
||||
{
|
||||
stream = null;
|
||||
Stream = null;
|
||||
return;
|
||||
}
|
||||
|
||||
stream?.Dispose();
|
||||
stream = null;
|
||||
Stream?.Dispose();
|
||||
Stream = null;
|
||||
}
|
||||
|
||||
private IndirectReferenceToken AddToken(IToken token, int reservedNumber)
|
||||
@@ -196,6 +151,21 @@
|
||||
return referenceToken;
|
||||
}
|
||||
|
||||
private IndirectReferenceToken FindToken(IToken token)
|
||||
{
|
||||
foreach (var pair in tokenReferences)
|
||||
{
|
||||
var reference = pair.Key;
|
||||
var storedToken = pair.Value;
|
||||
if (storedToken.Equals(token))
|
||||
{
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void WriteString(string text, Stream stream)
|
||||
{
|
||||
var bytes = OtherEncodings.StringAsLatin1Bytes(text);
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<PackageTags>PDF;Reader;Document;Adobe;PDFBox;PdfPig;pdf-extract;pdf-to-text;pdf;file;text;C#;dotnet;.NET</PackageTags>
|
||||
<RepositoryUrl>https://github.com/UglyToad/PdfPig</RepositoryUrl>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<Version>0.1.4</Version>
|
||||
<AssemblyVersion>0.1.1.0</AssemblyVersion>
|
||||
<FileVersion>0.1.1.0</FileVersion>
|
||||
<PackageIconUrl>https://raw.githubusercontent.com/UglyToad/PdfPig/master/documentation/pdfpig.png</PackageIconUrl>
|
||||
|
||||
Reference in New Issue
Block a user