diff --git a/src-ui/.eslintrc.json b/src-ui/.eslintrc.json index 12b35ce26..37f9c7c12 100644 --- a/src-ui/.eslintrc.json +++ b/src-ui/.eslintrc.json @@ -1,7 +1,8 @@ { "root": true, "ignorePatterns": [ - "projects/**/*" + "projects/**/*", + "/src/app/components/common/pdf-viewer/**" ], "overrides": [ { diff --git a/src-ui/angular.json b/src-ui/angular.json index 1fef2fe7d..9a73922c7 100644 --- a/src-ui/angular.json +++ b/src-ui/angular.json @@ -65,7 +65,7 @@ "src/assets", "src/manifest.webmanifest", { - "glob": "pdf.worker.min.js", + "glob": "{pdf.worker.min.js,pdf.min.js}", "input": "node_modules/pdfjs-dist/build/", "output": "/assets/js/" } @@ -75,7 +75,8 @@ ], "scripts": [], "allowedCommonJsDependencies": [ - "ng2-pdf-viewer" + "pdfjs-dist", + "pdfjs-dist/web/pdf_viewer" ], "vendorChunk": true, "extractLicenses": false, @@ -109,7 +110,7 @@ { "type": "anyComponentStyle", "maximumWarning": "6kb", - "maximumError": "10kb" + "maximumError": "30kb" } ] }, diff --git a/src-ui/e2e/document-detail/document-detail.spec.ts b/src-ui/e2e/document-detail/document-detail.spec.ts index 1bc2b6aa9..e40b88ccc 100644 --- a/src-ui/e2e/document-detail/document-detail.spec.ts +++ b/src-ui/e2e/document-detail/document-detail.spec.ts @@ -79,7 +79,7 @@ test('should show a mobile preview', async ({ page }) => { await page.setViewportSize({ width: 400, height: 1000 }) await expect(page.getByRole('tab', { name: 'Preview' })).toBeVisible() await page.getByRole('tab', { name: 'Preview' }).click() - await page.waitForSelector('pdf-viewer') + await page.waitForSelector('pngx-pdf-viewer') }) test('should show a list of notes', async ({ page }) => { diff --git a/src-ui/jest.config.js b/src-ui/jest.config.js index 78edc0822..b544a4767 100644 --- a/src-ui/jest.config.js +++ b/src-ui/jest.config.js @@ -7,6 +7,7 @@ module.exports = { 'abstract-name-filter-service', 'abstract-paperless-service', ], + coveragePathIgnorePatterns: ['/src/app/components/common/pdf-viewer/*'], transformIgnorePatterns: [`/node_modules/(?!.*\\.mjs$|lodash-es)`], moduleNameMapper: { '^src/(.*)': '/src/$1', diff --git a/src-ui/package-lock.json b/src-ui/package-lock.json index cf0882708..4e898bf81 100644 --- a/src-ui/package-lock.json +++ b/src-ui/package-lock.json @@ -25,11 +25,11 @@ "bootstrap": "^5.3.2", "file-saver": "^2.0.5", "mime-names": "^1.0.0", - "ng2-pdf-viewer": "^10.0.0", "ngx-color": "^9.0.0", "ngx-cookie-service": "^16.0.1", "ngx-file-drop": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^13.0.6", + "pdfjs-dist": "^3.11.174", "rxjs": "^7.8.1", "tslib": "^2.6.2", "uuid": "^9.0.1", @@ -4728,6 +4728,116 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "optional": true + }, "node_modules/@ng-bootstrap/ng-bootstrap": { "version": "15.1.2", "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.2.tgz", @@ -6728,7 +6838,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "devOptional": true }, "node_modules/accepts": { "version": "1.3.8", @@ -6823,7 +6933,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "4" }, @@ -6974,7 +7084,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true + "devOptional": true }, "node_modules/are-we-there-yet": { "version": "3.0.1", @@ -7332,7 +7442,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "devOptional": true }, "node_modules/base64-js": { "version": "1.5.1", @@ -7476,7 +7586,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "devOptional": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -7682,6 +7792,21 @@ } ] }, + "node_modules/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -7740,7 +7865,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -7884,7 +8009,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, + "devOptional": true, "bin": { "color-support": "bin.js" } @@ -7974,7 +8099,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "devOptional": true }, "node_modules/concurrently": { "version": "8.2.2", @@ -8101,7 +8226,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true + "devOptional": true }, "node_modules/content-disposition": { "version": "0.5.4", @@ -8616,6 +8741,18 @@ "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dedent": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", @@ -8705,7 +8842,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "devOptional": true }, "node_modules/depd": { "version": "2.0.0", @@ -8735,6 +8872,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8863,12 +9009,6 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/dommatrix": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dommatrix/-/dommatrix-1.0.3.tgz", - "integrity": "sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==", - "deprecated": "dommatrix is no longer maintained. Please use @thednp/dommatrix." - }, "node_modules/domutils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", @@ -10175,7 +10315,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "devOptional": true }, "node_modules/fsevents": { "version": "2.3.2", @@ -10469,7 +10609,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true + "devOptional": true }, "node_modules/hasown": { "version": "2.0.0", @@ -10706,7 +10846,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, + "devOptional": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -10907,7 +11047,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, + "devOptional": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -10917,7 +11057,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "devOptional": true }, "node_modules/ini": { "version": "4.1.1", @@ -13824,6 +13964,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mini-css-extract-plugin": { "version": "2.7.6", "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", @@ -13853,7 +14005,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "devOptional": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13874,7 +14026,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -14048,7 +14200,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, + "devOptional": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -14061,7 +14213,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14073,13 +14225,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -14129,6 +14281,12 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/nan": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", + "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", @@ -14209,18 +14367,6 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, - "node_modules/ng2-pdf-viewer": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/ng2-pdf-viewer/-/ng2-pdf-viewer-10.0.0.tgz", - "integrity": "sha512-zEefcAsTpDoxFceQYs3ycPMaUAkt5UX4OcTstVQoNqRK6w+vOY+V8z8aFCuBwnt+7iN1EHaIpquOf4S9mWc04g==", - "dependencies": { - "pdfjs-dist": "~2.16.105", - "tslib": "^2.3.0" - }, - "peerDependencies": { - "pdfjs-dist": "~2.16.105" - } - }, "node_modules/ngx-color": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/ngx-color/-/ngx-color-9.0.0.tgz", @@ -14312,6 +14458,48 @@ "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "dev": true }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "optional": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "optional": true + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "optional": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "optional": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", @@ -14776,6 +14964,15 @@ "node": ">=8" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -14825,7 +15022,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, + "devOptional": true, "dependencies": { "wrappy": "1" } @@ -15396,7 +15593,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -15456,21 +15653,25 @@ "node": ">=8" } }, + "node_modules/path2d-polyfill": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path2d-polyfill/-/path2d-polyfill-2.0.1.tgz", + "integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pdfjs-dist": { - "version": "2.16.105", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz", - "integrity": "sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==", - "dependencies": { - "dommatrix": "^1.0.3", - "web-streams-polyfill": "^3.2.1" + "version": "3.11.174", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz", + "integrity": "sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==", + "engines": { + "node": ">=18" }, - "peerDependencies": { - "worker-loader": "^3.0.8" - }, - "peerDependenciesMeta": { - "worker-loader": { - "optional": true - } + "optionalDependencies": { + "canvas": "^2.11.2", + "path2d-polyfill": "^2.0.1" } }, "node_modules/picocolors": { @@ -15990,7 +16191,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -16243,7 +16444,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, + "devOptional": true, "dependencies": { "glob": "^7.1.3" }, @@ -16258,7 +16459,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, + "devOptional": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -16333,7 +16534,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -16647,7 +16848,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true + "devOptional": true }, "node_modules/set-function-length": { "version": "1.1.1", @@ -16755,6 +16956,37 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -17018,7 +17250,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, + "devOptional": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -17186,7 +17418,7 @@ "version": "6.1.15", "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", - "dev": true, + "devOptional": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -17219,7 +17451,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, + "devOptional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -17231,7 +17463,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -17243,7 +17475,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/terser": { "version": "5.19.2", @@ -17892,7 +18124,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "devOptional": true }, "node_modules/utils-merge": { "version": "1.0.1", @@ -18108,14 +18340,6 @@ "defaults": "^1.0.3" } }, - "node_modules/web-streams-polyfill": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", - "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -18484,7 +18708,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, + "devOptional": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } @@ -18596,7 +18820,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "devOptional": true }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/src-ui/package.json b/src-ui/package.json index 7f0720388..a2c94084c 100644 --- a/src-ui/package.json +++ b/src-ui/package.json @@ -27,11 +27,11 @@ "bootstrap": "^5.3.2", "file-saver": "^2.0.5", "mime-names": "^1.0.0", - "ng2-pdf-viewer": "^10.0.0", "ngx-color": "^9.0.0", "ngx-cookie-service": "^16.0.1", "ngx-file-drop": "^16.0.0", "ngx-ui-tour-ng-bootstrap": "^13.0.6", + "pdfjs-dist": "^3.11.174", "rxjs": "^7.8.1", "tslib": "^2.6.2", "uuid": "^9.0.1", diff --git a/src-ui/src/app/app.module.ts b/src-ui/src/app/app.module.ts index 684cf6d18..8d7ea5663 100644 --- a/src-ui/src/app/app.module.ts +++ b/src-ui/src/app/app.module.ts @@ -51,7 +51,6 @@ import { SavedViewWidgetComponent } from './components/dashboard/widgets/saved-v import { StatisticsWidgetComponent } from './components/dashboard/widgets/statistics-widget/statistics-widget.component' import { UploadFileWidgetComponent } from './components/dashboard/widgets/upload-file-widget/upload-file-widget.component' import { WidgetFrameComponent } from './components/dashboard/widgets/widget-frame/widget-frame.component' -import { PdfViewerModule } from 'ng2-pdf-viewer' import { WelcomeWidgetComponent } from './components/dashboard/widgets/welcome-widget/welcome-widget.component' import { YesNoPipe } from './pipes/yes-no.pipe' import { FileSizePipe } from './pipes/file-size.pipe' @@ -106,6 +105,7 @@ import { CustomFieldsComponent } from './components/manage/custom-fields/custom- import { CustomFieldEditDialogComponent } from './components/common/edit-dialog/custom-field-edit-dialog/custom-field-edit-dialog.component' import { CustomFieldsDropdownComponent } from './components/common/custom-fields-dropdown/custom-fields-dropdown.component' import { ProfileEditDialogComponent } from './components/common/profile-edit-dialog/profile-edit-dialog.component' +import { PdfViewerComponent } from './components/common/pdf-viewer/pdf-viewer.component' import localeAf from '@angular/common/locales/af' import localeAr from '@angular/common/locales/ar' @@ -258,6 +258,7 @@ function initializeApp(settings: SettingsService) { CustomFieldEditDialogComponent, CustomFieldsDropdownComponent, ProfileEditDialogComponent, + PdfViewerComponent, ], imports: [ BrowserModule, @@ -267,7 +268,6 @@ function initializeApp(settings: SettingsService) { FormsModule, ReactiveFormsModule, NgxFileDropModule, - PdfViewerModule, NgSelectModule, ColorSliderModule, TourNgBootstrapModule, diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html new file mode 100644 index 000000000..cd2c85af5 --- /dev/null +++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.html @@ -0,0 +1,3 @@ +
+
+
diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss new file mode 100644 index 000000000..408bbb0a0 --- /dev/null +++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.scss @@ -0,0 +1,1014 @@ +/** + * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/pdf-viewer.component.scss + * Created by vadimdez on 21/06/16. + */ +.pngx-pdf-viewer-container { + overflow-x: auto; + position: absolute; + height: 100%; + width: 100%; + -webkit-overflow-scrolling: touch; + } + + :host { + display: block; + position: relative; + } + + :host ::ng-deep { + /* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + --pdfViewer-padding-bottom: 0; + --page-margin: 1px auto -8px; + --page-border: none; + --spreadHorizontalWrapped-margin-LR: -3.5px; + --viewer-container-height: 0; + --annotation-unfocused-field-background: url("data:image/svg+xml;charset=UTF-8,"); + --xfa-unfocused-field-background: var( + --annotation-unfocused-field-background + ); + --page-border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAATCAYAAAByUDbMAAAA1ElEQVQ4jbWUWw6EIAxFy2NFs/8NzR4UJhpqLsdi5mOmSSMUOfYWqv3S0gMr4XlYH/64gZa/gN3ANYA7KAXALt4ktoQ5MI9YxqaG8bWmsIysMuT6piSQCa4whZThCu8CM4zP9YJaKci9jicPq3NcBWYoPMGUlhG7ivtkB+gVyFY75wXghOvh8t5mto1Mdim6e+MBqH6XsY+YAwjpq3vGF7weTWQptLEDVCZvPTMl5JZZsdh47FHW6qFMyvLYqjcnmdFfY9Xk/KDOlzCusX2mi/ofM7MPkzBcSp4Q1/wAAAAASUVORK5CYII=') + 9 9 repeat; + --scale-factor: 1; + + --focus-outline: solid 2px blue; + --hover-outline: dashed 2px blue; + --freetext-line-height: 1.35; + --freetext-padding: 2px; + --editorInk-editing-cursor: pointer; + + @media screen and (forced-colors: active) { + --pdfViewer-padding-bottom: 9px; + --page-margin: 8px auto -1px; + --page-border: 1px solid CanvasText; + --page-border-image: none; + --spreadHorizontalWrapped-margin-LR: 3.5px; + } + + @media (forced-colors: active) { + --focus-outline: solid 3px ButtonText; + --hover-outline: dashed 3px ButtonText; + } + + .textLayer { + position: absolute; + text-align: initial; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; + opacity: 0.2; + line-height: 1; + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + text-size-adjust: none; + forced-color-adjust: none; + } + + .textLayer span, + .textLayer br { + color: transparent; + position: absolute; + white-space: pre; + cursor: text; + transform-origin: 0% 0%; + } + + /* Only necessary in Google Chrome, see issue 14205, and most unfortunately + * the problem doesn't show up in "text" reference tests. */ + .textLayer span.markedContent { + top: 0; + height: 0; + } + + .textLayer .highlight { + margin: -1px; + padding: 1px; + background-color: rgba(180, 0, 170, 1); + border-radius: 4px; + } + + .textLayer .highlight.appended { + position: initial; + } + + .textLayer .highlight.begin { + border-radius: 4px 0 0 4px; + } + + .textLayer .highlight.end { + border-radius: 0 4px 4px 0; + } + + .textLayer .highlight.middle { + border-radius: 0; + } + + .textLayer .highlight.selected { + background-color: rgba(0, 100, 0, 1); + } + + .textLayer ::-moz-selection { + background: rgba(0, 0, 255, 1); + } + + .textLayer ::selection { + background: rgba(0, 0, 255, 1); + } + + /* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */ + .textLayer br::-moz-selection { + background: transparent; + } + + .textLayer br::selection { + background: transparent; + } + + .textLayer .endOfContent { + display: block; + position: absolute; + left: 0; + top: 100%; + right: 0; + bottom: 0; + z-index: -1; + cursor: default; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + } + + .textLayer .endOfContent.active { + top: 0; + } + + @media (forced-colors: active) { + .annotationLayer .textWidgetAnnotation input:required, + .annotationLayer .textWidgetAnnotation textarea:required, + .annotationLayer .choiceWidgetAnnotation select:required, + .annotationLayer .buttonWidgetAnnotation.checkBox input:required, + .annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid selectedItem; + } + } + + .annotationLayer { + position: absolute; + top: 0; + left: 0; + pointer-events: none; + transform-origin: 0 0; + } + + .annotationLayer section { + position: absolute; + text-align: initial; + pointer-events: auto; + box-sizing: border-box; + transform-origin: 0 0; + } + + .annotationLayer .linkAnnotation > a, + .annotationLayer .buttonWidgetAnnotation.pushButton > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .annotationLayer .buttonWidgetAnnotation.pushButton > canvas { + width: 100%; + height: 100%; + } + + .annotationLayer .linkAnnotation > a:hover, + .annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { + opacity: 0.2; + background: rgba(255, 255, 0, 1); + box-shadow: 0 2px 10px rgba(255, 255, 0, 1); + } + + .annotationLayer .textAnnotation img { + position: absolute; + cursor: pointer; + width: 100%; + height: 100%; + } + + .annotationLayer .textWidgetAnnotation input, + .annotationLayer .textWidgetAnnotation textarea, + .annotationLayer .choiceWidgetAnnotation select, + .annotationLayer .buttonWidgetAnnotation.checkBox input, + .annotationLayer .buttonWidgetAnnotation.radioButton input { + background-image: var(--annotation-unfocused-field-background); + border: 1px solid transparent; + box-sizing: border-box; + font: calc(9px * var(--scale-factor)) sans-serif; + height: 100%; + margin: 0; + vertical-align: top; + width: 100%; + } + + .annotationLayer .textWidgetAnnotation input:required, + .annotationLayer .textWidgetAnnotation textarea:required, + .annotationLayer .choiceWidgetAnnotation select:required, + .annotationLayer .buttonWidgetAnnotation.checkBox input:required, + .annotationLayer .buttonWidgetAnnotation.radioButton input:required { + outline: 1.5px solid red; + } + + .annotationLayer .choiceWidgetAnnotation select option { + padding: 0; + } + + .annotationLayer .buttonWidgetAnnotation.radioButton input { + border-radius: 50%; + } + + .annotationLayer .textWidgetAnnotation textarea { + resize: none; + } + + .annotationLayer .textWidgetAnnotation input[disabled], + .annotationLayer .textWidgetAnnotation textarea[disabled], + .annotationLayer .choiceWidgetAnnotation select[disabled], + .annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], + .annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { + background: none; + border: 1px solid transparent; + cursor: not-allowed; + } + + .annotationLayer .textWidgetAnnotation input:hover, + .annotationLayer .textWidgetAnnotation textarea:hover, + .annotationLayer .choiceWidgetAnnotation select:hover, + .annotationLayer .buttonWidgetAnnotation.checkBox input:hover, + .annotationLayer .buttonWidgetAnnotation.radioButton input:hover { + border: 1px solid rgba(0, 0, 0, 1); + } + + .annotationLayer .textWidgetAnnotation input:focus, + .annotationLayer .textWidgetAnnotation textarea:focus, + .annotationLayer .choiceWidgetAnnotation select:focus { + background: none; + border: 1px solid transparent; + } + + .annotationLayer .textWidgetAnnotation input :focus, + .annotationLayer .textWidgetAnnotation textarea :focus, + .annotationLayer .choiceWidgetAnnotation select :focus, + .annotationLayer .buttonWidgetAnnotation.checkBox :focus, + .annotationLayer .buttonWidgetAnnotation.radioButton :focus { + background-image: none; + background-color: transparent; + outline: auto; + } + + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after, + .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { + background-color: CanvasText; + content: ''; + display: block; + position: absolute; + } + + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { + height: 80%; + left: 45%; + width: 1px; + } + + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before { + transform: rotate(45deg); + } + + .annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { + transform: rotate(-45deg); + } + + .annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { + border-radius: 50%; + height: 50%; + left: 30%; + top: 20%; + width: 50%; + } + + .annotationLayer .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; + } + + .annotationLayer .textWidgetAnnotation input.comb:focus { + /* + * Letter spacing is placed on the right side of each character. Hence, the + * letter spacing of the last character may be placed outside the visible + * area, causing horizontal scrolling. We avoid this by extending the width + * when the element has focus and revert this when it loses focus. + */ + width: 103%; + } + + .annotationLayer .buttonWidgetAnnotation.checkBox input, + .annotationLayer .buttonWidgetAnnotation.radioButton input { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + } + + .annotationLayer .popupTriggerArea { + height: 100%; + width: 100%; + } + + .annotationLayer .popupWrapper { + position: absolute; + font-size: calc(9px * var(--scale-factor)); + width: 100%; + min-width: calc(180px * var(--scale-factor)); + pointer-events: none; + } + + .annotationLayer .popup { + position: absolute; + max-width: calc(180px * var(--scale-factor)); + background-color: rgba(255, 255, 153, 1); + box-shadow: 0 calc(2px * var(--scale-factor)) + calc(5px * var(--scale-factor)) rgba(136, 136, 136, 1); + border-radius: calc(2px * var(--scale-factor)); + padding: calc(6px * var(--scale-factor)); + margin-left: calc(5px * var(--scale-factor)); + cursor: pointer; + font: message-box; + white-space: normal; + word-wrap: break-word; + pointer-events: auto; + } + + .annotationLayer .popup > * { + font-size: calc(9px * var(--scale-factor)); + } + + .annotationLayer .popup h1 { + display: inline-block; + } + + .annotationLayer .popupDate { + display: inline-block; + margin-left: calc(5px * var(--scale-factor)); + } + + .annotationLayer .popupContent { + border-top: 1px solid rgba(51, 51, 51, 1); + margin-top: calc(2px * var(--scale-factor)); + padding-top: calc(2px * var(--scale-factor)); + } + + .annotationLayer .richText > * { + white-space: pre-wrap; + font-size: calc(9px * var(--scale-factor)); + } + + .annotationLayer .highlightAnnotation, + .annotationLayer .underlineAnnotation, + .annotationLayer .squigglyAnnotation, + .annotationLayer .strikeoutAnnotation, + .annotationLayer .freeTextAnnotation, + .annotationLayer .lineAnnotation svg line, + .annotationLayer .squareAnnotation svg rect, + .annotationLayer .circleAnnotation svg ellipse, + .annotationLayer .polylineAnnotation svg polyline, + .annotationLayer .polygonAnnotation svg polygon, + .annotationLayer .caretAnnotation, + .annotationLayer .inkAnnotation svg polyline, + .annotationLayer .stampAnnotation, + .annotationLayer .fileAttachmentAnnotation { + cursor: pointer; + } + + .annotationLayer section svg { + position: absolute; + width: 100%; + height: 100%; + } + + .annotationLayer .annotationTextContent { + position: absolute; + width: 100%; + height: 100%; + opacity: 0; + color: transparent; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + pointer-events: none; + } + + .annotationLayer .annotationTextContent span { + width: 100%; + display: inline-block; + } + + @media (forced-colors: active) { + .xfaLayer *:required { + outline: 1.5px solid selectedItem; + } + } + + .xfaLayer .highlight { + margin: -1px; + padding: 1px; + background-color: rgba(239, 203, 237, 1); + border-radius: 4px; + } + + .xfaLayer .highlight.appended { + position: initial; + } + + .xfaLayer .highlight.begin { + border-radius: 4px 0 0 4px; + } + + .xfaLayer .highlight.end { + border-radius: 0 4px 4px 0; + } + + .xfaLayer .highlight.middle { + border-radius: 0; + } + + .xfaLayer .highlight.selected { + background-color: rgba(203, 223, 203, 1); + } + + .xfaLayer ::-moz-selection { + background: rgba(0, 0, 255, 1); + } + + .xfaLayer ::selection { + background: rgba(0, 0, 255, 1); + } + + .xfaPage { + overflow: hidden; + position: relative; + } + + .xfaContentarea { + position: absolute; + } + + .xfaPrintOnly { + display: none; + } + + .xfaLayer { + position: absolute; + text-align: initial; + top: 0; + left: 0; + transform-origin: 0 0; + line-height: 1.2; + } + + .xfaLayer * { + color: inherit; + font: inherit; + font-style: inherit; + font-weight: inherit; + font-kerning: inherit; + letter-spacing: -0.01px; + text-align: inherit; + text-decoration: inherit; + box-sizing: border-box; + background-color: transparent; + padding: 0; + margin: 0; + pointer-events: auto; + line-height: inherit; + } + + .xfaLayer *:required { + outline: 1.5px solid red; + } + + .xfaLayer div { + pointer-events: none; + } + + .xfaLayer svg { + pointer-events: none; + } + + .xfaLayer svg * { + pointer-events: none; + } + + .xfaLayer a { + color: blue; + } + + .xfaRich li { + margin-left: 3em; + } + + .xfaFont { + color: black; + font-weight: normal; + font-kerning: none; + font-size: 10px; + font-style: normal; + letter-spacing: 0; + text-decoration: none; + vertical-align: 0; + } + + .xfaCaption { + overflow: hidden; + flex: 0 0 auto; + } + + .xfaCaptionForCheckButton { + overflow: hidden; + flex: 1 1 auto; + } + + .xfaLabel { + height: 100%; + width: 100%; + } + + .xfaLeft { + display: flex; + flex-direction: row; + align-items: center; + } + + .xfaRight { + display: flex; + flex-direction: row-reverse; + align-items: center; + } + + .xfaLeft > .xfaCaption, + .xfaLeft > .xfaCaptionForCheckButton, + .xfaRight > .xfaCaption, + .xfaRight > .xfaCaptionForCheckButton { + max-height: 100%; + } + + .xfaTop { + display: flex; + flex-direction: column; + align-items: flex-start; + } + + .xfaBottom { + display: flex; + flex-direction: column-reverse; + align-items: flex-start; + } + + .xfaTop > .xfaCaption, + .xfaTop > .xfaCaptionForCheckButton, + .xfaBottom > .xfaCaption, + .xfaBottom > .xfaCaptionForCheckButton { + width: 100%; + } + + .xfaBorder { + background-color: transparent; + position: absolute; + pointer-events: none; + } + + .xfaWrapped { + width: 100%; + height: 100%; + } + + .xfaTextfield:focus, + .xfaSelect:focus { + background-image: none; + background-color: transparent; + outline: auto; + outline-offset: -1px; + } + + .xfaCheckbox:focus, + .xfaRadio:focus { + outline: auto; + } + + .xfaTextfield, + .xfaSelect { + height: 100%; + width: 100%; + flex: 1 1 auto; + border: none; + resize: none; + background-image: var(--xfa-unfocused-field-background); + } + + .xfaTop > .xfaTextfield, + .xfaTop > .xfaSelect, + .xfaBottom > .xfaTextfield, + .xfaBottom > .xfaSelect { + flex: 0 1 auto; + } + + .xfaButton { + cursor: pointer; + width: 100%; + height: 100%; + border: none; + text-align: center; + } + + .xfaLink { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + } + + .xfaCheckbox, + .xfaRadio { + width: 100%; + height: 100%; + flex: 0 0 auto; + border: none; + } + + .xfaRich { + white-space: pre-wrap; + width: 100%; + height: 100%; + } + + .xfaImage { + -o-object-position: left top; + object-position: left top; + -o-object-fit: contain; + object-fit: contain; + width: 100%; + height: 100%; + } + + .xfaLrTb, + .xfaRlTb, + .xfaTb { + display: flex; + flex-direction: column; + align-items: stretch; + } + + .xfaLr { + display: flex; + flex-direction: row; + align-items: stretch; + } + + .xfaRl { + display: flex; + flex-direction: row-reverse; + align-items: stretch; + } + + .xfaTb > div { + justify-content: left; + } + + .xfaPosition { + position: relative; + } + + .xfaArea { + position: relative; + } + + .xfaValignMiddle { + display: flex; + align-items: center; + } + + .xfaTable { + display: flex; + flex-direction: column; + align-items: stretch; + } + + .xfaTable .xfaRow { + display: flex; + flex-direction: row; + align-items: stretch; + } + + .xfaTable .xfaRlRow { + display: flex; + flex-direction: row-reverse; + align-items: stretch; + flex: 1; + } + + .xfaTable .xfaRlRow > div { + flex: 1; + } + + .xfaNonInteractive input, + .xfaNonInteractive textarea, + .xfaDisabled input, + .xfaDisabled textarea, + .xfaReadOnly input, + .xfaReadOnly textarea { + background: initial; + } + + @media print { + .xfaTextfield, + .xfaSelect { + background: transparent; + } + + .xfaSelect { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + text-indent: 1px; + text-overflow: ''; + } + } + + [data-editor-rotation='90'] { + transform: rotate(90deg); + } + [data-editor-rotation='180'] { + transform: rotate(180deg); + } + [data-editor-rotation='270'] { + transform: rotate(270deg); + } + + .annotationEditorLayer { + background: transparent; + position: absolute; + top: 0; + left: 0; + font-size: calc(100px * var(--scale-factor)); + transform-origin: 0 0; + } + + .annotationEditorLayer .selectedEditor { + outline: var(--focus-outline); + resize: none; + } + + .annotationEditorLayer .freeTextEditor { + position: absolute; + background: transparent; + border-radius: 3px; + padding: calc(var(--freetext-padding) * var(--scale-factor)); + resize: none; + width: auto; + height: auto; + z-index: 1; + transform-origin: 0 0; + touch-action: none; + } + + .annotationEditorLayer .freeTextEditor .internal { + background: transparent; + border: none; + top: 0; + left: 0; + overflow: visible; + white-space: nowrap; + resize: none; + font: 10px sans-serif; + line-height: var(--freetext-line-height); + } + + .annotationEditorLayer .freeTextEditor .overlay { + position: absolute; + display: none; + background: transparent; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .annotationEditorLayer .freeTextEditor .overlay.enabled { + display: block; + } + + .annotationEditorLayer .freeTextEditor .internal:empty::before { + content: attr(default-content); + color: gray; + } + + .annotationEditorLayer .freeTextEditor .internal:focus { + outline: none; + } + + .annotationEditorLayer .inkEditor.disabled { + resize: none; + } + + .annotationEditorLayer .inkEditor.disabled.selectedEditor { + resize: horizontal; + } + + .annotationEditorLayer .freeTextEditor:hover:not(.selectedEditor), + .annotationEditorLayer .inkEditor:hover:not(.selectedEditor) { + outline: var(--hover-outline); + } + + .annotationEditorLayer .inkEditor { + position: absolute; + background: transparent; + border-radius: 3px; + overflow: auto; + width: 100%; + height: 100%; + z-index: 1; + transform-origin: 0 0; + cursor: auto; + } + + .annotationEditorLayer .inkEditor.editing { + resize: none; + cursor: var(--editorInk-editing-cursor), pointer; + } + + .annotationEditorLayer .inkEditor .inkEditorCanvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + touch-action: none; + } + + [data-main-rotation='90'] { + transform: rotate(90deg) translateY(-100%); + } + [data-main-rotation='180'] { + transform: rotate(180deg) translate(-100%, -100%); + } + [data-main-rotation='270'] { + transform: rotate(270deg) translateX(-100%); + } + + .pdfViewer { + padding-bottom: var(--pdfViewer-padding-bottom); + } + + .pdfViewer .canvasWrapper { + overflow: hidden; + } + + .pdfViewer .page { + direction: ltr; + width: 816px; + height: 1056px; + margin: var(--page-margin); + position: relative; + overflow: visible; + border: var(--page-border); + -o-border-image: var(--page-border-image); + border-image: var(--page-border-image); + background-clip: content-box; + background-color: rgba(255, 255, 255, 1); + } + + .pdfViewer .dummyPage { + position: relative; + width: 0; + height: var(--viewer-container-height); + } + + .pdfViewer.removePageBorders .page { + margin: 0 auto 10px; + border: none; + } + + .pdfViewer.singlePageView { + display: inline-block; + } + + .pdfViewer.singlePageView .page { + margin: 0; + border: none; + } + + .pdfViewer.scrollHorizontal, + .pdfViewer.scrollWrapped, + .spread { + margin-left: 3.5px; + margin-right: 3.5px; + text-align: center; + } + + .pdfViewer.scrollHorizontal, + .spread { + white-space: nowrap; + } + + .pdfViewer.removePageBorders, + .pdfViewer.scrollHorizontal .spread, + .pdfViewer.scrollWrapped .spread { + margin-left: 0; + margin-right: 0; + } + + .spread .page, + .spread .dummyPage, + .pdfViewer.scrollHorizontal .page, + .pdfViewer.scrollWrapped .page, + .pdfViewer.scrollHorizontal .spread, + .pdfViewer.scrollWrapped .spread { + display: inline-block; + vertical-align: middle; + } + + .spread .page, + .pdfViewer.scrollHorizontal .page, + .pdfViewer.scrollWrapped .page { + margin-left: var(--spreadHorizontalWrapped-margin-LR); + margin-right: var(--spreadHorizontalWrapped-margin-LR); + } + + .pdfViewer.removePageBorders .spread .page, + .pdfViewer.removePageBorders.scrollHorizontal .page, + .pdfViewer.removePageBorders.scrollWrapped .page { + margin-left: 5px; + margin-right: 5px; + } + + .pdfViewer .page canvas { + margin: 0; + display: block; + } + + .pdfViewer .page canvas[hidden] { + display: none; + } + + .pdfViewer .page .loadingIcon { + position: absolute; + display: block; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: url('data:image/gif;base64,R0lGODlhGAAYAPQQAM7Ozvr6+uDg4LCwsOjo6I6OjsjIyJycnNjY2KioqMDAwPLy8nZ2doaGhri4uGhoaP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/ilPcHRpbWl6ZWQgd2l0aCBodHRwczovL2V6Z2lmLmNvbS9vcHRpbWl6ZQAh+QQJBwAQACwAAAAAGAAYAAAFmiAkjiTkOGVaBgjZNGSgkgKjjM8zLoI8iy+BKCdiCX8iBeMAhEEIPRXLxViYUE9CbCQoFAzFhHY3zkaT3oPvBz1zE4UBsr1eWZH4vAowOBwGAHk8AoQLfH6Agm0Ed3qOAXWOIgQKiWyFJQgDgJEpdG+WEACNEFNFmKVlVzJQk6qdkwqBoi1mebJ3ALNGeIZHtGSwNDS1RZKueCEAIfkECQcAEAAsAAAAABgAGAAABZcgJI4kpChlWgYCWRQkEKgjURgjw4zOg9CjVwuiEyEeO6CxkBC9nA+HiuUqLEyoBZI0Mx4SAFFgQCDZuguBoGv6Dtg0gvpqdhxQQDkBzuUr/4A1JwMKP39pc2mDhYCIc4GQYn6QCwCMeY91l0p6dBAEJ0OfcFRimZ91Mwt0alxxAIZyRmuAsKxDLKKvZbM1tJxmvGKRpn8hACH5BAkHABAALAAAAAAYABgAAAWhICSOJGQYZVoGAnkcJBKoI3EAY1GMCtPSosSBINKJBIwGkHdwBGGQA0OhYpEGQxNqkYzNIITBACEKKBaxxNfBeOCO4vMy0Hg8nDHFeCktkKtfNAtoS4UqAicKBj9zBAKPC4iKi4aRkISGmWWBmjUIAIyHkCUEAKCVo2WmREecVqoCgZhgP4NHrGWCj7e3szSpuxAsoVWxnp6cVV4kyZW+KSEAIfkECQcAEAAsAAAAABgAGAAABZkgJI4kBABlWgYEOQykEKgjMSDjcYxG0dKi108nEhQKQN4rCIMkCgbawjWYnSCLY2yGVSgEooBhWqsGGwxc0RtNBgoMhmJ1QgETjANYFeBKyUmBKQQIdT9JDmgPDQ6EhoKJD4sOgpWWgiwChyqEBH5hmptSoSOZgJ4kLKWkYTF7C2SaqaM/hEWygay4mYG8t6uffFuzl1iANCEAIfkECQcAEAAsAAAAABgAGAAABZ0gJI4khCBlmhKkopBCoI6LIozDMAIHO4uuBVBnOiR+I4FrCDwAZsKdQnaCLIwwmRUA8JmioprWUCjcwlwUMnAoG0qL03k2KCS8cC0UjOzDCQKBfHQFDAwFU4CCfgqFhy9+kZJWgzSKSAcPZn+BfQENDw8OljGWJAFeDoZPYTBnC1GdSXqnsoBolSulX2GyP6hgvnG0KrS3NJNhuSQhACH5BAkHABAALAAAAAAYABgAAAWaICSOJCQIZZoupGGQRKCOC0CMijIiwz2LABtQZxoMfjQhxAXszWQ7gOwECRhh0MCJJRJARTUoIHFAgbfI6uBwAJS01J/i4PClVYHvfV8lbLlIBmwFbQt+aGmChG18jXeGT4dICQxlb4g/AQUMDER9XjR6BAdiDQwINDBmkAsPDVh4cX4imw53iLKuaVqAcUsPqEiidkt6j4AzIQAh+QQJBwAQACwAAAAAGAAYAAAFmSAkjiREEGWaBiSCtCoZCMsIAKOg1LEo0KKbaKFQ9EYLoOkFuQlirNxzCQkUW9GZ0hQd4nyDAWr4G/esYSbyZFYZwu3jqiuvr8u8I2BwOAwASXh1e31/doeHC3klWnElfAlTd46MfQUGk2stCVEGBQWSdCciDg5VDAVYKoEiDQ0iBwxGcj9RDw8+qHIzebc2DJJQJK6qiKVyIQAh+QQJBwAQACwAAAAAGAAYAAAFmSAkjiS0LGWaBiRBtCoZCKgoCCMB1DF0sz6cCQDo5W62l28XAyZFpyECBv3lnCbhUqHMIo0Qg4Jbmn1jRCa4iV27TzfXGjEecOFWMN1OdvvfPGUuXSoKBw6EXokrAwcHRVU0UAeEBANAAAmUI1gNDyhjJgUHLW0iDg8FIqOnBQZrDA9TELE2rEYIDw4jta2LMpCrqld/YQpgIQAh+QQJBwAQACwAAAAAGAAYAAAFmyAkjiS0LGWaBiRBkKw6BgIqCsJcyyMe4yJajhcEml5H26o1PN2QQd3uFiv2AADlAgflIbDdZLgkABOJgep5LfWty4p4zeU+w+XsvJWXliEKDwdEBgMKYQ4PDw1qK3EDCCMAiQ5BCV0LCj+FSDQkgCgGBiYHAy2MIgoMghAHqw4HAGsNDEMFBTekdgwKI7aRB2MwkL2rVHoQoWchACH5BAkHABAALAAAAAAYABgAAAWWICSOJLQsZZoGJEGQrDoGAioKwlzLIx7jIlqOFwSaXkfbqjU83ZBB3e4WK0qrCxyU55peid0qcUwuixyNx6PhILsAcAJazXYj4lvz2MkLiFsHDAlEcABKZwwMBX8pBgoKQxAIigpBA1sLBj+PSDQkB4uSACYDlTMyBgWDEKVnl2QFBUigN61gBQYjtLV5JZ4jtlR6omMhACH5BAkHABAALAAAAAAYABgAAAWaICSOJLQsZZoGJEGQrDoGAioKwlzLIx7jIlqOFwSaXkdbidYanm7I4AjwYDh6saJuJ3JUG1mZi9srPA7EcRimJLrfJYWZUVC8TziXnEG3u/E+cIJaPAFrPQl1aQAIbRAGBZGHJQiMUQKRBkEKbQsAPZaEXQcslSYKmjMyAAdXj34ACkNEiUgDA5t+PAQHn6Ogjkuzry2DNwhuIQAh+QQFBwAQACwAAAAAGAAYAAAFnCAkjiS0LGVaBgBJEGSguo8zCsK4CPIsMg+ECCcKEH0ix6MwhJl4KiOp8UCdmrEbo6EoHpxF8A6aBBZ6vhf5dmAkkGr0CoWs21WGQ2FvsI9xC3l7B311fy93iWGKJQQOhHCAJQB6A3IqcWwJLU90i2FkUiMKlhBELEI6MwgDXRAGhQgAYD6tTqRFAJxpA6mvrqazSKJJhUWMpjlIIQA7') + center no-repeat; + } + + .pdfViewer .page .loadingIcon.notVisible { + background: none; + } + + .pdfViewer.enablePermissions .textLayer span { + -webkit-user-select: none !important; + -moz-user-select: none !important; + user-select: none !important; + cursor: not-allowed; + } + + .pdfPresentationMode .pdfViewer { + padding-bottom: 0; + } + + .pdfPresentationMode .spread { + margin: 0; + } + + .pdfPresentationMode .pdfViewer .page { + margin: 0 auto; + border: 2px solid transparent; + } + } diff --git a/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts new file mode 100644 index 000000000..f9e7eb005 --- /dev/null +++ b/src-ui/src/app/components/common/pdf-viewer/pdf-viewer.component.ts @@ -0,0 +1,599 @@ +/** + * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/pdf-viewer.component.ts + * Created by vadimdez on 21/06/16. + */ +import { + Component, + Input, + Output, + ElementRef, + EventEmitter, + OnChanges, + SimpleChanges, + OnInit, + OnDestroy, + ViewChild, + AfterViewChecked, + NgZone, +} from '@angular/core' +import { from, fromEvent, Subject } from 'rxjs' +import { debounceTime, filter, takeUntil } from 'rxjs/operators' +import * as PDFJS from 'pdfjs-dist' +import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer' + +import { createEventBus } from './utils/event-bus-utils' + +import type { + PDFSource, + PDFPageProxy, + PDFProgressData, + PDFDocumentProxy, + PDFDocumentLoadingTask, + PDFViewerOptions, + ZoomScale, +} from './typings' +import { PDFSinglePageViewer } from 'pdfjs-dist/web/pdf_viewer' + +PDFJS['verbosity'] = PDFJS.VerbosityLevel.ERRORS + +// Yea this is a straight hack +declare global { + interface WeakKeyTypes { + symbol: Object + } + + type WeakKey = WeakKeyTypes[keyof WeakKeyTypes] +} + +export enum RenderTextMode { + DISABLED, + ENABLED, + ENHANCED, +} + +@Component({ + selector: 'pngx-pdf-viewer', + templateUrl: './pdf-viewer.component.html', + styleUrls: ['./pdf-viewer.component.scss'], +}) +export class PdfViewerComponent + implements OnChanges, OnInit, OnDestroy, AfterViewChecked +{ + static CSS_UNITS = 96.0 / 72.0 + static BORDER_WIDTH = 9 + + @ViewChild('pdfViewerContainer') + pdfViewerContainer!: ElementRef + + public eventBus!: PDFJSViewer.EventBus + public pdfLinkService!: PDFJSViewer.PDFLinkService + public pdfViewer!: PDFJSViewer.PDFViewer | PDFSinglePageViewer + + private isVisible = false + + private _cMapsUrl = + typeof PDFJS !== 'undefined' + ? `https://unpkg.com/pdfjs-dist@${(PDFJS as any).version}/cmaps/` + : null + private _imageResourcesPath = + typeof PDFJS !== 'undefined' + ? `https://unpkg.com/pdfjs-dist@${(PDFJS as any).version}/web/images/` + : undefined + private _renderText = true + private _renderTextMode: RenderTextMode = RenderTextMode.ENABLED + private _stickToPage = false + private _originalSize = true + private _pdf: PDFDocumentProxy | undefined + private _page = 1 + private _zoom = 1 + private _zoomScale: ZoomScale = 'page-width' + private _rotation = 0 + private _showAll = true + private _canAutoResize = true + private _fitToPage = false + private _externalLinkTarget = 'blank' + private _showBorders = false + private lastLoaded!: string | Uint8Array | PDFSource | null + private _latestScrolledPage!: number + + private resizeTimeout: number | null = null + private pageScrollTimeout: number | null = null + private isInitialized = false + private loadingTask?: PDFDocumentLoadingTask | null + private destroy$ = new Subject() + + @Output('after-load-complete') afterLoadComplete = + new EventEmitter() + @Output('page-rendered') pageRendered = new EventEmitter() + @Output('pages-initialized') pageInitialized = new EventEmitter() + @Output('text-layer-rendered') textLayerRendered = + new EventEmitter() + @Output('error') onError = new EventEmitter() + @Output('on-progress') onProgress = new EventEmitter() + @Output() pageChange: EventEmitter = new EventEmitter(true) + @Input() src?: string | Uint8Array | PDFSource + + @Input('c-maps-url') + set cMapsUrl(cMapsUrl: string) { + this._cMapsUrl = cMapsUrl + } + + @Input('page') + set page(_page: number | string | any) { + _page = parseInt(_page, 10) || 1 + const originalPage = _page + + if (this._pdf) { + _page = this.getValidPageNumber(_page) + } + + this._page = _page + if (originalPage !== _page) { + this.pageChange.emit(_page) + } + } + + @Input('render-text') + set renderText(renderText: boolean) { + this._renderText = renderText + } + + @Input('render-text-mode') + set renderTextMode(renderTextMode: RenderTextMode) { + this._renderTextMode = renderTextMode + } + + @Input('original-size') + set originalSize(originalSize: boolean) { + this._originalSize = originalSize + } + + @Input('show-all') + set showAll(value: boolean) { + this._showAll = value + } + + @Input('stick-to-page') + set stickToPage(value: boolean) { + this._stickToPage = value + } + + @Input('zoom') + set zoom(value: number) { + if (value <= 0) { + return + } + + this._zoom = value + } + + get zoom() { + return this._zoom + } + + @Input('zoom-scale') + set zoomScale(value: ZoomScale) { + this._zoomScale = value + } + + get zoomScale() { + return this._zoomScale + } + + @Input('rotation') + set rotation(value: number) { + if (!(typeof value === 'number' && value % 90 === 0)) { + console.warn('Invalid pages rotation angle.') + return + } + + this._rotation = value + } + + @Input('external-link-target') + set externalLinkTarget(value: string) { + this._externalLinkTarget = value + } + + @Input('autoresize') + set autoresize(value: boolean) { + this._canAutoResize = Boolean(value) + } + + @Input('fit-to-page') + set fitToPage(value: boolean) { + this._fitToPage = Boolean(value) + } + + @Input('show-borders') + set showBorders(value: boolean) { + this._showBorders = Boolean(value) + } + + static getLinkTarget(type: string) { + switch (type) { + case 'blank': + return (PDFJSViewer as any).LinkTarget.BLANK + case 'none': + return (PDFJSViewer as any).LinkTarget.NONE + case 'self': + return (PDFJSViewer as any).LinkTarget.SELF + case 'parent': + return (PDFJSViewer as any).LinkTarget.PARENT + case 'top': + return (PDFJSViewer as any).LinkTarget.TOP + } + + return null + } + + constructor( + private element: ElementRef, + private ngZone: NgZone + ) { + PDFJS.GlobalWorkerOptions['workerSrc'] = '/assets/js/pdf.worker.min.js' + } + + ngAfterViewChecked(): void { + if (this.isInitialized) { + return + } + + const offset = this.pdfViewerContainer.nativeElement.offsetParent + + if (this.isVisible === true && offset == null) { + this.isVisible = false + return + } + + if (this.isVisible === false && offset != null) { + this.isVisible = true + + setTimeout(() => { + this.initialize() + this.ngOnChanges({ src: this.src } as any) + }) + } + } + + ngOnInit() { + this.initialize() + this.setupResizeListener() + } + + ngOnDestroy() { + this.clear() + this.destroy$.next() + this.loadingTask = null + } + + ngOnChanges(changes: SimpleChanges) { + if (!this.isVisible) { + return + } + + if ('src' in changes) { + this.loadPDF() + } else if (this._pdf) { + if ('renderText' in changes || 'showAll' in changes) { + this.setupViewer() + this.resetPdfDocument() + } + if ('page' in changes) { + const { page } = changes + if (page.currentValue === this._latestScrolledPage) { + return + } + + // New form of page changing: The viewer will now jump to the specified page when it is changed. + // This behavior is introduced by using the PDFSinglePageViewer + this.pdfViewer.scrollPageIntoView({ pageNumber: this._page }) + } + + this.update() + } + } + + public updateSize() { + from( + this._pdf!.getPage( + this.pdfViewer.currentPageNumber + ) as unknown as Promise + ) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (page: PDFPageProxy) => { + const rotation = this._rotation + page.rotate + const viewportWidth = + (page as any).getViewport({ + scale: this._zoom, + rotation, + }).width * PdfViewerComponent.CSS_UNITS + let scale = this._zoom + let stickToPage = true + + // Scale the document when it shouldn't be in original size or doesn't fit into the viewport + if ( + !this._originalSize || + (this._fitToPage && + viewportWidth > this.pdfViewerContainer.nativeElement.clientWidth) + ) { + const viewPort = (page as any).getViewport({ scale: 1, rotation }) + scale = this.getScale(viewPort.width, viewPort.height) + stickToPage = !this._stickToPage + } + + setTimeout(() => { + this.pdfViewer.currentScale = scale + }) + }, + }) + } + + public clear() { + if (this.loadingTask && !this.loadingTask.destroyed) { + this.loadingTask.destroy() + } + + if (this._pdf) { + this._latestScrolledPage = 0 + this._pdf.destroy() + this._pdf = undefined + } + } + + private getPDFLinkServiceConfig() { + const linkTarget = PdfViewerComponent.getLinkTarget( + this._externalLinkTarget + ) + + if (linkTarget) { + return { externalLinkTarget: linkTarget } + } + + return {} + } + + private initEventBus() { + this.eventBus = createEventBus(PDFJSViewer, this.destroy$) + + fromEvent(this.eventBus, 'pagerendered') + .pipe(takeUntil(this.destroy$)) + .subscribe((event) => { + this.pageRendered.emit(event) + }) + + fromEvent(this.eventBus, 'pagesinit') + .pipe(takeUntil(this.destroy$)) + .subscribe((event) => { + this.pageInitialized.emit(event) + }) + + fromEvent(this.eventBus, 'pagechanging') + .pipe(takeUntil(this.destroy$)) + .subscribe(({ pageNumber }: any) => { + if (this.pageScrollTimeout) { + clearTimeout(this.pageScrollTimeout) + } + + this.pageScrollTimeout = window.setTimeout(() => { + this._latestScrolledPage = pageNumber + this.pageChange.emit(pageNumber) + }, 100) + }) + + fromEvent(this.eventBus, 'textlayerrendered') + .pipe(takeUntil(this.destroy$)) + .subscribe((event) => { + this.textLayerRendered.emit(event) + }) + } + + private initPDFServices() { + this.pdfLinkService = new PDFJSViewer.PDFLinkService({ + eventBus: this.eventBus, + ...this.getPDFLinkServiceConfig(), + }) + } + + private getPDFOptions(): PDFViewerOptions { + return { + eventBus: this.eventBus, + container: this.element.nativeElement.querySelector('div')!, + removePageBorders: !this._showBorders, + linkService: this.pdfLinkService, + textLayerMode: this._renderText + ? this._renderTextMode + : RenderTextMode.DISABLED, + imageResourcesPath: this._imageResourcesPath, + } + } + + private setupViewer() { + PDFJS['disableTextLayer'] = !this._renderText + + this.initPDFServices() + + if (this._showAll) { + this.pdfViewer = new PDFJSViewer.PDFViewer(this.getPDFOptions()) + } else { + this.pdfViewer = new PDFJSViewer.PDFSinglePageViewer(this.getPDFOptions()) + } + this.pdfLinkService.setViewer(this.pdfViewer) + + this.pdfViewer._currentPageNumber = this._page + } + + private getValidPageNumber(page: number): number { + if (page < 1) { + return 1 + } + + if (page > this._pdf!.numPages) { + return this._pdf!.numPages + } + + return page + } + + private getDocumentParams() { + const srcType = typeof this.src + + if (!this._cMapsUrl) { + return this.src + } + + const params: any = { + cMapUrl: this._cMapsUrl, + cMapPacked: true, + enableXfa: true, + } + + if (srcType === 'string') { + params.url = this.src + } else if (srcType === 'object') { + if ((this.src as any).byteLength !== undefined) { + params.data = this.src + } else { + Object.assign(params, this.src) + } + } + + return params + } + + private loadPDF() { + if (!this.src) { + return + } + + if (this.lastLoaded === this.src) { + this.update() + return + } + + this.clear() + + this.setupViewer() + + this.loadingTask = PDFJS.getDocument(this.getDocumentParams()) + + this.loadingTask!.onProgress = (progressData: PDFProgressData) => { + this.onProgress.emit(progressData) + } + + const src = this.src + + from(this.loadingTask!.promise as Promise) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (pdf) => { + this._pdf = pdf + this.lastLoaded = src + + this.afterLoadComplete.emit(pdf) + this.resetPdfDocument() + + this.update() + }, + error: (error) => { + this.lastLoaded = null + this.onError.emit(error) + }, + }) + } + + private update() { + this.page = this._page + + this.render() + } + + private render() { + this._page = this.getValidPageNumber(this._page) + + if ( + this._rotation !== 0 || + this.pdfViewer.pagesRotation !== this._rotation + ) { + setTimeout(() => { + this.pdfViewer.pagesRotation = this._rotation + }) + } + + if (this._stickToPage) { + setTimeout(() => { + this.pdfViewer.currentPageNumber = this._page + }) + } + + this.updateSize() + } + + private getScale(viewportWidth: number, viewportHeight: number) { + const borderSize = this._showBorders + ? 2 * PdfViewerComponent.BORDER_WIDTH + : 0 + const pdfContainerWidth = + this.pdfViewerContainer.nativeElement.clientWidth - borderSize + const pdfContainerHeight = + this.pdfViewerContainer.nativeElement.clientHeight - borderSize + + if ( + pdfContainerHeight === 0 || + viewportHeight === 0 || + pdfContainerWidth === 0 || + viewportWidth === 0 + ) { + return 1 + } + + let ratio = 1 + switch (this._zoomScale) { + case 'page-fit': + ratio = Math.min( + pdfContainerHeight / viewportHeight, + pdfContainerWidth / viewportWidth + ) + break + case 'page-height': + ratio = pdfContainerHeight / viewportHeight + break + case 'page-width': + default: + ratio = pdfContainerWidth / viewportWidth + break + } + + return (this._zoom * ratio) / PdfViewerComponent.CSS_UNITS + } + + private resetPdfDocument() { + this.pdfLinkService.setDocument(this._pdf, null) + this.pdfViewer.setDocument(this._pdf!) + } + + private initialize(): void { + if (!this.isVisible) { + return + } + + this.isInitialized = true + this.initEventBus() + this.setupViewer() + } + + private setupResizeListener(): void { + this.ngZone.runOutsideAngular(() => { + fromEvent(window, 'resize') + .pipe( + debounceTime(100), + filter(() => this._canAutoResize && !!this._pdf), + takeUntil(this.destroy$) + ) + .subscribe(() => { + this.updateSize() + }) + }) + } +} diff --git a/src-ui/src/app/components/common/pdf-viewer/typings.ts b/src-ui/src/app/components/common/pdf-viewer/typings.ts new file mode 100644 index 000000000..8dad45f30 --- /dev/null +++ b/src-ui/src/app/components/common/pdf-viewer/typings.ts @@ -0,0 +1,17 @@ +export type PDFPageProxy = + import('pdfjs-dist/types/src/display/api').PDFPageProxy +export type PDFSource = + import('pdfjs-dist/types/src/display/api').DocumentInitParameters +export type PDFDocumentProxy = + import('pdfjs-dist/types/src/display/api').PDFDocumentProxy +export type PDFDocumentLoadingTask = + import('pdfjs-dist/types/src/display/api').PDFDocumentLoadingTask +export type PDFViewerOptions = + import('pdfjs-dist/types/web/pdf_viewer').PDFViewerOptions + +export interface PDFProgressData { + loaded: number + total: number +} + +export type ZoomScale = 'page-height' | 'page-fit' | 'page-width' diff --git a/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts b/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts new file mode 100644 index 000000000..8ae501169 --- /dev/null +++ b/src-ui/src/app/components/common/pdf-viewer/utils/event-bus-utils.ts @@ -0,0 +1,182 @@ +/** + * This file is taken and modified from https://github.com/VadimDez/ng2-pdf-viewer/blob/10.0.0/src/app/pdf-viewer/utils/event-bus-utils.ts + * Created by vadimdez on 21/06/16. + */ +import { fromEvent, Subject } from 'rxjs' +import { takeUntil } from 'rxjs/operators' + +import type { EventBus } from 'pdfjs-dist/web/pdf_viewer' + +// interface EventBus { +// on(eventName: string, listener: Function): void; +// off(eventName: string, listener: Function): void; +// _listeners: any; +// dispatch(eventName: string, data: Object): void; +// _on(eventName: any, listener: any, options?: null): void; +// _off(eventName: any, listener: any, options?: null): void; +// } + +export function createEventBus(pdfJsViewer: any, destroy$: Subject) { + const globalEventBus: EventBus = new pdfJsViewer.EventBus() + attachDOMEventsToEventBus(globalEventBus, destroy$) + return globalEventBus +} + +function attachDOMEventsToEventBus( + eventBus: EventBus, + destroy$: Subject +): void { + fromEvent(eventBus, 'documentload') + .pipe(takeUntil(destroy$)) + .subscribe(() => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('documentload', true, true, {}) + window.dispatchEvent(event) + }) + + fromEvent(eventBus, 'pagerendered') + .pipe(takeUntil(destroy$)) + .subscribe(({ pageNumber, cssTransform, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('pagerendered', true, true, { + pageNumber, + cssTransform, + }) + source.div.dispatchEvent(event) + }) + + fromEvent(eventBus, 'textlayerrendered') + .pipe(takeUntil(destroy$)) + .subscribe(({ pageNumber, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('textlayerrendered', true, true, { pageNumber }) + source.textLayerDiv?.dispatchEvent(event) + }) + + fromEvent(eventBus, 'pagechanging') + .pipe(takeUntil(destroy$)) + .subscribe(({ pageNumber, source }: any) => { + const event = document.createEvent('UIEvents') as any + event.initEvent('pagechanging', true, true) + /* tslint:disable:no-string-literal */ + event['pageNumber'] = pageNumber + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'pagesinit') + .pipe(takeUntil(destroy$)) + .subscribe(({ source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('pagesinit', true, true, null) + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'pagesloaded') + .pipe(takeUntil(destroy$)) + .subscribe(({ pagesCount, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('pagesloaded', true, true, { pagesCount }) + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'scalechange') + .pipe(takeUntil(destroy$)) + .subscribe(({ scale, presetValue, source }: any) => { + const event = document.createEvent('UIEvents') as any + event.initEvent('scalechange', true, true) + /* tslint:disable:no-string-literal */ + event['scale'] = scale + /* tslint:disable:no-string-literal */ + event['presetValue'] = presetValue + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'updateviewarea') + .pipe(takeUntil(destroy$)) + .subscribe(({ location, source }: any) => { + const event = document.createEvent('UIEvents') as any + event.initEvent('updateviewarea', true, true) + event['location'] = location + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'find') + .pipe(takeUntil(destroy$)) + .subscribe( + ({ + source, + type, + query, + phraseSearch, + caseSensitive, + highlightAll, + findPrevious, + }: any) => { + if (source === window) { + return // event comes from FirefoxCom, no need to replicate + } + const event = document.createEvent('CustomEvent') + event.initCustomEvent('find' + type, true, true, { + query, + phraseSearch, + caseSensitive, + highlightAll, + findPrevious, + }) + window.dispatchEvent(event) + } + ) + + fromEvent(eventBus, 'attachmentsloaded') + .pipe(takeUntil(destroy$)) + .subscribe(({ attachmentsCount, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('attachmentsloaded', true, true, { + attachmentsCount, + }) + source.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'sidebarviewchanged') + .pipe(takeUntil(destroy$)) + .subscribe(({ view, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('sidebarviewchanged', true, true, { view }) + source.outerContainer.dispatchEvent(event) + }) + + fromEvent(eventBus, 'pagemode') + .pipe(takeUntil(destroy$)) + .subscribe(({ mode, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('pagemode', true, true, { mode }) + source.pdfViewer.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'namedaction') + .pipe(takeUntil(destroy$)) + .subscribe(({ action, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('namedaction', true, true, { action }) + source.pdfViewer.container.dispatchEvent(event) + }) + + fromEvent(eventBus, 'presentationmodechanged') + .pipe(takeUntil(destroy$)) + .subscribe(({ active, switchInProgress }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('presentationmodechanged', true, true, { + active, + switchInProgress, + }) + window.dispatchEvent(event) + }) + + fromEvent(eventBus, 'outlineloaded') + .pipe(takeUntil(destroy$)) + .subscribe(({ outlineCount, source }: any) => { + const event = document.createEvent('CustomEvent') + event.initCustomEvent('outlineloaded', true, true, { outlineCount }) + source.container.dispatchEvent(event) + }) +} diff --git a/src-ui/src/app/components/document-detail/document-detail.component.html b/src-ui/src/app/components/document-detail/document-detail.component.html index 19ee955ae..8b50e6f2e 100644 --- a/src-ui/src/app/components/document-detail/document-detail.component.html +++ b/src-ui/src/app/components/document-detail/document-detail.component.html @@ -1,9 +1,20 @@ -
-
Page
- -
of {{previewNumPages}}
-
+ +
+
Page
+ +
of {{previewNumPages}}
+
+
+ + + +
+