From 6ffa80c3869da16e023763b2f3a34eec1694392c Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 27 May 2024 16:31:00 +0100 Subject: [PATCH 1/4] changes --- build.gradle | 8 +- cucumber/exampleFiles/example.docx | Bin 0 -> 6499 bytes cucumber/exampleFiles/example.odp | Bin 0 -> 10371 bytes cucumber/exampleFiles/example.odt | Bin 0 -> 8222 bytes cucumber/exampleFiles/example.pptx | Bin 0 -> 37394 bytes cucumber/exampleFiles/example.rtf | 158 +++++++++++++++ cucumber/features/examples.feature | 136 ++----------- cucumber/features/external.feature | 184 ++++++++++++++++++ cucumber/features/general.feature | 95 +++++++++ cucumber/features/steps/step_definitions.py | 49 ++++- gradle/wrapper/gradle-wrapper.properties | 2 +- .../SPDF/controller/api/MergeController.java | 4 +- .../controller/api/PdfOverlayController.java | 4 +- .../controller/api/SplitPDFController.java | 2 +- .../api/SplitPdfBySectionsController.java | 7 +- .../api/converters/ConvertWebsiteToPDF.java | 2 +- .../api/misc/AutoSplitPdfController.java | 2 +- .../controller/api/misc/StampController.java | 8 +- .../api/security/WatermarkController.java | 6 +- .../software/SPDF/utils/GeneralUtils.java | 4 +- .../resources/templates/misc/ocr-pdf.html | 2 +- 21 files changed, 526 insertions(+), 147 deletions(-) create mode 100644 cucumber/exampleFiles/example.docx create mode 100644 cucumber/exampleFiles/example.odp create mode 100644 cucumber/exampleFiles/example.odt create mode 100644 cucumber/exampleFiles/example.pptx create mode 100644 cucumber/exampleFiles/example.rtf create mode 100644 cucumber/features/external.feature create mode 100644 cucumber/features/general.feature diff --git a/build.gradle b/build.gradle index 4c4bfaaf..0b3c26da 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,13 @@ dependencies { implementation("io.github.pixee:java-security-toolkit:1.1.3") implementation 'org.yaml:snakeyaml:2.2' - implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4' + + // Exclude Tomcat and include Jetty + implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + } + implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4' if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { diff --git a/cucumber/exampleFiles/example.docx b/cucumber/exampleFiles/example.docx new file mode 100644 index 0000000000000000000000000000000000000000..7d2981d6807d50603c1dea3226b8dcc3e31504d0 GIT binary patch literal 6499 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 literal 0 HcmV?d00001 diff --git a/cucumber/exampleFiles/example.odt b/cucumber/exampleFiles/example.odt new file mode 100644 index 0000000000000000000000000000000000000000..5be6133801b541ec368a92afcd757be5d46f4d77 GIT binary patch literal 8222 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 RKSU4s69tSaYXWZf{tujCbV2|C literal 0 HcmV?d00001 diff --git a/cucumber/exampleFiles/example.rtf b/cucumber/exampleFiles/example.rtf new file mode 100644 index 00000000..4aa19a5d --- /dev/null +++ b/cucumber/exampleFiles/example.rtf @@ -0,0 +1,158 @@ +{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67; +\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font; +}{\*\ts11\tsrowd\snext11\ssemihidden\spriority0\aspalpha\aspnum\adjustright\ltrpar\li0\lin0\ri0\rin0\ql\faauto\tsvertalt\tsbrdrl\tsbrdrr\tsbrdrt\tsbrdrb\tsbrdrdgr\tsbrdrdgl\tsbrdrh\tsbrdrv\trpaddl108\trpaddfl3\trwWidthB0\trftsWidthB3\trpaddt0\trpaddft3\trpaddb0 +\trpaddfb3\trpaddr108\trpaddfr3 Normal Table;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2 +\dbch\af2\hich\f2\strike0\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062\rsid13249109}{\*\generator Aspose.Words for Java 23.4.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0{ +\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{ +\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc +\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\utinl\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\dgmargin\pgbrdrhead\pgbrdrfoot\rsidroot10976062\sectd\sectlinegrid360\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery720\footery720\colsx720\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar +\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2\dbch\af2 +\hich\f2\strike0\ulnone\cf1 A}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar +\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2 +\dbch\af2\hich\f2\strike0\ulnone\cf1 B}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar +\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard +\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 C}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{ +\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3; +\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8; +\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing; +\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2; +\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1; +\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1; +\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1; +\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2; +\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2; +\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3; +\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3; +\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4; +\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4; +\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5; +\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5; +\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6; +\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6; +\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography; +\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}} \ No newline at end of file diff --git a/cucumber/features/examples.feature b/cucumber/features/examples.feature index c18e0516..04e4081f 100644 --- a/cucumber/features/examples.feature +++ b/cucumber/features/examples.feature @@ -1,5 +1,7 @@ +@example Feature: API Validation + @positive @password Scenario: Remove password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -12,7 +14,8 @@ Feature: API Validation And the response file should have size greater than 0 And the response PDF is not passworded And the response status code should be 200 - + + @negative @password Scenario: Remove password wrong password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -24,6 +27,7 @@ Feature: API Validation Then the response status code should be 500 And the response should contain error message "Internal Server Error" + @positive @info Scenario: Get info Given I generate a PDF file as "fileInput" When I send the API request to the endpoint "/api/v1/security/get-info-on-pdf" @@ -31,6 +35,7 @@ Feature: API Validation And the response file should have size greater than 100 And the response status code should be 200 + @positive @password Scenario: Add password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -43,6 +48,7 @@ Feature: API Validation And the response PDF is passworded And the response status code should be 200 + @positive @password Scenario: Add password with other params Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -59,7 +65,7 @@ Feature: API Validation And the response PDF is passworded And the response status code should be 200 - + @positive @watermark Scenario: Add watermark Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -76,18 +82,8 @@ Feature: API Validation Then the response content type should be "application/pdf" And the response file should have size greater than 100 And the response status code should be 200 - - - - Scenario: Repair PDF - Given I generate a PDF file as "fileInput" - When I send the API request to the endpoint "/api/v1/misc/repair" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - - + @positive Scenario: Remove blank pages Given I generate a PDF file as "fileInput" And the pdf contains 3 blank pages @@ -100,80 +96,8 @@ Feature: API Validation And the response file should have size greater than 0 And the response PDF should contain 0 pages And the response status code should be 200 - - @ocr - Scenario: Process PDF with OCR - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Normal | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - @ocr - Scenario: Process PDF with text and OCR with type normal - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Normal | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response status code should be 500 - - @ocr - Scenario: Process PDF with OCR - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Force | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - - @ocr - Scenario: Process PDF with OCR with sidecar - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | true | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Force | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/octet-stream" - And the response file should have extension ".zip" - And the response file should have size greater than 0 - And the response status code should be 200 - - + @positive @flatten Scenario: Flatten PDF Given I generate a PDF file as "fileInput" And the request data includes @@ -184,6 +108,7 @@ Feature: API Validation And the response file should have size greater than 0 And the response status code should be 200 + @positive @metadata Scenario: Update metadata Given I generate a PDF file as "fileInput" And the request data includes @@ -202,41 +127,4 @@ Feature: API Validation And the response PDF metadata should include "Title" as "Sample Title" And the response status code should be 200 - @libre - Scenario: Convert PDF to DOCX - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | docx | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have size greater than 100 - And the response file should have extension ".docx" -# And the response DOCX should contain 3 pages - - @libre - Scenario: Convert PDF to ODT - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | odt | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have size greater than 100 - And the response file should have extension ".odt" -# And the response ODT should contain 3 pages - - @libre - Scenario: Convert PDF to DOC - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | doc | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have extension ".doc" - And the response file should have size greater than 100 -# And the response DOC should contain 3 pages \ No newline at end of file + \ No newline at end of file diff --git a/cucumber/features/external.feature b/cucumber/features/external.feature new file mode 100644 index 00000000..bf6ca9dd --- /dev/null +++ b/cucumber/features/external.feature @@ -0,0 +1,184 @@ +Feature: API Validation + + + @libre @positive + Scenario: Repair PDF + Given I generate a PDF file as "fileInput" + When I send the API request to the endpoint "/api/v1/misc/repair" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + + @ocr @positive + Scenario: Process PDF with OCR + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Normal | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + @ocr @positive + Scenario: Process PDF with text and OCR with type normal + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Normal | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response status code should be 500 + + @ocr @positive + Scenario: Process PDF with OCR + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Force | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + @ocr @positive + Scenario: Process PDF with OCR with sidecar + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | true | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Force | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/octet-stream" + And the response file should have extension ".zip" + And the response file should have size greater than 0 + And the response status code should be 200 + + + @libre @positive + Scenario Outline: Convert PDF to various word formats + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | | + When I send the API request to the endpoint "/api/v1/convert/pdf/word" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension "" + + Examples: + | format | extension | + | docx | .docx | + | odt | .odt | + | doc | .doc | + + + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 4 | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 1 | + | expectedOutputSize | 5KB | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 1 | + | expectedOutputSize | 5KB | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @libre @positive + Scenario Outline: Convert PDF to various types + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | | + When I send the API request to the endpoint "/api/v1/convert/pdf/" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension "" + + Examples: + | type | format | extension | + | text | rtf | .rtf | + | text | txt | .txt | + | presentation | ppt | .ppt | + | presentation | pptx | .pptx | + | presentation | odp | .odp | + | html | html | .zip | + + + @libre @positive @topdf + Scenario Outline: Convert PDF to various types + Given I use an example file at "exampleFiles/example" as parameter "fileInput" + When I send the API request to the endpoint "/api/v1/convert/file/pdf" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension ".pdf" + + Examples: + | extension | + | .docx | + | .odp | + | .odt | + | .pptx | + | .rtf | + + + diff --git a/cucumber/features/general.feature b/cucumber/features/general.feature new file mode 100644 index 00000000..81b801be --- /dev/null +++ b/cucumber/features/general.feature @@ -0,0 +1,95 @@ +@general +Feature: API Validation + + @split-pdf-by-sections + Scenario Outline: split-pdf-by-sections with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 2 pages + And the request data includes + | parameter | value | + | horizontalDivisions | | + | verticalDivisions | | + | merge | true | + When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections" + Then the response content type should be "application/pdf" + And the response file should have size greater than 200 + And the response status code should be 200 + And the response PDF should contain pages + + Examples: + | horizontalDivisions | verticalDivisions | page_count | + | 0 | 1 | 4 | + | 1 | 1 | 8 | + | 1 | 2 | 12 | + | 2 | 2 | 18 | + + @split-pdf-by-sections + Scenario Outline: split-pdf-by-sections with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 2 pages + And the request data includes + | parameter | value | + | horizontalDivisions | | + | verticalDivisions | | + | merge | true | + When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections" + Then the response content type should be "application/pdf" + And the response file should have size greater than 200 + And the response status code should be 200 + And the response PDF should contain pages + + Examples: + | horizontalDivisions | verticalDivisions | page_count | + | 0 | 1 | 4 | + | 1 | 1 | 8 | + | 1 | 2 | 12 | + | 2 | 2 | 18 | + + + + @split-pdf-by-pages + Scenario Outline: split-pdf-by-pages with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 20 pages + And the request data includes + | parameter | value | + | fileInput | fileInput | + | pageNumbers | | + When I send the API request to the endpoint "/api/v1/general/split-pages" + Then the response content type should be "application/octet-stream" + And the response status code should be 200 + And the response file should have size greater than 200 + And the response ZIP should contain files + + Examples: + | pageNumbers | file_count | + | 1,3,5-9 | 8 | + | all | 20 | + | 2n+1 | 11 | + | 3n | 7 | + + + + @split-pdf-by-size-or-count + Scenario Outline: split-pdf-by-size-or-count with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 20 pages + And the request data includes + | parameter | value | + | fileInput | fileInput | + | splitType | | + | splitValue | | + When I send the API request to the endpoint "/api/v1/general/split-by-size-or-count" + Then the response content type should be "application/octet-stream" + And the response status code should be 200 + And the response file should have size greater than 200 + And the response ZIP file should contain documents each having pages + + Examples: + | splitType | splitValue | doc_count | pages_per_doc | + | 1 | 5 | 4 | 5 | + | 2 | 2 | 2 | 10 | + | 2 | 4 | 4 | 5 | + | 1 | 10 | 2 | 10 | + + diff --git a/cucumber/features/steps/step_definitions.py b/cucumber/features/steps/step_definitions.py index 50022d4e..40ddd22b 100644 --- a/cucumber/features/steps/step_definitions.py +++ b/cucumber/features/steps/step_definitions.py @@ -9,6 +9,8 @@ from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas import mimetypes import requests +import zipfile +import shutil ######### # GIVEN # @@ -26,6 +28,23 @@ def step_generate_pdf(context, fileInput): context.files = {} context.files[context.param_name] = open(context.file_name, 'rb') + +@given('I use an example file at "{filePath}" as parameter "{fileInput}"') +def step_use_example_file(context, filePath, fileInput): + context.param_name = fileInput + context.file_name = filePath.split('/')[-1] + if not hasattr(context, 'files'): + context.files = {} + + # Ensure the file exists before opening + try: + example_file = open(filePath, 'rb') + context.files[context.param_name] = example_file + except FileNotFoundError: + raise FileNotFoundError(f"The example file '{filePath}' does not exist.") + + + @given('the pdf contains {page_count:d} pages') def step_pdf_contains_pages(context, page_count): writer = PdfWriter() @@ -129,7 +148,7 @@ def step_send_api_request(context, endpoint): for key, file in files.items(): mime_type, _ = mimetypes.guess_type(file.name) mime_type = mime_type or 'application/octet-stream' - print("form_data " + file.name + " with " + mime_type) + print(f"form_data {file.name} with {mime_type}") form_data.append((key, (file.name, file, mime_type))) response = requests.post(url, files=form_data) @@ -205,3 +224,31 @@ def step_save_response_file(context, filename): with open(filename, 'wb') as f: f.write(context.response.content) print(f"Saved response content to {filename}") + + +@then('the response PDF should contain {page_count:d} pages') +def step_check_response_pdf_page_count(context, page_count): + response_file = io.BytesIO(context.response.content) + reader = PdfReader(io.BytesIO(response_file.getvalue())) + actual_page_count = len(reader.pages) + assert actual_page_count == page_count, f"Expected {page_count} pages but got {actual_page_count} pages" + +@then('the response ZIP should contain {file_count:d} files') +def step_check_response_zip_file_count(context, file_count): + response_file = io.BytesIO(context.response.content) + with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file: + actual_file_count = len(zip_file.namelist()) + assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files" + +@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages') +def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc): + response_file = io.BytesIO(context.response.content) + with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file: + actual_doc_count = len(zip_file.namelist()) + assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents" + + for file_name in zip_file.namelist(): + with zip_file.open(file_name) as pdf_file: + reader = PdfReader(pdf_file) + actual_pages_per_doc = len(reader.pages) + assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 070cb702..48c0a02c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 1719d325..5e9f97df 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -135,7 +135,9 @@ public class MergeController { throw ex; } finally { for (File file : filesToDelete) { - file.delete(); + if (file != null) { + Files.deleteIfExists(file.toPath()); + } } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java index a6148d4a..48c987cf 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java @@ -87,12 +87,12 @@ public class PdfOverlayController { } finally { for (File overlayPdfFile : overlayPdfFiles) { if (overlayPdfFile != null) { - overlayPdfFile.delete(); + Files.deleteIfExists(overlayPdfFile.toPath()); } } for (File tempFile : tempFiles) { // Delete temporary files if (tempFile != null) { - tempFile.delete(); + Files.deleteIfExists(tempFile.toPath()); } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 51efa644..ab80cad3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -121,7 +121,7 @@ public class SplitPDFController { logger.info("Successfully created zip file with split documents: {}", zipFile.toString()); byte[] data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); // return the Resource in the response return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index 3682aaf7..d9077d9e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -63,10 +63,7 @@ public class SplitPdfBySectionsController { MergeController mergeController = new MergeController(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mergeController.mergeDocuments(splitDocuments).save(baos); - return WebResponseUtils.bytesToWebResponse( - baos.toByteArray(), - filename + "_split.pdf", - MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf"); } for (PDDocument doc : splitDocuments) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -98,7 +95,7 @@ public class SplitPdfBySectionsController { e.printStackTrace(); } finally { data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); } return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index 815018e8..8bce1b22 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -59,7 +59,7 @@ public class ConvertWebsiteToPDF { pdfBytes = Files.readAllBytes(tempOutputFile); } finally { // Clean up the temporary files - Files.delete(tempOutputFile); + Files.deleteIfExists(tempOutputFile); } // Convert URL to a safe filename String outputFilename = convertURLToFileName(URL); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index 0b578d8f..23c08f26 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -118,7 +118,7 @@ public class AutoSplitPdfController { e.printStackTrace(); } finally { data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); } return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java index e5d326a9..ecf41557 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java @@ -185,10 +185,12 @@ public class StampController { try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { IOUtils.copy(is, os); + font = PDType0Font.load(document, tempFile); + } finally { + if (tempFile != null) { + Files.deleteIfExists(tempFile.toPath()); + } } - - font = PDType0Font.load(document, tempFile); - tempFile.deleteOnExit(); } contentStream.setFont(font, fontSize); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index 9506822c..2133ae26 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -150,10 +150,10 @@ public class WatermarkController { try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { IOUtils.copy(is, os); + font = PDType0Font.load(document, tempFile); + } finally { + if (tempFile != null) Files.deleteIfExists(tempFile.toPath()); } - - font = PDType0Font.load(document, tempFile); - tempFile.deleteOnExit(); } contentStream.setFont(font, fontSize); diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 61b72da6..03ae55ce 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -38,14 +38,14 @@ public class GeneralUtils { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); + Files.deleteIfExists(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); + Files.deleteIfExists(dir); return FileVisitResult.CONTINUE; } }); diff --git a/src/main/resources/templates/misc/ocr-pdf.html b/src/main/resources/templates/misc/ocr-pdf.html index a74242ae..65dd17da 100644 --- a/src/main/resources/templates/misc/ocr-pdf.html +++ b/src/main/resources/templates/misc/ocr-pdf.html @@ -232,7 +232,7 @@ // Step 3: Apply the function to your labels document.addEventListener('DOMContentLoaded', () => { - const labels = document.querySelectorAll('#languages .form-check-label'); + const labels = document.querySelectorAll('#languages label'); labels.forEach(label => { const languageCode = label.getAttribute('for').split('-')[1]; label.textContent = getFullLanguageName(languageCode); From 65f943863933e9bb48ce45f75bc6a071a3bd3c33 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 27 May 2024 17:53:18 +0100 Subject: [PATCH 2/4] deletion changes --- .github/workflows/build.yml | 8 +- .../converters/ConvertOfficeController.java | 48 ++-- .../api/converters/ConvertPDFToPDFA.java | 4 +- .../api/misc/CompressController.java | 28 +- .../api/misc/ExtractImageScansController.java | 15 +- .../controller/api/misc/OCRController.java | 241 +++++++++--------- .../controller/api/misc/RepairController.java | 40 +-- .../software/SPDF/utils/FileToPdf.java | 4 +- .../software/SPDF/utils/PDFToFile.java | 11 +- 9 files changed, 198 insertions(+), 201 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4d37599..de3b3569 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,14 +3,8 @@ name: "Build repo" on: push: branches: ["main"] - paths-ignore: - - ".github/**" - - "**/*.md" pull_request: branches: ["main"] - paths-ignore: - - ".github/**" - - "**/*.md" jobs: build: @@ -36,7 +30,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Build with Gradle run: ./gradlew build --no-build-cache diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index c6740b5f..5e6b3dfd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.converters; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -41,34 +40,35 @@ public class ConvertOfficeController { // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Run the LibreOffice command - List command = - new ArrayList<>( - Arrays.asList( - "unoconv", - "-vvv", - "-f", - "pdf", - "-o", - tempOutputFile.toString(), - tempInputFile.toString())); - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) - .runCommandWithOutputHandling(command); + try { + // Run the LibreOffice command + List command = + new ArrayList<>( + Arrays.asList( + "unoconv", + "-vvv", + "-f", + "pdf", + "-o", + tempOutputFile.toString(), + tempInputFile.toString())); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) + .runCommandWithOutputHandling(command); - // Read the converted PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); - - return pdfBytes; + // Read the converted PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + return pdfBytes; + } finally { + // Clean up the temporary files + if (tempInputFile != null) Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); + } } private boolean isValidFileExtension(String fileExtension) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index aec4e347..e3578826 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -61,8 +61,8 @@ public class ConvertPDFToPDFA { byte[] pdfBytes = Files.readAllBytes(tempOutputFile); // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); // Return the optimized PDF as a response String outputFilename = diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 9e3d6a99..12dd625c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -136,10 +136,10 @@ public class CompressController { // Increase optimization level for next iteration optimizeLevel++; if (autoMode && optimizeLevel > 4) { - System.out.println("Skipping level 5 due to bad results in auto mode"); + logger.info("Skipping level 5 due to bad results in auto mode"); sizeMet = true; } else { - System.out.println( + logger.info( "Increasing ghostscript optimisation level to " + optimizeLevel); } } @@ -230,10 +230,10 @@ public class CompressController { if (currentSize > expectedOutputSize) { // Log the current file size and scaleFactor - System.out.println( + logger.info( "Current file size: " + FileUtils.byteCountToDisplaySize(currentSize)); - System.out.println("Current scale factor: " + scaleFactor); + logger.info("Current scale factor: " + scaleFactor); // The file is still too large, reduce scaleFactor and try again scaleFactor *= 0.9f; // reduce scaleFactor by 10% @@ -256,7 +256,6 @@ public class CompressController { } } } - // Read the optimized PDF file pdfBytes = Files.readAllBytes(tempOutputFile); @@ -269,17 +268,18 @@ public class CompressController { // Read the original file again pdfBytes = Files.readAllBytes(tempInputFile); } + // Return the optimized PDF as a response + String outputFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_Optimized.pdf"; + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } finally { // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + // deleted by multipart file handler deu to transferTo? + // Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); } - - // Return the optimized PDF as a response - String outputFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_Optimized.pdf"; - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index 9ed00dd8..4a6acaee 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -5,7 +5,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -103,10 +102,7 @@ public class ExtractImageScansController { } } else { tempInputFile = Files.createTempFile("input_", "." + extension); - Files.copy( - form.getFileInput().getInputStream(), - tempInputFile, - StandardCopyOption.REPLACE_EXISTING); + form.getFileInput().transferTo(tempInputFile); // Add input file path to images list images.add(tempInputFile.toString()); } @@ -176,11 +172,14 @@ public class ExtractImageScansController { byte[] zipBytes = Files.readAllBytes(tempZipFile); // Clean up the temporary zip file - Files.delete(tempZipFile); + Files.deleteIfExists(tempZipFile); return WebResponseUtils.bytesToWebResponse( zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); - } else { + } if (processedImageBytes.size() == 0) { + throw new IllegalArgumentException("No images detected"); + }else { + // Return the processed image as a response byte[] imageBytes = processedImageBytes.get(0); return WebResponseUtils.bytesToWebResponse( @@ -201,7 +200,7 @@ public class ExtractImageScansController { if (tempZipFile != null && Files.exists(tempZipFile)) { try { - Files.delete(tempZipFile); + Files.deleteIfExists(tempZipFile); } catch (IOException e) { logger.error("Failed to delete temporary zip file: " + tempZipFile, e); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index 07947587..bb8a18cd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -5,7 +5,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -91,139 +90,145 @@ public class OCRController { } // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); - - // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - - // Prepare the output file path Path sidecarTextPath = null; - // Run OCR Command - String languageOption = String.join("+", selectedLanguages); + try { + inputFile.transferTo(tempInputFile.toFile()); - List command = - new ArrayList<>( - Arrays.asList( - "ocrmypdf", - "--verbose", - "2", - "--output-type", - "pdf", - "--pdf-renderer", - ocrRenderType)); + // Run OCR Command + String languageOption = String.join("+", selectedLanguages); - if (sidecar != null && sidecar) { - sidecarTextPath = Files.createTempFile("sidecar", ".txt"); - command.add("--sidecar"); - command.add(sidecarTextPath.toString()); - } - - if (deskew != null && deskew) { - command.add("--deskew"); - } - if (clean != null && clean) { - command.add("--clean"); - } - if (cleanFinal != null && cleanFinal) { - command.add("--clean-final"); - } - if (ocrType != null && !"".equals(ocrType)) { - if ("skip-text".equals(ocrType)) { - command.add("--skip-text"); - } else if ("force-ocr".equals(ocrType)) { - command.add("--force-ocr"); - } else if ("Normal".equals(ocrType)) { + List command = + new ArrayList<>( + Arrays.asList( + "ocrmypdf", + "--verbose", + "2", + "--output-type", + "pdf", + "--pdf-renderer", + ocrRenderType)); + if (sidecar != null && sidecar) { + sidecarTextPath = Files.createTempFile("sidecar", ".txt"); + command.add("--sidecar"); + command.add(sidecarTextPath.toString()); } - } - command.addAll( - Arrays.asList( - "--language", - languageOption, - tempInputFile.toString(), - tempOutputFile.toString())); + if (deskew != null && deskew) { + command.add("--deskew"); + } + if (clean != null && clean) { + command.add("--clean"); + } + if (cleanFinal != null && cleanFinal) { + command.add("--clean-final"); + } + if (ocrType != null && !"".equals(ocrType)) { + if ("skip-text".equals(ocrType)) { + command.add("--skip-text"); + } else if ("force-ocr".equals(ocrType)) { + command.add("--force-ocr"); + } else if ("Normal".equals(ocrType)) { - // Run CLI command - ProcessExecutorResult result = - ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) - .runCommandWithOutputHandling(command); - if (result.getRc() != 0 - && result.getMessages().contains("multiprocessing/synchronize.py") - && result.getMessages().contains("OSError: [Errno 38] Function not implemented")) { - command.add("--jobs"); - command.add("1"); - result = + } + } + + command.addAll( + Arrays.asList( + "--language", + languageOption, + tempInputFile.toString(), + tempOutputFile.toString())); + + // Run CLI command + ProcessExecutorResult result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) .runCommandWithOutputHandling(command); - } - - // Remove images from the OCR processed PDF if the flag is set to true - if (removeImagesAfter != null && removeImagesAfter) { - Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); - - List gsCommand = - Arrays.asList( - "gs", - "-sDEVICE=pdfwrite", - "-dFILTERIMAGE", - "-o", - tempPdfWithoutImages.toString(), - tempOutputFile.toString()); - - ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) - .runCommandWithOutputHandling(gsCommand); - tempOutputFile = tempPdfWithoutImages; - } - // Read the OCR processed PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - // Clean up the temporary files - Files.delete(tempInputFile); - - // Return the OCR processed PDF as a response - String outputFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_OCR.pdf"; - - if (sidecar != null && sidecar) { - // Create a zip file containing both the PDF and the text file - String outputZipFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_OCR.zip"; - Path tempZipFile = Files.createTempFile("output_", ".zip"); - - try (ZipOutputStream zipOut = - new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { - // Add PDF file to the zip - ZipEntry pdfEntry = new ZipEntry(outputFilename); - zipOut.putNextEntry(pdfEntry); - Files.copy(tempOutputFile, zipOut); - zipOut.closeEntry(); - - // Add text file to the zip - ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); - zipOut.putNextEntry(txtEntry); - Files.copy(sidecarTextPath, zipOut); - zipOut.closeEntry(); + if (result.getRc() != 0 + && result.getMessages().contains("multiprocessing/synchronize.py") + && result.getMessages() + .contains("OSError: [Errno 38] Function not implemented")) { + command.add("--jobs"); + command.add("1"); + result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + .runCommandWithOutputHandling(command); } - byte[] zipBytes = Files.readAllBytes(tempZipFile); + // Remove images from the OCR processed PDF if the flag is set to true + if (removeImagesAfter != null && removeImagesAfter) { + Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); - // Clean up the temporary zip file - Files.delete(tempZipFile); - Files.delete(tempOutputFile); - Files.delete(sidecarTextPath); + List gsCommand = + Arrays.asList( + "gs", + "-sDEVICE=pdfwrite", + "-dFILTERIMAGE", + "-o", + tempPdfWithoutImages.toString(), + tempOutputFile.toString()); + + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(gsCommand); + tempOutputFile = tempPdfWithoutImages; + } + // Read the OCR processed PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - // Return the zip file containing both the PDF and the text file - return WebResponseUtils.bytesToWebResponse( - zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); - } else { // Return the OCR processed PDF as a response - Files.delete(tempOutputFile); - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + String outputFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_OCR.pdf"; + + if (sidecar != null && sidecar) { + // Create a zip file containing both the PDF and the text file + String outputZipFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_OCR.zip"; + Path tempZipFile = Files.createTempFile("output_", ".zip"); + + try (ZipOutputStream zipOut = + new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + // Add PDF file to the zip + ZipEntry pdfEntry = new ZipEntry(outputFilename); + zipOut.putNextEntry(pdfEntry); + Files.copy(tempOutputFile, zipOut); + zipOut.closeEntry(); + + // Add text file to the zip + ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); + zipOut.putNextEntry(txtEntry); + Files.copy(sidecarTextPath, zipOut); + zipOut.closeEntry(); + } + + byte[] zipBytes = Files.readAllBytes(tempZipFile); + + // Clean up the temporary zip file + Files.deleteIfExists(tempZipFile); + Files.deleteIfExists(tempOutputFile); + Files.deleteIfExists(sidecarTextPath); + + // Return the zip file containing both the PDF and the text file + return WebResponseUtils.bytesToWebResponse( + zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); + } else { + // Return the OCR processed PDF as a response + Files.deleteIfExists(tempOutputFile); + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } + } finally { + // Clean up the temporary files + Files.deleteIfExists(tempOutputFile); + // Comment out as transferTo makes multipart handle cleanup + // Files.deleteIfExists(tempInputFile); + if (sidecarTextPath != null) { + Files.deleteIfExists(sidecarTextPath); + } } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index 85d4560c..812a8cae 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -41,34 +41,36 @@ public class RepairController { MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); - inputFile.transferTo(tempInputFile.toFile()); - - // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + byte[] pdfBytes = null; + inputFile.transferTo(tempInputFile.toFile()); + try { - List command = new ArrayList<>(); - command.add("gs"); - command.add("-o"); - command.add(tempOutputFile.toString()); - command.add("-sDEVICE=pdfwrite"); - command.add(tempInputFile.toString()); + List command = new ArrayList<>(); + command.add("gs"); + command.add("-o"); + command.add(tempOutputFile.toString()); + command.add("-sDEVICE=pdfwrite"); + command.add(tempInputFile.toString()); - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) - .runCommandWithOutputHandling(command); - - // Read the optimized PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(command); + // Read the optimized PDF file + pdfBytes = Files.readAllBytes(tempOutputFile); + // Return the optimized PDF as a response String outputFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()) .replaceFirst("[.][^.]+$", "") + "_repaired.pdf"; return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } finally { + // Clean up the temporary files + Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); + } + } } diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index da408a23..2a891c08 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -79,8 +79,8 @@ public class FileToPdf { } finally { // Clean up temporary files - Files.delete(tempOutputFile); - Files.delete(tempInputFile); + Files.deleteIfExists(tempOutputFile); + Files.deleteIfExists(tempInputFile); } return pdfBytes; diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index 38579aa8..f5297a10 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -6,7 +6,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -47,8 +46,7 @@ public class PDFToFile { try { // Save the uploaded file to a temporary location tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy( - inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output directory tempOutputDir = Files.createTempDirectory("output_"); @@ -85,7 +83,7 @@ public class PDFToFile { } finally { // Clean up the temporary files - if (tempInputFile != null) Files.delete(tempInputFile); + if (tempInputFile != null) Files.deleteIfExists(tempInputFile); if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } @@ -127,8 +125,7 @@ public class PDFToFile { try { // Save the uploaded file to a temporary location tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy( - inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output directory tempOutputDir = Files.createTempDirectory("output_"); @@ -180,7 +177,7 @@ public class PDFToFile { } finally { // Clean up the temporary files - if (tempInputFile != null) Files.delete(tempInputFile); + Files.deleteIfExists(tempInputFile); if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } System.out.println("fileBytes=" + fileBytes.length); From 3d78e01559d4fb849ae698a4879cd8cd9a8a30b0 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 27 May 2024 17:53:33 +0100 Subject: [PATCH 3/4] cuke --- cucumber/features/external.feature | 48 ++++++++++++++++++- cucumber/features/general.feature | 9 ++-- cucumber/features/steps/step_definitions.py | 53 +++++++++++++++++++++ 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/cucumber/features/external.feature b/cucumber/features/external.feature index bf6ca9dd..1d503135 100644 --- a/cucumber/features/external.feature +++ b/cucumber/features/external.feature @@ -28,7 +28,28 @@ Feature: API Validation And the response file should have size greater than 0 And the response status code should be 200 + @ocr @positive + Scenario: Extract Image Scans + Given I generate a PDF file as "fileInput" + And the pdf contains 3 images on 2 pages + And the request data includes + | parameter | value | + | angleThreshold | 5 | + | tolerance | 20 | + | minArea | 8000 | + | minContourArea | 500 | + | borderSize | 1 | + When I send the API request to the endpoint "/api/v1/misc/extract-image-scans" + Then the response content type should be "application/octet-stream" + And the response file should have extension ".zip" + And the response ZIP should contain 2 files + And the response file should have size greater than 0 + And the response status code should be 200 + + + + @ocr @negative Scenario: Process PDF with text and OCR with type normal Given I generate a PDF file as "fileInput" And the pdf contains 3 pages with random text @@ -79,6 +100,7 @@ Feature: API Validation When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" Then the response content type should be "application/octet-stream" And the response file should have extension ".zip" + And the response ZIP should contain 2 files And the response file should have size greater than 0 And the response status code should be 200 @@ -101,8 +123,30 @@ Feature: API Validation | odt | .odt | | doc | .doc | - - + @ocr + Scenario: PDFA + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | pdfa | + When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @ocr + Scenario: PDFA1 + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | pdfa-1 | + When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + @compress @ghostscript @positive Scenario: Compress Given I generate a PDF file as "fileInput" diff --git a/cucumber/features/general.feature b/cucumber/features/general.feature index 81b801be..229d37b5 100644 --- a/cucumber/features/general.feature +++ b/cucumber/features/general.feature @@ -1,7 +1,8 @@ @general Feature: API Validation - @split-pdf-by-sections + + @split-pdf-by-sections @positive Scenario Outline: split-pdf-by-sections with different parameters Given I generate a PDF file as "fileInput" And the pdf contains 2 pages @@ -23,7 +24,7 @@ Feature: API Validation | 1 | 2 | 12 | | 2 | 2 | 18 | - @split-pdf-by-sections + @split-pdf-by-sections @positive Scenario Outline: split-pdf-by-sections with different parameters Given I generate a PDF file as "fileInput" And the pdf contains 2 pages @@ -47,7 +48,7 @@ Feature: API Validation - @split-pdf-by-pages + @split-pdf-by-pages @positive Scenario Outline: split-pdf-by-pages with different parameters Given I generate a PDF file as "fileInput" And the pdf contains 20 pages @@ -70,7 +71,7 @@ Feature: API Validation - @split-pdf-by-size-or-count + @split-pdf-by-size-or-count @positive Scenario Outline: split-pdf-by-size-or-count with different parameters Given I generate a PDF file as "fileInput" And the pdf contains 20 pages diff --git a/cucumber/features/steps/step_definitions.py b/cucumber/features/steps/step_definitions.py index 40ddd22b..60479b6e 100644 --- a/cucumber/features/steps/step_definitions.py +++ b/cucumber/features/steps/step_definitions.py @@ -66,6 +66,59 @@ def step_pdf_contains_blank_pages(context, page_count): context.files[context.param_name].close() context.files[context.param_name] = open(context.file_name, 'rb') + + +def create_black_box_image(file_name, size): + can = canvas.Canvas(file_name, pagesize=size) + width, height = size + can.setFillColorRGB(0, 0, 0) + can.rect(0, 0, width, height, fill=1) + can.showPage() + can.save() + +def create_pdf_with_black_boxes(file_name, image_count, page_count): + page_width, page_height = letter + box_size = 72 # 1 inch by 1 inch black box + boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0) + + writer = PdfWriter() + box_counter = 0 + + for page in range(page_count): + packet = io.BytesIO() + can = canvas.Canvas(packet, pagesize=letter) + + for i in range(boxes_per_page): + if box_counter >= image_count: + break + x = (i % (page_width // box_size)) * box_size + y = page_height - ((i // (page_width // box_size) + 1) * box_size) + can.setFillColorRGB(0, 0, 0) + can.rect(x, y, box_size, box_size, fill=1) + box_counter += 1 + + can.showPage() + can.save() + packet.seek(0) + new_pdf = PdfReader(packet) + writer.add_page(new_pdf.pages[0]) + + with open(file_name, 'wb') as f: + writer.write(f) + +@given('the pdf contains {image_count:d} images on {page_count:d} pages') +def step_pdf_contains_images(context, image_count, page_count): + if not hasattr(context, 'param_name'): + context.param_name = "default" + context.file_name = "genericNonCustomisableName.pdf" + create_pdf_with_black_boxes(context.file_name, image_count, page_count) + if not hasattr(context, 'files'): + context.files = {} + if context.param_name in context.files: + context.files[context.param_name].close() + context.files[context.param_name] = open(context.file_name, 'rb') + + @given('the pdf contains {page_count:d} pages with random text') def step_pdf_contains_pages_with_random_text(context, page_count): buffer = io.BytesIO() From 11497f52d4c7216cf804f3c35e190ee39e6901ed Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com.> Date: Mon, 27 May 2024 18:12:06 +0100 Subject: [PATCH 4/4] gradle bump --- .github/workflows/push-docker.yml | 2 +- .github/workflows/releaseArtifacts.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index f8fc4200..8bcf7803 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -24,7 +24,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Run Gradle Command run: ./gradlew clean build diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml index f9b5f2b0..151e126e 100644 --- a/.github/workflows/releaseArtifacts.yml +++ b/.github/workflows/releaseArtifacts.yml @@ -29,7 +29,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Generate jar (With Security=${{ matrix.enable_security }}) run: ./gradlew clean createExe