From 68bcaf39018d553f2bd01599ebd458b9e221cd21 Mon Sep 17 00:00:00 2001 From: Eliot Jones Date: Tue, 8 Oct 2019 14:04:36 +0100 Subject: [PATCH] #55 move support for images to page and add inline images support both xobject and inline images. adds unsupported filters so that exceptions are only thrown when accessing lazily evaluated image.bytes property rather than when opening the page. treat all warnings as errors. --- .../Operations/GraphicsStateOperationTests.cs | 6 +- .../Graphics/TestOperationContext.cs | 12 + .../Graphics/TestResourceStore.cs | 33 --- ...Single Page Images - from libre office.pdf | Bin 0 -> 89006 bytes .../Integration/IntegrationDocumentTests.cs | 6 +- .../Integration/OldGutnishTests.cs | 2 +- .../SinglePageLibreOfficeImages.cs | 87 +++++++ .../PublicApiScannerTests.cs | 2 + .../TestFilterProvider.cs | 5 + .../Tokens/TestPdfTokenScanner.cs | 4 + src/UglyToad.PdfPig/Content/IPdfImage.cs | 94 ++++++++ src/UglyToad.PdfPig/Content/IResourceStore.cs | 2 + src/UglyToad.PdfPig/Content/InlineImage.cs | 98 ++++++++ src/UglyToad.PdfPig/Content/Page.cs | 20 +- src/UglyToad.PdfPig/Content/PageContent.cs | 39 ++-- ...{ResourceContainer.cs => ResourceStore.cs} | 58 ++++- src/UglyToad.PdfPig/Filters/Ascii85Filter.cs | 2 - .../Filters/CcittFaxDecodeFilter.cs | 15 ++ .../Filters/DctDecodeFilter.cs | 3 +- .../Filters/IFilterProvider.cs | 2 + .../Filters/Jbig2DecodeFilter.cs | 15 ++ .../Filters/JpxDecodeFilter.cs | 15 ++ .../Filters/MemoryFilterProvider.cs | 30 ++- .../Graphics/ContentStreamProcessor.cs | 104 +++++++-- .../Graphics/IOperationContext.cs | 17 +- .../Graphics/InlineImageBuilder.cs | 213 ++++++++++++++++++ .../InlineImages/BeginInlineImage.cs | 1 + .../InlineImages/BeginInlineImageData.cs | 22 +- .../Operations/InlineImages/EndInlineImage.cs | 15 +- .../Graphics/XObjectContentRecord.cs | 7 +- .../Parser/PageContentParser.cs | 19 +- src/UglyToad.PdfPig/Parser/PageFactory.cs | 6 +- .../Parser/PdfDocumentFactory.cs | 5 +- src/UglyToad.PdfPig/PdfDocument.cs | 1 + .../Tokenization/Scanner/IPdfTokenScanner.cs | 18 ++ .../Tokenization/Scanner/PdfTokenScanner.cs | 50 ++-- .../Tokens/NameToken.Constants.cs | 3 +- src/UglyToad.PdfPig/UglyToad.PdfPig.csproj | 5 + src/UglyToad.PdfPig/Util/OtherEncodings.cs | 12 + .../XObjects/XObjectFactory.cs | 118 +++++++++- src/UglyToad.PdfPig/XObjects/XObjectImage.cs | 86 +++++-- 41 files changed, 1083 insertions(+), 169 deletions(-) delete mode 100644 src/UglyToad.PdfPig.Tests/Graphics/TestResourceStore.cs create mode 100644 src/UglyToad.PdfPig.Tests/Integration/Documents/Single Page Images - from libre office.pdf create mode 100644 src/UglyToad.PdfPig.Tests/Integration/SinglePageLibreOfficeImages.cs create mode 100644 src/UglyToad.PdfPig/Content/IPdfImage.cs create mode 100644 src/UglyToad.PdfPig/Content/InlineImage.cs rename src/UglyToad.PdfPig/Content/{ResourceContainer.cs => ResourceStore.cs} (67%) create mode 100644 src/UglyToad.PdfPig/Filters/CcittFaxDecodeFilter.cs create mode 100644 src/UglyToad.PdfPig/Filters/Jbig2DecodeFilter.cs create mode 100644 src/UglyToad.PdfPig/Filters/JpxDecodeFilter.cs create mode 100644 src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs create mode 100644 src/UglyToad.PdfPig/Tokenization/Scanner/IPdfTokenScanner.cs diff --git a/src/UglyToad.PdfPig.Tests/Graphics/Operations/GraphicsStateOperationTests.cs b/src/UglyToad.PdfPig.Tests/Graphics/Operations/GraphicsStateOperationTests.cs index 1aacdee0..b7c8c97c 100644 --- a/src/UglyToad.PdfPig.Tests/Graphics/Operations/GraphicsStateOperationTests.cs +++ b/src/UglyToad.PdfPig.Tests/Graphics/Operations/GraphicsStateOperationTests.cs @@ -44,7 +44,11 @@ } else if (operationType == typeof(EndInlineImage)) { - operation = new EndInlineImage(new List(), new List()); + operation = new EndInlineImage(new List()); + } + else if (operationType == typeof(BeginInlineImageData)) + { + operation = new BeginInlineImageData(new Dictionary()); } else { diff --git a/src/UglyToad.PdfPig.Tests/Graphics/TestOperationContext.cs b/src/UglyToad.PdfPig.Tests/Graphics/TestOperationContext.cs index 508e8c20..9aceb9d9 100644 --- a/src/UglyToad.PdfPig.Tests/Graphics/TestOperationContext.cs +++ b/src/UglyToad.PdfPig.Tests/Graphics/TestOperationContext.cs @@ -77,5 +77,17 @@ public void SetNamedGraphicsState(NameToken stateName) { } + + public void BeginInlineImage() + { + } + + public void SetInlineImageProperties(IReadOnlyDictionary properties) + { + } + + public void EndInlineImage(IReadOnlyList bytes) + { + } } } diff --git a/src/UglyToad.PdfPig.Tests/Graphics/TestResourceStore.cs b/src/UglyToad.PdfPig.Tests/Graphics/TestResourceStore.cs deleted file mode 100644 index 66778454..00000000 --- a/src/UglyToad.PdfPig.Tests/Graphics/TestResourceStore.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace UglyToad.PdfPig.Tests.Graphics -{ - using Content; - using PdfPig.Fonts; - using PdfPig.Tokens; - - internal class TestResourceStore : IResourceStore - { - public void LoadResourceDictionary(DictionaryToken dictionary, bool isLenientParsing) - { - } - - public IFont GetFont(NameToken name) - { - return null; - } - - public StreamToken GetXObject(NameToken name) - { - return null; - } - - public DictionaryToken GetExtendedGraphicsStateDictionary(NameToken name) - { - return null; - } - - public IFont GetFontDirectly(IndirectReferenceToken fontReferenceToken, bool isLenientParsing) - { - return null; - } - } -} \ No newline at end of file diff --git a/src/UglyToad.PdfPig.Tests/Integration/Documents/Single Page Images - from libre office.pdf b/src/UglyToad.PdfPig.Tests/Integration/Documents/Single Page Images - from libre office.pdf new file mode 100644 index 0000000000000000000000000000000000000000..98ebd288565b936a9de15212a3d1e3ef243e483d GIT binary patch literal 89006 zcmb@tQ+Op!^gbHf_QbY5v7L!Mv9)8{+Tp~uZA@%WY)@=w&i;PK&-1@H7w4wCtGa7d zm)3gMTeYZEB&3+wm^l%s28#!~2U`d85ZFmsNgaRMAP5SwD407~0<1_mKsjnGQr30= za~BpVI}?Dpgt@7snYpkqf-Atq+{7NiGuu2t01<)>E%f~(x&r#Z!7#nwIjlTC2dy5B z!6-GF`^%5#_yG!@acynQ5n~~?tTW{B<*-E*P)$K1-GaZjt_+vZlxcAk61ysS2uY0y zLwLB@N_90-O0_84a5$#^!o9^@wGsY-Lf zV$;~oJLOPbE&{h{Wwc#nZv^|}60vd{VqE$B_qXVA8G?68;Ve9A&hN#ASn=h-c-^J2 zuc)Wcdc=|`2<8rE|3_0mPye+Z;lD=z_rS)-{@>gG$>aRLyXJ?NlR1mF@=qIcQvi#) z+fTrMX<2&{OLG=2Yco)%vGZ}V$e3GOS^@s&QOp|Ps$%XU?r87i=wR*uAm#l(dzbsa z-bMIdbT5mzqn)FRx|4~iIg5n3yS1sgnzR@Ki@KtTt1T(d|CIWFj=-fsgb&v8`g6cf z4|P(qVjfa5odtKEHAnuC(kVGip z^%k*qIN!-s_OG0^Uu#0w_ddR?e80T?@BiHAt{9pY_5n0CTGCRv(o?-ACv?F|N~TLn zdIP$)w);^(|G|9_evaRRQeZExyph)tH;(C(dWI-vbMMT*Sm#9rnQ>L}hi6&7aeZXl zz{B&(al4j<|LfFObZFb24S~nxItt&qTGY(`eaAo?0>NpjqhsskW$V_mJsS&;>9wF~ zxc%`HB@0XV{g30?-%rM0tON2*c}8SSsNx9iEemQ1ixtlt78qyy6_s~s=~=qzchd-t$w%FX5j=l4m1;fSD8yc=|?Cx9yk_rFy8O7^kIC z_$$+Vy)j{!n?lbRqTEX4cfs#i^Zg6;m;6l!9D^tkRr!jr68>a)Au1y#&o6}?$#BkB z+$N~m78&YH-KBe~yNSuiTy%wIjF)m4qvx?mDRH3=cMHM;1J9_kO8PNUN=u=?!g-3J z)W7KK)-D~)zH(m8n?Wx!zN_1a-YKO`KmxZbD&~Fuu&UDb{{GrUlG(DfTMgWvJdm4z z$Q4jHlvPL5H-#y7Pc*R!wx}+j^C4kN!f{&5oW#Nrmr@d&X6&|wDN0$kB+%}E*G!wY z(C1*k)^;uhFR4;6F+v~VQoor&EEQIF4@V#VA~?tt6`2$$y?DU!)R~x)!69TL&7|#i z^xQ&bEG$fZdBdJeNMxZP-Vdg2aJ_hJ=T)p(3ovR1Br^_>M zX@^oykrrIY$qO-N7Xm#G^?(X3VN}Jb#7(RrX@MbA-P|xJz}%c|*FV+lHf@tkG8SoO zp(5n5XYNfFel8`$XJ(=pFwT+M88-}n#)AykiBNQqWizPo8_?QV2^kn3b0?`2)1nb9 zWZCD$H-FoiJLHs)A0>+}52k zA2$F-I^D^tH#BkQfCK~1rhMXlK8>*)YRv;9E=tK)%cJ!J?X3nJZwpThM(|8dK1;Fo z>@*~~TH+Vk5NQp&70+d7uIk@iO3)uOlP1j+6X(*Mjj(Sa@k8VlbC~FE%C9tM&&P>+ z7|UALN(w!6DK=QahoA>~xGRY9?@;r`Iv|xoa0AY`qQs>ucD0*B2UPl{RAYAt!hjA_ zex|IXH>y~WmZyKCWAL}|6p$Iu2;wpeitI3lHhEwMA+G1}*>yzlnM|$-Lk5D}PGi#; zt@1Z=L)Ma!O2Pi~)r;RJ9bSkxep1>j!&j8nOEzl8?UPGJZMW~%Q6EttTo*rLIOKsufNFGd2roNn< z7D`L^=%}w(x@L$a>Msh1MM>~8QbOqt?mrLIf@Fa+OitQ?)|?K@U(St!pbo&nu8~tz zjE*Ltpl@X}WjFmvzkh&BLo>J$p@rHc_8fx>sn1Lw908wzMPldWD=27|^^%3h0b_{M z-Do>J+-W3;vx*j73b;??kwaT;>x4W_qaWr%|7G0Nlbcx2EcDs6x!LEI1J;5vn&9P? zXJPq|fx=WrZX$a~BBN&!1LK&AYT(~*?B8|%6O&a;bn~CT219~Jq0^xfmV{kqURWGgw7$38jaH-7nrA9yr{6#ATRCQW$Tn{M8l?SGB8##k`X8 zB`lOO!1#}RMMcMQIRw!>8E>3+(JNm1t1vMDd1Bi6HcDM3uGI7ezNY{wy&8I@z`*|M z?UY{?Nq@RjRKR?lhlb}}Vj1!0=VR2>o=%R1op?FD$8ONLEzJ6(et1?#O6W1(V7TSU zXy>~c7I8sy0yrXrLn^96vF^zt8NW3G4UYp+4*^w-}}>eGELvt z+qp)SW@>6GxX@V{S~i^Ui&jkZDNEh<*@yexp+29bs{FoPYs=Xg&!OJ1R&?|(x49p| zF5B-x{T^>FL2b|Oe;@a#f;JZnR*sHsy?>ztpS`s>e0V*LyZJN@Xi_ApJHNvUt595? z&KIq!jAC_8L-cLe#gO@FS zh9!Iy8Wov@SWv_>HU;(LW^XX~Kp{42-VY%PQXKpdrz%r^z*!pzB=?MxPh&*yJ{=es zn3+);f4$iqfP9flPfwShY5N-*niWbbAdFoLQ2Ycp*3`S4n90QFru=&JW%@KW>0)PVr_XD zI>F#(&|}+6+DYU_$SqH7e~#IT&!#O)X#gBFc@PZuUv3ZH-`^n}UmwmE>HIZ=VUv=R zIqcV)QyH|bHaqSw)|wt4A6M60{(bp5@nc|ON*)_pC`?^PP6h~m|F%i10~m{phv5QP zO<-Y!L6^Jn#vh%83}>)#$e~Wyx>`_k7khQoI{{B=$c2T!dY-o-v$BU+SSeTxyW9QV z-FH(4_hfF8aY+OOKl<@J=?>6$Zgx}XI+e*}^BM6pO!FWxv47ow*Aiut!r`T{V$QWq z;X2*mGHQeE*sC}N>nv?-ybt3VDUhDLFBBHl^*EjP_hnIxeuCQ~0MRYS?4JYT@`P`Kz!8Pj}V z-1i|wmb10u3RwuCEwkz6ge}O6Gx%JopVO|lx}=5hNBc)#tLl31yn7(dPm`5}MJepE z6^I3Jj**;b(q=vzbQq0^NTw&%c?MC||7e{^8GNbYe4C{^jZmVI)fyQYaoA|B>FOe+ zA`sN52d7rdlyGInoBGsnb8FevYQn^H%M(md5mKBJn;##lpM>g_Mv|{uo_HnLU9z;a zRG$%78i^xpGZ};dzrNZ8H~8BhuUuNiCLbHUo{{8iFj-j2U*@W;q(qjZgZOroTwRTP zcDB0ndwRRaM@xkBW>P7KpUZRv3DGSRl0+C+3tW6F+BZePM9rn2krvKji%3x?oef*R zs85afv%5~OEk28;rKN>c7K8RTgfG5;`vs_SA8&7ndt?g~-&J$sP|RHd+JSYnOyylA zrLry~JNNgZ1YwNOZ$Y-w@kHP(8DF&r`}_Yb{FC^4i)n%ZuopFaFX$k!3}|T4Vt6U7 zYSy~ogL-}@WWNJrw7W9oIauocwI<5}fbVtB^TgyN)xGmgl(5GY(Ld7NrMX%Dj2|0~ zjaTPChM~F{5)#V#bd~x`u*td%He!Qb)7h8oU2# zpv9Y{4&$V{x_HHk3@J(Lh5(yScgLQyB$( zx{Z4w;^Y))ZU+TCd$~VBMYqfO`hL9LpX^y9AtMJh5=y(n-{0MVwD0ovHuI}-L+yk9 z{gA?oN|!kw0`#0tSCO~E#|8Yvmh2xgh4V*(S4}V6RA835w8e4J2Vg*({-x65*gYv9c0kV zG-681%DxGDstj<1MkJ8fKUdLtx`M~$jTP!)hQV6ifCkKsS&JH2SzBAHInr(tpcuw` z#^}HN)PGZnJY2A;KR(0SOW3Uar_N}07C45%d5&yEgGmLgk{@0ot@H^$?c&&!kwVm) z!K-=`y|}QDGL4gAL4Gxb7hwKH!Vyqghl`sYlhzNuHD?yYD4I^?&*xlt#X~|FEITF3 z?@Y~3#~0X%zA8UIKmUL}85qcro|nhMPKY=X*JaoG!i}K9b6r*vrJVs9h0n(oqty9J zknw8Of-H6-zw8tbMT$n2cl7e&{gncqzg2Rz)5ntT&>?e#X9R*8VFOd}yC6j3eAqd+ zx~k1=PO4>NYYQC#A)xu3g-jB?ay?hPH)XEo;zX9a^7Z+Cb`rI6jy>;ile(SxtKwa# zG9D2cI?Aj~3IDA3^GR>O83dNx`qVY@r|MHSOD6!-R1wcf(JZ2LVLv;A4qCtSXgw1y z!3E&r#xPz%-rs@dLL**;CStXs{Ng9KMi9hcV@oGLqoQ1Y5}i5}%eqYqH#hgfoZ?W1 zLR7qPl}#BVKUD_&em>uCE=LcGn8nT3d8>NuGEI~0FcOPT z{@L~R_NHRQ`^RS4Ll1U2h#-v|V2iRyOZ5%WSz|QFywZ2AFW`F-Ap+@^Ap4nSwYF#@ z4i1Z9Y`hURGg~s4%Ai`Ymz`%;3Q;XKwuv4K2sivP+CX|rh^vpKVxX4lKG0u<^yN=V z^Zpj_(Gchm5)x9SRVO7)Eh)K$GD;(RJ>ga2&6u%XjI^sR7LlBpNx)3Ay||d1loUja zY1tI!azZc&>S?lt((6Ig??}GAf6ve7>E)E%6?3cKh4eRRboj%_ln=EIkx#oNX=wzSu=HOCli4-@On*^M}1KBtv zzx*xpEG^IdkHUZBcjYt+<%`Je@5lC8sIaMMp>$Q5dOm39!}=D|Oq|II&t@ZoH;?cD zw&-3Vj;*vi9ewjR2cYOF2BQuRG-cs+?V?g+Ppt+WNXrF|qg%spWsY8MrB+3BZ0Fit z(D#SnRZd*&XQr@r2AD={KDT6ugJNNf(WaFuZW8xYSI+%f3WEAm#h*J74qW62^jYY1 z@^Y12WZg3Y8jAn$rsc8h&kS$affP@jgc=N{+mT=$cSEtYB0jeXCZjvrN764$>QjIF zQZQZ7(eb;bCAfK8h zvY>TdC>apn{l2uM!mm~=$n9uSa3qdRv5XjK*5qhobK3dKIEYVP%A}+Ty%=mDA*!V> zj*p{MRZX(}C`2=01GqfND-M2VIR6`VYl3=}Y9gRy?FWrgH?@$Tv)sh`!-EhXpGR{s zH<7Q0(v0rEv)y8+zV)0dFq1O}D5<=;UD-f~+%c4_AN%~n<|9K^d)MCq`~yQ`8;?Ue zHCgN&=S!MUPW{O3iv!BL?3@#<>(0Z1%!^NYnn)?m_9(C=viKt=$=_q5tJ}>sEeU*+ z|0T(_cO4vPED7h!%1e6GxD1clCdH0u7W7kaFEP^;1O-Y2O%qX_;f%y?M92IJL{Qnf zs-&S+PvqJ5G=09>I{#A>6CauvAq7pDR)rhv1Lcg;9R@?jSRPxlC*@k1sQ6ieGg$>6x@K{-ELuu>n;I1TV5;-#@wP6nbD6UMMa&q>0GF3A zk6UH+qIClf`~*3mdTKCPe_J^B z)8m9UZgmMRM?w>wOG?o7_MyiHvA=!U#3Cy@V&R53K%|-AVwyk$5Mp6J&7q9WaP^f` zVhEH%Aem}n&4{()^=9vCwl|cs8#Zt$siH=;hp5(n{>sZJzb-o<+?;re9@7&JY@jbe z8;SM7UAejR`*i1otksCDB}=8sCn%$$v{F?rd|38~`3deIH=vc%R_G77kL;c6G@nJUjEB_D>5uKNp$T6_7Q*&~JocD)f zW04mA*inLbim=i(IQKm=Uk!35uDM87rABK*PmbCo;}6Eg(j}F^e+ror73;v{5q{(ODk*7}Wrpc4e83&bPRm<=)&wA%Q#EjG%ev|yc$V`L`DS?m_Z07MXWmIcM- zw2FNrkB7{kz74uHh&YVAApaoGoD!diNYM3o3WS_%)EHqLoejJ@=|5v+JRXaeXE~O?^yZ%*KIKTU%T73vtOH3>=1t z8=dXp@Ut*lc&odZ@&RieXVMK4EB6c{4SaOpep3+Si1t+#9}`IMOvXn=y?(u4Yn%`m zfxHNF3KkWE^xWDBdqaB0q1*#vWd5s6=&8z~{eZBrRj@o=x4=9MZcRv8S=kQX*Tz!7 z!C~8uKcRBus`d|?{D>O$FxaX;5tFgTRcSe$Q;J|pN&ogrxDI_GM z?Mkg1e-6kGDeKF&yNHW{i!UuhflV zZjOh^%0@fhyUW6J!&<_3V&<;}>C_BXPvCkYo=nHxj^bc_OKw(WQFzbdaRhqKZ*38e zPU_fgwyS14gM1()q?y@S+;6_O`@>e7YWh{fg`NfAtt-r98>02Rf`NbA;da2Y6ZEL4 zqC$sm|Iyhjee|CVk<9&U0F9npDAxCX9~=R`N=or#yScd}qP_0}A+YMFamMKfKAT4EYs-66&3o`|wCIS6`S<~2qf`KC!j8-UZvu zKD})Bk}T@rdLI&lV3FrZ6e@ofT2eR0JUmSC9p%*Kk|oe$q&y2`hC+iEV>amg!Roo* zYz-^}(*X^Fx7S10%BO+8U{XEZwr3U-#53s#L`jAgy(YeOEab)PG!@h6Ok7;5-LK)j z$tcYE@3UiLczAedG!PlSR;_O!nt(hmkIOdA*hy3r9QnXut24}y3@?q72M$FSCCCBC zp8K4QZq7`f<83g~lE~QJn?K0a6k#N;XdIVuqy}BD&%cKfou8k7?z`{vmWS+t0du;s zleafN@_|UBbHR^sH_SG9IT@e&VBdT-Xn9YO@?AN33NIc8^d*6ux&xbCK5$#P#`r>l z=Mx&G>_jtBOG}!!2xD;INP1ct0!#>Xi5XGrp-aLkaCuTvlJXKpjpW<6#%+`l*w!Hi zpobk6ES{G7n9&_&)@5-yb3Zdgr~73lC1QW2ged`NqEW}?3Hh`dJDHe}Q5}E)MwsE@ z2__BD;je_hFx?Lbi0E0k8umEIi}<*@o~KMRIc68&E(8ek{*pM|K*ca^_(<1>)Qo1h zeEEFdv5rg(zL0EjJyBZNHJ?m_hf&TG&H*~d;Ic@u0Dr8u+TNKZ)rgDSTDK`C$Odtn z9Y29vh8dE#YyOZ}XADVxjfz^WL9p1T$3q1<-r}L;lrwZdVi5NgW6Y}P9++IwJ~JMX zGMU3YFXvqU*L z-i8EX!igT$Dd;^-O%^;Fe7>kDs30&8qWH7JQz{x`{Y!uJQ^+!yENNH@6(C};+%d@2 zi%Lk>7@r#N@p=B~l*v#pdkf-G^<5Nz$rHB+wp`mElS^o&Fc6wll5))KgQ*ZpiA!wFdH^o?Fo5=$;b*iI&^$l16pQo4@Rj_QEcWN+Duu#(ks*hSQv*4 zJ4A5GibGv|7#G1e9D_{tF(ubzDPfhNUOe1eUmW^OKZpr&sA(KWQ*&aIbn!>W`}_Ml z-Olxyam_vNcLzeKsHn)vrUM_Io-{Qzf3j4Am~#+GGc-NM_Cqmk2~o%sZAvB6!+>j4dkFRy>JNri8YeJcUzJm74lDsqJm>i9i@ibGCSB z<$B)CbRJbs7iKz;{y`0D?DGG7i^b;%?wFBfV_Sc$K#^x1v4(m%cAS|Sa@dxj)2w=& z=Gp0XIg0)(K}baO0ir)FXR@u~rEoXwK%jS-ZJ8RAHpI|`Nb)8$j6u?=r~Rd-xY#&$ zitVfa%QDYTQ%{eOaz0nk>kh<1{im3_$(oA$FBui&qF0Y=2f&cCJU_5g!L4g*Ee4Wz zoktw%lw{!is}`%PtA}H713+{O$jkuk4u@4QK6`?#@v~rT3+V$pB`HQ$fBlxMXaO;Yd7jR&w%zdsmR6<1LhLKgg&<>+wXx=|5%3 z%Hoo@msQcDX=ZiXsJVzO$|^q-vZ2>vVKd3gl9|II(K=NysHy9O71N`+oJjhmZ|=go z9v$un4K@hSSbB>En?%9Fn0fhrtRz!!nLg^$gS+i#8YzxA?^l!?*Jiu7YwCZPNJ)LI zHdL-<4nz$bg?59s%a+NiP?n->tE(mOPdFHLXoN1EK^r#B+aE(ix88W!2i2M??I4GJ z@)BxH1oc)Sdy^>WstuF7+_l4hW_Aboi_h{mlp0~@?(n|j8xIfLEr(^8^XN=y03NQZ^;72xZz+H4;xr-hzZ%IZt5p3A~oGB6!BpXnZb;o0U;>?fm@PZ+GFr~+NEwtHLBSpq=^$mBiM;ewZkdWkp*3dP8 z&RDfX9E|vQ^&*NBfBxDykEjx)b?y*AhJcIa;qJzmk!0hiAtBMKBm;rYQ}t%XDqu^+ z4?lj)vBv+GkC3l0z%~dB5(US~O39uB)SHDhox@aWGRv`**=W1hUnqZGaEC`(QE_D^Ct)V< zTXhYw_s#D3~OKB}Y0&JEvojyAn0mJ<_f#PApDn?Zqc; zuP+VP3DQ!A+iqGb^`jh4m%7&jAx!rtF+>Q=vpl^;rmN>+RepcAgxFcZHql=(J!Abw z;|Ij0zG022m=;|x7CaBZ!DJMRII&^&a;me=j9Ej(|4d=OdvQLo%ri+?Y;~`RJwQT% zc+9l5kzl?Uk0X45MUol)Q^V=;dAed`P6RMJoL_4yu0x)~`HcDEu#_ot&}m1Dr1Ki- zEs~zDx|k~la~D802OX|qcVn2e?CEi_Xb#}d5sp&|Z(XiBB7uF`=yj$z`wdg8pX4Kg zrjpO;fsF9uDoOOn%VI_X6}l3cc=O-=?CL$l*6Fu)IQuxH>R?-E{^g%UaNN_DI9$)o zJqA~skY}G?J_FAE+3HT&^OmzRx@WgxhzRj3udgQ{c?1#z1F+XSj+NC|L=3XUiOCdKAv#csn0U(UM$sYI~xoScUStw=qr ztcO@cH9jlMnO#>xP9kf|PhU47yDGcx^GNqw^Bt}(%CN^C_nRG^${60p*xBjz>C!62 zMXZc*_|2i$t&JT#+Io|1OhH@|@sR94jCKy)5t)&YIG`i`zMs*6M6UJdt(C+hp&~-i ziMzR-vM#~gFdIaVcv(Rq8E?_R3;kd`$H2|Jtc|TW&fV;-vAYUt?tQm% z_*DtMyxHGk8+~HlBI8ME^wLv%B;Oa5-sj*tuGHC!W@|Jd6Z1Z zV|7IU$9%2nbywZhl~k4ld%Z0=3Ky&Q!bvA=Fq*~$YQ3BlL7_xgDPaisGLf&?eA}kS z1>PwmYhI@1tb<-t_=BJS`aC+X5@@;oglTT^@}vv=^Sq6zT48Uya>s&1&BSkEW}NT! zr~1Tn*W}NVCoYqXUQ4Cv{c&xMKv1!Pk)NdN3UQuqFbPpJw!X<^FtpQ()BA2roSid9Uwa>+<&~DI_7*28!Ke*2k_rRuDhE_0_0nR*GzVLa%*SjzX$6tu)P>o)_QdV z_0FJXJ|fOojvuA@|EBN()Xk2oq`a!}VJ zD8-;r(Q|fm;80KvHFWHPCA@$UVsNw=nq_C%+OR@(xFh>4{CVCPRT8@fQU$R&nE?WI z`BC)922WJM{JwvNcGNLwK|qQmn37Dk+6RGe{d+}yvVhinkPno+?fVypgc7Zljt~11 z8@SN2?J}&cVP1Q*w@T`}@pt)D#yJ9-k$h_E<6e2es{tIgejfsoU-%1;_@=rI_lMby zM_f(f08X^=E5x>yG1w*rf8e*P{?fhOr{Uw9W@`yy0#t3LMUgERKF@-Akg;)Hb>wX& z!6P(<4;?T8=Jy?)Lx9(F{L1sO`FA4@02@;=*eWve_wv0ZCn93xXI1gjnBLmT$6D8{ zDeShbE{T{v+B0QD{RRVLTs7aXjD!?8a0=G$w`J5r-%rmwZeO{CJrRxs17Bc#p7YYs z4ptv~{`ItRqTJhv@yVnuU3~u&^4NE_pCZ=NMX&^6M0}0jd{T(VRktr= zz()|uF7ChYJ`ZrjVEzqPb%xl+)U6vrx*FJWBSD^%lhYVtXP{1@%YM!0hScJB{;`hr+2WN0fYMWsr3{CB{}JO9 zqM^**K4=G6$UqM9EatVcO(j3~{aV1Eh&bxeHOM>&9Awy}VWEU|R#6cJWs_`eciPeI zW@hX+K+`ebev97;ey?eSM;#f$H!MmC0C_uypVqmNwz`0_WKY91vL}rzo#e|FU{NTt zQX_bN2LGWcYTf+8uOi$XgIg4^9q>2BNi8+~`{jj%^j$@q!2z1FKIr(usiPk>_o3~e z?)7fZUjbzmV@PUy8^#AsM$Q-wy@y({0d^E$+Aq9WLPukU!vVwnIzv&nw9t4$Jha#V z$x%Av&Aq`Ck4uAQxX*iO%h_J`>gJRmiqoiHrl0M9aHSw}ooi~ySHk1$QG`764=6>0 zT4?l|mAztO92+Y44$^vd0uucC{G<~g@O|8`uf976H%F@7>`4;iWW)`r9}p3Jfnzha zp9=vZ?`j+PQ5-O7Vcrn02)<$ru=-^|+6o3GY#Ie>fkC?pLy6MG9%M)h0tz+B)v$Lj z#~T}Ax(tj)f;bm1Rq~7MqaF`vOP>3K(j({@#9<>Plx{Rkx&?7xfG%~g!syMhgRjmh z?3+tN-?6wMzZMuHP$!!#e;b9^-@Kg<@dWF2N$V646Bu90I(l_%+xal3eD*#>>jfLC z2tL0k>8ndZ^NUb^TLIZk{@m+lx=0Q8%T>|I_7eHj&3YrKTYNhMsy-{&uBigISC@qr zlcJ*Hzd3V|j>_TO;55w00Qr-LMy6CIaYD(;l!oKQ{^csrT(_t3_xz^!1{Y7F4N!F8 z>E|$o$?qtIQ5b6FvgL1c4Cts9L0G*2i;G1CM+VPX_ws zcs?s$zfj7GT&h)epwS@KyQS{$>n#WS;@6uvF&+eJu0H+@cG3sguN2rHV9-Hxd!?nv zUiWk2Ch7M8yKN%8xke|lr$CCqLHqMR1+YU59zAYEbi`;A(dHcsb}}1kW?t;{{a9Z= z6@-ns3lH)(yXlCyJ(%hKZhubmd?c*~kI=L^RJg1(BIpJZ+$33(KP34_+djWFVvV9! zADtyz&h{Tey@l`~prU<6FX3u29n~UHJ}ZtQV9FItMMVGs+9Tj}{NLY^rgA|WB_K-= zBedFMi~E9WHCh8!Z-6Tm9=;35TGTz67L)*k!`NEALZh4)oObvPtxq^!ON=rjp%@YMNk&|CSSgqf%%*UG;Ca=NJ*coeuQh zQrX~Dok*q7N0QLQ9NiM!)xws75h>`1-)XRbubqFlqhOZv5QI8~rZCxuy<4mzy;)L% zfJ-xRsm?D|vo6UatcX z!ykR0bM{X-DveTc>GsPUKa2Hm)i%<|A`6X8SBhI-$>B=H8C$LTY)4T5#h%ElLiJ~l zmW5QGl*Vmxq8pCf8k+RBy#GE%u+Mf#MWfU3IEYD^Q^IJ;NDcl~sGq+3JxsE=Bowt% zQIs=r9-@HG&k-XfYo>NN=(V8;y^JN(G@^zXd_CE`M}qm<+F`etKW(*J-8SK{w!DWU z@B&is`D==3gWc`3X-^@m?aC4?KR-VzjfLy&@rP^!&*q;7U^A-l@sl=)FuLyiQuH0A z?0gug_T~Tnh>&qwV7wxvuUU#+OaUY~ZQWxP_h4OEK=0UJ?++J4?h6c|pj5i7tYWaE zB{9)8^lPri0-HACLM5Mp5Zi@Pm|$U%?zS8X9gXY-`ZP15MWZq1*}-EOo5Y0g{vWEo zWlLB8zHpz@DJm~K6l@B~d!r~3?6w1bR36BIQ2M^m-X1qBvysD2eZln@y2IJ$*iAi1 zrmH$7>}d8=vB)WYnd&Kq;d#;h$v2};D6r+@jr->r;;(Rs2iI#5sO>%YxE;Ok3Ax@u zy`Y#f*_}x3Js1^ev!^gme!XGVg*u23$}P*undL$Be}IdZrI$|eXlb$#>eFpMXVCIZ z_ju>lud#dsc!Bib`FAguo4kPB>nz`snf|>fz1jQy)!P8TUQJS)0LAHFDjjQdMs~h-3p%FOU?ioml@3`muS|r^;}DaD5!I_ zC^l6=BwwG7ZJ}}*_Gnt_HZ3-V!ysR05_AFQ6#Ld`21>$^0~V{2I}FJgIA%b_!G21H zqf7B9b+b>)Z#}aZv!V~#dOzmAcf$KsaKK9O!^lQ1DY)c0^jP-HkTWFymKh~Qaqg6C zbU%ox7LkkDeh+8G!zn&0QRpflU+D%_fF_dd5Pr#5Q8eeVjp*A52$)2I33|cZ;1vMi zDV}x}v9`qrK`MYX6chIUKn@mTX`JZK2*ck?j7|3Z-7KMFaEmQCc?dd-PJy-`pQSvY zq4`%RU6F!GscaLBT0Th{Wr%6~WWAkq%Ol_s)K`frG2S#yD2tGMNl8rYeZl$B-fV7PN<@x zWWXVP89sUKR{@luwvIj|XMJEXelZuX`KV_Z^vWR>z}Q!U+nK9n%m5I0M9je@KE=g? z_?4yOyPP3~(3jp`cW-4zTYSrT?# ztOo>y-(!_U;(yXNRuAt)5dH7rAfhD=jcUW=)MEXCFiS}xn`?aj75*!Rni%7uy1w)Y zw%7S_19VD*V3-=V48}GC9W-JDHloKyIhc zz`(B(@mXs;cIr3f!$%gW`?nT6#AA*q1?&6kq?Nyp<6|u^H@&-&{rrBCTEKvFJ!NE@ z$vQ*5&%U3JgD^?|VD;P#QU4b~SO3LA{TFG)#=*(Q_J4(4{TJ)>UoI#j?f<{9E8hR_ zVOP>FCSC}j5Ul@V-bnRG*+3-?5dL4GS2^$MKThyRw+wL5?98Dd(;`jO#Z$Oz$l22P ziODl!xTl9(xQWYh=4;vJ6n`RRacla!0X1=LeqJsW;HeSn%F6AB6^>N4ysga_eYuIpv-fDm4L2Z*vRdW#Bo4-{zF(aaWI_XNszR z42v|;h#ErRT>*1_^dK_~OzhyU^FzqD*kA0idWv(iu1)>fQ$(u3BFP=g{%no54j62LtmzBZ$cHRb0@9D#W^jCxMY%aV%z~ovdxeLxt$29&Ht2| z!T8xd4u~E#*5{8Ff8H02WYjdxwM_2WGNH+GO)0FO%7A}F?mQG!r6oM44FG3m#nY=p z`A(2h0knt)_3YHw94cyo^VV6MGd4v;$SFm(8X;{vXtF{(lDL)e5uM11OB#X9>P*jW zf0@t3Aw60i$=@?(fGCO*>E+PY(ty;50VqJwdg7fHF;&m(kV6&@CD$qSnLW0ptqz4r zmlE)=fI&S*}tYg~CC@qLAX8w6{F0p$2hRS)W~L+2+x;@`hr+wAXVSrF{}b zD&m$gUOHe(pEYg8(d4Pi1G!>X)^`J(jIOApn&qXoXMX^lH@jXIp$FuSIBp?Jf=wf% z<=<3Uf^GsCg#4JfZ;!Tn(u|yg*c5yDacb-vXEnV&Hp!A7>mx&Dqi{GPI=Bw}7QiLo%{frOtHr_agM*=7b8Tw;qKuf8u4U-LFQ*PwlX4KDkc z*?sbpsu3#9op7#T^?+qQjYQP)XTq>5ke~Qe;bJfHF9yl80F9W$W$Bb8&CjGlI9Xz6 zZ!8+_)<|KOx<2#pb6m*G>+YK&gi8@`72(+G>RSa2rT@FVRJ{;Bt6Z672*zA@<=Lnwt?b1Kdqw$-KQRXqcn8%5%h$ zqsBP_&`h5&Il*rJxXnMpl?M1{p_!(o%ZzjO^Qmz8q0fs^W%(me280blFT^a3^Qiq< z(}S6uw*&&y5X5ptERDaRl9hUAV~CTHS)U?ZB#YJ-QDg*NnVv#f?DqM#OAKu50w6e4 z+R$_Qk$&_9D;dN5`5pqgPQ3IoL+_lF?b6cjVPYX5Sma}71o`st2jOS<(}1pH9gv_B zX1e=K7Cp|x^BmX{vU}2Wv`$X)SiCQ|(xM3e9!vX&r6I7)?o_Fu`Fz901^~hvC8T+E z*(xeGq#y~jkN=4@|JVnEK02R+pAtEu{_jtwSvXa1BDO&PmmifkRgizg5Bm1>SPQmz ze78)Qjy86(P35RDB`>-xKX|7;-x!h1x1UlLlZ+VDI!xc$;P2z4qEa`tM;>!@+Loeo z3(E6!+9$91B|C-c^)s#cB?154`@CRLt;L81$9U%WRxRAiEl~oB*{BCW+X!KxA#jXf zY$+zMom% zPf5`A#?1*c_}!WRd=vjh9_q0~^C6K9hd4-?WK_6VKT+x=Z~w83Cu(=Omp1l!Drk3C z!v=hzp;0idrut&fM0>h7WlHiom0)E5E{3sJrUL!qR$OxLn?M>JS}P-es0yHd@?@05 z2$-kO9I=xB2s@W4eUR4MuPc#mR1(3kZYnZu2)k@fq-q4qiDAM)kRunE?^rDqU0vAK znjg^q`IcW#)rg-HOVt`FL*9dbYITvgO<@uB0g)3&OfN~5BtL%YRGs({)lSujkrR)t zIh-i5jyZq3+P{V~iE&$a-!Ra`MwNup8^9;wVreH|Oq7!dotvI0(Wy3vep@{928kQI zLyxpgkh1TxY2=X$&{)R^!1irwrVx9(pJ!{BIxT9D$eibExF`Wij8UBC%RsfJhrW(Q z#Ya=zn(E4%G4V6Rr&mxVZFkL^s4Db{Iz|yiE{5>8THpiWUfK9r!c!Y5ld685_QN&S zjyFksMJmPaDzj6e-(m4L-g=tJldrhU6~s|zS)>t;5AX^YnC#SvsBOkEna9!$o-s(k z@&MP`Qgp~`oSQqdQ6r<#9@%qXVeS9a&)%Hw z%$LI;3rGk_hMWqu4iLox6H+#A26Ocy?2II{|(zO-8^C#OzBfA&?aW)2myqTfk=%n$j3hh?zG2f=V= zb>f6)0@A2NaP>OFw`LtcQT}@KfqWT`?PfDkZZNYJeljW^ZB(NC@M&-C}>z z<*Ms!h7MIX5{*hX7h*2d&j^G=C>?Rvqrg7(Xo`A557xnmq(}BAbS+BVE3oC5Y4L~3 z{Hfh9FnXj|lE#V`VRyjXy(#h#jT4&;Pj$*}tJX_68vDyq>!0%R{aaSRkPa;_d*Gz6 z0@=D`GTByDY;L@0e5^yXLyXhTHjQ^3Ua|h+1A64}?Vj)&T`gHE%50g2>2WKg0fyQQ z0bQB5^XS+);KY*C)tr~Rv2EM7 z@y51o+sVY5Bs0lue&3#NchA}VXRGV$&&2+?i&!;xn-p{}6##9X-$g z#MVksNF4mUs<6YH^87whp86~-I`Kaw=vI_HzReo`r?O>l2K6pw*?RbK!Ah)Cw>LUuLuPT1qBHS1rGxQ4T}hmh=}l|5{iV3 zj*5hghKzuKij9hffr*8Mg@}TKi;ankj){f&pO=6^d_4mR1qTHMhlzxMg!%s+{tW`q zV8L?0^&r5|0N`j~5NKfkh5;j>8(sro_c_>7XuqFd2xwK7c z2dU*LpC>tI(Wh^g4cu)V9&Z;OHDij>AqpV#G53qm(eM;H=ukUxI`x zWh)}0Q9*(tL7j@FvzaAE0X=pvOF6Ib76*Ee44szc)OG5kM)5V*we!1_7=@76C2CQiK<10R zCOkj9jh@-6N*>ipy%+>3Z9)}=QI?T~W-4;;Iz8St0aUdy$2tG&>#y`^ovoH}s*3Is zri+Is(ttb61}3tcJF+MzV^>C

RKF!WrJ*8=gnniYf}0gc>Mmd4Qe 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≻)p7pDep!drzX7g2hvC0YbUeQ|1s>lZ_ zJAB-`j+;Vcg9QT?>ray6zIBq0%}k#3aX)pxlTa1>z+duOazPd&Ng$AKiu2syDe+A2 z{=|Emp+XB>x#Nar*YzPZV#ownG^VUlSGC)~6P=-T}S3mg31=Lw_Y)uK&;uexYS8*gT_6Yb6h+>`?@Po)NI zR5k2f-;F`&<`vc)Z(g7^=d4}uolu&m8i?Bs&j|V`C9XiEV$=g6t1C;+>pS{`hp%m< z0OtJ6!#bu=34)Y8+DBiMC&ozR;CZ5O?2N2<-nNwFutFy!b9$w$+?cA)?0vO|Ze9=Y{G*iwICw@+v+q}HjS<~)DB zbY1wK>aeMSG2b(Bt+A2j4ZOL0ejz=ZhXq!|Zm&obH)iXXbK>)294f|4{00QF>|uhg% zE8tglwuDE199`^;T`yHUoNJe)k+cn0h&%8+dU;q@{!l|~#MdVm zY7X;>@{J0^Uc035=1eqAB=$-#hL-r8FE2Si(fe`Ieu=mIV&}R>3HfQFO1S#KXzl2W zd!io&xSEL3u*%$eRpUN0?4ZOVzi#3MZK%a(j0pP&;fYJVYA)4%EgSQ{)w-zy*L~@Z zRG)^asl*Z`o<&M=L-Ia+Hkv2@l$wuTVN!?v@Sn+Tc+D!2^V9Q`&lY`|MLdL}p_O;(5Rl;WCn>&@$jU9R2g`Boaj!5X=<=Gc>~HBGM(s2p;1l9bc!Pfi%WB2Kr!F} zsHv$*^RZS$qmWQVBpe9^5Lg@(k3-nP^gW4g6p{wj$&Cz!bHFqhBr??#io*jOFjg?u zi6R03y=W0V^vDz^XHU=$ylCP{b~l6KKogdGkiH`$pj-5wF$YN2{Z05Gf84}hgdYNp z_z$-4p9G(6jNyqoC|~H*S_D4#3is1oLj%3ty&=Ywp*71Fn36S%m zIQoYA^ZF6KucGE;LCPMb<9hS826{M>;`CgZq;p5LjfvWol|LE%1iH*bL}aCI2yHSjqfFk^h}2gX3S>ar{UR zG}y|Il@b)})iH_#!y3B9tFunEXkM(^{v!l{{UwGL5nKgmPGCKpz}w#{2M6qg2brot zVl61v-+jR>EUlnaFE_XEf8d~R10vN)hD=q|)s+3I*tj17w7@3-;CiK^L(tKI!_inc z9E*U%F?eu9fg^}DEaH1h9lS{7|J6fjeb>Q&x?B379tyO_VE)=cdkhlXK{==bN3uo& zIHEvbTOq-`wZVr8GTO=rIDiL0HvoV}q5*k0APt90%l@MtK6J7p2OJ9EIY3W;ZBPsv z)P_1jzsqn~91iRhYlBk%l7YtnSgT)Ti2u+-!hf{|uy8zxvY%`LBpw5H=2sc+*BGEJ z7Muz{>EUp2G}xt|We9MY|5b*Cvu5hgdRVZ0|B@k)zvhes;SWy6Uu?lH{#%CqR}35g zE+)U&;t;5R)PPC!cP!+H% zcnnTc1CGLDv~_Se0EfWiH4#VxS_7pC;4x|(|Gx@rifGeFTFzvWE5pkj3d6y*bTn|_ w&y)^I2aCX=0R$3_(I9AmE2JiX!xLCQXLu6no?EcN;ZO(!homIogbv650PDOfh5!Hn literal 0 HcmV?d00001 diff --git a/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs b/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs index b4847cb9..6af240a3 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/IntegrationDocumentTests.cs @@ -81,14 +81,14 @@ { var page = document.GetPage(i + 1); - var images = page.ExperimentalAccess.GetRawImages(); + var images = page.GetImages(); Assert.NotNull(images); foreach (var image in images) { - Assert.True(image.Width > 0, $"Image had width of zero on page {i + 1}."); - Assert.True(image.Height > 0, $"Image had height of zero on page {i + 1}."); + Assert.True(image.WidthInSamples > 0, $"Image had width of zero on page {i + 1}."); + Assert.True(image.HeightInSamples > 0, $"Image had height of zero on page {i + 1}."); } } } diff --git a/src/UglyToad.PdfPig.Tests/Integration/OldGutnishTests.cs b/src/UglyToad.PdfPig.Tests/Integration/OldGutnishTests.cs index fa501848..ffbbffbf 100644 --- a/src/UglyToad.PdfPig.Tests/Integration/OldGutnishTests.cs +++ b/src/UglyToad.PdfPig.Tests/Integration/OldGutnishTests.cs @@ -41,7 +41,7 @@ { var page = document.GetPage(1); - var images = page.ExperimentalAccess.GetRawImages().ToList(); + var images = page.GetImages().ToList(); Assert.Single(images); } } diff --git a/src/UglyToad.PdfPig.Tests/Integration/SinglePageLibreOfficeImages.cs b/src/UglyToad.PdfPig.Tests/Integration/SinglePageLibreOfficeImages.cs new file mode 100644 index 00000000..daf173d5 --- /dev/null +++ b/src/UglyToad.PdfPig.Tests/Integration/SinglePageLibreOfficeImages.cs @@ -0,0 +1,87 @@ +namespace UglyToad.PdfPig.Tests.Integration +{ + using System; + using System.Linq; + using Xunit; + + public class SinglePageLibreOfficeImages + { + private static string GetFilePath() => IntegrationHelpers.GetDocumentPath(@"Single Page Images - from libre office.pdf"); + + [Fact] + public void Has3Images() + { + using (var document = PdfDocument.Open(GetFilePath(), ParsingOptions.LenientParsingOff)) + { + var page = document.GetPage(1); + + var images = page.GetImages().ToList(); + + Assert.Equal(3, images.Count); + } + } + + [Fact] + public void ImagesHaveCorrectDimensionsAndLocations() + { + using (var document = PdfDocument.Open(GetFilePath(), ParsingOptions.LenientParsingOff)) + { + var page = document.GetPage(1); + + var images = page.GetImages().OrderBy(x => x.Bounds.Width).ToList(); + + var pdfPigSquare = images[0]; + + Assert.Equal(148.3m, pdfPigSquare.Bounds.Width); + Assert.Equal(148.3m, pdfPigSquare.Bounds.Height); + Assert.Equal(60.1m, pdfPigSquare.Bounds.Left); + Assert.Equal(765.8m, pdfPigSquare.Bounds.Top); + + var pdfPigSquished = images[1]; + + Assert.Equal(206.8m, pdfPigSquished.Bounds.Width); + Assert.Equal(83.2m, pdfPigSquished.Bounds.Height); + Assert.Equal(309.8m, pdfPigSquished.Bounds.Left); + Assert.Equal(552.1m, pdfPigSquished.Bounds.Top); + + var birthdayPigs = images[2]; + + Assert.Equal(391m, birthdayPigs.Bounds.Width); + Assert.Equal(267.1m, birthdayPigs.Bounds.Height); + Assert.Equal(102.2m, birthdayPigs.Bounds.Left); + Assert.Equal(426.3m, birthdayPigs.Bounds.Top); + } + } + + [Fact] + public void HasCorrectText() + { + using (var document = PdfDocument.Open(GetFilePath(), ParsingOptions.LenientParsingOff)) + { + var page = document.GetPage(1); + Assert.Equal("Oink oink", page.Text); + } + } + + [Fact] + public void CanAccessImageBytesExceptUnsupported() + { + using (var document = PdfDocument.Open(GetFilePath(), ParsingOptions.LenientParsingOff)) + { + var page = document.GetPage(1); + foreach (var image in page.GetImages()) + { + try + { + Assert.NotNull(image.Bytes); + } + catch (NotSupportedException ) + { + // Should allow access to raw bytes. + Assert.NotNull(image.RawBytes); + } + } + } + } + } +} diff --git a/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs b/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs index 1e8488e6..a8a28fe7 100644 --- a/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs +++ b/src/UglyToad.PdfPig.Tests/PublicApiScannerTests.cs @@ -53,6 +53,8 @@ "UglyToad.PdfPig.Annotations.AnnotationType", "UglyToad.PdfPig.Content.Catalog", "UglyToad.PdfPig.Content.DocumentInformation", + "UglyToad.PdfPig.Content.InlineImage", + "UglyToad.PdfPig.Content.IPdfImage", "UglyToad.PdfPig.Content.Letter", "UglyToad.PdfPig.Content.Page", "UglyToad.PdfPig.Content.PageRotationDegrees", diff --git a/src/UglyToad.PdfPig.Tests/TestFilterProvider.cs b/src/UglyToad.PdfPig.Tests/TestFilterProvider.cs index 49b1113f..f024771b 100644 --- a/src/UglyToad.PdfPig.Tests/TestFilterProvider.cs +++ b/src/UglyToad.PdfPig.Tests/TestFilterProvider.cs @@ -11,6 +11,11 @@ return new List(); } + public IReadOnlyList GetNamedFilters(IReadOnlyList names) + { + return new List(); + } + public IReadOnlyList GetAllFilters() { return new List(); diff --git a/src/UglyToad.PdfPig.Tests/Tokens/TestPdfTokenScanner.cs b/src/UglyToad.PdfPig.Tests/Tokens/TestPdfTokenScanner.cs index f1913b7a..d1e42d2d 100644 --- a/src/UglyToad.PdfPig.Tests/Tokens/TestPdfTokenScanner.cs +++ b/src/UglyToad.PdfPig.Tests/Tokens/TestPdfTokenScanner.cs @@ -42,5 +42,9 @@ namespace UglyToad.PdfPig.Tests.Tokens { return Objects[reference]; } + + public void Dispose() + { + } } } diff --git a/src/UglyToad.PdfPig/Content/IPdfImage.cs b/src/UglyToad.PdfPig/Content/IPdfImage.cs new file mode 100644 index 00000000..2aeeff39 --- /dev/null +++ b/src/UglyToad.PdfPig/Content/IPdfImage.cs @@ -0,0 +1,94 @@ +namespace UglyToad.PdfPig.Content +{ + using System.Collections.Generic; + using Geometry; + using Graphics.Colors; + using Graphics.Core; + using XObjects; + + ///

+ /// An image in a PDF document, may be an or a PostScript image XObject (). + /// + public interface IPdfImage + { + /// + /// The placement rectangle of the image in PDF coordinates. + /// + PdfRectangle Bounds { get; } + + /// + /// The width of the image in samples. + /// + int WidthInSamples { get; } + + /// + /// The height of the image in samples. + /// + int HeightInSamples { get; } + + /// + /// The used to interpret the image. + /// This defines the number of color components per sample, e.g. + /// 1 component for , + /// 3 components for , + /// 4 components for , + /// etc. + /// This is not defined where is and is optional where the image is JPXEncoded for . + /// + ColorSpace? ColorSpace { get; } + + /// + /// The number of bits used to represent each color component. + /// + int BitsPerComponent { get; } + + /// + /// The bytes of the image with any filters decoded. + /// If the filter used to encode the bytes is not supported accessing this property will throw, access the + /// instead. + /// + IReadOnlyList Bytes { get; } + + /// + /// The encoded bytes of the image with all filters still applied. + /// + IReadOnlyList RawBytes { get; } + + /// + /// The color rendering intent to be used when rendering the image. + /// + RenderingIntent RenderingIntent { get; } + + /// + /// Indicates whether the image is to be treated as an image mask. + /// If the image is a monochrome image in which each sample + /// is specified by a single bit ( is 1). + /// The image represents a stencil where sample values represent places on the page + /// that should be marked with the current color or masked (not marked). + /// + bool IsImageMask { get; } + + /// + /// Describes how to map image samples into the values appropriate for the + /// . + /// The image data is initially composed of values in the range 0 to 2^n - 1 + /// where n is . + /// The decode array contains a pair of numbers for each component in the . + /// The value from the image data is then interpolated into the values relevant to the + /// using the corresponding values of the decode array. + /// + IReadOnlyList Decode { get; } + + /// + /// Specifies whether interpolation is to be performed. Interpolation smooths images where a single component in the image + /// as defined may correspond to many pixels on the output device. The interpolation algorithm is implementation + /// dependent and is not defined by the specification. + /// + bool Interpolate { get; } + + /// + /// Whether this image is an or a . + /// + bool IsInlineImage { get; } + } +} diff --git a/src/UglyToad.PdfPig/Content/IResourceStore.cs b/src/UglyToad.PdfPig/Content/IResourceStore.cs index 112f6624..66b03752 100644 --- a/src/UglyToad.PdfPig/Content/IResourceStore.cs +++ b/src/UglyToad.PdfPig/Content/IResourceStore.cs @@ -14,5 +14,7 @@ DictionaryToken GetExtendedGraphicsStateDictionary(NameToken name); IFont GetFontDirectly(IndirectReferenceToken fontReferenceToken, bool isLenientParsing); + + bool TryGetNamedColorSpace(NameToken name, out IToken namedColorSpace); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Content/InlineImage.cs b/src/UglyToad.PdfPig/Content/InlineImage.cs new file mode 100644 index 00000000..817aa2d8 --- /dev/null +++ b/src/UglyToad.PdfPig/Content/InlineImage.cs @@ -0,0 +1,98 @@ +namespace UglyToad.PdfPig.Content +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Filters; + using Geometry; + using Graphics.Colors; + using Graphics.Core; + using Tokens; + + /// + /// + /// A small image that is completely defined directly inline within a 's content stream. + /// + public class InlineImage : IPdfImage + { + private readonly Lazy> bytesFactory; + + /// + public PdfRectangle Bounds { get; } + + /// + public int WidthInSamples { get; } + + /// + public int HeightInSamples { get; } + + /// + public ColorSpace? ColorSpace { get; } + + /// + public int BitsPerComponent { get; } + + /// + public bool IsImageMask { get; } + + /// + public IReadOnlyList Decode { get; } + + /// + public bool IsInlineImage { get; } = true; + + /// + public RenderingIntent RenderingIntent { get; } + + /// + public bool Interpolate { get; } + + /// + public IReadOnlyList Bytes => bytesFactory.Value; + + /// + public IReadOnlyList RawBytes { get; } + + /// + /// Create a new . + /// + internal InlineImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent, bool isImageMask, + RenderingIntent renderingIntent, + bool interpolate, + ColorSpace? colorSpace, + IReadOnlyList decode, + IReadOnlyList bytes, + IReadOnlyList filters, + DictionaryToken streamDictionary) + { + Bounds = bounds; + WidthInSamples = widthInSamples; + HeightInSamples = heightInSamples; + ColorSpace = colorSpace; + Decode = decode; + BitsPerComponent = bitsPerComponent; + IsImageMask = isImageMask; + RenderingIntent = renderingIntent; + Interpolate = interpolate; + + RawBytes = bytes; + bytesFactory = new Lazy>(() => + { + var b = bytes.ToArray(); + for (var i = 0; i < filters.Count; i++) + { + var filter = filters[i]; + b = filter.Decode(b, streamDictionary, i); + } + + return b; + }); + } + + /// + public override string ToString() + { + return $"Inline Image (w {Bounds.Width}, h {Bounds.Height})"; + } + } +} diff --git a/src/UglyToad.PdfPig/Content/Page.cs b/src/UglyToad.PdfPig/Content/Page.cs index e18c0bf9..be633031 100644 --- a/src/UglyToad.PdfPig/Content/Page.cs +++ b/src/UglyToad.PdfPig/Content/Page.cs @@ -8,7 +8,6 @@ using Tokens; using Util; using Util.JetBrains.Annotations; - using XObjects; using Geometry; /// @@ -60,7 +59,7 @@ public decimal Height { get; } /// - /// The size of the page according to the standard page sizes or Custom if no matching standard size found. + /// The size of the page according to the standard page sizes or if no matching standard size found. /// public PageSize Size { get; } @@ -68,7 +67,7 @@ /// The parsed graphics state operations in the content stream for this page. /// public IReadOnlyList Operations => Content.GraphicsStateOperations; - + /// /// Access to members whose future locations within the API will change without warning. /// @@ -131,6 +130,11 @@ return (wordExtractor ?? DefaultWordExtractor.Instance).GetWords(Letters); } + /// + /// Gets any images on the page. + /// + public IEnumerable GetImages() => Content.GetImages(); + /// /// Provides access to useful members which will change in future releases. /// @@ -150,16 +154,6 @@ this.annotationProvider = annotationProvider; } - /// - /// Retrieve any images referenced in this page's content. - /// These are returned as s which are - /// raw data from the PDF's content rather than images. - /// - public IEnumerable GetRawImages() - { - return page.Content.GetImages(); - } - /// /// Get the annotation objects from the page. /// diff --git a/src/UglyToad.PdfPig/Content/PageContent.cs b/src/UglyToad.PdfPig/Content/PageContent.cs index 0ff1b699..54f3f9f7 100644 --- a/src/UglyToad.PdfPig/Content/PageContent.cs +++ b/src/UglyToad.PdfPig/Content/PageContent.cs @@ -1,14 +1,17 @@ namespace UglyToad.PdfPig.Content { + using System; using System.Collections.Generic; + using Filters; using Graphics; using Graphics.Operations; using Tokenization.Scanner; using XObjects; - using UglyToad.PdfPig.Geometry; + using Geometry; + using Util; /// - /// + /// Wraps content parsed from a page content stream for access. /// /// /// This should contain a replayable stack of drawing instructions for page content @@ -16,36 +19,46 @@ /// internal class PageContent { - private readonly IReadOnlyDictionary> xObjects; + private readonly IReadOnlyList> images; private readonly IPdfTokenScanner pdfScanner; - private readonly XObjectFactory xObjectFactory; + private readonly IFilterProvider filterProvider; + private readonly IResourceStore resourceStore; private readonly bool isLenientParsing; internal IReadOnlyList GraphicsStateOperations { get; } public IReadOnlyList Letters { get; } + public IReadOnlyList Paths { get; } - internal PageContent(IReadOnlyList graphicsStateOperations, IReadOnlyList letters, List paths, - IReadOnlyDictionary> xObjects, + internal PageContent(IReadOnlyList graphicsStateOperations, IReadOnlyList letters, + IReadOnlyList paths, + IReadOnlyList> images, IPdfTokenScanner pdfScanner, - XObjectFactory xObjectFactory, + IFilterProvider filterProvider, + IResourceStore resourceStore, bool isLenientParsing) { GraphicsStateOperations = graphicsStateOperations; Letters = letters; Paths = paths; - this.xObjects = xObjects; - this.pdfScanner = pdfScanner; - this.xObjectFactory = xObjectFactory; + this.images = images; + this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner)); + this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider)); + this.resourceStore = resourceStore ?? throw new ArgumentNullException(nameof(resourceStore)); this.isLenientParsing = isLenientParsing; } - public IEnumerable GetImages() + public IEnumerable GetImages() { - foreach (var contentRecord in xObjects[XObjectType.Image]) + foreach (var image in images) { - yield return xObjectFactory.CreateImage(contentRecord, pdfScanner, isLenientParsing); + + IPdfImage result = null; + image.Match(x => { result = XObjectFactory.ReadImage(x, pdfScanner, filterProvider, resourceStore, isLenientParsing); }, + x => { result = x; }); + + yield return result; } } } diff --git a/src/UglyToad.PdfPig/Content/ResourceContainer.cs b/src/UglyToad.PdfPig/Content/ResourceStore.cs similarity index 67% rename from src/UglyToad.PdfPig/Content/ResourceContainer.cs rename to src/UglyToad.PdfPig/Content/ResourceStore.cs index 15969430..e8a70ce9 100644 --- a/src/UglyToad.PdfPig/Content/ResourceContainer.cs +++ b/src/UglyToad.PdfPig/Content/ResourceStore.cs @@ -8,7 +8,7 @@ using Tokenization.Scanner; using Tokens; - internal class ResourceContainer : IResourceStore + internal class ResourceStore : IResourceStore { private readonly IPdfTokenScanner scanner; private readonly IFontFactory fontFactory; @@ -18,7 +18,9 @@ private readonly Dictionary extendedGraphicsStates = new Dictionary(); - public ResourceContainer(IPdfTokenScanner scanner, IFontFactory fontFactory) + private readonly Dictionary colorSpaceNames = new Dictionary(); + + public ResourceStore(IPdfTokenScanner scanner, IFontFactory fontFactory) { this.scanner = scanner; this.fontFactory = fontFactory; @@ -58,6 +60,39 @@ extendedGraphicsStates[name] = state; } } + + if (resourceDictionary.TryGet(NameToken.ColorSpace, scanner, out DictionaryToken colorSpaceDictionary)) + { + foreach (var nameColorSpacePair in colorSpaceDictionary.Data) + { + var name = NameToken.Create(nameColorSpacePair.Key); + + if (DirectObjectFinder.TryGet(nameColorSpacePair.Value, scanner, out NameToken colorSpaceName)) + { + colorSpaceNames[name] = colorSpaceName; + } + else if (DirectObjectFinder.TryGet(nameColorSpacePair.Value, scanner, out ArrayToken colorSpaceArray)) + { + if (colorSpaceArray.Length == 0) + { + throw new PdfDocumentFormatException($"Empty ColorSpace array encountered in page resource dictionary: {resourceDictionary}."); + } + + var first = colorSpaceArray.Data[0]; + + if (!(first is NameToken arrayNamedColorSpace)) + { + throw new PdfDocumentFormatException($"Invalid ColorSpace array encountered in page resource dictionary: {colorSpaceArray}."); + } + + colorSpaceNames[name] = arrayNamedColorSpace; + } + else + { + throw new PdfDocumentFormatException($"Invalid ColorSpace token encountered in page resource dictionary: {nameColorSpacePair.Value}."); + } + } + } } private void LoadFontDictionary(DictionaryToken fontDictionary, bool isLenientParsing) @@ -115,6 +150,25 @@ return font; } + public bool TryGetNamedColorSpace(NameToken name, out IToken namedToken) + { + namedToken = null; + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (!colorSpaceNames.TryGetValue(name, out var colorSpaceName)) + { + return false; + } + + namedToken = colorSpaceName; + + return true; + } + public StreamToken GetXObject(NameToken name) { var reference = currentResourceState[name]; diff --git a/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs b/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs index a4729ac2..f115de88 100644 --- a/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs +++ b/src/UglyToad.PdfPig/Filters/Ascii85Filter.cs @@ -33,8 +33,6 @@ using (var stream = new MemoryStream()) using (var writer = new BinaryWriter(stream)) { - - for (var i = 0; i < input.Count; i++) { var value = input[i]; diff --git a/src/UglyToad.PdfPig/Filters/CcittFaxDecodeFilter.cs b/src/UglyToad.PdfPig/Filters/CcittFaxDecodeFilter.cs new file mode 100644 index 00000000..98707a0e --- /dev/null +++ b/src/UglyToad.PdfPig/Filters/CcittFaxDecodeFilter.cs @@ -0,0 +1,15 @@ +namespace UglyToad.PdfPig.Filters +{ + using System; + using System.Collections.Generic; + using Tokens; + + internal class CcittFaxDecodeFilter : IFilter + { + public byte[] Decode(IReadOnlyList input, DictionaryToken streamDictionary, int filterIndex) + { + throw new NotSupportedException("The CCITT Fax Filter for image data is not currently supported. " + + "Try accessing the raw compressed data directly."); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Filters/DctDecodeFilter.cs b/src/UglyToad.PdfPig/Filters/DctDecodeFilter.cs index 1a850da4..ced83f76 100644 --- a/src/UglyToad.PdfPig/Filters/DctDecodeFilter.cs +++ b/src/UglyToad.PdfPig/Filters/DctDecodeFilter.cs @@ -8,7 +8,8 @@ { public byte[] Decode(IReadOnlyList input, DictionaryToken streamDictionary, int filterIndex) { - throw new NotImplementedException(); + throw new NotSupportedException("The DST (Discrete Cosine Transform) Filter indicates data is encoded in JPEG format. " + + "This filter is not currently supported but the raw data can be supplied to JPEG supporting libraries."); } } } diff --git a/src/UglyToad.PdfPig/Filters/IFilterProvider.cs b/src/UglyToad.PdfPig/Filters/IFilterProvider.cs index 4a702172..0460174c 100644 --- a/src/UglyToad.PdfPig/Filters/IFilterProvider.cs +++ b/src/UglyToad.PdfPig/Filters/IFilterProvider.cs @@ -7,6 +7,8 @@ { IReadOnlyList GetFilters(DictionaryToken dictionary); + IReadOnlyList GetNamedFilters(IReadOnlyList names); + IReadOnlyList GetAllFilters(); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Filters/Jbig2DecodeFilter.cs b/src/UglyToad.PdfPig/Filters/Jbig2DecodeFilter.cs new file mode 100644 index 00000000..d51eb3fb --- /dev/null +++ b/src/UglyToad.PdfPig/Filters/Jbig2DecodeFilter.cs @@ -0,0 +1,15 @@ +namespace UglyToad.PdfPig.Filters +{ + using System; + using System.Collections.Generic; + using Tokens; + + internal class Jbig2DecodeFilter : IFilter + { + public byte[] Decode(IReadOnlyList input, DictionaryToken streamDictionary, int filterIndex) + { + throw new NotSupportedException("The JBIG2 Filter for monochrome image data is not currently supported. " + + "Try accessing the raw compressed data directly."); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Filters/JpxDecodeFilter.cs b/src/UglyToad.PdfPig/Filters/JpxDecodeFilter.cs new file mode 100644 index 00000000..9531b15e --- /dev/null +++ b/src/UglyToad.PdfPig/Filters/JpxDecodeFilter.cs @@ -0,0 +1,15 @@ +namespace UglyToad.PdfPig.Filters +{ + using System; + using System.Collections.Generic; + using Tokens; + + internal class JpxDecodeFilter : IFilter + { + public byte[] Decode(IReadOnlyList input, DictionaryToken streamDictionary, int filterIndex) + { + throw new NotSupportedException("The JPX Filter (JPEG2000) for image data is not currently supported. " + + "Try accessing the raw compressed data directly."); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Filters/MemoryFilterProvider.cs b/src/UglyToad.PdfPig/Filters/MemoryFilterProvider.cs index fab84ca0..44d5b5d1 100644 --- a/src/UglyToad.PdfPig/Filters/MemoryFilterProvider.cs +++ b/src/UglyToad.PdfPig/Filters/MemoryFilterProvider.cs @@ -2,6 +2,7 @@ { using System; using System.Collections.Generic; + using System.Linq; using Exceptions; using Logging; using Tokens; @@ -15,7 +16,11 @@ { var ascii85 = new Ascii85Filter(); var asciiHex = new AsciiHexDecodeFilter(); + var ccitt = new CcittFaxDecodeFilter(); + var dct = new DctDecodeFilter(); var flate = new FlateFilter(decodeParameterResolver, pngPredictor, log); + var jbig2 = new Jbig2DecodeFilter(); + var jpx = new JpxDecodeFilter(); var runLength = new RunLengthFilter(); var lzw = new LzwFilter(decodeParameterResolver, pngPredictor); @@ -25,8 +30,14 @@ {NameToken.Ascii85DecodeAbbreviation.Data, ascii85}, {NameToken.AsciiHexDecode.Data, asciiHex}, {NameToken.AsciiHexDecodeAbbreviation.Data, asciiHex}, + {NameToken.CcittfaxDecode.Data, ccitt}, + {NameToken.CcittfaxDecodeAbbreviation.Data, ccitt}, + {NameToken.DctDecode.Data, dct}, + {NameToken.DctDecodeAbbreviation.Data, dct}, {NameToken.FlateDecode.Data, flate}, {NameToken.FlateDecodeAbbreviation.Data, flate}, + {NameToken.Jbig2Decode.Data, jbig2}, + {NameToken.JpxDecode.Data, jpx}, {NameToken.RunLengthDecode.Data, runLength}, {NameToken.RunLengthDecodeAbbreviation.Data, runLength}, {NameToken.LzwDecode, lzw}, @@ -64,6 +75,23 @@ throw new PdfDocumentFormatException($"The filter for the stream was not a valid object. Expected name or array, instead got: {token}."); } } + + public IReadOnlyList GetNamedFilters(IReadOnlyList names) + { + if (names == null) + { + throw new ArgumentNullException(nameof(names)); + } + + var result = new List(); + + foreach (var name in names) + { + result.Add(GetFilterStrict(name)); + } + + return result; + } private IFilter GetFilterStrict(string name) { @@ -77,7 +105,7 @@ public IReadOnlyList GetAllFilters() { - throw new System.NotImplementedException(); + return filterInstances.Values.Distinct().ToList(); } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs index 337e6f10..2f07cd85 100644 --- a/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs +++ b/src/UglyToad.PdfPig/Graphics/ContentStreamProcessor.cs @@ -6,6 +6,8 @@ using Colors; using Content; using Core; + using Exceptions; + using Filters; using Fonts; using Geometry; using IO; @@ -19,27 +21,43 @@ internal class ContentStreamProcessor : IOperationContext { + /// + /// Stores each letter as it is encountered in the content stream. + /// + private readonly List letters = new List(); + + /// + /// Stores each path as it is encountered in the content stream. + /// private readonly List paths = new List(); + + /// + /// Stores a link to each image (either inline or XObject) as it is encountered in the content stream. + /// + private readonly List> images = new List>(); + private readonly IResourceStore resourceStore; private readonly UserSpaceUnit userSpaceUnit; private readonly PageRotationDegrees rotation; private readonly bool isLenientParsing; private readonly IPdfTokenScanner pdfScanner; - private readonly XObjectFactory xObjectFactory; + private readonly IFilterProvider filterProvider; private readonly ILog log; private Stack graphicsStack = new Stack(); - private IFont activeExtendedGraphicsStateFont = null; + private IFont activeExtendedGraphicsStateFont; + private InlineImageBuilder inlineImageBuilder; - //a sequence number of ShowText operation to determine whether letters belong to same operation or not (letters that belong to different operations have less changes to belong to same word) - private int textSequence = 0; + /// + /// A counter to track individual calls to operations used to determine if letters are likely to be + /// in the same word/group. This exposes internal grouping of letters used by the PDF creator which may correspond to the + /// intended grouping of letters into words. + /// + private int textSequence; public TextMatrices TextMatrices { get; } = new TextMatrices(); - public TransformationMatrix CurrentTransformationMatrix - { - get { return GetCurrentState().CurrentTransformationMatrix; } - } + public TransformationMatrix CurrentTransformationMatrix => GetCurrentState().CurrentTransformationMatrix; public PdfPath CurrentPath { get; private set; } @@ -56,18 +74,18 @@ {XObjectType.PostScript, new List()} }; - public List Letters = new List(); - public ContentStreamProcessor(PdfRectangle cropBox, IResourceStore resourceStore, UserSpaceUnit userSpaceUnit, PageRotationDegrees rotation, bool isLenientParsing, + public ContentStreamProcessor(PdfRectangle cropBox, IResourceStore resourceStore, UserSpaceUnit userSpaceUnit, PageRotationDegrees rotation, + bool isLenientParsing, IPdfTokenScanner pdfScanner, - XObjectFactory xObjectFactory, + IFilterProvider filterProvider, ILog log) { this.resourceStore = resourceStore; this.userSpaceUnit = userSpaceUnit; this.rotation = rotation; this.isLenientParsing = isLenientParsing; - this.pdfScanner = pdfScanner; - this.xObjectFactory = xObjectFactory; + this.pdfScanner = pdfScanner ?? throw new ArgumentNullException(nameof(pdfScanner)); + this.filterProvider = filterProvider ?? throw new ArgumentNullException(nameof(filterProvider)); this.log = log; graphicsStack.Push(new CurrentGraphicsState()); ColorSpaceContext = new ColorSpaceContext(GetCurrentState); @@ -75,11 +93,11 @@ public PageContent Process(IReadOnlyList operations) { - var currentState = CloneAllStates(); + CloneAllStates(); ProcessOperations(operations); - return new PageContent(operations, Letters, paths, xObjects, pdfScanner, xObjectFactory, isLenientParsing); + return new PageContent(operations, letters, paths, images, pdfScanner, filterProvider, resourceStore, isLenientParsing); } private void ProcessOperations(IReadOnlyList operations) @@ -265,7 +283,7 @@ var xObjectStream = resourceStore.GetXObject(xObjectName); // For now we will determine the type and store the object with the graphics state information preceding it. - // Then consumers of the page can request the object/s to be retrieved by type. + // Then consumers of the page can request the object(s) to be retrieved by type. var subType = (NameToken)xObjectStream.StreamDictionary.Data[NameToken.Subtype.Data]; var state = GetCurrentState(); @@ -274,15 +292,15 @@ if (subType.Equals(NameToken.Ps)) { - xObjects[XObjectType.PostScript].Add(new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix)); + xObjects[XObjectType.PostScript].Add(new XObjectContentRecord(XObjectType.PostScript, xObjectStream, matrix, state.RenderingIntent)); } else if (subType.Equals(NameToken.Image)) { - xObjects[XObjectType.Image].Add(new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix)); + images.Add(Union.One(new XObjectContentRecord(XObjectType.Image, xObjectStream, matrix, state.RenderingIntent))); } else if (subType.Equals(NameToken.Form)) { - xObjects[XObjectType.Form].Add(new XObjectContentRecord(XObjectType.Form, xObjectStream, matrix)); + xObjects[XObjectType.Form].Add(new XObjectContentRecord(XObjectType.Form, xObjectStream, matrix, state.RenderingIntent)); } else { @@ -361,6 +379,52 @@ } } + public void BeginInlineImage() + { + if (inlineImageBuilder != null && !isLenientParsing) + { + throw new PdfDocumentFormatException("Begin inline image (BI) command encountered while another inline image was active."); + } + + inlineImageBuilder = new InlineImageBuilder(); + } + + public void SetInlineImageProperties(IReadOnlyDictionary properties) + { + if (inlineImageBuilder == null) + { + if (isLenientParsing) + { + return; + } + + throw new PdfDocumentFormatException("Begin inline image data (ID) command encountered without a corresponding begin inline image (BI) command."); + } + + inlineImageBuilder.Properties = properties; + } + + public void EndInlineImage(IReadOnlyList bytes) + { + if (inlineImageBuilder == null) + { + if (isLenientParsing) + { + return; + } + + throw new PdfDocumentFormatException("End inline image (EI) command encountered without a corresponding begin inline image (BI) command."); + } + + inlineImageBuilder.Bytes = bytes; + + var image = inlineImageBuilder.CreateInlineImage(CurrentTransformationMatrix, filterProvider, pdfScanner, GetCurrentState().RenderingIntent, resourceStore); + + images.Add(Union.Two(image)); + + inlineImageBuilder = null; + } + private void AdjustTextMatrix(decimal tx, decimal ty) { var matrix = TransformationMatrix.GetTranslationMatrix(tx, ty); @@ -390,7 +454,7 @@ pointSize, textSequence); - Letters.Add(letter); + letters.Add(letter); } } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Graphics/IOperationContext.cs b/src/UglyToad.PdfPig/Graphics/IOperationContext.cs index b77a415a..24c55d8c 100644 --- a/src/UglyToad.PdfPig/Graphics/IOperationContext.cs +++ b/src/UglyToad.PdfPig/Graphics/IOperationContext.cs @@ -4,7 +4,7 @@ using Geometry; using IO; using Tokens; - using UglyToad.PdfPig.Core; + using PdfPig.Core; using Util.JetBrains.Annotations; /// @@ -104,5 +104,20 @@ /// /// The name of the state to apply. void SetNamedGraphicsState(NameToken stateName); + + /// + /// Indicate that an inline image is being defined. + /// + void BeginInlineImage(); + + /// + /// Define the properties of the inline image currently being drawn. + /// + void SetInlineImageProperties(IReadOnlyDictionary properties); + + /// + /// Indicates that the current inline image is complete. + /// + void EndInlineImage(IReadOnlyList bytes); } } \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs b/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs new file mode 100644 index 00000000..0eee843e --- /dev/null +++ b/src/UglyToad.PdfPig/Graphics/InlineImageBuilder.cs @@ -0,0 +1,213 @@ +namespace UglyToad.PdfPig.Graphics +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Colors; + using Content; + using Core; + using Exceptions; + using Filters; + using Geometry; + using PdfPig.Core; + using Tokenization.Scanner; + using Tokens; + using Util; + + internal class InlineImageBuilder + { + public IReadOnlyDictionary Properties { get; set; } + + public IReadOnlyList Bytes { get; set; } + + public InlineImage CreateInlineImage(TransformationMatrix transformationMatrix, IFilterProvider filterProvider, + IPdfTokenScanner tokenScanner, + RenderingIntent defaultRenderingIntent, + IResourceStore resourceStore) + { + if (Properties == null || Bytes == null) + { + throw new InvalidOperationException($"Inline image builder not completely defined before calling {nameof(CreateInlineImage)}."); + } + + bool TryMapColorSpace(NameToken name, out ColorSpace colorSpaceResult) + { + if (name.TryMapToColorSpace(out colorSpaceResult)) + { + return true; + } + + if (TryExtendedColorSpaceNameMapping(name, out colorSpaceResult)) + { + return true; + } + + if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken) || !(colorSpaceNamedToken is NameToken newName)) + { + return false; + } + + if (newName.TryMapToColorSpace(out colorSpaceResult)) + { + return true; + } + + if (TryExtendedColorSpaceNameMapping(newName, out colorSpaceResult)) + { + return true; + } + + return false; + } + + var bounds = transformationMatrix.Transform(new PdfRectangle(new PdfPoint(1, 1), + new PdfPoint(0, 0))); + + var width = GetByKeys(NameToken.Width, NameToken.W, true).Int; + + var height = GetByKeys(NameToken.Height, NameToken.H, true).Int; + + var maskToken = GetByKeys(NameToken.ImageMask, NameToken.Im, false); + + var isMask = maskToken?.Data == true; + + var bitsPerComponent = GetByKeys(NameToken.BitsPerComponent, NameToken.Bpc, !isMask)?.Int ?? 1; + + var colorSpace = default(ColorSpace?); + + if (!isMask) + { + var colorSpaceName = GetByKeys(NameToken.ColorSpace, NameToken.Cs, false); + + if (colorSpaceName == null) + { + var colorSpaceArray = GetByKeys(NameToken.ColorSpace, NameToken.Cs, true); + + if (colorSpaceArray.Length == 0) + { + throw new PdfDocumentFormatException("Empty ColorSpace array defined for inline image."); + } + + if (!(colorSpaceArray.Data[0] is NameToken firstColorSpaceName)) + { + throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for inline image: {colorSpaceArray}."); + } + + if (!TryMapColorSpace(firstColorSpaceName, out var colorSpaceMapped)) + { + throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {firstColorSpaceName}."); + } + + colorSpace = colorSpaceMapped; + } + else + { + if (!TryMapColorSpace(colorSpaceName, out var colorSpaceMapped)) + { + throw new PdfDocumentFormatException($"Invalid ColorSpace defined for inline image: {colorSpaceName}."); + } + + colorSpace = colorSpaceMapped; + } + } + + var renderingIntent = GetByKeys(NameToken.Intent, null, false)?.Data?.ToRenderingIntent() ?? defaultRenderingIntent; + + var filterNames = new List(); + + var filterName = GetByKeys(NameToken.Filter, NameToken.F, false); + + if (filterName == null) + { + var filterArray = GetByKeys(NameToken.Filter, NameToken.F, false); + + if (filterArray != null) + { + filterNames.AddRange(filterArray.Data.OfType()); + } + } + else + { + filterNames.Add(filterName); + } + + var filters = filterProvider.GetNamedFilters(filterNames); + + var decodeRaw = GetByKeys(NameToken.Decode, NameToken.D, false) ?? new ArrayToken(EmptyArray.Instance); + + var decode = decodeRaw.Data.OfType().Select(x => x.Data).ToArray(); + + var filterDictionaryEntries = new Dictionary(); + var decodeParamsDict = GetByKeys(NameToken.DecodeParms, NameToken.Dp, false); + + if (decodeParamsDict == null) + { + var decodeParamsArray = GetByKeys(NameToken.DecodeParms, NameToken.Dp, false); + + if (decodeParamsArray != null) + { + filterDictionaryEntries[NameToken.DecodeParms] = decodeParamsArray; + } + } + else + { + filterDictionaryEntries[NameToken.DecodeParms] = decodeParamsDict; + } + + var streamDictionary = new DictionaryToken(filterDictionaryEntries); + + var interpolate = GetByKeys(NameToken.Interpolate, NameToken.I, false)?.Data ?? false; + + return new InlineImage(bounds, width, height, bitsPerComponent, isMask, renderingIntent, interpolate, colorSpace, decode, Bytes, + filters, + streamDictionary); + } + + private static bool TryExtendedColorSpaceNameMapping(NameToken name, out ColorSpace result) + { + result = ColorSpace.DeviceGray; + + switch (name.Data) + { + case "G": + result = ColorSpace.DeviceGray; + return true; + case "RGB": + result = ColorSpace.DeviceRGB; + return true; + case "CMYK": + result = ColorSpace.DeviceCMYK; + return true; + case "I": + result = ColorSpace.Indexed; + return true; + } + + return false; + } + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private T GetByKeys(NameToken name1, NameToken name2, bool required) where T : IToken + { + if (Properties.TryGetValue(name1, out var val) && val is T result) + { + return result; + } + + if (name2 != null) + { + if (Properties.TryGetValue(name2, out val) && val is T result2) + { + return result2; + } + } + + if (required) + { + throw new PdfDocumentFormatException($"Inline image dictionary missing required entry {name1}/{name2}."); + } + + return default(T); + } + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImage.cs b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImage.cs index d46f917c..c30667e8 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImage.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImage.cs @@ -28,6 +28,7 @@ /// public void Run(IOperationContext operationContext) { + operationContext.BeginInlineImage(); } /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs index e21e1031..06a4bfaa 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/BeginInlineImageData.cs @@ -1,6 +1,9 @@ namespace UglyToad.PdfPig.Graphics.Operations.InlineImages { + using System; + using System.Collections.Generic; using System.IO; + using Tokens; /// /// @@ -12,22 +15,27 @@ /// The symbol for this operation in a stream. /// public const string Symbol = "ID"; - - /// - /// The instance of the operation. - /// - public static readonly BeginInlineImageData Value = new BeginInlineImageData(); - + /// public string Operator => Symbol; - private BeginInlineImageData() + /// + /// The key-value pairs which specify attributes of the following image. + /// + public IReadOnlyDictionary Dictionary { get; } + + /// + /// Create a new . + /// + public BeginInlineImageData(IReadOnlyDictionary dictionary) { + Dictionary = dictionary ?? throw new ArgumentNullException(nameof(dictionary)); } /// public void Run(IOperationContext operationContext) { + operationContext.SetInlineImageProperties(Dictionary); } /// diff --git a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/EndInlineImage.cs b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/EndInlineImage.cs index f6b7ceb3..206af990 100644 --- a/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/EndInlineImage.cs +++ b/src/UglyToad.PdfPig/Graphics/Operations/InlineImages/EndInlineImage.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.IO; - using Tokens; /// /// @@ -15,14 +14,9 @@ /// The symbol for this operation in a stream. /// public const string Symbol = "EI"; - + /// - /// The tokens declared in order for this inline image object. - /// - public IReadOnlyList ImageTokens { get; } - - /// - /// The raw data for the inline image which should be interpreted according to the . + /// The raw data for the inline image which should be interpreted according to the corresponding . /// public IReadOnlyList ImageData { get; } @@ -32,17 +26,16 @@ /// /// Create a new operation. /// - /// The tokens which were set during the declaration of this image. /// The raw byte data of this image. - public EndInlineImage(IReadOnlyList imageTokens, IReadOnlyList imageData) + public EndInlineImage(IReadOnlyList imageData) { - ImageTokens = imageTokens ?? throw new ArgumentNullException(nameof(imageTokens)); ImageData = imageData ?? throw new ArgumentNullException(nameof(imageData)); } /// public void Run(IOperationContext operationContext) { + operationContext.EndInlineImage(ImageData); } /// diff --git a/src/UglyToad.PdfPig/Graphics/XObjectContentRecord.cs b/src/UglyToad.PdfPig/Graphics/XObjectContentRecord.cs index af132c31..1d3c820f 100644 --- a/src/UglyToad.PdfPig/Graphics/XObjectContentRecord.cs +++ b/src/UglyToad.PdfPig/Graphics/XObjectContentRecord.cs @@ -1,6 +1,7 @@ namespace UglyToad.PdfPig.Graphics { using System; + using Core; using PdfPig.Core; using Tokens; using Util.JetBrains.Annotations; @@ -15,11 +16,15 @@ public TransformationMatrix AppliedTransformation { get; } - public XObjectContentRecord(XObjectType type, StreamToken stream, TransformationMatrix appliedTransformation) + public RenderingIntent DefaultRenderingIntent { get; } + + public XObjectContentRecord(XObjectType type, StreamToken stream, TransformationMatrix appliedTransformation, + RenderingIntent defaultRenderingIntent) { Type = type; Stream = stream ?? throw new ArgumentNullException(nameof(stream)); AppliedTransformation = appliedTransformation; + DefaultRenderingIntent = defaultRenderingIntent; } } } diff --git a/src/UglyToad.PdfPig/Parser/PageContentParser.cs b/src/UglyToad.PdfPig/Parser/PageContentParser.cs index a777a0be..dfe2b0eb 100644 --- a/src/UglyToad.PdfPig/Parser/PageContentParser.cs +++ b/src/UglyToad.PdfPig/Parser/PageContentParser.cs @@ -30,8 +30,23 @@ if (token is InlineImageDataToken inlineImageData) { - graphicsStateOperations.Add(BeginInlineImageData.Value); - graphicsStateOperations.Add(new EndInlineImage(precedingTokens, inlineImageData.Data)); + var dictionary = new Dictionary(); + + for (var i = 0; i < precedingTokens.Count - 1; i++) + { + var t = precedingTokens[i]; + if (!(t is NameToken n)) + { + continue; + } + + i++; + + dictionary[n] = precedingTokens[i]; + } + + graphicsStateOperations.Add(new BeginInlineImageData(dictionary)); + graphicsStateOperations.Add(new EndInlineImage(inlineImageData.Data)); precedingTokens.Clear(); } else if (token is OperatorToken op) diff --git a/src/UglyToad.PdfPig/Parser/PageFactory.cs b/src/UglyToad.PdfPig/Parser/PageFactory.cs index 05c8e6e1..7d717f24 100644 --- a/src/UglyToad.PdfPig/Parser/PageFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PageFactory.cs @@ -14,7 +14,6 @@ using Tokenization.Scanner; using Tokens; using Util; - using XObjects; internal class PageFactory : IPageFactory { @@ -22,18 +21,15 @@ private readonly IResourceStore resourceStore; private readonly IFilterProvider filterProvider; private readonly IPageContentParser pageContentParser; - private readonly XObjectFactory xObjectFactory; private readonly ILog log; public PageFactory(IPdfTokenScanner pdfScanner, IResourceStore resourceStore, IFilterProvider filterProvider, IPageContentParser pageContentParser, - XObjectFactory xObjectFactory, ILog log) { this.resourceStore = resourceStore; this.filterProvider = filterProvider; this.pageContentParser = pageContentParser; - this.xObjectFactory = xObjectFactory; this.log = log; this.pdfScanner = pdfScanner; } @@ -125,7 +121,7 @@ { var operations = pageContentParser.Parse(new ByteArrayInputBytes(contentBytes)); - var context = new ContentStreamProcessor(cropBox.Bounds, resourceStore, userSpaceUnit, rotation, isLenientParsing, pdfScanner, xObjectFactory, log); + var context = new ContentStreamProcessor(cropBox.Bounds, resourceStore, userSpaceUnit, rotation, isLenientParsing, pdfScanner, filterProvider, log); return context.Process(operations); } diff --git a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs index e5f7dbaa..79d8ed0c 100644 --- a/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs +++ b/src/UglyToad.PdfPig/Parser/PdfDocumentFactory.cs @@ -26,7 +26,6 @@ using Tokenization.Scanner; using Tokens; using Util; - using XObjects; internal static class PdfDocumentFactory { @@ -123,11 +122,11 @@ new Type1FontParser(new Type1EncryptedPortionParser()), compactFontFormatParser), new Type3FontHandler(pdfScanner, cMapCache, filterProvider, encodingReader)); - var resourceContainer = new ResourceContainer(pdfScanner, fontFactory); + var resourceContainer = new ResourceStore(pdfScanner, fontFactory); var pageFactory = new PageFactory(pdfScanner, resourceContainer, filterProvider, new PageContentParser(new ReflectionGraphicsStateOperationFactory()), - new XObjectFactory(), log); + log); var informationFactory = new DocumentInformationFactory(); var information = informationFactory.Create(pdfScanner, crossReferenceTable.Trailer); diff --git a/src/UglyToad.PdfPig/PdfDocument.cs b/src/UglyToad.PdfPig/PdfDocument.cs index 146387aa..bc6b9ea6 100644 --- a/src/UglyToad.PdfPig/PdfDocument.cs +++ b/src/UglyToad.PdfPig/PdfDocument.cs @@ -220,6 +220,7 @@ { try { + pdfScanner.Dispose(); inputBytes.Dispose(); } catch (Exception ex) diff --git a/src/UglyToad.PdfPig/Tokenization/Scanner/IPdfTokenScanner.cs b/src/UglyToad.PdfPig/Tokenization/Scanner/IPdfTokenScanner.cs new file mode 100644 index 00000000..79ba1d08 --- /dev/null +++ b/src/UglyToad.PdfPig/Tokenization/Scanner/IPdfTokenScanner.cs @@ -0,0 +1,18 @@ +namespace UglyToad.PdfPig.Tokenization.Scanner +{ + using System; + using Tokens; + + /// + /// Tokenizes objects from bytes in a PDF file. + /// + internal interface IPdfTokenScanner : ISeekableTokenScanner, IDisposable + { + /// + /// Tokenize the object with a given object number. + /// + /// The object number for the object to tokenize. + /// The tokenized object. + ObjectToken Get(IndirectReference reference); + } +} \ No newline at end of file diff --git a/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs b/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs index 993186fa..28e8f575 100644 --- a/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs +++ b/src/UglyToad.PdfPig/Tokenization/Scanner/PdfTokenScanner.cs @@ -13,19 +13,6 @@ using Parser.Parts; using Tokens; - /// - /// Tokenizes objects from bytes in a PDF file. - /// - internal interface IPdfTokenScanner : ISeekableTokenScanner - { - /// - /// Tokenize the object with a given object number. - /// - /// The object number for the object to tokenize. - /// The tokenized object. - ObjectToken Get(IndirectReference reference); - } - internal class PdfTokenScanner : IPdfTokenScanner { private static readonly byte[] EndstreamBytes = @@ -41,6 +28,7 @@ private readonly CoreTokenScanner coreTokenScanner; private IEncryptionHandler encryptionHandler; + private bool isDisposed; /// /// Stores tokens encountered between obj - endobj markers for each call. @@ -75,6 +63,11 @@ public bool MoveNext() { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + // Read until we find object-number generation obj, e.g. "69 420 obj". int tokensRead = 0; while (coreTokenScanner.MoveNext() && !Equals(coreTokenScanner.CurrentToken, OperatorToken.StartObject)) @@ -576,26 +569,51 @@ public bool TryReadToken(out T token) where T : class, IToken { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + return coreTokenScanner.TryReadToken(out token); } public void Seek(long position) { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + coreTokenScanner.Seek(position); } public void RegisterCustomTokenizer(byte firstByte, ITokenizer tokenizer) { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + coreTokenScanner.RegisterCustomTokenizer(firstByte, tokenizer); } public void DeregisterCustomTokenizer(ITokenizer tokenizer) { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + coreTokenScanner.DeregisterCustomTokenizer(tokenizer); } public ObjectToken Get(IndirectReference reference) { + if (isDisposed) + { + throw new ObjectDisposedException(nameof(PdfTokenScanner)); + } + if (objectLocationProvider.TryGetCached(reference, out var objectToken)) { return objectToken; @@ -717,5 +735,11 @@ return results; } + + public void Dispose() + { + inputBytes?.Dispose(); + isDisposed = true; + } } } diff --git a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs index 2054f372..31497d7b 100644 --- a/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs +++ b/src/UglyToad.PdfPig/Tokens/NameToken.Constants.cs @@ -114,7 +114,7 @@ public static readonly NameToken ColorDodge = new NameToken("ColorDodge"); public static readonly NameToken Colorants = new NameToken("Colorants"); public static readonly NameToken Colors = new NameToken("Colors"); - public static readonly NameToken Colorspace = new NameToken("ColorSpace"); + public static readonly NameToken ColorSpace = new NameToken("ColorSpace"); public static readonly NameToken Columns = new NameToken("Columns"); public static readonly NameToken Compatible = new NameToken("Compatible"); public static readonly NameToken Components = new NameToken("Components"); @@ -272,6 +272,7 @@ public static readonly NameToken Info = new NameToken("Info"); public static readonly NameToken Ink = new NameToken("Ink"); public static readonly NameToken Inklist = new NameToken("InkList"); + public static readonly NameToken Intent = new NameToken("Intent"); public static readonly NameToken Interpolate = new NameToken("Interpolate"); public static readonly NameToken It = new NameToken("IT"); public static readonly NameToken ItalicAngle = new NameToken("ItalicAngle"); diff --git a/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj b/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj index 8c3452a3..77bc61af 100644 --- a/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj +++ b/src/UglyToad.PdfPig/UglyToad.PdfPig.csproj @@ -23,6 +23,11 @@ $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb + + true + + + diff --git a/src/UglyToad.PdfPig/Util/OtherEncodings.cs b/src/UglyToad.PdfPig/Util/OtherEncodings.cs index 8100e5bd..7ca578ad 100644 --- a/src/UglyToad.PdfPig/Util/OtherEncodings.cs +++ b/src/UglyToad.PdfPig/Util/OtherEncodings.cs @@ -1,5 +1,7 @@ namespace UglyToad.PdfPig.Util { + using System.Collections.Generic; + using System.Linq; using System.Text; internal static class OtherEncodings @@ -19,6 +21,16 @@ return Iso88591.GetBytes(s); } + public static string BytesAsLatin1String(IReadOnlyList bytes) + { + if (bytes is byte[] arr) + { + return BytesAsLatin1String(arr); + } + + return BytesAsLatin1String(bytes.ToArray()); + } + public static string BytesAsLatin1String(byte[] bytes) { if (bytes == null) diff --git a/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs b/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs index f07924ca..c8ef0120 100644 --- a/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs +++ b/src/UglyToad.PdfPig/XObjects/XObjectFactory.cs @@ -1,13 +1,25 @@ namespace UglyToad.PdfPig.XObjects { using System; + using System.Collections.Generic; + using System.Linq; + using Content; + using Exceptions; + using Filters; + using Geometry; using Graphics; + using Graphics.Colors; + using Graphics.Core; using Tokenization.Scanner; using Tokens; + using Util; - internal class XObjectFactory + internal static class XObjectFactory { - public XObjectImage CreateImage(XObjectContentRecord xObject, IPdfTokenScanner pdfScanner, bool isLenientParsing) + public static XObjectImage ReadImage(XObjectContentRecord xObject, IPdfTokenScanner pdfScanner, + IFilterProvider filterProvider, + IResourceStore resourceStore, + bool isLenientParsing) { if (xObject == null) { @@ -19,19 +31,103 @@ throw new InvalidOperationException($"Cannot create an image from an XObject with type: {xObject.Type}."); } - var width = xObject.Stream.StreamDictionary.Get(NameToken.Width, pdfScanner).Int; - var height = xObject.Stream.StreamDictionary.Get(NameToken.Height, pdfScanner).Int; + var dictionary = xObject.Stream.StreamDictionary; - var isJpxDecode = xObject.Stream.StreamDictionary.TryGet(NameToken.Filter, out var token) + var bounds = xObject.AppliedTransformation.Transform(new PdfRectangle(new PdfPoint(0, 0), new PdfPoint(1, 1))); + + var width = dictionary.Get(NameToken.Width, pdfScanner).Int; + var height = dictionary.Get(NameToken.Height, pdfScanner).Int; + + var isImageMask = dictionary.TryGet(NameToken.ImageMask, pdfScanner, out BooleanToken isMaskToken) + && isMaskToken.Data; + + var isJpxDecode = dictionary.TryGet(NameToken.Filter, out var token) && token is NameToken filterName && filterName.Equals(NameToken.JpxDecode); - - var isImageMask = xObject.Stream.StreamDictionary.TryGet(NameToken.ImageMask, out var maskToken) - && maskToken is BooleanToken maskBoolean - && maskBoolean.Data; - return new XObjectImage(width, height, isJpxDecode, isImageMask, xObject.Stream.StreamDictionary, - xObject.Stream.Data); + int bitsPerComponent = 0; + if (!isImageMask && !isJpxDecode) + { + if (!dictionary.TryGet(NameToken.BitsPerComponent, pdfScanner, out NumericToken bitsPerComponentToken)) + { + throw new PdfDocumentFormatException($"No bits per component defined for image: {dictionary}."); + } + + bitsPerComponent = bitsPerComponentToken.Int; + } + else if (isImageMask) + { + bitsPerComponent = 1; + } + + var intent = xObject.DefaultRenderingIntent; + if (dictionary.TryGet(NameToken.Intent, out NameToken renderingIntentToken)) + { + intent = renderingIntentToken.Data.ToRenderingIntent(); + } + + var interpolate = dictionary.TryGet(NameToken.Interpolate, pdfScanner, out BooleanToken interpolateToken) + && interpolateToken.Data; + + var decodedBytes = new Lazy>(() => xObject.Stream.Decode(filterProvider)); + + var decode = EmptyArray.Instance; + + if (dictionary.TryGet(NameToken.Decode, pdfScanner, out ArrayToken decodeArrayToken)) + { + decode = decodeArrayToken.Data.OfType() + .Select(x => x.Data) + .ToArray(); + } + + var colorSpace = default(ColorSpace?); + + if (!isImageMask) + { + if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out NameToken colorSpaceNameToken) + && TryMapColorSpace(colorSpaceNameToken, resourceStore, out var colorSpaceResult)) + { + colorSpace = colorSpaceResult; + } + else if (dictionary.TryGet(NameToken.ColorSpace, pdfScanner, out ArrayToken colorSpaceArrayToken)) + { + if (colorSpaceArrayToken.Length == 0) + { + throw new PdfDocumentFormatException($"Empty ColorSpace array defined for image XObject: {dictionary}."); + } + + var first = colorSpaceArrayToken.Data[0]; + + if (!(first is NameToken firstColorSpaceName) || !TryMapColorSpace(firstColorSpaceName, resourceStore, out colorSpaceResult)) + { + throw new PdfDocumentFormatException($"Invalid ColorSpace array defined for image XObject: {colorSpaceArrayToken}."); + } + + colorSpace = colorSpaceResult; + } + else if (!isJpxDecode) + { + throw new PdfDocumentFormatException($"No ColorSpace defined for image XObject: {dictionary}."); + } + } + + return new XObjectImage(bounds, width, height, bitsPerComponent, colorSpace, isJpxDecode, isImageMask, intent, interpolate, decode, + dictionary, xObject.Stream.Data, decodedBytes); + } + + private static bool TryMapColorSpace(NameToken name, IResourceStore resourceStore, out ColorSpace colorSpaceResult) + { + if (name.TryMapToColorSpace(out colorSpaceResult)) + { + return true; + } + + if (!resourceStore.TryGetNamedColorSpace(name, out var colorSpaceNamedToken) || !(colorSpaceNamedToken is NameToken newName)) + { + return false; + } + + return newName.TryMapToColorSpace(out colorSpaceResult); } } } diff --git a/src/UglyToad.PdfPig/XObjects/XObjectImage.cs b/src/UglyToad.PdfPig/XObjects/XObjectImage.cs index 37aea041..625abf8a 100644 --- a/src/UglyToad.PdfPig/XObjects/XObjectImage.cs +++ b/src/UglyToad.PdfPig/XObjects/XObjectImage.cs @@ -2,23 +2,35 @@ { using System; using System.Collections.Generic; + using Content; + using Geometry; + using Graphics.Colors; + using Graphics.Core; using Tokens; using Util.JetBrains.Annotations; + /// /// - /// The raw stream from a PDF document representing an image XObject. + /// A PostScript image XObject. /// - public class XObjectImage + public class XObjectImage : IPdfImage { - /// - /// The width of the image in samples. - /// - public int Width { get; } + private readonly Lazy> bytes; - /// - /// The height of the image in samples. - /// - public int Height { get; } + /// + public PdfRectangle Bounds { get; } + + /// + public int WidthInSamples { get; } + + /// + public int HeightInSamples { get; } + + /// + public ColorSpace? ColorSpace { get; } + + /// + public int BitsPerComponent { get; } /// /// The JPX filter encodes data using the JPEG2000 compression method. @@ -27,41 +39,67 @@ /// public bool IsJpxEncoded { get; } - /// - /// Whether this image should be treated as an image maske. - /// + /// + public RenderingIntent RenderingIntent { get; } + + /// public bool IsImageMask { get; } + /// + public IReadOnlyList Decode { get; } + + /// + public bool Interpolate { get; } + + /// + public bool IsInlineImage { get; } = false; + /// /// The full dictionary for this Image XObject. /// [NotNull] public DictionaryToken ImageDictionary { get; } - /// - /// The encoded bytes of this image, must be decoded via any - /// filters defined in the prior to consumption. - /// - [NotNull] - public IReadOnlyList Bytes { get; } + /// + public IReadOnlyList RawBytes { get; } + /// + [NotNull] + public IReadOnlyList Bytes => bytes.Value; + /// /// Creates a new . /// - internal XObjectImage(int width, int height, bool isJpxEncoded, bool isImageMask, DictionaryToken imageDictionary, IReadOnlyList bytes) + internal XObjectImage(PdfRectangle bounds, int widthInSamples, int heightInSamples, int bitsPerComponent, + ColorSpace? colorSpace, + bool isJpxEncoded, + bool isImageMask, + RenderingIntent renderingIntent, + bool interpolate, + IReadOnlyList decode, + DictionaryToken imageDictionary, + IReadOnlyList rawBytes, + Lazy> bytes) { - Width = width; - Height = height; + Bounds = bounds; + WidthInSamples = widthInSamples; + HeightInSamples = heightInSamples; + BitsPerComponent = bitsPerComponent; + ColorSpace = colorSpace; IsJpxEncoded = isJpxEncoded; IsImageMask = isImageMask; + RenderingIntent = renderingIntent; + Interpolate = interpolate; + Decode = decode; ImageDictionary = imageDictionary ?? throw new ArgumentNullException(nameof(imageDictionary)); - Bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); + RawBytes = rawBytes; + this.bytes = bytes ?? throw new ArgumentNullException(nameof(bytes)); } /// public override string ToString() { - return ImageDictionary.ToString(); + return $"XObject Image (w {Bounds.Width}, h {Bounds.Height}): {ImageDictionary}"; } } }