mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
5 Commits
xref-strea
...
object-cop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
487e368e9e | ||
|
|
704c56285c | ||
|
|
a190653683 | ||
|
|
8f0326a818 | ||
|
|
5a98b21c2f |
30
.github/workflows/build_and_test.yml
vendored
30
.github/workflows/build_and_test.yml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Build and test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: |
|
||||
2.1.x
|
||||
6.0.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
|
||||
55
.github/workflows/nightly_release.yml
vendored
55
.github/workflows/nightly_release.yml
vendored
@@ -1,55 +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-2019
|
||||
name: build_and_publish_nightly
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v2
|
||||
with:
|
||||
dotnet-version: |
|
||||
2.1.x
|
||||
6.0.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 -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg
|
||||
|
||||
- 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 }}
|
||||
56
.github/workflows/run_integration_tests.yml
vendored
56
.github/workflows/run_integration_tests.yml
vendored
@@ -1,56 +0,0 @@
|
||||
name: Run Integration Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: "2.1.x"
|
||||
|
||||
- name: Restore cached part 1
|
||||
id: restore-cache-p1
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: archive/part1-archive.7z
|
||||
key: ${{ runner.os }}-part-1
|
||||
|
||||
- name: Restore cached part 2
|
||||
id: restore-cache-p2
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: archive/part2-archive.7z
|
||||
key: ${{ runner.os }}-part-2
|
||||
|
||||
- name: Restore cached part 3
|
||||
id: restore-cache-p3
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: archive/part3-archive.7z
|
||||
key: ${{ runner.os }}-part-3
|
||||
|
||||
- name: Download the archive part 1
|
||||
if: steps.restore-cache-p1.outputs.cache-hit != 'true'
|
||||
run: wget http://eliot-jones.com:8081/part1-archive.7z -nv -P archive/ && ls archive
|
||||
|
||||
- name: Download the archive part 2
|
||||
if: steps.restore-cache-p2.outputs.cache-hit != 'true'
|
||||
run: wget http://eliot-jones.com:8081/part2-archive.7z -nv -P archive/ && ls archive
|
||||
|
||||
- name: Download the archive part 3
|
||||
if: steps.restore-cache-p3.outputs.cache-hit != 'true'
|
||||
run: wget http://eliot-jones.com:8081/part3-archive.7z -nv -P archive/ && ls archive
|
||||
|
||||
- name: Unzip archives
|
||||
run: sudo apt-get install p7zip-full && cd archive && 7z x part1-archive.7z && 7z x part2-archive.7z && 7z x part3-archive.7z
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "archive/"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -244,6 +244,3 @@ _Pvt_Extensions
|
||||
/src/CodeCoverage/OpenCover.4.6.519
|
||||
/src/test-results.xml
|
||||
/tools/benchmark
|
||||
/tools/ConsoleRunner/Properties/launchSettings.json
|
||||
|
||||
/docs/doxygen
|
||||
|
||||
@@ -45,7 +45,7 @@ Open `.\src\UglyToad.PdfPig\UglyToad.PdfPig.csproj` in a text editor and change:
|
||||
|
||||
```
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<PackageId>PdfPig</PackageId>
|
||||
...
|
||||
```
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
# PdfPig
|
||||
|
||||
[](https://dev.azure.com/pdfpig/pdfpig/_build/latest?definitionId=1&branchName=master)
|
||||
[](https://codecov.io/gh/UglyToad/PdfPig)
|
||||
[](https://gitter.im/pdfpig/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
||||
[](https://www.nuget.org/packages/PdfPig/)
|
||||
@@ -11,7 +12,7 @@ containing text and geometrical shapes.
|
||||
|
||||
This project aims to port [PDFBox](https://github.com/apache/pdfbox) to C#.
|
||||
|
||||
**Migrating to 0.1.6 from 0.1.x?** Use this guide: [migration to 0.1.6](https://github.com/UglyToad/PdfPig/wiki/Migration-to-0.1.6).
|
||||
**Migrating to 0.1.x from 0.0.x?** Use this guide: [migration to 0.1.x](https://github.com/UglyToad/PdfPig/wiki/Migration-0.0.X-to-0.1.0).
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -298,10 +299,6 @@ You can merge 2 or more existing PDF files using the `PdfMerger` class:
|
||||
var resultFileBytes = PdfMerger.Merge(filePath1, filePath2);
|
||||
File.WriteAllBytes(@"C:\pdfs\outputfilename.pdf", resultFileBytes);
|
||||
|
||||
## API Reference
|
||||
|
||||
If you wish to generate doxygen documentation, run `doxygen doxygen-docs` and open `docs/doxygen/html/index.html`.
|
||||
|
||||
## Issues
|
||||
|
||||
Please do file an issue if you encounter a bug.
|
||||
|
||||
@@ -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.
|
||||
2658
doxygen-docs
2658
doxygen-docs
File diff suppressed because it is too large
Load Diff
@@ -67,7 +67,7 @@
|
||||
{
|
||||
return new PdfPoint(X + dx, Y);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="PdfPoint"/> which is the current point moved in the y direction relative to its current position by a value.
|
||||
/// </summary>
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
private double width;
|
||||
/// <summary>
|
||||
/// Width of the rectangle.
|
||||
/// <para>A positive number.</para>
|
||||
/// </summary>
|
||||
public double Width
|
||||
{
|
||||
@@ -67,7 +66,6 @@
|
||||
private double height;
|
||||
/// <summary>
|
||||
/// Height of the rectangle.
|
||||
/// <para>A positive number.</para>
|
||||
/// </summary>
|
||||
public double Height
|
||||
{
|
||||
@@ -199,16 +197,13 @@
|
||||
var cos = Math.Cos(t);
|
||||
var sin = Math.Sin(t);
|
||||
|
||||
var inverseRotation = new TransformationMatrix(
|
||||
var inverseRotation = new TransformationMatrix(
|
||||
cos, -sin, 0,
|
||||
sin, cos, 0,
|
||||
0, 0, 1);
|
||||
|
||||
// Using Abs as a proxy for Euclidean distance in 1D
|
||||
// as it might happen that points have negative coordinates.
|
||||
var bl = inverseRotation.Transform(BottomLeft);
|
||||
width = Math.Abs(inverseRotation.Transform(BottomRight).X - bl.X);
|
||||
height = Math.Abs(inverseRotation.Transform(TopLeft).Y - bl.Y);
|
||||
width = inverseRotation.Transform(BottomRight).X - inverseRotation.Transform(BottomLeft).X;
|
||||
height = inverseRotation.Transform(TopLeft).Y - inverseRotation.Transform(BottomLeft).Y;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -160,9 +160,9 @@
|
||||
throw new ArgumentNullException("LineTo(): currentPosition is null.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Add a rectangle following the pdf specification (m, l, l, l, c) path. A new subpath is created.
|
||||
/// Adds 4 <see cref="Line"/>s forming a rectangle to the path.
|
||||
/// </summary>
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
@@ -174,6 +174,8 @@
|
||||
CloseSubpath(); // h
|
||||
IsDrawnAsRectangle = true;
|
||||
}
|
||||
|
||||
internal void QuadraticCurveTo(double x1, double y1, double x2, double y2) { }
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="BezierCurve"/> to the path.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
namespace UglyToad.PdfPig.DocumentLayoutAnalysis
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface that stores options that configure the operation of methods of the document layout analysis algorithm.
|
||||
/// Abstract class that stores options that configure the operation of methods of the document layout analysis algorithm.
|
||||
/// </summary>
|
||||
public interface IDlaOptions
|
||||
public abstract class DlaOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of concurrent tasks enabled.
|
||||
/// <para>A positive property value limits the number of concurrent operations to the set value.
|
||||
/// If it is -1, there is no limit on the number of concurrently running operations.</para>
|
||||
/// <para>Default value is -1.</para>
|
||||
/// </summary>
|
||||
int MaxDegreeOfParallelism { get; set; }
|
||||
public int MaxDegreeOfParallelism { get; set; } = -1;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
{
|
||||
using Alto;
|
||||
using Content;
|
||||
using Core;
|
||||
using DocumentLayoutAnalysis;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
@@ -146,7 +147,7 @@
|
||||
altoPage.PrintSpace.TextBlock = blocks;
|
||||
|
||||
altoPage.PrintSpace.Illustrations = page.GetImages().Select(i => ToAltoIllustration(i, page.Height)).ToArray();
|
||||
|
||||
|
||||
if (includePaths)
|
||||
{
|
||||
altoPage.PrintSpace.GraphicalElements = page.ExperimentalAccess.Paths
|
||||
@@ -287,7 +288,7 @@
|
||||
ProcessingSoftware = new AltoDocument.AltoProcessingSoftware
|
||||
{
|
||||
SoftwareName = "PdfPig",
|
||||
SoftwareCreator = "https://github.com/UglyToad/PdfPig",
|
||||
SoftwareCreator = @"https://github.com/UglyToad/PdfPig",
|
||||
ApplicationDescription = "Read and extract text and other content from PDFs in C# (port of PdfBox)",
|
||||
SoftwareVersion = "x.x.xx"
|
||||
},
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
/// </summary>
|
||||
/// <param name="document">The document.</param>
|
||||
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
|
||||
/// <param name="useHocrjs">Will add a reference to the 'hocrjs' script just before the closing 'body' tag, adding the
|
||||
/// <param name="useHocrjs">Will add a reference to the 'hocrjs' script just before the closing 'body' tag, adding the
|
||||
/// interface to a plain hOCR file.<para>See https://github.com/kba/hocrjs for more information.</para></param>
|
||||
public string Get(PdfDocument document, bool includePaths = false, bool useHocrjs = false)
|
||||
{
|
||||
@@ -61,13 +61,10 @@
|
||||
hocr += GetCode(page, includePaths) + "\n";
|
||||
}
|
||||
|
||||
if (useHocrjs)
|
||||
{
|
||||
hocr += indentChar + indentChar + Hocrjs;
|
||||
}
|
||||
|
||||
if (useHocrjs) hocr += indentChar + indentChar + Hocrjs;
|
||||
hocr += indentChar + "</body>";
|
||||
return XmlHeader + AddHtmlHeader(hocr);
|
||||
hocr = XmlHeader + AddHtmlHeader(hocr);
|
||||
return hocr;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -83,8 +80,8 @@
|
||||
/// Get the hOCR (HTML) string of the page layout.
|
||||
/// </summary>
|
||||
/// <param name="page">The page.</param>
|
||||
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
|
||||
/// <param name="imageName">The image name, if any.</param>
|
||||
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
|
||||
/// <param name="useHocrjs">Will add a reference to the 'hocrjs' script just before the closing 'body' tag, adding the interface to a plain hOCR file.<para>See https://github.com/kba/hocrjs for more information.</para></param>
|
||||
public string Get(Page page, bool includePaths = false, string imageName = "unknown", bool useHocrjs = false)
|
||||
{
|
||||
@@ -92,13 +89,10 @@
|
||||
|
||||
hocr += GetCode(page, includePaths, imageName) + "\n";
|
||||
|
||||
if (useHocrjs)
|
||||
{
|
||||
hocr += indentChar + indentChar + Hocrjs;
|
||||
}
|
||||
|
||||
if (useHocrjs) hocr += indentChar + indentChar + Hocrjs;
|
||||
hocr += indentChar + "</body>";
|
||||
return XmlHeader + AddHtmlHeader(hocr);
|
||||
hocr = XmlHeader + AddHtmlHeader(hocr);
|
||||
return hocr;
|
||||
}
|
||||
|
||||
private string GetHead()
|
||||
@@ -135,14 +129,14 @@
|
||||
/// <para>http://kba.cloud/hocr-spec/1.2/#elementdef-ocr_page</para>
|
||||
/// </summary>
|
||||
/// <param name="page"></param>
|
||||
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
|
||||
/// <param name="imageName"></param>
|
||||
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
|
||||
private string GetCode(Page page, bool includePaths, string imageName = "unknown")
|
||||
{
|
||||
pageCount++;
|
||||
int level = 2;
|
||||
|
||||
string hocr = GetIndent(level) + "<div class='ocr_page' id='page_" + page.Number.ToString() +
|
||||
string hocr = GetIndent(level) + @"<div class='ocr_page' id='page_" + page.Number.ToString() +
|
||||
"' title='image \"" + imageName + "\"; bbox 0 0 " +
|
||||
(int)Math.Round(page.Width * scale) + " " + (int)Math.Round(page.Height * scale) +
|
||||
"; ppageno " + (page.Number - 1) + "\'>";
|
||||
@@ -162,15 +156,16 @@
|
||||
|
||||
var words = page.GetWords(wordExtractor);
|
||||
|
||||
if (words.Any())
|
||||
if (words.Count() > 0)
|
||||
{
|
||||
foreach (var block in pageSegmenter.GetBlocks(words))
|
||||
var blocks = pageSegmenter.GetBlocks(words);
|
||||
foreach (var block in blocks)
|
||||
{
|
||||
hocr += "\n" + GetCodeArea(block, page.Height, level + 1);
|
||||
}
|
||||
}
|
||||
|
||||
hocr += "\n" + GetIndent(level) + "</div>";
|
||||
hocr += "\n" + GetIndent(level) + @"</div>";
|
||||
return hocr;
|
||||
}
|
||||
|
||||
@@ -184,10 +179,7 @@
|
||||
/// <param name="level">The indent level.</param>
|
||||
private string GetCode(PdfPath path, double pageHeight, bool subPaths, int level)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (path == null) return string.Empty;
|
||||
|
||||
string hocr = string.Empty;
|
||||
|
||||
@@ -197,7 +189,7 @@
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
areaCount++;
|
||||
hocr += GetIndent(level) + "<div class='ocr_carea' id='block_" + pageCount + "_"
|
||||
hocr += GetIndent(level) + @"<div class='ocr_carea' id='block_" + pageCount + "_"
|
||||
+ areaCount + "' title='" + GetCode(bbox.Value, pageHeight) + "'>\n";
|
||||
foreach (var subPath in path)
|
||||
{
|
||||
@@ -205,11 +197,11 @@
|
||||
if (subBbox.HasValue)
|
||||
{
|
||||
pathCount++;
|
||||
hocr += GetIndent(level + 1) + "<span class='ocr_linedrawing' id='drawing_" + pageCount + "_"
|
||||
hocr += GetIndent(level + 1) + @"<span class='ocr_linedrawing' id='drawing_" + pageCount + "_"
|
||||
+ pathCount + "' title='" + GetCode(subBbox.Value, pageHeight) + "' />\n";
|
||||
}
|
||||
}
|
||||
hocr += GetIndent(level) + "</div>";
|
||||
hocr += GetIndent(level) + @"</div>";
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -218,7 +210,7 @@
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
pathCount++;
|
||||
hocr += GetIndent(level) + "<span class='ocr_linedrawing' id='drawing_" + pageCount + "_"
|
||||
hocr += GetIndent(level) + @"<span class='ocr_linedrawing' id='drawing_" + pageCount + "_"
|
||||
+ pathCount + "' title='" + GetCode(bbox.Value, pageHeight) + "' />";
|
||||
}
|
||||
}
|
||||
@@ -230,7 +222,7 @@
|
||||
{
|
||||
imageCount++;
|
||||
var bbox = pdfImage.Bounds;
|
||||
return GetIndent(level) + "<span class='ocr_image' id='image_" + pageCount + "_"
|
||||
return GetIndent(level) + @"<span class='ocr_image' id='image_" + pageCount + "_"
|
||||
+ imageCount + "' title='" + GetCode(bbox, pageHeight) + "' />";
|
||||
}
|
||||
|
||||
@@ -245,11 +237,12 @@
|
||||
{
|
||||
areaCount++;
|
||||
|
||||
string hocr = GetIndent(level) + "<div class='ocr_carea' id='block_" + pageCount + "_"
|
||||
string bbox = GetCode(block.BoundingBox, pageHeight);
|
||||
string hocr = GetIndent(level) + @"<div class='ocr_carea' id='block_" + pageCount + "_"
|
||||
+ areaCount + "' title='" + GetCode(block.BoundingBox, pageHeight) + "'>";
|
||||
|
||||
hocr += GetCodeParagraph(block, pageHeight, level + 1); // we concider 1 area = 1 block. should change in the future
|
||||
hocr += "\n" + GetIndent(level) + "</div>";
|
||||
hocr += "\n" + GetIndent(level) + @"</div>";
|
||||
return hocr;
|
||||
}
|
||||
|
||||
@@ -263,14 +256,14 @@
|
||||
private string GetCodeParagraph(TextBlock block, double pageHeight, int level)
|
||||
{
|
||||
paraCount++;
|
||||
string hocr = "\n" + GetIndent(level) + "<p class='ocr_par' id='par_" + pageCount + "_"
|
||||
string hocr = "\n" + GetIndent(level) + @"<p class='ocr_par' id='par_" + pageCount + "_"
|
||||
+ paraCount + "' title='" + GetCode(block.BoundingBox, pageHeight) + "'>"; // lang='eng'
|
||||
|
||||
foreach (var line in block.TextLines)
|
||||
{
|
||||
hocr += "\n" + GetCode(line, pageHeight, level + 1);
|
||||
}
|
||||
hocr += "\n" + GetIndent(level) + "</p>";
|
||||
hocr += "\n" + GetIndent(level) + @"</p>";
|
||||
|
||||
return hocr;
|
||||
}
|
||||
@@ -292,14 +285,14 @@
|
||||
double baseLine = (double)line.Words[0].Letters[0].StartBaseLine.Y;
|
||||
baseLine = (double)line.BoundingBox.Bottom - baseLine;
|
||||
|
||||
string hocr = GetIndent(level) + "<span class='ocr_line' id='line_" + pageCount + "_" + lineCount + "' title='" +
|
||||
string hocr = GetIndent(level) + @"<span class='ocr_line' id='line_" + pageCount + "_" + lineCount + "' title='" +
|
||||
GetCode(line.BoundingBox, pageHeight) + "; baseline " + angle + " 0'>"; //"; x_size 42; x_descenders 5; x_ascenders 12' >";
|
||||
|
||||
foreach (var word in line.Words)
|
||||
{
|
||||
hocr += "\n" + GetCode(word, pageHeight, level + 1);
|
||||
}
|
||||
hocr += "\n" + GetIndent(level) + "</span>";
|
||||
hocr += "\n" + GetIndent(level) + @"</span>";
|
||||
return hocr;
|
||||
}
|
||||
|
||||
@@ -314,7 +307,7 @@
|
||||
{
|
||||
wordCount++;
|
||||
string hocr = GetIndent(level) +
|
||||
"<span class='ocrx_word' id='word_" + pageCount + "_" + wordCount +
|
||||
@"<span class='ocrx_word' id='word_" + pageCount + "_" + wordCount +
|
||||
"' title='" + GetCode(word.BoundingBox, pageHeight) + "; x_wconf " + GetConfidence(word);
|
||||
|
||||
hocr += "; x_font " + word.FontName;
|
||||
@@ -350,7 +343,7 @@
|
||||
var right = (int)Math.Round(rectangle.Right * scale);
|
||||
var bottom = (int)Math.Round((pageHeight - rectangle.Bottom) * scale);
|
||||
|
||||
return "bbox " + (left > 0 ? left : 0) + " "
|
||||
return @"bbox " + (left > 0 ? left : 0) + " "
|
||||
+ (top > 0 ? top : 0) + " "
|
||||
+ (right > 0 ? right : 0) + " "
|
||||
+ (bottom > 0 ? bottom : 0);
|
||||
|
||||
@@ -104,50 +104,34 @@
|
||||
return Serialize(pageXmlDocument);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a point to a string
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
/// <param name="pageWidth">The width of the page where the pdf point is located on</param>
|
||||
/// <param name="pageHeight">The height of the page where the pdf point is located on</param>
|
||||
/// <param name="scaleToApply"></param>
|
||||
/// <returns></returns>
|
||||
public static string PointToString(PdfPoint point, double pageWidth, double pageHeight, double scaleToApply = 1.0)
|
||||
private string PointToString(PdfPoint point, double height)
|
||||
{
|
||||
double x = Math.Round(point.X * scaleToApply);
|
||||
double y = Math.Round((pageHeight - point.Y) * scaleToApply);
|
||||
|
||||
// move away from borders
|
||||
x = x > 1 ? x : 1;
|
||||
y = y > 1 ? y : 1;
|
||||
|
||||
x = x < (pageWidth - 1) ? x : pageWidth - 1;
|
||||
y = y < (pageHeight - 1) ? y : pageHeight - 1;
|
||||
|
||||
return x.ToString("0") + "," + y.ToString("0");
|
||||
double x = Math.Round(point.X * scale);
|
||||
double y = Math.Round((height - point.Y) * scale);
|
||||
return (x > 0 ? x : 0).ToString("0") + "," + (y > 0 ? y : 0).ToString("0");
|
||||
}
|
||||
|
||||
private string ToPoints(IEnumerable<PdfPoint> points, double pageWidth, double pageHeight)
|
||||
private string ToPoints(IEnumerable<PdfPoint> points, double height)
|
||||
{
|
||||
return string.Join(" ", points.Select(p => PointToString(p, pageWidth, pageHeight, this.scale)));
|
||||
return string.Join(" ", points.Select(p => PointToString(p, height)));
|
||||
}
|
||||
|
||||
private string ToPoints(PdfRectangle pdfRectangle, double pageWidth, double pageHeight)
|
||||
private string ToPoints(PdfRectangle pdfRectangle, double height)
|
||||
{
|
||||
return ToPoints(new[] { pdfRectangle.BottomLeft, pdfRectangle.TopLeft, pdfRectangle.TopRight, pdfRectangle.BottomRight }, pageWidth, pageHeight);
|
||||
return ToPoints(new[] { pdfRectangle.BottomLeft, pdfRectangle.TopLeft, pdfRectangle.TopRight, pdfRectangle.BottomRight }, height);
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlCoords ToCoords(PdfRectangle pdfRectangle, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlCoords ToCoords(PdfRectangle pdfRectangle, double height)
|
||||
{
|
||||
return new PageXmlDocument.PageXmlCoords()
|
||||
{
|
||||
Points = ToPoints(pdfRectangle, pageWidth, pageHeight)
|
||||
Points = ToPoints(pdfRectangle, height)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PageXml Text colour in RGB encoded format
|
||||
/// <para>(red value) + (256 x green value) + (65536 x blue value).</para>
|
||||
/// <para>(red value) + (256 x green value) + (65536 x blue value).</para>
|
||||
/// </summary>
|
||||
private string ToRgbEncoded(IColor color)
|
||||
{
|
||||
@@ -182,9 +166,9 @@
|
||||
blocks = readingOrderDetector.Get(blocks).ToList();
|
||||
}
|
||||
|
||||
regions.AddRange(blocks.Select(b => ToPageXmlTextRegion(b, page.Width, page.Height)));
|
||||
regions.AddRange(blocks.Select(b => ToPageXmlTextRegion(b, page.Height)));
|
||||
|
||||
if (orderedRegions.Count > 0)
|
||||
if (orderedRegions.Any())
|
||||
{
|
||||
pageXmlPage.ReadingOrder = new PageXmlDocument.PageXmlReadingOrder()
|
||||
{
|
||||
@@ -200,13 +184,13 @@
|
||||
var images = page.GetImages().ToList();
|
||||
if (images.Count > 0)
|
||||
{
|
||||
regions.AddRange(images.Select(i => ToPageXmlImageRegion(i, page.Width, page.Height)));
|
||||
regions.AddRange(images.Select(i => ToPageXmlImageRegion(i, page.Height)));
|
||||
}
|
||||
|
||||
if (includePaths)
|
||||
{
|
||||
var graphicalElements = page.ExperimentalAccess.Paths.Select(p => ToPageXmlLineDrawingRegion(p, page.Width, page.Height));
|
||||
if (graphicalElements.Count(g => g != null) > 0)
|
||||
var graphicalElements = page.ExperimentalAccess.Paths.Select(p => ToPageXmlLineDrawingRegion(p, page.Height));
|
||||
if (graphicalElements.Where(g => g != null).Count() > 0)
|
||||
{
|
||||
regions.AddRange(graphicalElements.Where(g => g != null));
|
||||
}
|
||||
@@ -216,7 +200,7 @@
|
||||
return pageXmlPage;
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlLineDrawingRegion ToPageXmlLineDrawingRegion(PdfPath pdfPath, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlLineDrawingRegion ToPageXmlLineDrawingRegion(PdfPath pdfPath, double height)
|
||||
{
|
||||
var bbox = pdfPath.GetBoundingRectangle();
|
||||
if (bbox.HasValue)
|
||||
@@ -224,25 +208,25 @@
|
||||
regionCount++;
|
||||
return new PageXmlDocument.PageXmlLineDrawingRegion()
|
||||
{
|
||||
Coords = ToCoords(bbox.Value, pageWidth, pageHeight),
|
||||
Coords = ToCoords(bbox.Value, height),
|
||||
Id = "r" + regionCount
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlImageRegion ToPageXmlImageRegion(IPdfImage pdfImage, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlImageRegion ToPageXmlImageRegion(IPdfImage pdfImage, double height)
|
||||
{
|
||||
regionCount++;
|
||||
var bbox = pdfImage.Bounds;
|
||||
return new PageXmlDocument.PageXmlImageRegion()
|
||||
{
|
||||
Coords = ToCoords(bbox, pageWidth, pageHeight),
|
||||
Coords = ToCoords(bbox, height),
|
||||
Id = "r" + regionCount
|
||||
};
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlTextRegion ToPageXmlTextRegion(TextBlock textBlock, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlTextRegion ToPageXmlTextRegion(TextBlock textBlock, double height)
|
||||
{
|
||||
regionCount++;
|
||||
string regionId = "r" + regionCount;
|
||||
@@ -258,45 +242,45 @@
|
||||
|
||||
return new PageXmlDocument.PageXmlTextRegion()
|
||||
{
|
||||
Coords = ToCoords(textBlock.BoundingBox, pageWidth, pageHeight),
|
||||
Coords = ToCoords(textBlock.BoundingBox, height),
|
||||
Type = PageXmlDocument.PageXmlTextSimpleType.Paragraph,
|
||||
TextLines = textBlock.TextLines.Select(l => ToPageXmlTextLine(l, pageWidth, pageHeight)).ToArray(),
|
||||
TextLines = textBlock.TextLines.Select(l => ToPageXmlTextLine(l, height)).ToArray(),
|
||||
TextEquivs = new[] { new PageXmlDocument.PageXmlTextEquiv() { Unicode = textBlock.Text } },
|
||||
Id = regionId
|
||||
};
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlTextLine ToPageXmlTextLine(TextLine textLine, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlTextLine ToPageXmlTextLine(TextLine textLine, double height)
|
||||
{
|
||||
lineCount++;
|
||||
return new PageXmlDocument.PageXmlTextLine()
|
||||
{
|
||||
Coords = ToCoords(textLine.BoundingBox, pageWidth, pageHeight),
|
||||
Coords = ToCoords(textLine.BoundingBox, height),
|
||||
Production = PageXmlDocument.PageXmlProductionSimpleType.Printed,
|
||||
Words = textLine.Words.Select(w => ToPageXmlWord(w, pageWidth, pageHeight)).ToArray(),
|
||||
Words = textLine.Words.Select(w => ToPageXmlWord(w, height)).ToArray(),
|
||||
TextEquivs = new[] { new PageXmlDocument.PageXmlTextEquiv() { Unicode = textLine.Text } },
|
||||
Id = "l" + lineCount
|
||||
};
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlWord ToPageXmlWord(Word word, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlWord ToPageXmlWord(Word word, double height)
|
||||
{
|
||||
wordCount++;
|
||||
return new PageXmlDocument.PageXmlWord()
|
||||
{
|
||||
Coords = ToCoords(word.BoundingBox, pageWidth, pageHeight),
|
||||
Glyphs = word.Letters.Select(l => ToPageXmlGlyph(l, pageWidth, pageHeight)).ToArray(),
|
||||
Coords = ToCoords(word.BoundingBox, height),
|
||||
Glyphs = word.Letters.Select(l => ToPageXmlGlyph(l, height)).ToArray(),
|
||||
TextEquivs = new[] { new PageXmlDocument.PageXmlTextEquiv() { Unicode = word.Text } },
|
||||
Id = "w" + wordCount
|
||||
};
|
||||
}
|
||||
|
||||
private PageXmlDocument.PageXmlGlyph ToPageXmlGlyph(Letter letter, double pageWidth, double pageHeight)
|
||||
private PageXmlDocument.PageXmlGlyph ToPageXmlGlyph(Letter letter, double height)
|
||||
{
|
||||
glyphCount++;
|
||||
return new PageXmlDocument.PageXmlGlyph()
|
||||
{
|
||||
Coords = ToCoords(letter.GlyphRectangle, pageWidth, pageHeight),
|
||||
Coords = ToCoords(letter.GlyphRectangle, height),
|
||||
Ligature = false,
|
||||
Production = PageXmlDocument.PageXmlProductionSimpleType.Printed,
|
||||
TextStyle = new PageXmlDocument.PageXmlTextStyle()
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
builder.Append("</g></svg>");
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
|
||||
private static string LetterToSvg(Letter l, double height, XmlDocument doc)
|
||||
{
|
||||
string fontFamily = GetFontFamily(l.FontName, out string style, out string weight);
|
||||
@@ -69,7 +69,7 @@
|
||||
var x = Math.Round(l.StartBaseLine.X, Rounding);
|
||||
var y = Math.Round(height - l.StartBaseLine.Y, Rounding);
|
||||
|
||||
return $"<text x='{x}' y='{y}'{rotation} font-family='{fontFamily}' font-style='{style}' font-weight='{weight}' {fontSize} fill='{ColorToSvg(l.Color)}'>{safeValue}</text>"
|
||||
return $"<text x='{x}' y='{y}'{rotation} font-family='{fontFamily}' font-style='{style}' font-weight='{weight}' {fontSize} fill='{ColorToSvg(l.Color)}'>{safeValue}</text>"
|
||||
+ Environment.NewLine;
|
||||
}
|
||||
|
||||
@@ -123,11 +123,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
if (Fonts.ContainsKey(fontName))
|
||||
{
|
||||
fontName = Fonts[fontName];
|
||||
}
|
||||
|
||||
if (Fonts.ContainsKey(fontName)) fontName = Fonts[fontName];
|
||||
return fontName;
|
||||
}
|
||||
|
||||
@@ -140,11 +136,7 @@
|
||||
|
||||
private static string ColorToSvg(IColor color)
|
||||
{
|
||||
if (color == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (color == null) return "";
|
||||
var (r, g, b) = color.ToRGBValues();
|
||||
return $"rgb({Math.Ceiling(r * 255)},{Math.Ceiling(g * 255)},{Math.Ceiling(b * 255)})";
|
||||
}
|
||||
|
||||
@@ -11,67 +11,47 @@
|
||||
/// </summary>
|
||||
public class DefaultPageSegmenter : IPageSegmenter
|
||||
{
|
||||
private readonly DefaultPageSegmenterOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of default page segmenter, <see cref="DefaultPageSegmenter"/>.
|
||||
/// </summary>
|
||||
public static DefaultPageSegmenter Instance { get; } = new DefaultPageSegmenter();
|
||||
|
||||
/// <summary>
|
||||
/// using default options values.
|
||||
/// Get the blocks using default options values.
|
||||
/// </summary>
|
||||
public DefaultPageSegmenter() : this(new DefaultPageSegmenterOptions())
|
||||
/// <param name="words">The page's words to generate text blocks for.</param>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create using options values.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DefaultPageSegmenterOptions"/> to use.</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public DefaultPageSegmenter(DefaultPageSegmenterOptions options)
|
||||
{
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
return GetBlocks(words, new DefaultPageSegmenterOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the text blocks using options.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to generate text blocks for.</param>
|
||||
/// <param name="options">The <see cref="DefaultPageSegmenterOptions"/> to use.</param>
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the default method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words)
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words, DlaOptions options)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
if (options is DefaultPageSegmenterOptions dOptions)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
if (words?.Any() != true)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
|
||||
return new List<TextBlock>() { new TextBlock(new XYLeaf(words).GetLines(options.WordSeparator), options.LineSeparator) };
|
||||
return new List<TextBlock>() { new TextBlock(new XYLeaf(words).GetLines(dOptions.WordSeparator), dOptions.LineSeparator) };
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Options provided must be of type " + nameof(DefaultPageSegmenterOptions) + ".", nameof(options));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default page segmenter options.
|
||||
/// </summary>
|
||||
public class DefaultPageSegmenterOptions : IPageSegmenterOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// Default value is -1.
|
||||
/// </summary>
|
||||
public int MaxDegreeOfParallelism { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is ' ' (space).</para>
|
||||
/// </summary>
|
||||
public string WordSeparator { get; set; } = " ";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is '\n' (new line).</para>
|
||||
/// </summary>
|
||||
public string LineSeparator { get; set; } = "\n";
|
||||
}
|
||||
public class DefaultPageSegmenterOptions : PageSegmenterOptions
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -17,49 +17,48 @@
|
||||
/// </summary>
|
||||
public class DocstrumBoundingBoxes : IPageSegmenter
|
||||
{
|
||||
private readonly DocstrumBoundingBoxesOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Docstrum for bounding boxes page segmenter, <see cref="DocstrumBoundingBoxes"/>.
|
||||
/// </summary>
|
||||
public static DocstrumBoundingBoxes Instance { get; } = new DocstrumBoundingBoxes();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Docstrum for bounding boxes page segmenter using default options values.
|
||||
/// </summary>
|
||||
public DocstrumBoundingBoxes() : this(new DocstrumBoundingBoxesOptions())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Docstrum for bounding boxes page segmenter using options values.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="DocstrumBoundingBoxesOptions"/> to use.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public DocstrumBoundingBoxes(DocstrumBoundingBoxesOptions options)
|
||||
{
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the blocks.
|
||||
/// Get the blocks using default options values.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to segment into <see cref="TextBlock"/>s.</param>
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the document spectrum method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
return GetBlocks(words, new DocstrumBoundingBoxesOptions());
|
||||
}
|
||||
|
||||
return GetBlocks(words.ToList(),
|
||||
options.WithinLineBounds, options.WithinLineMultiplier, options.WithinLineBinSize,
|
||||
options.BetweenLineBounds, options.BetweenLineMultiplier, options.BetweenLineBinSize,
|
||||
options.AngularDifferenceBounds,
|
||||
options.Epsilon,
|
||||
options.WordSeparator, options.LineSeparator,
|
||||
options.MaxDegreeOfParallelism);
|
||||
/// <summary>
|
||||
/// Get the blocks using options values.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to segment into <see cref="TextBlock"/>s.</param>
|
||||
/// <param name="options">The <see cref="DocstrumBoundingBoxesOptions"/> to use.</param>
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the document spectrum method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words, DlaOptions options)
|
||||
{
|
||||
if (options is DocstrumBoundingBoxesOptions dbbOptions)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
|
||||
return GetBlocks(words.ToList(),
|
||||
dbbOptions.WithinLineBounds, dbbOptions.WithinLineMultiplier, dbbOptions.WithinLineBinSize,
|
||||
dbbOptions.BetweenLineBounds, dbbOptions.BetweenLineMultiplier, dbbOptions.BetweenLineBinSize,
|
||||
dbbOptions.AngularDifferenceBounds,
|
||||
dbbOptions.Epsilon,
|
||||
dbbOptions.WordSeparator, dbbOptions.LineSeparator,
|
||||
dbbOptions.MaxDegreeOfParallelism);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Options provided must be of type " + nameof(DocstrumBoundingBoxesOptions) + ".", nameof(options));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -77,8 +76,8 @@
|
||||
/// <param name="blBinSize">The bin size used when building the between-line distances distribution.</param>
|
||||
/// <param name="angularDifferenceBounds">The angular difference bounds between two lines to be considered in the same block. This defines if two lines are parallel enough.</param>
|
||||
/// <param name="epsilon">Precision when testing equalities.</param>
|
||||
/// <param name="wordSeparator">Separator used between words when building lines.</param>
|
||||
/// <param name="lineSeparator">Separator used between lines when building paragraphs.</param>
|
||||
/// <param name="wordSeparator"></param>
|
||||
/// <param name="lineSeparator"></param>
|
||||
/// <param name="maxDegreeOfParallelism">Sets the maximum number of concurrent tasks enabled.
|
||||
/// <para>A positive property value limits the number of concurrent operations to the set value.
|
||||
/// If it is -1, there is no limit on the number of concurrently running operations.</para></param>
|
||||
@@ -103,19 +102,12 @@
|
||||
maxDegreeOfParallelism,
|
||||
out double withinLineDistance, out double betweenLineDistance))
|
||||
{
|
||||
if (double.IsNaN(withinLineDistance))
|
||||
{
|
||||
withinLineDistance = 0;
|
||||
}
|
||||
|
||||
if (double.IsNaN(betweenLineDistance))
|
||||
{
|
||||
betweenLineDistance = 0;
|
||||
}
|
||||
if (double.IsNaN(withinLineDistance)) withinLineDistance = 0;
|
||||
if (double.IsNaN(betweenLineDistance)) betweenLineDistance = 0;
|
||||
}
|
||||
|
||||
// 2. Determination of Text Lines
|
||||
double maxWithinLineDistance = wlMultiplier * withinLineDistance;
|
||||
double maxWithinLineDistance = wlMultiplier * withinLineDistance; //Math.Min(3 * withinLineDistance.Value, 1.4142 * betweenLineDistance.Value);
|
||||
var lines = GetLines(words, maxWithinLineDistance, wlBounds, wordSeparator, maxDegreeOfParallelism).ToArray();
|
||||
|
||||
// 3. Structural Block Determination
|
||||
@@ -126,20 +118,9 @@
|
||||
#region Spacing Estimation
|
||||
/// <summary>
|
||||
/// Estimation of within-line and between-line spacing.
|
||||
/// <para>This is the Docstrum algorithm's 1st step.</para>
|
||||
/// </summary>
|
||||
/// <param name="words">The list of words.</param>
|
||||
/// <param name="wlBounds">Angle bounds for words to be considered as neighbours on the same line.</param>
|
||||
/// <param name="wlBinSize">The bin size used when building the within-line distances distribution.</param>
|
||||
/// <param name="blBounds">Angle bounds for words to be considered as neighbours on separate lines.</param>
|
||||
/// <param name="blBinSize">The bin size used when building the between-line distances distribution.</param>
|
||||
/// <param name="maxDegreeOfParallelism">Sets the maximum number of concurrent tasks enabled.
|
||||
/// <para>A positive property value limits the number of concurrent operations to the set value.
|
||||
/// If it is -1, there is no limit on the number of concurrently running operations.</para></param>
|
||||
/// <param name="withinLineDistance">The estimated within-line distance. Computed as the average peak value of distribution.</param>
|
||||
/// <param name="betweenLineDistance">The estimated between-line distance. Computed as the average peak value of distribution.</param>
|
||||
/// <returns>False if either 'withinLineDistance' or 'betweenLineDistance' is <see cref="double.NaN"/>.</returns>
|
||||
public static bool GetSpacingEstimation(IReadOnlyList<Word> words,
|
||||
/// <returns>False if either 'withinLineDistance' or 'betweenLineDistance' is NaN.</returns>
|
||||
private static bool GetSpacingEstimation(IReadOnlyList<Word> words,
|
||||
AngleBounds wlBounds, int wlBinSize,
|
||||
AngleBounds blBounds, int blBinSize,
|
||||
int maxDegreeOfParallelism,
|
||||
@@ -191,10 +172,7 @@
|
||||
|
||||
// The perpendicular distance can be negative because of the subtractions.
|
||||
// Could occur when words are overlapping, we ignore that.
|
||||
if (dist >= 0)
|
||||
{
|
||||
betweenLineDistList.Add(dist);
|
||||
}
|
||||
if (dist >= 0) betweenLineDistList.Add(dist);
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -264,19 +242,7 @@
|
||||
#endregion
|
||||
|
||||
#region Text Lines
|
||||
/// <summary>
|
||||
/// Get the <see cref="TextLine"/>s by grouping words using nearest neighbours.
|
||||
/// <para>This is the Docstrum algorithm's 2nd step.</para>
|
||||
/// </summary>
|
||||
/// <param name="words">The words to segment into <see cref="TextLine"/>s.</param>
|
||||
/// <param name="maxWLDistance">The maximum within-line distance. Computed as the estimated within-line spacing times the within-line multiplier in the default implementation.</param>
|
||||
/// <param name="wlBounds">Angle bounds for words to be considered as neighbours on the same line.</param>
|
||||
/// <param name="wordSeparator">Separator used between words when building lines.</param>
|
||||
/// <param name="maxDegreeOfParallelism">Sets the maximum number of concurrent tasks enabled.
|
||||
/// <para>A positive property value limits the number of concurrent operations to the set value.
|
||||
/// If it is -1, there is no limit on the number of concurrently running operations.</para></param>
|
||||
/// <returns>The <see cref="TextLine"/>s built.</returns>
|
||||
public static IEnumerable<TextLine> GetLines(IReadOnlyList<Word> words, double maxWLDistance, AngleBounds wlBounds,
|
||||
private static IEnumerable<TextLine> GetLines(IReadOnlyList<Word> words, double maxWLDistance, AngleBounds withinLine,
|
||||
string wordSeparator, int maxDegreeOfParallelism)
|
||||
{
|
||||
var groupedWords = Clustering.NearestNeighbours(words,
|
||||
@@ -286,7 +252,7 @@
|
||||
pivot => pivot.BoundingBox.BottomRight,
|
||||
candidate => candidate.BoundingBox.BottomLeft,
|
||||
_ => true,
|
||||
(pivot, candidate) => wlBounds.Contains(AngleWL(pivot, candidate)),
|
||||
(pivot, candidate) => withinLine.Contains(AngleWL(pivot, candidate)),
|
||||
maxDegreeOfParallelism).ToList();
|
||||
|
||||
foreach (var g in groupedWords)
|
||||
@@ -319,28 +285,8 @@
|
||||
#endregion
|
||||
|
||||
#region Blocking
|
||||
/// <summary>
|
||||
/// Get the <see cref="TextBlock"/>s.
|
||||
/// <para>This is the Docstrum algorithm's 3rd and final step.</para>
|
||||
/// <para>
|
||||
/// Method: We want to measure the distance between two lines using the following method:
|
||||
/// <br>- We check if two lines are overlapping horizontally and compute the perpendicular distance.</br>
|
||||
/// <br>- We check if the angle between the two line is within 'angularDifference'.</br>
|
||||
/// <br>- If the two lines are not overlapping or the angle is too wide, the distance is set to the infinity.</br>
|
||||
/// <para>If two text lines are approximately parallel, close in perpendicular distance, and they either overlap to some specified degree or are separated by only a small distance in parallel distance, then they are said to meet the criteria to belong to the same structural block.</para>
|
||||
/// </para>
|
||||
/// </summary>
|
||||
/// <param name="lines">The lines to segment into <see cref="TextBlock"/>s.</param>
|
||||
/// <param name="maxBLDistance">The maximum between-line distance. Computed as the estimated between-line spacing times the between-line multiplier in the default implementation.</param>
|
||||
/// <param name="angularDifferenceBounds">The angular difference bounds between two lines to be considered in the same block. This defines if two lines are parallel enough.</param>
|
||||
/// <param name="epsilon">Precision when testing equalities.</param>
|
||||
/// <param name="lineSeparator">Separator used between lines when building paragraphs.</param>
|
||||
/// <param name="maxDegreeOfParallelism">Sets the maximum number of concurrent tasks enabled.
|
||||
/// <para>A positive property value limits the number of concurrent operations to the set value.
|
||||
/// If it is -1, there is no limit on the number of concurrently running operations.</para></param>
|
||||
/// <returns>The <see cref="TextBlock"/>s built.</returns>
|
||||
public static IEnumerable<TextBlock> GetStructuralBlocks(IReadOnlyList<TextLine> lines,
|
||||
double maxBLDistance, AngleBounds angularDifferenceBounds, double epsilon, string lineSeparator, int maxDegreeOfParallelism)
|
||||
private static IEnumerable<TextBlock> GetStructuralBlocks(IReadOnlyList<TextLine> lines,
|
||||
double maxBLDistance, AngleBounds angularDifference, double epsilon, string lineSeparator, int maxDegreeOfParallelism)
|
||||
{
|
||||
/******************************************************************************************************
|
||||
* We want to measure the distance between two lines using the following method:
|
||||
@@ -355,7 +301,7 @@
|
||||
|
||||
var groupedLines = Clustering.NearestNeighbours(
|
||||
lines,
|
||||
(l1, l2) => PerpendicularOverlappingDistance(l1, l2, angularDifferenceBounds, epsilon),
|
||||
(l1, l2) => PerpendicularOverlappingDistance(l1, l2, angularDifference, epsilon),
|
||||
(_, __) => maxBLDistance,
|
||||
pivot => new PdfLine(pivot.BoundingBox.BottomLeft, pivot.BoundingBox.BottomRight),
|
||||
candidate => new PdfLine(candidate.BoundingBox.TopLeft, candidate.BoundingBox.TopRight),
|
||||
@@ -514,6 +460,7 @@
|
||||
}
|
||||
else // If dXj = 0, then yAj is calculated first, and xAj is calculated from that.
|
||||
{
|
||||
// TODO: check that
|
||||
yAj = (yPi * dYidYj + yPj * dXidXj + dYj * dXi * (xPi - xPj)) / denominator;
|
||||
xAj = xPj;
|
||||
}
|
||||
@@ -536,7 +483,9 @@
|
||||
double by = pl2.Y - pl1.Y;
|
||||
|
||||
double dotProd1 = ax * bx + ay * by;
|
||||
return dotProd1 >= 0 && dotProd1 <= (bx * bx + by * by);
|
||||
if (dotProd1 < 0) return false;
|
||||
|
||||
return dotProd1 <= (bx * bx + by * by);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -613,26 +562,8 @@
|
||||
/// <summary>
|
||||
/// Docstrum bounding boxes page segmenter options.
|
||||
/// </summary>
|
||||
public class DocstrumBoundingBoxesOptions : IPageSegmenterOptions
|
||||
public class DocstrumBoundingBoxesOptions : PageSegmenterOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// Default value is -1.
|
||||
/// </summary>
|
||||
public int MaxDegreeOfParallelism { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is ' ' (space).</para>
|
||||
/// </summary>
|
||||
public string WordSeparator { get; set; } = " ";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is '\n' (new line).</para>
|
||||
/// </summary>
|
||||
public string LineSeparator { get; set; } = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// Precision when testing equalities.
|
||||
/// <para>Default value is 1e-3.</para>
|
||||
@@ -659,6 +590,7 @@
|
||||
/// </summary>
|
||||
public int WithinLineBinSize { get; set; } = 10;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Angle bounds for words to be considered as neighbours on separate lines.
|
||||
/// <para>Default value is 45 ≤ θ ≤ 135.</para>
|
||||
|
||||
@@ -10,10 +10,18 @@
|
||||
public interface IPageSegmenter
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the blocks.
|
||||
/// Get the blocks using default options values.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to generate text blocks for.</param>
|
||||
/// <returns>A list of text blocks from this approach.</returns>
|
||||
IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words);
|
||||
|
||||
/// <summary>
|
||||
/// Get the text blocks using options.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to generate text blocks for.</param>
|
||||
/// <param name="options"></param>
|
||||
/// <returns>A list of text blocks from this approach.</returns>
|
||||
IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words, DlaOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
namespace UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter
|
||||
{
|
||||
/// <summary>
|
||||
/// Page segmenter options interface.
|
||||
/// Abstract page segmenter options.
|
||||
/// </summary>
|
||||
public interface IPageSegmenterOptions : IDlaOptions
|
||||
public abstract class PageSegmenterOptions : DlaOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Separator used between words when building lines.
|
||||
/// <para>Default value is ' ' (space).</para>
|
||||
/// </summary>
|
||||
string WordSeparator { get; set; }
|
||||
public string WordSeparator { get; set; } = " ";
|
||||
|
||||
/// <summary>
|
||||
/// Separator used between lines when building paragraphs.
|
||||
/// <para>Default value is '\n' (new line).</para>
|
||||
/// </summary>
|
||||
string LineSeparator { get; set; }
|
||||
public string LineSeparator { get; set; } = "\n";
|
||||
}
|
||||
}
|
||||
@@ -15,48 +15,47 @@
|
||||
/// </summary>
|
||||
public class RecursiveXYCut : IPageSegmenter
|
||||
{
|
||||
private readonly RecursiveXYCutOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Recursive X-Y Cut page segmenter, <see cref="RecursiveXYCut"/>.
|
||||
/// </summary>
|
||||
public static RecursiveXYCut Instance { get; } = new RecursiveXYCut();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Recursive X-Y Cut page segmenter using default options values.
|
||||
/// </summary>
|
||||
public RecursiveXYCut() : this(new RecursiveXYCutOptions())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Recursive X-Y Cut page segmenter using options values.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="RecursiveXYCutOptions"/> to use.</param>
|
||||
/// <exception cref="ArgumentException"></exception>
|
||||
public RecursiveXYCut(RecursiveXYCutOptions options)
|
||||
{
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the blocks.
|
||||
/// Get the blocks using default options values.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to segment into <see cref="TextBlock"/>s.</param>
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the Recursive X-Y cut method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
return GetBlocks(words, new RecursiveXYCutOptions());
|
||||
}
|
||||
|
||||
return GetBlocks(words,
|
||||
options.MinimumWidth,
|
||||
options.DominantFontWidthFunc,
|
||||
options.DominantFontHeightFunc,
|
||||
options.WordSeparator,
|
||||
options.LineSeparator);
|
||||
/// <summary>
|
||||
/// Get the blocks using options values.
|
||||
/// </summary>
|
||||
/// <param name="words">The page's words to segment into <see cref="TextBlock"/>s.</param>
|
||||
/// <param name="options">The <see cref="RecursiveXYCutOptions"/> to use.</param>
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the Recursive X-Y cut method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words, DlaOptions options)
|
||||
{
|
||||
if (options is RecursiveXYCutOptions ryxcOptions)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
{
|
||||
return EmptyArray<TextBlock>.Instance;
|
||||
}
|
||||
|
||||
return GetBlocks(words,
|
||||
ryxcOptions.MinimumWidth,
|
||||
ryxcOptions.DominantFontWidthFunc,
|
||||
ryxcOptions.DominantFontHeightFunc,
|
||||
ryxcOptions.WordSeparator,
|
||||
ryxcOptions.LineSeparator);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException("Options provided must be of type " + nameof(RecursiveXYCutOptions) + ".", nameof(options));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -93,7 +92,7 @@
|
||||
|
||||
if (leaves.Count > 0)
|
||||
{
|
||||
return leaves.ConvertAll(l => new TextBlock(l.GetLines(wordSeparator), lineSeparator));
|
||||
return leaves.Select(l => new TextBlock(l.GetLines(wordSeparator), lineSeparator)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,11 +183,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == wordsCount - 1)
|
||||
{
|
||||
projectionProfile.Add(currentProjection);
|
||||
}
|
||||
if (i == wordsCount - 1) projectionProfile.Add(currentProjection);
|
||||
}
|
||||
|
||||
var newLeavesEnums = projectionProfile.Select(p => leaf.Words.Where(w =>
|
||||
@@ -276,11 +271,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i == wordsCount - 1)
|
||||
{
|
||||
projectionProfile.Add(currentProjection);
|
||||
}
|
||||
if (i == wordsCount - 1) projectionProfile.Add(currentProjection);
|
||||
}
|
||||
|
||||
if (projectionProfile.Count == 1)
|
||||
@@ -338,26 +329,8 @@
|
||||
/// <summary>
|
||||
/// Recursive X-Y cut page segmenter options.
|
||||
/// </summary>
|
||||
public class RecursiveXYCutOptions : IPageSegmenterOptions
|
||||
public class RecursiveXYCutOptions : PageSegmenterOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// Default value is -1.
|
||||
/// </summary>
|
||||
public int MaxDegreeOfParallelism { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is ' ' (space).</para>
|
||||
/// </summary>
|
||||
public string WordSeparator { get; set; } = " ";
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// <para>Default value is '\n' (new line).</para>
|
||||
/// </summary>
|
||||
public string LineSeparator { get; set; } = "\n";
|
||||
|
||||
/// <summary>
|
||||
/// The minimum width for a block.
|
||||
/// <para>Default value is 1.</para>
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
public XYNode(params XYNode[] children)
|
||||
: this(children?.ToList())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -39,12 +40,12 @@
|
||||
/// <param name="children">The node's children.</param>
|
||||
public XYNode(IEnumerable<XYNode> children)
|
||||
{
|
||||
if (children?.Any() == true)
|
||||
if (children != null && children.Count() != 0)
|
||||
{
|
||||
Children = children.ToArray();
|
||||
BoundingBox = new PdfRectangle(children.Min(b => b.BoundingBox.Left),
|
||||
children.Min(b => b.BoundingBox.Bottom),
|
||||
children.Max(b => b.BoundingBox.Right),
|
||||
BoundingBox = new PdfRectangle(children.Min(b => b.BoundingBox.Left),
|
||||
children.Min(b => b.BoundingBox.Bottom),
|
||||
children.Max(b => b.BoundingBox.Right),
|
||||
children.Max(b => b.BoundingBox.Top));
|
||||
}
|
||||
else
|
||||
@@ -86,11 +87,7 @@
|
||||
|
||||
private void RecursiveCount(IEnumerable<XYNode> children, ref int count)
|
||||
{
|
||||
if (!children.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (children.Count() == 0) return;
|
||||
foreach (XYNode node in children.Where(x => x.IsLeaf))
|
||||
{
|
||||
count += node.CountWords();
|
||||
@@ -104,11 +101,7 @@
|
||||
|
||||
private void RecursiveGetLeaves(IEnumerable<XYNode> children, ref List<XYLeaf> leaves, int level)
|
||||
{
|
||||
if (!children.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (children.Count() == 0) return;
|
||||
bool isVerticalCut = level % 2 == 0;
|
||||
|
||||
foreach (XYLeaf node in children.Where(x => x.IsLeaf))
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Content;
|
||||
using System.Collections.Generic;
|
||||
using Util;
|
||||
|
||||
/// <summary>
|
||||
@@ -11,13 +10,6 @@
|
||||
/// </summary>
|
||||
public static class ContentOrderTextExtractor
|
||||
{
|
||||
private static readonly HashSet<string> ReplaceableWhitespace = new HashSet<string>
|
||||
{
|
||||
"\t",
|
||||
"\v",
|
||||
"\f"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human readable representation of the text from the page based on
|
||||
/// the letter order of the original PDF document.
|
||||
@@ -25,23 +17,7 @@
|
||||
/// <param name="page">A page from the document.</param>
|
||||
/// <param name="addDoubleNewline">Whether to include a double new-line when the text is likely to be a new paragraph.</param>
|
||||
public static string GetText(Page page, bool addDoubleNewline = false)
|
||||
=> GetText(
|
||||
page,
|
||||
new Options
|
||||
{
|
||||
SeparateParagraphsWithDoubleNewline = addDoubleNewline
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Gets a human readable representation of the text from the page based on
|
||||
/// the letter order of the original PDF document.
|
||||
/// </summary>
|
||||
/// <param name="page">A page from the document.</param>
|
||||
/// <param name="options">Control various aspects of the generated text.</param>
|
||||
public static string GetText(Page page, Options options)
|
||||
{
|
||||
options ??= new Options();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
var previous = default(Letter);
|
||||
@@ -55,21 +31,6 @@
|
||||
continue;
|
||||
}
|
||||
|
||||
if (options.ReplaceWhitespaceWithSpace && ReplaceableWhitespace.Contains(letter.Value))
|
||||
{
|
||||
letter = new Letter(
|
||||
" ",
|
||||
letter.GlyphRectangle,
|
||||
letter.StartBaseLine,
|
||||
letter.EndBaseLine,
|
||||
letter.Width,
|
||||
letter.FontSize,
|
||||
letter.Font,
|
||||
letter.Color,
|
||||
letter.PointSize,
|
||||
letter.TextSequence);
|
||||
}
|
||||
|
||||
if (letter.Value == " " && !hasJustAddedWhitespace)
|
||||
{
|
||||
if (previous != null && IsNewline(previous, letter, page, out _))
|
||||
@@ -97,7 +58,7 @@
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
if (options.SeparateParagraphsWithDoubleNewline && isDoubleNewline)
|
||||
if (addDoubleNewline && isDoubleNewline)
|
||||
{
|
||||
sb.AppendLine();
|
||||
}
|
||||
@@ -159,23 +120,5 @@
|
||||
|
||||
return gap > minPtSize * 0.9;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options controlling the text generation algorithm.
|
||||
/// </summary>
|
||||
public class Options
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether to include a double new-line when the text is likely to be a new paragraph.
|
||||
/// Default <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool SeparateParagraphsWithDoubleNewline { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether to replace all whitespace characters (except line breaks) with single space ' '
|
||||
/// character. Default <see langword="false"/>.
|
||||
/// </summary>
|
||||
public bool ReplaceWhitespaceWithSpace { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// Word extractor options interface.
|
||||
/// </summary>
|
||||
public interface IWordExtractorOptions : IDlaOptions
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -13,78 +13,77 @@
|
||||
/// </summary>
|
||||
public class NearestNeighbourWordExtractor : IWordExtractor
|
||||
{
|
||||
private readonly NearestNeighbourWordExtractorOptions options;
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Nearest Neighbour Word Extractor, <see cref="NearestNeighbourWordExtractor"/>.
|
||||
/// </summary>
|
||||
public static NearestNeighbourWordExtractor Instance { get; } = new NearestNeighbourWordExtractor();
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Nearest Neighbour Word Extractor using default options values.
|
||||
/// </summary>
|
||||
public NearestNeighbourWordExtractor() : this(new NearestNeighbourWordExtractorOptions())
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of Nearest Neighbour Word Extractor using options values.
|
||||
/// </summary>
|
||||
/// <param name="options">The <see cref="NearestNeighbourWordExtractorOptions"/> to use.</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
public NearestNeighbourWordExtractor(NearestNeighbourWordExtractorOptions options)
|
||||
{
|
||||
this.options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the words.
|
||||
/// Get the words using default options values.
|
||||
/// </summary>
|
||||
/// <param name="letters">The page's letters to group into <see cref="Word"/>s.</param>
|
||||
/// <returns>The <see cref="Word"/>s generated by the nearest neighbour method.</returns>
|
||||
public IEnumerable<Word> GetWords(IReadOnlyList<Letter> letters)
|
||||
{
|
||||
if (letters == null || letters.Count == 0)
|
||||
return GetWords(letters, new NearestNeighbourWordExtractorOptions());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the words using options values.
|
||||
/// </summary>
|
||||
/// <param name="letters">The page's letters to group into <see cref="Word"/>s.</param>
|
||||
/// <param name="options">The <see cref="NearestNeighbourWordExtractorOptions"/> to use.</param>
|
||||
/// <returns>The <see cref="Word"/>s generated by the nearest neighbour method.</returns>
|
||||
public IEnumerable<Word> GetWords(IReadOnlyList<Letter> letters, DlaOptions options)
|
||||
{
|
||||
if (options is NearestNeighbourWordExtractorOptions nnOptions)
|
||||
{
|
||||
return EmptyArray<Word>.Instance;
|
||||
}
|
||||
if (letters == null || letters.Count == 0)
|
||||
{
|
||||
return EmptyArray<Word>.Instance;
|
||||
}
|
||||
|
||||
if (options.GroupByOrientation)
|
||||
{
|
||||
// axis aligned
|
||||
List<Word> words = GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Horizontal).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism);
|
||||
if (nnOptions.GroupByOrientation)
|
||||
{
|
||||
// axis aligned
|
||||
List<Word> words = GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Horizontal).ToList(),
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasureAA, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism);
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate270).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate270).ToList(),
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasureAA, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism));
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate180).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate180).ToList(),
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasureAA, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism));
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate90).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate90).ToList(),
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasureAA, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism));
|
||||
|
||||
// not axis aligned
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Other).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasure, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
// not axis aligned
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Other).ToList(),
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasure, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism));
|
||||
|
||||
return words;
|
||||
return words;
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetWords(letters,
|
||||
nnOptions.MaximumDistance, nnOptions.DistanceMeasure, nnOptions.FilterPivot,
|
||||
nnOptions.Filter, nnOptions.MaxDegreeOfParallelism);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetWords(letters,
|
||||
options.MaximumDistance, options.DistanceMeasure, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism);
|
||||
throw new ArgumentException("Options provided must be of type " + nameof(NearestNeighbourWordExtractorOptions) + ".", nameof(options));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,10 +107,7 @@
|
||||
Func<Letter, bool> filterPivotFunction,
|
||||
Func<Letter, Letter, bool> filterFunction, int maxDegreeOfParallelism)
|
||||
{
|
||||
if (letters == null || letters.Count == 0)
|
||||
{
|
||||
return new List<Word>();
|
||||
}
|
||||
if (letters == null || letters.Count == 0) return new List<Word>();
|
||||
|
||||
var groupedLetters = Clustering.NearestNeighbours(letters,
|
||||
distMeasure, maxDistanceFunction,
|
||||
@@ -132,17 +128,11 @@
|
||||
/// <summary>
|
||||
/// Nearest neighbour word extractor options.
|
||||
/// </summary>
|
||||
public class NearestNeighbourWordExtractorOptions : IWordExtractorOptions
|
||||
public class NearestNeighbourWordExtractorOptions : DlaOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// Default value is -1.
|
||||
/// </summary>
|
||||
public int MaxDegreeOfParallelism { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum distance between two letters (start and end base line points) within the same word, as a function of the two letters.
|
||||
/// <para>If the distance between the two letters is greater than this maximum, they will belong to different words.</para>
|
||||
/// If the distance between the two letters is greater than this maximum, they will belong to different words.
|
||||
/// <para>Default value is 20% of the Max(Width, PointSize) of both letters. If <see cref="TextOrientation"/> is Other, this distance is doubled.</para>
|
||||
/// </summary>
|
||||
public Func<Letter, Letter, double> MaximumDistance { get; set; } = (l1, l2) =>
|
||||
@@ -169,15 +159,15 @@
|
||||
|
||||
/// <summary>
|
||||
/// The distance measure used between two letters (start and end base line points) with axis aligned <see cref="TextOrientation"/>.
|
||||
/// <para>Only used if <see cref="GroupByOrientation"/> is set to <c>true</c>.</para>
|
||||
/// Only used if GroupByOrientation is set to true.
|
||||
/// <para>Default value is the Manhattan distance.</para>
|
||||
/// </summary>
|
||||
public Func<PdfPoint, PdfPoint, double> DistanceMeasureAA { get; set; } = Distances.Manhattan;
|
||||
|
||||
/// <summary>
|
||||
/// Function used to filter out connection between letters, e.g. check if the letters have the same color.
|
||||
/// If the function returns <c>false</c>, letters will belong to different words.
|
||||
/// <para>Default value checks whether the neighbour is a white space or not. If it is the case, it returns <c>false</c>.</para>
|
||||
/// If the function returns false, letters will belong to different words.
|
||||
/// <para>Default value checks whether the neighbour is a white space or not. If it is the case, it returns false.</para>
|
||||
/// </summary>
|
||||
public Func<Letter, Letter, bool> Filter { get; set; } = (_, l2) => !string.IsNullOrWhiteSpace(l2.Value);
|
||||
|
||||
@@ -188,9 +178,9 @@
|
||||
public Func<Letter, bool> FilterPivot { get; set; } = l => !string.IsNullOrWhiteSpace(l.Value);
|
||||
|
||||
/// <summary>
|
||||
/// If <c>true</c>, letters will be grouped by <see cref="TextOrientation"/> before processing.
|
||||
/// The <see cref="DistanceMeasureAA"/> will be used on axis aligned letters, and the <see cref="DistanceMeasure"/> on others.
|
||||
/// <para>If <c>false</c>, <see cref="DistanceMeasure"/> will be used for all letters, and <see cref="DistanceMeasureAA"/> won't be used.</para>
|
||||
/// If true, letters will be grouped by <see cref="TextOrientation"/> before processing.
|
||||
/// The DistanceMeasureAA will be used on axis aligned letters, and the DistanceMeasure on others.
|
||||
/// If false, DistanceMeasure will be used for all letters and DistanceMeasureAA won't be used.
|
||||
/// <para>Default value is true.</para>
|
||||
/// </summary>
|
||||
public bool GroupByOrientation { get; set; } = true;
|
||||
|
||||
@@ -795,8 +795,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
* and the fourth byte contains the lowest order bits.
|
||||
* This number is interpreted as a Fixed; that is, a signed number with 16 bits of fraction
|
||||
*/
|
||||
var lead = (short)(bytes[++i] << 8) + bytes[++i];
|
||||
var fractionalPart = (bytes[++i] << 8) + bytes[++i];
|
||||
var lead = bytes[++i] << 8 | bytes[++i];
|
||||
var fractionalPart = bytes[++i] << 8 | bytes[++i];
|
||||
|
||||
return lead + (fractionalPart / 65535.0f);
|
||||
}
|
||||
@@ -860,8 +860,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
return new Type2CharStrings.CommandSequence.CommandIdentifier(precedingValues.Count, false, 255);
|
||||
}
|
||||
|
||||
private static int CalculatePrecedingHintBytes(List<float> precedingValues,
|
||||
List<Type2CharStrings.CommandSequence.CommandIdentifier> precedingCommands)
|
||||
private static int CalculatePrecedingHintBytes(IReadOnlyList<float> precedingValues,
|
||||
IReadOnlyList<Type2CharStrings.CommandSequence.CommandIdentifier> precedingCommands)
|
||||
{
|
||||
int SafeStemCount(int counts)
|
||||
{
|
||||
@@ -873,7 +873,7 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
|
||||
return (counts - 1) / 2;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* The hintmask operator is followed by one or more data bytes that specify the stem hints which are to be active for the
|
||||
* subsequent path construction. The number of data bytes must be exactly the number needed to represent the number of
|
||||
@@ -891,14 +891,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
precedingNumbers++;
|
||||
}
|
||||
|
||||
for (var j = 0; j < precedingCommands.Count; j++)
|
||||
foreach (var identifier in precedingCommands.Where(x => x.CommandIndex == i + 1))
|
||||
{
|
||||
var identifier = precedingCommands[j];
|
||||
if (identifier.CommandIndex != i + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!identifier.IsMultiByteCommand
|
||||
&& (identifier.CommandId == HintmaskByte || identifier.CommandId == CntrmaskByte)
|
||||
&& !hasEncounteredInitialHintMask)
|
||||
|
||||
@@ -76,13 +76,6 @@
|
||||
AddAdobeFontMetrics("Times,Italic", "Times-Italic");
|
||||
AddAdobeFontMetrics("Times,Bold", "Times-Bold");
|
||||
AddAdobeFontMetrics("Times,BoldItalic", "Times-BoldItalic");
|
||||
|
||||
// Additional names for Arial
|
||||
AddAdobeFontMetrics("ArialMT", "Helvetica");
|
||||
AddAdobeFontMetrics("Arial-ItalicMT", "Helvetica-Oblique");
|
||||
AddAdobeFontMetrics("Arial-BoldMT", "Helvetica-Bold");
|
||||
AddAdobeFontMetrics("Arial-BoldMT,Bold", "Helvetica-Bold");
|
||||
AddAdobeFontMetrics("Arial-BoldItalicMT", "Helvetica-BoldOblique");
|
||||
}
|
||||
|
||||
private static void AddAdobeFontMetrics(string fontName, Standard14Font? type = null)
|
||||
@@ -150,7 +143,7 @@
|
||||
{
|
||||
return Standard14Cache[BuilderTypesToNames[fontType]];
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a font with this name is a standard 14 font.
|
||||
/// </summary>
|
||||
@@ -166,7 +159,7 @@
|
||||
{
|
||||
return new HashSet<string>(Standard14Names);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get the official Standard 14 name of the actual font which the given font name maps to.
|
||||
/// </summary>
|
||||
|
||||
@@ -22,19 +22,10 @@
|
||||
}
|
||||
|
||||
var strings = new TrueTypeNameRecord[count];
|
||||
var offset = header.Offset + stringOffset;
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
strings[i] = GetTrueTypeNameRecord(names[i], data, offset);
|
||||
}
|
||||
var nameRecord = names[i];
|
||||
|
||||
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
private static TrueTypeNameRecord GetTrueTypeNameRecord(NameRecordBuilder nameRecord, TrueTypeDataBytes data, uint offset)
|
||||
{
|
||||
try
|
||||
{
|
||||
var encoding = OtherEncodings.Iso88591;
|
||||
|
||||
switch (nameRecord.PlatformId)
|
||||
@@ -71,27 +62,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
var position = offset + nameRecord.Offset;
|
||||
|
||||
if (position >= data.Length)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var position = header.Offset + stringOffset + nameRecord.Offset;
|
||||
|
||||
data.Seek(position);
|
||||
|
||||
if (data.TryReadString(nameRecord.Length, encoding, out var str))
|
||||
{
|
||||
return nameRecord.ToNameRecord(str);
|
||||
}
|
||||
var str = data.ReadString(nameRecord.Length, encoding);
|
||||
|
||||
// Damaged font
|
||||
return null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
strings[i] = nameRecord.ToNameRecord(str);
|
||||
}
|
||||
|
||||
return new NameTable(header, GetName(4, strings), GetName(1, strings), GetName(2, strings), strings);
|
||||
}
|
||||
|
||||
private static string GetName(int nameId, TrueTypeNameRecord[] names)
|
||||
@@ -104,7 +84,7 @@
|
||||
{
|
||||
var name = names[i];
|
||||
|
||||
if (name is null || name.NameId != nameId)
|
||||
if (name.NameId != nameId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType.Parser
|
||||
{
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Tables;
|
||||
|
||||
internal static class TableParser
|
||||
@@ -14,20 +13,6 @@
|
||||
|
||||
public static T Parse<T>(TrueTypeHeaderTable table, TrueTypeDataBytes data, TableRegister.Builder register) where T : ITrueTypeTable
|
||||
{
|
||||
//checksum https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
|
||||
uint sum = 0;
|
||||
var nLongs = (table.Length + 3) / 4;
|
||||
data.Seek(table.Offset);
|
||||
while (nLongs-- > 0)
|
||||
{
|
||||
sum += (uint)data.ReadSignedInt();
|
||||
}
|
||||
|
||||
if (sum != table.CheckSum)
|
||||
{
|
||||
Trace.TraceWarning("Table with invalid checksum found in TrueType font file.");
|
||||
}
|
||||
|
||||
if (typeof(T) == typeof(CMapTable))
|
||||
{
|
||||
return (T) (object) CMapTableParser.Parse(table, data, register);
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
string any = null;
|
||||
foreach (var nameRecord in NameRecords)
|
||||
{
|
||||
if (nameRecord is null || nameRecord.NameId != 6)
|
||||
if (nameRecord.NameId != 6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -203,10 +203,7 @@
|
||||
for (var i = 0; i < namesLength; i++)
|
||||
{
|
||||
var numberOfCharacters = data.ReadByte();
|
||||
if (data.TryReadString(numberOfCharacters, Encoding.UTF8, out var str))
|
||||
{
|
||||
nameArray[i] = str;
|
||||
}
|
||||
nameArray[i] = data.ReadString(numberOfCharacters, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Fonts.TrueType
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Core;
|
||||
|
||||
@@ -80,33 +81,22 @@
|
||||
/// </summary>
|
||||
public string ReadTag()
|
||||
{
|
||||
if (TryReadString(4, Encoding.UTF8, out var result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Could not read Tag from TrueType file at {inputBytes.CurrentOffset}.");
|
||||
return ReadString(4, Encoding.UTF8);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see langword="string"/> of the given number of bytes in length with the specified encoding.
|
||||
/// </summary>
|
||||
public bool TryReadString(int bytesToRead, Encoding encoding, out string result)
|
||||
public string ReadString(int bytesToRead, Encoding encoding)
|
||||
{
|
||||
result = null;
|
||||
if (encoding == null)
|
||||
{
|
||||
return false;
|
||||
throw new ArgumentNullException(nameof(encoding));
|
||||
}
|
||||
|
||||
byte[] data = new byte[bytesToRead];
|
||||
if (ReadBuffered(data, bytesToRead))
|
||||
{
|
||||
result = encoding.GetString(data, 0, data.Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
ReadBuffered(data, bytesToRead);
|
||||
return encoding.GetString(data, 0, data.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -244,15 +234,13 @@
|
||||
return $"@: {Position} of {inputBytes.Length} bytes.";
|
||||
}
|
||||
|
||||
private bool ReadBuffered(byte[] buffer, int length)
|
||||
private void ReadBuffered(byte[] buffer, int length)
|
||||
{
|
||||
var read = inputBytes.Read(buffer, length);
|
||||
if (read < length)
|
||||
{
|
||||
return false;
|
||||
throw new EndOfStreamException($"Could not read a buffer of {length} bytes.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction;
|
||||
|
||||
/// <summary>
|
||||
/// Call other subroutine command. Arguments are pushed onto the PostScript interpreter operand stack then
|
||||
@@ -25,13 +23,13 @@
|
||||
|
||||
public static bool TakeFromStackBottom { get; } = false;
|
||||
public static bool ClearsOperandStack { get; } = false;
|
||||
|
||||
|
||||
public static LazyType1Command Lazy { get; } = new LazyType1Command(Name, Run);
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var index = (int)context.Stack.PopTop();
|
||||
|
||||
var index = (int) context.Stack.PopTop();
|
||||
|
||||
// What it should do
|
||||
var numberOfArguments = (int)context.Stack.PopTop();
|
||||
var otherSubroutineArguments = new List<double>(numberOfArguments);
|
||||
@@ -44,44 +42,17 @@
|
||||
{
|
||||
// Other subrs 0-2 implement flex
|
||||
case FlexEnd:
|
||||
{
|
||||
// https://github.com/apache/pdfbox/blob/2c23d8b4e3ad61852f0b6ee2b95b907eefba1fcf/fontbox/src/main/java/org/apache/fontbox/cff/Type1CharString.java#L339
|
||||
context.IsFlexing = false;
|
||||
if (context.FlexPoints.Count < 7)
|
||||
{
|
||||
throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
}
|
||||
{
|
||||
context.IsFlexing = false;
|
||||
// TODO: I don't really care about flexpoints, but we should probably handle them... one day.
|
||||
//if (context.FlexPoints.Count < 7)
|
||||
//{
|
||||
// throw new NotSupportedException("There must be at least 7 flex points defined by an other subroutine.");
|
||||
//}
|
||||
|
||||
// reference point is relative to start point
|
||||
PdfPoint reference = context.FlexPoints[0];
|
||||
reference = reference.Translate(context.CurrentPosition.X, context.CurrentPosition.Y);
|
||||
|
||||
// first point is relative to reference point
|
||||
PdfPoint first = context.FlexPoints[1];
|
||||
first = first.Translate(reference.X, reference.Y);
|
||||
|
||||
// make the first point relative to the start point
|
||||
first = first.Translate(-context.CurrentPosition.X, -context.CurrentPosition.Y);
|
||||
|
||||
context.Stack.Push(first.X);
|
||||
context.Stack.Push(first.Y);
|
||||
context.Stack.Push(context.FlexPoints[2].X);
|
||||
context.Stack.Push(context.FlexPoints[2].Y);
|
||||
context.Stack.Push(context.FlexPoints[3].X);
|
||||
context.Stack.Push(context.FlexPoints[3].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.Stack.Push(context.FlexPoints[4].X);
|
||||
context.Stack.Push(context.FlexPoints[4].Y);
|
||||
context.Stack.Push(context.FlexPoints[5].X);
|
||||
context.Stack.Push(context.FlexPoints[5].Y);
|
||||
context.Stack.Push(context.FlexPoints[6].X);
|
||||
context.Stack.Push(context.FlexPoints[6].Y);
|
||||
RelativeRCurveToCommand.Run(context);
|
||||
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
context.ClearFlexPoints();
|
||||
break;
|
||||
}
|
||||
case FlexBegin:
|
||||
Debug.Assert(otherSubroutineArguments.Count == 0, "Flex begin should have no arguments.");
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
/// <summary>
|
||||
/// Sets the current point to (x, y) in absolute character space coordinates without performing a charstring moveto command.
|
||||
/// <para>This establishes the current point for a subsequent relative path building command.
|
||||
/// The 'setcurrentpoint' command is used only in conjunction with results from 'OtherSubrs' procedures.</para>
|
||||
/// </summary>
|
||||
internal static class SetCurrentPointCommand
|
||||
{
|
||||
@@ -24,8 +22,8 @@
|
||||
var x = context.Stack.PopBottom();
|
||||
var y = context.Stack.PopBottom();
|
||||
|
||||
//context.CurrentPosition = new PdfPoint(x, y);
|
||||
// TODO: need to investigate why odd behavior when the current point is actualy set.
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
|
||||
context.Stack.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,19 @@
|
||||
|
||||
public static void Run(Type1BuildCharContext context)
|
||||
{
|
||||
var deltaX = context.Stack.PopBottom();
|
||||
var x = context.Stack.PopBottom();
|
||||
|
||||
var actualX = context.CurrentPosition.X + x;
|
||||
var y = context.CurrentPosition.Y;
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, 0));
|
||||
// TODO: flex support
|
||||
}
|
||||
else
|
||||
{
|
||||
var x = context.CurrentPosition.X + deltaX;
|
||||
var y = context.CurrentPosition.Y;
|
||||
context.CurrentPosition = new PdfPoint(x, y);
|
||||
context.Path.MoveTo(x, y);
|
||||
context.CurrentPosition = new PdfPoint(actualX, y);
|
||||
context.Path.MoveTo(actualX, y);
|
||||
}
|
||||
|
||||
context.Stack.Clear();
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
context.AddFlexPoint(new PdfPoint(deltaX, deltaY));
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Fonts.Type1.CharStrings.Commands.PathConstruction
|
||||
{
|
||||
using Core;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// Vertical move to. Moves relative to the current point.
|
||||
@@ -24,8 +23,7 @@
|
||||
|
||||
if (context.IsFlexing)
|
||||
{
|
||||
// not in the Type 1 spec, but exists in some fonts
|
||||
context.AddFlexPoint(new PdfPoint(0, deltaY));
|
||||
// TODO: flex commands
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/// Sets left sidebearing and the character width vector.
|
||||
/// This command also sets the current point to(sbx, sby), but does not place the point in the character path.
|
||||
/// </summary>
|
||||
internal static class SbwCommand
|
||||
internal class SbwCommand
|
||||
{
|
||||
public const string Name = "sbw";
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
public CharStringStack PostscriptStack { get; } = new CharStringStack();
|
||||
|
||||
public List<PdfPoint> FlexPoints { get; } = new List<PdfPoint>();
|
||||
public IReadOnlyList<PdfPoint> FlexPoints { get; }
|
||||
|
||||
public Type1BuildCharContext(IReadOnlyDictionary<int, Type1CharStrings.CommandSequence> subroutines,
|
||||
Func<int, PdfSubpath> characterByIndexFactory,
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
public void AddFlexPoint(PdfPoint point)
|
||||
{
|
||||
FlexPoints.Add(point);
|
||||
|
||||
}
|
||||
|
||||
public PdfSubpath GetCharacter(int characterCode)
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
public void ClearFlexPoints()
|
||||
{
|
||||
FlexPoints.Clear();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
new Dictionary<int, Type1CharStrings.CommandSequence>());
|
||||
return (defaultPrivateDictionary, defaultCharstrings);
|
||||
}
|
||||
|
||||
|
||||
var tokenizer = new Type1Tokenizer(new ByteArrayInputBytes(decrypted));
|
||||
|
||||
/*
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
var length = next.AsInt();
|
||||
ReadExpected(tokenizer, Type1Token.TokenType.Name, "dict");
|
||||
|
||||
|
||||
// Could also be "/Private 10 dict def Private begin" instead of the "dup"
|
||||
ReadExpectedAfterOptional(tokenizer, Type1Token.TokenType.Name, "def", Type1Token.TokenType.Name, "dup");
|
||||
ReadExpected(tokenizer, Type1Token.TokenType.Name, "begin");
|
||||
@@ -74,7 +74,7 @@
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var token = tokenizer.GetNext();
|
||||
|
||||
|
||||
// premature end
|
||||
if (token == null || token.Type != Type1Token.TokenType.Literal)
|
||||
{
|
||||
@@ -88,7 +88,7 @@
|
||||
case Type1Symbols.RdProcedure:
|
||||
case Type1Symbols.RdProcedureAlt:
|
||||
{
|
||||
var procedureTokens = ReadProcedure(tokenizer, false);
|
||||
var procedureTokens = ReadProcedure(tokenizer);
|
||||
builder.Rd = procedureTokens;
|
||||
ReadTillDef(tokenizer);
|
||||
break;
|
||||
@@ -96,7 +96,7 @@
|
||||
case Type1Symbols.NoAccessDef:
|
||||
case Type1Symbols.NoAccessDefAlt:
|
||||
{
|
||||
var procedureTokens = ReadProcedure(tokenizer, false);
|
||||
var procedureTokens = ReadProcedure(tokenizer);
|
||||
builder.NoAccessDef = procedureTokens;
|
||||
ReadTillDef(tokenizer);
|
||||
break;
|
||||
@@ -104,7 +104,7 @@
|
||||
case Type1Symbols.NoAccessPut:
|
||||
case Type1Symbols.NoAccessPutAlt:
|
||||
{
|
||||
var procedureTokens = ReadProcedure(tokenizer, false);
|
||||
var procedureTokens = ReadProcedure(tokenizer);
|
||||
builder.NoAccessPut = procedureTokens;
|
||||
ReadTillDef(tokenizer);
|
||||
break;
|
||||
@@ -161,27 +161,9 @@
|
||||
}
|
||||
case Type1Symbols.MinFeature:
|
||||
{
|
||||
try
|
||||
{
|
||||
var startToken = tokenizer.GetNext();
|
||||
var procedureTokens = ReadProcedure(tokenizer);
|
||||
|
||||
if (startToken.Type == Type1Token.TokenType.StartArray)
|
||||
{
|
||||
var arrayTokens = ReadArrayValues(tokenizer, x => x.AsInt(), true);
|
||||
|
||||
builder.MinFeature = new MinFeature(arrayTokens[0], arrayTokens[1]);
|
||||
}
|
||||
else if (startToken.Type == Type1Token.TokenType.StartProc)
|
||||
{
|
||||
var procedureTokens = ReadProcedure(tokenizer, true);
|
||||
|
||||
builder.MinFeature = new MinFeature(procedureTokens[0].AsInt(), procedureTokens[1].AsInt());
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignored.
|
||||
}
|
||||
builder.MinFeature = new MinFeature(procedureTokens[0].AsInt(), procedureTokens[1].AsInt());
|
||||
|
||||
ReadTillDef(tokenizer);
|
||||
|
||||
@@ -451,10 +433,10 @@
|
||||
throw new InvalidOperationException($"Found invalid token {token} when type {type} with text {text} was expected.");
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Type1Token> ReadProcedure(Type1Tokenizer tokenizer, bool hasReadStartProc)
|
||||
private static IReadOnlyList<Type1Token> ReadProcedure(Type1Tokenizer tokenizer)
|
||||
{
|
||||
var tokens = new List<Type1Token>();
|
||||
var depth = hasReadStartProc ? 1 : -1;
|
||||
var depth = -1;
|
||||
ReadProcedure(tokenizer, tokens, ref depth);
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47;net6.0</TargetFrameworks>
|
||||
<TargetFrameworks>netstandard2.0;net45;net451;net452;net46;net461;net462;net47</TargetFrameworks>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Version>0.1.7-alpha-001</Version>
|
||||
<Version>0.1.3-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
|
||||
@@ -1,173 +1,95 @@
|
||||
namespace UglyToad.PdfPig.Tests.Dla
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
||||
using UglyToad.PdfPig.Fonts.SystemFonts;
|
||||
using Xunit;
|
||||
|
||||
public class DocstrumBoundingBoxesTests
|
||||
{
|
||||
public static IEnumerable<object[]> DataExtract => new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
"complex rotated.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse at ullamcorper libero. Cras sit amet dui laoreet tellus tristique commodo. Nam pretium id ligula ac malesuada. Mauris at lacinia magna. Curabitur ex lectus, lobortis lobortis turpis ac, congue aliquet quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consectetur sem et ex sagittis pretium. Praesent urna velit, mollis vitae ex vel, hendrerit finibus mauris. In at luctus orci. Nunc odio justo, rhoncus et euismod nec, bibendum vitae lacus. Aenean maximus sapien lacus, ut pellentesque tellus egestas eget. Nulla semper massa ut vehicula faucibus. Nam rhoncus, dolor consectetur pulvinar gravida, nisi sem luctus nibh, non venenatis nunc lorem et velit.",
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam.",
|
||||
"Mauris tincidunt massa id lorem consectetur, in vestibulum nibh feugiat. Quisque eget commodo tortor. Duis iaculis, urna eget porttitor consectetur, metus lacus tempus urna, vehicula facilisis quam est scelerisque dui. Vestibulum imperdiet, tellus vel vulputate pretium, dolor mauris aliquet erat, sit amet fringilla nisi dui ut felis.",
|
||||
"Cras gravida vel risus sit amet sagittis. Vestibulum et purus pretium, accumsan turpis ac, consectetur augue. Nam viverra purus in urna mollis eleifend. Donec non imperdiet justo. In commodo tortor in diam feugiat, eget placerat augue posuere. Donec justo arcu, rutrum in massa quis, dictum condimentum risus. Nunc euismod et dolor at elementum. Duis pretium risus rhoncus mauris pulvinar, vel semper elit tempus. Quisque imperdiet, odio et hendrerit laoreet, justo dolor blandit sapien, ut mollis risus elit sit amet lacus. Vivamus id tortor eleifend, gravida tortor vitae, dignissim mauris. Integer efficitur ac neque id venenatis. Suspendisse pharetra neque sit amet ornare convallis. Sed eget eros dignissim risus eleifend elementum. Duis non bibendum ipsum.",
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam."
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"90 180 270 rotated.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam.",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse at ullamcorper libero. Cras sit amet dui laoreet tellus tristique commodo. Nam pretium id ligula ac malesuada. Mauris at lacinia magna. Curabitur ex lectus, lobortis lobortis turpis ac, congue aliquet quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consectetur sem et ex sagittis pretium. Praesent urna velit, mollis vitae ex vel, hendrerit finibus mauris. In at luctus orci. Nunc odio justo, rhoncus et euismod nec, bibendum vitae lacus. Aenean maximus sapien lacus, ut pellentesque tellus egestas eget. Nulla semper massa ut vehicula faucibus. Nam rhoncus, dolor consectetur pulvinar gravida, nisi sem luctus nibh, non venenatis nunc lorem et velit.",
|
||||
"Cras gravida vel risus sit amet sagittis. Vestibulum et purus pretium, accumsan turpis ac, consectetur augue. Nam viverra purus in urna mollis eleifend. Donec non imperdiet justo. In commodo tortor in diam feugiat, eget placerat augue posuere. Donec justo arcu, rutrum in massa quis, dictum condimentum risus. Nunc euismod et dolor at elementum. Duis pretium risus rhoncus mauris pulvinar, vel semper elit tempus. Quisque imperdiet, odio et hendrerit laoreet, justo dolor blandit sapien, ut mollis risus elit sit amet lacus. Vivamus id tortor eleifend, gravida tortor vitae, dignissim mauris. Integer efficitur ac neque id venenatis. Suspendisse pharetra neque sit amet ornare convallis. Sed eget eros dignissim risus eleifend elementum. Duis non bibendum ipsum.",
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Random 2 Columns Lists Hyph - Justified.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Random Big Title",
|
||||
"Lorem Ipsum text with lists Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales gravida felis, in rhoncus velit rutrum at. Curabitur hendrerit dapibus nulla, ut hendrerit diam imperdiet quis. Pellentesque id neque ali- quam, pulvinar neque in, vulputate elit. Pel- lentesque ut erat sit amet massa suscipit ullamcor- per. Sed porttitor viverra convallis. Duis vitae sem- per metus. Pellentesque eros purus, egestas eget velit eget, elementum aliquet velit. Suspendisse potenti. Nulla vitae massa rutrum, blandit erat vi- tae, aliquet arcu.",
|
||||
"Aenean feugiat leo sed enim sodales vehicula. Sus- pendisse tempus hendrerit magna sagittis dictum. Duis ultrices dapibus egestas. Cras eu felis eu lectus suscipit pharetra at at lacus. Nulla facilisi. Proin in- terdum faucibus elit nec rhoncus. Proin sodaless metus sed tincidunt hendrerit.",
|
||||
"Donec ultricies cursus odio sed rutrum. Nam ven- enatis metus vitae elementum scelerisque. Ali- quam tempor sapien at turpis posuere eleifend. Sed placerat posuere nunc vel efficitur. Quisque auctor felis vel lectus dictum fringilla. Quisque vo- lutpat pulvinar© elit. Aliquam ultrices feugiat ali- quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sus- pendisse imperdiet ex lorem, porta bibendum pu- rus ultricies id.",
|
||||
"Integer vel lacus sapien. Nam sodales ante eu risus facilisis placerat. Aliquam suscipit pulvinar ultricies. Aenean pulvinar, ex ac fermentum egestas, erat nisi feugiat velit, vitae suscipit tellus odio vitae quam. Morbi elementum sem in elit posuere, non",
|
||||
"•","•","•","•",
|
||||
"Duis leo enim, convallis sit amet orci eget, condimentum mattis mi ; Etiam dolor erat, maximus nec mi sed, con- vallis convallis orci ; Morbi viverra diam in diam cursus, vitae aliquet velit tempus ; Donec at nisi fermentum, ultricies odio eget, egestas massa at nisi fermentum, ul- tricies odio eget, egestas massa.",
|
||||
"rhoncus magna fringilla. Phasellus cursus in dolor laoreet rutrum. Curabitur tincidunt risus ullamcor- per, vehicula velit at, pulvinar metus.",
|
||||
"Donec quis ante leo. Vivamus pharetra, nisl ac vehi- cula tempor, tellus lacus aliquam sapien, eu congue nibh quam sit amet odio. Quisque metus arcu, sem- per nec consequat eu, pellentesque vel sem. Sed purus risus, tincidunt¹ sit amet dictum vitae, euis- mod id nibh. Praesent ultrices libero quis enim porta, sit amet pellentesque augue pretium. Viva- mus nec molestie nunc. Donec finibus enim nec tel- lus laoreet elementum. Curabitur efficitur placerat dolor et semper.",
|
||||
"Morbi laoreet dui eu tortor luctus, nec ultrices do- lor ullamcorper. Ut gravida sed nisl a efficitur. In tincidunt orci a condimentum semper. Suspendisse scelerisque fermentum lacinia. Vestibulum sit amet ornare tellus, aliquet euismod mauris. Cras suscipit venenatis ultrices. Sed diam erat, aliquet a tellus ut, viverra 12º ongue magna. Cras id justo tortor. Mauris in tortor vulputate, pellentesque nisl ac, facilisis ligula. Class aptent taciti² sociosqu ad li- tora torquent per conubia nostra³, per inceptos himenaeos. Aliquam eget dolor turpis. Mauris id molestie tellus. Sed elementum molestie nisi, at ali- quet sem vehicula nec. Morbi tempus nulla enim, a vulputate magna €51 luctus £66 eu. Fusce sodales, libero quis suscipit ultrices, metus erat auctor urna, sit amet dictum arcu tortor eu metus.",
|
||||
"Morbi vestibulum varius ipsum nec molestie. Proin auctor efficitur diam ut luctus. Phasellus cursus maximus ultricies. Mauris eu neque ut sem semper tempus. Curabitur non lorem eu nunc lobortis vi- verra at in diam. Pellentesque euismod purus a leo lobortis tempor. Maecenas mollis ligula at sem sus- cipit fringilla. Mauris sollicitudin tincidunt lectus id tempor. Etiam ut nisi est.",
|
||||
"1. Ut volutpat, velit at interdum consectetur, nisl lorem consequat mauris, feugiat dignissim tellus massa ut nisl. 2. Praesent at est nisi. Pellentesque rutrum lorem sed dui accumsan gravida. 3. Pellentesque dictum nisl vitae urna luctus, congue pulvinar mi congue.",
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical distance.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Document’s second line left aligned.",
|
||||
"Document’s first line right aligned."
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no horizontal distance.pdf",
|
||||
new string[]
|
||||
{
|
||||
"First. Second."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[SkippableTheory]
|
||||
[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)))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters);
|
||||
var blocks = new DocstrumBoundingBoxes(options).GetBlocks(words);
|
||||
|
||||
Assert.Equal(expected.Length, blocks.Count);
|
||||
var orderedBlocks = blocks.OrderBy(b => b.BoundingBox.BottomLeft.X)
|
||||
.ThenByDescending(b => b.BoundingBox.BottomLeft.Y).ToList();
|
||||
|
||||
for (int i = 0; i < orderedBlocks.Count; i++)
|
||||
{
|
||||
Assert.Equal(expected[i], orderedBlocks[i].Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[SkippableTheory]
|
||||
[MemberData(nameof(DataExtract))]
|
||||
public void GetBlocksStatic(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)))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters).ToList();
|
||||
|
||||
// Docstrum using static methods
|
||||
// Filter out white spaces
|
||||
words = words.Where(w => !string.IsNullOrWhiteSpace(w.Text)).ToList();
|
||||
|
||||
var wlBounds = options.WithinLineBounds;
|
||||
var wlBinSize = options.WithinLineBinSize;
|
||||
var wlMultiplier = options.WithinLineMultiplier;
|
||||
var blBounds = options.BetweenLineBounds;
|
||||
var blBinSize = options.BetweenLineBinSize;
|
||||
var blMultiplier = options.BetweenLineMultiplier;
|
||||
var maxDegreeOfParallelism = options.MaxDegreeOfParallelism;
|
||||
var angularDifferenceBounds = options.AngularDifferenceBounds;
|
||||
var wordSeparator = options.WordSeparator;
|
||||
var lineSeparator = options.LineSeparator;
|
||||
var epsilon = options.Epsilon;
|
||||
|
||||
// 1. Estimate within line and between line spacing
|
||||
if (!DocstrumBoundingBoxes.GetSpacingEstimation(words, wlBounds, wlBinSize, blBounds, blBinSize,
|
||||
maxDegreeOfParallelism,
|
||||
out double withinLineDistance, out double betweenLineDistance))
|
||||
{
|
||||
if (double.IsNaN(withinLineDistance))
|
||||
{
|
||||
withinLineDistance = 0;
|
||||
}
|
||||
|
||||
if (double.IsNaN(betweenLineDistance))
|
||||
{
|
||||
betweenLineDistance = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Determination of Text Lines
|
||||
double maxWithinLineDistance = wlMultiplier * withinLineDistance;
|
||||
var lines = DocstrumBoundingBoxes.GetLines(words, maxWithinLineDistance, wlBounds, wordSeparator, maxDegreeOfParallelism).ToArray();
|
||||
|
||||
// 3. Structural Block Determination
|
||||
double maxBetweenLineDistance = blMultiplier * betweenLineDistance;
|
||||
var blocks = DocstrumBoundingBoxes.GetStructuralBlocks(lines, maxBetweenLineDistance, angularDifferenceBounds, epsilon, lineSeparator, maxDegreeOfParallelism).ToList();
|
||||
|
||||
Assert.Equal(expected.Length, blocks.Count);
|
||||
var orderedBlocks = blocks.OrderBy(b => b.BoundingBox.BottomLeft.X)
|
||||
.ThenByDescending(b => b.BoundingBox.BottomLeft.Y).ToList();
|
||||
|
||||
for (int i = 0; i < orderedBlocks.Count; i++)
|
||||
{
|
||||
Assert.Equal(expected[i], orderedBlocks[i].Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace UglyToad.PdfPig.Tests.Dla
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.WordExtractor;
|
||||
using Xunit;
|
||||
|
||||
public class DocstrumBoundingBoxesTests
|
||||
{
|
||||
public static IEnumerable<object[]> DataExtract => new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
"complex rotated.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse at ullamcorper libero. Cras sit amet dui laoreet tellus tristique commodo. Nam pretium id ligula ac malesuada. Mauris at lacinia magna. Curabitur ex lectus, lobortis lobortis turpis ac, congue aliquet quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consectetur sem et ex sagittis pretium. Praesent urna velit, mollis vitae ex vel, hendrerit finibus mauris. In at luctus orci. Nunc odio justo, rhoncus et euismod nec, bibendum vitae lacus. Aenean maximus sapien lacus, ut pellentesque tellus egestas eget. Nulla semper massa ut vehicula faucibus. Nam rhoncus, dolor consectetur pulvinar gravida, nisi sem luctus nibh, non venenatis nunc lorem et velit.",
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam.",
|
||||
"Mauris tincidunt massa id lorem consectetur, in vestibulum nibh feugiat. Quisque eget commodo tortor. Duis iaculis, urna eget porttitor consectetur, metus lacus tempus urna, vehicula facilisis quam est scelerisque dui. Vestibulum imperdiet, tellus vel vulputate pretium, dolor mauris aliquet erat, sit amet fringilla nisi dui ut felis.",
|
||||
"Cras gravida vel risus sit amet sagittis. Vestibulum et purus pretium, accumsan turpis ac, consectetur augue. Nam viverra purus in urna mollis eleifend. Donec non imperdiet justo. In commodo tortor in diam feugiat, eget placerat augue posuere. Donec justo arcu, rutrum in massa quis, dictum condimentum risus. Nunc euismod et dolor at elementum. Duis pretium risus rhoncus mauris pulvinar, vel semper elit tempus. Quisque imperdiet, odio et hendrerit laoreet, justo dolor blandit sapien, ut mollis risus elit sit amet lacus. Vivamus id tortor eleifend, gravida tortor vitae, dignissim mauris. Integer efficitur ac neque id venenatis. Suspendisse pharetra neque sit amet ornare convallis. Sed eget eros dignissim risus eleifend elementum. Duis non bibendum ipsum.",
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam."
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"90 180 270 rotated.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Morbi euismod mattis libero, nec porta neque aliquam et. Nunc sed felis id libero tincidunt malesuada et laoreet orci. Phasellus massa libero, cursus imperdiet rhoncus quis, consequat eu eros. Nullam imperdiet felis sed ligula faucibus bibendum. Vestibulum rhoncus metus eu congue cursus. Maecenas vulputate dignissim dolor a iaculis. Praesent vel diam congue, dapibus lorem nec, viverra dolor. Vestibulum quis odio a risus semper aliquam.",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse at ullamcorper libero. Cras sit amet dui laoreet tellus tristique commodo. Nam pretium id ligula ac malesuada. Mauris at lacinia magna. Curabitur ex lectus, lobortis lobortis turpis ac, congue aliquet quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Etiam consectetur sem et ex sagittis pretium. Praesent urna velit, mollis vitae ex vel, hendrerit finibus mauris. In at luctus orci. Nunc odio justo, rhoncus et euismod nec, bibendum vitae lacus. Aenean maximus sapien lacus, ut pellentesque tellus egestas eget. Nulla semper massa ut vehicula faucibus. Nam rhoncus, dolor consectetur pulvinar gravida, nisi sem luctus nibh, non venenatis nunc lorem et velit.",
|
||||
"Cras gravida vel risus sit amet sagittis. Vestibulum et purus pretium, accumsan turpis ac, consectetur augue. Nam viverra purus in urna mollis eleifend. Donec non imperdiet justo. In commodo tortor in diam feugiat, eget placerat augue posuere. Donec justo arcu, rutrum in massa quis, dictum condimentum risus. Nunc euismod et dolor at elementum. Duis pretium risus rhoncus mauris pulvinar, vel semper elit tempus. Quisque imperdiet, odio et hendrerit laoreet, justo dolor blandit sapien, ut mollis risus elit sit amet lacus. Vivamus id tortor eleifend, gravida tortor vitae, dignissim mauris. Integer efficitur ac neque id venenatis. Suspendisse pharetra neque sit amet ornare convallis. Sed eget eros dignissim risus eleifend elementum. Duis non bibendum ipsum.",
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Random 2 Columns Lists Hyph - Justified.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Random Big Title",
|
||||
"Lorem Ipsum text with lists Lorem ipsum dolor sit amet, consectetur adipiscing elit. In sodales gravida felis, in rhoncus velit rutrum at. Curabitur hendrerit dapibus nulla, ut hendrerit diam imperdiet quis. Pellentesque id neque ali- quam, pulvinar neque in, vulputate elit. Pel- lentesque ut erat sit amet massa suscipit ullamcor- per. Sed porttitor viverra convallis. Duis vitae sem- per metus. Pellentesque eros purus, egestas eget velit eget, elementum aliquet velit. Suspendisse potenti. Nulla vitae massa rutrum, blandit erat vi- tae, aliquet arcu.",
|
||||
"Aenean feugiat leo sed enim sodales vehicula. Sus- pendisse tempus hendrerit magna sagittis dictum. Duis ultrices dapibus egestas. Cras eu felis eu lectus suscipit pharetra at at lacus. Nulla facilisi. Proin in- terdum faucibus elit nec rhoncus. Proin sodaless metus sed tincidunt hendrerit.",
|
||||
"Donec ultricies cursus odio sed rutrum. Nam ven- enatis metus vitae elementum scelerisque. Ali- quam tempor sapien at turpis posuere eleifend. Sed placerat posuere nunc vel efficitur. Quisque auctor felis vel lectus dictum fringilla. Quisque vo- lutpat pulvinar© elit. Aliquam ultrices feugiat ali- quam. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Sus- pendisse imperdiet ex lorem, porta bibendum pu- rus ultricies id.",
|
||||
"Integer vel lacus sapien. Nam sodales ante eu risus facilisis placerat. Aliquam suscipit pulvinar ultricies. Aenean pulvinar, ex ac fermentum egestas, erat nisi feugiat velit, vitae suscipit tellus odio vitae quam. Morbi elementum sem in elit posuere, non",
|
||||
"•","•","•","•",
|
||||
"Duis leo enim, convallis sit amet orci eget, condimentum mattis mi ; Etiam dolor erat, maximus nec mi sed, con- vallis convallis orci ; Morbi viverra diam in diam cursus, vitae aliquet velit tempus ; Donec at nisi fermentum, ultricies odio eget, egestas massa at nisi fermentum, ul- tricies odio eget, egestas massa.",
|
||||
"rhoncus magna fringilla. Phasellus cursus in dolor laoreet rutrum. Curabitur tincidunt risus ullamcor- per, vehicula velit at, pulvinar metus.",
|
||||
"Donec quis ante leo. Vivamus pharetra, nisl ac vehi- cula tempor, tellus lacus aliquam sapien, eu congue nibh quam sit amet odio. Quisque metus arcu, sem- per nec consequat eu, pellentesque vel sem. Sed purus risus, tincidunt¹ sit amet dictum vitae, euis- mod id nibh. Praesent ultrices libero quis enim porta, sit amet pellentesque augue pretium. Viva- mus nec molestie nunc. Donec finibus enim nec tel- lus laoreet elementum. Curabitur efficitur placerat dolor et semper.",
|
||||
"Morbi laoreet dui eu tortor luctus, nec ultrices do- lor ullamcorper. Ut gravida sed nisl a efficitur. In tincidunt orci a condimentum semper. Suspendisse scelerisque fermentum lacinia. Vestibulum sit amet ornare tellus, aliquet euismod mauris. Cras suscipit venenatis ultrices. Sed diam erat, aliquet a tellus ut, viverra 12º ongue magna. Cras id justo tortor. Mauris in tortor vulputate, pellentesque nisl ac, facilisis ligula. Class aptent taciti² sociosqu ad li- tora torquent per conubia nostra³, per inceptos himenaeos. Aliquam eget dolor turpis. Mauris id molestie tellus. Sed elementum molestie nisi, at ali- quet sem vehicula nec. Morbi tempus nulla enim, a vulputate magna €51 luctus £66 eu. Fusce sodales, libero quis suscipit ultrices, metus erat auctor urna, sit amet dictum arcu tortor eu metus.",
|
||||
"Morbi vestibulum varius ipsum nec molestie. Proin auctor efficitur diam ut luctus. Phasellus cursus maximus ultricies. Mauris eu neque ut sem semper tempus. Curabitur non lorem eu nunc lobortis vi- verra at in diam. Pellentesque euismod purus a leo lobortis tempor. Maecenas mollis ligula at sem sus- cipit fringilla. Mauris sollicitudin tincidunt lectus id tempor. Etiam ut nisi est.",
|
||||
"1. Ut volutpat, velit at interdum consectetur, nisl lorem consequat mauris, feugiat dignissim tellus massa ut nisl. 2. Praesent at est nisi. Pellentesque rutrum lorem sed dui accumsan gravida. 3. Pellentesque dictum nisl vitae urna luctus, congue pulvinar mi congue.",
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical distance.pdf",
|
||||
new string[]
|
||||
{
|
||||
"Document’s second line left aligned.",
|
||||
"Document’s first line right aligned."
|
||||
}
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no horizontal distance.pdf",
|
||||
new string[]
|
||||
{
|
||||
"First. Second."
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DataExtract))]
|
||||
public void GetBlocks(string name, string[] expected)
|
||||
{
|
||||
var options = new DocstrumBoundingBoxes.DocstrumBoundingBoxesOptions() { LineSeparator = " " };
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters);
|
||||
var blocks = DocstrumBoundingBoxes.Instance.GetBlocks(words, options);
|
||||
|
||||
Assert.Equal(expected.Length, blocks.Count);
|
||||
var orderedBlocks = blocks.OrderBy(b => b.BoundingBox.BottomLeft.X)
|
||||
.ThenByDescending(b => b.BoundingBox.BottomLeft.Y).ToList();
|
||||
|
||||
for (int i = 0; i < orderedBlocks.Count; i++)
|
||||
{
|
||||
Assert.Equal(expected[i], orderedBlocks[i].Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters);
|
||||
var options = new RecursiveXYCut.RecursiveXYCutOptions() { MinimumWidth = page.Width / 3.0, LineSeparator = " " };
|
||||
var blocks = new RecursiveXYCut(options).GetBlocks(words);
|
||||
var blocks = RecursiveXYCut.Instance.GetBlocks(words, options);
|
||||
|
||||
Assert.Equal(expected.Length, blocks.Count);
|
||||
var orderedBlocks = blocks.OrderBy(b => b.BoundingBox.BottomLeft.X)
|
||||
|
||||
@@ -23,9 +23,7 @@ namespace UglyToad.PdfPig.Tests.Encryption
|
||||
|
||||
var output = AesEncryptionHelper.Decrypt(data, key);
|
||||
|
||||
var actual = OtherEncodings.BytesAsLatin1String(output);
|
||||
|
||||
Assert.Equal("D:20180808103317-07'00'", actual);
|
||||
Assert.Equal("D:20180808103317-07'00'", OtherEncodings.BytesAsLatin1String(output));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
var builder = new CharacterMapBuilder();
|
||||
|
||||
parser.Parse(new NumericToken(1), input.scanner, builder);
|
||||
parser.Parse(new NumericToken(1), input, builder);
|
||||
|
||||
Assert.Equal(2, builder.BaseFontCharacterMap.Count);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
var builder = new CharacterMapBuilder();
|
||||
|
||||
parser.Parse(new NumericToken(1), input.scanner, builder);
|
||||
parser.Parse(new NumericToken(1), input, builder);
|
||||
|
||||
Assert.Equal(7, builder.BaseFontCharacterMap.Count);
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
var builder = new CharacterMapBuilder();
|
||||
|
||||
parser.Parse(new NumericToken(2), input.scanner, builder);
|
||||
parser.Parse(new NumericToken(2), input, builder);
|
||||
|
||||
Assert.Equal(6, builder.BaseFontCharacterMap.Count);
|
||||
|
||||
|
||||
@@ -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,12 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Fonts.SystemFonts;
|
||||
using UglyToad.PdfPig.Tests.Dla;
|
||||
using Xunit;
|
||||
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
{
|
||||
using PdfPig.Core;
|
||||
|
||||
public class Linux
|
||||
{
|
||||
public static IEnumerable<object[]> DataExtract => new[]
|
||||
@@ -14,76 +11,33 @@ namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
new object[]
|
||||
{
|
||||
"90 180 270 rotated.pdf",
|
||||
new ExpectedLetterData[]
|
||||
{
|
||||
new ExpectedLetterData
|
||||
{
|
||||
TopLeft = new PdfPoint(53.88, 759.48),
|
||||
Width = 2.495859375,
|
||||
Height = 0,
|
||||
Rotation = 0
|
||||
},
|
||||
new ExpectedLetterData
|
||||
{
|
||||
TopLeft = new PdfPoint(514.925312502883, 744.099765720344),
|
||||
Width = 6.83203125,
|
||||
Height = 7.94531249999983,
|
||||
Rotation = -90
|
||||
},
|
||||
new ExpectedLetterData
|
||||
{
|
||||
TopLeft = new PdfPoint(512.505390717836, 736.603703191305),
|
||||
Width = 5.1796875,
|
||||
Height = 5.68945312499983,
|
||||
Rotation = -90
|
||||
},
|
||||
new ExpectedLetterData
|
||||
{
|
||||
TopLeft = new PdfPoint(512.505390785898, 730.931828191305),
|
||||
Width = 3.99609375,
|
||||
Height = 5.52539062499994,
|
||||
Rotation = -90
|
||||
},
|
||||
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]
|
||||
[Theory]
|
||||
[MemberData(nameof(DataExtract))]
|
||||
public void GetCorrectBBoxLinux(string name, ExpectedLetterData[] 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.");
|
||||
|
||||
public void GetCorrectBBoxLinux(string name, object[][] expected)
|
||||
{
|
||||
// success on Windows but LinuxSystemFontLister cannot find the 'TimesNewRomanPSMT' font
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(name)))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
for (int i = 0; i < expected.Length; i++)
|
||||
{
|
||||
var expectedData = expected[i];
|
||||
|
||||
var current = page.Letters[i];
|
||||
|
||||
Assert.Equal(expectedData.TopLeft.X, current.GlyphRectangle.TopLeft.X, 7);
|
||||
Assert.Equal(expectedData.TopLeft.Y, current.GlyphRectangle.TopLeft.Y, 7);
|
||||
Assert.Equal(expectedData.Width, current.GlyphRectangle.Width, 7);
|
||||
Assert.Equal(expectedData.Height, current.GlyphRectangle.Height, 7);
|
||||
Assert.Equal(expectedData.Rotation, current.GlyphRectangle.Rotation, 3);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExpectedLetterData
|
||||
{
|
||||
public PdfPoint TopLeft { get; set; }
|
||||
|
||||
public double Width { get; set; }
|
||||
|
||||
public double Height { get; set; }
|
||||
|
||||
public double Rotation { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -528,52 +528,6 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static IEnumerable<object[]> Issue458Data => new[]
|
||||
{
|
||||
new object[]
|
||||
{
|
||||
new PdfPoint[]
|
||||
{
|
||||
new PdfPoint(134.74199999999985, 1611.657),
|
||||
new PdfPoint(277.58043749999985, 1611.657),
|
||||
new PdfPoint(314.74199999999985, 1611.657),
|
||||
new PdfPoint(507.0248828124999, 1611.657),
|
||||
new PdfPoint(545.1419999999998, 1611.657),
|
||||
new PdfPoint(632.9658281249997, 1611.657),
|
||||
new PdfPoint(668.5709999999998, 1611.657),
|
||||
new PdfPoint(831.3395078125002, 1611.657),
|
||||
new PdfPoint(868.1139999999998, 1611.657),
|
||||
new PdfPoint(892.8907187499999, 1611.657),
|
||||
new PdfPoint(1010.0569999999999, 1611.6569999999997),
|
||||
new PdfPoint(1046.2174003906248, 1611.7654812011715),
|
||||
new PdfPoint(1010.0569999999999, 1611.657),
|
||||
new PdfPoint(1046.2174003906248, 1611.7654812011717),
|
||||
new PdfPoint(1085.145, 1611.8822639999996),
|
||||
new PdfPoint(1255.484941406251, 1612.3932838242179),
|
||||
new PdfPoint(1301.144, 1612.5302609999994),
|
||||
new PdfPoint(1359.0006406250002, 1612.7038309218747),
|
||||
new PdfPoint(1301.144, 1612.5302609999997),
|
||||
new PdfPoint(1359.0006406250002, 1612.703830921875),
|
||||
new PdfPoint(1400.915, 1612.8295739999996),
|
||||
new PdfPoint(1505.379062499999, 1613.1429661874993),
|
||||
new PdfPoint(1400.915, 1612.8295739999999),
|
||||
new PdfPoint(1505.379062499999, 1613.1429661874995),
|
||||
new PdfPoint(1543.886, 1613.2584869999996),
|
||||
new PdfPoint(1600.9401015625003, 1613.4296493046872),
|
||||
new PdfPoint(1641.597, 1613.5516199999997),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265612),
|
||||
new PdfPoint(1641.597, 1613.55162),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265614)
|
||||
},
|
||||
new PdfPoint[]
|
||||
{
|
||||
new PdfPoint(134.74199999999985, 1611.657),
|
||||
new PdfPoint(1010.0569999999999, 1611.6569999999997),
|
||||
new PdfPoint(1764.5577421874998, 1613.9205022265614),
|
||||
}
|
||||
}
|
||||
};
|
||||
#endregion
|
||||
|
||||
[Fact]
|
||||
@@ -616,6 +570,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(MinimumAreaRectangleData))]
|
||||
public void MinimumAreaRectangle(PdfPoint[] points, PdfPoint[] expected)
|
||||
@@ -630,51 +585,5 @@
|
||||
Assert.Equal(expected[i], mar[i], PointComparer);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Issue458Data))]
|
||||
public void Issue458(PdfPoint[] points, PdfPoint[] expected)
|
||||
{
|
||||
/*
|
||||
* https://github.com/UglyToad/PdfPig/issues/458
|
||||
* An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Linq.dll: 'Specified argument was out of the range of valid values.'
|
||||
* at System.Linq.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument)
|
||||
* at System.Linq.Enumerable.ElementAt[TSource](IEnumerable`1 source, Int32 index)
|
||||
*/
|
||||
|
||||
var result = GeometryExtensions.GrahamScan(points);
|
||||
|
||||
// Data is noisy so we just check it does not throw an exception
|
||||
// and that key points are present (other points might be present,
|
||||
// e.g. 'not enough equal dupplicates' or 'not collinear enough' points)
|
||||
foreach (var point in expected)
|
||||
{
|
||||
Assert.Contains(point, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Issue458Data))]
|
||||
public void Issue458_Inv(PdfPoint[] points, PdfPoint[] expected)
|
||||
{
|
||||
/*
|
||||
* https://github.com/UglyToad/PdfPig/issues/458
|
||||
* An unhandled exception of type 'System.ArgumentOutOfRangeException' occurred in System.Linq.dll: 'Specified argument was out of the range of valid values.'
|
||||
* at System.Linq.ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument argument)
|
||||
* at System.Linq.Enumerable.ElementAt[TSource](IEnumerable`1 source, Int32 index)
|
||||
*/
|
||||
var pointsInv = points.Select(p => new PdfPoint(p.Y, p.X)).ToArray();
|
||||
var expectedInv = expected.Select(p => new PdfPoint(p.Y, p.X)).ToArray();
|
||||
|
||||
var result = GeometryExtensions.GrahamScan(pointsInv);
|
||||
|
||||
// Data is noisy so we just check it does not throw an exception
|
||||
// and that key points are present (other points might be present,
|
||||
// e.g. 'not enough equal dupplicates' or 'not collinear enough' points)
|
||||
foreach (var point in expectedInv)
|
||||
{
|
||||
Assert.Contains(point, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1682,17 +1682,5 @@
|
||||
Assert.Equal(expected[2], rectR.TopLeft, PointComparer);
|
||||
Assert.Equal(expected[3], rectR.BottomLeft, PointComparer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue261()
|
||||
{
|
||||
// Some points have negative coordinates after rotation, resulting in negative Height
|
||||
PdfRectangle rect = new PdfRectangle(new PdfPoint(401.51, 461.803424),
|
||||
new PdfPoint(300.17322363281249, 461.803424),
|
||||
new PdfPoint(401.51, 291.45),
|
||||
new PdfPoint(300.17322363281249, 291.45));
|
||||
|
||||
Assert.True(rect.Height > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@
|
||||
using PdfPig.Tokens;
|
||||
using PdfPig.Core;
|
||||
using Tokens;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Graphics.Operations.TextPositioning;
|
||||
|
||||
internal class TestOperationContext : IOperationContext
|
||||
{
|
||||
@@ -90,66 +88,6 @@
|
||||
|
||||
}
|
||||
|
||||
public void MoveTo(double x, double y)
|
||||
{
|
||||
BeginSubpath();
|
||||
var point = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
CurrentPosition = point;
|
||||
CurrentSubpath.MoveTo(point.X, point.Y);
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(CurrentPosition.X, CurrentPosition.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void BezierCurveTo(double x1, double y1, double x2, double y2, double x3, double y3)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var controlPoint1 = CurrentTransformationMatrix.Transform(new PdfPoint(x1, y1));
|
||||
var controlPoint2 = CurrentTransformationMatrix.Transform(new PdfPoint(x2, y2));
|
||||
var end = CurrentTransformationMatrix.Transform(new PdfPoint(x3, y3));
|
||||
|
||||
CurrentSubpath.BezierCurveTo(controlPoint1.X, controlPoint1.Y, controlPoint2.X, controlPoint2.Y, end.X, end.Y);
|
||||
CurrentPosition = end;
|
||||
}
|
||||
|
||||
public void LineTo(double x, double y)
|
||||
{
|
||||
if (CurrentSubpath == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var endPoint = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
|
||||
CurrentSubpath.LineTo(endPoint.X, endPoint.Y);
|
||||
CurrentPosition = endPoint;
|
||||
}
|
||||
|
||||
public void Rectangle(double x, double y, double width, double height)
|
||||
{
|
||||
BeginSubpath();
|
||||
var lowerLeft = CurrentTransformationMatrix.Transform(new PdfPoint(x, y));
|
||||
var upperRight = CurrentTransformationMatrix.Transform(new PdfPoint(x + width, y + height));
|
||||
|
||||
CurrentSubpath.Rectangle(lowerLeft.X, lowerLeft.Y, upperRight.X - lowerLeft.X, upperRight.Y - lowerLeft.Y);
|
||||
AddCurrentSubpath();
|
||||
}
|
||||
|
||||
public void EndPath()
|
||||
{
|
||||
}
|
||||
@@ -186,91 +124,6 @@
|
||||
{
|
||||
}
|
||||
|
||||
private void AdjustTextMatrix(double tx, double ty)
|
||||
{
|
||||
var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty);
|
||||
TextMatrices.TextMatrix = matrix.Multiply(TextMatrices.TextMatrix);
|
||||
}
|
||||
|
||||
public void SetFlatnessTolerance(decimal tolerance)
|
||||
{
|
||||
GetCurrentState().Flatness = tolerance;
|
||||
}
|
||||
|
||||
public void SetLineCap(LineCapStyle cap)
|
||||
{
|
||||
GetCurrentState().CapStyle = cap;
|
||||
}
|
||||
|
||||
public void SetLineDashPattern(LineDashPattern pattern)
|
||||
{
|
||||
GetCurrentState().LineDashPattern = pattern;
|
||||
}
|
||||
|
||||
public void SetLineJoin(LineJoinStyle join)
|
||||
{
|
||||
GetCurrentState().JoinStyle = join;
|
||||
}
|
||||
|
||||
public void SetLineWidth(decimal width)
|
||||
{
|
||||
GetCurrentState().LineWidth = width;
|
||||
}
|
||||
|
||||
public void SetMiterLimit(decimal limit)
|
||||
{
|
||||
GetCurrentState().MiterLimit = limit;
|
||||
}
|
||||
|
||||
public void MoveToNextLineWithOffset()
|
||||
{
|
||||
var tdOperation = new MoveToNextLineWithOffset(0, -1 * (decimal)GetCurrentState().FontState.Leading);
|
||||
tdOperation.Run(this);
|
||||
}
|
||||
|
||||
public void SetFontAndSize(NameToken font, double size)
|
||||
{
|
||||
var currentState = GetCurrentState();
|
||||
currentState.FontState.FontSize = size;
|
||||
currentState.FontState.FontName = font;
|
||||
}
|
||||
|
||||
public void SetHorizontalScaling(double scale)
|
||||
{
|
||||
GetCurrentState().FontState.HorizontalScaling = scale;
|
||||
}
|
||||
|
||||
public void SetTextLeading(double leading)
|
||||
{
|
||||
GetCurrentState().FontState.Leading = leading;
|
||||
}
|
||||
|
||||
public void SetTextRenderingMode(TextRenderingMode mode)
|
||||
{
|
||||
GetCurrentState().FontState.TextRenderingMode = mode;
|
||||
}
|
||||
|
||||
public void SetTextRise(double rise)
|
||||
{
|
||||
GetCurrentState().FontState.Rise = rise;
|
||||
}
|
||||
|
||||
public void SetWordSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.WordSpacing = spacing;
|
||||
}
|
||||
|
||||
public void ModifyCurrentTransformationMatrix(double[] value)
|
||||
{
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
}
|
||||
|
||||
public void SetCharacterSpacing(double spacing)
|
||||
{
|
||||
GetCurrentState().FontState.CharacterSpacing = spacing;
|
||||
}
|
||||
|
||||
private class TestFontFactory : IFontFactory
|
||||
{
|
||||
public IFont Get(DictionaryToken dictionary)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 77 B |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 522 KiB |
@@ -1 +0,0 @@
|
||||
h・m
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 69 B |
@@ -1,52 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Images
|
||||
{
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
|
||||
public static class ImageHelpers
|
||||
{
|
||||
private static readonly string FilesFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files"));
|
||||
|
||||
public static byte[] LoadFileBytes(string filename, bool isCompressed = false)
|
||||
{
|
||||
var filePath = Path.Combine(FilesFolder, filename);
|
||||
var memoryStream = new MemoryStream();
|
||||
if (isCompressed)
|
||||
{
|
||||
using (var deflateStream = new DeflateStream(File.OpenRead(filePath), CompressionMode.Decompress))
|
||||
{
|
||||
deflateStream.CopyTo(memoryStream);
|
||||
}
|
||||
|
||||
return memoryStream.ToArray();
|
||||
}
|
||||
|
||||
return File.ReadAllBytes(filePath);
|
||||
}
|
||||
|
||||
public static bool ImagesAreEqual(byte[] first, byte[] second)
|
||||
{
|
||||
var png1 = Png.Open(first);
|
||||
var png2 = Png.Open(second);
|
||||
if (png1.Width != png2.Width || png1.Height != png2.Height || png1.HasAlphaChannel != png2.HasAlphaChannel)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var y = 0; y < png1.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < png1.Width; x++)
|
||||
{
|
||||
if (!png1.GetPixel(x, y).Equals(png2.GetPixel(x, y)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,267 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Images
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using Xunit;
|
||||
|
||||
public class PngFromPdfImageFactoryTests
|
||||
{
|
||||
private static readonly byte[] RgbBlack = new byte[] { 0, 0, 0 };
|
||||
private static readonly byte[] RgbWhite = new byte[] { 255, 255, 255 };
|
||||
private static readonly byte[][] RgbPalette = new[] { RgbBlack, RgbWhite };
|
||||
|
||||
private static readonly byte[] CmykBlack = new byte[] { 0, 0, 0, 255 };
|
||||
private static readonly byte[] CmykWhite = new byte[] { 0, 0, 0, 0 };
|
||||
|
||||
private static readonly byte GrayscaleBlack = 0;
|
||||
private static readonly byte GrayscaleWhite = 255;
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceRgbImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
RgbWhite, RgbBlack, RgbWhite,
|
||||
RgbBlack, RgbWhite, RgbBlack,
|
||||
RgbWhite, RgbBlack, RgbWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceCMYKImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
CmykWhite, CmykBlack, CmykWhite,
|
||||
CmykBlack, CmykWhite, CmykBlack,
|
||||
CmykWhite, CmykBlack, CmykWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceCmykColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels.SelectMany(b => b).ToArray(),
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromDeviceGrayscaleImageData()
|
||||
{
|
||||
var pixels = new[]
|
||||
{
|
||||
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite,
|
||||
GrayscaleBlack, GrayscaleWhite, GrayscaleBlack,
|
||||
GrayscaleWhite, GrayscaleBlack, GrayscaleWhite
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceGrayColorSpaceDetails.Instance,
|
||||
DecodedBytes = pixels,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromIndexedImageData8bpc()
|
||||
{
|
||||
var indices = new byte[]
|
||||
{
|
||||
1, 0, 1,
|
||||
0, 1, 0,
|
||||
1, 0, 1
|
||||
};
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, RgbPalette.SelectMany(b => b).ToArray()),
|
||||
DecodedBytes = indices,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromIndexedImageData1bpc()
|
||||
{
|
||||
// Indices for a 3x3 RGB image, each index is represented by a single bit
|
||||
// 1, 0, 1,
|
||||
// 0, 1, 0,
|
||||
// 1, 0, 1
|
||||
//
|
||||
// A scanline must be at least one byte wide, so the remaining five bits are padding:
|
||||
// Byte 0: 10100000 (bits #7 and #5 are 1)
|
||||
// Byte 1: 01000000 (bit #6 is 1)
|
||||
// Byte 2: 10100000 (bits #7 and #5 are 1)
|
||||
// ||||||||
|
||||
// Bit # : 76543210
|
||||
|
||||
var lines = new byte[3];
|
||||
lines[0] |= (1 << 7); // Set bit #7 to 1
|
||||
lines[0] |= (1 << 5); // Set bit #5 to 1
|
||||
|
||||
lines[1] |= (1 << 6); // Set bit #6 to 1
|
||||
|
||||
lines[2] |= (1 << 7); // Set bit #7 to 1
|
||||
lines[2] |= (1 << 5); // Set bit #5 to 1
|
||||
|
||||
var colorTable = RgbPalette.SelectMany(b => b).ToArray();
|
||||
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new IndexedColorSpaceDetails(DeviceRgbColorSpaceDetails.Instance, 1, colorTable),
|
||||
DecodedBytes = lines,
|
||||
WidthInSamples = 3,
|
||||
HeightInSamples = 3,
|
||||
BitsPerComponent = 1
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCcittFaxDecodedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance, new[] { 1m, 0 }),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1800,
|
||||
HeightInSamples = 3113,
|
||||
BitsPerComponent = 1
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("ccittfax.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromICCBasedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("iccbased.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AlternateColorSpaceDetailsIsCurrentlyUsedInPdfPigWhenGeneratingPngsFromICCBasedImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("iccbased-decoded.bin");
|
||||
var iccBasedImage = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new ICCBasedColorSpaceDetails(
|
||||
numberOfColorComponents: 3,
|
||||
alternateColorSpaceDetails: DeviceRgbColorSpaceDetails.Instance,
|
||||
range: new List<decimal> { 0, 1, 0, 1, 0, 1 },
|
||||
metadata: null),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
var deviceRGBImage = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = DeviceRgbColorSpaceDetails.Instance,
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 1,
|
||||
HeightInSamples = 1,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(iccBasedImage, out var iccPngBytes));
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(deviceRGBImage, out var deviceRgbBytes));
|
||||
Assert.Equal(iccPngBytes, deviceRgbBytes);
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCalRGBImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("calrgb-decoded.bin");
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new CalRGBColorSpaceDetails(
|
||||
whitePoint: new List<decimal> { 0.95043m, 1, 1.09m },
|
||||
blackPoint: null,
|
||||
gamma: new List<decimal> { 2.2m, 2.2m, 2.2m },
|
||||
matrix: new List<decimal> {
|
||||
0.41239m, 0.21264m, 0.01933m,
|
||||
0.35758m, 0.71517m, 0.11919m,
|
||||
0.18045m, 0.07218m, 0.9504m }),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 153,
|
||||
HeightInSamples = 83,
|
||||
BitsPerComponent = 8
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calrgb.png"), bytes));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGeneratePngFromCalGrayImageData()
|
||||
{
|
||||
var decodedBytes = ImageHelpers.LoadFileBytes("calgray-decoded.bin", isCompressed: true);
|
||||
var image = new TestPdfImage
|
||||
{
|
||||
ColorSpaceDetails = new CalGrayColorSpaceDetails(
|
||||
whitePoint: new List<decimal> { 0.9505000114m, 1, 1.0889999866m },
|
||||
blackPoint: null,
|
||||
gamma: 2.2000000477m),
|
||||
DecodedBytes = decodedBytes,
|
||||
WidthInSamples = 2480,
|
||||
HeightInSamples = 1748,
|
||||
BitsPerComponent = 8,
|
||||
};
|
||||
|
||||
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));
|
||||
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("calgray.png"), bytes));
|
||||
}
|
||||
|
||||
private static byte[] LoadImage(string name)
|
||||
{
|
||||
return ImageHelpers.LoadFileBytes(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using PdfPig.Tokens;
|
||||
using System.Collections.Generic;
|
||||
using Xunit;
|
||||
|
||||
public class AdvancedPdfDocumentAccessTests
|
||||
{
|
||||
[Fact]
|
||||
public void ReplacesObjectsFunc()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, tk =>
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replaced = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
return replaced;
|
||||
});
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReplacesObjects()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path))
|
||||
{
|
||||
var dict = new Dictionary<NameToken, IToken>();
|
||||
dict[NameToken.Length] = new NumericToken(0);
|
||||
var replacement = new StreamToken(new DictionaryToken(dict), new List<byte>());
|
||||
|
||||
var pg = document.Structure.Catalog.GetPageNode(1).NodeDictionary;
|
||||
var contents = pg.Data[NameToken.Contents] as IndirectReferenceToken;
|
||||
document.Advanced.ReplaceIndirectObject(contents.Data, replacement);
|
||||
|
||||
var page = document.GetPage(1);
|
||||
Assert.Empty(page.Letters);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,61 +0,0 @@
|
||||
%PDF-1.1
|
||||
1 0 obj
|
||||
<</Type/Catalog/Pages 2 0 R>>
|
||||
endobj
|
||||
2 0 obj
|
||||
<</Type/Pages/Count 1/Kids[3 0 R]/MediaBox [0 0 200 100]>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Type/Page
|
||||
/Parent 2 0 R
|
||||
/Resources <<
|
||||
/XObject << /SomeImage 4 0 R >>
|
||||
>>
|
||||
/Contents 5 0 R
|
||||
>>
|
||||
endobj
|
||||
4 0 obj
|
||||
<<
|
||||
/Type/XObject
|
||||
/Subtype/Image
|
||||
/Width 200 % The width or height directly affects the image's file size.
|
||||
/Height 100
|
||||
/ColorSpace/DeviceRGB
|
||||
/DecodeParms [] % Forces NativeImageDecoder.isSupported to return false.
|
||||
/BitsPerComponent 8
|
||||
/Length 580
|
||||
/Filter [ /ASCIIHexDecode /DCTDecode ]
|
||||
>>
|
||||
% convert -size 1x1 xc:red jpeg:- | xxd -p -c40
|
||||
stream
|
||||
ffd8ffe000104a46494600010100000100010000ffdb004300030202020202030202020303030304
|
||||
060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c
|
||||
12131210130f101010ffdb00430103030304030408040408100b090b101010101010101010101010
|
||||
1010101010101010101010101010101010101010101010101010101010101010101010101010ffc0
|
||||
0011080001000103011100021101031101ffc40014000100000000000000000000000000000008ff
|
||||
c40014100100000000000000000000000000000000ffc40015010101000000000000000000000000
|
||||
00000709ffc40014110100000000000000000000000000000000ffda000c03010002110311003f00
|
||||
3a03154dffd9
|
||||
endstream
|
||||
endobj
|
||||
5 0 obj
|
||||
<</Length 14>>
|
||||
stream
|
||||
500 0 0 400 0 0 cm
|
||||
/SomeImage Do
|
||||
endstream
|
||||
endobj
|
||||
xref
|
||||
0 6
|
||||
0000000000 65535 f
|
||||
0000000008 00000 n
|
||||
0000000054 00000 n
|
||||
0000000128 00000 n
|
||||
0000000246 00000 n
|
||||
0000001201 00000 n
|
||||
trailer
|
||||
<</Root 1 0 R/Size 6>>
|
||||
startxref
|
||||
1281
|
||||
%%EOF
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -42,5 +42,6 @@
|
||||
Assert.Contains("financial results for the fiscal quarter ended June 30, 2017 and (2) a conference call to discuss those results and Farmer Mac", page.Text);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration;
|
||||
|
||||
using Xunit;
|
||||
|
||||
public class Math119FakingDataTests
|
||||
{
|
||||
[Fact]
|
||||
public void CombinesDiaeresisForWords()
|
||||
{
|
||||
using var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Math119FakingData.pdf"));
|
||||
|
||||
var lastPage = document.GetPage(8);
|
||||
|
||||
var words = lastPage.GetWords();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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,94 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using DocumentLayoutAnalysis.Export;
|
||||
using DocumentLayoutAnalysis.PageSegmenter;
|
||||
using DocumentLayoutAnalysis.ReadingOrderDetector;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Util;
|
||||
using Xunit;
|
||||
|
||||
public class PageXmlTextExporterTests
|
||||
{
|
||||
private static string GetFilename()
|
||||
{
|
||||
return IntegrationHelpers.GetDocumentPath("2006_Swedish_Touring_Car_Championship.pdf");
|
||||
}
|
||||
|
||||
private static string GetXml(PageXmlTextExporter pageXmlTextExporter = null)
|
||||
{
|
||||
pageXmlTextExporter = pageXmlTextExporter ?? new PageXmlTextExporter(
|
||||
DefaultWordExtractor.Instance,
|
||||
RecursiveXYCut.Instance,
|
||||
UnsupervisedReadingOrderDetector.Instance);
|
||||
|
||||
string xml;
|
||||
using (var document = PdfDocument.Open(GetFilename()))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
xml = pageXmlTextExporter.Get(page);
|
||||
}
|
||||
|
||||
return xml;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhenReadingOrder_ContainsReadingOrderXmlElements()
|
||||
{
|
||||
var pageXmlTextExporter = new PageXmlTextExporter(
|
||||
DefaultWordExtractor.Instance,
|
||||
RecursiveXYCut.Instance,
|
||||
UnsupervisedReadingOrderDetector.Instance);
|
||||
var xml = GetXml(pageXmlTextExporter);
|
||||
|
||||
Assert.Contains("<ReadingOrder>", xml);
|
||||
Assert.Contains("</OrderedGroup>", xml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PageHeightAndWidthArePresent()
|
||||
{
|
||||
var xml = GetXml();
|
||||
Assert.Contains(@"<Page imageFilename=""unknown"" imageWidth=""595"" imageHeight=""842"">", xml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsExpectedNumberOfTextRegions()
|
||||
{
|
||||
var xml = GetXml();
|
||||
var count = Regex.Matches(xml, "</TextRegion>").Count;
|
||||
|
||||
Assert.Equal(22, count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContainsExpectedText()
|
||||
{
|
||||
var xml = GetXml();
|
||||
Assert.Contains("2006 Swedish Touring Car Championship", xml);
|
||||
// the coords for that text
|
||||
Assert.Contains(@"<Coords points=""35,77 35,62 397,62 397,77"" />", xml);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoPointsAreOnThePageBoundary()
|
||||
{
|
||||
var pageWidth = 100;
|
||||
var pageHeight = 200;
|
||||
|
||||
var topLeftPagePoint = new PdfPoint(0,0);
|
||||
var bottomLeftPagePoint = new PdfPoint(0, pageHeight);
|
||||
var bottomRightPagePoint = new PdfPoint(pageWidth, pageHeight);
|
||||
var normalPoint = new PdfPoint(60, 60);
|
||||
|
||||
Assert.Equal("1,199", PageXmlTextExporter.PointToString(topLeftPagePoint, pageWidth, pageHeight));
|
||||
Assert.Equal("1,1", PageXmlTextExporter.PointToString(bottomLeftPagePoint, pageWidth, pageHeight));
|
||||
Assert.Equal("99,1", PageXmlTextExporter.PointToString(bottomRightPagePoint, pageWidth, pageHeight));
|
||||
Assert.Equal($"60,140", PageXmlTextExporter.PointToString(normalPoint, pageWidth, pageHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using Content;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using Images.Png;
|
||||
using Xunit;
|
||||
|
||||
public class SwedishTouringCarChampionshipTests
|
||||
|
||||
@@ -4,9 +4,6 @@
|
||||
using Logging;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Parser.FileStructure;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using PdfPig.Tokens;
|
||||
using System.Linq;
|
||||
using Xunit;
|
||||
|
||||
public class FileHeaderParserTests
|
||||
@@ -15,7 +12,7 @@
|
||||
[Fact]
|
||||
public void NullScannerThrows()
|
||||
{
|
||||
Action action = () => FileHeaderParser.Parse(null, null, false, log);
|
||||
Action action = () => FileHeaderParser.Parse(null, false, log);
|
||||
|
||||
Assert.Throws<ArgumentNullException>(action);
|
||||
}
|
||||
@@ -33,7 +30,7 @@
|
||||
|
||||
var scanner = StringBytesTestConverter.Scanner(input);
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
var result = FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Equal(format, result.VersionString);
|
||||
Assert.Equal(0, result.OffsetInFile);
|
||||
@@ -48,10 +45,10 @@
|
||||
|
||||
var scanner = StringBytesTestConverter.Scanner(input);
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
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]
|
||||
@@ -59,7 +56,7 @@
|
||||
{
|
||||
var scanner = StringBytesTestConverter.Scanner(string.Empty);
|
||||
|
||||
Action action = () => FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
Action action = () => FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Throws<PdfDocumentFormatException>(action);
|
||||
}
|
||||
@@ -70,10 +67,10 @@
|
||||
var scanner = StringBytesTestConverter.Scanner(@"one
|
||||
%PDF-1.2");
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
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]
|
||||
@@ -82,10 +79,10 @@
|
||||
var scanner = StringBytesTestConverter.Scanner(@"one
|
||||
%PDF-1.7");
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
|
||||
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]
|
||||
@@ -94,10 +91,10 @@
|
||||
var scanner = StringBytesTestConverter.Scanner(@"one two
|
||||
three %PDF-1.6");
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
|
||||
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]
|
||||
@@ -105,7 +102,7 @@ three %PDF-1.6");
|
||||
{
|
||||
var scanner = StringBytesTestConverter.Scanner(@"one two");
|
||||
|
||||
Action action = () => FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
|
||||
Action action = () => FileHeaderParser.Parse(scanner, true, log);
|
||||
|
||||
Assert.Throws<PdfDocumentFormatException>(action);
|
||||
}
|
||||
@@ -115,7 +112,7 @@ three %PDF-1.6");
|
||||
{
|
||||
var scanner = StringBytesTestConverter.Scanner("%Pdeef-1.69");
|
||||
|
||||
Action action = () => FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
Action action = () => FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Throws<PdfDocumentFormatException>(action);
|
||||
}
|
||||
@@ -125,7 +122,7 @@ three %PDF-1.6");
|
||||
{
|
||||
var scanner = StringBytesTestConverter.Scanner("%Pdeef-1.69");
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, true, log);
|
||||
var result = FileHeaderParser.Parse(scanner, true, log);
|
||||
|
||||
Assert.Equal(1.4m, result.Version);
|
||||
}
|
||||
@@ -135,44 +132,10 @@ three %PDF-1.6");
|
||||
{
|
||||
var scanner = StringBytesTestConverter.Scanner(@"%FDF-1.6");
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
var result = FileHeaderParser.Parse(scanner, false, log);
|
||||
|
||||
Assert.Equal(0, scanner.scanner.CurrentPosition);
|
||||
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 bytes = new ByteArrayInputBytes(input);
|
||||
|
||||
var scanner = new CoreTokenScanner(bytes, ScannerScope.None);
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner, bytes, false, log);
|
||||
|
||||
Assert.Equal(1.7m, result.Version);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue443()
|
||||
{
|
||||
const string hex =
|
||||
@"00 0F 4A 43 42 31 33 36 36 31 32 32 37 2E 70 64 66 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 50 44 46 20 43 41 52 4F 01 00 FF FF FF FF 00 00 00 00 00 04 DF 28 00 00 00 00 AF 51 7E 82 AF 52 D7 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 81 81 03 0D 00 00 25 50 44 46 2D 31 2E 31 0A 25 E2 E3 CF D3 0D 0A 31 20 30 20 6F 62 6A";
|
||||
|
||||
var bytes = hex.Split(' ', StringSplitOptions.RemoveEmptyEntries).Select(x => HexToken.Convert(x[0], x[1]));
|
||||
|
||||
var str = OtherEncodings.BytesAsLatin1String(bytes.ToArray());
|
||||
|
||||
var scanner = StringBytesTestConverter.Scanner(str);
|
||||
|
||||
var result = FileHeaderParser.Parse(scanner.scanner, scanner.bytes, false, log);
|
||||
|
||||
Assert.Equal(0, scanner.scanner.CurrentPosition);
|
||||
Assert.Equal(128, result.OffsetInFile);
|
||||
Assert.Equal(1.1m, result.Version);
|
||||
Assert.Equal("PDF-1.1", result.VersionString);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
trailer
|
||||
<< >>");
|
||||
|
||||
var result = CrossReferenceTableParser.Parse(input.scanner, 4, false);
|
||||
var result = CrossReferenceTableParser.Parse(input, 4, false);
|
||||
|
||||
Assert.Equal(4, result.ObjectOffsets.Count);
|
||||
}
|
||||
@@ -288,7 +288,7 @@ trailer
|
||||
|
||||
private static CoreTokenScanner GetReader(string input)
|
||||
{
|
||||
return StringBytesTestConverter.Scanner(input).scanner;
|
||||
return StringBytesTestConverter.Scanner(input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,20 +102,11 @@
|
||||
"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",
|
||||
"UglyToad.PdfPig.Graphics.Core.RenderingIntent",
|
||||
"UglyToad.PdfPig.Graphics.Core.TextRenderingMode",
|
||||
"UglyToad.PdfPig.Graphics.CurrentFontState",
|
||||
"UglyToad.PdfPig.Graphics.CurrentGraphicsState",
|
||||
"UglyToad.PdfPig.Graphics.IColorSpaceContext",
|
||||
@@ -197,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",
|
||||
@@ -209,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",
|
||||
@@ -220,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"
|
||||
|
||||
@@ -31,12 +31,11 @@
|
||||
public IInputBytes Bytes { get; set; }
|
||||
}
|
||||
|
||||
internal static (CoreTokenScanner scanner, IInputBytes bytes) Scanner(string s)
|
||||
internal static CoreTokenScanner Scanner(string s)
|
||||
{
|
||||
var inputBytes = new ByteArrayInputBytes(OtherEncodings.StringAsLatin1Bytes(s));
|
||||
var result = new CoreTokenScanner(inputBytes);
|
||||
var result = new CoreTokenScanner(new ByteArrayInputBytes(OtherEncodings.StringAsLatin1Bytes(s)));
|
||||
|
||||
return (result, inputBytes);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests
|
||||
{
|
||||
using System;
|
||||
|
||||
public static class TestEnvironment
|
||||
{
|
||||
public static readonly bool IsUnixPlatform = Environment.NewLine.Length == 1;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,9 @@
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using PdfPig.Filters;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using PdfPig.Tokens;
|
||||
|
||||
internal class TestFilterProvider : ILookupFilterProvider
|
||||
internal class TestFilterProvider : IFilterProvider
|
||||
{
|
||||
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary)
|
||||
{
|
||||
@@ -21,10 +20,5 @@
|
||||
{
|
||||
return new List<IFilter>();
|
||||
}
|
||||
|
||||
public IReadOnlyList<IFilter> GetFilters(DictionaryToken dictionary, IPdfTokenScanner scanner)
|
||||
{
|
||||
return new List<IFilter>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests
|
||||
{
|
||||
using System.Collections.Generic;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.Graphics.Colors;
|
||||
using UglyToad.PdfPig.Graphics.Core;
|
||||
using UglyToad.PdfPig.Images.Png;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
|
||||
public class TestPdfImage : IPdfImage
|
||||
{
|
||||
public PdfRectangle Bounds { get; set; }
|
||||
|
||||
public int WidthInSamples { get; set; }
|
||||
|
||||
public int HeightInSamples { get; set; }
|
||||
|
||||
public ColorSpace? ColorSpace => IsImageMask ? default(ColorSpace?) : ColorSpaceDetails.Type;
|
||||
|
||||
public int BitsPerComponent { get; set; } = 8;
|
||||
|
||||
public IReadOnlyList<byte> RawBytes { get; }
|
||||
|
||||
public RenderingIntent RenderingIntent { get; set; } = RenderingIntent.RelativeColorimetric;
|
||||
|
||||
public bool IsImageMask { get; set; }
|
||||
|
||||
public IReadOnlyList<decimal> Decode { get; set; }
|
||||
|
||||
public bool Interpolate { get; set; }
|
||||
|
||||
public bool IsInlineImage { get; set; }
|
||||
|
||||
public DictionaryToken ImageDictionary { get; set; }
|
||||
|
||||
public ColorSpaceDetails ColorSpaceDetails { get; set; }
|
||||
|
||||
public IReadOnlyList<byte> DecodedBytes { get; set; }
|
||||
|
||||
public bool TryGetBytes(out IReadOnlyList<byte> bytes)
|
||||
{
|
||||
bytes = DecodedBytes;
|
||||
return bytes != null;
|
||||
}
|
||||
|
||||
public bool TryGetPng(out byte[] bytes) => PngFromPdfImageFactory.TryGenerate(this, out bytes);
|
||||
}
|
||||
}
|
||||
@@ -96,18 +96,6 @@
|
||||
Assert.Equal(0m, AssertNumericToken(token).Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleDoubleDashedNumber()
|
||||
{
|
||||
// This is a really weird format but seen in the wild. PDF, shine on, you crazy diamond.
|
||||
var input = StringBytesTestConverter.Convert("--10.25");
|
||||
|
||||
var result = tokenizer.TryTokenize(input.First, input.Bytes, out var token);
|
||||
|
||||
Assert.True(result);
|
||||
Assert.Equal(-10.25m, AssertNumericToken(token).Data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesDot()
|
||||
{
|
||||
|
||||
@@ -169,33 +169,7 @@ endobj";
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[2], "Tj");
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[3], -91);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScansStringWithWeirdWeirdDoubleSymbolNumerics()
|
||||
{
|
||||
const string content = @"
|
||||
0.00 --21.72 TD
|
||||
/F1 8.00 Tf";
|
||||
|
||||
var tokens = new List<IToken>();
|
||||
|
||||
var scanner = scannerFactory(StringBytesTestConverter.Convert(content, false).Bytes);
|
||||
|
||||
while (scanner.MoveNext())
|
||||
{
|
||||
tokens.Add(scanner.CurrentToken);
|
||||
}
|
||||
|
||||
Assert.Equal(6, tokens.Count);
|
||||
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[0], 0m);
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[1], -21.72m);
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[2], "TD");
|
||||
AssertCorrectToken<NameToken, string>(tokens[3], "F1");
|
||||
AssertCorrectToken<NumericToken, decimal>(tokens[4], 8m);
|
||||
AssertCorrectToken<OperatorToken, string>(tokens[5], "Tf");
|
||||
|
||||
}
|
||||
|
||||
private static void AssertCorrectToken<T, TData>(IToken token, TData expected) where T : IDataToken<TData>
|
||||
{
|
||||
var cast = Assert.IsType<T>(token);
|
||||
|
||||
@@ -15,9 +15,7 @@
|
||||
[Fact]
|
||||
public void ReadsSimpleObject()
|
||||
{
|
||||
const string s = @"294 0 obj
|
||||
/WDKAAR+CMBX12
|
||||
endobj";
|
||||
const string s = @"294 0 obj
|
||||
/WDKAAR+CMBX12
|
||||
endobj";
|
||||
|
||||
@@ -118,15 +116,7 @@ endobj
|
||||
}
|
||||
|
||||
[Fact]
|
||||
const string s = @"
|
||||
endobj
|
||||
|
||||
295 0 obj
|
||||
[
|
||||
676 938 875 787 750 880 813 875 813 875 813 656 625 625 938 938 313
|
||||
344 563 563 563 563 563 850 500 574 813 875 563 1019 1144 875 313
|
||||
]
|
||||
endobj";
|
||||
public void ReadsArrayObject()
|
||||
{
|
||||
const string s = @"
|
||||
endobj
|
||||
@@ -153,29 +143,8 @@ endobj";
|
||||
Assert.Equal(295, obj.Number.ObjectNumber);
|
||||
Assert.Equal(0, obj.Number.Generation);
|
||||
|
||||
274 0 obj
|
||||
<<
|
||||
/Type /Pages
|
||||
/Count 2
|
||||
/Parent 275 0 R
|
||||
/Kids [ 121 0 R 125 0 R ]
|
||||
>>
|
||||
endobj
|
||||
|
||||
%Other parts...
|
||||
|
||||
310 0 obj
|
||||
/WPXNWT+CMR9
|
||||
endobj 311 0 obj
|
||||
<<
|
||||
/Type /Font
|
||||
/Subtype /Type1
|
||||
/FirstChar 0
|
||||
/LastChar 127
|
||||
/Widths 313 0 R
|
||||
/BaseFont 310 0 R /FontDescriptor 312 0 R
|
||||
>>
|
||||
endobj";
|
||||
Assert.StartsWith("295 0 obj", s.Substring((int)obj.Position));
|
||||
|
||||
Assert.False(pdfScanner.MoveNext());
|
||||
}
|
||||
|
||||
@@ -222,17 +191,12 @@ endobj";
|
||||
|
||||
Assert.Equal("WPXNWT+CMR9", nameObject.Data);
|
||||
Assert.Equal(310, tokens[1].Number.ObjectNumber);
|
||||
352 0 obj
|
||||
<< /S 1273 /Filter /FlateDecode /Length 353 0 R >>
|
||||
stream
|
||||
H‰œUkLSgþÚh¹IÝÅlK(%[ÈÅ©+ƒåꩊèæÇtnZ)Z¹¨Oå~9ŠÊµo”[éiK)÷B¹´
|
||||
ɲ ©¸˜ n±º×dKöcÏ÷ãœç{ßï}¾÷ÍÉs Ô;€
|
||||
À»—ÀF`ÇF@ƒ4˜ï @¥T¨³fY: žw̵;’’Îq®]cƒÿdp¨ÛI3F#G©#œ)TÇqW£NÚѬgOKbü‡µ#á¡£Þaîtƒƒ›ß–
|
||||
¾“S>}µuÕõ5M±¢ª†»øÞû•q÷îÜ~¬PòžÞ~•¬ëɃGÅ-Ñím·°gêêb,/,£P§õ^v¾ãÁô¿¿ŠTE]²±{šuwÔ`LG³DªìTÈ
|
||||
A¡¬àð‰É©ˆ°‘¼›‚%¥×s³®í»š}%§X{{tøNåÝž¶ö¢ÖÞ¾–~´¼¬°À“Éððr¥8»P£ØêÁi½®Û(éhŽ‘ú;x#dÃÄ$m
|
||||
+)
|
||||
)†…±n
|
||||
9ùyŽA·n\ï»t!=3£½¡:®µåâ¹Ô³ø¼ËiûSÎsë;•Dt—ö$WÉ4U‘¢ºÚšñá1íÐèÔó‚svõ(/(+D²#mZÏ6êüÝ7x‡—†”‡E„²‚|ê«êªDµ5q°šR¦RÈ£n¾[è~“}ýƒÝ½SꞦ'æQŽzÝ‚mæ
|
||||
Assert.StartsWith("310 0 obj", s.Substring((int)tokens[1].Position));
|
||||
|
||||
dictionary = Assert.IsType<DictionaryToken>(tokens[2].Data);
|
||||
|
||||
Assert.Equal(7, dictionary.Data.Count);
|
||||
Assert.Equal(311, tokens[2].Number.ObjectNumber);
|
||||
Assert.StartsWith("311 0 obj", s.Substring((int)tokens[2].Position));
|
||||
}
|
||||
|
||||
@@ -357,7 +321,7 @@ endobj
|
||||
|
||||
[Fact]
|
||||
public void ReadsStreamWithMissingLength()
|
||||
xœ]‘ËjÃ0E÷ÿÃ,ÓEð#NÒ€1¤N^ôA~€-]A-YYøï+Ï4¡t#qfîFWQY*Dïv5:è”–§ñjB‹½Òa¤ •p7¤K ƒÈûëyr8Tº!Ïà úð‚ÉÙVG9¶ø@Å7+Ñ*ÝÃ곬¹T_ùƵƒ8Š$vË̗Ƽ6BDöu%½B¹yí$—Ù ¤\Hx71JœL#Ð6ºÇ0È㸀ü|. µüßõÏ""WÛ‰¯Æ.êÄ«ã8;¤iL°!Ø %É`K°ßì¸ÃöÜáÜ) [‚#CFðİ#(yƒg^ÿ¶æò
|
||||
{
|
||||
const string s = @"
|
||||
12655 0 obj
|
||||
|
||||
@@ -375,31 +339,7 @@ endobj";
|
||||
Assert.Equal(12655, token.Number.ObjectNumber);
|
||||
|
||||
var stream = Assert.IsType<StreamToken>(token.Data);
|
||||
const string input = @"5 0 obj
|
||||
<<
|
||||
/Kids [4 0 R 12 0 R 17 0 R 20 0 R 25 0 R 28 0 R ]
|
||||
/Count 6
|
||||
/Type /Pages
|
||||
/MediaBox [ 0 0 612 792 ]
|
||||
>>
|
||||
endobj
|
||||
1 0 obj
|
||||
<<
|
||||
/Creator (Corel WordPerfect - [D:\Wpdocs\WEBSITE\PROC&POL.WP6 (unmodified)
|
||||
/CreationDate (D:19980224130723)
|
||||
/Title (Proc&Pol.pdf)
|
||||
/Author (J. L. Swezey)
|
||||
/Producer (Acrobat PDFWriter 3.03 for Windows NT)
|
||||
/Keywords (Budapest Treaty; Patent deposits; IDA)
|
||||
/Subject (Patent Collection Procedures and Policies)
|
||||
>>
|
||||
endobj
|
||||
3 0 obj
|
||||
<<
|
||||
/Pages 5 0 R
|
||||
/Type /Catalog
|
||||
>>
|
||||
endobj";
|
||||
|
||||
Assert.Equal("1245", stream.StreamDictionary.Data["S"].ToString());
|
||||
|
||||
Assert.Equal("%¥×³®í»š}%§X{{tøNåÝž¶ö¢ÖÞgrehtyyy$&%&£$££(*¾–~´¼", Encoding.UTF8.GetString(stream.Data.ToArray()));
|
||||
@@ -417,35 +357,6 @@ endobj";
|
||||
<< /Length 288
|
||||
/Filter /FlateDecode >>
|
||||
stream
|
||||
[Fact]
|
||||
public void ReadsDictionaryContainingNull()
|
||||
{
|
||||
const string input = @"14224 0 obj
|
||||
<</Type /XRef
|
||||
/Root 8 0 R
|
||||
/Prev 116
|
||||
/Length 84
|
||||
/Size 35
|
||||
/W [1 3 2]
|
||||
/Index [0 1 6 1 8 2 25 10]
|
||||
/ID [ (ù¸7<C2B8>ãA×<41>žòÜ4<C39C><34>Š•)]
|
||||
/Info 6 0 R
|
||||
/Encrypt null>>
|
||||
endobj";
|
||||
|
||||
var scanner = GetScanner(input);
|
||||
|
||||
var tokens = ReadToEnd(scanner);
|
||||
|
||||
var dictionaryToken = tokens[0].Data as DictionaryToken;
|
||||
|
||||
Assert.NotNull(dictionaryToken);
|
||||
|
||||
var encryptValue = dictionaryToken.Data["Encrypt"];
|
||||
|
||||
Assert.IsType<NullToken>(encryptValue);
|
||||
}
|
||||
|
||||
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^ÿ¶æò
|
||||
ÿž“¸Zë#¢?¢h–P”Æû?šÑï÷ø¯‰Šendstream
|
||||
endobj
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netcoreapp2.0;net6.0</TargetFrameworks>
|
||||
<TargetFramework>netcoreapp2.0</TargetFramework>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
<IsPackable>false</IsPackable>
|
||||
<DebugType>full</DebugType>
|
||||
@@ -39,7 +39,6 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Andada-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\google-simple-doc.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\issue-258-corrupt-name-table.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\PMingLiU.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\TrueType\Roboto-Regular.ttf" />
|
||||
<EmbeddedResource Remove="Fonts\Type1\AdobeUtopia.pfa" />
|
||||
@@ -65,9 +64,6 @@
|
||||
<Content Include="Fonts\TrueType\google-simple-doc.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\issue-258-corrupt-name-table.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="Fonts\TrueType\PMingLiU.ttf">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -101,7 +97,6 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xunit.SkippableFact" Version="1.4.13" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -134,8 +129,4 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Images\" />
|
||||
<Folder Include="Images\Files\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Util
|
||||
{
|
||||
using Xunit;
|
||||
using PdfPig.Util;
|
||||
|
||||
public class Matrix3x3Tests
|
||||
{
|
||||
[Fact]
|
||||
public void CanCreate()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
Assert.Equal(new double[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, matrix);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExposesIdentityMatrix()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 0, 0,
|
||||
0, 1, 0,
|
||||
0, 0, 1
|
||||
);
|
||||
|
||||
Assert.Equal(matrix, Matrix3x3.Identity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithFactor()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var result = matrix.Multiply(3);
|
||||
|
||||
Assert.Equal(new double[] { 3, 6, 9, 12, 15, 18, 21, 24, 27 }, result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithVector()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var vector = (1, 2, 3);
|
||||
|
||||
var product = matrix.Multiply(vector);
|
||||
|
||||
Assert.Equal((14, 32, 50), product);
|
||||
// Result can be verified here:
|
||||
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C+3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B1%2C2%2C3%7D
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMultiplyWithMatrix()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var product = matrix.Multiply(matrix);
|
||||
|
||||
var expected = new Matrix3x3(
|
||||
30, 36, 42,
|
||||
66, 81, 96,
|
||||
102, 126, 150);
|
||||
|
||||
Assert.Equal(expected, product);
|
||||
// Result can be verified here:
|
||||
// https://www.wolframalpha.com/input/?i=%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D.%7B%7B1%2C2%2C3%7D%2C%7B4%2C5%2C6%7D%2C%7B7%2C8%2C9%7D%7D
|
||||
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanTranspose()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var transposed = matrix.Transpose();
|
||||
|
||||
Assert.Equal(new double[] { 1, 4, 7, 2, 5, 8, 3, 6, 9 }, transposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InverseReturnsMatrixIfPossible()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
0, 1, 4,
|
||||
5, 6, 0);
|
||||
|
||||
var inverse = matrix.Inverse();
|
||||
|
||||
Assert.Equal(new double[] { -24, 18, 5, 20, -15, -4, -5, 4, 1 }, inverse);
|
||||
Assert.Equal(Matrix3x3.Identity, matrix.Multiply(inverse));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InverseReturnsNullIfNotPossible()
|
||||
{
|
||||
var matrix = new Matrix3x3(
|
||||
1, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 9);
|
||||
|
||||
var inverse = matrix.Inverse();
|
||||
Assert.Null(inverse);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,6 @@
|
||||
{
|
||||
using Integration;
|
||||
using PdfPig.Writer;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Xunit;
|
||||
|
||||
@@ -16,24 +14,20 @@
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(one, two);
|
||||
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "Write something inInkscape", "I am a simple pdf.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMerge2SimpleDocumentsIntoStream()
|
||||
{
|
||||
var one = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from open office.pdf");
|
||||
|
||||
using (var outputStream = GetSelfDestructingNewFileStream("merge2"))
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
if (outputStream is null)
|
||||
{
|
||||
return;//we can't create a file in this test session
|
||||
}
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
|
||||
PdfMerger.Merge(one, two, outputStream);
|
||||
CanMerge2SimpleDocumentsAssertions(outputStream, "Write something inInkscape", "I am a simple pdf.");
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
|
||||
var page1 = document.GetPage(1);
|
||||
|
||||
Assert.Equal("Write something inInkscape", page1.Text);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
|
||||
Assert.Equal("I am a simple pdf.", page2.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,25 +38,20 @@
|
||||
var two = IntegrationHelpers.GetDocumentPath("Single Page Simple - from inkscape.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(one, two);
|
||||
CanMerge2SimpleDocumentsAssertions(new MemoryStream(result), "I am a simple pdf.", "Write something inInkscape");
|
||||
}
|
||||
|
||||
internal static void CanMerge2SimpleDocumentsAssertions(Stream stream, string page1Text, string page2Text, bool checkVersion=true)
|
||||
{
|
||||
stream.Position = 0;
|
||||
using (var document = PdfDocument.Open(stream, ParsingOptions.LenientParsingOff))
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
if (checkVersion)
|
||||
{
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
}
|
||||
|
||||
Assert.Equal(1.5m, document.Version);
|
||||
|
||||
var page1 = document.GetPage(1);
|
||||
Assert.Equal(page1Text, page1.Text);
|
||||
|
||||
Assert.Equal("I am a simple pdf.", page1.Text);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
Assert.Equal(page2Text, page2.Text);
|
||||
|
||||
Assert.Equal("Write something inInkscape", page2.Text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,26 +89,11 @@
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 24,
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count < 24,
|
||||
"Expected object count to be lower than 24");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DedupsObjectsFromSameDoc()
|
||||
{
|
||||
var one = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
|
||||
var result = PdfMerger.Merge(new List<byte[]> { File.ReadAllBytes(one) }, new List<IReadOnlyList<int>> { new List<int> { 1, 2} });
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(2, document.NumberOfPages);
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count <= 29,
|
||||
"Expected object count to be lower than 30"); // 45 objects with duplicates, 29 with correct re-use
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeWithObjectStream()
|
||||
{
|
||||
@@ -141,52 +115,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeWithSelection()
|
||||
{
|
||||
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
var contents = File.ReadAllBytes(first);
|
||||
|
||||
var toCopy = new[] {2, 1, 4, 3, 6, 5};
|
||||
var result = PdfMerger.Merge(new [] { contents }, new [] { toCopy });
|
||||
|
||||
WriteFile(nameof(CanMergeWithSelection), result);
|
||||
|
||||
using (var existing = PdfDocument.Open(contents, ParsingOptions.LenientParsingOff))
|
||||
using (var merged = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(6, merged.NumberOfPages);
|
||||
|
||||
for (var i =1;i<merged.NumberOfPages;i++)
|
||||
{
|
||||
Assert.Equal(
|
||||
existing.GetPage(toCopy[i-1]).Text,
|
||||
merged.GetPage(i).Text
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanMergeMultipleWithSelection()
|
||||
{
|
||||
var first = IntegrationHelpers.GetDocumentPath("Multiple Page - from Mortality Statistics.pdf");
|
||||
var second = IntegrationHelpers.GetDocumentPath("Old Gutnish Internet Explorer.pdf");
|
||||
var result = PdfMerger.Merge(new[] { File.ReadAllBytes(first), File.ReadAllBytes(second) }, new[] { new[] { 2, 1, 4, 3, 6, 5 }, new []{ 3, 2, 1 } });
|
||||
|
||||
WriteFile(nameof(CanMergeMultipleWithSelection), result);
|
||||
|
||||
using (var document = PdfDocument.Open(result, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(9, document.NumberOfPages);
|
||||
|
||||
foreach (var page in document.GetPages())
|
||||
{
|
||||
Assert.NotNull(page.Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteFile(string name, byte[] bytes)
|
||||
{
|
||||
try
|
||||
@@ -205,40 +133,5 @@
|
||||
// ignored.
|
||||
}
|
||||
}
|
||||
|
||||
private static FileStream GetSelfDestructingNewFileStream(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists("Merger"))
|
||||
{
|
||||
Directory.CreateDirectory("Merger");
|
||||
}
|
||||
|
||||
var output = Path.Combine("Merger", $"{name}.pdf");
|
||||
return File.Create(output, 4096, FileOptions.DeleteOnClose);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NoStackoverflow()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bytes = PdfMerger.Merge(IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf"));
|
||||
using (var document = PdfDocument.Open(bytes, ParsingOptions.LenientParsingOff))
|
||||
{
|
||||
Assert.Equal(45, document.NumberOfPages);
|
||||
}
|
||||
}
|
||||
catch (StackOverflowException)
|
||||
{
|
||||
Assert.True(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace UglyToad.PdfPig.Tests.Writer
|
||||
{
|
||||
using Integration;
|
||||
using PdfPig.Writer;
|
||||
using System.IO;
|
||||
using UglyToad.PdfPig.Tokens;
|
||||
using Xunit;
|
||||
|
||||
public class TokenWriterTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public void EscapeSpecialCharacter()
|
||||
{
|
||||
using (var memStream = new MemoryStream())
|
||||
{
|
||||
TokenWriter.WriteToken(new StringToken("\\"), memStream);
|
||||
TokenWriter.WriteToken(new StringToken("(Hello)"), memStream);
|
||||
|
||||
// Read Test
|
||||
memStream.Position = 0;
|
||||
using (var streamReader = new StreamReader(memStream))
|
||||
{
|
||||
var line = streamReader.ReadToEnd();
|
||||
Assert.Equal("(\\\\) (\\(Hello\\)) ", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
internal class NumericTokenizer : ITokenizer
|
||||
{
|
||||
private readonly StringBuilder stringBuilder = new StringBuilder();
|
||||
private static readonly StringBuilderPool StringBuilderPool = new StringBuilderPool(10);
|
||||
|
||||
private const byte Zero = 48;
|
||||
private const byte Nine = 57;
|
||||
@@ -20,45 +20,28 @@
|
||||
token = null;
|
||||
|
||||
StringBuilder characters;
|
||||
var initialSymbol = currentByte == '-' || currentByte == '+';
|
||||
|
||||
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '.')
|
||||
if ((currentByte >= Zero && currentByte <= Nine) || currentByte == '-' || currentByte == '+' || currentByte == '.')
|
||||
{
|
||||
characters = stringBuilder;
|
||||
characters = StringBuilderPool.Borrow();
|
||||
characters.Append((char)currentByte);
|
||||
}
|
||||
else if (initialSymbol)
|
||||
{
|
||||
characters = stringBuilder;
|
||||
characters.Append((char) currentByte);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var previousSymbol = initialSymbol;
|
||||
|
||||
while (inputBytes.MoveNext())
|
||||
{
|
||||
var b = inputBytes.CurrentByte;
|
||||
|
||||
if (b == '+' || b == '-')
|
||||
if ((b >= Zero && b <= Nine) ||
|
||||
b == '-' ||
|
||||
b == '+' ||
|
||||
b == '.' ||
|
||||
b == 'E' ||
|
||||
b == 'e')
|
||||
{
|
||||
if (previousSymbol)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
characters.Append((char) b);
|
||||
previousSymbol = true;
|
||||
}
|
||||
else if ((b >= Zero && b <= Nine) ||
|
||||
b == '.' ||
|
||||
b == 'E' ||
|
||||
b == 'e')
|
||||
{
|
||||
previousSymbol = false;
|
||||
characters.Append((char)b);
|
||||
}
|
||||
else
|
||||
@@ -70,7 +53,7 @@
|
||||
try
|
||||
{
|
||||
var str = characters.ToString();
|
||||
characters.Clear();
|
||||
StringBuilderPool.Return(characters);
|
||||
|
||||
switch (str)
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user