From 4d8b5975cbd95c3ba1535f90a1a37f9b5b5877f0 Mon Sep 17 00:00:00 2001 From: typhoon71 Date: Thu, 10 Sep 2020 19:18:41 +0200 Subject: [PATCH] Add section to documentation with howto for AcroForm fields extraction (#458) * Create aforms.rst Add section to documentation with howto for AcroForm fields extraction * Update index.rst Added reference to aforms.rst * Update aforms.rst * Update aforms.rst * Update index.rst * Update and rename aforms.rst to acro_forms.rst * Update acro_forms.rst * Update acro_forms.rst * Update acro_forms.rst * Update index.rst * Update acro_forms.rst * Update acro_forms.rst * Update acro_forms.rst * Update pdfdocument.py * Update pdfdocument.py * Update pdfdocument.py * Update acro_forms.rst * Update docs/source/howto/acro_forms.rst Co-authored-by: Jake Stockwin * Update docs/source/howto/acro_forms.rst Co-authored-by: Jake Stockwin * Update docs/source/howto/acro_forms.rst Co-authored-by: Jake Stockwin * Update acro_forms.rst * reverted changes * Update README.md * Proper processing of ComboBox ComboBox fields hold multiple values, so the must be returned as a list. * PDF with AcroForm (samples) * Create tmp * Delete AcroForm_TEST.pdf * Delete AcroForm_TEST_compiled.pdf * PDF file with AcroForms * Delete tmp * Fixed typo * Update index.rst * Update README.md * Update index.rst * Update pdfdocument.py * Update docs/source/howto/acro_forms.rst Co-authored-by: Jake Stockwin * Update pdfdocument.py * Update pdfdocument.py * Update pdfdocument.py Co-authored-by: Jake Stockwin --- README.md | 1 + docs/source/howto/acro_forms.rst | 145 ++++++++++++++++++++ docs/source/howto/index.rst | 1 + docs/source/index.rst | 4 +- samples/acroform/AcroForm_TEST.pdf | Bin 0 -> 7652 bytes samples/acroform/AcroForm_TEST_compiled.pdf | Bin 0 -> 11400 bytes 6 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 docs/source/howto/acro_forms.rst create mode 100644 samples/acroform/AcroForm_TEST.pdf create mode 100644 samples/acroform/AcroForm_TEST_compiled.pdf diff --git a/README.md b/README.md index 0285d95..e2552a6 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Features * Various font types (Type1, TrueType, Type3, and CID) support. * Support for extracting images (JPG, JBIG2 and Bitmaps). * Support for RC4 and AES encryption. + * Support for AcroForm interactive form extraction. * Table of contents extraction. * Tagged contents extraction. * Automatic layout analysis. diff --git a/docs/source/howto/acro_forms.rst b/docs/source/howto/acro_forms.rst new file mode 100644 index 0000000..23444ff --- /dev/null +++ b/docs/source/howto/acro_forms.rst @@ -0,0 +1,145 @@ +.. _acro_forms: + +How to extract AcroForm interactive form fields from a PDF using PDFMiner +******************************** + +Before you start, make sure you have :ref:`installed pdfminer.six`. + +The second thing you need is a PDF with AcroForms (as found in PDF files with fillable forms or multiple choices). There are some examples of these in the GitHub repository under `samples/acroform`. + +Only AcroForm interactive forms are supported, XFA forms are not supported. + +.. code-block:: python + + from pdfminer.pdfparser import PDFParser + from pdfminer.pdfdocument import PDFDocument + from pdfminer.pdftypes import resolve1 + from pdfminer.psparser import PSLiteral, PSKeyword + from pdfminer.utils import decode_text + + + data = {} + + + def decode_value(value): + + # decode PSLiteral, PSKeyword + if isinstance(value, (PSLiteral, PSKeyword)): + value = value.name + + # decode bytes + if isinstance(value, bytes): + value = decode_text(value) + + return value + + + with open(file_path, 'rb') as fp: + parser = PDFParser(fp) + + doc = PDFDocument(parser) + res = resolve1(doc.catalog) + + if 'AcroForm' not in res: + raise ValueError("No AcroForm Found") + + fields = resolve1(doc.catalog['AcroForm'])['Fields'] # may need further resolving + + for f in fields: + field = resolve1(f) + name, values = field.get('T'), field.get('V') + + # decode name + name = decode_text(name) + + # resolve indirect obj + values = resolve1(values) + + # decode value(s) + if isinstance(values, list): + values = [decode_value(v) for v in values] + else: + values = decode_value(values) + + data.update({name: values}) + + print(name, values) + +This code snippet will print all the fields name and value and save them in the "data" dictionary. + + +How it works: + +- Initialize the parser and the PDFDocument objects + +.. code-block:: python + + parser = PDFParser(fp) + doc = PDFDocument(parser) + +- Get the catalog +(the catalog contains references to other objects defining the document structure, see section 7.7.2 of PDF 32000-1:2008 specs: https://www.adobe.com/devnet/pdf/pdf_reference.html) + +.. code-block:: python + + res = resolve1(doc.catalog) + +- Check if the catalog contains the AcroForm key and raise ValueError if not +(the PDF does not contain Acroform type of interactive forms if this key is missing in the catalog, see section 12.7.2 of PDF 32000-1:2008 specs) + +.. code-block:: python + + if 'AcroForm' not in res: + raise ValueError("No AcroForm Found") + +- Get the field list resolving the entry in the catalog + +.. code-block:: python + + fields = resolve1(doc.catalog['AcroForm'])['Fields'] + for f in fields: + field = resolve1(f) + +- Get field name and field value(s) + +.. code-block:: python + + name, values = field.get('T'), field.get('V') + +- Decode field name. + +.. code-block:: python + + name = decode_text(name) + +- Resolve indirect field value objects + +.. code-block:: python + + values = resolve1(value) + +- Call the value(s) decoding method as needed +(a single field can hold multiple values, for example a combo box can hold more than one value at time) + +.. code-block:: python + + if isinstance(values, list): + values = [decode_value(v) for v in values] + else: + values = decode_value(values) + +(the decode_value method takes care of decoding the fields value returning a string) + +- Decode PSLiteral and PSKeyword field values + +.. code-block:: python + + if isinstance(value, (PSLiteral, PSKeyword)): + value = value.name + +- Decode bytes field values + +.. code-block:: python + + if isinstance(value, bytes): + value = utils.decode_text(value) diff --git a/docs/source/howto/index.rst b/docs/source/howto/index.rst index b8a758b..9d3269a 100644 --- a/docs/source/howto/index.rst +++ b/docs/source/howto/index.rst @@ -9,3 +9,4 @@ How-to guides help you to solve specific problems with pdfminer.six. :maxdepth: 1 images + acro_forms diff --git a/docs/source/index.rst b/docs/source/index.rst index 75588f9..e5d19a7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -48,10 +48,10 @@ Features * Extract text, images (JPG, JBIG2 and Bitmaps), table-of-contents, tagged contents and more. * Support for (almost all) features from the PDF-1.7 specification -* Support for Chinese, Japanese and Korean CJK) languages as well as vertical - writing. +* Support for Chinese, Japanese and Korean CJK) languages as well as vertical writing. * Support for various font types (Type1, TrueType, Type3, and CID). * Support for RC4 and AES encryption. +* Support for AcroForm interactive form extraction. Installation instructions diff --git a/samples/acroform/AcroForm_TEST.pdf b/samples/acroform/AcroForm_TEST.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8c366d553a85c5cb1b0ceb4aa5fe6bc47be3e933 GIT binary patch literal 7652 zcmdrx3p7-1*Oib#A%s*XL%GhJGxwPoF=nE22}vZz%wQNZjDw*Yib_(_T`Ebs5g{an zuDTI=RZ3SyDN3P`{|r&z`}$V@T3`SFul3KGnRA|V_TJCFKhNHefs+l(48o9=Q3e?W zR~wa45C8%q-#}%QxjEJ~Dujn+uwk}9SgzyMGdmfk1wiZb|5m*o)K{$X+#d%>FVo}IH zpE~oxMB!o%FAQOkB@)8Yi#rPf@T4De)9F|%p-=>ec>=?a0ED2FZvZ!P9p)RrQHP%s zq+%oQ@xxbSW`r)nUl@QNhWu3^DbByB$lcL5kjH_sF5$kgRAo}l4E;$J2tZhSHZ0~N zvPp@9!~b4b>rufd5C%uZBXeUw63Gj@m?!jy0{|j~W$^_tPmE;=*f7tA#}RRPbb1&p z=COm7QIZ2DuW+?^GVv?{a;@O1XN74SY8kp~z13c>W1=o(Ik zheX~8`_0a>I9Zo@^pevmg?BUAg8CykW>3HDyS$mtwU>jdJefHa45sBb-o%+Nu-i7r`rJ>C-q)R{*KgKaA?tyPxTDp0Z- z^K?thd{mT)?U7>@Es75B!qU$ee5)zY($W_md3v?}dXT@bq4?6>mXC zP|xvX^m^xOk44*eRpq04j_JlTmtPAgWIIcAz|2(x&-2}zTFv$*YER0Hmx)Y%#PEzd zUij#-ZXI6H=>5uF$veKY-W!~5n#4)d++}qBjm+td;T!z9mF>YAMS;MT4-F1h)vHfB ze}0*6;a}i9V_|8uLX>7QeQP)MhS)=Smsa!L)u+G4UJ?X*CyZ?d=>qjXAL1m^Z>%u9 zm@J*I!fab1hmu3Fc1I`7xrSnCMqkrpp_nI7{1ve);(d%{Rs@zPX((y@>SII&^};cQ z!~N;}MZsgJWHNw5R!S=2XZ|AKej}Y%3bf-gpu}9|kTEr2`}`P(?X+rD^Hf()Zd&g_Lmi{ta%o^=^moegU_$zhr+Ml1NL{3RDouQW~UR+f3gm+KKEX#mE}x+my^8*bmZuDH%lR=Y2)NfJN>)N>Cf(jYxa(_c(8cveG~3a=g+aV zi6i_je|n`@B};v~haT_f!>)*N1mE$usqp=XTkalfJfjd%=W4s;U`6-3&KMi7&i?*E z>uT2^mVC>*0b9^vJ+0G>^Shlz0LR@It94aJLvZ__3UUY zqw0J2*GBGHb2xVH=ER^{GO=T*&COgaO#-Tx;1@qz}E0?+a=NV7gn~)icd!@8r!}9bS53!AQ0tu`*N>s z&}6AWA`ex~%>(mi)2-f|TQEN`dvaUdyGDHq-&p_3)YV;2Cu~0XBES3)$WyDnvDwx5 zVuag%yhFV4wA-6^D4g$p_D(oG(mU@hJsXLL;?mRQ%BP`gd<_cF6lr(%7QaMM?SyFDd9=bWNA)`JoI8) z#r8*`*7Ze8%OD`?**F#ba}jSpLdX!AQ4NZO`?dzP{13r;~$C|F#YP+=o3m$A? zg@wWIly5zpgB??Q_&;4&LCC(5O2q*L9EM8#Stddh@DFHjTmIy@`AP{rbmoF^!^X1p znI>{1`5lWsk5`#0KQ-jz$M>@-F~>>^`4_(r4#;R3&nSBqGB1e?25isSzKJQa`!wOY zNq244;XNd>v#;;>jEdvD%l<*TgV#C5n_kSL%~;w-h7;snL+jF&v}UBHOyI~b599Bc zacyQqjm5Qj4WZ1i!#@29A2g$F!R2e3vn#qZ;sL#e&*l zSmd`#=JR>GDP!wgTHXK2%@m#pB0CKVK%ijA;7^YwlYirl(%n=6NFtEPZlk5SR+>($ zw!4SnUia30)7_0nYx8iOeQ{T;0z-^iaIT;$C?~Ew;s@SCHt!1KbgZ-$#}-uDlo%0o zq>3Z_j;r_-3&$%UV&(6sZSQ09U+UX0h<;!BF_fC9x=Y1mu~J@VPj!}UW8%VW+Z0}T z&AT&aj@*xX)_c*?2bB<1@;KR3j&!)Af=V6urm(xVN#}z9(Q9!LXLx-pRMk>K42#a+ z$e-pNc)I!XxxH!k>P$gYS-f^Xr`NQgMyax9VZk-U5?0;`ufK%CZ+D)Yt|le#d#yoR zY75FMadGA=!pkP{n{Q9|H&8ZLbZjdb4D+IS6vkHWm#elPfEniSN1oS8^eXwP4ek5+ z7CmKiz3ORJ882@J5=@t$8aV#uTw5KrR1Qr~-<7R=btCEE+s_{6nwK^`fKQC6Kcoi! zslTBv*MC^k{oCOD6RB-`B??Jx2^~?1x=X=DkAz;isqbI2ooeN472>KQK7F*m7xnPy z&VIDmYPL}t!hAv-B_Tj2$AAJi_>dscRCCYpTJ?eIyCi=aeg^G(<(GN#`m^?4d z>F6}l_4W218yz}td)~hOP0pf zNmCyBo@b=tstd{$Y!Y>Mmv~;6iBi39(9!s{T&?l;S)~osT1L>*k8<8#E;q*1{7`>z z#B+xYFCOVkP)Nj|9WQfBNeG)0#Df7}odnbS)67v8e24}_rK?oq>F%X_gz*9{D90-9}kc!2T%^*AtPp08Wz>kj} z-CWG|qdD8KeoTdoEc60kIE049Mn*Cg=plR#2@vuNRbd!rNEi%96pTe)U?B`%D$o!hmoqwh zNVr%qG&h%nL;6t}k80q~lxWklSJ}lr3qgx5!5js-YX#%#;-$GAf#^w33!v(M&eW`E= zn)?sxQp_LJhv^{$L>DxMlue!u@}uKG9B2lT&2R))2&74kPBH-@8VH(W{|5O3*IvZs z`$dg`ON3~6f(eKl#r*|x1ouZDeGKe>%sdh}L$FdIq?-H1c}7);c;wLiXeb~f)X!)_ zI>8otkw~zN9k73|8Ud`MZ=eet3}79QwmPy9io;+4*%bd+hA33RkHr!Aca7`vT}{bN zB$Whj-Uq$eF*lyuaaFZCXA9%rlM>AI4VAnNcV9RaVJ5r#fUx-@<7TrYxG;7a6L_FI z^NQ-Fb$*61>nHAu8()ZWc5;rD&CxwP4z03b+&jW1brlzxxH;-YYU5qr&976LCa0eB z>TXpU*|e`ai(Am2D|lG)uyF=Zb|NuCA@RT+Hc_kg$irNt?swv8R|O>j@P zrDk8vy4~-RPp5rqdU_`}Ad|AXj=;&x^{ZFb%E@9cCo~oJfR7@*T)Iju8}oA0?snY! z+MIo|#AF^x_NuUk?vU+x@y#n?Td2VfPw=LJv&xe9YL^wxwrqw=e2dASRWrT!RHV8p zF0jEW`Cex!dUS6-jCl}uzT-iXeS+$$W%Shs7at~k?=L7rpT&E8n^#hierJ4?$lmsr zkFwnJSE;3zYh5B6Hmqr{Z@=1H_`cA1)`f#}PJfD3^9U8xGn)FoWT<(>&;2I9x4C8N zBFi|z&SIWcH~m(w$D8)xa$(}hi|9vR@`R^2gNq3caa%iXeenhZXP()$$ZqYNJk>r zw7lw2w?nu0cNi;ATicqnN&Q5=4e#KzrCn8&zaAgBp3h8V$?HVLsb8vU)z3m5F__bO zds~J11@$D#cr>PB(^T+@EbHsL9vT25tQn6g5~FHE-4~ zO$CoW)AAgfeK!|z?sFDx=+0`mT((aMo8SRg$8l4Jx7f7YwqJ(AsC* z+BtjuJNB&1wtG5{6Q3+*sGfRy^4ux~~(|9@o@$gRLd9p;z_HfNG9&Fn0af78$CkhWJ^)4 za~39d%EEc)LsGI~uNX~R-X;t$Snb1cU9TufsCj>nz#{f5`rF1;FV~sASW~F|+=(Bi z7A%=Tss18MNhD>rU1X+HwBzm=p=iMhhrcerckESHK$CEduaMy%shh63g0R#X!D6kW9c@j?$kk%b8 z{Agrp9O)yyaj}Dg`?O+r0%-JJH=d8rge`aw zHz2>`ywYIh=M{%3wpB{ju`MYL(05L+%nSU%__vnwM`K?PEb{k}t;T2L`{g#{i>zDD zFanN60CG161u;!q(h?rj$T`z?!5_V0@8vM0|*h3qNH3PlmtRhKu|!zj#NR!1}ZiX zEGVK z1#x7R*$=y#+f-0gG=LVcgH+7S@C$f+m?h-Jz+5zlUw{TdA^_lBkSm^!CQ-?zcxR-X ziTnluBHkYDjV4k^XplnjwXjfu`CPFSw1kfe3L4XiEzA*cVON$gGy>_;Mi3Gi%8x)3 z@vcIc%j1XyLNo|jSm5n=AtG3aw+ms3;GrgX2U~A50>#FTY-?w0L!yIp64};@NU*Y| zQ%y~gKRY`j)xr#-5|OK|HE0V_>2xA!O}2&rTZlrmvxNY%DaFDU@4)B6QQlNE0TSQ) z3(yn<12_Bvm>(btL{rFOU6v1;X^M9f2t;TQ7_8uqK!YYC5JZQ;cuzOjAHUceL_=s0 z0U$yogjt~~sK8D(o2%;Q$M(yW)7OYikn7S^4WFFkcSg>wRqA1<)FcMFc+UFk<8@;# zf1ROfqFwuoV(GR-&p#{^VTIHvkJ_r*%OyJ8sj_<4YFF2_-IJD4mU3UOYNUcf03PmR z80rWSODKvG!u~2KKm|2>Hr>Te1vLb}3W_M!c-XCyphQ3>e=!&YAiO)8Xo~khQ%%3J z#LrKa?dJ#iOUZ1IQeal0YB9<>D(K~ks%VGtR-o)f2L)WRtYvOPMU{V{%)}|ptS!Zl z5<_=voomU2Ai(~vO<;1gC8nzCj@hf+d=LG_!cBx*G73co2S3KeOUX+0Hcd8`VAvqq z!@65>gaSK(FceJ~asdMtvA8S|3qf7%GFMgr9Dybe-Vb`k26+}D2pASVcEDLJ%qBz+&dxAu`1mPdgAz5RVTx3*>|FO@uHW4GfV?Y~L7L z#2OC+WBsKk3W$TK1T+D}0VI;Iq;Vt0PYjNDP!Cbu6vQ)FA|Wpdp|9A6!Eax@dn8*V zrnb1jFv(rmK`=)&N{XmFlSfbs!4x=71esObsrw@|4K)lsHJ)oO(K6A{ey*9b-Dv85 z<9*@sLNyssDq2I?U6O~znE#(Jglzg_hQ#(O2TC$T%$olfhL}n)1ft?VA^{;ijzB>O zE@2+|o?0Z}Z&8aWRh=)w$;!*h>p7X*OyS7uDVcMW^pf(KNyK8yV(XGjX`HFwl+9OX zCHe!^MC=}CsWm$%Ka=Dd@ksvGH9un!L?HaUNs(!D`LYYg(LbC@}XtrENpzPciRGp+}=NFmNa%VbS12TlKeo|Osq;n)p@XJEaOn9@Mwm-^6)?ajN zrkQ%M_IY`CP?mKv?EbETo)(W8#ziFZI+6dZ{pN>T3-(pIt{nej1}mg3AENhulz&k& zIn4 zKKWC&vABDi;w@3pCieLUsvavjzlhj*Z1$hcCp0wl`T6%Qwbcg) zu%o)=IyLfMw}MrWVM9$go? zHh^2*9jbOJ2wnSoyR%h8>|wX}T|3PKPPk2XERPu2{I8I#{(XI-S1+mG9C2w=C~c|GxOv%QPd9m0COA-3>T2RU1bkt zDk+*|IKy(XJw+-CCXer&-l|{k*fXaKUv~}Nxl4(3=Az7ziCx;0d&-x_1b;YD(qTjd{V z4ZJp)boGx93zAAb6n-&0b+NfRam`+0cY8d+zsPlbWMIp-p!xi%n6+-Xin2G_u~a4D z?lyA=Fui;IJ^Fo>hu!7(x6OE#yR>ZFXSd{Xe)eszN{ncUeUZD(b^C>#TFwVp6*h@Q zyro(M&G!$|R!mI8OxN8gTyUzn1FmeT+F|?9N8}Vn-f47_`P;OB#~ zOJFoKW|8{0905}P#>&ojD>y;yh!wkQ=9B=Xvn;Hhl+CvcDxC1F^~Ftn3eQ--c1rBK zdomjiKP)}J8-OP_T;9MmzOdXQm*AXaJnia+^zo>SM)h_}B$M3os~NtdQ|DNgQ*tayK;CxEphX`@JmFh|dvI6~?JL!|y= zh}0jL^NMjVMo8TeS^QdFt<9Mr2}_BK$Uq(2! z%Jb>*RIkG1!)fVx!@S1wytGb0PaE}K&d_zB*%>ulyS{2gcHEwXIU6t#L#7<$A0$w46eMsmCE@@w^+%^`B=r8`kYPE4vPSQH z@e(2|dpKk{njl`^A`uo6+6y@Du*e&aY=hvLaFhs%-G3zTvEM*!tVY!Us&XwKVlV0Z zk#4Y~Zr{yqZg_J;>D5&psk#Tzt1f6eONq&^`WWZ?? zJ6|X0Hjrau}Sf)vS1cnQ!V z!7DHV?T_IYl^40SbV8z~a&oT)eSW0j&5AVzCJH3Qbf@>ys#6rFguQw5auy~2K>11D zh0g<@WHpSZSKJSqyO|6GrWU2Hrwbh3%G8_mwAAd$CXwCP?9Y3v4#F1$g13dPc1^-u zD4|VX^o%S@R%C{^?o`&8o{=WQQCt$iOP^jhV|lZA-Q4zYdc+>TkIBEQ$Jhf);yMef z-l-*_b?*(7=oN-K7fska{@tS-HSZ(6n_`qs`LC3Hf7W55B)Gqem%cDtBIMp~jfaHD^mBYL}O0Ha)IBe{r8h_}RQ!J_*+j zJaXP^b-U`4%;ekl+tNA4hdMGfGG#g^cm$L)&#gLqyCOZQf{!6mSf3M~eM(h0pk}bC z;~7i+#_wJ&-gk{mZeTYzL3J@}qeB-dIpBERX#>^|H}>sXn{d?cLf)rO8?%v(r2IGY z^(8s!yAW{LNyz>MvP`5P`3?_nX{r^BU9K(6ZWaDoxp3UWnmy&U zimm`?JZb%#H|H||QDEh=w^kg0CQ~abb&oRZ4;2#qKO4>W%2_uk=F~XNkDGEds zZ!izknBDE&rQyKFgKE98uPf2x7A z_R+F+cc#Y-d^Y@$i%S80PS@Gnd!v@kSeWmSAY*N*Y^im530fuqLdz!Tq{_D^KcQG! z?@E)VHD;LmT~>CLDvJL7#Z4jP_~`D(p1Iz6g8nD8tPIV0yFK8G`P11EDF3%tf`sz8 z!*f2SwfU~9I5bU1ao&dTlZiAI?E&q<+%$Tr8I@I0u<7ZJf)MQPh@2uwmbg=vdf|~~ zc5Ix&qr0o}-kk07EA)FL!qqd33cbT_q-O{oCz&}JtPa|r^~*`CB<_iMQ!ltZUu4|e zdE)V#9Z8e7U#|}P7?;G!JQ3T00p2BM29#N)mhA~ub>@XF9gjMbnccg%ao#;)fAs0{ zCT*$hars`304K$tMp~0!ztKPXz z+PIM1|EJ03BkN=h-nw6XlC!xkuE(plVQnG!hc4GXpsDwn=i|@QcHZUCo~B(fjHk~| z4W`dEfr{gt41qJ!v=i6;7ptf-X0}iZR(1B|#5$^m?|%D?arKsGb7b+O3i&zLT=KfZ zA6k2>eYA+1Z8i(maa^`R`$^Yn|I)zo206HC8*b1TqW1NM3`1YshgzLeYYn3e>gqFF zYjUf?gAzI?UUy&Qvq<)jc3N=smc@}XnoG7hc6Q$|>0ex4OsaUB+n8B!yz}L&nT_+E zZr$Fr+WbC$S7Tg7M^8EDzHdOz-09&fNax1&UJuGLIAUK>{DOF>pzL-?D|gB%4fo^I zy%I3#3e!)9otsRnU1`x+RFa^ee*a=Ie@1_$teMZ+r;nv}#gWeCQx1Fa{Q{3}TBP)A z>z{HCOWx_qdEa!nc7wmL_90eOSpOD#ux$&gLpeiv+gy3CNeAUM&w#-%`U6GtW^Itw z@0Au67qmT=_Uv_P6n+M7O@iK4NUb!4EXKu2F*V6fC0d%&l^Zw$xFt9jYS}K>~DJ5xKj@iMGFfNDph#?|*bo52#C8e3W)B?y` z`!j0m%lOhRea8Hlm(_2=sT(I{shTWME_vPCu;2dXM#nQd;Z6Kv=|4ogy$R1Es=0avZkrm^3 zomaI3W~-PfxjZ{F5aCPnI+;+NtI)vsB(gCRy@7p~>aA3)S=*h&aL!w6FgqjWX(Cvw2Zr zEKV>iLbKrj9^YK|RYj#Pn#VQQU2Mt#7-2SWAkRKV2)oBPGdVHA94c3Lo(0M*iWU_b z7K$Wg&{3fw{0Lffe=QU;z(Ey0FuyD z5C@V7B#?wbLjXv@Bd-96iUmnDQwohp{gP1BwGbz_%!FKjnwzcNaCgWrbKO9ZD2#^3 zuUN4Hw}OBZ2m|mSl}g1USuF^{A~moP(R>jr3d@hs8xrzGjx8L)5%R)BJOLjqmdj!b zB1Pu9y2Dwtp`M5A3LDcAKLR&sBaR~o#Ul^!AdY~S)QHO&Q7$Y}7&53DmxG5x;81Zk z326sPY==NP((tR!qF7-_BjCSl#9;h&v(Qld2moRX5Z!4=Jj9LUAeq)dc!x|LeBcB} zuMh~i5nnyva%ld@+La};V1~{{?<)R>0S# zUmk?;#&#+$#f#nZb^XB}zZWjF6_^W z3=!#CjAS2~aD-?`i4fk`B8E!8mOQB1w@Qc=kX{jqFQHXF-jLqrydOCZ8jc+mQ<+kV|A^2~vcQzC4l!!X+%!m)p;Bb#LndAP$gKM{ zi-suk&wq~^{LjlCk@#;XBT)Rv&A)K{2udS${)KAa)cGs8P@{1haxBnXcLfraNr(%_2OCza@dzDH@Bq%c`Q}Lgb0SUlgX>;6lNE-~c;WECprF{j? zk%4Kr+8p74akw5iRIu~J@@s36cP~1CrK(yUU zBPUQ>!%8GS^>JaQL1E%a`D_Dtzd^Rtl3CklQx9pSG|>C`{q%CwtV5S(=Pq9^C9NQ} zT4WL|!Mrh<0m)}w3IDTaUB5lwN+f)LsFnKNnO5MNGp&Bek=D$z0JdLFeQnj8>e{MX lO0~V)HICt=HiFWio+Yx~WECW*DFKf$=Tt$EG;&h)zX0U2O1%I8 literal 0 HcmV?d00001