Compare commits

...
Author SHA1 Message Date
Anthony Stirling 5aec8b32e2 chore: miscellaneous improvements and dependency updates
Collection of various improvements and fixes:

Build & Dependencies:
- Update build.gradle configuration
- Update frontend dependencies (package.json/package-lock.json)
- Update Tauri configuration (tauri.conf.json)

Frontend improvements:
- PDF validation signature report enhancements
- Form fill improvements (FormFieldOverlay, PdfLibFormProvider)
- Image to PDF utilities
- PDF link utilities and signature flattening
- Account service improvements
- PDF export service enhancements
- Testing utilities updates
- Theme updates (mantineTheme)
- Footer info hook
- PDF lib links hook

Tool operation updates:
- Adjust contrast operation
- Remove annotations operation

Desktop services:
- API client improvements
- Tauri HTTP client enhancements

Auth/Config:
- UseSession improvements
- Admin security section config UI
- Server experience simulations

Onboarding:
- Orchestrator refinements

These changes include bug fixes, code quality improvements, and
feature enhancements that don't fit into specific functional categories.
2026-02-16 10:46:58 +00:00
33 changed files with 1359 additions and 621 deletions
+1 -1
View File
@@ -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'
+247 -183
View File
@@ -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
View File
@@ -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",
+5 -3
View File
@@ -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,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,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,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,4 +1,4 @@
import { PDFFont, PDFPage, rgb } from 'pdf-lib';
import { PDFFont, PDFPage, rgb } from '@cantoo/pdf-lib';
interface StatusBadgeOptions {
page: PDFPage;
@@ -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,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[] = [];
+9 -2
View File
@@ -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,
+43 -265
View File
@@ -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 });
}
+5 -2
View File
@@ -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;
+4 -5
View File
@@ -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 -1
View File
@@ -1,4 +1,4 @@
import { PDFDocument, PageSizes } from 'pdf-lib';
import { PDFDocument, PageSizes } from '@cantoo/pdf-lib';
export interface ImageToPdfOptions {
imageResolution?: 'full' | 'reduced';
+553
View File
@@ -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 01). */
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 01. 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;
}
+114 -54
View File
@@ -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';
+14 -9
View File
@@ -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);
@@ -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;