mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
1 Commits
v0.1.5
...
edit-exist
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31bcb92c4c |
24
.github/workflows/build_and_test.yml
vendored
24
.github/workflows/build_and_test.yml
vendored
@@ -1,24 +0,0 @@
|
||||
name: Build and test
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "2.1.x"
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
|
||||
# Build the release build
|
||||
- name: Build the solution
|
||||
run: dotnet build -c Release src/UglyToad.PdfPig.sln
|
||||
|
||||
- name: Run the tests
|
||||
run: dotnet test src/UglyToad.PdfPig.sln
|
||||
53
.github/workflows/nightly_release.yml
vendored
53
.github/workflows/nightly_release.yml
vendored
@@ -1,53 +0,0 @@
|
||||
name: Nightly Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
check_date:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check latest commit
|
||||
outputs:
|
||||
should_run: ${{ steps.should_run.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: print latest_commit
|
||||
run: echo ${{ github.sha }}
|
||||
|
||||
- id: should_run
|
||||
continue-on-error: true
|
||||
name: check latest commit is less than a day ago
|
||||
if: ${{ github.event_name == 'schedule' }}
|
||||
run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "::set-output name=should_run::false"
|
||||
build_and_publish_nightly:
|
||||
needs: check_date
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||
runs-on: windows-latest
|
||||
name: build_and_publish_nightly
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "2.1.x"
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
|
||||
- name: Write nightly version to projects
|
||||
run: |
|
||||
$newVer = .\tools\generate-nightly-version.ps1; .\tools\set-version.ps1 $newVer
|
||||
|
||||
- name: Restore packages
|
||||
run: dotnet restore tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj
|
||||
|
||||
- name: Build package
|
||||
run: dotnet pack -c Release -o package tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj
|
||||
|
||||
- name: Publish Nuget to GitHub registry
|
||||
run: dotnet nuget push **/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -244,4 +244,3 @@ _Pvt_Extensions
|
||||
/src/CodeCoverage/OpenCover.4.6.519
|
||||
/src/test-results.xml
|
||||
/tools/benchmark
|
||||
/tools/ConsoleRunner/Properties/launchSettings.json
|
||||
|
||||
@@ -193,10 +193,10 @@ PdfPig also comes with some tools for document layout analysis such as the Recur
|
||||
|
||||
An example of the output of the Recursive XY Cut algorithm viewed in an external viewer such as [LayoutEvalGUI](https://www.primaresearch.org/tools/PerformanceEvaluation) is shown below:
|
||||
|
||||

|
||||

|
||||
|
||||
See the [document layout analysis](https://github.com/UglyToad/PdfPig/wiki/Document-Layout-Analysis) page on the wiki for full details.
|
||||
|
||||
## Credit ##
|
||||
|
||||
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.
|
||||
This project wouldn't be possible without the work done by the [PDFBox](https://pdfbox.apache.org/) team and the Apache Foundation.
|
||||
@@ -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
|
||||
@@ -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})";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 (Allen’s 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) Allen’s 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 (Allen’s interval relations) and possibly the rendering order (TextSequence).</para>
|
||||
/// <para>This detector uses the (spatial) Allen’s 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 (Allen’s interval relations) and possibly the rendering order (TextSequence).
|
||||
/// Algorithm that retrieve the blocks' reading order using both (spatial) Allen’s 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Tables;
|
||||
|
||||
internal static class TableParser
|
||||
@@ -14,20 +13,6 @@
|
||||
|
||||
public static T Parse<T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITrueTypeTable
|
||||
{
|
||||
//checksum https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
|
||||
uint sum = 0;
|
||||
var nLongs = (table.Length + 3) / 4;
|
||||
data.Seek(table.Offset);
|
||||
while (nLongs-- > 0)
|
||||
{
|
||||
sum += (uint)data.ReadSignedInt();
|
||||
}
|
||||
|
||||
if (sum != table.CheckSum)
|
||||
{
|
||||
Trace.TraceWarning("Table with invalid checksum found in TrueType font file.");
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(CMapTable))
|
||||
{
|
||||
return (T) (object) CMapTableParser.Parse(table, data, register);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
string any = null;
|
||||
foreach (var nameRecord in NameRecords)
|
||||
{
|
||||
if (nameRecord is null || nameRecord.NameId != 6)
|
||||
if (nameRecord.NameId != 6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,10 +203,7 @@
|
||||
for (var i = 0; i < namesLength; i++)
|
||||
{
|
||||
var numberOfCharacters = data.ReadByte();
|
||||
if (data.TryReadString(numberOfCharacters, Encoding.UTF8, out var str))
|
||||
{
|
||||
nameArray[i] = str;
|
||||
}
|
||||
nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core;
|
||||
|
||||
@@ -80,33 +81,22 @@
|
||||
/// </summary>
|
||||
public string ReadTag()
|
||||
{
|
||||
if (TryReadString(4, Encoding.UTF8, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not read Tag from TrueType file at {inputBytes.CurrentOffset}.");
|
||||
return ReadString(4, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see langword="string"/> of the given number of bytes in length with the specified encoding.
|
||||
/// </summary>
|
||||
public bool TryReadString(int bytesToRead, Encoding encoding, out string result)
|
||||
public string ReadString(int bytesToRead, Encoding encoding)
|
||||
{
|
||||
result = null;
|
||||
if (encoding == null)
|
||||
{
|
||||
return false;
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
byte[] data = new byte[bytesToRead];
|
||||
if (ReadBuffered(data, bytesToRead))
|
||||
{
|
||||
result = encoding.GetString(data, 0, data.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
ReadBuffered(data, bytesToRead);
|
||||
return encoding.GetString(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -244,15 +234,13 @@
|
||||
return $"@: {Position} of {inputBytes.Length} bytes.";
|
||||
}
|
||||
|
||||
private bool ReadBuffered(byte[] buffer, int length)
|
||||
private void ReadBuffered(byte[] buffer, int length)
|
||||
{
|
||||
var read = inputBytes.Read(buffer, length);
|
||||
if (read < length)
|
||||
{
|
||||
return false;
|
||||
throw new EndOfStreamException($"Could not read a buffer of {length} bytes.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction;
|
||||
|
||||
/// <summary>
|
||||
/// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then
|
||||
@@ -25,13 +23,13 @@
|
||||
|
||||
public static bool TakeFromStackBottom { get; } = false;
|
||||
public static bool ClearsOperandStack { get; } = false;
|
||||
|
||||
|
||||
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var index = (int)context.Stack.PopTop();
|
||||
|
||||
var index = (int) context.Stack.PopTop();
|
||||
|
||||
// What it should do
|
||||
var numberOfArguments = (int)context.Stack.PopTop();
|
||||
var otherSubroutineArguments = new List<double>(numberOfArguments);
|
||||
@@ -44,44 +42,17 @@
|
||||
{
|
||||
// Other subrs 0-2 implement flex
|
||||
case FlexEnd:
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/2c23d8b4e3ad61852f0b6ee2b95b907eefba1fcf/fontbox/src/main/java/org/apache/fontbox/cff/Type1CharString.java#L339
|
||||
context.IsFlexing = false;
|
||||
if (context.FlexPoints.Count < 7)
|
||||
{
|
||||
throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
}
|
||||
{
|
||||
context.IsFlexing = false;
|
||||
// TODO: I don't really care about flexpoints, but we should probably handle them... one day.
|
||||
//if (context.FlexPoints.Count < 7)
|
||||
//{
|
||||
// throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
//}
|
||||
|
||||
// reference point is relative to start point
|
||||
PdfPoint reference = context.FlexPoints[0];
|
||||
reference = reference.Translate(context.CurrentPosition.X, context.CurrentPosition.Y);
|
||||
|
||||
// first point is relative to reference point
|
||||
PdfPoint first = context.FlexPoints[1];
|
||||
first = first.Translate(reference.X, reference.Y);
|
||||
|
||||
// make the first point relative to the start point
|
||||
first = first.Translate(-context.CurrentPosition.X, -context.CurrentPosition.Y);
|
||||
|
||||
context.Stack.Push(first.X);
|
||||
context.Stack.Push(first.Y);
|
||||
context.Stack.Push(context.FlexPoints[2].X);
|
||||
context.Stack.Push(context.FlexPoints[2].Y);
|
||||
context.Stack.Push(context.FlexPoints[3].X);
|
||||
context.Stack.Push(context.FlexPoints[3].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.Stack.Push(context.FlexPoints[4].X);
|
||||
context.Stack.Push(context.FlexPoints[4].Y);
|
||||
context.Stack.Push(context.FlexPoints[5].X);
|
||||
context.Stack.Push(context.FlexPoints[5].Y);
|
||||
context.Stack.Push(context.FlexPoints[6].X);
|
||||
context.Stack.Push(context.FlexPoints[6].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
case FlexBegin:
|
||||
Debug.Assert(otherSubroutineArguments.Count == 0, "Flex begin should have no arguments.");
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
|
||||
/// <para>This establishes the current point for a subsequent relative path building command.
|
||||
/// The 'setcurrentpoint' command is used only in conjunction with results from 'OtherSubrs' procedures.</para>
|
||||
/// </summary>
|
||||
internal static class SetCurrentPointCommand
|
||||
{
|
||||
@@ -24,8 +22,8 @@
|
||||
var x = context.Stack.PopBottom();
|
||||
var y = context.Stack.PopBottom();
|
||||
|
||||
//context.CurrentPosition = new PdfPoint(x, y);
|
||||
// TODO: need to investigate why odd behavior when the current point is actualy set.
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
|
||||
context.Stack.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var deltaX = context.Stack.PopBottom();
|
||||
var x = context.Stack.PopBottom();
|
||||
|
||||
var actualX = context.CurrentPosition.X + x;
|
||||
var y = context.CurrentPosition.Y;
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, 0));
|
||||
// TODO: flex support
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y;
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
context.CurrentPosition = new PdfPoint(actualX, y);
|
||||
context.Path.MoveTo(actualX, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
|
||||
{
|
||||
using Core;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Vertical move to. Moves relative to the current point.
|
||||
@@ -24,8 +23,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(0, deltaY));
|
||||
// TODO: flex commands
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/// Sets left sidebearing and the character width vector.
|
||||
/// This command also sets the current point to(sbx, sby), but does not place the point in the character path.
|
||||
/// </summary>
|
||||
internal static class SbwCommand
|
||||
internal class SbwCommand
|
||||
{
|
||||
public const string Name = "sbw";
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
public CharStringStack PostscriptStack { get; } = new CharStringStack();
|
||||
|
||||
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
||||
public IReadOnlyList<PdfPoint> FlexPoints { get; }
|
||||
|
||||
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
||||
Func<int, PdfSubpath> characterByIndexFactory,
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
public void AddFlexPoint(PdfPoint point)
|
||||
{
|
||||
FlexPoints.Add(point);
|
||||
|
||||
}
|
||||
|
||||
public PdfSubpath GetCharacter(int characterCode)
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
public void ClearFlexPoints()
|
||||
{
|
||||
FlexPoints.Clear();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
||||
using UglyToad.PdfPig.Fonts.SystemFonts;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
||||
using Xunit;
|
||||
|
||||
public class DocstrumBoundingBoxesTests
|
||||
@@ -71,18 +70,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
[SkippableTheory]
|
||||
[Theory]
|
||||
[MemberData(nameof(DataExtract))]
|
||||
public void GetBlocks(string name, string[] expected)
|
||||
{
|
||||
if (name == "90 180 270 rotated.pdf")
|
||||
{
|
||||
// The 'TimesNewRomanPSMT' font is used by this particular document. Thus, results cannot be trusted on
|
||||
// platforms where this font isn't generally available (e.g. OSX, Linux, etc.), so we skip it!
|
||||
var font = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPSMT");
|
||||
Skip.If(font == null, "Skipped because the font TimesNewRomanPSMT could not be found in the execution environment.");
|
||||
}
|
||||
|
||||
var options = new DocstrumBoundingBoxes.DocstrumBoundingBoxesOptions() { LineSeparator = " " };
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
|
||||
{
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Filters
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Filters;
|
||||
using UglyToad.PdfPig.Tests.Images;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class CcittFaxDecodeFilterTests
|
||||
{
|
||||
[Fact]
|
||||
public void CanDecodeCCittFaxCompressedImageData()
|
||||
{
|
||||
var encodedBytes = ImageHelpers.LoadFileBytes("ccittfax-encoded.bin");
|
||||
|
||||
var filter = new CcittFaxDecodeFilter();
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.D, new ArrayToken(new []{ new NumericToken(1), new NumericToken(0) })},
|
||||
{ NameToken.W, new NumericToken(1800) },
|
||||
{ NameToken.H, new NumericToken(3113) },
|
||||
{ NameToken.Bpc, new NumericToken(1) },
|
||||
{ NameToken.F, NameToken.CcittfaxDecode },
|
||||
{ NameToken.DecodeParms,
|
||||
new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.K, new NumericToken(-1) },
|
||||
{ NameToken.Columns, new NumericToken(1800) },
|
||||
{ NameToken.Rows, new NumericToken(3113) },
|
||||
{ NameToken.BlackIs1, BooleanToken.True }
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
var expectedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
|
||||
var decodedBytes = filter.Decode(encodedBytes, new DictionaryToken(dictionary), 0);
|
||||
Assert.Equal(expectedBytes, decodedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,106 +31,5 @@
|
||||
|
||||
Assert.Empty(result.Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleFilter_ReturnsParameterDictionary()
|
||||
{
|
||||
var filter = NameToken.CcittfaxDecode;
|
||||
var filterParameters = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.K, new NumericToken(-1) },
|
||||
{ NameToken.Columns, new NumericToken(1800) },
|
||||
{ NameToken.Rows, new NumericToken(3113) },
|
||||
{ NameToken.BlackIs1, BooleanToken.True }
|
||||
});
|
||||
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.F, filter },
|
||||
{ NameToken.DecodeParms, filterParameters }
|
||||
};
|
||||
|
||||
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
|
||||
|
||||
Assert.Equal(filterParameters, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SingleFilter_SpecifiedInArray_ReturnsParameterDictionary()
|
||||
{
|
||||
var filter = NameToken.CcittfaxDecode;
|
||||
var filterParameters = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.K, new NumericToken(-1) },
|
||||
{ NameToken.Columns, new NumericToken(1800) },
|
||||
{ NameToken.Rows, new NumericToken(3113) },
|
||||
{ NameToken.BlackIs1, BooleanToken.True }
|
||||
});
|
||||
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.F, new ArrayToken(new [] { filter }) },
|
||||
{ NameToken.DecodeParms, new ArrayToken(new [] { filterParameters }) }
|
||||
};
|
||||
|
||||
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
|
||||
|
||||
Assert.Equal(filterParameters, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleFilters_WhenParameterIsNull_ReturnsEmptyDictionary()
|
||||
{
|
||||
var filter1 = NameToken.FlateDecode;
|
||||
var filter1Parameters = NullToken.Instance;
|
||||
|
||||
var filter2 = NameToken.CcittfaxDecode;
|
||||
var filter2Parameters = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.K, new NumericToken(-1) },
|
||||
{ NameToken.Columns, new NumericToken(1800) },
|
||||
{ NameToken.Rows, new NumericToken(3113) },
|
||||
{ NameToken.BlackIs1, BooleanToken.True }
|
||||
|
||||
});
|
||||
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.F, new ArrayToken(new [] { filter1, filter2 }) },
|
||||
{ NameToken.DecodeParms, new ArrayToken(new IToken[] { filter1Parameters, filter2Parameters }) }
|
||||
};
|
||||
|
||||
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 0);
|
||||
|
||||
Assert.Equal(new DictionaryToken(new Dictionary<NameToken, IToken>()), result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleFilters_ReturnsParameterDictionary()
|
||||
{
|
||||
var filter1 = NameToken.FlateDecode;
|
||||
var filter1Parameters = NullToken.Instance;
|
||||
|
||||
var filter2 = NameToken.CcittfaxDecode;
|
||||
var filter2Parameters = new DictionaryToken(new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.K, new NumericToken(-1) },
|
||||
{ NameToken.Columns, new NumericToken(1800) },
|
||||
{ NameToken.Rows, new NumericToken(3113) },
|
||||
{ NameToken.BlackIs1, BooleanToken.True }
|
||||
});
|
||||
|
||||
var dictionary = new Dictionary<NameToken, IToken>
|
||||
{
|
||||
{ NameToken.F, new ArrayToken(new [] { filter1, filter2 }) },
|
||||
{ NameToken.DecodeParms, new ArrayToken(new IToken[] { filter1Parameters, filter2Parameters }) }
|
||||
};
|
||||
|
||||
var result = DecodeParameterResolver.GetFilterParameters(new DictionaryToken(dictionary), 1);
|
||||
|
||||
Assert.Equal(filter2Parameters, result);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
using System.Linq;
|
||||
using PdfFonts.Cmap;
|
||||
using PdfPig.Tokens;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.PdfFonts.Parser.Parts;
|
||||
using UglyToad.PdfPig.Tokenization.Scanner;
|
||||
using Xunit;
|
||||
|
||||
public class CodespaceRangeTests
|
||||
@@ -103,29 +100,6 @@
|
||||
Assert.True(matches);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ColorspaceParserError()
|
||||
{
|
||||
var parser = new CodespaceRangeParser();
|
||||
var byteArrayInput = new ByteArrayInputBytes(OtherEncodings.StringAsLatin1Bytes("1 begincodespacerange\nendcodespacerange"));
|
||||
var tokenScanner = new CoreTokenScanner(byteArrayInput);
|
||||
|
||||
Assert.True(tokenScanner.MoveNext());
|
||||
Assert.True(tokenScanner.CurrentToken is NumericToken);
|
||||
var numeric = (NumericToken) tokenScanner.CurrentToken;
|
||||
|
||||
Assert.True(tokenScanner.MoveNext());
|
||||
Assert.True(tokenScanner.CurrentToken is OperatorToken);
|
||||
var opToken = (OperatorToken)tokenScanner.CurrentToken;
|
||||
Assert.Equal("begincodespacerange", opToken.Data);
|
||||
|
||||
var cmapBuilder = new CharacterMapBuilder();
|
||||
|
||||
parser.Parse(numeric, tokenScanner, cmapBuilder);
|
||||
|
||||
Assert.Empty(cmapBuilder.CodespaceRanges);
|
||||
}
|
||||
|
||||
private static byte[] GetHexBytes(params char[] characters)
|
||||
{
|
||||
var token = new HexToken(characters);
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts
|
||||
{
|
||||
using UglyToad.PdfPig.Tests.Dla;
|
||||
using Xunit;
|
||||
|
||||
public class PointSizeTests
|
||||
{
|
||||
[Fact]
|
||||
public void RotatedText()
|
||||
{
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath("complex rotated")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
Assert.Equal(12, letter.PointSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
{
|
||||
var names = Standard14.GetNames().Count;
|
||||
|
||||
Assert.Equal(39, names);
|
||||
Assert.Equal(34, names);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,20 +210,6 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
Assert.Equal(points, glyph.Points.Length);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseIssue258CorruptNameTable()
|
||||
{
|
||||
var bytes = TrueTypeTestHelper.GetFileBytes("issue-258-corrupt-name-table");
|
||||
|
||||
var input = new TrueTypeDataBytes(new ByteArrayInputBytes(bytes));
|
||||
|
||||
var font = TrueTypeFontParser.Parse(input);
|
||||
|
||||
Assert.NotNull(font);
|
||||
Assert.NotNull(font.TableRegister.NameTable);
|
||||
Assert.NotEmpty(font.TableRegister.NameTable.NameRecords);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,39 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.Type1
|
||||
{
|
||||
public class Type1CharStringParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void CorrectBoundingBoxesFlexPoints()
|
||||
{
|
||||
PointComparer pointComparer = new PointComparer(new DoubleComparer(3));
|
||||
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
|
||||
var filePath = Path.Combine(documentFolder, "data.pdf");
|
||||
|
||||
using (var doc = PdfDocument.Open(filePath))
|
||||
{
|
||||
var page = doc.GetPage(1);
|
||||
|
||||
var letters = page.Letters;
|
||||
|
||||
// check 'm'
|
||||
var m = letters[0];
|
||||
Assert.Equal("m", m.Value);
|
||||
Assert.Equal(new PdfPoint(253.4458, 658.431), m.GlyphRectangle.BottomLeft, pointComparer);
|
||||
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.GlyphRectangle.TopRight, pointComparer);
|
||||
|
||||
// check 'p'
|
||||
var p = letters[1];
|
||||
Assert.Equal("p", p.Value);
|
||||
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.GlyphRectangle.BottomLeft, pointComparer);
|
||||
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.GlyphRectangle.TopRight, pointComparer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1682,17 +1682,5 @@
|
||||
Assert.Equal(expected[2], rectR.TopLeft, PointComparer);
|
||||
Assert.Equal(expected[3], rectR.BottomLeft, PointComparer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue261()
|
||||
{
|
||||
// Some points have negative coordinates after rotation, resulting in negative Height
|
||||
PdfRectangle rect = new PdfRectangle(new PdfPoint(401.51, 461.803424),
|
||||
new PdfPoint(300.17322363281249, 461.803424),
|
||||
new PdfPoint(401.51, 291.45),
|
||||
new PdfPoint(300.17322363281249, 291.45));
|
||||
|
||||
Assert.True(rect.Height > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
using PdfPig.Tokens;
|
||||
using PdfPig.Core;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
|
||||
|
||||
internal class TestOperationContext : IOperationContext
|
||||
{
|
||||
@@ -90,66 +88,6 @@
|
||||
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
BeginSubpath();
|
||||
var point = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
CurrentPosition = point;
|
||||
CurrentSubpath.MoveTo(point.X, point.Y);
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(CurrentPosition.X, CurrentPosition.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint1 = CurrentTransformationMatrix.Transform(new PdfPoint(x1, y1));
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void LineTo(double x, double y)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPoint = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
|
||||
CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
|
||||
CurrentPosition = endPoint;
|
||||
}
|
||||
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
BeginSubpath();
|
||||
var lowerLeft = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
var upperRight = CurrentTransformationMatrix.Transform(new PdfPoint(x + width, y + height));
|
||||
|
||||
CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
|
||||
AddCurrentSubpath();
|
||||
}
|
||||
|
||||
public void EndPath()
|
||||
{
|
||||
}
|
||||
@@ -186,91 +124,6 @@
|
||||
{
|
||||
}
|
||||
|
||||
private void AdjustTextMatrix(double tx, double ty)
|
||||
{
|
||||
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
||||
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
|
||||
}
|
||||
|
||||
public void SetFlatnessTolerance(decimal tolerance)
|
||||
{
|
||||
GetCurrentState().Flatness = tolerance;
|
||||
}
|
||||
|
||||
public void SetLineCap(LineCapStyle cap)
|
||||
{
|
||||
GetCurrentState().CapStyle = cap;
|
||||
}
|
||||
|
||||
public void SetLineDashPattern(LineDashPattern pattern)
|
||||
{
|
||||
GetCurrentState().LineDashPattern = pattern;
|
||||
}
|
||||
|
||||
public void SetLineJoin(LineJoinStyle join)
|
||||
{
|
||||
GetCurrentState().JoinStyle = join;
|
||||
}
|
||||
|
||||
public void SetLineWidth(decimal width)
|
||||
{
|
||||
GetCurrentState().LineWidth = width;
|
||||
}
|
||||
|
||||
public void SetMiterLimit(decimal limit)
|
||||
{
|
||||
GetCurrentState().MiterLimit = limit;
|
||||
}
|
||||
|
||||
public void MoveToNextLineWithOffset()
|
||||
{
|
||||
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)GetCurrentState().FontState.Leading);
|
||||
tdOperation.Run(this);
|
||||
}
|
||||
|
||||
public void SetFontAndSize(NameToken font, double size)
|
||||
{
|
||||
var currentState = GetCurrentState();
|
||||
currentState.FontState.FontSize = size;
|
||||
currentState.FontState.FontName = font;
|
||||
}
|
||||
|
||||
public void SetHorizontalScaling(double scale)
|
||||
{
|
||||
GetCurrentState().FontState.HorizontalScaling = scale;
|
||||
}
|
||||
|
||||
public void SetTextLeading(double leading)
|
||||
{
|
||||
GetCurrentState().FontState.Leading = leading;
|
||||
}
|
||||
|
||||
public void SetTextRenderingMode(TextRenderingMode mode)
|
||||
{
|
||||
GetCurrentState().FontState.TextRenderingMode = mode;
|
||||
}
|
||||
|
||||
public void SetTextRise(double rise)
|
||||
{
|
||||
GetCurrentState().FontState.Rise = rise;
|
||||
}
|
||||
|
||||
public void SetWordSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.WordSpacing = spacing;
|
||||
}
|
||||
|
||||
public void ModifyCurrentTransformationMatrix(double[] value)
|
||||
{
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
}
|
||||
|
||||
public void SetCharacterSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.CharacterSpacing = spacing;
|
||||
}
|
||||
|
||||
private class TestFontFactory : IFontFactory
|
||||
{
|
||||
public IFont Get(DictionaryToken dictionary)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 77 B |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 522 KiB |
@@ -1 +0,0 @@
|
||||
h・m
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 B |
@@ -1,52 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Images
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
|
||||
public static class ImageHelpers
|
||||
{
|
||||
private static readonly string FilesFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files"));
|
||||
|
||||
public static byte[] LoadFileBytes(string filename, bool isCompressed = false)
|
||||
{
|
||||
var filePath = Path.Combine(FilesFolder, filename);
|
||||
var memoryStream = new MemoryStream();
|
||||
if (isCompressed)
|
||||
{
|
||||
using (var deflateStream = new DeflateStream(File.OpenRead(filePath), CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.CopyTo(memoryStream);
|
||||
}
|
||||
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
return File.ReadAllBytes(filePath);
|
||||
}
|
||||
|
||||
public static bool ImagesAreEqual(byte[] first, byte[] second)
|
||||
{
|
||||
var png1 = Png.Open(first);
|
||||
var png2 = Png.Open(second);
|
||||
if (png1.Width != png2.Width || png1.Height != png2.Height || png1.HasAlphaChannel != png2.HasAlphaChannel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var y = 0; y < png1.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < png1.Width; x++)
|
||||
{
|
||||
if (!png1.GetPixel(x, y).Equals(png2.GetPixel(x, y)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Images
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using Xunit;
|
||||
|
||||
public class PngFromPdfImageFactoryTests
|
||||
{
|
||||
private static readonly byte[] RgbBlack = new byte[] { 0, 0, 0 };
|
||||
private static readonly byte[] RgbWhite = new byte[] { 255, 255, 255 };
|
||||
private static readonly byte[][] RgbPalette = new[] { RgbBlack, RgbWhite };
|
||||
|
||||
private static readonly byte[] CmykBlack = new byte[] { 0, 0, 0, 255 };
|
||||
private static readonly byte[] CmykWhite = new byte[] { 0, 0, 0, 0 };
|
||||
|
||||
private static readonly byte GrayscaleBlack = 0;
|
||||
private static readonly byte GrayscaleWhite = 255;
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceRgbImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
RgbWhite, RgbBlack, RgbWhite,
|
||||
RgbBlack, RgbWhite, RgbBlack,
|
||||
RgbWhite, RgbBlack, RgbWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceCMYKImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
CmykWhite, CmykBlack, CmykWhite,
|
||||
CmykBlack, CmykWhite, CmykBlack,
|
||||
CmykWhite, CmykBlack, CmykWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceCmykColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceGrayscaleImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite,
|
||||
GrayscaleBlack, GrayscaleWhite, GrayscaleBlack,
|
||||
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceGrayColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromIndexedImageData8bpc()
|
||||
{
|
||||
var indices = new byte[]
|
||||
{
|
||||
1, 0, 1,
|
||||
0, 1, 0,
|
||||
1, 0, 1
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, RgbPalette.SelectMany(b => b).ToArray()),
|
||||
DecodedBytes = indices,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromIndexedImageData1bpc()
|
||||
{
|
||||
// Indices for a 3x3 RGB image, each index is represented by a single bit
|
||||
// 1, 0, 1,
|
||||
// 0, 1, 0,
|
||||
// 1, 0, 1
|
||||
//
|
||||
// A scanline must be at least one byte wide, so the remaining five bits are padding:
|
||||
// Byte 0: 10100000 (bits #7 and #5 are 1)
|
||||
// Byte 1: 01000000 (bit #6 is 1)
|
||||
// Byte 2: 10100000 (bits #7 and #5 are 1)
|
||||
// ||||||||
|
||||
// Bit # : 76543210
|
||||
|
||||
var lines = new byte[3];
|
||||
lines[0] |= (1 << 7); // Set bit #7 to 1
|
||||
lines[0] |= (1 << 5); // Set bit #5 to 1
|
||||
|
||||
lines[1] |= (1 << 6); // Set bit #6 to 1
|
||||
|
||||
lines[2] |= (1 << 7); // Set bit #7 to 1
|
||||
lines[2] |= (1 << 5); // Set bit #5 to 1
|
||||
|
||||
var colorTable = RgbPalette.SelectMany(b => b).ToArray();
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, colorTable),
|
||||
DecodedBytes = lines,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3,
|
||||
BitsPerComponent = 1
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCcittFaxDecodedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance, new[] { 1m, 0 }),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1800,
|
||||
HeightInSamples = 3113,
|
||||
BitsPerComponent = 1
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("ccittfax.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromICCBasedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("iccbased.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlternateColorSpaceDetailsIsCurrentlyUsedInPdfPigWhenGeneratingPngsFromICCBasedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
|
||||
var iccBasedImage = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
var deviceRGBImage = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(iccBasedImage, out var iccPngBytes));
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(deviceRGBImage, out var deviceRgbBytes));
|
||||
Assert.Equal(iccPngBytes, deviceRgbBytes);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCalRGBImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("calrgb-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new CalRGBColorSpaceDetails(
|
||||
whitePoint: new List<decimal> { 0.95043m, 1, 1.09m },
|
||||
blackPoint: null,
|
||||
gamma: new List<decimal> { 2.2m, 2.2m, 2.2m },
|
||||
matrix: new List<decimal> {
|
||||
0.41239m, 0.21264m, 0.01933m,
|
||||
0.35758m, 0.71517m, 0.11919m,
|
||||
0.18045m, 0.07218m, 0.9504m }),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 153,
|
||||
HeightInSamples = 83,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calrgb.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCalGrayImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("calgray-decoded.bin", isCompressed: true);
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new CalGrayColorSpaceDetails(
|
||||
whitePoint: new List<decimal> { 0.9505000114m, 1, 1.0889999866m },
|
||||
blackPoint: null,
|
||||
gamma: 2.2000000477m),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 2480,
|
||||
HeightInSamples = 1748,
|
||||
BitsPerComponent = 8,
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calgray.png"), bytes));
|
||||
}
|
||||
|
||||
private static byte[] LoadImage(string name)
|
||||
{
|
||||
return ImageHelpers.LoadFileBytes(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using PdfPig.Tokens;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
public class AdvancedPdfDocumentAccessTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplacesObjectsFunc()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, tk =>
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replaced = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
return replaced;
|
||||
});
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacesObjects()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replacement = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, replacement);
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,61 +0,0 @@
|
||||
%PDF-1.1
|
||||
1 0 obj
|
||||
<</Type/Catalog/Pages 2 0 R>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<</Type/Pages/Count 1/Kids[3 0 R]/MediaBox [0 0 200 100]>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Type/Page
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/XObject << /SomeImage 4 0 R >>
|
||||
>>
|
||||
/Contents 5 0 R
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Type/XObject
|
||||
/Subtype/Image
|
||||
/Width 200 % The width or height directly affects the image's file size.
|
||||
/Height 100
|
||||
/ColorSpace/DeviceRGB
|
||||
/DecodeParms [] % Forces NativeImageDecoder.isSupported to return false.
|
||||
/BitsPerComponent 8
|
||||
/Length 580
|
||||
/Filter [ /ASCIIHexDecode /DCTDecode ]
|
||||
>>
|
||||
% convert -size 1x1 xc:red jpeg:- | xxd -p -c40
|
||||
stream
|
||||
ffd8ffe000104a46494600010100000100010000ffdb004300030202020202030202020303030304
|
||||
060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c
|
||||
12131210130f101010ffdb00430103030304030408040408100b090b101010101010101010101010
|
||||
1010101010101010101010101010101010101010101010101010101010101010101010101010ffc0
|
||||
0011080001000103011100021101031101ffc40014000100000000000000000000000000000008ff
|
||||
c40014100100000000000000000000000000000000ffc40015010101000000000000000000000000
|
||||
00000709ffc40014110100000000000000000000000000000000ffda000c03010002110311003f00
|
||||
3a03154dffd9
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<</Length 14>>
|
||||
stream
|
||||
500 0 0 400 0 0 cm
|
||||
/SomeImage Do
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000008 00000 n
|
||||
0000000054 00000 n
|
||||
0000000128 00000 n
|
||||
0000000246 00000 n
|
||||
0000001201 00000 n
|
||||
trailer
|
||||
<</Root 1 0 R/Size 6>>
|
||||
startxref
|
||||
1281
|
||||
%%EOF
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB |
Binary file not shown.
@@ -1,66 +0,0 @@
|
||||
using System;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
public class OptionalContentTests
|
||||
{
|
||||
[Fact]
|
||||
public void NoMarkedOptionalContent()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("AcroFormsBasicFields.pdf")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var oc = page.ExperimentalAccess.GetOptionalContents();
|
||||
|
||||
Assert.Equal(0, oc.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkedOptionalContent()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("odwriteex.pdf")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var oc = page.ExperimentalAccess.GetOptionalContents();
|
||||
|
||||
Assert.Equal(3, oc.Count);
|
||||
|
||||
Assert.Contains("0", oc);
|
||||
Assert.Contains("Dimentions", oc);
|
||||
Assert.Contains("Text", oc);
|
||||
|
||||
Assert.Equal(1, oc["0"].Count);
|
||||
Assert.Equal(2, oc["Dimentions"].Count);
|
||||
Assert.Equal(1, oc["Text"].Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MarkedOptionalContentRecursion()
|
||||
{
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Layer pdf - 322_High_Holborn_building_Brochure.pdf")))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
var oc1 = page1.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(16, oc1.Count);
|
||||
Assert.Contains("NEW ARRANGEMENT", oc1);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
var oc2 = page2.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(15, oc2.Count);
|
||||
Assert.DoesNotContain("NEW ARRANGEMENT", oc2);
|
||||
Assert.Contains("WDL Shell text", oc2);
|
||||
Assert.Equal(2, oc2["WDL Shell text"].Count);
|
||||
|
||||
var page3 = document.GetPage(3);
|
||||
var oc3 = page3.ExperimentalAccess.GetOptionalContents();
|
||||
Assert.Equal(15, oc3.Count);
|
||||
Assert.Contains("WDL Shell text", oc3);
|
||||
Assert.Equal(2, oc3["WDL Shell text"].Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,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()))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using Logging;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Parser.FileStructure;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using Xunit;
|
||||
|
||||
public class FileHeaderParserTests
|
||||
@@ -49,7 +48,7 @@
|
||||
var result = FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Equal(1.2m, result.Version);
|
||||
Assert.Equal(TestEnvironment.IsUnixPlatform ? 7 : 9, result.OffsetInFile);
|
||||
Assert.Equal(9, result.OffsetInFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -71,7 +70,7 @@
|
||||
var result = FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Equal(1.2m, result.Version);
|
||||
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
|
||||
Assert.Equal(13, result.OffsetInFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -83,7 +82,7 @@
|
||||
var result = FileHeaderParser.Parse(scanner, true, log);
|
||||
|
||||
Assert.Equal(1.7m, result.Version);
|
||||
Assert.Equal(TestEnvironment.IsUnixPlatform ? 12 : 13, result.OffsetInFile);
|
||||
Assert.Equal(13, result.OffsetInFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -95,7 +94,7 @@ three %PDF-1.6");
|
||||
var result = FileHeaderParser.Parse(scanner, true, log);
|
||||
|
||||
Assert.Equal(1.6m, result.Version);
|
||||
Assert.Equal(TestEnvironment.IsUnixPlatform ? 14 : 15, result.OffsetInFile);
|
||||
Assert.Equal(15, result.OffsetInFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -138,17 +137,5 @@ three %PDF-1.6");
|
||||
Assert.Equal(0, scanner.CurrentPosition);
|
||||
Assert.Equal(0, result.OffsetInFile);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue334()
|
||||
{
|
||||
var input = OtherEncodings.StringAsLatin1Bytes("%PDF-1.7\r\n%âãÏÓ\r\n1 0 obj\r\n<</Lang(en-US)>>\r\nendobj");
|
||||
|
||||
var scanner = new CoreTokenScanner(new ByteArrayInputBytes(input), ScannerScope.None);
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Equal(1.7m, result.Version);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,8 +74,6 @@
|
||||
"UglyToad.PdfPig.Content.IPdfImage",
|
||||
"UglyToad.PdfPig.Content.Letter",
|
||||
"UglyToad.PdfPig.Content.MarkedContentElement",
|
||||
"UglyToad.PdfPig.Content.MediaBox",
|
||||
"UglyToad.PdfPig.Content.OptionalContentGroupElement",
|
||||
"UglyToad.PdfPig.Content.Page",
|
||||
"UglyToad.PdfPig.Content.PageRotationDegrees",
|
||||
"UglyToad.PdfPig.Content.PageSize",
|
||||
@@ -104,16 +102,6 @@
|
||||
"UglyToad.PdfPig.Graphics.Colors.GrayColor",
|
||||
"UglyToad.PdfPig.Graphics.Colors.IColor",
|
||||
"UglyToad.PdfPig.Graphics.Colors.RGBColor",
|
||||
"UglyToad.PdfPig.Graphics.Colors.ColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.CalGrayColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.CalRGBColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.DeviceGrayColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.DeviceRgbColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.DeviceCmykColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.ICCBasedColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.IndexedColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.SeparationColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Colors.UnsupportedColorSpaceDetails",
|
||||
"UglyToad.PdfPig.Graphics.Core.LineCapStyle",
|
||||
"UglyToad.PdfPig.Graphics.Core.LineDashPattern",
|
||||
"UglyToad.PdfPig.Graphics.Core.LineJoinStyle",
|
||||
@@ -198,7 +186,6 @@
|
||||
"UglyToad.PdfPig.Graphics.Operations.TextState.Type3SetGlyphWidth",
|
||||
"UglyToad.PdfPig.Graphics.Operations.TextState.Type3SetGlyphWidthAndBoundingBox",
|
||||
"UglyToad.PdfPig.Graphics.TextMatrices",
|
||||
"UglyToad.PdfPig.Images.ColorSpaceDetailsByteConverter",
|
||||
"UglyToad.PdfPig.Logging.ILog",
|
||||
"UglyToad.PdfPig.Outline.Bookmarks",
|
||||
"UglyToad.PdfPig.Outline.BookmarkNode",
|
||||
@@ -210,8 +197,6 @@
|
||||
"UglyToad.PdfPig.ParsingOptions",
|
||||
"UglyToad.PdfPig.PdfDocument",
|
||||
"UglyToad.PdfPig.PdfExtensions",
|
||||
"UglyToad.PdfPig.Rendering.IPageImageRenderer",
|
||||
"UglyToad.PdfPig.Rendering.PdfRendererImageFormat",
|
||||
"UglyToad.PdfPig.Structure",
|
||||
"UglyToad.PdfPig.Util.Adler32Checksum",
|
||||
"UglyToad.PdfPig.Util.IWordExtractor",
|
||||
@@ -221,7 +206,6 @@
|
||||
"UglyToad.PdfPig.Writer.PdfAStandard",
|
||||
"UglyToad.PdfPig.Writer.PdfDocumentBuilder",
|
||||
"UglyToad.PdfPig.Writer.PdfMerger",
|
||||
"UglyToad.PdfPig.Writer.PdfWriterType",
|
||||
"UglyToad.PdfPig.Writer.PdfPageBuilder",
|
||||
"UglyToad.PdfPig.Writer.TokenWriter",
|
||||
"UglyToad.PdfPig.XObjects.XObjectImage"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests
|
||||
{
|
||||
using System;
|
||||
|
||||
public static class TestEnvironment
|
||||
{
|
||||
public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using PdfPig.Filters;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using PdfPig.Tokens;
|
||||
|
||||
internal class TestFilterProvider : ILookupFilterProvider
|
||||
internal class TestFilterProvider : IFilterProvider
|
||||
{
|
||||
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
|
||||
{
|
||||
@@ -21,10 +20,5 @@
|
||||
{
|
||||
return new List<IFilter>();
|
||||
}
|
||||
|
||||
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary, IPdfTokenScanner scanner)
|
||||
{
|
||||
return new List<IFilter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
public class TestPdfImage : IPdfImage
|
||||
{
|
||||
public PdfRectangle Bounds { get; set; }
|
||||
|
||||
public int WidthInSamples { get; set; }
|
||||
|
||||
public int HeightInSamples { get; set; }
|
||||
|
||||
public ColorSpace? ColorSpace => IsImageMask ? default(ColorSpace?) : ColorSpaceDetails.Type;
|
||||
|
||||
public int BitsPerComponent { get; set; } = 8;
|
||||
|
||||
public IReadOnlyList<byte> RawBytes { get; }
|
||||
|
||||
public RenderingIntent RenderingIntent { get; set; } = RenderingIntent.RelativeColorimetric;
|
||||
|
||||
public bool IsImageMask { get; set; }
|
||||
|
||||
public IReadOnlyList<decimal> Decode { get; set; }
|
||||
|
||||
public bool Interpolate { get; set; }
|
||||
|
||||
public bool IsInlineImage { get; set; }
|
||||
|
||||
public DictionaryToken ImageDictionary { get; set; }
|
||||
|
||||
public ColorSpaceDetails ColorSpaceDetails { get; set; }
|
||||
|
||||
public IReadOnlyList<byte> DecodedBytes { get; set; }
|
||||
|
||||
public bool TryGetBytes(out IReadOnlyList<byte> bytes)
|
||||
{
|
||||
bytes = DecodedBytes;
|
||||
return bytes != null;
|
||||
}
|
||||
|
||||
public bool TryGetPng(out byte[] bytes) => PngFromPdfImageFactory.TryGenerate(this, out bytes);
|
||||
}
|
||||
}
|
||||
@@ -96,18 +96,6 @@
|
||||
Assert.Equal(0m, AssertNumericToken(token).Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleDoubleDashedNumber()
|
||||
{
|
||||
// This is a really weird format but seen in the wild. PDF, shine on, you crazy diamond.
|
||||
var input = StringBytesTestConverter.Convert("--10.25");
|
||||
|
||||
var result = tokenizer.TryTokenize(input.First, input.Bytes, out var token);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(-10.25m, AssertNumericToken(token).Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesDot()
|
||||
{
|
||||
|
||||
@@ -169,33 +169,7 @@ endobj";
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[2], "Tj");
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[3], -91);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScansStringWithWeirdWeirdDoubleSymbolNumerics()
|
||||
{
|
||||
const string content = @"
|
||||
0.00 --21.72 TD
|
||||
/F1 8.00 Tf";
|
||||
|
||||
var tokens = new List<IToken>();
|
||||
|
||||
var scanner = scannerFactory(StringBytesTestConverter.Convert(content, false).Bytes);
|
||||
|
||||
while (scanner.MoveNext())
|
||||
{
|
||||
tokens.Add(scanner.CurrentToken);
|
||||
}
|
||||
|
||||
Assert.Equal(6, tokens.Count);
|
||||
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[0], 0m);
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[1], -21.72m);
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[2], "TD");
|
||||
AssertCorrectToken<NameToken, string>(tokens[3], "F1");
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[4], 8m);
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[5], "Tf");
|
||||
|
||||
}
|
||||
|
||||
private static void AssertCorrectToken<T, TData>(IToken token, TData expected) where T : IDataToken<TData>
|
||||
{
|
||||
var cast = Assert.IsType<T>(token);
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
[Fact]
|
||||
public void ReadsSimpleObject()
|
||||
{
|
||||
const string s = @"294 0 obj
|
||||
/WDKAAR+CMBX12
|
||||
endobj";
|
||||
const string s = @"294 0 obj
|
||||
/WDKAAR+CMBX12
|
||||
endobj";
|
||||
|
||||
@@ -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: žw̵;’’Îq®]cƒÿdp¨ÛI3F#G©#œ)TÇqW£NÚѬgOKbü‡µ#á¡£Þaîtƒƒ›ß–
|
||||
¾“S>}µuÕõ5M±¢ª†»øÞû•q÷îÜ~¬PòžÞ~•¬ëɃGÅ-Ñím·°gêêb,/,£P§õ^v¾ãÁô¿¿ŠTE]²±{šuwÔ`LG³DªìTÈ
|
||||
A¡¬àð‰É©ˆ°‘¼›‚%¥×s³®í»š}%§X{{tøNåÝž¶ö¢ÖÞ¾–~´¼¬°À“Éððr¥8»P£ØêÁi½®Û(éhŽ‘ú;x#dÃÄ$m
|
||||
+)
|
||||
)†…±n
|
||||
9ùyŽA·n\ï»t!=3£½¡:®µåâ¹Ô³ø¼ËiûSÎsë;•Dt—ö$WÉ4U‘¢ºÚšñá1íÐèÔó‚svõ(/(+D²#mZÏ6êüÝ7x‡—†”‡E„²‚|ê«êªDµ5q°šR¦RÈ£n¾[è~“}ýƒÝ½SꞦ'æQŽzÝ‚mæ
|
||||
Assert.StartsWith("310 0 obj", s.Substring((int)tokens[1].Position));
|
||||
|
||||
dictionary = Assert.IsType<DictionaryToken>(tokens[2].Data);
|
||||
|
||||
Assert.Equal(7, dictionary.Data.Count);
|
||||
Assert.Equal(311, tokens[2].Number.ObjectNumber);
|
||||
Assert.StartsWith("311 0 obj", s.Substring((int)tokens[2].Position));
|
||||
}
|
||||
|
||||
@@ -357,7 +291,7 @@ endobj
|
||||
|
||||
[Fact]
|
||||
public void ReadsStreamWithMissingLength()
|
||||
xœ]‘ËjÃ0E÷ÿÃ,ÓEð#NÒ€1¤N^ôA~€-]A-YYøï+Ï4¡t#qfîFWQY*Dïv5:è”–§ñjB‹½Òa¤ •p7¤K ƒÈûëyr8Tº!Ïà úð‚ÉÙVG9¶ø@Å7+Ñ*ÝÃ곬¹T_ùƵƒ8Š$vË̗Ƽ6BDöu%½B¹yí$—Ù ¤\Hx71JœL#Ð6ºÇ0È㸀ü|. µüßõÏ""WÛ‰¯Æ.êÄ«ã8;¤iL°!Ø %É`K°ßì¸ÃöÜáÜ) [‚#CFðİ#(yƒg^ÿ¶æò
|
||||
{
|
||||
const string s = @"
|
||||
12655 0 obj
|
||||
|
||||
@@ -375,31 +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()));
|
||||
|
||||
@@ -45,11 +45,6 @@ namespace UglyToad.PdfPig.Tests.Tokens
|
||||
return Objects[reference];
|
||||
}
|
||||
|
||||
public void ReplaceToken(IndirectReference reference, IToken token)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -39,7 +39,6 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
|
||||
@@ -65,9 +64,6 @@
|
||||
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\PMingLiU.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -101,7 +97,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -134,8 +129,4 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Images\" />
|
||||
<Folder Include="Images\Files\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Util
|
||||
{
|
||||
using Xunit;
|
||||
using PdfPig.Util;
|
||||
|
||||
public class Matrix3x3Tests
|
||||
{
|
||||
[Fact]
|
||||
public void CanCreate()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
Assert.Equal(new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, matrix);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExposesIdentityMatrix()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1
|
||||
);
|
||||
|
||||
Assert.Equal(matrix, Matrix3x3.Identity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithFactor()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var result = matrix.Multiply(3);
|
||||
|
||||
Assert.Equal(new double[] { 3, 6, 9, 12, 15, 18, 21, 24, 27 }, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithVector()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var vector = (1, 2, 3);
|
||||
|
||||
var product = matrix.Multiply(vector);
|
||||
|
||||
Assert.Equal((14, 32, 50), product);
|
||||
// Result can be verified here:
|
||||
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C+3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B1%2C2%2C3%7D
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithMatrix()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var product = matrix.Multiply(matrix);
|
||||
|
||||
var expected = new Matrix3x3(
|
||||
30, 36, 42,
|
||||
66, 81, 96,
|
||||
102, 126, 150);
|
||||
|
||||
Assert.Equal(expected, product);
|
||||
// Result can be verified here:
|
||||
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanTranspose()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var transposed = matrix.Transpose();
|
||||
|
||||
Assert.Equal(new double[] { 1, 4, 7, 2, 5, 8, 3, 6, 9 }, transposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InverseReturnsMatrixIfPossible()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
0, 1, 4,
|
||||
5, 6, 0);
|
||||
|
||||
var inverse = matrix.Inverse();
|
||||
|
||||
Assert.Equal(new double[] { -24, 18, 5, 20, -15, -4, -5, 4, 1 }, inverse);
|
||||
Assert.Equal(Matrix3x3.Identity, matrix.Multiply(inverse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InverseReturnsNullIfNotPossible()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var inverse = matrix.Inverse();
|
||||
Assert.Null(inverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -2,8 +2,6 @@
|
||||
{
|
||||
using Integration;
|
||||
using PdfPig.Writer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
@@ -16,24 +14,20 @@
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(one, two);
|
||||
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMerge2SimpleDocumentsIntoStream()
|
||||
{
|
||||
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
|
||||
|
||||
using (var outputStream = GetSelfDestructingNewFileStream("merge2"))
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
if (outputStream is null)
|
||||
{
|
||||
return;//we can't create a file in this test session
|
||||
}
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
|
||||
PdfMerger.Merge(one, two, outputStream);
|
||||
CanMerge2SimpleDocumentsAssertions(outputStream, "Write something inInkscape", "I am a simple pdf.");
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
|
||||
var page1 = document.GetPage(1);
|
||||
|
||||
Assert.Equal("Write something inInkscape", page1.Text);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
|
||||
Assert.Equal("I am a simple pdf.", page2.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,25 +38,20 @@
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(one, two);
|
||||
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape");
|
||||
}
|
||||
|
||||
internal static void CanMerge2SimpleDocumentsAssertions(Stream stream, string page1Text, string page2Text, bool checkVersion=true)
|
||||
{
|
||||
stream.Position = 0;
|
||||
using (var document = PdfDocument.Open(stream, ParsingOptions.LenientParsingOff))
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
if (checkVersion)
|
||||
{
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
}
|
||||
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
|
||||
var page1 = document.GetPage(1);
|
||||
Assert.Equal(page1Text, page1.Text);
|
||||
|
||||
Assert.Equal("I am a simple pdf.", page1.Text);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
Assert.Equal(page2Text, page2.Text);
|
||||
|
||||
Assert.Equal("Write something inInkscape", page2.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,26 +89,11 @@
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 24,
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count < 24,
|
||||
"Expected object count to be lower than 24");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DedupsObjectsFromSameDoc()
|
||||
{
|
||||
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(new List<byte[]> { File.ReadAllBytes(one) }, new List<IReadOnlyList<int>> { new List<int> { 1, 2} });
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29,
|
||||
"Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeWithObjectStream()
|
||||
{
|
||||
@@ -141,52 +115,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeWithSelection()
|
||||
{
|
||||
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
var contents = File.ReadAllBytes(first);
|
||||
|
||||
var toCopy = new[] {2, 1, 4, 3, 6, 5};
|
||||
var result = PdfMerger.Merge(new [] { contents }, new [] { toCopy });
|
||||
|
||||
WriteFile(nameof(CanMergeWithSelection), result);
|
||||
|
||||
using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff))
|
||||
using (var merged = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(6, merged.NumberOfPages);
|
||||
|
||||
for (var i =1;i<merged.NumberOfPages;i++)
|
||||
{
|
||||
Assert.Equal(
|
||||
existing.GetPage(toCopy[i-1]).Text,
|
||||
merged.GetPage(i).Text
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeMultipleWithSelection()
|
||||
{
|
||||
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
var second = IntegrationHelpers.GetDocumentPath("Old Gutnish Internet Explorer.pdf");
|
||||
var result = PdfMerger.Merge(new[] { File.ReadAllBytes(first), File.ReadAllBytes(second) }, new[] { new[] { 2, 1, 4, 3, 6, 5 }, new []{ 3, 2, 1 } });
|
||||
|
||||
WriteFile(nameof(CanMergeMultipleWithSelection), result);
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(9, document.NumberOfPages);
|
||||
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
Assert.NotNull(page.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes)
|
||||
{
|
||||
try
|
||||
@@ -205,40 +133,5 @@
|
||||
// ignored.
|
||||
}
|
||||
}
|
||||
|
||||
private static FileStream GetSelfDestructingNewFileStream(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists("Merger"))
|
||||
{
|
||||
Directory.CreateDirectory("Merger");
|
||||
}
|
||||
|
||||
var output = Path.Combine("Merger", $"{name}.pdf");
|
||||
return File.Create(output, 4096, FileOptions.DeleteOnClose);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoStackoverflow()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = PdfMerger.Merge(IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf"));
|
||||
using (var document = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(45, document.NumberOfPages);
|
||||
}
|
||||
}
|
||||
catch (StackOverflowException)
|
||||
{
|
||||
Assert.True(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Writer
|
||||
{
|
||||
using Integration;
|
||||
using PdfPig.Writer;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class TokenWriterTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void EscapeSpecialCharacter()
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
TokenWriter.WriteToken(new StringToken("\\"), memStream);
|
||||
TokenWriter.WriteToken(new StringToken("(Hello)"), memStream);
|
||||
|
||||
// Read Test
|
||||
memStream.Position = 0;
|
||||
using (var streamReader = new StreamReader(memStream))
|
||||
{
|
||||
var line = streamReader.ReadToEnd();
|
||||
Assert.Equal("(\\\\) (\\(Hello\\)) ", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
internal class NumericTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
|
||||
|
||||
private const byte Zero = 48;
|
||||
private const byte Nine = 57;
|
||||
@@ -20,45 +20,28 @@
|
||||
token = null;
|
||||
|
||||
StringBuilder characters;
|
||||
var initialSymbol = currentByte == '-' || currentByte == '+';
|
||||
|
||||
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '.')
|
||||
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.')
|
||||
{
|
||||
characters = stringBuilder;
|
||||
characters = StringBuilderPool.Borrow();
|
||||
characters.Append((char)currentByte);
|
||||
}
|
||||
else if (initialSymbol)
|
||||
{
|
||||
characters = stringBuilder;
|
||||
characters.Append((char) currentByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var previousSymbol = initialSymbol;
|
||||
|
||||
while (inputBytes.MoveNext())
|
||||
{
|
||||
var b = inputBytes.CurrentByte;
|
||||
|
||||
if (b == '+' || b == '-')
|
||||
if ((b >= Zero && b <= Nine) ||
|
||||
b == '-' ||
|
||||
b == '+' ||
|
||||
b == '.' ||
|
||||
b == 'E' ||
|
||||
b == 'e')
|
||||
{
|
||||
if (previousSymbol)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
characters.Append((char) b);
|
||||
previousSymbol = true;
|
||||
}
|
||||
else if ((b >= Zero && b <= Nine) ||
|
||||
b == '.' ||
|
||||
b == 'E' ||
|
||||
b == 'e')
|
||||
{
|
||||
previousSymbol = false;
|
||||
characters.Append((char)b);
|
||||
}
|
||||
else
|
||||
@@ -70,7 +53,7 @@
|
||||
try
|
||||
{
|
||||
var str = characters.ToString();
|
||||
characters.Clear();
|
||||
StringBuilderPool.Return(characters);
|
||||
|
||||
switch (str)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Tokenization
|
||||
{
|
||||
using Core;
|
||||
using System.Text;
|
||||
using Tokens;
|
||||
|
||||
internal class PlainTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
|
||||
|
||||
public bool ReadsNextByte { get; } = true;
|
||||
|
||||
@@ -19,7 +18,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = stringBuilder;
|
||||
var builder = StringBuilderPool.Borrow();
|
||||
builder.Append((char)currentByte);
|
||||
while (inputBytes.MoveNext())
|
||||
{
|
||||
@@ -40,7 +39,7 @@
|
||||
}
|
||||
|
||||
var text = builder.ToString();
|
||||
builder.Clear();
|
||||
StringBuilderPool.Return(builder);
|
||||
|
||||
switch (text)
|
||||
{
|
||||
|
||||
@@ -15,12 +15,9 @@
|
||||
private static readonly DictionaryTokenizer DictionaryTokenizer = new DictionaryTokenizer();
|
||||
private static readonly HexTokenizer HexTokenizer = new HexTokenizer();
|
||||
private static readonly NameTokenizer NameTokenizer = new NameTokenizer();
|
||||
|
||||
// NOTE: these are not thread safe so should not be static. Each instance includes a
|
||||
// StringBuilder it re-uses.
|
||||
private readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
|
||||
private readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
|
||||
private readonly StringTokenizer StringTokenizer = new StringTokenizer();
|
||||
private static readonly NumericTokenizer NumericTokenizer = new NumericTokenizer();
|
||||
private static readonly PlainTokenizer PlainTokenizer = new PlainTokenizer();
|
||||
private static readonly StringTokenizer StringTokenizer = new StringTokenizer();
|
||||
|
||||
private readonly ScannerScope scope;
|
||||
private readonly IInputBytes inputBytes;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
internal class StringTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(16);
|
||||
public bool ReadsNextByte { get; } = false;
|
||||
|
||||
public bool TryTokenize(byte currentByte, IInputBytes inputBytes, out IToken token)
|
||||
@@ -23,7 +23,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
var builder = stringBuilder;
|
||||
var builder = StringBuilderPool.Borrow();
|
||||
var numberOfBrackets = 1;
|
||||
var isEscapeActive = false;
|
||||
var isLineBreaking = false;
|
||||
@@ -178,7 +178,7 @@
|
||||
encodedWith = StringToken.Encoding.Iso88591;
|
||||
}
|
||||
|
||||
builder.Clear();
|
||||
StringBuilderPool.Return(builder);
|
||||
|
||||
token = new StringToken(tokenStr, encodedWith);
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.2</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
/// A dictionary object is an associative table containing pairs of objects, known as the dictionary's entries.
|
||||
/// The key must be a <see cref="NameToken"/> and the value may be an kind of <see cref="IToken"/>.
|
||||
/// </summary>
|
||||
public class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>>, IEquatable<DictionaryToken>
|
||||
public class DictionaryToken : IDataToken<IReadOnlyDictionary<string, IToken>>
|
||||
{
|
||||
/// <summary>
|
||||
/// The key value pairs in this dictionary.
|
||||
@@ -102,16 +102,6 @@
|
||||
/// <returns>A new <see cref="DictionaryToken"/> with the entry created or modified.</returns>
|
||||
public DictionaryToken With(string key, IToken value)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
var result = new Dictionary<string, IToken>(Data.Count + 1);
|
||||
|
||||
foreach (var keyValuePair in Data)
|
||||
@@ -124,63 +114,20 @@
|
||||
return new DictionaryToken(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of this dictionary with the entry with the specified key removed (if it exists).
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the entry to remove.</param>
|
||||
/// <returns>A new <see cref="DictionaryToken"/> with the entry removed.</returns>
|
||||
public DictionaryToken Without(NameToken key) => Without(key.Data);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a copy of this dictionary with the entry with the specified key removed (if it exists).
|
||||
/// </summary>
|
||||
/// <param name="key">The key of the entry to remove.</param>
|
||||
/// <returns>A new <see cref="DictionaryToken"/> with the entry removed.</returns>
|
||||
public DictionaryToken Without(string key)
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
var result = new Dictionary<string, IToken>(Data.ContainsKey(key) ? Data.Count - 1 : Data.Count);
|
||||
|
||||
foreach (var keyValuePair in Data.Where(x => !x.Key.Equals(key)))
|
||||
{
|
||||
result[keyValuePair.Key] = keyValuePair.Value;
|
||||
}
|
||||
|
||||
return new DictionaryToken(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="DictionaryToken"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">The data this dictionary will contain.</param>
|
||||
public static DictionaryToken With(IReadOnlyDictionary<string, IToken> data)
|
||||
{
|
||||
return new DictionaryToken(data ?? throw new ArgumentNullException(nameof(data)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(IToken obj)
|
||||
{
|
||||
return Equals(obj as DictionaryToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Equals(DictionaryToken other)
|
||||
{
|
||||
if (other == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ReferenceEquals(this, other))
|
||||
if (ReferenceEquals(this, obj))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!(obj is DictionaryToken other))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Data.Count != other.Data.Count)
|
||||
{
|
||||
return false;
|
||||
@@ -194,14 +141,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Join(", ", Data.Select(x => $"<{x.Key}, {x.Value}>"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,12 +338,10 @@
|
||||
public static readonly NameToken MarkInfo = new NameToken("MarkInfo");
|
||||
public static readonly NameToken Mask = new NameToken("Mask");
|
||||
public static readonly NameToken Matrix = new NameToken("Matrix");
|
||||
public static readonly NameToken Matte = new NameToken("Matte");
|
||||
public static readonly NameToken MaxLen = new NameToken("MaxLen");
|
||||
public static readonly NameToken MaxWidth = new NameToken("MaxWidth");
|
||||
public static readonly NameToken Mcid = new NameToken("MCID");
|
||||
public static readonly NameToken Mdp = new NameToken("MDP");
|
||||
public static readonly NameToken Measure = new NameToken("Measure");
|
||||
public static readonly NameToken MediaBox = new NameToken("MediaBox");
|
||||
public static readonly NameToken Metadata = new NameToken("Metadata");
|
||||
public static readonly NameToken MissingWidth = new NameToken("MissingWidth");
|
||||
@@ -551,12 +549,10 @@
|
||||
public static readonly NameToken Unix = new NameToken("Unix");
|
||||
public static readonly NameToken Uri = new NameToken("URI");
|
||||
public static readonly NameToken Url = new NameToken("URL");
|
||||
public static readonly NameToken Usage = new NameToken("Usage");
|
||||
public static readonly NameToken UserUnit = new NameToken("UserUnit");
|
||||
// V
|
||||
public static readonly NameToken V = new NameToken("V");
|
||||
public static readonly NameToken V2 = new NameToken("V2");
|
||||
public static readonly NameToken VE = new NameToken("VE");
|
||||
public static readonly NameToken VerisignPpkvs = new NameToken("VeriSign.PPKVS");
|
||||
public static readonly NameToken Version = new NameToken("Version");
|
||||
public static readonly NameToken Vertices = new NameToken("Vertices");
|
||||
@@ -565,7 +561,6 @@
|
||||
public static readonly NameToken ViewClip = new NameToken("ViewClip");
|
||||
public static readonly NameToken ViewerPreferences = new NameToken("ViewerPreferences");
|
||||
public static readonly NameToken Volume = new NameToken("Volume");
|
||||
public static readonly NameToken Vp = new NameToken("VP");
|
||||
// W
|
||||
public static readonly NameToken W = new NameToken("W");
|
||||
public static readonly NameToken W2 = new NameToken("W2");
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.5</Version>
|
||||
<Version>0.1.2</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_ARGUMENTS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/WRAP_PARAMETERS_STYLE/@EntryValue">CHOP_IF_LONG</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CSharpUsing/AddImportsToDeepestScope/@EntryValue">True</s:Boolean>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=BE/@EntryIndexedValue">BE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=CIE/@EntryIndexedValue">CIE</s:String>
|
||||
@@ -9,8 +7,4 @@
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=RGB/@EntryIndexedValue">RGB</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=XY/@EntryIndexedValue">XY</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></s:String></wpf:ResourceDictionary>
|
||||
@@ -6,6 +6,7 @@
|
||||
using Content;
|
||||
using Core;
|
||||
using CrossReference;
|
||||
using Exceptions;
|
||||
using Fields;
|
||||
using Filters;
|
||||
using Parser.Parts;
|
||||
@@ -29,10 +30,10 @@
|
||||
};
|
||||
|
||||
private readonly IPdfTokenScanner tokenScanner;
|
||||
private readonly ILookupFilterProvider filterProvider;
|
||||
private readonly IFilterProvider filterProvider;
|
||||
private readonly CrossReferenceTable crossReferenceTable;
|
||||
|
||||
public AcroFormFactory(IPdfTokenScanner tokenScanner, ILookupFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
|
||||
public AcroFormFactory(IPdfTokenScanner tokenScanner, IFilterProvider filterProvider, CrossReferenceTable crossReferenceTable)
|
||||
{
|
||||
this.tokenScanner = tokenScanner ?? throw new ArgumentNullException(nameof(tokenScanner));
|
||||
this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider));
|
||||
@@ -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))
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content;
|
||||
using Core;
|
||||
using Filters;
|
||||
using Parser.Parts;
|
||||
using Tokenization.Scanner;
|
||||
@@ -16,13 +15,13 @@
|
||||
public class AdvancedPdfDocumentAccess : IDisposable
|
||||
{
|
||||
private readonly IPdfTokenScanner pdfScanner;
|
||||
private readonly ILookupFilterProvider filterProvider;
|
||||
private readonly IFilterProvider filterProvider;
|
||||
private readonly Catalog catalog;
|
||||
|
||||
private bool isDisposed;
|
||||
|
||||
internal AdvancedPdfDocumentAccess(IPdfTokenScanner pdfScanner,
|
||||
ILookupFilterProvider filterProvider,
|
||||
IFilterProvider filterProvider,
|
||||
Catalog catalog)
|
||||
{
|
||||
this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner));
|
||||
@@ -73,7 +72,7 @@
|
||||
fileSpecification = fileSpecificationToken.Data;
|
||||
}
|
||||
|
||||
var fileBytes = fileStreamToken.Decode(filterProvider, pdfScanner);
|
||||
var fileBytes = fileStreamToken.Decode(filterProvider);
|
||||
|
||||
result.Add(new EmbeddedFile(keyValuePair.Key, fileSpecification, fileBytes, fileStreamToken));
|
||||
}
|
||||
@@ -83,30 +82,6 @@
|
||||
return embeddedFiles.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the token in an internal cache that will be returned instead of
|
||||
/// scanning the source PDF data for future requests.
|
||||
/// </summary>
|
||||
/// <param name="reference">The object number for the object to replace.</param>
|
||||
/// <param name="replacer">Func that takes existing token as input and return new token.</param>
|
||||
public void ReplaceIndirectObject(IndirectReference reference, Func<IToken, IToken> replacer)
|
||||
{
|
||||
var obj = pdfScanner.Get(reference);
|
||||
var replacement = replacer(obj.Data);
|
||||
pdfScanner.ReplaceToken(reference, replacement);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the token in an internal cache that will be returned instead of
|
||||
/// scanning the source PDF data for future requests.
|
||||
/// </summary>
|
||||
/// <param name="reference">The object number for the object to replace.</param>
|
||||
/// <param name="replacement">Replacement token to use.</param>
|
||||
public void ReplaceIndirectObject(IndirectReference reference, IToken replacement)
|
||||
{
|
||||
pdfScanner.ReplaceToken(reference, replacement);
|
||||
}
|
||||
|
||||
private void GuardDisposed()
|
||||
{
|
||||
if (isDisposed)
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
using System.Collections.Generic;
|
||||
using Core;
|
||||
using Graphics.Colors;
|
||||
using Graphics.Core;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Graphics.Core;
|
||||
using XObjects;
|
||||
|
||||
/// <summary>
|
||||
@@ -85,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
|
||||
/// <summary>
|
||||
/// The size of the font in points.
|
||||
/// <para>This is considered experimental because the calculated value is incorrect for some documents at present.</para>
|
||||
/// </summary>
|
||||
public double PointSize { get; }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/// <remarks>
|
||||
/// See table 3.27 from the PDF specification version 1.7.
|
||||
/// </remarks>
|
||||
public class MediaBox
|
||||
internal class MediaBox
|
||||
{
|
||||
///<summary>
|
||||
/// User space units per inch.
|
||||
@@ -66,14 +66,8 @@
|
||||
/// </summary>
|
||||
public static readonly MediaBox A6 = new MediaBox(new PdfRectangle(0, 0, 105 * PointsPerMm, 148 * PointsPerMm));
|
||||
|
||||
/// <summary>
|
||||
/// A rectangle, expressed in default user space units, that defines the boundaries of the physical medium on which the page shall be displayed or printed.
|
||||
/// </summary>
|
||||
public PdfRectangle Bounds { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="MediaBox"/>.
|
||||
/// </summary>
|
||||
public MediaBox(PdfRectangle? bounds)
|
||||
{
|
||||
Bounds = bounds ?? throw new ArgumentNullException(nameof(bounds));
|
||||
|
||||
@@ -1,157 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Tokenization.Scanner;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
namespace UglyToad.PdfPig.Content
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional content group is a dictionary representing a collection of graphics
|
||||
/// that can be made visible or invisible dynamically by users of viewers applications.
|
||||
/// </summary>
|
||||
public class OptionalContentGroupElement
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of PDF object that this dictionary describes.
|
||||
/// <para>Must be OCG for an optional content group dictionary.</para>
|
||||
/// </summary>
|
||||
public string Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the optional content group, suitable for presentation in a viewer application's user interface.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A single name or an array containing any combination of names.
|
||||
/// <para>Default value is 'View'.</para>
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Intent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A usage dictionary describing the nature of the content controlled by the group.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, IToken> Usage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Underlying <see cref="MarkedContentElement"/>.
|
||||
/// </summary>
|
||||
public MarkedContentElement MarkedContent { get; }
|
||||
|
||||
internal OptionalContentGroupElement(MarkedContentElement markedContentElement, IPdfTokenScanner pdfTokenScanner)
|
||||
{
|
||||
MarkedContent = markedContentElement;
|
||||
|
||||
// Type - Required
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out NameToken type))
|
||||
{
|
||||
Type = type.Data;
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Type, pdfTokenScanner, out StringToken typeStr))
|
||||
{
|
||||
Type = typeStr.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Cannot parse optional content's {nameof(Type)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
|
||||
}
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case "OCG": // Optional content group dictionary
|
||||
// Name - Required
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out NameToken name))
|
||||
{
|
||||
Name = name.Data;
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Name, pdfTokenScanner, out StringToken nameStr))
|
||||
{
|
||||
Name = nameStr.Data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Cannot parse optional content's {nameof(Name)} from {nameof(markedContentElement.Properties)}. This is a required field.", nameof(markedContentElement.Properties));
|
||||
}
|
||||
|
||||
// Intent - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out NameToken intentName))
|
||||
{
|
||||
Intent = new string[] { intentName.Data };
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out StringToken intentStr))
|
||||
{
|
||||
Intent = new string[] { intentStr.Data };
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Intent, pdfTokenScanner, out ArrayToken intentArray))
|
||||
{
|
||||
List<string> intentList = new List<string>();
|
||||
foreach (var token in intentArray.Data)
|
||||
{
|
||||
if (token is NameToken nameA)
|
||||
{
|
||||
intentList.Add(nameA.Data);
|
||||
}
|
||||
else if (token is StringToken strA)
|
||||
{
|
||||
intentList.Add(strA.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
Intent = intentList;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default value is 'View'.
|
||||
Intent = new string[] { "View" };
|
||||
}
|
||||
|
||||
// Usage - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Usage, pdfTokenScanner, out DictionaryToken usage))
|
||||
{
|
||||
this.Usage = usage.Data;
|
||||
}
|
||||
break;
|
||||
|
||||
case "OCMD":
|
||||
// OCGs - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out DictionaryToken ocgsD))
|
||||
{
|
||||
// dictionary or array
|
||||
throw new NotImplementedException($"{NameToken.Ocgs}");
|
||||
}
|
||||
else if (markedContentElement.Properties.TryGet(NameToken.Ocgs, pdfTokenScanner, out ArrayToken ocgsA))
|
||||
{
|
||||
// dictionary or array
|
||||
throw new NotImplementedException($"{NameToken.Ocgs}");
|
||||
}
|
||||
|
||||
// P - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.P, pdfTokenScanner, out NameToken p))
|
||||
{
|
||||
throw new NotImplementedException($"{NameToken.P}");
|
||||
}
|
||||
|
||||
// VE - Optional
|
||||
if (markedContentElement.Properties.TryGet(NameToken.VE, pdfTokenScanner, out ArrayToken ve))
|
||||
{
|
||||
throw new NotImplementedException($"{NameToken.VE}");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unknown Optional Content of type '{Type}' not known.", nameof(Type));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Type} - {Name} [{string.Join(",", Intent)}]: {MarkedContent?.ToString()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user