mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
5 Commits
v0.1.5
...
object-cop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487e368e9e | ||
|
|
704c56285c | ||
|
|
a190653683 | ||
|
|
8f0326a818 | ||
|
|
5a98b21c2f |
24
.github/workflows/build_and_test.yml
vendored
24
.github/workflows/build_and_test.yml
vendored
@@ -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
|
||||
53
.github/workflows/nightly_release.yml
vendored
53
.github/workflows/nightly_release.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -244,4 +244,3 @@ _Pvt_Extensions
|
||||
/src/CodeCoverage/OpenCover.4.6.519
|
||||
/src/test-results.xml
|
||||
/tools/benchmark
|
||||
/tools/ConsoleRunner/Properties/launchSettings.json
|
||||
|
||||
@@ -193,10 +193,10 @@ PdfPig also comes with some tools for document layout analysis such as the Recur
|
||||
|
||||
An example of the output of the Recursive XY Cut algorithm viewed in an external viewer such as [LayoutEvalGUI](https://www.primaresearch.org/tools/PerformanceEvaluation) is shown below:
|
||||
|
||||

|
||||

|
||||
|
||||
See the [document layout analysis](https://github.com/UglyToad/PdfPig/wiki/Document-Layout-Analysis) page on the wiki for full details.
|
||||
|
||||
## Credit ##
|
||||
|
||||
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.
|
||||
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.
|
||||
@@ -49,7 +49,6 @@
|
||||
private double width;
|
||||
/// <summary>
|
||||
/// Width of the rectangle.
|
||||
/// <para>A positive number.</para>
|
||||
/// </summary>
|
||||
public double Width
|
||||
{
|
||||
@@ -67,7 +66,6 @@
|
||||
private double height;
|
||||
/// <summary>
|
||||
/// Height of the rectangle.
|
||||
/// <para>A positive number.</para>
|
||||
/// </summary>
|
||||
public double Height
|
||||
{
|
||||
@@ -199,16 +197,13 @@
|
||||
var cos = Math.Cos(t);
|
||||
var sin = Math.Sin(t);
|
||||
|
||||
var inverseRotation = new TransformationMatrix(
|
||||
var inverseRotation = new TransformationMatrix(
|
||||
cos, -sin, 0,
|
||||
sin, cos, 0,
|
||||
0, 0, 1);
|
||||
|
||||
// Using Abs as a proxy for Euclidean distance in 1D
|
||||
// as it might happen that points have negative coordinates.
|
||||
var bl = inverseRotation.Transform(BottomLeft);
|
||||
width = Math.Abs(inverseRotation.Transform(BottomRight).X - bl.X);
|
||||
height = Math.Abs(inverseRotation.Transform(TopLeft).Y - bl.Y);
|
||||
width = inverseRotation.Transform(BottomRight).X - inverseRotation.Transform(BottomLeft).X;
|
||||
height = inverseRotation.Transform(TopLeft).Y - inverseRotation.Transform(BottomLeft).Y;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -160,9 +160,9 @@
|
||||
throw new ArgumentNullException("LineTo(): currentPosition is null.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a rectangle following the pdf specification (m, l, l, l, c) path. A new subpath is created.
|
||||
/// Adds 4 <see cref="Line"/>s forming a rectangle to the path.
|
||||
/// </summary>
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
@@ -174,6 +174,8 @@
|
||||
CloseSubpath(); // h
|
||||
IsDrawnAsRectangle = true;
|
||||
}
|
||||
|
||||
internal void QuadraticCurveTo(double x1, double y1, double x2, double y2) { }
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="BezierCurve"/> to the path.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -76,13 +76,6 @@
|
||||
AddAdobeFontMetrics("Times,Italic", "Times-Italic");
|
||||
AddAdobeFontMetrics("Times,Bold", "Times-Bold");
|
||||
AddAdobeFontMetrics("Times,BoldItalic", "Times-BoldItalic");
|
||||
|
||||
// Additional names for Arial
|
||||
AddAdobeFontMetrics("ArialMT", "Helvetica");
|
||||
AddAdobeFontMetrics("Arial-ItalicMT", "Helvetica-Oblique");
|
||||
AddAdobeFontMetrics("Arial-BoldMT", "Helvetica-Bold");
|
||||
AddAdobeFontMetrics("Arial-BoldMT,Bold", "Helvetica-Bold");
|
||||
AddAdobeFontMetrics("Arial-BoldItalicMT", "Helvetica-BoldOblique");
|
||||
}
|
||||
|
||||
private static void AddAdobeFontMetrics(string fontName, Standard14Font? type = null)
|
||||
@@ -150,7 +143,7 @@
|
||||
{
|
||||
return Standard14Cache[BuilderTypesToNames[fontType]];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a font with this name is a standard 14 font.
|
||||
/// </summary>
|
||||
@@ -166,7 +159,7 @@
|
||||
{
|
||||
return new HashSet<string>(Standard14Names);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the official Standard 14 name of the actual font which the given font name maps to.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,19 +22,10 @@
|
||||
}
|
||||
|
||||
var strings = new TrueTypeNameRecord[count];
|
||||
var offset = header.Offset + stringOffset;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
strings[i] = GetTrueTypeNameRecord(names[i], data, offset);
|
||||
}
|
||||
var nameRecord = names[i];
|
||||
|
||||
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
private static TrueTypeNameRecord GetTrueTypeNameRecord(NameRecordBuilder nameRecord, TrueTypeDataBytes data, uint offset)
|
||||
{
|
||||
try
|
||||
{
|
||||
var encoding = OtherEncodings.Iso88591;
|
||||
|
||||
switch (nameRecord.PlatformId)
|
||||
@@ -71,27 +62,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
var position = offset + nameRecord.Offset;
|
||||
|
||||
if (position >= data.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var position = header.Offset + stringOffset + nameRecord.Offset;
|
||||
|
||||
data.Seek(position);
|
||||
|
||||
if (data.TryReadString(nameRecord.Length, encoding, out var str))
|
||||
{
|
||||
return nameRecord.ToNameRecord(str);
|
||||
}
|
||||
var str = data.ReadString(nameRecord.Length, encoding);
|
||||
|
||||
// Damaged font
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
strings[i] = nameRecord.ToNameRecord(str);
|
||||
}
|
||||
|
||||
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
private static string GetName(int nameId, TrueTypeNameRecord[] names)
|
||||
@@ -104,7 +84,7 @@
|
||||
{
|
||||
var name = names[i];
|
||||
|
||||
if (name is null || name.NameId != nameId)
|
||||
if (name.NameId != nameId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Tables;
|
||||
|
||||
internal static class TableParser
|
||||
@@ -14,20 +13,6 @@
|
||||
|
||||
public static T Parse<T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITrueTypeTable
|
||||
{
|
||||
//checksum https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
|
||||
uint sum = 0;
|
||||
var nLongs = (table.Length + 3) / 4;
|
||||
data.Seek(table.Offset);
|
||||
while (nLongs-- > 0)
|
||||
{
|
||||
sum += (uint)data.ReadSignedInt();
|
||||
}
|
||||
|
||||
if (sum != table.CheckSum)
|
||||
{
|
||||
Trace.TraceWarning("Table with invalid checksum found in TrueType font file.");
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(CMapTable))
|
||||
{
|
||||
return (T) (object) CMapTableParser.Parse(table, data, register);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
string any = null;
|
||||
foreach (var nameRecord in NameRecords)
|
||||
{
|
||||
if (nameRecord is null || nameRecord.NameId != 6)
|
||||
if (nameRecord.NameId != 6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,10 +203,7 @@
|
||||
for (var i = 0; i < namesLength; i++)
|
||||
{
|
||||
var numberOfCharacters = data.ReadByte();
|
||||
if (data.TryReadString(numberOfCharacters, Encoding.UTF8, out var str))
|
||||
{
|
||||
nameArray[i] = str;
|
||||
}
|
||||
nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core;
|
||||
|
||||
@@ -80,33 +81,22 @@
|
||||
/// </summary>
|
||||
public string ReadTag()
|
||||
{
|
||||
if (TryReadString(4, Encoding.UTF8, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not read Tag from TrueType file at {inputBytes.CurrentOffset}.");
|
||||
return ReadString(4, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see langword="string"/> of the given number of bytes in length with the specified encoding.
|
||||
/// </summary>
|
||||
public bool TryReadString(int bytesToRead, Encoding encoding, out string result)
|
||||
public string ReadString(int bytesToRead, Encoding encoding)
|
||||
{
|
||||
result = null;
|
||||
if (encoding == null)
|
||||
{
|
||||
return false;
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
byte[] data = new byte[bytesToRead];
|
||||
if (ReadBuffered(data, bytesToRead))
|
||||
{
|
||||
result = encoding.GetString(data, 0, data.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
ReadBuffered(data, bytesToRead);
|
||||
return encoding.GetString(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -244,15 +234,13 @@
|
||||
return $"@: {Position} of {inputBytes.Length} bytes.";
|
||||
}
|
||||
|
||||
private bool ReadBuffered(byte[] buffer, int length)
|
||||
private void ReadBuffered(byte[] buffer, int length)
|
||||
{
|
||||
var read = inputBytes.Read(buffer, length);
|
||||
if (read < length)
|
||||
{
|
||||
return false;
|
||||
throw new EndOfStreamException($"Could not read a buffer of {length} bytes.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction;
|
||||
|
||||
/// <summary>
|
||||
/// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then
|
||||
@@ -25,13 +23,13 @@
|
||||
|
||||
public static bool TakeFromStackBottom { get; } = false;
|
||||
public static bool ClearsOperandStack { get; } = false;
|
||||
|
||||
|
||||
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var index = (int)context.Stack.PopTop();
|
||||
|
||||
var index = (int) context.Stack.PopTop();
|
||||
|
||||
// What it should do
|
||||
var numberOfArguments = (int)context.Stack.PopTop();
|
||||
var otherSubroutineArguments = new List<double>(numberOfArguments);
|
||||
@@ -44,44 +42,17 @@
|
||||
{
|
||||
// Other subrs 0-2 implement flex
|
||||
case FlexEnd:
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/2c23d8b4e3ad61852f0b6ee2b95b907eefba1fcf/fontbox/src/main/java/org/apache/fontbox/cff/Type1CharString.java#L339
|
||||
context.IsFlexing = false;
|
||||
if (context.FlexPoints.Count < 7)
|
||||
{
|
||||
throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
}
|
||||
{
|
||||
context.IsFlexing = false;
|
||||
// TODO: I don't really care about flexpoints, but we should probably handle them... one day.
|
||||
//if (context.FlexPoints.Count < 7)
|
||||
//{
|
||||
// throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
//}
|
||||
|
||||
// reference point is relative to start point
|
||||
PdfPoint reference = context.FlexPoints[0];
|
||||
reference = reference.Translate(context.CurrentPosition.X, context.CurrentPosition.Y);
|
||||
|
||||
// first point is relative to reference point
|
||||
PdfPoint first = context.FlexPoints[1];
|
||||
first = first.Translate(reference.X, reference.Y);
|
||||
|
||||
// make the first point relative to the start point
|
||||
first = first.Translate(-context.CurrentPosition.X, -context.CurrentPosition.Y);
|
||||
|
||||
context.Stack.Push(first.X);
|
||||
context.Stack.Push(first.Y);
|
||||
context.Stack.Push(context.FlexPoints[2].X);
|
||||
context.Stack.Push(context.FlexPoints[2].Y);
|
||||
context.Stack.Push(context.FlexPoints[3].X);
|
||||
context.Stack.Push(context.FlexPoints[3].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.Stack.Push(context.FlexPoints[4].X);
|
||||
context.Stack.Push(context.FlexPoints[4].Y);
|
||||
context.Stack.Push(context.FlexPoints[5].X);
|
||||
context.Stack.Push(context.FlexPoints[5].Y);
|
||||
context.Stack.Push(context.FlexPoints[6].X);
|
||||
context.Stack.Push(context.FlexPoints[6].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
case FlexBegin:
|
||||
Debug.Assert(otherSubroutineArguments.Count == 0, "Flex begin should have no arguments.");
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
|
||||
/// <para>This establishes the current point for a subsequent relative path building command.
|
||||
/// The 'setcurrentpoint' command is used only in conjunction with results from 'OtherSubrs' procedures.</para>
|
||||
/// </summary>
|
||||
internal static class SetCurrentPointCommand
|
||||
{
|
||||
@@ -24,8 +22,8 @@
|
||||
var x = context.Stack.PopBottom();
|
||||
var y = context.Stack.PopBottom();
|
||||
|
||||
//context.CurrentPosition = new PdfPoint(x, y);
|
||||
// TODO: need to investigate why odd behavior when the current point is actualy set.
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
|
||||
context.Stack.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var deltaX = context.Stack.PopBottom();
|
||||
var x = context.Stack.PopBottom();
|
||||
|
||||
var actualX = context.CurrentPosition.X + x;
|
||||
var y = context.CurrentPosition.Y;
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, 0));
|
||||
// TODO: flex support
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y;
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
context.CurrentPosition = new PdfPoint(actualX, y);
|
||||
context.Path.MoveTo(actualX, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
|
||||
{
|
||||
using Core;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Vertical move to. Moves relative to the current point.
|
||||
@@ -24,8 +23,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(0, deltaY));
|
||||
// TODO: flex commands
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/// Sets left sidebearing and the character width vector.
|
||||
/// This command also sets the current point to(sbx, sby), but does not place the point in the character path.
|
||||
/// </summary>
|
||||
internal static class SbwCommand
|
||||
internal class SbwCommand
|
||||
{
|
||||
public const string Name = "sbw";
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
public CharStringStack PostscriptStack { get; } = new CharStringStack();
|
||||
|
||||
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
||||
public IReadOnlyList<PdfPoint> FlexPoints { get; }
|
||||
|
||||
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
||||
Func<int, PdfSubpath> characterByIndexFactory,
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
public void AddFlexPoint(PdfPoint point)
|
||||
{
|
||||
FlexPoints.Add(point);
|
||||
|
||||
}
|
||||
|
||||
public PdfSubpath GetCharacter(int characterCode)
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
public void ClearFlexPoints()
|
||||
{
|
||||
FlexPoints.Clear();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -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)))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
using System.Linq;
|
||||
using PdfFonts.Cmap;
|
||||
using PdfPig.Tokens;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.PdfFonts.Parser.Parts;
|
||||
using UglyToad.PdfPig.Tokenization.Scanner;
|
||||
using Xunit;
|
||||
|
||||
public class CodespaceRangeTests
|
||||
@@ -103,29 +100,6 @@
|
||||
Assert.True(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorspaceParserError()
|
||||
{
|
||||
var parser = new CodespaceRangeParser();
|
||||
var byteArrayInput = new ByteArrayInputBytes(OtherEncodings.StringAsLatin1Bytes("1 begincodespacerange\nendcodespacerange"));
|
||||
var tokenScanner = new CoreTokenScanner(byteArrayInput);
|
||||
|
||||
Assert.True(tokenScanner.MoveNext());
|
||||
Assert.True(tokenScanner.CurrentToken is NumericToken);
|
||||
var numeric = (NumericToken) tokenScanner.CurrentToken;
|
||||
|
||||
Assert.True(tokenScanner.MoveNext());
|
||||
Assert.True(tokenScanner.CurrentToken is OperatorToken);
|
||||
var opToken = (OperatorToken)tokenScanner.CurrentToken;
|
||||
Assert.Equal("begincodespacerange", opToken.Data);
|
||||
|
||||
var cmapBuilder = new CharacterMapBuilder();
|
||||
|
||||
parser.Parse(numeric, tokenScanner, cmapBuilder);
|
||||
|
||||
Assert.Empty(cmapBuilder.CodespaceRanges);
|
||||
}
|
||||
|
||||
private static byte[] GetHexBytes(params char[] characters)
|
||||
{
|
||||
var token = new HexToken(characters);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts
|
||||
{
|
||||
using UglyToad.PdfPig.Tests.Dla;
|
||||
using Xunit;
|
||||
|
||||
public class PointSizeTests
|
||||
{
|
||||
[Fact]
|
||||
public void RotatedText()
|
||||
{
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath("complex rotated")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
Assert.Equal(12, letter.PointSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
var names = Standard14.GetNames().Count;
|
||||
|
||||
Assert.Equal(39, names);
|
||||
Assert.Equal(34, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -210,20 +210,6 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
Assert.Equal(points, glyph.Points.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseIssue258CorruptNameTable()
|
||||
{
|
||||
var bytes = TrueTypeTestHelper.GetFileBytes("issue-258-corrupt-name-table");
|
||||
|
||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));
|
||||
|
||||
var font = TrueTypeFontParser.Parse(input);
|
||||
|
||||
Assert.NotNull(font);
|
||||
Assert.NotNull(font.TableRegister.NameTable);
|
||||
Assert.NotEmpty(font.TableRegister.NameTable.NameRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.Type1
|
||||
{
|
||||
public class Type1CharStringParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void CorrectBoundingBoxesFlexPoints()
|
||||
{
|
||||
PointComparer pointComparer = new PointComparer(new DoubleComparer(3));
|
||||
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
|
||||
var filePath = Path.Combine(documentFolder, "data.pdf");
|
||||
|
||||
using (var doc = PdfDocument.Open(filePath))
|
||||
{
|
||||
var page = doc.GetPage(1);
|
||||
|
||||
var letters = page.Letters;
|
||||
|
||||
// check 'm'
|
||||
var m = letters[0];
|
||||
Assert.Equal("m", m.Value);
|
||||
Assert.Equal(new PdfPoint(253.4458, 658.431), m.GlyphRectangle.BottomLeft, pointComparer);
|
||||
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.GlyphRectangle.TopRight, pointComparer);
|
||||
|
||||
// check 'p'
|
||||
var p = letters[1];
|
||||
Assert.Equal("p", p.Value);
|
||||
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.GlyphRectangle.BottomLeft, pointComparer);
|
||||
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.GlyphRectangle.TopRight, pointComparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1682,17 +1682,5 @@
|
||||
Assert.Equal(expected[2], rectR.TopLeft, PointComparer);
|
||||
Assert.Equal(expected[3], rectR.BottomLeft, PointComparer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue261()
|
||||
{
|
||||
// Some points have negative coordinates after rotation, resulting in negative Height
|
||||
PdfRectangle rect = new PdfRectangle(new PdfPoint(401.51, 461.803424),
|
||||
new PdfPoint(300.17322363281249, 461.803424),
|
||||
new PdfPoint(401.51, 291.45),
|
||||
new PdfPoint(300.17322363281249, 291.45));
|
||||
|
||||
Assert.True(rect.Height > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
using PdfPig.Tokens;
|
||||
using PdfPig.Core;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
|
||||
|
||||
internal class TestOperationContext : IOperationContext
|
||||
{
|
||||
@@ -90,66 +88,6 @@
|
||||
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
BeginSubpath();
|
||||
var point = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
CurrentPosition = point;
|
||||
CurrentSubpath.MoveTo(point.X, point.Y);
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(CurrentPosition.X, CurrentPosition.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint1 = CurrentTransformationMatrix.Transform(new PdfPoint(x1, y1));
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void LineTo(double x, double y)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPoint = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
|
||||
CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
|
||||
CurrentPosition = endPoint;
|
||||
}
|
||||
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
BeginSubpath();
|
||||
var lowerLeft = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
var upperRight = CurrentTransformationMatrix.Transform(new PdfPoint(x + width, y + height));
|
||||
|
||||
CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
|
||||
AddCurrentSubpath();
|
||||
}
|
||||
|
||||
public void EndPath()
|
||||
{
|
||||
}
|
||||
@@ -186,91 +124,6 @@
|
||||
{
|
||||
}
|
||||
|
||||
private void AdjustTextMatrix(double tx, double ty)
|
||||
{
|
||||
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
||||
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
|
||||
}
|
||||
|
||||
public void SetFlatnessTolerance(decimal tolerance)
|
||||
{
|
||||
GetCurrentState().Flatness = tolerance;
|
||||
}
|
||||
|
||||
public void SetLineCap(LineCapStyle cap)
|
||||
{
|
||||
GetCurrentState().CapStyle = cap;
|
||||
}
|
||||
|
||||
public void SetLineDashPattern(LineDashPattern pattern)
|
||||
{
|
||||
GetCurrentState().LineDashPattern = pattern;
|
||||
}
|
||||
|
||||
public void SetLineJoin(LineJoinStyle join)
|
||||
{
|
||||
GetCurrentState().JoinStyle = join;
|
||||
}
|
||||
|
||||
public void SetLineWidth(decimal width)
|
||||
{
|
||||
GetCurrentState().LineWidth = width;
|
||||
}
|
||||
|
||||
public void SetMiterLimit(decimal limit)
|
||||
{
|
||||
GetCurrentState().MiterLimit = limit;
|
||||
}
|
||||
|
||||
public void MoveToNextLineWithOffset()
|
||||
{
|
||||
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)GetCurrentState().FontState.Leading);
|
||||
tdOperation.Run(this);
|
||||
}
|
||||
|
||||
public void SetFontAndSize(NameToken font, double size)
|
||||
{
|
||||
var currentState = GetCurrentState();
|
||||
currentState.FontState.FontSize = size;
|
||||
currentState.FontState.FontName = font;
|
||||
}
|
||||
|
||||
public void SetHorizontalScaling(double scale)
|
||||
{
|
||||
GetCurrentState().FontState.HorizontalScaling = scale;
|
||||
}
|
||||
|
||||
public void SetTextLeading(double leading)
|
||||
{
|
||||
GetCurrentState().FontState.Leading = leading;
|
||||
}
|
||||
|
||||
public void SetTextRenderingMode(TextRenderingMode mode)
|
||||
{
|
||||
GetCurrentState().FontState.TextRenderingMode = mode;
|
||||
}
|
||||
|
||||
public void SetTextRise(double rise)
|
||||
{
|
||||
GetCurrentState().FontState.Rise = rise;
|
||||
}
|
||||
|
||||
public void SetWordSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.WordSpacing = spacing;
|
||||
}
|
||||
|
||||
public void ModifyCurrentTransformationMatrix(double[] value)
|
||||
{
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
}
|
||||
|
||||
public void SetCharacterSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.CharacterSpacing = spacing;
|
||||
}
|
||||
|
||||
private class TestFontFactory : IFontFactory
|
||||
{
|
||||
public IFont Get(DictionaryToken dictionary)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 77 B |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 522 KiB |
@@ -1 +0,0 @@
|
||||
h・m
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 B |
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using PdfPig.Tokens;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
public class AdvancedPdfDocumentAccessTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplacesObjectsFunc()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, tk =>
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replaced = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
return replaced;
|
||||
});
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacesObjects()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replacement = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, replacement);
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
public class OptionalContentTests
|
||||
{
|
||||
[Fact]
|
||||
public void NoMarkedOptionalContent()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("AcroFormsBasicFields.pdf")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var oc = page.ExperimentalAccess.GetOptionalContents();
|
||||
|
||||
Assert.Equal(0, oc.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkedOptionalContent()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("odwriteex.pdf")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var oc = page.ExperimentalAccess.GetOptionalContents();
|
||||
|
||||
Assert.Equal(3, oc.Count);
|
||||
|
||||
Assert.Contains("0", oc);
|
||||
Assert.Contains("Dimentions", oc);
|
||||
Assert.Contains("Text", oc);
|
||||
|
||||
Assert.Equal(1, oc["0"].Count);
|
||||
Assert.Equal(2, oc["Dimentions"].Count);
|
||||
Assert.Equal(1, oc["Text"].Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkedOptionalContentRecursion()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Layer pdf - 322_High_Holborn_building_Brochure.pdf")))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
var oc1 = page1.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(16, oc1.Count);
|
||||
Assert.Contains("NEW ARRANGEMENT", oc1);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
var oc2 = page2.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(15, oc2.Count);
|
||||
Assert.DoesNotContain("NEW ARRANGEMENT", oc2);
|
||||
Assert.Contains("WDL Shell text", oc2);
|
||||
Assert.Equal(2, oc2["WDL Shell text"].Count);
|
||||
|
||||
var page3 = document.GetPage(3);
|
||||
var oc3 = page3.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(15, oc3.Count);
|
||||
Assert.Contains("WDL Shell text", oc3);
|
||||
Assert.Equal(2, oc3["WDL Shell text"].Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using Content;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using Images.Png;
|
||||
using Xunit;
|
||||
|
||||
public class SwedishTouringCarChampionshipTests
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests
|
||||
{
|
||||
using System;
|
||||
|
||||
public static class TestEnvironment
|
||||
{
|
||||
public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1;
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: žw̵;’’Îq®]cƒÿdp¨ÛI3F#G©#œ)TÇqW£NÚѬgOKbü‡µ#á¡£Þaîtƒƒ›ß–
|
||||
¾“S>}µuÕõ5M±¢ª†»øÞû•q÷îÜ~¬PòžÞ~•¬ëɃGÅ-Ñím·°gêêb,/,£P§õ^v¾ãÁô¿¿Š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ŽzÝ‚mæ
|
||||
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Ã0E÷ÿÃ,Ó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()));
|
||||
|
||||
@@ -45,11 +45,6 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
return Objects[reference];
|
||||
}
|
||||
|
||||
public void ReplaceToken(IndirectReference reference, IToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
|
||||
@@ -65,9 +64,6 @@
|
||||
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\PMingLiU.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Writer
|
||||
{
|
||||
using Integration;
|
||||
using PdfPig.Writer;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class TokenWriterTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void EscapeSpecialCharacter()
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
TokenWriter.WriteToken(new StringToken("\\"), memStream);
|
||||
TokenWriter.WriteToken(new StringToken("(Hello)"), memStream);
|
||||
|
||||
// Read Test
|
||||
memStream.Position = 0;
|
||||
using (var streamReader = new StreamReader(memStream))
|
||||
{
|
||||
var line = streamReader.ReadToEnd();
|
||||
Assert.Equal("(\\\\) (\\(Hello\\)) ", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
internal class NumericTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
|
||||
|
||||
private const byte Zero = 48;
|
||||
private const byte Nine = 57;
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Tokenization
|
||||
{
|
||||
using Core;
|
||||
using System.Text;
|
||||
using Tokens;
|
||||
|
||||
internal class PlainTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
|
||||
|
||||
public bool ReadsNextByte { get; } = true;
|
||||
|
||||
@@ -19,7 +18,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = stringBuilder;
|
||||
var builder = StringBuilderPool.Borrow();
|
||||
builder.Append((char)currentByte);
|
||||
while (inputBytes.MoveNext())
|
||||
{
|
||||
@@ -40,7 +39,7 @@
|
||||
}
|
||||
|
||||
var text = builder.ToString();
|
||||
builder.Clear();
|
||||
StringBuilderPool.Return(builder);
|
||||
|
||||
switch (text)
|
||||
{
|
||||
|
||||
@@ -15,12 +15,9 @@
|
||||
private static readonly DictionaryTokenizer DictionaryTokenizer = new DictionaryTokenizer();
|
||||
private static readonly HexTokenizer HexTokenizer = new HexTokenizer();
|
||||
private static readonly NameTokenizer NameTokenizer = new NameTokenizer();
|
||||
|
||||
// NOTE: these are not thread safe so should not be static. Each instance includes a
|
||||
// StringBuilder it re-uses.
|
||||
private readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
|
||||
private readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
|
||||
private readonly StringTokenizer StringTokenizer = new StringTokenizer();
|
||||
private static readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
|
||||
private static readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
|
||||
private static readonly StringTokenizer StringTokenizer = new StringTokenizer();
|
||||
|
||||
private readonly ScannerScope scope;
|
||||
private readonly IInputBytes inputBytes;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
internal class StringTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(16);
|
||||
public bool ReadsNextByte { get; } = false;
|
||||
|
||||
public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token)
|
||||
@@ -23,7 +23,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = stringBuilder;
|
||||
var builder = StringBuilderPool.Borrow();
|
||||
var numberOfBrackets = 1;
|
||||
var isEscapeActive = false;
|
||||
var isLineBreaking = false;
|
||||
@@ -178,7 +178,7 @@
|
||||
encodedWith = StringToken.Encoding.Iso88591;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
StringBuilderPool.Return(builder);
|
||||
|
||||
token = new StringToken(tokenStr, encodedWith);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -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}>"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,12 +338,10 @@
|
||||
public static readonly NameToken MarkInfo = new NameToken("MarkInfo");
|
||||
public static readonly NameToken Mask = new NameToken("Mask");
|
||||
public static readonly NameToken Matrix = new NameToken("Matrix");
|
||||
public static readonly NameToken Matte = new NameToken("Matte");
|
||||
public static readonly NameToken MaxLen = new NameToken("MaxLen");
|
||||
public static readonly NameToken MaxWidth = new NameToken("MaxWidth");
|
||||
public static readonly NameToken Mcid = new NameToken("MCID");
|
||||
public static readonly NameToken Mdp = new NameToken("MDP");
|
||||
public static readonly NameToken Measure = new NameToken("Measure");
|
||||
public static readonly NameToken MediaBox = new NameToken("MediaBox");
|
||||
public static readonly NameToken Metadata = new NameToken("Metadata");
|
||||
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
||||
@@ -551,12 +549,10 @@
|
||||
public static readonly NameToken Unix = new NameToken("Unix");
|
||||
public static readonly NameToken Uri = new NameToken("URI");
|
||||
public static readonly NameToken Url = new NameToken("URL");
|
||||
public static readonly NameToken Usage = new NameToken("Usage");
|
||||
public static readonly NameToken UserUnit = new NameToken("UserUnit");
|
||||
// V
|
||||
public static readonly NameToken V = new NameToken("V");
|
||||
public static readonly NameToken V2 = new NameToken("V2");
|
||||
public static readonly NameToken VE = new NameToken("VE");
|
||||
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
|
||||
public static readonly NameToken Version = new NameToken("Version");
|
||||
public static readonly NameToken Vertices = new NameToken("Vertices");
|
||||
@@ -565,7 +561,6 @@
|
||||
public static readonly NameToken ViewClip = new NameToken("ViewClip");
|
||||
public static readonly NameToken ViewerPreferences = new NameToken("ViewerPreferences");
|
||||
public static readonly NameToken Volume = new NameToken("Volume");
|
||||
public static readonly NameToken Vp = new NameToken("VP");
|
||||
// W
|
||||
public static readonly NameToken W = new NameToken("W");
|
||||
public static readonly NameToken W2 = new NameToken("W2");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
|
||||
@@ -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"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></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"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String></wpf:ResourceDictionary>
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// The size of the font in points.
|
||||
/// <para>This is considered experimental because the calculated value is incorrect for some documents at present.</para>
|
||||
/// </summary>
|
||||
public double PointSize { get; }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/// <remarks>
|
||||
/// See table 3.27 from the PDF specification version 1.7.
|
||||
/// </remarks>
|
||||
public class MediaBox
|
||||
internal class MediaBox
|
||||
{
|
||||
///<summary>
|
||||
/// User space units per inch.
|
||||
@@ -66,14 +66,8 @@
|
||||
/// </summary>
|
||||
public static readonly MediaBox A6 = new MediaBox(new PdfRectangle(0, 0, 105 * PointsPerMm, 148 * PointsPerMm));
|
||||
|
||||
/// <summary>
|
||||
/// A rectangle, expressed in default user space units, that defines the boundaries of the physical medium on which the page shall be displayed or printed.
|
||||
/// </summary>
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="MediaBox"/>.
|
||||
/// </summary>
|
||||
public MediaBox(PdfRectangle? bounds)
|
||||
{
|
||||
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Tokenization.Scanner;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
namespace UglyToad.PdfPig.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional content group is a dictionary representing a collection of graphics
|
||||
/// that can be made visible or invisible dynamically by users of viewers applications.
|
||||
/// </summary>
|
||||
public class OptionalContentGroupElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of PDF object that this dictionary describes.
|
||||
/// <para>Must be OCG for an optional content group dictionary.</para>
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the optional content group, suitable for presentation in a viewer application's user interface.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A single name or an array containing any combination of names.
|
||||
/// <para>Default value is 'View'.</para>
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Intent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A usage dictionary describing the nature of the content controlled by the group.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, IToken> Usage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Underlying <see cref="MarkedContentElement"/>.
|
||||
/// </summary>
|
||||
public MarkedContentElement MarkedContent { get; }
|
||||
|
||||
internal OptionalContentGroupElement(MarkedContentElement markedContentElement, IPdfTokenScanner pdfTokenScanner)
|
||||
{
|
||||
MarkedContent = markedContentElement;
|
||||
|
||||
// Type - Required
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out NameToken type))
|
||||
{
|
||||
Type = type.Data;
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out StringToken typeStr))
|
||||
{
|
||||
Type = typeStr.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Cannot parse optional content's {nameof(Type)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
|
||||
}
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case "OCG": // Optional content group dictionary
|
||||
// Name - Required
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out NameToken name))
|
||||
{
|
||||
Name = name.Data;
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out StringToken nameStr))
|
||||
{
|
||||
Name = nameStr.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Cannot parse optional content's {nameof(Name)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
|
||||
}
|
||||
|
||||
// Intent - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out NameToken intentName))
|
||||
{
|
||||
Intent = new string[] { intentName.Data };
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out StringToken intentStr))
|
||||
{
|
||||
Intent = new string[] { intentStr.Data };
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out ArrayToken intentArray))
|
||||
{
|
||||
List<string> intentList = new List<string>();
|
||||
foreach (var token in intentArray.Data)
|
||||
{
|
||||
if (token is NameToken nameA)
|
||||
{
|
||||
intentList.Add(nameA.Data);
|
||||
}
|
||||
else if (token is StringToken strA)
|
||||
{
|
||||
intentList.Add(strA.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
Intent = intentList;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default value is 'View'.
|
||||
Intent = new string[] { "View" };
|
||||
}
|
||||
|
||||
// Usage - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Usage, pdfTokenScanner, out DictionaryToken usage))
|
||||
{
|
||||
this.Usage = usage.Data;
|
||||
}
|
||||
break;
|
||||
|
||||
case "OCMD":
|
||||
// OCGs - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out DictionaryToken ocgsD))
|
||||
{
|
||||
// dictionary or array
|
||||
throw new NotImplementedException($"{NameToken.Ocgs}");
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out ArrayToken ocgsA))
|
||||
{
|
||||
// dictionary or array
|
||||
throw new NotImplementedException($"{NameToken.Ocgs}");
|
||||
}
|
||||
|
||||
// P - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.P, pdfTokenScanner, out NameToken p))
|
||||
{
|
||||
throw new NotImplementedException($"{NameToken.P}");
|
||||
}
|
||||
|
||||
// VE - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.VE, pdfTokenScanner, out ArrayToken ve))
|
||||
{
|
||||
throw new NotImplementedException($"{NameToken.VE}");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unknown Optional Content of type '{Type}' not known.", nameof(Type));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Type} - {Name} [{string.Join(",", Intent)}]: {MarkedContent?.ToString()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@
|
||||
using Util.JetBrains.Annotations;
|
||||
using Tokenization.Scanner;
|
||||
using Graphics;
|
||||
using System.Linq;
|
||||
|
||||
/// <summary>
|
||||
/// Contains the content and provides access to methods of a single page in the <see cref="PdfDocument"/>.
|
||||
@@ -18,7 +17,7 @@
|
||||
public class Page
|
||||
{
|
||||
private readonly AnnotationProvider annotationProvider;
|
||||
internal readonly IPdfTokenScanner pdfScanner;
|
||||
private readonly IPdfTokenScanner pdfScanner;
|
||||
private readonly Lazy<string> textLazy;
|
||||
|
||||
/// <summary>
|
||||
@@ -36,10 +35,7 @@
|
||||
/// </summary>
|
||||
public CropBox CropBox { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the boundaries of the physical medium on which the page shall be displayed or printed.
|
||||
/// </summary>
|
||||
public MediaBox MediaBox { get; }
|
||||
internal MediaBox MediaBox { get; }
|
||||
|
||||
internal PageContent Content { get; }
|
||||
|
||||
@@ -194,46 +190,6 @@
|
||||
{
|
||||
return annotationProvider.GetAnnotations();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets any optional content on the page.
|
||||
/// <para>Does not handle XObjects and annotations for the time being.</para>
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, IReadOnlyList<OptionalContentGroupElement>> GetOptionalContents()
|
||||
{
|
||||
List<OptionalContentGroupElement> mcesOptional = new List<OptionalContentGroupElement>();
|
||||
|
||||
// 4.10.2
|
||||
// Optional content in content stream
|
||||
GetOptionalContentsRecursively(page.Content?.GetMarkedContents(), ref mcesOptional);
|
||||
|
||||
// Optional content in XObjects and annotations
|
||||
// TO DO
|
||||
//var annots = GetAnnotations().ToList();
|
||||
|
||||
return mcesOptional.GroupBy(oc => oc.Name).ToDictionary(g => g.Key, g => g.ToList() as IReadOnlyList<OptionalContentGroupElement>);
|
||||
}
|
||||
|
||||
private void GetOptionalContentsRecursively(IReadOnlyList<MarkedContentElement> markedContentElements, ref List<OptionalContentGroupElement> mcesOptional)
|
||||
{
|
||||
if (markedContentElements.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var mce in markedContentElements)
|
||||
{
|
||||
if (mce.Tag == "OC")
|
||||
{
|
||||
mcesOptional.Add(new OptionalContentGroupElement(mce, page.pdfScanner));
|
||||
// we don't recurse
|
||||
}
|
||||
else if (mce.Children?.Count > 0)
|
||||
{
|
||||
GetOptionalContentsRecursively(mce.Children, ref mcesOptional);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Content
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Filters;
|
||||
using Graphics;
|
||||
using Graphics.Operations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tokenization.Scanner;
|
||||
using XObjects;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -55,13 +55,6 @@
|
||||
int offset = 0;
|
||||
while (offset < rowlength && ((i = input.Read(actline, offset, rowlength - offset)) != -1))
|
||||
{
|
||||
if (i == 0)
|
||||
{
|
||||
// TODO: #291, this indicates a bug in reading logic.
|
||||
// This only avoids the infinite loop it does not fix the logic bug.
|
||||
break;
|
||||
}
|
||||
|
||||
offset += i;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,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
Reference in New Issue
Block a user