mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2026-09-03 05:10:16 +03:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aec8b32e2 |
+1
-1
@@ -67,7 +67,7 @@ springBoot {
|
||||
|
||||
allprojects {
|
||||
group = 'stirling.software'
|
||||
version = '2.4.5'
|
||||
version = '2.5.0'
|
||||
|
||||
configurations.configureEach {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
|
||||
Generated
+247
-183
@@ -10,30 +10,31 @@
|
||||
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@cantoo/pdf-lib": "^2.5.3",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^2.5.0",
|
||||
"@embedpdf/engines": "^2.5.0",
|
||||
"@embedpdf/models": "^2.5.0",
|
||||
"@embedpdf/plugin-annotation": "^2.5.0",
|
||||
"@embedpdf/plugin-attachment": "^2.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.5.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-export": "^2.5.0",
|
||||
"@embedpdf/plugin-history": "^2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-pan": "^2.5.0",
|
||||
"@embedpdf/plugin-print": "^2.5.0",
|
||||
"@embedpdf/plugin-redaction": "^2.5.0",
|
||||
"@embedpdf/plugin-render": "^2.5.0",
|
||||
"@embedpdf/plugin-rotate": "^2.5.0",
|
||||
"@embedpdf/plugin-scroll": "^2.5.0",
|
||||
"@embedpdf/plugin-search": "^2.5.0",
|
||||
"@embedpdf/plugin-selection": "^2.5.0",
|
||||
"@embedpdf/plugin-spread": "^2.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.5.0",
|
||||
"@embedpdf/plugin-tiling": "^2.5.0",
|
||||
"@embedpdf/plugin-viewport": "^2.5.0",
|
||||
"@embedpdf/plugin-zoom": "^2.5.0",
|
||||
"@embedpdf/core": "^2.6.0",
|
||||
"@embedpdf/engines": "^2.6.0",
|
||||
"@embedpdf/models": "^2.6.0",
|
||||
"@embedpdf/plugin-annotation": "^2.6.0",
|
||||
"@embedpdf/plugin-attachment": "^2.6.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.6.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.6.0",
|
||||
"@embedpdf/plugin-export": "^2.6.0",
|
||||
"@embedpdf/plugin-history": "^2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.6.0",
|
||||
"@embedpdf/plugin-pan": "^2.6.0",
|
||||
"@embedpdf/plugin-print": "^2.6.0",
|
||||
"@embedpdf/plugin-redaction": "^2.6.0",
|
||||
"@embedpdf/plugin-render": "^2.6.0",
|
||||
"@embedpdf/plugin-rotate": "^2.6.0",
|
||||
"@embedpdf/plugin-scroll": "^2.6.0",
|
||||
"@embedpdf/plugin-search": "^2.6.0",
|
||||
"@embedpdf/plugin-selection": "^2.6.0",
|
||||
"@embedpdf/plugin-spread": "^2.6.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.6.0",
|
||||
"@embedpdf/plugin-tiling": "^2.6.0",
|
||||
"@embedpdf/plugin-viewport": "^2.6.0",
|
||||
"@embedpdf/plugin-zoom": "^2.6.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -61,7 +62,6 @@
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
"license-report": "^6.8.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
@@ -366,6 +366,21 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@cantoo/pdf-lib": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@cantoo/pdf-lib/-/pdf-lib-2.5.3.tgz",
|
||||
"integrity": "sha512-SBQp8i/XdWNUhLutn5P67Pwj4X9vU046BRpfOMODJZuYVrgChtsTfgdnlW2O7x8gdXs8j7NoTaWI/b78E2oVmQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"color": "^4.2.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"node-html-better-parser": ">=1.4.0",
|
||||
"pako": "^1.0.11",
|
||||
"tslib": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
|
||||
@@ -555,13 +570,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/core": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.5.0.tgz",
|
||||
"integrity": "sha512-nI7GnA5xCNtJHAdKBLPKJVvi4+yAKjy1sysaDf+qp+z3D81Hy8oAcl///QTaZ9ob0SL2jyqi3x//hKl0Rwmgrw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-2.6.0.tgz",
|
||||
"integrity": "sha512-859GUvZ3BLpJuKTiwcPPMNn9CSlMaPjQ4yXnyQRngfbvDAiijIIpVLaC98B08Nx6QsUcD3cs/6+wkB888lNsDw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/engines": "2.5.0",
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/engines": "2.6.0",
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -572,9 +587,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/engines": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.5.0.tgz",
|
||||
"integrity": "sha512-SEknNmQrYvkAZgJllRKXuvXSrHSndDQsr7b3mrIVa9bzV6TeZua0a/YUlvI3/jf74Sdajru3XKPe22iHEOH4Zg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/engines/-/engines-2.6.0.tgz",
|
||||
"integrity": "sha512-zW3927u0wbFBD2tQLWbE45DEBIMkZyN7n5O2p70er6u7mP1XYEz7Ud9NxcPL/3b5MzDfPBTSyxM3T12e+ZeAxw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/fonts-arabic": "1.0.0",
|
||||
@@ -584,8 +599,8 @@
|
||||
"@embedpdf/fonts-latin": "1.0.0",
|
||||
"@embedpdf/fonts-sc": "1.0.0",
|
||||
"@embedpdf/fonts-tc": "1.0.0",
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/pdfium": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0",
|
||||
"@embedpdf/pdfium": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -638,31 +653,31 @@
|
||||
"license": "OFL-1.1"
|
||||
},
|
||||
"node_modules/@embedpdf/models": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.5.0.tgz",
|
||||
"integrity": "sha512-wu7XgargYBQEh46hVnfsmkTF6TvuoP9nAkTASR60s5ourjlT12qL9RiFLpwGkOBfs8E58h8V5hkgKsra5t03Lw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/models/-/models-2.6.0.tgz",
|
||||
"integrity": "sha512-6zuoJE79WXyRXKhJXhl+8p4njuC1nxPpKYRIs54PRLgTkHOLaou+G+ZunEd99XOoVssHLCjxWBUpg46ihQwXDw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/pdfium": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.5.0.tgz",
|
||||
"integrity": "sha512-2VEO4cNZsV8ig9upS+C+x3Tb58aqNxiAdaUMlD2ZZT8FgszhsV9xMyEuM2maFRdjeT7EO37FtzYBdXc/K67ivA==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/pdfium/-/pdfium-2.6.0.tgz",
|
||||
"integrity": "sha512-eYXU1VvVI0e9OqOzvsTcsU6YSLq9F7jcAiIbtMB+NxApvvH3kHz3FPEcf8ha2ZiLftF5OAD8K89SSE5GLE6t1A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-annotation": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.5.0.tgz",
|
||||
"integrity": "sha512-S5zCeWU3hM9jrnaGuW5RAXt+AzXXvQbFtAdCtxHW1hFADiZ97FKr8KS9MGCkkj6C9madtZP6iUJikvnhoLCABQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-annotation/-/plugin-annotation-2.6.0.tgz",
|
||||
"integrity": "sha512-FJgGy6lhKrWsiJjh7jZ92NwMBob5GOwfYejQl28JFk6muEQORLtysz5gaeyMpMIyxnfjlf9Eqv8Z6LBBfGLGOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0",
|
||||
"@embedpdf/utils": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-history": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-selection": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-history": "2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.6.0",
|
||||
"@embedpdf/plugin-selection": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -671,15 +686,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-attachment": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-attachment/-/plugin-attachment-2.5.0.tgz",
|
||||
"integrity": "sha512-dVVnklI2V1Tsnkf2Ob1PY/R8U6bImhaYwiWm8TwJnNzXXT3aTHQ81lGQOv+pIA9f02L8Y4J5OOKkVrKIpqBHug==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-attachment/-/plugin-attachment-2.6.0.tgz",
|
||||
"integrity": "sha512-6UZkj7jFWCruR69OPQFMqbJTgwdra4rnJSBfLA8yLxgz2zTsgt3owjfQDmlJvAQ7G1/rZM2T+EJeuozulj4NoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -688,15 +703,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-bookmark": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.5.0.tgz",
|
||||
"integrity": "sha512-2N5kGoamUrQqWZC5SMWIhdyBHqZN/CdcGf8GVH71FFw3AU6rmZ1AD/AkLzgqoYGIuZFE8ACckdrhtbpsZMmSDQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-bookmark/-/plugin-bookmark-2.6.0.tgz",
|
||||
"integrity": "sha512-4JmaFD+gFaLj8Bayi6Fm5qxMoRH+JUy+L3S6xk1KM8YWjJyzsoz9C2mHSXKJ0GBMgiOkjaBuJSWqLgMe/oz7OQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -705,15 +720,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-document-manager": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.5.0.tgz",
|
||||
"integrity": "sha512-I8Z/0B7R/YhtVaJFruwFO+QBLIDmQfHx9WVlrDXWZs68YiGwEbjSyizEIEqtulUJxcXfPs2Tf7oIBbdSuPG2NQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-document-manager/-/plugin-document-manager-2.6.0.tgz",
|
||||
"integrity": "sha512-fcx0JKDboEV8eQ4r++ksDHPDuUz40oOmtHDqxYLw6cpos0fqW0p55OP+fKp6LfC/bY7ULVDrmcEQf1cD9Qho4w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -722,15 +737,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-export": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.5.0.tgz",
|
||||
"integrity": "sha512-KC9jXqwcxe76QqfxLx0tnrSdFoApTFOpT+dwrvox186uxYKSmSt1JHFWe4THB/A63hCNr8uMwyswYdFO8fWNHw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-export/-/plugin-export-2.6.0.tgz",
|
||||
"integrity": "sha512-i1Xy7qUipVVLDPnnY22hm3RNMx33lvuNbCuPggql5Ws6WBLG9YhDsK+v0JVe2sDSlifa5SJwuBlMHZWPRTZyxg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -739,15 +754,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-history": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.5.0.tgz",
|
||||
"integrity": "sha512-Av9NBSE9Or1Y6cXcNWpx0bBZN3yI4vywa6kSNjhaqOrgpQDWMaTO57eApJpyHzBodqEztY+klE9YJ7MH88zm6w==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-2.6.0.tgz",
|
||||
"integrity": "sha512-cfVoBjkIbFiRsQu/cwPEi0rrTAF7jriAGzABWawnSTKYEPFrU3LDHO7TewgBz45kHl9pSwvRexaIdTR8ECIKbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -756,15 +771,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-interaction-manager": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.5.0.tgz",
|
||||
"integrity": "sha512-QrmowLVvC5FNZdvVr2kczSDdnHHOuhf+So0VG5Ythts/OL1bIR/0OOpuyJsScTyo5boYnRkXv8yPf8htL57YKQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-2.6.0.tgz",
|
||||
"integrity": "sha512-9bruF6M6GKVdABRTinHsZ+izf2tDQwDEcNI0CHVc5gurrz3CQfAGP2sJkv8uQrXyYTK3zV2Oq6zGknk7Hdx9mA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -773,17 +788,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-pan": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.5.0.tgz",
|
||||
"integrity": "sha512-DfdA+hBm9kGYYy7OuJym6azk2h2U/Geirud+tmVzFSL7+OZ3tZ3K9fqj07w66zx0msyUVlYrXzkYSU9NEmwpLA==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-pan/-/plugin-pan-2.6.0.tgz",
|
||||
"integrity": "sha512-r8AXcXUy6NMYDaQeixScbeFfmZIvWpUUjx3gxjP4J90xfxXnuz/g/lnh4D2DBaiK4mt6crIVBpXU9IUwMIcUMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.6.0",
|
||||
"@embedpdf/plugin-viewport": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -792,15 +807,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-print": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.5.0.tgz",
|
||||
"integrity": "sha512-qejq7/0K9hh3hzop+u+Qmn7ijTqGcDhxaiXoPkyl91CZVOyAD8qMBzWnhC7vRNOB7hcYgBP81uegE3se+EIlcA==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-print/-/plugin-print-2.6.0.tgz",
|
||||
"integrity": "sha512-cgWRqVtRgCCLCn1ViuZEFr+ZJ3QI61/5s9tl3T9x81rwkBN4HT582BYzyRnLBzTMYKxkQZDr1WxfS8ctdlHEUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0",
|
||||
@@ -809,20 +824,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-redaction": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.5.0.tgz",
|
||||
"integrity": "sha512-G0cm1hLWi09gU8WV+IShq2XHkmLtEbk+EvD3dIiyJV2kbOjgwGSC2Ezt8br3DzH6R/0bF6RAbDIpFyS2Q0oMfg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-redaction/-/plugin-redaction-2.6.0.tgz",
|
||||
"integrity": "sha512-DdDnmOl9K0N4dpTeUohavxQyrfollhkjT+zdfkna3Fc7F4jfl3Vg6uKoGmT71A+Vp4uTGNLt6cNCscbJW9E9kQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0",
|
||||
"@embedpdf/utils": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-annotation": "2.5.0",
|
||||
"@embedpdf/plugin-history": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/plugin-selection": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-annotation": "2.6.0",
|
||||
"@embedpdf/plugin-history": "2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.6.0",
|
||||
"@embedpdf/plugin-selection": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -831,15 +846,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-render": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.5.0.tgz",
|
||||
"integrity": "sha512-nrTmg8cVMohcKYiQ/7erErsaWlyaq20OtXbVjmnPNnqz4amJLAjlPyudTJRlWWPyIiri9SF4A0ue5ICDY2sypg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-2.6.0.tgz",
|
||||
"integrity": "sha512-Rk4QCxDOzhQrvKPt/G3G+p5ELwnKFkC5ljHMd7ND23atR9E3wm5W3+Nx3FaAYYPrpfqQ7BrbKnfQ7SkUbDxS3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -848,15 +863,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-rotate": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.5.0.tgz",
|
||||
"integrity": "sha512-crFsXduaxNZJmVRfgklBpO4x4i9cRxPmfFBvdIoyJ1ea6AGOCL0rQKQcfqHTFdgtPzlVUiIg6Hi2v+033jdLUg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-rotate/-/plugin-rotate-2.6.0.tgz",
|
||||
"integrity": "sha512-zgF2S5cfkOxkOWrwoLQLN8scJgKBEhyhVOv/RNdeAKP6qE3h28AGRmDeMsekBDbiInlIxIHzynE5vVcTNf5EnQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -865,16 +880,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-scroll": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.5.0.tgz",
|
||||
"integrity": "sha512-AdLuSgvAaukLl1uQ0FbswcAIPFaR3Jk2ZbEJpWLd9E6iQ+66Cta0Sz8d5J6ndx7VBlRYAZwoqiXF85utJxpQ5g==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-2.6.0.tgz",
|
||||
"integrity": "sha512-BEgSy6cs9+MLCS0Z3/FYMdA4Ygt6ddYIAg28XlF20kN3tLj8BQUo5qx6adI+SlwrFFGY52VAjjK7VSBuGfn19g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-viewport": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -883,15 +898,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-search": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.5.0.tgz",
|
||||
"integrity": "sha512-ycHJh05vBZ1PTSdEMgdx6K1py0oklwbwY2eXO4nD54EN9EVZgWlYC4Q+u8nyGOiNL6VmJYcqJ0HmjSWBdmGWBw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-search/-/plugin-search-2.6.0.tgz",
|
||||
"integrity": "sha512-GSzJkmuK9LE7LmlTwnDl71KdD9prHlCjgFs5Tm0K8qjELOSH+oduFXusIuf654+UQveDYczpzBVUcqb4yBf1xA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -900,17 +915,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-selection": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.5.0.tgz",
|
||||
"integrity": "sha512-M3WDjahig/6KE83SZGvTaJWhqEOIzH002k2fpJVuks926UBnfgYCH8uqV7SOUQTneQDmIa0PlyFiuEXDw1Ocrw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-2.6.0.tgz",
|
||||
"integrity": "sha512-VrW0duVxLwaquInwmuNDMz8o0tfCDwe3j81fvTUDW/s7KqnzFbxK7vEuq5TEtxWuSng2DXxyX3r1ntCm4X/NCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0",
|
||||
"@embedpdf/utils": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0",
|
||||
"@embedpdf/utils": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -919,15 +934,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-spread": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.5.0.tgz",
|
||||
"integrity": "sha512-kG8HZMZmbpUVDxCOEyQzIiMPW+VjjebOl93V+quAH+GAI5Tkg6exPyyQ2+/DOJPCtYX4Kh2z2aeoyK2b7NRgIQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-spread/-/plugin-spread-2.6.0.tgz",
|
||||
"integrity": "sha512-0mzPCJlw1X7jWeDg5JssU6/HCFtyOP7scEdbIaASYzofGXa2Rj8/+L+UDBrb+KTF6CR4X6fEfeNmxWmAatOAWQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -936,16 +951,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-thumbnail": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.5.0.tgz",
|
||||
"integrity": "sha512-iWofJSXKbWrgvS2fe8v3U1+e2wjBRXD2i1DUcJKnTrqyfjZ8YzUomc5EzdG2RT7uUjtqrcu7463TZ9JHXUkASQ==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-thumbnail/-/plugin-thumbnail-2.6.0.tgz",
|
||||
"integrity": "sha512-Sj4jCV1MNk+19zKWX4KfSl5c0YHrqVG83pEYfqexjSkSX7y7HRwAOtMBtNd3uLInPPSBnzDxj+KlJlIe8RPPJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-render": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-render": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -954,18 +969,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-tiling": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.5.0.tgz",
|
||||
"integrity": "sha512-oih0GyGOJvfaXPLSEY+qfC05UUU1ZkADEbr6uCwRMmdHIXu/0ZTJnAToegfWXtfE+Sw0J5wscVkipXXIX5azlw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-tiling/-/plugin-tiling-2.6.0.tgz",
|
||||
"integrity": "sha512-qyiHWljryHWQ7uzip2WDg4x28o/1QM0wh9oIyz5WlBnrDaK6bLJGsWUym5P6WfLp0Y8h6GFAslNNcgjBv6E3qw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-render": "2.5.0",
|
||||
"@embedpdf/plugin-scroll": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-render": "2.6.0",
|
||||
"@embedpdf/plugin-scroll": "2.6.0",
|
||||
"@embedpdf/plugin-viewport": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -974,15 +989,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-viewport": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.5.0.tgz",
|
||||
"integrity": "sha512-z0AXHA9Z3rZdCLje7P2NsQbxKLJ4b/l8lgzXOVn5Ow/pIPE0D2P3fn9WzImHTNI1RNrZMdkW9OH3lfkEXFTqHw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-2.6.0.tgz",
|
||||
"integrity": "sha512-Ea7s+LivQ4ph01mVngU2tu2Ni/zulxzIyiifCpMaBMHmvjGFQjJcNNYHR90YuM8keto82KCszxdNDuAEEzT6Wg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -991,17 +1006,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/plugin-zoom": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.5.0.tgz",
|
||||
"integrity": "sha512-HWJlqXOXdv/kttV+XWCCStUZAeLl66AuaO8BsnPlAPwEADLLCH4tR4XqJQoWr7/r5watKP7UeQ00FsWu0oGclw==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/plugin-zoom/-/plugin-zoom-2.6.0.tgz",
|
||||
"integrity": "sha512-2XUgasN2ZQm2MgpB6ls/re/SKhsREvt2D1gIcvJgXvGkene0NcpxGNIRi/+JN7W0fw4x3QtwLQtyF+/0uMgPmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@embedpdf/models": "2.5.0"
|
||||
"@embedpdf/models": "2.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@embedpdf/core": "2.5.0",
|
||||
"@embedpdf/plugin-scroll": "2.5.0",
|
||||
"@embedpdf/plugin-viewport": "2.5.0",
|
||||
"@embedpdf/core": "2.6.0",
|
||||
"@embedpdf/plugin-scroll": "2.6.0",
|
||||
"@embedpdf/plugin-viewport": "2.6.0",
|
||||
"preact": "^10.26.4",
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0",
|
||||
@@ -1010,9 +1025,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@embedpdf/utils": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.5.0.tgz",
|
||||
"integrity": "sha512-JjYj6BRzu9oesA1JOqKPFMEWKinjvJIjziWu1j6lDXxLsE59bkShjUKbaEG+lkXRspuZRWNP++rzE2p2Ht4veg==",
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@embedpdf/utils/-/utils-2.6.0.tgz",
|
||||
"integrity": "sha512-FT6U6L3Et688urUTyISpYH05w4sG+WzoWxaI7aPU4ieh4c/vVgadUtjZj/QCC8v+DebPYRAX1gpUY7e0Y0HlTQ==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"preact": "^10.26.4",
|
||||
@@ -6480,11 +6495,23 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1",
|
||||
"color-string": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
@@ -6497,9 +6524,18 @@
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/color-string": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
|
||||
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "^1.0.0",
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
@@ -6618,6 +6654,12 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/crypto-js": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz",
|
||||
@@ -8867,6 +8909,22 @@
|
||||
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-entities": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/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/html-escaper": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||
@@ -10713,6 +10771,15 @@
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-html-better-parser": {
|
||||
"version": "1.5.8",
|
||||
"resolved": "https://registry.npmjs.org/node-html-better-parser/-/node-html-better-parser-1.5.8.tgz",
|
||||
"integrity": "sha512-t/wAKvaTSKco43X+yf9+76RiMt18MtMmzd4wc7rKj+fWav6DV4ajDEKdWlLzSE8USDF5zr/06uGj0Wr/dGAFtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"html-entities": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.27",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
|
||||
@@ -11268,24 +11335,6 @@
|
||||
"node": ">= 14.16"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib": {
|
||||
"version": "1.17.1",
|
||||
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
|
||||
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pdf-lib/standard-fonts": "^1.0.0",
|
||||
"@pdf-lib/upng": "^1.0.1",
|
||||
"pako": "^1.0.11",
|
||||
"tslib": "^1.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/pdf-lib/node_modules/tslib": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/pdfjs-dist": {
|
||||
"version": "5.4.530",
|
||||
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.530.tgz",
|
||||
@@ -12993,6 +13042,21 @@
|
||||
"integrity": "sha512-zyxW5vuJVnQdGcU+kAj9FYl7WaAunY3kA5S7mPg0xJiujL9+sPAWfSQHS5tXaJXDUa4FuZeKhfdCDQ6K3wfkpQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/simple-swizzle": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
|
||||
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-arrayish": "^0.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-swizzle/node_modules/is-arrayish": {
|
||||
"version": "0.3.4",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
|
||||
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/slash": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz",
|
||||
|
||||
+24
-24
@@ -7,29 +7,29 @@
|
||||
"dependencies": {
|
||||
"@atlaskit/pragmatic-drag-and-drop": "^1.7.7",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@embedpdf/core": "^2.5.0",
|
||||
"@embedpdf/engines": "^2.5.0",
|
||||
"@embedpdf/models": "^2.5.0",
|
||||
"@embedpdf/plugin-annotation": "^2.5.0",
|
||||
"@embedpdf/plugin-attachment": "^2.5.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.5.0",
|
||||
"@embedpdf/plugin-export": "^2.5.0",
|
||||
"@embedpdf/plugin-history": "^2.5.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.5.0",
|
||||
"@embedpdf/plugin-pan": "^2.5.0",
|
||||
"@embedpdf/plugin-print": "^2.5.0",
|
||||
"@embedpdf/plugin-redaction": "^2.5.0",
|
||||
"@embedpdf/plugin-render": "^2.5.0",
|
||||
"@embedpdf/plugin-rotate": "^2.5.0",
|
||||
"@embedpdf/plugin-scroll": "^2.5.0",
|
||||
"@embedpdf/plugin-search": "^2.5.0",
|
||||
"@embedpdf/plugin-selection": "^2.5.0",
|
||||
"@embedpdf/plugin-spread": "^2.5.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.5.0",
|
||||
"@embedpdf/plugin-tiling": "^2.5.0",
|
||||
"@embedpdf/plugin-viewport": "^2.5.0",
|
||||
"@embedpdf/plugin-zoom": "^2.5.0",
|
||||
"@embedpdf/core": "^2.6.0",
|
||||
"@embedpdf/engines": "^2.6.0",
|
||||
"@embedpdf/models": "^2.6.0",
|
||||
"@embedpdf/plugin-annotation": "^2.6.0",
|
||||
"@embedpdf/plugin-attachment": "^2.6.0",
|
||||
"@embedpdf/plugin-bookmark": "^2.6.0",
|
||||
"@embedpdf/plugin-export": "^2.6.0",
|
||||
"@embedpdf/plugin-history": "^2.6.0",
|
||||
"@embedpdf/plugin-document-manager": "^2.6.0",
|
||||
"@embedpdf/plugin-interaction-manager": "^2.6.0",
|
||||
"@embedpdf/plugin-pan": "^2.6.0",
|
||||
"@embedpdf/plugin-print": "^2.6.0",
|
||||
"@embedpdf/plugin-redaction": "^2.6.0",
|
||||
"@embedpdf/plugin-render": "^2.6.0",
|
||||
"@embedpdf/plugin-rotate": "^2.6.0",
|
||||
"@embedpdf/plugin-scroll": "^2.6.0",
|
||||
"@embedpdf/plugin-search": "^2.6.0",
|
||||
"@embedpdf/plugin-selection": "^2.6.0",
|
||||
"@embedpdf/plugin-spread": "^2.6.0",
|
||||
"@embedpdf/plugin-thumbnail": "^2.6.0",
|
||||
"@embedpdf/plugin-tiling": "^2.6.0",
|
||||
"@embedpdf/plugin-viewport": "^2.6.0",
|
||||
"@embedpdf/plugin-zoom": "^2.6.0",
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.1",
|
||||
"@iconify/react": "^6.0.2",
|
||||
@@ -57,7 +57,7 @@
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
"jszip": "^3.10.1",
|
||||
"license-report": "^6.8.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"@cantoo/pdf-lib": "^2.5.3",
|
||||
"pdfjs-dist": "^5.4.149",
|
||||
"peerjs": "^1.5.5",
|
||||
"posthog-js": "^1.268.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
|
||||
"productName": "Stirling-PDF",
|
||||
"version": "2.4.6",
|
||||
"version": "2.5.0",
|
||||
"identifier": "stirling.pdf.dev",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
@@ -13,8 +13,10 @@
|
||||
"windows": [
|
||||
{
|
||||
"title": "Stirling-PDF",
|
||||
"width": 1280,
|
||||
"height": 800,
|
||||
"width": 1600,
|
||||
"height": 1000,
|
||||
"minWidth": 1100,
|
||||
"minHeight": 700,
|
||||
"resizable": true,
|
||||
"fullscreen": false,
|
||||
"additionalBrowserArgs": "--enable-features=CertVerifierBuiltinFeature"
|
||||
|
||||
@@ -208,7 +208,7 @@ export function useOnboardingOrchestrator(
|
||||
|
||||
try {
|
||||
const [accountData, loginPageData] = await Promise.all([
|
||||
accountService.getAccountData(),
|
||||
accountService.getAccountData({ suppressErrorToast: true }),
|
||||
accountService.getLoginPageData(),
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolType, useToolOperation, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { AdjustContrastParameters, defaultParameters } from '@app/hooks/tools/adjustContrast/useAdjustContrastParameters';
|
||||
import { PDFDocument as PDFLibDocument } from 'pdf-lib';
|
||||
import { PDFDocument as PDFLibDocument } from '@cantoo/pdf-lib';
|
||||
import { applyAdjustmentsToCanvas } from '@app/components/tools/adjustContrast/utils';
|
||||
import { pdfWorkerManager } from '@app/services/pdfWorkerManager';
|
||||
import { createFileFromApiResponse } from '@app/utils/fileResponseUtils';
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useToolOperation, ToolType, CustomProcessorResult } from '@app/hooks/tools/shared/useToolOperation';
|
||||
import { createStandardErrorHandler } from '@app/utils/toolErrorHandler';
|
||||
import { RemoveAnnotationsParameters, defaultParameters } from '@app/hooks/tools/removeAnnotations/useRemoveAnnotationsParameters';
|
||||
import { PDFDocument, PDFName, PDFRef, PDFDict } from 'pdf-lib';
|
||||
import { PDFDocument, PDFName, PDFRef, PDFDict } from '@cantoo/pdf-lib';
|
||||
// Client-side PDF processing using PDF-lib
|
||||
const removeAnnotationsProcessor = async (_parameters: RemoveAnnotationsParameters, files: File[]): Promise<CustomProcessorResult> => {
|
||||
const processedFiles: File[] = [];
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PDFFont, PDFPage, rgb } from 'pdf-lib';
|
||||
import { PDFFont, PDFPage, rgb } from '@cantoo/pdf-lib';
|
||||
import { wrapText } from '@app/hooks/tools/validateSignature/utils/pdfText';
|
||||
import { colorPalette } from '@app/hooks/tools/validateSignature/utils/pdfPalette';
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PDFFont, PDFPage } from 'pdf-lib';
|
||||
import { PDFFont, PDFPage } from '@cantoo/pdf-lib';
|
||||
import { wrapText } from '@app/hooks/tools/validateSignature/utils/pdfText';
|
||||
import { colorPalette } from '@app/hooks/tools/validateSignature/utils/pdfPalette';
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { PDFFont, PDFPage } from 'pdf-lib';
|
||||
import { PDFFont, PDFPage } from '@cantoo/pdf-lib';
|
||||
import { SignatureValidationSignature } from '@app/types/validateSignature';
|
||||
import { drawFieldBox } from '@app/hooks/tools/validateSignature/outputtedPDFSections/FieldBoxSection';
|
||||
import { drawStatusBadge } from '@app/hooks/tools/validateSignature/outputtedPDFSections/StatusBadgeSection';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PDFFont, PDFPage, rgb } from 'pdf-lib';
|
||||
import { PDFFont, PDFPage, rgb } from '@cantoo/pdf-lib';
|
||||
|
||||
interface StatusBadgeOptions {
|
||||
page: PDFPage;
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { PDFFont, PDFImage, PDFPage } from 'pdf-lib';
|
||||
import { PDFFont, PDFImage, PDFPage } from '@cantoo/pdf-lib';
|
||||
import { SignatureValidationReportEntry } from '@app/types/validateSignature';
|
||||
import { drawFieldBox } from '@app/hooks/tools/validateSignature/outputtedPDFSections/FieldBoxSection';
|
||||
import { drawThumbnailImage, drawThumbnailPlaceholder } from '@app/hooks/tools/validateSignature/outputtedPDFSections/ThumbnailSection';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { PDFFont, PDFPage, PDFImage } from 'pdf-lib';
|
||||
import { PDFFont, PDFPage, PDFImage } from '@cantoo/pdf-lib';
|
||||
import { colorPalette } from '@app/hooks/tools/validateSignature/utils/pdfPalette';
|
||||
|
||||
export const drawThumbnailPlaceholder = (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PDFDocument, PDFPage, StandardFonts } from 'pdf-lib';
|
||||
import { PDFDocument, PDFPage, StandardFonts } from '@cantoo/pdf-lib';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { SignatureValidationReportEntry } from '@app/types/validateSignature';
|
||||
import { REPORT_PDF_FILENAME } from '@app/hooks/tools/validateSignature/utils/signatureUtils';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PDFDocument, PDFFont, PDFImage } from 'pdf-lib';
|
||||
import { PDFDocument, PDFFont, PDFImage } from '@cantoo/pdf-lib';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { colorPalette } from '@app/hooks/tools/validateSignature/utils/pdfPalette';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { rgb } from 'pdf-lib';
|
||||
import { rgb } from '@cantoo/pdf-lib';
|
||||
|
||||
type RgbTuple = [number, number, number];
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PDFFont } from 'pdf-lib';
|
||||
import { PDFFont } from '@cantoo/pdf-lib';
|
||||
|
||||
export const wrapText = (text: string, font: PDFFont, fontSize: number, maxWidth: number): string[] => {
|
||||
const lines: string[] = [];
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
export interface FooterInfo {
|
||||
analyticsEnabled?: boolean;
|
||||
@@ -29,8 +30,14 @@ export function useFooterInfo() {
|
||||
setFooterInfo(response.data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('[useFooterInfo] Failed to fetch footer info:', err);
|
||||
setError(err as Error);
|
||||
const status = err instanceof AxiosError ? err.response?.status : undefined;
|
||||
if (status !== 404) {
|
||||
console.error('[useFooterInfo] Failed to fetch footer info:', err);
|
||||
setError(err as Error);
|
||||
} else {
|
||||
// Older servers may not expose this endpoint.
|
||||
setError(null);
|
||||
}
|
||||
// Set defaults on error
|
||||
setFooterInfo({
|
||||
analyticsEnabled: false,
|
||||
|
||||
@@ -1,32 +1,14 @@
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import {
|
||||
PDFDocument,
|
||||
PDFDict,
|
||||
PDFName,
|
||||
PDFArray,
|
||||
PDFString,
|
||||
PDFHexString,
|
||||
PDFNumber,
|
||||
PDFRef,
|
||||
PDFPage,
|
||||
PDFContext,
|
||||
} from 'pdf-lib';
|
||||
} from '@cantoo/pdf-lib';
|
||||
import {
|
||||
PdfLibLink,
|
||||
extractLinksFromPage,
|
||||
} from '@app/utils/pdfLinkUtils';
|
||||
|
||||
|
||||
export type LinkType = 'internal' | 'external' | 'unknown';
|
||||
|
||||
export interface PdfLibLink {
|
||||
id: string;
|
||||
/** Index of this annotation in the page's /Annots array (used for deletion matching). */
|
||||
annotIndex: number;
|
||||
/** Rectangle in PDF-page coordinate space (top-left origin, unscaled). */
|
||||
rect: { x: number; y: number; width: number; height: number };
|
||||
type: LinkType;
|
||||
/** 0-based target page index (internal links). */
|
||||
targetPage?: number;
|
||||
/** URI for external links. */
|
||||
uri?: string;
|
||||
}
|
||||
export type { PdfLibLink };
|
||||
|
||||
export interface PdfLibLinksResult {
|
||||
links: PdfLibLink[];
|
||||
@@ -43,6 +25,9 @@ interface CachedDoc {
|
||||
refCount: number;
|
||||
/** Per-page extracted links (lazy, filled on first request). */
|
||||
pageLinks: Map<number, { links: PdfLibLink[]; width: number; height: number }>;
|
||||
/** Set to true when the PDF catalog/pages tree is invalid, so we
|
||||
* skip link extraction on all subsequent calls without retrying. */
|
||||
invalidCatalog?: boolean;
|
||||
}
|
||||
|
||||
const docCache = new Map<string, Promise<CachedDoc>>();
|
||||
@@ -54,11 +39,17 @@ async function acquireDocument(url: string): Promise<CachedDoc> {
|
||||
const buffer = await response.arrayBuffer();
|
||||
const doc = await PDFDocument.load(new Uint8Array(buffer), {
|
||||
ignoreEncryption: true,
|
||||
updateMetadata: false,
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
|
||||
return { doc, refCount: 0, pageLinks: new Map() };
|
||||
})();
|
||||
docCache.set(url, promise);
|
||||
|
||||
promise.catch(() => {
|
||||
docCache.delete(url);
|
||||
});
|
||||
}
|
||||
const cached = await docCache.get(url)!;
|
||||
cached.refCount++;
|
||||
@@ -76,241 +67,7 @@ function releaseDocument(url: string): void {
|
||||
});
|
||||
}
|
||||
|
||||
function num(ctx: PDFContext, value: unknown): number {
|
||||
if (value instanceof PDFRef) value = ctx.lookup(value);
|
||||
if (value instanceof PDFNumber) return value.asNumber();
|
||||
if (typeof value === 'number') return value;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function str(ctx: PDFContext, value: unknown): string | undefined {
|
||||
if (value instanceof PDFRef) value = ctx.lookup(value);
|
||||
if (value instanceof PDFString) return value.decodeText();
|
||||
if (value instanceof PDFHexString) return value.decodeText();
|
||||
if (typeof value === 'string') return value;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolvePageIndex(doc: PDFDocument, pageRef: PDFRef): number | undefined {
|
||||
const pages = doc.getPages();
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const ref = pages[i].ref;
|
||||
if (
|
||||
ref === pageRef ||
|
||||
(ref.objectNumber === pageRef.objectNumber &&
|
||||
ref.generationNumber === pageRef.generationNumber)
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveDestArray(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
destArr: PDFArray,
|
||||
): number | undefined {
|
||||
if (destArr.size() < 1) return undefined;
|
||||
const first = destArr.get(0);
|
||||
if (first instanceof PDFRef) {
|
||||
return resolvePageIndex(doc, first);
|
||||
}
|
||||
const n = num(ctx, first);
|
||||
if (typeof n === 'number' && n >= 0) return n;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveNamedDest(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
name: string,
|
||||
): number | undefined {
|
||||
try {
|
||||
const catalog = doc.catalog;
|
||||
|
||||
const namesRaw = catalog.get(PDFName.of('Names'));
|
||||
const namesDict = namesRaw instanceof PDFRef ? ctx.lookup(namesRaw) : namesRaw;
|
||||
if (namesDict instanceof PDFDict) {
|
||||
const destsRaw = namesDict.get(PDFName.of('Dests'));
|
||||
const destsTree = destsRaw instanceof PDFRef ? ctx.lookup(destsRaw) : destsRaw;
|
||||
if (destsTree instanceof PDFDict) {
|
||||
const result = searchNameTree(doc, ctx, destsTree, name);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
|
||||
const destsRaw = catalog.get(PDFName.of('Dests'));
|
||||
const destsDict = destsRaw instanceof PDFRef ? ctx.lookup(destsRaw) : destsRaw;
|
||||
if (destsDict instanceof PDFDict) {
|
||||
const dest = destsDict.get(PDFName.of(name));
|
||||
const destResolved = dest instanceof PDFRef ? ctx.lookup(dest) : dest;
|
||||
if (destResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, destResolved);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Swallow – named dest resolution is best-effort
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function searchNameTree(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
node: PDFDict,
|
||||
name: string,
|
||||
): number | undefined {
|
||||
const namesArr = node.get(PDFName.of('Names'));
|
||||
const resolved = namesArr instanceof PDFRef ? ctx.lookup(namesArr) : namesArr;
|
||||
if (resolved instanceof PDFArray) {
|
||||
for (let i = 0; i < resolved.size(); i += 2) {
|
||||
const key = str(ctx, resolved.get(i));
|
||||
if (key === name) {
|
||||
const val = resolved.get(i + 1);
|
||||
const valResolved = val instanceof PDFRef ? ctx.lookup(val) : val;
|
||||
if (valResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, valResolved);
|
||||
}
|
||||
if (valResolved instanceof PDFDict) {
|
||||
const d = valResolved.get(PDFName.of('D'));
|
||||
const dResolved = d instanceof PDFRef ? ctx.lookup(d) : d;
|
||||
if (dResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, dResolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const kidsArr = node.get(PDFName.of('Kids'));
|
||||
const kidsResolved = kidsArr instanceof PDFRef ? ctx.lookup(kidsArr) : kidsArr;
|
||||
if (kidsResolved instanceof PDFArray) {
|
||||
for (let i = 0; i < kidsResolved.size(); i++) {
|
||||
const kidRef = kidsResolved.get(i);
|
||||
const kid = kidRef instanceof PDFRef ? ctx.lookup(kidRef) : kidRef;
|
||||
if (kid instanceof PDFDict) {
|
||||
const limits = kid.get(PDFName.of('Limits'));
|
||||
const limitsResolved = limits instanceof PDFRef ? ctx.lookup(limits) : limits;
|
||||
if (limitsResolved instanceof PDFArray && limitsResolved.size() >= 2) {
|
||||
const lo = str(ctx, limitsResolved.get(0)) ?? '';
|
||||
const hi = str(ctx, limitsResolved.get(1)) ?? '';
|
||||
if (name < lo || name > hi) continue;
|
||||
}
|
||||
const result = searchNameTree(doc, ctx, kid, name);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractLinksFromPage(
|
||||
doc: PDFDocument,
|
||||
page: PDFPage,
|
||||
pageIndex: number,
|
||||
): PdfLibLink[] {
|
||||
const links: PdfLibLink[] = [];
|
||||
const ctx = doc.context;
|
||||
const { height: pageHeight } = page.getSize();
|
||||
|
||||
const annotsRaw = page.node.get(PDFName.of('Annots'));
|
||||
if (!annotsRaw) return links;
|
||||
|
||||
const annots = annotsRaw instanceof PDFRef ? ctx.lookup(annotsRaw) : annotsRaw;
|
||||
if (!(annots instanceof PDFArray)) return links;
|
||||
|
||||
for (let i = 0; i < annots.size(); i++) {
|
||||
try {
|
||||
const annotRaw = annots.get(i);
|
||||
const annot = annotRaw instanceof PDFRef ? ctx.lookup(annotRaw) : annotRaw;
|
||||
if (!(annot instanceof PDFDict)) continue;
|
||||
|
||||
const subtype = annot.get(PDFName.of('Subtype'));
|
||||
if (subtype?.toString() !== '/Link') continue;
|
||||
|
||||
const rectRaw = annot.get(PDFName.of('Rect'));
|
||||
const rect = rectRaw instanceof PDFRef ? ctx.lookup(rectRaw) : rectRaw;
|
||||
if (!(rect instanceof PDFArray) || rect.size() < 4) continue;
|
||||
|
||||
const x1 = num(ctx, rect.get(0));
|
||||
const y1 = num(ctx, rect.get(1));
|
||||
const x2 = num(ctx, rect.get(2));
|
||||
const y2 = num(ctx, rect.get(3));
|
||||
|
||||
const left = Math.min(x1, x2);
|
||||
const bottom = Math.min(y1, y2);
|
||||
const width = Math.abs(x2 - x1);
|
||||
const height = Math.abs(y2 - y1);
|
||||
|
||||
const top = pageHeight - bottom - height;
|
||||
|
||||
let linkType: LinkType = 'unknown';
|
||||
let targetPage: number | undefined;
|
||||
let uri: string | undefined;
|
||||
|
||||
const actionRaw = annot.get(PDFName.of('A'));
|
||||
const action = actionRaw instanceof PDFRef ? ctx.lookup(actionRaw) : actionRaw;
|
||||
|
||||
if (action instanceof PDFDict) {
|
||||
const actionType = action.get(PDFName.of('S'))?.toString();
|
||||
|
||||
if (actionType === '/URI') {
|
||||
linkType = 'external';
|
||||
uri = str(ctx, action.get(PDFName.of('URI')));
|
||||
} else if (actionType === '/GoTo') {
|
||||
linkType = 'internal';
|
||||
const dest = action.get(PDFName.of('D'));
|
||||
const destResolved = dest instanceof PDFRef ? ctx.lookup(dest) : dest;
|
||||
if (destResolved instanceof PDFArray) {
|
||||
targetPage = resolveDestArray(doc, ctx, destResolved);
|
||||
} else {
|
||||
const destName = str(ctx, destResolved);
|
||||
if (destName) {
|
||||
targetPage = resolveNamedDest(doc, ctx, destName);
|
||||
}
|
||||
}
|
||||
} else if (actionType === '/GoToR') {
|
||||
linkType = 'external';
|
||||
uri = str(ctx, action.get(PDFName.of('F')));
|
||||
} else if (actionType === '/Launch') {
|
||||
linkType = 'external';
|
||||
uri = str(ctx, action.get(PDFName.of('F')));
|
||||
}
|
||||
}
|
||||
|
||||
if (linkType === 'unknown') {
|
||||
const destRaw = annot.get(PDFName.of('Dest'));
|
||||
const dest = destRaw instanceof PDFRef ? ctx.lookup(destRaw) : destRaw;
|
||||
|
||||
if (dest instanceof PDFArray) {
|
||||
linkType = 'internal';
|
||||
targetPage = resolveDestArray(doc, ctx, dest);
|
||||
} else {
|
||||
const destName = str(ctx, dest);
|
||||
if (destName) {
|
||||
linkType = 'internal';
|
||||
targetPage = resolveNamedDest(doc, ctx, destName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
links.push({
|
||||
id: `pdflib-link-${pageIndex}-${i}`,
|
||||
annotIndex: i,
|
||||
rect: { x: left, y: top, width, height },
|
||||
type: linkType,
|
||||
targetPage,
|
||||
uri,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[usePdfLibLinks] Failed to parse annotation:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
export function usePdfLibLinks(
|
||||
pdfUrl: string | null,
|
||||
@@ -350,20 +107,41 @@ export function usePdfLibLinks(
|
||||
return;
|
||||
}
|
||||
|
||||
if (cached.invalidCatalog) {
|
||||
setResult({ links: [], pdfPageWidth: 0, pdfPageHeight: 0, loading: false });
|
||||
releaseDocument(url);
|
||||
return;
|
||||
}
|
||||
|
||||
let pageData = cached.pageLinks.get(pageIndex);
|
||||
if (!pageData) {
|
||||
const pageCount = cached.doc.getPageCount();
|
||||
let pageCount: number;
|
||||
try {
|
||||
pageCount = cached.doc.getPageCount();
|
||||
} catch {
|
||||
cached.invalidCatalog = true;
|
||||
setResult({ links: [], pdfPageWidth: 0, pdfPageHeight: 0, loading: false });
|
||||
releaseDocument(url);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageIndex < 0 || pageIndex >= pageCount) {
|
||||
setResult({ links: [], pdfPageWidth: 0, pdfPageHeight: 0, loading: false });
|
||||
releaseDocument(url);
|
||||
return;
|
||||
}
|
||||
|
||||
const page = cached.doc.getPage(pageIndex);
|
||||
const { width, height } = page.getSize();
|
||||
const links = extractLinksFromPage(cached.doc, page, pageIndex);
|
||||
pageData = { links, width, height };
|
||||
cached.pageLinks.set(pageIndex, pageData);
|
||||
try {
|
||||
const page = cached.doc.getPage(pageIndex);
|
||||
const { width, height } = page.getSize();
|
||||
const links = extractLinksFromPage(cached.doc, page, pageIndex);
|
||||
pageData = { links, width, height };
|
||||
cached.pageLinks.set(pageIndex, pageData);
|
||||
} catch (pageError) {
|
||||
console.warn(`[usePdfLibLinks] Failed to read page ${pageIndex}:`, pageError);
|
||||
pageData = { links: [], width: 0, height: 0 };
|
||||
cached.pageLinks.set(pageIndex, pageData);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cancelled && mountedRef.current) {
|
||||
@@ -377,7 +155,7 @@ export function usePdfLibLinks(
|
||||
|
||||
releaseDocument(url);
|
||||
} catch (error) {
|
||||
console.error('[usePdfLibLinks] Failed to extract links:', error);
|
||||
console.warn('[usePdfLibLinks] Failed to extract links:', error);
|
||||
if (!cancelled && mountedRef.current) {
|
||||
setResult({ links: [], pdfPageWidth: 0, pdfPageHeight: 0, loading: false });
|
||||
}
|
||||
|
||||
@@ -35,8 +35,11 @@ export const accountService = {
|
||||
/**
|
||||
* Get current user account data
|
||||
*/
|
||||
async getAccountData(): Promise<AccountData> {
|
||||
const response = await apiClient.get<AccountData>('/api/v1/proprietary/ui-data/account');
|
||||
async getAccountData(options?: { suppressErrorToast?: boolean; skipAuthRedirect?: boolean }): Promise<AccountData> {
|
||||
const response = await apiClient.get<AccountData>('/api/v1/proprietary/ui-data/account', {
|
||||
suppressErrorToast: options?.suppressErrorToast,
|
||||
skipAuthRedirect: options?.skipAuthRedirect,
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PDFDocument as PDFLibDocument, degrees, PageSizes } from 'pdf-lib';
|
||||
import { PDFDocument as PDFLibDocument, degrees, PageSizes } from '@cantoo/pdf-lib';
|
||||
import { downloadFile } from '@app/services/downloadService';
|
||||
import { PDFDocument, PDFPage } from '@app/types/pageEditor';
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
||||
|
||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||
enableAnalytics: true,
|
||||
appVersion: '2.4.6',
|
||||
appVersion: '2.5.0',
|
||||
serverCertificateEnabled: false,
|
||||
enableAlphaFunctionality: false,
|
||||
serverPort: 8080,
|
||||
@@ -189,4 +189,3 @@ export function getSimulatedLicenseInfo(): LicenseInfo | null {
|
||||
}
|
||||
|
||||
export const DEV_TESTING_ENABLED = DEV_TESTING_MODE;
|
||||
|
||||
|
||||
@@ -356,11 +356,10 @@ export const mantineTheme = createTheme({
|
||||
},
|
||||
control: {
|
||||
color: 'var(--text-secondary)',
|
||||
'&[data-active]': {
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
color: 'var(--text-primary)',
|
||||
boxShadow: 'var(--shadow-sm)',
|
||||
},
|
||||
},
|
||||
indicator: {
|
||||
backgroundColor: 'var(--bg-surface)',
|
||||
boxShadow: 'var(--shadow-sm)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PDFDocument, PageSizes } from 'pdf-lib';
|
||||
import { PDFDocument, PageSizes } from '@cantoo/pdf-lib';
|
||||
|
||||
export interface ImageToPdfOptions {
|
||||
imageResolution?: 'full' | 'reduced';
|
||||
|
||||
@@ -0,0 +1,553 @@
|
||||
/**
|
||||
* pdfLinkUtils — Create, modify, and extract link annotations in PDF documents.
|
||||
*/
|
||||
import {
|
||||
PDFDocument,
|
||||
PDFPage,
|
||||
PDFName,
|
||||
PDFString,
|
||||
PDFArray,
|
||||
PDFDict,
|
||||
PDFRef,
|
||||
PDFContext,
|
||||
PDFNumber,
|
||||
PDFHexString,
|
||||
} from '@cantoo/pdf-lib';
|
||||
|
||||
export type LinkType = 'internal' | 'external' | 'unknown';
|
||||
export type LinkBorderStyle = 'solid' | 'dashed' | 'beveled' | 'inset' | 'underline';
|
||||
export type LinkHighlightMode = 'none' | 'invert' | 'outline' | 'push';
|
||||
|
||||
export interface PdfLibLink {
|
||||
id: string;
|
||||
/** Index of this annotation in the page's /Annots array (used for deletion matching). */
|
||||
annotIndex: number;
|
||||
/** Rectangle in PDF-page coordinate space (top-left origin, unscaled). */
|
||||
rect: { x: number; y: number; width: number; height: number };
|
||||
type: LinkType;
|
||||
/** 0-based target page index (internal links). */
|
||||
targetPage?: number;
|
||||
/** URI for external links. */
|
||||
uri?: string;
|
||||
/** Tooltip / alt text from the /Contents entry. */
|
||||
title?: string;
|
||||
/** RGB color of the link annotation border (each component 0–1). */
|
||||
color?: [number, number, number];
|
||||
/** Border width and style. */
|
||||
borderStyle?: { width: number; style: LinkBorderStyle };
|
||||
/** Visual feedback when the link is clicked. */
|
||||
highlightMode?: LinkHighlightMode;
|
||||
}
|
||||
|
||||
export interface CreateLinkOptions {
|
||||
/** Page to place the link on. */
|
||||
page: PDFPage;
|
||||
/** Link rectangle in PDF user-space coordinates (lower-left origin). */
|
||||
rect: { x: number; y: number; width: number; height: number };
|
||||
/** External URL (mutually exclusive with destinationPage). */
|
||||
url?: string;
|
||||
/** Internal destination page index, 0-based (mutually exclusive with url). */
|
||||
destinationPage?: number;
|
||||
/** Tooltip text shown on hover (stored in /Contents). */
|
||||
title?: string;
|
||||
/** RGB colour for the border, each component 0–1. Defaults to blue. */
|
||||
color?: [number, number, number];
|
||||
/** Border width in points. 0 = invisible (PDF convention). */
|
||||
borderWidth?: number;
|
||||
/** Border line style. */
|
||||
borderStyle?: LinkBorderStyle;
|
||||
/** Visual feedback when the link is clicked. */
|
||||
highlightMode?: LinkHighlightMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a link annotation on a PDF page.
|
||||
* Supports both external URIs and internal GoTo page destinations.
|
||||
*/
|
||||
export function createLinkAnnotation(
|
||||
pdfDoc: PDFDocument,
|
||||
options: CreateLinkOptions,
|
||||
): void {
|
||||
const {
|
||||
page,
|
||||
rect,
|
||||
url,
|
||||
destinationPage,
|
||||
title,
|
||||
color = [0, 0, 1],
|
||||
borderWidth = 0,
|
||||
borderStyle = 'solid',
|
||||
highlightMode = 'invert',
|
||||
} = options;
|
||||
|
||||
if (!url && destinationPage === undefined) {
|
||||
throw new Error('createLinkAnnotation: must provide either url or destinationPage');
|
||||
}
|
||||
if (url && destinationPage !== undefined) {
|
||||
throw new Error('createLinkAnnotation: url and destinationPage are mutually exclusive');
|
||||
}
|
||||
if (destinationPage !== undefined) {
|
||||
const pageCount = pdfDoc.getPageCount();
|
||||
if (destinationPage < 0 || destinationPage >= pageCount) {
|
||||
throw new RangeError(
|
||||
`createLinkAnnotation: destinationPage ${destinationPage} out of range [0, ${pageCount})`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (rect.width <= 0 || rect.height <= 0) {
|
||||
throw new Error('createLinkAnnotation: rect dimensions must be positive');
|
||||
}
|
||||
if (color.some((c) => c < 0 || c > 1)) {
|
||||
throw new RangeError('createLinkAnnotation: color components must be between 0 and 1');
|
||||
}
|
||||
if (borderWidth < 0) {
|
||||
throw new RangeError('createLinkAnnotation: borderWidth must be non-negative');
|
||||
}
|
||||
|
||||
const ctx = pdfDoc.context;
|
||||
|
||||
const entries: Record<string, any> = {
|
||||
Type: 'Annot',
|
||||
Subtype: 'Link',
|
||||
Rect: [rect.x, rect.y, rect.x + rect.width, rect.y + rect.height],
|
||||
Border: [0, 0, borderWidth],
|
||||
C: color,
|
||||
H: PDFName.of(highlightModeCode(highlightMode)),
|
||||
};
|
||||
|
||||
if (title) {
|
||||
entries.Contents = PDFString.of(title);
|
||||
}
|
||||
|
||||
const annotDict = ctx.obj(entries);
|
||||
|
||||
if (borderStyle !== 'solid' && borderWidth > 0) {
|
||||
const bsDict = ctx.obj({
|
||||
W: borderWidth,
|
||||
S: PDFName.of(borderStyleCode(borderStyle)),
|
||||
});
|
||||
(annotDict as PDFDict).set(PDFName.of('BS'), bsDict);
|
||||
}
|
||||
|
||||
if (url) {
|
||||
const actionDict = ctx.obj({
|
||||
S: 'URI',
|
||||
URI: PDFString.of(url),
|
||||
});
|
||||
(annotDict as PDFDict).set(PDFName.of('A'), actionDict);
|
||||
} else if (destinationPage !== undefined) {
|
||||
const destPage = pdfDoc.getPage(destinationPage);
|
||||
const destArray = ctx.obj([destPage.ref, 'XYZ', null, null, null]);
|
||||
(annotDict as PDFDict).set(PDFName.of('Dest'), destArray);
|
||||
}
|
||||
|
||||
const annotRef = ctx.register(annotDict);
|
||||
|
||||
const existingAnnots = page.node.get(PDFName.of('Annots'));
|
||||
if (existingAnnots) {
|
||||
const resolvedAnnots =
|
||||
existingAnnots instanceof PDFRef ? ctx.lookup(existingAnnots) : existingAnnots;
|
||||
if (resolvedAnnots instanceof PDFArray) {
|
||||
resolvedAnnots.push(annotRef);
|
||||
} else {
|
||||
page.node.set(PDFName.of('Annots'), ctx.obj([annotRef]));
|
||||
}
|
||||
} else {
|
||||
page.node.set(PDFName.of('Annots'), ctx.obj([annotRef]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a link annotation from a page by its index in the /Annots array.
|
||||
* Returns true if the annotation was found and removed.
|
||||
*/
|
||||
export function removeLinkAnnotation(
|
||||
pdfDoc: PDFDocument,
|
||||
page: PDFPage,
|
||||
annotIndex: number,
|
||||
): boolean {
|
||||
const ctx = pdfDoc.context;
|
||||
const annotsRaw = page.node.get(PDFName.of('Annots'));
|
||||
if (!annotsRaw) return false;
|
||||
|
||||
const annots =
|
||||
annotsRaw instanceof PDFRef ? ctx.lookup(annotsRaw) : annotsRaw;
|
||||
if (!(annots instanceof PDFArray)) return false;
|
||||
|
||||
if (annotIndex < 0 || annotIndex >= annots.size()) return false;
|
||||
|
||||
const entry = annots.get(annotIndex);
|
||||
if (entry instanceof PDFRef) {
|
||||
ctx.delete(entry);
|
||||
}
|
||||
|
||||
annots.remove(annotIndex);
|
||||
|
||||
if (annots.size() === 0) {
|
||||
page.node.delete(PDFName.of('Annots'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all link annotations from a given PDF page.
|
||||
*/
|
||||
export function extractLinksFromPage(
|
||||
doc: PDFDocument,
|
||||
page: PDFPage,
|
||||
pageIndex: number,
|
||||
): PdfLibLink[] {
|
||||
const links: PdfLibLink[] = [];
|
||||
const ctx = doc.context;
|
||||
const { height: pageHeight } = page.getSize();
|
||||
|
||||
const annotsRaw = page.node.get(PDFName.of('Annots'));
|
||||
if (!annotsRaw) return links;
|
||||
|
||||
const annots = annotsRaw instanceof PDFRef ? ctx.lookup(annotsRaw) : annotsRaw;
|
||||
if (!(annots instanceof PDFArray)) return links;
|
||||
|
||||
for (let i = 0; i < annots.size(); i++) {
|
||||
try {
|
||||
const annotRaw = annots.get(i);
|
||||
const annot = annotRaw instanceof PDFRef ? ctx.lookup(annotRaw) : annotRaw;
|
||||
if (!(annot instanceof PDFDict)) continue;
|
||||
|
||||
const subtype = annot.get(PDFName.of('Subtype'));
|
||||
if (subtype?.toString() !== '/Link') continue;
|
||||
|
||||
const rectRaw = annot.get(PDFName.of('Rect'));
|
||||
const rect = rectRaw instanceof PDFRef ? ctx.lookup(rectRaw) : rectRaw;
|
||||
if (!(rect instanceof PDFArray) || rect.size() < 4) continue;
|
||||
|
||||
const x1 = num(ctx, rect.get(0));
|
||||
const y1 = num(ctx, rect.get(1));
|
||||
const x2 = num(ctx, rect.get(2));
|
||||
const y2 = num(ctx, rect.get(3));
|
||||
|
||||
const left = Math.min(x1, x2);
|
||||
const bottom = Math.min(y1, y2);
|
||||
const width = Math.abs(x2 - x1);
|
||||
const height = Math.abs(y2 - y1);
|
||||
|
||||
const top = pageHeight - bottom - height;
|
||||
|
||||
let linkType: LinkType = 'unknown';
|
||||
let targetPage: number | undefined;
|
||||
let uri: string | undefined;
|
||||
|
||||
const actionRaw = annot.get(PDFName.of('A'));
|
||||
const action = actionRaw instanceof PDFRef ? ctx.lookup(actionRaw) : actionRaw;
|
||||
|
||||
if (action instanceof PDFDict) {
|
||||
const actionType = action.get(PDFName.of('S'))?.toString();
|
||||
|
||||
if (actionType === '/URI') {
|
||||
linkType = 'external';
|
||||
uri = str(ctx, action.get(PDFName.of('URI')));
|
||||
} else if (actionType === '/GoTo') {
|
||||
linkType = 'internal';
|
||||
const dest = action.get(PDFName.of('D'));
|
||||
const destResolved = dest instanceof PDFRef ? ctx.lookup(dest) : dest;
|
||||
if (destResolved instanceof PDFArray) {
|
||||
targetPage = resolveDestArray(doc, ctx, destResolved);
|
||||
} else {
|
||||
const destName = str(ctx, destResolved);
|
||||
if (destName) {
|
||||
targetPage = resolveNamedDest(doc, ctx, destName);
|
||||
}
|
||||
}
|
||||
} else if (actionType === '/GoToR' || actionType === '/Launch') {
|
||||
linkType = 'external';
|
||||
uri = str(ctx, action.get(PDFName.of('F')));
|
||||
}
|
||||
}
|
||||
|
||||
if (linkType === 'unknown') {
|
||||
const destRaw = annot.get(PDFName.of('Dest'));
|
||||
const dest = destRaw instanceof PDFRef ? ctx.lookup(destRaw) : destRaw;
|
||||
|
||||
if (dest instanceof PDFArray) {
|
||||
linkType = 'internal';
|
||||
targetPage = resolveDestArray(doc, ctx, dest);
|
||||
} else {
|
||||
const destName = str(ctx, dest);
|
||||
if (destName) {
|
||||
linkType = 'internal';
|
||||
targetPage = resolveNamedDest(doc, ctx, destName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const title = extractTitle(ctx, annot);
|
||||
const color = extractColor(ctx, annot);
|
||||
const borderStyle = extractBorderStyle(ctx, annot);
|
||||
const highlightMode = parseHighlightMode(ctx, annot.get(PDFName.of('H')));
|
||||
|
||||
links.push({
|
||||
id: `pdflib-link-${pageIndex}-${i}`,
|
||||
annotIndex: i,
|
||||
rect: { x: left, y: top, width, height },
|
||||
type: linkType,
|
||||
targetPage,
|
||||
uri,
|
||||
title,
|
||||
color,
|
||||
borderStyle,
|
||||
highlightMode,
|
||||
});
|
||||
} catch (e) {
|
||||
console.warn('[pdfLinkUtils] Failed to parse annotation:', e);
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Private Helpers (Internal to extraction logic)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function num(ctx: PDFContext, value: unknown): number {
|
||||
const resolved = value instanceof PDFRef ? ctx.lookup(value) : value;
|
||||
if (resolved instanceof PDFNumber) return resolved.asNumber();
|
||||
if (typeof resolved === 'number') return resolved;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function str(ctx: PDFContext, value: unknown): string | undefined {
|
||||
const resolved = value instanceof PDFRef ? ctx.lookup(value) : value;
|
||||
if (resolved instanceof PDFString) return resolved.decodeText();
|
||||
if (resolved instanceof PDFHexString) return resolved.decodeText();
|
||||
if (typeof resolved === 'string') return resolved;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolvePageIndex(doc: PDFDocument, pageRef: PDFRef): number | undefined {
|
||||
const pages = doc.getPages();
|
||||
for (let i = 0; i < pages.length; i++) {
|
||||
const ref = pages[i].ref;
|
||||
if (
|
||||
ref === pageRef ||
|
||||
(ref.objectNumber === pageRef.objectNumber &&
|
||||
ref.generationNumber === pageRef.generationNumber)
|
||||
) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveDestArray(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
destArr: PDFArray,
|
||||
): number | undefined {
|
||||
if (destArr.size() < 1) return undefined;
|
||||
const first = destArr.get(0);
|
||||
if (first instanceof PDFRef) {
|
||||
return resolvePageIndex(doc, first);
|
||||
}
|
||||
const n = num(ctx, first);
|
||||
if (typeof n === 'number' && n >= 0) return n;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveNamedDest(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
name: string,
|
||||
): number | undefined {
|
||||
try {
|
||||
const catalog = doc.catalog;
|
||||
|
||||
const namesRaw = catalog.get(PDFName.of('Names'));
|
||||
const namesDict = namesRaw instanceof PDFRef ? ctx.lookup(namesRaw) : namesRaw;
|
||||
if (namesDict instanceof PDFDict) {
|
||||
const destsRaw = namesDict.get(PDFName.of('Dests'));
|
||||
const destsTree = destsRaw instanceof PDFRef ? ctx.lookup(destsRaw) : destsRaw;
|
||||
if (destsTree instanceof PDFDict) {
|
||||
const result = searchNameTree(doc, ctx, destsTree, name);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
|
||||
const destsRaw = catalog.get(PDFName.of('Dests'));
|
||||
const destsDict = destsRaw instanceof PDFRef ? ctx.lookup(destsRaw) : destsRaw;
|
||||
if (destsDict instanceof PDFDict) {
|
||||
const dest = destsDict.get(PDFName.of(name));
|
||||
const destResolved = dest instanceof PDFRef ? ctx.lookup(dest) : dest;
|
||||
if (destResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, destResolved);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function searchNameTree(
|
||||
doc: PDFDocument,
|
||||
ctx: PDFContext,
|
||||
node: PDFDict,
|
||||
name: string,
|
||||
): number | undefined {
|
||||
const namesArr = node.get(PDFName.of('Names'));
|
||||
const resolved = namesArr instanceof PDFRef ? ctx.lookup(namesArr) : namesArr;
|
||||
if (resolved instanceof PDFArray) {
|
||||
for (let i = 0; i < resolved.size(); i += 2) {
|
||||
const key = str(ctx, resolved.get(i));
|
||||
if (key === name) {
|
||||
const val = resolved.get(i + 1);
|
||||
const valResolved = val instanceof PDFRef ? ctx.lookup(val) : val;
|
||||
if (valResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, valResolved);
|
||||
}
|
||||
if (valResolved instanceof PDFDict) {
|
||||
const d = valResolved.get(PDFName.of('D'));
|
||||
const dResolved = d instanceof PDFRef ? ctx.lookup(d) : d;
|
||||
if (dResolved instanceof PDFArray) {
|
||||
return resolveDestArray(doc, ctx, dResolved);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const kidsArr = node.get(PDFName.of('Kids'));
|
||||
const kidsResolved = kidsArr instanceof PDFRef ? ctx.lookup(kidsArr) : kidsArr;
|
||||
if (kidsResolved instanceof PDFArray) {
|
||||
for (let i = 0; i < kidsResolved.size(); i++) {
|
||||
const kidRef = kidsResolved.get(i);
|
||||
const kid = kidRef instanceof PDFRef ? ctx.lookup(kidRef) : kidRef;
|
||||
if (kid instanceof PDFDict) {
|
||||
const limits = kid.get(PDFName.of('Limits'));
|
||||
const limitsResolved = limits instanceof PDFRef ? ctx.lookup(limits) : limits;
|
||||
if (limitsResolved instanceof PDFArray && limitsResolved.size() >= 2) {
|
||||
const lo = str(ctx, limitsResolved.get(0)) ?? '';
|
||||
const hi = str(ctx, limitsResolved.get(1)) ?? '';
|
||||
if (name < lo || name > hi) continue;
|
||||
}
|
||||
const result = searchNameTree(doc, ctx, kid, name);
|
||||
if (result !== undefined) return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function borderStyleCode(style: LinkBorderStyle): string {
|
||||
switch (style) {
|
||||
case 'dashed': return 'D';
|
||||
case 'beveled': return 'B';
|
||||
case 'inset': return 'I';
|
||||
case 'underline': return 'U';
|
||||
default: return 'S';
|
||||
}
|
||||
}
|
||||
|
||||
function highlightModeCode(mode: LinkHighlightMode): string {
|
||||
switch (mode) {
|
||||
case 'none': return 'N';
|
||||
case 'outline': return 'O';
|
||||
case 'push': return 'P';
|
||||
default: return 'I';
|
||||
}
|
||||
}
|
||||
|
||||
function parseBorderStyleName(ctx: PDFContext, value: unknown): LinkBorderStyle {
|
||||
if (!value) return 'solid';
|
||||
const resolved = value instanceof PDFRef ? ctx.lookup(value) : value;
|
||||
const s = resolved instanceof PDFName ? resolved.decodeText() : String(resolved);
|
||||
switch (s) {
|
||||
case 'D': return 'dashed';
|
||||
case 'B': return 'beveled';
|
||||
case 'I': return 'inset';
|
||||
case 'U': return 'underline';
|
||||
default: return 'solid';
|
||||
}
|
||||
}
|
||||
|
||||
function parseHighlightMode(ctx: PDFContext, value: unknown): LinkHighlightMode {
|
||||
if (!value) return 'invert';
|
||||
const resolved = value instanceof PDFRef ? ctx.lookup(value) : value;
|
||||
const s = resolved instanceof PDFName ? resolved.decodeText() : String(resolved);
|
||||
switch (s) {
|
||||
case 'N': return 'none';
|
||||
case 'I': return 'invert';
|
||||
case 'O': return 'outline';
|
||||
case 'P': return 'push';
|
||||
default: return 'invert';
|
||||
}
|
||||
}
|
||||
|
||||
function extractBorderStyle(
|
||||
ctx: PDFContext,
|
||||
annot: PDFDict,
|
||||
): PdfLibLink['borderStyle'] | undefined {
|
||||
const bsRaw = annot.get(PDFName.of('BS'));
|
||||
const bs = bsRaw instanceof PDFRef ? ctx.lookup(bsRaw) : bsRaw;
|
||||
if (bs instanceof PDFDict) {
|
||||
const w = bs.get(PDFName.of('W'));
|
||||
const s = bs.get(PDFName.of('S'));
|
||||
return {
|
||||
width: num(ctx, w) || 1,
|
||||
style: parseBorderStyleName(ctx, s),
|
||||
};
|
||||
}
|
||||
|
||||
const borderRaw = annot.get(PDFName.of('Border'));
|
||||
const border = borderRaw instanceof PDFRef ? ctx.lookup(borderRaw) : borderRaw;
|
||||
if (border instanceof PDFArray && border.size() >= 3) {
|
||||
const width = num(ctx, border.get(2));
|
||||
const style: LinkBorderStyle = border.size() >= 4 ? 'dashed' : 'solid';
|
||||
return { width, style };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractColor(
|
||||
ctx: PDFContext,
|
||||
annot: PDFDict,
|
||||
): [number, number, number] | undefined {
|
||||
const cRaw = annot.get(PDFName.of('C'));
|
||||
const c = cRaw instanceof PDFRef ? ctx.lookup(cRaw) : cRaw;
|
||||
if (!(c instanceof PDFArray)) return undefined;
|
||||
|
||||
const len = c.size();
|
||||
if (len === 3) {
|
||||
return [num(ctx, c.get(0)), num(ctx, c.get(1)), num(ctx, c.get(2))];
|
||||
}
|
||||
if (len === 1) {
|
||||
const g = num(ctx, c.get(0));
|
||||
return [g, g, g];
|
||||
}
|
||||
if (len === 4) {
|
||||
const cVal = num(ctx, c.get(0));
|
||||
const m = num(ctx, c.get(1));
|
||||
const y = num(ctx, c.get(2));
|
||||
const k = num(ctx, c.get(3));
|
||||
return [
|
||||
(1 - cVal) * (1 - k),
|
||||
(1 - m) * (1 - k),
|
||||
(1 - y) * (1 - k),
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function extractTitle(
|
||||
ctx: PDFContext,
|
||||
annot: PDFDict,
|
||||
): string | undefined {
|
||||
const raw = annot.get(PDFName.of('Contents'));
|
||||
const resolved = raw instanceof PDFRef ? ctx.lookup(raw) : raw;
|
||||
if (resolved instanceof PDFString || resolved instanceof PDFHexString) {
|
||||
return resolved.decodeText();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { PDFDocument, rgb } from 'pdf-lib';
|
||||
import { PdfAnnotationSubtype } from '@embedpdf/models';
|
||||
import { generateThumbnailWithMetadata } from '@app/utils/thumbnailUtils';
|
||||
import { createProcessedFile, createChildStub } from '@app/contexts/file/fileActions';
|
||||
import { createStirlingFile, StirlingFile, FileId, StirlingFileStub } from '@app/types/fileContext';
|
||||
import type { SignatureAPI } from '@app/components/viewer/viewerTypes';
|
||||
import {PDFDocument, rgb} from '@cantoo/pdf-lib';
|
||||
import {PdfAnnotationSubtype} from '@embedpdf/models';
|
||||
import {generateThumbnailWithMetadata} from '@app/utils/thumbnailUtils';
|
||||
import {createChildStub, createProcessedFile} from '@app/contexts/file/fileActions';
|
||||
import {createStirlingFile, FileId, StirlingFile, StirlingFileStub} from '@app/types/fileContext';
|
||||
import type {SignatureAPI} from '@app/components/viewer/viewerTypes';
|
||||
|
||||
interface MinimalFileContextSelectors {
|
||||
getAllFileIds: () => FileId[];
|
||||
@@ -38,29 +38,20 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
|
||||
if (signatureApiRef?.current) {
|
||||
|
||||
// Get actual page count from viewer
|
||||
const scrollState = getScrollState();
|
||||
const totalPages = scrollState.totalPages;
|
||||
|
||||
// Check only actual pages that exist in the document
|
||||
for (let pageIndex = 0; pageIndex < totalPages; pageIndex++) {
|
||||
try {
|
||||
const pageAnnotations = await signatureApiRef.current.getPageAnnotations(pageIndex);
|
||||
if (pageAnnotations && pageAnnotations.length > 0) {
|
||||
// Filter to only include annotations added in this session
|
||||
const sessionAnnotations = pageAnnotations.filter(annotation => {
|
||||
// Check if this annotation has stored image data (indicates it was added this session)
|
||||
const hasStoredImageData = annotation.id && getImageData(annotation.id);
|
||||
|
||||
// Also check if it has image data directly in the annotation (new signatures)
|
||||
const hasDirectImageData = annotation.imageData || annotation.appearance ||
|
||||
annotation.stampData || annotation.imageSrc ||
|
||||
annotation.contents || annotation.data;
|
||||
|
||||
const isSessionAnnotation = hasStoredImageData || (hasDirectImageData && typeof hasDirectImageData === 'string' && hasDirectImageData.startsWith('data:image'));
|
||||
|
||||
|
||||
return isSessionAnnotation;
|
||||
return hasStoredImageData || (hasDirectImageData && typeof hasDirectImageData === 'string' && hasDirectImageData.startsWith('data:image'));
|
||||
});
|
||||
|
||||
if (sessionAnnotations.length > 0) {
|
||||
@@ -74,12 +65,11 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
}
|
||||
|
||||
// Step 2: Delete ONLY session annotations from EmbedPDF before export (they'll be rendered manually)
|
||||
// Leave old annotations alone - they will remain as annotations in the PDF
|
||||
if (allAnnotations.length > 0 && signatureApiRef?.current) {
|
||||
for (const pageData of allAnnotations) {
|
||||
for (const annotation of pageData.annotations) {
|
||||
try {
|
||||
await signatureApiRef.current.deleteAnnotation(annotation.id, pageData.pageIndex);
|
||||
signatureApiRef.current.deleteAnnotation(annotation.id, pageData.pageIndex);
|
||||
} catch (deleteError) {
|
||||
console.warn(`Failed to delete annotation ${annotation.id}:`, deleteError);
|
||||
}
|
||||
@@ -96,17 +86,12 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
|
||||
if (pdfArrayBuffer) {
|
||||
|
||||
// Try loading with more permissive PDF-lib options
|
||||
|
||||
// Convert ArrayBuffer to File
|
||||
const blob = new Blob([pdfArrayBuffer], { type: 'application/pdf' });
|
||||
|
||||
// Get the current file - try from originalFile first, then from all files
|
||||
let currentFile = originalFile;
|
||||
if (!currentFile) {
|
||||
const allFileIds = selectors.getAllFileIds();
|
||||
if (allFileIds.length > 0) {
|
||||
// Use activeFileIndex if provided, otherwise default to 0
|
||||
const fileIndex = activeFileIndex !== undefined && activeFileIndex < allFileIds.length ? activeFileIndex : 0;
|
||||
const fileStub = selectors.getStirlingFileStub(allFileIds[fileIndex]);
|
||||
const fileObject = selectors.getFile(allFileIds[fileIndex]);
|
||||
@@ -128,7 +113,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
try {
|
||||
const pdfArrayBufferForFlattening = await signedFile.arrayBuffer();
|
||||
|
||||
// Try different loading options to handle problematic PDFs
|
||||
let pdfDoc: PDFDocument;
|
||||
try {
|
||||
pdfDoc = await PDFDocument.load(pdfArrayBufferForFlattening, {
|
||||
@@ -139,7 +123,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
} catch {
|
||||
console.warn('Failed to load with standard options, trying createProxy...');
|
||||
try {
|
||||
// Create a fresh PDF and copy pages instead of modifying
|
||||
pdfDoc = await PDFDocument.create();
|
||||
const sourcePdf = await PDFDocument.load(pdfArrayBufferForFlattening, {
|
||||
ignoreEncryption: true,
|
||||
@@ -169,22 +152,18 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
const rect = annotation.rect || annotation.bounds || annotation.rectangle || annotation.position;
|
||||
|
||||
if (rect) {
|
||||
// Extract original annotation position and size
|
||||
const originalX = rect.origin?.x || rect.x || rect.left || 0;
|
||||
const originalY = rect.origin?.y || rect.y || rect.top || 0;
|
||||
const width = rect.size?.width || rect.width || 100;
|
||||
const height = rect.size?.height || rect.height || 50;
|
||||
|
||||
// Convert EmbedPDF coordinates to PDF-lib coordinates
|
||||
const pdfX = originalX;
|
||||
const pdfY = pageHeight - originalY - height;
|
||||
|
||||
|
||||
// Try to get annotation image data
|
||||
let imageDataUrl = annotation.imageData || annotation.appearance || annotation.stampData ||
|
||||
annotation.imageSrc || annotation.contents || annotation.data;
|
||||
|
||||
// If no image data found directly, try to get it from storage
|
||||
if (!imageDataUrl && annotation.id) {
|
||||
const storedImageData = getImageData(annotation.id);
|
||||
if (storedImageData) {
|
||||
@@ -192,24 +171,63 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
}
|
||||
}
|
||||
|
||||
if (imageDataUrl && typeof imageDataUrl === 'string' && imageDataUrl.startsWith('data:image')) {
|
||||
if (imageDataUrl && typeof imageDataUrl === 'string' && imageDataUrl.startsWith('data:image/svg+xml')) {
|
||||
let svgRendered = false;
|
||||
try {
|
||||
const svgContent = decodeSvgDataUrl(imageDataUrl);
|
||||
if (svgContent && typeof (page as any).drawSvg === 'function') {
|
||||
// drawSvg from @cantoo/pdf-lib renders SVG natively as
|
||||
(page as any).drawSvg(svgContent, {
|
||||
x: pdfX,
|
||||
y: pdfY,
|
||||
width: width,
|
||||
height: height,
|
||||
});
|
||||
svgRendered = true;
|
||||
}
|
||||
} catch (svgError) {
|
||||
console.warn('Native SVG embed failed, falling back to raster:', svgError);
|
||||
}
|
||||
|
||||
if (!svgRendered) {
|
||||
try {
|
||||
const pngBytes = await rasteriseSvgToPng(imageDataUrl, width * 2, height * 2);
|
||||
if (pngBytes) {
|
||||
const image = await pdfDoc.embedPng(pngBytes);
|
||||
page.drawImage(image, { x: pdfX, y: pdfY, width, height });
|
||||
svgRendered = true;
|
||||
}
|
||||
} catch (rasterError) {
|
||||
console.error('SVG raster fallback also failed:', rasterError);
|
||||
}
|
||||
}
|
||||
|
||||
if (!svgRendered) {
|
||||
page.drawRectangle({
|
||||
x: pdfX,
|
||||
y: pdfY,
|
||||
width: width,
|
||||
height: height,
|
||||
borderColor: rgb(0.8, 0, 0),
|
||||
borderWidth: 1,
|
||||
color: rgb(1, 0.95, 0.95),
|
||||
opacity: 0.7,
|
||||
});
|
||||
}
|
||||
} else if (imageDataUrl && typeof imageDataUrl === 'string' && imageDataUrl.startsWith('data:image')) {
|
||||
try {
|
||||
// Convert data URL to bytes
|
||||
const base64Data = imageDataUrl.split(',')[1];
|
||||
const imageBytes = Uint8Array.from(atob(base64Data), c => c.charCodeAt(0));
|
||||
|
||||
// Embed image in PDF based on data URL type
|
||||
let image;
|
||||
if (imageDataUrl.includes('data:image/jpeg') || imageDataUrl.includes('data:image/jpg')) {
|
||||
image = await pdfDoc.embedJpg(imageBytes);
|
||||
} else if (imageDataUrl.includes('data:image/png')) {
|
||||
image = await pdfDoc.embedPng(imageBytes);
|
||||
} else {
|
||||
// Default to PNG for other formats (including converted SVGs)
|
||||
image = await pdfDoc.embedPng(imageBytes);
|
||||
}
|
||||
|
||||
// Draw image on page at annotation position
|
||||
page.drawImage(image, {
|
||||
x: pdfX,
|
||||
y: pdfY,
|
||||
@@ -221,8 +239,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
console.error('Failed to render image annotation:', imageError);
|
||||
}
|
||||
} else if (annotation.content || annotation.text) {
|
||||
console.warn('Rendering text annotation instead');
|
||||
// Handle text annotations
|
||||
page.drawText(annotation.content || annotation.text, {
|
||||
x: pdfX,
|
||||
y: pdfY + height - 12, // Adjust for text baseline
|
||||
@@ -230,26 +246,17 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
color: rgb(0, 0, 0)
|
||||
});
|
||||
} else if (annotation.type === PdfAnnotationSubtype.INK || annotation.type === PdfAnnotationSubtype.LINE) {
|
||||
// Handle ink annotations (drawn signatures)
|
||||
page.drawRectangle({
|
||||
x: pdfX,
|
||||
y: pdfY,
|
||||
width: width,
|
||||
height: height,
|
||||
borderColor: rgb(0, 0, 0),
|
||||
borderWidth: 2,
|
||||
color: rgb(0.9, 0.9, 0.9), // Light gray background
|
||||
opacity: 0.8
|
||||
});
|
||||
|
||||
page.drawText('Drawn Signature', {
|
||||
x: pdfX + 5,
|
||||
y: pdfY + height / 2,
|
||||
size: 10,
|
||||
color: rgb(0, 0, 0)
|
||||
borderWidth: 1,
|
||||
color: rgb(0.95, 0.95, 0.95),
|
||||
opacity: 0.6
|
||||
});
|
||||
} else {
|
||||
// Handle other annotation types
|
||||
page.drawRectangle({
|
||||
x: pdfX,
|
||||
y: pdfY,
|
||||
@@ -257,7 +264,7 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
height: height,
|
||||
borderColor: rgb(1, 0, 0),
|
||||
borderWidth: 2,
|
||||
color: rgb(1, 1, 0), // Yellow background
|
||||
color: rgb(1, 1, 0),
|
||||
opacity: 0.5
|
||||
});
|
||||
}
|
||||
@@ -270,7 +277,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
}
|
||||
|
||||
|
||||
// Save the PDF with rendered annotations
|
||||
const flattenedPdfBytes = await pdfDoc.save({ useObjectStreams: false, addDefaultPage: false });
|
||||
|
||||
const arrayBuffer = new ArrayBuffer(flattenedPdfBytes.length);
|
||||
@@ -284,11 +290,9 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
}
|
||||
}
|
||||
|
||||
// Generate thumbnail and metadata for the signed file
|
||||
const thumbnailResult = await generateThumbnailWithMetadata(signedFile);
|
||||
const processedFileMetadata = createProcessedFile(thumbnailResult.pageCount, thumbnailResult.thumbnail);
|
||||
|
||||
// Prepare input file data for replacement
|
||||
const inputFileIds: FileId[] = [currentFile.fileId];
|
||||
|
||||
const record = selectors.getStirlingFileStub(currentFile.fileId);
|
||||
@@ -297,7 +301,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
return null;
|
||||
}
|
||||
|
||||
// Create output stub and file as a child of the original (increments version)
|
||||
const outputStub = createChildStub(
|
||||
record,
|
||||
{ toolId: 'sign', timestamp: Date.now() },
|
||||
@@ -307,7 +310,6 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
);
|
||||
const outputStirlingFile = createStirlingFile(signedFile, outputStub.id);
|
||||
|
||||
// Return the flattened file data for consumption by caller
|
||||
return {
|
||||
inputFileIds,
|
||||
outputStirlingFile,
|
||||
@@ -321,3 +323,61 @@ export async function flattenSignatures(options: SignatureFlatteningOptions): Pr
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode an SVG data URL to its raw XML string.
|
||||
* Handles both base64-encoded and URI-encoded SVG data URLs.
|
||||
*/
|
||||
function decodeSvgDataUrl(dataUrl: string): string | null {
|
||||
try {
|
||||
if (dataUrl.includes(';base64,')) {
|
||||
const base64 = dataUrl.split(',')[1];
|
||||
return atob(base64);
|
||||
}
|
||||
// URI-encoded SVG
|
||||
const encoded = dataUrl.split(',')[1];
|
||||
return decodeURIComponent(encoded);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rasterise an SVG data URL to PNG bytes via an offscreen canvas.
|
||||
* Used as a fallback when native SVG embedding is unavailable.
|
||||
*/
|
||||
function rasteriseSvgToPng(svgDataUrl: string, width: number, height: number): Promise<Uint8Array | null> {
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = Math.max(1, Math.round(width));
|
||||
canvas.height = Math.max(1, Math.round(height));
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
if (!blob) {
|
||||
resolve(null);
|
||||
return;
|
||||
}
|
||||
blob.arrayBuffer().then(
|
||||
(buf) => resolve(new Uint8Array(buf)),
|
||||
() => resolve(null),
|
||||
);
|
||||
},
|
||||
'image/png',
|
||||
);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
};
|
||||
img.onerror = () => resolve(null);
|
||||
img.src = svgDataUrl;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ setupApiInterceptors(apiClient as unknown as AxiosInstance);
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
// Let the auth-refresh interceptor handle first-pass 401s silently.
|
||||
// If refresh/retry fails, the request is marked `_retry` and will fall through
|
||||
// to normal global handling below.
|
||||
if (error?.response?.status === 401 && error?.config?._retry !== true) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
await handleHttpError(error); // Handle error (shows toast unless suppressed)
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@@ -118,6 +118,16 @@ class TauriHttpClient {
|
||||
return error;
|
||||
}
|
||||
|
||||
private isResponseLike(value: unknown): value is TauriHttpResponse {
|
||||
if (!value || typeof value !== 'object') return false;
|
||||
const candidate = value as Partial<TauriHttpResponse>;
|
||||
return (
|
||||
typeof candidate.status === 'number' &&
|
||||
'data' in candidate &&
|
||||
candidate.config !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
private buildUrl(config: TauriHttpRequestConfig): string {
|
||||
let url = config.url || '';
|
||||
|
||||
@@ -316,6 +326,46 @@ class TauriHttpClient {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Some layers may throw a response-like object directly.
|
||||
// Handle it by status instead of incorrectly wrapping as ERR_NETWORK.
|
||||
if (this.isResponseLike(error)) {
|
||||
if (error.status >= 200 && error.status < 300) {
|
||||
return error as TauriHttpResponse<T>;
|
||||
}
|
||||
|
||||
const errorCode =
|
||||
error.status === 401
|
||||
? 'ERR_UNAUTHORIZED'
|
||||
: error.status === 403
|
||||
? 'ERR_FORBIDDEN'
|
||||
: error.status === 404
|
||||
? 'ERR_NOT_FOUND'
|
||||
: error.status >= 500
|
||||
? 'ERR_SERVER_ERROR'
|
||||
: 'ERR_BAD_REQUEST';
|
||||
|
||||
const errorMessage = `Request failed with status code ${error.status}`;
|
||||
const httpError = this.createError(
|
||||
errorMessage,
|
||||
finalConfig,
|
||||
errorCode,
|
||||
error as TauriHttpResponse,
|
||||
error
|
||||
);
|
||||
|
||||
let finalError: unknown = httpError;
|
||||
for (const handler of this.interceptors.response.handlers) {
|
||||
if (handler.rejected) {
|
||||
try {
|
||||
finalError = await Promise.resolve(handler.rejected(finalError));
|
||||
} catch (e) {
|
||||
finalError = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw finalError;
|
||||
}
|
||||
|
||||
// Create detailed error messages for network/other failures
|
||||
let errorMessage = 'Network Error';
|
||||
let errorCode = 'ERR_NETWORK';
|
||||
|
||||
@@ -108,12 +108,20 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const mountId = Math.random().toString(36).substring(7);
|
||||
console.log(`[Auth:${mountId}] 🔵 AuthProvider mounted`);
|
||||
|
||||
const initializeAuth = async () => {
|
||||
const initializeAuth = async (showLoading = false) => {
|
||||
try {
|
||||
if (showLoading) {
|
||||
setLoading(true);
|
||||
}
|
||||
console.debug(`[Auth:${mountId}] Initializing auth...`);
|
||||
console.debug(`[Auth:${mountId}] Path: ${window.location.pathname} Search: ${window.location.search}`);
|
||||
// Clear any platform-specific cached auth on login page init.
|
||||
if (typeof window !== 'undefined' && window.location.pathname.startsWith('/login')) {
|
||||
// Clear platform auth cache on login init only when no local JWT exists yet.
|
||||
// This avoids wiping freshly-saved desktop tokens during jwt-available re-init.
|
||||
if (
|
||||
typeof window !== 'undefined' &&
|
||||
window.location.pathname.startsWith('/login') &&
|
||||
!localStorage.getItem('stirling_jwt')
|
||||
) {
|
||||
await clearPlatformAuthOnLoginInit();
|
||||
}
|
||||
|
||||
@@ -147,18 +155,15 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
};
|
||||
|
||||
initializeAuth();
|
||||
initializeAuth(true);
|
||||
|
||||
// Listen for jwt-available event (triggered by desktop auth or other sources)
|
||||
const handleJwtAvailable = () => {
|
||||
console.log(`[Auth:${mountId}] ════════════════════════════════════`);
|
||||
console.log(`[Auth:${mountId}] 🔄 JWT available event received`);
|
||||
console.log(`[Auth:${mountId}] Current state: loading=${loading}, hasSession=${!!session}`);
|
||||
console.log(`[Auth:${mountId}] Setting loading=true to stabilize auth state`);
|
||||
setLoading(true); // Prevent unstable renders during auth state transition
|
||||
setError(null);
|
||||
console.log(`[Auth:${mountId}] Refreshing session...`);
|
||||
void initializeAuth();
|
||||
console.log(`[Auth:${mountId}] Refreshing session in background...`);
|
||||
void initializeAuth(false);
|
||||
};
|
||||
|
||||
window.addEventListener('jwt-available', handleJwtAvailable);
|
||||
|
||||
+61
-1
@@ -22,6 +22,9 @@ interface SecuritySettingsData {
|
||||
enableKeyRotation?: boolean;
|
||||
enableKeyCleanup?: boolean;
|
||||
keyRetentionDays?: number;
|
||||
tokenExpiryMinutes?: number;
|
||||
allowedClockSkewSeconds?: number;
|
||||
refreshGraceMinutes?: number;
|
||||
secureCookie?: boolean;
|
||||
};
|
||||
audit?: {
|
||||
@@ -132,6 +135,9 @@ export default function AdminSecuritySection() {
|
||||
'security.jwt.enableKeyRotation': securitySettings.jwt?.enableKeyRotation,
|
||||
'security.jwt.enableKeyCleanup': securitySettings.jwt?.enableKeyCleanup,
|
||||
'security.jwt.keyRetentionDays': securitySettings.jwt?.keyRetentionDays,
|
||||
'security.jwt.tokenExpiryMinutes': securitySettings.jwt?.tokenExpiryMinutes,
|
||||
'security.jwt.allowedClockSkewSeconds': securitySettings.jwt?.allowedClockSkewSeconds,
|
||||
'security.jwt.refreshGraceMinutes': securitySettings.jwt?.refreshGraceMinutes,
|
||||
'security.jwt.secureCookie': securitySettings.jwt?.secureCookie,
|
||||
// Premium audit settings
|
||||
'premium.enterpriseFeatures.audit.enabled': audit?.enabled,
|
||||
@@ -392,7 +398,7 @@ export default function AdminSecuritySection() {
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.security.jwt.keyRetentionDays.description', 'Number of days to retain old JWT keys for verification')}
|
||||
value={settings?.jwt?.keyRetentionDays || 7}
|
||||
value={settings?.jwt?.keyRetentionDays || 30}
|
||||
onChange={(value) => setSettings({ ...settings, jwt: { ...settings?.jwt, keyRetentionDays: Number(value) } })}
|
||||
min={1}
|
||||
max={365}
|
||||
@@ -400,6 +406,60 @@ export default function AdminSecuritySection() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NumberInput
|
||||
name="jwt_tokenExpiryMinutes"
|
||||
label={
|
||||
<Group component="span" gap="xs">
|
||||
<span>{t('admin.settings.security.jwt.tokenExpiryMinutes.label', 'Token Expiry (minutes)')}</span>
|
||||
<PendingBadge show={isFieldPending('jwt.tokenExpiryMinutes')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.security.jwt.tokenExpiryMinutes.description', 'Access token lifetime in minutes')}
|
||||
value={settings?.jwt?.tokenExpiryMinutes ?? 1440}
|
||||
onChange={(value) => setSettings({ ...settings, jwt: { ...settings?.jwt, tokenExpiryMinutes: Number(value) } })}
|
||||
min={1}
|
||||
max={10080}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NumberInput
|
||||
name="jwt_allowedClockSkewSeconds"
|
||||
label={
|
||||
<Group component="span" gap="xs">
|
||||
<span>{t('admin.settings.security.jwt.allowedClockSkewSeconds.label', 'Allowed Clock Skew (seconds)')}</span>
|
||||
<PendingBadge show={isFieldPending('jwt.allowedClockSkewSeconds')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.security.jwt.allowedClockSkewSeconds.description', 'Tolerance for client/server clock drift during token validation')}
|
||||
value={settings?.jwt?.allowedClockSkewSeconds ?? 60}
|
||||
onChange={(value) => setSettings({ ...settings, jwt: { ...settings?.jwt, allowedClockSkewSeconds: Number(value) } })}
|
||||
min={0}
|
||||
max={300}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<NumberInput
|
||||
name="jwt_refreshGraceMinutes"
|
||||
label={
|
||||
<Group component="span" gap="xs">
|
||||
<span>{t('admin.settings.security.jwt.refreshGraceMinutes.label', 'Refresh Grace (minutes)')}</span>
|
||||
<PendingBadge show={isFieldPending('jwt.refreshGraceMinutes')} />
|
||||
</Group>
|
||||
}
|
||||
description={t('admin.settings.security.jwt.refreshGraceMinutes.description', 'Allow refresh with an expired access token only within this grace period')}
|
||||
value={settings?.jwt?.refreshGraceMinutes ?? 15}
|
||||
onChange={(value) => setSettings({ ...settings, jwt: { ...settings?.jwt, refreshGraceMinutes: Number(value) } })}
|
||||
min={0}
|
||||
max={1440}
|
||||
disabled={!loginEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<div>
|
||||
<Text fw={500} size="sm">{t('admin.settings.security.jwt.secureCookie.label', 'Secure Cookie')}</Text>
|
||||
|
||||
@@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = {
|
||||
|
||||
const BASE_NO_LOGIN_CONFIG: AppConfig = {
|
||||
enableAnalytics: true,
|
||||
appVersion: '2.4.6',
|
||||
appVersion: '2.5.0',
|
||||
serverCertificateEnabled: false,
|
||||
enableAlphaFunctionality: false,
|
||||
enableDesktopInstallSlide: true,
|
||||
@@ -208,4 +208,3 @@ export function getSimulatedLicenseInfo(): LicenseInfo | null {
|
||||
}
|
||||
|
||||
export const DEV_TESTING_ENABLED = DEV_TESTING_MODE;
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ function WidgetInputInner({
|
||||
|
||||
const borderColor = error ? '#f44336' : (isActive ? '#2196F3' : 'rgba(33, 150, 243, 0.4)');
|
||||
const bgColor = error
|
||||
? 'rgba(244, 67, 54, 0.08)'
|
||||
: (isActive ? 'rgba(33, 150, 243, 0.08)' : 'rgba(255, 255, 255, 0.85)');
|
||||
? '#FFEBEE' // Red 50 (Opaque)
|
||||
: (isActive ? '#E3F2FD' : '#FFFFFF'); // Blue 50 (Opaque) : White (Opaque)
|
||||
|
||||
const commonStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
@@ -84,10 +84,41 @@ function WidgetInputInner({
|
||||
alignItems: field.multiline ? 'stretch' : 'center',
|
||||
};
|
||||
|
||||
// Scale font size with the widget height (using Y scale as a proxy for uniform font scaling).
|
||||
// PDF form fields use fontSize=0 to mean "auto-size" (scale to fit the box).
|
||||
// For single-line fields (e.g. Title), scale closer to the box height.
|
||||
// For multiline fields (e.g. Description), use a smaller capped size.
|
||||
const stopPropagation = (e: React.SyntheticEvent) => {
|
||||
e.stopPropagation();
|
||||
// Also stop immediate propagation to native listeners to block non-React subscribers
|
||||
if (e.nativeEvent) {
|
||||
e.nativeEvent.stopImmediatePropagation?.();
|
||||
}
|
||||
};
|
||||
|
||||
const commonProps = {
|
||||
style: commonStyle,
|
||||
onPointerDown: stopPropagation,
|
||||
onPointerUp: stopPropagation,
|
||||
onMouseDown: stopPropagation,
|
||||
onMouseUp: stopPropagation,
|
||||
onClick: stopPropagation,
|
||||
onDoubleClick: stopPropagation,
|
||||
onKeyDown: stopPropagation,
|
||||
onKeyUp: stopPropagation,
|
||||
onKeyPress: stopPropagation,
|
||||
onDragStart: stopPropagation,
|
||||
onSelect: stopPropagation,
|
||||
onContextMenu: stopPropagation,
|
||||
};
|
||||
|
||||
const captureStopProps = {
|
||||
onPointerDownCapture: stopPropagation,
|
||||
onPointerUpCapture: stopPropagation,
|
||||
onMouseDownCapture: stopPropagation,
|
||||
onMouseUpCapture: stopPropagation,
|
||||
onClickCapture: stopPropagation,
|
||||
onKeyDownCapture: stopPropagation,
|
||||
onKeyUpCapture: stopPropagation,
|
||||
onKeyPressCapture: stopPropagation,
|
||||
};
|
||||
|
||||
const fontSize = widget.fontSize
|
||||
? widget.fontSize * scaleY
|
||||
: field.multiline
|
||||
@@ -115,7 +146,7 @@ function WidgetInputInner({
|
||||
switch (field.type) {
|
||||
case 'text':
|
||||
return (
|
||||
<div style={commonStyle} title={error || field.tooltip || field.label}>
|
||||
<div {...commonProps} title={error || field.tooltip || field.label}>
|
||||
{field.multiline ? (
|
||||
<textarea
|
||||
value={value}
|
||||
@@ -129,6 +160,7 @@ function WidgetInputInner({
|
||||
overflow: 'auto',
|
||||
paddingTop: `${Math.max(1, 2 * scaleY)}px`,
|
||||
}}
|
||||
{...captureStopProps}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
@@ -144,6 +176,7 @@ function WidgetInputInner({
|
||||
aria-required={field.required}
|
||||
aria-invalid={!!error}
|
||||
aria-describedby={error ? `${field.name}-error` : undefined}
|
||||
{...captureStopProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -156,6 +189,7 @@ function WidgetInputInner({
|
||||
const onValue = widget.exportValue || 'Yes';
|
||||
return (
|
||||
<div
|
||||
{...commonProps}
|
||||
style={{
|
||||
...commonStyle,
|
||||
display: 'flex',
|
||||
@@ -164,10 +198,11 @@ function WidgetInputInner({
|
||||
cursor: field.readOnly ? 'default' : 'pointer',
|
||||
}}
|
||||
title={error || field.tooltip || field.label}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (field.readOnly) return;
|
||||
handleFocus();
|
||||
onChange(field.name, isChecked ? 'Off' : onValue);
|
||||
stopPropagation(e);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
@@ -206,7 +241,7 @@ function WidgetInputInner({
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={commonStyle} title={error || field.tooltip || field.label}>
|
||||
<div {...commonProps} title={error || field.tooltip || field.label}>
|
||||
<select
|
||||
id={inputId}
|
||||
value={selectValue}
|
||||
@@ -224,6 +259,7 @@ function WidgetInputInner({
|
||||
aria-label={field.label || field.name}
|
||||
aria-required={field.required}
|
||||
aria-invalid={!!error}
|
||||
{...captureStopProps}
|
||||
>
|
||||
{!field.multiSelect && <option value="">— select —</option>}
|
||||
{(field.options || []).map((opt, idx) => (
|
||||
@@ -243,6 +279,7 @@ function WidgetInputInner({
|
||||
const isSelected = value === optionValue;
|
||||
return (
|
||||
<div
|
||||
{...commonProps}
|
||||
style={{
|
||||
...commonStyle,
|
||||
display: 'flex',
|
||||
@@ -251,10 +288,11 @@ function WidgetInputInner({
|
||||
cursor: field.readOnly ? 'default' : 'pointer',
|
||||
}}
|
||||
title={error || field.tooltip || `${field.label}: ${optionValue}`}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
if (field.readOnly || value === optionValue) return; // Don't deselect radio buttons
|
||||
handleFocus();
|
||||
onChange(field.name, optionValue);
|
||||
stopPropagation(e);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
@@ -276,6 +314,7 @@ function WidgetInputInner({
|
||||
// Just render a highlighted area — not editable
|
||||
return (
|
||||
<div
|
||||
{...commonProps}
|
||||
style={{
|
||||
...commonStyle,
|
||||
background: 'rgba(200,200,200,0.3)',
|
||||
@@ -289,7 +328,7 @@ function WidgetInputInner({
|
||||
|
||||
default:
|
||||
return (
|
||||
<div style={commonStyle} title={field.tooltip || field.label}>
|
||||
<div {...commonProps} title={field.tooltip || field.label}>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
@@ -297,6 +336,7 @@ function WidgetInputInner({
|
||||
onFocus={handleFocus}
|
||||
disabled={field.readOnly}
|
||||
style={inputBaseStyle}
|
||||
{...captureStopProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
*/
|
||||
import { PDFDocument, PDFForm, PDFField, PDFTextField, PDFCheckBox,
|
||||
PDFDropdown, PDFRadioGroup, PDFOptionList, PDFButton, PDFSignature,
|
||||
PDFName, PDFDict, PDFArray, PDFNumber, PDFRef, PDFPage } from 'pdf-lib';
|
||||
PDFName, PDFDict, PDFArray, PDFNumber, PDFRef, PDFPage,
|
||||
PDFString, PDFHexString } from '@cantoo/pdf-lib';
|
||||
import type { FormField, FormFieldType, WidgetCoordinates } from '@proprietary/tools/formFill/types';
|
||||
import type { IFormDataProvider } from '@proprietary/tools/formFill/providers/types';
|
||||
|
||||
@@ -141,11 +142,18 @@ function extractWidgets(
|
||||
if (ap instanceof PDFDict) {
|
||||
const normal = ap.lookup(PDFName.of('N'));
|
||||
if (normal instanceof PDFDict) {
|
||||
// The keys of /N (other than /Off) are the export values
|
||||
const keys = normal.entries()
|
||||
.map(([k]) => k.decodeText())
|
||||
.filter(k => k !== 'Off');
|
||||
if (keys.length > 0) exportValue = keys[0];
|
||||
// The keys of /N (other than /Off) are the export values.
|
||||
// PDFDict.entries() reliably returns [PDFName, PDFObject][] in
|
||||
// @cantoo/pdf-lib — no optional chaining needed.
|
||||
try {
|
||||
const entries = normal.entries();
|
||||
const keys = entries
|
||||
.map(([k]) => k.decodeText())
|
||||
.filter((k) => k !== 'Off');
|
||||
if (keys.length > 0) exportValue = keys[0];
|
||||
} catch {
|
||||
// Malformed AP dict — skip export value extraction
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also check /AS for current appearance state
|
||||
@@ -353,7 +361,12 @@ function mapAppearanceStateToOption(
|
||||
const normal = ap.lookup(PDFName.of('N'));
|
||||
if (!(normal instanceof PDFDict)) continue;
|
||||
|
||||
const keys = normal.entries().map(([k]) => k.decodeText());
|
||||
let keys: string[] = [];
|
||||
try {
|
||||
keys = normal.entries().map(([k]) => k.decodeText());
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (keys.includes(stateName) && i < options.length) {
|
||||
return options[i];
|
||||
}
|
||||
@@ -381,7 +394,7 @@ function resolveRadioValueForSelect(
|
||||
}
|
||||
|
||||
const lower = value.toLowerCase();
|
||||
const match = options.find(o => o.toLowerCase() === lower);
|
||||
const match = options.find((o: string) => o.toLowerCase() === lower);
|
||||
if (match) return match;
|
||||
|
||||
return null;
|
||||
@@ -407,6 +420,68 @@ function getFieldOptions(field: PDFField): string[] | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract display labels from the /Opt array if it contains [export, display]
|
||||
* pairs. PDF spec §12.7.4.4: each element of /Opt may be either a text
|
||||
* string (export value == display value) or a two-element array where the
|
||||
* first element is the export value and the second is the display text.
|
||||
*
|
||||
* Returns null when every display value equals its export value (no distinct
|
||||
* display labels exist), keeping the interface lean for the common case.
|
||||
*/
|
||||
function getFieldDisplayOptions(field: PDFField): string[] | null {
|
||||
if (!(field instanceof PDFDropdown) && !(field instanceof PDFOptionList)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const acroDict = (field.acroField as any).dict as PDFDict;
|
||||
const optRaw = acroDict.lookup(PDFName.of('Opt'));
|
||||
if (!(optRaw instanceof PDFArray)) return null;
|
||||
|
||||
const displays: string[] = [];
|
||||
let hasDifference = false;
|
||||
|
||||
for (let i = 0; i < optRaw.size(); i++) {
|
||||
try {
|
||||
const entry = optRaw.lookup(i);
|
||||
|
||||
if (entry instanceof PDFArray && entry.size() >= 2) {
|
||||
// [exportValue, displayValue] pair
|
||||
const exp = decodeText(entry.lookup(0));
|
||||
const disp = decodeText(entry.lookup(1));
|
||||
displays.push(disp);
|
||||
if (exp !== disp) hasDifference = true;
|
||||
} else {
|
||||
// Plain string — export and display are the same
|
||||
const val = decodeText(entry);
|
||||
displays.push(val);
|
||||
}
|
||||
} catch {
|
||||
// Malformed /Opt entry — skip but continue processing remaining entries
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (displays.length === 0) return null;
|
||||
return hasDifference ? displays : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Decode a PDFString, PDFHexString, or PDFName to a JS string. */
|
||||
function decodeText(obj: unknown): string {
|
||||
if (obj instanceof PDFString || obj instanceof PDFHexString) {
|
||||
return obj.decodeText();
|
||||
}
|
||||
if (obj instanceof PDFName) {
|
||||
return obj.decodeText();
|
||||
}
|
||||
if (typeof obj === 'string') return obj;
|
||||
return String(obj ?? '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a field is read-only.
|
||||
*/
|
||||
@@ -431,17 +506,22 @@ function isFieldRequired(field: PDFField): boolean {
|
||||
|
||||
/**
|
||||
* Get field tooltip (TU entry).
|
||||
* Uses proper PDFString/PDFHexString decoding for correct Unicode support.
|
||||
*/
|
||||
function getFieldTooltip(acroField: PDFDict): string | null {
|
||||
const tu = acroField.lookup(PDFName.of('TU'));
|
||||
if (tu) {
|
||||
try {
|
||||
return tu.toString().replace(/^\(|\)$/g, '');
|
||||
} catch {
|
||||
// ignore
|
||||
if (!tu) return null;
|
||||
|
||||
try {
|
||||
// Prefer decodeText() for proper Unicode handling (UTF-16BE / PDFDocEncoding)
|
||||
if (tu instanceof PDFString || tu instanceof PDFHexString) {
|
||||
return tu.decodeText();
|
||||
}
|
||||
// Fallback: strip parentheses from raw toString() for other object types
|
||||
return tu.toString().replace(/^\(|\)$/g, '');
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -471,49 +551,82 @@ export class PdfLibFormProvider implements IFormDataProvider {
|
||||
|
||||
async fetchFields(file: File | Blob): Promise<FormField[]> {
|
||||
const arrayBuffer = await readAsArrayBuffer(file);
|
||||
const doc = await PDFDocument.load(arrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
updateMetadata: false,
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
let doc: PDFDocument;
|
||||
try {
|
||||
doc = await PDFDocument.load(arrayBuffer, {
|
||||
ignoreEncryption: true,
|
||||
updateMetadata: false,
|
||||
throwOnInvalidObject: false,
|
||||
});
|
||||
} catch (loadError) {
|
||||
console.warn('[PdfLibFormProvider] Failed to load PDF document:', loadError);
|
||||
return [];
|
||||
}
|
||||
|
||||
let form: PDFForm;
|
||||
try {
|
||||
form = doc.getForm();
|
||||
} catch {
|
||||
// No AcroForm — return empty
|
||||
} catch (formError) {
|
||||
// No AcroForm or broken catalog — return empty
|
||||
console.warn('[PdfLibFormProvider] Failed to access AcroForm:', formError);
|
||||
return [];
|
||||
}
|
||||
|
||||
const fields = form.getFields();
|
||||
let fields: PDFField[];
|
||||
try {
|
||||
fields = form.getFields();
|
||||
} catch (fieldsError) {
|
||||
console.warn('[PdfLibFormProvider] Failed to enumerate form fields:', fieldsError);
|
||||
return [];
|
||||
}
|
||||
if (fields.length === 0) return [];
|
||||
|
||||
const pages = doc.getPages();
|
||||
let pages: PDFPage[];
|
||||
try {
|
||||
pages = doc.getPages();
|
||||
} catch (pagesError) {
|
||||
// Pages tree is invalid (same issue as usePdfLibLinks "invalid catalog").
|
||||
// Without page references we can't place widgets, so return empty.
|
||||
// The viewer will fall back to native form rendering via withForms.
|
||||
console.warn(
|
||||
'[PdfLibFormProvider] PDF pages tree is invalid — cannot place form widgets.',
|
||||
'Native form rendering will be used as fallback.',
|
||||
pagesError,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const result: FormField[] = [];
|
||||
|
||||
for (const field of fields) {
|
||||
const type = getFieldType(field);
|
||||
const widgets = extractWidgets(field, pages, doc);
|
||||
const fieldName = field.getName();
|
||||
try {
|
||||
const type = getFieldType(field);
|
||||
const widgets = extractWidgets(field, pages, doc);
|
||||
|
||||
// Skip fields with no visible widgets
|
||||
if (widgets.length === 0) continue;
|
||||
// Skip fields with no visible widgets
|
||||
if (widgets.length === 0) continue;
|
||||
|
||||
const formField: FormField = {
|
||||
name: field.getName(),
|
||||
label: getFieldLabel(field),
|
||||
type,
|
||||
value: getFieldValue(field),
|
||||
options: getFieldOptions(field),
|
||||
displayOptions: null, // pdf-lib doesn't expose display vs export values separately
|
||||
required: isFieldRequired(field),
|
||||
readOnly: isFieldReadOnly(field),
|
||||
multiSelect: field instanceof PDFOptionList,
|
||||
multiline: isMultiline(field),
|
||||
tooltip: getFieldTooltip((field.acroField as any).dict as PDFDict),
|
||||
widgets,
|
||||
};
|
||||
const formField: FormField = {
|
||||
name: field.getName(),
|
||||
label: getFieldLabel(field),
|
||||
type,
|
||||
value: getFieldValue(field),
|
||||
options: getFieldOptions(field),
|
||||
displayOptions: getFieldDisplayOptions(field),
|
||||
required: isFieldRequired(field),
|
||||
readOnly: isFieldReadOnly(field),
|
||||
multiSelect: field instanceof PDFOptionList,
|
||||
multiline: isMultiline(field),
|
||||
tooltip: getFieldTooltip((field.acroField as any).dict as PDFDict),
|
||||
widgets,
|
||||
};
|
||||
|
||||
result.push(formField);
|
||||
result.push(formField);
|
||||
} catch (fieldError) {
|
||||
// Skip individual malformed fields but continue processing
|
||||
console.warn(`[PdfLibFormProvider] Skipping field "${fieldName}":`, fieldError);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user