diff --git a/assets/dbp-official-signature-pdf-upload.metadata.json b/assets/dbp-official-signature-pdf-upload.metadata.json index dc743276d5ac572684259ba03e9567ca12080b32..0bb9f68b08b552c3f5ad61707c80f8167d9eb5c6 100644 --- a/assets/dbp-official-signature-pdf-upload.metadata.json +++ b/assets/dbp-official-signature-pdf-upload.metadata.json @@ -14,6 +14,6 @@ "de": "Erlaubt das Hochladen von PDF-Dokumenten, um sie mit einer Amtssignatur zu versehen", "en": "Allows upload of PDF-documents to officially sign them" }, - "subscribe": "lang,entry-point-url,nextcloud-web-app-password-url,nextcloud-webdav-url,nextcloud-name,nextcloud-file-url,show-nextcloud-file-picker,auth", + "subscribe": "lang,entry-point-url,nextcloud-web-app-password-url,nextcloud-webdav-url,nextcloud-name,nextcloud-file-url,show-nextcloud-file-picker,auth,allow-annotating", "required_roles": ["ROLE_SCOPE_OFFICIAL-SIGNATURE"] } diff --git a/assets/dbp-qualified-signature-pdf-upload.metadata.json b/assets/dbp-qualified-signature-pdf-upload.metadata.json index e62c9df4477c975610399ec4636302b410f01cd6..b6eeb98bf9b495bcd49a91656c5468c6d2ed9f4c 100644 --- a/assets/dbp-qualified-signature-pdf-upload.metadata.json +++ b/assets/dbp-qualified-signature-pdf-upload.metadata.json @@ -14,5 +14,5 @@ "de": "Erlaubt das Hochladen von PDF-Dokumenten, um sie mit einer persönlichen elektronischen Signatur zu versehen", "en": "Allows upload of PDF-documents to personally sign them" }, - "subscribe": "lang,entry-point-url,nextcloud-web-app-password-url,nextcloud-webdav-url,nextcloud-name,nextcloud-file-url,show-nextcloud-file-picker,auth" + "subscribe": "lang,entry-point-url,nextcloud-web-app-password-url,nextcloud-webdav-url,nextcloud-name,nextcloud-file-url,show-nextcloud-file-picker,auth,allow-annotating" } diff --git a/assets/dbp-signature.html.ejs b/assets/dbp-signature.html.ejs index 472aac624480178c649e3652508df721b74a4971..970c5e10eb7b1399b19a56cf88d1b6d8c138e41d 100644 --- a/assets/dbp-signature.html.ejs +++ b/assets/dbp-signature.html.ejs @@ -58,6 +58,7 @@ show-nextcloud-file-picker <%= buildInfo.env !== 'production' ? 'allow-signature-rotation' : '' %> <%= buildInfo.env !== 'production' ? 'show-clipboard' : '' %> + <%= buildInfo.env !== 'production' ? 'allow-annotating' : '' %> nextcloud-web-app-password-url="<%= nextcloudWebAppPasswordURL %>" nextcloud-webdav-url="<%= nextcloudWebDavURL %>" nextcloud-name="<%= nextcloudName %>" diff --git a/package.json b/package.json index 5f4707502d28761f2bed565368eb42a83d639ac2..67854416e261df740072e7e03bc6d8224c4d123f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@dbp-toolkit/language-select": "^0.2.0", "@dbp-toolkit/notification": "^0.2.0", "@dbp-toolkit/person-profile": "^0.2.0", + "@digital-blueprint/annotpdf": "^1.0.13a", "@open-wc/scoped-elements": "^1.1.1", "fabric": "^4.2.0", "file-saver": "^2.0.2", diff --git a/src/dbp-official-signature-pdf-upload.js b/src/dbp-official-signature-pdf-upload.js index 1db422ee19f9c123cf28dd85310c3164827df925..e94b45552e82c7ba9e036007d6bac3f459be94c7 100644 --- a/src/dbp-official-signature-pdf-upload.js +++ b/src/dbp-official-signature-pdf-upload.js @@ -47,8 +47,7 @@ class OfficialSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitElem this.queuedFilesPlacementModes = []; this.queuedFilesNeedsPlacement = new Map(); this.currentPreviewQueueKey = ''; - - + this.allowAnnotating = false; } static get scopedElements() { @@ -89,6 +88,7 @@ class OfficialSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitElem signaturePlacementInProgress: { type: Boolean, attribute: false }, withSigBlock: { type: Boolean, attribute: false }, isSignaturePlacement: { type: Boolean, attribute: false }, + allowAnnotating: { type: Boolean, attribute: 'allow-annotating' } }; } @@ -670,7 +670,12 @@ class OfficialSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitElem <dbp-icon name="trash"></dbp-icon></button> </div> <div class="bottom-line"> - <div></div> + <div class="${classMap({hidden: this.allowAnnotating})}"></div> + <button class="button ${classMap({hidden: !this.allowAnnotating})}" + ?disabled="${this.signingProcessEnabled}" + title="${i18n.t('official-pdf-upload.add-annotation-title')}" + @click="${() => { this.addAnnotation(id, i18n); }}"> + <dbp-icon name="plus"></dbp-icon></button> <button class="button" ?disabled="${this.signingProcessEnabled}" @click="${() => { this.showPreview(id); }}">${i18n.t('official-pdf-upload.show-preview')}</button> diff --git a/src/dbp-qualified-signature-pdf-upload.js b/src/dbp-qualified-signature-pdf-upload.js index c75fe5cfc34cd862e205fb43a2ea2f33be70c4e0..98ed7580f3e49cd4bdbf42deff876f982f821fb0 100644 --- a/src/dbp-qualified-signature-pdf-upload.js +++ b/src/dbp-qualified-signature-pdf-upload.js @@ -48,6 +48,7 @@ class QualifiedSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitEle this.queuedFilesPlacementModes = []; this.queuedFilesNeedsPlacement = new Map(); this.currentPreviewQueueKey = ''; + this.allowAnnotating = false; this._onReceiveIframeMessage = this.onReceiveIframeMessage.bind(this); this._onReceiveBeforeUnload = this.onReceiveBeforeUnload.bind(this); @@ -92,6 +93,7 @@ class QualifiedSignaturePdfUpload extends ScopedElementsMixin(DBPSignatureLitEle signaturePlacementInProgress: { type: Boolean, attribute: false }, withSigBlock: { type: Boolean, attribute: false }, isSignaturePlacement: { type: Boolean, attribute: false }, + allowAnnotating: { type: Boolean, attribute: 'allow-annotating' } }; } diff --git a/src/dbp-signature-lit-element.js b/src/dbp-signature-lit-element.js index 049f524cc5a895b2690920c960743425247a8b71..57426292d7476e08818ab86b1bb23c68754a0d33 100644 --- a/src/dbp-signature-lit-element.js +++ b/src/dbp-signature-lit-element.js @@ -113,6 +113,39 @@ export default class DBPSignatureLitElement extends DBPSignatureBaseLitElement { return file; } + /** + * Add an annotation to a file on the queue + * + * @param key + */ + async addAnnotation(key, i18n) { + const annotationKey = prompt("Please enter a key"); + + if (annotationKey === null || annotationKey === "") { + return; + } + + const annotationValue = prompt("Please enter a value"); + + if (annotationValue === null || annotationValue === "") { + return; + } + + let file = this.queuedFiles[key]; + + // console.log("file before annotation", file); + + // annotate the pwd with the key and value + file = await utils.addKeyValuePdfAnnotation(file, i18n, 'AppName', this.auth['user-full-name'], annotationKey, annotationValue); + + // console.log("file after annotation", file); + + // overwrite the current pdf + this.queuedFiles[key] = file; + + return file; + } + uploadOneQueuedFile() { const file = this.takeFileFromQueue(); diff --git a/src/i18n/de/translation.json b/src/i18n/de/translation.json index 5596fdb4f3123bed50c9a4540797b27fe496b5ad..d0123f94e0d608f79defb1b1d77c01b64b595c97 100644 --- a/src/i18n/de/translation.json +++ b/src/i18n/de/translation.json @@ -32,7 +32,8 @@ "signature-placement-label": "Signatur platzieren", "positioning": "Positionierung", "confirm-page-leave": "Sind Sie sicher, dass Sie die Seite verlassen wollen? Es stehen signierte Dokumente zum Download bereit.", - "file-picker-context": "PDF-Dokumente zum Signieren auswählen" + "file-picker-context": "PDF-Dokumente zum Signieren auswählen", + "add-annotation-title": "Dem PDF eine Kennzahl hinzufügen" }, "qualified-pdf-upload": { "upload-field-label": "Dokument persönlich signieren", @@ -67,7 +68,8 @@ "signature-placement-label": "Signatur platzieren", "positioning": "Positionierung", "confirm-page-leave": "Sind Sie sicher, dass Sie die Seite verlassen wollen? Es stehen signierte Dokumente zum Download bereit.", - "file-picker-context": "PDF-Dokumente zum Signieren auswählen" + "file-picker-context": "PDF-Dokumente zum Signieren auswählen", + "add-annotation-title": "Dem PDF eine Kennzahl hinzufügen" }, "signature-verification": { "upload-field-label": "PDF-Dokumente zum Überprüfen der Signaturen hochladen", @@ -123,5 +125,6 @@ "error-cancel-message": "Der Signaturprozess wurde manuell abgebrochen.", "error-rights-message": "Abbruch auf Grund mangelnder Rechte Ihres Accounts.", "label-manual-positioning-missing": "Bestehende Signatur vorhanden, bitte wählen Sie ihre Positionierung manuell.", - "error-manual-positioning-missing": "Ein oder mehrere Signaturen in der Warteschlange müssen manuell positioniert werden." + "error-manual-positioning-missing": "Ein oder mehrere Signaturen in der Warteschlange müssen manuell positioniert werden.", + "annotation-author": "({{appName}}) im Namen von {{personName}}" } diff --git a/src/i18n/en/translation.json b/src/i18n/en/translation.json index b7b0638f80dc4a736464bc71a6d79d866748e759..21d70eeb8f7e5eed0789d59dddb2bd69894621e9 100644 --- a/src/i18n/en/translation.json +++ b/src/i18n/en/translation.json @@ -31,7 +31,8 @@ "signature-placement-label": "Place signature", "positioning": "Positioning", "confirm-page-leave": "Are you sure you want to leave this page? There are still signed documents ready to be downloaded.", - "file-picker-context": "Upload PDF-documents to sign" + "file-picker-context": "Upload PDF-documents to sign", + "add-annotation-title": "Add annotation to PDF" }, "qualified-pdf-upload": { "upload-field-label": "Personally Sign Document", @@ -66,7 +67,8 @@ "signature-placement-label": "Place signature", "positioning": "Positioning", "confirm-page-leave": "Are you sure you want to leave this page? There are still signed documents ready to be downloaded.", - "file-picker-context": "Upload PDF-documents to sign" + "file-picker-context": "Upload PDF-documents to sign", + "add-annotation-title": "Add annotation to PDF" }, "signature-verification": { "upload-field-label": "Verify signed documents", @@ -123,5 +125,6 @@ "error-cancel-message": "The signature process was manually aborted.", "error-rights-message": "Abort due to insufficient rights of your account.", "label-manual-positioning-missing": "Existing signature present, please select its positioning manually.", - "error-manual-positioning-missing": "One or more signatures in the queue must be positioned manually." + "error-manual-positioning-missing": "One or more signatures in the queue must be positioned manually.", + "annotation-author": "({{appName}}) in the name of {{personName}}" } diff --git a/src/utils.js b/src/utils.js index 0eadfe898121ce6f0b643d1298608012d976a0fc..5c5ad9946feae7982c4c6e51ba3ccbc05ba24626 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,5 @@ +import {AnnotationFactory} from '@digital-blueprint/annotpdf/_bundles/pdfAnnotate.js'; + /** * Finds an object in a JSON result by identifier * @@ -100,6 +102,25 @@ export const readBinaryFileContent = async (file) => { }); }; +/** + * Returns the content of the file as array buffer + * + * @param {File} file The file to read + * @returns {string} The content + */ +export const readArrayBufferFileContent = async (file) => { + return new Promise((resolve, reject) => { + let reader = new FileReader(); + reader.onload = () => { + resolve(reader.result); + }; + reader.onerror = () => { + reject(reader.error); + }; + reader.readAsArrayBuffer(file); + }); +}; + /** * Given a PDF file returns the amount of signatures found in it. * @@ -119,4 +140,35 @@ export const getPDFSignatureCount = async (file) => { matches++; } return matches; -}; \ No newline at end of file +}; + +export const addKeyValuePdfAnnotation = async (file, i18n, appName, personName, key, value) => { + const data = await readArrayBufferFileContent(file); + let pdfFactory = new AnnotationFactory(data); + + // console.log("pdfFactory.getAnnotations() before", pdfFactory.getAnnotations()); + + const page = 0; + const rect = [1, 1, 1, 1]; + const author = i18n.t('annotation-author', { + appName: appName, + personName: personName + }); + const contents = 'DBP-SIGNATURE-' + key + ': ' + value; + + // pdfFactory.checkRect(4, rect); + + // Create single free text annotation with print flag and 0 font size + let annot = Object.assign(pdfFactory.createBaseAnnotation(page, rect, contents, author), { + annotation_flag: 4, // enable print to be PDF/A conform + color: {r: 1, g: 1, b: 1}, // white to (maybe) hide it better + opacity: 0.001, // we can't set to 0 because of "if (opacity) {" + defaultAppearance: "/Invalid_font 0 Tf" // font size 0 to (maybe) hide it better + }); + annot.type = "/FreeText"; + pdfFactory.annotations.push(annot); + + const blob = pdfFactory.write(); + + return new File([blob], file.name, { type: file.type }); +}; diff --git a/yarn.lock b/yarn.lock index 23472ebb5c18863badce70c7acc58107b357568c..d5788d112385562ea4db1dc4b1d42ee1a4bf0b93 100644 --- a/yarn.lock +++ b/yarn.lock @@ -895,6 +895,16 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" +"@digital-blueprint/annotpdf@^1.0.13a": + version "1.0.13-a" + resolved "https://registry.npmjs.org/@digital-blueprint/annotpdf/-/annotpdf-1.0.13-a.tgz#f588188a6e745dabcafa7756ca2c8b24a74e43f6" + integrity sha512-Nr/YcOYswe5XZG9NnjBVOa+tz5uNcQD1vg41pirbW7/zsnu0W48xjcCIpf46PkWsjAf+NxBRQB+i8gx/n0iQIg== + dependencies: + "@types/crypto-js" "^4.0.1" + "@types/pako" "^1.0.1" + crypto-js "^4.0.0" + pako "^1.0.10" + "@eslint/eslintrc@^0.2.1": version "0.2.2" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.2.2.tgz#d01fc791e2fc33e88a29d6f3dc7e93d0cd784b76" @@ -1122,6 +1132,11 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.10.tgz#61cc8469849e5bcdd0c7044122265c39cec10cf4" integrity sha512-C7srjHiVG3Ey1nR6d511dtDkCEjxuN9W1HWAEjGq8kpcwmNM6JJkpC0xvabM7BXTG2wDq8Eu33iH9aQKa7IvLQ== +"@types/crypto-js@^4.0.1": + version "4.0.1" + resolved "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.0.1.tgz#3a4bd24518b0e6c5940da4e2659eeb2ef0806963" + integrity sha512-6+OPzqhKX/cx5xh+yO8Cqg3u3alrkhoxhE5ZOdSEv0DOzJ13lwJ6laqGU0Kv6+XDMFmlnGId04LtY22PsFLQUw== + "@types/ejs@^3.0.4": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.0.5.tgz#95a3a1c3d9603eba80fe67ff56da1ba275ef2eda" @@ -1172,6 +1187,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.28.tgz#cade4b64f8438f588951a6b35843ce536853f25b" integrity sha512-lg55ArB+ZiHHbBBttLpzD07akz0QPrZgUODNakeC09i62dnrywr9mFErHuaPlB6I7z+sEbK+IYmplahvplCj2g== +"@types/pako@^1.0.1": + version "1.0.1" + resolved "https://registry.npmjs.org/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61" + integrity sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg== + "@types/resolve@1.17.1": version "1.17.1" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" @@ -1454,12 +1474,12 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-mutex@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.2.4.tgz#f6ea5f9cc73147f395f86fa573a2af039fe63082" - integrity sha512-fcQKOXUKMQc57JlmjBCHtkKNrfGpHyR7vu18RfuLfeTAf4hK9PgOadPR5cDrBQ682zasrLUhJFe7EKAHJOduDg== +async-mutex@^0.3.0: + version "0.3.1" + resolved "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz#7033af665f1c7cebed8b878267a43ba9e77c5f67" + integrity sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw== dependencies: - tslib "^2.0.0" + tslib "^2.1.0" async@0.9.x: version "0.9.2" @@ -2019,6 +2039,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc" + integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg== + cssom@^0.4.1: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -2425,19 +2450,6 @@ escodegen@^1.11.1: optionalDependencies: source-map "~0.6.1" -eslint-plugin-jsdoc@^31.0.0: - version "31.6.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-31.6.1.tgz#98040c801500572fff71c984a097d89946f1e450" - integrity sha512-5hCV3u+1VSEUMyfdTl+dpWsioD7tqQr2ILQw+KbXrF42AVxCLO8gnNLR6zDCDjqGGpt79V1sgY0RRchCWuCigg== - dependencies: - comment-parser "1.1.2" - debug "^4.3.1" - jsdoctypeparser "^9.0.0" - lodash "^4.17.20" - regextras "^0.7.1" - semver "^7.3.4" - spdx-expression-parse "^3.0.1" - eslint-plugin-jsdoc@^32.0.0: version "32.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-32.2.0.tgz#d848ea8475a9be63d8d261bd7fa01cf2a2bb32d5" @@ -4484,7 +4496,7 @@ package-name-regex@1.0.9: resolved "https://registry.yarnpkg.com/package-name-regex/-/package-name-regex-1.0.9.tgz#d7d33ff3f2ef43a28fbb02aa4fcf48fc1ab26588" integrity sha512-+U2oQCfEz2IlGqws8gmfKzdMDbSd6+RZp6UIFdKo+GAw3+o+kfnsgXkWtJ1JMoKhpP2kEvuYyTy1lXOEQEe0ZA== -pako@~1.0.2: +pako@^1.0.10, pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -5747,7 +5759,7 @@ ts-simple-type@~1.0.5: resolved "https://registry.npmjs.org/ts-simple-type/-/ts-simple-type-1.0.7.tgz#03930af557528dd40eaa121913c7035a0baaacf8" integrity sha512-zKmsCQs4dZaeSKjEA7pLFDv7FHHqAFLPd0Mr//OIJvu8M+4p4bgSFJwZSEBEg3ec9W7RzRz1vi8giiX0+mheBQ== -tslib@2.1.0, tslib@^2.0.3: +tslib@2.1.0, tslib@^2.0.3, tslib@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== @@ -5757,11 +5769,6 @@ tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" - integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"