Compare commits

..

61 Commits

Author SHA1 Message Date
davebrokit
f3e37eafae Introduce IBlock and ILettersBlock interfaces (Round 2) (#1263)
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Common Crawl Tests / build (0008-0009) (push) Has been cancelled
Run Common Crawl Tests / build (0010-0011) (push) Has been cancelled
Run Common Crawl Tests / build (0012-0013) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Tag Release / tag_if_version_changed (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
* Code review changes
- Keep the Bounds property on the image classes so this isn't a major breaking API change
- Don't expose letters collection

* Minor fix

* Switch to using BoundingBox in the library

---------

Co-authored-by: davmarksman <david@brokit.co.uk>
2026-02-28 16:25:51 +00:00
Eliot Jones
a4047247a8 Additional digital corpora testing (#1261)
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Common Crawl Tests / build (0008-0009) (push) Has been cancelled
Run Common Crawl Tests / build (0010-0011) (push) Has been cancelled
Run Common Crawl Tests / build (0012-0013) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Tag Release / tag_if_version_changed (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
* add additional testing pairs 0010-0011 for integration

some of these files required the skip missing fonts flag set to true

- propagate use lenient parsing for dictionaries inside arrays, handles a
corrupt file 0012710 not in this test set

* add pair 0012-0013
2026-02-22 21:01:03 +00:00
BobLd
34db05f735 Fix Benchmarks solution and add BruteForceBenchmarks 2026-02-22 20:05:57 +00:00
Eliot Jones
9c0d6893e0 revert flate decode handling to more lenient processing (#1254)
* revert flate decode handling to more lenient processing

the change to use zlib/a adler checksum verification flow meant that
invalid flate streams would not be decoded correctly. this caused
issues for files that included invalid/missing checksums. this reverts
the processing to the old approach for files like #1235

* fix object stream offset handling and track circular refs

* update tests

* normalize line endings for mac runner

* fixes for mac clownery

* add next pair to common crawl action

* add a test case for the root cause of the int overflow
2026-02-22 15:49:50 +00:00
BobLd
adb5713621 Make LinkAnnotation public
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Tag Release / tag_if_version_changed (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2026-02-18 14:09:20 +00:00
BobLd
665623561a Add UglyToad.PdfPig.Benchmarks and misc performance improvements
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Tag Release / tag_if_version_changed (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2026-02-17 11:53:09 +00:00
EliotJones
7e370a0eab confirm tag is on master before running publish
Some checks failed
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Tag Release / tag_if_version_changed (push) Has been cancelled
2026-02-15 18:39:45 +00:00
EliotJones
b9aa53166d replace release flow single job with pr process
since actions do not have permissions to push
directly to master and bot accounts to achieve
the same are hard to manage we will change the
release flow to work as follows.

1. manually invoke `prepare_release_pr.yml` action,
this creates a new branch with the version of all
project files updated and creates a pull request for
that version. this pr then should be merged using
rebase merge

2. `tag_release.yml` checks if the newest commit
name starts with the text "Release " and also verifies
if it changed the version of the package csproj. if
both those conditions are met it will create and
push a new tag, e.g. `v0.1.17` to master

3. `publish_nuget.yml` listens for new `v*` tags on
master and triggers the nuget deployment

this is all chat gpt code so who knows if it will work
2026-02-15 18:39:45 +00:00
BobLd
0a2b1e076f Improve HasFormXObjectCircularReference and fix #1250 2026-02-15 18:06:35 +00:00
EliotJones
f732718852 both Tj and TJ operators should increment text sequence #1241
the text sequence was added to group letters added by the same
content stream operation together for layout analysis. TJ allows for
individual letter positioning in the same operation which modifies
the text matrix while writing whereas Tj just writes the text at the current
location. both should be semantically equivalent for this sequence
number. this was originally added in #61
2026-02-15 17:50:07 +00:00
Alexander Vinnikov
d69e3fa6a9 make ext gstates stacked
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2026-02-11 18:39:33 +00:00
BobLd
d6e86b057e Handle empty encoding in Type1FontSimple and fix #1248
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2026-02-10 06:14:39 +00:00
BobLd
c27f1b6553 Only throw if ArrayToken length is less than 4 in ToRectangle() and fix #1238
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2026-02-01 16:10:24 +00:00
BobLd
629891e80e Make LinkAnnotation internal to fix unit tests 2026-02-01 15:50:21 +00:00
David Ochsner
13825b9424 Add Links to Pdf Generation 2026-02-01 15:13:55 +00:00
BobLd
7c4f5e2424 Introduce StackDepthGuard class to check for stack depth in CoreTokenScanner and fix #1217
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-12-23 16:24:04 +01:00
BobLd
bd573b2a1c Increment version to 0.1.14 2025-12-23 12:00:19 +01:00
BobLd
baeac0d0c3 Do not return glyph bbox and path in Type1Font if character name is '.notdef' 2025-12-23 11:09:00 +01:00
BobLd
ee0cb1dc4a Use file header offset when doing brute force find and fix #1223
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-12-07 13:43:22 +00:00
BobLd
c70b343caa Minor Type1FontParser optimisations
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-12-02 17:29:55 +00:00
Carlo Kok
ce563db133 Clarify handling of optional content group names
Updated comments to clarify handling of optional content group names in PDFs.
2025-11-23 10:52:21 +00:00
BobLd
37a5dffcaa Skip in ReadCompositeGlyph when glyphIndex is out of range and fix #1213 2025-11-21 21:31:18 +00:00
EliotJones
aef0a78ee6 update release logic to check out master before commit
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-11-14 09:50:07 +00:00
BobLd
005e52783e Ensure no key end up missing in ResolveInternal and fix #1209 2025-11-13 14:53:28 +00:00
BobLd
f4e7db5b5b Simply order by offset also when not doing brute force to fix #1208 2025-11-13 14:26:35 +00:00
BobLd
52ecef0e28 Increment version to 0.1.13 2025-11-13 09:34:17 +00:00
BobLd
599ce04bef Add test that fails before revert of e11dc6b
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-11-07 22:31:43 +00:00
BobLd
2a6ee918b7 Revert "Avoid a lot of seeks by making most tokenizers no longer read to far by using seek."
This reverts commit e11dc6bf40.
2025-11-07 22:31:43 +00:00
BobLd
9d3cd0a429 Check for array size before slice in ColorSpaceDetailsByteConverter.Convert() 2025-11-07 20:02:39 +00:00
BobLd
b49e5aa697 Check for index out of range in GlyphDataTable.ReadFlags() and fix #1199 2025-11-07 19:39:18 +00:00
Alexander Vinnikov
181fa9d837 make transform stack consistent
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-11-03 20:06:40 +00:00
BobLd
6ce6986d78 Update test run command to use Release configuration
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-29 20:29:42 +00:00
BobLd
37c9fef20b Do not slice the stream to the length breaks decoding in FlateDecode 2025-10-29 20:27:40 +00:00
Bert Huijben
e11dc6bf40 Avoid a lot of seeks by making most tokenizers no longer read to far by using seek.
Optimize the FirstPassParser to just fetch a final chunk before doing things char-by-char backwards.
2025-10-28 06:48:41 +00:00
BobLd
40bcc22ea1 Add CMap caching at document level and add MurmurHash3 hashing function
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-26 16:20:27 +00:00
Bert Huijben
94d515061e Update NameToUnicodeConvertAglSpecification to test what was intended
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-25 19:47:56 +01:00
BobLd
3555521634 Fix regression introduced in 3592fc8 where slicing the stream to the length breaks decoding
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-23 19:08:05 +01:00
Bert Huijben
6fba565d66 Avoid doing a true file seek for simple peeking the next char in the token parser
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-20 06:33:34 +01:00
Bert Huijben
3592fc8438 Use zlib information to verify compressed content before using it
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-10-15 18:46:36 +01:00
BobLd
c9034f991c Only apply RemoveStridePadding() when bytes per pixel is one and fix #1183 2025-10-15 12:57:25 +01:00
BobLd
255e70f0a7 Set Type 3 font ascent to Top instead of Height, see #1164
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-14 12:18:52 +01:00
BobLd
2216ade1f2 Trim excess in long lived font collections
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-10-14 10:26:41 +01:00
BobLd
cf0c33b1e0 Improve DfsIterative() performance
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-10-13 12:35:21 +01:00
BobLd
ffba176060 Improve GroupIndexes() performance with #1178 2025-10-13 12:35:21 +01:00
BobLd
b14f45f59f Add more tests to NearestNeighbourWordExtractorTests
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-10-12 19:51:04 +01:00
ricflams
c28d114b79 Guard against circular references in XRef tables/streams
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
- Detect and prevent an xref table/stream at a certain offset from being read twice; malformed xref tables with circular references could otherwise cause the table-reading to loop forever.
- Another approach could be to prevent TryReadTableAtOffset from changing the bytes' CurrentOffset to the lastObjPosition in its attempt to read a table (eg restore CurrentOffset after the attempt to read a Table) so the outer bytes-loop could continue its search through the entire bytes unaffected.
2025-10-01 06:32:38 +01:00
Richard Flamsholt
d7d01f842e Update test Issue874: No longer missing a font
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
Including the stream-xref means that the formerly missing font is no longer missing, so simply run the two test-cases under the (stricter) assumption of SkipMissingFonts=false.
2025-09-30 18:35:45 +01:00
Richard Flamsholt
33a8d829ee Update test Issue874: Also more text on page 2
Page two has had four more characters added, which is now delected by this xref-stream fix
2025-09-30 18:35:45 +01:00
Richard Flamsholt
57921c7e9b Update test Issue874: Now finds more text on page 1
With the fix for including associated streams, this test now finds more text on the first page. I've verified using Aspose.PDF and by viewing the ErcotFacts.pdf file being tested that yes, it was indeed missing part of the text before.
2025-09-30 18:35:45 +01:00
ricflams
5a6b3970f0 Add table-xref's associated stream-xrefs
- If an XrefTable has an associated stream, as indicated via the XrefStm-property, then read and add that XrefStream
- Any table can have 0 or 1 such associated streams
- A caveat: such an associated stream might also theoretically be part of the Parts-sequence in which case it would be encountered both by looping through all those parts along with all the regular tables and now also by association to any of those tables. It doesn't seem harmful since the offsets are flattened eventually anyway and stored by their offset-key into a mapping-table.
2025-09-30 18:35:45 +01:00
ricflams
397ccb15d6 Add xref-streams tied to any parts, not just the first
On a large sample of pdf-files PdfPig failed to read the correct StructTree-object for about 1% of them. The StructTree object was simply missing in the CrossReferenceTable.CrossReferenceTable.
It turned out that the constructed CrossReferenceTable could miss Stream-parts if there were multiple Table-parts because a stream will only be added if it's associated with the very first Table-part. The remedy would seem to be to check for and add streams that are associated with any of the Table-parts, not just the first one.
On a sample of 72 files where this failed, this changed fixed the StructTree for all of them.
2025-09-30 18:35:45 +01:00
BobLd
ca284e0cb9 Use pageFactoryCache.Clear() in Pages dispose and fix #1170 2025-09-28 17:18:00 +01:00
BobLd
b2f4ca8839 Add GetDescent() and GetAscent() methods to IFont, improve font matrix for TrueTypeSimpleFont and TrueTypeStandard14FallbackSimpleFont and add loose bounding box to Letter
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-09-21 15:07:52 +01:00
BobLd
008959457a Expose letter's font via GetFont(), make Font property as obsolete and use FontDetails instead
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-09-20 17:11:38 +01:00
BobLd
a53d96cb73 Use record struct in FileHeaderOffset 2025-09-20 13:45:50 +01:00
EliotJones
efdedb9495 handle case where offsets are out of range
Some checks failed
Build, test and publish draft / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Common Crawl Tests / build (0000-0001) (push) Has been cancelled
Run Common Crawl Tests / build (0002-0003) (push) Has been cancelled
Run Common Crawl Tests / build (0004-0005) (push) Has been cancelled
Run Common Crawl Tests / build (0006-0007) (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / Check if this commit has already been published (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
default to returning empty glyph where the offset is out of the
file length range, this fixes file 12623 where the truetype file
is completely broken
2025-09-14 15:26:12 +01:00
BobLd
eb906a776d Handle non seekable stream by copying it into a memory stream and fix #1146 2025-09-14 14:42:59 +01:00
BobLd
44e638ee4d Add initial support to process CFF fonts contained inside a TrueType font 2025-09-14 11:32:32 +01:00
BobLd
304d7dde5a Use correct font matrix when transforming the width in Type 0 font and fix #1156 2025-09-14 08:22:58 +01:00
Eliot Jones
07df6fd740 read last line of ignore file (#1155)
* read last line of ignore file

- do not cancel other matrix jobs if one test fails
- read all lines of the ignore list even if it doesn't end with a newline
- add ignore list for 0008 and 0009

* support missing object numbers when brute-forcing

the file 10404 (ironically) contains not found references with number 43 0
for its info dictionary. changes brute-force code so that objects can be
entirely missing

* fix test since document is now opened successfully but mediabox is broken
2025-09-13 16:57:35 +02:00
Eliot Jones
c96880ac61 handle case where xobjects use same key as fonts (#1154)
in document 10122 the font and xobject names are the same so the
xobject overwrote references to the font for the page content, separate
the dictionaries
2025-09-13 16:49:24 +02:00
206 changed files with 144992 additions and 1871 deletions

View 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
View 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 }}

View File

@@ -1,48 +0,0 @@
name: Release Publish
on:
release:
types: [published]
permissions:
contents: write
packages: write
jobs:
build_and_publish_release:
runs-on: windows-2022
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up dotnet core
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 **/*.nupkg --api-key ${{secrets.NUGET_API_KEY}} --source https://api.nuget.org/v3/index.json
- name: Increment version after release
run: |
$newVer = .\tools\get-next-main-version.ps1
.\tools\set-version.ps1 $newVer -UpdateAssemblyAndFileVersion
git config user.name "github-actions"
git config user.email "github-actions@github.com"
git commit -am "Increment version to $newVer"
git push

View File

@@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
pair: ["0000-0001", "0002-0003", "0004-0005", "0006-0007"]
pair: ["0000-0001", "0002-0003", "0004-0005", "0006-0007", "0008-0009", "0010-0011", "0012-0013"]
steps:
- uses: actions/checkout@v2
@@ -55,4 +55,4 @@ jobs:
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

43
.github/workflows/tag_release.yml vendored Normal file
View 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 }}"

1
.gitignore vendored
View File

@@ -249,3 +249,4 @@ _Pvt_Extensions
/docs/doxygen
/tools/UglyToad.PdfPig.ConsoleRunner/Properties/launchSettings.json
/src/UglyToad.PdfPig.Tests/Images/Files/Pdf/indexed-png-with-mask.png

View 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;
}
}
}
}

View File

@@ -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];

View File

@@ -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>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.12</Version>
<Version>0.1.14</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -0,0 +1,20 @@
namespace UglyToad.PdfPig.Core;
/// <summary>
/// Indicates where an object is located in the Xref.
/// </summary>
public enum XrefEntryType : byte
{
/// <summary>
/// Free object.
/// </summary>
Free = 0,
/// <summary>
/// Located as an object in the file.
/// </summary>
File = 1,
/// <summary>
/// Located in a compressed object stream.
/// </summary>
ObjectStream = 2
}

View File

@@ -0,0 +1,42 @@
namespace UglyToad.PdfPig.Core;
/// <summary>
/// Information about where an object is located in the file according to the Xref (or brute force parsing).
/// </summary>
public readonly struct XrefLocation
{
/// <summary>
/// Which type of location is indicated.
/// </summary>
public readonly XrefEntryType Type;
/// <summary>
/// If <see cref="Type"/> is <see cref="XrefEntryType.File"/> then byte offset, otherwise <see cref="XrefEntryType.ObjectStream"/> this is the stream number.
/// </summary>
public readonly long Value1;
/// <summary>
/// If <see cref="Type"/> is <see cref="XrefEntryType.ObjectStream"/> then the index of the object in the stream.
/// </summary>
public readonly int Value2; // only used for ObjectStream
private XrefLocation(XrefEntryType type, long value1, int value2)
{
Type = type;
Value1 = value1;
Value2 = value2;
}
/// <summary>
/// Create a location mapped to a byte offset in the file.
/// </summary>
public static XrefLocation File(long offset)
=> new XrefLocation(XrefEntryType.File, offset, 0);
/// <summary>
/// Create a location mapped to an index inside and object stream.
/// </summary>
public static XrefLocation Stream(long objStream, int index)
=> new XrefLocation(XrefEntryType.ObjectStream, objStream, index);
}

View File

@@ -50,7 +50,15 @@
* (i,j,k) will form a group and (m,n) will form another group.
*************************************************************************************/
int[] indexes = Enumerable.Repeat(-1, elements.Count).ToArray();
int[] indexes = new int[elements.Count];
#if NET6_0_OR_GREATER
Array.Fill(indexes, -1);
#else
for (int k = 0; k < indexes.Length; k++)
{
indexes[k] = -1;
}
#endif
KdTree<T> kdTree = new KdTree<T>(elements, candidatesPoint);
ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
@@ -118,7 +126,15 @@
* (i,j,k) will form a group and (m,n) will form another group.
*************************************************************************************/
int[] indexes = Enumerable.Repeat(-1, elements.Count).ToArray();
int[] indexes = new int[elements.Count];
#if NET6_0_OR_GREATER
Array.Fill(indexes, -1);
#else
for (int l = 0; l < indexes.Length; l++)
{
indexes[l] = -1;
}
#endif
KdTree<T> kdTree = new KdTree<T>(elements, candidatesPoint);
ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
@@ -186,7 +202,15 @@
* (i,j,k) will form a group and (m,n) will form another group.
*************************************************************************************/
int[] indexes = Enumerable.Repeat(-1, elements.Count).ToArray();
int[] indexes = new int[elements.Count];
#if NET6_0_OR_GREATER
Array.Fill(indexes, -1);
#else
for (int k = 0; k < indexes.Length; k++)
{
indexes[k] = -1;
}
#endif
ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
@@ -216,71 +240,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 +280,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;
}
}
}

View File

@@ -3,7 +3,6 @@
using Core;
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Contains helpful tools for distance measures.
@@ -169,12 +168,11 @@
distance = double.MaxValue;
int closestPointIndex = -1;
var candidatesPoints = candidates.Select(candidatePoint).ToList();
var pivot = pivotPoint(element);
for (var i = 0; i < candidates.Count; i++)
{
double currentDistance = distanceMeasure(pivot, candidatesPoints[i]);
double currentDistance = distanceMeasure(pivot, candidatePoint(candidates[i]));
if (currentDistance < distance && !candidates[i].Equals(element))
{
distance = currentDistance;
@@ -211,12 +209,11 @@
distance = double.MaxValue;
int closestLineIndex = -1;
var candidatesLines = candidates.Select(candidateLine).ToList();
var pivot = pivotLine(element);
for (var i = 0; i < candidates.Count; i++)
{
double currentDistance = distanceMeasure(pivot, candidatesLines[i]);
double currentDistance = distanceMeasure(pivot, candidateLine(candidates[i]));
if (currentDistance < distance && !candidates[i].Equals(element))
{
distance = currentDistance;

View File

@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
using UglyToad.PdfPig.Content;
using UglyToad.PdfPig.PdfFonts;
/// <summary>
/// Checks if each letter is a duplicate and overlaps any other letter and remove the duplicate, and flag the remaining as bold.
@@ -24,41 +23,51 @@
return letters?.ToList();
}
var queue = new Queue<Letter>(letters);
var cleanLetters = new List<Letter>() { queue.Dequeue() }; // dequeue the first letter
// Use a dictionary keyed by (Value, FontName) to look up candidate duplicates in O(1)
var duplicateIndex = new Dictionary<(string, string), List<int>>();
var cleanLetters = new List<Letter>();
while (queue.Count > 0)
foreach (var letter in letters)
{
var letter = queue.Dequeue();
bool addLetter = true;
int duplicatesOverlappingIndex = -1;
var duplicates = cleanLetters.Where(l => l.Value.Equals(letter.Value) && l.FontName.Equals(letter.FontName)); // do other checks?
if (duplicates.Any())
var key = (letter.Value, letter.FontName);
if (duplicateIndex.TryGetValue(key, out var candidateIndices))
{
double tolerance = letter.GlyphRectangle.Width / (letter.Value.Length == 0 ? 1 : letter.Value.Length) / 3.0;
double minX = letter.GlyphRectangle.BottomLeft.X - tolerance;
double maxX = letter.GlyphRectangle.BottomLeft.X + tolerance;
double minY = letter.GlyphRectangle.BottomLeft.Y - tolerance;
double maxY = letter.GlyphRectangle.BottomLeft.Y + tolerance;
double tolerance = letter.BoundingBox.Width / (letter.Value.Length == 0 ? 1 : letter.Value.Length) / 3.0;
double minX = letter.BoundingBox.BottomLeft.X - tolerance;
double maxX = letter.BoundingBox.BottomLeft.X + tolerance;
double minY = letter.BoundingBox.BottomLeft.Y - tolerance;
double maxY = letter.BoundingBox.BottomLeft.Y + tolerance;
var duplicatesOverlapping = duplicates.FirstOrDefault(l => minX <= l.GlyphRectangle.BottomLeft.X &&
maxX >= l.GlyphRectangle.BottomLeft.X &&
minY <= l.GlyphRectangle.BottomLeft.Y &&
maxY >= l.GlyphRectangle.BottomLeft.Y);
if (duplicatesOverlapping != default)
for (int ci = 0; ci < candidateIndices.Count; ci++)
{
// duplicate overlapping letter was found, keeping the existing one and not adding this one.
addLetter = false;
duplicatesOverlappingIndex = cleanLetters.IndexOf(duplicatesOverlapping);
int idx = candidateIndices[ci];
var l = cleanLetters[idx];
if (minX <= l.BoundingBox.BottomLeft.X &&
maxX >= l.BoundingBox.BottomLeft.X &&
minY <= l.BoundingBox.BottomLeft.Y &&
maxY >= l.BoundingBox.BottomLeft.Y)
{
addLetter = false;
duplicatesOverlappingIndex = idx;
break;
}
}
}
if (addLetter)
{
int newIndex = cleanLetters.Count;
cleanLetters.Add(letter);
if (!duplicateIndex.TryGetValue(key, out var list))
{
list = new List<int>();
duplicateIndex[key] = list;
}
list.Add(newIndex);
}
else if (duplicatesOverlappingIndex != -1)
{
@@ -68,25 +77,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();
}
}

View File

@@ -229,7 +229,7 @@
private AltoDocument.AltoIllustration ToAltoIllustration(IPdfImage pdfImage, double height)
{
illustrationCount++;
var rectangle = pdfImage.Bounds;
var rectangle = pdfImage.BoundingBox;
return new AltoDocument.AltoIllustration
{
@@ -311,10 +311,10 @@
glyphCount++;
return new AltoDocument.AltoGlyph
{
VerticalPosition = (float)Math.Round((height - letter.GlyphRectangle.Top) * scale),
HorizontalPosition = (float)Math.Round(letter.GlyphRectangle.Left * scale),
Height = (float)Math.Round(letter.GlyphRectangle.Height * scale),
Width = (float)Math.Round(letter.GlyphRectangle.Width * scale),
VerticalPosition = (float)Math.Round((height - letter.BoundingBox.Top) * scale),
HorizontalPosition = (float)Math.Round(letter.BoundingBox.Left * scale),
Height = (float)Math.Round(letter.BoundingBox.Height * scale),
Width = (float)Math.Round(letter.BoundingBox.Width * scale),
Gc = 1.0f,
Content = invalidCharacterHandler(letter.Value),
Id = "P" + pageCount + "_ST" + stringCount.ToString("#00000") + "_G" + glyphCount.ToString("#00")

View File

@@ -277,7 +277,7 @@
private string GetCode(IPdfImage pdfImage, double pageHeight, int level)
{
imageCount++;
var bbox = pdfImage.Bounds;
var bbox = pdfImage.BoundingBox;
return GetIndent(level) + "<span class='ocr_image' id='image_" + pageCount + "_"
+ imageCount + "' title='" + GetCode(bbox, pageHeight) + "' />";
}

View File

@@ -280,7 +280,7 @@
private PageXmlDocument.PageXmlImageRegion ToPageXmlImageRegion(IPdfImage pdfImage, PageXmlData data, double pageWidth, double pageHeight)
{
data.RegionsCount++;
var bbox = pdfImage.Bounds;
var bbox = pdfImage.BoundingBox;
return new PageXmlDocument.PageXmlImageRegion()
{
Coords = ToCoords(bbox, pageWidth, pageHeight),
@@ -360,7 +360,7 @@
data.GlyphsCount++;
return new PageXmlDocument.PageXmlGlyph()
{
Coords = ToCoords(letter.GlyphRectangle, pageWidth, pageHeight),
Coords = ToCoords(letter.BoundingBox, pageWidth, pageHeight),
Ligature = false,
Production = PageXmlDocument.PageXmlProductionSimpleType.Printed,
TextStyle = new PageXmlDocument.PageXmlTextStyle()

View File

@@ -96,12 +96,12 @@
{
string fontFamily = GetFontFamily(l.FontName, out string style, out string weight);
string rotation = "";
if (l.GlyphRectangle.Rotation != 0)
if (l.BoundingBox.Rotation != 0)
{
rotation = $" transform='rotate({Math.Round(-l.GlyphRectangle.Rotation, Rounding)} {Math.Round(l.GlyphRectangle.BottomLeft.X, Rounding)},{Math.Round(height - l.GlyphRectangle.TopLeft.Y, Rounding)})'";
rotation = $" transform='rotate({Math.Round(-l.BoundingBox.Rotation, Rounding)} {Math.Round(l.BoundingBox.BottomLeft.X, Rounding)},{Math.Round(height - l.BoundingBox.TopLeft.Y, Rounding)})'";
}
string fontSize = l.FontSize != 1 ? $"font-size='{l.FontSize:0}'" : $"style='font-size:{Math.Round(l.GlyphRectangle.Height, 2)}px'";
string fontSize = l.FontSize != 1 ? $"font-size='{l.FontSize:0}'" : $"style='font-size:{Math.Round(l.BoundingBox.Height, 2)}px'";
var safeValue = XmlEscape(l, doc);
var x = Math.Round(l.StartBaseLine.X, Rounding);

View File

@@ -54,9 +54,6 @@
/// <typeparam name="T"></typeparam>
public class KdTree<T>
{
private readonly KdTreeComparerY kdTreeComparerY = new KdTreeComparerY();
private readonly KdTreeComparerX kdTreeComparerX = new KdTreeComparerX();
/// <summary>
/// The root of the tree.
/// </summary>
@@ -111,11 +108,11 @@
if (depth % 2 == 0)
{
P.Sort(kdTreeComparerX);
P.Sort((p0, p1) => p0.Value.X.CompareTo(p1.Value.X));
}
else
{
P.Sort(kdTreeComparerY);
P.Sort((p0, p1) => p0.Value.Y.CompareTo(p1.Value.Y));
}
if (P.Length == 2)
@@ -131,6 +128,26 @@
return new KdTreeNode<T>(vLeft, vRight, P[median], depth);
}
#else
private sealed class KdTreeComparerY : IComparer<KdTreeElement<T>>
{
public static readonly KdTreeComparerY Shared = new KdTreeComparerY();
public int Compare(KdTreeElement<T> p0, KdTreeElement<T> p1)
{
return p0.Value.Y.CompareTo(p1.Value.Y);
}
}
private sealed class KdTreeComparerX : IComparer<KdTreeElement<T>>
{
public static readonly KdTreeComparerX Shared = new KdTreeComparerX();
public int Compare(KdTreeElement<T> p0, KdTreeElement<T> p1)
{
return p0.Value.X.CompareTo(p1.Value.X);
}
}
private KdTreeNode<T> BuildTree(ArraySegment<KdTreeElement<T>> P, int depth = 0)
{
if (P.Count == 0)
@@ -145,13 +162,13 @@
if (depth % 2 == 0)
{
P.Sort(kdTreeComparerX);
P.Sort(KdTreeComparerX.Shared);
}
else
{
P.Sort(kdTreeComparerY);
P.Sort(KdTreeComparerY.Shared);
}
if (P.Count == 2)
{
return new KdTreeNode<T>(new KdTreeLeaf<T>(P.GetAt(0), depth + 1), null, P.GetAt(1), depth);
@@ -179,13 +196,14 @@
/// <returns>The nearest neighbour's element.</returns>
public T FindNearestNeighbour(T pivot, Func<T, PdfPoint> pivotPointFunc, Func<PdfPoint, PdfPoint, double> distanceMeasure, out int index, out double distance)
{
var result = FindNearestNeighbour(Root, pivot, pivotPointFunc, distanceMeasure);
var pivotPoint = pivotPointFunc(pivot);
var result = FindNearestNeighbour(Root, pivot, pivotPoint, distanceMeasure);
index = result.Item1 != null ? result.Item1.Index : -1;
distance = result.Item2 ?? double.NaN;
return result.Item1 != null ? result.Item1.Element : default;
}
private static (KdTreeNode<T>, double?) FindNearestNeighbour(KdTreeNode<T> node, T pivot, Func<T, PdfPoint> pivotPointFunc, Func<PdfPoint, PdfPoint, double> distance)
private static (KdTreeNode<T>, double?) FindNearestNeighbour(KdTreeNode<T> node, T pivot, PdfPoint pivotPoint, Func<PdfPoint, PdfPoint, double> distance)
{
if (node == null)
{
@@ -197,23 +215,22 @@
{
return (null, null);
}
return (node, distance(node.Value, pivotPointFunc(pivot)));
return (node, distance(node.Value, pivotPoint));
}
else
{
var point = pivotPointFunc(pivot);
var currentNearestNode = node;
var currentDistance = distance(node.Value, point);
var currentDistance = distance(node.Value, pivotPoint);
KdTreeNode<T> newNode = null;
double? newDist = null;
var pointValue = node.IsAxisCutX ? point.X : point.Y;
var pointValue = node.IsAxisCutX ? pivotPoint.X : pivotPoint.Y;
if (pointValue < node.L)
{
// start left
(newNode, newDist) = FindNearestNeighbour(node.LeftChild, pivot, pivotPointFunc, distance);
(newNode, newDist) = FindNearestNeighbour(node.LeftChild, pivot, pivotPoint, distance);
if (newDist.HasValue && newDist <= currentDistance && !newNode.Element.Equals(pivot))
{
@@ -223,13 +240,13 @@
if (node.RightChild != null && pointValue + currentDistance >= node.L)
{
(newNode, newDist) = FindNearestNeighbour(node.RightChild, pivot, pivotPointFunc, distance);
(newNode, newDist) = FindNearestNeighbour(node.RightChild, pivot, pivotPoint, distance);
}
}
else
{
// start right
(newNode, newDist) = FindNearestNeighbour(node.RightChild, pivot, pivotPointFunc, distance);
(newNode, newDist) = FindNearestNeighbour(node.RightChild, pivot, pivotPoint, distance);
if (newDist.HasValue && newDist <= currentDistance && !newNode.Element.Equals(pivot))
{
@@ -239,7 +256,7 @@
if (node.LeftChild != null && pointValue - currentDistance <= node.L)
{
(newNode, newDist) = FindNearestNeighbour(node.LeftChild, pivot, pivotPointFunc, distance);
(newNode, newDist) = FindNearestNeighbour(node.LeftChild, pivot, pivotPoint, distance);
}
}
@@ -258,7 +275,7 @@
/// <summary>
/// Get the k nearest neighbours to the pivot element.
/// Might return more than k neighbours if points are equidistant.
/// <para>Use <see cref="FindNearestNeighbour(KdTreeNode{T}, T, Func{T, PdfPoint}, Func{PdfPoint, PdfPoint, double})"/> if only looking for the (single) closest point.</para>
/// <para>Use <see cref="FindNearestNeighbour(T, Func{T, PdfPoint}, Func{PdfPoint, PdfPoint, double}, out int, out double)"/> if only looking for the (single) closest point.</para>
/// </summary>
/// <param name="pivot">The element for which to find the k nearest neighbours.</param>
/// <param name="k">The number of neighbours to return. Might return more than k neighbours if points are equidistant.</param>
@@ -267,13 +284,24 @@
/// <returns>Returns a list of tuples of the k nearest neighbours. Tuples are (element, index, distance).</returns>
public IReadOnlyList<(T, int, double)> FindNearestNeighbours(T pivot, int k, Func<T, PdfPoint> pivotPointFunc, Func<PdfPoint, PdfPoint, double> distanceMeasure)
{
var pivotPoint = pivotPointFunc(pivot);
var kdTreeNodes = new KNearestNeighboursQueue(k);
FindNearestNeighbours(Root, pivot, k, pivotPointFunc, distanceMeasure, kdTreeNodes);
return kdTreeNodes.SelectMany(n => n.Value.Select(e => (e.Element, e.Index, n.Key))).ToArray();
FindNearestNeighbours(Root, pivot, k, pivotPoint, distanceMeasure, kdTreeNodes);
var results = new List<(T, int, double)>();
for (int i = 0; i < kdTreeNodes.Count; i++)
{
double dist = kdTreeNodes.Keys[i];
foreach (var e in kdTreeNodes.Values[i])
{
results.Add((e.Element, e.Index, dist));
}
}
return results;
}
private static (KdTreeNode<T>, double) FindNearestNeighbours(KdTreeNode<T> node, T pivot, int k,
Func<T, PdfPoint> pivotPointFunc, Func<PdfPoint, PdfPoint, double> distance, KNearestNeighboursQueue queue)
PdfPoint pivotPoint, Func<PdfPoint, PdfPoint, double> distance, KNearestNeighboursQueue queue)
{
if (node == null)
{
@@ -286,7 +314,7 @@
return (null, double.NaN);
}
var currentDistance = distance(node.Value, pivotPointFunc(pivot));
var currentDistance = distance(node.Value, pivotPoint);
var currentNearestNode = node;
if (!queue.IsFull || currentDistance <= queue.LastDistance)
@@ -300,9 +328,8 @@
}
else
{
var point = pivotPointFunc(pivot);
var currentNearestNode = node;
var currentDistance = distance(node.Value, point);
var currentDistance = distance(node.Value, pivotPoint);
if ((!queue.IsFull || currentDistance <= queue.LastDistance) && !node.Element.Equals(pivot))
{
queue.Add(currentDistance, currentNearestNode);
@@ -313,12 +340,12 @@
KdTreeNode<T> newNode = null;
double newDist = double.NaN;
var pointValue = node.IsAxisCutX ? point.X : point.Y;
var pointValue = node.IsAxisCutX ? pivotPoint.X : pivotPoint.Y;
if (pointValue < node.L)
{
// start left
(newNode, newDist) = FindNearestNeighbours(node.LeftChild, pivot, k, pivotPointFunc, distance, queue);
(newNode, newDist) = FindNearestNeighbours(node.LeftChild, pivot, k, pivotPoint, distance, queue);
if (!double.IsNaN(newDist) && newDist <= currentDistance && !newNode.Element.Equals(pivot))
{
@@ -329,13 +356,13 @@
if (node.RightChild != null && pointValue + currentDistance >= node.L)
{
(newNode, newDist) = FindNearestNeighbours(node.RightChild, pivot, k, pivotPointFunc, distance, queue);
(newNode, newDist) = FindNearestNeighbours(node.RightChild, pivot, k, pivotPoint, distance, queue);
}
}
else
{
// start right
(newNode, newDist) = FindNearestNeighbours(node.RightChild, pivot, k, pivotPointFunc, distance, queue);
(newNode, newDist) = FindNearestNeighbours(node.RightChild, pivot, k, pivotPoint, distance, queue);
if (!double.IsNaN(newDist) && newDist <= currentDistance && !newNode.Element.Equals(pivot))
{
@@ -346,7 +373,7 @@
if (node.LeftChild != null && pointValue - currentDistance <= node.L)
{
(newNode, newDist) = FindNearestNeighbours(node.LeftChild, pivot, k, pivotPointFunc, distance, queue);
(newNode, newDist) = FindNearestNeighbours(node.LeftChild, pivot, k, pivotPoint, distance, queue);
}
}
@@ -395,9 +422,14 @@
if (this[key].Add(value))
{
var last = this.Last();
LastElement = last.Value.Last();
LastDistance = last.Key;
LastDistance = Keys[Count - 1];
var lastSet = Values[Count - 1];
KdTreeNode<T> lastElement = null;
foreach (var e in lastSet)
{
lastElement = e;
}
LastElement = lastElement;
}
}
}
@@ -418,23 +450,7 @@
public R Element { get; }
}
private sealed class KdTreeComparerY : IComparer<KdTreeElement<T>>
{
public int Compare(KdTreeElement<T> p0, KdTreeElement<T> p1)
{
return p0.Value.Y.CompareTo(p1.Value.Y);
}
}
private sealed class KdTreeComparerX : IComparer<KdTreeElement<T>>
{
public int Compare(KdTreeElement<T> p0, KdTreeElement<T> p1)
{
return p0.Value.X.CompareTo(p1.Value.X);
}
}
/// <summary>
/// K-D tree leaf.
/// </summary>

View File

@@ -3,7 +3,6 @@
using Content;
using Core;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -154,57 +153,72 @@
{
ParallelOptions parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = maxDegreeOfParallelism };
var withinLineDistList = new ConcurrentBag<double>();
var betweenLineDistList = new ConcurrentBag<double>();
var withinLineDistList = new List<double>();
var betweenLineDistList = new List<double>();
// 1. Estimate within line and between line spacing
KdTree<Word> kdTreeBottomLeft = new KdTree<Word>(words, w => w.BoundingBox.BottomLeft);
Parallel.For(0, words.Count, parallelOptions, i =>
{
var word = words[i];
// Within-line distance
// 1.1.1 Find the 2 closest neighbours words to the candidate, using euclidean distance.
foreach (var n in kdTreeBottomLeft.FindNearestNeighbours(word, 2, w => w.BoundingBox.BottomRight, Distances.Euclidean))
Parallel.For(0, words.Count, parallelOptions,
() => (wl: new List<double>(), bl: new List<double>()),
(i, _, local) =>
{
// 1.1.2 Check if the neighbour word is within the angle of the candidate
if (wlBounds.Contains(AngleWL(word, n.Item1)))
var word = words[i];
// Within-line distance
// 1.1.1 Find the 2 closest neighbours words to the candidate, using euclidean distance.
foreach (var n in kdTreeBottomLeft.FindNearestNeighbours(word, 2, w => w.BoundingBox.BottomRight, Distances.Euclidean))
{
withinLineDistList.Add(Distances.Euclidean(word.BoundingBox.BottomRight, n.Item1.BoundingBox.BottomLeft));
}
}
// Between-line distance
// 1.2.1 Find the 2 closest neighbours words to the candidate, using euclidean distance.
foreach (var n in kdTreeBottomLeft.FindNearestNeighbours(word, 2, w => w.BoundingBox.TopLeft, Distances.Euclidean))
{
// 1.2.2 Check if the candidate words is within the angle
var angle = AngleBL(word, n.Item1);
if (blBounds.Contains(angle))
{
// 1.2.3 Compute the vertical (between-line) distance between the candidate
// and the neighbour and add it to the between-line distances list
double hypotenuse = Distances.Euclidean(word.BoundingBox.Centroid, n.Item1.BoundingBox.Centroid);
// Angle is kept within [-90, 90]
if (angle > 90)
// 1.1.2 Check if the neighbour word is within the angle of the candidate
if (wlBounds.Contains(AngleWL(word, n.Item1)))
{
angle -= 180;
}
var dist = Math.Abs(hypotenuse * Math.Cos((90 - angle) * Math.PI / 180))
- word.BoundingBox.Height / 2.0 - n.Item1.BoundingBox.Height / 2.0;
// The perpendicular distance can be negative because of the subtractions.
// Could occur when words are overlapping, we ignore that.
if (dist >= 0)
{
betweenLineDistList.Add(dist);
local.wl.Add(Distances.Euclidean(word.BoundingBox.BottomRight, n.Item1.BoundingBox.BottomLeft));
}
}
}
});
// Between-line distance
// 1.2.1 Find the 2 closest neighbours words to the candidate, using euclidean distance.
foreach (var n in kdTreeBottomLeft.FindNearestNeighbours(word, 2, w => w.BoundingBox.TopLeft, Distances.Euclidean))
{
// 1.2.2 Check if the candidate words is within the angle
var angle = AngleBL(word, n.Item1);
if (blBounds.Contains(angle))
{
// 1.2.3 Compute the vertical (between-line) distance between the candidate
// and the neighbour and add it to the between-line distances list
double hypotenuse = Distances.Euclidean(word.BoundingBox.Centroid, n.Item1.BoundingBox.Centroid);
// Angle is kept within [-90, 90]
if (angle > 90)
{
angle -= 180;
}
var dist = Math.Abs(hypotenuse * Math.Cos((90 - angle) * Math.PI / 180))
- word.BoundingBox.Height / 2.0 - n.Item1.BoundingBox.Height / 2.0;
// The perpendicular distance can be negative because of the subtractions.
// Could occur when words are overlapping, we ignore that.
if (dist >= 0)
{
local.bl.Add(dist);
}
}
}
return local;
},
local =>
{
lock (withinLineDistList)
{
withinLineDistList.AddRange(local.wl);
}
lock (betweenLineDistList)
{
betweenLineDistList.AddRange(local.bl);
}
});
// Compute average peak value of distribution
double? withinLinePeak = GetPeakAverageDistance(withinLineDistList, wlBinSize);
@@ -221,9 +235,9 @@
/// </summary>
/// <param name="distances">The set of distances to average.</param>
/// <param name="binLength"></param>
private static double? GetPeakAverageDistance(IEnumerable<double> distances, int binLength = 1)
private static double? GetPeakAverageDistance(List<double> distances, int binLength = 1)
{
if (!distances.Any())
if (distances.Count == 0)
{
return null;
}
@@ -233,7 +247,16 @@
throw new ArgumentException("DocstrumBoundingBoxes: the bin length must be positive when commputing peak average distance.", nameof(binLength));
}
double maxDbl = Math.Ceiling(distances.Max());
double maxDbl = distances[0];
for (int i = 1; i < distances.Count; i++)
{
if (distances[i] > maxDbl)
{
maxDbl = distances[i];
}
}
maxDbl = Math.Ceiling(maxDbl);
if (maxDbl > int.MaxValue)
{
throw new OverflowException($"Error while casting maximum distance of {maxDbl} to integer.");
@@ -249,30 +272,49 @@
binLength = binLength > max ? max : binLength;
}
var bins = Enumerable.Range(0, (int)Math.Ceiling(max / (double)binLength) + 1)
.Select(x => x * binLength)
.ToDictionary(x => x, _ => new List<double>());
foreach (var distance in distances)
int binCount = (int)Math.Ceiling(max / (double)binLength) + 1;
var bins = new List<double>[binCount];
for (int i = 0; i < binCount; i++)
{
bins[i] = new List<double>();
}
for (int i = 0; i < distances.Count; i++)
{
var distance = distances[i];
int bin = (int)Math.Floor(distance / binLength);
if (bin < 0)
{
throw new ArgumentOutOfRangeException(nameof(bin), "DocstrumBoundingBoxes: Negative distance found while commputing peak average distance.");
}
bins[bins.Keys.ElementAt(bin)].Add(distance);
if (bin >= binCount)
{
bin = binCount - 1;
}
bins[bin].Add(distance);
}
var best = default(List<double>);
foreach (var bin in bins)
List<double> best = null;
for (int i = 0; i < binCount; i++)
{
if (best == null || bin.Value.Count > best.Count)
var bin = bins[i];
if (best == null || bin.Count > best.Count)
{
best = bin.Value;
best = bin;
}
}
return best?.Average();
if (best == null || best.Count == 0)
{
return null;
}
double sum = 0;
for (int i = 0; i < best.Count; i++)
{
sum += best[i];
}
return sum / best.Count;
}
#endregion
@@ -417,28 +459,7 @@
return double.PositiveInfinity;
}
}
private sealed class PdfPointXYComparer : IComparer<PdfPoint>
{
public static readonly PdfPointXYComparer Instance = new();
public int Compare(PdfPoint p1, PdfPoint p2)
{
int comp = p1.X.CompareTo(p2.X);
return comp == 0 ? p1.Y.CompareTo(p2.Y) : comp;
}
}
private sealed class PdfPointYComparer : IComparer<PdfPoint>
{
public static readonly PdfPointYComparer Instance = new();
public int Compare(PdfPoint p1, PdfPoint p2)
{
return p1.Y.CompareTo(p2.Y);
}
}
/// <summary>
/// Get the structural blocking parameters.
/// </summary>
@@ -486,22 +507,30 @@
if (dXj != 0)
{
ps.Sort(PdfPointXYComparer.Instance);
ps.Sort((p1, p2) =>
{
int comp = p1.X.CompareTo(p2.X);
return comp == 0 ? p1.Y.CompareTo(p2.Y) : comp;
});
}
else if (dYj != 0)
{
ps.Sort(PdfPointYComparer.Instance);
ps.Sort((p1, p2) => p1.Y.CompareTo(p2.Y));
}
#else
PdfPoint[] ps = [j.Point1, j.Point2, Aj.Value, Bj.Value];
if (dXj != 0)
{
Array.Sort(ps, PdfPointXYComparer.Instance);
Array.Sort(ps, (p1, p2) =>
{
int comp = p1.X.CompareTo(p2.X);
return comp == 0 ? p1.Y.CompareTo(p2.Y) : comp;
});
}
else if (dYj != 0)
{
Array.Sort(ps, PdfPointYComparer.Instance);
Array.Sort(ps, (p1, p2) => p1.Y.CompareTo(p2.Y));
}
#endif

View File

@@ -372,7 +372,7 @@
public Func<IEnumerable<Letter>, double> DominantFontWidthFunc { get; set; } =
(letters) =>
{
var widths = letters.Select(x => Math.Max(Math.Round(x.Width, 3), Math.Round(x.GlyphRectangle.Width, 3)));
var widths = letters.Select(x => Math.Max(Math.Round(x.Width, 3), Math.Round(x.BoundingBox.Width, 3)));
var mode = widths.Mode();
if (double.IsNaN(mode) || mode == 0)
{
@@ -389,7 +389,7 @@
public Func<IEnumerable<Letter>, double> DominantFontHeightFunc { get; set; } =
(letters) =>
{
var heights = letters.Select(x => Math.Round(x.GlyphRectangle.Height, 3));
var heights = letters.Select(x => Math.Round(x.BoundingBox.Height, 3));
var mode = heights.Mode();
if (double.IsNaN(mode) || mode == 0)
{

View File

@@ -9,7 +9,7 @@
/// <summary>
/// A block of text.
/// </summary>
public class TextBlock
public class TextBlock: IBoundingBox
{
/// <summary>
/// The separator used between lines in the block.

View File

@@ -59,12 +59,13 @@
{
letter = new Letter(
" ",
letter.GlyphRectangle,
letter.BoundingBox,
letter.GlyphRectangleLoose,
letter.StartBaseLine,
letter.EndBaseLine,
letter.Width,
letter.FontSize,
letter.Font,
letter.GetFont()!,
letter.RenderingMode,
letter.StrokeColor,
letter.FillColor,

View File

@@ -9,7 +9,7 @@
/// <summary>
/// A line of text.
/// </summary>
public class TextLine
public class TextLine : IBoundingBox
{
/// <summary>
/// The separator used between words in the line.

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.12</Version>
<Version>0.1.14</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>

View File

@@ -25,8 +25,8 @@
{
return GetWhitespaces(words,
images,
words.SelectMany(w => w.Letters).Select(x => x.GlyphRectangle.Width).Mode() * 1.25,
words.SelectMany(w => w.Letters).Select(x => x.GlyphRectangle.Height).Mode() * 1.25,
words.SelectMany(w => w.Letters).Select(x => x.BoundingBox.Width).Mode() * 1.25,
words.SelectMany(w => w.Letters).Select(x => x.BoundingBox.Height).Mode() * 1.25,
maxRectangleCount: maxRectangleCount,
maxBoundQueueSize: maxBoundQueueSize);
}
@@ -51,7 +51,7 @@
if (images?.Any() == true)
{
bboxes.AddRange(images.Where(w => w.Bounds.Width > 0 && w.Bounds.Height > 0).Select(o => o.Bounds));
bboxes.AddRange(images.Where(w => w.BoundingBox.Width > 0 && w.BoundingBox.Height > 0).Select(o => o.BoundingBox));
}
return GetWhitespaces(bboxes,

View File

@@ -163,8 +163,8 @@
public Func<Letter, Letter, double> MaximumDistance { get; set; } = (l1, l2) =>
{
double maxDist = Math.Max(Math.Max(Math.Max(Math.Max(Math.Max(
Math.Abs(l1.GlyphRectangle.Width),
Math.Abs(l2.GlyphRectangle.Width)),
Math.Abs(l1.BoundingBox.Width),
Math.Abs(l2.BoundingBox.Width)),
Math.Abs(l1.Width)),
Math.Abs(l2.Width)),
l1.PointSize), l2.PointSize) * 0.2;

View File

@@ -770,6 +770,8 @@ namespace UglyToad.PdfPig.Fonts.CompactFontFormat.CharStrings
}
}
values.TrimExcess();
return new Type2CharStrings.CommandSequence(values, commandIdentifiers);
}

View File

@@ -257,6 +257,10 @@
gidToStringIdAndNameMap[gid++] = pair;
}
#if NET
gidToStringIdAndNameMap.TrimExcess();
#endif
glyphIdToStringIdAndName = gidToStringIdAndNameMap;
}

View File

@@ -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))
@@ -125,7 +155,7 @@
OptionallyParseTables(tables, data, builder);
return new TrueTypeFont(version, tables, builder.Build());
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();

View File

@@ -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; }

View File

@@ -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;
}

View File

@@ -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];

View File

@@ -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;

View File

@@ -2,11 +2,12 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0;net9.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.12</Version>
<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\*" />

View File

@@ -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]

View File

@@ -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);
}
}
}

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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;
}

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Tests.Filters
{
using PdfPig.Core;
using PdfPig.Filters;
using PdfPig.Tokens;
@@ -11,15 +12,32 @@
public void EncodeAndDecodePreservesInput()
{
var parameters = new DictionaryToken(new Dictionary<NameToken, IToken>());
var input = new byte[] {67, 69, 69, 10, 4, 20, 6, 19, 120, 64, 64, 64, 32};
var input = new byte[] { 67, 69, 69, 10, 4, 20, 6, 19, 120, 64, 64, 64, 32 };
using (var inputStream = new MemoryStream(input))
{
inputStream.Seek(0, SeekOrigin.Begin);
var result = filter.Encode(inputStream, parameters, 0);
var result = filter.Encode(inputStream, parameters);
var decoded = filter.Decode(result, parameters, TestFilterProvider.Instance, 0);
Assert.Equal(input, decoded.ToArray());
}
}
[Fact]
public void CanDecodeCorruptedInputIssue1235()
{
const string hexStr =
"789C958D5D0AC2400C844FB077980B74BB7FD9D982F820B43E8B7B03C542C187EAFDC1F84B7D1164200999E49BD9044C6653D10E1E443DA1AF6636ED76EF315E7572968E1ECDAB7FB7506C4C59C0AEB3912EE270366AAAF4E36D364BF7911450DC274A5112B1AC9751D77A58680B51A4D8AE433D62953C037396E0F290FBE098B267A43051725AA34E77E44EF50B1B52B42C90E4ADF83FB94FDD0000000000";
var hex = new HexToken(hexStr.AsSpan());
var parameters = new DictionaryToken(new Dictionary<NameToken, IToken>());
var result = filter.Decode(hex.Bytes.ToArray(), parameters, TestFilterProvider.Instance, 0);
var text = OtherEncodings.BytesAsLatin1String(result.ToArray());
Assert.StartsWith("q", text);
}
}
}

View File

@@ -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);

View File

@@ -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);
}
}
}

View File

@@ -4,6 +4,7 @@ using UglyToad.PdfPig.Tests.Dla;
namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
{
using PdfPig.Core;
using PdfPig.Geometry;
public class Linux
{
@@ -64,11 +65,14 @@ namespace UglyToad.PdfPig.Tests.Fonts.SystemFonts
var current = page.Letters[i];
Assert.Equal(expectedData.TopLeft.X, current.GlyphRectangle.TopLeft.X, 6);
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.TopLeft.X, current.BoundingBox.TopLeft.X, 6);
Assert.Equal(expectedData.TopLeft.Y, current.BoundingBox.TopLeft.Y, 6);
Assert.Equal(expectedData.Width, current.BoundingBox.Width, 6);
Assert.Equal(expectedData.Height, current.BoundingBox.Height, 6);
Assert.Equal(expectedData.Rotation, current.BoundingBox.Rotation, 3);
Assert.True(current.BoundingBox.IntersectsWith(current.GlyphRectangleLoose));
Assert.Equal(current.BoundingBox.Rotation, current.GlyphRectangleLoose.Rotation, 3);
}
}
}

View File

@@ -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 _);
}
}
}

View File

@@ -20,14 +20,14 @@
// check 'm'
var m = letters[0];
Assert.Equal("m", m.Value);
Assert.Equal(new PdfPoint(253.4458, 658.431), m.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.GlyphRectangle.TopRight, pointComparer);
Assert.Equal(new PdfPoint(253.4458, 658.431), m.BoundingBox.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(261.22659, 662.83446), m.BoundingBox.TopRight, pointComparer);
// check 'p'
var p = letters[1];
Assert.Equal("p", p.Value);
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.GlyphRectangle.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.GlyphRectangle.TopRight, pointComparer);
Assert.Equal(new PdfPoint(261.70778, 656.49825), p.BoundingBox.BottomLeft, pointComparer);
Assert.Equal(new PdfPoint(266.6193, 662.83446), p.BoundingBox.TopRight, pointComparer);
}
}
}

View File

@@ -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)

View File

@@ -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

View File

@@ -50,7 +50,7 @@
Assert.Equal(Width, letter.Width, 1);
if (includeHeight)
{
Assert.Equal(Height, letter.GlyphRectangle.Height, 1);
Assert.Equal(Height, letter.BoundingBox.Height, 1);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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

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

File diff suppressed because one or more lines are too long

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
(Jl σ3 法海阔』咱国司被NA咿清峭阉且啊辛茹海耀表3-19车辆齿轮用钢系列牌号化学成分质量分数%国内牌号国外牌号c Si Mn p s Ni Cr Mo Al Cu Ti B 16CrMnTiH 0.130.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. 001.40 O.\KJ-1.20 20MnCrH 20MnCr5 0.170.23 1.10-1.50 1.00-1.30 0.02 罢王0.20运0.120.020.仍525MnCrH 25MnCr5 0.230.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.40o.ro1.40I. 70 1.40-1.70 16CrNiH 16CrNi4 0.13“0.18 0.02-0.04 0.150.35 0.70句1.100. 80-1. 200. 801.20 :;;;0.10 0.02-0. 05 19CrNiH 19CrNi5 0.160. 21 0. 02 -0. 035 17Cr2Ni2MoH ZFlA 0.15”0.19 0.150.40 o. 40 -o. ro0.0150.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.0170.032 0.350. 75 0.35”0.65 0. 15 -0. 25 20CrNiMoH2 8620田0.045 15CrMoH 0.13”0.18 0.250.45 0.170. 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.550. \Kl 0.85句I.25 0.15”0.35 0.02-0.0击:;;;0.15 35CrMo 0. 32 -0. 40 0.400. 70 0.80斗100. 15 -0. 25 运0.03520CrH 0.170.37 0.70-1.00 运0.200.500.80 40Cr 0. 18 -0. 23 0.370.440. 80 -1.10

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

View File

@@ -33,7 +33,7 @@
var rect = new PdfRectangle(207, 158, 229, 168.5);
var missingChars = letters.Where(l => rect.Contains(l.GlyphRectangle)).ToArray();
var missingChars = letters.Where(l => rect.Contains(l.BoundingBox)).ToArray();
Assert.NotEmpty(missingChars);
Assert.True(missingChars.Length == 2);

View File

@@ -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);
}
}
}

View File

@@ -4,9 +4,301 @@
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()
{
@@ -35,7 +327,7 @@
var path = IntegrationHelpers.GetSpecificTestDocumentPath("StackOverflow_Issue_1122.pdf");
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
Assert.StartsWith("Reached maximum search depth while getting indirect reference.", ex.Message);
Assert.StartsWith("Circular reference encountered when looking", ex.Message);
}
[Fact]
@@ -94,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.StartsWith("Object stream cannot contain itself", ex.Message);
}
[Fact]
@@ -259,6 +551,7 @@
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page = document.GetPage(13);
// This used to fail with an overflow exception when we failed to validate the zlib encoded data
Assert.Throws<OverflowException>(() => DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
}
}
@@ -442,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);
}
}
@@ -471,7 +758,7 @@
{
var letter = page1.Letters[l];
Assert.Equal(TextOrientation.Other, letter.TextOrientation);
Assert.Equal(45.0, letter.GlyphRectangle.Rotation, 5);
Assert.Equal(45.0, letter.BoundingBox.Rotation, 5);
}
var page2 = document.GetPage(2);

View File

@@ -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.BoundingBox;
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)

View File

@@ -34,7 +34,7 @@ namespace UglyToad.PdfPig.Tests.Integration
{
var page = document.GetPage(1);
Assert.NotEqual(0, page.Letters[0].GlyphRectangle.Height);
Assert.NotEqual(0, page.Letters[0].BoundingBox.Height);
}
}

View File

@@ -12,8 +12,8 @@
var page = document.GetPage(1);
Assert.Equal(612, page.Width); // Due to cropping
Assert.Equal(792, page.Height); // Due to cropping
var minX = page.Letters.Select(l => l.GlyphRectangle.Left).Min();
var maxX = page.Letters.Select(l => l.GlyphRectangle.Right).Max();
var minX = page.Letters.Select(l => l.BoundingBox.Left).Min();
var maxX = page.Letters.Select(l => l.BoundingBox.Right).Max();
Assert.Equal(74, minX, 0); // If cropping is not applied correctly, these values will be off
Assert.Equal(540, maxX, 0); // If cropping is not applied correctly, these values will be off
// The page is cropped at

View File

@@ -47,29 +47,29 @@
{
var page = document.GetPage(1);
var images = page.GetImages().OrderBy(x => x.Bounds.Width).ToList();
var images = page.GetImages().OrderBy(x => x.BoundingBox.Width).ToList();
var pdfPigSquare = images[0];
Assert.Equal(148.3d, pdfPigSquare.Bounds.Width, doubleComparer);
Assert.Equal(148.3d, pdfPigSquare.Bounds.Height, doubleComparer);
Assert.Equal(60.1d, pdfPigSquare.Bounds.Left, doubleComparer);
Assert.Equal(765.8d, pdfPigSquare.Bounds.Top, doubleComparer);
Assert.Equal(148.3d, pdfPigSquare.BoundingBox.Width, doubleComparer);
Assert.Equal(148.3d, pdfPigSquare.BoundingBox.Height, doubleComparer);
Assert.Equal(60.1d, pdfPigSquare.BoundingBox.Left, doubleComparer);
Assert.Equal(765.8d, pdfPigSquare.BoundingBox.Top, doubleComparer);
var pdfPigSquished = images[1];
Assert.Equal(206.8d, pdfPigSquished.Bounds.Width, doubleComparer);
Assert.Equal(83.2d, pdfPigSquished.Bounds.Height, doubleComparer);
Assert.Equal(309.8d, pdfPigSquished.Bounds.Left, doubleComparer);
Assert.Equal(552.1d, pdfPigSquished.Bounds.Top, doubleComparer);
Assert.Equal(206.8d, pdfPigSquished.BoundingBox.Width, doubleComparer);
Assert.Equal(83.2d, pdfPigSquished.BoundingBox.Height, doubleComparer);
Assert.Equal(309.8d, pdfPigSquished.BoundingBox.Left, doubleComparer);
Assert.Equal(552.1d, pdfPigSquished.BoundingBox.Top, doubleComparer);
var birthdayPigs = images[2];
Assert.Equal(391d, birthdayPigs.Bounds.Width, doubleComparer);
Assert.Equal(267.1d, birthdayPigs.Bounds.Height, doubleComparer);
Assert.Equal(102.2d, birthdayPigs.Bounds.Left, doubleComparer);
Assert.Equal(426.3d, birthdayPigs.Bounds.Top, doubleComparer);
Assert.Equal(391d, birthdayPigs.BoundingBox.Width, doubleComparer);
Assert.Equal(267.1d, birthdayPigs.BoundingBox.Height, doubleComparer);
Assert.Equal(102.2d, birthdayPigs.BoundingBox.Left, doubleComparer);
Assert.Equal(426.3d, birthdayPigs.BoundingBox.Top, doubleComparer);
}
}

View File

@@ -42,27 +42,27 @@
Assert.Equal("I", page.Letters[0].Value);
Assert.Equal(90.1d, page.Letters[0].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[0].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(90.1d, page.Letters[0].BoundingBox.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[0].BoundingBox.BottomLeft.Y, comparer);
Assert.Equal(94.0d, page.Letters[0].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(719.89d, page.Letters[0].GlyphRectangle.TopRight.Y, comparer);
Assert.Equal(94.0d, page.Letters[0].BoundingBox.TopRight.X, comparer);
Assert.Equal(719.89d, page.Letters[0].BoundingBox.TopRight.Y, comparer);
Assert.Equal("a", page.Letters[5].Value);
Assert.Equal(114.5d, page.Letters[5].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[5].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(114.5d, page.Letters[5].BoundingBox.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[5].BoundingBox.BottomLeft.Y, comparer);
Assert.Equal(119.82d, page.Letters[5].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(714.89d, page.Letters[5].GlyphRectangle.TopRight.Y, comparer);
Assert.Equal(119.82d, page.Letters[5].BoundingBox.TopRight.X, comparer);
Assert.Equal(714.89d, page.Letters[5].BoundingBox.TopRight.Y, comparer);
Assert.Equal("f", page.Letters[16].Value);
Assert.Equal(169.9d, page.Letters[16].GlyphRectangle.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[16].GlyphRectangle.BottomLeft.Y, comparer);
Assert.Equal(169.9d, page.Letters[16].BoundingBox.BottomLeft.X, comparer);
Assert.Equal(709.2d, page.Letters[16].BoundingBox.BottomLeft.Y, comparer);
Assert.Equal(176.89d, page.Letters[16].GlyphRectangle.TopRight.X, comparer);
Assert.Equal(719.89d, page.Letters[16].GlyphRectangle.TopRight.Y, comparer);
Assert.Equal(176.89d, page.Letters[16].BoundingBox.TopRight.X, comparer);
Assert.Equal(719.89d, page.Letters[16].BoundingBox.TopRight.Y, comparer);
}
}

View File

@@ -51,8 +51,10 @@ namespace UglyToad.PdfPig.Tests.Integration
{
var page = document.GetPage(1);
Assert.Contains(page.Letters, x => x.GlyphRectangle.Width != 0);
Assert.Contains(page.Letters, x => x.GlyphRectangle.Height != 0);
Assert.Contains(page.Letters, x => x.BoundingBox.Width != 0);
Assert.Contains(page.Letters, x => x.BoundingBox.Height != 0);
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Width != 0);
Assert.Contains(page.Letters, x => x.GlyphRectangleLoose.Height != 0);
}
}
}

View File

@@ -14,8 +14,8 @@ namespace UglyToad.PdfPig.Tests.Integration
{
var page = document.GetPage(1);
Assert.Contains(page.Letters, x => x.GlyphRectangle.Width != 0);
Assert.Contains(page.Letters, x => x.GlyphRectangle.Height != 0);
Assert.Contains(page.Letters, x => x.BoundingBox.Width != 0);
Assert.Contains(page.Letters, x => x.BoundingBox.Height != 0);
}
}
}

View File

@@ -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);
@@ -168,7 +175,7 @@
foreach (var letter in page.Letters)
{
DrawRectangle(letter.GlyphRectangle, graphics, violetPen, imageHeight, scale);
DrawRectangle(letter.BoundingBox, graphics, violetPen, imageHeight, scale);
}
foreach (var annotation in page.GetAnnotations())
@@ -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"));
}
}
}

View File

@@ -72,7 +72,7 @@
{
foreach (var letter in page.Letters)
{
DrawRectangle(letter.GlyphRectangle, canvas, redPaint, size.Height, Scale);
DrawRectangle(letter.BoundingBox, canvas, redPaint, size.Height, Scale);
}
}
@@ -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);
}
}
}
}

View File

@@ -42,12 +42,12 @@ public class FirstPassParserTests
var results = FirstPassParser.Parse(
new FileHeaderOffset(0),
ib.Bytes,
new CoreTokenScanner(ib.Bytes, true));
new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
Assert.Equal(2, results.Parts.Count);
Assert.NotNull(results.Trailer);
Assert.Equal(results.XrefOffsets[new IndirectReference(8, 0)], 500);
Assert.Equal(results.XrefOffsets[new IndirectReference(8, 0)].Value1, 500);
}
[Fact]
@@ -109,14 +109,12 @@ public class FirstPassParserTests
%%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 = FirstPassParser.Parse(new FileHeaderOffset(0), ib.Bytes, new CoreTokenScanner(ib.Bytes, true));
var results = FirstPassParser.Parse(new FileHeaderOffset(0), ib.Bytes, new CoreTokenScanner(ib.Bytes, true, new StackDepthGuard(256)));
var offsets = results.Parts.Select(x => x.Offset).OrderBy(x => x).ToList();
@@ -125,7 +123,7 @@ public class FirstPassParserTests
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);
}

View File

@@ -589,7 +589,7 @@ public class XrefTableParserTests
{
Assert.True(table.ObjectOffsets.TryGetValue(offset.Key, out var actual));
Assert.Equal(offset.Value, actual);
Assert.Equal(offset.Value, actual.Value1);
}
}

View File

@@ -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]
@@ -210,7 +210,69 @@ l";
var content = File.ReadAllText(path);
var input = StringBytesTestConverter.Convert(content, false);
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, true);
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256), true);
var result = lenientParser.Parse(1, input.Bytes, log);
Assert.NotEmpty(result);
}
[Fact]
public void HandlesIssue953_IntOverflowContent()
{
// After ( + ) Tj operator the content stream becomes corrupt, our current parser therefore reads wrong
// values for operations and this results in a problem when applying the show text operations, we should safely discard or recover on BT/ET boundaries.
const string s =
"""
BT
/TT6 1 Tf
12.007 0 0 12.007 163.2j
-0.19950 Tc
0 Tw
(x)Tj
-0.1949 1.4142 TD
(H)Tj
/TT7 1 Tf
12.031 0 0 12.031 157.38 85.2 Tm
<0077>Tj
-0.1945 1.4114 TD
<0077>Tj
/TT4 1 Tf
12.007 0 0 12.007 174.42 94.5601 Tm
0.0004 Tc
-0.0005 Tw
( + )Tj
E9 478l)]T862.68E9 478E9 484.54 9 155l)]T862.6av9 478E9 15.2(
ET
154.386( i92 m
171.6 97.62 l
S
BT
/TT6 28 Tf
12.03128 T2002.0307 163.2j
-0.19950 DAc
0 Tw853Tj
0.1945 1.4142 om)873j
-0.574142 om)68.80
-0.5797 0 TD
(f)Tj
/TT( )7Tf
0.31945 1.5341 TD371.4j
2.82
8.2652 0 5.724 TD
0 Tc
-0.0001 2748.3( = 091ity )-27483
[(te27483
[(te27483
[(te27483
[(te27483
[(te27483
[(Eq.)52 \(2.1
(
""";
var input = StringBytesTestConverter.Convert(s, false);
var lenientParser = new PageContentParser(ReflectionGraphicsStateOperationFactory.Instance, new StackDepthGuard(256), true);
var result = lenientParser.Parse(1, input.Bytes, log);
Assert.NotEmpty(result);

View File

@@ -59,7 +59,7 @@ startxref
Assert.Equal(4, locations.Count);
Assert.Equal(TestDataOffsets, locations.Values);
Assert.Equal(TestDataOffsets, locations.Values.Select(x => x.Value1));
}
[Fact]
@@ -111,7 +111,7 @@ endobj
s.IndexOf("11 0 obj", StringComparison.OrdinalIgnoreCase)
};
Assert.Equal(expectedLocations, locations.Values);
Assert.Equal(expectedLocations, locations.Values.Select(x => x.Value1));
}
[Fact]
@@ -142,7 +142,7 @@ endobj";
s.IndexOf("5 0 obj", StringComparison.OrdinalIgnoreCase)
};
Assert.Equal(expectedLocations, locations.Values);
Assert.Equal(expectedLocations, locations.Values.Select(x => x.Value1));
}
[Fact]
@@ -156,17 +156,17 @@ endobj";
Assert.Equal(13, locations.Count);
Assert.Equal(6183, locations[new IndirectReference(1, 0)]);
Assert.Equal(244, locations[new IndirectReference(2, 0)]);
Assert.Equal(15, locations[new IndirectReference(3, 0)]);
Assert.Equal(222, locations[new IndirectReference(4, 0)]);
Assert.Equal(5766, locations[new IndirectReference(5, 0)]);
Assert.Equal(353, locations[new IndirectReference(6, 0)]);
Assert.Equal(581, locations[new IndirectReference(7, 0)]);
Assert.Equal(5068, locations[new IndirectReference(8, 0)]);
Assert.Equal(5091, locations[new IndirectReference(9, 0)]);
Assert.Equal(6183, locations[new IndirectReference(1, 0)].Value1);
Assert.Equal(244, locations[new IndirectReference(2, 0)].Value1);
Assert.Equal(15, locations[new IndirectReference(3, 0)].Value1);
Assert.Equal(222, locations[new IndirectReference(4, 0)].Value1);
Assert.Equal(5766, locations[new IndirectReference(5, 0)].Value1);
Assert.Equal(353, locations[new IndirectReference(6, 0)].Value1);
Assert.Equal(581, locations[new IndirectReference(7, 0)].Value1);
Assert.Equal(5068, locations[new IndirectReference(8, 0)].Value1);
Assert.Equal(5091, locations[new IndirectReference(9, 0)].Value1);
var s = GetStringAt(bytes, locations[new IndirectReference(3, 0)]);
var s = GetStringAt(bytes, locations[new IndirectReference(3, 0)].Value1);
Assert.StartsWith("3 0 obj", s);
}
}
@@ -180,17 +180,17 @@ endobj";
Assert.Equal(13, locations.Count);
Assert.Equal(6183, locations[new IndirectReference(1, 0)]);
Assert.Equal(244, locations[new IndirectReference(2, 0)]);
Assert.Equal(15, locations[new IndirectReference(3, 0)]);
Assert.Equal(222, locations[new IndirectReference(4, 0)]);
Assert.Equal(5766, locations[new IndirectReference(5, 0)]);
Assert.Equal(353, locations[new IndirectReference(6, 0)]);
Assert.Equal(581, locations[new IndirectReference(7, 0)]);
Assert.Equal(5068, locations[new IndirectReference(8, 0)]);
Assert.Equal(5091, locations[new IndirectReference(9, 0)]);
Assert.Equal(6183, locations[new IndirectReference(1, 0)].Value1);
Assert.Equal(244, locations[new IndirectReference(2, 0)].Value1);
Assert.Equal(15, locations[new IndirectReference(3, 0)].Value1);
Assert.Equal(222, locations[new IndirectReference(4, 0)].Value1);
Assert.Equal(5766, locations[new IndirectReference(5, 0)].Value1);
Assert.Equal(353, locations[new IndirectReference(6, 0)].Value1);
Assert.Equal(581, locations[new IndirectReference(7, 0)].Value1);
Assert.Equal(5068, locations[new IndirectReference(8, 0)].Value1);
Assert.Equal(5091, locations[new IndirectReference(9, 0)].Value1);
var s = GetStringAt(bytes, locations[new IndirectReference(3, 0)]);
var s = GetStringAt(bytes, locations[new IndirectReference(3, 0)].Value1);
Assert.StartsWith("3 0 obj", s);
}
@@ -203,21 +203,21 @@ endobj";
Assert.Equal(13, locations.Count);
Assert.Equal(17, locations[new IndirectReference(1, 0)]);
Assert.Equal(249, locations[new IndirectReference(2, 0)]);
Assert.Equal(14291, locations[new IndirectReference(3, 0)]);
Assert.Equal(275, locations[new IndirectReference(4, 0)]);
Assert.Equal(382, locations[new IndirectReference(5, 0)]);
Assert.Equal(13283, locations[new IndirectReference(6, 0)]);
Assert.Equal(13309, locations[new IndirectReference(7, 0)]);
Assert.Equal(13556, locations[new IndirectReference(8, 0)]);
Assert.Equal(13926, locations[new IndirectReference(9, 0)]);
Assert.Equal(14183, locations[new IndirectReference(10, 0)]);
Assert.Equal(14224, locations[new IndirectReference(11, 0)]);
Assert.Equal(14428, locations[new IndirectReference(12, 0)]);
Assert.Equal(14488, locations[new IndirectReference(13, 0)]);
Assert.Equal(17, locations[new IndirectReference(1, 0)].Value1);
Assert.Equal(249, locations[new IndirectReference(2, 0)].Value1);
Assert.Equal(14291, locations[new IndirectReference(3, 0)].Value1);
Assert.Equal(275, locations[new IndirectReference(4, 0)].Value1);
Assert.Equal(382, locations[new IndirectReference(5, 0)].Value1);
Assert.Equal(13283, locations[new IndirectReference(6, 0)].Value1);
Assert.Equal(13309, locations[new IndirectReference(7, 0)].Value1);
Assert.Equal(13556, locations[new IndirectReference(8, 0)].Value1);
Assert.Equal(13926, locations[new IndirectReference(9, 0)].Value1);
Assert.Equal(14183, locations[new IndirectReference(10, 0)].Value1);
Assert.Equal(14224, locations[new IndirectReference(11, 0)].Value1);
Assert.Equal(14428, locations[new IndirectReference(12, 0)].Value1);
Assert.Equal(14488, locations[new IndirectReference(13, 0)].Value1);
var s = GetStringAt(bytes, locations[new IndirectReference(12, 0)]);
var s = GetStringAt(bytes, locations[new IndirectReference(12, 0)].Value1);
Assert.StartsWith("12 0 obj", s);
}
@@ -230,7 +230,7 @@ endobj";
var locations = BruteForceSearcher.GetObjectLocations(input);
Assert.Equal(TestDataOffsets, locations.Values);
Assert.Equal(TestDataOffsets, locations.Values.Select(x => x.Value1));
}
[Fact]
@@ -265,7 +265,7 @@ endobj
s.IndexOf("11 0 obj", StringComparison.OrdinalIgnoreCase)
};
Assert.Equal(expectedLocations, locations.Values);
Assert.Equal(expectedLocations, locations.Values.Select(x => x.Value1));
}
private static string GetStringAt(IInputBytes bytes, long location)

View File

@@ -15,8 +15,8 @@
var reference1 = new IndirectReference(7, 0);
var reference2 = new IndirectReference(9, 0);
scanner.Objects[reference1] = new ObjectToken(10, reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(12, reference2, new NumericToken(69));
scanner.Objects[reference1] = new ObjectToken(XrefLocation.File(10), reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(XrefLocation.File(12), reference2, new NumericToken(69));
Assert.True(DirectObjectFinder.TryGet(new IndirectReferenceToken(reference1), scanner, out NumericToken result));
@@ -29,8 +29,8 @@
var reference1 = new IndirectReference(7, 0);
var reference2 = new IndirectReference(9, 0);
scanner.Objects[reference1] = new ObjectToken(10, reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(12, reference2, new NumericToken(69));
scanner.Objects[reference1] = new ObjectToken(XrefLocation.File(10), reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(XrefLocation.File(12), reference2, new NumericToken(69));
var result = DirectObjectFinder.Get<NumericToken>(reference1, scanner);
@@ -43,8 +43,8 @@
var reference1 = new IndirectReference(7, 0);
var reference2 = new IndirectReference(9, 0);
scanner.Objects[reference1] = new ObjectToken(10, reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(12, reference2, new NumericToken(69));
scanner.Objects[reference1] = new ObjectToken(XrefLocation.File(10), reference1, new IndirectReferenceToken(reference2));
scanner.Objects[reference2] = new ObjectToken(XrefLocation.File(12), reference2, new NumericToken(69));
var result = DirectObjectFinder.Get<NumericToken>(new IndirectReferenceToken(reference1), scanner);
@@ -57,7 +57,7 @@
var reference = new IndirectReference(10, 0);
const string expected = "Goopy";
scanner.Objects[reference] = new ObjectToken(10, reference, new ArrayToken(new []
scanner.Objects[reference] = new ObjectToken(XrefLocation.File(10), reference, new ArrayToken(new []
{
new StringToken(expected)
}));
@@ -74,12 +74,12 @@
var reference2 = new IndirectReference(69, 0);
const string expected = "Goopy";
scanner.Objects[reference] = new ObjectToken(10, reference, new ArrayToken(new[]
scanner.Objects[reference] = new ObjectToken(XrefLocation.File(10), reference, new ArrayToken(new[]
{
new IndirectReferenceToken(reference2)
}));
scanner.Objects[reference2] = new ObjectToken(69, reference2, new StringToken(expected));
scanner.Objects[reference2] = new ObjectToken(XrefLocation.File(69), reference2, new StringToken(expected));
var result = DirectObjectFinder.Get<StringToken>(reference, scanner);
@@ -91,7 +91,7 @@
{
var reference = new IndirectReference(10, 0);
scanner.Objects[reference] = new ObjectToken(10, reference, new ArrayToken(new[]
scanner.Objects[reference] = new ObjectToken(XrefLocation.File(10), reference, new ArrayToken(new[]
{
new NumericToken(5), new NumericToken(6), new NumericToken(0)
}));

View File

@@ -149,7 +149,7 @@ three %PDF-1.6";
var bytes = new MemoryInputBytes(input);
var scanner = new CoreTokenScanner(bytes, true, ScannerScope.None);
var scanner = new CoreTokenScanner(bytes, true, new StackDepthGuard(256), ScannerScope.None);
var result = FileHeaderParser.Parse(scanner, bytes, false, log);

View File

@@ -1,5 +1,6 @@
namespace UglyToad.PdfPig.Tests.Parser.Parts;
using PdfPig.Core;
using PdfPig.Parser.FileStructure;
using PdfPig.Tokenization.Scanner;
@@ -26,7 +27,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(456, result.StartXRefDeclaredOffset);
@@ -59,7 +60,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(17, result.StartXRefDeclaredOffset);
@@ -93,7 +94,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(1384733, result.StartXRefDeclaredOffset);
@@ -106,7 +107,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -130,7 +131,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -151,7 +152,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Null(result.StartXRefDeclaredOffset);
@@ -185,7 +186,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(1274665676543, result.StartXRefDeclaredOffset);
@@ -207,7 +208,7 @@ public class FirstPassParserStartXrefTests
var result = FirstPassParser.GetFirstCrossReferenceOffset(
input.Bytes,
new CoreTokenScanner(input.Bytes, true),
new CoreTokenScanner(input.Bytes, true, new StackDepthGuard(256)),
new TestingLog());
Assert.Equal(57695, result.StartXRefDeclaredOffset);

View File

@@ -77,6 +77,7 @@
"UglyToad.PdfPig.Content.DocumentInformation",
"UglyToad.PdfPig.Content.EmbeddedFile",
"UglyToad.PdfPig.Content.Hyperlink",
"UglyToad.PdfPig.Content.IBoundingBox",
"UglyToad.PdfPig.Content.InlineImage",
"UglyToad.PdfPig.Content.IPageFactory`1",
"UglyToad.PdfPig.Content.IPdfImage",
@@ -285,6 +286,7 @@
"UglyToad.PdfPig.Util.Diacritics",
"UglyToad.PdfPig.Util.WhitespaceSizeStatistics",
"UglyToad.PdfPig.Writer.ITokenWriter",
"UglyToad.PdfPig.Writer.LinkAnnotation",
"UglyToad.PdfPig.Writer.PdfAStandard",
"UglyToad.PdfPig.Writer.PdfDocumentBuilder",
"UglyToad.PdfPig.Writer.PdfMerger",

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