feat: pdf encryption image extraction rendering and table reading
This commit is contained in:
parent
23d292fdff
commit
84d73f1b86
@ -28,7 +28,8 @@ export default {
|
||||
asarUnpack: [
|
||||
"node_modules/onnxruntime-node/**",
|
||||
"node_modules/sharp/**",
|
||||
"node_modules/@gutenye/**"
|
||||
"node_modules/@gutenye/**",
|
||||
"node_modules/@napi-rs/**"
|
||||
],
|
||||
compression: "maximum",
|
||||
npmRebuild: false,
|
||||
|
||||
382
package-lock.json
generated
382
package-lock.json
generated
@ -8,8 +8,10 @@
|
||||
"name": "rpa-studio",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@cantoo/pdf-lib": "^2.11.1",
|
||||
"@gutenye/ocr-models": "^1.4.2",
|
||||
"@gutenye/ocr-node": "^1.4.9",
|
||||
"@napi-rs/canvas": "^1.1.0",
|
||||
"@xyflow/react": "^12.12.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"basic-ftp": "^6.2.3",
|
||||
@ -360,6 +362,27 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cantoo/pdf-lib": {
|
||||
"version": "2.11.1",
|
||||
"resolved": "https://registry.npmmirror.com/@cantoo/pdf-lib/-/pdf-lib-2.11.1.tgz",
|
||||
"integrity": "sha512-lYuRJFfXgoP8WVE8HYSeoLfDcnSXmjMLmdZS7YvmYHngbcZfiqDOruHrZBqjYizkrNa4ad1yW4WCrPYyb26T3Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"culori": ">=4",
|
||||
"fflate": "^0.8.2",
|
||||
"node-html-better-parser": ">=1.5.9",
|
||||
"tslib": ">=2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"html-entities": ">=2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@cantoo/pdf-lib/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/@electron/asar": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmmirror.com/@electron/asar/-/asar-3.4.1.tgz",
|
||||
@ -1745,11 +1768,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-0.1.100.tgz",
|
||||
"integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-1.1.0.tgz",
|
||||
"integrity": "sha512-k+ph/D8H9GS8ZZ48okvHrKeJynkqAA1kMUZRuzRWNlQUDHXngq7oRaWMZBdmSfd9ZhVy9bwNKei/7wMboI/uMg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"workspaces": [
|
||||
"e2e/*"
|
||||
],
|
||||
@ -1761,23 +1783,23 @@
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-x64": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "0.1.100",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.100"
|
||||
"@napi-rs/canvas-android-arm64": "1.1.0",
|
||||
"@napi-rs/canvas-darwin-arm64": "1.1.0",
|
||||
"@napi-rs/canvas-darwin-x64": "1.1.0",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "1.1.0",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "1.1.0",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "1.1.0",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "1.1.0",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "1.1.0",
|
||||
"@napi-rs/canvas-linux-x64-musl": "1.1.0",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "1.1.0",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-mKrSmRyhWK4q699Q1xthbZIZi7JBvLtsRNnOZAeFaLPOFT1yo8y82aRC2nNZbXTGJXU7fVDONgGQPr0vo3kerw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1795,9 +1817,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.1.0.tgz",
|
||||
"integrity": "sha512-iqwST4g0oH+x8VCpBIYh+MYokv/mVvkM+v9oJGB0d9A3P2qBh42ogJlVyWq9SpPCrAjS7tplMb1D3OIfxl+utw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1815,9 +1837,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz",
|
||||
"integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.1.0.tgz",
|
||||
"integrity": "sha512-jkeA8eVTxDum03LTvc32YJ28F6KB1U0gYwnWMfOAUQZvWWEh/kvONby4iWGJx3+Tw45UqMGMXjtw2GKNVZDhVg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1835,9 +1857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz",
|
||||
"integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.1.0.tgz",
|
||||
"integrity": "sha512-Y4YkKG1ijoqkgUdTEEBNG4k+2BK5bR5jGL6S56YxCXYPwGfUd5nED8/74xyc7z+bpixPasCjSYoP3xYPOIGNxw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -1855,9 +1877,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.1.0.tgz",
|
||||
"integrity": "sha512-dE/XthqYkiWcBVtKFlZbUXwAYfDuQGqDWixznipJNmevLGpprNnTVQZyftm1RZJudkIOn2EkzYZDMMETM08Row==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1875,9 +1897,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.1.0.tgz",
|
||||
"integrity": "sha512-DqZ/CMcHlhU56pPjv9AP0h3U7MFQ5LzC1HzMyRMotL+PFsJmZJA/ziApPYVyyz78DaVLhI8t3So3ztqtijbYpg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1915,9 +1937,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.1.0.tgz",
|
||||
"integrity": "sha512-uQ9JuJlRhQdHyttcMWOiwLkfIqkqaB0YFUVe835LUe12THt2b51HXkTItq0fxWT1jdhUPX2ylWZ1walkJyD6GQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1935,9 +1957,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.1.0.tgz",
|
||||
"integrity": "sha512-has4CPPAkkCac/LZ13VAw5p+MJLdaKVoyrhumN/A7uJd6960xYkJNDZgUebyxiha82AFzXx6RV+QJg5qGG079g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -1955,9 +1977,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.1.0.tgz",
|
||||
"integrity": "sha512-MJnktLKNijmXqdjTughHDZ9TDLZ9uUm2hvFXqaOqn0+w7lphXFnft6bpSgQAc9U1exc20UGIBlzGu9+kiCj9Ag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -1975,9 +1997,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.1.0.tgz",
|
||||
"integrity": "sha512-08575bFk7iiadxap4h+JxgGut9xDCHAGum6wh0eInN53zEMImkmEHi5MushX2FOmNO7qRNX7BPiDC5ZYBwHeEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -4461,6 +4483,15 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/culori": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmmirror.com/culori/-/culori-4.0.2.tgz",
|
||||
"integrity": "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-color": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz",
|
||||
@ -5570,6 +5601,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmmirror.com/fflate/-/fflate-0.8.3.tgz",
|
||||
"integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/filelist": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz",
|
||||
@ -6021,6 +6058,22 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmmirror.com/html-entities/-/html-entities-2.6.0.tgz",
|
||||
"integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/mdevils"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://patreon.com/mdevils"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz",
|
||||
@ -7153,6 +7206,15 @@
|
||||
"node": "^20.17.0 || >=22.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-better-parser": {
|
||||
"version": "1.5.9",
|
||||
"resolved": "https://registry.npmmirror.com/node-html-better-parser/-/node-html-better-parser-1.5.9.tgz",
|
||||
"integrity": "sha512-z1I5UINMezJXYL9cH3h0a9KBth2G978gSLlfkpQ+CQzzVHVQy9gpARgm9eDsz1O4gn1HtgUqjdAIYxKFZm6uHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"html-entities": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/node-int64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
|
||||
@ -7475,6 +7537,236 @@
|
||||
"@napi-rs/canvas": "^0.1.65"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas/-/canvas-0.1.100.tgz",
|
||||
"integrity": "sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"workspaces": [
|
||||
"e2e/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-arm64": "0.1.100",
|
||||
"@napi-rs/canvas-darwin-x64": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "0.1.100",
|
||||
"@napi-rs/canvas-linux-x64-musl": "0.1.100",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "0.1.100",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "0.1.100"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.100.tgz",
|
||||
"integrity": "sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.100.tgz",
|
||||
"integrity": "sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.100.tgz",
|
||||
"integrity": "sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.100.tgz",
|
||||
"integrity": "sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.100.tgz",
|
||||
"integrity": "sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pdfjs-dist/node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "0.1.100",
|
||||
"resolved": "https://registry.npmmirror.com/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.100.tgz",
|
||||
"integrity": "sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz",
|
||||
|
||||
@ -17,8 +17,10 @@
|
||||
"test:e2e": "node scripts/demo-e2e.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cantoo/pdf-lib": "^2.11.1",
|
||||
"@gutenye/ocr-models": "^1.4.2",
|
||||
"@gutenye/ocr-node": "^1.4.9",
|
||||
"@napi-rs/canvas": "^1.1.0",
|
||||
"@xyflow/react": "^12.12.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"basic-ftp": "^6.2.3",
|
||||
|
||||
54
src/main/capabilities/pdf-high.test.ts
Normal file
54
src/main/capabilities/pdf-high.test.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { PDFDocument, StandardFonts } from "pdf-lib";
|
||||
import { PNG } from "pngjs";
|
||||
import { getHandler } from "../engine/registry";
|
||||
import type { CatalogComponent, FlowNode } from "../../shared/types";
|
||||
import type { RunContext } from "../engine/types";
|
||||
import "./pdf";
|
||||
|
||||
const directories: string[] = [];
|
||||
const context = {} as RunContext;
|
||||
const component = {} as CatalogComponent;
|
||||
const node = (id: string): FlowNode => ({ id, componentId: id, position: { x: 0, y: 0 }, values: {}, options: {} });
|
||||
async function run(id: string, args: Record<string, unknown>) {
|
||||
const handler = getHandler(id);
|
||||
expect(handler, id).toBeTruthy();
|
||||
return handler!(context, node(id), args, component);
|
||||
}
|
||||
afterEach(async () => Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true }))));
|
||||
|
||||
describe("PDF high-level capabilities", () => {
|
||||
it("creates a pdf fixture", async () => {
|
||||
const directory = await mkdtempSafe();
|
||||
const pdf = await PDFDocument.create();
|
||||
const page = pdf.addPage([220, 120]);
|
||||
const font = await pdf.embedFont(StandardFonts.Helvetica);
|
||||
const png = new PNG({ width: 8, height: 8 });
|
||||
png.data.fill(255); png.data[0] = 20; png.data[1] = 90; png.data[2] = 200; png.data[3] = 255;
|
||||
page.drawImage(await pdf.embedPng(PNG.sync.write(png)), { x: 140, y: 70, width: 40, height: 40 });
|
||||
for (const [value, x, y] of [["Name", 24, 90], ["Age", 120, 90], ["Alice", 24, 72], ["30", 120, 72]] as Array<[string, number, number]>) page.drawText(value, { x, y, size: 9, font });
|
||||
await writeFile(path.join(directory, "source.pdf"), await pdf.save());
|
||||
const filePath = path.join(directory, "source.pdf");
|
||||
expect((await readFile(path.join(directory, "source.pdf"))).byteLength).toBeGreaterThan(0);
|
||||
const images = await run("PDF_GetPdfImg", { fileInPath: filePath, pdfRange: "all", pdfNum: "1", imgSavePath: directory, imageType: "png" }) as string[];
|
||||
expect(images.length).toBeGreaterThan(0);
|
||||
expect((await readFile(images[0])).subarray(0, 4)).toEqual(Buffer.from([0x89, 0x50, 0x4e, 0x47]));
|
||||
|
||||
await expect(run("PDF_GetPdfTable", { fileInPath: filePath, pdfRange: "all", pdfNum: "1" })).resolves.toEqual([["Name", "Age"], ["Alice", "30"]]);
|
||||
|
||||
const encryptedPath = path.join(directory, "encrypted.pdf");
|
||||
await writeFile(encryptedPath, await readFile(filePath));
|
||||
await expect(run("PDF_PdfEncryption", { fileInPath: encryptedPath, passWord: "secret" })).resolves.toBe(encryptedPath);
|
||||
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
||||
const opened = await pdfjs.getDocument({ data: new Uint8Array(await readFile(encryptedPath)), password: "secret" }).promise;
|
||||
expect(opened.numPages).toBe(1);
|
||||
});
|
||||
});
|
||||
async function mkdtempSafe(): Promise<string> {
|
||||
const directory = await (await import("node:fs/promises")).mkdtemp(path.join(os.tmpdir(), "rpa-pdf-high-"));
|
||||
directories.push(directory);
|
||||
return directory;
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import fsSync from "node:fs";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { PDFDocument } from "pdf-lib";
|
||||
import path from "node:path";
|
||||
import { register } from "../engine/registry";
|
||||
import { RuntimeError, type CapabilityHandler } from "../engine/types";
|
||||
|
||||
@ -60,3 +62,203 @@ register("PDF_GetPdfText", async (_ctx, node, args) => {
|
||||
}
|
||||
return chunks.join("\n");
|
||||
});
|
||||
|
||||
type PdfJsDocument = Awaited<ReturnType<typeof import("pdfjs-dist/legacy/build/pdf.mjs")["getDocument"]>["promise"]>;
|
||||
|
||||
function standardFontUrl(): string | undefined {
|
||||
const candidates = [
|
||||
process.env.RPA_PDFJS_STANDARD_FONTS,
|
||||
path.join(process.cwd(), "node_modules", "pdfjs-dist", "standard_fonts"),
|
||||
path.join(process.execPath, "resources", "app.asar", "node_modules", "pdfjs-dist", "standard_fonts"),
|
||||
path.join(process.execPath, "..", "resources", "app.asar", "node_modules", "pdfjs-dist", "standard_fonts")
|
||||
].filter(Boolean) as string[];
|
||||
const found = candidates.find((candidate) => fsSync.existsSync(candidate));
|
||||
return found ? pathToFileURL(path.join(found, path.sep)).href : undefined;
|
||||
}
|
||||
|
||||
class LocalStandardFontFactory {
|
||||
baseUrl: string;
|
||||
constructor(options: { baseUrl: string }) { this.baseUrl = options.baseUrl; }
|
||||
async fetch({ filename }: { filename: string }): Promise<Uint8Array> {
|
||||
return new Uint8Array(await fs.readFile(path.join(fileURLToPath(this.baseUrl), filename)));
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPdf(filePath: string, password: string) {
|
||||
const canvasModule = await import("@napi-rs/canvas");
|
||||
const globals = globalThis as unknown as Record<string, unknown>;
|
||||
globals.Path2D = canvasModule.Path2D;
|
||||
globals.DOMMatrix = canvasModule.DOMMatrix;
|
||||
globals.ImageData = canvasModule.ImageData;
|
||||
const pdfjs = await import("pdfjs-dist/legacy/build/pdf.mjs");
|
||||
return { pdfjs, document: await pdfjs.getDocument({ data: new Uint8Array(await fs.readFile(filePath)), password: password || undefined, isEvalSupported: false, useSystemFonts: false, standardFontDataUrl: standardFontUrl(), StandardFontDataFactory: LocalStandardFontFactory }).promise };
|
||||
}
|
||||
|
||||
function pageIndices(document: PdfJsDocument, raw: unknown): number[] {
|
||||
const range = String(raw ?? "all").trim().toLowerCase();
|
||||
if (!range || range === "all") return Array.from({ length: document.numPages }, (_, index) => index + 1);
|
||||
const result = new Set<number>();
|
||||
for (const part of range.split(/[,,;;\s]+/).filter(Boolean)) {
|
||||
const match = part.match(/^(\d+)-(\d+)$/);
|
||||
if (match) {
|
||||
const start = Math.max(1, Number(match[1])); const end = Math.min(document.numPages, Number(match[2]));
|
||||
for (let index = start; index <= end; index += 1) result.add(index);
|
||||
} else {
|
||||
const index = Number(part);
|
||||
if (index >= 1 && index <= document.numPages) result.add(Math.floor(index));
|
||||
}
|
||||
}
|
||||
const values = [...result].sort((left, right) => left - right);
|
||||
if (!values.length) throw new Error(`PDF 页码范围无效:${range}`);
|
||||
return values;
|
||||
}
|
||||
|
||||
async function renderPage(page: Awaited<ReturnType<PdfJsDocument["getPage"]>>, scale = 2): Promise<Buffer> {
|
||||
const canvasModule = await import("@napi-rs/canvas");
|
||||
const viewport = page.getViewport({ scale });
|
||||
const canvas = canvasModule.createCanvas(Math.ceil(viewport.width), Math.ceil(viewport.height));
|
||||
const context = canvas.getContext("2d");
|
||||
context.fillStyle = "white"; context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
await page.render({ canvasContext: context as unknown as CanvasRenderingContext2D, viewport }).promise;
|
||||
return canvas.toBuffer("image/png");
|
||||
}
|
||||
|
||||
function saveImage(folder: string, prefix: string, suffix: string, data: Buffer, type: string): string {
|
||||
const ext = /jpe?g/i.test(type) ? "jpg" : "png";
|
||||
const output = path.join(path.resolve(folder), `${prefix}_${suffix}_${Date.now()}.${ext}`);
|
||||
fsSync.mkdirSync(path.dirname(output), { recursive: true });
|
||||
fsSync.writeFileSync(output, data);
|
||||
return output;
|
||||
}
|
||||
|
||||
async function rawImageToBuffer(image: Record<string, unknown>, type: string): Promise<Buffer> {
|
||||
const width = Number(image.width || 0); const height = Number(image.height || 0);
|
||||
const data = image.data;
|
||||
if (!width || !height || !(data instanceof Uint8Array) || data.length === 0) throw new Error("PDF 图片数据无效");
|
||||
if (data[0] === 0xff && data[1] === 0xd8) return Buffer.from(data);
|
||||
const canvasModule = await import("@napi-rs/canvas");
|
||||
const canvas = canvasModule.createCanvas(width, height); const context = canvas.getContext("2d");
|
||||
const target = context.createImageData(width, height); const kind = Number(image.kind || 0);
|
||||
if (kind === 1) {
|
||||
const bytesPerRow = Math.ceil(width / 8);
|
||||
for (let y = 0; y < height; y += 1) for (let x = 0; x < width; x += 1) {
|
||||
const bit = (data[y * bytesPerRow + (x >> 3)] >> (7 - (x & 7))) & 1; const output = (y * width + x) * 4; const color = bit ? 255 : 0;
|
||||
target.data[output] = color; target.data[output + 1] = color; target.data[output + 2] = color; target.data[output + 3] = 255;
|
||||
}
|
||||
} else if (kind === 3) { target.data.set(data.subarray(0, Math.min(data.length, target.data.length))); }
|
||||
else {
|
||||
for (let source = 0, targetIndex = 0; source < data.length && targetIndex < target.data.length; source += 3, targetIndex += 4) {
|
||||
target.data[targetIndex] = data[source]; target.data[targetIndex + 1] = data[source + 1] ?? 0; target.data[targetIndex + 2] = data[source + 2] ?? 0; target.data[targetIndex + 3] = 255;
|
||||
}
|
||||
}
|
||||
context.putImageData(target, 0, 0);
|
||||
return /jpe?g/i.test(type) ? canvas.toBuffer("image/jpeg") : canvas.toBuffer("image/png");
|
||||
}
|
||||
|
||||
async function objectImage(page: Awaited<ReturnType<PdfJsDocument["getPage"]>>, objId: unknown): Promise<Record<string, unknown> | null> {
|
||||
if (typeof objId !== "string") return null;
|
||||
const objects = objId.startsWith("g_") ? page.commonObjs : page.objs;
|
||||
try { if (objects.has(objId)) return objects.get(objId) as Record<string, unknown>; } catch {}
|
||||
return await new Promise((resolve) => { try { objects.get(objId, (value: unknown) => resolve(value as Record<string, unknown>)); } catch { resolve(null); } });
|
||||
}
|
||||
|
||||
const encryption: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const filePath = required(text(args, ["fileInPath", "path", "file"]), "PDF文件路径", node.id, node.componentId);
|
||||
const password = String(args.passWord ?? args.password ?? "");
|
||||
if (!password) throw new RuntimeError("validation_error", "PDF密码为空", { nodeId: node.id, componentId: node.componentId });
|
||||
const { PDFDocument } = await import("@cantoo/pdf-lib");
|
||||
const document = await PDFDocument.load(await fs.readFile(filePath), { ignoreEncryption: true });
|
||||
document.encrypt({ userPassword: password, ownerPassword: password, permissions: { printing: "highResolution", copying: true, modifying: false } });
|
||||
const temporary = `${filePath}.${Date.now()}.enc`;
|
||||
await fs.writeFile(temporary, await document.save());
|
||||
await fs.rename(temporary, filePath);
|
||||
return filePath;
|
||||
};
|
||||
|
||||
const extractImages: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const filePath = required(text(args, ["fileInPath", "path", "file"]), "PDF文件路径", node.id, node.componentId);
|
||||
const folder = required(text(args, ["imgSavePath", "pdfSavePath", "folder", "path"]), "图片保存路径", node.id, node.componentId);
|
||||
const type = text(args, ["imageType", "type"], "png");
|
||||
const prefix = text(args, ["imageName", "name"], "pdf-image");
|
||||
const { pdfjs, document } = await loadPdf(filePath, String(args.pdfPassWord ?? args.password ?? ""));
|
||||
const outputs: string[] = [];
|
||||
for (const pageNumber of pageIndices(document, args.pdfNum)) {
|
||||
const page = await document.getPage(pageNumber);
|
||||
const opList = await page.getOperatorList();
|
||||
let sequence = 0;
|
||||
for (let index = 0; index < opList.fnArray.length; index += 1) {
|
||||
const op = opList.fnArray[index]; const commandArgs = opList.argsArray[index] as unknown[];
|
||||
let image: Record<string, unknown> | null = null;
|
||||
if (op === pdfjs.OPS.paintImageXObject || op === pdfjs.OPS.paintImageXObjectRepeat) image = await objectImage(page, commandArgs[0]);
|
||||
else if (op === pdfjs.OPS.paintInlineImageXObject) image = commandArgs[0] as Record<string, unknown>;
|
||||
else if (op === pdfjs.OPS.paintInlineImageXObjectGroup) image = commandArgs[0] as Record<string, unknown>;
|
||||
if (!image) continue;
|
||||
sequence += 1;
|
||||
outputs.push(saveImage(folder, `${prefix}-p${pageNumber}`, String(sequence), await rawImageToBuffer(image, type), type));
|
||||
}
|
||||
}
|
||||
return outputs;
|
||||
};
|
||||
|
||||
const pdfToImages: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const filePath = required(text(args, ["fileInPath", "path", "file"]), "PDF文件路径", node.id, node.componentId);
|
||||
const folder = required(text(args, ["pdfSavePath", "imgSavePath", "folder", "path"]), "图片保存路径", node.id, node.componentId);
|
||||
const type = text(args, ["imageType", "type"], "png"); const prefix = text(args, ["imageName", "name"], "pdf-page");
|
||||
const { document } = await loadPdf(filePath, String(args.pdfPassWord ?? args.password ?? ""));
|
||||
const outputs: string[] = [];
|
||||
for (const pageNumber of pageIndices(document, args.pdfNum)) {
|
||||
const buffer = await renderPage(await document.getPage(pageNumber));
|
||||
outputs.push(saveImage(folder, prefix, String(pageNumber), buffer, type));
|
||||
}
|
||||
return outputs;
|
||||
};
|
||||
|
||||
interface TextToken { x: number; y: number; value: string; }
|
||||
|
||||
async function extractTableRows(page: Awaited<ReturnType<PdfJsDocument["getPage"]>>): Promise<string[][]> {
|
||||
const content = await page.getTextContent();
|
||||
const tokens = content.items.flatMap((item) => {
|
||||
if (!("str" in item) || !item.str.trim()) return [];
|
||||
const transform = (item as { transform?: number[] }).transform ?? [1,0,0,1,0,0];
|
||||
return [{ x: Number(transform[4] || 0), y: Number(transform[5] || 0), value: item.str.trim() }];
|
||||
});
|
||||
if (tokens.length < 2) return [];
|
||||
const lines = new Map<number, TextToken[]>();
|
||||
for (const token of [...tokens].sort((left, right) => right.y - left.y || left.x - right.x)) {
|
||||
const lineKey = [...lines.keys()].find((key) => Math.abs(key - token.y) <= 3.5) ?? token.y;
|
||||
if (!lines.has(lineKey)) lines.set(lineKey, []);
|
||||
lines.get(lineKey)!.push(token);
|
||||
}
|
||||
const lineList = [...lines.entries()].map(([y, values]) => ({ y, values: values.sort((left, right) => left.x - right.x) })).sort((left, right) => right.y - left.y);
|
||||
const counts = new Map<number, number>();
|
||||
for (const line of lineList) for (const token of line.values) {
|
||||
const key = Math.round(token.x / 5) * 5;
|
||||
counts.set(key, (counts.get(key) || 0) + 1);
|
||||
}
|
||||
const columns = [...counts.entries()].filter(([, count]) => count >= Math.min(2, lineList.length)).sort((left, right) => left[0] - right[0]).map(([key]) => key);
|
||||
if (columns.length < 2) return [];
|
||||
const rows: string[][] = [];
|
||||
for (const line of lineList) {
|
||||
const cells = columns.map(() => "");
|
||||
for (const token of line.values) {
|
||||
let nearest = 0;
|
||||
for (let index = 1; index < columns.length; index += 1) if (Math.abs(token.x - columns[index]) < Math.abs(token.x - columns[nearest])) nearest = index;
|
||||
cells[nearest] = cells[nearest] ? `${cells[nearest]} ${token.value}` : token.value;
|
||||
}
|
||||
if (cells.filter(Boolean).length >= 2) rows.push(cells);
|
||||
}
|
||||
return rows.length >= 2 ? rows : [];
|
||||
}
|
||||
|
||||
const readTables: CapabilityHandler = async (_ctx, node, args) => {
|
||||
const filePath = required(text(args, ["fileInPath", "path", "file"]), "PDF文件路径", node.id, node.componentId);
|
||||
const { document } = await loadPdf(filePath, String(args.pdfPassWord ?? args.password ?? ""));
|
||||
const rows: string[][] = [];
|
||||
for (const pageNumber of pageIndices(document, args.pdfNum)) rows.push(...await extractTableRows(await document.getPage(pageNumber)));
|
||||
return rows;
|
||||
};
|
||||
|
||||
register("PDF_PdfEncryption", encryption);
|
||||
register("PDF_GetPdfImg", extractImages);
|
||||
register("PDF_PdfToImg", pdfToImages);
|
||||
register("PDF_GetPdfTable", readTables);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user