Compare commits

..

1 Commits

Author SHA1 Message Date
Eliot Jones
31bcb92c4c add classes to support loading existing documents into builder 2020-07-19 17:46:04 +01:00
236 changed files with 2729 additions and 12789 deletions

View File

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

View File

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

1
.gitignore vendored
View File

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

View File

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

View File

@@ -1,94 +1,4 @@
root = true
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_style = space
indent_size = 4
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
# Parameter preferences
dotnet_code_quality_unused_parameters = all:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
# Code-block preferences
csharp_prefer_braces = true:warning
# 'using' directive preferences
csharp_using_directive_placement = inside_namespace:silent
#### Naming styles ####
# Naming rules
dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.severity = warning
dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.symbols = private_or_internal_static_field
dotnet_naming_rule.private_or_internal_static_field_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.private_or_internal_field_should_be_not_underscored.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_not_underscored.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_not_underscored.style = not_underscored
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.private_or_internal_static_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_static_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_static_field.required_modifiers = static
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
dotnet_naming_style.not_underscored.required_prefix =
dotnet_naming_style.not_underscored.required_suffix =
dotnet_naming_style.not_underscored.word_separator =
dotnet_naming_style.not_underscored.capitalization = camel_case
indent_size = 4

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Core
{
using System.Diagnostics;
using System.Globalization;
/// <summary>
/// A point in a PDF file.
@@ -113,7 +112,7 @@
/// <inheritdoc />
public override string ToString()
{
return $"(x:{X.ToString(CultureInfo.InvariantCulture)}, y:{Y.ToString(CultureInfo.InvariantCulture)})";
return $"(x:{X}, y:{Y})";
}
}
}

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Core
{
using System;
using System.Globalization;
/// <summary>
/// A rectangle in a PDF file.
@@ -49,7 +48,6 @@
private double width;
/// <summary>
/// Width of the rectangle.
/// <para>A positive number.</para>
/// </summary>
public double Width
{
@@ -67,7 +65,6 @@
private double height;
/// <summary>
/// Height of the rectangle.
/// <para>A positive number.</para>
/// </summary>
public double Height
{
@@ -199,22 +196,19 @@
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 />
public override string ToString()
{
return $"[{TopLeft}, {Width.ToString(CultureInfo.InvariantCulture)}, {Height.ToString(CultureInfo.InvariantCulture)}]";
return $"[{TopLeft}, {Width}, {Height}]";
}
}
}

View File

@@ -18,8 +18,7 @@
public IReadOnlyList<IPathCommand> Commands => commands;
/// <summary>
/// True if the <see cref="PdfSubpath"/> was originaly drawn using the rectangle ('re') operator.
/// <para>Always false if paths are clipped.</para>
/// True if the <see cref="PdfSubpath"/> was originaly draw as a rectangle.
/// </summary>
public bool IsDrawnAsRectangle { get; internal set; }
@@ -30,12 +29,28 @@
/// <summary>
/// Return true if points are organised in a clockwise order. Works only with closed paths.
/// </summary>
public bool IsClockwise => IsClosed() && shoeLaceSum > 0;
/// <returns></returns>
public bool IsClockwise
{
get
{
if (!IsClosed()) return false;
return shoeLaceSum > 0;
}
}
/// <summary>
/// Return true if points are organised in a counterclockwise order. Works only with closed paths.
/// </summary>
public bool IsCounterClockwise => IsClosed() && shoeLaceSum < 0;
/// <returns></returns>
public bool IsCounterClockwise
{
get
{
if (!IsClosed()) return false;
return shoeLaceSum < 0;
}
}
/// <summary>
/// Get the <see cref="PdfSubpath"/>'s centroid point.
@@ -43,14 +58,9 @@
public PdfPoint GetCentroid()
{
var filtered = commands.Where(c => c is Line || c is BezierCurve).ToList();
if (filtered.Count == 0)
{
return new PdfPoint();
}
if (filtered.Count == 0) return new PdfPoint();
var points = filtered.Select(GetStartPoint).ToList();
points.AddRange(filtered.Select(GetEndPoint));
return new PdfPoint(points.Average(p => p.X), points.Average(p => p.Y));
}
@@ -160,9 +170,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 +184,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.
@@ -218,40 +230,10 @@
/// </summary>
public bool IsClosed()
{
var filteredCount = 0;
IPathCommand last = null;
IPathCommand first = null;
for (int i = Commands.Count - 1; i >= 0; i--)
{
var cmd = Commands[i];
if (cmd is Close)
{
return true;
}
if (cmd is Line || cmd is BezierCurve || cmd is Move)
{
if (last == null)
{
last = cmd;
}
first = cmd;
filteredCount++;
}
}
if (filteredCount < 2 || last == null || first == null)
{
return false;
}
if (!GetStartPoint(first).Equals(GetEndPoint(last)))
{
return false;
}
if (Commands.Any(c => c is Close)) return true;
var filtered = Commands.Where(c => c is Line || c is BezierCurve || c is Move).ToList();
if (filtered.Count < 2) return false;
if (!GetStartPoint(filtered.First()).Equals(GetEndPoint(filtered.Last()))) return false;
return true;
}
@@ -314,42 +296,6 @@
return new PdfRectangle(minX, minY, maxX, maxY);
}
/// <summary>
/// If <see cref="IsDrawnAsRectangle"/> then returns the rectangle dimensions specified. Otherwise returns <see langword="null"/>.
/// </summary>
/// <remarks>
/// Since a rectangle is interpreted as a move command followed by 3 lines and a close command this condenses the 5 commands back into a single rectangle.
/// </remarks>
public PdfRectangle? GetDrawnRectangle()
{
if (!IsDrawnAsRectangle || Commands.Count != 5)
{
return null;
}
if (!(Commands[0] is Move mv) || !(Commands[1] is Line line1) || !(Commands[2] is Line line2) || !(Commands[3] is Line line3)
|| !(Commands[4] is Close))
{
return null;
}
if (!line1.From.Equals(mv.Location) || line1.To.Y != mv.Location.Y)
{
return null;
}
var width = line1.To.X - mv.Location.X;
if (!line2.From.Equals(line1.To) || line2.To.X != line1.To.X)
{
return null;
}
var height = line2.To.Y - line1.To.Y;
return new PdfRectangle(mv.Location, new PdfPoint(mv.Location.X + width, mv.Location.Y + height));
}
/// <summary>
/// A command in a <see cref="PdfSubpath"/>.
/// </summary>
@@ -504,7 +450,6 @@
{
return From.Equals(line.From) && To.Equals(line.To);
}
return false;
}
@@ -747,20 +692,17 @@
/// </summary>
public override bool Equals(object obj)
{
if (!(obj is PdfSubpath path) || Commands.Count != path.Commands.Count)
if (obj is PdfSubpath path)
{
return false;
}
if (Commands.Count != path.Commands.Count) return false;
for (int i = 0; i < Commands.Count; i++)
{
if (!Commands[i].Equals(path.Commands[i]))
for (int i = 0; i < Commands.Count; i++)
{
return false;
if (!Commands[i].Equals(path.Commands[i])) return false;
}
return true;
}
return true;
return false;
}
/// <summary>
@@ -773,7 +715,6 @@
{
hash = hash * (i + 1) * 17 + Commands[i].GetHashCode();
}
return hash;
}
}

View File

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

View File

@@ -1,116 +1,31 @@
namespace UglyToad.PdfPig.DocumentLayoutAnalysis.ReadingOrderDetector
{
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Algorithm that retrieve the blocks' reading order using spatial reasoning (Allens interval relations) and possibly the rendering order (TextSequence).
/// <para>See section 4.1 of 'Unsupervised document structure analysis of digital scientific articles' by S. Klampfl, M. Granitzer, K. Jack, R. Kern
/// and 'Document Understanding for a Broad Class of Documents' by L. Todoran, M. Worring, M. Aiello and C. Monz.</para>
/// Algorithm that retrieve the blocks' reading order using both (spatial) Allens interval relations and rendering order (TextSequence).
/// <para>See section 4.1 of 'Unsupervised document structure analysis of digital scientific articles' by S. Klampfl, M. Granitzer, K. Jack, R. Kern and 'Document Understanding for a Broad Class of Documents' by L. Todoran, M. Worring, M. Aiello and C. Monz.</para>
/// </summary>
public class UnsupervisedReadingOrderDetector : IReadingOrderDetector
{
/// <summary>
/// The rules encoding the spatial reasoning constraints.
/// <para>See 'Document Understanding for a Broad Class of Documents' by L. Todoran, M. Worring, M. Aiello and C. Monz.</para>
/// </summary>
public enum SpatialReasoningRules
{
/// <summary>
/// Basic spacial reasoning.
/// <para>In western culture the reading order is from left to right and from top to bottom.</para>
/// </summary>
Basic = 0,
/// <summary>
/// Text-blocks are read in rows from left-to-right, top-to-bottom.
/// <para>The diagonal direction 'left-bottom to top-right' cannot be present among the Basic relations allowed.</para>
/// </summary>
RowWise = 1,
/// <summary>
/// Text-blocks are read in columns, from top-to-bottom and from left-to-right.
/// <para>The diagonal direction 'right-top to bottom-left' cannot be present among the Basic relations allowed.</para>
/// </summary>
ColumnWise = 2
}
/// <summary>
/// Create an instance of unsupervised reading order detector, <see cref="UnsupervisedReadingOrderDetector"/>.
/// <para>This detector uses spatial reasoning (Allens interval relations) and possibly the rendering order (TextSequence).</para>
/// <para>This detector uses the (spatial) Allens interval relations and rendering order (TextSequence).</para>
/// </summary>
public static UnsupervisedReadingOrderDetector Instance { get; } = new UnsupervisedReadingOrderDetector();
/// <summary>
/// Whether or not to also use the rendering order, as indicated by the TextSequence.
/// </summary>
public bool UseRenderingOrder { get; }
private readonly double T;
/// <summary>
/// The rule to be used that encodes the spatial reasoning constraints.
/// </summary>
public SpatialReasoningRules SpatialReasoningRule { get; }
/// <summary>
/// The tolerance parameter T. If two coordinates are closer than T they are considered equal.
/// <para>This flexibility is necessary because due to the inherent noise in the PDF extraction text blocks in the
/// same column might not be exactly aligned.</para>
/// </summary>
public double T { get; }
private Func<TextBlock, TextBlock, double, bool> getBeforeInMethod;
/// <summary>
/// Algorithm that retrieve the blocks' reading order using spatial reasoning (Allens interval relations) and possibly the rendering order (TextSequence).
/// Algorithm that retrieve the blocks' reading order using both (spatial) Allens interval relations and rendering order.
/// </summary>
/// <param name="T">The tolerance parameter T. If two coordinates are closer than T they are considered equal.
/// This flexibility is necessary because due to the inherent noise in the PDF extraction text blocks in the
/// same column might not be exactly aligned.</param>
/// <param name="spatialReasoningRule">The rule to be used that encodes the spatial reasoning constraints.</param>
/// <param name="useRenderingOrder">Whether or not to also use the rendering order, as indicated by the TextSequence.</param>
public UnsupervisedReadingOrderDetector(double T = 5, SpatialReasoningRules spatialReasoningRule = SpatialReasoningRules.ColumnWise, bool useRenderingOrder = true)
public UnsupervisedReadingOrderDetector(double T = 5)
{
this.T = T;
this.SpatialReasoningRule = spatialReasoningRule;
this.UseRenderingOrder = useRenderingOrder;
switch (SpatialReasoningRule)
{
case SpatialReasoningRules.ColumnWise:
if (UseRenderingOrder)
{
getBeforeInMethod = (TextBlock a, TextBlock b, double t) => GetBeforeInReadingVertical(a, b, t) || GetBeforeInRendering(a, b);
}
else
{
getBeforeInMethod = GetBeforeInReadingVertical;
}
break;
case SpatialReasoningRules.RowWise:
if (UseRenderingOrder)
{
getBeforeInMethod = (TextBlock a, TextBlock b, double t) => GetBeforeInReadingHorizontal(a, b, t) || GetBeforeInRendering(a, b);
}
else
{
getBeforeInMethod = GetBeforeInReadingHorizontal;
}
break;
case SpatialReasoningRules.Basic:
default:
if (UseRenderingOrder)
{
getBeforeInMethod = (TextBlock a, TextBlock b, double t) => GetBeforeInReading(a, b, t) || GetBeforeInRendering(a, b);
}
else
{
getBeforeInMethod = GetBeforeInReading;
}
break;
}
}
/// <summary>
@@ -163,7 +78,7 @@
if (i == j) continue;
var b = textBlocks[j];
if (getBeforeInMethod(a, b, T))
if (GetBeforeInReadingRendering(a, b, T))
{
graph[i].Add(j);
}
@@ -173,20 +88,19 @@
return graph;
}
private static bool GetBeforeInRendering(TextBlock a, TextBlock b)
private bool GetBeforeInReadingRendering(TextBlock a, TextBlock b, double T)
{
return GetBeforeInReadingVertical(a, b, T) || GetBeforeInRendering(a, b);
}
private bool GetBeforeInRendering(TextBlock a, TextBlock b)
{
var avgTextSequenceA = a.TextLines.SelectMany(tl => tl.Words).SelectMany(w => w.Letters).Select(l => l.TextSequence).Average();
var avgTextSequenceB = b.TextLines.SelectMany(tl => tl.Words).SelectMany(w => w.Letters).Select(l => l.TextSequence).Average();
return avgTextSequenceA < avgTextSequenceB;
}
/// <summary>
/// Rule encoding the fact that in western culture the reading order is from left to right and from top to bottom.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="T">The tolerance parameter T.</param>
private static bool GetBeforeInReading(TextBlock a, TextBlock b, double T)
private bool GetBeforeInReading(TextBlock a, TextBlock b, double T)
{
IntervalRelations xRelation = GetIntervalRelationX(a, b, T);
IntervalRelations yRelation = GetIntervalRelationY(a, b, T);
@@ -205,7 +119,8 @@
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="T">The tolerance parameter T.</param>
private static bool GetBeforeInReadingVertical(TextBlock a, TextBlock b, double T)
/// <returns></returns>
private bool GetBeforeInReadingVertical(TextBlock a, TextBlock b, double T)
{
IntervalRelations xRelation = GetIntervalRelationX(a, b, T);
IntervalRelations yRelation = GetIntervalRelationY(a, b, T);
@@ -235,7 +150,7 @@
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="T">The tolerance parameter T.</param>
private static bool GetBeforeInReadingHorizontal(TextBlock a, TextBlock b, double T)
private bool GetBeforeInReadingHorizontal(TextBlock a, TextBlock b, double T)
{
IntervalRelations xRelation = GetIntervalRelationX(a, b, T);
IntervalRelations yRelation = GetIntervalRelationY(a, b, T);
@@ -268,7 +183,7 @@
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="T">The tolerance parameter T. If two coordinates are closer than T they are considered equal.</param>
private static IntervalRelations GetIntervalRelationX(TextBlock a, TextBlock b, double T)
private IntervalRelations GetIntervalRelationX(TextBlock a, TextBlock b, double T)
{
if (a.BoundingBox.Right < b.BoundingBox.Left - T)
{
@@ -352,7 +267,7 @@
/// <param name="a"></param>
/// <param name="b"></param>
/// <param name="T">The tolerance parameter T. If two coordinates are closer than T they are considered equal.</param>
private static IntervalRelations GetIntervalRelationY(TextBlock a, TextBlock b, double T)
private IntervalRelations GetIntervalRelationY(TextBlock a, TextBlock b, double T)
{
if (a.BoundingBox.Bottom < b.BoundingBox.Top - T)
{
@@ -517,5 +432,13 @@
/// </summary>
Equals
}
private class NodeComparer : IComparer<KeyValuePair<int, List<int>>>
{
public int Compare(KeyValuePair<int, List<int>> x, KeyValuePair<int, List<int>> y)
{
return x.Value.Count.CompareTo(y.Value.Count);
}
}
}
}

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Core;
@@ -185,7 +184,7 @@
if (i >= 0)
{
var value = Values[i];
stringBuilder.AppendLine(value.ToString("N", CultureInfo.InvariantCulture));
stringBuilder.AppendLine(value.ToString("N"));
}
foreach (var identifier in GetCommandsAt(i + 1))

View File

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

View File

@@ -19,10 +19,6 @@
try
{
var folder = Environment.GetEnvironmentVariable("$HOME");
if (string.IsNullOrWhiteSpace(folder))
{
folder = Environment.GetEnvironmentVariable("HOME");
}
if (!string.IsNullOrWhiteSpace(folder))
{
@@ -52,7 +48,7 @@
try
{
files = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories);
files = Directory.GetFiles(directory);
}
catch
{

View File

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

View File

@@ -35,7 +35,7 @@
var selectionFlags = data.ReadUnsignedShort();
var firstCharacterIndex = data.ReadUnsignedShort();
var lastCharacterIndex = data.ReadUnsignedShort();
var unicodeCharRange = new[] { ulCharRange1, ulCharRange2, ulCharRange3, ulCharRange4 };
var unicodeCharRange = new[] {ulCharRange1, ulCharRange2, ulCharRange3, ulCharRange4};
var vendorId = Encoding.ASCII.GetString(vendorIdBytes);
@@ -67,42 +67,11 @@
lastCharacterIndex);
}
short sTypoAscender;
short sTypoDescender;
short sTypoLineGap;
ushort usWinAscent;
ushort usWinDescent;
try
{
sTypoAscender = data.ReadSignedShort();
sTypoDescender = data.ReadSignedShort();
sTypoLineGap = data.ReadSignedShort();
usWinAscent = data.ReadUnsignedShort();
usWinDescent = data.ReadUnsignedShort();
}
catch
{
// Font may be invalid. Try falling back to shorter version...
return new Os2Table(header, version, xAvgCharWidth,
weightClass, widthClass, typeFlags, ySubscriptXSize,
ySubscriptYSize,
ySubscriptXOffset,
ySubscriptYOffset,
ySuperscriptXSize,
ySuperscriptYSize,
ySuperscriptXOffset,
ySuperscriptYOffset,
yStrikeoutSize,
yStrikeoutPosition,
familyClass,
panose,
unicodeCharRange,
vendorId,
selectionFlags,
firstCharacterIndex,
lastCharacterIndex);
}
var sTypoAscender = data.ReadSignedShort();
var sTypoDescender = data.ReadSignedShort();
var sTypoLineGap = data.ReadSignedShort();
var usWinAscent = data.ReadUnsignedShort();
var usWinDescent = data.ReadUnsignedShort();
if (version == 0)
{
@@ -124,7 +93,7 @@
selectionFlags,
firstCharacterIndex,
lastCharacterIndex,
sTypoAscender,
sTypoAscender,
sTypoDescender,
sTypoLineGap,
usWinAscent,
@@ -204,7 +173,7 @@
var usLowerOpticalPointSize = data.ReadUnsignedShort();
var usUpperOpticalPointSize = data.ReadUnsignedShort();
return new Os2Version5OpenTypeTable(header, version, xAvgCharWidth,
weightClass, widthClass, typeFlags, ySubscriptXSize,
ySubscriptYSize,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
{
using System;
using System.Collections.Generic;
using System.Globalization;
internal class Type1CharstringDecryptedBytes
{
@@ -26,7 +25,7 @@
{
Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes));
Index = index;
Name = name ?? index.ToString(CultureInfo.InvariantCulture);
Name = name ?? index.ToString();
Source = SourceType.Charstring;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,48 +0,0 @@
using System.Collections.Generic;
using UglyToad.PdfPig.Fonts.SystemFonts;
using UglyToad.PdfPig.Tests.Dla;
using Xunit;
namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
{
public class Linux
{
public static IEnumerable<object[]> DataExtract => new[]
{
new object[]
{
"90 180 270 rotated.pdf",
new object[][]
{
new object[] { "[(x:53.88, y:759.48), 2.495859375, 0]", 0.0 },
new object[] { "[(x:514.925312502883, y:744.099765720344), 6.83203125, 7.94531249999983]", -90.0 },
new object[] { "[(x:512.505390717836, y:736.603703191305), 5.1796875, 5.68945312499983]", -90.0 },
new object[] { "[(x:512.505390785898, y:730.931828191305), 3.99609375, 5.52539062499994]", -90.0 },
}
},
};
[SkippableTheory]
[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.");
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
{
var page = document.GetPage(1);
for (int i = 0; i < expected.Length; i++)
{
string bbox = (string)expected[i][0];
var rotation = (double)expected[i][1];
var current = page.Letters[i];
Assert.Equal(bbox, current.GlyphRectangle.ToString());
Assert.Equal(rotation, current.GlyphRectangle.Rotation, 3);
}
}
}
}
}

View File

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

View File

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

View File

@@ -1,19 +0,0 @@
using UglyToad.PdfPig.Tests.Integration;
using Xunit;
namespace UglyToad.PdfPig.Tests.Geometry
{
public class ClippingTests
{
[Fact]
public void ContainsRectangleEvenOdd()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("SPARC - v9 Architecture Manual"),
new ParsingOptions() { ClipPaths = true }))
{
var page = document.GetPage(45);
Assert.Equal(28, page.ExperimentalAccess.Paths.Count);
}
}
}
}

View File

@@ -1,38 +0,0 @@
namespace UglyToad.PdfPig.Tests.Geometry
{
using System.Linq;
using UglyToad.PdfPig.Core;
using UglyToad.PdfPig.Geometry;
using UglyToad.PdfPig.Tests.Integration;
using Xunit;
public class PdfPathExtensionsTests
{
[Fact]
public void ContainsRectangleEvenOdd()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("path_ext_oddeven"),
new ParsingOptions() { ClipPaths = true }))
{
var page = document.GetPage(1);
var words = page.GetWords().ToList();
foreach (var path in page.ExperimentalAccess.Paths)
{
Assert.NotEqual(FillingRule.NonZeroWinding, path.FillingRule); // allow none and even-odd
foreach (var c in words.Where(w => path.Contains(w.BoundingBox)).ToList())
{
Assert.Equal("in", c.Text.Split("_").Last());
words.Remove(c);
}
}
foreach (var w in words)
{
Assert.NotEqual("in", w.Text.Split("_").Last());
}
}
}
}
}

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 522 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

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

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using Content;
@@ -64,10 +63,7 @@
{
var stripped = x.Trim();
var parts = stripped.Split('|');
var index = int.Parse(parts[0]);
var xval = double.Parse(parts[2], CultureInfo.InvariantCulture);
var yval = double.Parse(parts[3], CultureInfo.InvariantCulture);
return (index, letter: parts[1], x: xval, y: yval);
return (index: int.Parse(parts[0]), letter: parts[1], x: double.Parse(parts[2]), y: double.Parse(parts[3]));
}).ToArray();
using (var document = PdfDocument.Open(GetFilename()))

View File

@@ -1,7 +1,6 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using Content;
using UglyToad.PdfPig.Images.Png;
using Xunit;
public class SwedishTouringCarChampionshipTests
@@ -90,29 +89,5 @@
Assert.Equal("https://en.wikipedia.org/wiki/Swedish_Touring_Car_Championship", fullLink.Uri);
}
}
[Fact]
public void GetsImagesAsPng()
{
using (var document = PdfDocument.Open(GetFilename()))
{
foreach (var page in document.GetPages())
{
foreach (var image in page.GetImages())
{
if (!image.TryGetBytes(out _))
{
continue;
}
Assert.True(image.TryGetPng(out var png));
var pngActual = Png.Open(png);
Assert.NotNull(pngActual);
}
}
}
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
namespace UglyToad.PdfPig.Tests.Writer
{
using System.IO;
using Integration;
using PdfPig.Writer;
using Xunit;
public class PdfDocumentBuilderFromExistingTests
{
[Fact]
public void LoadAndSaveExistingNoModifications()
{
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
var bytes = File.ReadAllBytes(path);
var builder = PdfDocumentBuilder.FromPdf(bytes);
var output = builder.Build();
Assert.NotNull(output);
}
}
}

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,6 +6,7 @@
using Content;
using Core;
using CrossReference;
using Exceptions;
using Fields;
using Filters;
using Parser.Parts;
@@ -29,10 +30,10 @@
};
private readonly IPdfTokenScanner tokenScanner;
private readonly ILookupFilterProvider filterProvider;
private readonly IFilterProvider filterProvider;
private readonly CrossReferenceTable crossReferenceTable;
public AcroFormFactory(IPdfTokenScanner tokenScanner, ILookupFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
{
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
@@ -161,10 +162,6 @@
}
var kidObject = tokenScanner.Get(kidReferenceToken.Data);
if (kidObject is null)
{
throw new InvalidOperationException($"Could not find the object with reference: {kidReferenceToken.Data}.");
}
if (kidObject.Data is DictionaryToken kidDictionaryToken)
{
@@ -313,7 +310,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 +474,6 @@
var isChecked = false;
if (!fieldDictionary.TryGetOptionalTokenDirect(NameToken.V, tokenScanner, out NameToken valueToken))
{
if (fieldDictionary.TryGetOptionalTokenDirect(NameToken.As, tokenScanner, out NameToken appearanceStateName)
&& fieldDictionary.TryGetOptionalTokenDirect(NameToken.Ap, tokenScanner, out DictionaryToken _))
{
// Issue #267 - Use the set appearance instead, this might not work for 3 state checkboxes.
isChecked = !string.Equals(
appearanceStateName.Data,
NameToken.Off,
StringComparison.OrdinalIgnoreCase);
valueToken = appearanceStateName;
return (isChecked, valueToken);
}
valueToken = NameToken.Off;
}
else if (inheritsValue && fieldDictionary.TryGet(NameToken.As, tokenScanner, out NameToken appearanceStateName))

View File

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

View File

@@ -3,8 +3,7 @@
using System.Collections.Generic;
using Core;
using Graphics.Colors;
using Graphics.Core;
using UglyToad.PdfPig.Tokens;
using Graphics.Core;
using XObjects;
/// <summary>
@@ -85,25 +84,10 @@
/// </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.
/// </summary>
bool TryGetBytes(out IReadOnlyList<byte> bytes);
/// <summary>
/// Try to convert the image to PNG. Doesn't support conversion of JPG to PNG.
/// </summary>
bool TryGetPng(out byte[] bytes);
}
}

View File

@@ -8,9 +8,7 @@
using Graphics.Colors;
using Graphics.Core;
using Tokens;
using Images.Png;
using UglyToad.PdfPig.Util.JetBrains.Annotations;
/// <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 +41,6 @@
/// <inheritdoc />
public bool IsInlineImage { get; } = true;
/// <inheritdoc />
[NotNull]
public DictionaryToken ImageDictionary { get; }
/// <inheritdoc />
public RenderingIntent RenderingIntent { get; }
@@ -56,9 +50,6 @@
/// <inheritdoc />
public IReadOnlyList<byte> RawBytes { get; }
/// <inheritdoc />
public ColorSpaceDetails ColorSpaceDetails { get; }
/// <summary>
/// Create a new <see cref="InlineImage"/>.
/// </summary>
@@ -69,8 +60,7 @@
IReadOnlyList<decimal> decode,
IReadOnlyList<byte> bytes,
IReadOnlyList<IFilter> filters,
DictionaryToken streamDictionary,
ColorSpaceDetails colorSpaceDetails)
DictionaryToken streamDictionary)
{
Bounds = bounds;
WidthInSamples = widthInSamples;
@@ -81,10 +71,8 @@
IsImageMask = isImageMask;
RenderingIntent = renderingIntent;
Interpolate = interpolate;
ImageDictionary = streamDictionary;
RawBytes = bytes;
ColorSpaceDetails = colorSpaceDetails;
var supportsFilters = true;
foreach (var filter in filters)
@@ -123,9 +111,6 @@
return true;
}
/// <inheritdoc />
public bool TryGetPng(out byte[] bytes) => PngFromPdfImageFactory.TryGenerate(this, out bytes);
/// <inheritdoc />
public override string ToString()
{

View File

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

View File

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

View File

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

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