Compare commits

...

115 Commits

Author SHA1 Message Date
EliotJones
f8af7daeeb restore copy link func logic 2025-07-24 19:00:53 -05:00
EliotJones
d7f0fd96d8 make link copying more tolerant when adding page
in #1082 and other issues relating to annotations we're running into
constraints of the current model of building a pdf document. currently
we skip all link type annotations, i think we can support copying of links
where the link destination is outside the current document. however the
more i look at this code the more i think we need a radical redesign of
how document building is done because it has been pushed far beyond
its current capabilities, i'll detail my thinking in the related pr in more
detail
2025-07-23 20:56:26 -05:00
EliotJones
5abdfcb96c fix test case due to field renaming 2025-07-20 20:33:46 +01:00
EliotJones
00ca268092 move last uncovered operators to switch statement
in order to remove reflection from the core content stream operators
construction we ensure all types covered by the operations dictionary
have corresponding switch statement support. this moves the remaining
2 operators to the switch statement. fix for #1062
2025-07-20 20:33:46 +01:00
BobLd
813d3baa18 Track IndirectReference instead of only ObjectNumber when checking for cycles during indirect reference resolutionv and add test 2025-07-20 19:24:31 +01:00
EliotJones
2b11961c8c remove debug asserts causing test failures
we encountered a fence constructed in the middle of a field for an unknown
reason so we demolished it. i think this was intended to catch flaws in the
parser logic but the reality is in a pdf anything can happen so we no longer
want to catch these issues and this restores a green test run in debug mode.

fix for #915
2025-07-20 17:42:34 +01:00
EliotJones
efb8c2a803 i merged a pr which broke the build, this updates the build to work
move all arguments to add page to a setting object so it can be extended
in future in a non-breaking api change
2025-07-20 17:36:19 +01:00
jan-sutter
e636212ec8 check for cycles during indirect reference resolution (#1097)
Co-authored-by: Jan Sutter <jan@suttermail.de>
2025-07-20 11:12:55 -05:00
EnraH
3b318e1944 add option to strip annotation (#492)
* add option to strip annotation

* fix implementation and tests

---------

Co-authored-by: arne.hansen <arne.hansen@digitecgalaxus.ch>
Co-authored-by: Eliot Jones <elioty@hotmail.co.uk>
2025-07-20 11:10:15 -05:00
EliotJones
377eb507e8 when writing content to an existing page inverse any global transform #614
when adding a page to a builder from an existing document using either
addpage or copyfrom methods the added page's content stream can contain
a global transform matrix change that will subsequently change all the locations
of any modifications made by the user. here whenever using an existing stream
we apply the inverse of any active transformation matrix

there could be a bug here where if you use 'copy from' with a global transform
active, we then apply the inverse, and you use 'copy from' again to the same
destination page our inverse transform is now active and could potentially
affect the second stream, but I don't think it will
2025-07-20 00:53:03 +01:00
BobLd
ff4e763192 Update hack for 1bpc + DeviceGray 2025-07-19 21:45:41 +01:00
BobLd
6a06452103 Remove decode parameter application from Stencil color space for consistency 2025-07-19 13:45:06 +01:00
BobLd
a5e92cd11c Update run_common_crawl_tests.yml 2025-07-19 12:21:10 +01:00
EliotJones
4bf746c747 add new action to run integration against common crawl corpus 2025-07-19 11:49:34 +01:00
EliotJones
bffd51425d support bfrange having incorrect length in a cmap
the corpus file 0001413.pdf has an off-by-one error in its count
for cmap bfranges. here we exit early if an unexpected
endbfrange operator is encountered early. this matches the pdfbox
behavior:

067d56e4db/fontbox/src/main/java/org/apache/fontbox/cmap/CMapParser.java (L373)
2025-07-19 11:48:04 +01:00
Eliot Jones
e3388ec6b6 fix colorspace error when form xobject contains a transparency group (#1088)
* fix colorspace error when form xobject contains a transparency group

when a form xobject contains a reference to a group xobject this can only
be used to change attributes of the transparency imaging model. the old
code was setting the main colorspaces incorrectly causing errors when the
transparency component had a different number of channels. this was
causing #1071 in addition to the failure in file 0000355.pdf of the test corpus

* add master integration tests for corpus group 0000

* tidy up actions

* remove invalid reference in echo

* move new action to different branch
2025-07-19 11:46:56 +01:00
EliotJones
31658ca020 allow reading to continue if encountering an invalid surrogate pair
investigating the corpus at
https://digitalcorpora.s3.amazonaws.com/s3_browser.html#corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/

the input file 0000000.pdf contained a utf-16 surrogate pair in an input
defined as usc2. the approach of various parsers varies here, adobe
acrobat seems to hard crash, pdf js returns the same text we now
parse, chrome parses the intended text (2 invalid characters and
"ib exam"). we don't care too much about matching chrome exactly
so doing the same as firefox is fine here
2025-07-16 07:45:40 +01:00
EliotJones
1021729727 fall back to times-roman as standard 14 font when lenient
if parsing in lenient mode and encountering a malformed base name
(in this case 'helveticai') we fallback to times-roman as the adobe font
metrics file for a standard 14 font. this aligns with the behavior of pdfbox.
we also log a more informative error in non-lenient modes

this fixes document 0000086.pdf from the corpus
2025-07-16 07:43:49 +01:00
Eliot Jones
9503f9c137 fix off-by-one and optimize brute force xref search #1078 (#1079)
* fix off-by-one and optimize brute force xref search #1078

when performing a brute force xref search we were ending up
off-by-one, update the search to use a ring buffer to reduce
seeking and fix xref detection

* make method testable and add test coverage

* normalize test input on other platforms

* seal circular buffer class
2025-07-16 07:35:24 +01:00
Eliot Jones
016b754c5b back-calculate first char if last char and widths present (#1081)
* back-calculate first char if last char and widths present

when a truetype font has a last char and widths array in its font
dictionary the first char can be calculated #644

* fix off by 1 in last char calculation
2025-07-14 21:57:01 +01:00
Eliot Jones
de3b6ac6f4 use correct bounding boxes for standard 14 glyphs #850 (#1080)
* use correct bounding boxes for standard 14 glyphs #850

previously every bounding box for type 1 standard 14 fonts was assumed
to start at 0,0 and ignored the bounding box data in the font metrics file.
now we correctly read the glyph bounding box while preserving the
existing advance width values for advancing the renderer position

* update test case for new logic
2025-07-14 21:54:42 +01:00
EliotJones
b11f936f22 fix copying of sub-dictionary when keys collide
when copying from a ancestor node of a page's resource dictionary
we were incorrectly writing nested nodes of e.g. /fonts to the root
of the target dictionary, here we write to the intended target node
correctly
2025-07-10 18:32:20 +01:00
EliotJones
7fe60ff8c3 skip single letter final blocks
align with the behavior of pdfbox and c implementations where
single character final blocks are ignored rather than being written.
also makes the error more informative in case it is ever encountered
again.

add more test cases.

it is possible this is hiding the problem and will move the error elsewhere
but this matches the implementation behavior of the 2 reference
implementations. one other potential source for the error is if pdf supports
'<~' as a start of data marker which i can't find in the spec but wikipedia
says might be possible? without documents to trigger the error i think
this is the best fix for now
2025-07-09 07:33:12 +01:00
EliotJones
781991b6bf fix #670 by ignoring duplicate endstream definitions
when parsing a stream object with multiple endstream tokens
the last parsed token was selected instead of the actual stream
token so instead we just skip all following tokens if the first
is a stream and the following tokens are `endstream` operators
only
2025-07-07 20:34:26 +01:00
EliotJones
daaac9350d writer util did not follow reference links #1032
when copying various dictionaries from a source document
to the builder any indirect references in the source document
would throw because the code expected the dictionary token
directly. now we follow the list of indirect references until we
find a non-indirect leaf token. also changes the exception type.
2025-07-06 07:15:35 +01:00
EliotJones
f099dd5827 add test coverage to stream scanning 2025-07-06 07:13:55 +01:00
EliotJones
0586713da3 skip comments in pdf objects streams #926
the file provided in issue #926 contains the following syntax
in pdf object streams:

```
% 750 0 obj
<< >>
```

currently we read the comment token and skip the rest
however this producer is writing nonsense to the stream.
comment tokens are only valid outside streams in pdf files
so we align to the behavior of pdfbox here by skipping the
entire line containing a comment inside a stream which fixes
parsing this file.
2025-07-06 07:13:55 +01:00
xufeng
62612588c8 Fix bug in PngFromPdfImageFactory where softmask is wrongly referenced. 2025-07-06 07:10:11 +01:00
BobLd
bf664c3f0b Use ReadOnlyMemory<byte> in ShowText operators and implement MoveToNextLineShowTextWithSpacing parsing 2025-06-29 14:27:14 +01:00
BobLd
6a50160e65 Prevent RunLengthFilter malicious OOM 2025-06-29 13:57:01 +01:00
BobLd
73ce5bbb73 Make classes related to page content parsing public 2025-06-28 13:17:40 +01:00
BobLd
d1d79b0b4c Check ColorSpace token as dictionary and fix issue #1061 2025-06-25 19:20:02 +01:00
BobLd
89abf6de54 Skip creating IndirectReference in CrossReferenceTablePartBuilder when generationNumber is more than 65,535
Some checks failed
Build and test / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-06-01 14:16:22 +01:00
BobLd
24431b1f9f Optimize internal representation of IndirectReference 2025-06-01 12:02:29 +01:00
BobLd
8f9194c9a4 Miscellaneous minor changes 2025-05-31 23:02:46 +01:00
BobLd
fe3d15d5db Add extension method to get Memory<byte> from MemoryStream, attempting to do it without allocation and update CMapParser 2025-05-30 13:02:55 +01:00
BobLd
b5b58434e9 Make the Diacritics class public for use in external StreamProcessors 2025-05-30 09:25:03 +01:00
BobLd
d9b3891eb3 Do not throw if the Mask dictionary contains a ColorSpace key 2025-05-30 07:53:25 +01:00
BobLd
4bdb85d1ff Modernise PngPredictor and refactor LzwFilter and FlateFilter to reduce memory allocation 2025-05-29 22:43:46 +01:00
BobLd
f84f2aceec Improve memory allocation by changing IFilter.Decode() signature to use Memory<byte> instead of ReadOnlyMemory/ReadOnlySpan 2025-05-29 12:41:50 +01:00
BobLd
2b54a546d3 Check for infinite recursion in ObjectLocationProvider.TryGetOffset() and fix #1050 2025-05-28 20:24:31 +01:00
BobLd
5b566b53da Only reset missed attempts count if table is found in CrossReferenceParser.Parse() and fix #1047 2025-05-27 20:57:38 +02:00
BobLd
ca9f70ffb0 Skip control chars in CoreTokenScanner.MoveNext() and fix #1048 2025-05-27 20:57:38 +02:00
BobLd
67d3dde04a Handle TrueType case in CidFontFactory where the font is CFF, implement missing members in PdfCidCompactFontFormatFont and fix #554
Some checks failed
Build and test / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-05-19 00:27:51 +01:00
BobLd
e4d7805a1f Add test to ensure #822 is fixed 2025-05-18 22:32:07 +01:00
BobLd
6911f31b49 Try to repair xref offset by looking for all startxref and fix #1040
Some checks are pending
Build and test / build (push) Waiting to run
Build and test [MacOS] / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-05-18 17:32:27 +01:00
BobLd
bf7c3c01d0 Fix bug introduced in #1039
Some checks failed
Build and test / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-05-13 18:44:31 +01:00
ricflams
c3c477a2ba Bugfix and optimize GetStartXrefPosition
The bugfix was the important part but the optimization is pretty nice too.

- Bugfix: If startxref was found so far back (eg in the very beginning which can be the case for Linearized PDFs) that we ended up setting actualStartOffset to 0 then the loop would exit immediately without actually searching that part.
- Optimization: GetStartXrefPosition would search for startxref in the last 2048 bytes and then double that search-range (looking back 4096, 8192, etc bytes) to look for startxref until the entire file was searched. This was rather inefficient since each step would search the same parts over and over again. This has been changed to properly search (still increasingly larger) chunks that doesn't overlap. On a test of 5000 PDFs that reduced their load-time by 10%.
- Change: No need for the exception to say that startxref couldn't be found "in the last 2048 characters" since the entire file was searched anyway.
2025-05-13 18:21:31 +01:00
BobLd
4dab2ef239 Add early support for Stencil masking, rename SoftMaskImage property into MaskImage and make sure IsInlineImage is true for InlineImage
Some checks failed
Build and test / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-05-11 16:05:55 +01:00
BobLd
0bed135bad Make sure the value of the ImageMask / Im token is check in ColorSpaceDetailsParser 2025-05-11 14:34:40 +01:00
BobLd
47584716ec Add support for MacCatalyst in SystemFontFinder
Some checks failed
Build and test / build (push) Has been cancelled
Build and test [MacOS] / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-04-24 19:17:48 +01:00
BobLd
afdd1f8924 Fix issue #1013 2025-04-20 18:03:04 +01:00
BobLd
580858348b Seal PdfSubpath class and IPathCommand implementations, fix Close.GetHashCode() and fix #1027 2025-04-12 16:39:48 +01:00
BobLd
24902f1839 Update README.md 2025-04-06 12:06:36 +01:00
BobLd
87f5735b26 Refactor AesEncryptionHelper and check for sting length when using < net8 2025-04-06 12:04:24 +01:00
BobLd
eeac910e44 Fix CanFilterClippedLetters() failing on MacOS because font is not available 2025-04-06 12:04:24 +01:00
BobLd
5439c07ef5 Make ExplicitDestinationType a byte a fix CanCreateDocumentWithOutline() failing test 2025-04-06 12:04:24 +01:00
BobLd
b8bd40e486 Create build_and_test_macos.yml 2025-04-06 12:04:24 +01:00
BobLd
4fbcc112d3 Fix bug where FormXObject bbox needs to be normalised 2025-03-30 22:01:12 +01:00
BobLd
ede77c20f5 Update README.md 2025-03-30 11:39:47 +01:00
BobLd
5fb36d452f Handle SoftMask 2025-03-30 10:33:27 +01:00
BobLd
74d61bd985 Fix PatternColor Equals() method and fix #1016 2025-03-26 19:48:51 +00:00
Matěj Štágl
ba8e1e1b45 fix #1017
fix test name
2025-03-26 18:59:39 +00:00
BobLd
0754e7f003 Implement clipping in ProcessFormXObject()
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-03-23 21:18:29 +00:00
BobLd
306642a234 Add SetStrokeDetails() and SetFillDetails() to PdfPath and tidy up ContentStreamProcessor 2025-03-23 20:07:43 +00:00
BobLd
204f488ebf Improve Jpeg2000Helper to support J2K codec and add test
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-03-09 14:05:05 +00:00
BobLd
a4a0fe220a Bump version to 0.1.11-alpha001
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-03-08 13:42:57 +00:00
BobLd
f1f27a63e1 Update run_integration_tests.yml 2025-03-08 13:15:40 +00:00
BobLd
d36e9a900f version 0.1.10 2025-03-08 13:00:43 +00:00
BobLd
1b3c7bd355 Better handle integer overflow in DocstrumBoundingBoxes
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2025-03-02 18:29:21 +00:00
BobLd
67d8f56366 Do not throw exception when lenient parsing in GetExtendedGraphicsStateDictionary() and improve StackDictionary.TryGetValue() to not throw on empty 2025-03-02 11:51:26 +00:00
BobLd
f26e7d90a3 Pass IFilterProvider to IFilter.Decode() and handle null in PdfExtensions.Resolve()
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-02-23 09:37:25 +00:00
BobLd
d973e03206 Resolve image data (implementation from @kasperdaff)
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-02-22 20:58:17 +00:00
BobLd
c4a235fb62 Update Microsoft NuGet packages for UglyToad.PdfPig.Package
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-02-22 12:55:11 +00:00
BobLd
7ceeb5bff8 Update Microsoft and SkiaSharp NuGet packages
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-02-20 20:36:20 +00:00
BobLd
b9bcfa5997 Make DecodeParameterResolver class public 2025-02-20 19:53:39 +00:00
BobLd
5a06e1e1cc Assess if transformedGlyphBounds and use transformedPdfBounds as fallback and fix #987
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-02-20 00:11:06 +00:00
BobLd
1298356f10 Fix typo in CharacterBoundingBox and small optimisation in TrueTypeFont and seal both classes 2025-02-20 00:11:06 +00:00
BobLd
1660c734e2 Add JPX bits per component decoding
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-02-08 11:08:44 +00:00
BobLd
fdb8835b37 CcittFaxDecodeFilter: do not check for input length, invert bitmap with ref byte and fix #982
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-02-02 14:26:16 +00:00
BobLd
5209850af7 Update UnpackComponents() to account for 1bpc + DeviceGray (hack for Jbig2)
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-01-21 06:49:46 +00:00
BobLd
b7e2245eca Refactor XObjectFactory
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-01-19 21:31:33 +00:00
BobLd
24b9a99b8a Fix warnings about "type 'K' cannot be used as type parameter 'TKey' in the generic type or method 'Dictionary<TKey, TValue>'"
Some checks are pending
Build and test / build (push) Waiting to run
Run Integration Tests / build (push) Waiting to run
2025-01-19 11:45:19 +00:00
BobLd
c4576e4ffa Do not throw error on Pop when stack size is 1 in lenient mode and fix #973 2025-01-19 11:19:32 +00:00
BobLd
92d3439465 Update UglyToad.PdfPig.ConsoleRunner target framework to net8
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2025-01-14 22:00:18 +00:00
simonedd
1ca52f4d70 Update OptionalContentGroupElement.cs
Add HexToken case in OptionalContent parsing
2025-01-14 21:42:20 +00:00
BobLd
f86cc582ab Execute RemoveStridePadding in place when possible 2025-01-05 17:42:53 +00:00
BobLd
53cf4f2ced Properly handle ZapfDingbats font for TrueTypeSimpleFont and add tests 2025-01-05 17:28:32 +00:00
BobLd
4430a01e43 Properly handle ZapfDingbats font for Type1FontSimple and Type1Standard14Font and add tests 2025-01-05 16:17:59 +00:00
BobLd
585e940acf Always check for additional glyph list and add tests 2025-01-05 16:17:59 +00:00
BobLd
d1779cc739 Make ColorSpaceDetails.BaseNumberOfColorComponents public to allow for external image factories 2025-01-04 09:29:28 +00:00
BobLd
2b14ae03c1 Return a copy of the ArrayPoolBufferWriter buffer in Ascii85, AsciiHex and RunLength filters and fix #964 2024-12-31 14:05:17 +01:00
BobLd
366fc4fe04 Fix out of range exception in AnnotationProvider 2024-12-31 11:54:58 +01:00
BobLd
b6474dda4c Improve UnwrapIndexedColorSpaceBytes 2024-12-31 11:09:57 +01:00
BobLd
50dca593da Do not throw exception when lenient parsing in ON in CrossReferenceParser and fix #959 2024-12-28 12:29:28 +01:00
BobLd
7ec4e692a9 Fix "Nightly Release" pipeline following csproj changes 2024-12-15 10:58:38 +00:00
GrabzIt
952a07ef41 Fix for IndexOutOfRangeException exception
Fixes issue: https://github.com/UglyToad/PdfPig/issues/954
2024-12-12 19:21:16 +00:00
Kizaemon
a2ae1f16d6 New GetText() option: NegativeGapAsWhitespace
When parsing PDF files with tables containing multiple lines in a cell or "merged" cells, the separate words can appear out of horizontal order. This option can better predict the spaces between the words.
2024-12-09 20:39:25 +00:00
BobLd
97ae62ce6f Update additional glyph list to latest from PDFBox (#948) 2024-11-27 19:52:23 +00:00
BobLd
9ad51067b0 Handle odd ligatures names and fix #945 (#946) 2024-11-27 19:44:17 +00:00
BobLd
20804245d0 Handle alternate Unicode name representation cXXX and fix #943 (#944) 2024-11-24 20:24:36 +00:00
BobLd
bcc8ccecbe Stop treating Warnings as Errors (#941) 2024-11-23 18:23:22 +00:00
madelson
7db347e9f7 Enable trimming and AOT (#939)
* Enable trimming and AOT analysis for all none-test projects

* Try annotating various XmlSerializer related functions with RequiresUnreferencedCode

* fix remaining AOT warnings

* fix attribute warning message

---------

Co-authored-by: Richard Webb <richard.webb@helpsystems.com>
2024-11-20 07:42:37 +00:00
madelson
8ca539942d Optimize cross reference object offset validation by avoiding nested loop
* Optimize cross reference object offset validation by avoiding nested loops

* Address https://github.com/UglyToad/PdfPig/pull/935#discussion_r1839585652
2024-11-13 19:47:50 +00:00
BobLd
132ada7fac Upgrade tests project NuGet packages 2024-11-02 10:07:48 +00:00
BobLd
d3bf63fdbf Move Paths, GetAnnotations() and GetOptionalContents() outside of ExperimentalAccess and mark Experimental class and reference as obsolete 2024-10-30 19:22:13 +00:00
BobLd
40af401364 Default page number to 0 in ExplicitDestination when the Dest has no page number and fix #736
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
Nightly Release / tests (push) Has been cancelled
Nightly Release / Check latest commit (push) Has been cancelled
Nightly Release / build_and_publish_nightly (push) Has been cancelled
2024-10-25 21:28:11 +01:00
BobLd
e10609e4e1 Use pdfScanner in ReadVerticalDisplacements and fix #693 and return 0 in CMap on exception in ReadByte() if useLenientParsing is true and fix #692
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2024-10-19 00:29:42 +01:00
Arnaud TAMAILLON
ea95a7ae7a Support decrypting V4/R4 files with AESV2 and no Length property
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2024-10-17 20:43:21 +01:00
BobLd
8cee4f480f Introduce ParsingOptions.FilterProvider and BaseFilterProvider and make CcittFaxCompressionType a byte 2024-10-17 20:27:24 +01:00
BobLd
4b5cb4736f Make IFilter implementation classes public 2024-10-17 20:27:24 +01:00
BobLd
c6793da4f4 Reorganise Filters folder 2024-10-17 20:27:24 +01:00
BobLd
e903b6c365 Remove list from FileTrailerParser.GetStartXrefPosition()
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2024-10-15 20:02:45 +01:00
BobLd
f40545b0a5 Add early version of BrowserSystemFontLister
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2024-10-12 16:49:36 +01:00
BobLd
a258090e1c Fix GetTextOrientation by cleanly checking if rotation is divisible by 90 and fix #913
Some checks failed
Build and test / build (push) Has been cancelled
Run Integration Tests / build (push) Has been cancelled
2024-10-07 20:08:24 +01:00
258 changed files with 146012 additions and 3557 deletions

View File

@@ -0,0 +1,28 @@
name: Build and test [MacOS]
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@master
- name: Set up dotnet core
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
2.1.x
6.0.x
8.0.x
# Build the release build
- name: Build the solution
run: dotnet build -c Release src/UglyToad.PdfPig.sln
- name: Run the tests
run: dotnet test -c Release src/UglyToad.PdfPig.sln

View File

@@ -0,0 +1,58 @@
name: Run Common Crawl Tests
on:
push:
branches: [master]
workflow_dispatch:
workflow_call:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up dotnet core
uses: actions/setup-dotnet@v3
with:
dotnet-version: "8.0.x"
- name: Restore corpus cache 0000, 0001
id: restore-corpus
uses: actions/cache@v4
with:
path: corpus/
key: ${{ runner.os }}-pdf-corpus-0000-0001
- name: Download corpus if cache missed 0000, 0001
if: steps.restore-corpus.outputs.cache-hit != 'true'
run: |
mkdir -p corpus/zipfiles
cd corpus/zipfiles
echo "Downloading 0000.zip"
wget -nv https://digitalcorpora.s3.amazonaws.com/corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/0000.zip -O 0000.zip
wget -nv https://digitalcorpora.s3.amazonaws.com/corpora/files/CC-MAIN-2021-31-PDF-UNTRUNCATED/zipfiles/0000-0999/0001.zip -O 0001.zip
cd ..
unzip 'zipfiles/*.zip' -d extracted
# run: rm -f zipfiles/*.zip
- name: Remove unwanted test files
run: |
skip_files=(
"corpus/extracted/0000399.pdf"
"corpus/extracted/0000819.pdf"
"corpus/extracted/0000920.pdf"
"corpus/extracted/0000300.pdf"
"corpus/extracted/0001589.pdf"
"corpus/extracted/0001957.pdf"
)
for file in "${skip_files[@]}"; do
if [ -f "$file" ]; then
echo "Removing $file"
rm "$file"
fi
done
- name: Run tests against corpus
run: dotnet run --project tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj "corpus/extracted"

View File

@@ -19,21 +19,21 @@ jobs:
- name: Restore cached part 1
id: restore-cache-p1
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: archive/part1-archive.7z
key: ${{ runner.os }}-part-1
- name: Restore cached part 2
id: restore-cache-p2
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: archive/part2-archive.7z
key: ${{ runner.os }}-part-2
- name: Restore cached part 3
id: restore-cache-p3
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: archive/part3-archive.7z
key: ${{ runner.os }}-part-3

1
.gitignore vendored
View File

@@ -105,6 +105,7 @@ $tf/
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
.idea
# JustCode is a .NET coding add-in
.JustCode

View File

@@ -5,13 +5,14 @@
[![Gitter](https://badges.gitter.im/pdfpig/community.svg)](https://gitter.im/pdfpig/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![nuget](https://img.shields.io/nuget/dt/PdfPig)](https://www.nuget.org/packages/PdfPig/)
[![Build and test](https://github.com/UglyToad/PdfPig/actions/workflows/build_and_test.yml/badge.svg)](https://github.com/UglyToad/PdfPig/actions/workflows/build_and_test.yml)
[![Build and test [MacOS]](https://github.com/UglyToad/PdfPig/actions/workflows/build_and_test_macos.yml/badge.svg)](https://github.com/UglyToad/PdfPig/actions/workflows/build_and_test_macos.yml)
This project allows users to read and extract text and other content from PDF files. In addition the library can be used to create simple PDF documents
containing text and geometrical shapes.
This project aims to port [PDFBox](https://github.com/apache/pdfbox) to C#.
**Migrating to 0.1.6 from 0.1.x?** Use this guide: [migration to 0.1.6](https://github.com/UglyToad/PdfPig/wiki/Migration-to-0.1.6).
## Wiki
Check out our [wiki](https://github.com/UglyToad/PdfPig/wiki) for more examples and detailed guides on the API.
@@ -55,7 +56,7 @@ An example of the output of this is shown below:
Where for the PDF text ("Write something in") shown at the top the 3 words (in pink) are detected and each word contains the individual letters with glyph bounding boxes.
### Ceate PDF Document
### Create PDF Document
To create documents use the class `PdfDocumentBuilder`. The Standard 14 fonts provide a quick way to get started:
```cs
@@ -77,10 +78,10 @@ The output is a 1 page PDF document with the text "Hello World!" in Helvetica ne
![Image shows a PDF document in Google Chrome's PDF viewer. The text "Hello World!" is visible](https://raw.githubusercontent.com/UglyToad/Pdf/master/documentation/builder-output.png)
Each font must be registered with the PdfDocumentBuilder prior to use enable pages to share the font resources. Only Standard 14 fonts and TrueType fonts (.ttf) are supported.
Each font must be registered with the `PdfDocumentBuilder` prior to use enable pages to share the font resources. Only Standard 14 fonts and TrueType fonts (.ttf) are supported.
### Advanced Document Extraction
In this example a more advanced document extraction is performed. PdfDocumentBuilder is used to create a copy of the pdf with debug information (bounding boxes and reading order) added.
In this example a more advanced document extraction is performed. `PdfDocumentBuilder` is used to create a copy of the pdf with debug information (bounding boxes and reading order) added.
```cs
@@ -183,7 +184,7 @@ The document contains the version of the PDF specification it complies with, acc
decimal version = document.Version;
### Document Creation (0.0.5)
### Document Creation
The `PdfDocumentBuilder` creates a new document with no pages or content.
@@ -256,7 +257,7 @@ string title = document.Information.Title;
// etc...
```
### Document Structure (0.0.3)
### Document Structure
The document now has a Structure member:
@@ -286,21 +287,21 @@ bool isA4 = size == PageSize.A4;
string text = page.Text;
There is a new (0.0.3) method which provides access to the words. This uses basic heuristics and is not reliable or well-tested:
There is a method which provides access to the words. The default method uses basic heuristics. For advanced cases, You can also implement your own `IWordExtractor` or use the `NearestNeighbourWordExtractor`:
IEnumerable<Word> words = page.GetWords();
You can also (0.0.6) access the raw operations used in the page's content stream for drawing graphics and content on the page:
You can also access the raw operations used in the page's content stream for drawing graphics and content on the page:
IReadOnlyList<IGraphicsStateOperation> operations = page.Operations;
Consult the PDF specification for the meaning of individual operators.
There is also an early access (0.0.3) API for retrieving the raw bytes of PDF image objects per page:
There is also an API for retrieving the PDF image objects per page:
IEnumerable<XObjectImage> images = page.ExperimentalAccess.GetRawImages();
IEnumerable<XObjectImage> images = page.GetImages();
This API will be changed in future releases.
Please read the [wiki on Images](https://github.com/UglyToad/PdfPig/wiki/Images).
### Letter
@@ -322,15 +323,15 @@ These letters contain:
Letter position is measured in PDF coordinates where the origin is the lower left corner of the page. Therefore a higher Y value means closer to the top of the page.
### Annotations (0.0.5)
### Annotations
Early support for retrieving annotations on each page is provided using the method:
Retrieving annotations on each page is provided using the method:
page.ExperimentalAccess.GetAnnotations()
page.GetAnnotations()
This call is not cached and the document must not have been disposed prior to use. The annotations API may change in future.
This call is not cached and the document must not have been disposed prior to use.
### Bookmarks (0.0.10)
### Bookmarks
The bookmarks (outlines) of a document may be retrieved at the document level:
@@ -338,7 +339,7 @@ The bookmarks (outlines) of a document may be retrieved at the document level:
This will return `false` if the document does not define any bookmarks.
### Forms (0.0.10)
### Forms
Form fields for interactive forms (AcroForms) can be retrieved using:
@@ -350,15 +351,15 @@ The fields can be accessed using the `AcroForm`'s `Fields` property. Since the f
Please note the forms are readonly and values cannot be changed or added using PdfPig.
### Hyperlinks (0.1.0)
### Hyperlinks
A page has a method to extract hyperlinks (annotations of link type):
IReadOnlyList<UglyToad.PdfPig.Content.Hyperlink> hyperlinks = page.GetHyperlinks();
### TrueType (0.1.0)
### TrueType
The classes used to work with TrueType fonts in the PDF file are now available for public consumption. Given an input file:
The classes used to work with TrueType fonts in the PDF file are available for public consumption. Given an input file:
```cs
@@ -372,7 +373,7 @@ TrueTypeFont font = TrueTypeFontParser.Parse(input);
The parsed font can then be inspected.
### Embedded Files (0.1.0)
### Embedded Files
PDF files may contain other files entirely embedded inside them for document annotations. The list of embedded files and their byte content may be accessed:
@@ -386,7 +387,7 @@ if (document.Advanced.TryGetEmbeddedFiles(out IReadOnlyList<EmbeddedFile> files)
}
```
### Merging (0.1.2)
### Merging
You can merge 2 or more existing PDF files using the `PdfMerger` class:

View File

@@ -0,0 +1,6 @@
<Project>
<PropertyGroup Condition="'$(IsTestProject)' != 'true'">
<IsTrimmable Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">true</IsTrimmable>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
</PropertyGroup>
</Project>

View File

@@ -3,20 +3,33 @@
using System;
using System.Diagnostics;
// https://github.com/apache/pdfbox/blob/trunk/pdfbox/src/main/java/org/apache/pdfbox/cos/COSObjectKey.java#L25
/// <summary>
/// Used to uniquely identify and refer to objects in the PDF file.
/// </summary>
public readonly struct IndirectReference
public readonly struct IndirectReference : IEquatable<IndirectReference>
{
private const int NUMBER_OFFSET = sizeof(ushort) * 8;
private static readonly long GENERATION_MASK = (long)Math.Pow(2, NUMBER_OFFSET) - 1;
private static readonly long MAX_OBJECT_NUMBER = (long)(Math.Pow(2, sizeof(long) * 8 - NUMBER_OFFSET) - 1) / 2;
// combined number and generation
// The lowest 16 bits hold the generation 0-65535
// The rest is used for the number (even though 34 bit are sufficient for 10 digits)
private readonly long numberAndGeneration;
/// <summary>
/// A positive integer object number.
/// </summary>
public long ObjectNumber { get; }
// Below is different from PdfBox as we keep the sign of the offset number (use >> instead of >>> (unsigned right shift))
public long ObjectNumber => numberAndGeneration >> NUMBER_OFFSET;
/// <summary>
/// A non-negative integer generation number which starts as 0 and increases if the file is updated incrementally.
/// <para>The maximum generation number is 65,535.</para>
/// </summary>
public int Generation { get; }
public int Generation => (int)(numberAndGeneration & GENERATION_MASK);
/// <summary>
/// Create a new <see cref="IndirectReference"/>
@@ -26,26 +39,47 @@
[DebuggerStepThrough]
public IndirectReference(long objectNumber, int generation)
{
ObjectNumber = objectNumber;
Generation = generation;
if (generation < 0)
{
// Note: We do not check generation for max value and let it overflow
throw new ArgumentOutOfRangeException(nameof(generation), "Generation number must not be a negative value.");
}
if (objectNumber < -MAX_OBJECT_NUMBER || objectNumber > MAX_OBJECT_NUMBER)
{
throw new ArgumentOutOfRangeException(nameof(objectNumber), $"Object number must be between -{MAX_OBJECT_NUMBER:##,###} and {MAX_OBJECT_NUMBER:##,###}.");
}
numberAndGeneration = ComputeInternalHash(objectNumber, generation);
}
/// <summary>
/// Calculate the internal hash value for the given object number and generation number.
/// </summary>
/// <param name="num">The object number.</param>
/// <param name="gen">The generation number.</param>
/// <returns>The internal hash for the given values.</returns>
private static long ComputeInternalHash(long num, int gen)
{
return num << NUMBER_OFFSET | (gen & GENERATION_MASK);
}
/// <inheritdoc />
public bool Equals(IndirectReference other)
{
return other.numberAndGeneration == numberAndGeneration;
}
/// <inheritdoc />
public override bool Equals(object obj)
{
if (obj is IndirectReference reference)
{
return reference.ObjectNumber == ObjectNumber
&& reference.Generation == Generation;
}
return false;
return obj is IndirectReference other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(ObjectNumber, Generation);
return numberAndGeneration.GetHashCode();
}
/// <inheritdoc />
@@ -53,5 +87,17 @@
{
return $"{ObjectNumber} {Generation}";
}
/// <inheritdoc/>
public static bool operator ==(IndirectReference left, IndirectReference right)
{
return left.Equals(right);
}
/// <inheritdoc/>
public static bool operator !=(IndirectReference left, IndirectReference right)
{
return !(left == right);
}
}
}

View File

@@ -0,0 +1,38 @@
namespace UglyToad.PdfPig.Core
{
using System;
using System.IO;
using System.Runtime.InteropServices;
/// <summary>
/// Memory extensions.
/// </summary>
public static class MemoryHelper
{
/// <summary>
/// Gets a read-only <see cref="MemoryStream"/> from a ReadOnlyMemory&lt;byte&gt;, attempting without memory allocation.
/// </summary>
public static MemoryStream AsReadOnlyMemoryStream(this ReadOnlyMemory<byte> memory)
{
if (MemoryMarshal.TryGetArray(memory, out ArraySegment<byte> array))
{
return new MemoryStream(array.Array!, array.Offset, array.Count, false);
}
return new MemoryStream(memory.ToArray(), false);
}
/// <summary>
/// Gets the Memory&lt;byte&gt; from a <see cref="MemoryStream"/>, attempting without memory allocation.
/// </summary>
public static Memory<byte> AsMemory(this MemoryStream stream)
{
if (stream.TryGetBuffer(out ArraySegment<byte> segment))
{
return segment.AsMemory();
}
return stream.ToArray();
}
}
}

View File

@@ -10,7 +10,7 @@
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
/// </remarks>
public readonly struct PdfLine
public readonly struct PdfLine : IEquatable<PdfLine>
{
/// <summary>
/// Length of the line.
@@ -70,14 +70,17 @@
/// <summary>
/// Returns a value indicating whether this <see cref="PdfLine"/> is equal to a specified <see cref="PdfLine"/> .
/// </summary>
/// <param name="obj"></param>
public override bool Equals(object obj)
{
if (obj is PdfLine line)
{
return line.Point1.Equals(Point1) && line.Point2.Equals(Point2);
}
return false;
return obj is PdfLine other && Equals(other);
}
/// <summary>
/// Returns a value indicating whether this <see cref="PdfLine"/> is equal to a specified <see cref="PdfLine"/> .
/// </summary>
public bool Equals(PdfLine other)
{
return Point1.Equals(other.Point1) && Point2.Equals(other.Point2);
}
/// <summary>
@@ -87,5 +90,17 @@
{
return HashCode.Combine(Point1, Point2);
}
/// <inheritdoc/>
public static bool operator ==(PdfLine left, PdfLine right)
{
return left.Equals(right);
}
/// <inheritdoc/>
public static bool operator !=(PdfLine left, PdfLine right)
{
return !(left == right);
}
}
}

View File

@@ -12,7 +12,7 @@
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
/// </remarks>
public readonly struct PdfPoint
public readonly struct PdfPoint : IEquatable<PdfPoint>
{
/// <summary>
/// The origin of the coordinates system.
@@ -83,14 +83,17 @@
/// <summary>
/// Returns a value indicating whether this <see cref="PdfPoint"/> is equal to a specified <see cref="PdfPoint"/> .
/// </summary>
/// <param name="obj"></param>
public override bool Equals(object obj)
{
if (obj is PdfPoint point)
{
return point.X == X && point.Y == Y;
}
return false;
return obj is PdfPoint other && Equals(other);
}
/// <summary>
/// Returns a value indicating whether this <see cref="PdfPoint"/> is equal to a specified <see cref="PdfPoint"/> .
/// </summary>
public bool Equals(PdfPoint other)
{
return X.Equals(other.X) && Y.Equals(other.Y);
}
/// <summary>
@@ -106,5 +109,17 @@
{
return $"(x:{X.ToString(CultureInfo.InvariantCulture)}, y:{Y.ToString(CultureInfo.InvariantCulture)})";
}
/// <inheritdoc/>
public static bool operator ==(PdfPoint left, PdfPoint right)
{
return left.Equals(right);
}
/// <inheritdoc/>
public static bool operator !=(PdfPoint left, PdfPoint right)
{
return !(left == right);
}
}
}

View File

@@ -11,7 +11,7 @@
/// The Y-axis extends vertically upwards and the X-axis horizontally to the right.
/// Unless otherwise specified on a per-page basis, units in PDF space are equivalent to a typographic point (1/72 inch).
/// </remarks>
public readonly struct PdfRectangle
public readonly struct PdfRectangle : IEquatable<PdfRectangle>
{
/// <summary>
/// Top left point of the rectangle.
@@ -166,5 +166,38 @@
{
return $"[{TopLeft}, {Width.ToString(CultureInfo.InvariantCulture)}, {Height.ToString(CultureInfo.InvariantCulture)}]";
}
/// <inheritdoc />
public bool Equals(PdfRectangle other)
{
return TopLeft.Equals(other.TopLeft) &&
TopRight.Equals(other.TopRight) &&
BottomRight.Equals(other.BottomRight) &&
BottomLeft.Equals(other.BottomLeft);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return obj is PdfRectangle other && Equals(other);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(TopLeft, TopRight, BottomRight, BottomLeft);
}
/// <inheritdoc/>
public static bool operator ==(PdfRectangle left, PdfRectangle right)
{
return left.Equals(right);
}
/// <inheritdoc/>
public static bool operator !=(PdfRectangle left, PdfRectangle right)
{
return !(left == right);
}
}
}

View File

@@ -8,7 +8,7 @@
/// <summary>
/// A supbpath is made up of a sequence of connected segments.
/// </summary>
public class PdfSubpath
public sealed class PdfSubpath
{
private readonly List<IPathCommand> commands = new List<IPathCommand>();
@@ -416,8 +416,10 @@
/// <summary>
/// Close the current <see cref="PdfSubpath"/>.
/// </summary>
public class Close : IPathCommand
public sealed class Close : IPathCommand
{
private static readonly int _hash = typeof(Close).GetHashCode();
/// <inheritdoc />
public PdfRectangle? GetBoundingRectangle()
{
@@ -439,15 +441,14 @@
/// <inheritdoc />
public override int GetHashCode()
{
// ReSharper disable once BaseObjectGetHashCodeCallInGetHashCode
return base.GetHashCode();
return _hash;
}
}
/// <summary>
/// Move drawing of the current <see cref="PdfSubpath"/> to the specified location.
/// </summary>
public class Move : IPathCommand
public sealed class Move : IPathCommand
{
/// <summary>
/// The location to move to.
@@ -497,7 +498,7 @@
/// <summary>
/// Draw a straight line between two points.
/// </summary>
public class Line : IPathCommand
public sealed class Line : IPathCommand
{
/// <summary>
/// The start of the line.

View File

@@ -189,7 +189,34 @@
return found;
}
/// <summary>
/// Whether the given string is at this position in the input.
/// Resets to the current offset once read.
/// </summary>
public static bool IsString(IInputBytes bytes, ReadOnlySpan<byte> s)
{
bool found = true;
var startOffset = bytes.CurrentOffset;
foreach (var c in s)
{
if (bytes.CurrentByte != c)
{
found = false;
break;
}
bytes.MoveNext();
}
bytes.Seek(startOffset);
return found;
}
/// <summary>
/// Read a long from the input.
/// </summary>
@@ -252,14 +279,6 @@
throw new PdfDocumentFormatException($"Error: Expected an integer type at offset {bytes.CurrentOffset}, instead got \'{OtherEncodings.BytesAsLatin1String(intBytes)}\'");
}
}
/// <summary>
/// Whether the given character is a space.
/// </summary>
public static bool IsSpace(int c)
{
return c == ' ';
}
/// <summary>
/// Whether the given character value is a valid hex value.

View File

@@ -32,17 +32,17 @@
{
if (stream == null)
{
throw new ArgumentNullException();
throw new ArgumentNullException(nameof(stream));
}
if (!stream.CanRead)
{
throw new ArgumentException("The provided stream did not support reading.");
throw new ArgumentException("The provided stream did not support reading.", nameof(stream));
}
if (!stream.CanSeek)
{
throw new ArgumentException("The provided stream did not support seeking.");
throw new ArgumentException("The provided stream did not support seeking.", nameof(stream));
}
this.stream = stream;

View File

@@ -6,7 +6,7 @@
/// <summary>
/// Specifies the conversion from the transformed coordinate space to the original untransformed coordinate space.
/// </summary>
public readonly struct TransformationMatrix
public readonly struct TransformationMatrix : IEquatable<TransformationMatrix>
{
/// <summary>
/// The default <see cref="TransformationMatrix"/>.
@@ -465,12 +465,21 @@
/// <inheritdoc />
public override bool Equals(object obj)
{
if (!(obj is TransformationMatrix m))
{
return false;
}
return obj is TransformationMatrix other && Equals(other);
}
return Equals(this, m);
/// <inheritdoc />
public bool Equals(TransformationMatrix other)
{
return row1.Equals(other.row1) &&
row2.Equals(other.row2) &&
row3.Equals(other.row3) &&
A.Equals(other.A) &&
B.Equals(other.B) &&
C.Equals(other.C) &&
D.Equals(other.D) &&
E.Equals(other.E) &&
F.Equals(other.F);
}
/// <summary>
@@ -478,25 +487,13 @@
/// </summary>
public static bool Equals(TransformationMatrix a, TransformationMatrix b)
{
for (var i = 0; i < Rows; i++)
{
for (var j = 0; j < Columns; j++)
{
if (a[i, j] != b[i, j])
{
return false;
}
}
}
return true;
return a.Equals(b);
}
/// <inheritdoc />
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(row1);
hashCode.Add(row2);
hashCode.Add(row3);
@@ -506,7 +503,6 @@
hashCode.Add(D);
hashCode.Add(E);
hashCode.Add(F);
return hashCode.ToHashCode();
}

View File

@@ -1,27 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.10-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net471'">
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.1" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' OR '$(TargetFramework)'=='net471'">
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
<PackageReference Include="System.Memory" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
</Project>

View File

@@ -4,6 +4,7 @@
using Content;
using DocumentLayoutAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Xml;
@@ -94,6 +95,9 @@
/// </summary>
/// <param name="document">The document to extract page layouts from.</param>
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from AltoDocument may be trimmed if not referenced directly")]
#endif
public string Get(PdfDocument document, bool includePaths = false)
{
var altoDocument = CreateAltoDocument("unknown");
@@ -105,6 +109,9 @@
/// Get the Alto (XML) string of the page layout. Excludes <see cref="T:UglyToad.PdfPig.Geometry.PdfSubpath" />s.
/// </summary>
/// <param name="page">The page to export the XML layout for.</param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from AltoDocument may be trimmed if not referenced directly")]
#endif
public string Get(Page page) => Get(page, false);
/// <summary>
@@ -112,6 +119,9 @@
/// </summary>
/// <param name="page">The page to export the XML layout for.</param>
/// <param name="includePaths">Whether the output should include the PdfPaths present in the page.</param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from AltoDocument may be trimmed if not referenced directly")]
#endif
public string Get(Page page, bool includePaths)
{
var document = CreateAltoDocument("unknown");
@@ -184,7 +194,7 @@
if (includePaths)
{
altoPage.PrintSpace.GraphicalElements = page.ExperimentalAccess.Paths
altoPage.PrintSpace.GraphicalElements = page.Paths
.Select(p => ToAltoGraphicalElement(p, page.Height))
.ToArray();
}
@@ -355,6 +365,9 @@
};
}
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from AltoDocument may be trimmed if not referenced directly")]
#endif
private string Serialize(AltoDocument altoDocument)
{
var serializer = new XmlSerializer(typeof(AltoDocument));
@@ -377,6 +390,9 @@
/// <summary>
/// Deserialize an <see cref="AltoDocument"/> from a given Alto format XML document.
/// </summary>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from AltoDocument may be trimmed if not referenced directly")]
#endif
public static AltoDocument Deserialize(string xmlPath)
{
var serializer = new XmlSerializer(typeof(AltoDocument));

View File

@@ -4,6 +4,7 @@
using Core;
using DocumentLayoutAnalysis;
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using UglyToad.PdfPig.DocumentLayoutAnalysis.PageSegmenter;
using UglyToad.PdfPig.Graphics;
@@ -118,6 +119,9 @@
/// Get the hOCR (HTML) string of the page layout. Excludes PdfPaths.
/// </summary>
/// <param name="page">The page.</param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.")]
#endif
public string Get(Page page)
{
return Get(page, false);
@@ -193,7 +197,7 @@
if (includePaths)
{
foreach (var path in page.ExperimentalAccess.Paths)
foreach (var path in page.Paths)
{
hocr += "\n" + GetCode(path, page.Height, true, level + 1);
}

View File

@@ -1,6 +1,7 @@
namespace UglyToad.PdfPig.DocumentLayoutAnalysis.Export
{
using Content;
using System.Diagnostics.CodeAnalysis;
/// <summary>
/// Exports the page's text into the desired format.
@@ -12,6 +13,9 @@
/// </summary>
/// <param name="page">The page to convert to the format.</param>
/// <returns>The <see langword="string"/> containing the page contents represented in a compatible format.</returns>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.")]
#endif
string Get(Page page);
}
}

View File

@@ -10,6 +10,7 @@
using ReadingOrderDetector;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
@@ -105,6 +106,9 @@
/// Get the PAGE-XML (XML) string of the pages layout. Excludes PdfPaths.
/// </summary>
/// <param name="page"></param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from PageXmlDocument may be trimmed if not referenced directly")]
#endif
public string Get(Page page)
{
return Get(page, false);
@@ -115,6 +119,9 @@
/// </summary>
/// <param name="page"></param>
/// <param name="includePaths">Draw PdfPaths present in the page.</param>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from PageXmlDocument may be trimmed if not referenced directly")]
#endif
public string Get(Page page, bool includePaths)
{
PageXmlData data = new PageXmlData();
@@ -240,7 +247,7 @@
if (includePaths)
{
foreach (var path in page.ExperimentalAccess.Paths)
foreach (var path in page.Paths)
{
var graphicalElement = ToPageXmlLineDrawingRegion(path, data, page.Width, page.Height);
@@ -373,6 +380,9 @@
};
}
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from PageXmlDocument may be trimmed if not referenced directly")]
#endif
private string Serialize(PageXmlDocument pageXmlDocument)
{
XmlSerializer serializer = new XmlSerializer(typeof(PageXmlDocument));
@@ -395,6 +405,9 @@
/// <summary>
/// Deserialize an <see cref="PageXmlDocument"/> from a given PAGE format XML document.
/// </summary>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("Members from PageXmlDocument may be trimmed if not referenced directly")]
#endif
public static PageXmlDocument Deserialize(string xmlPath)
{
XmlSerializer serializer = new XmlSerializer(typeof(PageXmlDocument));

View File

@@ -2,6 +2,7 @@
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Xml;
@@ -65,11 +66,14 @@
/// <summary>
/// Get the page contents as an SVG.
/// </summary>
#if NET6_0_OR_GREATER
[RequiresUnreferencedCode("'RequiresUnreferencedCodeAttribute' annotations must match across all interface implementations or overrides.")]
#endif
public string Get(Page page)
{
var builder = new StringBuilder($"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width='{Math.Round(page.Width, Rounding)}' height='{Math.Round(page.Height, Rounding)}'>\n<g transform=\"scale(1, 1) translate(0, 0)\">\n");
foreach (var path in page.ExperimentalAccess.Paths)
foreach (var path in page.Paths)
{
if (!path.IsClipping)
{

View File

@@ -226,7 +226,13 @@
throw new ArgumentException("DocstrumBoundingBoxes: the bin length must be positive when commputing peak average distance.", nameof(binLength));
}
var max = (int)Math.Ceiling(distances.Max());
double maxDbl = Math.Ceiling(distances.Max());
if (maxDbl > int.MaxValue)
{
throw new OverflowException($"Error while casting maximum distance of {maxDbl} to integer.");
}
int max = (int)maxDbl;
if (max == 0)
{
max = binLength;

View File

@@ -110,6 +110,11 @@
{
var gap = letter.StartBaseLine.X - previous.EndBaseLine.X;
if (options.NegativeGapAsWhitespace)
{
gap = Math.Abs(gap);
}
if (WhitespaceSizeStatistics.IsProbablyWhitespace(gap, previous))
{
sb.Append(" ");
@@ -178,6 +183,13 @@
/// character. Default <see langword="false"/>.
/// </summary>
public bool ReplaceWhitespaceWithSpace { get; set; }
/// <summary>
/// When parsing PDF files with tables containing multiple lines in a cell or "merged" cells,
/// the separate words can appear out of horizontal order. This option can better predict the
/// spaces between the words. Default <see langword="false"/>.
/// </summary>
public bool NegativeGapAsWhitespace { get; set; }
}
}
}

View File

@@ -1,26 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.10-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Fonts\UglyToad.PdfPig.Fonts.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig\UglyToad.PdfPig.csproj" />
</ItemGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Fonts\UglyToad.PdfPig.Fonts.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig\UglyToad.PdfPig.csproj" />
</ItemGroup>
</Project>

View File

@@ -13,7 +13,7 @@
public class GlyphList
{
/// <summary>
/// <c>.notdef</c>.
/// <c>.notdef</c> name.
/// </summary>
public const string NotDefined = ".notdef";
@@ -22,22 +22,15 @@
private readonly Dictionary<string, string> oddNameToUnicodeCache = new Dictionary<string, string>();
private static readonly Lazy<GlyphList> LazyAdobeGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("glyphlist"));
private static readonly Lazy<GlyphList> LazyAdobeGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("glyphlist", "additional"));
/// <summary>
/// The Adobe Glyph List.
/// The Adobe Glyph List (includes an extension to the Adobe Glyph List.).
/// </summary>
public static GlyphList AdobeGlyphList => LazyAdobeGlyphList.Value;
private static readonly Lazy<GlyphList> LazyAdditionalGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("additional"));
/// <summary>
/// An extension to the Adobe Glyph List.
/// </summary>
public static GlyphList AdditionalGlyphList => LazyAdditionalGlyphList.Value;
private static readonly Lazy<GlyphList> LazyZapfDingbatsGlyphList = new Lazy<GlyphList>(() => GlyphListFactory.Get("zapfdingbats"));
/// <summary>
/// Zapf Dingbats.
/// </summary>
@@ -84,6 +77,7 @@
/// <summary>
/// Get the unicode value for the glyph name.
/// See <see href="https://github.com/adobe-type-tools/agl-specification"/>.
/// </summary>
public string NameToUnicode(string name)
{
@@ -102,26 +96,48 @@
return result;
}
string unicode;
// Remove suffixes
string? unicode;
// 1. Drop all the characters from the glyph name starting with the first occurrence of a period (U+002E FULL STOP), if any.
if (name.IndexOf('.') > 0)
{
unicode = NameToUnicode(name.Substring(0, name.IndexOf('.')));
}
else if (name.StartsWith("uni") && name.Length == 7)
// 2. Split the remaining string into a sequence of components, using underscore (U+005F LOW LINE) as the delimiter.
else if (name.IndexOf('_') > 0)
{
/*
* MOZILLA-3136-0.pdf
* 68-1990-01_A.pdf
* TIKA-2054-0.pdf
*/
var sb = new StringBuilder();
foreach (var s in name.Split('_'))
{
sb.Append(NameToUnicode(s));
}
unicode = sb.ToString();
}
// Otherwise, if the component is of the form uni (U+0075, U+006E, and U+0069) followed by a sequence of uppercase hexadecimal
// digits (09 and AF, meaning U+0030 through U+0039 and U+0041 through U+0046), if the length of that sequence is a multiple
// of four, and if each group of four digits represents a value in the ranges 0000 through D7FF or E000 through FFFF, then
// interpret each as a Unicode scalar value and map the component to the string made of those scalar values. Note that the range
// and digit-length restrictions mean that the uni glyph name prefix can be used only with UVs in the Basic Multilingual Plane (BMP).
else if (name.StartsWith("uni") && (name.Length - 3) % 4 == 0)
{
// test for Unicode name in the format uniXXXX where X is hex
int nameLength = name.Length;
var uniStr = new StringBuilder();
var foundUnicode = true;
for (int chPos = 3; chPos + 4 <= nameLength; chPos += 4)
{
if (!int.TryParse(name.AsSpanOrSubstring(chPos, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var codePoint))
if (!int.TryParse(name.AsSpanOrSubstring(chPos, 4),
NumberStyles.HexNumber,
CultureInfo.InvariantCulture,
out var codePoint))
{
foundUnicode = false;
break;
return null;
}
if (codePoint > 0xD7FF && codePoint < 0xE000)
@@ -132,26 +148,30 @@
uniStr.Append((char)codePoint);
}
if (!foundUnicode)
{
return null;
}
unicode = uniStr.ToString();
}
else if (name.StartsWith("u", StringComparison.Ordinal) && name.Length == 5)
// Otherwise, if the component is of the form u (U+0075) followed by a sequence of four to six uppercase hexadecimal digits (09
// and AF, meaning U+0030 through U+0039 and U+0041 through U+0046), and those digits represents a value in the ranges 0000 through
// D7FF or E000 through 10FFFF, then interpret it as a Unicode scalar value and map the component to the string made of this scalar value.
else if (name.StartsWith("u", StringComparison.Ordinal) && name.Length >= 5 && name.Length <= 7)
{
// test for an alternate Unicode name representation uXXXX
var codePoint = int.Parse(name.AsSpanOrSubstring(1), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
if (codePoint > 0xD7FF && codePoint < 0xE000)
{
throw new InvalidFontFormatException(
$"Unicode character name with disallowed code area: {name}");
throw new InvalidFontFormatException($"Unicode character name with disallowed code area: {name}");
}
unicode = char.ConvertFromUtf32(codePoint);
}
// Ad-hoc special cases
else if (name.StartsWith("c", StringComparison.OrdinalIgnoreCase) && name.Length >= 3 && name.Length <= 4)
{
// name representation cXXX
var codePoint = int.Parse(name.AsSpanOrSubstring(1), NumberStyles.Integer, CultureInfo.InvariantCulture);
unicode = char.ConvertFromUtf32(codePoint);
}
// Otherwise, map the component to an empty string.
else
{
return null;

View File

@@ -4,49 +4,56 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using Util;
internal class GlyphListFactory
internal static class GlyphListFactory
{
public static GlyphList Get(string listName)
#if NET
private const char Semicolon = ';';
#else
private static readonly char[] Semicolon = [';'];
#endif
public static GlyphList Get(params string[] listNames)
{
using (var resource =
typeof(GlyphListFactory).Assembly.GetManifestResourceStream(
$"UglyToad.PdfPig.Fonts.Resources.GlyphList.{listName}"))
var result = new Dictionary<string, string>(listNames.Any(n => string.Equals("glyphlist", n, StringComparison.OrdinalIgnoreCase)) ? 4300 : 0);
foreach (var listName in listNames)
{
if (resource == null)
using (var resource =
typeof(GlyphListFactory).Assembly.GetManifestResourceStream(
$"UglyToad.PdfPig.Fonts.Resources.GlyphList.{listName}"))
{
throw new ArgumentException($"No embedded glyph list resource was found with the name {listName}.");
}
if (resource == null)
{
throw new ArgumentException($"No embedded glyph list resource was found with the name {listName}.");
}
int? capacity = null;
// Prevent too much wasted memory capacity for Adobe GlyphList
if (string.Equals("glyphlist", listName, StringComparison.OrdinalIgnoreCase))
{
capacity = 4300;
ReadInternal(resource, result);
}
return ReadInternal(resource, capacity);
}
#if NET
result.TrimExcess();
#endif
return new GlyphList(result);
}
public static GlyphList Read(Stream stream)
{
return ReadInternal(stream);
var result = new Dictionary<string, string>();
ReadInternal(stream, result);
return new GlyphList(result);
}
private static readonly char[] Semicolon = [';'];
private static GlyphList ReadInternal(Stream stream, int? defaultDictionaryCapacity = 0)
private static void ReadInternal(Stream stream, Dictionary<string, string> result)
{
if (stream == null)
{
throw new ArgumentNullException(nameof(stream));
}
var result = defaultDictionaryCapacity.HasValue ? new Dictionary<string, string>(defaultDictionaryCapacity.Value) : [];
using (var reader = new StreamReader(stream))
{
while (!reader.EndOfStream)
@@ -62,7 +69,7 @@
{
continue;
}
var parts = line.Split(Semicolon, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
@@ -86,11 +93,10 @@
value += char.ConvertFromUtf32(code);
}
System.Diagnostics.Debug.Assert(!result.ContainsKey(key));
result[key] = value;
}
}
return new GlyphList(result);
}
}
}

View File

@@ -66,10 +66,12 @@ bracketleftbig;005B
bracketleftBig;005B
bracketleftbigg;005B
bracketleftBigg;005B
bracketleftmath;005B
bracketrightBig;005D
bracketrightbig;005D
bracketrightbigg;005D
bracketrightBigg;005D
bracketrightmath;005D
ceilingleftbig;2308
ceilingleftBig;2308
ceilingleftBigg;2308
@@ -88,6 +90,8 @@ contintegraldisplay;222E
contintegraltext;222E
coproductdisplay;2210
coproducttext;2210
epsilon1;03B5
equalmath;003D
floorleftBig;230A
floorleftbig;230A
floorleftbigg;230A
@@ -113,10 +117,13 @@ parenleftBig;0028
parenleftbig;0028
parenleftBigg;0028
parenleftbigg;0028
parenleftmath;0028
parenrightBig;0029
parenrightbig;0029
parenrightBigg;0029
parenrightbigg;0029
parenrightmath;0029
plusmath;002B
prime;2032
productdisplay;220F
producttext;220F

View File

@@ -0,0 +1,16 @@
namespace UglyToad.PdfPig.Fonts.SystemFonts
{
#if NET
using System.Collections.Generic;
internal sealed class BrowserSystemFontLister : ISystemFontLister
{
// Very early version, intended to help developing support for browser
public IEnumerable<SystemFontRecord> GetAllFonts()
{
yield break;
}
}
#endif
}

View File

@@ -92,7 +92,15 @@ namespace UglyToad.PdfPig.Fonts.SystemFonts
#if NET
else if (OperatingSystem.IsAndroid())
{
lister = new AndroidSystemFontLister();
lister = new AndroidSystemFontLister();
}
else if (OperatingSystem.IsBrowser())
{
lister = new BrowserSystemFontLister();
}
else if (OperatingSystem.IsMacCatalyst())
{
lister = new MacSystemFontLister();
}
#endif
else

View File

@@ -8,8 +8,6 @@
{
public IEnumerable<SystemFontRecord> GetAllFonts()
{
// TODO: Could use System.Drawing InstalledFontCollection to do this?
var winDir = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
var fonts = Path.Combine(winDir, "Fonts");

View File

@@ -22,7 +22,7 @@ namespace UglyToad.PdfPig.Fonts.TrueType.Glyphs
r2c1 = r2C1;
}
public static CompositeTransformMatrix3By2 Identity { get; } = new CompositeTransformMatrix3By2(1, 0, 0, 1, 0, 0);
public static readonly CompositeTransformMatrix3By2 Identity = new CompositeTransformMatrix3By2(1, 0, 0, 1, 0, 0);
public static CompositeTransformMatrix3By2 CreateTranslation(double x, double y) => new CompositeTransformMatrix3By2(1, 0, 0, 1, x, y);
public CompositeTransformMatrix3By2 WithTranslation(double x, double y)

View File

@@ -83,7 +83,7 @@
private ushort[] MergeContourEndPoints(IGlyphDescription glyph)
{
var destinationLastEndPoint = EndPointsOfContours[EndPointsOfContours.Length - 1] + 1;
var destinationLastEndPoint = (EndPointsOfContours.Length > 0 ? EndPointsOfContours[EndPointsOfContours.Length - 1] : 0) + 1;
var endPoints = new ushort[EndPointsOfContours.Length + glyph.EndPointsOfContours.Length];

View File

@@ -9,7 +9,7 @@
/// <summary>
/// A TrueType font.
/// </summary>
public class TrueTypeFont
public sealed class TrueTypeFont
{
/// <summary>
/// The font version number.
@@ -107,8 +107,12 @@
{
boundingBox = default(PdfRectangle);
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index)
|| TableRegister.GlyphTable == null)
if (TableRegister.GlyphTable == null)
{
return false;
}
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index))
{
return false;
}
@@ -139,8 +143,12 @@
{
path = null;
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index)
|| TableRegister.GlyphTable == null)
if (TableRegister.GlyphTable == null)
{
return false;
}
if (!TryGetGlyphIndex(characterCode, characterCodeToGlyphId, out var index))
{
return false;
}

View File

@@ -1,44 +1,40 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.10-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(TargetFramework)|$(Platform)'=='Debug|netstandard2.0|AnyCPU'">
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors />
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\AdobeFontMetrics\*" />
<None Remove="Resources\GlyphList\*" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\AdobeFontMetrics\MustRead.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\AdobeFontMetrics\*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\GlyphList\*" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
<PackageReference Include="System.Memory" Version="4.5.5" />
</ItemGroup>
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net462;net471;net6.0;net8.0</TargetFrameworks>
<LangVersion>12</LangVersion>
<Version>0.1.11-alpha001</Version>
<IsTestProject>False</IsTestProject>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>..\pdfpig.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\AdobeFontMetrics\*" />
<None Remove="Resources\GlyphList\*" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\AdobeFontMetrics\MustRead.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\AdobeFontMetrics\*" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\GlyphList\*" />
</ItemGroup>
<ItemGroup>
<None Include="..\pdfpig.snk" Link="pdfpig.snk" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\UglyToad.PdfPig.Core\UglyToad.PdfPig.Core.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Tokenization\UglyToad.PdfPig.Tokenization.csproj" />
<ProjectReference Include="..\UglyToad.PdfPig.Tokens\UglyToad.PdfPig.Tokens.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='net462'">
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)'=='netstandard2.0' or '$(TargetFramework)'=='net462' or '$(TargetFramework)'=='net471'">
<PackageReference Include="System.Memory" Version="4.6.0" />
</ItemGroup>
</Project>

View File

@@ -30,6 +30,55 @@
Assert.True(reference1.Equals(reference2));
}
[Fact]
public void IndirectReferenceHashTest()
{
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 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 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 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 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);
}
[Fact]
public void TwoIndirectReferenceNotEqual()
{

View File

@@ -20,9 +20,13 @@ O<DJ+*.@<*K0@<6L(Df-\0Ec5e;DffZ(EZee.Bl.9pF""AGXBPCsi + DGm >@3BB / F * &OCAfu2
l(DId<j@<? 3r@:F % a + D58'ATD4$Bl@l3De:,-DJs`8ARoFb/0JMK@qB4^F!,R<AKZ&-DfTqBG%G
> uD.RTpAKYo'+CT/5+Cei#DII?(E,9)oF*2M7/c~>");
var result = filter.Decode(bytes, dictionary, 0);
var result = filter.Decode(bytes, dictionary, TestFilterProvider.Instance, 0);
var text = Encoding.ASCII.GetString(result.ToArray());
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
Assert.Equal("Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, " +
"that by a perseverance of delight in the continued and indefatigable generation of knowledge, " +
@@ -30,16 +34,49 @@ O<DJ+*.@<*K0@<6L(Df-\0Ec5e;DffZ(EZee.Bl.9pF""AGXBPCsi + DGm >@3BB / F * &OCAfu2
text);
}
[Fact]
public void ReplacesZWithEmptyBytes()
[Theory]
[InlineData("BE", "h")]
[InlineData("BOq", "he")]
[InlineData("BOtu", "hel")]
[InlineData("BOu!r", "hell")]
[InlineData("BOu!rDZ", "hello")]
[InlineData("BOu!rD]f", "hello ")]
[InlineData("BOu!rD]j6", "hello w")]
[InlineData("BOu!rD]j7B", "hello wo")]
[InlineData("BOu!rD]j7BEW", "hello wor")]
[InlineData("BOu!rD]j7BEbk", "hello worl")]
[InlineData("BOu!rD]j7BEbo7", "hello world")]
[InlineData("BOu!rD]j7BEbo80", "hello world!")]
public void DecodesHelloWorld(string encoded, string decoded)
{
var bytes = Encoding.ASCII.GetBytes("9jqo^zBlbD-");
var result = filter.Decode(
Encoding.ASCII.GetBytes(encoded),
dictionary,
TestFilterProvider.Instance,
0);
var result = filter.Decode(bytes, dictionary, 1);
Assert.Equal(decoded, Encoding.ASCII.GetString(result.ToArray()));
}
var text = Encoding.ASCII.GetString(result.ToArray());
[Theory]
[InlineData("9jqo^zBlbD-", "Man \0\0\0\0is d")]
[InlineData("", "")]
[InlineData("z", "\0\0\0\0")]
[InlineData("zz", "\0\0\0\0\0\0\0\0")]
[InlineData("zzz", "\0\0\0\0\0\0\0\0\0\0\0\0")]
public void ReplacesZWithEmptyBytes(string encoded, string decoded)
{
var bytes = Encoding.ASCII.GetBytes(encoded);
Assert.Equal("Man \0\0\0\0is d", text);
var result = filter.Decode(bytes, dictionary, TestFilterProvider.Instance, 1);
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
Assert.Equal(decoded, text);
}
[Fact]
@@ -47,19 +84,22 @@ O<DJ+*.@<*K0@<6L(Df-\0Ec5e;DffZ(EZee.Bl.9pF""AGXBPCsi + DGm >@3BB / F * &OCAfu2
{
var bytes = Encoding.ASCII.GetBytes("qjzqo^");
Action action = () => filter.Decode(bytes, dictionary, 0);
Action action = () => filter.Decode(bytes, dictionary, TestFilterProvider.Instance, 0);
Assert.Throws<InvalidOperationException>(action);
}
[Fact]
public void SingleCharacterLastThrows()
[Theory]
[InlineData("@rH:%B", "cool")]
[InlineData("A~>", "")]
[InlineData("@rH:%A~>", "cool")]
public void SingleCharacterLastIgnores(string encoded, string decoded)
{
var bytes = Encoding.ASCII.GetBytes("9jqo^B");
var bytes = Encoding.ASCII.GetBytes(encoded);
Action action = () => filter.Decode(bytes, dictionary, 1);
var result = filter.Decode(bytes, dictionary, TestFilterProvider.Instance, 1);
Assert.Throws<ArgumentOutOfRangeException>(action);
Assert.Equal(decoded, Encoding.ASCII.GetString(result.ToArray()));
}
private const string PdfContent = @"1 0 obj
@@ -102,10 +142,14 @@ T()<(%'A;f?Ma+CT;%+E_a:A0>K&EZek1D/aN,F)u&6DBNA*A0>f4BOu4*+EM76E,9eK+B3(_<%9""p.
F*22=@:F%a+=SF4C'moi+=Li?EZeh0FD)e-@<>p#@;]TuBl.9kATKCFGA(],AKYo5BOu4*+CT;%+C#7pF_Pr+@VfTuDf0B:+=SF4C'moi+=
Li?EZek1DKKT1F`2DD/TboKAKY](@:s.m/h%oBC'mC/$>""*cF*)G6@;Q?_DIdZpC&~>";
var result = filter.Decode(Encoding.ASCII.GetBytes(input), dictionary, 0);
var result = filter.Decode(Encoding.ASCII.GetBytes(input), dictionary, TestFilterProvider.Instance, 0);
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
var text = Encoding.ASCII.GetString(result.ToArray());
Assert.Equal(PdfContent.Replace("\r\n", "\n"), text);
}
@@ -122,13 +166,59 @@ T()<(%'A;f?Ma+CT;%+E_a:A0>K&EZek1D/aN,F)u&6DBNA*A0>f4BOu4*+EM76E,9eK+B3(_<%9""p.
F*22=@:F%a+=SF4C'moi+=Li?EZeh0FD)e-@<>p#@;]TuBl.9kATKCFGA(],AKYo5BOu4*+CT;%+C#7pF_Pr+@VfTuDf0B:+=SF4C'moi+=
Li?EZek1DKKT1F`2DD/TboKAKY](@:s.m/h%oBC'mC/$>""*cF*)G6@;Q?_DIdZpC&";
var result = filter.Decode(Encoding.ASCII.GetBytes(input), dictionary, 0);
var result = filter.Decode(Encoding.ASCII.GetBytes(input), dictionary, TestFilterProvider.Instance, 0);
var text = Encoding.ASCII.GetString(result.ToArray());
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
Assert.Equal(PdfContent.Replace("\r\n", "\n"), text);
}
[Fact]
public void DecodeParallel()
{
Parallel.For(0, 100_000, i =>
{
if (i % 2 == 0)
{
var bytes = Encoding.ASCII.GetBytes("9jqo^zBlbD-");
var result = filter.Decode(bytes, dictionary, TestFilterProvider.Instance, 1);
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
Assert.Equal("Man \0\0\0\0is d", text);
}
else
{
const string input =
@"0d&.mDdmGg4?O`>9P&*SFD)dS2E2gC4pl@QEb/Zr$8N_r$:7]!01IZ=0eskNAdU47<+?7h+B3Ol2_m!C+?)#1+B1
`9>:<KhASu!rA7]9oF*)G6@;U'.@ps6t@V$[&ART*lARTXoCj@HP2DlU*/0HBI+B1r?0H_r%1a#ac$<nof.3LB""+=MAS+D58'ATD3qCj@.
F@;@;70ea^uAKYi.Eb-A7E+*6f+EV:*DBN1?0ek+_+B1r?<%9""=ASu!rA7]9oF*)G6@;U'<.3MT)$8<SS1,pCU6jd-H;e7
C#1,U1&Ft""Og2'=;YEa`c,ASu!rA8,po+Dk\3BQ%F&+CT;%+CQ]A1,'h!Ft""Oh2'=;UBl%3eCh4`'DBMbD7O]H>0H_br.:""&q8d[6p/M
T()<(%'A;f?Ma+CT;%+E_a:A0>K&EZek1D/aN,F)u&6DBNA*A0>f4BOu4*+EM76E,9eK+B3(_<%9""p.!0AMEb031ATMF#F<G%,DIIR2+Cno
&@3B9%+CT.1.3LK*+=KNS6V0ilAoD^,@<=+N>p**=$</Jt-rY&$AKYo'+EV:.+Cf>,E,oN2F(oQ1+D#G#De*R""B-;&&FD,T'F!+n3AKY4b
F*22=@:F%a+=SF4C'moi+=Li?EZeh0FD)e-@<>p#@;]TuBl.9kATKCFGA(],AKYo5BOu4*+CT;%+C#7pF_Pr+@VfTuDf0B:+=SF4C'moi+=
Li?EZek1DKKT1F`2DD/TboKAKY](@:s.m/h%oBC'mC/$>""*cF*)G6@;Q?_DIdZpC&~>";
var result = filter.Decode(Encoding.ASCII.GetBytes(input), dictionary, TestFilterProvider.Instance, 0);
#if !NET
string text = Encoding.ASCII.GetString(result.ToArray());
#else
string text = Encoding.ASCII.GetString(result.Span);
#endif
Assert.Equal(PdfContent.Replace("\r\n", "\n"), text);
}
});
}
}
}

View File

@@ -16,7 +16,7 @@
var input = Encoding.ASCII.GetBytes(
"7368652073656C6C73207365617368656C6C73206F6E20746865207365612073686F7265");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
var decodedText = Encoding.ASCII.GetString(decoded.ToArray());
@@ -31,7 +31,7 @@
var input = Encoding.ASCII.GetBytes(
"<7368652073656C6C73207365617368656C6C73206F6E20746865207365612073686F7265>");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
var decodedText = Encoding.ASCII.GetString(decoded.ToArray());
@@ -47,7 +47,7 @@
@"6F6E6365207 5706F6E206120 74696D6520696E
20612067616C6178792046617220466172204177 6179");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
var decodedText = Encoding.ASCII.GetString(decoded.ToArray());
@@ -61,7 +61,7 @@
var input = Encoding.ASCII.GetBytes("6f6e63652075706f6e20612074696d6520696e20612067616c61787920466172204661722041776179");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
var decodedText = Encoding.ASCII.GetString(decoded.ToArray());
@@ -75,7 +75,7 @@
{
var input = Encoding.ASCII.GetBytes(inputString);
Action action = () => new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
Action action = () => new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
Assert.Throws<InvalidOperationException>(action);
}
@@ -85,7 +85,7 @@
{
var input = Encoding.ASCII.GetBytes("AE5>");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
#pragma warning disable SYSLIB0001
var decodedText = Encoding.UTF7.GetString(decoded.ToArray());
@@ -101,7 +101,7 @@
var input = Encoding.ASCII.GetBytes("6f6e63652075706f6e20612074696d6520696e20612067616c61787920466172204661722041776179> There is stuff following the EOD.");
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, 1);
var decoded = new AsciiHexDecodeFilter().Decode(input, dictionary, TestFilterProvider.Instance, 1);
var decodedText = Encoding.ASCII.GetString(decoded.ToArray());

View File

@@ -1,6 +1,6 @@
namespace UglyToad.PdfPig.Tests.Filters
{
using PdfPig.Filters;
using PdfPig.Filters.Lzw;
public class BitStreamTests
{

View File

@@ -31,7 +31,7 @@
};
var expectedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
var decodedBytes = filter.Decode(encodedBytes, new DictionaryToken(dictionary), 0);
var decodedBytes = filter.Decode(encodedBytes, new DictionaryToken(dictionary), TestFilterProvider.Instance, 0);
Assert.Equal(expectedBytes, decodedBytes.ToArray());
}
}

View File

@@ -17,7 +17,7 @@
{
inputStream.Seek(0, SeekOrigin.Begin);
var result = filter.Encode(inputStream, parameters, 0);
var decoded = filter.Decode(result, parameters, 0);
var decoded = filter.Decode(result, parameters, TestFilterProvider.Instance, 0);
Assert.Equal(input, decoded.ToArray());
}
}

View File

@@ -24,7 +24,7 @@
1, 10, 19
};
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), 1);
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), TestFilterProvider.Instance, 1);
var expectedResult = new byte[]
{
@@ -53,7 +53,7 @@
90, 6, 7
};
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), 0);
var decoded = filter.Decode(data, new DictionaryToken(new Dictionary<NameToken, IToken>()), TestFilterProvider.Instance, 0);
var expectedResult = new byte[]
{

View File

@@ -57,7 +57,7 @@
{
var list = new GlyphList(new Dictionary<string, string>
{
{"Boris", "B"}
{ "Boris", "B" }
});
var result = list.NameToUnicode("Boris.Special");
@@ -70,7 +70,7 @@
{
var list = new GlyphList(new Dictionary<string, string>
{
{"B", "X"}
{ "B", "X" }
});
var result = list.NameToUnicode("uni0042");
@@ -83,12 +83,27 @@
{
var list = new GlyphList(new Dictionary<string, string>
{
{"E", "Æ"}
{ "E", "Æ" }
});
var result = list.NameToUnicode("u0045");
Assert.Equal("E", result);
}
[Fact(Skip = "TODO - String don't match")]
public void NameToUnicodeConvertAglSpecification()
{
// https://github.com/adobe-type-tools/agl-specification?tab=readme-ov-file#3-examples
var list = new GlyphList(new Dictionary<string, string>
{
{ "Lcommaaccent", "\u013B" }
});
var result = list.NameToUnicode("Lcommaaccent_uni20AC0308_u1040C.alternate");
Assert.Equal("\u013B\u20AC\u0308\u1040C", result);
}
}
}

View File

@@ -146,6 +146,72 @@ endbfchar";
Assert.Equal("10.003", cmap.Version);
}
[Fact]
public void CanParseFile0001413CMap()
{
const string cmap =
"""
/CIDInit /ProcSet findresource
begin
12 dict
begin
begincmap
/CIDSystemInfo <</Ordering (GB1) /Registry (Adobe) /Supplement 2 >> def
/CMapName /Adobe-GB1-2 def
/CMapType 2 def
1 begincodespacerange
<0000> <ffff> endcodespacerange
100 beginbfrange
<0001> <0002> <0022> <0003> <0019> <0025> <001b> <0023> <003f> <0024> <0025> <0049> <0026> <0033> <004c> <0034> <0036> <005d> <0037> <003d> <0061> <003e> <004b> <0069> <004c> <004d> <0079> <0053> <0054> <3001> <0055> <005a> <300a> <005c> <005d> <4e00> <0060> <0062> <4e09> <0063> <0064> <4e0d> <0066> <0067> <4e13> <006a> <006d> <4e1a> <006f> <0070> <4e24> <0076> <0079> <4e38> <007a> <007b> <4e3d> <007e> <007f> <4e48> <0080> <0085> <4e4b> <0089> <008b> <4e5f> <008d> <008e> <4e70> <0090> <0091> <4e88> <0092> <0093> <4e8b> <0094> <0095> <4e8e> <0097> <0098> <4e94> <0099> <009a> <4e9a> <009c> <00a0> <4ea4> <00a3> <00a4> <4ec0> <00a5> <00a6> <4ec5> <00a7> <00a8> <4eca> <00a9> <00aa> <4ecd> <00ac> <00af> <4ed3> <00b0> <00b1> <4ed8> <00b3> <00b5> <4ee3> <00b9> <00ba> <4ef6> <00bf> <00c1> <4f0f> <00c2> <00c5> <4f17> <00c6> <00c7> <4f1f> <00cb> <00cc> <4f2f> <00d2> <00d4> <4f4d> <00d8> <00d9> <4f5b> <00e2> <00e3> <4fa6> <00e7> <00e8> <4fc3> <00ef> <00f0> <5018> <0108> <010a> <516b> <010b> <010c> <5170> <010d> <010e> <5173> <010f> <0112> <5176> <0113> <0114> <517b> <0117> <0118> <518c> <011a> <011b> <519b> <011e> <011f> <51b2> <0124> <0125> <51e0> <0128> <012a> <51f9> <012b> <012c> <5200> <012d> <012e> <5206> <0132> <0134> <5219> <0138> <0139> <5228> <0144> <0147> <529e> <0149> <014b> <52a8> <0156> <0157> <5316> <0158> <0159> <533a> <015e> <015f> <534e> <0161> <0163> <5355> <0167> <0169> <5360> <016b> <016c> <5370> <016d> <016e> <5373> <0175> <0176> <538b> <017c> <0181> <53c8> <0183> <0186> <53d6> <0187> <018a> <53e3> <018b> <0193> <53e8> <0194> <0195> <53f2> <0196> <0197> <53f7> <019a> <019b> <5403> <019c> <019d> <5408> <019e> <01a0> <540c> <01a4> <01a5> <542b> <01ae> <01b0> <547b> <01be> <01bf> <552e> <01cb> <01cc> <56da> <01d5> <01d6> <56fd> <01e3> <01e4> <574e> <01e7> <01e8> <575a> <01e9> <01ea> <5760> <01f8> <01f9> <57f9> <0211> <0212> <5915> <0216> <0218> <5929> <0223> <0224> <5956> <0233> <0234> <5a03> <023e> <023f> <5b57> <0242> <0243> <5b63> <0248> <0249> <5b88> <024e> <0250> <5b9c> <0251> <0253> <5ba1> <0256> <0257> <5bb3> <025a> <025c> <5bbd> endbfrange
100 beginbfrange
<0260> <0264> <5bf8> <0268> <0269> <5c09> <0272> <0274> <5c3c> <0279> <027a> <5c4a> <0286> <0289> <5de5> <028c> <028f> <5df1> <0291> <0293> <5e01> <0298> <0299> <5e15> <029a> <029b> <5e1c> <029d> <029e> <5e2d> <02a2> <02a4> <5e72> <02ab> <02ad> <5e93> <02b0> <02b1> <5ea6> <02b3> <02b4> <5eb6> <02b6> <02b7> <5ef6> <02b9> <02ba> <5eff> <02bb> <02bc> <5f02> <02c1> <02c2> <5f17> <02c7> <02c8> <5f39> <02c9> <02ca> <5f52> <02cf> <02d0> <5f7b> <02d1> <02d2> <5f80> <02d3> <02d4> <5f84> <02e6> <02e7> <6000> <0300> <0301> <6167> <0308> <0309> <6210> <030e> <030f> <623f> <0311> <0312> <624d> <0315> <0316> <6266> <031c> <031e> <627e> <0321> <0324> <6295> <0327> <0328> <62a4> <0330> <0331> <62cc> <0332> <0333> <62d8> <0337> <0338> <62e8> <033b> <033c> <6301> <0340> <0341> <631a> <0355> <0356> <63a7> <0358> <0359> <63cf> <036b> <036c> <653e> <037d> <037e> <65af> <0387> <038b> <65e5> <038d> <038e> <65f6> <0393> <0394> <661f> <0397> <0398> <666e> <03a0> <03a2> <66fe> <03a3> <03a4> <6708> <03aa> <03ad> <672a> <03b2> <03b3> <6742> <03b4> <03b5> <6750> <03c2> <03c3> <67d0> <03ca> <03cc> <6837> <03db> <03de> <6b20> <03e2> <03e7> <6b62> <03e8> <03e9> <6b8a> <03f1> <03f2> <6bd4> <03fb> <03fc> <6c2e> <040d> <040e> <6cbb> <040f> <0410> <6cbe> <0421> <0422> <6d3d> <0425> <0426> <6d4a> <042d> <042e> <6d88> <043b> <043c> <6e20> <0457> <0458> <707e> <045a> <045b> <70b8> <045d> <045e> <70c2> <046d> <046e> <7247> <0476> <0477> <7279> <047b> <047c> <72ec> <0485> <0486> <73af> <0494> <0495> <7530> <0498> <0499> <7537> <04a9> <04aa> <767d> <04b0> <04b1> <76d0> <04ca> <04cb> <786b> <04d5> <04d6> <795d> <04e1> <04e2> <79ef> <04e9> <04ea> <7a76> <04f2> <04f4> <7ade> <0515> <0516> <7ea6> <0519> <051a> <7eb2> <051e> <051f> <7ec3> <0520> <0522> <7ec6> <0526> <0528> <7ed8> <0529> <052a> <7edc> <0531> <0532> <7f13> <0542> <0543> <800c> <0546> <0547> <804b> <0564> <0565> <81f3> <056f> <0570> <826f> <0579> <057a> <82e5> <0586> <0587> <83b1> <05b6> <05b7> <89c1> <05ba> <05bb> <89c8> <05c1> <05c2> <8ba1> <05c4> <05c5> <8ba8> <05c6> <05c9> <8bad> <05cd> <05ce> <8bbe> <05da> <05db> <8be5> <05dc> <05dd> <8bec> endbfrange
33 beginbfrange
<05df> <05e0> <8bf1> <05e2> <05e3> <8bf7> <05f4> <05f6> <8d21> <05f8> <05f9> <8d27> <05fa> <05fb> <8d2a> <05fc> <05fd> <8d2e> <05ff> <0601> <8d37> <060e> <060f> <8d8a> <061e> <061f> <8f90> <0623> <0624> <8fa8> <0629> <062a> <8fc4> <062d> <062e> <8fd0> <0630> <0631> <8fd8> <0632> <0636> <8fdb> <0637> <0638> <8fea> <063d> <0640> <9000> <0641> <0642> <9009> <0646> <0647> <901a> <0648> <0649> <901f> <0651> <0652> <906d> <0654> <0655> <907f> <0658> <0659> <90ae> <065d> <065e> <90e7> <0660> <0662> <914b> <0667> <066a> <91cc> <0671> <0672> <94c0> <067f> <0680> <95ed> <068d> <068f> <9644> <0694> <0695> <9676> <069c> <069d> <96c5> <069f> <06a0> <96f6> <06b0> <06b2> <9879> <06b6> <06b7> <9886> <06bc> <06bd> <98de> endbfrange
100 beginbfchar
<0000> <0020> <001a> <003d> <004e> <007d> <004f> <00a8> <0050> <2014> <0051> <2026> <0052> <25a1> <005b> <ff0c> <005e> <4e03> <005f> <4e07> <0065> <4e11> <0068> <4e16> <0069> <4e18> <006e> <4e22> <0071> <4e27> <0072> <4e2a> <0073> <4e2d> <0074> <4e30> <0075> <4e34> <007c> <4e43> <007d> <4e45> <0086> <4e53> <0087> <4e59> <0088> <4e5d> <008c> <4e66> <008f> <4e86> <0096> <4e92> <009b> <4ea2> <00a1> <4eab> <00a2> <4eba> <00ab> <4ed1> <00b2> <4edf> <00b6> <4eea> <00b7> <4eec> <00b8> <4ef2> <00bb> <4efb> <00bc> <4efd> <00bd> <4f01> <00be> <4f0a> <00c8> <4f24> <00c9> <4f26> <00ca> <4f2a> <00cd> <4f34> <00ce> <4f36> <00cf> <4f38> <00d0> <4f3c> <00d1> <4f46> <00d5> <4f53> <00d6> <4f55> <00d7> <4f59> <00da> <4f69> <00db> <4f6f> <00dc> <4f73> <00dd> <4f7f> <00de> <4f8b> <00df> <4f9b> <00e0> <4f9d> <00e1> <4fa3> <00e4> <4fae> <00e5> <4fb5> <00e6> <4fbf> <00e9> <4fdd> <00ea> <4fe1> <00eb> <4fee> <00ec> <4ff1> <00ed> <500d> <00ee> <5012> <00f1> <501f> <00f2> <5021> <00f3> <503c> <00f4> <503e> <00f5> <5047> <00f6> <505a> <00f7> <505c> <00f8> <5065> <00f9> <507f> <00fa> <5085> <00fb> <50a8> <00fc> <50b2> <00fd> <50f3> <00fe> <50fb> <00ff> <513f> <0100> <5141> <0101> <5143> <0102> <5145> <0103> <5148> <0104> <514b> <0105> <514d> <0106> <5165> <0107> <5168> <0115> <5180> <0116> <5185> <0119> <5199> <011c> <51ac> <011d> <51b0> <0120> <51b5> <0121> <51c0> <0122> <51c6> <0123> <51cf> <0126> <51ed> endbfchar
100 beginbfchar
<0127> <51ef> <012f> <520a> <0130> <5212> <0131> <5217> <0135> <521d> <0136> <5220> <0137> <5224> <013a> <522b> <013b> <5230> <013c> <5236> <013d> <523b> <013e> <5242> <013f> <524a> <0140> <524d> <0141> <5269> <0142> <526f> <0143> <529b> <0148> <52a3> <014c> <52b1> <014d> <52b3> <014e> <52bf> <014f> <52c3> <0150> <52d2> <0151> <52d8> <0152> <52e4> <0153> <52fa> <0154> <5300> <0155> <5305> <015a> <5341> <015b> <5343> <015c> <5347> <015d> <534a> <0160> <5353> <0164> <535a> <0165> <535c> <0166> <535e> <016a> <536b> <016f> <5378> <0170> <537f> <0171> <5382> <0172> <5384> <0173> <5386> <0174> <5389> <0177> <5398> <0178> <539a> <0179> <539f> <017a> <53bb> <017b> <53c2> <0182> <53d1> <0198> <53fc> <0199> <5401> <01a1> <5411> <01a2> <5417> <01a3> <5426> <01a6> <542f> <01a7> <5431> <01a8> <5446> <01a9> <544a> <01aa> <5458> <01ab> <5468> <01ac> <5473> <01ad> <5475> <01b1> <548c> <01b2> <5490> <01b3> <5496> <01b4> <54a8> <01b5> <54b1> <01b6> <54c1> <01b7> <54c8> <01b8> <54cd> <01b9> <54df> <01ba> <54e5> <01bb> <54ea> <01bc> <5510> <01bd> <5524> <01c0> <5531> <01c1> <5546> <01c2> <554a> <01c3> <5564> <01c4> <5566> <01c5> <5580> <01c6> <5584> <01c7> <558a> <01c8> <559c> <01c9> <5668> <01ca> <566a> <01cd> <56de> <01ce> <56e0> <01cf> <56e2> <01d0> <56e4> <01d1> <56ed> <01d2> <56f0> <01d3> <56f4> <01d4> <56fa> <01d7> <5703> <01d8> <5706> <01d9> <5708> <01da> <571f> <01db> <5723> <01dc> <5728> endbfchar
100 beginbfchar
<01dd> <572d> <01de> <5730> <01df> <573a> <01e0> <5740> <01e1> <5747> <01e2> <574a> <01e5> <5751> <01e6> <5757> <01eb> <5764> <01ec> <5766> <01ed> <5782> <01ee> <5784> <01ef> <578b> <01f0> <5792> <01f1> <579b> <01f2> <57a6> <01f3> <57ab> <01f4> <57c3> <01f5> <57ce> <01f6> <57d4> <01f7> <57df> <01fa> <5802> <01fb> <5806> <01fc> <5815> <01fd> <5821> <01fe> <5824> <01ff> <582a> <0200> <5851> <0201> <5854> <0202> <585e> <0203> <586b> <0204> <5883> <0205> <5893> <0206> <5899> <0207> <589e> <0208> <58a8> <0209> <58c1> <020a> <58e4> <020b> <58eb> <020c> <58f0> <020d> <5904> <020e> <5907> <020f> <590d> <0210> <590f> <0213> <591a> <0214> <591f> <0215> <5927> <0219> <592e> <021a> <5931> <021b> <5934> <021c> <5938> <021d> <593a> <021e> <5947> <021f> <5949> <0220> <594b> <0221> <594e> <0222> <5951> <0225> <5962> <0226> <5965> <0227> <5973> <0228> <5979> <0229> <597d> <022a> <5982> <022b> <5999> <022c> <59a5> <022d> <59a8> <022e> <59bb> <022f> <59c6> <0230> <59cb> <0231> <59d4> <0232> <5a01> <0235> <5a1c> <0236> <5a31> <0237> <5a74> <0238> <5a92> <0239> <5ac1> <023a> <5acc> <023b> <5ae9> <023c> <5b50> <023d> <5b54> <0240> <5b5c> <0241> <5b5f> <0244> <5b66> <0245> <5b6a> <0246> <5b83> <0247> <5b85> <024a> <5b8c> <024b> <5b8f> <024c> <5b97> <024d> <5b9a> <0254> <5baa> <0255> <5bb0> <0258> <5bb6> <0259> <5bb9> <025d> <5bc6> <025e> <5bcc> <025f> <5bdf> <0265> <5bff> <0266> <5c04> <0267> <5c06> endbfchar
100 beginbfchar
<026a> <5c0f> <026b> <5c11> <026c> <5c14> <026d> <5c1a> <026e> <5c1d> <026f> <5c24> <0270> <5c31> <0271> <5c38> <0275> <5c40> <0276> <5c42> <0277> <5c45> <0278> <5c48> <027b> <5c55> <027c> <5c5e> <027d> <5c65> <027e> <5c71> <027f> <5c7f> <0280> <5c97> <0281> <5c9b> <0282> <5cad> <0283> <5cb8> <0284> <5ddd> <0285> <5de1> <028a> <5deb> <028b> <5dee> <0290> <5dfe> <0294> <5e05> <0295> <5e08> <0296> <5e0c> <0297> <5e10> <029c> <5e26> <029f> <5e38> <02a0> <5e45> <02a1> <5e55> <02a5> <5e76> <02a6> <5e78> <02a7> <5e7f> <02a8> <5e86> <02a9> <5e8a> <02aa> <5e8f> <02ae> <5e9c> <02af> <5e9f> <02b2> <5ead> <02b5> <5ec9> <02b8> <5efa> <02bd> <5f0a> <02be> <5f0f> <02bf> <5f13> <02c0> <5f15> <02c3> <5f20> <02c4> <5f25> <02c5> <5f27> <02c6> <5f31> <02cb> <5f55> <02cc> <5f62> <02cd> <5f64> <02ce> <5f71> <02d5> <5f88> <02d6> <5f8b> <02d7> <5f92> <02d8> <5f97> <02d9> <5fa1> <02da> <5faa> <02db> <5fae> <02dc> <5fb5> <02dd> <5fb7> <02de> <5fc3> <02df> <5fc5> <02e0> <5fcd> <02e1> <5fd7> <02e2> <5fd9> <02e3> <5feb> <02e4> <5ff1> <02e5> <5ff5> <02e8> <600e> <02e9> <6015> <02ea> <601d> <02eb> <6025> <02ec> <6027> <02ed> <603b> <02ee> <6050> <02ef> <6062> <02f0> <6069> <02f1> <606f> <02f2> <6076> <02f3> <6089> <02f4> <60a0> <02f5> <60ac> <02f6> <60c5> <02f7> <60ca> <02f8> <60e7> <02f9> <60ef> <02fa> <60f3> <02fb> <6108> <02fc> <610f> <02fd> <611f> <02fe> <613f> <02ff> <614e> <0302> <6177> <0303> <61be> endbfchar
100 beginbfchar
<0304> <61c2> <0305> <61c8> <0306> <61d2> <0307> <6208> <030a> <6216> <030b> <6218> <030c> <621a> <030d> <6234> <0310> <624b> <0313> <6258> <0314> <6263> <0317> <6269> <0318> <6270> <0319> <6273> <031a> <6276> <031b> <6279> <031f> <628a> <0320> <6291> <0325> <629b> <0326> <62a2> <0329> <62b1> <032a> <62b5> <032b> <62b9> <032c> <62bd> <032d> <62c2> <032e> <62c5> <032f> <62c9> <0334> <62db> <0335> <62df> <0336> <62e5> <0339> <62ec> <033a> <62ff> <033d> <6307> <033e> <6309> <033f> <6311> <0342> <631d> <0343> <6325> <0344> <6328> <0345> <632a> <0346> <633d> <0347> <634e> <0348> <6350> <0349> <6355> <034a> <635f> <034b> <6362> <034c> <636e> <034d> <6377> <034e> <6388> <034f> <638c> <0350> <6392> <0351> <6398> <0352> <63a0> <0353> <63a2> <0354> <63a5> <0357> <63aa> <035a> <63d2> <035b> <63e1> <035c> <63ed> <035d> <63f4> <035e> <641c> <035f> <641e> <0360> <642d> <0361> <6458> <0362> <6469> <0363> <64a4> <0364> <64ad> <0365> <64bc> <0366> <64cd> <0367> <652f> <0368> <6536> <0369> <6539> <036a> <653b> <036d> <6545> <036e> <6548> <036f> <6551> <0370> <6559> <0371> <6563> <0372> <6566> <0373> <656c> <0374> <6570> <0375> <6574> <0376> <6587> <0377> <6597> <0378> <6599> <0379> <659f> <037b> <65a5> <037c> <65ad> <037f> <65b9> <0380> <65bd> <0381> <65c5> <0382> <65cb> <0383> <65cf> <0384> <65d7> <0385> <65e0> <0386> <65e2> <038c> <65f1> <038f> <65fa> <0390> <6606> <0391> <660e> <0392> <6613> endbfchar
100 beginbfchar
<0395> <662f> <0396> <663e> <0399> <667a> <039a> <6682> <039b> <66b4> <039c> <66dd> <039d> <66f2> <039e> <66f4> <039f> <66fc> <03a5> <670d> <03a6> <6717> <03a7> <671b> <03a8> <671f> <03a9> <6728> <03ae> <672f> <03af> <6734> <03b0> <673a> <03b1> <6740> <03b6> <675f> <03b7> <6761> <03b8> <6765> <03b9> <6770> <03ba> <677e> <03bb> <6781> <03bc> <6784> <03bd> <6790> <03be> <6797> <03bf> <679c> <03c0> <67af> <03c1> <67b6> <03c4> <67d3> <03c5> <67e5> <03c6> <6807> <03c7> <6811> <03c8> <6816> <03c9> <6821> <03cd> <683c> <03ce> <6848> <03cf> <6851> <03d0> <6863> <03d1> <6886> <03d2> <68a2> <03d3> <68c0> <03d4> <68da> <03d5> <68ee> <03d6> <690d> <03d7> <695a> <03d8> <6982> <03d9> <6a0a> <03da> <6a21> <03df> <6b27> <03e0> <6b32> <03e1> <6b3e> <03ea> <6b96> <03eb> <6bb5> <03ec> <6bc1> <03ed> <6bcb> <03ee> <6bcd> <03ef> <6bcf> <03f0> <6bd2> <03f3> <6bd7> <03f4> <6bdb> <03f5> <6beb> <03f6> <6c0f> <03f7> <6c11> <03f8> <6c14> <03f9> <6c1b> <03fa> <6c27> <03fd> <6c34> <03fe> <6c38> <03ff> <6c42> <0400> <6c47> <0401> <6c50> <0402> <6c57> <0403> <6c5f> <0404> <6c61> <0405> <6c7e> <0406> <6c89> <0407> <6c99> <0408> <6c9f> <0409> <6ca1> <040a> <6caa> <040b> <6cb3> <040c> <6cb9> <0411> <6cc4> <0412> <6cca> <0413> <6cd5> <0414> <6cdb> <0415> <6ce2> <0416> <6ce5> <0417> <6ce8> <0418> <6cf0> <0419> <6cfb> <041a> <6cfd> <041b> <6d01> <041c> <6d0b> <041d> <6d1b> <041e> <6d2a> <041f> <6d32> <0420> <6d3b> endbfchar
100 beginbfchar
<0423> <6d41> <0424> <6d46> <0427> <6d4e> <0428> <6d53> <0429> <6d66> <042a> <6d6a> <042b> <6d6e> <042c> <6d77> <042f> <6d95> <0430> <6da8> <0431> <6db2> <0432> <6dc0> <0433> <6dc6> <0434> <6de1> <0435> <6df1> <0436> <6df7> <0437> <6dfb> <0438> <6e05> <0439> <6e10> <043a> <6e14> <043d> <6e23> <043e> <6e29> <043f> <6e2f> <0440> <6e38> <0441> <6e56> <0442> <6e58> <0443> <6e7f> <0444> <6e89> <0445> <6e90> <0446> <6ecb> <0447> <6ee1> <0448> <6ee8> <0449> <6f0f> <044a> <6f14> <044b> <6f20> <044c> <6f2b> <044d> <6f5c> <044e> <6f6e> <044f> <6f84> <0450> <6fb3> <0451> <6fc0> <0452> <704c> <0453> <706b> <0454> <706d> <0455> <7070> <0456> <7075> <0459> <70ad> <045c> <70bc> <045f> <70c8> <0460> <70e7> <0461> <70ed> <0462> <711a> <0463> <7136> <0464> <7164> <0465> <7167> <0466> <719f> <0467> <71c3> <0468> <71e5> <0469> <7206> <046a> <722a> <046b> <722c> <046c> <7231> <046f> <7259> <0470> <725b> <0471> <7261> <0472> <7267> <0473> <7269> <0474> <7272> <0475> <7275> <0478> <72af> <0479> <72b6> <047a> <72de> <047d> <730e> <047e> <731b> <047f> <732e> <0480> <736d> <0481> <7387> <0482> <738b> <0483> <7396> <0484> <73ab> <0487> <73cd> <0488> <73d0> <0489> <73ed> <048a> <7403> <048b> <7406> <048c> <745e> <048d> <74a7> <048e> <74dc> <048f> <74e3> <0490> <74e6> <0491> <751a> <0492> <751f> <0493> <7528> <0496> <7533> <0497> <7535> <049a> <754c> <049b> <7559> <049c> <755c> <049d> <7565> <049e> <7574> endbfchar
100 beginbfchar
<049f> <7578> <04a0> <7591> <04a1> <75ab> <04a2> <75be> <04a3> <75c5> <04a4> <75c7> <04a5> <75d8> <04a6> <75db> <04a7> <764c> <04a8> <767b> <04ab> <7684> <04ac> <7687> <04ad> <76bf> <04ae> <76c6> <04af> <76ca> <04b2> <76d6> <04b3> <76d8> <04b4> <76df> <04b5> <76ee> <04b6> <76f2> <04b7> <76f4> <04b8> <76f8> <04b9> <76fe> <04ba> <770b> <04bb> <771f> <04bc> <773c> <04bd> <7740> <04be> <7763> <04bf> <77db> <04c0> <77e5> <04c1> <77ed> <04c2> <77ff> <04c3> <7801> <04c4> <780d> <04c5> <7814> <04c6> <7834> <04c7> <7838> <04c8> <7840> <04c9> <7845> <04cc> <786e> <04cd> <7877> <04ce> <788d> <04cf> <7891> <04d0> <78b0> <04d1> <78b3> <04d2> <78cb> <04d3> <793a> <04d4> <793e> <04d7> <7965> <04d8> <7968> <04d9> <7978> <04da> <7981> <04db> <798f> <04dc> <79bb> <04dd> <79c1> <04de> <79cd> <04df> <79d1> <04e0> <79d8> <04e3> <79fb> <04e4> <7a00> <04e5> <7a0b> <04e6> <7a0e> <04e7> <7a33> <04e8> <7a46> <04eb> <7a7a> <04ec> <7a81> <04ed> <7a84> <04ee> <7a92> <04ef> <7a96> <04f0> <7acb> <04f1> <7ad9> <04f5> <7ae5> <04f6> <7aed> <04f7> <7aef> <04f8> <7af9> <04f9> <7b14> <04fa> <7b26> <04fb> <7b2c> <04fc> <7b49> <04fd> <7b4f> <04fe> <7b51> <04ff> <7b54> <0500> <7b56> <0501> <7b79> <0502> <7b7e> <0503> <7b80> <0504> <7b97> <0505> <7ba1> <0506> <7c73> <0507> <7c7b> <0508> <7c92> <0509> <7c95> <050a> <7cae> <050b> <7cbe> <050c> <7cca> <050d> <7cfb> <050e> <7d20> <050f> <7d22> <0510> <7d27> <0511> <7d2f> endbfchar
100 beginbfchar
<0512> <7e41> <0513> <7e82> <0514> <7ea0> <0517> <7eaa> <0518> <7eaf> <051b> <7eb5> <051c> <7ebd> <051d> <7ebf> <0523> <7ecd> <0524> <7ecf> <0525> <7ed3> <052b> <7edf> <052c> <7ee7> <052d> <7ee9> <052e> <7eed> <052f> <7ef4> <0530> <7efc> <0533> <7f16> <0534> <7f29> <0535> <7f3a> <0536> <7f51> <0537> <7f57> <0538> <7f6e> <0539> <7f72> <053a> <7f8e> <053b> <7fa4> <053c> <7fc5> <053d> <7ff0> <053e> <7ffb> <053f> <8001> <0540> <8003> <0541> <8005> <0544> <8017> <0545> <8033> <0548> <8054> <0549> <8058> <054a> <805a> <054b> <806a> <054c> <8083> <054d> <8089> <054e> <8098> <054f> <809b> <0550> <80a5> <0551> <80a9> <0552> <80af> <0553> <80b2> <0554> <80c1> <0555> <80cc> <0556> <80ce> <0557> <80da> <0558> <80dc> <0559> <80fd> <055a> <8106> <055b> <811a> <055c> <8131> <055d> <814a> <055e> <8155> <055f> <8179> <0560> <818a> <0561> <818f> <0562> <81ea> <0563> <81ed> <0566> <8206> <0567> <820c> <0568> <8212> <0569> <821f> <056a> <822a> <056b> <822c> <056c> <8236> <056d> <8239> <056e> <8247> <0571> <8272> <0572> <827a> <0573> <8282> <0574> <82a6> <0575> <82ac> <0576> <82af> <0577> <82c7> <0578> <82cf> <057b> <82ef> <057c> <82f1> <057d> <8303> <057e> <8328> <057f> <8349> <0580> <835a> <0581> <8363> <0582> <836f> <0583> <8377> <0584> <8392> <0585> <83ab> <0588> <83b7> <0589> <83c7> <058a> <83cc> <058b> <83f2> <058c> <8404> <058d> <841d> <058e> <8425> <058f> <8428> <0590> <843d> <0591> <8461> endbfchar
100 beginbfchar
<0592> <848b> <0593> <8499> <0594> <84c4> <0595> <84dd> <0596> <8584> <0597> <85c9> <0598> <85cf> <0599> <8651> <059a> <866b> <059b> <867d> <059c> <8680> <059d> <8682> <059e> <86cb> <059f> <8718> <05a0> <8757> <05a1> <878d> <05a2> <87f9> <05a3> <8840> <05a4> <884c> <05a5> <8854> <05a6> <8857> <05a7> <8861> <05a8> <8863> <05a9> <8865> <05aa> <8868> <05ab> <8877> <05ac> <8896> <05ad> <88ab> <05ae> <88c1> <05af> <88f4> <05b0> <8902> <05b1> <8925> <05b2> <8944> <05b3> <897f> <05b4> <8981> <05b5> <8986> <05b8> <89c4> <05b9> <89c6> <05bc> <89e3> <05bd> <89e6> <05be> <8a00> <05bf> <8b66> <05c0> <8b6c> <05c3> <8ba4> <05ca> <8bb2> <05cb> <8bb8> <05cc> <8bba> <05cf> <8bc1> <05d0> <8bc4> <05d1> <8bc6> <05d2> <8bca> <05d3> <8bcd> <05d4> <8bd1> <05d5> <8bd5> <05d6> <8bd7> <05d7> <8bda> <05d8> <8bdd> <05d9> <8be2> <05de> <8bef> <05e1> <8bf4> <05e4> <8bfa> <05e5> <8bfe> <05e6> <8c03> <05e7> <8c08> <05e8> <8c0b> <05e9> <8c10> <05ea> <8c13> <05eb> <8c22> <05ec> <8c28> <05ed> <8c34> <05ee> <8c37> <05ef> <8c41> <05f0> <8c61> <05f1> <8c6a> <05f2> <8d1d> <05f3> <8d1f> <05f7> <8d25> <05fe> <8d35> <0602> <8d44> <0603> <8d4b> <0604> <8d4f> <0605> <8d54> <0606> <8d56> <0607> <8d5b> <0608> <8d5e> <0609> <8d66> <060a> <8d70> <060b> <8d75> <060c> <8d77> <060d> <8d85> <0610> <8da3> <0611> <8db3> <0612> <8dc3> <0613> <8dd1> <0614> <8ddd> <0615> <8def> <0616> <8e0a> <0617> <8eab> <0618> <8f67> <0619> <8f6c> endbfchar
100 beginbfchar
<061a> <8f7b> <061b> <8f7d> <061c> <8f83> <061d> <8f85> <0620> <8f93> <0621> <8f96> <0622> <8f9b> <0625> <8fb1> <0626> <8fb9> <0627> <8fbe> <0628> <8fc1> <062b> <8fc7> <062c> <8fce> <062f> <8fd4> <0639> <8ff0> <063a> <8ff7> <063b> <8ff9> <063c> <8ffd> <0643> <9010> <0644> <9012> <0645> <9014> <064a> <903b> <064b> <9047> <064c> <904d> <064d> <9053> <064e> <9057> <064f> <9063> <0650> <9065> <0653> <9075> <0656> <90a3> <0657> <90a6> <065a> <90b1> <065b> <90bb> <065c> <90d1> <065f> <90fd> <0663> <9192> <0664> <919a> <0665> <91c7> <0666> <91ca> <066b> <91d1> <066c> <9274> <066d> <9488> <066e> <94ae> <066f> <94b1> <0670> <94b3> <0673> <94f2> <0674> <94f6> <0675> <94fe> <0676> <9500> <0677> <9504> <0678> <9519> <0679> <9521> <067a> <9528> <067b> <952e> <067c> <953a> <067d> <957f> <067e> <95e8> <0681> <95f4> <0682> <95f7> <0683> <95fb> <0684> <95fd> <0685> <9602> <0686> <9610> <0687> <9614> <0688> <9632> <0689> <9634> <068a> <9636> <068b> <963b> <068c> <963f> <0690> <9650> <0691> <9662> <0692> <9664> <0693> <9669> <0696> <9686> <0697> <968f> <0698> <9694> <0699> <969c> <069a> <96be> <069b> <96c0> <069e> <96e8> <06a1> <9700> <06a2> <9707> <06a3> <970d> <06a4> <9713> <06a5> <9732> <06a6> <9752> <06a7> <9759> <06a8> <975e> <06a9> <9760> <06aa> <9762> <06ab> <978b> <06ac> <97e6> <06ad> <97e9> <06ae> <97f3> <06af> <9877> <06b3> <987e> <06b4> <9881> <06b5> <9884> <06b8> <9898> <06b9> <989d> endbfchar
24 beginbfchar
<06ba> <98a0> <06bb> <98ce> <06be> <9996> <06bf> <9a6c> <06c0> <9a71> <06c1> <9a7b> <06c2> <9a8c> <06c3> <9aa4> <06c4> <9aa8> <06c5> <9ad8> <06c6> <9c7c> <06c7> <9c81> <06c8> <9c9c> <06c9> <9ca4> <06ca> <9cb8> <06cb> <9cc3> <06cc> <9cd6> <06cd> <9e64> <06ce> <9ea6> <06cf> <9ece> <06d0> <9f13> <06d1> <9f3b> <06d2> <9f84> <06d3> <9f9a> endbfchar
endcmap
CMapName
currentdict
/CMap defineresource
pop
end
end
""";
var input = StringBytesTestConverter.Convert(cmap, false);
var result = cMapParser.Parse(input.Bytes);
Assert.Equal("Adobe", result.Info.Registry);
Assert.Equal("GB1", result.Info.Ordering);
Assert.Equal(2, result.Info.Supplement);
Assert.Equal("Adobe-GB1-2", result.Name);
Assert.Equal(2, result.Type);
}
private static byte[] ReadResourceBytes(string name)
{
using (var resource = typeof(CMapParser).Assembly.GetManifestResourceStream(name))

View File

@@ -11,7 +11,7 @@ namespace UglyToad.PdfPig.Tests.Geometry
new ParsingOptions { ClipPaths = true }))
{
var page = document.GetPage(45);
Assert.Equal(28, page.ExperimentalAccess.Paths.Count);
Assert.Equal(28, page.Paths.Count);
}
}
}

View File

@@ -15,7 +15,7 @@
var page = document.GetPage(1);
var words = page.GetWords().ToList();
foreach (var path in page.ExperimentalAccess.Paths)
foreach (var path in page.Paths)
{
Assert.NotEqual(FillingRule.NonZeroWinding, path.FillingRule); // allow none and even-odd

View File

@@ -68,12 +68,20 @@
[Fact]
public void ReflectionGraphicsStateOperationFactoryKnowsAllOperations()
{
var operationsField = typeof(ReflectionGraphicsStateOperationFactory).GetField("operations", BindingFlags.NonPublic | BindingFlags.Static);
var operationDictionary = operationsField.GetValue(null) as IReadOnlyDictionary<string, Type>;
Assert.NotNull(operationDictionary);
var operationsField = typeof(ReflectionGraphicsStateOperationFactory)
.GetField("Operations", BindingFlags.NonPublic | BindingFlags.Static);
Assert.NotNull(operationsField);
var operationDictionaryRaw = operationsField.GetValue(null) as IReadOnlyDictionary<string, Type>;
Assert.NotNull(operationDictionaryRaw);
var operationDictionary = new Dictionary<string, Type>(operationDictionaryRaw!.ToDictionary(x => x.Key, x => x.Value));
var allOperations = GetOperationTypes();
Assert.Equal(allOperations.Count(), operationDictionary.Count);
Assert.Equal(allOperations.Count, operationDictionary.Count);
var mapped = allOperations.Select(o =>
{
@@ -84,14 +92,14 @@
Assert.Equivalent(operationDictionary, mapped, strict: true);
}
private static IEnumerable<Type> GetOperationTypes()
private static IReadOnlyList<Type> GetOperationTypes()
{
var assembly = Assembly.GetAssembly(typeof(IGraphicsStateOperation));
var operationTypes = assembly.GetTypes().Where(x => typeof(IGraphicsStateOperation).IsAssignableFrom(x)
&& !x.IsInterface);
return operationTypes;
return operationTypes.ToList();
}
private static object[] GetConstructorParameters(ParameterInfo[] parameters)

View File

@@ -14,7 +14,7 @@
Assert.Equal("Q", Pop.Value.Operator);
}
[Fact]
[Fact(Skip = "The stack size check has been moved out of the Pop Operation, and is now in BaseStreamProcessor.PopState().")]
public void CannotPopWithSingleFrame()
{
Action action = () => Pop.Value.Run(context);

View File

@@ -180,7 +180,7 @@
{
}
public void EndInlineImage(ReadOnlyMemory<byte> bytes)
public void EndInlineImage(Memory<byte> bytes)
{
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

View File

@@ -0,0 +1,55 @@
namespace UglyToad.PdfPig.Tests.Images
{
using PdfPig.Images;
using System;
using UglyToad.PdfPig.Tests.Integration;
public class Jpeg2000HelperTests
{
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files", "Jpx")));
public static IEnumerable<object[]> GetAllJp2Files
{
get
{
return Directory.GetFiles(DocumentFolder.Value, "*.jp2").Select(x => new object[] { Path.GetFileName(x) });
}
}
[Theory]
[MemberData(nameof(GetAllJp2Files))]
public void GetJp2BitsPerComponent_ReturnsCorrectBitsPerComponent_WhenValidInput(string path)
{
byte[] image = File.ReadAllBytes(Path.Combine(DocumentFolder.Value, path));
Assert.Equal(8, Jpeg2000Helper.GetBitsPerComponent(image));
}
[Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenInputIsTooShort()
{
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[11]));
}
[Fact]
public void GetJp2BitsPerComponent_ThrowsException_WhenSignatureBoxIsInvalid()
{
Assert.Throws<InvalidOperationException>(() => Jpeg2000Helper.GetBitsPerComponent(new byte[12]));
}
[Fact]
public void GetJp2BitsPerComponentJ2K()
{
string path = IntegrationHelpers.GetSpecificTestDocumentPath("GHOSTSCRIPT-688999-2.pdf");
using (var document = PdfDocument.Open(path))
{
var page1 = document.GetPage(1);
var jpxImage = page1.GetImages().Single();
var bpc = Jpeg2000Helper.GetBitsPerComponent(jpxImage.RawBytes);
Assert.Equal(8, bpc);
}
}
}
}

View File

@@ -103,6 +103,30 @@
Assert.True(ImageHelpers.ImagesAreEqual(LoadImage("3x3.png"), bytes));
}
[Fact]
public void CanExtractPngFromPdfWithIndexedImageData8bpc()
{
var documetFolder = Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Images", "Files"));
var pdfPath = Path.Combine(documetFolder, "Pdf", "indexed-png-with-mask.pdf");
using (PdfDocument document = PdfDocument.Open(pdfPath))
{
var page = document.GetPage(1);
var img = page.GetImages().First();
var result = img.TryGetPng(out var bytes);
if (result)
{
var outputPath = Path.Combine(documetFolder, "Pdf", "indexed-png-with-mask.png");
File.WriteAllBytes(outputPath, bytes);
}
}
}
[Fact]
public void CanGeneratePngFromIndexedImageData1bpc()
{
@@ -148,11 +172,12 @@
var decodedBytes = ImageHelpers.LoadFileBytes("ccittfax-decoded.bin");
var image = new TestPdfImage
{
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance, new[] { 1.0, 0 }),
ColorSpaceDetails = IndexedColorSpaceDetails.Stencil(DeviceGrayColorSpaceDetails.Instance),
DecodedBytes = decodedBytes,
WidthInSamples = 1800,
HeightInSamples = 3113,
BitsPerComponent = 1
BitsPerComponent = 1,
Decode = [1.0, 0]
};
Assert.True(PngFromPdfImageFactory.TryGenerate(image, out var bytes));

View File

@@ -0,0 +1,42 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using System.Linq;
public class AdditionalGlyphListTests
{
[Fact]
public void Type1FontSimple1()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("2108.11480")))
{
var page = document.GetPage(2);
Assert.Contains("\u22c3", page.Letters.Select(l => l.Value));
}
}
[Fact]
public void Type1FontSimple2()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("ICML03-081")))
{
var page = document.GetPage(2);
Assert.Contains("\u2211", page.Letters.Select(l => l.Value));
Assert.Contains("\u220f", page.Letters.Select(l => l.Value));
Assert.Contains("[", page.Letters.Select(l => l.Value));
Assert.Contains("]", page.Letters.Select(l => l.Value));
}
}
[Fact]
public void Type1FontSimple3()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetDocumentPath("Math119FakingData")))
{
var page = document.GetPage(4);
Assert.Contains("(", page.Letters.Select(l => l.Value));
Assert.Contains(")", page.Letters.Select(l => l.Value));
Assert.Contains("\u2211", page.Letters.Select(l => l.Value));
}
}
}
}

View File

@@ -16,9 +16,9 @@ public class AnnotationReplyToTests
var page = document.GetPage(1);
var annotations = page.ExperimentalAccess.GetAnnotations().ToList();
var annotations = page.GetAnnotations().ToArray();
Assert.Equal(4, annotations.Count);
Assert.Equal(4, annotations.Length);
Assert.Equal(AnnotationType.Text, annotations[0].Type);
Assert.Equal(AnnotationType.Popup, annotations[1].Type);
@@ -33,7 +33,7 @@ public class AnnotationReplyToTests
var page = document.GetPage(1);
var annotations = page.ExperimentalAccess.GetAnnotations().ToList();
var annotations = page.GetAnnotations().ToArray();
Assert.Equal(annotations[0], annotations[2].InReplyTo);
}

View File

@@ -11,7 +11,7 @@
using (var doc = PdfDocument.Open(pdf))
{
var annots = doc.GetPage(1).ExperimentalAccess.GetAnnotations().ToArray();
var annots = doc.GetPage(1).GetAnnotations().ToArray();
Assert.Equal(5, annots.Length);
Assert.All(annots, a => Assert.NotNull(a.Action));
Assert.All(annots, a => Assert.IsType<GoToAction>(a.Action));
@@ -25,7 +25,7 @@
var pdf = IntegrationHelpers.GetSpecificTestDocumentPath("appearances");
using (var doc = PdfDocument.Open(pdf))
{
var annotations = doc.GetPage(1).ExperimentalAccess.GetAnnotations().ToArray();
var annotations = doc.GetPage(1).GetAnnotations().ToArray();
var annotation = Assert.Single(annotations);
Assert.True(annotation.HasDownAppearance);

View File

@@ -28,7 +28,7 @@
{
var page = document.GetPage(1);
var annotations = page.ExperimentalAccess.GetAnnotations().ToList();
var annotations = page.GetAnnotations().ToArray();
Assert.NotEmpty(annotations);

View File

@@ -285,7 +285,7 @@
{
var page1 = document.GetPage(1);
var background = page1.ExperimentalAccess.Paths[0];
var background = page1.Paths[0];
Assert.True(background.IsFilled);
var (r, g, b) = background.FillColor.ToRGBValues();
@@ -327,7 +327,7 @@
using (var document = PdfDocument.Open(path))
{
Page page1 = document.GetPage(1);
var paths1 = page1.ExperimentalAccess.Paths.Where(p => p.IsFilled).ToArray();
var paths1 = page1.Paths.Where(p => p.IsFilled).ToArray();
var reflexRed = paths1[0].FillColor.ToRGBValues(); // 'Reflex Red' Separation color space
Assert.Equal(0.930496, reflexRed.r, 6);
Assert.Equal(0.111542, reflexRed.g, 6);
@@ -341,7 +341,7 @@
Assert.Equal("w", firstLetter.Value);
Assert.Equal((0, 0, 1), firstLetter.Color.ToRGBValues()); // Blue
var paths2 = page2.ExperimentalAccess.Paths;
var paths2 = page2.Paths;
var filledPath = paths2.Where(p => p.IsFilled).ToArray();
var filledRects = filledPath.Where(p => p.Count == 1 && p[0].IsDrawnAsRectangle).ToArray();

View File

@@ -63,6 +63,15 @@
}
}
[Fact]
public void CanReadDocumentWithNoKeyLengthAndRevision4()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetSpecificTestDocumentPath("r4_aesv2_no_length")))
{
Assert.Empty(document.Information.Producer);
}
}
private static string GetPath() => IntegrationHelpers.GetSpecificTestDocumentPath(FileName);
}
}

View File

@@ -0,0 +1,126 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using PdfPig.Filters;
using PdfPig.Tokens;
using System;
using System.Collections.Generic;
using System.Linq;
public class FilterTests
{
private static readonly Lazy<string> DocumentFolder = new Lazy<string>(() => Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..", "..", "..", "Integration", "Documents")));
private static readonly HashSet<string> _documentsToIgnore =
[
"issue_671.pdf",
"GHOSTSCRIPT-698363-0.pdf",
"ErcotFacts.pdf"
];
[Theory]
[MemberData(nameof(GetAllDocuments))]
public void NoImageDecoding(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);
var parsingOptions = new ParsingOptions
{
UseLenientParsing = true,
FilterProvider = MyFilterProvider.Instance
};
using (var document = PdfDocument.Open(documentName, parsingOptions))
{
for (var i = 0; i < document.NumberOfPages; i++)
{
var page = document.GetPage(i + 1);
foreach (var pdfImage in page.GetImages())
{
if (pdfImage.ImageDictionary.TryGet(NameToken.Filter, out NameToken filter))
{
if (filter.Data.Equals(NameToken.FlateDecode.Data) ||
filter.Data.Equals(NameToken.FlateDecodeAbbreviation.Data) ||
filter.Data.Equals(NameToken.LzwDecode.Data) ||
filter.Data.Equals(NameToken.LzwDecodeAbbreviation.Data))
{
continue;
}
}
else
{
continue;
}
Assert.False(pdfImage.TryGetPng(out _));
}
}
}
}
public sealed class NoFilter : IFilter
{
public bool IsSupported => false;
public Memory<byte> Decode(Memory<byte> input, DictionaryToken streamDictionary, IFilterProvider filterProvider, int filterIndex)
{
throw new NotImplementedException();
}
}
public class MyFilterProvider : BaseFilterProvider
{
/// <summary>
/// The single instance of this provider.
/// </summary>
public static readonly IFilterProvider Instance = new MyFilterProvider();
/// <inheritdoc/>
protected MyFilterProvider() : base(GetDictionary())
{
}
private static Dictionary<string, IFilter> GetDictionary()
{
var ascii85 = new Ascii85Filter();
var asciiHex = new AsciiHexDecodeFilter();
var flate = new FlateFilter();
var runLength = new RunLengthFilter();
var lzw = new LzwFilter();
var noFilter = new NoFilter();
return new Dictionary<string, IFilter>
{
{ NameToken.Ascii85Decode.Data, ascii85 },
{ NameToken.Ascii85DecodeAbbreviation.Data, ascii85 },
{ NameToken.AsciiHexDecode.Data, asciiHex },
{ NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex },
{ NameToken.CcittfaxDecode.Data, noFilter },
{ NameToken.CcittfaxDecodeAbbreviation.Data, noFilter },
{ NameToken.DctDecode.Data, noFilter },
{ NameToken.DctDecodeAbbreviation.Data, noFilter },
{ NameToken.FlateDecode.Data, flate },
{ NameToken.FlateDecodeAbbreviation.Data, flate },
{ NameToken.Jbig2Decode.Data, noFilter },
{ NameToken.JpxDecode.Data, noFilter },
{ NameToken.RunLengthDecode.Data, runLength },
{ NameToken.RunLengthDecodeAbbreviation.Data, runLength },
{NameToken.LzwDecode, lzw },
{NameToken.LzwDecodeAbbreviation, lzw }
};
}
}
public static IEnumerable<object[]> GetAllDocuments
{
get
{
var files = Directory.GetFiles(DocumentFolder.Value, "*.pdf");
// Return the shortname so we can see it in the test explorer.
return files.Where(x => !_documentsToIgnore.Any(i => x.EndsWith(i))).Select(x => new object[] { Path.GetFileName(x) });
}
}
}
}

View File

@@ -1,7 +1,415 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using Content;
using DocumentLayoutAnalysis.PageSegmenter;
using DocumentLayoutAnalysis.WordExtractor;
using PdfPig.Core;
public class GithubIssuesTests
{
[Fact]
public void Issue1096()
{
// Ensure no StackOverflowException
// (already fixed by https://github.com/UglyToad/PdfPig/pull/1097)
var path = IntegrationHelpers.GetSpecificTestDocumentPath("issue_1096.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
for (int p = 1; p <= document.NumberOfPages; p++)
{
var page = document.GetPage(p);
foreach (var image in page.GetImages())
{
Assert.NotNull(image);
}
}
}
}
[Fact]
public void Issue1067()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("GHOSTSCRIPT-691770-0.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
var ex = Assert.Throws<PdfDocumentFormatException>(() => document.GetPage(1));
Assert.StartsWith("Decoded stream size exceeds the estimated maximum size.", ex.Message);
}
}
[Fact]
public void Issue1054()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("MOZILLA-11518-0.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
for (int p = 1; p <= document.NumberOfPages; p++)
{
var page = document.GetPage(p);
foreach (var image in page.GetImages())
{
Assert.NotNull(image);
}
}
}
}
[Fact]
public void Issue1050()
{
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);
}
[Fact]
public void Issue1047()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("Hang.pdf");
var ex = Assert.Throws<PdfDocumentFormatException>(() => PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }));
Assert.Equal("The cross reference was not found.", ex.Message);
}
[Fact]
public void Issue1048()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("InvalidCast.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
var page = document.GetPage(1);
Assert.NotNull(page.Letters);
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters);
var blocks = DocstrumBoundingBoxes.Instance.GetBlocks(words);
Assert.Single(blocks);
Assert.Equal("hey, i'm a bug.", blocks[0].Text);
}
}
[Fact]
public void Issue554()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("2022.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.Letters);
if (p < document.NumberOfPages)
{
Assert.NotEmpty(page.Letters);
}
}
}
}
[Fact]
public void Issue822()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("FileData_7.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.Letters);
}
}
}
[Fact]
public void Issue1040()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("pdfpig-issue-1040.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true}))
{
var page1 = document.GetPage(1);
Assert.NotEmpty(page1.Letters);
var page2 = document.GetPage(2);
Assert.NotEmpty(page2.Letters);
}
}
[Fact]
public void Issue1013()
{
// NB: We actually do not fix issue 953 here, but another bug found with the same document.
var path = IntegrationHelpers.GetSpecificTestDocumentPath("document_with_failed_fonts.pdf");
// Lenient parsing ON + Skip missing fonts
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page2 = document.GetPage(2);
Assert.NotEmpty(page2.Letters);
var words2 = NearestNeighbourWordExtractor.Instance.GetWords(page2.Letters).ToArray();
Assert.Equal("Doplňující", words2[0].Text);
var page3 = document.GetPage(3);
Assert.NotEmpty(page3.Letters);
var words3 = NearestNeighbourWordExtractor.Instance.GetWords(page3.Letters).ToArray();
Assert.Equal("Vinohradská", words3[8].Text);
}
}
[Fact]
public void Issue1016()
{
// Doc has letters with Shading pattern color
var path = IntegrationHelpers.GetSpecificTestDocumentPath("colorcomparecrash.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page = document.GetPage(1);
var letters = page.Letters;
var firstLetter = letters[0];
Assert.NotNull(firstLetter.Color);
var secondLetter = letters[1];
Assert.NotNull(secondLetter.Color);
Assert.True(firstLetter.Color.Equals(secondLetter.Color));
}
}
[Fact]
public void Issue953()
{
// NB: We actually do not fix issue 953 here, but another bug found with the same document.
var path = IntegrationHelpers.GetSpecificTestDocumentPath("FailedToParseContentForPage32.pdf");
// Lenient parsing ON + Skip missing fonts
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true}))
{
var page = document.GetPage(33);
Assert.Equal(33, page.Number);
Assert.Equal(792, page.Height);
Assert.Equal(612, page.Width);
}
// Lenient parsing ON + Do not Skip missing fonts
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = false }))
{
var pageException = Assert.Throws<InvalidOperationException>(() => document.GetPage(33));
Assert.Equal("Could not find the font with name /TT4 in the resource store. It has not been loaded yet.", pageException.Message);
}
var docException = Assert.Throws<PdfDocumentFormatException>(() =>
PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = false, SkipMissingFonts = false }));
Assert.Equal("Could not find dictionary associated with reference in pages kids array: 102 0.", docException.Message);
}
[Fact]
public void Issue953_IntOverflow()
{
// There is an integer overflow in Docstrum. We might want to fix that later on.
var path = IntegrationHelpers.GetSpecificTestDocumentPath("FailedToParseContentForPage32.pdf");
// Lenient parsing ON + Skip missing fonts
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page = document.GetPage(13);
Assert.Throws<OverflowException>(() => DocstrumBoundingBoxes.Instance.GetBlocks(page.GetWords()));
}
}
[Fact]
public void Issue987()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("zeroheightdemo.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(1);
var words = page.GetWords().ToArray();
foreach (var word in words)
{
Assert.True(word.BoundingBox.Width > 0);
Assert.True(word.BoundingBox.Height > 0);
}
}
}
[Fact]
public void Issue982()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("PDFBOX-659-0.pdf");
using (var document = PdfDocument.Open(path))
{
for (int p = 1; p <= document.NumberOfPages; ++p)
{
var page = document.GetPage(p);
foreach (var pdfImage in page.GetImages())
{
Assert.True(pdfImage.TryGetPng(out _));
}
}
}
}
[Fact]
public void Issue973()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("JD5008.pdf");
// Lenient parsing ON
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
var page = document.GetPage(2);
Assert.NotNull(page);
Assert.Equal(2, page.Number);
Assert.NotEmpty(page.Letters);
}
// Lenient parsing OFF
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = false }))
{
var exception = Assert.Throws<InvalidOperationException>(() => document.GetPage(2));
Assert.Equal("Cannot execute a pop of the graphics state stack, it would leave the stack empty.",
exception.Message);
}
}
[Fact]
public void Issue959()
{
var path = IntegrationHelpers.GetSpecificTestDocumentPath("algo.pdf");
// Lenient parsing ON
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true }))
{
for (int i = 1; i <= document.NumberOfPages; ++i)
{
var page = document.GetPage(i);
Assert.NotNull(page);
Assert.Equal(i, page.Number);
}
}
// Lenient parsing OFF
var exception = Assert.Throws<PdfDocumentFormatException>(() =>
PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = false }));
Assert.Equal("The cross references formed an infinite loop.", exception.Message);
}
[Fact]
public void Issue945()
{
// Odd ligatures names
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-3136-0.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(2);
Assert.Contains("ff", page.Letters.Select(l => l.Value));
}
path = IntegrationHelpers.GetDocumentPath("68-1990-01_A.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(7);
Assert.Contains("fi", page.Letters.Select(l => l.Value));
}
path = IntegrationHelpers.GetDocumentPath("TIKA-2054-0.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(3);
Assert.Contains("fi", page.Letters.Select(l => l.Value));
page = document.GetPage(4);
Assert.Contains("ff", page.Letters.Select(l => l.Value));
page = document.GetPage(6);
Assert.Contains("fl", page.Letters.Select(l => l.Value));
page = document.GetPage(16);
Assert.Contains("ffi", page.Letters.Select(l => l.Value));
}
}
[Fact]
public void Issue943()
{
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-10225-0.pdf");
using (var document = PdfDocument.Open(path))
{
var page = document.GetPage(1);
Assert.NotNull(page);
var letters = page.Letters;
Assert.NotNull(letters);
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters);
var blocks = DocstrumBoundingBoxes.Instance.GetBlocks(words);
Assert.Equal("Rocket and Spacecraft Propulsion", blocks[0].TextLines[0].Text);
Assert.Equal("Principles, Practice and New Developments (Second Edition)", blocks[0].TextLines[1].Text);
}
}
[Fact]
public void Issue736()
{
var doc = IntegrationHelpers.GetDocumentPath("Approved_Document_B__fire_safety__volume_2_-_Buildings_other_than_dwellings__2019_edition_incorporating_2020_and_2022_amendments.pdf");
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
Assert.True(document.TryGetBookmarks(out var bookmarks));
Assert.Single(bookmarks.Roots);
Assert.Equal(36, bookmarks.Roots[0].Children.Count);
}
}
[Fact]
public void Issue693()
{
var doc = IntegrationHelpers.GetDocumentPath("reference-2-numeric-error.pdf");
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page1 = document.GetPage(1);
Assert.Equal(1269, page1.Letters.Count);
}
}
[Fact]
public void Issue692()
{
var doc = IntegrationHelpers.GetDocumentPath("cmap-parsing-exception.pdf");
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page1 = document.GetPage(1);
Assert.Equal(796, page1.Letters.Count);
}
using (var document = PdfDocument.Open(doc, new ParsingOptions() { UseLenientParsing = false, SkipMissingFonts = false }))
{
var ex = Assert.Throws<InvalidOperationException>(() => document.GetPage(1));
Assert.StartsWith("Read byte called on input bytes which was at end of byte set.", ex.Message);
}
}
[Fact]
public void Issue874()
{
@@ -22,5 +430,32 @@
Assert.StartsWith("Value cannot be null.", ex.Message);
}
}
[Fact]
public void Issue913()
{
var doc = IntegrationHelpers.GetSpecificTestDocumentPath("Rotation 45.pdf");
using (var document = PdfDocument.Open(doc))
{
var page1 = document.GetPage(1);
for (int l = 131; l <= 137; ++l)
{
var letter = page1.Letters[l];
Assert.Equal(TextOrientation.Other, letter.TextOrientation);
Assert.Equal(45.0, letter.GlyphRectangle.Rotation, 5);
}
var page2 = document.GetPage(2);
Assert.Equal(157, page2.Letters.Count);
var page3 = document.GetPage(3);
Assert.Equal(283, page3.Letters.Count);
var page4 = document.GetPage(4);
Assert.Equal(304, page4.Letters.Count);
}
}
}
}

View File

@@ -0,0 +1,27 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using DocumentLayoutAnalysis.WordExtractor;
using System.Linq;
public class IndirectReferenceTests
{
[Fact]
public void Generation65536()
{
using (var document = PdfDocument.Open(IntegrationHelpers.GetSpecificTestDocumentPath("93101_1.pdf")))
{
var page = document.GetPage(1);
var words = NearestNeighbourWordExtractor.Instance.GetWords(page.Letters).ToArray();
Assert.NotEmpty(words);
Assert.Equal("Railway", words[0].Text);
for (int p = 2; p <= document.NumberOfPages; ++p)
{
page = document.GetPage(p);
Assert.NotEmpty(page.Letters);
}
}
}
}
}

View File

@@ -7,7 +7,8 @@
[
"issue_671.pdf",
"GHOSTSCRIPT-698363-0.pdf",
"ErcotFacts.pdf"
"ErcotFacts.pdf",
"cmap-parsing-exception.pdf"
];
[Theory]
@@ -23,7 +24,7 @@
{
var page = document.GetPage(i + 1);
Assert.NotNull(page.ExperimentalAccess.GetAnnotations().ToList());
Assert.NotNull(page.GetAnnotations().ToArray());
}
}
}
@@ -44,7 +45,7 @@
{
var page = document.GetPage(i + 1);
Assert.NotNull(page.ExperimentalAccess.GetAnnotations().ToList());
Assert.NotNull(page.GetAnnotations().ToArray());
}
}
}

View File

@@ -1,14 +1,22 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using PdfPig.Fonts.SystemFonts;
using System.Linq;
public class LetterFilterTests
{
[Fact]
[SkippableFact]
public void CanFilterClippedLetters()
{
var one = IntegrationHelpers.GetDocumentPath("ClipPathLetterFilter-Test1.pdf");
// The 'TimesNewRomanPSMT' font is used by this particular document. Thus, results cannot be trusted on
// platforms where this font isn't generally available (e.g. OSX, Linux, etc.), so we skip it!
var font = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPSMT");
var font1 = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPS-BoldMT");
var font2 = SystemFontFinder.Instance.GetTrueTypeFont("TimesNewRomanPS-ItalicMT");
Skip.If(font is null || font1 is null || font2 is null, "Skipped because the font TimesNewRomanPSMT or a font from TimesNewRoman family could not be found in the execution environment.");
using (var doc1 = PdfDocument.Open(one, new ParsingOptions { ClipPaths = true }))
using (var doc2 = PdfDocument.Open(one, new ParsingOptions { ClipPaths = false }))
{

View File

@@ -0,0 +1,73 @@
namespace UglyToad.PdfPig.Tests.Integration
{
using SkiaSharp;
using System.Linq;
public class MaskTests
{
[Fact]
public void PigProductionHandbook()
{
var path = IntegrationHelpers.GetDocumentPath("Pig Production Handbook.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page = document.GetPage(1);
var images = page.GetImages().ToArray();
var image1 = images[1];
Assert.NotNull(image1.MaskImage);
Assert.True(image1.TryGetPng(out var png1));
using (var skImage1 = SKImage.FromEncodedData(png1))
using (var skBitmap1 = SKBitmap.FromImage(skImage1))
{
var pixel = skBitmap1.GetPixel(0, 0);
Assert.Equal(0, pixel.Alpha);
}
var image2 = images[2];
Assert.NotNull(image2.MaskImage);
Assert.True(image2.TryGetPng(out var png2));
using (var skImage2 = SKImage.FromEncodedData(png2))
using (var skBitmap2 = SKBitmap.FromImage(skImage2))
{
var pixel = skBitmap2.GetPixel(0, 0);
Assert.Equal(0, pixel.Alpha);
}
}
}
[Fact]
public void MOZILLA_LINK_3264_0()
{
var path = IntegrationHelpers.GetDocumentPath("MOZILLA-LINK-3264-0.pdf");
using (var document = PdfDocument.Open(path, new ParsingOptions() { UseLenientParsing = true, SkipMissingFonts = true }))
{
var page = document.GetPage(1);
var images = page.GetImages().ToArray();
var image1 = images[1];
Assert.NotNull(image1.MaskImage);
Assert.True(image1.TryGetPng(out var png1));
using (var skImage1 = SKImage.FromEncodedData(png1))
using (var skBitmap1 = SKBitmap.FromImage(skImage1))
{
var pixel = skBitmap1.GetPixel(0, 0);
Assert.Equal(0, pixel.Alpha);
}
page = document.GetPage(2);
images = page.GetImages().ToArray();
var image2 = images[1];
Assert.NotNull(image2.MaskImage);
Assert.True(image2.TryGetPng(out var png2));
// TODO - Check alpha value
}
}
}
}

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