Compare commits

..

5 Commits

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

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

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

View File

@@ -1,24 +0,0 @@
name: Build and test
on: [push]
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@master
- name: Set up dotnet core
uses: actions/setup-dotnet@v1
with:
dotnet-version: "2.1.x"
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
# Build the release build
- name: Build the solution
run: dotnet build -c Release src/UglyToad.PdfPig.sln
- name: Run the tests
run: dotnet test src/UglyToad.PdfPig.sln

View File

@@ -1,53 +0,0 @@
name: Nightly Release
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
jobs:
check_date:
runs-on: ubuntu-latest
name: Check latest commit
outputs:
should_run: ${{ steps.should_run.outputs.should_run }}
steps:
- uses: actions/checkout@master
- name: print latest_commit
run: echo ${{ github.sha }}
- id: should_run
continue-on-error: true
name: check latest commit is less than a day ago
if: ${{ github.event_name == 'schedule' }}
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
build_and_publish_nightly:
needs: check_date
if: ${{ needs.check_date.outputs.should_run != 'false' }}
runs-on: windows-latest
name: build_and_publish_nightly
steps:
- uses: actions/checkout@master
- name: Set up dotnet core
uses: actions/setup-dotnet@v1
with:
dotnet-version: "2.1.x"
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.0.2
- name: Write nightly version to projects
run: |
$newVer = .\tools\generate-nightly-version.ps1; .\tools\set-version.ps1 $newVer
- name: Restore packages
run: dotnet restore tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj
- name: Build package
run: dotnet pack -c Release -o package tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj
- name: Publish Nuget to GitHub registry
run: dotnet nuget push **/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -244,4 +244,3 @@ _Pvt_Extensions
/src/CodeCoverage/OpenCover.4.6.519
/src/test-results.xml
/tools/benchmark
/tools/ConsoleRunner/Properties/launchSettings.json

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -860,8 +860,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
return new Type2CharStrings.CommandSequence.CommandIdentifier(precedingValues.Count, false, 255);
}
private static int CalculatePrecedingHintBytes(List<float> precedingValues,
List<Type2CharStrings.CommandSequence.CommandIdentifier> precedingCommands)
private static int CalculatePrecedingHintBytes(IReadOnlyList<float> precedingValues,
IReadOnlyList<Type2CharStrings.CommandSequence.CommandIdentifier> precedingCommands)
{
int SafeStemCount(int counts)
{
@@ -873,7 +873,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
return (counts - 1) / 2;
}
/*
* The hintmask operator is followed by one or more data bytes that specify the stem hints which are to be active for the
* subsequent path construction. The number of data bytes must be exactly the number needed to represent the number of
@@ -891,14 +891,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
precedingNumbers++;
}
for (var j = 0; j < precedingCommands.Count; j++)
foreach (var identifier in precedingCommands.Where(x => x.CommandIndex == i + 1))
{
var identifier = precedingCommands[j];
if (identifier.CommandIndex != i + 1)
{
continue;
}
if (!identifier.IsMultiByteCommand
&& (identifier.CommandId == HintmaskByte || identifier.CommandId == CntrmaskByte)
&& !hasEncounteredInitialHintMask)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,8 +3,7 @@
using System.Collections.Generic;
using System.Linq;
using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
using UglyToad.PdfPig.Fonts.SystemFonts;
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
using Xunit;
public class DocstrumBoundingBoxesTests
@@ -71,18 +70,10 @@
}
};
[SkippableTheory]
[Theory]
[MemberData(nameof(DataExtract))]
public void GetBlocks(string name, string[] expected)
{
if (name == "90 180 270 rotated.pdf")
{
// The 'TimesNewRomanPSMT' font is used by this particular document. Thus, results cannot be trusted on
// platforms where this font isn't generally available (e.g. OSX, Linux, etc.), so we skip it!
var font = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPSMT");
Skip.If(font == null, "Skipped because the font TimesNewRomanPSMT could not be found in the execution environment.");
}
var options = new DocstrumBoundingBoxes.DocstrumBoundingBoxesOptions() { LineSeparator = " " };
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
{

View File

@@ -1,40 +0,0 @@
namespace UglyToad.PdfPig.Tests.Filters
{
using System.Collections.Generic;
using UglyToad.PdfPig.Filters;
using UglyToad.PdfPig.Tests.Images;
using UglyToad.PdfPig.Tokens;
using Xunit;
public class CcittFaxDecodeFilterTests
{
[Fact]
public void CanDecodeCCittFaxCompressedImageData()
{
var encodedBytes = ImageHelpers.LoadFileBytes("ccittfax-encoded.bin");
var filter = new CcittFaxDecodeFilter();
var dictionary = new Dictionary<NameToken, IToken>
{
{ NameToken.D, new ArrayToken(new []{ new NumericToken(1), new NumericToken(0) })},
{ NameToken.W, new NumericToken(1800) },
{ NameToken.H, new NumericToken(3113) },
{ NameToken.Bpc, new NumericToken(1) },
{ NameToken.F, NameToken.CcittfaxDecode },
{ NameToken.DecodeParms,
new DictionaryToken(new Dictionary<NameToken, IToken>
{
{ NameToken.K, new NumericToken(-1) },
{ NameToken.Columns, new NumericToken(1800) },
{ NameToken.Rows, new NumericToken(3113) },
{ NameToken.BlackIs1, BooleanToken.True }
})
}
};
var expectedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
var decodedBytes = filter.Decode(encodedBytes, new DictionaryToken(dictionary), 0);
Assert.Equal(expectedBytes, decodedBytes);
}
}
}

View File

@@ -31,106 +31,5 @@
Assert.Empty(result.Data);
}
[Fact]
public void SingleFilter_ReturnsParameterDictionary()
{
var filter = NameToken.CcittfaxDecode;
var filterParameters = new DictionaryToken(new Dictionary<NameToken, IToken>
{
{ NameToken.K, new NumericToken(-1) },
{ NameToken.Columns, new NumericToken(1800) },
{ NameToken.Rows, new NumericToken(3113) },
{ NameToken.BlackIs1, BooleanToken.True }
});
var dictionary = new Dictionary<NameToken, IToken>
{
{ NameToken.F, filter },
{ NameToken.DecodeParms, filterParameters }
};
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
Assert.Equal(filterParameters, result);
}
[Fact]
public void SingleFilter_SpecifiedInArray_ReturnsParameterDictionary()
{
var filter = NameToken.CcittfaxDecode;
var filterParameters = new DictionaryToken(new Dictionary<NameToken, IToken>
{
{ NameToken.K, new NumericToken(-1) },
{ NameToken.Columns, new NumericToken(1800) },
{ NameToken.Rows, new NumericToken(3113) },
{ NameToken.BlackIs1, BooleanToken.True }
});
var dictionary = new Dictionary<NameToken, IToken>
{
{ NameToken.F, new ArrayToken(new [] { filter }) },
{ NameToken.DecodeParms, new ArrayToken(new [] { filterParameters }) }
};
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
Assert.Equal(filterParameters, result);
}
[Fact]
public void MultipleFilters_WhenParameterIsNull_ReturnsEmptyDictionary()
{
var filter1 = NameToken.FlateDecode;
var filter1Parameters = NullToken.Instance;
var filter2 = NameToken.CcittfaxDecode;
var filter2Parameters = new DictionaryToken(new Dictionary<NameToken, IToken>
{
{ NameToken.K, new NumericToken(-1) },
{ NameToken.Columns, new NumericToken(1800) },
{ NameToken.Rows, new NumericToken(3113) },
{ NameToken.BlackIs1, BooleanToken.True }
});
var dictionary = new Dictionary<NameToken, IToken>
{
{ NameToken.F, new ArrayToken(new [] { filter1, filter2 }) },
{ NameToken.DecodeParms, new ArrayToken(new IToken[] { filter1Parameters, filter2Parameters }) }
};
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
Assert.Equal(new DictionaryToken(new Dictionary<NameToken, IToken>()), result);
}
[Fact]
public void MultipleFilters_ReturnsParameterDictionary()
{
var filter1 = NameToken.FlateDecode;
var filter1Parameters = NullToken.Instance;
var filter2 = NameToken.CcittfaxDecode;
var filter2Parameters = new DictionaryToken(new Dictionary<NameToken, IToken>
{
{ NameToken.K, new NumericToken(-1) },
{ NameToken.Columns, new NumericToken(1800) },
{ NameToken.Rows, new NumericToken(3113) },
{ NameToken.BlackIs1, BooleanToken.True }
});
var dictionary = new Dictionary<NameToken, IToken>
{
{ NameToken.F, new ArrayToken(new [] { filter1, filter2 }) },
{ NameToken.DecodeParms, new ArrayToken(new IToken[] { filter1Parameters, filter2Parameters }) }
};
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 1);
Assert.Equal(filter2Parameters, result);
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
using System.Collections.Generic;
using UglyToad.PdfPig.Fonts.SystemFonts;
using UglyToad.PdfPig.Tests.Dla;
using Xunit;
@@ -22,15 +21,11 @@ namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
},
};
[SkippableTheory]
[Theory]
[MemberData(nameof(DataExtract))]
public void GetCorrectBBoxLinux(string name, object[][] expected)
{
// success on Windows but LinuxSystemFontLister cannot find the 'TimesNewRomanPSMT' font
var font = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPSMT");
Skip.If(font == null, "Skipped because the font TimesNewRomanPSMT could not be found in the execution environment.");
{
// success on Windows but LinuxSystemFontLister cannot find the 'TimesNewRomanPSMT' font
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
{
var page = document.GetPage(1);

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

View File

@@ -1,52 +0,0 @@
namespace UglyToad.PdfPig.Tests.Images
{
using System;
using System.IO;
using System.IO.Compression;
using UglyToad.PdfPig.Images.Png;
public static class ImageHelpers
{
private static readonly string FilesFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files"));
public static byte[] LoadFileBytes(string filename, bool isCompressed = false)
{
var filePath = Path.Combine(FilesFolder, filename);
var memoryStream = new MemoryStream();
if (isCompressed)
{
using (var deflateStream = new DeflateStream(File.OpenRead(filePath), CompressionMode.Decompress))
{
deflateStream.CopyTo(memoryStream);
}
return memoryStream.ToArray();
}
return File.ReadAllBytes(filePath);
}
public static bool ImagesAreEqual(byte[] first, byte[] second)
{
var png1 = Png.Open(first);
var png2 = Png.Open(second);
if (png1.Width != png2.Width || png1.Height != png2.Height || png1.HasAlphaChannel != png2.HasAlphaChannel)
{
return false;
}
for (var y = 0; y < png1.Height; y++)
{
for (var x = 0; x < png1.Width; x++)
{
if (!png1.GetPixel(x, y).Equals(png2.GetPixel(x, y)))
{
return false;
}
}
}
return true;
}
}
}

View File

@@ -1,267 +0,0 @@
namespace UglyToad.PdfPig.Tests.Images
{
using System.Collections.Generic;
using System.Linq;
using UglyToad.PdfPig.Graphics.Colors;
using UglyToad.PdfPig.Images.Png;
using Xunit;
public class PngFromPdfImageFactoryTests
{
private static readonly byte[] RgbBlack = new byte[] { 0, 0, 0 };
private static readonly byte[] RgbWhite = new byte[] { 255, 255, 255 };
private static readonly byte[][] RgbPalette = new[] { RgbBlack, RgbWhite };
private static readonly byte[] CmykBlack = new byte[] { 0, 0, 0, 255 };
private static readonly byte[] CmykWhite = new byte[] { 0, 0, 0, 0 };
private static readonly byte GrayscaleBlack = 0;
private static readonly byte GrayscaleWhite = 255;
[Fact]
public void CanGeneratePngFromDeviceRgbImageData()
{
var pixels = new[]
{
RgbWhite, RgbBlack, RgbWhite,
RgbBlack, RgbWhite, RgbBlack,
RgbWhite, RgbBlack, RgbWhite
};
var image = new TestPdfImage
{
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
WidthInSamples = 3,
HeightInSamples = 3
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanGeneratePngFromDeviceCMYKImageData()
{
var pixels = new[]
{
CmykWhite, CmykBlack, CmykWhite,
CmykBlack, CmykWhite, CmykBlack,
CmykWhite, CmykBlack, CmykWhite
};
var image = new TestPdfImage
{
ColorSpaceDetails = DeviceCmykColorSpaceDetails.Instance,
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
WidthInSamples = 3,
HeightInSamples = 3
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanGeneratePngFromDeviceGrayscaleImageData()
{
var pixels = new[]
{
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite,
GrayscaleBlack, GrayscaleWhite, GrayscaleBlack,
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite
};
var image = new TestPdfImage
{
ColorSpaceDetails = DeviceGrayColorSpaceDetails.Instance,
DecodedBytes = pixels,
WidthInSamples = 3,
HeightInSamples = 3
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanGeneratePngFromIndexedImageData8bpc()
{
var indices = new byte[]
{
1, 0, 1,
0, 1, 0,
1, 0, 1
};
var image = new TestPdfImage
{
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, RgbPalette.SelectMany(b => b).ToArray()),
DecodedBytes = indices,
WidthInSamples = 3,
HeightInSamples = 3
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanGeneratePngFromIndexedImageData1bpc()
{
// Indices for a 3x3 RGB image, each index is represented by a single bit
// 1, 0, 1,
// 0, 1, 0,
// 1, 0, 1
//
// A scanline must be at least one byte wide, so the remaining five bits are padding:
// Byte 0: 10100000 (bits #7 and #5 are 1)
// Byte 1: 01000000 (bit #6 is 1)
// Byte 2: 10100000 (bits #7 and #5 are 1)
// ||||||||
// Bit # : 76543210
var lines = new byte[3];
lines[0] |= (1 << 7); // Set bit #7 to 1
lines[0] |= (1 << 5); // Set bit #5 to 1
lines[1] |= (1 << 6); // Set bit #6 to 1
lines[2] |= (1 << 7); // Set bit #7 to 1
lines[2] |= (1 << 5); // Set bit #5 to 1
var colorTable = RgbPalette.SelectMany(b => b).ToArray();
var image = new TestPdfImage
{
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, colorTable),
DecodedBytes = lines,
WidthInSamples = 3,
HeightInSamples = 3,
BitsPerComponent = 1
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanGeneratePngFromCcittFaxDecodedImageData()
{
var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
var image = new TestPdfImage
{
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance, new[] { 1m, 0 }),
DecodedBytes = decodedBytes,
WidthInSamples = 1800,
HeightInSamples = 3113,
BitsPerComponent = 1
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("ccittfax.png"), bytes));
}
[Fact]
public void CanGeneratePngFromICCBasedImageData()
{
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
var image = new TestPdfImage
{
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
numberOfColorComponents: 3,
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
metadata: null),
DecodedBytes = decodedBytes,
WidthInSamples = 1,
HeightInSamples = 1,
BitsPerComponent = 8
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("iccbased.png"), bytes));
}
[Fact]
public void AlternateColorSpaceDetailsIsCurrentlyUsedInPdfPigWhenGeneratingPngsFromICCBasedImageData()
{
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
var iccBasedImage = new TestPdfImage
{
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
numberOfColorComponents: 3,
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
metadata: null),
DecodedBytes = decodedBytes,
WidthInSamples = 1,
HeightInSamples = 1,
BitsPerComponent = 8
};
var deviceRGBImage = new TestPdfImage
{
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
DecodedBytes = decodedBytes,
WidthInSamples = 1,
HeightInSamples = 1,
BitsPerComponent = 8
};
Assert.True(PngFromPdfImageFactory.TryGenerate(iccBasedImage, out var iccPngBytes));
Assert.True(PngFromPdfImageFactory.TryGenerate(deviceRGBImage, out var deviceRgbBytes));
Assert.Equal(iccPngBytes, deviceRgbBytes);
}
[Fact]
public void CanGeneratePngFromCalRGBImageData()
{
var decodedBytes = ImageHelpers.LoadFileBytes("calrgb-decoded.bin");
var image = new TestPdfImage
{
ColorSpaceDetails = new CalRGBColorSpaceDetails(
whitePoint: new List<decimal> { 0.95043m, 1, 1.09m },
blackPoint: null,
gamma: new List<decimal> { 2.2m, 2.2m, 2.2m },
matrix: new List<decimal> {
0.41239m, 0.21264m, 0.01933m,
0.35758m, 0.71517m, 0.11919m,
0.18045m, 0.07218m, 0.9504m }),
DecodedBytes = decodedBytes,
WidthInSamples = 153,
HeightInSamples = 83,
BitsPerComponent = 8
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calrgb.png"), bytes));
}
[Fact]
public void CanGeneratePngFromCalGrayImageData()
{
var decodedBytes = ImageHelpers.LoadFileBytes("calgray-decoded.bin", isCompressed: true);
var image = new TestPdfImage
{
ColorSpaceDetails = new CalGrayColorSpaceDetails(
whitePoint: new List<decimal> { 0.9505000114m, 1, 1.0889999866m },
blackPoint: null,
gamma: 2.2000000477m),
DecodedBytes = decodedBytes,
WidthInSamples = 2480,
HeightInSamples = 1748,
BitsPerComponent = 8,
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calgray.png"), bytes));
}
private static byte[] LoadImage(string name)
{
return ImageHelpers.LoadFileBytes(name);
}
}
}

View File

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

View File

@@ -1,61 +0,0 @@
%PDF-1.1
1 0 obj
<</Type/Catalog/Pages 2 0 R>>
endobj
2 0 obj
<</Type/Pages/Count 1/Kids[3 0 R]/MediaBox [0 0 200 100]>>
endobj
3 0 obj
<<
/Type/Page
/Parent 2 0 R
/Resources <<
/XObject << /SomeImage 4 0 R >>
>>
/Contents 5 0 R
>>
endobj
4 0 obj
<<
/Type/XObject
/Subtype/Image
/Width 200 % The width or height directly affects the image's file size.
/Height 100
/ColorSpace/DeviceRGB
/DecodeParms [] % Forces NativeImageDecoder.isSupported to return false.
/BitsPerComponent 8
/Length 580
/Filter [ /ASCIIHexDecode /DCTDecode ]
>>
% convert -size 1x1 xc:red jpeg:- | xxd -p -c40
stream
ffd8ffe000104a46494600010100000100010000ffdb004300030202020202030202020303030304
060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c
12131210130f101010ffdb00430103030304030408040408100b090b101010101010101010101010
1010101010101010101010101010101010101010101010101010101010101010101010101010ffc0
0011080001000103011100021101031101ffc40014000100000000000000000000000000000008ff
c40014100100000000000000000000000000000000ffc40015010101000000000000000000000000
00000709ffc40014110100000000000000000000000000000000ffda000c03010002110311003f00
3a03154dffd9
endstream
endobj
5 0 obj
<</Length 14>>
stream
500 0 0 400 0 0 cm
/SomeImage Do
endstream
endobj
xref
0 6
0000000000 65535 f
0000000008 00000 n
0000000054 00000 n
0000000128 00000 n
0000000246 00000 n
0000001201 00000 n
trailer
<</Root 1 0 R/Size 6>>
startxref
1281
%%EOF

View File

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

View File

@@ -1,7 +1,7 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using Content;
using UglyToad.PdfPig.Images.Png;
using Images.Png;
using Xunit;
public class SwedishTouringCarChampionshipTests

View File

@@ -4,7 +4,6 @@
using Logging;
using PdfPig.Core;
using PdfPig.Parser.FileStructure;
using PdfPig.Tokenization.Scanner;
using Xunit;
public class FileHeaderParserTests
@@ -49,7 +48,7 @@
var result = FileHeaderParser.Parse(scanner, false, log);
Assert.Equal(1.2m, result.Version);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 7 : 9, result.OffsetInFile);
Assert.Equal(9, result.OffsetInFile);
}
[Fact]
@@ -71,7 +70,7 @@
var result = FileHeaderParser.Parse(scanner, false, log);
Assert.Equal(1.2m, result.Version);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
Assert.Equal(13, result.OffsetInFile);
}
[Fact]
@@ -83,7 +82,7 @@
var result = FileHeaderParser.Parse(scanner, true, log);
Assert.Equal(1.7m, result.Version);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
Assert.Equal(13, result.OffsetInFile);
}
[Fact]
@@ -95,7 +94,7 @@ three %PDF-1.6");
var result = FileHeaderParser.Parse(scanner, true, log);
Assert.Equal(1.6m, result.Version);
Assert.Equal(TestEnvironment.IsUnixPlatform ? 14 : 15, result.OffsetInFile);
Assert.Equal(15, result.OffsetInFile);
}
[Fact]
@@ -138,17 +137,5 @@ three %PDF-1.6");
Assert.Equal(0, scanner.CurrentPosition);
Assert.Equal(0, result.OffsetInFile);
}
[Fact]
public void Issue334()
{
var input = OtherEncodings.StringAsLatin1Bytes("%PDF-1.7\r\n%âãÏÓ\r\n1 0 obj\r\n<</Lang(en-US)>>\r\nendobj");
var scanner = new CoreTokenScanner(new ByteArrayInputBytes(input), ScannerScope.None);
var result = FileHeaderParser.Parse(scanner, false, log);
Assert.Equal(1.7m, result.Version);
}
}
}

View File

@@ -74,8 +74,6 @@
"UglyToad.PdfPig.Content.IPdfImage",
"UglyToad.PdfPig.Content.Letter",
"UglyToad.PdfPig.Content.MarkedContentElement",
"UglyToad.PdfPig.Content.MediaBox",
"UglyToad.PdfPig.Content.OptionalContentGroupElement",
"UglyToad.PdfPig.Content.Page",
"UglyToad.PdfPig.Content.PageRotationDegrees",
"UglyToad.PdfPig.Content.PageSize",
@@ -104,16 +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.CalGrayColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.CalRGBColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails",
"UglyToad.PdfPig.Graphics.Colors.ICCBasedColorSpaceDetails",
"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",
@@ -198,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",
@@ -210,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",
@@ -221,7 +206,6 @@
"UglyToad.PdfPig.Writer.PdfAStandard",
"UglyToad.PdfPig.Writer.PdfDocumentBuilder",
"UglyToad.PdfPig.Writer.PdfMerger",
"UglyToad.PdfPig.Writer.PdfWriterType",
"UglyToad.PdfPig.Writer.PdfPageBuilder",
"UglyToad.PdfPig.Writer.TokenWriter",
"UglyToad.PdfPig.XObjects.XObjectImage"

View File

@@ -1,9 +0,0 @@
namespace UglyToad.PdfPig.Tests
{
using System;
public static class TestEnvironment
{
public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1;
}
}

View File

@@ -2,10 +2,9 @@
{
using System.Collections.Generic;
using PdfPig.Filters;
using PdfPig.Tokenization.Scanner;
using PdfPig.Tokens;
internal class TestFilterProvider : ILookupFilterProvider
internal class TestFilterProvider : IFilterProvider
{
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
{
@@ -21,10 +20,5 @@
{
return new List<IFilter>();
}
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary, IPdfTokenScanner scanner)
{
return new List<IFilter>();
}
}
}

View File

@@ -1,49 +0,0 @@
namespace UglyToad.PdfPig.Tests
{
using System.Collections.Generic;
using UglyToad.PdfPig.Content;
using UglyToad.PdfPig.Core;
using UglyToad.PdfPig.Graphics.Colors;
using UglyToad.PdfPig.Graphics.Core;
using UglyToad.PdfPig.Images.Png;
using UglyToad.PdfPig.Tokens;
public class TestPdfImage : IPdfImage
{
public PdfRectangle Bounds { get; set; }
public int WidthInSamples { get; set; }
public int HeightInSamples { get; set; }
public ColorSpace? ColorSpace => IsImageMask ? default(ColorSpace?) : ColorSpaceDetails.Type;
public int BitsPerComponent { get; set; } = 8;
public IReadOnlyList<byte> RawBytes { get; }
public RenderingIntent RenderingIntent { get; set; } = RenderingIntent.RelativeColorimetric;
public bool IsImageMask { get; set; }
public IReadOnlyList<decimal> Decode { get; set; }
public bool Interpolate { get; set; }
public bool IsInlineImage { get; set; }
public DictionaryToken ImageDictionary { get; set; }
public ColorSpaceDetails ColorSpaceDetails { get; set; }
public IReadOnlyList<byte> DecodedBytes { get; set; }
public bool TryGetBytes(out IReadOnlyList<byte> bytes)
{
bytes = DecodedBytes;
return bytes != null;
}
public bool TryGetPng(out byte[] bytes) => PngFromPdfImageFactory.TryGenerate(this, out bytes);
}
}

View File

@@ -96,18 +96,6 @@
Assert.Equal(0m, AssertNumericToken(token).Data);
}
[Fact]
public void HandleDoubleDashedNumber()
{
// This is a really weird format but seen in the wild. PDF, shine on, you crazy diamond.
var input = StringBytesTestConverter.Convert("--10.25");
var result = tokenizer.TryTokenize(input.First, input.Bytes, out var token);
Assert.True(result);
Assert.Equal(-10.25m, AssertNumericToken(token).Data);
}
[Fact]
public void HandlesDot()
{

View File

@@ -169,33 +169,7 @@ endobj";
AssertCorrectToken<OperatorToken, string>(tokens[2], "Tj");
AssertCorrectToken<NumericToken, decimal>(tokens[3], -91);
}
[Fact]
public void ScansStringWithWeirdWeirdDoubleSymbolNumerics()
{
const string content = @"
0.00 --21.72 TD
/F1 8.00 Tf";
var tokens = new List<IToken>();
var scanner = scannerFactory(StringBytesTestConverter.Convert(content, false).Bytes);
while (scanner.MoveNext())
{
tokens.Add(scanner.CurrentToken);
}
Assert.Equal(6, tokens.Count);
AssertCorrectToken<NumericToken, decimal>(tokens[0], 0m);
AssertCorrectToken<NumericToken, decimal>(tokens[1], -21.72m);
AssertCorrectToken<OperatorToken, string>(tokens[2], "TD");
AssertCorrectToken<NameToken, string>(tokens[3], "F1");
AssertCorrectToken<NumericToken, decimal>(tokens[4], 8m);
AssertCorrectToken<OperatorToken, string>(tokens[5], "Tf");
}
private static void AssertCorrectToken<T, TData>(IToken token, TData expected) where T : IDataToken<TData>
{
var cast = Assert.IsType<T>(token);

View File

@@ -15,9 +15,7 @@
[Fact]
public void ReadsSimpleObject()
{
const string s = @"294 0 obj
/WDKAAR+CMBX12
endobj";
const string s = @"294 0 obj
/WDKAAR+CMBX12
endobj";
@@ -118,15 +116,7 @@ endobj
}
[Fact]
const string s = @"
endobj
295 0 obj
[
676 938 875 787 750 880 813 875 813 875 813 656 625 625 938 938 313
344 563 563 563 563 563 850 500 574 813 875 563 1019 1144 875 313
]
endobj";
public void ReadsArrayObject()
{
const string s = @"
endobj
@@ -153,29 +143,8 @@ endobj";
Assert.Equal(295, obj.Number.ObjectNumber);
Assert.Equal(0, obj.Number.Generation);
274 0 obj
<<
/Type /Pages
/Count 2
/Parent 275 0 R
/Kids [ 121 0 R 125 0 R ]
>>
endobj
%Other parts...
310 0 obj
/WPXNWT+CMR9
endobj 311 0 obj
<<
/Type /Font
/Subtype /Type1
/FirstChar 0
/LastChar 127
/Widths 313 0 R
/BaseFont 310 0 R /FontDescriptor 312 0 R
>>
endobj";
Assert.StartsWith("295 0 obj", s.Substring((int)obj.Position));
Assert.False(pdfScanner.MoveNext());
}
@@ -222,17 +191,12 @@ endobj";
Assert.Equal("WPXNWT+CMR9", nameObject.Data);
Assert.Equal(310, tokens[1].Number.ObjectNumber);
352 0 obj
<< /S 1273 /Filter /FlateDecode /Length 353 0 R >>
stream
H‰œUkLSgþÚh¹IÝÅlK(%[ÈÅ©+ ƒåꩊèæÇtnZ)Z¹¨Oå~9ŠÊµo”[éiK)÷B¹´
É² ©¸˜ n±º×dKöcÏ÷ãœç{ßï}¾÷ÍÉs  Ô;€
À»—ÀF`ÇF@ƒ 4 ˜ï @¥T¨³fY: žµ;Îq®]cƒÿdp¨ÛI3F#G©#œ)TÇqW£NÚѬgOKbü‡µ#á¡£Þaîtƒƒß
¾“S>}µuÕõ5M±¢ª†»øÞû•q÷îÜ~¬PòžÞ~•¬ëɃGÅ-Ñ­ím·°gêêb,/,£P§õ^ ãÁô¿¿ŠTE]²±{šuwÔ`LG³DªìTÈ
A¡¬àð‰É©ˆ°¼×s³®í»š}%§X{{tøNåÝž¶ö¢ÖÞ¾~´¼¬°À“Éððr¥8»P£ØêÁi½®Û(éhŽú;x#dÃÄ$m
+)
)†…±n
9ùyŽA·n\ï»t!=3£½¡:®­µåâ¹Ô³ø¼ËiûSÎsë;•Dt—ö$WÉ4U¢ºÚšñá1íÐèÔó‚svõ(/(+D²#mZÏ6êüÝ7x‡—†”‡E„²|ê«êªDµ5q°šR¦RÈ£ n¾[è~“}ýƒÝ½SꞦ'æQŽ
Assert.StartsWith("310 0 obj", s.Substring((int)tokens[1].Position));
dictionary = Assert.IsType<DictionaryToken>(tokens[2].Data);
Assert.Equal(7, dictionary.Data.Count);
Assert.Equal(311, tokens[2].Number.ObjectNumber);
Assert.StartsWith("311 0 obj", s.Substring((int)tokens[2].Position));
}
@@ -357,7 +321,7 @@ endobj
[Fact]
public void ReadsStreamWithMissingLength()
xœ]ËjÃ0ÿÃ,ÓEð#NÒ€1¤N^ôA~€-]A- YYøï+Ï4¡t#qfîFWQY*­Dïv5:è”–§ñjB½Òa¤ •p7¤K  ƒÈûëyr8Tº!Ïà úð‚ÉÙVG9¶ø@Å7+Ñ*ÝÃ곬¹T_ùƵƒ8 Š$vË̗Ƽ6BDöu%½B¹yí$—Ù ¤\Hx71JœL#Ð6ºÇ0È㸀ü|. µüßõÏ""WÛ‰¯Æ.êÄ«ã8; ¤iL°!Ø %É`K°ßì¸ÃöÜáÜ)  [#CFðİ#(yƒg^ÿ¶æò
{
const string s = @"
12655 0 obj
@@ -375,31 +339,7 @@ endobj";
Assert.Equal(12655, token.Number.ObjectNumber);
var stream = Assert.IsType<StreamToken>(token.Data);
const string input = @"5 0 obj
<<
/Kids [4 0 R 12 0 R 17 0 R 20 0 R 25 0 R 28 0 R ]
/Count 6
/Type /Pages
/MediaBox [ 0 0 612 792 ]
>>
endobj
1 0 obj
<<
/Creator (Corel WordPerfect - [D:\Wpdocs\WEBSITE\PROC&POL.WP6 (unmodified)
/CreationDate (D:19980224130723)
/Title (Proc&Pol.pdf)
/Author (J. L. Swezey)
/Producer (Acrobat PDFWriter 3.03 for Windows NT)
/Keywords (Budapest Treaty; Patent deposits; IDA)
/Subject (Patent Collection Procedures and Policies)
>>
endobj
3 0 obj
<<
/Pages 5 0 R
/Type /Catalog
>>
endobj";
Assert.Equal("1245", stream.StreamDictionary.Data["S"].ToString());
Assert.Equal("%¥×³®í»š}%§X{{tøNåÝž¶ö¢ÖÞgrehtyyy$&%&£$££(*¾–~´¼", Encoding.UTF8.GetString(stream.Data.ToArray()));

View File

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

View File

@@ -39,7 +39,6 @@
<ItemGroup>
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
@@ -65,9 +64,6 @@
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Fonts\TrueType\PMingLiU.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -101,7 +97,6 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
</ItemGroup>
<ItemGroup>
@@ -134,8 +129,4 @@
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="Images\" />
<Folder Include="Images\Files\" />
</ItemGroup>
</Project>

View File

@@ -1,121 +0,0 @@
namespace UglyToad.PdfPig.Tests.Util
{
using Xunit;
using PdfPig.Util;
public class Matrix3x3Tests
{
[Fact]
public void CanCreate()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
Assert.Equal(new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, matrix);
}
[Fact]
public void ExposesIdentityMatrix()
{
var matrix = new Matrix3x3(
1, 0, 0,
0, 1, 0,
0, 0, 1
);
Assert.Equal(matrix, Matrix3x3.Identity);
}
[Fact]
public void CanMultiplyWithFactor()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
var result = matrix.Multiply(3);
Assert.Equal(new double[] { 3, 6, 9, 12, 15, 18, 21, 24, 27 }, result);
}
[Fact]
public void CanMultiplyWithVector()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
var vector = (1, 2, 3);
var product = matrix.Multiply(vector);
Assert.Equal((14, 32, 50), product);
// Result can be verified here:
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C+3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B1%2C2%2C3%7D
}
[Fact]
public void CanMultiplyWithMatrix()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
var product = matrix.Multiply(matrix);
var expected = new Matrix3x3(
30, 36, 42,
66, 81, 96,
102, 126, 150);
Assert.Equal(expected, product);
// Result can be verified here:
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D
}
[Fact]
public void CanTranspose()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
var transposed = matrix.Transpose();
Assert.Equal(new double[] { 1, 4, 7, 2, 5, 8, 3, 6, 9 }, transposed);
}
[Fact]
public void InverseReturnsMatrixIfPossible()
{
var matrix = new Matrix3x3(
1, 2, 3,
0, 1, 4,
5, 6, 0);
var inverse = matrix.Inverse();
Assert.Equal(new double[] { -24, 18, 5, 20, -15, -4, -5, 4, 1 }, inverse);
Assert.Equal(Matrix3x3.Identity, matrix.Multiply(inverse));
}
[Fact]
public void InverseReturnsNullIfNotPossible()
{
var matrix = new Matrix3x3(
1, 2, 3,
4, 5, 6,
7, 8, 9);
var inverse = matrix.Inverse();
Assert.Null(inverse);
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
internal class NumericTokenizer : ITokenizer
{
private readonly StringBuilder stringBuilder = new StringBuilder();
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
private const byte Zero = 48;
private const byte Nine = 57;
@@ -20,45 +20,28 @@
token = null;
StringBuilder characters;
var initialSymbol = currentByte == '-' || currentByte == '+';
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '.')
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.')
{
characters = stringBuilder;
characters = StringBuilderPool.Borrow();
characters.Append((char)currentByte);
}
else if (initialSymbol)
{
characters = stringBuilder;
characters.Append((char) currentByte);
}
else
{
return false;
}
var previousSymbol = initialSymbol;
while (inputBytes.MoveNext())
{
var b = inputBytes.CurrentByte;
if (b == '+' || b == '-')
if ((b >= Zero && b <= Nine) ||
b == '-' ||
b == '+' ||
b == '.' ||
b == 'E' ||
b == 'e')
{
if (previousSymbol)
{
continue;
}
characters.Append((char) b);
previousSymbol = true;
}
else if ((b >= Zero && b <= Nine) ||
b == '.' ||
b == 'E' ||
b == 'e')
{
previousSymbol = false;
characters.Append((char)b);
}
else
@@ -70,7 +53,7 @@
try
{
var str = characters.ToString();
characters.Clear();
StringBuilderPool.Return(characters);
switch (str)
{

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
/// A dictionary object is an associative table containing pairs of objects, known as the dictionary's entries.
/// The key must be a <see cref="NameToken"/> and the value may be an kind of <see cref="IToken"/>.
/// </summary>
public class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>>, IEquatable<DictionaryToken>
public class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>>
{
/// <summary>
/// The key value pairs in this dictionary.
@@ -102,16 +102,6 @@
/// <returns>A new <see cref="DictionaryToken"/> with the entry created or modified.</returns>
public DictionaryToken With(string key, IToken value)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
var result = new Dictionary<string, IToken>(Data.Count + 1);
foreach (var keyValuePair in Data)
@@ -124,63 +114,20 @@
return new DictionaryToken(result);
}
/// <summary>
/// Creates a copy of this dictionary with the entry with the specified key removed (if it exists).
/// </summary>
/// <param name="key">The key of the entry to remove.</param>
/// <returns>A new <see cref="DictionaryToken"/> with the entry removed.</returns>
public DictionaryToken Without(NameToken key) => Without(key.Data);
/// <summary>
/// Creates a copy of this dictionary with the entry with the specified key removed (if it exists).
/// </summary>
/// <param name="key">The key of the entry to remove.</param>
/// <returns>A new <see cref="DictionaryToken"/> with the entry removed.</returns>
public DictionaryToken Without(string key)
{
if (key == null)
{
throw new ArgumentNullException(nameof(key));
}
var result = new Dictionary<string, IToken>(Data.ContainsKey(key) ? Data.Count - 1 : Data.Count);
foreach (var keyValuePair in Data.Where(x => !x.Key.Equals(key)))
{
result[keyValuePair.Key] = keyValuePair.Value;
}
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)
{
return Equals(obj as DictionaryToken);
}
/// <inheritdoc />
public bool Equals(DictionaryToken other)
{
if (other == null)
{
return false;
}
if (ReferenceEquals(this, other))
if (ReferenceEquals(this, obj))
{
return true;
}
}
if (!(obj is DictionaryToken other))
{
return false;
}
if (Data.Count != other.Data.Count)
{
return false;
@@ -194,14 +141,13 @@
}
}
return true;
return true;
}
/// <inheritdoc />
public override string ToString()
{
return string.Join(", ", Data.Select(x => $"<{x.Key}, {x.Value}>"));
}
}
}
}

View File

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

View File

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

View File

@@ -1,6 +1,4 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
@@ -9,8 +7,4 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XY/@EntryIndexedValue">XY</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String></wpf:ResourceDictionary>

View File

@@ -6,6 +6,7 @@
using Content;
using Core;
using CrossReference;
using Exceptions;
using Fields;
using Filters;
using Parser.Parts;
@@ -29,10 +30,10 @@
};
private readonly IPdfTokenScanner tokenScanner;
private readonly ILookupFilterProvider filterProvider;
private readonly IFilterProvider filterProvider;
private readonly CrossReferenceTable crossReferenceTable;
public AcroFormFactory(IPdfTokenScanner tokenScanner, ILookupFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
{
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
@@ -313,7 +314,7 @@
}
else if (DirectObjectFinder.TryGet(textValueToken, tokenScanner, out StreamToken valueStreamToken))
{
textValue = OtherEncodings.BytesAsLatin1String(valueStreamToken.Decode(filterProvider, tokenScanner).ToArray());
textValue = OtherEncodings.BytesAsLatin1String(valueStreamToken.Decode(filterProvider).ToArray());
}
}
@@ -477,17 +478,6 @@
var isChecked = false;
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
{
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.As, tokenScanner, out NameToken appearanceStateName)
&& fieldDictionary.TryGetOptionalTokenDirect(NameToken.Ap, tokenScanner, out DictionaryToken _))
{
// Issue #267 - Use the set appearance instead, this might not work for 3 state checkboxes.
isChecked = !string.Equals(
appearanceStateName.Data,
NameToken.Off,
StringComparison.OrdinalIgnoreCase);
valueToken = appearanceStateName;
return (isChecked, valueToken);
}
valueToken = NameToken.Off;
}
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))

View File

@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using Content;
using Core;
using Filters;
using Parser.Parts;
using Tokenization.Scanner;
@@ -16,13 +15,13 @@
public class AdvancedPdfDocumentAccess : IDisposable
{
private readonly IPdfTokenScanner pdfScanner;
private readonly ILookupFilterProvider filterProvider;
private readonly IFilterProvider filterProvider;
private readonly Catalog catalog;
private bool isDisposed;
internal AdvancedPdfDocumentAccess(IPdfTokenScanner pdfScanner,
ILookupFilterProvider filterProvider,
IFilterProvider filterProvider,
Catalog catalog)
{
this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner));
@@ -73,7 +72,7 @@
fileSpecification = fileSpecificationToken.Data;
}
var fileBytes = fileStreamToken.Decode(filterProvider, pdfScanner);
var fileBytes = fileStreamToken.Decode(filterProvider);
result.Add(new EmbeddedFile(keyValuePair.Key, fileSpecification, fileBytes, fileStreamToken));
}
@@ -83,30 +82,6 @@
return embeddedFiles.Count > 0;
}
/// <summary>
/// Replaces the token in an internal cache that will be returned instead of
/// scanning the source PDF data for future requests.
/// </summary>
/// <param name="reference">The object number for the object to replace.</param>
/// <param name="replacer">Func that takes existing token as input and return new token.</param>
public void ReplaceIndirectObject(IndirectReference reference, Func<IToken, IToken> replacer)
{
var obj = pdfScanner.Get(reference);
var replacement = replacer(obj.Data);
pdfScanner.ReplaceToken(reference, replacement);
}
/// <summary>
/// Replaces the token in an internal cache that will be returned instead of
/// scanning the source PDF data for future requests.
/// </summary>
/// <param name="reference">The object number for the object to replace.</param>
/// <param name="replacement">Replacement token to use.</param>
public void ReplaceIndirectObject(IndirectReference reference, IToken replacement)
{
pdfScanner.ReplaceToken(reference, replacement);
}
private void GuardDisposed()
{
if (isDisposed)

View File

@@ -3,8 +3,7 @@
using System.Collections.Generic;
using Core;
using Graphics.Colors;
using Graphics.Core;
using UglyToad.PdfPig.Tokens;
using Graphics.Core;
using XObjects;
/// <summary>
@@ -85,16 +84,6 @@
/// </summary>
bool IsInlineImage { get; }
/// <summary>
/// The full dictionary for this image object.
/// </summary>
DictionaryToken ImageDictionary { get; }
/// <summary>
/// Full details for the <see cref="ColorSpace"/> with any associated data.
/// </summary>
ColorSpaceDetails ColorSpaceDetails { get; }
/// <summary>
/// Get the decoded bytes of the image if applicable. For JPEG images and some other types the
/// <see cref="RawBytes"/> should be used directly.

View File

@@ -8,9 +8,8 @@
using Graphics.Colors;
using Graphics.Core;
using Tokens;
using Images.Png;
using UglyToad.PdfPig.Util.JetBrains.Annotations;
using Images.Png;
/// <inheritdoc />
/// <summary>
/// A small image that is completely defined directly inline within a <see cref="T:UglyToad.PdfPig.Content.Page" />'s content stream.
@@ -43,10 +42,6 @@
/// <inheritdoc />
public bool IsInlineImage { get; } = true;
/// <inheritdoc />
[NotNull]
public DictionaryToken ImageDictionary { get; }
/// <inheritdoc />
public RenderingIntent RenderingIntent { get; }
@@ -56,9 +51,6 @@
/// <inheritdoc />
public IReadOnlyList<byte> RawBytes { get; }
/// <inheritdoc />
public ColorSpaceDetails ColorSpaceDetails { get; }
/// <summary>
/// Create a new <see cref="InlineImage"/>.
/// </summary>
@@ -69,8 +61,7 @@
IReadOnlyList<decimal> decode,
IReadOnlyList<byte> bytes,
IReadOnlyList<IFilter> filters,
DictionaryToken streamDictionary,
ColorSpaceDetails colorSpaceDetails)
DictionaryToken streamDictionary)
{
Bounds = bounds;
WidthInSamples = widthInSamples;
@@ -81,10 +72,8 @@
IsImageMask = isImageMask;
RenderingIntent = renderingIntent;
Interpolate = interpolate;
ImageDictionary = streamDictionary;
RawBytes = bytes;
ColorSpaceDetails = colorSpaceDetails;
var supportsFilters = true;
foreach (var filter in filters)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
namespace UglyToad.PdfPig.Content
{
using System;
using System.Collections.Generic;
using Core;
using Filters;
using Graphics;
using Graphics.Operations;
using System;
using System.Collections.Generic;
using Tokenization.Scanner;
using XObjects;
@@ -21,7 +21,7 @@
private readonly IReadOnlyList<Union<XObjectContentRecord, InlineImage>> images;
private readonly IReadOnlyList<MarkedContentElement> markedContents;
private readonly IPdfTokenScanner pdfScanner;
private readonly ILookupFilterProvider filterProvider;
private readonly IFilterProvider filterProvider;
private readonly IResourceStore resourceStore;
internal IReadOnlyList<IGraphicsStateOperation> GraphicsStateOperations { get; }
@@ -37,7 +37,7 @@
IReadOnlyList<Union<XObjectContentRecord, InlineImage>> images,
IReadOnlyList<MarkedContentElement> markedContents,
IPdfTokenScanner pdfScanner,
ILookupFilterProvider filterProvider,
IFilterProvider filterProvider,
IResourceStore resourceStore)
{
GraphicsStateOperations = graphicsStateOperations;

View File

@@ -6,7 +6,6 @@
using System.Xml.Linq;
using Core;
using Filters;
using Tokenization.Scanner;
using Tokens;
using Util.JetBrains.Annotations;
@@ -16,8 +15,7 @@
/// </summary>
public class XmpMetadata
{
private readonly ILookupFilterProvider filterProvider;
private readonly IPdfTokenScanner pdfTokenScanner;
private readonly IFilterProvider filterProvider;
/// <summary>
/// The underlying <see cref="StreamToken"/> for this metadata.
@@ -25,10 +23,9 @@
[NotNull]
public StreamToken MetadataStreamToken { get; }
internal XmpMetadata(StreamToken stream, ILookupFilterProvider filterProvider, IPdfTokenScanner pdfTokenScanner)
internal XmpMetadata(StreamToken stream, IFilterProvider filterProvider)
{
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
this.pdfTokenScanner = pdfTokenScanner;
MetadataStreamToken = stream ?? throw new ArgumentNullException(nameof(stream));
}
@@ -38,7 +35,7 @@
/// <returns>The bytes for the metadata object with any filters removed.</returns>
public IReadOnlyList<byte> GetXmlBytes()
{
return MetadataStreamToken.Decode(filterProvider, pdfTokenScanner);
return MetadataStreamToken.Decode(filterProvider);
}
/// <summary>

View File

@@ -28,7 +28,7 @@
parts.Add(part);
}
public CrossReferenceTable Build(long firstCrossReferenceOffset, long offsetCorrection, ILog log)
public CrossReferenceTable Build(long firstCrossReferenceOffset, ILog log)
{
CrossReferenceType type = CrossReferenceType.Table;
DictionaryToken trailerDictionary = new DictionaryToken(new Dictionary<NameToken, IToken>());
@@ -65,7 +65,7 @@
break;
}
currentPart = parts.FirstOrDefault(x => x.Offset == prevBytePos || x.Offset == prevBytePos + offsetCorrection);
currentPart = parts.FirstOrDefault(x => x.Offset == prevBytePos);
if (currentPart == null)
{
log.Warn("Did not found XRef object pointed to by 'Prev' key at position " + prevBytePos);
@@ -88,7 +88,7 @@
// merge used and sorted XRef/trailer
foreach (long bPos in xrefSeqBytePos)
{
var currentObject = parts.First(x => x.Offset == bPos || x.Offset == bPos + offsetCorrection);
var currentObject = parts.First(x => x.Offset == bPos);
if (currentObject.Dictionary != null)
{
foreach (var entry in currentObject.Dictionary.Data)

View File

@@ -402,7 +402,7 @@
var decrypted = DecryptData(data, reference);
token = GetStringTokenFromDecryptedData(decrypted);
token = new StringToken(OtherEncodings.BytesAsLatin1String(decrypted));
break;
}
@@ -465,28 +465,6 @@
return token;
}
private static StringToken GetStringTokenFromDecryptedData(byte[] data)
{
if (data.Length >= 2)
{
if (data[0] == 0xFE && data[1] == 0xFF)
{
var str = Encoding.BigEndianUnicode.GetString(data).Substring(1);
return new StringToken(str, StringToken.Encoding.Utf16BE);
}
if (data[0] == 0xFF && data[1] == 0xFE)
{
var str = Encoding.Unicode.GetString(data).Substring(1);
return new StringToken(str, StringToken.Encoding.Utf16);
}
}
return new StringToken(OtherEncodings.BytesAsLatin1String(data), StringToken.Encoding.Iso88591);
}
private byte[] DecryptData(byte[] data, IndirectReference reference)
{
if (useAes && encryptionKey.Length == 32)

View File

@@ -1,25 +0,0 @@
namespace UglyToad.PdfPig.Filters
{
/// <summary>
/// Specifies the compression type to use with <see cref="T:UglyToad.PdfPig.Filters.CcittFaxDecoderStream" />.
/// </summary>
internal enum CcittFaxCompressionType
{
/// <summary>
/// Modified Huffman (MH) - Group 3 variation (T2)
/// </summary>
ModifiedHuffman,
/// <summary>
/// Modified Huffman (MH) - Group 3 (T4)
/// </summary>
Group3_1D,
/// <summary>
/// Modified Read (MR) - Group 3 (T4)
/// </summary>
Group3_2D,
/// <summary>
/// Modified Modified Read (MMR) - Group 4 (T6)
/// </summary>
Group4_2D
}
}

View File

@@ -1,121 +1,19 @@
namespace UglyToad.PdfPig.Filters
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Tokens;
using Util;
/// <summary>
/// Decodes image data that has been encoded using either Group 3 or Group 4.
///
/// Ported from https://github.com/apache/pdfbox/blob/714156a15ea6fcfe44ac09345b01e192cbd74450/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxFilter.java
/// </summary>
internal class CcittFaxDecodeFilter : IFilter
{
/// <inheritdoc />
public bool IsSupported { get; } = true;
/// <inheritdoc />
public byte[] Decode(IReadOnlyList<byte> input, DictionaryToken streamDictionary, int filterIndex)
{
var decodeParms = DecodeParameterResolver.GetFilterParameters(streamDictionary, filterIndex);
var cols = decodeParms.GetIntOrDefault(NameToken.Columns, 1728);
var rows = decodeParms.GetIntOrDefault(NameToken.Rows, 0);
var height = streamDictionary.GetIntOrDefault(NameToken.Height, NameToken.H, 0);
if (rows > 0 && height > 0)
{
// PDFBOX-771, PDFBOX-3727: rows in DecodeParms sometimes contains an incorrect value
rows = height;
}
else
{
// at least one of the values has to have a valid value
rows = Math.Max(rows, height);
}
var k = decodeParms.GetIntOrDefault(NameToken.K, 0);
var encodedByteAlign = decodeParms.GetBooleanOrDefault(NameToken.EncodedByteAlign, false);
var compressionType = DetermineCompressionType(input, k);
using (var stream = new CcittFaxDecoderStream(new MemoryStream(input.ToArray()), cols, compressionType, encodedByteAlign))
{
var arraySize = (cols + 7) / 8 * rows;
var decompressed = new byte[arraySize];
ReadFromDecoderStream(stream, decompressed);
// we expect black to be 1, if not invert the bitmap
var blackIsOne = decodeParms.GetBooleanOrDefault(NameToken.BlackIs1, false);
if (!blackIsOne)
{
InvertBitmap(decompressed);
}
return decompressed;
}
}
private static CcittFaxCompressionType DetermineCompressionType(IReadOnlyList<byte> input, int k)
{
if (k == 0)
{
var compressionType = CcittFaxCompressionType.Group3_1D; // Group 3 1D
if (input.Count < 20)
{
throw new InvalidOperationException("The format is invalid");
}
if (input[0] != 0 || (input[1] >> 4 != 1 && input[1] != 1))
{
// leading EOL (0b000000000001) not found, search further and
// try RLE if not found
compressionType = CcittFaxCompressionType.ModifiedHuffman;
var b = (short)(((input[0] << 8) + (input[1] & 0xff)) >> 4);
for (var i = 12; i < 160; i++)
{
b = (short)((b << 1) + ((input[(i / 8)] >> (7 - (i % 8))) & 0x01));
if ((b & 0xFFF) == 1)
{
return CcittFaxCompressionType.Group3_1D;
}
}
}
return compressionType;
}
if (k > 0)
{
// Group 3 2D
return CcittFaxCompressionType.Group3_2D;
}
return CcittFaxCompressionType.Group4_2D;
}
private static void ReadFromDecoderStream(CcittFaxDecoderStream decoderStream, byte[] result)
{
var pos = 0;
int read;
while ((read = decoderStream.Read(result, pos, result.Length - pos)) > -1)
{
pos += read;
if (pos >= result.Length)
{
break;
}
}
decoderStream.Close();
}
private static void InvertBitmap(byte[] bufferData)
{
for (int i = 0, c = bufferData.Length; i < c; i++)
{
bufferData[i] = (byte)(~bufferData[i] & 0xFF);
}
}
}
}
namespace UglyToad.PdfPig.Filters
{
using System;
using System.Collections.Generic;
using Tokens;
internal class CcittFaxDecodeFilter : IFilter
{
/// <inheritdoc />
public bool IsSupported { get; } = false;
/// <inheritdoc />
public byte[] Decode(IReadOnlyList<byte> input, DictionaryToken streamDictionary, int filterIndex)
{
throw new NotSupportedException("The CCITT Fax Filter for image data is not currently supported. " +
"Try accessing the raw compressed data directly.");
}
}
}

View File

@@ -1,774 +0,0 @@
namespace UglyToad.PdfPig.Filters
{
using System;
using System.IO;
using IO;
using Util;
/// <summary>
/// CCITT Modified Huffman RLE, Group 3 (T4) and Group 4 (T6) fax compression.
///
/// Ported from https://github.com/apache/pdfbox/blob/e644c29279e276bde14ce7a33bdeef0cb1001b3e/pdfbox/src/main/java/org/apache/pdfbox/filter/CCITTFaxDecoderStream.java
/// </summary>
internal class CcittFaxDecoderStream : StreamWrapper
{
// See TIFF 6.0 Specification, Section 10: "Modified Huffman Compression", page 43.
private readonly int columns;
private readonly byte[] decodedRow;
private readonly bool optionByteAligned;
private readonly CcittFaxCompressionType type;
private int decodedLength;
private int decodedPos;
private int[] changesReferenceRow;
private int[] changesCurrentRow;
private int changesReferenceRowCount;
private int changesCurrentRowCount;
private int lastChangingElement;
private int buffer = -1;
private int bufferPos = -1;
/// <summary>
/// Creates a CCITTFaxDecoderStream.
/// This constructor may be used for CCITT streams embedded in PDF files,
/// which use EncodedByteAlign.
/// </summary>
public CcittFaxDecoderStream(Stream stream, int columns, CcittFaxCompressionType type, bool byteAligned)
: base(stream)
{
this.columns = columns;
this.type = type;
// We know this is only used for b/w (1 bit)
decodedRow = new byte[(columns + 7) / 8];
changesReferenceRow = new int[columns + 2];
changesCurrentRow = new int[columns + 2];
optionByteAligned = byteAligned;
}
private void Fetch()
{
if (decodedPos >= decodedLength)
{
decodedLength = 0;
try
{
DecodeRow();
}
catch (InvalidOperationException)
{
if (decodedLength != 0)
{
throw;
}
// ..otherwise, just let client code try to read past the
// end of stream
decodedLength = -1;
}
decodedPos = 0;
}
}
private void Decode1D()
{
var index = 0;
var white = true;
changesCurrentRowCount = 0;
do
{
var completeRun = white ? DecodeRun(WhiteRunTree) : DecodeRun(BlackRunTree);
index += completeRun;
changesCurrentRow[changesCurrentRowCount++] = index;
// Flip color for next run
white = !white;
} while (index < columns);
}
private void Decode2D()
{
changesReferenceRowCount = changesCurrentRowCount;
var tmp = changesCurrentRow;
changesCurrentRow = changesReferenceRow;
changesReferenceRow = tmp;
var white = true;
var index = 0;
changesCurrentRowCount = 0;
mode: while (index < columns)
{
var node = CodeTree.Root;
while (true)
{
node = node.Walk(ReadBit());
if (node == null)
{
goto mode;
}
else if (node.IsLeaf)
{
switch (node.Value)
{
case VALUE_HMODE:
var runLength = DecodeRun(white ? WhiteRunTree : BlackRunTree);
index += runLength;
changesCurrentRow[changesCurrentRowCount++] = index;
runLength = DecodeRun(white ? BlackRunTree : WhiteRunTree);
index += runLength;
changesCurrentRow[changesCurrentRowCount++] = index;
break;
case VALUE_PASSMODE:
var pChangingElement = GetNextChangingElement(index, white) + 1;
if (pChangingElement >= changesReferenceRowCount)
{
index = columns;
}
else
{
index = changesReferenceRow[pChangingElement];
}
break;
default:
// Vertical mode (-3 to 3)
var vChangingElement = GetNextChangingElement(index, white);
if (vChangingElement >= changesReferenceRowCount || vChangingElement == -1)
{
index = columns + node.Value;
}
else
{
index = changesReferenceRow[vChangingElement] + node.Value;
}
changesCurrentRow[changesCurrentRowCount] = index;
changesCurrentRowCount++;
white = !white;
break;
}
goto mode;
}
}
}
}
private int GetNextChangingElement(int a0, bool white)
{
var start = (int)(lastChangingElement & 0xFFFF_FFFE) + (white ? 0 : 1);
if (start > 2)
{
start -= 2;
}
if (a0 == 0)
{
return start;
}
for (var i = start; i < changesReferenceRowCount; i += 2)
{
if (a0 < changesReferenceRow[i])
{
lastChangingElement = i;
return i;
}
}
return -1;
}
private void DecodeRowType2()
{
if (optionByteAligned)
{
ResetBuffer();
}
Decode1D();
}
private void DecodeRowType4()
{
if (optionByteAligned)
{
ResetBuffer();
}
eof: while (true)
{
// read till next EOL code
var node = EolOnlyTree.Root;
while (true)
{
node = node.Walk(ReadBit());
if (node == null)
{
goto eof;
}
if (node.IsLeaf)
{
goto done;
}
}
}
done:
if (type == CcittFaxCompressionType.Group3_1D || ReadBit())
{
Decode1D();
}
else
{
Decode2D();
}
}
private void DecodeRowType6()
{
if (optionByteAligned)
{
ResetBuffer();
}
Decode2D();
}
private void DecodeRow()
{
switch (type)
{
case CcittFaxCompressionType.ModifiedHuffman:
DecodeRowType2();
break;
case CcittFaxCompressionType.Group3_1D:
case CcittFaxCompressionType.Group3_2D:
DecodeRowType4();
break;
case CcittFaxCompressionType.Group4_2D:
DecodeRowType6();
break;
default:
throw new InvalidOperationException(type + " is not a supported compression type.");
}
var index = 0;
var white = true;
lastChangingElement = 0;
for (var i = 0; i <= changesCurrentRowCount; i++)
{
var nextChange = columns;
if (i != changesCurrentRowCount)
{
nextChange = changesCurrentRow[i];
}
if (nextChange > columns)
{
nextChange = columns;
}
var byteIndex = index / 8;
while (index % 8 != 0 && (nextChange - index) > 0)
{
decodedRow[byteIndex] |= (byte)(white ? 0 : 1 << (7 - ((index) % 8)));
index++;
}
if (index % 8 == 0)
{
byteIndex = index / 8;
var value = (byte)(white ? 0x00 : 0xff);
while ((nextChange - index) > 7)
{
decodedRow[byteIndex] = value;
index += 8;
++byteIndex;
}
}
while ((nextChange - index) > 0)
{
if (index % 8 == 0)
{
decodedRow[byteIndex] = 0;
}
decodedRow[byteIndex] |= (byte)(white ? 0 : 1 << (7 - ((index) % 8)));
index++;
}
white = !white;
}
if (index != columns)
{
throw new InvalidOperationException("Sum of run-lengths does not equal scan line width: " + index + " > " + columns);
}
decodedLength = (index + 7) / 8;
}
private int DecodeRun(Tree tree)
{
var total = 0;
var node = tree.Root;
while (true)
{
var bit = ReadBit();
node = node.Walk(bit);
if (node == null)
{
throw new InvalidOperationException("Unknown code in Huffman RLE stream");
}
if (node.IsLeaf)
{
total += node.Value;
if (node.Value >= 64)
{
node = tree.Root;
}
else if (node.Value >= 0)
{
return total;
}
else
{
return columns;
}
}
}
}
private void ResetBuffer()
{
bufferPos = -1;
}
private bool ReadBit()
{
if (bufferPos < 0 || bufferPos > 7)
{
buffer = Stream.ReadByte();
if (buffer == -1)
{
throw new InvalidOperationException("Unexpected end of Huffman RLE stream");
}
bufferPos = 0;
}
var isSet = ((buffer >> (7 - bufferPos)) & 1) == 1;
bufferPos++;
if (bufferPos > 7)
{
bufferPos = -1;
}
return isSet;
}
public override int ReadByte()
{
if (decodedLength < 0)
{
return 0x0;
}
if (decodedPos >= decodedLength)
{
Fetch();
if (decodedLength < 0)
{
return 0x0;
}
}
return decodedRow[decodedPos++] & 0xff;
}
public override int Read(byte[] b, int off, int len)
{
if (decodedLength < 0)
{
ArrayHelper.Fill(b, off, off + len, (byte)0x0);
return len;
}
if (decodedPos >= decodedLength)
{
Fetch();
if (decodedLength < 0)
{
ArrayHelper.Fill(b, off, off + len, (byte)0x0);
return len;
}
}
var read = Math.Min(decodedLength - decodedPos, len);
Array.Copy(decodedRow, decodedPos, b, off, read);
decodedPos += read;
return read;
}
private class Node
{
public Node Left { get; set; }
public Node Right { get; set; }
public int Value { get; set; }
public bool CanBeFill { get; set; }
public bool IsLeaf { get; set; }
public void Set(bool next, Node node)
{
if (!next)
{
Left = node;
}
else
{
Right = node;
}
}
public Node Walk(bool next)
{
return next ? Right : Left;
}
public override string ToString()
{
return $"[{nameof(IsLeaf)}={IsLeaf}, {nameof(Value)}={Value}, {nameof(CanBeFill)}={CanBeFill}]";
}
}
private class Tree
{
public Node Root { get; } = new Node();
public void Fill(int depth, int path, int value)
{
var current = Root;
for (var i = 0; i < depth; i++)
{
var bitPos = depth - 1 - i;
var isSet = ((path >> bitPos) & 1) == 1;
var next = current.Walk(isSet);
if (next == null)
{
next = new Node();
if (i == depth - 1)
{
next.Value = value;
next.IsLeaf = true;
}
if (path == 0)
{
next.CanBeFill = true;
}
current.Set(isSet, next);
}
else if (next.IsLeaf)
{
throw new InvalidOperationException("node is leaf, no other following");
}
current = next;
}
}
public void Fill(int depth, int path, Node node)
{
var current = Root;
for (var i = 0; i < depth; i++)
{
var bitPos = depth - 1 - i;
var isSet = ((path >> bitPos) & 1) == 1;
var next = current.Walk(isSet);
if (next == null)
{
if (i == depth - 1)
{
next = node;
}
else
{
next = new Node();
}
if (path == 0)
{
next.CanBeFill = true;
}
current.Set(isSet, next);
}
else if (next.IsLeaf)
{
throw new InvalidOperationException("node is leaf, no other following");
}
current = next;
}
}
}
private static readonly short[][] BLACK_CODES = new short[][] {
new short[]{ // 2 bits
0x2, 0x3,
},
new short[]{ // 3 bits
0x2, 0x3,
},
new short[]{ // 4 bits
0x2, 0x3,
},
new short[]{ // 5 bits
0x3,
},
new short[]{ // 6 bits
0x4, 0x5,
},
new short[]{ // 7 bits
0x4, 0x5, 0x7,
},
new short[]{ // 8 bits
0x4, 0x7,
},
new short[]{ // 9 bits
0x18,
},
new short[]{ // 10 bits
0x17, 0x18, 0x37, 0x8, 0xf,
},
new short[]{ // 11 bits
0x17, 0x18, 0x28, 0x37, 0x67, 0x68, 0x6c, 0x8, 0xc, 0xd,
},
new short[]{ // 12 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f, 0x24, 0x27, 0x28, 0x2b, 0x2c, 0x33,
0x34, 0x35, 0x37, 0x38, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x64, 0x65,
0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xd2, 0xd3,
0xd4, 0xd5, 0xd6, 0xd7, 0xda, 0xdb,
},
new short[]{ // 13 bits
0x4a, 0x4b, 0x4c, 0x4d, 0x52, 0x53, 0x54, 0x55, 0x5a, 0x5b, 0x64, 0x65, 0x6c, 0x6d, 0x72, 0x73,
0x74, 0x75, 0x76, 0x77,
}
};
private static readonly short[][] BLACK_RUN_LENGTHS = new short[][]{
new short[]{ // 2 bits
3, 2,
},
new short[]{ // 3 bits
1, 4,
},
new short[]{ // 4 bits
6, 5,
},
new short[]{ // 5 bits
7,
},
new short[]{ // 6 bits
9, 8,
},
new short[]{ // 7 bits
10, 11, 12,
},
new short[]{ // 8 bits
13, 14,
},
new short[]{ // 9 bits
15,
},
new short[]{ // 10 bits
16, 17, 0, 18, 64,
},
new short[]{ // 11 bits
24, 25, 23, 22, 19, 20, 21, 1792, 1856, 1920,
},
new short[]{ // 12 bits
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560, 52, 55, 56, 59, 60, 320, 384, 448, 53,
54, 50, 51, 44, 45, 46, 47, 57, 58, 61, 256, 48, 49, 62, 63, 30, 31, 32, 33, 40, 41, 128, 192, 26,
27, 28, 29, 34, 35, 36, 37, 38, 39, 42, 43,
},
new short[]{ // 13 bits
640, 704, 768, 832, 1280, 1344, 1408, 1472, 1536, 1600, 1664, 1728, 512, 576, 896, 960, 1024, 1088,
1152, 1216,
}
};
private static readonly short[][] WHITE_CODES = new short[][]{
new short[]{ // 4 bits
0x7, 0x8, 0xb, 0xc, 0xe, 0xf,
},
new short[]{ // 5 bits
0x12, 0x13, 0x14, 0x1b, 0x7, 0x8,
},
new short[]{ // 6 bits
0x17, 0x18, 0x2a, 0x2b, 0x3, 0x34, 0x35, 0x7, 0x8,
},
new short[]{ // 7 bits
0x13, 0x17, 0x18, 0x24, 0x27, 0x28, 0x2b, 0x3, 0x37, 0x4, 0x8, 0xc,
},
new short[]{ // 8 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1a, 0x1b, 0x2, 0x24, 0x25, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d,
0x3, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x4, 0x4a, 0x4b, 0x5, 0x52, 0x53, 0x54, 0x55, 0x58, 0x59,
0x5a, 0x5b, 0x64, 0x65, 0x67, 0x68, 0xa, 0xb,
},
new short[]{ // 9 bits
0x98, 0x99, 0x9a, 0x9b, 0xcc, 0xcd, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
},
new short[]{ // 10 bits
},
new short[]{ // 11 bits
0x8, 0xc, 0xd,
},
new short[]{ // 12 bits
0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x1c, 0x1d, 0x1e, 0x1f,
}
};
private static readonly short[][] WHITE_RUN_LENGTHS = new short[][]{
new short[]{ // 4 bits
2, 3, 4, 5, 6, 7,
},
new short[]{ // 5 bits
128, 8, 9, 64, 10, 11,
},
new short[]{ // 6 bits
192, 1664, 16, 17, 13, 14, 15, 1, 12,
},
new short[]{ // 7 bits
26, 21, 28, 27, 18, 24, 25, 22, 256, 23, 20, 19,
},
new short[]{ // 8 bits
33, 34, 35, 36, 37, 38, 31, 32, 29, 53, 54, 39, 40, 41, 42, 43, 44, 30, 61, 62, 63, 0, 320, 384, 45,
59, 60, 46, 49, 50, 51, 52, 55, 56, 57, 58, 448, 512, 640, 576, 47, 48,
},
new short[]{ // 9 bits
1472, 1536, 1600, 1728, 704, 768, 832, 896, 960, 1024, 1088, 1152, 1216, 1280, 1344, 1408,
},
new short[]{ // 10 bits
},
new short[]{ // 11 bits
1792, 1856, 1920,
},
new short[]{ // 12 bits
1984, 2048, 2112, 2176, 2240, 2304, 2368, 2432, 2496, 2560,
}
};
private static readonly Node EOL;
private static readonly Node FILL;
private static readonly Tree BlackRunTree;
private static readonly Tree WhiteRunTree;
private static readonly Tree EolOnlyTree;
private static readonly Tree CodeTree;
const int VALUE_EOL = -2000;
const int VALUE_FILL = -1000;
const int VALUE_PASSMODE = -3000;
const int VALUE_HMODE = -4000;
static CcittFaxDecoderStream()
{
EOL = new Node
{
IsLeaf = true,
Value = VALUE_EOL
};
FILL = new Node
{
Value = VALUE_FILL
};
FILL.Left = FILL;
FILL.Right = EOL;
EolOnlyTree = new Tree();
EolOnlyTree.Fill(12, 0, FILL);
EolOnlyTree.Fill(12, 1, EOL);
BlackRunTree = new Tree();
for (var i = 0; i < BLACK_CODES.Length; i++)
{
for (var j = 0; j < BLACK_CODES[i].Length; j++)
{
BlackRunTree.Fill(i + 2, BLACK_CODES[i][j], BLACK_RUN_LENGTHS[i][j]);
}
}
BlackRunTree.Fill(12, 0, FILL);
BlackRunTree.Fill(12, 1, EOL);
WhiteRunTree = new Tree();
for (var i = 0; i < WHITE_CODES.Length; i++)
{
for (var j = 0; j < WHITE_CODES[i].Length; j++)
{
WhiteRunTree.Fill(i + 4, WHITE_CODES[i][j], WHITE_RUN_LENGTHS[i][j]);
}
}
WhiteRunTree.Fill(12, 0, FILL);
WhiteRunTree.Fill(12, 1, EOL);
CodeTree = new Tree();
CodeTree.Fill(4, 1, VALUE_PASSMODE); // pass mode
CodeTree.Fill(3, 1, VALUE_HMODE); // H mode
CodeTree.Fill(1, 1, 0); // V(0)
CodeTree.Fill(3, 3, 1); // V_R(1)
CodeTree.Fill(6, 3, 2); // V_R(2)
CodeTree.Fill(7, 3, 3); // V_R(3)
CodeTree.Fill(3, 2, -1); // V_L(1)
CodeTree.Fill(6, 2, -2); // V_L(2)
CodeTree.Fill(7, 2, -3); // V_L(3)
}
}
}

View File

@@ -2,9 +2,8 @@
{
using System;
using System.Collections.Generic;
using Tokens;
using UglyToad.PdfPig.Util;
using Tokens;
internal static class DecodeParameterResolver
{
public static DictionaryToken GetFilterParameters(DictionaryToken streamDictionary, int index)
@@ -19,9 +18,9 @@
throw new ArgumentOutOfRangeException(nameof(index), "Index must be 0 or greater");
}
var filter = streamDictionary.GetObjectOrDefault(NameToken.Filter, NameToken.F);
var filter = GetDictionaryObject(streamDictionary, NameToken.Filter, NameToken.F);
var parameters = streamDictionary.GetObjectOrDefault(NameToken.DecodeParms, NameToken.Dp);
var parameters = GetDictionaryObject(streamDictionary, NameToken.DecodeParms, NameToken.Dp);
switch (filter)
{
@@ -34,7 +33,7 @@
case ArrayToken array:
if (parameters is ArrayToken arr)
{
if (index < arr.Data.Count && arr.Data[index] is DictionaryToken dictionary)
if (index < arr.Data.Count && array.Data[index] is DictionaryToken dictionary)
{
return dictionary;
}
@@ -46,5 +45,20 @@
return new DictionaryToken(new Dictionary<NameToken, IToken>());
}
private static IToken GetDictionaryObject(DictionaryToken dictionary, NameToken first, NameToken second)
{
if (dictionary.TryGet(first, out var token))
{
return token;
}
if (dictionary.TryGet(second, out token))
{
return token;
}
return null;
}
}
}

View File

@@ -1,124 +1,121 @@
namespace UglyToad.PdfPig.Filters
{
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Tokens;
using UglyToad.PdfPig.Util;
/// <inheritdoc />
/// <summary>
/// The default implementation of the <see cref="T:UglyToad.PdfPig.Filters.IFilterProvider" />.
/// </summary>
public class DefaultFilterProvider : IFilterProvider
{
private readonly IReadOnlyDictionary<string, IFilter> filterInstances;
/// <summary>
/// The single instance of this provider.
/// </summary>
public static readonly IFilterProvider Instance = new DefaultFilterProvider();
private DefaultFilterProvider()
{
var ascii85 = new Ascii85Filter();
var asciiHex = new AsciiHexDecodeFilter();
var ccitt = new CcittFaxDecodeFilter();
var dct = new DctDecodeFilter();
var flate = new FlateFilter();
var jbig2 = new Jbig2DecodeFilter();
var jpx = new JpxDecodeFilter();
var runLength = new RunLengthFilter();
var lzw = new LzwFilter();
filterInstances = new Dictionary<string, IFilter>
{
{NameToken.Ascii85Decode.Data, ascii85},
{NameToken.Ascii85DecodeAbbreviation.Data, ascii85},
{NameToken.AsciiHexDecode.Data, asciiHex},
{NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex},
{NameToken.CcittfaxDecode.Data, ccitt},
{NameToken.CcittfaxDecodeAbbreviation.Data, ccitt},
{NameToken.DctDecode.Data, dct},
{NameToken.DctDecodeAbbreviation.Data, dct},
{NameToken.FlateDecode.Data, flate},
{NameToken.FlateDecodeAbbreviation.Data, flate},
{NameToken.Jbig2Decode.Data, jbig2},
{NameToken.JpxDecode.Data, jpx},
{NameToken.RunLengthDecode.Data, runLength},
{NameToken.RunLengthDecodeAbbreviation.Data, runLength},
{NameToken.LzwDecode, lzw},
{NameToken.LzwDecodeAbbreviation, lzw}
};
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
{
if (dictionary == null)
{
throw new ArgumentNullException(nameof(dictionary));
}
var token = dictionary.GetObjectOrDefault(NameToken.Filter, NameToken.F);
if (token == null)
{
return EmptyArray<IFilter>.Instance;
}
switch (token)
{
case ArrayToken filters:
var result = new IFilter[filters.Data.Count];
for (var i = 0; i < filters.Data.Count; i++)
{
var filterToken = filters.Data[i];
var filterName = ((NameToken) filterToken).Data;
result[i] = GetFilterStrict(filterName);
}
return result;
case NameToken name:
return new[] { GetFilterStrict(name.Data) };
default:
throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}.");
}
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetNamedFilters(IReadOnlyList<NameToken> names)
{
if (names == null)
{
throw new ArgumentNullException(nameof(names));
}
var result = new List<IFilter>();
foreach (var name in names)
{
result.Add(GetFilterStrict(name));
}
return result;
}
private IFilter GetFilterStrict(string name)
{
if (!filterInstances.TryGetValue(name, out var factory))
{
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
}
return factory;
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetAllFilters()
{
return filterInstances.Values.Distinct().ToList();
}
}
namespace UglyToad.PdfPig.Filters
{
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Tokens;
/// <inheritdoc />
/// <summary>
/// The default implementation of the <see cref="T:UglyToad.PdfPig.Filters.IFilterProvider" />.
/// </summary>
public class DefaultFilterProvider : IFilterProvider
{
private readonly IReadOnlyDictionary<string, IFilter> filterInstances;
/// <summary>
/// The single instance of this provider.
/// </summary>
public static readonly IFilterProvider Instance = new DefaultFilterProvider();
private DefaultFilterProvider()
{
var ascii85 = new Ascii85Filter();
var asciiHex = new AsciiHexDecodeFilter();
var ccitt = new CcittFaxDecodeFilter();
var dct = new DctDecodeFilter();
var flate = new FlateFilter();
var jbig2 = new Jbig2DecodeFilter();
var jpx = new JpxDecodeFilter();
var runLength = new RunLengthFilter();
var lzw = new LzwFilter();
filterInstances = new Dictionary<string, IFilter>
{
{NameToken.Ascii85Decode.Data, ascii85},
{NameToken.Ascii85DecodeAbbreviation.Data, ascii85},
{NameToken.AsciiHexDecode.Data, asciiHex},
{NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex},
{NameToken.CcittfaxDecode.Data, ccitt},
{NameToken.CcittfaxDecodeAbbreviation.Data, ccitt},
{NameToken.DctDecode.Data, dct},
{NameToken.DctDecodeAbbreviation.Data, dct},
{NameToken.FlateDecode.Data, flate},
{NameToken.FlateDecodeAbbreviation.Data, flate},
{NameToken.Jbig2Decode.Data, jbig2},
{NameToken.JpxDecode.Data, jpx},
{NameToken.RunLengthDecode.Data, runLength},
{NameToken.RunLengthDecodeAbbreviation.Data, runLength},
{NameToken.LzwDecode, lzw},
{NameToken.LzwDecodeAbbreviation, lzw}
};
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
{
if (dictionary == null)
{
throw new ArgumentNullException(nameof(dictionary));
}
if (!dictionary.TryGet(NameToken.Filter, out var token))
{
return EmptyArray<IFilter>.Instance;
}
switch (token)
{
case ArrayToken filters:
var result = new IFilter[filters.Data.Count];
for (var i = 0; i < filters.Data.Count; i++)
{
var filterToken = filters.Data[i];
var filterName = ((NameToken) filterToken).Data;
result[i] = GetFilterStrict(filterName);
}
return result;
case NameToken name:
return new[] { GetFilterStrict(name.Data) };
default:
throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}.");
}
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetNamedFilters(IReadOnlyList<NameToken> names)
{
if (names == null)
{
throw new ArgumentNullException(nameof(names));
}
var result = new List<IFilter>();
foreach (var name in names)
{
result.Add(GetFilterStrict(name));
}
return result;
}
private IFilter GetFilterStrict(string name)
{
if (!filterInstances.TryGetValue(name, out var factory))
{
throw new NotSupportedException($"The filter with the name {name} is not supported yet. Please raise an issue.");
}
return factory;
}
/// <inheritdoc />
public IReadOnlyList<IFilter> GetAllFilters()
{
return filterInstances.Values.Distinct().ToList();
}
}
}

View File

@@ -1,75 +0,0 @@
namespace UglyToad.PdfPig.Filters
{
using System;
using System.Collections.Generic;
using System.Linq;
using Core;
using Parser.Parts;
using Tokenization.Scanner;
using Tokens;
using UglyToad.PdfPig.Util;
internal class FilterProviderWithLookup : ILookupFilterProvider
{
private readonly IFilterProvider inner;
public FilterProviderWithLookup(IFilterProvider inner)
{
this.inner = inner;
}
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
=> inner.GetFilters(dictionary);
public IReadOnlyList<IFilter> GetNamedFilters(IReadOnlyList<NameToken> names)
=> inner.GetNamedFilters(names);
public IReadOnlyList<IFilter> GetAllFilters()
=> inner.GetAllFilters();
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary, IPdfTokenScanner scanner)
{
if (dictionary == null)
{
throw new ArgumentNullException(nameof(dictionary));
}
var token = dictionary.GetObjectOrDefault(NameToken.Filter, NameToken.F);
if (token == null)
{
return EmptyArray<IFilter>.Instance;
}
switch (token)
{
case ArrayToken filters:
var result = new NameToken[filters.Data.Count];
for (var i = 0; i < filters.Data.Count; i++)
{
var filterToken = filters.Data[i];
var filterName = (NameToken)filterToken;
result[i] = filterName;
}
return GetNamedFilters(result);
case NameToken name:
return GetNamedFilters(new[] {name});
case IndirectReferenceToken irt:
if (DirectObjectFinder.TryGet<NameToken>(irt, scanner, out var indirectName))
{
return GetNamedFilters(new []{ indirectName });
}
else if (DirectObjectFinder.TryGet<ArrayToken>(irt, scanner, out var indirectArray))
{
return GetNamedFilters(indirectArray.Data.Select(x => (NameToken) x).ToList());
}
else
{
throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}.");
}
default:
throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}.");
}
}
}
}

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Filters
{
using System.Collections.Generic;
using Tokenization.Scanner;
using Tokens;
/// <summary>
@@ -24,9 +23,4 @@
/// </summary>
IReadOnlyList<IFilter> GetAllFilters();
}
internal interface ILookupFilterProvider : IFilterProvider
{
IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary, IPdfTokenScanner scanner);
}
}

View File

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

View File

@@ -1,111 +0,0 @@
namespace UglyToad.PdfPig.Graphics.Colors
{
using System;
using UglyToad.PdfPig.Util;
/// <summary>
/// Transformer for CIEBased color spaces.
///
/// In addition to the PDF spec itself, the transformation implementation is based on the descriptions in:
/// https://en.wikipedia.org/wiki/SRGB#The_forward_transformation_(CIE_XYZ_to_sRGB) and
/// http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_RGB.html
/// </summary>
internal class CIEBasedColorSpaceTransformer
{
private readonly RGBWorkingSpace destinationWorkingSpace;
private readonly Matrix3x3 transformationMatrix;
private readonly ChromaticAdaptation chromaticAdaptation;
// These properties control how the color is translated from ABC to XYZ:
public Func<(double A, double B, double C), (double A, double B, double C)> DecoderABC { get; set; } = color => color;
public Func<(double L, double M, double N), (double L, double M, double N)> DecoderLMN { get; set; } = color => color;
public Matrix3x3 MatrixABC { get; set; } = Matrix3x3.Identity;
public Matrix3x3 MatrixLMN { get; set; } = Matrix3x3.Identity;
public CIEBasedColorSpaceTransformer((double X, double Y, double Z) sourceReferenceWhite, RGBWorkingSpace destinationWorkingSpace)
{
this.destinationWorkingSpace = destinationWorkingSpace;
// Create an adapter capable of adapting from one reference white to another
chromaticAdaptation = new ChromaticAdaptation(sourceReferenceWhite, destinationWorkingSpace.ReferenceWhite);
// Construct the transformation matrix capable of transforming from XYZ of the source color space
// to RGB of the destination color space
var xr = destinationWorkingSpace.RedPrimary.x;
var yr = destinationWorkingSpace.RedPrimary.y;
var xg = destinationWorkingSpace.GreenPrimary.x;
var yg = destinationWorkingSpace.GreenPrimary.y;
var xb = destinationWorkingSpace.BluePrimary.x;
var yb = destinationWorkingSpace.BluePrimary.y;
var Xr = xr / yr;
var Yr = 1;
var Zr = (1 - xr - yr) / yr;
var Xg = xg / yg;
var Yg = 1;
var Zg = (1 - xg - yg) / yg;
var Xb = xb / yb;
var Yb = 1;
var Zb = (1 - xb - yb) / yb;
var mXYZ = new Matrix3x3(
Xr, Xg, Xb,
Yr, Yg, Yb,
Zr, Zg, Zb).Inverse();
var S = mXYZ.Multiply(destinationWorkingSpace.ReferenceWhite);
var Sr = S.Item1;
var Sg = S.Item2;
var Sb = S.Item3;
var M = new Matrix3x3(
Sr * Xr, Sg * Xg, Sb * Xb,
Sr * Yr, Sg * Yg, Sb * Yb,
Sr * Zr, Sg * Zg, Sb * Zb);
transformationMatrix = M.Inverse();
}
/// <summary>
/// Transforms the supplied ABC color to the RGB color of the <see cref="RGBWorkingSpace"/>
/// that was supplied to this <see cref="CIEBasedColorSpaceTransformer"/> as the destination
/// workspace.
/// A, B and C represent red, green and blue calibrated color values in the range 0 to 1.
/// </summary>
public (double R, double G, double B) TransformToRGB((double A, double B, double C) color)
{
var xyz = TransformToXYZ(color);
var adaptedColor = chromaticAdaptation.Transform(xyz);
var rgb = transformationMatrix.Multiply(adaptedColor);
var gammaCorrectedR = destinationWorkingSpace.GammaCorrection(rgb.Item1);
var gammaCorrectedG = destinationWorkingSpace.GammaCorrection(rgb.Item2);
var gammaCorrectedB = destinationWorkingSpace.GammaCorrection(rgb.Item3);
return (Clamp(gammaCorrectedR), Clamp(gammaCorrectedG), Clamp(gammaCorrectedB));
}
private (double X, double Y, double Z) TransformToXYZ((double A, double B, double C) color)
{
var decodedABC = DecoderABC(color);
var lmn = MatrixABC.Multiply(decodedABC);
var decodedLMN = DecoderLMN(lmn);
var xyz = MatrixLMN.Multiply(decodedLMN);
return xyz;
}
private static double Clamp(double value)
{
// Force value into range [0..1]
return value < 0 ? 0 : value > 1 ? 1 : value;
}
}
}

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