mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
5 Commits
v0.1.5-alp
...
object-cop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487e368e9e | ||
|
|
704c56285c | ||
|
|
a190653683 | ||
|
|
8f0326a818 | ||
|
|
5a98b21c2f |
@@ -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:
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
@@ -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 />
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
string any = null;
|
||||
foreach (var nameRecord in NameRecords)
|
||||
{
|
||||
if (nameRecord is null || nameRecord.NameId != 6)
|
||||
if (nameRecord.NameId != 6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
var names = Standard14.GetNames().Count;
|
||||
|
||||
Assert.Equal(39, names);
|
||||
Assert.Equal(34, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetFlatnessTolerance(Tolerance);
|
||||
operationContext.GetCurrentState().Flatness = Tolerance;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetLineCap(Cap);
|
||||
operationContext.GetCurrentState().CapStyle = Cap;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetLineDashPattern(Pattern);
|
||||
operationContext.GetCurrentState().LineDashPattern = Pattern;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetLineJoin(Join);
|
||||
operationContext.GetCurrentState().JoinStyle = Join;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetLineWidth(Width);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.LineWidth = Width;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetMiterLimit(Limit);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.MiterLimit = Limit;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
// need to implement marked content here
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetCharacterSpacing((double)Spacing);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.CharacterSpacing = (double)Spacing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetHorizontalScaling((double)Scale);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.HorizontalScaling = (double)Scale;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetTextLeading((double)Leading);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.Leading = (double)Leading;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetTextRenderingMode(Mode);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.TextRenderingMode = Mode;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetTextRise((double)Rise);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.Rise = (double)Rise;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -34,7 +34,9 @@
|
||||
/// <inheritdoc />
|
||||
public void Run(IOperationContext operationContext)
|
||||
{
|
||||
operationContext.SetWordSpacing((double)Spacing);
|
||||
var currentState = operationContext.GetCurrentState();
|
||||
|
||||
currentState.FontState.WordSpacing = (double)Spacing;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user