From 2823787d469fc689143518386e58a0375ab79218 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Fri, 9 Jan 2026 07:13:59 +0000 Subject: [PATCH] Drobne bug-fixes (maile i czytanie pdf dla GPT-api --- admin/edit_kb_document.php | 26 +- admin/order_details_error.log | 1 + chat_send.php | 6 +- .../036_add_file_content_to_kb_documents.sql | 1 + debug_price.log | 1096 ++++++++++++++ .../CMap/Registry/Adobe/Identity0.php | 20 + .../Document/CMap/Registry/CMapResource.php | 10 + .../CMap/Registry/RegistryOrchestrator.php | 17 + .../Document/CMap/ToUnicode/BFChar.php | 27 + .../Document/CMap/ToUnicode/BFRange.php | 35 + .../Document/CMap/ToUnicode/CodePoint.php | 37 + .../CMap/ToUnicode/CodeSpaceRange.php | 11 + .../Document/CMap/ToUnicode/ToUnicodeCMap.php | 64 + .../CMap/ToUnicode/ToUnicodeCMapOperator.php | 12 + .../CMap/ToUnicode/ToUnicodeCMapParser.php | 91 ++ .../Command/ContentStreamCommand.php | 28 + .../Operator/Object/CompatibilityOperator.php | 13 + .../Operator/Object/InlineImageOperator.php | 14 + .../Operator/Object/MarkedContentOperator.php | 16 + .../Operator/Object/TextObjectOperator.php | 14 + .../Operator/State/ClippingPathOperator.php | 13 + .../Command/Operator/State/ColorOperator.php | 23 + .../Operator/State/GraphicsStateOperator.php | 55 + .../Interaction/InteractsWithTextState.php | 9 + .../InteractsWithTransformationMatrix.php | 9 + .../ProducesPositionedTextElements.php | 11 + .../State/PathConstructionOperator.php | 18 + .../Operator/State/PathPaintingOperator.php | 22 + .../State/TextPositioningOperator.php | 76 + .../Operator/State/TextShowingOperator.php | 53 + .../Operator/State/TextStateOperator.php | 124 ++ .../Operator/State/Type3FontOperator.php | 13 + .../Operator/State/XObjectOperator.php | 12 + .../Document/ContentStream/ContentStream.php | 101 ++ .../ContentStream/ContentStreamParser.php | 217 +++ .../ContentStream/Object/TextObject.php | 22 + .../PositionedText/PositionedTextElement.php | 106 ++ .../PositionedText/TextState.php | 20 + .../PositionedText/TransformationMatrix.php | 27 + .../CrossReferenceSourceParser.php | 92 ++ .../CrossReference/CrossReferenceType.php | 8 + .../Source/CrossReferenceSource.php | 78 + .../Source/Section/CrossReferenceSection.php | 32 + .../SubSection/CrossReferenceSubSection.php | 54 + .../Entry/CrossReferenceEntryCompressed.php | 21 + .../Entry/CrossReferenceEntryFreeObject.php | 13 + .../Entry/CrossReferenceEntryInUseObject.php | 13 + .../Stream/CrossReferenceStreamParser.php | 83 ++ .../Stream/CrossReferenceStreamType.php | 11 + .../Table/CrossReferenceTableInUseOrFree.php | 10 + .../Table/CrossReferenceTableParser.php | 57 + .../Document/Dictionary/Dictionary.php | 143 ++ .../DictionaryEntry/DictionaryEntry.php | 19 + .../DictionaryEntryFactory.php | 78 + .../Document/Dictionary/DictionaryFactory.php | 34 + .../DictionaryKey/DictionaryKey.php | 1271 +++++++++++++++++ .../DictionaryKey/DictionaryKeyInterface.php | 12 + .../DictionaryKey/ExtendedDictionaryKey.php | 26 + .../DictionaryParseContext.php | 17 + .../DictionaryParseContext/NestingContext.php | 101 ++ .../Document/Dictionary/DictionaryParser.php | 112 ++ .../DictionaryValue/Array/ArrayValue.php | 69 + .../DictionaryValue/Array/CIDFontWidths.php | 65 + .../Array/CrossReferenceStreamByteSizes.php | 50 + .../Array/DictionaryArrayValue.php | 55 + .../Array/DifferencesArrayValue.php | 57 + .../Array/Item/ConsecutiveCIDWidth.php | 20 + .../Array/Item/DifferenceRange.php | 33 + .../Array/Item/RangeCIDWidth.php | 20 + .../DictionaryValue/Boolean/BooleanValue.php | 27 + .../DictionaryValue/Date/DateValue.php | 64 + .../DictionaryValue/DictionaryValue.php | 8 + .../DictionaryValue/Float/FloatValue.php | 25 + .../DictionaryValue/Integer/IntegerValue.php | 25 + .../Name/AuthEventNameValue.php | 8 + .../Name/BlendModeNameValue.php | 19 + .../Name/BorderStyleNameValue.php | 11 + .../DictionaryValue/Name/CFMNameValue.php | 9 + .../Name/CIEColorSpaceNameValue.php | 10 + .../Name/DeviceColorSpaceNameValue.php | 19 + .../Name/DirectionNameValue.php | 8 + .../Name/EncodingNameValue.php | 26 + .../DictionaryValue/Name/EventNameValue.php | 9 + .../DictionaryValue/Name/FilterNameValue.php | 75 + .../DictionaryValue/Name/IntentNameValue.php | 9 + .../Name/ListModeNameValue.php | 8 + .../DictionaryValue/Name/NameValue.php | 8 + .../Name/NonFullScreenPageModeNameValue.php | 10 + .../Name/NumberingStyleNameValue.php | 11 + .../Name/PageLayoutNameValue.php | 12 + .../Name/PageModeNameValue.php | 12 + .../Name/PaperHandlingNameValue.php | 9 + .../Name/RenderingIntentNameValue.php | 10 + .../Name/SecurityHandlerNameValue.php | 7 + .../Name/SpecialColorSpaceNameValue.php | 10 + .../DictionaryValue/Name/SubtypeNameValue.php | 21 + .../DictionaryValue/Name/TabsNameValue.php | 15 + .../Name/TransitionStyleNameValue.php | 18 + .../DictionaryValue/Name/TrappedNameValue.php | 10 + .../DictionaryValue/Name/TypeNameValue.php | 155 ++ .../DictionaryValue/Name/ViewNameValue.php | 9 + .../Name/VisibilityPolicyNameValue.php | 10 + .../DictionaryValue/Rectangle/Rectangle.php | 36 + .../Reference/ReferenceValue.php | 40 + .../Reference/ReferenceValueArray.php | 55 + .../TextString/TextStringValue.php | 57 + .../Normalization/NameValueNormalizer.php | 25 + includes/pdfparser/Document/Document.php | 178 +++ .../pdfparser/Document/Encoding/Encoding.php | 8 + .../pdfparser/Document/Encoding/MacRoman.php | 28 + .../pdfparser/Document/Encoding/WinAnsi.php | 28 + .../pdfparser/Document/Encryption/RC4.php | 29 + .../Document/Filter/Decode/ASCII85Decode.php | 52 + .../Document/Filter/Decode/CCITTFaxDecode.php | 52 + .../Document/Filter/Decode/FlateDecode.php | 87 ++ .../Filter/Decode/LZWFlatePredictorValue.php | 25 + .../Filter/Decode/PNGPredictorAlgorithm.php | 12 + .../Document/Filter/Decode/TiffTag.php | 13 + .../pdfparser/Document/Font/FontWidths.php | 16 + .../Generic/Character/DelimiterCharacter.php | 39 + .../LiteralStringEscapeCharacter.php | 65 + .../Generic/Character/WhitespaceCharacter.php | 28 + .../pdfparser/Document/Generic/Marker.php | 21 + .../Generic/Parsing/InfiniteBuffer.php | 48 + .../Generic/Parsing/RollingCharBuffer.php | 94 ++ .../Document/Image/ColorSpace/ColorSpace.php | 46 + .../Image/ColorSpace/ColorSpaceFactory.php | 43 + .../Document/Image/ColorSpace/Components.php | 9 + .../pdfparser/Document/Image/ImageType.php | 27 + .../Document/Image/RasterizedImage.php | 110 ++ .../Document/Object/Decorator/Catalog.php | 49 + .../Object/Decorator/DecoratedObject.php | 40 + .../Decorator/DecoratedObjectFactory.php | 33 + .../Object/Decorator/EmbeddedFile.php | 48 + .../Object/Decorator/EncryptDictionary.php | 109 ++ .../Object/Decorator/FileSpecification.php | 39 + .../Document/Object/Decorator/Font.php | 275 ++++ .../Object/Decorator/GenericObject.php | 6 + .../Decorator/InformationDictionary.php | 53 + .../Document/Object/Decorator/Page.php | 102 ++ .../Document/Object/Decorator/Pages.php | 39 + .../Document/Object/Decorator/XObject.php | 150 ++ .../CompressedObject/CompressedObject.php | 77 + .../CompressedObjectByteOffsetParser.php | 69 + .../CompressedObjectByteOffsets.php | 28 + .../CompressedObjectContentParser.php | 71 + .../Document/Object/Item/ObjectItem.php | 15 + .../UncompressedObject/UncompressedObject.php | 112 ++ .../UncompressedObjectParser.php | 29 + .../Document/Security/SecurityAlgorithm.php | 21 + .../Document/Security/StandardSecurity.php | 154 ++ .../StandardSecurityHandlerRevision.php | 13 + .../pdfparser/Document/Version/Version.php | 27 + .../Document/Version/VersionParser.php | 26 + .../AuthenticationFailedException.php | 6 + .../Exception/InvalidArgumentException.php | 7 + .../Exception/NotImplementedException.php | 6 + .../Exception/ParseFailureException.php | 7 + .../Exception/PdfParserException.php | 9 + .../pdfparser/Exception/RuntimeException.php | 6 + includes/pdfparser/PdfParser.php | 60 + includes/pdfparser/Stream/AbstractStream.php | 65 + includes/pdfparser/Stream/FileStream.php | 157 ++ includes/pdfparser/Stream/InMemoryStream.php | 79 + includes/pdfparser/Stream/Stream.php | 41 + includes/pdfparser_autoloader.php | 19 + mail/config.php | 84 +- ..._Opis_dzia__ania_algorytmu_losuj__cego.pdf | Bin 0 -> 177005 bytes uploads/kb_documents/695f7992c2b65_test.pdf | 1 + uploads/kb_documents/695f799cf3eb0_test.pdf | 1 + uploads/kb_documents/695f79a6b7bc5_test.pdf | Bin 0 -> 13264 bytes uploads/kb_documents/695f79d3473af_test.pdf | Bin 0 -> 13264 bytes uploads/kb_documents/695f79de3ea94_test.pdf | Bin 0 -> 13264 bytes uploads/kb_documents/695f79e4205ab_test.pdf | Bin 0 -> 13264 bytes .../695f9142a7bc4_695f79ef7d3a7_test.pdf | Bin 0 -> 13264 bytes 175 files changed, 9188 insertions(+), 27 deletions(-) create mode 100644 admin/order_details_error.log create mode 100644 db/migrations/036_add_file_content_to_kb_documents.sql create mode 100644 includes/pdfparser/Document/CMap/Registry/Adobe/Identity0.php create mode 100644 includes/pdfparser/Document/CMap/Registry/CMapResource.php create mode 100644 includes/pdfparser/Document/CMap/Registry/RegistryOrchestrator.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/BFChar.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/BFRange.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/CodePoint.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/CodeSpaceRange.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMap.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMapOperator.php create mode 100644 includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMapParser.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/ContentStreamCommand.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/Object/CompatibilityOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/Object/InlineImageOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/Object/MarkedContentOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/Object/TextObjectOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/ClippingPathOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/ColorOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/GraphicsStateOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/InteractsWithTextState.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/InteractsWithTransformationMatrix.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/ProducesPositionedTextElements.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/PathConstructionOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/PathPaintingOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/TextPositioningOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/TextShowingOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/TextStateOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/Type3FontOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/Command/Operator/State/XObjectOperator.php create mode 100644 includes/pdfparser/Document/ContentStream/ContentStream.php create mode 100644 includes/pdfparser/Document/ContentStream/ContentStreamParser.php create mode 100644 includes/pdfparser/Document/ContentStream/Object/TextObject.php create mode 100644 includes/pdfparser/Document/ContentStream/PositionedText/PositionedTextElement.php create mode 100644 includes/pdfparser/Document/ContentStream/PositionedText/TextState.php create mode 100644 includes/pdfparser/Document/ContentStream/PositionedText/TransformationMatrix.php create mode 100644 includes/pdfparser/Document/CrossReference/CrossReferenceSourceParser.php create mode 100644 includes/pdfparser/Document/CrossReference/CrossReferenceType.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/CrossReferenceSource.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/Section/CrossReferenceSection.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/Section/SubSection/CrossReferenceSubSection.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryCompressed.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryFreeObject.php create mode 100644 includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryInUseObject.php create mode 100644 includes/pdfparser/Document/CrossReference/Stream/CrossReferenceStreamParser.php create mode 100644 includes/pdfparser/Document/CrossReference/Stream/CrossReferenceStreamType.php create mode 100644 includes/pdfparser/Document/CrossReference/Table/CrossReferenceTableInUseOrFree.php create mode 100644 includes/pdfparser/Document/CrossReference/Table/CrossReferenceTableParser.php create mode 100644 includes/pdfparser/Document/Dictionary/Dictionary.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryEntry/DictionaryEntry.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryEntry/DictionaryEntryFactory.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryFactory.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKey.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKeyInterface.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryKey/ExtendedDictionaryKey.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryParseContext/DictionaryParseContext.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryParseContext/NestingContext.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryParser.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/ArrayValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CIDFontWidths.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CrossReferenceStreamByteSizes.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DictionaryArrayValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DifferencesArrayValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/ConsecutiveCIDWidth.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/DifferenceRange.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/RangeCIDWidth.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Boolean/BooleanValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Date/DateValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/DictionaryValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Float/FloatValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Integer/IntegerValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/AuthEventNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/BlendModeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/BorderStyleNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/CFMNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/CIEColorSpaceNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/DeviceColorSpaceNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/DirectionNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/EncodingNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/EventNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/FilterNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/IntentNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/ListModeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/NameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/NonFullScreenPageModeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/NumberingStyleNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/PageLayoutNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/PageModeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/PaperHandlingNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/RenderingIntentNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/SecurityHandlerNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/SpecialColorSpaceNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/SubtypeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/TabsNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/TransitionStyleNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/TrappedNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/TypeNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/ViewNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Name/VisibilityPolicyNameValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Rectangle/Rectangle.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Reference/ReferenceValue.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/Reference/ReferenceValueArray.php create mode 100644 includes/pdfparser/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php create mode 100644 includes/pdfparser/Document/Dictionary/Normalization/NameValueNormalizer.php create mode 100644 includes/pdfparser/Document/Document.php create mode 100644 includes/pdfparser/Document/Encoding/Encoding.php create mode 100644 includes/pdfparser/Document/Encoding/MacRoman.php create mode 100644 includes/pdfparser/Document/Encoding/WinAnsi.php create mode 100644 includes/pdfparser/Document/Encryption/RC4.php create mode 100644 includes/pdfparser/Document/Filter/Decode/ASCII85Decode.php create mode 100644 includes/pdfparser/Document/Filter/Decode/CCITTFaxDecode.php create mode 100644 includes/pdfparser/Document/Filter/Decode/FlateDecode.php create mode 100644 includes/pdfparser/Document/Filter/Decode/LZWFlatePredictorValue.php create mode 100644 includes/pdfparser/Document/Filter/Decode/PNGPredictorAlgorithm.php create mode 100644 includes/pdfparser/Document/Filter/Decode/TiffTag.php create mode 100644 includes/pdfparser/Document/Font/FontWidths.php create mode 100644 includes/pdfparser/Document/Generic/Character/DelimiterCharacter.php create mode 100644 includes/pdfparser/Document/Generic/Character/LiteralStringEscapeCharacter.php create mode 100644 includes/pdfparser/Document/Generic/Character/WhitespaceCharacter.php create mode 100644 includes/pdfparser/Document/Generic/Marker.php create mode 100644 includes/pdfparser/Document/Generic/Parsing/InfiniteBuffer.php create mode 100644 includes/pdfparser/Document/Generic/Parsing/RollingCharBuffer.php create mode 100644 includes/pdfparser/Document/Image/ColorSpace/ColorSpace.php create mode 100644 includes/pdfparser/Document/Image/ColorSpace/ColorSpaceFactory.php create mode 100644 includes/pdfparser/Document/Image/ColorSpace/Components.php create mode 100644 includes/pdfparser/Document/Image/ImageType.php create mode 100644 includes/pdfparser/Document/Image/RasterizedImage.php create mode 100644 includes/pdfparser/Document/Object/Decorator/Catalog.php create mode 100644 includes/pdfparser/Document/Object/Decorator/DecoratedObject.php create mode 100644 includes/pdfparser/Document/Object/Decorator/DecoratedObjectFactory.php create mode 100644 includes/pdfparser/Document/Object/Decorator/EmbeddedFile.php create mode 100644 includes/pdfparser/Document/Object/Decorator/EncryptDictionary.php create mode 100644 includes/pdfparser/Document/Object/Decorator/FileSpecification.php create mode 100644 includes/pdfparser/Document/Object/Decorator/Font.php create mode 100644 includes/pdfparser/Document/Object/Decorator/GenericObject.php create mode 100644 includes/pdfparser/Document/Object/Decorator/InformationDictionary.php create mode 100644 includes/pdfparser/Document/Object/Decorator/Page.php create mode 100644 includes/pdfparser/Document/Object/Decorator/Pages.php create mode 100644 includes/pdfparser/Document/Object/Decorator/XObject.php create mode 100644 includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObject.php create mode 100644 includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsetParser.php create mode 100644 includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsets.php create mode 100644 includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectContent/CompressedObjectContentParser.php create mode 100644 includes/pdfparser/Document/Object/Item/ObjectItem.php create mode 100644 includes/pdfparser/Document/Object/Item/UncompressedObject/UncompressedObject.php create mode 100644 includes/pdfparser/Document/Object/Item/UncompressedObject/UncompressedObjectParser.php create mode 100644 includes/pdfparser/Document/Security/SecurityAlgorithm.php create mode 100644 includes/pdfparser/Document/Security/StandardSecurity.php create mode 100644 includes/pdfparser/Document/Security/StandardSecurityHandlerRevision.php create mode 100644 includes/pdfparser/Document/Version/Version.php create mode 100644 includes/pdfparser/Document/Version/VersionParser.php create mode 100644 includes/pdfparser/Exception/AuthenticationFailedException.php create mode 100644 includes/pdfparser/Exception/InvalidArgumentException.php create mode 100644 includes/pdfparser/Exception/NotImplementedException.php create mode 100644 includes/pdfparser/Exception/ParseFailureException.php create mode 100644 includes/pdfparser/Exception/PdfParserException.php create mode 100644 includes/pdfparser/Exception/RuntimeException.php create mode 100644 includes/pdfparser/PdfParser.php create mode 100644 includes/pdfparser/Stream/AbstractStream.php create mode 100644 includes/pdfparser/Stream/FileStream.php create mode 100644 includes/pdfparser/Stream/InMemoryStream.php create mode 100644 includes/pdfparser/Stream/Stream.php create mode 100644 includes/pdfparser_autoloader.php create mode 100644 uploads/kb_documents/695f78e68b6ef_Opis_dzia__ania_algorytmu_losuj__cego.pdf create mode 100644 uploads/kb_documents/695f7992c2b65_test.pdf create mode 100644 uploads/kb_documents/695f799cf3eb0_test.pdf create mode 100644 uploads/kb_documents/695f79a6b7bc5_test.pdf create mode 100644 uploads/kb_documents/695f79d3473af_test.pdf create mode 100644 uploads/kb_documents/695f79de3ea94_test.pdf create mode 100644 uploads/kb_documents/695f79e4205ab_test.pdf create mode 100644 uploads/kb_documents/695f9142a7bc4_695f79ef7d3a7_test.pdf diff --git a/admin/edit_kb_document.php b/admin/edit_kb_document.php index 4161e66..abbcb6d 100644 --- a/admin/edit_kb_document.php +++ b/admin/edit_kb_document.php @@ -1,5 +1,9 @@ parseFile($target_path); + $file_content = $pdf->getText(); } } } if ($id) { - $stmt = db()->prepare("UPDATE kb_documents SET title = ?, content = ?, tags = ?, product_id = ?, language = ?, is_active = ?, file_path = ? WHERE id = ?"); - $stmt->execute([$title, $content, $tags, $product_id, $language, $is_active, $file_path, $id]); + $stmt = db()->prepare("UPDATE kb_documents SET title = ?, content = ?, tags = ?, product_id = ?, language = ?, is_active = ?, file_path = ?, file_content = ? WHERE id = ?"); + $stmt->execute([$title, $content, $tags, $product_id, $language, $is_active, $file_path, $file_content, $id]); } else { - $stmt = db()->prepare("INSERT INTO kb_documents (title, content, tags, product_id, language, is_active, file_path) VALUES (?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute([$title, $content, $tags, $product_id, $language, $is_active, $file_path]); + $stmt = db()->prepare("INSERT INTO kb_documents (title, content, tags, product_id, language, is_active, file_path, file_content) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); + $stmt->execute([$title, $content, $tags, $product_id, $language, $is_active, $file_path, $file_content]); } header('Location: kb_documents.php'); @@ -160,4 +170,4 @@ require_once __DIR__ . '/../includes/html_head.php'; - + \ No newline at end of file diff --git a/admin/order_details_error.log b/admin/order_details_error.log new file mode 100644 index 0000000..4aef25d --- /dev/null +++ b/admin/order_details_error.log @@ -0,0 +1 @@ +[09-Jan-2026 07:03:16 UTC] PHP Warning: Undefined variable $pageTitle in /home/ubuntu/executor/workspace/admin/order_details.php on line 133 diff --git a/chat_send.php b/chat_send.php index caf4789..57321bb 100644 --- a/chat_send.php +++ b/chat_send.php @@ -26,7 +26,7 @@ function search_kb($message) { $sql = "SELECT * FROM kb_documents WHERE is_active = 1 AND ("; $conditions = []; foreach ($terms as $term) { - $conditions[] = "title LIKE ? OR content LIKE ?"; + $conditions[] = "title LIKE ? OR content LIKE ? OR file_content LIKE ?"; } $sql .= implode(' OR ', $conditions) . ") LIMIT 3"; @@ -35,6 +35,7 @@ function search_kb($message) { foreach ($terms as $term) { $params[] = '%' . $term . '%'; $params[] = '%' . $term . '%'; + $params[] = '%' . $term . '%'; } $stmt->execute($params); return $stmt->fetchAll(); @@ -62,6 +63,9 @@ if (!empty($kb_documents)) { foreach ($kb_documents as $doc) { $system_prompt .= "- Title: " . $doc['title'] . "\n"; $system_prompt .= " Content: " . $doc['content'] . "\n"; + if (!empty($doc['file_content'])) { + $system_prompt .= " File Content: " . $doc['file_content'] . "\n"; + } } } diff --git a/db/migrations/036_add_file_content_to_kb_documents.sql b/db/migrations/036_add_file_content_to_kb_documents.sql new file mode 100644 index 0000000..0db133d --- /dev/null +++ b/db/migrations/036_add_file_content_to_kb_documents.sql @@ -0,0 +1 @@ +ALTER TABLE `kb_documents` ADD `file_content` TEXT NULL; \ No newline at end of file diff --git a/debug_price.log b/debug_price.log index 9b1b8de..60250af 100644 --- a/debug_price.log +++ b/debug_price.log @@ -12099,3 +12099,1099 @@ Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84 Found product price. Net: 233.2, Gross: 286.84 FINAL: Returning Net: 233.2, Gross: 286.84 --- +--- +START getEffectivePrice for product 1, client 1 +Client price query executed. Found: {"price_net":"837.40","price_gross":"1030.00"} +Found client price. Net: 837.4, Gross: 1030 +FINAL: Returning Net: 837.4, Gross: 1030 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client 1 +Client price query executed. Found: {"price_net":"837.40","price_gross":"1030.00"} +Found client price. Net: 837.4, Gross: 1030 +FINAL: Returning Net: 837.4, Gross: 1030 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 1, client 1 +Client price query executed. Found: {"price_net":"837.40","price_gross":"1030.00"} +Found client price. Net: 837.4, Gross: 1030 +FINAL: Returning Net: 837.4, Gross: 1030 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client 1 +Client price query executed. Found: {"price_net":"837.40","price_gross":"1030.00"} +Found client price. Net: 837.4, Gross: 1030 +FINAL: Returning Net: 837.4, Gross: 1030 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client 1 +Client price query executed. Found: {"price_net":"837.40","price_gross":"1030.00"} +Found client price. Net: 837.4, Gross: 1030 +FINAL: Returning Net: 837.4, Gross: 1030 +--- +--- +START getEffectivePrice for product 2, client 1 +Client price query executed. Found: {"price_net":"1056.91","price_gross":"1300.00"} +Found client price. Net: 1056.91, Gross: 1300 +FINAL: Returning Net: 1056.91, Gross: 1300 +--- +--- +START getEffectivePrice for product 3, client 1 +Client price query executed. Found: {"price_net":"32.52","price_gross":"40.00"} +Found client price. Net: 32.52, Gross: 40 +FINAL: Returning Net: 32.52, Gross: 40 +--- +--- +START getEffectivePrice for product 4, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client 1 +Client price query executed. Found: No +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- +--- +START getEffectivePrice for product 1, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1111.00","price_gross":"1366.53"} +Found product price. Net: 1111, Gross: 1366.53 +FINAL: Returning Net: 1111, Gross: 1366.53 +--- +--- +START getEffectivePrice for product 2, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"1318.05","price_gross":"1621.20"} +Found product price. Net: 1318.05, Gross: 1621.2 +FINAL: Returning Net: 1318.05, Gross: 1621.2 +--- +--- +START getEffectivePrice for product 3, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 4, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"9.95","price_gross":"12.24"} +Found product price. Net: 9.95, Gross: 12.24 +FINAL: Returning Net: 9.95, Gross: 12.24 +--- +--- +START getEffectivePrice for product 5, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"68.00","price_gross":"83.64"} +Found product price. Net: 68, Gross: 83.64 +FINAL: Returning Net: 68, Gross: 83.64 +--- +--- +START getEffectivePrice for product 6, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"171.60","price_gross":"211.07"} +Found product price. Net: 171.6, Gross: 211.07 +FINAL: Returning Net: 171.6, Gross: 211.07 +--- +--- +START getEffectivePrice for product 7, client +Client price not found or not set, falling back to product price. +Product price query executed. Found: {"price_net":"233.20","price_gross":"286.84"} +Found product price. Net: 233.2, Gross: 286.84 +FINAL: Returning Net: 233.2, Gross: 286.84 +--- diff --git a/includes/pdfparser/Document/CMap/Registry/Adobe/Identity0.php b/includes/pdfparser/Document/CMap/Registry/Adobe/Identity0.php new file mode 100644 index 0000000..6e919c7 --- /dev/null +++ b/includes/pdfparser/Document/CMap/Registry/Adobe/Identity0.php @@ -0,0 +1,20 @@ +getText(), $ordering->getText(), $supplement->value]) { + ['Adobe', 'Identity', 0] => new Identity0(), + default => null, + }; + } +} diff --git a/includes/pdfparser/Document/CMap/ToUnicode/BFChar.php b/includes/pdfparser/Document/CMap/ToUnicode/BFChar.php new file mode 100644 index 0000000..f790ce5 --- /dev/null +++ b/includes/pdfparser/Document/CMap/ToUnicode/BFChar.php @@ -0,0 +1,27 @@ +sourceCode; + } + + /** @throws ParseFailureException */ + public function toUnicode(int $characterCode): ?string { + if ($characterCode !== $this->sourceCode) { + throw new ParseFailureException(sprintf('This BFChar does not contain character code %d', $characterCode)); + } + + return CodePoint::toString($this->destinationString); + } +} diff --git a/includes/pdfparser/Document/CMap/ToUnicode/BFRange.php b/includes/pdfparser/Document/CMap/ToUnicode/BFRange.php new file mode 100644 index 0000000..89541f3 --- /dev/null +++ b/includes/pdfparser/Document/CMap/ToUnicode/BFRange.php @@ -0,0 +1,35 @@ + $destinationCodePoints */ + public function __construct( + public readonly int $sourceCodeStart, + public readonly int $sourceCodeEnd, + public readonly array $destinationCodePoints, + ) { + } + + public function containsCharacterCode(int $characterCode): bool { + return $characterCode >= $this->sourceCodeStart + && $characterCode <= $this->sourceCodeEnd; + } + + /** @throws ParseFailureException */ + public function toUnicode(int $characterCode): ?string { + if (count($this->destinationCodePoints) === 1) { + return CodePoint::toString( + dechex(((int) hexdec($this->destinationCodePoints[0])) + $characterCode - $this->sourceCodeStart), + ); + } + + return CodePoint::toString( + $this->destinationCodePoints[$characterCode - $this->sourceCodeStart] + ?? throw new ParseFailureException(), + ); + } +} diff --git a/includes/pdfparser/Document/CMap/ToUnicode/CodePoint.php b/includes/pdfparser/Document/CMap/ToUnicode/CodePoint.php new file mode 100644 index 0000000..00fc414 --- /dev/null +++ b/includes/pdfparser/Document/CMap/ToUnicode/CodePoint.php @@ -0,0 +1,37 @@ +> 16) & 0xFFFF) >= 0xD800 + && $highSurrogate <= 0xDBFF + && ($lowSurrogate = $surrogateCodePoint & 0xFFFF) >= 0xDC00 + && $lowSurrogate <= 0xDFFF) { + $charCodepoint = (($highSurrogate - 0xD800) << 10) + ($lowSurrogate - 0xDC00) + 0x10000; + $i += 8; // Surrogate Pairs are 4 bytes long + } else { + $charCodepoint = (int) hexdec(substr($hexString, $i, 4)); + $i += 4; // Non surrogate pairs are 2 bytes long + } + + if (($char = mb_chr($charCodepoint)) === false) { + throw new ParseFailureException(); + } + + $chars[] = $char; + } + + return implode('', $chars); + } +} diff --git a/includes/pdfparser/Document/CMap/ToUnicode/CodeSpaceRange.php b/includes/pdfparser/Document/CMap/ToUnicode/CodeSpaceRange.php new file mode 100644 index 0000000..e1cb89a --- /dev/null +++ b/includes/pdfparser/Document/CMap/ToUnicode/CodeSpaceRange.php @@ -0,0 +1,11 @@ + */ + private readonly array $bfCharRangeInfo; + + /** + * @no-named-arguments + * + * @param list $codeSpaceRanges + * @param int<1, max> $byteSize + * @throws InvalidArgumentException + */ + public function __construct( + public readonly array $codeSpaceRanges, + public readonly int $byteSize, + BFRange|BFChar ...$bfCharRangeInfo, + ) { + $this->bfCharRangeInfo = $bfCharRangeInfo; + if ($this->byteSize < 1) { + throw new InvalidArgumentException(); + } + } + + /** @throws PdfParserException */ + public function textToUnicode(string $characterGroup): string { + return implode( + '', + array_map( + fn (string $character) => $this->charToUnicode((int) hexdec($character)) ?? '', + str_split($characterGroup, $this->byteSize * 2) + ) + ); + } + + /** @throws PdfParserException */ + protected function charToUnicode(int $characterCode): ?string { + $char = null; + foreach ($this->bfCharRangeInfo as $bfCharRangeInfo) { + if (!$bfCharRangeInfo->containsCharacterCode($characterCode)) { + continue; + } + + if (($char = $bfCharRangeInfo->toUnicode($characterCode)) !== "\0") { // Some characters map to NULL in one BFRange and to an actual character in another + return $char; + } + } + + if ($char === "\0") { + return $char; // Only return NULL when it is the only character this is mapped to + } + + if ($characterCode === 0) { + return ''; + } + + return null; + } +} diff --git a/includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMapOperator.php b/includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMapOperator.php new file mode 100644 index 0000000..0bd4d52 --- /dev/null +++ b/includes/pdfparser/Document/CMap/ToUnicode/ToUnicodeCMapOperator.php @@ -0,0 +1,12 @@ +firstPos(ToUnicodeCMapOperator::BeginCodeSpaceRange, $startOffset, $startOffset + $nrOfBytes) + ?? throw new ParseFailureException(sprintf('Missing %s', ToUnicodeCMapOperator::BeginCodeSpaceRange->value)); + $beginCodeSpaceRangePos += strlen(ToUnicodeCMapOperator::BeginCodeSpaceRange->value); + $endCodeSpaceRangePos = $stream->firstPos(ToUnicodeCMapOperator::EndCodeSpaceRange, $beginCodeSpaceRangePos, $startOffset + $nrOfBytes) + ?? throw new ParseFailureException(); + $codeSpaceRangeSectionString = $stream->read($beginCodeSpaceRangePos, $endCodeSpaceRangePos - $beginCodeSpaceRangePos); + $codeSpaceRanges = []; + $byteSize = null; + foreach (explode("\n", $codeSpaceRangeSectionString) as $codeSpaceRangeSectionStringLine) { + if (trim($codeSpaceRangeSectionStringLine) === '') { + continue; + } + + if (preg_match('/^\s*<\s*(?P[0-9a-fA-F]+)\s*>\s*<\s*(?P[0-9a-fA-F]+)\s*>\s*$/', $codeSpaceRangeSectionStringLine, $matchesSpaceRange) !== 1) { + throw new ParseFailureException('Unrecognized codespacerange format'); + } + + if (strlen($matchesSpaceRange['start']) !== strlen($matchesSpaceRange['end'])) { + throw new ParseFailureException(sprintf('Start(%s) and end(%s) of codespacerange don\'t have the same number of bytes', $matchesSpaceRange['start'], $matchesSpaceRange['end'])); + } + + if (($strlen = strlen($matchesSpaceRange['start'])) % 2 !== 0 || !is_int($byteSizeRange = $strlen / 2)) { + throw new ParseFailureException(sprintf('Codespaceranges must be an even number of hex digits, got %d', $strlen)); + } + + if ($byteSize !== null && $byteSizeRange !== $byteSize) { + throw new ParseFailureException(sprintf('Byte size of codespaceranges is inconsistent, expected %d, got %d', $byteSize, $byteSizeRange)); + } + + $byteSize = $byteSizeRange; + $codeSpaceRanges[] = new CodeSpaceRange((int) hexdec($matchesSpaceRange['start']), (int) hexdec($matchesSpaceRange['end'])); + } + + /** @var array> $bfCharRangeInfo where the first index is used to track the position of the element in the CMap */ + $bfCharRangeInfo = []; + $lastPos = $startOffset; + while (($beginBFCharPos = $stream->firstPos(ToUnicodeCMapOperator::BeginBFChar, $lastPos, $startOffset + $nrOfBytes)) !== null) { + $beginBFCharPos += strlen(ToUnicodeCMapOperator::BeginBFChar->value); + $endBFCharPos = $stream->firstPos(ToUnicodeCMapOperator::EndBFChar, $beginBFCharPos, $startOffset + $nrOfBytes) + ?? throw new ParseFailureException(); + if (preg_match_all('/\s*<(?P[^>]+)>\s*<(?P[^>]+)>\s*/', $stream->read($beginBFCharPos, $endBFCharPos - $beginBFCharPos), $matchesBFChar, PREG_SET_ORDER) === 0) { + throw new ParseFailureException('Unrecognized bfchar format'); + } + + foreach ($matchesBFChar as $matchBFChar) { + $bfCharRangeInfo[$beginBFCharPos][] = new BFChar((int) hexdec(trim($matchBFChar['source'])), trim($matchBFChar['destination'])); + } + $lastPos = $endBFCharPos; + } + + $lastPos = $startOffset; + while (($beginBFRangePos = $stream->firstPos(ToUnicodeCMapOperator::BeginBFRange, $lastPos, $startOffset + $nrOfBytes)) !== null) { + $endBFRangePos = $stream->firstPos(ToUnicodeCMapOperator::EndBFRange, $beginBFRangePos, $startOffset + $nrOfBytes) + ?? throw new ParseFailureException(); + if (preg_match_all('/\s*<(?P[^>]+)>\s*<(?P[^>]+)>\s*(?P(<[^>]+>)|(\[\s*(<[^>]+>\s*)+\]))/', $stream->read($beginBFRangePos, $endBFRangePos - $beginBFRangePos), $matchesBFRange, PREG_SET_ORDER) === 0) { + throw new ParseFailureException('Unrecognized bfrange format'); + } + + foreach ($matchesBFRange as $matchBFRange) { + $bfCharRangeInfo[$beginBFRangePos][] = new BFRange( + (int) hexdec(trim($matchBFRange['start'])), + (int) hexdec(trim($matchBFRange['end'])), + array_map( + fn (string $value) => trim($value), + explode('><', rtrim(ltrim(str_replace(' ', '', $matchBFRange['targetString']), '[<'), '>]')) + ) + ); + } + $lastPos = $endBFRangePos; + } + + ksort($bfCharRangeInfo); // Make sure that Char and Range are in order they occur in the CMap + return new ToUnicodeCMap( + $codeSpaceRanges, + $byteSize !== null ? $byteSize : 2, + ...array_merge(...$bfCharRangeInfo) + ); + } +} diff --git a/includes/pdfparser/Document/ContentStream/Command/ContentStreamCommand.php b/includes/pdfparser/Document/ContentStream/Command/ContentStreamCommand.php new file mode 100644 index 0000000..85edf12 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Command/ContentStreamCommand.php @@ -0,0 +1,28 @@ +multiplyWith( + new TransformationMatrix( + (float) $matrix[0], + (float) $matrix[1], + (float) $matrix[2], + (float) $matrix[3], + (float) $matrix[4], + (float) $matrix[5], + ) + ); + } + + return $transformationMatrix; + } +} diff --git a/includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/InteractsWithTextState.php b/includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/InteractsWithTextState.php new file mode 100644 index 0000000..fca117b --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Command/Operator/State/Interaction/InteractsWithTextState.php @@ -0,0 +1,9 @@ +scaleX, + $transformationMatrix->shearX, + $transformationMatrix->shearY, + $transformationMatrix->scaleY, + $transformationMatrix->offsetX + (float) $offsets[0], + $transformationMatrix->offsetY + (float) $offsets[1] + ); + } + + if ($this === self::SET_MATRIX) { + $matrix = explode(' ', trim($operands)); + if (count($matrix) !== 6) { + throw new ParseFailureException(); + } + + return new TransformationMatrix((float) $matrix[0], (float) $matrix[1], (float) $matrix[2], (float) $matrix[3], (float) $matrix[4], (float) $matrix[5]); + } + + return $transformationMatrix; + } + + /** @throws ParseFailureException */ + #[Override] + public function applyToTextState(string $operands, ?TextState $textState): ?TextState { + if ($this === self::MOVE_OFFSET_LEADING) { + $offsets = explode(' ', trim($operands)); + if (count($offsets) !== 2) { + throw new ParseFailureException(); + } + + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + -1 * (float) $offsets[1], + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + return $textState; + } +} diff --git a/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextShowingOperator.php b/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextShowingOperator.php new file mode 100644 index 0000000..d77a4a7 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextShowingOperator.php @@ -0,0 +1,53 @@ +fontName ?? null, + $textState->fontSize ?? null, + (float) $spacing[1], + (float) $spacing[0], + $textState->scale ?? 100, + $textState->leading ?? 0, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + return $textState; + } + + #[Override] + public function getPositionedTextElement(string $operands, TransformationMatrix $textMatrix, TransformationMatrix $globalTransformationMatrix, TextState $textState): PositionedTextElement { + return new PositionedTextElement( + $operands, + $globalTransformationMatrix->multiplyWith($textMatrix), + $textState + ); + } +} diff --git a/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextStateOperator.php b/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextStateOperator.php new file mode 100644 index 0000000..2618c23 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Command/Operator/State/TextStateOperator.php @@ -0,0 +1,124 @@ +fontName ?? null, + $textState->fontSize ?? null, + (float) $operands, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + $textState->leading ?? 0, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + if ($this === self::WORD_SPACE) { + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + (float) $operands, + $textState->scale ?? 100, + $textState->leading ?? 0, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + if ($this === self::SCALE) { + if (trim($operands) !== (string)($scale = (int) $operands) && trim($operands) !== (string)($scale = (float) $operands)) { + throw new ParseFailureException(sprintf('Invalid scale operand "%s" for scale operator', $operands)); + } + + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $scale, + $textState->leading ?? 0, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + if ($this === self::LEADING) { + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + (float) $operands, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + if ($this === self::FONT_SIZE) { + if (preg_match('/^\/(?[A-Za-z_0-9\.\-\+]+)\s+(?-?[0-9]+(\.[0-9]+)?)$/', $operands, $matches) !== 1) { + throw new InvalidArgumentException(sprintf('Invalid font operand "%s" for Tf operator', substr($operands, 0, 200))); + } + + return new TextState( + DictionaryKey::tryFrom($matches['fontReference']) ?? new ExtendedDictionaryKey($matches['fontReference']), + (float) $matches['FontSize'], + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + $textState->leading ?? 0, + $textState->render ?? 0, + $textState->rise ?? 0, + ); + } + + if ($this === self::RENDER) { + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + $textState->leading ?? 0, + (int) $operands, + $textState->rise ?? 0, + ); + } + + return new TextState( + $textState->fontName ?? null, + $textState->fontSize ?? null, + $textState->charSpace ?? 0, + $textState->wordSpace ?? 0, + $textState->scale ?? 100, + $textState->leading ?? 0, + $textState->render ?? 0, + (float) $operands, + ); + } +} diff --git a/includes/pdfparser/Document/ContentStream/Command/Operator/State/Type3FontOperator.php b/includes/pdfparser/Document/ContentStream/Command/Operator/State/Type3FontOperator.php new file mode 100644 index 0000000..8c93aee --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Command/Operator/State/Type3FontOperator.php @@ -0,0 +1,13 @@ + */ + public readonly array $content; + + /** @no-named-arguments */ + public function __construct( + TextObject|ContentStreamCommand... $content + ) { + $this->content = $content; + } + + /** @return list */ + public function getPositionedTextElements(): array { + $positionedTextElements = $transformationStateStack = []; + $textState = null; // See table 103, Tf operator for initial value + $transformationMatrix = new TransformationMatrix(1, 0, 0, 1, 0, 0); // Identity matrix + foreach ($this->content as $content) { + if ($content instanceof ContentStreamCommand) { + if ($content->operator instanceof InteractsWithTextState) { + $textState = $content->operator->applyToTextState($content->operands, $textState); + } elseif ($content->operator === GraphicsStateOperator::SaveCurrentStateToStack) { + $transformationStateStack[] = clone $transformationMatrix; + } elseif ($content->operator === GraphicsStateOperator::RestoreMostRecentStateFromStack) { + $transformationMatrix = array_pop($transformationStateStack) + ?? throw new ParseFailureException(); + } elseif ($content->operator instanceof InteractsWithTransformationMatrix) { + $transformationMatrix = $content->operator->applyToTransformationMatrix($content->operands, $transformationMatrix); + } + + continue; + } + + $textMatrix = new TransformationMatrix(1, 0, 0, 1, 0, 0); // Identity matrix, See Table 106, Tm operator for initial value in text object + foreach ($content->contentStreamCommands as $contentStreamCommand) { + if ($contentStreamCommand->operator instanceof InteractsWithTextState) { + $textState = $contentStreamCommand->operator->applyToTextState($contentStreamCommand->operands, $textState); + } + + if ($contentStreamCommand->operator instanceof InteractsWithTransformationMatrix) { + $textMatrix = $contentStreamCommand->operator->applyToTransformationMatrix($contentStreamCommand->operands, $textMatrix); + } + + if ($contentStreamCommand->operator instanceof ProducesPositionedTextElements && $textState !== null) { + $positionedTextElements[] = $contentStreamCommand->operator->getPositionedTextElement($contentStreamCommand->operands, $textMatrix, $transformationMatrix, $textState); + } + } + } + + usort( + $positionedTextElements, + static function (PositionedTextElement $a, PositionedTextElement $b): int { + if (($differenceY = $b->absoluteMatrix->offsetY <=> $a->absoluteMatrix->offsetY) !== 0) { + return $differenceY; + } + + return $a->absoluteMatrix->offsetX <=> $b->absoluteMatrix->offsetX; + } + ); + + return $positionedTextElements; + } + + /** @throws PdfParserException */ + public function getText(Document $document, Page $page): string { + $text = ''; + $previousPositionedTextElement = null; + foreach ($this->getPositionedTextElements() as $positionedTextElement) { + if ($previousPositionedTextElement !== null) { + if ($previousPositionedTextElement->absoluteMatrix->offsetY !== $positionedTextElement->absoluteMatrix->offsetY) { + $text .= "\n"; + } elseif (($positionedTextElement->absoluteMatrix->offsetX - $previousPositionedTextElement->absoluteMatrix->offsetX - $positionedTextElement->getFont($document, $page)->getWidthForChars($previousPositionedTextElement->getCodePoints(), $previousPositionedTextElement->textState, $previousPositionedTextElement->absoluteMatrix)) >= ($previousPositionedTextElement->textState->fontSize ?? 10) * $previousPositionedTextElement->absoluteMatrix->scaleX * 0.40) { + $text .= ' '; + } + } + + $text .= $positionedTextElement->getText($document, $page); + $previousPositionedTextElement = $positionedTextElement; + } + + return $text; + } +} diff --git a/includes/pdfparser/Document/ContentStream/ContentStreamParser.php b/includes/pdfparser/Document/ContentStream/ContentStreamParser.php new file mode 100644 index 0000000..b069870 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/ContentStreamParser.php @@ -0,0 +1,217 @@ + $contentsObjects + * @throws ParseFailureException + */ + public static function parse(array $contentsObjects): ContentStream { + $content = []; + $inStringLiteral = $inResourceName = $inDictionary = false; + $inArrayLevel = $inStringLevel = 0; + $textObject = $previousChar = $secondToLastChar = $thirdToLastChar = $previousContentStream = $startPreviousOperandIndex = null; + foreach ($contentsObjects as $contentsObject) { + $startCurrentOperandIndex = 0; + $contentStream = $contentsObject->getStream(); + $contentStreamSize = $contentStream->getSizeInBytes(); + for ($index = 0; $index < $contentStreamSize; $index++) { + $char = $contentStream->read($index, 1); + if ($inStringLiteral === true) { + if ($char === ')' && $previousChar !== '\\') { + $inStringLiteral = false; + } + } elseif ($inResourceName === true) { + if (in_array($char, [' ', '<', '(', '/', "\r", "\n"], true) && $previousChar !== '\\') { + $inResourceName = false; + } + } elseif ($inDictionary === true) { + if ($char === '>' && $previousChar === '>' && $secondToLastChar !== '\\') { + $inDictionary = false; + } + } elseif ($char === '[' && $previousChar !== '\\') { + $inArrayLevel++; + } elseif ($char === '<' && $previousChar === '<' && $secondToLastChar !== '\\') { + $inDictionary = true; + } elseif ($char === '<' && $previousChar !== '\\' && $contentStream->read($index + 1, 1) !== '<') { + $inStringLevel++; + } elseif ($char === '(' && $previousChar !== '\\') { + $inStringLiteral = true; + } elseif ($char === '/' && $previousChar !== '\\') { + $inResourceName = true; + } elseif ($inStringLevel > 0 || $inArrayLevel > 0) { + if ($inStringLevel > 0 && $char === '>' && $previousChar !== '\\') { + $inStringLevel--; + } elseif ($inArrayLevel > 0 && $char === ']' && $previousChar !== '\\') { + $inArrayLevel--; + } + } elseif ($char === 'T' && $previousChar === 'B') { // TextObjectOperator::BEGIN + $startCurrentOperandIndex = $index + 1; + $textObject = new TextObject(); + } elseif ($char === 'T' && $previousChar === 'E') { // TextObjectOperator::END + $startCurrentOperandIndex = $index + 1; + if ($textObject === null) { + throw new ParseFailureException('Encountered TextObjectOperator::END without preceding TextObjectOperator::BEGIN'); + } + + $content[] = $textObject; + $textObject = null; + } elseif ($char === 'C' + && (($secondToLastChar === 'B' && ($previousChar === 'M' || $previousChar === 'D')) || ($secondToLastChar === 'E' && $previousChar === 'M'))) { // MarkedContentOperator::BeginMarkedContent, MarkedContentOperator::EndMarkedContent, MarkedContentOperator::BeginMarkedContentWithProperties + $startCurrentOperandIndex = $index + 1; + } elseif (($operator = self::getOperator($char, $previousChar, $secondToLastChar, $thirdToLastChar)) !== null + && (($nextChar = $contentStream->read($index + 1, 1)) === '' || self::getOperator($nextChar, $char, $previousChar, $secondToLastChar) === null)) { // Skip the current hit if the next iteration is also a valid operator + $operands = ''; + if ($previousContentStream !== null && $startPreviousOperandIndex !== null && $startPreviousOperandIndex < $previousContentStream->getSizeInBytes()) { + $operands .= $previousContentStream->read($startPreviousOperandIndex, $previousContentStream->getSizeInBytes() - $startPreviousOperandIndex); + $startPreviousOperandIndex = null; + } + if (($operandLength = $index + 1 - $startCurrentOperandIndex - strlen($operator->value)) > 0) { + $operands .= $contentStream->read($startCurrentOperandIndex, $operandLength); + } + + $command = new ContentStreamCommand($operator, trim($operands)); + if ($textObject !== null) { + $textObject->addContentStreamCommand($command); + } else { + $content[] = $command; + } + + $startCurrentOperandIndex = $index + 1; + } + + $thirdToLastChar = $secondToLastChar; + $secondToLastChar = $previousChar; + $previousChar = $char; + } + + $previousContentStream = $contentStream; + $startPreviousOperandIndex = $startCurrentOperandIndex; + } + + return new ContentStream(...$content); + } + + /** + * This method uses three maps instead of calling $enum::tryFrom for all possible enums + * as operator retrieval happens possibly millions of times in a single file + */ + public static function getOperator(string $currentChar, ?string $previousChar, ?string $secondToLastChar, ?string $thirdToLastChar): CompatibilityOperator|InlineImageOperator|MarkedContentOperator|TextObjectOperator|ClippingPathOperator|ColorOperator|GraphicsStateOperator|PathConstructionOperator|PathPaintingOperator|TextPositioningOperator|TextShowingOperator|TextStateOperator|Type3FontOperator|XObjectOperator|null { + $threeLetterMatch = match ($secondToLastChar . $previousChar . $currentChar) { + 'BMC' => MarkedContentOperator::BeginMarkedContent, + 'BDC' => MarkedContentOperator::BeginMarkedContentWithProperties, + 'EMC' => MarkedContentOperator::EndMarkedContent, + 'SCN' => ColorOperator::SetStrokingParams, + 'scn' => ColorOperator::SetColorParams, + default => null, + }; + if ($threeLetterMatch !== null) { + return in_array($thirdToLastChar, ['\\', '/'], true) ? null : $threeLetterMatch; + } + + $twoLetterMatch = match ($previousChar . $currentChar) { + 'BX' => CompatibilityOperator::BeginCompatibilitySection, + 'EX' => CompatibilityOperator::EndCompatibilitySection, + 'BI' => InlineImageOperator::Begin, + 'ID' => InlineImageOperator::BeginImageData, + 'EI' => InlineImageOperator::End, + 'MD' => MarkedContentOperator::Tag, + 'DP' => MarkedContentOperator::TagProperties, + 'BT' => TextObjectOperator::BEGIN, + 'ET' => TextObjectOperator::END, + 'W*' => ClippingPathOperator::INTERSECT_EVEN_ODD, + 'CS' => ColorOperator::SetName, + 'cs' => ColorOperator::SetNameNonStroking, + 'SC' => ColorOperator::SetStrokingColor, + 'sc' => ColorOperator::SetColor, + 'RG' => ColorOperator::SetStrokingColorDeviceRGB, + 'rg' => ColorOperator::SetColorDeviceRGB, + 'cm' => GraphicsStateOperator::ModifyCurrentTransformationMatrix, + 'ri' => GraphicsStateOperator::SetIntent, + 'gs' => GraphicsStateOperator::SetDictName, + 're' => PathConstructionOperator::RECTANGLE, + 'f*' => PathPaintingOperator::FILL_EVEN_ODD, + 'B*' => PathPaintingOperator::FILL_STROKE_EVEN_ODD, + 'b*' => PathPaintingOperator::CLOSE_FILL_STROKE, + 'Td' => TextPositioningOperator::MOVE_OFFSET, + 'TD' => TextPositioningOperator::MOVE_OFFSET_LEADING, + 'Tm' => TextPositioningOperator::SET_MATRIX, + 'T*' => TextPositioningOperator::NEXT_LINE, + 'Tj' => TextShowingOperator::SHOW, + 'TJ' => TextShowingOperator::SHOW_ARRAY, + 'Tc' => TextStateOperator::CHAR_SPACE, + 'Tw' => TextStateOperator::WORD_SPACE, + 'Tz' => TextStateOperator::SCALE, + 'TL' => TextStateOperator::LEADING, + 'Tf' => TextStateOperator::FONT_SIZE, + 'Tr' => TextStateOperator::RENDER, + 'Ts' => TextStateOperator::RISE, + 'd0' => Type3FontOperator::SetWidth, + 'd1' => Type3FontOperator::SetWidthAndBoundingBox, + 'Do' => XObjectOperator::Paint, + default => null, + }; + if ($twoLetterMatch !== null) { + return in_array($secondToLastChar, ['\\', '/'], true) ? null : $twoLetterMatch; + } + + $oneLetterMatch = match ($currentChar) { + 'W' => ClippingPathOperator::INTERSECT, + 'G' => ColorOperator::SetStrokingColorSpace, + 'g' => ColorOperator::SetColorSpace, + 'K' => ColorOperator::SetStrokingColorDeviceCMYK, + 'k' => ColorOperator::SetColorDeviceCMYK, + 'q' => GraphicsStateOperator::SaveCurrentStateToStack, + 'Q' => GraphicsStateOperator::RestoreMostRecentStateFromStack, + 'w' => GraphicsStateOperator::SetLineWidth, + 'J' => GraphicsStateOperator::SetLineCap, + 'j' => GraphicsStateOperator::SetLineJoin, + 'M' => GraphicsStateOperator::SetMiterJoin, + 'd' => GraphicsStateOperator::SetLineDash, + 'i' => GraphicsStateOperator::SetFlatness, + 'm' => PathConstructionOperator::MOVE, + 'l' => PathConstructionOperator::LINE, + 'c' => PathConstructionOperator::CURVE_BEZIER_123, + 'v' => PathConstructionOperator::CURVE_BEZIER_23, + 'y' => PathConstructionOperator::CURVE_BEZIER_13, + 'h' => PathConstructionOperator::CLOSE, + 'S' => PathPaintingOperator::STROKE, + 's' => PathPaintingOperator::CLOSE_STROKE, + 'f' => PathPaintingOperator::FILL, + 'F' => PathPaintingOperator::FILL_DEPRECATED, + 'B' => PathPaintingOperator::FILL_STROKE, + 'n' => PathPaintingOperator::END, + '\'' => TextShowingOperator::MOVE_SHOW, + '"' => TextShowingOperator::MOVE_SHOW_SPACING, + default => null, + }; + + if ($oneLetterMatch !== null) { + return in_array($previousChar, ['\\', '/'], true) ? null : $oneLetterMatch; + } + + return null; + } +} diff --git a/includes/pdfparser/Document/ContentStream/Object/TextObject.php b/includes/pdfparser/Document/ContentStream/Object/TextObject.php new file mode 100644 index 0000000..da1248c --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/Object/TextObject.php @@ -0,0 +1,22 @@ + */ + public array $contentStreamCommands = []; + + public function addContentStreamCommand(ContentStreamCommand $textOperator): self { + $this->contentStreamCommands[] = $textOperator; + + return $this; + } + + public function isEmpty(): bool { + return $this->contentStreamCommands === []; + } +} diff --git a/includes/pdfparser/Document/ContentStream/PositionedText/PositionedTextElement.php b/includes/pdfparser/Document/ContentStream/PositionedText/PositionedTextElement.php new file mode 100644 index 0000000..bc8e061 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/PositionedText/PositionedTextElement.php @@ -0,0 +1,106 @@ +textState->fontName === null) { + throw new ParseFailureException('Unable to locate font for text element'); + } + + return $page->getFontDictionary()?->getObjectForReference($document, $this->textState->fontName, Font::class) + ?? throw new ParseFailureException(sprintf('Unable to locate font with reference "/%s"', $this->textState->fontName->value)); + } + + /** @throws ParseFailureException */ + public function getText(Document $document, Page $page): string { + if (($result = preg_match_all('/(?(<(\\\\>|[^>])*>)|(\((\\\\\)|[^)])*\)))(?-?[0-9]+(\.[0-9]+)?)?/', $this->rawTextContent, $matches, PREG_SET_ORDER)) === false) { + throw new ParseFailureException(sprintf('Error with regex')); + } elseif ($result === 0) { + throw new ParseFailureException(sprintf('Operands "%s" is not in a recognized format', $this->rawTextContent)); + } + + $string = ''; + $font = $this->getFont($document, $page); + foreach ($matches as $match) { + if (str_starts_with($match['chars'], '(') && str_ends_with($match['chars'], ')')) { + $unescapedChars = LiteralStringEscapeCharacter::unescapeCharacters(substr($match['chars'], 1, -1)); + if (preg_match('/^\\\\\d{3}$/', substr($match['chars'], 1, -1)) === 1 && ($glyph = $font->getDifferences()?->getGlyph((int) octdec(substr($match['chars'], 2, -1)))) !== null) { + $chars = $glyph->getChar(); + } elseif (strlen($unescapedChars) === 1 && ($glyph = $font->getDifferences()?->getGlyph(ord($unescapedChars))) !== null) { + $chars = $glyph->getChar(); + } elseif (in_array($encoding = $font->getEncoding(), [EncodingNameValue::MacExpertEncoding, EncodingNameValue::WinAnsiEncoding], true)) { + $chars = $encoding->decodeString($unescapedChars); + } elseif (($toUnicodeCMap = $font->getToUnicodeCMap() ?? $font->getToUnicodeCMapDescendantFont()) !== null) { + $chars = $toUnicodeCMap->textToUnicode(bin2hex($unescapedChars)); + } elseif ($encoding !== null) { + $chars = $encoding->decodeString($unescapedChars); + } else { + $chars = $unescapedChars; + } + + $string .= $chars; + } elseif (str_starts_with($match['chars'], '<') && str_ends_with($match['chars'], '>')) { + $chars = substr($match['chars'], 1, -1); + if (($toUnicodeCMap = $font->getToUnicodeCMap() ?? $font->getToUnicodeCMapDescendantFont()) !== null) { + $string .= $toUnicodeCMap->textToUnicode($chars); + } elseif (($encoding = $font->getEncoding()) !== null) { + $string .= $encoding->decodeString(implode('', array_map(fn (string $character) => mb_chr((int) hexdec($character)), str_split($chars, 2)))); + } else { + throw new ParseFailureException('Unable to use CMap or decode string to retrieve characters for text object'); + } + } else { + throw new ParseFailureException(sprintf('Unrecognized character group format "%s"', $match['chars'])); + } + + if (isset($match['offset']) && (float) $match['offset'] < -100) { + $string .= ' '; + } + } + + return $string; + } + + /** @return list */ + public function getCodePoints(): array { + $codePoints = []; + if (($result = preg_match_all('/(?(<(\\\\>|[^>])*>)|(\((\\\\\)|[^)])*\)))(?-?[0-9]+(\.[0-9]+)?)?/', $this->rawTextContent, $matches, PREG_SET_ORDER)) === false) { + throw new ParseFailureException(sprintf('Error with regex')); + } elseif ($result === 0) { + throw new ParseFailureException(sprintf('Operands "%s" is not in a recognized format', $this->rawTextContent)); + } + + foreach ($matches as $match) { + if (str_starts_with($match['chars'], '(') && str_ends_with($match['chars'], ')')) { + $chars = str_replace(['\(', '\)', '\n', '\r'], ['(', ')', "\n", "\r"], substr($match['chars'], 1, -1)); + $chars = preg_replace_callback('/\\\\([0-7]{3})/', fn (array $matches) => mb_chr((int) octdec($matches[1])), $chars) + ?? throw new ParseFailureException(); + foreach (str_split($chars) as $char) { + $codePoints[] = ord($char); + } + } elseif (str_starts_with($match['chars'], '<') && str_ends_with($match['chars'], '>')) { + foreach (str_split(substr($match['chars'], 1, -1), 4) as $char) { + $codePoints[] = is_int($codePoint = hexdec($char)) ? $codePoint : throw new ParseFailureException(); + } + } else { + throw new ParseFailureException(sprintf('Unrecognized character group format "%s"', $match['chars'])); + } + } + + return $codePoints; + } +} diff --git a/includes/pdfparser/Document/ContentStream/PositionedText/TextState.php b/includes/pdfparser/Document/ContentStream/PositionedText/TextState.php new file mode 100644 index 0000000..c5741d0 --- /dev/null +++ b/includes/pdfparser/Document/ContentStream/PositionedText/TextState.php @@ -0,0 +1,20 @@ +scaleX * $other->scaleX + $this->shearX * $other->shearY, + $this->scaleX * $other->shearX + $this->shearX * $other->scaleY, + $this->shearY * $other->scaleX + $this->scaleY * $other->shearY, + $this->shearY * $other->shearX + $this->scaleY * $other->scaleY, + $this->offsetX * $other->scaleX + $this->offsetY * $other->shearY + $other->offsetX, + $this->offsetX * $other->shearX + $this->offsetY * $other->scaleY + $other->offsetY, + ); + } +} diff --git a/includes/pdfparser/Document/CrossReference/CrossReferenceSourceParser.php b/includes/pdfparser/Document/CrossReference/CrossReferenceSourceParser.php new file mode 100644 index 0000000..fbfab3b --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/CrossReferenceSourceParser.php @@ -0,0 +1,92 @@ +lastPos(Marker::EOF, 0) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::EOF->value)); + $startXrefMarkerPos = $stream->lastPos(Marker::START_XREF, $stream->getSizeInBytes() - $eofMarkerPos) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::START_XREF->value)); + $startByteOffset = $stream->getStartOfNextLine($startXrefMarkerPos, $stream->getSizeInBytes()) + ?? throw new ParseFailureException('Expected a carriage return or line feed after startxref marker, none found'); + $endByteOffset = $stream->getEndOfCurrentLine($startByteOffset, $stream->getSizeInBytes()) + ?? throw new ParseFailureException('Expected a carriage return or line feed after the byte offset, none found'); + + $byteOffsetLastCrossReferenceSection = trim($stream->read($startByteOffset, $endByteOffset - $startByteOffset)); + if ($byteOffsetLastCrossReferenceSection !== (string)(int) $byteOffsetLastCrossReferenceSection) { + throw new ParseFailureException(sprintf('Invalid byte offset last crossReference section "%s", "%s"', $byteOffsetLastCrossReferenceSection, $stream->read($startXrefMarkerPos, $stream->getSizeInBytes() - $startXrefMarkerPos))); + } + + $byteOffsetLastCrossReferenceSection = (int) $byteOffsetLastCrossReferenceSection; + if ($byteOffsetLastCrossReferenceSection > $stream->getSizeInBytes()) { + throw new ParseFailureException(sprintf('Invalid byte offset: position of last crossReference section %d is greater than total size of stream %d. Should this be %d?', $byteOffsetLastCrossReferenceSection, $stream->getSizeInBytes(), $stream->lastPos(Marker::XREF, $stream->getSizeInBytes() - $startXrefMarkerPos) ?? $stream->lastPos(Marker::OBJ, $stream->getSizeInBytes() - $startXrefMarkerPos) ?? 0)); + } + + $eolPosByteOffset = $stream->getEndOfCurrentLine($byteOffsetLastCrossReferenceSection, $stream->getSizeInBytes()) + ?? throw new ParseFailureException('Expected a newline after byte offset for last cross reference stream'); + + $crossReferenceType = self::getCrossReferenceType($stream, $byteOffsetLastCrossReferenceSection, $eolPosByteOffset); + if ($crossReferenceType === null) { // Try to recover from an invalid byte offset crossReference section + $lastPosXrefSection = $stream->lastPos(Marker::XREF, $stream->getSizeInBytes() - $startXrefMarkerPos); + $lastPosObject = $stream->lastPos(Marker::OBJ, $stream->getSizeInBytes() - $startXrefMarkerPos); + if ($lastPosXrefSection === null && $lastPosObject === null) { + throw new ParseFailureException(sprintf('Unable to determine cross reference type for start line "%s" of crossReference source, and no other crossReference table or stream was found.', $stream->read($byteOffsetLastCrossReferenceSection, $eolPosByteOffset - $byteOffsetLastCrossReferenceSection))); + } + + $lastPossibleXrefSectionPos = $lastPosObject === null ? $lastPosXrefSection : ($lastPosXrefSection === null ? $lastPosObject : max($lastPosXrefSection, $lastPosObject)); + $eolStartXrefSectionPos = $stream->getEndOfCurrentLine($lastPossibleXrefSectionPos, $stream->getSizeInBytes()) + ?? throw new ParseFailureException(sprintf('Unable to determine cross reference type for start line "%s" of crossReference source, and no other crossReference table or stream was found.', $stream->read($startByteOffset, $endByteOffset - $startByteOffset))); + $crossReferenceType = self::getCrossReferenceType($stream, $lastPossibleXrefSectionPos, $eolStartXrefSectionPos) + ?? throw new ParseFailureException(sprintf('Unable to determine cross reference type for start line "%s" of crossReference source, and no other crossReference table or stream was found.', $stream->read($startByteOffset, $endByteOffset - $startByteOffset))); + } + + $endCrossReferenceSection = $crossReferenceType === CrossReferenceType::Table + ? ($stream->firstPos(Marker::START_XREF, $eolPosByteOffset, $stream->getSizeInBytes()) ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::START_XREF->value))) + : ($stream->firstPos(Marker::END_OBJ, $eolPosByteOffset, $stream->getSizeInBytes()) ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::END_OBJ->value))); + $currentCrossReferenceSection = $crossReferenceType === CrossReferenceType::Table + ? CrossReferenceTableParser::parse($stream, $eolPosByteOffset, $endCrossReferenceSection - $eolPosByteOffset) + : CrossReferenceStreamParser::parse($stream, $eolPosByteOffset, $endCrossReferenceSection - $eolPosByteOffset); + $crossReferenceSections = [$currentCrossReferenceSection]; + while (($previous = $currentCrossReferenceSection->dictionary->getValueForKey(DictionaryKey::PREV, IntegerValue::class)) !== null && $previous->value !== 0) { + $eolPosByteOffset = $stream->getEndOfCurrentLine($previous->value + 1, $stream->getSizeInBytes()) + ?? throw new ParseFailureException('Expected a newline after byte offset for cross reference stream'); + $endCrossReferenceSection = $crossReferenceType === CrossReferenceType::Table + ? $stream->firstPos(Marker::START_XREF, $eolPosByteOffset, $stream->getSizeInBytes()) ?? throw new ParseFailureException('Unable to locate startxref') + : $stream->firstPos(Marker::END_OBJ, $eolPosByteOffset, $stream->getSizeInBytes()) ?? throw new ParseFailureException('Unable to locate endobj'); + + $currentCrossReferenceSection = $crossReferenceType === CrossReferenceType::Table + ? CrossReferenceTableParser::parse($stream, $eolPosByteOffset, $endCrossReferenceSection - $eolPosByteOffset) + : CrossReferenceStreamParser::parse($stream, $eolPosByteOffset, $endCrossReferenceSection - $eolPosByteOffset); + $crossReferenceSections[] = $currentCrossReferenceSection; + } + + return new CrossReferenceSource(... $crossReferenceSections); + } + + private static function getCrossReferenceType(Stream $stream, int $byteOffsetLastCrossReferenceSection, int $byteOffsetEndOfCurrentLine): ?CrossReferenceType { + $startCrossReferenceContent = trim($stream->read($byteOffsetLastCrossReferenceSection, $byteOffsetEndOfCurrentLine - $byteOffsetLastCrossReferenceSection)); + if ($startCrossReferenceContent === Marker::XREF->value) { + return CrossReferenceType::Table; + } + + if (preg_match('/^[0-9]*\s*[0-9]*\s*obj$/', $startCrossReferenceContent) === 1) { + return CrossReferenceType::Stream; + } + + return null; + } +} diff --git a/includes/pdfparser/Document/CrossReference/CrossReferenceType.php b/includes/pdfparser/Document/CrossReference/CrossReferenceType.php new file mode 100644 index 0000000..0a92742 --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/CrossReferenceType.php @@ -0,0 +1,8 @@ + Where the first is the newest incremental update and the last one is the oldest */ + private readonly array $crossReferenceSections; + + /** @no-named-arguments */ + public function __construct( + CrossReferenceSection... $crossReferenceSections, + ) { + $this->crossReferenceSections = $crossReferenceSections; + } + + public function getCrossReferenceEntry(int $objNumber): CrossReferenceEntryInUseObject|CrossReferenceEntryCompressed|null { + foreach ($this->crossReferenceSections as $crossReferenceSection) { + $crossReferenceEntry = $crossReferenceSection->getCrossReferenceEntry($objNumber); + if ($crossReferenceEntry !== null) { + return $crossReferenceEntry; + } + } + + return null; + } + + public function getReferenceForKey(DictionaryKey $dictionaryKey): ?ReferenceValue { + return $this->getValueForKey($dictionaryKey, ReferenceValue::class); + } + + /** + * @template T of DictionaryValue|NameValue|Dictionary + * @param class-string $valueType + * @return T + */ + public function getValueForKey(DictionaryKey $dictionaryKey, string $valueType): DictionaryValue|Dictionary|NameValue|null { + foreach ($this->crossReferenceSections as $crossReferenceSection) { + $valueForKey = $crossReferenceSection->dictionary->getValueForKey($dictionaryKey, $valueType); + if ($valueForKey !== null) { + return $valueForKey; + } + } + + return null; + } + + public function getFirstId(): string { + $value = $this->getValueForKey(DictionaryKey::ID, ArrayValue::class)->value[0] + ?? throw new ParseFailureException('Unable to retrieve first id from cross reference source'); + if (!is_string($value)) { + throw new ParseFailureException('First id is not a string'); + } + + if (!str_starts_with($value, '<') || !str_ends_with($value, '>')) { + throw new ParseFailureException('Unsupported first id format, expected ""'); + } + + $firstId = hex2bin(substr($value, 1, -1)); + if ($firstId === false) { + throw new ParseFailureException('Unable to retrieve binary value from first id'); + } + + return $firstId; + } +} diff --git a/includes/pdfparser/Document/CrossReference/Source/Section/CrossReferenceSection.php b/includes/pdfparser/Document/CrossReference/Source/Section/CrossReferenceSection.php new file mode 100644 index 0000000..c0261c1 --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/Source/Section/CrossReferenceSection.php @@ -0,0 +1,32 @@ + */ + public readonly array $crossReferenceSubSections; + + /** @no-named-arguments */ + public function __construct( + public readonly Dictionary $dictionary, + CrossReferenceSubSection... $crossReferenceSubSections, + ) { + $this->crossReferenceSubSections = $crossReferenceSubSections; + } + + public function getCrossReferenceEntry(int $objNumber): CrossReferenceEntryInUseObject|CrossReferenceEntryCompressed|null { + foreach ($this->crossReferenceSubSections as $crossReferenceSubSection) { + if ($crossReferenceSubSection->containsObject($objNumber)) { + return $crossReferenceSubSection->getCrossReferenceEntry($objNumber); + } + } + + return null; + } +} diff --git a/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/CrossReferenceSubSection.php b/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/CrossReferenceSubSection.php new file mode 100644 index 0000000..620e352 --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/CrossReferenceSubSection.php @@ -0,0 +1,54 @@ + */ + public array $crossReferenceEntries = []; + + /** + * @phpstan-assert int<0, max> $nrOfEntries + * + * @throws InvalidArgumentException + * + * @no-named-arguments + */ + public function __construct( + public readonly int $firstObjectNumber, + public readonly int $nrOfEntries, + CrossReferenceEntryInUseObject|CrossReferenceEntryFreeObject|CrossReferenceEntryCompressed... $crossReferenceEntries + ) { + if ($this->nrOfEntries < 0) { + throw new InvalidArgumentException('$nrOfEntries should be a positive number'); + } + + $this->crossReferenceEntries = $crossReferenceEntries; + } + + public function containsObject(int $objNumber): bool { + return $objNumber >= $this->firstObjectNumber + && $objNumber < $this->firstObjectNumber + $this->nrOfEntries; + } + + /** @throws RuntimeException */ + public function getCrossReferenceEntry(int $objNumber): CrossReferenceEntryInUseObject|CrossReferenceEntryCompressed|null { + if (self::containsObject($objNumber) === false) { + return null; + } + + $object = $this->crossReferenceEntries[$objNumber - $this->firstObjectNumber] + ?? throw new RuntimeException(sprintf('Object with key %d not found', $objNumber - $this->firstObjectNumber)); + if ($object instanceof CrossReferenceEntryFreeObject) { + throw new RuntimeException('Cross reference entry for object should point to either a compressed or uncompressed entry, not a free object nr'); + } + + return $object; + } +} diff --git a/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryCompressed.php b/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryCompressed.php new file mode 100644 index 0000000..983085d --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/Source/Section/SubSection/Entry/CrossReferenceEntryCompressed.php @@ -0,0 +1,21 @@ + $startPos + * @phpstan-assert int<1, max> $nrOfBytes + * + * @throws PdfParserException + */ + public static function parse(Stream $stream, int $startPos, int $nrOfBytes): CrossReferenceSection { + $dictionary = DictionaryParser::parse($stream, $startPos, $nrOfBytes); + if ($dictionary->getType() !== TypeNameValue::X_REF) { + throw new ParseFailureException('Expected stream of type xref'); + } + + $wValue = $dictionary->getValueForKey(DictionaryKey::W, CrossReferenceStreamByteSizes::class) + ?? throw new ParseFailureException('Cross reference streams should have a dictionary entry for "W"'); + $startStream = $stream->getStartNextLineAfter(Marker::STREAM, $startPos, $startPos + $nrOfBytes) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::STREAM->value)); + + if (($length = $dictionary->getValueForKey(DictionaryKey::LENGTH, IntegerValue::class)?->value) === null) { + $endStream = $stream->lastPos(Marker::END_STREAM, $stream->getSizeInBytes() - $startPos + $nrOfBytes); + if ($endStream === null || $endStream > ($startPos + $nrOfBytes)) { + throw new ParseFailureException(sprintf('Expected end of stream content marked by %s, none found', Marker::END_STREAM->value)); + } + + $length = $endStream - $startStream - 1; + } + + $entries = []; + $hexContent = bin2hex(CompressedObjectContentParser::parseBinary($stream, $startStream, $length, $dictionary)->toString()); + foreach (str_split($hexContent, $wValue->getTotalLengthInBytes() * self::HEX_CHARS_IN_BYTE) as $referenceRow) { + $field1 = hexdec(substr($referenceRow, 0, $wValue->lengthRecord1InBytes * self::HEX_CHARS_IN_BYTE)); + $field2 = hexdec(substr($referenceRow, $wValue->lengthRecord1InBytes * self::HEX_CHARS_IN_BYTE, $wValue->lengthRecord2InBytes * self::HEX_CHARS_IN_BYTE)); + $field3 = hexdec(substr($referenceRow, ($wValue->lengthRecord1InBytes + $wValue->lengthRecord2InBytes) * self::HEX_CHARS_IN_BYTE, $wValue->lengthRecord3InBytes * self::HEX_CHARS_IN_BYTE)); + if (!is_int($field1) || !is_int($field2) || !is_int($field3)) { + throw new ParseFailureException(sprintf('Field 1, 2 and 3 in cross reference entries should be int, got %s, %s and %s', gettype($field1), gettype($field2), gettype($field3))); + } + + $entries[] = match (CrossReferenceStreamType::tryFrom($field1)) { + CrossReferenceStreamType::LINKED_LIST_FREE_OBJECT => new CrossReferenceEntryFreeObject($field2, $field3), + CrossReferenceStreamType::UNCOMPRESSED_OBJECT => new CrossReferenceEntryInUseObject($field2, $field3), + CrossReferenceStreamType::COMPRESSED_OBJECT => new CrossReferenceEntryCompressed($field2, $field3), + null => throw new ParseFailureException(sprintf('Unrecognized CrossReferenceStream type "%s"', $field1)), + }; + } + + /** @var list $startObjNrOfItemsArray where all even items are the start object number and all odd items are the number of objects */ + $startObjNrOfItemsArray = $dictionary->getValueForKey(DictionaryKey::INDEX, ArrayValue::class)->value + ?? [0, $dictionary->getValueForKey(DictionaryKey::SIZE, IntegerValue::class)->value ?? throw new ParseFailureException('Cross reference streams should have either an index or a size, neither was found')]; + + $crossReferenceSubSections = []; + foreach (array_chunk($startObjNrOfItemsArray, 2) as $startNrNrOfObjects) { + /** @phpstan-ignore offsetAccess.notFound, offsetAccess.notFound */ + $crossReferenceSubSections[] = new CrossReferenceSubSection($startNrNrOfObjects[0], $startNrNrOfObjects[1], ... array_slice($entries, 0, $startNrNrOfObjects[1])); + $entries = array_slice($entries, $startNrNrOfObjects[1]); + } + + return new CrossReferenceSection($dictionary, ... $crossReferenceSubSections); + } +} diff --git a/includes/pdfparser/Document/CrossReference/Stream/CrossReferenceStreamType.php b/includes/pdfparser/Document/CrossReference/Stream/CrossReferenceStreamType.php new file mode 100644 index 0000000..d27717c --- /dev/null +++ b/includes/pdfparser/Document/CrossReference/Stream/CrossReferenceStreamType.php @@ -0,0 +1,11 @@ +firstPos(Marker::TRAILER, $startPos, $startPos + $nrOfBytes) + ?? throw new ParseFailureException('Unable to locate trailer for crossReferenceTable'); + $dictionary = DictionaryParser::parse($stream, $startTrailerPos + Marker::TRAILER->length(), $nrOfBytes - ($startTrailerPos + Marker::TRAILER->length() - $startPos)); + + $firstObjectNumber = $nrOfEntries = null; + $crossReferenceSubSections = $crossReferenceEntries = []; + $content = trim($stream->read($startPos, $startTrailerPos - $startPos)); + $content = str_replace([WhitespaceCharacter::CARRIAGE_RETURN->value, WhitespaceCharacter::LINE_FEED->value . WhitespaceCharacter::LINE_FEED->value], WhitespaceCharacter::LINE_FEED->value, $content); + foreach (explode(WhitespaceCharacter::LINE_FEED->value, $content) as $line) { + $sections = explode(WhitespaceCharacter::SPACE->value, trim($line)); + switch (count($sections)) { + case 2: + if ($firstObjectNumber !== null && $nrOfEntries !== null) { + $crossReferenceSubSections[] = new CrossReferenceSubSection($firstObjectNumber, $nrOfEntries, ... $crossReferenceEntries); // Use previous objectNr and nrOfEntries + } + $crossReferenceEntries = []; + $firstObjectNumber = (int) $sections[0]; + $nrOfEntries = (int) $sections[1]; + break; + case 3: + $crossReferenceEntries[] = match (CrossReferenceTableInUseOrFree::tryFrom(trim($sections[2]))) { + CrossReferenceTableInUseOrFree::IN_USE => new CrossReferenceEntryInUseObject((int) $sections[0], (int) $sections[1]), + CrossReferenceTableInUseOrFree::FREE => new CrossReferenceEntryFreeObject((int) $sections[0], (int) $sections[1]), + null => throw new ParseFailureException(sprintf('Unrecognized crossReference table record type %s', trim($sections[2]))) + }; + break; + default: + throw new ParseFailureException(sprintf('Invalid line "%s", 2 or 3 sections expected, %d found', substr(trim($line), 0, 30), count($sections))); + } + } + + if ($firstObjectNumber !== null && $nrOfEntries !== null) { + $crossReferenceSubSections[] = new CrossReferenceSubSection($firstObjectNumber, $nrOfEntries, ... $crossReferenceEntries); + } + + return new CrossReferenceSection($dictionary, ... $crossReferenceSubSections); + } +} diff --git a/includes/pdfparser/Document/Dictionary/Dictionary.php b/includes/pdfparser/Document/Dictionary/Dictionary.php new file mode 100644 index 0000000..54084af --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/Dictionary.php @@ -0,0 +1,143 @@ + */ + public readonly array $dictionaryEntries; + + /** @no-named-arguments */ + public function __construct( + DictionaryEntry... $dictionaryEntries + ) { + $this->dictionaryEntries = $dictionaryEntries; + } + + /** + * @template T of DictionaryValue|NameValue|Dictionary + * @param class-string $valueType + * @return T + */ + public function getValueForKey(DictionaryKey|ExtendedDictionaryKey $dictionaryKey, string $valueType): DictionaryValue|Dictionary|NameValue|null { + foreach ($this->dictionaryEntries as $dictionaryEntry) { + if (($dictionaryKey instanceof DictionaryKey && $dictionaryEntry->key === $dictionaryKey) + || ($dictionaryKey instanceof ExtendedDictionaryKey && $dictionaryEntry->key instanceof ExtendedDictionaryKey && $dictionaryEntry->key->value === $dictionaryKey->value)) { + $value = $dictionaryEntry->value; + if (is_a($value, $valueType) === false) { + throw new InvalidArgumentException(sprintf('Expected value with value %s to be of type %s, got %s', $dictionaryKey->value, $valueType, get_class($value))); + } + + return $value; + } + } + + return null; + } + + /** @return class-string */ + public function getTypeForKey(DictionaryKey $dictionaryKey): ?string { + foreach ($this->dictionaryEntries as $dictionaryEntry) { + if ($dictionaryEntry->key === $dictionaryKey) { + return $dictionaryEntry->value::class; + } + } + + return null; + } + + public function getSubDictionary(?Document $document, DictionaryKey $dictionaryKey): ?Dictionary { + $subDictionaryType = $this->getTypeForKey($dictionaryKey); + if ($subDictionaryType === null) { + return null; + } + + if ($subDictionaryType === Dictionary::class) { + return $this->getValueForKey($dictionaryKey, Dictionary::class) ?? throw new RuntimeException(); + } + + if ($subDictionaryType === DictionaryArrayValue::class) { + return ($this->getValueForKey($dictionaryKey, DictionaryArrayValue::class) ?? throw new RuntimeException())->toSingleDictionary(); + } + + if ($subDictionaryType === ReferenceValue::class) { + if ($document === null) { + throw new ParseFailureException('Document is required to get subDictionary for reference'); + } + + return ($this->getObjectForReference($document, $dictionaryKey) ?? throw new ParseFailureException()) + ->getDictionary(); + } + + throw new ParseFailureException(sprintf('Invalid type "%s" for subDictionary with key %s', $subDictionaryType, $dictionaryKey->name)); + } + + /** + * @template T of DecoratedObject + * @param class-string|null $expectedDecoratorFQN + * @return ($expectedDecoratorFQN is null ? DecoratedObject : T) + */ + public function getObjectForReference(Document $document, DictionaryKey|ExtendedDictionaryKey $dictionaryKey, ?string $expectedDecoratorFQN = null): ?DecoratedObject { + $reference = $this->getValueForKey($dictionaryKey, ReferenceValue::class); + if ($reference === null) { + return null; + } + + return $document->getObject($reference->objectNumber, $expectedDecoratorFQN) + ?? throw new ParseFailureException(); + } + + /** + * @template T of DecoratedObject + * @param class-string|null $expectedDecoratorFQN + * @return ($expectedDecoratorFQN is null ? list : list) + */ + public function getObjectsForReference(Document $document, DictionaryKey|ExtendedDictionaryKey $dictionaryKey, ?string $expectedDecoratorFQN = null): array { + $references = $this->getValueForKey($dictionaryKey, ReferenceValueArray::class); + if ($references === null) { + return []; + } + + $objects = []; + foreach ($references->referenceValues as $referenceValue) { + $objects[] = $document->getObject($referenceValue->objectNumber, $expectedDecoratorFQN) + ?? throw new ParseFailureException(); + } + + return $objects; + } + + public function getType(): ?TypeNameValue { + if ($this->getTypeForKey(DictionaryKey::TYPE) === Dictionary::class) { + return $this->getValueForKey(DictionaryKey::TYPE, Dictionary::class) + ?->getValueForKey(DictionaryKey::TYPE, TypeNameValue::class); + } + + return $this->getValueForKey(DictionaryKey::TYPE, TypeNameValue::class); + } + + public function getSubType(): ?SubtypeNameValue { + if ($this->getTypeForKey(DictionaryKey::SUBTYPE) === Dictionary::class) { + return $this->getValueForKey(DictionaryKey::SUBTYPE, Dictionary::class) + ?->getValueForKey(DictionaryKey::SUBTYPE, SubtypeNameValue::class); + } + + return $this->getValueForKey(DictionaryKey::SUBTYPE, SubtypeNameValue::class); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryEntry/DictionaryEntry.php b/includes/pdfparser/Document/Dictionary/DictionaryEntry/DictionaryEntry.php new file mode 100644 index 0000000..f9784c3 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryEntry/DictionaryEntry.php @@ -0,0 +1,19 @@ + $dictionaryValue + * @throws PdfParserException + */ + public static function fromKeyValuePair(string $keyString, string|array $dictionaryValue): ?DictionaryEntry { + $dictionaryKey = DictionaryKey::tryFromKeyString($keyString) + ?? ExtendedDictionaryKey::fromKeyString($keyString); + + return new DictionaryEntry($dictionaryKey, self::getValue($dictionaryKey, $dictionaryValue)); + } + + /** + * @param string|array $value + * @throws PdfParserException + */ + protected static function getValue(DictionaryKey|ExtendedDictionaryKey $dictionaryKey, string|array $value): Dictionary|DictionaryValue|NameValue { + $allowedValueTypes = $dictionaryKey->getValueTypes(); + if ((in_array(Dictionary::class, $allowedValueTypes, true) || in_array(ArrayValue::class, $allowedValueTypes, true)) + && is_array($value)) { + return DictionaryFactory::fromArray($value); + } + + if ((in_array(Dictionary::class, $allowedValueTypes, true) || in_array(ArrayValue::class, $allowedValueTypes, true)) + && is_string($value) + && preg_match('/^[0-9]+ [0-9]+ R$/', $value) === 1 + && ($referenceValue = ReferenceValue::fromValue($value)) !== null) { + return $referenceValue; + } + + foreach ($allowedValueTypes as $allowedValueType) { + if (is_a($allowedValueType, BackedEnum::class, true) + && is_string($value) + && ($resolvedValue = $allowedValueType::tryFrom(NameValueNormalizer::normalize($value))) !== null) { + return $resolvedValue; + } + } + + foreach ($allowedValueTypes as $allowedValueType) { + if (!is_a($allowedValueType, DictionaryValue::class, true) + || $allowedValueType === TextStringValue::class) { // TextStrings accept everything, so we check that last + continue; + } + + if (!is_string($value) || ($valueObject = $allowedValueType::fromValue($value)) === null) { + continue; + } + + return $valueObject; + } + + if (in_array(TextStringValue::class, $allowedValueTypes, true) && is_string($value)) { + return TextStringValue::fromValue($value); + } + + throw new ParseFailureException(sprintf('Value "%s" for dictionary key %s could not be parsed to a valid value type', is_array($value) ? 'array()' : $value, $dictionaryKey->value)); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryFactory.php b/includes/pdfparser/Document/Dictionary/DictionaryFactory.php new file mode 100644 index 0000000..8ad36d5 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryFactory.php @@ -0,0 +1,34 @@ + $dictionaryArray + * @throws PdfParserException + */ + public static function fromArray(array $dictionaryArray): Dictionary { + $dictionaryEntries = []; + foreach ($dictionaryArray as $keyString => $value) { + if (!is_string($value) && (!is_array($value) || array_is_list($value))) { + throw new InvalidArgumentException(sprintf('values should be either strings or non-list array, %s given', gettype($value))); + } + + /** @var non-empty-array|string $value */ + $dictionaryEntry = DictionaryEntryFactory::fromKeyValuePair($keyString, $value); + if ($dictionaryEntry === null) { + continue; + } + + $dictionaryEntries[] = $dictionaryEntry; + } + + return new Dictionary(... $dictionaryEntries); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKey.php b/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKey.php new file mode 100644 index 0000000..dde3515 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKey.php @@ -0,0 +1,1271 @@ + [Dictionary::class, BooleanValue::class, DictionaryArrayValue::class, IntegerValue::class, TextStringValue::class], + self::AA, self::BS => [Dictionary::class], + self::AC => [TextStringValue::class, ArrayValue::class], + self::AF => [DictionaryArrayValue::class, ReferenceValueArray::class, ReferenceValue::class], + self::ACCURATE_SCREENS => [BooleanValue::class], + self::ACRO_FORM => [Dictionary::class], + self::ACTION => [TextStringValue::class], + self::ADD_REV_INFO => [BooleanValue::class], + self::ADDITIONAL_STREAMS => [ArrayValue::class], + self::AFTER => [TextStringValue::class], + self::AFTER_PERMS_READY => [TextStringValue::class], + self::AIS => [BooleanValue::class, TextStringValue::class], + self::ALT => [ArrayValue::class], + self::ALTERNATE => [ArrayValue::class, TextStringValue::class], + self::ALTERNATE_IMAGES => [IntegerValue::class], + self::ALTERNATE_PRESENTATIONS => [TextStringValue::class], + self::ALTERNATES => [DictionaryArrayValue::class], + self::AN => [Dictionary::class], + self::ANGLE => [IntegerValue::class, FloatValue::class], + self::ANNOTATION => [Dictionary::class], + self::ANNOTATIONS => [IntegerValue::class], + self::ANNOTS => [ReferenceValue::class, ReferenceValueArray::class, DictionaryArrayValue::class], + self::ANTI_ALIAS => [BooleanValue::class], + self::AP => [DictionaryArrayValue::class], + self::APREF => [Dictionary::class], + self::ART_BOX => [Rectangle::class], + self::AS => [DictionaryArrayValue::class, TextStringValue::class], + self::ASCENT => [IntegerValue::class, FloatValue::class], + self::ASPECT => [ArrayValue::class], + self::ATTESTATION => [TextStringValue::class], + self::AUTH_EVENT => [AuthEventNameValue::class], + self::AUTHOR => [TextStringValue::class], + self::AVG_WIDTH => [IntegerValue::class, FloatValue::class], + self::B => [ArrayValue::class, BooleanValue::class, Dictionary::class, IntegerValue::class], + self::BACKGROUND => [ArrayValue::class], + self::BASE => [TextStringValue::class], + self::BASE_ENCODING => [EncodingNameValue::class], + self::BASE_FONT => [TextStringValue::class], + self::BASE_STATE => [TextStringValue::class], + self::BASE_VERSION => [Version::class], + self::BBOX => [Rectangle::class], + self::BC => [ArrayValue::class], + self::BE => [Dictionary::class], + self::BEFORE => [TextStringValue::class], + self::BG => [TextStringValue::class, ArrayValue::class, Dictionary::class], + self::BG2 => [TextStringValue::class], + self::BI => [Dictionary::class], + self::BITS_PER_COMPONENT => [IntegerValue::class], + self::BITS_PER_COORDINATE => [IntegerValue::class], + self::BITS_PER_FLAG => [IntegerValue::class], + self::BITS_PER_SAMPLE => [IntegerValue::class], + self::BLACK_IS1 => [BooleanValue::class], + self::BLACK_POINT => [ArrayValue::class], + self::BLEED_BOX => [Rectangle::class], + self::BM => [BlendModeNameValue::class], + self::BORDER => [ArrayValue::class], + self::BOUNDS => [ArrayValue::class], + self::BOX_COLOR_INFO => [Dictionary::class], + self::BU => [TextStringValue::class], + self::BYTE_RANGE => [ArrayValue::class], + self::C => [Dictionary::class, ArrayValue::class, IntegerValue::class, FloatValue::class, BooleanValue::class], + self::C0 => [ArrayValue::class], + self::C1 => [ArrayValue::class], + self::C2W => [ArrayValue::class], + self::CA => [IntegerValue::class, FloatValue::class, TextStringValue::class], + self::CA_L => [IntegerValue::class, FloatValue::class], + self::CAP => [BooleanValue::class], + self::CAP_HEIGHT => [IntegerValue::class, FloatValue::class], + self::CATEGORY => [ArrayValue::class], + self::CENTER_WINDOW => [BooleanValue::class], + self::CERT => [Dictionary::class, ArrayValue::class, TextStringValue::class], + self::CF => [Dictionary::class], + self::CFM => [CFMNameValue::class], + self::CHANGES => [ArrayValue::class], + self::CHAR_PROCS => [Dictionary::class], + self::CHAR_SET => [TextStringValue::class], + self::CHECK_SUM => [TextStringValue::class], + self::CI => [Dictionary::class], + self::CIDSET => [TextStringValue::class], + self::CIDSYSTEM_INFO => [Dictionary::class, DictionaryArrayValue::class], + self::CIDTO_GIDMAP => [TextStringValue::class], + self::CL => [ArrayValue::class], + self::CLR_F => [IntegerValue::class], + self::CLR_FF => [TextStringValue::class], + self::CMAP_NAME => [TextStringValue::class], + self::CO => [ArrayValue::class, TextStringValue::class, IntegerValue::class, FloatValue::class], + self::CO_ => [TextStringValue::class], + self::COLLECTION => [Dictionary::class], + self::COLOR_SPACE => [DeviceColorSpaceNameValue::class, CIEColorSpaceNameValue::class, SpecialColorSpaceNameValue::class, Dictionary::class, ArrayValue::class], + self::COLOR_TRANSFORM => [IntegerValue::class], + self::COLORANTS => [Dictionary::class], + self::COLORS => [IntegerValue::class], + self::COLUMNS => [IntegerValue::class], + self::COMPANY => [TextStringValue::class], + self::COMPONENTS => [ArrayValue::class], + self::CONFIGS => [DictionaryArrayValue::class], + self::CONTACT_INFO => [TextStringValue::class], + self::CONTENT_TYPE_ID => [TextStringValue::class], + self::CONTENTS => [ReferenceValue::class, ReferenceValueArray::class, TextStringValue::class], + self::COORDS => [ArrayValue::class], + self::COUNT => [IntegerValue::class], + self::CP => [TextStringValue::class], + self::CREATION_DATE => [DateValue::class, ReferenceValue::class, TextStringValue::class], + self::CREATOR => [IntegerValue::class, TextStringValue::class], + self::CREATOR_INFO => [Dictionary::class], + self::CROP_BOX => [Rectangle::class, ReferenceValue::class], + self::CS => [TextStringValue::class, ArrayValue::class], + self::CS_L => [TextStringValue::class], + self::CT => [TextStringValue::class], + self::CV => [IntegerValue::class, FloatValue::class], + self::CYX => [IntegerValue::class, FloatValue::class], + self::D => [TextStringValue::class, DateValue::class, IntegerValue::class, FloatValue::class, ArrayValue::class, Dictionary::class], + self::DA => [TextStringValue::class], + self::DAMAGED_ROWS_BEFORE_ERROR => [IntegerValue::class], + self::DATA => [TextStringValue::class], + self::DECODE => [ArrayValue::class], + self::DECODE_PARMS => [Dictionary::class, DictionaryArrayValue::class], + self::DEFAULT => [Dictionary::class, TextStringValue::class], + self::DEFAULT_FOR_PRINTING => [BooleanValue::class], + self::DESC => [TextStringValue::class], + self::DESCENDANT_FONTS => [ReferenceValueArray::class, Dictionary::class, DictionaryArrayValue::class], + self::DESCENT => [IntegerValue::class, FloatValue::class], + self::DEST => [TextStringValue::class, ArrayValue::class], + self::DESTS => [Dictionary::class, TextStringValue::class], + self::DEV_DEP_GS_BG => [IntegerValue::class], + self::DEV_DEP_GS_FL => [IntegerValue::class], + self::DEV_DEP_GS_HT => [IntegerValue::class], + self::DEV_DEP_GS_OP => [IntegerValue::class], + self::DEV_DEP_GS_TR => [IntegerValue::class], + self::DEV_DEP_GS_UCR => [IntegerValue::class], + self::DI => [IntegerValue::class, FloatValue::class, TextStringValue::class], + self::DIFFERENCES => [DifferencesArrayValue::class, TextStringValue::class], + self::DIGEST_METHOD => [ArrayValue::class, TextStringValue::class], + self::DIRECTION => [DirectionNameValue::class], + self::DIS => [TextStringValue::class], + self::DISPLAY_DOC_TITLE => [BooleanValue::class, ReferenceValue::class], + self::DL => [IntegerValue::class], + self::DM => [TextStringValue::class], + self::DOC => [ArrayValue::class], + self::DOC_CHECKSUM => [TextStringValue::class], + self::DOC_MDP => [Dictionary::class], + self::DOCUMENT => [ArrayValue::class], + self::DOMAIN => [ArrayValue::class], + self::DOS => [TextStringValue::class], + self::DOT_GAIN => [Dictionary::class], + self::DP => [Dictionary::class], + self::DR => [Dictionary::class], + self::DS => [TextStringValue::class, Dictionary::class], + self::DUPLEX => [PaperHandlingNameValue::class], + self::DUR => [IntegerValue::class, FloatValue::class], + self::DURATION => [TextStringValue::class], + self::DV => [TextStringValue::class], + self::DW => [IntegerValue::class], + self::DW2 => [ArrayValue::class], + self::E => [BooleanValue::class, Dictionary::class, TextStringValue::class], + self::EA => [BooleanValue::class], + self::EARLY_CHANGE => [IntegerValue::class], + self::EF => [Dictionary::class, ReferenceValue::class], + self::EFF => [TextStringValue::class], + self::EMBEDDED_FDFS => [DictionaryArrayValue::class], + self::EMBEDDED_FILES => [Dictionary::class], + self::ENCODE => [ArrayValue::class], + self::ENCODED_BYTE_ALIGN => [BooleanValue::class], + self::ENCODING => [EncodingNameValue::class, Dictionary::class], + self::ENCRYPT => [Dictionary::class], + self::ENCRYPT_METADATA => [BooleanValue::class], + self::ENCRYPTION_REVISION => [IntegerValue::class], + self::END_OF_BLOCK => [BooleanValue::class], + self::END_OF_LINE => [BooleanValue::class], + self::EVENT => [EventNameValue::class], + self::EX_DATA => [Dictionary::class], + self::EXPORT => [Dictionary::class], + self::EXT_GSTATE => [Dictionary::class], + self::EXTEND => [ArrayValue::class], + self::EXTENDS => [TextStringValue::class], + self::EXTENSION_LEVEL => [IntegerValue::class], + self::EXTENSIONS => [Dictionary::class], + self::EXTERNAL_OPIDICTS => [IntegerValue::class], + self::EXTERNAL_REF_XOBJECTS => [IntegerValue::class], + self::EXTERNAL_STREAMS => [IntegerValue::class], + self::F => [TextStringValue::class, IntegerValue::class, FloatValue::class, Dictionary::class], + self::FB => [BooleanValue::class], + self::FC => [ArrayValue::class, TextStringValue::class], + self::FD => [Dictionary::class, BooleanValue::class], + self::FDECODE_PARMS => [Dictionary::class, DictionaryArrayValue::class], + self::FDF => [Dictionary::class], + self::FF => [IntegerValue::class], + self::FFILTER => [FilterNameValue::class, ArrayValue::class], + self::FIELDS => [ReferenceValue::class, ReferenceValueArray::class, DictionaryArrayValue::class], + self::FILTER => [FilterNameValue::class, SecurityHandlerNameValue::class, ArrayValue::class], + self::FIRST => [IntegerValue::class, Dictionary::class], + self::FIRST_CHAR => [IntegerValue::class], + self::FIT_WINDOW => [BooleanValue::class], + self::FIXED_PRINT => [Dictionary::class], + self::FL => [IntegerValue::class, FloatValue::class], + self::FLAGS => [IntegerValue::class], + self::FO => [Dictionary::class], + self::FONT => [Dictionary::class, ArrayValue::class], + self::FONT_BBOX => [Rectangle::class, ReferenceValue::class], + self::FONT_DESCRIPTOR => [Dictionary::class], + self::FONT_FAMILY => [TextStringValue::class], + self::FONT_FAUXING => [DictionaryArrayValue::class], + self::FONT_FILE => [TextStringValue::class], + self::FONT_FILE2 => [TextStringValue::class], + self::FONT_FILE3 => [TextStringValue::class], + self::FONT_MATRIX => [ArrayValue::class], + self::FONT_NAME => [TextStringValue::class], + self::FONT_STRETCH => [TextStringValue::class], + self::FONT_WEIGHT => [IntegerValue::class, FloatValue::class], + self::FORM => [ArrayValue::class], + self::FORM_TYPE => [IntegerValue::class], + self::FOV => [IntegerValue::class, FloatValue::class], + self::FREQUENCY => [IntegerValue::class, FloatValue::class], + self::FS => [TextStringValue::class], + self::FT => [TextStringValue::class], + self::FUNCTION => [TextStringValue::class], + self::FUNCTION_TYPE => [IntegerValue::class], + self::FUNCTIONS => [ArrayValue::class], + self::FWPOSITION => [ArrayValue::class], + self::FWSCALE => [ArrayValue::class], + self::G => [ReferenceValue::class, TextStringValue::class], + self::GAMMA => [IntegerValue::class, FloatValue::class, ArrayValue::class], + self::GO_TO_REMOTE_ACTIONS => [IntegerValue::class], + self::GROUP => [Dictionary::class], + self::GS => [TextStringValue::class], + self::GS_L => [TextStringValue::class], + self::H => [TextStringValue::class, IntegerValue::class, FloatValue::class, BooleanValue::class, ArrayValue::class], + self::HALFTONE_NAME => [TextStringValue::class], + self::HALFTONE_TYPE => [IntegerValue::class], + self::HEIGHT => [IntegerValue::class], + self::HEIGHT2 => [IntegerValue::class], + self::HELV => [TextStringValue::class], + self::HI => [BooleanValue::class], + self::HIDE_ANNOTATION_ACTIONS => [IntegerValue::class], + self::HIDE_MENUBAR => [BooleanValue::class], + self::HIDE_TOOLBAR => [BooleanValue::class], + self::HIDE_WINDOW_UI => [BooleanValue::class], + self::HT => [Dictionary::class, TextStringValue::class], + self::I => [BooleanValue::class, Dictionary::class, IntegerValue::class, FloatValue::class, TextStringValue::class], + self::IC => [ArrayValue::class], + self::ID => [ArrayValue::class, TextStringValue::class], + self::IDTREE => [Dictionary::class], + self::IDENTITY => [TextStringValue::class], + self::IDS => [TextStringValue::class], + self::IF => [Dictionary::class], + self::IM => [TextStringValue::class], + self::IMAGE => [TextStringValue::class], + self::IMAGE_B => [TextStringValue::class], + self::IMAGE_C => [TextStringValue::class], + self::IMAGE_I => [TextStringValue::class], + self::IMAGE_MASK => [BooleanValue::class], + self::IN => [TextStringValue::class], + self::IN_DESIGN => [TextStringValue::class, Dictionary::class], + self::INDEX => [ArrayValue::class], + self::INFO => [Dictionary::class, TextStringValue::class], + self::INK_LIST => [ArrayValue::class], + self::INTENT => [IntentNameValue::class, RenderingIntentNameValue::class, ArrayValue::class], + self::INTERPOLATE => [BooleanValue::class], + self::IRT => [Dictionary::class], + self::IS_MAP => [BooleanValue::class], + self::ISSUER => [ArrayValue::class], + self::IT => [TextStringValue::class], + self::ITALIC_ANGLE => [IntegerValue::class, FloatValue::class], + self::IV => [BooleanValue::class], + self::IX => [TextStringValue::class], + self::JAVA_SCRIPT => [TextStringValue::class, Dictionary::class], + self::JAVA_SCRIPT_ACTIONS => [IntegerValue::class], + self::JBIG2GLOBALS => [TextStringValue::class], + self::JS => [TextStringValue::class], + self::K => [IntegerValue::class, BooleanValue::class, Dictionary::class, DictionaryArrayValue::class], + self::KEY_USAGE => [ArrayValue::class], + self::KEYWORDS => [TextStringValue::class], + self::KIDS => [ReferenceValueArray::class], + self::L => [Rectangle::class, ArrayValue::class], + self::LANG => [TextStringValue::class], + self::LANGUAGE => [Dictionary::class], + self::LAST => [Dictionary::class], + self::LAST_CHAR => [IntegerValue::class], + self::LAST_MODIFIED => [DateValue::class], + self::LAUNCH_ACTIONS => [IntegerValue::class], + self::LC => [IntegerValue::class], + self::LE => [TextStringValue::class, ArrayValue::class], + self::LEADING => [IntegerValue::class, FloatValue::class], + self::LEGAL => [Dictionary::class], + self::LEGAL_ATTESTATION => [Dictionary::class], + self::LENGTH => [IntegerValue::class, ReferenceValue::class], + self::LENGTH1 => [IntegerValue::class], + self::LENGTH2 => [IntegerValue::class], + self::LENGTH3 => [IntegerValue::class], + self::LEVEL1 => [TextStringValue::class], + self::LI => [BooleanValue::class], + self::LIMITS => [ArrayValue::class], + self::LINEARIZED => [IntegerValue::class, FloatValue::class], + self::LIST_MODE => [ListModeNameValue::class], + self::LJ => [IntegerValue::class], + self::LL => [IntegerValue::class, FloatValue::class], + self::LLE => [IntegerValue::class, FloatValue::class], + self::LLO => [IntegerValue::class, FloatValue::class], + self::LOCATION => [TextStringValue::class], + self::LOCK => [Dictionary::class], + self::LOCKED => [ArrayValue::class], + self::LS => [Dictionary::class], + self::LW => [IntegerValue::class, FloatValue::class], + self::M => [TextStringValue::class, DateValue::class, IntegerValue::class, ArrayValue::class], + self::MA => [DictionaryArrayValue::class], + self::MAC => [TextStringValue::class, Dictionary::class], + self::MARK_INFO => [Dictionary::class], + self::MARKED => [BooleanValue::class], + self::MASK => [TextStringValue::class, ArrayValue::class], + self::MATRIX => [ArrayValue::class], + self::MATTE => [ArrayValue::class], + self::MAX_LEN => [IntegerValue::class], + self::MAX_WIDTH => [IntegerValue::class, FloatValue::class], + self::MCAF => [DictionaryArrayValue::class], + self::MD5 => [TextStringValue::class], + self::MDP => [Dictionary::class], + self::MEASURE => [Dictionary::class], + self::MEDIA_BOX => [Rectangle::class, ReferenceValue::class], + self::METADATA => [TextStringValue::class], + self::MH => [Dictionary::class], + self::MISSING_WIDTH => [IntegerValue::class, FloatValue::class], + self::MIX => [BooleanValue::class], + self::MIXING_HINTS => [Dictionary::class], + self::MK => [Dictionary::class], + self::ML => [IntegerValue::class, FloatValue::class], + self::MOD_DATE => [DateValue::class, ReferenceValue::class], + self::MODE => [TextStringValue::class], + self::MOVIE => [Dictionary::class], + self::MOVIE_ACTIONS => [IntegerValue::class], + self::MS => [TextStringValue::class], + self::MSG => [TextStringValue::class], + self::MU => [DictionaryArrayValue::class], + self::N => [IntegerValue::class, FloatValue::class, TextStringValue::class, Dictionary::class], + self::NA => [Dictionary::class, ArrayValue::class], + self::NAME => [TextStringValue::class, TextStringValue::class], + self::NAMES => [Dictionary::class, ArrayValue::class], + self::NEED_APPEARANCES => [BooleanValue::class], + self::NEEDS_RENDERING => [BooleanValue::class], + self::NEW_WINDOW => [BooleanValue::class], + self::NEXT => [Dictionary::class, DictionaryArrayValue::class], + self::NM => [TextStringValue::class], + self::NON_EMBEDDED_FONTS => [IntegerValue::class], + self::NON_FULL_SCREEN_PAGE_MODE => [NonFullScreenPageModeNameValue::class], + self::NP => [BooleanValue::class], + self::NR => [BooleanValue::class], + self::NU => [DictionaryArrayValue::class], + self::NUM_COPIES => [IntegerValue::class], + self::NUMS => [TextStringValue::class], + self::O => [TextStringValue::class, IntegerValue::class, FloatValue::class, Dictionary::class, ArrayValue::class, BooleanValue::class], + self::OB => [TextStringValue::class], + self::OC => [Dictionary::class], + self::OCGS => [ReferenceValueArray::class, DictionaryArrayValue::class], + self::OCPROPERTIES => [Dictionary::class], + self::OFF => [ArrayValue::class], + self::OID => [ArrayValue::class], + self::ON => [ReferenceValueArray::class, ArrayValue::class], + self::ON_INSTANTIATE => [TextStringValue::class], + self::OP => [BooleanValue::class, IntegerValue::class], + self::OP_L => [TextStringValue::class], + self::OPEN => [BooleanValue::class], + self::OPEN_ACTION => [Dictionary::class, ArrayValue::class], + self::OPERATION => [TextStringValue::class], + self::OPI => [Dictionary::class], + self::OPM => [IntegerValue::class], + self::OPT => [ArrayValue::class], + self::OPTIONAL_CONTENT => [BooleanValue::class], + self::ORDER => [IntegerValue::class, ArrayValue::class], + self::ORDERING => [TextStringValue::class], + self::OS => [ArrayValue::class, IntegerValue::class, FloatValue::class], + self::OUTLINES => [Dictionary::class], + self::OUTPUT_INTENTS => [ReferenceValue::class, ReferenceValueArray::class, DictionaryArrayValue::class], + self::OVERLAY_TEXT => [TextStringValue::class], + self::P => [IntegerValue::class, TextStringValue::class, VisibilityPolicyNameValue::class, Dictionary::class, BooleanValue::class, ArrayValue::class, DictionaryArrayValue::class], + self::PA => [Dictionary::class], + self::PAGE => [IntegerValue::class, TextStringValue::class], + self::PAGE_ELEMENT => [Dictionary::class], + self::PAGE_LABELS => [TextStringValue::class, ArrayValue::class], + self::PAGE_LAYOUT => [PageLayoutNameValue::class], + self::PAGE_MODE => [PageModeNameValue::class], + self::PAGES => [Dictionary::class, TextStringValue::class, DictionaryArrayValue::class], + self::PAINT_TYPE => [IntegerValue::class], + self::PARAMS => [Dictionary::class], + self::PARENT => [Dictionary::class], + self::PARENT_TREE => [Dictionary::class], + self::PATTERN => [Dictionary::class, TextStringValue::class], + self::PATTERN_TYPE => [IntegerValue::class], + self::PC => [Dictionary::class, IntegerValue::class, ArrayValue::class], + self::PDF => [TextStringValue::class], + self::PDFDOC_ENCODING => [TextStringValue::class], + self::PERMS => [Dictionary::class], + self::PI => [Dictionary::class], + self::PICK_TRAY_BY_PDFSIZE => [BooleanValue::class], + self::PID => [Dictionary::class], + self::PIECE_INFO => [Dictionary::class], + self::PL => [Dictionary::class], + self::PO => [Dictionary::class, IntegerValue::class, FloatValue::class], + self::POPUP => [Dictionary::class], + self::POSTER => [BooleanValue::class, TextStringValue::class], + self::PREDICTOR => [IntegerValue::class], + self::PRES_STEPS => [Dictionary::class], + self::PRESERVE_RB => [BooleanValue::class], + self::PREV => [IntegerValue::class, Dictionary::class], + self::PRINT => [Dictionary::class], + self::PRINT_AREA => [TextStringValue::class], + self::PRINT_CLIP => [TextStringValue::class], + self::PRINT_PAGE_RANGE => [TextStringValue::class, ArrayValue::class], + self::PRINT_SCALING => [TextStringValue::class], + self::PRINTING_ORDER => [ArrayValue::class], + self::PRIVATE => [TextStringValue::class], + self::PROC_SET => [ArrayValue::class, ReferenceValue::class], + self::PROCESS => [Dictionary::class], + self::PRODUCER => [TextStringValue::class], + self::PROP_AUTH_TIME => [IntegerValue::class], + self::PROP_AUTH_TYPE => [TextStringValue::class], + self::PROP_BUILD => [Dictionary::class], + self::PROPERTIES => [Dictionary::class], + self::PS => [TextStringValue::class, IntegerValue::class, FloatValue::class], + self::PT_DATA => [DictionaryArrayValue::class], + self::PTEX_FULLBANNER => [TextStringValue::class], + self::PV => [Dictionary::class], + self::PZ => [FloatValue::class], + self::Q => [IntegerValue::class], + self::QUAD_POINTS => [ArrayValue::class], + self::R => [StandardSecurityHandlerRevision::class, Rectangle::class, Dictionary::class, TextStringValue::class, ArrayValue::class], + self::RANGE => [ArrayValue::class], + self::RATE => [IntegerValue::class, FloatValue::class], + self::RBGROUPS => [ArrayValue::class], + self::RC => [TextStringValue::class, IntegerValue::class, FloatValue::class], + self::RD => [Rectangle::class, TextStringValue::class], + self::REASON => [TextStringValue::class], + self::REASONS => [ArrayValue::class], + self::RECIPIENTS => [ArrayValue::class, TextStringValue::class], + self::RECT => [Rectangle::class], + self::REF => [Dictionary::class], + self::REFERENCE => [DictionaryArrayValue::class], + self::REGISTRY => [TextStringValue::class], + self::RENAME => [BooleanValue::class], + self::RENDITIONS => [TextStringValue::class], + self::REPEAT => [BooleanValue::class], + self::REQUIREMENTS => [DictionaryArrayValue::class], + self::RES_FORK => [TextStringValue::class], + self::RESOURCE => [TextStringValue::class], + self::RESOURCES => [Dictionary::class], + self::RF => [Dictionary::class], + self::RH => [Dictionary::class, DictionaryArrayValue::class], + self::RI => [RenderingIntentNameValue::class, TextStringValue::class], + self::RIGHTS_WATCHMARK => [TextStringValue::class], + self::RM => [Dictionary::class], + self::RO => [TextStringValue::class], + self::ROOT => [Dictionary::class], + self::ROTATE => [IntegerValue::class], + self::ROWS => [IntegerValue::class], + self::RT => [TextStringValue::class, IntegerValue::class], + self::RV => [TextStringValue::class], + self::S => [TextStringValue::class, ArrayValue::class, NumberingStyleNameValue::class, TransitionStyleNameValue::class, BorderStyleNameValue::class, BooleanValue::class], + self::SA => [BooleanValue::class, ArrayValue::class], + self::SCHEMA => [Dictionary::class], + self::SCRIPT => [TextStringValue::class], + self::SE => [Dictionary::class], + self::SEPARATION_INFO => [Dictionary::class], + self::SET_F => [IntegerValue::class], + self::SET_FF => [TextStringValue::class], + self::SHADING => [Dictionary::class, TextStringValue::class], + self::SHADING_TYPE => [IntegerValue::class], + self::SHOW_CONTROLS => [BooleanValue::class], + self::SI => [DictionaryArrayValue::class], + self::SIG_FLAGS => [IntegerValue::class], + self::SIGNATURE => [ArrayValue::class], + self::SIZE => [IntegerValue::class, ArrayValue::class], + self::SM => [IntegerValue::class, FloatValue::class], + self::SMASK => [Dictionary::class, TextStringValue::class], + self::SMASK_IN_DATA => [IntegerValue::class], + self::SOLIDITIES => [Dictionary::class], + self::SORT => [Dictionary::class], + self::SOUND => [TextStringValue::class], + self::SOUND_ACTIONS => [IntegerValue::class], + self::SOURCE_MODIFIED => [DateValue::class], + self::SP => [Dictionary::class], + self::SPIDER_INFO => [Dictionary::class], + self::SPOT_FUNCTION => [TextStringValue::class], + self::SS => [IntegerValue::class, FloatValue::class, TextStringValue::class], + self::ST => [IntegerValue::class], + self::START => [TextStringValue::class], + self::START_RESOURCE => [TextStringValue::class], + self::STATE => [TextStringValue::class, ArrayValue::class], + self::STATE_MODEL => [TextStringValue::class], + self::STATUS => [TextStringValue::class], + self::STEM_H => [IntegerValue::class, FloatValue::class], + self::STEM_V => [IntegerValue::class, FloatValue::class, ReferenceValue::class], + self::STM_F => [TextStringValue::class], + self::STR_F => [TextStringValue::class], + self::STRUCT_PARENT => [IntegerValue::class], + self::STRUCT_PARENTS => [IntegerValue::class], + self::STRUCT_TREE_ROOT => [Dictionary::class], + self::STYLE => [Dictionary::class], + self::SUB_FILTER => [TextStringValue::class, ArrayValue::class], + self::SUBJ => [TextStringValue::class], + self::SUBJECT => [ArrayValue::class, TextStringValue::class], + self::SUBJECT_DN => [DictionaryArrayValue::class], + self::SUBTYPE => [SubtypeNameValue::class, TextStringValue::class], + self::SUPPLEMENT => [IntegerValue::class], + self::SUSPECTS => [BooleanValue::class], + self::SV => [Dictionary::class], + self::SW => [TextStringValue::class], + self::SY => [TextStringValue::class], + self::SYNCHRONOUS => [BooleanValue::class], + self::T => [Dictionary::class, TextStringValue::class, DictionaryArrayValue::class, BooleanValue::class], + self::TA => [Dictionary::class], + self::TABS => [TabsNameValue::class, TextStringValue::class], + self::TARGET => [TextStringValue::class], + self::TB => [BooleanValue::class], + self::TC => [TextStringValue::class], + self::TEMPLATE_INSTANTIATED => [TextStringValue::class], + self::TEMPLATES => [TextStringValue::class, DictionaryArrayValue::class], + self::TEXT => [TextStringValue::class], + self::TF => [TextStringValue::class], + self::THREADS => [DictionaryArrayValue::class], + self::THREE_DA => [Dictionary::class, TextStringValue::class], + self::THREE_DB => [Rectangle::class], + self::THREE_DD => [TextStringValue::class], + self::THREE_DI => [BooleanValue::class], + self::THREE_DV => [TextStringValue::class, Dictionary::class], + self::THUMB => [TextStringValue::class], + self::TILING_TYPE => [IntegerValue::class], + self::TIME_STAMP => [Dictionary::class], + self::TITLE => [TextStringValue::class], + self::TK => [BooleanValue::class], + self::TM => [TextStringValue::class, IntegerValue::class, FloatValue::class], + self::TO_UNICODE => [ReferenceValue::class], + self::TP => [IntegerValue::class], + self::TPL => [TextStringValue::class], + self::TR => [TextStringValue::class, ArrayValue::class], + self::TR2 => [TextStringValue::class], + self::TRANS => [Dictionary::class], + self::TRANSFER_FUNCTION => [TextStringValue::class], + self::TRANSFORM_METHOD => [TextStringValue::class], + self::TRANSFORM_PARAMS => [Dictionary::class], + self::TRAPPED => [TrappedNameValue::class], + self::TREF => [Dictionary::class], + self::TRIM_BOX => [Rectangle::class], + self::TRUE_TYPE_FONTS => [IntegerValue::class], + self::TT => [ArrayValue::class], + self::TU => [TextStringValue::class], + self::TYPE => [TypeNameValue::class, Dictionary::class], + self::U => [TextStringValue::class, Dictionary::class], + self::U3DPATH => [ArrayValue::class, TextStringValue::class], + self::UC => [BooleanValue::class], + self::UCR => [TextStringValue::class], + self::UCR2 => [TextStringValue::class], + self::UF => [TextStringValue::class], + self::UNIX => [TextStringValue::class], + self::UR3 => [Dictionary::class], + self::URI => [Dictionary::class, TextStringValue::class], + self::URIACTIONS => [IntegerValue::class], + self::URL => [TextStringValue::class], + self::URLS => [TextStringValue::class], + self::URLTYPE => [TextStringValue::class], + self::USAGE => [Dictionary::class], + self::USE_CMAP => [TextStringValue::class], + self::USER => [Dictionary::class], + self::USER_PROPERTIES => [BooleanValue::class], + self::USER_UNIT => [FloatValue::class], + self::V => [SecurityAlgorithm::class, FloatValue::class, BooleanValue::class, Dictionary::class, TextStringValue::class, ArrayValue::class], + self::VA => [DictionaryArrayValue::class], + self::VE => [ArrayValue::class], + self::VERIKET_CLASSIFICATION => [TextStringValue::class], + self::VERSION => [Version::class], + self::VERTICES => [ArrayValue::class], + self::VERTICES_PER_ROW => [IntegerValue::class], + self::VIEW => [Dictionary::class, ViewNameValue::class], + self::VIEW_AREA => [TextStringValue::class], + self::VIEW_CLIP => [TextStringValue::class], + self::VIEWER_PREFERENCES => [Dictionary::class], + self::VOLUME => [IntegerValue::class, FloatValue::class], + self::VP => [Dictionary::class, DictionaryArrayValue::class, ReferenceValueArray::class, ArrayValue::class], + self::W => [CrossReferenceStreamByteSizes::class, CIDFontWidths::class, IntegerValue::class, FloatValue::class, ReferenceValue::class], + self::W2 => [ArrayValue::class], + self::WC => [Dictionary::class], + self::WHITE_POINT => [ArrayValue::class], + self::WIDTH => [IntegerValue::class], + self::WIDTH2 => [IntegerValue::class], + self::WIDTHS => [ArrayValue::class], + self::WIN => [Dictionary::class], + self::WM => [TextStringValue::class], + self::WMODE => [IntegerValue::class], + self::WP => [Dictionary::class], + self::WS => [Dictionary::class], + self::X => [Dictionary::class, ArrayValue::class], + self::XFA => [TextStringValue::class, ArrayValue::class], + self::XHEIGHT => [IntegerValue::class, FloatValue::class], + self::XN => [TextStringValue::class], + self::XOBJECT => [Dictionary::class], + self::XREF_STM => [IntegerValue::class], + self::XSQUARE => [IntegerValue::class], + self::XSTEP => [IntegerValue::class, FloatValue::class], + self::XYZ => [TextStringValue::class], + self::Y => [ArrayValue::class], + self::YSQUARE => [IntegerValue::class], + self::YSTEP => [IntegerValue::class, FloatValue::class], + self::Z => [Dictionary::class], + self::ZA_DB => [ReferenceValue::class, TextStringValue::class], + self::ZOOM => [Dictionary::class], + }; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKeyInterface.php b/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKeyInterface.php new file mode 100644 index 0000000..6fe2fa5 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryKey/DictionaryKeyInterface.php @@ -0,0 +1,12 @@ +> */ + public function getValueTypes(): array; +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryKey/ExtendedDictionaryKey.php b/includes/pdfparser/Document/Dictionary/DictionaryKey/ExtendedDictionaryKey.php new file mode 100644 index 0000000..1a24471 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryKey/ExtendedDictionaryKey.php @@ -0,0 +1,26 @@ + */ + private array $nestingContext = []; + + /** @var array */ + private array $keyBuffer = []; + + /** @var array */ + private array $valueBuffer = []; + + public function __construct() { + $this->currentLevel = ''; + } + + public function incrementNesting(): self { + $this->currentLevel = (string) ($this->keyBuffer[$this->currentLevel] ?? (int) $this->currentLevel + 1); + + return $this; + } + + public function decrementNesting(): self { + array_pop($this->nestingContext); + $this->currentLevel = (string) array_key_last($this->nestingContext); + + return $this; + } + + public function setContext(DictionaryParseContext $dictionaryParseContext): self { + $this->nestingContext[$this->currentLevel] = $dictionaryParseContext; + + return $this; + } + + public function getContext(): DictionaryParseContext { + return $this->nestingContext[$this->currentLevel] ?? DictionaryParseContext::ROOT; + } + + public function getKeyBuffer(): InfiniteBuffer { + return $this->keyBuffer[$this->currentLevel] ??= new InfiniteBuffer(); + } + + public function addToKeyBuffer(string $char): self { + $this->getKeyBuffer()->addChar($char); + + return $this; + } + + public function removeFromKeyBuffer(int $nChars = 1): self { + $this->getKeyBuffer()->removeChar($nChars); + + return $this; + } + + public function getValueBuffer(): InfiniteBuffer { + return $this->valueBuffer[$this->currentLevel] ??= new InfiniteBuffer(); + } + + public function addToValueBuffer(string $char): self { + $this->getValueBuffer()->addChar($char); + + return $this; + } + + public function removeFromValueBuffer(int $nChars = 1): self { + $this->getValueBuffer()->removeChar($nChars); + + return $this; + } + + /** @return list */ + public function getKeysFromRoot(): array { + $keysFromRoot = []; + foreach ($this->keyBuffer as $keyBuffer) { + $keyBufferString = (string) $keyBuffer; + if ($keyBufferString === '') { + continue; + } + + $keysFromRoot[] = $keyBufferString; + } + + return $keysFromRoot; + } + + public function flush(): self { + ($this->valueBuffer[$this->currentLevel] ?? null)?->flush(); + ($this->keyBuffer[$this->currentLevel] ?? null)?->flush(); + + return $this; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryParser.php b/includes/pdfparser/Document/Dictionary/DictionaryParser.php new file mode 100644 index 0000000..735b1ba --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryParser.php @@ -0,0 +1,112 @@ + $startPos + * @phpstan-assert int<1, max> $nrOfBytes + * + * @throws PdfParserException + */ + public static function parse(Stream $stream, int $startPos, int $nrOfBytes): Dictionary { + $dictionaryArray = []; + $rollingCharBuffer = new RollingCharBuffer(6); + $nestingContext = (new NestingContext())->setContext(DictionaryParseContext::ROOT); + $arrayNestingLevel = 0; + foreach ($stream->chars($startPos, $nrOfBytes) as $char) { + $rollingCharBuffer->next($char); + if ($char === DelimiterCharacter::LESS_THAN_SIGN->value && $rollingCharBuffer->getPreviousCharacter() === DelimiterCharacter::LESS_THAN_SIGN->value && $rollingCharBuffer->getPreviousCharacter(2) !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) { + if ($nestingContext->getContext() === DictionaryParseContext::KEY) { + $nestingContext->removeFromKeyBuffer(); + } + + $nestingContext->setContext(DictionaryParseContext::DICTIONARY)->incrementNesting()->setContext(DictionaryParseContext::DICTIONARY); + } elseif ($char === DelimiterCharacter::LESS_THAN_SIGN->value && $nestingContext->getContext() === DictionaryParseContext::KEY) { + $nestingContext->setContext(DictionaryParseContext::VALUE); + } elseif ($char === DelimiterCharacter::GREATER_THAN_SIGN->value && $rollingCharBuffer->getPreviousCharacter() === DelimiterCharacter::GREATER_THAN_SIGN->value && $rollingCharBuffer->getPreviousCharacter(2) !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) { + $nestingContext->removeFromValueBuffer(); + self::flush($dictionaryArray, $nestingContext); + $nestingContext->decrementNesting()->flush(); + } elseif ($char === DelimiterCharacter::SOLIDUS->value && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) { + if ($nestingContext->getContext() === DictionaryParseContext::DICTIONARY) { + $nestingContext->setContext(DictionaryParseContext::KEY); + } elseif ($nestingContext->getContext() === DictionaryParseContext::VALUE) { + self::flush($dictionaryArray, $nestingContext); + $nestingContext->setContext(DictionaryParseContext::KEY); + } elseif ($nestingContext->getContext() === DictionaryParseContext::KEY || $nestingContext->getContext() === DictionaryParseContext::KEY_VALUE_SEPARATOR) { + $nestingContext->setContext(DictionaryParseContext::VALUE); + } + } elseif ($char === WhitespaceCharacter::LINE_FEED->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) { + if ($nestingContext->getContext() === DictionaryParseContext::KEY) { + $nestingContext->setContext(DictionaryParseContext::KEY_VALUE_SEPARATOR); + } elseif ($nestingContext->getContext() === DictionaryParseContext::VALUE) { + self::flush($dictionaryArray, $nestingContext); + } elseif ($nestingContext->getContext() === DictionaryParseContext::COMMENT) { + $nestingContext->setContext(DictionaryParseContext::DICTIONARY); + } + } elseif (WhitespaceCharacter::tryFrom($char) !== null && $nestingContext->getContext() === DictionaryParseContext::KEY) { + $nestingContext->setContext(DictionaryParseContext::KEY_VALUE_SEPARATOR); + } elseif ($char === DelimiterCharacter::LEFT_PARENTHESIS->value && (in_array($nestingContext->getContext(), [DictionaryParseContext::KEY, DictionaryParseContext::KEY_VALUE_SEPARATOR, DictionaryParseContext::VALUE], true))) { + $nestingContext->setContext(DictionaryParseContext::VALUE_IN_PARENTHESES); + } elseif ($char === DelimiterCharacter::RIGHT_PARENTHESIS->value && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() === DictionaryParseContext::VALUE_IN_PARENTHESES) { + $nestingContext->setContext(DictionaryParseContext::VALUE); + } elseif ($char === DelimiterCharacter::LEFT_SQUARE_BRACKET->value && (in_array($nestingContext->getContext(), [DictionaryParseContext::KEY, DictionaryParseContext::KEY_VALUE_SEPARATOR, DictionaryParseContext::VALUE, DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS], true))) { + $nestingContext->setContext(DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS); + $arrayNestingLevel++; + } elseif ($char === DelimiterCharacter::RIGHT_SQUARE_BRACKET->value && $nestingContext->getContext() === DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS) { + $arrayNestingLevel--; + if ($arrayNestingLevel === 0) { + $nestingContext->setContext(DictionaryParseContext::VALUE); + } + } elseif (trim($char) !== '' && $nestingContext->getContext() === DictionaryParseContext::KEY_VALUE_SEPARATOR) { + $nestingContext->setContext(DictionaryParseContext::VALUE); + } elseif ($char === DelimiterCharacter::PERCENT_SIGN->value && $rollingCharBuffer->getPreviousCharacter() !== LiteralStringEscapeCharacter::REVERSE_SOLIDUS->value && $nestingContext->getContext() !== DictionaryParseContext::VALUE_IN_PARENTHESES) { + $nestingContext->setContext(DictionaryParseContext::COMMENT); + } + + match ($nestingContext->getContext()) { + DictionaryParseContext::KEY => $nestingContext->addToKeyBuffer($char), + DictionaryParseContext::VALUE_IN_PARENTHESES, + DictionaryParseContext::VALUE_IN_SQUARE_BRACKETS, + DictionaryParseContext::VALUE => $nestingContext->addToValueBuffer($char), + default => null, + }; + } + + return DictionaryFactory::fromArray($dictionaryArray); + } + + /** @param array $dictionaryArray */ + private static function flush(array &$dictionaryArray, NestingContext $nestingContext): void { + if ($nestingContext->getValueBuffer()->isEmpty() || $nestingContext->getKeyBuffer()->isEmpty()) { + return; + } + + $dictionaryArrayPointer = &$dictionaryArray; + $keys = $nestingContext->getKeysFromRoot(); + foreach ($keys as $index => $key) { + if ($key === (string) $nestingContext->getKeyBuffer() && $index === array_key_last($keys)) { + break; + } + + /** @phpstan-ignore offsetAccess.nonOffsetAccessible */ + $dictionaryArrayPointer = &$dictionaryArrayPointer[trim($key)]; + } + + /** @phpstan-ignore offsetAccess.nonOffsetAccessible */ + $dictionaryArrayPointer[(string) $nestingContext->getKeyBuffer()] = trim((string) $nestingContext->getValueBuffer()); + $nestingContext->flush(); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/ArrayValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/ArrayValue.php new file mode 100644 index 0000000..00914ac --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/ArrayValue.php @@ -0,0 +1,69 @@ + $value */ + public function __construct( + public readonly array $value + ) { + } + + #[Override] + /** @throws PdfParserException */ + public static function fromValue(string $valueString): null|self|ReferenceValueArray { + $valueString = trim($valueString); + if (!str_starts_with($valueString, '[') || !str_ends_with($valueString, ']')) { + return null; + } + + $valueString = preg_replace('/(<[^>]*>)(?=<[^>]*>)/', '$1 $2', $valueString) + ?? throw new RuntimeException('An error occurred while sanitizing array value'); + $valueString = str_replace(['/', "\n"], [' /', ' '], rtrim(ltrim($valueString, '[ '), ' ]')); + $valueString = preg_replace('/\s+/', ' ', $valueString) + ?? throw new RuntimeException('An error occurred while removing duplicate spaces from array value'); + $values = explode(' ', $valueString); + if (count($values) % 3 === 0 && array_key_exists(2, $values) && $values[2] === 'R') { + return ReferenceValueArray::fromValue($valueString); + } + + $array = []; + foreach ($values as $value) { + if (str_starts_with($value, '[') && str_ends_with($value, ']')) { + $array[] = self::fromValue($value); + } elseif ((string) (int) $value === $value) { + $array[] = (int) $value; + } elseif ($value !== '') { + $array[] = $value; + } + } + + return new self($array); + } + + public function toString(): string { + $string = ''; + foreach ($this->value as $value) { + $string .= ' ' . match (true) { + is_int($value), + is_float($value), + is_string($value) => $value, + $value instanceof ArrayValue => $value->toString(), + $value instanceof ReferenceValueArray => implode(' ', array_map(fn (ReferenceValue $referenceValue) => $referenceValue->objectNumber . ' R', $value->referenceValues)), + default => throw new ParseFailureException('Unsupported array value type: ' . gettype($value)), + }; + } + + return '[' . trim($string) . ']'; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CIDFontWidths.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CIDFontWidths.php new file mode 100644 index 0000000..0dd3da0 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CIDFontWidths.php @@ -0,0 +1,65 @@ + */ + private readonly array $widths; + + /** @no-named-arguments */ + public function __construct( + ConsecutiveCIDWidth|RangeCIDWidth ...$widths, + ) { + $this->widths = $widths; + } + + public function getWidthForCharacter(int $characterCode): ?float { + foreach ($this->widths as $widthItem) { + if (($widthForCharacterCode = $widthItem->getWidthForCharacterCode($characterCode)) !== null) { + return $widthForCharacterCode; + } + } + + return null; + } + + #[Override] + public static function fromValue(string $valueString): ?self { + $valueString = str_replace("\n", ' ', $valueString); + if (preg_match_all('/(?[0-9]+)\s*(?[0-9]+\s*[0-9.]+|\[[0-9. ]+\])/', $valueString, $matches, PREG_SET_ORDER) <= 0) { + return null; + } + + $widths = []; + foreach ($matches as $match) { + if ((string) ($startingCID = (int) $match['startingCID']) !== $match['startingCID']) { + return null; + } + + if (str_starts_with($match['CIDS'], '[') && str_ends_with($match['CIDS'], ']')) { + $widths[] = new ConsecutiveCIDWidth($startingCID, array_map('floatval', explode(' ', rtrim(ltrim($match['CIDS'], '['), ']')))); + + continue; + } + + $arguments = explode(' ', $match['CIDS']); + if (count($arguments) !== 2) { + return null; + } + + if ((string)($endCID = (int) $arguments[0]) !== $arguments[0] || (string)($width = (float) $arguments[1]) !== $arguments[1]) { + return null; + } + + $widths[] = new RangeCIDWidth($startingCID, $endCID, $width); + } + + return new self(... $widths); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CrossReferenceStreamByteSizes.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CrossReferenceStreamByteSizes.php new file mode 100644 index 0000000..c2af98b --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/CrossReferenceStreamByteSizes.php @@ -0,0 +1,50 @@ + + */ + public function getTotalLengthInBytes(): int { + $totalLength = $this->lengthRecord1InBytes + $this->lengthRecord2InBytes + $this->lengthRecord3InBytes; + if ($totalLength < 1) { + throw new RuntimeException(sprintf('Total length should not be less than 1, got %d', $totalLength)); + } + + return $totalLength; + } + + #[Override] + public static function fromValue(string $valueString): ?self { + if (!str_starts_with($valueString, '[') || !str_ends_with($valueString, ']')) { + return null; + } + + $values = explode(' ', trim(rtrim(ltrim($valueString, '['), ']'))); + if (count($values) !== 3) { + return null; + } + + if ((string) (int) trim($values[0]) !== trim($values[0]) + || (string) (int) trim($values[1]) !== trim($values[1]) + || (string) (int) trim($values[2]) !== trim($values[2])) { + return null; + } + + return new self((int) $values[0], (int) $values[1], (int) $values[2]); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DictionaryArrayValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DictionaryArrayValue.php new file mode 100644 index 0000000..5700928 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DictionaryArrayValue.php @@ -0,0 +1,55 @@ + */ + public readonly array $dictionaries; + + /** @no-named-arguments */ + public function __construct( + Dictionary... $dictionaries, + ) { + $this->dictionaries = $dictionaries; + } + + #[Override] + /** @throws PdfParserException */ + public static function fromValue(string $valueString): ?self { + $valueStringWithoutSpaces = str_replace([' ', "\r", "\n"], '', $valueString); + if ((str_starts_with($valueStringWithoutSpaces, '[<<') === false && str_starts_with($valueStringWithoutSpaces, '[null') === false) + || (str_ends_with($valueStringWithoutSpaces, '>>]') === false && str_ends_with($valueStringWithoutSpaces, 'null]') === false)) { + return null; + } + + $dictionaryEntries = []; + $valueString = preg_replace('/(<<[^>]*>>)(?=<<[^>]*>>)/', '$1 $2', $valueString) + ?? throw new RuntimeException('An error occurred while sanitizing dictionary array value'); + foreach (explode('>> <<', substr($valueString, 3, -3)) as $dictionaryValueString) { + $dictionaryEntries[] = $dictionaryValueString === '' + ? new Dictionary() + : DictionaryParser::parse($memoryStream = new InMemoryStream('<<' . $dictionaryValueString . '>>'), 0, $memoryStream->getSizeInBytes()); + } + + return new self(... $dictionaryEntries); + } + + public function toSingleDictionary(): ?Dictionary { + $dictionaryEntries = []; + foreach ($this->dictionaries as $dictionary) { + foreach ($dictionary->dictionaryEntries as $dictionaryEntry) { + $dictionaryEntries[] = $dictionaryEntry; + } + } + + return new Dictionary(... $dictionaryEntries); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DifferencesArrayValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DifferencesArrayValue.php new file mode 100644 index 0000000..567f5cb --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/DifferencesArrayValue.php @@ -0,0 +1,57 @@ + $differenceRanges */ + public function __construct( + private readonly array $differenceRanges, + ) { + } + + #[Override] + public static function fromValue(string $valueString): ?self { + if (($arrayValue = ArrayValue::fromValue($valueString)) === null || $arrayValue instanceof ReferenceValueArray) { + return null; + } + + $startIndex = null; + $characters = $differenceRanges = []; + foreach ($arrayValue->value as $arrayValueItem) { + if (is_int($arrayValueItem)) { + if ($startIndex !== null) { + $differenceRanges[] = new DifferenceRange($startIndex, $characters); + $characters = []; + } + + $startIndex = $arrayValueItem; + } elseif (is_string($arrayValueItem)) { + $characters[] = AGlyphList::tryFrom(ltrim($arrayValueItem, '/')); + } else { + return null; + } + } + + if ($startIndex !== null) { + $differenceRanges[] = new DifferenceRange($startIndex, $characters); + } + + return new self($differenceRanges); + } + + public function getGlyph(int $int): ?AGlyphList { + foreach ($this->differenceRanges as $differenceRange) { + if ($differenceRange->contains($int)) { + return $differenceRange->getGlyph($int); + } + } + + return null; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/ConsecutiveCIDWidth.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/ConsecutiveCIDWidth.php new file mode 100644 index 0000000..2397b8e --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/ConsecutiveCIDWidth.php @@ -0,0 +1,20 @@ + $widths */ + public function __construct( + public readonly int $cidStart, + public readonly array $widths, + ) { + } + + public function getWidthForCharacterCode(int $characterCode): ?float { + if (array_key_exists($characterCode - $this->cidStart, $this->widths) === false) { + return null; + } + + return $this->widths[$characterCode - $this->cidStart] / 1000; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/DifferenceRange.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/DifferenceRange.php new file mode 100644 index 0000000..6dfca8b --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/DifferenceRange.php @@ -0,0 +1,33 @@ + $characters */ + public function __construct( + private readonly int $firstIndex, + private readonly array $characters, + ) { + } + + public function contains(int $index): bool { + return $index >= $this->firstIndex + && $index < $this->firstIndex + count($this->characters); + } + + public function getGlyph(int $index): ?AGlyphList { + if (!$this->contains($index)) { + throw new InvalidArgumentException('This difference range does not contain index ' . $index); + } + + if (!array_key_exists($index - $this->firstIndex, $this->characters)) { + throw new RuntimeException('Expected glyph to be present, but it was not'); + } + + return $this->characters[$index - $this->firstIndex]; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/RangeCIDWidth.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/RangeCIDWidth.php new file mode 100644 index 0000000..b11549c --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Array/Item/RangeCIDWidth.php @@ -0,0 +1,20 @@ +cidStart || $characterCode > $this->cidEnd) { + return null; + } + + return $this->width / 1000; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Boolean/BooleanValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Boolean/BooleanValue.php new file mode 100644 index 0000000..7e44699 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Boolean/BooleanValue.php @@ -0,0 +1,27 @@ +')) { + $valueString = substr($valueString, 1, -1); + if (!ctype_xdigit($valueString) || strlen($valueString) % 2 !== 0) { + throw new InvalidArgumentException(sprintf('String "%s" is not hexadecimal', substr($valueString, 0, 10))); + } + + $valueString = hex2bin($valueString); + if ($valueString === false) { + return null; + } + } + + if (str_starts_with($valueString, '(') && str_ends_with($valueString, ')')) { + $valueString = preg_replace_callback( + '/\\\\([0-7]{3})/', + fn (array $matches) => mb_chr((int) octdec($matches[1])), + substr($valueString, 1, -1) + ) ?? throw new ParseFailureException(); + } + + if (!str_starts_with($valueString, 'D:')) { + $valueString = mb_convert_encoding($valueString, 'UTF-8', 'UTF-16'); + if ($valueString === false || !str_starts_with($valueString, 'D:')) { + return null; + } + } + + try { + $parsedDate = DateTimeImmutable::createFromFormat( + preg_match('/^D:\d{14}$/', $valueString) === 1 ? '\D\:YmdHis' : '\D\:YmdHisP', + str_replace("'", '', $valueString) + ); + } catch (ValueError) { + return null; + } + + if ($parsedDate === false) { + return null; + } + + return new self($parsedDate); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/DictionaryValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/DictionaryValue.php new file mode 100644 index 0000000..59bc3b5 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/DictionaryValue.php @@ -0,0 +1,8 @@ + Components::Gray, + self::DeviceRGB => Components::RGB, + self::DeviceCMYK => Components::CMYK, + }; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/DirectionNameValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/DirectionNameValue.php new file mode 100644 index 0000000..076432d --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/DirectionNameValue.php @@ -0,0 +1,8 @@ + (new Identity0())->getToUnicodeCMap()->textToUnicode($characterGroup), + self::WinAnsiEncoding => WinAnsi::textToUnicode($characterGroup), + self::MacRomanEncoding => MacRoman::textToUnicode($characterGroup), + default => throw new ParseFailureException(sprintf('Unsupported encoding %s', $this->name)), + }; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/EventNameValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/EventNameValue.php new file mode 100644 index 0000000..02dfee1 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/EventNameValue.php @@ -0,0 +1,9 @@ +getSubDictionary($document, DictionaryKey::DECODE_PARMS); + + return match($this) { + self::JPX_DECODE, + self::JBIG2_DECODE, + self::DCT_DECODE => $content, // Don't decode JPEG content + self::FLATE_DECODE => FlateDecode::decodeBinary( + $content, + $decodeParams !== null && ($predictorValue = LZWFlatePredictorValue::tryFrom((int) $decodeParams->getValueForKey(DictionaryKey::PREDICTOR, IntegerValue::class)?->value)) !== null + ? $predictorValue + : LZWFlatePredictorValue::None, + $decodeParams?->getValueForKey(DictionaryKey::COLUMNS, IntegerValue::class)->value ?? 1 + ), + self::CCITT_FAX_DECODE => CCITTFaxDecode::addHeaderAndIFD( + $content, + $decodeParams?->getValueForKey(DictionaryKey::COLUMNS, IntegerValue::class)->value + ?? throw new ParseFailureException('Missing columns'), + $decodeParams->getValueForKey(DictionaryKey::ROWS, IntegerValue::class)->value + ?? $dictionary->getValueForKey(DictionaryKey::HEIGHT, IntegerValue::class)->value + ?? throw new ParseFailureException('Missing rows'), + $decodeParams->getValueForKey(DictionaryKey::K, IntegerValue::class)->value + ?? throw new ParseFailureException('Missing K'), + ), + self::ASCII_85_DECODE => ASCII85Decode::decodeBinary($content), + default => throw new ParseFailureException(sprintf('Content "%.100s..." cannot be decoded for filter "%s"', $content, $this->name)) + }; + } + + public function getImageType(): ?ImageType { + return match ($this) { + self::LZW_DECODE => ImageType::TIFF, + self::FLATE_DECODE => ImageType::PNG, + self::RUN_LENGTH_DECODE => ImageType::RAW, + self::CCITT_FAX_DECODE => ImageType::TIFF_FAX, + self::DCT_DECODE => ImageType::JPEG, + self::JPX_DECODE => ImageType::JPEG2000, + self::JBIG2_DECODE => ImageType::JBIG2, + default => null, + }; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/IntentNameValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/IntentNameValue.php new file mode 100644 index 0000000..d61fbf0 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/IntentNameValue.php @@ -0,0 +1,9 @@ + */ + public function getDecoratorFQN(): string { + return match($this) { + TypeNameValue::CATALOG => Catalog::class, + TypeNameValue::EMBEDDED_FILE => EmbeddedFile::class, + TypeNameValue::FILE_SPEC => FileSpecification::class, + TypeNameValue::FONT => Font::class, + TypeNameValue::PAGE => Page::class, + TypeNameValue::PAGES => Pages::class, + TypeNameValue::X_OBJECT => XObject::class, + default => GenericObject::class, + }; + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/ViewNameValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/ViewNameValue.php new file mode 100644 index 0000000..e904398 --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/Name/ViewNameValue.php @@ -0,0 +1,9 @@ + */ + public readonly array $referenceValues; + + /** @no-named-arguments */ + public function __construct(ReferenceValue ...$referenceValues) { + $this->referenceValues = $referenceValues; + } + + #[Override] + /** @throws ParseFailureException */ + public static function fromValue(string $valueString): ?self { + if (!str_starts_with($valueString, '[') || !str_ends_with($valueString, ']')) { + return null; + } + + $valueString = preg_replace('/\s+/', ' ', $valueString) + ?? throw new ParseFailureException('An unexpected error occurred while sanitizing reference value array'); + $valueString = trim(rtrim(ltrim($valueString, '['), ']')); + if (str_starts_with($valueString, '<<') && str_ends_with($valueString, '>>')) { + return null; + } + + if ($valueString === '') { + return new self(); + } + + $referenceParts = explode(' ', $valueString); + $nrOfReferenceParts = count($referenceParts); + if ($nrOfReferenceParts % 3 !== 0) { + return null; + } + + $referenceValues = []; + for ($i = 0; $i < $nrOfReferenceParts; $i += 3) { + /** @phpstan-ignore offsetAccess.notFound, offsetAccess.notFound, offsetAccess.notFound */ + $string = $referenceParts[$i] . ' ' . $referenceParts[$i + 1] . ' ' . $referenceParts[$i + 2]; + + $referenceValues[] = ReferenceValue::fromValue($string) + ?? throw new ParseFailureException(sprintf('Could not parse reference value "%s" at index %d in "%s"', $string, $i, $valueString)); + } + + return new self(... $referenceValues); + } +} diff --git a/includes/pdfparser/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php b/includes/pdfparser/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php new file mode 100644 index 0000000..a8b2a5f --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php @@ -0,0 +1,57 @@ +textStringValue, '(') && str_ends_with($this->textStringValue, ')')) { + return preg_replace_callback( + '/\\\\([0-7]{3})/', + fn (array $matches) => mb_chr((int) octdec($matches[1])), + str_replace(['\(', '\)', '\n', '\r'], ['(', ')', "\n", "\r"], substr($this->textStringValue, 1, -1)) + ) ?? throw new ParseFailureException(); + } + + if (str_starts_with($this->textStringValue, '<') && str_ends_with($this->textStringValue, '>')) { + $string = substr($this->textStringValue, 1, -1); + if (str_starts_with($string, 'FEFF')) { + $string = substr($string, 4); + } + + return implode( + '', + array_map( + fn (string $character) => mb_chr((int) hexdec($character)), + str_split($string, 4) + ) + ); + } + + if (str_starts_with($this->textStringValue, '/')) { + return preg_replace_callback( + '/#([0-9A-F]{2})/', + fn (array $matches) => chr((int) hexdec($matches[1])), + $this->textStringValue, + ) ?? throw new ParseFailureException(); + } + + throw new ParseFailureException(sprintf('Unrecognized format %s', $this->textStringValue)); + } + + #[Override] + public static function fromValue(string $valueString): self { + return new self($valueString); + } +} diff --git a/includes/pdfparser/Document/Dictionary/Normalization/NameValueNormalizer.php b/includes/pdfparser/Document/Dictionary/Normalization/NameValueNormalizer.php new file mode 100644 index 0000000..2dfe7eb --- /dev/null +++ b/includes/pdfparser/Document/Dictionary/Normalization/NameValueNormalizer.php @@ -0,0 +1,25 @@ + */ + private readonly array $pages; + + /** @var array */ + private array $objectCache = []; + + public function __construct( + public readonly Stream $stream, + public readonly Version $version, + public readonly CrossReferenceSource $crossReferenceSource, + public ?StandardSecurity $security, + ) { + if ($this->getEncryptDictionary() !== null) { + throw new NotImplementedException('Encrypted documents are not supported yet'); + } + } + + /** @throws PdfParserException */ + public function getInformationDictionary(): ?InformationDictionary { + $infoReference = $this->crossReferenceSource->getReferenceForKey(DictionaryKey::INFO); + if ($infoReference === null) { + return null; + } + + return $this->getObject($infoReference->objectNumber, InformationDictionary::class); + } + + public function getEncryptDictionary(): ?EncryptDictionary { + $infoReference = $this->crossReferenceSource->getReferenceForKey(DictionaryKey::ENCRYPT); + if ($infoReference === null) { + return null; + } + + return $this->getObject($infoReference->objectNumber, EncryptDictionary::class); + } + + /** @throws PdfParserException */ + public function getCatalog(): Catalog { + $rootReference = $this->crossReferenceSource->getReferenceForKey(DictionaryKey::ROOT) + ?? throw new ParseFailureException('Unable to locate root for document.'); + $catalog = $this->getObject($rootReference->objectNumber, Catalog::class) + ?? throw new ParseFailureException(sprintf('Document references object %d as root, but object couln\'t be located', $rootReference->objectNumber)); + if (!$catalog instanceof Catalog) { + throw new RuntimeException('Catalog should be a catalog item'); + } + + return $catalog; + } + + /** + * @template T of DecoratedObject + * @param class-string|null $expectedDecoratorFQN + * @throws PdfParserException + * @return ($expectedDecoratorFQN is null ? list : list) + */ + public function getObjectsByDictionaryKey(Dictionary $dictionary, DictionaryKey $dictionaryKey, ?string $expectedDecoratorFQN = null): array { + $dictionaryValueType = $dictionary->getTypeForKey($dictionaryKey); + if ($dictionaryValueType === ReferenceValue::class) { + return [$this->getObject($dictionary->getValueForKey($dictionaryKey, ReferenceValue::class)->objectNumber ?? throw new ParseFailureException(), $expectedDecoratorFQN) ?? throw new ParseFailureException()]; + } elseif ($dictionaryValueType === ReferenceValueArray::class) { + return array_map( + fn (ReferenceValue $referenceValue) => $this->getObject($referenceValue->objectNumber, $expectedDecoratorFQN) ?? throw new ParseFailureException(), + $dictionary->getValueForKey($dictionaryKey, ReferenceValueArray::class)->referenceValues ?? throw new ParseFailureException(), + ); + } + + throw new ParseFailureException(sprintf('Dictionary value with key "%s" is of type "%s", expected referencevalue(array)', $dictionaryKey->name, $dictionaryValueType ?? 'null')); + } + + /** + * @template T of DecoratedObject + * @param class-string|null $expectedDecoratorFQN + * @throws PdfParserException + * @return ($expectedDecoratorFQN is null ? DecoratedObject : T) + */ + public function getObject(int $objectNumber, ?string $expectedDecoratorFQN = null): ?DecoratedObject { + if (array_key_exists($objectNumber, $this->objectCache)) { + return $this->objectCache[$objectNumber]; + } + + $crossReferenceEntry = $this->crossReferenceSource->getCrossReferenceEntry($objectNumber); + if ($crossReferenceEntry === null) { + return null; + } + + if ($crossReferenceEntry instanceof CrossReferenceEntryCompressed) { + $parentObject = $this->getObject($crossReferenceEntry->storedInStreamWithObjectNumber) + ?? throw new RuntimeException(sprintf('Parent object for %d with number %d doesn\'t exist', $objectNumber, $crossReferenceEntry->storedInStreamWithObjectNumber)); + + if (!$parentObject->objectItem instanceof UncompressedObject) { + throw new RuntimeException('Parents for stream items shouldn\'t be stream items themselves'); + } + + $objectItem = $parentObject->objectItem->getCompressedObject($objectNumber, $this); + } else { + $objectItem = UncompressedObjectParser::parseObject($crossReferenceEntry, $objectNumber, $this->stream); + } + + return $this->objectCache[$objectNumber] = DecoratedObjectFactory::forItem($objectItem, $this, $expectedDecoratorFQN); + } + + /** @throws PdfParserException */ + public function getPage(int $pageNumber): ?Page { + return $this->getPages()[$pageNumber - 1] ?? null; + } + + /** @throws PdfParserException */ + public function getNumberOfPages(): int { + return count($this->getPages()); + } + + /** + * @throws PdfParserException + * @return list + */ + public function getPages(): array { + return $this->pages ??= $this->getCatalog() + ->getPagesRoot() + ->getPageItems(); + } + + /** + * @param ?string $pageSeparator an optional string to put between text of different pages + * @throws PdfParserException + */ + public function getText(?string $pageSeparator = null): string { + $text = ''; + foreach ($this->getPages() as $page) { + $text .= ($pageSeparator !== null ? $pageSeparator : '') + . $page->getText(); + } + + return $text; + } + + /** + * @throws PdfParserException + * @return list + */ + public function getImages(): array { + $images = []; + foreach ($this->getPages() as $page) { + $images = [... $images, ...$page->getImages()]; + } + + return $images; + } +} diff --git a/includes/pdfparser/Document/Encoding/Encoding.php b/includes/pdfparser/Document/Encoding/Encoding.php new file mode 100644 index 0000000..42a25f9 --- /dev/null +++ b/includes/pdfparser/Document/Encoding/Encoding.php @@ -0,0 +1,8 @@ +')) { + $string = substr($string, 2, -2); + } + + $string = preg_replace('/\s+/', '', $string) + ?? throw new RuntimeException('An unexpected error occurred while sanitizing ASCII85 string'); + $length = strlen($string); + $decoded = $block = ''; + for ($i = 0; $i < $length; ++$i) { + $char = $string[$i]; + if ($char === 'z') { + $decoded .= "\0\0\0\0"; + continue; + } + + $block .= $char; + if (strlen($block) === 5) { + $value = 0; + for ($j = 0; $j < 5; ++$j) { + $value = $value * 85 + (ord($block[$j]) - 33); + } + + $decoded .= pack('N', $value); + $block = ''; + } + } + + if ($block !== '') { + $padding = 5 - strlen($block); + $block = str_pad($block, 5, 'u'); + $value = 0; + for ($i = 0; $i < 5; ++$i) { + $value = $value * 85 + (ord($block[$i]) - 33); + } + + $binaryData = pack('N', $value); + $decoded .= substr($binaryData, 0, 4 - $padding); + } + + return $decoded; + } +} diff --git a/includes/pdfparser/Document/Filter/Decode/CCITTFaxDecode.php b/includes/pdfparser/Document/Filter/Decode/CCITTFaxDecode.php new file mode 100644 index 0000000..c9df473 --- /dev/null +++ b/includes/pdfparser/Document/Filter/Decode/CCITTFaxDecode.php @@ -0,0 +1,52 @@ += 0 ? 3 : 4), + self::createIfdEntry(TiffTag::PhotometricInterpretation, 3, 1, 0), + self::createIfdEntry(TiffTag::RowsPerStrip, 3, 1, $rows), + self::createIfdEntry(TiffTag::StripByteCounts, 4, 1, strlen($rawData)), + ]; + + $ifdEntries[] = self::createIfdEntry(TiffTag::StripOffsets, 4, 1, 8 + 2 + (12 * (count($ifdEntries) + 1)) + 4); + + return self::BYTE_ORDER_LITTLE_ENDIAN + . pack("v", self::MAGIC_NUMBER_TIFF) + . pack("V", self::IFD_OFFSET_IN_BYTES) + . pack("v", count($ifdEntries)) + . implode('', $ifdEntries) + . pack("V", self::END_OF_IFD_OFFSET) + . $rawData; + } + + /** + * @param int<3,4> $type + * @param int<1, max> $count + */ + private static function createIfdEntry(TiffTag $tiffTag, int $type, int $count, int $value): string { + $entry = pack("v", $tiffTag->value) . pack("v", $type) . pack("V", $count); + + if ($type === 3 && $count === 1) { + return $entry . pack("v", $value) . "\x00\x00"; + } elseif ($type === 4 || ($type === 3 && $count > 1)) { + return $entry . pack("V", $value); + } else { + throw new ParseFailureException("Unsupported IFD entry type or count."); + } + } +} diff --git a/includes/pdfparser/Document/Filter/Decode/FlateDecode.php b/includes/pdfparser/Document/Filter/Decode/FlateDecode.php new file mode 100644 index 0000000..ff1ae06 --- /dev/null +++ b/includes/pdfparser/Document/Filter/Decode/FlateDecode.php @@ -0,0 +1,87 @@ +value); + } + + $hexTable = array_map(fn (string $row) => str_split($row, 2), str_split(bin2hex($decodedValue), ($columns + 1) * 2)); + $decodedValue = ''; + foreach ($hexTable as $rowIndex => $row) { + if (!is_array($row) || !array_is_list($row) || count($row) < 2) { + throw new RuntimeException(sprintf('Expected at least 2 items per row, got %d', count($row))); + } + + if (!is_int($algorithmNumber = hexdec($row[0]))) { + throw new ParseFailureException(sprintf('Expected algorithm number to be an integer, got %s', $algorithmNumber)); + } + + $rowAlgorithm = PNGPredictorAlgorithm::tryFrom($algorithmNumber) + ?? throw new ParseFailureException(sprintf('Unrecognized row algorithm %d', $algorithmNumber)); + if ($rowAlgorithm === PNGPredictorAlgorithm::None) { + $decodedValue .= implode('', array_slice($row, 1)); + + continue; + } + + if ($rowAlgorithm === PNGPredictorAlgorithm::Up) { + if ($rowIndex === 0) { + $decodedValue .= implode('', array_slice($row, 1)); + + continue; + } + + foreach ($row as $columnIndex => $columnValue) { + /** @phpstan-ignore offsetAccess.notFound, offsetAccess.notFound */ + $hexTable[$rowIndex][$columnIndex] = str_pad(dechex((hexdec($columnValue) + hexdec($hexTable[$rowIndex - 1][$columnIndex])) % 256), 2, '0', STR_PAD_LEFT); + } + + $decodedValue .= implode('', array_slice($hexTable[$rowIndex], 1)); + + continue; + } + } + + if (($decodedValue = hex2bin($decodedValue)) === false) { + throw new ParseFailureException('Unable to hex2bin value "' . substr(trim($value), 0, 30) . '..."'); + } + + return $decodedValue; + } +} diff --git a/includes/pdfparser/Document/Filter/Decode/LZWFlatePredictorValue.php b/includes/pdfparser/Document/Filter/Decode/LZWFlatePredictorValue.php new file mode 100644 index 0000000..b39da93 --- /dev/null +++ b/includes/pdfparser/Document/Filter/Decode/LZWFlatePredictorValue.php @@ -0,0 +1,25 @@ + $widths */ + public function __construct( + public readonly int $firstChar, + public readonly array $widths, + ) { + } + + public function getWidthForCharacter(int $characterCode): ?float { + return $this->widths[$characterCode - $this->firstChar] ?? null; + } +} diff --git a/includes/pdfparser/Document/Generic/Character/DelimiterCharacter.php b/includes/pdfparser/Document/Generic/Character/DelimiterCharacter.php new file mode 100644 index 0000000..76f4d67 --- /dev/null +++ b/includes/pdfparser/Document/Generic/Character/DelimiterCharacter.php @@ -0,0 +1,39 @@ +'; + case LEFT_SQUARE_BRACKET = '['; + case RIGHT_SQUARE_BRACKET = ']'; + case LEFT_CURLY_BRACKET = '{'; + case RIGHT_CURLY_BRACKET = '}'; + case SOLIDUS = '/'; + + /** + * Any occurrence of the PERCENT SIGN outside a string or stream introduces a comment. The comment + * consists of all characters after the PERCENT SIGN and up to but not including the end of the line, including + * regular, delimiter, SPACE (20h), and HORIZONTAL TAB characters (09h). A conforming reader shall ignore + * comments, and treat them as single white-space characters. That is, a comment separates the token preceding + * it from the one following it. + * + * Comments (other than the %PDF–n.m and %%EOF comments described in 7.5, "File Structure") have no + * semantics. They are not necessarily preserved by applications that edit PDF files + */ + case PERCENT_SIGN = '%'; +} diff --git a/includes/pdfparser/Document/Generic/Character/LiteralStringEscapeCharacter.php b/includes/pdfparser/Document/Generic/Character/LiteralStringEscapeCharacter.php new file mode 100644 index 0000000..ee0fe6f --- /dev/null +++ b/includes/pdfparser/Document/Generic/Character/LiteralStringEscapeCharacter.php @@ -0,0 +1,65 @@ + "\n", + self::CARRIAGE_RETURN => "\r", + self::HORIZONTAL_TAB => "\t", + self::BACKSPACE => "\x08", + self::FORM_FEED => "\x0C", + self::LEFT_PARENTHESIS => "(", + self::RIGHT_PARENTHESIS => ")", + self::REVERSE_SOLIDUS => "\\", + }; + } + + /** @return array{0: list, 1: list} */ + private static function getReplacementSet(): array { + $find = $replace = []; + foreach (self::cases() as $case) { + $find[] = $case->value; + $replace[] = $case->getActualCharacter(); + } + + return [$find, $replace]; + } + + public static function unescapeCharacters(string $string): string { + $string = str_replace("\\\n", '', $string); // Example 2, 7.3.4.2 newlines preceded by reverse solidus should be handled like single lines + + [$find, $replace] = LiteralStringEscapeCharacter::getReplacementSet(); + + return preg_replace_callback( + '/\\\\([0-7]{1,3})/', + static function (array $matches) { + $decimal = octdec($matches[1]); + if (!is_int($decimal) || $decimal < 0 || $decimal > 255) { + throw new ParseFailureException(sprintf('Invalid octal value "%s"', $matches[1])); + } + + return mb_chr($decimal); + }, + str_replace($find, $replace, $string) + ) ?? throw new ParseFailureException(); + } +} diff --git a/includes/pdfparser/Document/Generic/Character/WhitespaceCharacter.php b/includes/pdfparser/Document/Generic/Character/WhitespaceCharacter.php new file mode 100644 index 0000000..74b209c --- /dev/null +++ b/includes/pdfparser/Document/Generic/Character/WhitespaceCharacter.php @@ -0,0 +1,28 @@ +value); + } +} diff --git a/includes/pdfparser/Document/Generic/Parsing/InfiniteBuffer.php b/includes/pdfparser/Document/Generic/Parsing/InfiniteBuffer.php new file mode 100644 index 0000000..3220203 --- /dev/null +++ b/includes/pdfparser/Document/Generic/Parsing/InfiniteBuffer.php @@ -0,0 +1,48 @@ +buffer .= $char; + + return $this; + } + + public function flush(): self { + return $this->setValue(''); + } + + #[Override] + public function __toString(): string { + return $this->buffer; + } + + public function getLength(): int { + return strlen($this->buffer); + } + + public function isEmpty(): bool { + return $this->getLength() === 0; + } + + public function setValue(string $buffer): self { + $this->buffer = $buffer; + + return $this; + } + + public function removeChar(int $nChars): self { + if ($this->buffer !== '') { + $this->buffer = substr($this->buffer, 0, -$nChars); + } + + return $this; + } +} diff --git a/includes/pdfparser/Document/Generic/Parsing/RollingCharBuffer.php b/includes/pdfparser/Document/Generic/Parsing/RollingCharBuffer.php new file mode 100644 index 0000000..501434d --- /dev/null +++ b/includes/pdfparser/Document/Generic/Parsing/RollingCharBuffer.php @@ -0,0 +1,94 @@ + $length */ + private int $length; + + /** @var int<0, max> */ + private int $currentIndex = 0; + + /** + * Rolling buffer, where the modulo of the index is used. Fe: when writing 'a', 'b', 'c', 'd', 'e', 'f' to a buffer of length 3: + * ['a'] + * ['a', 'b'] + * ['a', 'b', 'c'] + * ['d', 'b', 'c'] + * ['d', 'e', 'c'] + * ['d', 'e', 'f'] + * + * @var array, string> + */ + private array $buffer = []; + + /** @phpstan-assert int<1, max> $length */ + public function __construct(int $length) { + if ($length < 1) { + throw new InvalidArgumentException(sprintf('A negative or zero buffer length doesn\'t make sense, %d provided', $length)); + } + + $this->length = $length; + } + + public function next(string $char): self { + $this->currentIndex++; + $this->buffer[$this->currentIndex % $this->length] = $char; + + return $this; + } + + /** @throws InvalidArgumentException */ + public function getPreviousCharacter(int $nAgo = 1): ?string { + if ($nAgo >= $this->length) { + throw new InvalidArgumentException('Buffer length of "' . $this->length . '" configured, but character "-' . $nAgo . '" requested'); + } + + return $this->buffer[($this->currentIndex - $nAgo) % $this->length] ?? null; + } + + /** + * @phpstan-assert non-empty-string $string + * + * @throws InvalidArgumentException + */ + public function seenString(string $string): bool { + $strlen = strlen($string); + if ($strlen === 0) { + throw new InvalidArgumentException('Cannot assert if non empty string has been encountered'); + } + + if ($strlen > $this->length) { + throw new InvalidArgumentException(sprintf('Buffer length of %d configured, but value with length %d requested', $this->length, strlen($string))); + } + + foreach (str_split($string) as $index => $char) { + $previousChar = $this->getPreviousCharacter($strlen - $index - 1); + if ($previousChar !== $char) { + return false; + } + } + + return true; + } + + /** @throws InvalidArgumentException */ + public function seenReverseString(string $string): bool { + if (strlen($string) > $this->length) { + throw new InvalidArgumentException(sprintf('Buffer length of %d configured, but enum with length %d requested', $this->length, strlen($string))); + } + + foreach (str_split($string) as $index => $char) { + $previousChar = $this->getPreviousCharacter($index); + if ($previousChar !== $char) { + return false; + } + } + + return true; + } +} diff --git a/includes/pdfparser/Document/Image/ColorSpace/ColorSpace.php b/includes/pdfparser/Document/Image/ColorSpace/ColorSpace.php new file mode 100644 index 0000000..93253c4 --- /dev/null +++ b/includes/pdfparser/Document/Image/ColorSpace/ColorSpace.php @@ -0,0 +1,46 @@ +components)) { + return $this->components; + } + + if ($this->nameValue instanceof DeviceColorSpaceNameValue) { + return $this->components = $this->nameValue->getComponents(); + } + + if ($this->LUTObj?->getDictionary()->getTypeForKey(DictionaryKey::N) !== null) { + return $this->components = Components::tryFrom( + $this->LUTObj + ->getDictionary() + ->getValueForKey(DictionaryKey::N, IntegerValue::class) + ->value ?? throw new RuntimeException('Unable to determine number of components for color space') + ) ?? throw new ParseFailureException('Unable to determine number of components for color space'); + } + + return $this->components = Components::Gray; + } +} diff --git a/includes/pdfparser/Document/Image/ColorSpace/ColorSpaceFactory.php b/includes/pdfparser/Document/Image/ColorSpace/ColorSpaceFactory.php new file mode 100644 index 0000000..4b38eb8 --- /dev/null +++ b/includes/pdfparser/Document/Image/ColorSpace/ColorSpaceFactory.php @@ -0,0 +1,43 @@ +\/Indexed)?\s*(?\/[A-Za-z]+|([0-9]+\s+[0-9]+\s+R))(?(\s+(?[0-9]+))?\s+(?<[A-Fa-f0-9]*>|((?[0-9]+)\s+[0-9]+\s+R)))?\s*]\s*$/', $string, $matches) !== 1) { + throw new ParseFailureException(sprintf('Invalid color space string "%s"', $string)); + } + + if (preg_match('/^(?[0-9]+)\s+[0-9]+\s+R$/', $matches['name'], $nameObjectMatches) === 1) { + $colorSpaceObject = $document->getObject((int) $nameObjectMatches['objectNr']) + ?? throw new ParseFailureException(sprintf('Unable to locate object with number %d', (int) $nameObjectMatches['objectNr'])); + if (preg_match('/^\s*\[\s*\/(?[A-Za-z]+)\s+(?[0-9]+)\s+[0-9]+\s+R\s*]\s*$/', $colorSpaceObject->getStream()->toString(), $colorSpaceObjectMatches) !== 1) { + throw new ParseFailureException(sprintf('Invalid color space string "%s" in colorSpaceObject', $colorSpaceObject->getStream()->toString())); + } + + $colorSpaceName = DeviceColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name']) + ?? SpecialColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name']) + ?? CIEColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name']) + ?? throw new ParseFailureException(sprintf('Unsupported color space name "%s"', $colorSpaceObjectMatches['name'])); + } else { + $colorSpaceName = DeviceColorSpaceNameValue::tryFrom($nameString = substr($matches['name'], 1)) + ?? SpecialColorSpaceNameValue::tryFrom($nameString) + ?? CIEColorSpaceNameValue::tryFrom($nameString) + ?? throw new ParseFailureException(sprintf('Unsupported color space name "%s"', $nameString)); + } + + return new ColorSpace( + $matches['indexed'] !== '' && SpecialColorSpaceNameValue::tryFrom(substr($matches['indexed'], 1)) === SpecialColorSpaceNameValue::Indexed, + $colorSpaceName, + array_key_exists('lut_obj_nr', $matches) ? $document->getObject((int) $matches['lut_obj_nr']) : null, + $matches['lut_value'] !== '' && preg_match('/^(?[0-9]+)\s+[0-9]+\s+R$/', $matches['lut_value']) === 0 ? $matches['lut_value'] : null, + $matches['lut_count'] !== '' ? (int) $matches['lut_count'] : null, + ); + } +} diff --git a/includes/pdfparser/Document/Image/ColorSpace/Components.php b/includes/pdfparser/Document/Image/ColorSpace/Components.php new file mode 100644 index 0000000..92c421c --- /dev/null +++ b/includes/pdfparser/Document/Image/ColorSpace/Components.php @@ -0,0 +1,9 @@ + 'jpg', + self::JPEG2000 => 'jp2', + self::PNG => 'png', + self::TIFF, + self::TIFF_FAX => 'tiff', + self::CUSTOM, + self::RAW => 'raw', + self::JBIG2 => 'jbig2', + }; + } +} diff --git a/includes/pdfparser/Document/Image/RasterizedImage.php b/includes/pdfparser/Document/Image/RasterizedImage.php new file mode 100644 index 0000000..00c23fb --- /dev/null +++ b/includes/pdfparser/Document/Image/RasterizedImage.php @@ -0,0 +1,110 @@ + $width + * @param int<1, max> $height + * @throws ParseFailureException + */ + public static function toPNG(ColorSpace $colorSpace, int $width, int $height, int $bitsPerComponent, Stream $content): Stream { + $image = imagecreatetruecolor($width, $height); + if ($image === false) { + throw new ParseFailureException('Unable to create image'); + } + + if ($bitsPerComponent === 1) { + $streamLength = $content->getSizeInBytes(); + if ($streamLength < ceil($width * $height * $bitsPerComponent / 8)) { + throw new ParseFailureException('Stream content is smaller than expected'); + } + + $byteIndex = $bitsRemaining = 0; + $currentByte = null; + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + if ($bitsRemaining === 0) { + $currentByte = ord($content->read($byteIndex, 1)); + $bitsRemaining = 8; + $byteIndex++; + } + + $bitPosition = --$bitsRemaining; + $bit = ($currentByte >> $bitPosition) & 1; + if (($color = $bit === 0 ? imagecolorallocate($image, 0, 0, 0) : imagecolorallocate($image, 255, 255, 255)) === false) { + throw new ParseFailureException('Unable to allocate color'); + } + + imagesetpixel($image, $x, $y, $color); + } + + $endOfRowBits = $width % 8; + if ($endOfRowBits !== 0) { + $bitsRemaining = max(0, $bitsRemaining - (8 - $endOfRowBits)); + } + } + } elseif ($bitsPerComponent === 8) { + $pixelIndex = 0; + for ($y = 0; $y < $height; $y++) { + for ($x = 0; $x < $width; $x++) { + if ($colorSpace->isIndexed && $colorSpace->LUTObj !== null) { + $indexInLUT = ord($content->read($pixelIndex, 1)); + if ($indexInLUT > $colorSpace->maxIndexLUT) { + throw new ParseFailureException('Index in LUT is too large'); + } + + $color = match ($colorSpace->getComponents()) { + Components::RGB => imagecolorallocate($image, ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)), ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 1, 1)), ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 2, 1))), + Components::Gray => imagecolorallocate($image, $value = ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)), $value, $value), + Components::CMYK => imagecolorallocate( + $image, + min(255, max(0, (int)(255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), + min(255, max(0, (int)(255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 1, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), + min(255, max(0, (int)(255 * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 2, 1)) / 255)) * (1 - (ord($colorSpace->LUTObj->getStream()->read($indexInLUT + 3, 1)) / 255))))), + ), + }; + $pixelIndex++; + } else { + $color = match ($colorSpace->getComponents()) { + Components::RGB => imagecolorallocate($image, ord($content->read($pixelIndex, 1)), ord($content->read($pixelIndex + 1, 1)), ord($content->read($pixelIndex + 2, 1))), + Components::Gray => imagecolorallocate($image, $value = ord($content->read($pixelIndex, 1)), $value, $value), + Components::CMYK => imagecolorallocate( + $image, + min(255, max(0, (int)(255 * (1 - (ord($content->read($pixelIndex, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), + min(255, max(0, (int)(255 * (1 - (ord($content->read($pixelIndex + 1, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), + min(255, max(0, (int)(255 * (1 - (ord($content->read($pixelIndex + 2, 1)) / 255)) * (1 - (ord($content->read($pixelIndex + 3, 1)) / 255))))), + ), + }; + $pixelIndex += $colorSpace->getComponents()->value; + } + + if ($color === false) { + throw new ParseFailureException('Unable to allocate color'); + } + + imagesetpixel($image, $x, $y, $color); + } + } + } else { + throw new ParseFailureException(sprintf('Unsupported BitsPerComponent %d', $bitsPerComponent)); + } + + ob_start(); + imagepng($image); + $imageContent = ob_get_clean(); + if ($imageContent === false) { + throw new ParseFailureException('Unable to decode image'); + } + + return FileStream::fromString($imageContent); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/Catalog.php b/includes/pdfparser/Document/Object/Decorator/Catalog.php new file mode 100644 index 0000000..f83577c --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/Catalog.php @@ -0,0 +1,49 @@ +getDictionary()->getValueForKey(DictionaryKey::PAGES, ReferenceValue::class) + ?? throw new ParseFailureException('Every catalog dictionary should contain a pages reference, none found'); + + return $this->document->getObject($pagesReference->objectNumber, Pages::class) + ?? throw new ParseFailureException(sprintf('Unable to retrieve pages root object with number %d', $pagesReference->objectNumber)); + } + + /** @return list */ + public function getFileSpecifications(): array { + $afType = $this->getDictionary()->getTypeForKey(DictionaryKey::AF); + if ($afType === null) { + return []; + } + + if ($afType === ReferenceValue::class) { + $referenceArrayContent = $this->getDictionary() + ->getObjectForReference($this->document, DictionaryKey::AF, FileSpecification::class) + ?->getStream()->toString() ?? throw new ParseFailureException('Unable to retrieve AF object content'); + if (($AFReferences = ReferenceValueArray::fromValue($referenceArrayContent)) instanceof ReferenceValueArray === false) { + throw new ParseFailureException('AF object is not a reference array'); + } + + return array_map( + fn (ReferenceValue $referenceValue) => $this->document->getObject($referenceValue->objectNumber, FileSpecification::class) ?? throw new ParseFailureException('Unable to retrieve file specification'), + $AFReferences->referenceValues, + ); + } + + if ($afType === ReferenceValueArray::class) { + return $this->getDictionary() + ->getObjectsForReference($this->document, DictionaryKey::AF, FileSpecification::class); + } + + throw new ParseFailureException(sprintf('Unexpected type "%s" for AF key', $afType)); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/DecoratedObject.php b/includes/pdfparser/Document/Object/Decorator/DecoratedObject.php new file mode 100644 index 0000000..4e04f0a --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/DecoratedObject.php @@ -0,0 +1,40 @@ +objectItem->getDictionary($document)->getType(); + if ($typeNameValue !== null && !in_array($typeNameValue->getDecoratorFQN(), [static::class, GenericObject::class], true)) { + throw new InvalidArgumentException( + sprintf('Object should have decorator %s, got %s', $typeNameValue->getDecoratorFQN(), static::class) + ); + } + } + + /** @throws PdfParserException */ + public function getDictionary(): Dictionary { + return $this->objectItem->getDictionary($this->document); + } + + public function getStream(): Stream { + return $this->objectItem->getContent($this->document); + } + + #[Deprecated('Use self::getStream() instead')] + public function getContent(): string { + return $this->getStream()->toString(); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/DecoratedObjectFactory.php b/includes/pdfparser/Document/Object/Decorator/DecoratedObjectFactory.php new file mode 100644 index 0000000..7990efc --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/DecoratedObjectFactory.php @@ -0,0 +1,33 @@ +|null $expectedDecoratorFQN + * @throws PdfParserException + * @return ($expectedDecoratorFQN is null ? DecoratedObject : T) + */ + public static function forItem(?ObjectItem $objectItem, Document $document, ?string $expectedDecoratorFQN): ?DecoratedObject { + if ($objectItem === null) { + return null; + } + + $typeNameValue = $objectItem->getDictionary($document)->getType(); + if ($expectedDecoratorFQN !== null && $typeNameValue !== null && $expectedDecoratorFQN !== $typeNameValue->getDecoratorFQN()) { + throw new ParseFailureException(sprintf('Expected object of type %s, got %s', $expectedDecoratorFQN, $typeNameValue->getDecoratorFQN())); + } + + $decoratorFQN = $expectedDecoratorFQN + ?? $typeNameValue?->getDecoratorFQN() + ?? GenericObject::class; + + return new $decoratorFQN($objectItem, $document); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/EmbeddedFile.php b/includes/pdfparser/Document/Object/Decorator/EmbeddedFile.php new file mode 100644 index 0000000..8c976a7 --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/EmbeddedFile.php @@ -0,0 +1,48 @@ +getDictionary() + ->getValueForKey(DictionaryKey::LENGTH, IntegerValue::class) + ?->value; + } + + public function getFileSpecificInformation(): ?Dictionary { + return $this->getDictionary() + ->getSubDictionary($this->document, DictionaryKey::PARAMS); + } + + public function getSubType(): ?string { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::SUBTYPE, TextStringValue::class) + ?->getText(); + } + + public function getSize(): ?int { + return $this->getFileSpecificInformation() + ?->getValueForKey(DictionaryKey::SIZE, IntegerValue::class) + ?->value; + } + + public function getCreationDate(): ?DateTimeImmutable { + return $this->getFileSpecificInformation() + ?->getValueForKey(DictionaryKey::CREATION_DATE, DateValue::class) + ?->value; + } + + public function getModificationDate(): ?DateTimeImmutable { + return $this->getFileSpecificInformation() + ?->getValueForKey(DictionaryKey::MOD_DATE, DateValue::class) + ?->value; + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/EncryptDictionary.php b/includes/pdfparser/Document/Object/Decorator/EncryptDictionary.php new file mode 100644 index 0000000..ea03cfa --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/EncryptDictionary.php @@ -0,0 +1,109 @@ +getDictionary()->getTypeForKey(DictionaryKey::FILTER); + if ($filterType === null) { + return null; + } + + if ($filterType !== SecurityHandlerNameValue::class) { + throw new RuntimeException('Unable to retrieve security handler for non-security handler dictionaries'); + } + + return $this->getDictionary()->getValueForKey(DictionaryKey::FILTER, SecurityHandlerNameValue::class); + } + + public function getLengthFileEncryptionKeyInBits(): ?int { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::LENGTH, IntegerValue::class) + ?->value; + } + + public function getOwnerPasswordEntry(): string { + $textStringValue = $this->getDictionary() + ->getValueForKey(DictionaryKey::O, TextStringValue::class) + ->textStringValue + ?? throw new ParseFailureException(); + + if (str_starts_with($textStringValue, '<') && str_ends_with($textStringValue, '>')) { + $decodedValue = hex2bin(substr($textStringValue, 1, -1)); + if ($decodedValue === false) { + throw new ParseFailureException('Unable to decode owner password entry'); + } + } elseif (str_starts_with($textStringValue, '(') && str_ends_with($textStringValue, ')')) { + $decodedValue = substr($textStringValue, 1, -1); + } else { + throw new ParseFailureException(); + } + + $decodedValue = str_pad($decodedValue, 32, "\x00"); + if ($this->getStandardSecurityHandlerRevision()->value <= 4) { + return substr($decodedValue, 0, 32); + } + + return $decodedValue; + } + + public function getUserPasswordEntry(): string { + $textStringValue = $this->getDictionary() + ->getValueForKey(DictionaryKey::U, TextStringValue::class) + ->textStringValue + ?? throw new ParseFailureException(); + + if (str_starts_with($textStringValue, '<') && str_ends_with($textStringValue, '>')) { + $decodedValue = hex2bin(substr($textStringValue, 1, -1)); + if ($decodedValue === false) { + throw new ParseFailureException('Unable to decode user password entry'); + } + } elseif (str_starts_with($textStringValue, '(') && str_ends_with($textStringValue, ')')) { + $decodedValue = substr($textStringValue, 1, -1); + } else { + throw new ParseFailureException(); + } + + $expectedLength = $this->getStandardSecurityHandlerRevision() === StandardSecurityHandlerRevision::v2 ? 32 : 64; + return str_pad( + substr($decodedValue, 0, $expectedLength), + $expectedLength, + "\x00" + ); + } + + public function getPValue(): int { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::P, IntegerValue::class) + ->value + ?? throw new ParseFailureException('Unable to retrieve p value'); + } + + public function getSecurityAlgorithm(): ?SecurityAlgorithm { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::V, SecurityAlgorithm::class); + } + + public function getStandardSecurityHandlerRevision(): StandardSecurityHandlerRevision { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::R, StandardSecurityHandlerRevision::class) + ?? throw new ParseFailureException('Unable to retrieve standard security handler revision'); + } + + public function isMetadataEncrypted(): bool { + $encryptMetadata = $this->getDictionary() + ->getValueForKey(DictionaryKey::ENCRYPT_METADATA, BooleanValue::class); + + return $encryptMetadata === null || $encryptMetadata->value; // If key is not present, assume encrypted metadata + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/FileSpecification.php b/includes/pdfparser/Document/Object/Decorator/FileSpecification.php new file mode 100644 index 0000000..670214f --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/FileSpecification.php @@ -0,0 +1,39 @@ +getDictionary()->getTypeForKey(DictionaryKey::UF); + if ($ufType === TextStringValue::class) { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::UF, $ufType) + ?->getText() ?? throw new ParseFailureException(); + } + + $fType = $this->getDictionary()->getTypeForKey(DictionaryKey::F); + if ($fType === TextStringValue::class) { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::F, $fType) + ?->getText() ?? throw new ParseFailureException(); + } + + return null; + } + + public function getEmbeddedFileStreamDictionary(): ?Dictionary { + return $this->getDictionary() + ->getSubDictionary($this->document, DictionaryKey::EF); + } + + public function getEmbeddedFile(): ?EmbeddedFile { + return $this->getEmbeddedFileStreamDictionary() + ?->getObjectForReference($this->document, DictionaryKey::F, EmbeddedFile::class); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/Font.php b/includes/pdfparser/Document/Object/Decorator/Font.php new file mode 100644 index 0000000..3605608 --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/Font.php @@ -0,0 +1,275 @@ +getDictionary() + ->getValueForKey(DictionaryKey::BASE_FONT, TextStringValue::class) + ?->textStringValue; + } + + public function getEncodingDictionary(): ?Dictionary { + if (in_array($this->getDictionary()->getTypeForKey(DictionaryKey::ENCODING), [null, EncodingNameValue::class], true)) { + return null; + } + + return $this->getDictionary() + ->getSubDictionary($this->document, DictionaryKey::ENCODING); + } + + /** @throws PdfParserException */ + public function getEncoding(): ?EncodingNameValue { + $encodingType = $this->getDictionary()->getTypeForKey(DictionaryKey::ENCODING); + if ($encodingType === null) { + return null; + } + + if ($encodingType === EncodingNameValue::class) { + return $this->getDictionary()->getValueForKey(DictionaryKey::ENCODING, EncodingNameValue::class); + } + + return $this->getEncodingDictionary() + ?->getValueForKey(DictionaryKey::BASE_ENCODING, EncodingNameValue::class); + } + + public function getDifferences(): ?DifferencesArrayValue { + return $this->getEncodingDictionary() + ?->getValueForKey(DictionaryKey::DIFFERENCES, DifferencesArrayValue::class); + } + + /** @throws PdfParserException */ + public function getToUnicodeCMap(): ?ToUnicodeCMap { + if (isset($this->toUnicodeCMap)) { + if ($this->toUnicodeCMap === false) { + return null; + } + + return $this->toUnicodeCMap; + } + + $toUnicodeObject = $this->getDictionary() + ->getObjectForReference($this->document, DictionaryKey::TO_UNICODE); + if ($toUnicodeObject === null) { + $this->toUnicodeCMap = false; + + return null; + } + + if ($toUnicodeObject->objectItem instanceof UncompressedObject === false) { + throw new ParseFailureException(); + } + + $stream = $toUnicodeObject->objectItem->getContent($this->document); + return $this->toUnicodeCMap = ToUnicodeCMapParser::parse($stream, 0, $stream->getSizeInBytes()); + } + + public function getToUnicodeCMapDescendantFont(): ?ToUnicodeCMap { + foreach ($this->getDescendantFonts() as $descendantFont) { + $fontDictionary = $descendantFont instanceof Dictionary ? $descendantFont : $descendantFont->getDictionary(); + + if (($CIDSystemInfo = $fontDictionary->getValueForKey(DictionaryKey::CIDSYSTEM_INFO, Dictionary::class)) !== null) { + $fontResource = RegistryOrchestrator::getForRegistryOrderingSupplement( + $CIDSystemInfo->getValueForKey(DictionaryKey::REGISTRY, TextStringValue::class) ?? throw new ParseFailureException(), + $CIDSystemInfo->getValueForKey(DictionaryKey::ORDERING, TextStringValue::class) ?? throw new ParseFailureException(), + $CIDSystemInfo->getValueForKey(DictionaryKey::SUPPLEMENT, IntegerValue::class) ?? throw new ParseFailureException(), + ); + + if ($fontResource !== null) { + return $fontResource->getToUnicodeCMap(); + } + } + } + + return null; + } + + /** @throws PdfParserException */ + public function getFirstChar(): ?int { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::FIRST_CHAR, IntegerValue::class) + ?->value; + } + + /** @throws PdfParserException */ + public function getLastChar(): ?int { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::LAST_CHAR, IntegerValue::class) + ?->value; + } + + public function getWidthForChar(int $characterCode, TextState $textState, TransformationMatrix $transformationMatrix): float { + $fontWidths = $this->getWidths(); + if ($fontWidths !== null && ($charWidth = $fontWidths->getWidthForCharacter($characterCode)) !== null) { + $characterWidth = $charWidth; + } else { + $characterWidth = $this->getDefaultWidth(); + } + + return ($characterWidth * ($textState->fontSize ?? 10) + $textState->charSpace) * $transformationMatrix->scaleX; + } + + /** @param list $chars */ + public function getWidthForChars(array $chars, TextState $textState, TransformationMatrix $transformationMatrix): float { + $totalCharacterWidth = 0; + foreach ($chars as $char) { + $totalCharacterWidth += $this->getWidthForChar($char, $textState, $transformationMatrix); + } + + return $totalCharacterWidth; + } + + /** @return list */ + public function getDescendantFonts(): array { + $valueType = $this->getDictionary()->getTypeForKey(DictionaryKey::DESCENDANT_FONTS); + if ($valueType === null) { + return []; + } + + if ($valueType === ReferenceValue::class) { + $descendantFontsReference = $this->getDictionary()->getValueForKey(DictionaryKey::DESCENDANT_FONTS, ReferenceValue::class) ?? throw new ParseFailureException(); + return [ + $this->document->getObject($descendantFontsReference->objectNumber, Font::class) + ?? throw new ParseFailureException(sprintf('Descendant font with number %d could not be found', $descendantFontsReference->objectNumber)), + ]; + } + + if ($valueType === DictionaryArrayValue::class) { + return $this->getDictionary()->getValueForKey(DictionaryKey::DESCENDANT_FONTS, DictionaryArrayValue::class)->dictionaries ?? throw new ParseFailureException(); + } + + $descendantFonts = []; + foreach ($this->getDictionary()->getValueForKey(DictionaryKey::DESCENDANT_FONTS, ReferenceValueArray::class)->referenceValues ?? [] as $referenceValue) { + $descendantFonts[] = $this->document->getObject($referenceValue->objectNumber, Font::class) + ?? throw new ParseFailureException(sprintf('Descendant font with number %d could not be found', $referenceValue->objectNumber)); + } + + return $descendantFonts; + } + + public function isCIDFont(): bool { + return in_array( + $this->getDictionary()->getValueForKey(DictionaryKey::SUBTYPE, SubtypeNameValue::class), + [SubtypeNameValue::CID_FONT_TYPE_0, SubtypeNameValue::CID_FONT_TYPE_2, SubtypeNameValue::CID_FONT_TYPE_0_C], + true, + ); + } + + public function getDefaultWidth(): float { + if ($this->isCIDFont()) { + return ($this->getDictionary()->getValueForKey(DictionaryKey::DW, IntegerValue::class)->value + ?? 1000) / 1000; + } + + foreach ($this->getDescendantFonts() as $descendantFont) { + if ($descendantFont instanceof Dictionary && $descendantFont->getTypeForKey(DictionaryKey::W) === ReferenceValue::class) { + $descendantFont = $this->document->getObject($descendantFont->getValueForKey(DictionaryKey::W, ReferenceValue::class)->objectNumber ?? throw new ParseFailureException(), Font::class) ?? throw new ParseFailureException(); + } + + if ($descendantFont instanceof Font) { + return $descendantFont->getDefaultWidth(); + } + } + + return 1000; + } + + /** @throws PdfParserException */ + public function getWidths(): CIDFontWidths|FontWidths|null { + if (isset($this->widths)) { + if ($this->widths === false) { + return null; + } + + return $this->widths; + } + + if ($this->isCIDFont()) { + if ($this->getDictionary()->getTypeForKey(DictionaryKey::W) === CrossReferenceStreamByteSizes::class) { + $byteSizes = $this->getDictionary()->getValueForKey(DictionaryKey::W, CrossReferenceStreamByteSizes::class) ?? throw new ParseFailureException(); // TODO: fix misinterpretation + + return $this->widths = new CIDFontWidths(new RangeCIDWidth($byteSizes->lengthRecord1InBytes, $byteSizes->lengthRecord2InBytes, $byteSizes->lengthRecord3InBytes)); + } + + $this->widths = $this->getDictionary()->getValueForKey(DictionaryKey::W, CIDFontWidths::class) ?? false; + return $this->widths === false ? null : $this->widths; + } + + foreach ($this->getDescendantFonts() as $descendantFont) { + if ($descendantFont instanceof Dictionary && $descendantFont->getTypeForKey(DictionaryKey::W) === ReferenceValue::class) { + $descendantFont = $this->document->getObject($descendantFont->getValueForKey(DictionaryKey::W, ReferenceValue::class)->objectNumber ?? throw new ParseFailureException(), Font::class) ?? throw new ParseFailureException(); + } + + if ($descendantFont instanceof Font && ($widthsDescendantFont = $descendantFont->getWidths()) !== null) { + return $this->widths = $widthsDescendantFont; + } + } + + if ($this->getDictionary()->getTypeForKey(DictionaryKey::WIDTHS) === ReferenceValue::class) { + $object = $this->document->getObject(($widthsReference = $this->getDictionary()->getValueForKey(DictionaryKey::WIDTHS, ReferenceValue::class))->objectNumber ?? throw new ParseFailureException(), Font::class) + ?? throw new ParseFailureException(sprintf('Width dictionary with number %d could not be found', $widthsReference->objectNumber)); + $arrayValue = ArrayValue::fromValue($object->getStream()->toString()); + if ($arrayValue instanceof ArrayValue === false) { + throw new ParseFailureException(sprintf('Width dictionary with number %d does not contain a valid array, "%s"', $widthsReference->objectNumber, $object->getStream()->read(0, 100) . '...')); + } + + $widthsArray = $arrayValue->value; + } elseif (($widthsArray = $this->getDictionary()->getValueForKey(DictionaryKey::WIDTHS, ArrayValue::class)?->value) === null) { + $this->widths = false; + return null; + } + + if (($firstChar = $this->getFirstChar()) === null) { + $this->widths = false; + return null; + } + + return $this->widths = new FontWidths( + $firstChar, + array_values( + array_map( + fn (mixed $width): float => is_numeric($width) ? (float) $width : throw new InvalidArgumentException(sprintf('"%s" is not a valid width', ($jsonEncoded = json_encode($width)) !== false ? $jsonEncoded : 'value')), + array_filter( + $widthsArray, + fn (mixed $item) => $item !== '', + ), + ), + ), + ); + } + + /** @throws PdfParserException */ + public function getFontDescriptor(): ?ReferenceValue { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::FONT_DESCRIPTOR, ReferenceValue::class); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/GenericObject.php b/includes/pdfparser/Document/Object/Decorator/GenericObject.php new file mode 100644 index 0000000..1e2e5eb --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/GenericObject.php @@ -0,0 +1,6 @@ +getDictionary() + ->getValueForKey(DictionaryKey::TITLE, TextStringValue::class) + ?->getText(); + } + + /** @throws PdfParserException */ + public function getProducer(): ?string { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::PRODUCER, TextStringValue::class) + ?->getText(); + } + + /** @throws PdfParserException */ + public function getAuthor(): ?string { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::AUTHOR, TextStringValue::class) + ?->getText(); + } + + /** @throws PdfParserException */ + public function getCreator(): ?string { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::CREATOR, TextStringValue::class) + ?->getText(); + } + + /** @throws PdfParserException */ + public function getCreationDate(): ?DateTimeImmutable { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::CREATION_DATE, DateValue::class) + ?->value; + } + + /** @throws PdfParserException */ + public function getModificationDate(): ?DateTimeImmutable { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::MOD_DATE, DateValue::class) + ?->value; + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/Page.php b/includes/pdfparser/Document/Object/Decorator/Page.php new file mode 100644 index 0000000..f23a1b9 --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/Page.php @@ -0,0 +1,102 @@ + + */ + public function getPositionedTextElements(): array { + return $this->getContentStream() + ->getPositionedTextElements(); + } + + /** @throws PdfParserException */ + public function getText(): string { + return $this->getContentStream() + ->getText($this->document, $this); + } + + /** @throws PdfParserException */ + public function getContentStream(): ContentStream { + return ContentStreamParser::parse( + $this->document->getObjectsByDictionaryKey($this->getDictionary(), DictionaryKey::CONTENTS), + ); + } + + /** @throws PdfParserException */ + public function getResourceDictionary(): ?Dictionary { + return $this->getDictionary() + ->getSubDictionary($this->document, DictionaryKey::RESOURCES); + } + + /** @throws PdfParserException */ + public function getXObjectsDictionary(): ?Dictionary { + return $this->getResourceDictionary() + ?->getSubDictionary($this->document, DictionaryKey::XOBJECT); + } + + /** + * @throws PdfParserException + * @return list + */ + public function getXObjects(): array { + $xObjects = []; + foreach ($this->getXObjectsDictionary()->dictionaryEntries ?? [] as $xObjectDictionaryEntry) { + if (!$xObjectDictionaryEntry->value instanceof ReferenceValue) { + throw new InvalidArgumentException(sprintf('XObjects should be references, got %s', get_class($xObjectDictionaryEntry->value))); + } + + $xObjects[] = $this->document->getObject($xObjectDictionaryEntry->value->objectNumber, XObject::class) + ?? throw new ParseFailureException(sprintf('Unable to locate object with nr %d', $xObjectDictionaryEntry->value->objectNumber)); + } + + return $xObjects; + } + + /** + * @throws PdfParserException + * @return list + */ + public function getImages(): array { + return array_values(array_filter( + $this->getXObjects(), + fn (XObject $XObject) => $XObject->isImage(), + )); + } + + /** @throws PdfParserException */ + public function getFontDictionary(): ?Dictionary { + if (($pageFontDictionary = $this->getDictionary()->getSubDictionary($this->document, DictionaryKey::FONT)) !== null) { + return $pageFontDictionary; + } + + if (($pageResourceFontDictionary = $this->getResourceDictionary()?->getSubDictionary($this->document, DictionaryKey::FONT)) !== null) { + return $pageResourceFontDictionary; + } + + if (($pagesParent = $this->getDictionary()->getObjectForReference($this->document, DictionaryKey::PARENT, Pages::class)) === null) { + return null; + } + + return $pagesParent->getResourceDictionary() + ?->getSubDictionary($this->document, DictionaryKey::FONT); + } + + /** @return list */ + public function getFileSpecifications(): array { + return $this->getDictionary() + ->getObjectsForReference($this->document, DictionaryKey::AF, FileSpecification::class); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/Pages.php b/includes/pdfparser/Document/Object/Decorator/Pages.php new file mode 100644 index 0000000..e86b389 --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/Pages.php @@ -0,0 +1,39 @@ + + */ + public function getPageItems(): array { + $kids = []; + foreach ($this->getDictionary()->getValueForKey(DictionaryKey::KIDS, ReferenceValueArray::class)->referenceValues ?? [] as $referenceValue) { + $kidObject = $this->document->getObject($referenceValue->objectNumber) + ?? throw new ParseFailureException(sprintf('Child with number %d could not be found', $referenceValue->objectNumber)); + + if ($kidObject instanceof Pages) { + $kids = [...$kids, ...$kidObject->getPageItems()]; + } elseif ($kidObject instanceof Page) { + $kids[] = $kidObject; + } elseif ($kidObject instanceof GenericObject) { + $kids[] = new Page($kidObject->objectItem, $this->document); + } + } + + return $kids; + } + + /** @throws PdfParserException */ + public function getResourceDictionary(): ?Dictionary { + return $this->getDictionary() + ->getSubDictionary($this->document, DictionaryKey::RESOURCES); + } +} diff --git a/includes/pdfparser/Document/Object/Decorator/XObject.php b/includes/pdfparser/Document/Object/Decorator/XObject.php new file mode 100644 index 0000000..c67ebd6 --- /dev/null +++ b/includes/pdfparser/Document/Object/Decorator/XObject.php @@ -0,0 +1,150 @@ +getDictionary() + ->getSubType() === SubtypeNameValue::IMAGE; + } + + public function isForm(): bool { + return $this->getDictionary() + ->getSubType() === SubtypeNameValue::FORM; + } + + public function getWidth(): ?int { + if ($this->getDictionary()->getTypeForKey(DictionaryKey::WIDTH) === null) { + return null; + } + + return $this->getDictionary() + ->getValueForKey(DictionaryKey::WIDTH, IntegerValue::class) + ?->value; + } + + public function getHeight(): ?int { + if ($this->getDictionary()->getTypeForKey(DictionaryKey::HEIGHT) === null) { + return null; + } + + return $this->getDictionary() + ->getValueForKey(DictionaryKey::HEIGHT, IntegerValue::class) + ?->value; + } + + public function getLength(): ?int { + if ($this->getDictionary()->getTypeForKey(DictionaryKey::LENGTH) === null) { + return null; + } + + return $this->getDictionary() + ->getValueForKey(DictionaryKey::LENGTH, IntegerValue::class) + ?->value; + } + + public function getImageType(): ?ImageType { + if (!$this->isImage()) { + throw new RuntimeException('Unable to retrieve image type for XObjects that is not an image'); + } + + $filterValueType = $this->getDictionary()->getTypeForKey(DictionaryKey::FILTER); + if ($filterValueType === null) { + return null; + } + + if ($filterValueType === FilterNameValue::class) { + return $this->getDictionary()->getValueForKey(DictionaryKey::FILTER, FilterNameValue::class)?->getImageType(); + } + + if ($filterValueType === ArrayValue::class) { + foreach ($this->getDictionary()->getValueForKey(DictionaryKey::FILTER, ArrayValue::class)->value ?? throw new RuntimeException() as $filterValue) { + if (!is_string($filterValue)) { + throw new ParseFailureException(sprintf('Expected a string for filter value, got "%s"', ($jsonEncoded = json_encode($filterValue)) !== false ? $jsonEncoded : 'Unknown')); + } + + $filterValue = FilterNameValue::tryFrom(ltrim($filterValue, '/')) ?? throw new ParseFailureException(sprintf('Unsupported filter value "%s"', $filterValue)); + if ($filterValue->getImageType() !== null) { + return $filterValue->getImageType(); + } + } + } + + throw new ParseFailureException(sprintf('Unsupported filter value type %s', $filterValueType)); + } + + private function getBitsPerComponent(): ?int { + return $this->getDictionary() + ->getValueForKey(DictionaryKey::BITS_PER_COMPONENT, IntegerValue::class)?->value; + } + + private function getColorSpace(): ?ColorSpace { + if (($type = $this->getDictionary()->getTypeForKey(DictionaryKey::COLOR_SPACE)) === null) { + return null; + } + + if ($type === DeviceColorSpaceNameValue::class || $type === CIEColorSpaceNameValue::class || $type === SpecialColorSpaceNameValue::class) { + return new ColorSpace(false, $this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, $type) ?? throw new ParseFailureException(), null, null, null); + } + + if ($type === ArrayValue::class) { + $colorSpaceArray = $this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, ArrayValue::class) + ?? throw new ParseFailureException(); + + return ColorSpaceFactory::fromString($colorSpaceArray->toString(), $this->document); + } + + if ($type === ReferenceValue::class) { + $colorSpaceObject = $this->getDictionary()->getObjectForReference($this->document, DictionaryKey::COLOR_SPACE) + ?? throw new ParseFailureException('Unable to retrieve colorspace object'); + + return ColorSpaceFactory::fromString($colorSpaceObject->getStream()->toString(), $this->document); + } + + throw new ParseFailureException(sprintf('Unsupported colorspace format %s', $type)); + } + + #[Override] + public function getStream(): Stream { + $content = parent::getStream(); + if (!$this->isImage() || $this->getImageType() !== ImageType::PNG) { + return $content; + } + + $height = $this->getHeight() ?? throw new RuntimeException('Unable to retrieve height'); + if ($height < 1) { + throw new RuntimeException(sprintf('Height %d cannot be less than 1', $height)); + } + + $width = $this->getWidth() ?? throw new RuntimeException('Unable to retrieve width'); + if ($width < 1) { + throw new RuntimeException(sprintf('Width %d cannot be less than 1', $width)); + } + + return RasterizedImage::toPNG( + $this->getColorSpace() ?? throw new RuntimeException('Unable to retrieve colorspace'), + $width, + $height, + $this->getBitsPerComponent() ?? throw new RuntimeException('Unable to retrieve bits per component'), + $content, + ); + } +} diff --git a/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObject.php b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObject.php new file mode 100644 index 0000000..1f4d3ab --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObject.php @@ -0,0 +1,77 @@ +endByteOffsetInDecodedStream !== null && $this->startByteOffsetInDecodedStream > $this->endByteOffsetInDecodedStream) { + throw new InvalidArgumentException(sprintf('Start offset %d should be before end offset %d', $this->startByteOffsetInDecodedStream, $this->endByteOffsetInDecodedStream)); + } + } + + #[Override] + public function getDictionary(Document $document): Dictionary { + if (isset($this->dictionary)) { + return $this->dictionary; + } + + $objectContent = trim($this->getContent($document)->toString()); + if ($objectContent === '' || !str_starts_with($objectContent, '<<') || !str_ends_with($objectContent, '>>')) { + return $this->dictionary = new Dictionary(); + } + + $inMemoryStream = new InMemoryStream($objectContent); + return $this->dictionary = DictionaryParser::parse($inMemoryStream, 0, $inMemoryStream->getSizeInBytes()); + } + + #[Override] + public function getContent(Document $document): Stream { + $first = $this->storedInObject->getDictionary($document)->getValueForKey(DictionaryKey::FIRST, IntegerValue::class) + ?? throw new RuntimeException('Expected a dictionary entry for "First", none found'); + + $content = substr( + $this->storedInObject->getContent($document)->toString(), + $first->value + $this->startByteOffsetInDecodedStream, + $this->endByteOffsetInDecodedStream !== null ? $this->endByteOffsetInDecodedStream - $this->startByteOffsetInDecodedStream : null + ); + + if (str_starts_with($content, '[') && str_ends_with($content, ']') && ($referenceValueArray = ReferenceValueArray::fromValue($content)) !== null) { + $content = implode( + '', + array_map( + fn (ReferenceValue $referenceValue) => ($document->getObject($referenceValue->objectNumber) ?? throw new ParseFailureException()) + ->getStream() + ->toString(), + $referenceValueArray->referenceValues, + ) + ); + } + + return FileStream::fromString($content); + } +} diff --git a/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsetParser.php b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsetParser.php new file mode 100644 index 0000000..cb6a6cd --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsetParser.php @@ -0,0 +1,69 @@ +getStartNextLineAfter(Marker::STREAM, $startOffsetObject, $endOffsetObject) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::STREAM->value)); + if ($dictionary->getTypeForKey(DictionaryKey::LENGTH) === IntegerValue::class && ($lengthInteger = $dictionary->getValueForKey(DictionaryKey::LENGTH, IntegerValue::class)) !== null) { + $length = $lengthInteger->value; + } else { + $endStreamPos = $stream->lastPos(Marker::END_STREAM, $stream->getSizeInBytes() - $endOffsetObject) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::END_STREAM->value)); + $eolPos = $stream->getEndOfCurrentLine($endStreamPos - 1, $endOffsetObject) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', WhitespaceCharacter::LINE_FEED->value)); + $length = $eolPos - $startStreamPos; + } + + $content = bin2hex(CompressedObjectContentParser::parseBinary($stream, $startStreamPos, $length, $dictionary)->toString()); + $first = $dictionary->getValueForKey(DictionaryKey::FIRST, IntegerValue::class) + ?? throw new RuntimeException('Expected a dictionary entry for "First", none found'); + $buffer = new InfiniteBuffer(); + $previousObjectNumber = null; + $byteOffsets = []; + foreach (str_split(substr($content, 0, $first->value * 2), 2) as $char) { + $decodedChar = mb_chr((int) hexdec($char)); + if (WhitespaceCharacter::tryFrom($decodedChar) !== null) { + $numberInBuffer = $buffer->__toString(); + if (trim($numberInBuffer) === '') { + $buffer->flush(); + continue; + } + + if ($numberInBuffer !== (string)(int) $numberInBuffer) { + throw new ParseFailureException(sprintf('Number "%s" in buffer is not a valid number', $numberInBuffer)); + } + + $numberInBuffer = (int) $numberInBuffer; + if ($previousObjectNumber !== null) { + $byteOffsets[$previousObjectNumber] = $numberInBuffer; + $previousObjectNumber = null; + } else { + $previousObjectNumber = $numberInBuffer; + } + + $buffer->flush(); + continue; + } + + $buffer->addChar($decodedChar); + } + + return new CompressedObjectByteOffsets($byteOffsets); + } +} diff --git a/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsets.php b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsets.php new file mode 100644 index 0000000..3ce5e12 --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectByteOffsets.php @@ -0,0 +1,28 @@ + $objectNumberByteOffsets */ + public function __construct( + private readonly array $objectNumberByteOffsets, + ) { + } + + public function getRelativeByteOffsetForObject(int $objNumber): ?int { + return $this->objectNumberByteOffsets[$objNumber] ?? null; + } + + public function getNextRelativeByteOffset(int $currentByteOffset): ?int { + $byteOffsets = array_values($this->objectNumberByteOffsets); + sort($byteOffsets); + foreach ($byteOffsets as $byteOffset) { + if ($byteOffset > $currentByteOffset) { + return $byteOffset; + } + } + + return null; + } +} diff --git a/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectContent/CompressedObjectContentParser.php b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectContent/CompressedObjectContentParser.php new file mode 100644 index 0000000..c94c0f0 --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/CompressedObject/CompressedObjectContent/CompressedObjectContentParser.php @@ -0,0 +1,71 @@ +stream : $context)->read($startPos, $nrOfBytes); + if ($context instanceof Document && $context->security !== null && ($encryptDictionary = $context->getEncryptDictionary()) !== null) { + $binaryStreamContent = RC4::crypt( + $context->security->getUserFileEncryptionKey($encryptDictionary, $context->crossReferenceSource->getFirstId()), + $binaryStreamContent + ); + } + + if (($filterType = $dictionary->getTypeForKey(DictionaryKey::FILTER)) === FilterNameValue::class) { + $binaryStreamContent = ($dictionary->getValueForKey(DictionaryKey::FILTER, FilterNameValue::class) ?? throw new ParseFailureException()) + ->decodeBinary($binaryStreamContent, $dictionary, ($context instanceof Document ? $context : null)); + } elseif ($filterType === ArrayValue::class) { + foreach ($dictionary->getValueForKey(DictionaryKey::FILTER, ArrayValue::class)->value ?? throw new ParseFailureException() as $filterValue) { + if (is_string($filterValue) === false || ($filter = FilterNameValue::tryFrom(ltrim($filterValue, '/'))) === null) { + throw new ParseFailureException(); + } + + $binaryStreamContent = $filter + ->decodeBinary($binaryStreamContent, $dictionary, ($context instanceof Document ? $context : null)); + } + } elseif ($filterType === ReferenceValue::class) { + if (!$context instanceof Document) { + throw new ParseFailureException('Filter reference is only supported in a Document'); + } + + $filter = $dictionary->getObjectForReference($context, DictionaryKey::FILTER) ?? throw new ParseFailureException('Unable to retrieve filter object'); + if (($filterArray = ArrayValue::fromValue($filter->getStream()->toString())) instanceof ArrayValue === false) { + throw new ParseFailureException('Filter object is not an array'); + } + + foreach ($filterArray->value as $filterValue) { + if (is_string($filterValue) === false || ($filter = FilterNameValue::tryFrom(ltrim($filterValue, '/'))) === null) { + throw new ParseFailureException(); + } + + $binaryStreamContent = $filter + ->decodeBinary($binaryStreamContent, $dictionary, $context); + } + } elseif ($filterType !== null) { + throw new RuntimeException(sprintf('Expected filter to be a FilterNameValue or ArrayValue, got %s', $filterType)); + } + + return FileStream::fromString($binaryStreamContent); + } +} diff --git a/includes/pdfparser/Document/Object/Item/ObjectItem.php b/includes/pdfparser/Document/Object/Item/ObjectItem.php new file mode 100644 index 0000000..32ce927 --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/ObjectItem.php @@ -0,0 +1,15 @@ +dictionary)) { + return $this->dictionary; + } + + $startDictionaryPos = $document->stream->firstPos(DelimiterCharacter::LESS_THAN_SIGN, $this->startOffset, $this->endOffset); + if ($startDictionaryPos === null) { + return $this->dictionary = new Dictionary(); + } + + $endDictionaryPos = $document->stream->firstPos(Marker::STREAM, $startDictionaryPos, $this->endOffset) + ?? $document->stream->lastPos(Marker::END_OBJ, $document->stream->getSizeInBytes() - $this->endOffset) + ?? throw new ParseFailureException('Unable to locate start of stream or end of current object'); + + return $this->dictionary = DictionaryParser::parse($document->stream, $startDictionaryPos, $endDictionaryPos - $startDictionaryPos); + } + + public function getCompressedObject(int $objectNumber, Document $document): CompressedObject { + $byteOffsets = $this->getByteOffsets($document); + $startByteOffset = $byteOffsets->getRelativeByteOffsetForObject($objectNumber) + ?? throw new InvalidArgumentException('Compressed object does not exist in this uncompressed object'); + + return new CompressedObject( + $objectNumber, + $this, + $startByteOffset, + $byteOffsets->getNextRelativeByteOffset($startByteOffset), + ); + } + + public function getByteOffsets(Document $document): CompressedObjectByteOffsets { + if (isset($this->byteOffsets)) { + return $this->byteOffsets; + } + + $dictionary = $this->getDictionary($document); + if ($dictionary->getType() !== TypeNameValue::OBJ_STM) { + throw new ParseFailureException('Unable to get stream data from item that is not a stream'); + } + + return $this->byteOffsets = CompressedObjectByteOffsetParser::parse( + $document->stream, + $this->startOffset, + $this->endOffset, + $dictionary + ); + } + + #[Override] + public function getContent(Document $document): Stream { + if (($startStreamPos = $document->stream->getStartNextLineAfter(Marker::STREAM, $this->startOffset, $this->endOffset)) !== null + && ($endStreamPos = $document->stream->lastPos(Marker::END_STREAM, $document->stream->getSizeInBytes() - $this->endOffset)) !== null) { + return CompressedObjectContentParser::parseBinary( + $document, + $startStreamPos, + ($document->stream->getEndOfCurrentLine($endStreamPos - 1, $this->endOffset) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', WhitespaceCharacter::LINE_FEED->value))) - $startStreamPos, + $this->getDictionary($document), + ); + } + + $nextLineAfterStartObj = $document->stream->getStartNextLineAfter(Marker::OBJ, $this->startOffset, $this->endOffset) + ?? throw new ParseFailureException(sprintf('Unable to locate newline after marker %s', Marker::OBJ->value)); + $endObjPos = $document->stream->lastPos(Marker::END_OBJ, $document->stream->getSizeInBytes() - $this->endOffset) + ?? throw new ParseFailureException(sprintf('Unable to locate marker %s', Marker::END_OBJ->value)); + $eolObjContent = $document->stream->getEndOfCurrentLine($endObjPos - 2, $this->endOffset) + ?? throw new ParseFailureException(sprintf('Unable to locate newline after marker %s', Marker::END_OBJ->value)); + + return FileStream::fromString( + $document->stream->read( + $nextLineAfterStartObj, + $eolObjContent - $nextLineAfterStartObj, + ) + ); + } +} diff --git a/includes/pdfparser/Document/Object/Item/UncompressedObject/UncompressedObjectParser.php b/includes/pdfparser/Document/Object/Item/UncompressedObject/UncompressedObjectParser.php new file mode 100644 index 0000000..159bdf1 --- /dev/null +++ b/includes/pdfparser/Document/Object/Item/UncompressedObject/UncompressedObjectParser.php @@ -0,0 +1,29 @@ +firstPos(Marker::END_OBJ, $crossReferenceEntry->byteOffsetInDecodedStream, $stream->getSizeInBytes()) ?? throw new ParseFailureException('Unable to locate end of object'); + $startObj = $stream->firstPos(Marker::OBJ, $crossReferenceEntry->byteOffsetInDecodedStream, $endObj) ?? throw new ParseFailureException('Unable to locate start of object'); + $objHeader = $stream->read($crossReferenceEntry->byteOffsetInDecodedStream, $startObj + Marker::OBJ->length() - $crossReferenceEntry->byteOffsetInDecodedStream); + $objHeaderParts = explode(WhitespaceCharacter::SPACE->value, str_replace([WhitespaceCharacter::LINE_FEED->value], ' ', trim($objHeader))); + if (count($objHeaderParts) !== 3 || (int) $objHeaderParts[0] !== $objectNumber || (int) $objHeaderParts[1] !== $crossReferenceEntry->generationNumber || $objHeaderParts[2] !== Marker::OBJ->value) { + throw new ParseFailureException(sprintf('Expected "%d %d %s" on first line, got "%s"', $objectNumber, $crossReferenceEntry->generationNumber, Marker::OBJ->value, $objHeader)); + } + + return new UncompressedObject( + $objectNumber, + $crossReferenceEntry->generationNumber, + $crossReferenceEntry->byteOffsetInDecodedStream, + $endObj + Marker::END_OBJ->length(), + ); + } +} diff --git a/includes/pdfparser/Document/Security/SecurityAlgorithm.php b/includes/pdfparser/Document/Security/SecurityAlgorithm.php new file mode 100644 index 0000000..e101054 --- /dev/null +++ b/includes/pdfparser/Document/Security/SecurityAlgorithm.php @@ -0,0 +1,21 @@ +getUserPasswordEntry(); + $securityHandlerRevision = $encryptDictionary->getStandardSecurityHandlerRevision(); + + $fileEncryptionKey = $this->getUserFileEncryptionKey($encryptDictionary, $firstID); + if ($securityHandlerRevision === StandardSecurityHandlerRevision::v2) { // @see 7.6.4.4.3, step b + return hash_equals($userPasswordEntry, RC4::crypt($fileEncryptionKey, self::PADDING_STRING)); + } + + if (in_array($securityHandlerRevision, [StandardSecurityHandlerRevision::v3, StandardSecurityHandlerRevision::v4], true)) { // @see 7.6.4.4.4, step b through e + $hash = md5(self::PADDING_STRING . $firstID, true); + $encryptedHash = RC4::crypt($fileEncryptionKey, $hash); + for ($i = 1; $i <= 19; $i++) { + $encryptedHash = RC4::crypt( + implode('', array_map( + fn ($c) => chr(ord($c) ^ $i), + str_split($fileEncryptionKey) + )), + $encryptedHash, + ); + } + + return hash_equals(substr($userPasswordEntry, 0, 16), $encryptedHash); + } + + throw new NotImplementedException('Unsupported security handler revision: ' . $securityHandlerRevision->value); + } + + /** @see 7.6.4.4.6 */ + public function isOwnerPasswordValid(EncryptDictionary $encryptDictionary, string $firstID): bool { + $fileEncryptionKey = $this->getOwnerFileEncryptionKey($encryptDictionary); + + $ownerPasswordEntry = $encryptDictionary->getOwnerPasswordEntry(); + if ($encryptDictionary->getStandardSecurityHandlerRevision() === StandardSecurityHandlerRevision::v2) { + $userPassword = RC4::crypt($fileEncryptionKey, $ownerPasswordEntry); + } else { + $userPassword = $ownerPasswordEntry; + for ($i = 19; $i >= 0; $i--) { + $userPassword = RC4::crypt( + implode('', array_map( + fn ($c) => chr(ord($c) ^ $i), + str_split($fileEncryptionKey) + )), + $userPassword, + ); + } + } + + if ($this->userPassword !== null && $userPassword !== $this->userPassword) { + return false; + } + + $this->userPassword = $userPassword; + return $this->isUserPasswordValid($encryptDictionary, $firstID); + } + + /** @see 7.6.4.4.2 */ + public function getUserFileEncryptionKey(EncryptDictionary $encryptDictionary, string $firstIDValue): string { + if (in_array($encryptDictionary->getStandardSecurityHandlerRevision(), [StandardSecurityHandlerRevision::v2, StandardSecurityHandlerRevision::v3, StandardSecurityHandlerRevision::v4], true) === false) { + throw new NotImplementedException('Unsupported security handler revision: ' . $encryptDictionary->getStandardSecurityHandlerRevision()->value); + } + + $fileEncryptionKeyLengthInBits = $encryptDictionary->getLengthFileEncryptionKeyInBits() ?? throw new ParseFailureException(); + if ($encryptDictionary->getSecurityAlgorithm() === SecurityAlgorithm::AES_Key_length_256) { // V = 4 + throw new NotImplementedException('AES-based stream decryption is not yet supported.'); + } + + if ($fileEncryptionKeyLengthInBits % 8 !== 0 || !is_int($fileEncryptionKeyLengthInBytes = $fileEncryptionKeyLengthInBits / 8)) { + throw new ParseFailureException('Unsupported file encryption key length in bits: ' . $fileEncryptionKeyLengthInBits); + } + + $hashedString = + $this->getPaddedUserPassword() // step a+b + . $encryptDictionary->getOwnerPasswordEntry() // step c + . pack('V', $encryptDictionary->getPValue()) // step d + . $firstIDValue; // step e + if ($encryptDictionary->getStandardSecurityHandlerRevision()->value >= 4 && $encryptDictionary->isMetadataEncrypted() === false) { + $hashedString .= "\xFF\xFF\xFF\xFF"; + } + + $md5Hash = md5($hashedString, true); + if ($encryptDictionary->getStandardSecurityHandlerRevision() === StandardSecurityHandlerRevision::v2) { + return substr($md5Hash, 0, 5); + } + + for ($i = 1; $i <= 50; $i++) { // step h + $md5Hash = md5(substr($md5Hash, 0, $fileEncryptionKeyLengthInBytes), true); + } + + return substr($md5Hash, 0, $fileEncryptionKeyLengthInBytes); + } + + private function getOwnerFileEncryptionKey(EncryptDictionary $encryptDictionary): string { + if (in_array($encryptDictionary->getStandardSecurityHandlerRevision(), [StandardSecurityHandlerRevision::v2, StandardSecurityHandlerRevision::v3, StandardSecurityHandlerRevision::v4], true) === false) { + throw new NotImplementedException('Unsupported security handler revision: ' . $encryptDictionary->getStandardSecurityHandlerRevision()->value); + } + + $fileEncryptionKeyLengthInBits = $encryptDictionary->getLengthFileEncryptionKeyInBits() ?? throw new ParseFailureException(); + if ($encryptDictionary->getSecurityAlgorithm() === SecurityAlgorithm::AES_Key_length_256) { // V = 4 + throw new NotImplementedException('AES-based stream decryption is not yet supported.'); + } + + if ($fileEncryptionKeyLengthInBits % 8 !== 0 || !is_int($fileEncryptionKeyLengthInBytes = $fileEncryptionKeyLengthInBits / 8)) { + throw new ParseFailureException('Unsupported file encryption key length in bits: ' . $fileEncryptionKeyLengthInBits); + } + + $md5Hash = md5($this->getPaddedOwnerPassword(), true); + if ($encryptDictionary->getStandardSecurityHandlerRevision() !== StandardSecurityHandlerRevision::v2) { + for ($i = 1; $i <= 50; $i++) { // step c + $md5Hash = md5($md5Hash, true); + } + } + + if ($encryptDictionary->getStandardSecurityHandlerRevision() === StandardSecurityHandlerRevision::v2) { + return substr($md5Hash, 0, 5); + } + + return substr($md5Hash, 0, $fileEncryptionKeyLengthInBytes); + } + + /** @see 7.6.4.3.2 step a */ + public function getPaddedUserPassword(): string { + return substr($this->userPassword ?? '', 0, self::PASSWORD_LENGTH) + . substr(self::PADDING_STRING, 0, max(0, self::PASSWORD_LENGTH - strlen($this->userPassword ?? ''))); + } + + /** @see 7.6.4.3.2 step a */ + public function getPaddedOwnerPassword(): string { + return substr($this->ownerPassword ?? $this->userPassword ?? '', 0, self::PASSWORD_LENGTH) + . substr(self::PADDING_STRING, 0, max(0, self::PASSWORD_LENGTH - strlen($this->ownerPassword ?? $this->userPassword ?? ''))); + } +} diff --git a/includes/pdfparser/Document/Security/StandardSecurityHandlerRevision.php b/includes/pdfparser/Document/Security/StandardSecurityHandlerRevision.php new file mode 100644 index 0000000..f557320 --- /dev/null +++ b/includes/pdfparser/Document/Security/StandardSecurityHandlerRevision.php @@ -0,0 +1,13 @@ +read(0, Marker::VERSION->length()) !== Marker::VERSION->value) { + throw new ParseFailureException('Unexpected start of file format. is this a pdf?'); + } + + $versionString = $stream->read(strlen(Marker::VERSION->value), Version::length()); + $version = Version::tryFrom($versionString); + if ($version === null) { + throw new ParseFailureException(sprintf('Unsupported PDF version "%s"', $versionString)); + } + + return $version; + } +} diff --git a/includes/pdfparser/Exception/AuthenticationFailedException.php b/includes/pdfparser/Exception/AuthenticationFailedException.php new file mode 100644 index 0000000..fd4e1a1 --- /dev/null +++ b/includes/pdfparser/Exception/AuthenticationFailedException.php @@ -0,0 +1,6 @@ +parse($stream, $security); + } + + /** + * @param bool $useFileCache if set to true, the file will be cached to a temporary file. This will use less memory, but will be significantly slower + * @throws PdfParserException + */ + public function parseString(string $content, bool $useFileCache = false, ?StandardSecurity $security = null): Document { + if ($useFileCache) { + $stream = FileStream::fromString($content); + } else { + $stream = new InMemoryStream($content); + } + + return $this->parse($stream, $security); + } +} diff --git a/includes/pdfparser/Stream/AbstractStream.php b/includes/pdfparser/Stream/AbstractStream.php new file mode 100644 index 0000000..0267ff0 --- /dev/null +++ b/includes/pdfparser/Stream/AbstractStream.php @@ -0,0 +1,65 @@ +firstPos($needle, $offsetFromStart, $before); + if ($markerPos === null) { + return null; + } + + return $this->getStartOfNextLine($markerPos, $before); + } + + #[Override] + public function getStartOfNextLine(int $byteOffset, int $before): ?int { + $firstLineFeedPos = $this->firstPos(WhitespaceCharacter::LINE_FEED, $byteOffset, $before); + $firstCarriageReturnPos = $this->firstPos(WhitespaceCharacter::CARRIAGE_RETURN, $byteOffset, $before); + if ($firstLineFeedPos === null && $firstCarriageReturnPos === null) { + return null; + } + + if ($firstCarriageReturnPos === null) { + return $firstLineFeedPos + 1; + } + + if ($firstLineFeedPos === null) { + return $firstCarriageReturnPos + 1; + } + + return min($firstLineFeedPos, $firstCarriageReturnPos) + + (abs($firstCarriageReturnPos - $firstLineFeedPos) === 1 ? 2 : 1); // If the CR and LF are next to each other, we need to add 2 bytes, otherwise 1 + } + + #[Override] + public function getEndOfCurrentLine(int $byteOffset, int $before): ?int { + $firstLineFeedPos = $this->firstPos(WhitespaceCharacter::LINE_FEED, $byteOffset, $before); + $firstCarriageReturnPos = $this->firstPos(WhitespaceCharacter::CARRIAGE_RETURN, $byteOffset, $before); + if ($firstLineFeedPos === null && $firstCarriageReturnPos === null) { + return null; + } + + if ($firstCarriageReturnPos === null) { + return $firstLineFeedPos; + } + + if ($firstLineFeedPos === null) { + return $firstCarriageReturnPos; + } + + return min($firstLineFeedPos, $firstCarriageReturnPos); + } + + #[Override] + public function toString(): string { + return $this->read(0, $this->getSizeInBytes()); + } +} diff --git a/includes/pdfparser/Stream/FileStream.php b/includes/pdfparser/Stream/FileStream.php new file mode 100644 index 0000000..498c54f --- /dev/null +++ b/includes/pdfparser/Stream/FileStream.php @@ -0,0 +1,157 @@ +handle = $handle; + } + + public static function openFile(string $path): self { + $handle = fopen($path, 'rb'); + if ($handle === false) { + throw new InvalidArgumentException(sprintf('Failed to open file at path "%s"', $path)); + } + + return new self($handle); + } + + public static function fromString(string $content): self { + $handle = fopen('php://temp', 'rb+'); + if ($handle === false) { + throw new RuntimeException('Unable to create file handle to temp'); + } + + fwrite($handle, $content); + rewind($handle); + + return new self($handle); + } + + #[Override] + public function getSizeInBytes(): int { + $stats = fstat($this->handle); + if ($stats === false) { + throw new RuntimeException('Unable to retrieve file information'); + } + + return $stats['size']; + } + + #[Override] + public function read(int $from, int $nrOfBytes): string { + if ($nrOfBytes <= 0) { + throw new InvalidArgumentException(sprintf('$nrOfBytes must be greater than 0, %d given', $nrOfBytes)); + } + + fseek($this->handle, $from); + + $bytes = fread($this->handle, $nrOfBytes); + if ($bytes === false) { + throw new RuntimeException('Unable to read from handle'); + } + + return $bytes; + } + + #[Override] + public function slice(int $startByteOffset, int $endByteOffset): string { + if ($startByteOffset <= 0) { + throw new InvalidArgumentException(sprintf('$startByteOffset must be greater than 0, %d given', $startByteOffset)); + } + + if ($endByteOffset - $startByteOffset < 1) { + throw new InvalidArgumentException(sprintf('End byte offset %d should be bigger than start byte offset %d', $endByteOffset, $startByteOffset)); + } + + fseek($this->handle, $startByteOffset); + + $bytes = fread($this->handle, $endByteOffset - $startByteOffset); + if ($bytes === false) { + throw new RuntimeException('Unable to read bytes from handle'); + } + + return $bytes; + } + + #[Override] + public function chars(int $from, int $nrOfBytes): iterable { + if ($from < 0) { + throw new InvalidArgumentException(sprintf('StartOffset should be greater than zero, %d given', $from)); + } + + if ($nrOfBytes <= 0) { + throw new InvalidArgumentException(sprintf('$nrOfBytes to read must be greater than 0, %d given', $nrOfBytes)); + } + + $bytesRead = 0; + while ($bytesRead < $nrOfBytes) { + fseek($this->handle, $from + $bytesRead); + $bytes = fread($this->handle, 1); + if ($bytes === false) { + throw new RuntimeException('Unable to read bytes from stream'); + } + yield $bytes; + $bytesRead++; + } + } + + #[Override] + public function firstPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromStart, int $before): ?int { + $rollingCharBuffer = new RollingCharBuffer($needleLength = strlen($needle->value)); + while ($offsetFromStart < $before) { + fseek($this->handle, $offsetFromStart); + $character = fgetc($this->handle); + if ($character === false) { + throw new RuntimeException('Unable to get char from stream'); + } + $rollingCharBuffer->next($character); + $offsetFromStart++; + if ($rollingCharBuffer->seenString($needle->value)) { + return $offsetFromStart - $needleLength; + } + } + + return null; + } + + #[Override] + public function lastPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromEnd): ?int { + $rollingCharBuffer = new RollingCharBuffer(strlen($needle->value)); + $offsetFromEnd++; + while (fseek($this->handle, - $offsetFromEnd, SEEK_END) !== -1) { + $character = fgetc($this->handle); + if ($character === false) { + throw new RuntimeException('Unable to get character from stream'); + } + $rollingCharBuffer->next($character); + $offsetFromEnd++; + if ($rollingCharBuffer->seenReverseString($needle->value)) { + return $this->getSizeInBytes() - $offsetFromEnd + 1; + } + } + + return null; + } + + public function __destruct() { + fclose($this->handle); + } +} diff --git a/includes/pdfparser/Stream/InMemoryStream.php b/includes/pdfparser/Stream/InMemoryStream.php new file mode 100644 index 0000000..6f3d0fe --- /dev/null +++ b/includes/pdfparser/Stream/InMemoryStream.php @@ -0,0 +1,79 @@ +content); + } + + #[Override] + public function read(int $from, int $nrOfBytes): string { + if ($nrOfBytes <= 0) { + throw new InvalidArgumentException(sprintf('$nrOfBytes must be greater than 0, %d given', $nrOfBytes)); + } + + return substr($this->content, $from, $nrOfBytes); + } + + #[Override] + public function slice(int $startByteOffset, int $endByteOffset): string { + if ($startByteOffset <= 0) { + throw new InvalidArgumentException(sprintf('$startByteOffset must be greater than 0, %d given', $startByteOffset)); + } + + if ($endByteOffset - $startByteOffset < 1) { + throw new InvalidArgumentException(sprintf('End byte offset %d should be bigger than start byte offset %d', $endByteOffset, $startByteOffset)); + } + + return substr($this->content, $startByteOffset, $endByteOffset - $startByteOffset); + } + + #[Override] + public function chars(int $from, int $nrOfBytes): iterable { + if ($from < 0) { + throw new InvalidArgumentException(sprintf('$from must be greater than zero, %d given', $from)); + } + + if ($nrOfBytes <= 0) { + throw new InvalidArgumentException(sprintf('$nrOfBytes to read must be greater than zero, %d given', $nrOfBytes)); + } + + foreach (str_split(substr($this->content, $from, $nrOfBytes)) as $char) { + yield $char; + } + } + + #[Override] + public function firstPos(WhitespaceCharacter|DelimiterCharacter|ToUnicodeCMapOperator|Marker $needle, int $offsetFromStart, int $before): ?int { + $firstPos = strpos($this->content, $needle->value, $offsetFromStart); + if ($firstPos === false || $firstPos > $before) { + return null; + } + + return $firstPos; + } + + #[Override] + public function lastPos(WhitespaceCharacter|DelimiterCharacter|ToUnicodeCMapOperator|Marker $needle, int $offsetFromEnd): ?int { + $pos = strrpos($this->content, $needle->value, -$offsetFromEnd); + if ($pos === false) { + return null; + } + + return $pos; + } +} diff --git a/includes/pdfparser/Stream/Stream.php b/includes/pdfparser/Stream/Stream.php new file mode 100644 index 0000000..b40f438 --- /dev/null +++ b/includes/pdfparser/Stream/Stream.php @@ -0,0 +1,41 @@ + $nrOfBytes */ + public function read(int $from, int $nrOfBytes): string; + + public function toString(): string; + + /** + * @phpstan-assert int<0, max> $startByteOffset + * @phpstan-assert int<0, max> $endByteOffset + */ + public function slice(int $startByteOffset, int $endByteOffset): string; + + /** + * @phpstan-assert int<0, max> $from + * @phpstan-assert int<1, max> $nrOfBytes + * + * @return iterable + */ + public function chars(int $from, int $nrOfBytes): iterable; + + public function firstPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromStart, int $before): ?int; + + public function lastPos(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromEnd): ?int; + + public function getStartNextLineAfter(WhitespaceCharacter|Marker|DelimiterCharacter|ToUnicodeCMapOperator $needle, int $offsetFromStart, int $before): ?int; + + public function getStartOfNextLine(int $byteOffset, int $before): ?int; + + public function getEndOfCurrentLine(int $byteOffset, int $before): ?int; +} diff --git a/includes/pdfparser_autoloader.php b/includes/pdfparser_autoloader.php new file mode 100644 index 0000000..d388162 --- /dev/null +++ b/includes/pdfparser_autoloader.php @@ -0,0 +1,19 @@ + get_env_var('MAIL_TRANSPORT', 'smtp'), - 'smtp_host' => get_env_var('SMTP_HOST'), - 'smtp_port' => get_env_var('SMTP_PORT', 587), - 'smtp_secure' => get_env_var('SMTP_SECURE', 'tls'), // tls or ssl - 'smtp_user' => get_env_var('SMTP_USER'), - 'smtp_pass' => get_env_var('SMTP_PASS'), - 'from_email' => get_env_var('MAIL_FROM'), - 'from_name' => get_env_var('MAIL_FROM_NAME'), - 'reply_to' => get_env_var('MAIL_REPLY_TO'), + + 'transport' => mail_get_env_var('MAIL_TRANSPORT', 'smtp'), + + 'smtp_host' => mail_get_env_var('SMTP_HOST'), + + 'smtp_port' => mail_get_env_var('SMTP_PORT', 587), + + 'smtp_secure' => mail_get_env_var('SMTP_SECURE', 'tls'), // tls or ssl + + 'smtp_user' => mail_get_env_var('SMTP_USER'), + + 'smtp_pass' => mail_get_env_var('SMTP_PASS'), + + 'from_email' => mail_get_env_var('MAIL_FROM'), + + 'from_name' => mail_get_env_var('MAIL_FROM_NAME'), + + 'reply_to' => mail_get_env_var('MAIL_REPLY_TO'), + + // Optional DKIM signing - 'dkim_domain' => get_env_var('DKIM_DOMAIN', ''), - 'dkim_selector' => get_env_var('DKIM_SELECTOR', 'default'), - 'dkim_private_key_path' => get_env_var('DKIM_PRIVATE_KEY_PATH', ''), // Path to the private key file + + 'dkim_domain' => mail_get_env_var('DKIM_DOMAIN', ''), + + 'dkim_selector' => mail_get_env_var('DKIM_SELECTOR', 'default'), + + 'dkim_private_key_path' => mail_get_env_var('DKIM_PRIVATE_KEY_PATH', ''), // Path to the private key file + ]; diff --git a/uploads/kb_documents/695f78e68b6ef_Opis_dzia__ania_algorytmu_losuj__cego.pdf b/uploads/kb_documents/695f78e68b6ef_Opis_dzia__ania_algorytmu_losuj__cego.pdf new file mode 100644 index 0000000000000000000000000000000000000000..df1d156cffd5db6145aad9ad9a2aa7316d179ba8 GIT binary patch literal 177005 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.`O}%aKQ?W6}K5BYRTpXlVfWQ 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%0AbYU%h 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 literal 0 HcmV?d00001 diff --git a/uploads/kb_documents/695f79d3473af_test.pdf b/uploads/kb_documents/695f79d3473af_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..774c2ea70c55104973794121eae56bcad918da97 GIT binary patch literal 13264 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 literal 0 HcmV?d00001 diff --git a/uploads/kb_documents/695f79de3ea94_test.pdf b/uploads/kb_documents/695f79de3ea94_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..774c2ea70c55104973794121eae56bcad918da97 GIT binary patch literal 13264 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 literal 0 HcmV?d00001 diff --git a/uploads/kb_documents/695f79e4205ab_test.pdf b/uploads/kb_documents/695f79e4205ab_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..774c2ea70c55104973794121eae56bcad918da97 GIT binary patch literal 13264 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 literal 0 HcmV?d00001 diff --git a/uploads/kb_documents/695f9142a7bc4_695f79ef7d3a7_test.pdf b/uploads/kb_documents/695f9142a7bc4_695f79ef7d3a7_test.pdf new file mode 100644 index 0000000000000000000000000000000000000000..774c2ea70c55104973794121eae56bcad918da97 GIT binary patch literal 13264 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 literal 0 HcmV?d00001