mirror of
https://github.com/UglyToad/PdfPig.git
synced 2026-03-10 00:23:29 +08:00
Compare commits
90 Commits
rewrite-re
...
7e370a0eab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e370a0eab | ||
|
|
b9aa53166d | ||
|
|
0a2b1e076f | ||
|
|
f732718852 | ||
|
|
d69e3fa6a9 | ||
|
|
d6e86b057e | ||
|
|
c27f1b6553 | ||
|
|
629891e80e | ||
|
|
13825b9424 | ||
|
|
7c4f5e2424 | ||
|
|
bd573b2a1c | ||
|
|
baeac0d0c3 | ||
|
|
ee0cb1dc4a | ||
|
|
c70b343caa | ||
|
|
ce563db133 | ||
|
|
37a5dffcaa | ||
|
|
aef0a78ee6 | ||
|
|
005e52783e | ||
|
|
f4e7db5b5b | ||
|
|
52ecef0e28 | ||
|
|
599ce04bef | ||
|
|
2a6ee918b7 | ||
|
|
9d3cd0a429 | ||
|
|
b49e5aa697 | ||
|
|
181fa9d837 | ||
|
|
6ce6986d78 | ||
|
|
37c9fef20b | ||
|
|
e11dc6bf40 | ||
|
|
40bcc22ea1 | ||
|
|
94d515061e | ||
|
|
3555521634 | ||
|
|
6fba565d66 | ||
|
|
3592fc8438 | ||
|
|
c9034f991c | ||
|
|
255e70f0a7 | ||
|
|
2216ade1f2 | ||
|
|
cf0c33b1e0 | ||
|
|
ffba176060 | ||
|
|
b14f45f59f | ||
|
|
c28d114b79 | ||
|
|
d7d01f842e | ||
|
|
33a8d829ee | ||
|
|
57921c7e9b | ||
|
|
5a6b3970f0 | ||
|
|
397ccb15d6 | ||
|
|
ca284e0cb9 | ||
|
|
b2f4ca8839 | ||
|
|
008959457a | ||
|
|
a53d96cb73 | ||
|
|
efdedb9495 | ||
|
|
eb906a776d | ||
|
|
44e638ee4d | ||
|
|
304d7dde5a | ||
|
|
07df6fd740 | ||
|
|
c96880ac61 | ||
|
|
77db6c6b54 | ||
|
|
e886ae648f | ||
|
|
c4f442c0cd | ||
|
|
0ef120dc5c | ||
|
|
d5b97065bd | ||
|
|
22eab422a3 | ||
|
|
8408c98aec | ||
|
|
dd5aa46c75 | ||
|
|
e4ed4d1b39 | ||
|
|
0afe021ad3 | ||
|
|
3650e27432 | ||
|
|
a43b968ea9 | ||
|
|
1031dcc221 | ||
|
|
0f641774e6 | ||
|
|
a3edc926c8 | ||
|
|
f1923fcbcd | ||
|
|
7ff58893af | ||
|
|
bee6f13888 | ||
|
|
e6dd2d15c2 | ||
|
|
7dd5d68be3 | ||
|
|
bdf3b8e2b4 | ||
|
|
c8dff885bd | ||
|
|
0b228c57b7 | ||
|
|
ef21227b3c | ||
|
|
b9f2230a0a | ||
|
|
b6950a5fb0 | ||
|
|
1ed9e017f4 | ||
|
|
83d6fc6cc2 | ||
|
|
febfa4d4b3 | ||
|
|
0ebbe0540d | ||
|
|
52c0635273 | ||
|
|
b6bd0a3169 | ||
|
|
3d2e12cb16 | ||
|
|
9cb3b71e62 | ||
|
|
27df4af5f9 |
15
.github/workflows/build_and_test.yml
vendored
15
.github/workflows/build_and_test.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build and test
|
||||
name: Build, test and publish draft
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
build:
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v4
|
||||
@@ -29,3 +29,14 @@ jobs:
|
||||
|
||||
- name: Run the tests
|
||||
run: dotnet test -c Release src/UglyToad.PdfPig.sln
|
||||
|
||||
- name: Update draft release
|
||||
if: github.ref == 'refs/heads/master'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
draft: true
|
||||
name: "Draft release"
|
||||
tag_name: "unreleased" # fixed tag so this one draft is updated
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
5
.github/workflows/build_and_test_macos.yml
vendored
5
.github/workflows/build_and_test_macos.yml
vendored
@@ -19,10 +19,11 @@ jobs:
|
||||
2.1.x
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
# Build the release build
|
||||
- name: Build the solution
|
||||
run: dotnet build -c Release src/UglyToad.PdfPig.sln
|
||||
run: dotnet build -c Release src/UglyToad.PdfPig.sln -f net8.0
|
||||
|
||||
- name: Run the tests
|
||||
run: dotnet test -c Release src/UglyToad.PdfPig.sln
|
||||
run: dotnet test -c Release src/UglyToad.PdfPig.sln -f net8.0
|
||||
|
||||
67
.github/workflows/nightly_release.yml
vendored
67
.github/workflows/nightly_release.yml
vendored
@@ -5,39 +5,55 @@ on:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
uses: ./.github/workflows/run_integration_tests.yml
|
||||
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 }}
|
||||
permissions:
|
||||
contents: write # Grant write permission for tagging
|
||||
|
||||
jobs:
|
||||
check_publish_needed:
|
||||
runs-on: ubuntu-latest
|
||||
name: Check if this commit has already been published
|
||||
outputs:
|
||||
should_run: ${{ steps.check.outputs.should_run }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch tags
|
||||
run: git fetch --tags
|
||||
|
||||
- id: check
|
||||
run: |
|
||||
latest_commit=$(git rev-parse nightly-latest || echo "")
|
||||
echo "Latest published commit: $latest_commit"
|
||||
if [ "$latest_commit" = "${{ github.sha }}" ]; then
|
||||
echo "No new commit since last publish."
|
||||
echo "should_run=false" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "New commit detected."
|
||||
echo "should_run=true" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
tests:
|
||||
needs: check_publish_needed
|
||||
if: ${{ needs.check_publish_needed.outputs.should_run == 'true' }}
|
||||
uses: ./.github/workflows/run_integration_tests.yml
|
||||
|
||||
- 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, tests]
|
||||
if: ${{ needs.check_date.outputs.should_run != 'false' }}
|
||||
needs: [check_publish_needed, tests]
|
||||
if: ${{ needs.check_publish_needed.outputs.should_run == 'true' }}
|
||||
runs-on: windows-2022
|
||||
name: build_and_publish_nightly
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v2
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
2.1.x
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v1.0.2
|
||||
@@ -54,5 +70,10 @@ jobs:
|
||||
|
||||
- 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 }}
|
||||
|
||||
- name: Tag latest nightly commit
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
git tag -f nightly-latest ${{ github.sha }}
|
||||
git push origin nightly-latest --force
|
||||
|
||||
40
.github/workflows/prepare_release_pr.yml
vendored
Normal file
40
.github/workflows/prepare_release_pr.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Create Release PR
|
||||
|
||||
on:
|
||||
workflow_dispatch
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Calculate next version
|
||||
id: version
|
||||
run: |
|
||||
$newVer = .\tools\get-next-main-version.ps1
|
||||
echo "NEW_VERSION=$newVer" >> $env:GITHUB_ENV
|
||||
.\tools\set-version.ps1 $newVer -UpdateAssemblyAndFileVersion
|
||||
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
|
||||
git checkout -b release/$newVer
|
||||
git commit -am "Release $newVer"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
branch: release/${{ env.NEW_VERSION }}
|
||||
base: master
|
||||
title: "Release ${{ env.NEW_VERSION }}"
|
||||
body: "Automated release PR."
|
||||
delete-branch: true
|
||||
merge-method: rebase
|
||||
53
.github/workflows/publish_nuget.yml
vendored
Normal file
53
.github/workflows/publish_nuget.yml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Verify tag points to master
|
||||
id: verify
|
||||
run: |
|
||||
TAG_COMMIT=$(git rev-list -n 1 ${{ github.ref_name }})
|
||||
if ! git branch -r --contains $TAG_COMMIT | grep -q 'origin/master'; then
|
||||
echo "Tag is not on master — skipping publish"
|
||||
exit 78 # 78 = neutral in GitHub Actions
|
||||
fi
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
2.1.x
|
||||
6.0.x
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Run tests
|
||||
run: dotnet test -c Release src/UglyToad.PdfPig.sln
|
||||
|
||||
- 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 to NuGet
|
||||
run: dotnet nuget push package/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
44
.github/workflows/run_common_crawl_tests.yml
vendored
44
.github/workflows/run_common_crawl_tests.yml
vendored
@@ -9,50 +9,50 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
pair: ["0000-0001", "0002-0003", "0004-0005", "0006-0007"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Restore corpus cache 0000, 0001
|
||||
- name: Restore corpus cache
|
||||
id: restore-corpus
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: corpus/
|
||||
key: ${{ runner.os }}-pdf-corpus-0000-0001
|
||||
key: ${{ runner.os }}-pdf-corpus-${{ matrix.pair }}
|
||||
|
||||
- name: Download corpus if cache missed 0000, 0001
|
||||
- name: Download corpus if cache missed
|
||||
if: steps.restore-corpus.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
mkdir -p corpus/zipfiles
|
||||
cd corpus/zipfiles
|
||||
echo "Downloading 0000.zip"
|
||||
wget -nv https://digitalcorpora.s3.amazonaws.com/corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/0000.zip -O 0000.zip
|
||||
wget -nv https://digitalcorpora.s3.amazonaws.com/corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/0001.zip -O 0001.zip
|
||||
for file in $(echo "${{ matrix.pair }}" | tr '-' ' '); do
|
||||
echo "Downloading $file.zip"
|
||||
wget -nv "https://digitalcorpora.s3.amazonaws.com/corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/$file.zip" -O "$file.zip"
|
||||
done
|
||||
cd ..
|
||||
unzip 'zipfiles/*.zip' -d extracted
|
||||
# run: rm -f zipfiles/*.zip
|
||||
|
||||
- name: Remove unwanted test files
|
||||
run: |
|
||||
skip_files=(
|
||||
"corpus/extracted/0000399.pdf"
|
||||
"corpus/extracted/0000819.pdf"
|
||||
"corpus/extracted/0000920.pdf"
|
||||
"corpus/extracted/0000300.pdf"
|
||||
"corpus/extracted/0001589.pdf"
|
||||
"corpus/extracted/0001957.pdf"
|
||||
)
|
||||
|
||||
for file in "${skip_files[@]}"; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Removing $file"
|
||||
rm "$file"
|
||||
while read f || [ -n "$f" ]; do
|
||||
full="corpus/extracted/$f"
|
||||
if [ -f "$full" ]; then
|
||||
echo "Removing $full"
|
||||
rm "$full"
|
||||
fi
|
||||
done
|
||||
done < tools/common-crawl-ignore.txt
|
||||
|
||||
- name: Run tests against corpus
|
||||
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "corpus/extracted"
|
||||
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "corpus/extracted" --configuration Release
|
||||
|
||||
4
.github/workflows/run_integration_tests.yml
vendored
4
.github/workflows/run_integration_tests.yml
vendored
@@ -15,7 +15,9 @@ jobs:
|
||||
- name: Set up dotnet core
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
dotnet-version: |
|
||||
8.0.x
|
||||
9.0.x
|
||||
|
||||
- name: Restore cached part 1
|
||||
id: restore-cache-p1
|
||||
|
||||
43
.github/workflows/tag_release.yml
vendored
Normal file
43
.github/workflows/tag_release.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
name: Tag Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
tag_if_version_changed:
|
||||
if: startsWith(github.event.head_commit.message, 'Release ')
|
||||
runs-on: windows-2022
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect version change
|
||||
id: versioncheck
|
||||
run: |
|
||||
git fetch origin master --depth=2
|
||||
|
||||
$diff = git diff HEAD^ HEAD -- tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj
|
||||
|
||||
if ($diff -match "<Version>") {
|
||||
$version = (Select-String -Path tools/UglyToad.PdfPig.Package/UglyToad.PdfPig.Package.csproj -Pattern "<Version>(.*)</Version>").Matches.Groups[1].Value
|
||||
echo "version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "create=true" >> $env:GITHUB_OUTPUT
|
||||
} else {
|
||||
echo "create=false" >> $env:GITHUB_OUTPUT
|
||||
}
|
||||
|
||||
- name: Create tag
|
||||
if: steps.versioncheck.outputs.create == 'true'
|
||||
run: |
|
||||
git config user.name "github-actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
|
||||
git tag "v${{ steps.versioncheck.outputs.version }}"
|
||||
git push origin "v${{ steps.versioncheck.outputs.version }}"
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -248,3 +248,5 @@ _Pvt_Extensions
|
||||
/tools/ConsoleRunner/Properties/launchSettings.json
|
||||
|
||||
/docs/doxygen
|
||||
/tools/UglyToad.PdfPig.ConsoleRunner/Properties/launchSettings.json
|
||||
/src/UglyToad.PdfPig.Tests/Images/Files/Pdf/indexed-png-with-mask.png
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Buffers;
|
||||
|
||||
namespace UglyToad.PdfPig.Core;
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is IndirectReference other && Equals(other);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/// <summary>
|
||||
/// Convert the string to bytes using the ISO 8859-1 encoding.
|
||||
/// </summary>
|
||||
public static byte[] StringAsLatin1Bytes(string s)
|
||||
public static byte[]? StringAsLatin1Bytes(string? s)
|
||||
{
|
||||
if (s == null)
|
||||
{
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
/// Try to convert raw bytes to a PdfDocEncoding encoded string. If unsupported characters are encountered
|
||||
/// meaning we cannot safely round-trip the value to bytes this will instead return false.
|
||||
/// </summary>
|
||||
public static bool TryConvertBytesToString(ReadOnlySpan<byte> bytes, out string result)
|
||||
public static bool TryConvertBytesToString(ReadOnlySpan<byte> bytes, out string? result)
|
||||
{
|
||||
result = null;
|
||||
if (bytes.Length == 0)
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this <see cref="PdfLine"/> is equal to a specified <see cref="PdfLine"/> .
|
||||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is PdfLine other && Equals(other);
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether this <see cref="PdfPoint"/> is equal to a specified <see cref="PdfPoint"/> .
|
||||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is PdfPoint other && Equals(other);
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is PdfRectangle other && Equals(other);
|
||||
}
|
||||
|
||||
@@ -241,8 +241,8 @@
|
||||
public bool IsClosed()
|
||||
{
|
||||
var filteredCount = 0;
|
||||
IPathCommand last = null;
|
||||
IPathCommand first = null;
|
||||
IPathCommand? last = null;
|
||||
IPathCommand? first = null;
|
||||
for (int i = Commands.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var cmd = Commands[i];
|
||||
@@ -376,14 +376,14 @@
|
||||
/// Gets a <see cref="PdfRectangle"/> which entirely contains the geometry of the defined path.
|
||||
/// </summary>
|
||||
/// <returns>For paths which don't define any geometry this returns <see langword="null"/>.</returns>
|
||||
public static PdfRectangle? GetBoundingRectangle(IReadOnlyList<PdfSubpath> path)
|
||||
public static PdfRectangle? GetBoundingRectangle(IReadOnlyList<PdfSubpath>? path)
|
||||
{
|
||||
if (path == null || path.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var bboxes = path.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x.Value).ToList();
|
||||
var bboxes = path.Select(x => x.GetBoundingRectangle()).Where(x => x.HasValue).Select(x => x!.Value).ToList();
|
||||
if (bboxes.Count == 0)
|
||||
{
|
||||
return null;
|
||||
@@ -433,7 +433,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return (obj is Close);
|
||||
}
|
||||
@@ -479,7 +479,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Move move)
|
||||
{
|
||||
@@ -545,7 +545,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is Line line)
|
||||
{
|
||||
@@ -651,7 +651,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is QuadraticBezierCurve curve)
|
||||
{
|
||||
@@ -809,7 +809,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is CubicBezierCurve curve)
|
||||
{
|
||||
@@ -944,7 +944,7 @@
|
||||
/// <summary>
|
||||
/// Compares two <see cref="PdfSubpath"/>s for equality. Paths will only be considered equal if the commands which construct the paths are in the same order.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (!(obj is PdfSubpath path) || Commands.Count != path.Commands.Count)
|
||||
{
|
||||
|
||||
63
src/UglyToad.PdfPig.Core/StackDepthGuard.cs
Normal file
63
src/UglyToad.PdfPig.Core/StackDepthGuard.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace UglyToad.PdfPig.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a guard for tracking and limiting the depth of nested stack operations, such as recursive calls or
|
||||
/// nested parsing.
|
||||
/// </summary>
|
||||
/// <remarks>Use this class to prevent excessive stack usage by enforcing a maximum nesting depth. This is
|
||||
/// particularly useful in scenarios where untrusted or deeply nested input could cause stack overflows or
|
||||
/// performance issues.</remarks>
|
||||
public sealed class StackDepthGuard
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a stack depth guard with no effective limit on the allowed depth.
|
||||
/// </summary>
|
||||
/// <remarks>Use this instance when stack depth restrictions are not required.</remarks>
|
||||
public static readonly StackDepthGuard Infinite = new StackDepthGuard(int.MaxValue);
|
||||
|
||||
private readonly int maxStackDepth;
|
||||
|
||||
private int depth;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the StackDepthGuard class with the specified maximum stack depth.
|
||||
/// </summary>
|
||||
/// <param name="maxStackDepth">The maximum allowed stack depth for guarded operations. Must be a positive integer.</param>
|
||||
public StackDepthGuard(int maxStackDepth)
|
||||
{
|
||||
if (maxStackDepth <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxStackDepth));
|
||||
}
|
||||
this.maxStackDepth = maxStackDepth;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increments the current nesting depth and checks against the maximum allowed stack depth.
|
||||
/// </summary>
|
||||
/// <exception cref="PdfDocumentFormatException">Thrown if the maximum allowed nesting depth is exceeded.</exception>
|
||||
public void Enter()
|
||||
{
|
||||
if (++depth > maxStackDepth)
|
||||
{
|
||||
depth--; // Decrement so Exit remains balanced if someone catches this
|
||||
throw new PdfDocumentFormatException($"Exceeded maximum nesting depth of {maxStackDepth}.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the current depth level by one, ensuring that the depth does not become negative.
|
||||
/// </summary>
|
||||
/// <remarks>If the current depth is already zero, calling this method has no effect. This method
|
||||
/// is typically used to track or manage nested operations or scopes where depth must remain
|
||||
/// non-negative.</remarks>
|
||||
public void Exit()
|
||||
{
|
||||
depth--;
|
||||
if (depth < 0)
|
||||
{
|
||||
depth = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,12 @@
|
||||
{
|
||||
private readonly Stream stream;
|
||||
private readonly bool shouldDispose;
|
||||
private byte? peekByte;
|
||||
|
||||
private bool isAtEnd;
|
||||
|
||||
/// <inheritdoc />
|
||||
public long CurrentOffset => stream.Position;
|
||||
public long CurrentOffset => peekByte.HasValue ? stream.Position - 1 : stream.Position;
|
||||
|
||||
/// <inheritdoc />
|
||||
public byte CurrentByte { get; private set; }
|
||||
@@ -52,7 +53,8 @@
|
||||
/// <inheritdoc />
|
||||
public bool MoveNext()
|
||||
{
|
||||
var b = stream.ReadByte();
|
||||
var b = peekByte ?? stream.ReadByte();
|
||||
peekByte = null;
|
||||
|
||||
if (b == -1)
|
||||
{
|
||||
@@ -68,18 +70,21 @@
|
||||
/// <inheritdoc />
|
||||
public byte? Peek()
|
||||
{
|
||||
var current = CurrentOffset;
|
||||
|
||||
var b = stream.ReadByte();
|
||||
|
||||
stream.Seek(current, SeekOrigin.Begin);
|
||||
|
||||
if (b == -1)
|
||||
if (!peekByte.HasValue)
|
||||
{
|
||||
return null;
|
||||
var v = stream.ReadByte();
|
||||
|
||||
if (v >= 0)
|
||||
{
|
||||
peekByte = (byte)v;
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return (byte)b;
|
||||
return peekByte;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -92,6 +97,7 @@
|
||||
public void Seek(long position)
|
||||
{
|
||||
isAtEnd = false;
|
||||
peekByte = null;
|
||||
|
||||
if (position == 0)
|
||||
{
|
||||
@@ -112,9 +118,15 @@
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else if (peekByte.HasValue)
|
||||
{
|
||||
buffer[0] = peekByte.Value;
|
||||
peekByte = null;
|
||||
|
||||
return Read(buffer.Slice(1)) + 1;
|
||||
}
|
||||
|
||||
int read = stream.Read(buffer);
|
||||
|
||||
if (read > 0)
|
||||
{
|
||||
CurrentByte = buffer[read - 1];
|
||||
|
||||
@@ -224,9 +224,18 @@
|
||||
[Pure]
|
||||
public double TransformX(double x)
|
||||
{
|
||||
var xt = A * x + C * 0 + E;
|
||||
return A * x + E; // + C * 0
|
||||
}
|
||||
|
||||
return xt;
|
||||
|
||||
/// <summary>
|
||||
/// Transform an Y coordinate using this transformation matrix.
|
||||
/// </summary>
|
||||
/// <param name="y">The Y coordinate.</param>
|
||||
/// <returns>The transformed Y coordinate.</returns>
|
||||
public double TransformY(double y)
|
||||
{
|
||||
return D * y + F;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -463,7 +472,7 @@
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is TransformationMatrix other && Equals(other);
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.11-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.14</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
<Nullable>enable</Nullable>
|
||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -89,7 +89,7 @@ namespace UglyToad.PdfPig.Core
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetSecond(out B b)
|
||||
{
|
||||
b = default(B);
|
||||
b = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ namespace UglyToad.PdfPig.Core
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetFirst(out A a)
|
||||
{
|
||||
a = default(A);
|
||||
a = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -216,71 +216,37 @@
|
||||
yield return group.Select(i => elements[i]).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group elements using Depth-first search.
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
/// <param name="edges">The graph. edges[i] = j indicates that there is an edge between i and j.</param>
|
||||
/// <returns>A List of HashSets containing the grouped indexes.</returns>
|
||||
internal static List<HashSet<int>> GroupIndexes(int[] edges)
|
||||
|
||||
internal static List<List<int>> GroupIndexes(int[] edges)
|
||||
{
|
||||
int[][] adjacency = new int[edges.Length][];
|
||||
// Improved thanks to https://github.com/UglyToad/PdfPig/issues/1178
|
||||
var adjacency = new List<int>[edges.Length];
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
HashSet<int> matches = new HashSet<int>();
|
||||
if (edges[i] != -1) matches.Add(edges[i]);
|
||||
for (int j = 0; j < edges.Length; j++)
|
||||
{
|
||||
if (edges[j] == i) matches.Add(j);
|
||||
}
|
||||
adjacency[i] = matches.ToArray();
|
||||
adjacency[i] = new List<int>();
|
||||
}
|
||||
|
||||
List<HashSet<int>> groupedIndexes = new List<HashSet<int>>();
|
||||
// one pass O(n)
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
int j = edges[i];
|
||||
if (j != -1)
|
||||
{
|
||||
// i <-> j
|
||||
adjacency[i].Add(j);
|
||||
adjacency[j].Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
List<List<int>> groupedIndexes = new List<List<int>>();
|
||||
bool[] isDone = new bool[edges.Length];
|
||||
|
||||
for (int p = 0; p < edges.Length; p++)
|
||||
{
|
||||
if (isDone[p]) continue;
|
||||
groupedIndexes.Add(DfsIterative(p, adjacency, ref isDone));
|
||||
}
|
||||
return groupedIndexes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Group elements using Depth-first search.
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
/// <param name="edges">The graph. edges[i] = [j, k, l, ...] indicates that there is an edge between i and each element j, k, l, ...</param>
|
||||
/// <returns>A List of HashSets containing the grouped indexes.</returns>
|
||||
internal static List<HashSet<int>> GroupIndexes(int[][] edges)
|
||||
{
|
||||
int[][] adjacency = new int[edges.Length][];
|
||||
for (int i = 0; i < edges.Length; i++)
|
||||
{
|
||||
HashSet<int> matches = new HashSet<int>();
|
||||
for (int j = 0; j < edges[i].Length; j++)
|
||||
if (isDone[p])
|
||||
{
|
||||
if (edges[i][j] != -1) matches.Add(edges[i][j]);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = 0; j < edges.Length; j++)
|
||||
{
|
||||
for (int k = 0; k < edges[j].Length; k++)
|
||||
{
|
||||
if (edges[j][k] == i) matches.Add(j);
|
||||
}
|
||||
}
|
||||
adjacency[i] = matches.ToArray();
|
||||
}
|
||||
|
||||
List<HashSet<int>> groupedIndexes = new List<HashSet<int>>();
|
||||
bool[] isDone = new bool[edges.Length];
|
||||
|
||||
for (int p = 0; p < edges.Length; p++)
|
||||
{
|
||||
if (isDone[p]) continue;
|
||||
groupedIndexes.Add(DfsIterative(p, adjacency, ref isDone));
|
||||
}
|
||||
return groupedIndexes;
|
||||
@@ -290,22 +256,33 @@
|
||||
/// Depth-first search
|
||||
/// <para>https://en.wikipedia.org/wiki/Depth-first_search</para>
|
||||
/// </summary>
|
||||
private static HashSet<int> DfsIterative(int s, int[][] adj, ref bool[] isDone)
|
||||
private static List<int> DfsIterative(int s, List<int>[] adj, ref bool[] isDone)
|
||||
{
|
||||
HashSet<int> group = new HashSet<int>();
|
||||
Stack<int> S = new Stack<int>();
|
||||
List<int> group = new List<int>();
|
||||
Stack<int> S = new Stack<int>(4);
|
||||
S.Push(s);
|
||||
|
||||
isDone[s] = true;
|
||||
while (S.Count > 0)
|
||||
{
|
||||
var u = S.Pop();
|
||||
if (!isDone[u])
|
||||
group.Add(u);
|
||||
|
||||
#if NET
|
||||
var currentAdj = System.Runtime.InteropServices.CollectionsMarshal.AsSpan(adj[u]);
|
||||
int count = currentAdj.Length;
|
||||
#else
|
||||
var currentAdj = adj[u];
|
||||
int count = currentAdj.Count;
|
||||
#endif
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
group.Add(u);
|
||||
isDone[u] = true;
|
||||
foreach (var v in adj[u])
|
||||
var v = currentAdj[i];
|
||||
ref bool done = ref isDone[v];
|
||||
if (!done)
|
||||
{
|
||||
S.Push(v);
|
||||
done = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,13 +128,13 @@
|
||||
throw new ArgumentException("The algorithm cannot be used with a document of less than 2 pages.", nameof(pagesTextBlocks));
|
||||
}
|
||||
|
||||
ConcurrentDictionary<int, List<TextBlock>> pageDecorations = new ConcurrentDictionary<int, List<TextBlock>>();
|
||||
ConcurrentDictionary<int, OrderedSet<TextBlock>> pageDecorations = new ConcurrentDictionary<int, OrderedSet<TextBlock>>();
|
||||
|
||||
ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
|
||||
|
||||
Parallel.For(0, pagesTextBlocks.Count, parallelOptions, p =>
|
||||
{
|
||||
if (!pageDecorations.TryAdd(p, new List<TextBlock>()))
|
||||
if (!pageDecorations.TryAdd(p, new OrderedSet<TextBlock>()))
|
||||
{
|
||||
throw new ArgumentException("Cannot add element with index " + p + " in ConcurrentDictionary.");
|
||||
}
|
||||
@@ -165,7 +165,7 @@
|
||||
var score = Score(current, previousPage, nextPage, minimumEditDistanceNormalised, similarityThreshold, n);
|
||||
if (score >= similarityThreshold)
|
||||
{
|
||||
if (!pageDecorations[p].Contains(current)) pageDecorations[p].Add(current);
|
||||
pageDecorations[p].TryAdd(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@
|
||||
var score = Score(current, previousPage, nextPage, minimumEditDistanceNormalised, similarityThreshold, n);
|
||||
if (score >= similarityThreshold)
|
||||
{
|
||||
if (!pageDecorations[p].Contains(current)) pageDecorations[p].Add(current);
|
||||
pageDecorations[p].TryAdd(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
var score = Score(current, previousPage, nextPage, minimumEditDistanceNormalised, similarityThreshold, n);
|
||||
if (score >= similarityThreshold)
|
||||
{
|
||||
if (!pageDecorations[p].Contains(current)) pageDecorations[p].Add(current);
|
||||
pageDecorations[p].TryAdd(current);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,12 +210,12 @@
|
||||
var score = Score(current, previousPage, nextPage, minimumEditDistanceNormalised, similarityThreshold, n);
|
||||
if (score >= similarityThreshold)
|
||||
{
|
||||
if (!pageDecorations[p].Contains(current)) pageDecorations[p].Add(current);
|
||||
pageDecorations[p].TryAdd(current);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return pageDecorations.OrderBy(x => x.Key).Select(x => x.Value).ToList();
|
||||
return pageDecorations.OrderBy(x => x.Key).Select(x => x.Value.GetList()).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -68,25 +68,11 @@
|
||||
// update textSequence?
|
||||
|
||||
// update font details to bold
|
||||
var fontDetails = new FontDetails(letter.Font.Name, true, letter.Font.Weight, letter.Font.IsItalic);
|
||||
|
||||
var newLetter = new Letter(letter.Value,
|
||||
letter.GlyphRectangle,
|
||||
letter.StartBaseLine,
|
||||
letter.EndBaseLine,
|
||||
letter.Width,
|
||||
letter.FontSize,
|
||||
fontDetails,
|
||||
letter.RenderingMode,
|
||||
letter.StrokeColor,
|
||||
letter.FillColor,
|
||||
letter.PointSize,
|
||||
letter.TextSequence);
|
||||
|
||||
// update markedContentStack?
|
||||
|
||||
// update letters
|
||||
cleanLetters[duplicatesOverlappingIndex] = newLetter;
|
||||
cleanLetters[duplicatesOverlappingIndex] = letter.AsBold();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
56
src/UglyToad.PdfPig.DocumentLayoutAnalysis/OrderedSet.cs
Normal file
56
src/UglyToad.PdfPig.DocumentLayoutAnalysis/OrderedSet.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
namespace UglyToad.PdfPig.DocumentLayoutAnalysis
|
||||
{
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
internal class OrderedSet<T>
|
||||
{
|
||||
private readonly HashSet<T> _set;
|
||||
private readonly List<T> _list;
|
||||
|
||||
public OrderedSet() : this(EqualityComparer<T>.Default)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public OrderedSet(IEqualityComparer<T> comparer)
|
||||
{
|
||||
_set = new HashSet<T>(comparer);
|
||||
_list = new List<T>();
|
||||
}
|
||||
|
||||
public int Count => _set.Count;
|
||||
|
||||
public bool TryAdd(T item)
|
||||
{
|
||||
if (_set.Contains(item)) return false;
|
||||
|
||||
_list.Add(item);
|
||||
_set.Add(item);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_list.Clear();
|
||||
_set.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return item is not null && _set.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
_list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
public List<T> GetList()
|
||||
{
|
||||
return _list;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,12 +48,19 @@
|
||||
/// <returns>The <see cref="TextBlock"/>s generated by the document spectrum method.</returns>
|
||||
public IReadOnlyList<TextBlock> GetBlocks(IEnumerable<Word> words)
|
||||
{
|
||||
if (words?.Any() != true)
|
||||
if (words is null)
|
||||
{
|
||||
return Array.Empty<TextBlock>();
|
||||
}
|
||||
|
||||
return GetBlocks(words.ToList(),
|
||||
// Avoid multiple enumeration and unnecessary ToArray() if already a list
|
||||
var wordList = words as IReadOnlyList<Word> ?? words.ToArray();
|
||||
if (wordList.Count == 0)
|
||||
{
|
||||
return Array.Empty<TextBlock>();
|
||||
}
|
||||
|
||||
return GetBlocks(wordList,
|
||||
options.WithinLineBounds, options.WithinLineMultiplier, options.WithinLineBinSize,
|
||||
options.BetweenLineBounds, options.BetweenLineMultiplier, options.BetweenLineBinSize,
|
||||
options.AngularDifferenceBounds,
|
||||
|
||||
@@ -60,11 +60,12 @@
|
||||
letter = new Letter(
|
||||
" ",
|
||||
letter.GlyphRectangle,
|
||||
letter.GlyphRectangleLoose,
|
||||
letter.StartBaseLine,
|
||||
letter.EndBaseLine,
|
||||
letter.Width,
|
||||
letter.FontSize,
|
||||
letter.Font,
|
||||
letter.GetFont()!,
|
||||
letter.RenderingMode,
|
||||
letter.StrokeColor,
|
||||
letter.FillColor,
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.11-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Fonts\UglyToad.PdfPig.Fonts.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig\UglyToad.PdfPig.csproj" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.14</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Fonts\UglyToad.PdfPig.Fonts.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig\UglyToad.PdfPig.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -51,34 +51,49 @@
|
||||
|
||||
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);
|
||||
var buckets = new List<Letter>[5];
|
||||
for (int i = 0; i < buckets.Length; i++) buckets[i] = new List<Letter>();
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate270).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
foreach (var l in letters)
|
||||
{
|
||||
switch (l.TextOrientation)
|
||||
{
|
||||
case TextOrientation.Horizontal: buckets[0].Add(l); break;
|
||||
case TextOrientation.Rotate270: buckets[1].Add(l); break;
|
||||
case TextOrientation.Rotate180: buckets[2].Add(l); break;
|
||||
case TextOrientation.Rotate90: buckets[3].Add(l); break;
|
||||
default: buckets[4].Add(l); break;
|
||||
}
|
||||
}
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate180).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
// Use a thread-safe collection to avoid lock contention.
|
||||
var results = new List<Word>(letters.Count); // Pre-allocate for performance
|
||||
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Rotate90).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasureAA, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
// Limit parallelism to avoid oversubscription.
|
||||
var parallelOptions = new System.Threading.Tasks.ParallelOptions
|
||||
{
|
||||
MaxDegreeOfParallelism = options.MaxDegreeOfParallelism > 0 ? options.MaxDegreeOfParallelism : Environment.ProcessorCount
|
||||
};
|
||||
|
||||
// not axis aligned
|
||||
words.AddRange(GetWords(
|
||||
letters.Where(l => l.TextOrientation == TextOrientation.Other).ToList(),
|
||||
options.MaximumDistance, options.DistanceMeasure, options.FilterPivot,
|
||||
options.Filter, options.MaxDegreeOfParallelism));
|
||||
|
||||
return words;
|
||||
// Use partitioner for better load balancing and avoid ConcurrentBag overhead
|
||||
System.Threading.Tasks.Parallel.ForEach(
|
||||
System.Collections.Concurrent.Partitioner.Create(0, buckets.Length),
|
||||
parallelOptions,
|
||||
range =>
|
||||
{
|
||||
for (int i = range.Item1; i < range.Item2; i++)
|
||||
{
|
||||
if (buckets[i].Count == 0) continue;
|
||||
var measure = (i == 4) ? options.DistanceMeasure : options.DistanceMeasureAA;
|
||||
var words = GetWords(buckets[i], options.MaximumDistance, measure, options.FilterPivot, options.Filter, options.MaxDegreeOfParallelism);
|
||||
lock (results)
|
||||
{
|
||||
results.AddRange(words);
|
||||
}
|
||||
}
|
||||
});
|
||||
results.TrimExcess();
|
||||
return results;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -770,6 +770,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
|
||||
}
|
||||
}
|
||||
|
||||
values.TrimExcess();
|
||||
|
||||
return new Type2CharStrings.CommandSequence(values, commandIdentifiers);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
public virtual string GetNameByStringId(int stringId)
|
||||
{
|
||||
return GlyphIdToStringIdAndName.SingleOrDefault(x => x.Value.stringId == stringId).Value.name;
|
||||
return GlyphIdToStringIdAndName.FirstOrDefault(x => x.Value.stringId == stringId).Value.name;
|
||||
}
|
||||
|
||||
public virtual int GetStringIdByGlyphId(int glyphId)
|
||||
|
||||
@@ -257,6 +257,10 @@
|
||||
gidToStringIdAndNameMap[gid++] = pair;
|
||||
}
|
||||
|
||||
#if NET
|
||||
gidToStringIdAndNameMap.TrimExcess();
|
||||
#endif
|
||||
|
||||
glyphIdToStringIdAndName = gidToStringIdAndNameMap;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
/// <summary>
|
||||
/// The font matrix for this font.
|
||||
/// </summary>
|
||||
public TransformationMatrix FontMatrix => TopDictionary.FontMatrix.HasValue ? TopDictionary.FontMatrix.Value : TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);
|
||||
public TransformationMatrix FontMatrix => TopDictionary.FontMatrix ?? TransformationMatrix.FromValues(0.001, 0, 0, 0.001, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// The value of Weight from the top dictionary or <see langword="null"/>.
|
||||
@@ -219,22 +219,26 @@
|
||||
|
||||
public override TransformationMatrix? GetFontMatrix(string characterName)
|
||||
{
|
||||
// BobLd: It seems PdfBox just returns TopDictionary.FontMatrix
|
||||
// But see https://bugs.ghostscript.com/show_bug.cgi?id=690724
|
||||
// and https://github.com/veraPDF/veraPDF-library/issues/1010
|
||||
// TODO - We might need to multiply both matrices together
|
||||
bool hasDictionary = TryGetFontDictionaryForCharacter(characterName, out var dictionary);
|
||||
|
||||
if (TopDictionary.FontMatrix is not null)
|
||||
if (TopDictionary.FontMatrix.HasValue &&
|
||||
hasDictionary &&
|
||||
dictionary.FontMatrix.HasValue)
|
||||
{
|
||||
return TopDictionary.FontMatrix.Value.Multiply(dictionary.FontMatrix.Value);
|
||||
}
|
||||
|
||||
if (TopDictionary.FontMatrix.HasValue)
|
||||
{
|
||||
return TopDictionary.FontMatrix;
|
||||
}
|
||||
|
||||
if (!TryGetFontDictionaryForCharacter(characterName, out var dictionary))
|
||||
if (hasDictionary && dictionary.FontMatrix.HasValue)
|
||||
{
|
||||
return null;
|
||||
return dictionary.FontMatrix;
|
||||
}
|
||||
|
||||
return dictionary.FontMatrix;
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool TryGetPrivateDictionaryForCharacter(string characterName, out CompactFontFormatPrivateDictionary dictionary)
|
||||
|
||||
16
src/UglyToad.PdfPig.Fonts/SystemFonts/IOSSystemFontLister.cs
Normal file
16
src/UglyToad.PdfPig.Fonts/SystemFonts/IOSSystemFontLister.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
namespace UglyToad.PdfPig.Fonts.SystemFonts
|
||||
{
|
||||
#if NET
|
||||
using System.Collections.Generic;
|
||||
|
||||
internal sealed class IOSSystemFontLister : ISystemFontLister
|
||||
{
|
||||
// Very early version, intended to help developing support for iOS
|
||||
|
||||
public IEnumerable<SystemFontRecord> GetAllFonts()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -102,6 +102,10 @@ namespace UglyToad.PdfPig.Fonts.SystemFonts
|
||||
{
|
||||
lister = new MacSystemFontLister();
|
||||
}
|
||||
else if (OperatingSystem.IsIOS())
|
||||
{
|
||||
lister = new IOSSystemFontLister();
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
@@ -113,7 +117,7 @@ namespace UglyToad.PdfPig.Fonts.SystemFonts
|
||||
#error Missing ISystemFontLister for target framework
|
||||
#endif
|
||||
|
||||
AvailableFonts = new Lazy<IReadOnlyList<SystemFontRecord>>(() => lister.GetAllFonts().ToList());
|
||||
AvailableFonts = new Lazy<IReadOnlyList<SystemFontRecord>>(() => lister.GetAllFonts().ToArray());
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, string> nameToFileNameMap = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Tables;
|
||||
using UglyToad.PdfPig.Fonts.CompactFontFormat;
|
||||
|
||||
/// <summary>
|
||||
/// Parses TrueType fonts.
|
||||
@@ -59,7 +60,36 @@
|
||||
|
||||
private static TrueTypeFont ParseTables(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tables, TrueTypeDataBytes data)
|
||||
{
|
||||
var isPostScript = tables.ContainsKey(TrueTypeHeaderTable.Cff);
|
||||
bool isPostScript = false;
|
||||
CompactFontFormatFontCollection? cffFontCollection = null;
|
||||
|
||||
if (tables.TryGetValue(TrueTypeHeaderTable.Cff, out var cffTable))
|
||||
{
|
||||
isPostScript = true;
|
||||
try
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
data.Seek(cffTable.Offset);
|
||||
var buffer = data.ReadByteArray((int)cffTable.Length);
|
||||
cffFontCollection = CompactFontFormatParser.Parse(new CompactFontFormatData(buffer));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e);
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
var builder = new TableRegister.Builder();
|
||||
|
||||
@@ -102,7 +132,7 @@
|
||||
{
|
||||
builder.Os2Table = TableParser.Parse<Os2Table>(os2Table, data, builder);
|
||||
}
|
||||
|
||||
|
||||
if (!isPostScript)
|
||||
{
|
||||
if (!tables.TryGetValue(TrueTypeHeaderTable.Loca, out var indexToLocationHeaderTable))
|
||||
@@ -121,11 +151,11 @@
|
||||
|
||||
// glyf
|
||||
builder.GlyphDataTable = GlyphDataTable.Load(data, glyphHeaderTable, builder);
|
||||
|
||||
OptionallyParseTables(tables, data, builder);
|
||||
}
|
||||
|
||||
return new TrueTypeFont(version, tables, builder.Build());
|
||||
OptionallyParseTables(tables, data, builder);
|
||||
|
||||
return new TrueTypeFont(version, tables, builder.Build(), cffFontCollection);
|
||||
}
|
||||
|
||||
internal static NameTable GetNameTable(TrueTypeDataBytes data)
|
||||
@@ -134,7 +164,7 @@
|
||||
{
|
||||
throw new ArgumentNullException(nameof(data));
|
||||
}
|
||||
|
||||
|
||||
// Read these data points to move to the correct data location.
|
||||
data.Read32Fixed();
|
||||
int numberOfTables = data.ReadUnsignedShort();
|
||||
|
||||
@@ -121,14 +121,23 @@
|
||||
|
||||
for (var i = 0; i < glyphCount; i++)
|
||||
{
|
||||
if (offsets[i + 1] <= offsets[i])
|
||||
var offset = offsets[i];
|
||||
|
||||
if (offsets[i + 1] <= offset)
|
||||
{
|
||||
// empty glyph
|
||||
result[i] = emptyGlyph;
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Seek(offsets[i]);
|
||||
// Invalid table, just sub in the empty glyph
|
||||
if (offset >= data.Length)
|
||||
{
|
||||
result[i] = emptyGlyph;
|
||||
continue;
|
||||
}
|
||||
|
||||
data.Seek(offset);
|
||||
|
||||
var contourCount = data.ReadSignedShort();
|
||||
|
||||
@@ -231,9 +240,15 @@
|
||||
flags = (CompositeGlyphFlags)data.ReadUnsignedShort();
|
||||
var glyphIndex = data.ReadUnsignedShort();
|
||||
|
||||
var childGlyph = glyphs[glyphIndex];
|
||||
if (glyphIndex >= glyphs.Length)
|
||||
{
|
||||
// Unsure why this happens but fixes #1213
|
||||
continue; // TODO - Is there a better fix?
|
||||
}
|
||||
|
||||
if (childGlyph == null)
|
||||
IGlyphDescription? childGlyph = glyphs[glyphIndex];
|
||||
|
||||
if (childGlyph is null)
|
||||
{
|
||||
if (!compositeLocations.TryGetValue(glyphIndex, out var missingComposite))
|
||||
{
|
||||
@@ -310,7 +325,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
builderGlyph = builderGlyph ?? emptyGlyph;
|
||||
builderGlyph ??= emptyGlyph;
|
||||
|
||||
return new Glyph(false, builderGlyph.Instructions, builderGlyph.EndPointsOfContours, builderGlyph.Points, compositeLocation.Bounds);
|
||||
}
|
||||
@@ -329,7 +344,15 @@
|
||||
|
||||
for (int j = 0; j < numberOfRepeats; j++)
|
||||
{
|
||||
result[i + j + 1] = result[i];
|
||||
int p = i + j + 1;
|
||||
if (p >= result.Length)
|
||||
{
|
||||
// Unsure why this happens but fixes #1199
|
||||
// TODO - Is there a better fix?
|
||||
break;
|
||||
}
|
||||
|
||||
result[p] = result[i];
|
||||
}
|
||||
|
||||
i += numberOfRepeats;
|
||||
@@ -409,7 +432,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private class CompositeComponent
|
||||
private sealed class CompositeComponent
|
||||
{
|
||||
public int Index { get; }
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using Core;
|
||||
using Parser;
|
||||
using Tables.CMapSubTables;
|
||||
using UglyToad.PdfPig.Fonts.CompactFontFormat;
|
||||
|
||||
/// <summary>
|
||||
/// A TrueType font.
|
||||
@@ -54,17 +55,33 @@
|
||||
/// </summary>
|
||||
public int NumberOfTables { get; }
|
||||
|
||||
// TODO - It would be better to use 'PdfCidCompactFontFormatFont' but the class is not accessible from here.
|
||||
private readonly CompactFontFormatFontCollection? cffFontCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TrueTypeFont"/>.
|
||||
/// </summary>
|
||||
internal TrueTypeFont(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister)
|
||||
internal TrueTypeFont(float version, IReadOnlyDictionary<string, TrueTypeHeaderTable> tableHeaders, TableRegister tableRegister, CompactFontFormatFontCollection? cffFontCollection)
|
||||
{
|
||||
Version = version;
|
||||
TableHeaders = tableHeaders ?? throw new ArgumentNullException(nameof(tableHeaders));
|
||||
TableRegister = tableRegister ?? throw new ArgumentNullException(nameof(tableRegister));
|
||||
NumberOfTables = tableHeaders.Count;
|
||||
|
||||
if (TableRegister.CMapTable != null)
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
this.cffFontCollection = cffFontCollection;
|
||||
|
||||
if (TableRegister.CMapTable is not null)
|
||||
{
|
||||
const int encodingSymbol = 0;
|
||||
const int encodingUnicode = 1;
|
||||
@@ -72,19 +89,19 @@
|
||||
|
||||
foreach (var subTable in TableRegister.CMapTable.SubTables)
|
||||
{
|
||||
if (WindowsSymbolCMap == null
|
||||
if (WindowsSymbolCMap is null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Windows
|
||||
&& subTable.EncodingId == encodingSymbol)
|
||||
{
|
||||
WindowsSymbolCMap = subTable;
|
||||
}
|
||||
else if (WindowsUnicodeCMap == null
|
||||
else if (WindowsUnicodeCMap is null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Windows
|
||||
&& subTable.EncodingId == encodingUnicode)
|
||||
{
|
||||
WindowsUnicodeCMap = subTable;
|
||||
}
|
||||
else if (MacRomanCMap == null
|
||||
else if (MacRomanCMap is null
|
||||
&& subTable.PlatformId == TrueTypeCMapPlatform.Macintosh
|
||||
&& subTable.EncodingId == encodingMacRoman)
|
||||
{
|
||||
@@ -107,8 +124,36 @@
|
||||
{
|
||||
boundingBox = default(PdfRectangle);
|
||||
|
||||
if (TableRegister.GlyphTable == null)
|
||||
if (TableRegister.GlyphTable is null)
|
||||
{
|
||||
if (cffFontCollection is not null)
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
var name = cffFontCollection.FirstFont.GetCharacterName(characterCode, true); // TODO cid?
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var bbox = cffFontCollection.FirstFont.GetCharacterBoundingBox(name);
|
||||
if (bbox.HasValue)
|
||||
{
|
||||
boundingBox = bbox.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -143,8 +188,30 @@
|
||||
{
|
||||
path = null;
|
||||
|
||||
if (TableRegister.GlyphTable == null)
|
||||
if (TableRegister.GlyphTable is null)
|
||||
{
|
||||
if (cffFontCollection is not null)
|
||||
{
|
||||
/*
|
||||
* The presence of a CFF (Compact Font Format) table in a TrueType font creates a hybrid situation where the font
|
||||
* container uses TrueType structure but contains PostScript-based glyph descriptions. According to the OpenType
|
||||
* specification, when a TrueType font contains a CFF table instead of a traditional glyf table, it indicates
|
||||
* "an OpenType font with PostScript outlines". This creates what's known as an OpenType CFF font, which uses
|
||||
* PostScript Type 2 charstrings for glyph descriptions rather than TrueType quadratic curves.
|
||||
*
|
||||
* This is to fix P2P-33713919.pdf
|
||||
* See https://github.com/BobLd/PdfPig.Rendering.Skia/issues/46
|
||||
* TODO - Add test coverage and need to review if the logic belongs here
|
||||
*/
|
||||
|
||||
var name = cffFontCollection.FirstFont.GetCharacterName(characterCode, true);
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return cffFontCollection.FirstFont.TryGetPath(name, out path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -188,7 +255,7 @@
|
||||
{
|
||||
width = 0;
|
||||
|
||||
if (TableRegister.HorizontalMetricsTable == null)
|
||||
if (TableRegister.HorizontalMetricsTable is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -210,7 +277,7 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TableRegister.CMapTable == null)
|
||||
if (TableRegister.CMapTable is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -18,16 +18,19 @@
|
||||
|
||||
private const int PfbFileIndicator = 0x80;
|
||||
|
||||
private static readonly char[] Separators = [' '];
|
||||
|
||||
private static readonly Type1EncryptedPortionParser EncryptedPortionParser = new Type1EncryptedPortionParser();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Parses an embedded Adobe Type 1 font file.
|
||||
/// </summary>
|
||||
/// <param name="inputBytes">The bytes of the font program.</param>
|
||||
/// <param name="length1">The length in bytes of the clear text portion of the font program.</param>
|
||||
/// <param name="length2">The length in bytes of the encrypted portion of the font program.</param>
|
||||
/// <param name="stackDepthGuard"></param>
|
||||
/// <returns>The parsed type 1 font.</returns>
|
||||
public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2)
|
||||
public static Type1Font Parse(IInputBytes inputBytes, int length1, int length2, StackDepthGuard stackDepthGuard)
|
||||
{
|
||||
// Sometimes the entire PFB file including the header bytes can be included which prevents parsing in the normal way.
|
||||
var isEntirePfbFile = inputBytes.Peek() == PfbFileIndicator;
|
||||
@@ -42,15 +45,15 @@
|
||||
inputBytes = new MemoryInputBytes(ascii);
|
||||
}
|
||||
|
||||
var scanner = new CoreTokenScanner(inputBytes, false);
|
||||
var scanner = new CoreTokenScanner(inputBytes, false, stackDepthGuard);
|
||||
|
||||
if (!scanner.TryReadToken(out CommentToken comment) || !comment.Data.StartsWith("!"))
|
||||
{
|
||||
throw new InvalidFontFormatException("The Type1 program did not start with '%!'.");
|
||||
}
|
||||
|
||||
|
||||
string name;
|
||||
var parts = comment.Data.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var parts = comment.Data.Split(Separators, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
name = parts[1];
|
||||
@@ -60,11 +63,9 @@
|
||||
name = "Unknown";
|
||||
}
|
||||
|
||||
var comments = new List<string>();
|
||||
|
||||
while (scanner.MoveNext() && scanner.CurrentToken is CommentToken commentToken)
|
||||
while (scanner.MoveNext() && scanner.CurrentToken is CommentToken)
|
||||
{
|
||||
comments.Add(commentToken.Data);
|
||||
// We ignore comments
|
||||
}
|
||||
|
||||
var dictionaries = new List<DictionaryToken>();
|
||||
@@ -441,7 +442,7 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
private class PreviousTokenSet
|
||||
private sealed class PreviousTokenSet
|
||||
{
|
||||
private readonly IToken[] tokens = new IToken[3];
|
||||
|
||||
|
||||
@@ -62,6 +62,11 @@
|
||||
/// </summary>
|
||||
public PdfRectangle? GetCharacterBoundingBox(string characterName)
|
||||
{
|
||||
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var glyph = GetCharacterPath(characterName);
|
||||
return PdfSubpath.GetBoundingRectangle(glyph);
|
||||
}
|
||||
@@ -94,8 +99,13 @@
|
||||
/// <summary>
|
||||
/// Get the pdfpath for the character with the given name.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PdfSubpath> GetCharacterPath(string characterName)
|
||||
public IReadOnlyList<PdfSubpath>? GetCharacterPath(string characterName)
|
||||
{
|
||||
if (string.Equals(characterName, GlyphList.NotDefined, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!CharStrings.TryGenerate(characterName, out var glyph))
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -1,40 +1,41 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.11-alpha001</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\AdobeFontMetrics\*" />
|
||||
<None Remove="Resources\GlyphList\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\AdobeFontMetrics\MustRead.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\AdobeFontMetrics\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\GlyphList\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Version>0.1.14</Version>
|
||||
<IsTestProject>False</IsTestProject>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
<SignAssembly>true</SignAssembly>
|
||||
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
|
||||
<Nullable>annotations</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\AdobeFontMetrics\*" />
|
||||
<None Remove="Resources\GlyphList\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Resources\AdobeFontMetrics\MustRead.html">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\AdobeFontMetrics\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\GlyphList\*" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj" />
|
||||
<ProjectReference Include="..\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
|
||||
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
|
||||
<PackageReference Include="System.Memory" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,6 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Tests.ContentStream
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using System.Globalization;
|
||||
|
||||
public class IndirectReferenceTests
|
||||
{
|
||||
@@ -33,50 +34,59 @@
|
||||
[Fact]
|
||||
public void IndirectReferenceHashTest()
|
||||
{
|
||||
var reference0 = new IndirectReference(1574, 690);
|
||||
Assert.Equal(1574, reference0.ObjectNumber);
|
||||
Assert.Equal(690, reference0.Generation);
|
||||
CultureInfo lastCulture = CultureInfo.CurrentCulture;
|
||||
CultureInfo.CurrentCulture = new CultureInfo("en-US");
|
||||
try
|
||||
{
|
||||
var reference0 = new IndirectReference(1574, 690);
|
||||
Assert.Equal(1574, reference0.ObjectNumber);
|
||||
Assert.Equal(690, reference0.Generation);
|
||||
|
||||
var reference1 = new IndirectReference(-1574, 690);
|
||||
Assert.Equal(-1574, reference1.ObjectNumber);
|
||||
Assert.Equal(690, reference1.Generation);
|
||||
var reference1 = new IndirectReference(-1574, 690);
|
||||
Assert.Equal(-1574, reference1.ObjectNumber);
|
||||
Assert.Equal(690, reference1.Generation);
|
||||
|
||||
var reference2 = new IndirectReference(58949797283757, 16);
|
||||
Assert.Equal(58949797283757, reference2.ObjectNumber);
|
||||
Assert.Equal(16, reference2.Generation);
|
||||
var reference2 = new IndirectReference(58949797283757, 16);
|
||||
Assert.Equal(58949797283757, reference2.ObjectNumber);
|
||||
Assert.Equal(16, reference2.Generation);
|
||||
|
||||
var reference3 = new IndirectReference(-58949797283757, ushort.MaxValue);
|
||||
Assert.Equal(-58949797283757, reference3.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference3.Generation);
|
||||
var reference3 = new IndirectReference(-58949797283757, ushort.MaxValue);
|
||||
Assert.Equal(-58949797283757, reference3.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference3.Generation);
|
||||
|
||||
var reference4 = new IndirectReference(140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(140737488355327, reference4.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference4.Generation);
|
||||
var reference4 = new IndirectReference(140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(140737488355327, reference4.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference4.Generation);
|
||||
|
||||
var reference5 = new IndirectReference(-140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(-140737488355327, reference5.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference5.Generation);
|
||||
var reference5 = new IndirectReference(-140737488355327, ushort.MaxValue);
|
||||
Assert.Equal(-140737488355327, reference5.ObjectNumber);
|
||||
Assert.Equal(ushort.MaxValue, reference5.Generation);
|
||||
|
||||
var ex0 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex0.Message);
|
||||
var ex1 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(-140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex1.Message);
|
||||
|
||||
var ex2 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(1574, -1));
|
||||
Assert.StartsWith("Generation number must not be a negative value.", ex2.Message);
|
||||
|
||||
// We make sure object number is still correct even if generation is not
|
||||
var reference6 = new IndirectReference(1574, int.MaxValue);
|
||||
Assert.Equal(1574, reference6.ObjectNumber);
|
||||
|
||||
var reference7 = new IndirectReference(-1574, ushort.MaxValue + 10);
|
||||
Assert.Equal(-1574, reference7.ObjectNumber);
|
||||
var ex0 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex0.Message);
|
||||
var ex1 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(-140737488355328, 0));
|
||||
Assert.StartsWith("Object number must be between -140,737,488,355,327 and 140,737,488,355,327.", ex1.Message);
|
||||
|
||||
var reference9 = new IndirectReference(-140737488355327, ushort.MaxValue + 10);
|
||||
Assert.Equal(-140737488355327, reference9.ObjectNumber);
|
||||
var ex2 = Assert.Throws<ArgumentOutOfRangeException>(() => new IndirectReference(1574, -1));
|
||||
Assert.StartsWith("Generation number must not be a negative value.", ex2.Message);
|
||||
|
||||
var reference10 = new IndirectReference(140737488355327, ushort.MaxValue * 10);
|
||||
Assert.Equal(140737488355327, reference10.ObjectNumber);
|
||||
// We make sure object number is still correct even if generation is not
|
||||
var reference6 = new IndirectReference(1574, int.MaxValue);
|
||||
Assert.Equal(1574, reference6.ObjectNumber);
|
||||
|
||||
var reference7 = new IndirectReference(-1574, ushort.MaxValue + 10);
|
||||
Assert.Equal(-1574, reference7.ObjectNumber);
|
||||
|
||||
var reference9 = new IndirectReference(-140737488355327, ushort.MaxValue + 10);
|
||||
Assert.Equal(-140737488355327, reference9.ObjectNumber);
|
||||
|
||||
var reference10 = new IndirectReference(140737488355327, ushort.MaxValue * 10);
|
||||
Assert.Equal(140737488355327, reference10.ObjectNumber);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CultureInfo.CurrentCulture = lastCulture;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -2,16 +2,23 @@
|
||||
{
|
||||
internal static class DlaHelper
|
||||
{
|
||||
private static readonly string DlaFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents"));
|
||||
private static readonly string IntegrationFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents"));
|
||||
|
||||
public static string GetDocumentPath(string name, bool isPdf = true)
|
||||
{
|
||||
var documentFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents"));
|
||||
|
||||
if (!name.EndsWith(".pdf") && isPdf)
|
||||
{
|
||||
name += ".pdf";
|
||||
}
|
||||
|
||||
return Path.Combine(documentFolder, name);
|
||||
string doc = Path.Combine(DlaFolder, name);
|
||||
if (File.Exists(doc))
|
||||
{
|
||||
return doc;
|
||||
}
|
||||
|
||||
return Path.Combine(IntegrationFolder, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
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. • 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. 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 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. 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. 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.
|
||||
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/caly-issues-56-1.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/caly-issues-56-1.pdf
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/caly-issues-58-2.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/caly-issues-58-2.pdf
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/fseprd1102849.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Dla/Documents/fseprd1102849.pdf
Normal file
Binary file not shown.
@@ -4,19 +4,84 @@
|
||||
|
||||
public class NearestNeighbourWordExtractorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Words2559Doc()
|
||||
public static IEnumerable<object[]> DataWords => new[]
|
||||
{
|
||||
// Microsoft Word count of words = 2559
|
||||
new object[]
|
||||
{
|
||||
"2559 words.pdf",
|
||||
5118,
|
||||
2559
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"fseprd1102849.pdf",
|
||||
12903,
|
||||
11177
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"90 180 270 rotated.pdf",
|
||||
589,
|
||||
292
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"complex rotated.pdf",
|
||||
805,
|
||||
403
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no horizontal distance.pdf",
|
||||
4,
|
||||
2
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical distance.pdf",
|
||||
22,
|
||||
10
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"no vertical horizontal distance.pdf",
|
||||
4,
|
||||
2
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"Random 2 Columns Lists Hyph - Justified.pdf",
|
||||
1191,
|
||||
607
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"caly-issues-56-1.pdf",
|
||||
184,
|
||||
156
|
||||
},
|
||||
new object[]
|
||||
{
|
||||
"caly-issues-58-2.pdf",
|
||||
49,
|
||||
49
|
||||
},
|
||||
};
|
||||
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath("2559 words.pdf")))
|
||||
[SkippableTheory]
|
||||
[MemberData(nameof(DataWords))]
|
||||
public void WordCount(string path, int wordCount, int noSpacesWordCount)
|
||||
{
|
||||
using (var document = PdfDocument.Open(DlaHelper.GetDocumentPath(path)))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters).ToArray();
|
||||
|
||||
Assert.Equal(wordCount, words.Length);
|
||||
|
||||
var noSpacesWords = words.Where(x => !string.IsNullOrEmpty(x.Text.Trim())).ToArray();
|
||||
|
||||
Assert.Equal(2559, noSpacesWords.Length);
|
||||
Assert.Equal(noSpacesWordCount, noSpacesWords.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
namespace UglyToad.PdfPig.Tests.Dla
|
||||
{
|
||||
using System;
|
||||
using PdfFonts;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UglyToad.PdfPig.Content;
|
||||
using UglyToad.PdfPig.Core;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis;
|
||||
using UglyToad.PdfPig.DocumentLayoutAnalysis.ReadingOrderDetector;
|
||||
using UglyToad.PdfPig.Core;
|
||||
|
||||
public class UnsupervisedReadingOrderTests
|
||||
{
|
||||
@@ -62,10 +60,11 @@
|
||||
private static TextBlock CreateFakeTextBlock(PdfRectangle boundingBox)
|
||||
{
|
||||
var letter = new Letter("a",
|
||||
boundingBox,
|
||||
boundingBox,
|
||||
boundingBox.BottomLeft,
|
||||
boundingBox.BottomRight,
|
||||
10, 1, null, TextRenderingMode.NeitherClip, null, null, 0, 0);// These don't matter
|
||||
10, 1, (FontDetails)null, TextRenderingMode.NeitherClip, null, null, 0, 0);// These don't matter
|
||||
var leftTextBlock = new TextBlock(new[] { new TextLine(new[] { new Word(new[] { letter }) }) });
|
||||
return leftTextBlock;
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
{
|
||||
var parser = new CodespaceRangeParser();
|
||||
var byteArrayInput = new MemoryInputBytes(OtherEncodings.StringAsLatin1Bytes("1 begincodespacerange\nendcodespacerange"));
|
||||
var tokenScanner = new CoreTokenScanner(byteArrayInput, false);
|
||||
var tokenScanner = new CoreTokenScanner(byteArrayInput, false, new StackDepthGuard(256));
|
||||
|
||||
Assert.True(tokenScanner.MoveNext());
|
||||
Assert.True(tokenScanner.CurrentToken is NumericToken);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.Encodings
|
||||
{
|
||||
using System.Text;
|
||||
using PdfPig.Fonts;
|
||||
|
||||
public class GlyphListTests
|
||||
@@ -92,7 +93,7 @@
|
||||
}
|
||||
|
||||
|
||||
[Fact(Skip = "TODO - String don't match")]
|
||||
[Fact]
|
||||
public void NameToUnicodeConvertAglSpecification()
|
||||
{
|
||||
// https://github.com/adobe-type-tools/agl-specification?tab=readme-ov-file#3-examples
|
||||
@@ -103,7 +104,22 @@
|
||||
|
||||
var result = list.NameToUnicode("Lcommaaccent_uni20AC0308_u1040C.alternate");
|
||||
|
||||
Assert.Equal("\u013B\u20AC\u0308\u1040C", result);
|
||||
// This value is not encodable in single characters in UTF-16, so we get a surrogate pair for the final unicode character
|
||||
Assert.Equal("\u013B\u20AC\u0308\uD801\uDC0C", result);
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
// But in .Net we can get the unicode rune values to verify that this is really the expected value
|
||||
var runes = result.EnumerateRunes().ToList();
|
||||
|
||||
Assert.Equal(4, runes.Count);
|
||||
|
||||
Assert.Equal(0x013B, runes[0].Value);
|
||||
Assert.Equal(0x20AC, runes[1].Value);
|
||||
Assert.Equal(0x0308, runes[2].Value);
|
||||
Assert.Equal(0x1040C, runes[3].Value);
|
||||
#endif
|
||||
// Ok, so we know this is what the actual string is. Now lets encode that last value the C# way
|
||||
Assert.Equal("\u013B\u20AC\u0308\U0001040C", result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using UglyToad.PdfPig.Tests.Dla;
|
||||
namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
{
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Geometry;
|
||||
|
||||
public class Linux
|
||||
{
|
||||
@@ -68,7 +69,10 @@ namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
|
||||
Assert.Equal(expectedData.TopLeft.Y, current.GlyphRectangle.TopLeft.Y, 6);
|
||||
Assert.Equal(expectedData.Width, current.GlyphRectangle.Width, 6);
|
||||
Assert.Equal(expectedData.Height, current.GlyphRectangle.Height, 6);
|
||||
Assert.Equal(expectedData.Rotation, current.GlyphRectangle.Rotation, 3);
|
||||
Assert.Equal(expectedData.Rotation, current.GlyphRectangle.Rotation, 3);
|
||||
|
||||
Assert.True(current.GlyphRectangle.IntersectsWith(current.GlyphRectangleLoose));
|
||||
Assert.Equal(current.GlyphRectangle.Rotation, current.GlyphRectangleLoose.Rotation, 3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,5 +229,19 @@ namespace UglyToad.PdfPig.Tests.Fonts.TrueType.Parser
|
||||
Assert.NotNull(font.TableRegister.NameTable);
|
||||
Assert.NotEmpty(font.TableRegister.NameTable.NameRecords);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse12623CorruptFileAndGetGlyphs()
|
||||
{
|
||||
var bytes = TrueTypeTestHelper.GetFileBytes("corrupt-12623");
|
||||
|
||||
var input = new TrueTypeDataBytes(new MemoryInputBytes(bytes));
|
||||
|
||||
var font = TrueTypeFontParser.Parse(input);
|
||||
|
||||
Assert.NotNull(font);
|
||||
|
||||
font.TryGetPath(1, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/UglyToad.PdfPig.Tests/Fonts/TrueType/corrupt-12623.ttf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Fonts/TrueType/corrupt-12623.ttf
Normal file
Binary file not shown.
@@ -11,7 +11,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("AdobeUtopia.pfa");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -20,7 +20,7 @@
|
||||
// TODO: support reading in these pfb files
|
||||
var bytes = GetFileBytes("Raleway-Black.pfb");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -28,7 +28,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("CMBX10.pfa");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -36,7 +36,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("CMCSC10");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -44,7 +44,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("CMBX12");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -52,7 +52,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("CMBX10");
|
||||
|
||||
var result = Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
var result = Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
|
||||
var builder = new StringBuilder("<!DOCTYPE html><html><head></head><body>");
|
||||
foreach (var charString in result.CharStrings.CharStrings)
|
||||
@@ -71,7 +71,7 @@
|
||||
{
|
||||
var bytes = GetFileBytes("CMR10");
|
||||
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0);
|
||||
Type1FontParser.Parse(new MemoryInputBytes(bytes), 0, 0, new StackDepthGuard(256));
|
||||
}
|
||||
|
||||
private static byte[] GetFileBytes(string name)
|
||||
|
||||
@@ -270,10 +270,10 @@
|
||||
GetCurrentState().FontState.WordSpacing = spacing;
|
||||
}
|
||||
|
||||
public void ModifyCurrentTransformationMatrix(double[] value)
|
||||
public void ModifyCurrentTransformationMatrix(TransformationMatrix value)
|
||||
{
|
||||
var ctm = GetCurrentState().CurrentTransformationMatrix;
|
||||
GetCurrentState().CurrentTransformationMatrix = TransformationMatrix.FromArray(value).Multiply(ctm);
|
||||
var state = GetCurrentState();
|
||||
state.CurrentTransformationMatrix = value.Multiply(state.CurrentTransformationMatrix);
|
||||
}
|
||||
|
||||
public void SetCharacterSpacing(double spacing)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 79 KiB |
@@ -32,4 +32,18 @@ public class AccentedCharactersInBookmarksTests
|
||||
},
|
||||
nodes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReadContainerBookmarksCorrectly()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("dotnet-ai.pdf");
|
||||
|
||||
using var document = PdfDocument.Open(path);
|
||||
var isFound = document.TryGetBookmarks(out var bookmarks, false);
|
||||
Assert.True(isFound);
|
||||
Assert.True(bookmarks.Roots.Count == 3);
|
||||
isFound = document.TryGetBookmarks(out bookmarks, true);
|
||||
Assert.True(isFound);
|
||||
Assert.True(bookmarks.Roots.Count > 3);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
public class CMapLocalCachingTests
|
||||
{
|
||||
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
|
||||
private static readonly Lazy<string> DlaFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Dla", "Documents")));
|
||||
|
||||
public static object[][] DocumentsData = new object[][]
|
||||
{
|
||||
["68-1990-01_A.pdf"],
|
||||
["Type0 Font.pdf"],
|
||||
["11194059_2017-11_de_s.pdf"],
|
||||
["2108.11480.pdf"],
|
||||
["reference-2-numeric-error.pdf"],
|
||||
["MOZILLA-3136-0.pdf"],
|
||||
["FICTIF_TABLE_INDEX.pdf"],
|
||||
["Approved_Document_B__fire_safety__volume_2_-_Buildings_other_than_dwellings__2019_edition_incorporating_2020_and_2022_amendments.pdf"],
|
||||
["dotnet-ai.pdf"],
|
||||
["Old Gutnish Internet Explorer.pdf"],
|
||||
["Random 2 Columns Lists Hyph - Justified.pdf"]
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(DocumentsData))]
|
||||
public void CheckText(string documentName)
|
||||
{
|
||||
string fullPath = Path.Combine(DocumentFolder.Value, documentName);
|
||||
if (!File.Exists(fullPath))
|
||||
{
|
||||
fullPath = Path.Combine(DlaFolder.Value, documentName);
|
||||
}
|
||||
|
||||
Assert.True(File.Exists(fullPath));
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
using (var document = PdfDocument.Open(fullPath, new ParsingOptions { UseLenientParsing = true }))
|
||||
{
|
||||
for (var i = 0; i < document.NumberOfPages; i++)
|
||||
{
|
||||
var page = document.GetPage(i + 1);
|
||||
sb.Append(page.Text);
|
||||
}
|
||||
}
|
||||
|
||||
//File.WriteAllText(Path.ChangeExtension(fullPath, "txt"), sb.ToString());
|
||||
|
||||
string expected = File.ReadAllText(Path.ChangeExtension(fullPath, "txt"));
|
||||
Assert.Equal(expected, sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,5 +9,17 @@
|
||||
using var document = PdfDocument.Open(path);
|
||||
Assert.Equal(3, document.NumberOfPages);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanReadDocumentWithCircularXRef()
|
||||
{
|
||||
string path = IntegrationHelpers.GetSpecificTestDocumentPath("B17-2000-transportation-fuels.pdf");
|
||||
|
||||
// If parser can't deal with xrefs that have circular references then
|
||||
// opening the document will loop forever
|
||||
using var document = PdfDocument.Open(path);
|
||||
|
||||
Assert.Equal(1, document.NumberOfPages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/2108.11480.txt
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/2108.11480.txt
Normal file
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
TypeWeekly newspaperEditorJoe StuverFoundedOctober, 1918 (asthe BroadusIndependent), 1935(as the PowderRiver CountyExaminer), and1965 (as thePowder RiverExaminer)Headquarters119 1/2 N. ParkAve.Broadus, MT 59317United StatesPowder River ExaminerPowder River ExaminerThe Powder River Examiner, originally established in October, 1918 asthe Broadus Independent, is the only newspaper printed in Powder RiverCounty, Montana, and is located in the county seat of Broadus.The Broadus Independent was first published in Broadus, Montana inOctober, 1918, and continued until February, 1919.From March 6, 1919 until April 17, 1919, the paper was published inOlive, Montana as the Olive Branch.The Broadus Independent was published weekly from April 24, 1919until 1935.The Powder River County Examiner replaced the BroadusIndependent in 1935, beginning publication and continuing weekly until1965.In 1965 the newspaper's name was shortened to Powder RiverExaminer, and remains that today.Broadus Independent, Broadus, Montana, October, 1918-February, 1919.Olive Branch, Olive, Montana, March 6, 1919 – April 17, 1919.Broadus Independent, Broadus, Montana, April 24, 1919 – 1935.Powder River County Examiner, Broadus, Montana, 1935-1965.Powder River Examiner, Broadus, Montana, 1965-current.Joe Stuver, (current editor)Retrieved from "https://en.wikipedia.org/w/index.php?title=Powder_River_Examiner&oldid=747264669"This page was last edited on 1 November 2016, at 11:53.Text is available under the Creative Commons Attribution-ShareAlike License; additional terms may apply. By usingthis site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the WikimediaFoundation, Inc., a non-profit organization.HistoryPreceding TitlesNotable contributorsPowder River Examiner - Wikipediahttps://en.wikipedia.org/wiki/Powder_River_Examiner1 of 130/03/2018, 03:50
|
||||
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/dotnet-ai.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/dotnet-ai.pdf
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1 @@
|
||||
(Jl σ3 法海阔』咱国司被NA咿清峭阉且啊辛茹海耀表3-19车辆齿轮用钢系列牌号化学成分(质量分数)(%)国内牌号国外牌号c Si Mn p s Ni Cr Mo Al Cu Ti B 16CrMnTiH 0.13~0.18 0. \Kl -1. 20 20CrMnTiHI 20CrMnTiH2 。.80 -1. 10 1.00-1.30 20CrMnTiH3 0. 17 -0. 37军三0.0350. 04 -0. 10 0.18句0.2320CrMnTiH4 20CrMnTiH5 0. \Kl -1. 25 I.I。”1.4520CrMnTiH6 16MnCrH 16MnCr5 0. 14 -0. 20 I. 00~1.40 O.\KJ-1.20 20MnCrH 20MnCr5 0.17~0.23 1.10-1.50 1.00-1.30 0.02~ 罢王0.20运0.120.02~0.仍525MnCrH 25MnCr5 0.23~0.28 0.055 o. ro -o. so0. 80-1.10 28MnCrH 28MnCr5 0. 25 -0. 30 运0.15运0.1016CrMnBH ZF6 0. 13 -0. 18。.80-1.100.001 -18CrMnBH ZF7 0.15 -0. 40 1.00-1.30 0. 15 -0. 20军军aα丑。0. 015 -0. 035 1.00-1.30 0.α)317CrMnBH ZF7B 17Cr2Ni2H ZFI 0.15 -0.19 0.15 -0. 40 0.40~o.ro1.40~I. 70 1.40-1.70 16CrNiH 16CrNi4 0.13“0.18 0.02-0.04 0.15~0.35 0.70句1.100. 80-1. 200. 80~1.20 :;;;0.10 0.02-0. 05 19CrNiH 19CrNi5 0.16~0. 21 0. 02 -0. 035 17Cr2Ni2MoH ZFlA 0.15”0.19 0.15~0.40 o. 40 -o. ro0.015~0.035 I. 4。”I.70 1.50-1.80 0. 25 -0. 35 20CrNiMoHI 8620Hl 0.02-0.17”0.23 0.15町0.35o. ro -o. 95 0.017~0.032 0.35~0. 75 0.35”0.65 0. 15 -0. 25 20CrNiMoH2 8620田0.045 15CrMoH 0.13”0.18 0.25~0.45 0.17~0. 37 0.4。”0.70 髦。但50.8。”1.1020CrMo 0. 18 -0. 230.15 -0. 25 20CrMoH SCM420 0.17 -0. 23 0.17町0.350.55~0. \Kl 0.85句I.25 0.15”0.35 0.02-0.0击:;;;0.15 35CrMo 0. 32 -0. 40 0.40~0. 70 0.80斗100. 15 -0. 25 运0.03520CrH 0.17~0.37 0.70-1.00 运0.200.50~0.80 40Cr 0. 18 -0. 23 0.37~0.440. 80 -1.10
|
||||
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/test_a.pdf
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/test_a.pdf
Normal file
Binary file not shown.
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/test_a.png
Normal file
BIN
src/UglyToad.PdfPig.Tests/Integration/Documents/test_a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 196 KiB |
@@ -86,8 +86,8 @@
|
||||
var letter = page.Letters[l];
|
||||
var expected = DataBoldItalic[l];
|
||||
Assert.Equal((string)expected[0], letter.Value);
|
||||
Assert.Equal((bool)expected[1], letter.Font.IsBold);
|
||||
Assert.Equal((bool)expected[2], letter.Font.IsItalic);
|
||||
Assert.Equal((bool)expected[1], letter.FontDetails.IsBold);
|
||||
Assert.Equal((bool)expected[2], letter.FontDetails.IsItalic);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,332 @@
|
||||
using DocumentLayoutAnalysis.PageSegmenter;
|
||||
using DocumentLayoutAnalysis.WordExtractor;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Tokens;
|
||||
using SkiaSharp;
|
||||
using UglyToad.PdfPig.AcroForms;
|
||||
using UglyToad.PdfPig.AcroForms.Fields;
|
||||
|
||||
public class GithubIssuesTests
|
||||
{
|
||||
[Fact]
|
||||
public void Issues1250()
|
||||
{
|
||||
// Issue comes from HasFormXObjectCircularReference
|
||||
var path = IntegrationHelpers.GetDocumentPath("SPE8EF26T0545.pdf");
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
Assert.NotNull(page);
|
||||
Assert.NotEmpty(page.Letters);
|
||||
|
||||
page = document.GetPage(7);
|
||||
Assert.NotNull(page);
|
||||
Assert.NotEmpty(page.Letters);
|
||||
}
|
||||
|
||||
// Ensure still no StackOverflowException
|
||||
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("issue_671")))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
Assert.NotNull(page);
|
||||
Assert.NotEmpty(page.Letters);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issues1248()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("jtehm-melillo-2679746.pdf");
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
var font = letter.GetFont();
|
||||
|
||||
if (font?.Name?.Data.Contains("TimesLT") == true)
|
||||
{
|
||||
Assert.True(font.TryGetPath(100, out _));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issues1238()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("6.Secrets.to.Startup.Success.PDFDrive.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(159);
|
||||
Assert.NotNull(page);
|
||||
Assert.StartsWith("uct. At the longer-cycle, broader end of the spectrum are identity-level", page.Text);
|
||||
Assert.Equal(0, page.Rotation.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1217()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("stackoverflow_error.pdf");
|
||||
|
||||
var options = new ParsingOptions()
|
||||
{
|
||||
UseLenientParsing = true,
|
||||
MaxStackDepth = 100
|
||||
};
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, options));
|
||||
Assert.Equal($"Exceeded maximum nesting depth of {options.MaxStackDepth}.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1223()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("23056.PMC2132516.pdf");
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
Assert.NotNull(document);
|
||||
var firstPage = document.GetPage(1);
|
||||
Assert.NotNull(firstPage);
|
||||
Assert.Contains("The Rockefeller University Press", firstPage.Text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1213()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("GlyphDataTableReadCompositeGlyphError.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1208()
|
||||
{
|
||||
string[] files = ["Input.visible.pdf", "Input.invisible.pdf"];
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath(file);
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
Assert.True(document.TryGetForm(out AcroForm form));
|
||||
Assert.Single(form.Fields);
|
||||
Assert.Equal(AcroFieldType.Signature, form.Fields[0].FieldType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1209()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-9176-2.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
|
||||
foreach (var image in page.GetImages())
|
||||
{
|
||||
Assert.True(image.ImageDictionary.ContainsKey(NameToken.Height)); // Was missing
|
||||
Assert.True(image.ImageDictionary.ContainsKey(NameToken.Width));
|
||||
|
||||
if (image.ImageDictionary.TryGet<DictionaryToken>(NameToken.DecodeParms, out var decodeParms))
|
||||
{
|
||||
Assert.True(decodeParms.ContainsKey(NameToken.Columns)); // Was missing
|
||||
Assert.True(decodeParms.ContainsKey(NameToken.Rows));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Revert_e11dc6b()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("GHOSTSCRIPT-699488-0.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
var images = page.GetImages().ToArray();
|
||||
|
||||
Assert.Equal(9, images.Length);
|
||||
|
||||
foreach (var image in images)
|
||||
{
|
||||
if (image.ImageDictionary.TryGet(NameToken.Filter, out var token) && token is NameToken nt)
|
||||
{
|
||||
if (nt.Data.Contains("DCT"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(image.TryGetPng(out _));
|
||||
}
|
||||
|
||||
var paths = page.Paths;
|
||||
Assert.Equal(66, paths.Count);
|
||||
var letters = page.Letters;
|
||||
Assert.Equal(2685, letters.Count);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1199()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("TrueTypeTablesGlyphDataTableReadGlyphsError.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
for (int p = 1; p <= document.NumberOfPages; p++)
|
||||
{
|
||||
var page = document.GetPage(p);
|
||||
Assert.NotNull(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1183()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("test_a.pdf");
|
||||
|
||||
byte[] expected =
|
||||
[
|
||||
82, 85, 134, 255, 87, 90, 139, 255, 81, 84, 133, 255, 87, 89, 139, 255, 89, 91, 141, 255, 81, 83, 133,
|
||||
255, 84, 86, 136, 255, 84, 86, 136, 255, 70, 59, 113, 255, 69, 62, 116, 255, 75, 73, 126, 255, 45, 48,
|
||||
100, 255, 42, 48, 99, 255, 50, 55, 107, 255, 56, 59, 111, 255, 64, 66, 118, 255, 68, 63, 118, 255, 61,
|
||||
56, 111, 255, 70, 64, 120, 255, 67, 62, 117, 255, 61, 56, 111, 255, 68, 63, 118, 255, 68, 62, 118, 255,
|
||||
59, 54, 109, 255, 61, 60, 117, 255, 69, 65, 122, 255, 67, 59, 116, 255, 71, 62, 118, 255, 66, 60, 115,
|
||||
255, 47, 49, 102, 255, 40, 51, 102, 255, 35, 51, 100, 255, 70, 58, 114, 255, 68, 56, 112, 255, 76, 65,
|
||||
121, 255, 68, 58, 114, 255, 66, 58, 114, 255, 71, 64, 119, 255, 62, 56, 111, 255, 67, 62, 117, 255, 77,
|
||||
61, 118, 255, 71, 56, 113, 255, 76, 63, 119, 255, 74, 63, 118, 255, 63, 55, 108, 255, 71, 64, 116, 255,
|
||||
73, 68, 119, 255, 52, 49, 99, 255, 38, 51, 99, 255, 49, 62, 110, 255, 39, 51, 100, 255, 46, 55, 106,
|
||||
255, 50, 55, 107, 255, 63, 62, 116, 255, 67, 60, 116, 255, 71, 60, 116, 255, 67, 58, 112, 255, 68, 61,
|
||||
114, 255, 70, 67, 119, 255, 50, 50, 101, 255, 42, 47, 96, 255, 49, 59, 106, 255, 40, 54, 100, 255, 42,
|
||||
57, 103, 255, 51, 51, 102, 255, 67, 60, 112, 255, 73, 62, 114, 255, 71, 65, 117, 255, 48, 53, 103, 255,
|
||||
45, 55, 104, 255, 49, 55, 105, 255, 63, 63, 114, 255, 68, 59, 115, 255, 71, 59, 115, 255, 73, 59, 115,
|
||||
255, 74, 61, 118, 255, 66, 58, 114, 255, 50, 51, 105, 255, 39, 51, 104, 255, 34, 52, 103, 255, 64, 60,
|
||||
116, 255, 67, 64, 119, 255, 66, 66, 120, 255, 46, 49, 102, 255, 45, 51, 102, 255, 52, 61, 111, 255, 39,
|
||||
51, 99, 255, 41, 54, 102, 255, 42, 54, 100, 255, 43, 53, 99, 255, 47, 55, 103, 255, 51, 56, 104, 255,
|
||||
56, 57, 108, 255, 67, 65, 117, 255, 67, 63, 116, 255, 52, 47, 100, 255, 44, 55, 106, 255, 44, 56, 106,
|
||||
255, 42, 54, 103, 255, 42, 54, 102, 255, 40, 52, 100, 255, 41, 52, 99, 255, 45, 57, 103, 255, 42, 53,
|
||||
99, 255, 38, 54, 95, 255, 39, 55, 97, 255, 47, 64, 105, 255, 37, 53, 95, 255, 37, 53, 95, 255, 46, 63,
|
||||
104, 255, 39, 55, 96, 255, 42, 58, 99, 255, 41, 55, 105, 255, 45, 55, 106, 255, 46, 51, 103, 255, 51,
|
||||
51, 103, 255, 63, 61, 114, 255, 70, 68, 121, 255, 60, 60, 113, 255, 46, 48, 100, 255, 49, 51, 101, 255,
|
||||
51, 52, 103, 255, 58, 58, 109, 255, 69, 66, 119, 255, 64, 60, 113, 255, 61, 55, 109, 255, 70, 62, 118,
|
||||
255, 67, 58, 114, 255, 72, 59, 115, 255, 70, 58, 115, 255, 72, 62, 118, 255, 61, 55, 110, 255, 64, 62,
|
||||
116, 255, 65, 65, 119, 255, 47, 50, 104, 255, 52, 56, 109, 255, 39, 53, 106, 255, 41, 54, 107, 255, 40,
|
||||
50, 102, 255, 45, 51, 103, 255, 64, 66, 117, 255, 62, 61, 112, 255, 67, 63, 114, 255, 53, 47, 98, 255,
|
||||
49, 54, 101, 255, 51, 56, 104, 255, 43, 48, 95, 255, 50, 55, 102, 255, 49, 54, 102, 255, 42, 47, 94,
|
||||
255, 51, 56, 103, 255, 47, 52, 100, 255, 72, 62, 114, 255, 71, 62, 114, 255, 72, 67, 119, 255, 52, 52,
|
||||
103, 255, 44, 48, 99, 255, 48, 57, 106, 255, 39, 52, 100, 255, 43, 58, 106, 255, 43, 51, 98, 255, 44,
|
||||
52, 99, 255, 48, 57, 104, 255, 46, 55, 102, 255, 41, 50, 97, 255, 45, 55, 101, 255, 49, 59, 105, 255,
|
||||
43, 53, 100, 255, 51, 57, 106, 255, 41, 49, 98, 255, 40, 52, 100, 255, 45, 60, 107, 255, 38, 53, 101,
|
||||
255, 36, 48, 96, 255, 46, 54, 102, 255, 49, 55, 104, 255, 44, 55, 104, 255, 46, 56, 105, 255, 48, 58,
|
||||
107, 255, 41, 49, 99, 255, 43, 50, 100, 255, 52, 59, 108, 255, 50, 55, 105, 255, 50, 55, 105, 255, 43,
|
||||
54, 105, 255, 42, 51, 102, 255, 45, 53, 104, 255, 45, 49, 101, 255, 63, 63, 116, 255, 66, 63, 116, 255,
|
||||
68, 63, 117, 255, 62, 55, 109, 255, 74, 60, 120, 255, 73, 59, 119, 255, 72, 58, 119, 255, 76, 62, 122,
|
||||
255, 74, 60, 120, 255, 71, 57, 118, 255, 75, 61, 121, 255, 76, 62, 123, 255
|
||||
];
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(16);
|
||||
var images = page.GetImages().ToArray();
|
||||
|
||||
Assert.Single(images);
|
||||
|
||||
var image = images[0];
|
||||
|
||||
Assert.True(image.TryGetPng(out var bytes));
|
||||
|
||||
File.WriteAllBytes("test_a_16.png", bytes);
|
||||
|
||||
using (SKBitmap actual = SKBitmap.Decode(bytes, new SKImageInfo(431, 690, SKColorType.Bgra8888)))
|
||||
{
|
||||
var pixels = actual.GetPixelSpan();
|
||||
Assert.Equal(1189560, pixels.Length);
|
||||
Assert.Equal(expected, pixels.Slice(0, 4 * 200).ToArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1156()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("felltypes-test.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(1);
|
||||
|
||||
var letters = page.Letters;
|
||||
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(letters).ToArray();
|
||||
|
||||
var wordThe = words[0];
|
||||
Assert.Equal("THE", wordThe.Text);
|
||||
Assert.Equal(wordThe.BoundingBox.BottomLeft, new PdfPoint(x: 242.9877, y: 684.7435));
|
||||
Assert.Equal(wordThe.BoundingBox.BottomRight, new PdfPoint(x: 323.93999999999994, y: 684.7435));
|
||||
|
||||
var wordBook = words[2];
|
||||
Assert.Equal("BOOK:", wordBook.Text);
|
||||
Assert.Equal(wordBook.BoundingBox.BottomLeft, new PdfPoint(x: 280.4371, y: 652.0399));
|
||||
Assert.Equal(wordBook.BoundingBox.BottomRight, new PdfPoint(x: 405.65439999999995, y: 652.0399));
|
||||
|
||||
var wordPremeffa = words[35];
|
||||
Assert.Equal("preme\ue009a.", wordPremeffa.Text); // The 'ff' glyph is not properly parsed
|
||||
Assert.Equal(wordPremeffa.BoundingBox.BottomLeft, new PdfPoint(x: 331.16020000000003, y: 515.2256999999998));
|
||||
Assert.Equal(wordPremeffa.BoundingBox.BottomRight, new PdfPoint(x: 374.2954000000001, y: 515.2256999999998));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1148()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("P2P-33713919.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
|
||||
{
|
||||
var page = document.GetPage(2);
|
||||
|
||||
var letters = page.Letters;
|
||||
|
||||
var words = NearestNeighbourWordExtractor.Instance.GetWords(letters).ToArray();
|
||||
|
||||
var firstTableLine = words[42];
|
||||
|
||||
Assert.EndsWith("C<--,:", firstTableLine.Text); // Just to make sure we are looking at the correct line. Text might change as this is not actually correct
|
||||
|
||||
Assert.Equal(firstTableLine.BoundingBox.BottomLeft, new PdfPoint(x: 31.890118, y: 693.035685));
|
||||
Assert.Equal(firstTableLine.BoundingBox.BottomRight, new PdfPoint(x: 563.3851179999991, y: 693.035685));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1122()
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("StackOverflow_Issue_1122.pdf");
|
||||
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
|
||||
Assert.Equal("The root object in the trailer did not resolve to a readable dictionary.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Issue1096()
|
||||
{
|
||||
@@ -63,7 +386,7 @@
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("SpookyPass.pdf");
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
|
||||
Assert.Equal("Avoiding infinite recursion in ObjectLocationProvider.TryGetOffset() as 'offset' and 'reference.ObjectNumber' have the same value and opposite signs.", ex.Message);
|
||||
Assert.Equal("The root object in the trailer did not resolve to a readable dictionary.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -71,8 +394,10 @@
|
||||
{
|
||||
var path = IntegrationHelpers.GetSpecificTestDocumentPath("Hang.pdf");
|
||||
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
|
||||
Assert.Equal("The cross reference was not found.", ex.Message);
|
||||
using var doc = PdfDocument.Open(path, new ParsingOptions { UseLenientParsing = true });
|
||||
|
||||
var ex = Assert.Throws<PdfDocumentFormatException>(() => doc.GetPage(1));
|
||||
Assert.StartsWith("Could not find", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -226,7 +551,8 @@
|
||||
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
|
||||
{
|
||||
var page = document.GetPage(13);
|
||||
Assert.Throws<OverflowException>(() => DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
|
||||
// This used to fail with an overflow exception when we failed to validate the zlib encoded data
|
||||
Assert.NotNull(DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,12 +629,6 @@
|
||||
Assert.Equal(i, page.Number);
|
||||
}
|
||||
}
|
||||
|
||||
// Lenient parsing OFF
|
||||
var exception = Assert.Throws<PdfDocumentFormatException>(() =>
|
||||
PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = false }));
|
||||
|
||||
Assert.Equal("The cross references formed an infinite loop.", exception.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -415,19 +735,13 @@
|
||||
{
|
||||
var doc = IntegrationHelpers.GetDocumentPath("ErcotFacts.pdf");
|
||||
|
||||
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
|
||||
{
|
||||
var page1 = document.GetPage(1);
|
||||
Assert.Equal(1788, page1.Letters.Count);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
Assert.Equal(2430, page2.Letters.Count);
|
||||
}
|
||||
|
||||
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = false }))
|
||||
{
|
||||
var ex = Assert.Throws<ArgumentNullException>(() => document.GetPage(1));
|
||||
Assert.StartsWith("Value cannot be null.", ex.Message);
|
||||
var page1 = document.GetPage(1);
|
||||
Assert.Equal(1939, page1.Letters.Count);
|
||||
|
||||
var page2 = document.GetPage(2);
|
||||
Assert.Equal(2434, page2.Letters.Count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
using PdfPig.Geometry;
|
||||
|
||||
public class IntegrationDocumentTests
|
||||
{
|
||||
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
|
||||
@@ -11,6 +13,36 @@
|
||||
"cmap-parsing-exception.pdf"
|
||||
];
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetAllDocuments))]
|
||||
public void CheckGlyphLooseBoundingBoxes(string documentName)
|
||||
{
|
||||
// Add the full path back on, we removed it so we could see it in the test explorer.
|
||||
documentName = Path.Combine(DocumentFolder.Value, documentName);
|
||||
|
||||
using (var document = PdfDocument.Open(documentName, new ParsingOptions { UseLenientParsing = true }))
|
||||
{
|
||||
for (var i = 0; i < document.NumberOfPages; i++)
|
||||
{
|
||||
var page = document.GetPage(i + 1);
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
var bbox = letter.GlyphRectangle;
|
||||
if (bbox.Height > 0)
|
||||
{
|
||||
if (letter.GlyphRectangleLoose.Height <= 0)
|
||||
{
|
||||
_ = letter.GetFont().GetAscent();
|
||||
}
|
||||
|
||||
Assert.True(letter.GlyphRectangleLoose.Height > 0, $"Page {i + 1}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetAllDocuments))]
|
||||
public void CanReadAllPages(string documentName)
|
||||
@@ -60,13 +92,13 @@
|
||||
{
|
||||
Assert.NotNull(document.Structure.Catalog);
|
||||
|
||||
Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count > 0, "Cross reference table was empty.");
|
||||
foreach (var objectOffset in document.Structure.CrossReferenceTable.ObjectOffsets)
|
||||
{
|
||||
var token = document.Structure.GetObject(objectOffset.Key);
|
||||
//Assert.True(document.Structure.CrossReferenceTable.ObjectOffsets.Count > 0, "Cross reference table was empty.");
|
||||
//foreach (var objectOffset in document.Structure.CrossReferenceTable.ObjectOffsets)
|
||||
//{
|
||||
// var token = document.Structure.GetObject(objectOffset.Key);
|
||||
|
||||
Assert.NotNull(token);
|
||||
}
|
||||
// Assert.NotNull(token);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
namespace UglyToad.PdfPig.Tests.Integration
|
||||
{
|
||||
#if NET9_0_OR_GREATER
|
||||
using Microsoft.AspNetCore.WebUtilities;
|
||||
#endif
|
||||
|
||||
public class SinglePageLibreOfficeImages
|
||||
{
|
||||
private static string GetFilePath() => IntegrationHelpers.GetDocumentPath(@"Single Page Images - from libre office.pdf");
|
||||
@@ -17,6 +21,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
[Fact]
|
||||
public void CanUseFileBufferingReadStream()
|
||||
{
|
||||
var bytes = File.ReadAllBytes(GetFilePath());
|
||||
|
||||
using var mem = new MemoryStream(bytes);
|
||||
using var fbrs = new FileBufferingReadStream(mem, 256);
|
||||
|
||||
using var doc = PdfDocument.Open(fbrs);
|
||||
|
||||
var page = doc.GetPage(1);
|
||||
|
||||
Assert.NotEmpty(page.Text);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public void ImagesHaveCorrectDimensionsAndLocations()
|
||||
{
|
||||
|
||||
@@ -185,6 +185,20 @@ namespace UglyToad.PdfPig.Tests.Integration
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandleCorruptedFileOffsets()
|
||||
{
|
||||
var path = IntegrationHelpers.GetDocumentPath("Single Page Broken Offsets - from google drive.pdf");
|
||||
|
||||
using var doc = PdfDocument.Open(path);
|
||||
|
||||
var page = doc.GetPage(1);
|
||||
|
||||
var text = page.Text;
|
||||
|
||||
Assert.NotEmpty(text);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<AssertablePositionData> GetPdfBoxPositionData()
|
||||
{
|
||||
// X Y Width Letter FontSize Font
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,8 @@ namespace UglyToad.PdfPig.Tests.Integration
|
||||
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangle.Width != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangle.Height != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Width != 0);
|
||||
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Height != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,13 @@
|
||||
Run(Type3FontZeroHeight, 1255);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void test_a()
|
||||
{
|
||||
// Rendered glyphs are not correct, but we use the grid to assess
|
||||
Run("test_a", 1584, 1);
|
||||
}
|
||||
|
||||
private static void Run(string file, int imageHeight = 792, int pageNo = 1)
|
||||
{
|
||||
var pdfFileName = GetFilename(file);
|
||||
@@ -193,6 +200,32 @@
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
|
||||
using (var bitmap = SKBitmap.FromImage(image))
|
||||
using (var graphics = new SKCanvas(bitmap))
|
||||
{
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
DrawRectangle(letter.GlyphRectangleLoose, graphics, violetPen, imageHeight, scale);
|
||||
}
|
||||
|
||||
graphics.Flush();
|
||||
|
||||
var imageName = $"{file}_loose.jpg";
|
||||
|
||||
if (!Directory.Exists(OutputPath))
|
||||
{
|
||||
Directory.CreateDirectory(OutputPath);
|
||||
}
|
||||
|
||||
var savePath = Path.Combine(OutputPath, imageName);
|
||||
|
||||
using (var fs = new FileStream(savePath, FileMode.Create))
|
||||
using (SKData d = bitmap.Encode(SKEncodedImageFormat.Jpeg, 100))
|
||||
{
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,7 +253,11 @@
|
||||
|
||||
pdf = pdf.Replace(".pdf", ".jpg");
|
||||
|
||||
return SKImage.FromEncodedData(pdf);
|
||||
if (File.Exists(pdf))
|
||||
{
|
||||
return SKImage.FromEncodedData(pdf);
|
||||
}
|
||||
return SKImage.FromEncodedData(pdf.Replace(".jpg", ".png"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,31 @@
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
|
||||
using (var picture = document.GetPage<SKPicture>(pageNo))
|
||||
using (var image = SKImage.FromPicture(picture, size, ScaleMatrix))
|
||||
using (var bmp = SKBitmap.FromImage(image))
|
||||
using (var canvas = new SKCanvas(bmp))
|
||||
{
|
||||
Assert.NotNull(picture);
|
||||
|
||||
if (RenderGlyphRectangle)
|
||||
{
|
||||
foreach (var letter in page.Letters)
|
||||
{
|
||||
DrawRectangle(letter.GlyphRectangleLoose, canvas, redPaint, size.Height, Scale);
|
||||
}
|
||||
}
|
||||
|
||||
var imageName = $"{file}_{pageNo}_loose.png";
|
||||
var savePath = Path.Combine(OutputPath, imageName);
|
||||
|
||||
using (var fs = new FileStream(savePath, FileMode.Create))
|
||||
using (var d = bmp.Encode(SKEncodedImageFormat.Png, 100))
|
||||
{
|
||||
d.SaveTo(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
448
src/UglyToad.PdfPig.Tests/Parser/0007511-page-2.txt
Normal file
448
src/UglyToad.PdfPig.Tests/Parser/0007511-page-2.txt
Normal file
@@ -0,0 +1,448 @@
|
||||
/Span <</MCID 0 >>BDC
|
||||
0 0 0 rg
|
||||
1 i
|
||||
/RelativeColorimetric ri
|
||||
BT
|
||||
/TT0 1 Tf
|
||||
-0.0303 Tc 0.0303 Tw 12 0 0 12 54 813 Tm
|
||||
(Conference Information )Tj
|
||||
-0.0172 Tc 0.0172 Tw 36.25 0 Td
|
||||
(Page )Tj
|
||||
0 Tc 0 Tw (2)Tj
|
||||
-0.0415 Tc 0.0102 Tw ( of )Tj
|
||||
0 Tc 0 Tw 3.875 0 Td
|
||||
(3 )Tj
|
||||
ET
|
||||
EMC
|
||||
/Span <</MCID 1 >>BDC
|
||||
0.502 0 0.502 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
-0.0051 Tc 0.0329 Tw 13.5 0 0 13.5 93 746.25 Tm
|
||||
(CEU Residence and Conference Center: )Tj
|
||||
ET
|
||||
EMC
|
||||
/P <</MCID 2 >>BDC
|
||||
0.502 0 0.502 RG
|
||||
0.75 w 10 M 1 j 1 J []0 d
|
||||
93 744.75 m
|
||||
324 744.75 l
|
||||
S
|
||||
0 0 0 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
-0.0186 Tc 0.0463 Tw 13.5 0 0 13.5 327.75 746.25 Tm
|
||||
(The CEU )Tj
|
||||
-0.0024 Tc 0.0302 Tw -17.3889 -1.1111 Td
|
||||
(residential facility will available for conference )Tj
|
||||
-0.0025 Tc 0.0302 Tw 0 -1.1111 TD
|
||||
(participants. A modern facility, all rooms are air-)Tj
|
||||
0.0024 Tc 0.0184 Tw T*
|
||||
(conditioned and have private baths; some have phones )Tj
|
||||
-0.0043 Tc 0.0321 Tw T*
|
||||
(and TVs. Rates include breakfast and are $30 per night )Tj
|
||||
0 Tc 0.0227 Tw 0 -1.1111 TD
|
||||
(for singles \($60 double\) and $45 per night for room w/ )Tj
|
||||
0.0024 Tc 0.0184 Tw 0 -1.1111 TD
|
||||
(phone and TV. Guest facilities include sauna, indoor )Tj
|
||||
0.0018 Tc 0.026 Tw T*
|
||||
(swimming pool, fitness center, tennis court, and sport )Tj
|
||||
0.0046 Tc 0.0231 Tw T*
|
||||
(grounds. Contact the )Tj
|
||||
ET
|
||||
EMC
|
||||
/Span <</MCID 3 >>BDC
|
||||
0.4941 0.2824 0.8667 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
-0.0021 Tc 0.0299 Tw 13.5 0 0 13.5 219.75 626.25 Tm
|
||||
(CEU Residence and Conference )Tj
|
||||
ET
|
||||
EMC
|
||||
/P <</MCID 4 >>BDC
|
||||
0.6 0.4 1 RG
|
||||
219.75 624.75 m
|
||||
404.25 624.75 l
|
||||
S
|
||||
0 0 0 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
0 Tc 0.0092 Tw 13.5 0 0 13.5 93 611.25 Tm
|
||||
(by email \(click )Tj
|
||||
ET
|
||||
EMC
|
||||
/Span <</MCID 5 >>BDC
|
||||
0.4941 0.2824 0.8667 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
-0.0275 Tc 0 Tw 13.5 0 0 13.5 181.5 611.25 Tm
|
||||
(here)Tj
|
||||
ET
|
||||
EMC
|
||||
/P <</MCID 6 >>BDC
|
||||
181.5 609.75 m
|
||||
206.25 609.75 l
|
||||
S
|
||||
0 0 0 rg
|
||||
BT
|
||||
/TT1 1 Tf
|
||||
-0.0009 Tc 0.0176 Tw 13.5 0 0 13.5 206.25 611.25 Tm
|
||||
(\), phone \(00361-327-3155\) or Fax: )Tj
|
||||
-0.0028 Tc 0.0227 Tw -8.3889 -1.1111 Td
|
||||
(\(00361-327 3156\) for reservations. Major credit cards )Tj
|
||||
-0.0005 Tc 0.0283 Tw T*
|
||||
(accepted. Please note that the CEU Residential Facility )Tj
|
||||
-0.0006 Tc 0.0228 Tw T*
|
||||
(is not located near the CEU campus. Mini-bus service is )Tj
|
||||
0.0013 Tc 0.0265 Tw T*
|
||||
(available for travel to/from the meeting site but most )Tj
|
||||
0.0011 Tc 0.0266 Tw 0 -1.1111 TD
|
||||
(residents will need to use public transportation. Also, )Tj
|
||||
-0.0075 Tc 0.0284 Tw 0 -1.1111 TD
|
||||
(reservations will require a departure no later than )Tj
|
||||
0.0051 Tc 0.0227 Tw T*
|
||||
(Sunday, July 8th.\240)Tj
|
||||
ET
|
||||
EMC
|
||||
/Figure <</MCID 7 >>BDC
|
||||
q
|
||||
82.5 0 0 162.75 450 556.5 cm
|
||||
BI
|
||||
/W 110
|
||||
/H 217
|
||||
/BPC 1
|
||||
/CS /CS1
|
||||
ID
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?<3F><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>.D4L<34><4C><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r8<72><38><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r8<72> pr thanrdkr8<72> p<><70>thanrd<72><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r8<72> p<><70><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r<EFBFBD><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>d<EFBFBD><64>߿<EFBFBD><DFBF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r8<72> pr thanr<6E><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>hanr<6E><72><EFBFBD><EFBFBD>r8<72> prhanr<6E><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r t<><74><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r8<72> prha<61><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>߷<EFBFBD><DFB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r<EFBFBD><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>d<EFBFBD><64><EFBFBD><EFBFBD><EFBFBD><EFBFBD> prha<61><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r<EFBFBD><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>{<7B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><7F><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>r<EFBFBD><72><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
EI Q>>BDC
|
||||
q
|
||||
82.5- 0 0 162.757195 6156./Im0 Do
|
||||
QTj
|
||||
ET
|
||||
Pigure <</8CID 7 >82..42..8 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
-0.0013 T0.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
61
|
||||
q
|
||||
461
|
||||
61Tm
|
||||
(Conferenserregisbliserv: .\240)Tj
|
||||
ET
|
||||
Sper gure <</9CID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
38.0013 T016.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
205
|
||||
q
|
||||
461
|
||||
61Tm
|
||||
(Ans a dicipersidmuut regisbo lable ear onferense.\240)Tj
|
||||
ET
|
||||
Pigure <</10CID 7 >205
|
||||
q
|
||||
4590 16m
|
||||
4860 162.90 16l
|
||||
S
|
||||
BT
|
||||
/TT1 0.0f:0.0013.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
4860 16261
|
||||
61Tm
|
||||
(. than
|
||||
82.5rg
|
||||
)Tj
|
||||
50.0013 T031.028-31
|
||||
q
|
||||
Tw 0 -1.1d
|
||||
(Detailed m no ialslableregisbliservtions bequvailabletiothe earprelimina\
|
||||
ry p.\240)Tj
|
||||
ET
|
||||
Sper gure <</-1.ID 7 >BT
|
||||
/TT1 0.0f:13
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
487.2 446
|
||||
61Tm
|
||||
(r.\240)Tj
|
||||
ET
|
||||
Pigure <</12.ID 7 >4860 162.90 16m
|
||||
490.162.90 16l
|
||||
S
|
||||
BT
|
||||
/TT1 0.0f:13
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
493.10
|
||||
/ 446
|
||||
61Tm
|
||||
(ogram than )Tj
|
||||
-6.0013 T261.028-31
|
||||
972
|
||||
Tw 0 -1.1d
|
||||
(ine earSpring. Fbleplanning purposes,e earregisbliservtfee between May 1\
|
||||
and than
|
||||
0013 T277.028T*
|
||||
(June 15theis $195 \($90 studersi\)lablel norregisbliservt\(fees are to b\
|
||||
arpaid ineU.S. than )Tj
|
||||
16.0013 T251.028T*
|
||||
(dollars,edirectly bleby credit card bleo ealebank converserv\). Afbo lJu\
|
||||
ne 15thethan )Tj
|
||||
04.0013 T281.028T*
|
||||
(only bn-sinorregisbliservtis permitted at higealefees. Regisbliservtincl\
|
||||
udes 4 than )Tj
|
||||
-4.0013 T251.0280
|
||||
Tw 0 -1.1D
|
||||
(lunches duringe ear onferense.\240A limited numbaleofrregisbliservtfee g\
|
||||
rersidions than Tj
|
||||
13.0013 T265.0280
|
||||
Tw 0 -1.1D
|
||||
(bequvailabletableattendees from designated geographicalrregionst\(see be\
|
||||
low\).\240than
|
||||
0013.028T*
|
||||
([.\240)Tj
|
||||
ET
|
||||
Sper gure <</-3CID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
60.0013 T041.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
66 041
|
||||
61Tm
|
||||
(currensy converbo .\240)Tj
|
||||
ET
|
||||
Pigure <</14CID 7 >66 0390 16m
|
||||
1740 160390 16l
|
||||
S
|
||||
|
||||
82.5rg
|
||||
BT
|
||||
/TT1 0.0f:0.0013.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
1740 16041
|
||||
61Tm
|
||||
(] .\240)Tj
|
||||
ET
|
||||
Pigure <</15CID 7 >82..42..8 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj116.0013 T394.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
61
|
||||
q
|
||||
312.Tm
|
||||
(Travel arrergemersi: than
|
||||
82.5rg
|
||||
)Tj
|
||||
16.0013 T294.0289.333313.0d
|
||||
(LSA's travel agers, .\240)Tj
|
||||
ET
|
||||
Sper gure <</16.ID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
58.0013 T036.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
302
|
||||
61312.Tm
|
||||
(PionealeValley Travel.\240)Tj
|
||||
ET
|
||||
Pigure <</17.ID 7 >302
|
||||
61310.16m
|
||||
427.61310.16l
|
||||
S
|
||||
|
||||
82.5rg
|
||||
BT
|
||||
/TT1 0.0f:0Tj
|
||||
34.0013 T240.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
427.61312.Tm
|
||||
(, haidmade than )Tj
|
||||
37.0013 T265.028-27.0 -1.Tw 0 -1.1d
|
||||
(arrergemersitiothecerbaineairlines to provide specialediscounted lises t\
|
||||
o travelersethan )Tj
|
||||
08.0013 T24.028T*
|
||||
(from NortheAmo ica.t\(Call PVT at 800-245-0153\). PVT cer also handletot\
|
||||
ealethan )Tj
|
||||
-2.0013 T253.028T*
|
||||
(arrergemersit\(carrrentals, liis asses,ehotelrreserviservs,eetc.\)lable\
|
||||
those whotiosh than Tj
|
||||
12.0013 T265.028T*
|
||||
(to travel beyond Budapeut aid a deofrer extended stay. Pa dicipersidfrom\
|
||||
outside than)Tj
|
||||
-5.0013 T253.0280
|
||||
Tw 0 -1.1D
|
||||
(No.eAmo ica should beut consult localrtravel agerss. .\240)Tj
|
||||
ET
|
||||
Pigure <</18CID 7 >82..42..8 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
95.0013 T372
|
||||
02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
61
|
||||
q
|
||||
2070 16Tm
|
||||
(Travel -- Hunga iar entry requiremersi:than
|
||||
82.5rg
|
||||
|
||||
0013 T277.02817.222213.0d
|
||||
( Mout travelerseto Hunga y nead bnly a than )Tj
|
||||
12.0013 T289.028-17.22221Tw 0 -1.1d
|
||||
( assportlableentry. However, visas are requiredlablesome visitors. If yo\
|
||||
urehome than)Tj
|
||||
39.0013 T159.0280
|
||||
Tw 0 -1.1D
|
||||
(count y is nodeone eis lisbt\(click .\240)Tj
|
||||
ET
|
||||
Sper gure <</19CID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj275.0013.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
24681770 16Tm
|
||||
(eale.\240)Tj
|
||||
ET
|
||||
Pigure <</20CID 7 >2468176
|
||||
61m
|
||||
2700 16176
|
||||
61l
|
||||
S
|
||||
|
||||
82.5rg
|
||||
BT
|
||||
/TT1 0.0f:-)Tj
|
||||
-5.0013 T302.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
2700 161770 16Tm
|
||||
(\)la visadmay beqrequired. Please confirm than )Tj
|
||||
12.0013 T29.028-15
|
||||
q
|
||||
Tw 0 -1.1d
|
||||
(entry requiremersi sufficiersly ineadvanserofr earmeeting. If you are fr\
|
||||
om a than)Tj
|
||||
05.0013 T273.028T*
|
||||
(count y requiringeer officialrinvitiservtfbleentry,eplease nodifyr ear.\240)Tj
|
||||
ET
|
||||
Sper gure <</21.ID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
46.0013 T324.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
447/1470 16Tm
|
||||
(LSA Executive .\240)Tj
|
||||
ET
|
||||
Pigure <</22.ID 7 >447/146
|
||||
61m
|
||||
532
|
||||
q
|
||||
146
|
||||
61l
|
||||
S
|
||||
j
|
||||
ET
|
||||
Sper gure <</23.ID 7 >BT
|
||||
/TT1 0.0f:)Tj
|
||||
02.0013 T276.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
61
|
||||
q
|
||||
132
|
||||
16Tm
|
||||
(Office .\240)Tj
|
||||
ET
|
||||
Pigure <</24CID 7 >61
|
||||
q
|
||||
131
|
||||
61m
|
||||
100
|
||||
q
|
||||
131
|
||||
61l
|
||||
S
|
||||
|
||||
82.5rg
|
||||
BT
|
||||
/TT1 0.0f:-)Tj
|
||||
09.0013 T231.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
100
|
||||
q
|
||||
132
|
||||
16Tm
|
||||
(ofr eis requiremers.\240than9 T20513.0d
|
||||
(FblegereralrinfblmiservtrvtHunga iar Consularrand than )Tj
|
||||
1.0013 T103.028-11
|
||||
9094.Tw 0 -1.1d
|
||||
(governmental m nbo s,eclick .\240)Tj
|
||||
ET
|
||||
Sper gure <</25.ID 7 >8.494113 28242..8667 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
54.0013.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
226
|
||||
q
|
||||
1170 16Tm
|
||||
(eale. .\240)Tj
|
||||
ET
|
||||
Pigure <</26.ID 7 >226
|
||||
q
|
||||
116
|
||||
61m
|
||||
25q
|
||||
116
|
||||
61l
|
||||
S
|
||||
j
|
||||
ET
|
||||
Pigure <</27CID 7 >82..42..8 rg
|
||||
BT
|
||||
/TT1 0.0f: )Tj
|
||||
61.0013 T039.02813
|
||||
q
|
||||
82.5 3
|
||||
q
|
||||
61
|
||||
q
|
||||
88.16Tm
|
||||
(Travel and Regisbliservtgrersithan
|
||||
0013 T274.028(: than
|
||||
82.5rg
|
||||
)Tj
|
||||
13.0013 T211.02813
|
||||
6667 3.0d
|
||||
(Whiletmout onferenseeattendees muut fund eair than )Tj
|
||||
12.0013 T29.028-13
|
||||
@@ -1,10 +1,11 @@
|
||||
namespace UglyToad.PdfPig.Tests.Parser.FileStructure;
|
||||
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Parser.FileStructure;
|
||||
using PdfPig.Tokenization.Scanner;
|
||||
using PdfPig.Tokens;
|
||||
|
||||
public class XrefOffsetValidatorTests
|
||||
public class FirstPassParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void FindsTwoXrefs()
|
||||
@@ -18,7 +19,7 @@ public class XrefOffsetValidatorTests
|
||||
abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz
|
||||
endstream
|
||||
endobj
|
||||
xref0 1
|
||||
xref7 1
|
||||
0000000000 65535 f
|
||||
0000000500 00000 n
|
||||
4 0 obj
|
||||
@@ -36,17 +37,17 @@ public class XrefOffsetValidatorTests
|
||||
%%EOF
|
||||
""";
|
||||
|
||||
if (Environment.NewLine == "\n")
|
||||
{
|
||||
content = content.Replace("\n", "\r\n");
|
||||
}
|
||||
|
||||
var ib = StringBytesTestConverter.Convert(content, false);
|
||||
|
||||
var results = XrefOffsetValidator.BruteForceSearchForTables(ib.Bytes);
|
||||
var results = FirstPassParser.Parse(
|
||||
new FileHeaderOffset(0),
|
||||
ib.Bytes,
|
||||
new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
|
||||
|
||||
Assert.Contains(144, results);
|
||||
Assert.Contains(331, results);
|
||||
Assert.Equal(2, results.Parts.Count);
|
||||
Assert.NotNull(results.Trailer);
|
||||
|
||||
Assert.Equal(results.XrefOffsets[new IndirectReference(8, 0)], 500);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -108,20 +109,21 @@ public class XrefOffsetValidatorTests
|
||||
%%EOF
|
||||
""";
|
||||
|
||||
if (Environment.NewLine == "\n")
|
||||
{
|
||||
content = content.Replace("\n", "\r\n");
|
||||
}
|
||||
// Handle "\r\n" or "\n" in the sourcecode in the same way
|
||||
content = content.Replace("\r\n", "\n").Replace("\n", "\r\n");
|
||||
|
||||
var ib = StringBytesTestConverter.Convert(content, false);
|
||||
|
||||
var results = XrefOffsetValidator.BruteForceSearchForTables(ib.Bytes);
|
||||
var results = FirstPassParser.Parse(new FileHeaderOffset(0), ib.Bytes, new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
|
||||
|
||||
Assert.Contains(98, results);
|
||||
Assert.Contains(1186, results);
|
||||
var offsets = results.Parts.Select(x => x.Offset).OrderBy(x => x).ToList();
|
||||
|
||||
Assert.Equal(98, offsets[0]);
|
||||
Assert.Equal(1186, offsets[1]);
|
||||
Assert.NotNull(results.Trailer);
|
||||
|
||||
ib.Bytes.Seek(98);
|
||||
var scanner = new CoreTokenScanner(ib.Bytes, false);
|
||||
var scanner = new CoreTokenScanner(ib.Bytes, false, new StackDepthGuard(256));
|
||||
scanner.MoveNext();
|
||||
Assert.Equal(scanner.CurrentToken, OperatorToken.Xref);
|
||||
}
|
||||
@@ -0,0 +1,610 @@
|
||||
namespace UglyToad.PdfPig.Tests.Parser.FileStructure;
|
||||
|
||||
using Logging;
|
||||
using PdfPig.Core;
|
||||
using PdfPig.Parser.FileStructure;
|
||||
using PdfPig.Tokens;
|
||||
|
||||
public class XrefTableParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseSimpleXref()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref
|
||||
12 3
|
||||
0000000000 65535 f
|
||||
0000000443 00000 n
|
||||
0000000576 00000 n
|
||||
trailer
|
||||
<< /Size 323 >>
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(13, 0), 443 },
|
||||
{ new IndirectReference(14, 0), 576 },
|
||||
});
|
||||
|
||||
Assert.Equal(table.Offset, 0);
|
||||
|
||||
Assert.NotNull(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseSimpleXrefWithComments()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref
|
||||
12 2
|
||||
0000000000 65535 f % Hello
|
||||
0000000443 00000 n % comments are very bad and not allowed 0000000576 00000 n
|
||||
trailer
|
||||
<< /Size 323 >>
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(13, 0), 443 }
|
||||
});
|
||||
|
||||
Assert.Equal(table.Offset, 0);
|
||||
|
||||
Assert.NotNull(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseSimpleXrefFollowedByObject()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref
|
||||
19 3
|
||||
0000000000 65535 f
|
||||
23255 00000 n
|
||||
0000002122 00000 n
|
||||
4 0 obj
|
||||
12
|
||||
endobj
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(20, 0), 23255},
|
||||
{ new IndirectReference(21, 0), 2122},
|
||||
});
|
||||
|
||||
Assert.Equal(table.Offset, 0);
|
||||
|
||||
Assert.Null(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseXrefMissingLineBreaks()
|
||||
{
|
||||
const string input = "xref 10 2 000000 65535 f 00013772 10 n << /type /beans >>";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(11, 10), 13772 }
|
||||
});
|
||||
|
||||
Assert.Null(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseSimpleXrefMissingNewline()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref10 3
|
||||
0000000000 65535 f
|
||||
0000000443 00000 n
|
||||
0000000576 00000 n
|
||||
trailer
|
||||
<< /Type /Arg /Prev 2344 >>
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(11, 0), 443 },
|
||||
{ new IndirectReference(12, 0), 576 },
|
||||
});
|
||||
|
||||
Assert.Equal(table.Offset, 0);
|
||||
Assert.NotNull(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsePdfSpecXref()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref
|
||||
0 1
|
||||
0000000000 65535 f
|
||||
3 1
|
||||
0000025325 00000 n
|
||||
23 2
|
||||
0000025518 00002 n
|
||||
0000025635 00000 n
|
||||
30 1
|
||||
0000025777 00000 n
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
AssertObjectsMatch(table,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(3, 0), 25325 },
|
||||
{ new IndirectReference(23, 2), 25518 },
|
||||
{ new IndirectReference(24, 0), 25635 },
|
||||
{ new IndirectReference(30, 0), 25777 },
|
||||
});
|
||||
|
||||
Assert.Null(table.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseTrailerDictionaryMissingNewline()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref
|
||||
0 2
|
||||
0000000000 65535 f
|
||||
0000025325 00000 n trailer<< /Size 123>> %%EOF
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
Assert.NotNull(table.Dictionary);
|
||||
Assert.Equal(new NumericToken(123), table.Dictionary.Data["Size"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(
|
||||
"""
|
||||
wibbly290 243543
|
||||
434
|
||||
"""),
|
||||
InlineData(
|
||||
"""
|
||||
xref 0 10 trailer 33 5
|
||||
""")]
|
||||
[InlineData(
|
||||
"""
|
||||
xref 100 0
|
||||
10 5 n
|
||||
100 45 n
|
||||
xref
|
||||
trailer
|
||||
""")]
|
||||
public void ParseCorruptXrefs(string xref)
|
||||
{
|
||||
var table = GetTableForString(xref);
|
||||
|
||||
Assert.Null(table);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseTestDocumentExample()
|
||||
{
|
||||
const string input =
|
||||
"""
|
||||
xref0 40
|
||||
0000000000 65535 f
|
||||
0000000015 00000 n
|
||||
0000000085 00000 n
|
||||
0000000371 00000 n
|
||||
0000000658 00000 n
|
||||
0000000920 00000 n
|
||||
0000000969 00000 n
|
||||
0000001096 00000 n
|
||||
0000001448 00000 n
|
||||
0000002162 00000 n
|
||||
0000005207 00000 n
|
||||
0000005316 00000 n
|
||||
0000005543 00000 n
|
||||
0000056503 00000 n
|
||||
0000075543 00000 n
|
||||
0000075968 00000 n
|
||||
0000076313 00000 n
|
||||
0000077592 00000 n
|
||||
0000077721 00000 n
|
||||
0000078076 00000 n
|
||||
0000078846 00000 n
|
||||
0000082166 00000 n
|
||||
0000082275 00000 n
|
||||
0000082501 00000 n
|
||||
0000120640 00000 n
|
||||
0000122623 00000 n
|
||||
0000124952 00000 n
|
||||
0000138582 00000 n
|
||||
0000139875 00000 n
|
||||
0000141303 00000 n
|
||||
0000142686 00000 n
|
||||
0000143385 00000 n
|
||||
0000144099 00000 n
|
||||
0000144227 00000 n
|
||||
0000144584 00000 n
|
||||
0000145335 00000 n
|
||||
0000148764 00000 n
|
||||
0000148873 00000 n
|
||||
0000149022 00000 n
|
||||
0000152670 00000 n
|
||||
trailer
|
||||
<<
|
||||
/Root 5 0 R
|
||||
/Size 40
|
||||
>>
|
||||
startxref
|
||||
174834
|
||||
%%EOF
|
||||
""";
|
||||
|
||||
var table = GetTableForString(input);
|
||||
|
||||
Assert.NotNull(table);
|
||||
|
||||
Assert.Equal(39, table.ObjectOffsets.Count);
|
||||
}
|
||||
[Fact]
|
||||
public void ParseNewDefaultTable()
|
||||
{
|
||||
var input = StringBytesTestConverter.Scanner(
|
||||
"""
|
||||
one xref
|
||||
0 6
|
||||
0000000003 65535 f
|
||||
0000000090 00000 n
|
||||
0000000081 00000 n
|
||||
0000000000 00007 f
|
||||
0000000331 00000 n
|
||||
0000000409 00000 n
|
||||
|
||||
trailer
|
||||
<< >>
|
||||
""");
|
||||
|
||||
var result = XrefTableParser.TryReadTableAtOffset(
|
||||
new FileHeaderOffset(0), 4, input.bytes, input.scanner, new TestingLog());
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(4, result.ObjectOffsets.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffsetNotXrefThrows()
|
||||
{
|
||||
var result = Parse("12 0 obj <<>> endobj xref");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OffsetXButNotXrefThrows()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xtable
|
||||
trailer
|
||||
""");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EmptyTableReturnsEmpty()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Dictionary);
|
||||
Assert.Empty(result.ObjectOffsets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidSubsectionDefinitionLenientSkips()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
ab 12
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkipsFirstFreeLine()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
0 1
|
||||
0000000000 65535 f
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Dictionary);
|
||||
Assert.Empty(result.ObjectOffsets);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsEntries()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
0 3
|
||||
0000000000 65535 f
|
||||
0000000100 00000 n
|
||||
0000000200 00005 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
AssertObjectsMatch(result,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(1, 0), 100 },
|
||||
{ new IndirectReference(2, 5), 200 },
|
||||
});
|
||||
|
||||
Assert.NotNull(result.Dictionary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsEntriesOffsetFirstNumber()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
15 2
|
||||
0000000190 00000 n
|
||||
0000000250 00032 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
AssertObjectsMatch(result,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(15, 0), 190 },
|
||||
{ new IndirectReference(16, 32), 250 },
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsEntriesSkippingBlankLine()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
15 2
|
||||
0000000190 00000 n
|
||||
|
||||
0000000250 00032 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
AssertObjectsMatch(result,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{new IndirectReference(15, 0), 190},
|
||||
{new IndirectReference(16, 32), 250},
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadsEntriesFromMultipleSubsections()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
0 4
|
||||
0000000000 65535 f
|
||||
0000000100 00000 n
|
||||
0000000200 00005 n
|
||||
0000000230 00005 n
|
||||
15 2
|
||||
0000000190 00007 n
|
||||
0000000250 00032 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
AssertObjectsMatch(result,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(1, 0), 100 },
|
||||
{ new IndirectReference(2, 5), 200 },
|
||||
{ new IndirectReference(3, 5), 230 },
|
||||
{ new IndirectReference(15, 7), 190 },
|
||||
{ new IndirectReference(16, 32), 250 },
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntryPointingAtOffsetInTableDoesNotThrow()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
0 2
|
||||
0000000000 65535 f
|
||||
0000000010 00000 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
AssertObjectsMatch(result,
|
||||
new Dictionary<IndirectReference, long>
|
||||
{
|
||||
{ new IndirectReference(1, 0), 10 }
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EntryWithInvalidFormatThrows()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
0 22
|
||||
0000000000 65535 f
|
||||
0000aa0010 00000 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShortLineInTableReturnsThrows()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
15 2
|
||||
019 n
|
||||
0000000250 00032 n
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SkipsBlankLinesPrecedingTrailer()
|
||||
{
|
||||
var result = Parse(
|
||||
"""
|
||||
xref
|
||||
15 2
|
||||
0000000190 00000 n
|
||||
0000000250 00032 n
|
||||
|
||||
trailer
|
||||
<<>>
|
||||
""");
|
||||
|
||||
Assert.Equal(2, result.ObjectOffsets.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseEntriesAfterDeclaredCountIfLenient()
|
||||
{
|
||||
const string data =
|
||||
"""
|
||||
xref
|
||||
0 5
|
||||
0000000003 65535 f
|
||||
0000000090 00000 n
|
||||
0000000081 00000 n
|
||||
0000000223 00000 n
|
||||
0000000331 00000 n
|
||||
0000000127 00000 n
|
||||
0000000409 00000 f
|
||||
0000000418 00000 n
|
||||
|
||||
trailer
|
||||
<< >>
|
||||
""";
|
||||
|
||||
var result = GetTableForString(data);
|
||||
|
||||
Assert.Equal(6, result.ObjectOffsets.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParsesMissingWhitespaceAfterXref()
|
||||
{
|
||||
var data =
|
||||
"""
|
||||
xref15 2
|
||||
0000000190 00000 n
|
||||
0000000250 00032 n
|
||||
|
||||
trailer
|
||||
<<>>
|
||||
""";
|
||||
|
||||
var result = GetTableForString(data);
|
||||
|
||||
Assert.Equal(2, result.ObjectOffsets.Count);
|
||||
}
|
||||
|
||||
private static XrefTable Parse(string str)
|
||||
{
|
||||
var input = StringBytesTestConverter.Scanner(str);
|
||||
|
||||
return XrefTableParser.TryReadTableAtOffset(
|
||||
new FileHeaderOffset(0),
|
||||
0,
|
||||
input.bytes,
|
||||
input.scanner,
|
||||
new TestingLog());
|
||||
}
|
||||
|
||||
private static void AssertObjectsMatch(
|
||||
XrefTable table,
|
||||
Dictionary<IndirectReference, long> offsets)
|
||||
{
|
||||
Assert.NotNull(table);
|
||||
|
||||
Assert.Equal(table.ObjectOffsets.Count, offsets.Count);
|
||||
foreach (var offset in offsets)
|
||||
{
|
||||
Assert.True(table.ObjectOffsets.TryGetValue(offset.Key, out var actual));
|
||||
|
||||
Assert.Equal(offset.Value, actual);
|
||||
}
|
||||
}
|
||||
|
||||
private static XrefTable GetTableForString(string s)
|
||||
{
|
||||
var ib = StringBytesTestConverter.Scanner(s);
|
||||
var log = new NoOpLog();
|
||||
|
||||
var table = XrefTableParser.TryReadTableAtOffset(
|
||||
new FileHeaderOffset(0),
|
||||
0,
|
||||
ib.bytes,
|
||||
ib.scanner,
|
||||
log);
|
||||
|
||||
return table;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
public class PageContentParserTests
|
||||
{
|
||||
private readonly PageContentParser parser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance);
|
||||
private readonly PageContentParser parser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256));
|
||||
private readonly ILog log = new NoOpLog();
|
||||
|
||||
[Fact]
|
||||
@@ -203,6 +203,19 @@ l";
|
||||
Assert.Equal(4, result.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CorrectlyHandlesFile0007511CorruptInlineImage()
|
||||
{
|
||||
var path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Parser", "0007511-page-2.txt");
|
||||
var content = File.ReadAllText(path);
|
||||
var input = StringBytesTestConverter.Convert(content, false);
|
||||
|
||||
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256), true);
|
||||
var result = lenientParser.Parse(1, input.Bytes, log);
|
||||
|
||||
Assert.NotEmpty(result);
|
||||
}
|
||||
|
||||
private static string LineEndingsToWhiteSpace(string str)
|
||||
{
|
||||
return str.Replace("\r\n", " ").Replace('\n', ' ').Replace('\r', ' ');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user