diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9919efc92..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -open_collective: latex-workshop -custom: https://afdian.com/a/latex-workshop diff --git a/.github/workflows/texlive_on_linux.yml b/.github/workflows/texlive_on_linux.yml index ee65c2014..8aa7df721 100644 --- a/.github/workflows/texlive_on_linux.yml +++ b/.github/workflows/texlive_on_linux.yml @@ -1,7 +1,7 @@ name: TeX Live on Linux env: cache-version: v12 -on: [push, pull_request] +on: push permissions: contents: read diff --git a/.github/workflows/texlive_on_mac.yml b/.github/workflows/texlive_on_mac.yml index f1f013bec..c39d4ab55 100644 --- a/.github/workflows/texlive_on_mac.yml +++ b/.github/workflows/texlive_on_mac.yml @@ -1,14 +1,14 @@ name: TeX Live on macOS env: cache-version: v12 -on: [push, pull_request] +on: push permissions: contents: read jobs: macosx: - runs-on: macos-latest + runs-on: macos-13 timeout-minutes: 30 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/texlive_on_win.yml b/.github/workflows/texlive_on_win.yml index 268206836..529f51c26 100644 --- a/.github/workflows/texlive_on_win.yml +++ b/.github/workflows/texlive_on_win.yml @@ -1,7 +1,7 @@ name: TeX Live on Windows env: cache-version: v12 -on: [push, pull_request] +on: push permissions: contents: read diff --git a/CHANGELOG.md b/CHANGELOG.md index da76f2e41..a81f49d17 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Change Log +## [10.4.1] - 2024-09-20 + +### Fixed +- (#4397) When formatting a selection of `latex`, consider the leading spaces. +- (#4401) Tweaked macro suggestion order, prioritize `\(` and degrade starred macros. + +### Upgraded +- Upgrade to PDF.js 4.6.82 + +## [10.4.0] - 2024-09-18 + +### Added +- (#4379) Support `tex-fmt` as another LaTeX formatter. + - There are a few config item changes related to `latexindent`. + +### Fixed +- Macro suggestions filtered and sorted by both label and argument signature. +- (#4347) `--max-print-line` should be added to string when magic tex argument is present under MikTeX. +- (#4380) Wrong auto-completion after typing `\{`. + ## [10.3.2] - 2024-09-11 ### Fixed diff --git a/dev/editviewer.py b/dev/editviewer.py index de87e98f4..0070696a8 100644 --- a/dev/editviewer.py +++ b/dev/editviewer.py @@ -31,7 +31,7 @@ .replace('''this.removePageBorders = options.removePageBorders || false;''', '''this.removePageBorders = options.removePageBorders || true;''') \ .replace('''localStorage.setItem("pdfjs.history", databaseStr);''', '''// localStorage.setItem("pdfjs.history", databaseStr);''') \ .replace('''return localStorage.getItem("pdfjs.history");''', '''return // localStorage.getItem("pdfjs.history");''') \ - .replace('''this.setTitle(title);''', '''// this.setTitle(title);''') \ + .replace('''this.setTitle(title || url);''', '''// this.setTitle(title || url);''') \ .replace('''localStorage.setItem("pdfjs.preferences", JSON.stringify(prefObj));''', '''// localStorage.setItem("pdfjs.preferences", JSON.stringify(prefObj));''') \ .replace('''prefs: JSON.parse(localStorage.getItem("pdfjs.preferences"))''', '''prefs: undefined // JSON.parse(localStorage.getItem("pdfjs.preferences"))''') \ .replace('''(!event.shiftKey || window.chrome || window.opera)) {''', '''(!event.shiftKey || window.chrome || window.opera)) {\n if (window.parent !== window) {\n return;\n }''') \ diff --git a/dev/viewer/viewer.html.diff b/dev/viewer/viewer.html.diff index 2209f7a6d..8324be999 100644 --- a/dev/viewer/viewer.html.diff +++ b/dev/viewer/viewer.html.diff @@ -1,5 +1,5 @@ diff --git a/../../web/viewer.html b/../viewer/viewer.html -index badac98dc..bedfdd304 100644 +index 7c05f0ac5..4369c642c 100644 --- a/../../web/viewer.html +++ b/../viewer/viewer.html @@ -25,15 +25,17 @@ See https://github.com/adobe-type-tools/cmap-resources @@ -22,12 +22,12 @@ index badac98dc..bedfdd304 100644 -@@ -275,7 +277,7 @@ See https://github.com/adobe-type-tools/cmap-resources - -
+ - diff --git a/dev/viewer/viewer.mjs.diff b/dev/viewer/viewer.mjs.diff index 3c7d2631e..cf8fbf626 100644 --- a/dev/viewer/viewer.mjs.diff +++ b/dev/viewer/viewer.mjs.diff @@ -1,8 +1,8 @@ diff --git a/../../web/viewer.mjs b/../viewer/viewer.mjs -index da30c00c4..00c98d358 100644 +index f6fcf25fc..743ed5ce4 100644 --- a/../../web/viewer.mjs +++ b/../viewer/viewer.mjs -@@ -715,7 +715,7 @@ const defaultOptions = { +@@ -763,7 +763,7 @@ const defaultOptions = { kind: OptionKind.API }, cMapUrl: { @@ -11,7 +11,7 @@ index da30c00c4..00c98d358 100644 kind: OptionKind.API }, disableAutoFetch: { -@@ -763,7 +763,7 @@ const defaultOptions = { +@@ -815,7 +815,7 @@ const defaultOptions = { kind: OptionKind.API }, standardFontDataUrl: { @@ -19,8 +19,8 @@ index da30c00c4..00c98d358 100644 + value: "../standard_fonts/", kind: OptionKind.API }, - verbosity: { -@@ -775,7 +775,7 @@ const defaultOptions = { + useSystemFonts: { +@@ -832,7 +832,7 @@ const defaultOptions = { kind: OptionKind.WORKER }, workerSrc: { @@ -29,7 +29,7 @@ index da30c00c4..00c98d358 100644 kind: OptionKind.WORKER } }; -@@ -785,7 +785,7 @@ const defaultOptions = { +@@ -842,7 +842,7 @@ const defaultOptions = { kind: OptionKind.VIEWER }; defaultOptions.sandboxBundleSrc = { @@ -38,7 +38,7 @@ index da30c00c4..00c98d358 100644 kind: OptionKind.VIEWER }; defaultOptions.viewerCssTheme = { -@@ -2592,7 +2592,7 @@ class Localization { +@@ -2619,7 +2619,7 @@ class Localization { if (typeof console !== "undefined") { const locale = bundle.locales[0]; const ids = Array.from(missingIds).join(", "); @@ -47,7 +47,7 @@ index da30c00c4..00c98d358 100644 } } if (!hasAtLeastOneBundle && typeof console !== "undefined") { -@@ -3054,11 +3054,11 @@ class GenericScripting { +@@ -3099,11 +3099,11 @@ class GenericScripting { function initCom(app) {} class Preferences extends BasePreferences { async _writeToStorage(prefObj) { @@ -61,7 +61,7 @@ index da30c00c4..00c98d358 100644 }; } } -@@ -4475,7 +4475,7 @@ const FindState = { +@@ -5138,7 +5138,7 @@ const FindState = { PENDING: 3 }; const FIND_TIMEOUT = 250; @@ -70,7 +70,7 @@ index da30c00c4..00c98d358 100644 const MATCH_SCROLL_OFFSET_LEFT = -400; const CHARACTERS_TO_NORMALIZE = { "\u2010": "-", -@@ -6840,6 +6840,9 @@ function renderProgress(index, total) { +@@ -7518,6 +7518,9 @@ function renderProgress(index, total) { } window.addEventListener("keydown", function (event) { if (event.keyCode === 80 && (event.ctrlKey || event.metaKey) && !event.altKey && (!event.shiftKey || window.chrome || window.opera)) { @@ -80,7 +80,7 @@ index da30c00c4..00c98d358 100644 window.print(); event.preventDefault(); event.stopImmediatePropagation(); -@@ -7425,7 +7428,7 @@ class PDFSidebar { +@@ -8102,7 +8105,7 @@ class PDFSidebar { this.#dispatchEvent(); return; } @@ -89,8 +89,8 @@ index da30c00c4..00c98d358 100644 if (!this.isInitialEventDispatched) { this.#dispatchEvent(); } -@@ -9975,7 +9978,7 @@ class PDFViewer { - this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true; +@@ -10714,7 +10717,7 @@ class PDFViewer { + this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; - this.removePageBorders = options.removePageBorders || false; @@ -98,7 +98,7 @@ index da30c00c4..00c98d358 100644 this.maxCanvasPixels = options.maxCanvasPixels; this.l10n = options.l10n; this.l10n ||= new genericl10n_GenericL10n(); -@@ -10275,6 +10278,7 @@ class PDFViewer { +@@ -11028,6 +11031,7 @@ class PDFViewer { } } setDocument(pdfDocument) { @@ -106,7 +106,7 @@ index da30c00c4..00c98d358 100644 if (this.pdfDocument) { this.eventBus.dispatch("pagesdestroy", { source: this -@@ -10343,7 +10347,7 @@ class PDFViewer { +@@ -11094,7 +11098,7 @@ class PDFViewer { eventBus._on("pagerendered", onAfterDraw, { signal }); @@ -115,7 +115,7 @@ index da30c00c4..00c98d358 100644 if (pdfDocument !== this.pdfDocument) { return; } -@@ -10377,7 +10381,7 @@ class PDFViewer { +@@ -11131,7 +11135,7 @@ class PDFViewer { } } const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : viewer; @@ -124,7 +124,7 @@ index da30c00c4..00c98d358 100644 const viewport = firstPdfPage.getViewport({ scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS }); -@@ -10406,6 +10410,7 @@ class PDFViewer { +@@ -11161,6 +11165,7 @@ class PDFViewer { this._pages.push(pageView); } this._pages[0]?.setPdfPage(firstPdfPage); @@ -132,7 +132,7 @@ index da30c00c4..00c98d358 100644 if (this._scrollMode === ScrollMode.PAGE) { this.#ensurePageViewVisible(); } else if (this._spreadMode !== SpreadMode.NONE) { -@@ -10498,7 +10503,7 @@ class PDFViewer { +@@ -11253,7 +11258,7 @@ class PDFViewer { this._pages = []; this._currentPageNumber = 1; this._currentScale = UNKNOWN_SCALE; @@ -141,7 +141,7 @@ index da30c00c4..00c98d358 100644 this._pageLabels = null; this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE); this._location = null; -@@ -10517,7 +10522,7 @@ class PDFViewer { +@@ -11272,7 +11277,7 @@ class PDFViewer { }; this.#eventAbortController?.abort(); this.#eventAbortController = null; @@ -150,7 +150,7 @@ index da30c00c4..00c98d358 100644 this._updateScrollMode(); this.viewer.removeAttribute("lang"); this.#hiddenCopyElement?.remove(); -@@ -10697,8 +10702,8 @@ class PDFViewer { +@@ -11453,8 +11458,8 @@ class PDFViewer { } else if (this._scrollMode === ScrollMode.HORIZONTAL) { [hPadding, vPadding] = [vPadding, hPadding]; } @@ -161,7 +161,7 @@ index da30c00c4..00c98d358 100644 switch (value) { case "page-actual": scale = 1; -@@ -11946,10 +11951,10 @@ class ViewHistory { +@@ -12793,10 +12798,10 @@ class ViewHistory { } async _writeToStorage() { const databaseStr = JSON.stringify(this.database); @@ -174,16 +174,16 @@ index da30c00c4..00c98d358 100644 } async set(name, val) { await this._initializedPromise; -@@ -12524,7 +12529,7 @@ const PDFViewerApplication = { - title = url; - } +@@ -13381,7 +13386,7 @@ const PDFViewerApplication = { + title = decodeURIComponent(getFilenameFromUrl(url)); + } catch {} } -- this.setTitle(title); -+ // this.setTitle(title); +- this.setTitle(title || url); ++ // this.setTitle(title || url); }, setTitle(title = this._title) { this._title = title; -@@ -12971,7 +12976,7 @@ const PDFViewerApplication = { +@@ -13805,7 +13810,7 @@ const PDFViewerApplication = { this.metadata = metadata; this._contentDispositionFilename ??= contentDispositionFilename; this._contentLength ??= contentLength; @@ -192,7 +192,7 @@ index da30c00c4..00c98d358 100644 let pdfTitle = info.Title; const metadataTitle = metadata?.get("dc:title"); if (metadataTitle) { -@@ -13102,9 +13107,9 @@ const PDFViewerApplication = { +@@ -13936,9 +13941,9 @@ const PDFViewerApplication = { this.pdfSidebar?.setInitialView(sidebarView); setViewerModes(scrollMode, spreadMode); if (this.initialBookmark) { @@ -204,7 +204,7 @@ index da30c00c4..00c98d358 100644 this.initialBookmark = null; } else if (storedHash) { setRotation(rotation); -@@ -14419,7 +14424,7 @@ function webViewerLoad() { +@@ -15199,7 +15204,7 @@ function webViewerLoad() { try { parent.document.dispatchEvent(event); } catch (ex) { @@ -213,7 +213,7 @@ index da30c00c4..00c98d358 100644 document.dispatchEvent(event); } PDFViewerApplication.run(config); -@@ -14436,4 +14441,3 @@ var __webpack_exports__PDFViewerApplicationConstants = __webpack_exports__.PDFVi +@@ -15216,4 +15221,3 @@ var __webpack_exports__PDFViewerApplicationConstants = __webpack_exports__.PDFVi var __webpack_exports__PDFViewerApplicationOptions = __webpack_exports__.PDFViewerApplicationOptions; export { __webpack_exports__PDFViewerApplication as PDFViewerApplication, __webpack_exports__PDFViewerApplicationConstants as PDFViewerApplicationConstants, __webpack_exports__PDFViewerApplicationOptions as PDFViewerApplicationOptions }; diff --git a/package-lock.json b/package-lock.json index bce508739..6ef756679 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,53 +1,55 @@ { "name": "latex-workshop", - "version": "10.3.2", + "version": "10.4.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "latex-workshop", - "version": "10.3.2", + "version": "10.4.1", "license": "MIT", "dependencies": { - "cross-spawn": "7.0.3", - "glob": "11.0.0", - "iconv-lite": "0.6.3", - "latex-utensils": "6.2.0", - "mathjax-full": "3.2.2", - "micromatch": "4.0.8", - "pdfjs-dist": "4.3.136", - "tmp": "0.2.3", - "workerpool": "9.1.3", - "ws": "8.18.0" + "cross-spawn": "^7.0.3", + "glob": "^11.0.0", + "iconv-lite": "^0.6.3", + "latex-utensils": "^6.2.0", + "mathjax-full": "^3.2.2", + "micromatch": "^4.0.8", + "pdfjs-dist": "4.6.82", + "tmp": "^0.2.3", + "workerpool": "^9.1.3", + "ws": "^8.18.0" }, "devDependencies": { - "@stylistic/eslint-plugin": "2.7.2", - "@types/cross-spawn": "6.0.6", - "@types/glob": "8.1.0", - "@types/micromatch": "4.0.9", - "@types/mocha": "10.0.7", - "@types/node": "18.15.3", - "@types/sinon": "17.0.3", - "@types/tmp": "0.2.6", - "@types/vscode": "1.88.0", - "@types/ws": "8.5.12", - "@typescript-eslint/eslint-plugin": "8.4.0", - "@typescript-eslint/parser": "8.4.0", + "@stylistic/eslint-plugin": "^2.8.0", + "@types/cross-spawn": "^6.0.6", + "@types/glob": "^8.1.0", + "@types/micromatch": "^4.0.9", + "@types/mocha": "^10.0.8", + "@types/node": "^18.15.3", + "@types/node-fetch": "^2.6.11", + "@types/sinon": "^17.0.3", + "@types/tmp": "^0.2.6", + "@types/vscode": "^1.88.0", + "@types/ws": "^8.5.12", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "@unified-latex/unified-latex-types": "1.8.0", "@unified-latex/unified-latex-util-arguments": "1.8.0", "@unified-latex/unified-latex-util-parse": "1.8.0", "@unified-latex/unified-latex-util-to-string": "1.8.0", - "@vscode/test-electron": "2.4.1", - "@vscode/vsce": "3.1.0", - "c8": "10.1.2", - "esbuild": "0.23.1", - "eslint": "9.9.1", - "mocha": "10.7.3", - "rimraf": "6.0.1", - "sinon": "18.0.0", - "textmate-bailout": "1.1.0", - "typescript": "5.5.4", - "vscode-extend-language": "0.2.2" + "@vscode/test-electron": "^2.4.1", + "@vscode/vsce": "^3.1.0", + "c8": "^10.1.2", + "esbuild": "^0.23.1", + "eslint": "^9.10.0", + "mocha": "^10.7.3", + "node-fetch": "^2.7.0", + "rimraf": "^6.0.1", + "sinon": "^19.0.2", + "textmate-bailout": "^1.1.0", + "typescript": "^5.6.2", + "vscode-extend-language": "^0.2.2" }, "engines": { "vscode": "^1.88.0" @@ -774,9 +776,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.9.1.tgz", - "integrity": "sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", "dev": true, "license": "MIT", "engines": { @@ -793,6 +795,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1030,54 +1045,58 @@ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", - "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", + "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^2.0.0", + "@sinonjs/commons": "^3.0.1", "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "license": "MIT", + "engines": { + "node": ">=4" } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" }, "node_modules/@stylistic/eslint-plugin": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.7.2.tgz", - "integrity": "sha512-3DVLU5HEuk2pQoBmXJlzvrxbKNpu2mJ0SRqz5O/CJjyNCr12ZiPcYMEtuArTyPOk5i7bsAU44nywh1rGfe3gKQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.8.0.tgz", + "integrity": "sha512-Ufvk7hP+bf+pD35R/QfunF793XlSRIC7USr3/EdgduK9j13i2JjmsM0LUz3/foS+jDYp2fzyWZA9N44CPur0Ow==", "dev": true, "license": "MIT", "dependencies": { - "@types/eslint": "^9.6.1", - "@typescript-eslint/utils": "^8.3.0", + "@typescript-eslint/utils": "^8.4.0", "eslint-visitor-keys": "^4.0.0", "espree": "^10.1.0", "estraverse": "^5.3.0", @@ -1140,24 +1159,6 @@ "@types/node": "*" } }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/form-data": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", @@ -1183,13 +1184,6 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/micromatch": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.9.tgz", @@ -1207,9 +1201,9 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", - "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.8.tgz", + "integrity": "sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==", "dev": true, "license": "MIT" }, @@ -1219,6 +1213,32 @@ "integrity": "sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==", "dev": true }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/node-fetch/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/@types/qs": { "version": "6.9.10", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", @@ -1269,17 +1289,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.4.0.tgz", - "integrity": "sha512-rg8LGdv7ri3oAlenMACk9e+AR4wUV0yrrG+XKsGKOK0EVgeEDqurkXMPILG2836fW4ibokTB5v4b6Z9+GYQDEw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.6.0.tgz", + "integrity": "sha512-UOaz/wFowmoh2G6Mr9gw60B1mm0MzUtm6Ic8G2yM1Le6gyj5Loi/N+O5mocugRGY+8OeeKmkMmbxNqUCq3B4Sg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/type-utils": "8.4.0", - "@typescript-eslint/utils": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/type-utils": "8.6.0", + "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1303,16 +1323,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.4.0.tgz", - "integrity": "sha512-NHgWmKSgJk5K9N16GIhQ4jSobBoJwrmURaLErad0qlLjrpP5bECYg+wxVTGlGZmJbU03jj/dfnb6V9bw+5icsA==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.6.0.tgz", + "integrity": "sha512-eQcbCuA2Vmw45iGfcyG4y6rS7BhWfz9MQuk409WD47qMM+bKCGQWXxvoOs1DUp+T7UBMTtRTVT+kXr7Sh4O9Ow==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4" }, "engines": { @@ -1332,14 +1352,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.4.0.tgz", - "integrity": "sha512-n2jFxLeY0JmKfUqy3P70rs6vdoPjHK8P/w+zJcV3fk0b0BwRXC/zxRTEnAsgYT7MwdQDt/ZEbtdzdVC+hcpF0A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.6.0.tgz", + "integrity": "sha512-ZuoutoS5y9UOxKvpc/GkvF4cuEmpokda4wRg64JEia27wX+PysIE9q+lzDtlHHgblwUWwo5/Qn+/WyTUvDwBHw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0" + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1350,14 +1370,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.4.0.tgz", - "integrity": "sha512-pu2PAmNrl9KX6TtirVOrbLPLwDmASpZhK/XU7WvoKoCUkdtq9zF7qQ7gna0GBZFN0hci0vHaSusiL2WpsQk37A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.6.0.tgz", + "integrity": "sha512-dtePl4gsuenXVwC7dVNlb4mGDcKjDT/Ropsk4za/ouMBPplCLyznIaR+W65mvCvsyS97dymoBRrioEXI7k0XIg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.4.0", - "@typescript-eslint/utils": "8.4.0", + "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/utils": "8.6.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1375,9 +1395,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.4.0.tgz", - "integrity": "sha512-T1RB3KQdskh9t3v/qv7niK6P8yvn7ja1mS7QK7XfRVL6wtZ8/mFs/FHf4fKvTA0rKnqnYxl/uHFNbnEt0phgbw==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.6.0.tgz", + "integrity": "sha512-rojqFZGd4MQxw33SrOy09qIDS8WEldM8JWtKQLAjf/X5mGSeEFh5ixQlxssMNyPslVIk9yzWqXCsV2eFhYrYUw==", "dev": true, "license": "MIT", "engines": { @@ -1389,14 +1409,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.4.0.tgz", - "integrity": "sha512-kJ2OIP4dQw5gdI4uXsaxUZHRwWAGpREJ9Zq6D5L0BweyOrWsL6Sz0YcAZGWhvKnH7fm1J5YFE1JrQL0c9dd53A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.6.0.tgz", + "integrity": "sha512-MOVAzsKJIPIlLK239l5s06YXjNqpKTVhBVDnqUumQJja5+Y94V3+4VUFRA0G60y2jNnTVwRCkhyGQpavfsbq/g==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/visitor-keys": "8.4.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/visitor-keys": "8.6.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1444,16 +1464,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.4.0.tgz", - "integrity": "sha512-swULW8n1IKLjRAgciCkTCafyTHHfwVQFt8DovmaF69sKbOxTSFMmIZaSHjqO9i/RV0wIblaawhzvtva8Nmm7lQ==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.6.0.tgz", + "integrity": "sha512-eNp9cWnYf36NaOVjkEUznf6fEgVy1TWpE0o52e4wtojjBx7D1UV2WAWGzR+8Y5lVFtpMLPwNbC67T83DWSph4A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.4.0", - "@typescript-eslint/types": "8.4.0", - "@typescript-eslint/typescript-estree": "8.4.0" + "@typescript-eslint/scope-manager": "8.6.0", + "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/typescript-estree": "8.6.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1467,13 +1487,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.4.0.tgz", - "integrity": "sha512-zTQD6WLNTre1hj5wp09nBIDiOc2U5r/qmzo7wxPn4ZgAjHql09EofqhF9WF+fZHzL5aCyaIpPcT2hyxl73kr9A==", + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.6.0.tgz", + "integrity": "sha512-wapVFfZg9H0qOYh4grNVQiMklJGluQrOUiOhYRrQWhx7BY/+I1IYb8BczWNbbUpO+pqy0rDciv3lQH5E1bCLrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.4.0", + "@typescript-eslint/types": "8.6.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -3095,9 +3115,9 @@ } }, "node_modules/eslint": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.9.1.tgz", - "integrity": "sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg==", + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", "dev": true, "license": "MIT", "dependencies": { @@ -3105,7 +3125,8 @@ "@eslint-community/regexpp": "^4.11.0", "@eslint/config-array": "^0.18.0", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.9.1", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -3128,7 +3149,6 @@ "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", @@ -4399,7 +4419,8 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jwa": { "version": "2.0.0", @@ -4531,7 +4552,8 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.includes": { "version": "4.3.0", @@ -5159,16 +5181,17 @@ "dev": true }, "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^8.1.0" } }, "node_modules/node-abi": { @@ -5195,7 +5218,8 @@ "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, + "devOptional": true, + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -5614,30 +5638,36 @@ } }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==", - "dev": true + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.1.0.tgz", + "integrity": "sha512-Bqn3vc8CMHty6zuD+tG23s6v2kwxslHEhTj4eYaVKGIEB+YX/2wd0/rgXLFD9G9id9KCtbVy/3ZgmvZjpa0UdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } }, "node_modules/path2d": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.0.tgz", - "integrity": "sha512-KdPAykQX6kmLSOO6Jpu2KNcCED7CKjmaBNGGNuctOsG0hgYO1OdYQaan6cYXJiG0WmXOwZZPILPBimu5QAIw3A==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.1.tgz", + "integrity": "sha512-Fl2z/BHvkTNvkuBzYTpTuirHZg6wW9z8+4SND/3mDTEcYbbNKWAy21dz9D3ePNNwrrK8pqZO5vLPZ1hLF6T7XA==", + "license": "MIT", "optional": true, "engines": { "node": ">=6" } }, "node_modules/pdfjs-dist": { - "version": "4.3.136", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.3.136.tgz", - "integrity": "sha512-gzfnt1qc4yA+U46golPGYtU4WM2ssqP2MvFjKga8GEKOrEnzRPrA/9jogLLPYHiA3sGBPJ+p7BdAq+ytmw3jEg==", + "version": "4.6.82", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.6.82.tgz", + "integrity": "sha512-BUOryeRFwvbLe0lOU6NhkJNuVQUp06WxlJVVCsxdmJ4y5cU3O3s3/0DunVdK1PMm7v2MUw52qKYaidhDH1Z9+w==", + "license": "Apache-2.0", "engines": { "node": ">=18" }, "optionalDependencies": { "canvas": "^2.11.2", - "path2d": "^0.2.0" + "path2d": "^0.2.1" } }, "node_modules/pegjs-backtrace": { @@ -6173,23 +6203,34 @@ } }, "node_modules/sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "19.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-19.0.2.tgz", + "integrity": "sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/samsam": "^8.0.0", - "diff": "^5.2.0", - "nise": "^6.0.0", - "supports-color": "^7" + "@sinonjs/fake-timers": "^13.0.2", + "@sinonjs/samsam": "^8.0.1", + "diff": "^7.0.0", + "nise": "^6.1.1", + "supports-color": "^7.2.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/sinon" } }, + "node_modules/sinon/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/sinon/node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6702,7 +6743,8 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "optional": true + "devOptional": true, + "license": "MIT" }, "node_modules/trie-prefix-tree": { "version": "1.5.1", @@ -6779,6 +6821,7 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -6801,9 +6844,9 @@ "dev": true }, "node_modules/typescript": { - "version": "5.5.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", - "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -6947,13 +6990,15 @@ "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 + "devOptional": true, + "license": "BSD-2-Clause" }, "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, + "devOptional": true, + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" diff --git a/package.json b/package.json index e051fcac9..f3b00ecec 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "LaTeX Workshop", "description": "Boost LaTeX typesetting efficiency with preview, compile, autocomplete, colorize, and more.", "icon": "icons/icon.png", - "version": "10.3.2", + "version": "10.4.1", "publisher": "James-Yu", "license": "MIT", "homepage": "https://github.com/James-Yu/LaTeX-Workshop", @@ -1040,6 +1040,7 @@ "args": [ "--synctex", "--keep-logs", + "--print", "%DOC%.tex" ], "env": {} @@ -1503,7 +1504,7 @@ ], "markdownDescription": "PDF viewer used for [View on PDF] link on \\ref." }, - "latex-workshop.viewer.pdf.internal.port": { + "latex-workshop.view.pdf.internal.port": { "scope": "window", "type": "number", "default": 0, @@ -1519,7 +1520,7 @@ ], "markdownDescription": " Which keybinding to use for the internal viewer for reverse synctex. `ctrl`/`cmd` + click (default) or double click." }, - "latex-workshop.viewer.pdf.internal.keyboardEvent": { + "latex-workshop.view.pdf.internal.keyboardEvent": { "scope": "window", "type": "string", "default": "auto", @@ -1596,6 +1597,12 @@ "default": false, "markdownDescription": "Define if the hand tool is enabled by default in the PDF viewer." }, + "latex-workshop.view.pdf.invert": { + "scope": "window", + "type": "number", + "default": 0, + "markdownDescription": " Define the CSS invert filter level of the PDF viewer. This config can invert the color of PDF. Possible values are from 0 to 1." + }, "latex-workshop.view.pdf.invertMode.enabled": { "scope": "window", "type": "string", @@ -1614,12 +1621,6 @@ "Disable the invert filter" ] }, - "latex-workshop.view.pdf.invert": { - "scope": "window", - "type": "number", - "default": 0, - "markdownDescription": " Define the CSS invert filter level of the PDF viewer. This config can invert the color of PDF. Possible values are from 0 to 1." - }, "latex-workshop.view.pdf.invertMode.brightness": { "scope": "window", "type": "number", @@ -2634,44 +2635,46 @@ "update-grammar": "node ./dev/update-grammar.mjs" }, "dependencies": { - "cross-spawn": "7.0.3", - "glob": "11.0.0", - "iconv-lite": "0.6.3", - "latex-utensils": "6.2.0", - "mathjax-full": "3.2.2", - "micromatch": "4.0.8", - "pdfjs-dist": "4.3.136", - "tmp": "0.2.3", - "workerpool": "9.1.3", - "ws": "8.18.0" + "cross-spawn": "^7.0.3", + "glob": "^11.0.0", + "iconv-lite": "^0.6.3", + "latex-utensils": "^6.2.0", + "mathjax-full": "^3.2.2", + "micromatch": "^4.0.8", + "pdfjs-dist": "4.6.82", + "tmp": "^0.2.3", + "workerpool": "^9.1.3", + "ws": "^8.18.0" }, "devDependencies": { - "@stylistic/eslint-plugin": "2.7.2", - "@types/cross-spawn": "6.0.6", - "@types/glob": "8.1.0", - "@types/micromatch": "4.0.9", - "@types/mocha": "10.0.7", - "@types/node": "18.15.3", - "@types/sinon": "17.0.3", - "@types/tmp": "0.2.6", - "@types/vscode": "1.88.0", - "@types/ws": "8.5.12", - "@typescript-eslint/eslint-plugin": "8.4.0", - "@typescript-eslint/parser": "8.4.0", + "@stylistic/eslint-plugin": "^2.8.0", + "@types/cross-spawn": "^6.0.6", + "@types/glob": "^8.1.0", + "@types/micromatch": "^4.0.9", + "@types/mocha": "^10.0.8", + "@types/node": "^18.15.3", + "@types/node-fetch": "^2.6.11", + "@types/sinon": "^17.0.3", + "@types/tmp": "^0.2.6", + "@types/vscode": "^1.88.0", + "@types/ws": "^8.5.12", + "@typescript-eslint/eslint-plugin": "^8.6.0", + "@typescript-eslint/parser": "^8.6.0", "@unified-latex/unified-latex-types": "1.8.0", "@unified-latex/unified-latex-util-arguments": "1.8.0", "@unified-latex/unified-latex-util-parse": "1.8.0", "@unified-latex/unified-latex-util-to-string": "1.8.0", - "@vscode/test-electron": "2.4.1", - "@vscode/vsce": "3.1.0", - "c8": "10.1.2", - "esbuild": "0.23.1", - "eslint": "9.9.1", - "mocha": "10.7.3", - "rimraf": "6.0.1", - "sinon": "18.0.0", - "textmate-bailout": "1.1.0", - "typescript": "5.5.4", - "vscode-extend-language": "0.2.2" + "@vscode/test-electron": "^2.4.1", + "@vscode/vsce": "^3.1.0", + "c8": "^10.1.2", + "esbuild": "^0.23.1", + "eslint": "^9.10.0", + "mocha": "^10.7.3", + "node-fetch": "^2.7.0", + "rimraf": "^6.0.1", + "sinon": "^19.0.2", + "textmate-bailout": "^1.1.0", + "typescript": "^5.6.2", + "vscode-extend-language": "^0.2.2" } } diff --git a/src/compile/build.ts b/src/compile/build.ts index 91c60e748..00a8de7ef 100644 --- a/src/compile/build.ts +++ b/src/compile/build.ts @@ -1,6 +1,5 @@ import * as vscode from 'vscode' import * as path from 'path' -import * as cs from 'cross-spawn' import { pickRootPath } from '../utils/quick-pick' import { lw } from '../lw' import type { ProcessEnv, RecipeStep, Step } from '../types' @@ -143,6 +142,7 @@ async function build(skipSelection: boolean = false, rootFile: string | undefine */ async function buildLoop() { if (isBuilding) { + logger.log('Another build loop is already running.') return } @@ -156,16 +156,15 @@ async function buildLoop() { if (step === undefined) { break } - lw.compile.lastSteps.push(step) const env = spawnProcess(step) const success = await monitorProcess(step, env) - skipped = skipped && !(step.isExternal || !step.isSkipped) + skipped = skipped && !step.isExternal && step.isSkipped if (success && queue.isLastStep(step)) { await afterSuccessfulBuilt(step, skipped) } } isBuilding = false - setTimeout(() => lw.compile.compiledPDFWriting--, 1000) + setTimeout(() => lw.compile.compiledPDFWriting--, vscode.workspace.getConfiguration('latex-workshop').get('latex.watch.pdf.delay') as number) } /** @@ -208,9 +207,9 @@ function spawnProcess(step: Step): ProcessEnv { const args = step.args if (args && !step.name.endsWith(lw.constant.MAGIC_PROGRAM_ARGS_SUFFIX)) { // All optional arguments are given as a unique string (% !TeX options) if any, so we use {shell: true} - lw.compile.process = cs.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true}) + lw.compile.process = lw.external.spawn(`${step.command} ${args[0]}`, [], {cwd: path.dirname(step.rootFile), env, shell: true}) } else { - lw.compile.process = cs.spawn(step.command, args, {cwd: path.dirname(step.rootFile), env}) + lw.compile.process = lw.external.spawn(step.command, args ?? [], {cwd: path.dirname(step.rootFile), env}) } } else if (!step.isExternal) { let cwd = path.dirname(step.rootFile) @@ -218,10 +217,10 @@ function spawnProcess(step: Step): ProcessEnv { cwd = lw.root.dir.path } logger.log(`cwd: ${cwd}`) - lw.compile.process = cs.spawn(step.command, step.args, {cwd, env}) + lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd, env}) } else { logger.log(`cwd: ${step.cwd}`) - lw.compile.process = cs.spawn(step.command, step.args, {cwd: step.cwd}) + lw.compile.process = lw.external.spawn(step.command, step.args ?? [], {cwd: step.cwd}) } logger.log(`LaTeX build process spawned with PID ${lw.compile.process.pid}.`) return env @@ -245,13 +244,13 @@ async function monitorProcess(step: Step, env: ProcessEnv): Promise { return false } let stdout = '' - lw.compile.process.stdout.on('data', (msg: Buffer | string) => { + lw.compile.process.stdout?.on('data', (msg: Buffer | string) => { stdout += msg logger.logCompiler(msg.toString()) }) let stderr = '' - lw.compile.process.stderr.on('data', (msg: Buffer | string) => { + lw.compile.process.stderr?.on('data', (msg: Buffer | string) => { stderr += msg logger.logCompiler(msg.toString()) }) @@ -384,7 +383,7 @@ function handleNoRetryError(configuration: vscode.WorkspaceConfiguration, step: * shows an error message to the user and clears the BuildToolQueue. */ function handleExternalCommandError() { - logger.log(`Build returns with error on PID ${lw.compile.process?.pid}.`) + logger.log(`Build with external command returns error on PID ${lw.compile.process?.pid}.`) logger.refreshStatus('x', 'errorForeground', undefined, 'warning') void logger.showErrorMessageWithCompilerLogButton('Build terminated with error.') queue.clear() @@ -428,7 +427,7 @@ async function afterSuccessfulBuilt(lastStep: Step, skipped: boolean) { if (configuration.get('view.pdf.viewer') === 'external' && configuration.get('synctex.afterBuild.enabled')) { const pdfFile = vscode.Uri.parse("file://" + lw.file.getPdfPath(lastStep.rootFile)) logger.log('SyncTex after build invoked.') - lw.locate.synctex.toPDF(undefined, undefined, pdfFile) + lw.locate.synctex.toPDF(pdfFile) } if (['onSucceeded', 'onBuilt'].includes(configuration.get('latex.autoClean.run') as string)) { logger.log('Auto Clean invoked.') diff --git a/src/compile/index.ts b/src/compile/index.ts index 8bbd91fbb..b063dca8c 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -1,5 +1,4 @@ -import type { ChildProcessWithoutNullStreams } from 'child_process' -import type { Step } from '../types' +import type { ChildProcess } from 'child_process' import { build, autoBuild } from './build' import { terminate } from './terminate' @@ -7,9 +6,8 @@ export const compile = { build, autoBuild, terminate, - lastSteps: [] as Step[], lastAutoBuildTime: 0, compiledPDFPath: '', compiledPDFWriting: 0, - process: undefined as ChildProcessWithoutNullStreams | undefined + process: undefined as ChildProcess | undefined } diff --git a/src/compile/recipe.ts b/src/compile/recipe.ts index ccdd2df7e..b71b9a4d5 100644 --- a/src/compile/recipe.ts +++ b/src/compile/recipe.ts @@ -303,8 +303,8 @@ function findRecipe(rootFile: string, langId: string, recipeName?: string): Reci } } // Find default recipe of last used - if (recipe === undefined && defaultRecipeName === 'lastUsed' && recipes.find(candidate => candidate.name === state.prevRecipe?.name)) { - recipe = state.prevRecipe + if (recipe === undefined && defaultRecipeName === 'lastUsed') { + recipe = recipes.find(candidate => candidate.name === state.prevRecipe?.name) } // If still not found, fallback to 'first' if (recipe === undefined) { diff --git a/src/compile/terminate.ts b/src/compile/terminate.ts index 0d7af7f65..7527ba1ed 100644 --- a/src/compile/terminate.ts +++ b/src/compile/terminate.ts @@ -2,7 +2,7 @@ import * as cp from 'child_process' import { lw } from '../lw' import { queue } from './queue' -const logger = lw.log('Build', 'Recipe') +const logger = lw.log('Build', 'Terminate') /** * Terminate the current process of LaTeX building. This OS-specific function diff --git a/src/completion/completer/environment.ts b/src/completion/completer/environment.ts index 1b63e7a6b..ac3854dd9 100644 --- a/src/completion/completer/environment.ts +++ b/src/completion/completer/environment.ts @@ -283,7 +283,7 @@ function entryEnvToCompletion(item: EnvironmentInfo, type: EnvSnippetType): CmdE if (item.package) { suggestion.documentation += ` From package: ${item.package}.` } - suggestion.sortText = label.replace(/([A-Z])/g, '$10').toLowerCase() + suggestion.sortText = label.replace(/([a-z])/g, '$10').toLowerCase() if (type === EnvSnippetType.AsName) { return suggestion diff --git a/src/completion/completer/macro.ts b/src/completion/completer/macro.ts index ec1c023f7..195f9c66b 100644 --- a/src/completion/completer/macro.ts +++ b/src/completion/completer/macro.ts @@ -414,14 +414,19 @@ function entryCmdToCompletion(item: MacroRaw, packageName?: string, postAction?: } else { suggestion.insertText = item.name } - suggestion.filterText = item.detail ?? item.name + suggestion.filterText = item.name + (item.arg?.format ?? '') + (item.detail ?? '') suggestion.detail = item.detail ?? (item.arg?.snippet ? `\\${item.arg?.snippet?.replace(/\$\{\d+:([^$}]*)\}/g, '$1')}` : `\\${item.name}`) suggestion.documentation = item.doc ?? `Macro \\${item.name}${item.arg?.format ?? ''}.` if (packageName) { suggestion.documentation += ` From package: ${packageName}.` } suggestion.sortText = (item.name + (item.arg?.format ?? '')) - .replace(/([A-Z])/g, '$10').toLowerCase() + .replace(/([a-z])/g, '$10').toLowerCase() + .replaceAll('{', '0') + .replaceAll('[', '1') + .replace(/^(.+?)\(/g, '$12') // Skip \( + .replaceAll('|', '3') + .replaceAll('*', '9') if (postAction) { suggestion.command = { title: 'Post-Action', command: postAction } } else if (isTriggerSuggestNeeded(item.name)) { diff --git a/src/completion/completer/reference.ts b/src/completion/completer/reference.ts index a43cc5ba1..78a9277d9 100644 --- a/src/completion/completer/reference.ts +++ b/src/completion/completer/reference.ts @@ -15,6 +15,8 @@ export const reference = { setNumbersFromAuxFile } +lw.onConfigChange(['intellisense.label.command'], lw.parser.parse.reset) + const data = { suggestions: new Map(), prevIndexObj: new Map() diff --git a/src/core/commands.ts b/src/core/commands.ts index a18c2e8d9..1a4e9ab8e 100644 --- a/src/core/commands.ts +++ b/src/core/commands.ts @@ -99,7 +99,7 @@ export function synctex() { } else if (lw.root.file.path !== undefined) { pdfUri = vscode.Uri.parse("file://" + lw.file.getPdfPath(lw.root.file.path)) } - lw.locate.synctex.toPDF(undefined, undefined, pdfUri) + lw.locate.synctex.toPDF(pdfUri) } export function synctexonref(line: number, filePath: string) { @@ -176,7 +176,7 @@ export async function gotoSection(filePath: string, lineNumber: number) { if (vscode.window.activeTextEditor) { vscode.window.activeTextEditor.selection = new vscode.Selection(new vscode.Position(lineNumber, 0), new vscode.Position(lineNumber, 0)) if (vscode.workspace.getConfiguration('latex-workshop').get('view.outline.sync.viewer') as boolean) { - lw.locate.synctex.toPDF({ line: lineNumber, filePath: doc.fileName }) + lw.locate.synctex.toPDF(undefined, { line: lineNumber, filePath: doc.fileName }) } } } diff --git a/src/core/file.ts b/src/core/file.ts index 31edede2b..b0567946c 100644 --- a/src/core/file.ts +++ b/src/core/file.ts @@ -463,7 +463,8 @@ function getBibPath(bib: string, baseDir: string): string[] { * function will rethrow the caught error, making the calling code responsible * for handling the exception. * - * @param {string} filePath - The path to the file to be read. + * @param {string | vscode.Uri} filePath - The path / Uri to the file to be + * read. * @param {boolean} [raise=false] - A flag indicating whether to rethrow an * error if the file read operation fails. * @returns {Promise} - A promise that resolves to the file @@ -472,11 +473,17 @@ function getBibPath(bib: string, baseDir: string): string[] { * @throws Will throw an error if the file read operation fails and `raise` is * `true`. */ -async function read(filePath: string, raise: boolean = false): Promise { +async function read(fileUri: vscode.Uri, raise?: boolean): Promise +async function read(filePath: string, raise?: boolean): Promise +async function read(filePathOrUri: string | vscode.Uri, raise?: boolean): Promise { try { - return (await vscode.workspace.fs.readFile(vscode.Uri.file(filePath))).toString() + if (filePathOrUri instanceof vscode.Uri) { + return (await vscode.workspace.fs.readFile(filePathOrUri)).toString() + } else { + return (await vscode.workspace.fs.readFile(vscode.Uri.file(filePathOrUri))).toString() + } } catch (err) { - if (raise === false) { + if (raise === undefined || raise === false) { return undefined } throw err diff --git a/src/extras/snippet-view.ts b/src/extras/snippet-view.ts index 335b381d5..5145ff68f 100644 --- a/src/extras/snippet-view.ts +++ b/src/extras/snippet-view.ts @@ -25,7 +25,6 @@ async function render(pdfFileUri: vscode.Uri, opts: { height: number, width: num return } const uri = state.view.webview.asWebviewUri(pdfFileUri).toString() - let disposable: { dispose: () => void } | undefined const promise = new Promise((resolve) => { const rendered = (e: SnippetViewResult) => { if (e.type !== 'png') { @@ -49,9 +48,8 @@ async function render(pdfFileUri: vscode.Uri, opts: { height: number, width: num try { const renderResult = await promise return renderResult?.data - } finally { - disposable?.dispose() - } + } catch (_) { } + return } function receive(message: SnippetViewResult) { diff --git a/src/lint/latex-formatter.ts b/src/lint/latex-formatter.ts index 6f9a168d8..eb3db6e7f 100644 --- a/src/lint/latex-formatter.ts +++ b/src/lint/latex-formatter.ts @@ -29,12 +29,30 @@ class FormattingProvider implements vscode.DocumentFormattingEditProvider, vscod return undefined } - public provideDocumentFormattingEdits(document: vscode.TextDocument, _options: vscode.FormattingOptions, _token: vscode.CancellationToken): vscode.ProviderResult { - return this.formatter?.formatDocument(document) ?? [] + public async provideDocumentFormattingEdits(document: vscode.TextDocument, _options: vscode.FormattingOptions, _token: vscode.CancellationToken): Promise { + const edit = await this.formatter?.formatDocument(document) + if (edit === undefined) { + return [] + } + return [ edit ] } - public provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, _options: vscode.FormattingOptions, _token: vscode.CancellationToken): vscode.ProviderResult { - return this.formatter?.formatDocument(document, range) ?? [] + public async provideDocumentRangeFormattingEdits(document: vscode.TextDocument, range: vscode.Range, _options: vscode.FormattingOptions, _token: vscode.CancellationToken): Promise { + const edit = await this.formatter?.formatDocument(document, range) + if (edit === undefined) { + return [] + } + const useSpaces = vscode.window.activeTextEditor?.options.insertSpaces ?? true + const firstLine = document.lineAt(range.start.line) + // Replace all new line characters with new line and spaces, so that + // the indentations are added from the second line. + edit.newText = edit.newText.replaceAll('\n', '\n' + (useSpaces ? ' ' : '\\t').repeat(firstLine.firstNonWhitespaceCharacterIndex)) + if (firstLine.firstNonWhitespaceCharacterIndex > range.start.character) { + // \s\s\s|\sf(x)=ax+b + // In this case, the first line need some leading whitespaces. + edit.newText = ' '.repeat(firstLine.firstNonWhitespaceCharacterIndex - range.start.character) + edit.newText + } + return [ edit ] } } diff --git a/src/lint/latex-formatter/latexindent.ts b/src/lint/latex-formatter/latexindent.ts index 681dfccaf..1469fda94 100644 --- a/src/lint/latex-formatter/latexindent.ts +++ b/src/lint/latex-formatter/latexindent.ts @@ -25,7 +25,7 @@ let formatting: boolean = false lw.onConfigChange('formatting.latexindent.path', () => formatter = '') -async function formatDocument(document: vscode.TextDocument, range?: vscode.Range): Promise { +async function formatDocument(document: vscode.TextDocument, range?: vscode.Range): Promise { if (formatting) { logger.log('Formatting in progress. Aborted.') } @@ -42,7 +42,7 @@ async function formatDocument(document: vscode.TextDocument, range?: vscode.Rang formatter = '' logger.log(`Can not find ${formatter} in PATH: ${process.env.PATH}`) void logger.showErrorMessage('Can not find latexindent in PATH.') - return [] + return undefined } } const edit = await format(document, range) @@ -113,7 +113,7 @@ function checkPath(): Thenable { }) } -function format(document: vscode.TextDocument, range?: vscode.Range): Thenable { +function format(document: vscode.TextDocument, range?: vscode.Range): Thenable { return new Promise((resolve, _reject) => { const configuration = vscode.workspace.getConfiguration('latex-workshop') const useDocker = configuration.get('docker.enabled') as boolean @@ -165,7 +165,7 @@ function format(document: vscode.TextDocument, range?: vscode.Range): Thenable { removeTemporaryFiles() @@ -173,16 +173,16 @@ function format(document: vscode.TextDocument, range?: vscode.Range): Thenable { +async function formatDocument(document: vscode.TextDocument, range?: vscode.Range): Promise { const config = vscode.workspace.getConfiguration('latex-workshop') const program = config.get('formatting.tex-fmt.path') as string const args = ['--stdin'] @@ -23,25 +23,25 @@ async function formatDocument(document: vscode.TextDocument, range?: vscode.Rang stdout += msg }) - const promise = new Promise(resolve => { + const promise = new Promise(resolve => { process.on('error', err => { logger.logError(`Failed to run ${program}`, err) logger.showErrorMessage(`Failed to run ${program}. See extension log for more information.`) - resolve([]) + resolve(undefined) }) process.on('exit', code => { if (code !== 0) { logger.log(`${program} returned ${code} .`) logger.showErrorMessage(`${program} returned ${code} . Be cautious on the edits.`) - resolve([]) + resolve(undefined) } // tex-fmt adds an extra newline at the end if (stdout.endsWith('\n\n')) { stdout = stdout.slice(0, -1) } logger.log(`Formatted using ${program} .`) - resolve([ vscode.TextEdit.replace(range ?? document.validateRange(new vscode.Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE)), stdout) ]) + resolve(vscode.TextEdit.replace(range ?? document.validateRange(new vscode.Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE)), stdout)) }) }) diff --git a/src/locate/synctex.ts b/src/locate/synctex.ts index 49ffde825..a45c0cd14 100644 --- a/src/locate/synctex.ts +++ b/src/locate/synctex.ts @@ -182,14 +182,14 @@ function parseToPDFList(result: string): SyncTeXRecordToPDFAll[] { * document and cursor position. The forward SyncTeX can be executed with a * specific PDF viewer, and the PDF file can be specified. * + * @param pdfUri - The path of a PDF File compiled from the filePath of args. + * If undefined, it is automatically detected. * @param args - The arguments of forward SyncTeX. If undefined, the document * and cursor position of activeTextEditor are used. * @param forcedViewer - Indicates a PDF viewer with which SyncTeX is executed * ('auto', 'tabOrBrowser', or 'external'). - * @param pdfUri - The path of a PDF File compiled from the filePath of args. - * If undefined, it is automatically detected. */ -function toPDF(args?: {line: number, filePath: string}, forcedViewer: 'auto' | 'tabOrBrowser' | 'external' = 'auto', pdfUri?: vscode.Uri) { +function toPDF(pdfUri?: vscode.Uri, args?: {line: number, filePath: string}, forcedViewer: 'auto' | 'tabOrBrowser' | 'external' = 'auto') { let line: number let filePath: string let character = 0 @@ -338,9 +338,9 @@ function toPDFFromRef(args: {line: number, filePath: string}) { const viewer = configuration.get('view.pdf.ref.viewer') as 'auto' | 'tabOrBrowser' | 'external' args.line += 1 if (viewer) { - toPDF(args, viewer) + toPDF(undefined, args, viewer) } else { - toPDF(args) + toPDF(undefined, args) } } diff --git a/src/outline/structure/latex.ts b/src/outline/structure/latex.ts index bfb8b380d..73d2a3395 100644 --- a/src/outline/structure/latex.ts +++ b/src/outline/structure/latex.ts @@ -83,6 +83,15 @@ async function constructFile(filePath: string, config: StructureConfig, structs: } } +function chooseCaption(...args: (Ast.Argument | undefined)[]): string { + for (const arg of args) { + if ((arg?.content?.length ?? 0) > 0) { + return argContentToStr(arg?.content ?? []) + } + } + return '' +} + async function parseNode( node: Ast.Node, rnwSub: ReturnType, @@ -105,7 +114,7 @@ async function parseNode( element = { type: node.args?.[0]?.content[0] ? TeXElementType.SectionAst : TeXElementType.Section, name: node.content, - label: argContentToStr(((node.args?.[1]?.content?.length ?? 0) > 0 ? node.args?.[1]?.content : node.args?.[2]?.content) || []), + label: chooseCaption(node.args?.[1], node.args?.[2]), appendix: inAppendix, ...attributes } @@ -121,7 +130,7 @@ async function parseNode( inAppendix = true } else if ((node.type === 'environment') && node.env === 'frame') { const frameTitleMacro: Ast.Macro | undefined = node.content.find(sub => sub.type === 'macro' && sub.content === 'frametitle') as Ast.Macro | undefined - const caption = argContentToStr(node.args?.[3]?.content || []) || argContentToStr(frameTitleMacro?.args?.[2]?.content || []) + const caption = chooseCaption(node.args?.[3], frameTitleMacro?.args?.[2]) element = { type: TeXElementType.Environment, name: node.env, @@ -132,7 +141,7 @@ async function parseNode( (node.env === 'figure' || node.env === 'figure*') && config.macros.envs.includes('figure') || (node.env === 'table' || node.env === 'table*') && config.macros.envs.includes('table'))) { const captionMacro: Ast.Macro | undefined = node.content.find(sub => sub.type === 'macro' && sub.content === 'caption') as Ast.Macro | undefined - const caption = argContentToStr(captionMacro?.args?.[1]?.content || []) + const caption = chooseCaption(captionMacro?.args?.[0], captionMacro?.args?.[1]) if (node.env.endsWith('*')) { node.env = node.env.slice(0, -1) } diff --git a/src/parse/parser.ts b/src/parse/parser.ts index acde52fd8..bb52f0127 100644 --- a/src/parse/parser.ts +++ b/src/parse/parser.ts @@ -43,13 +43,13 @@ async function reset() { } async function bib(s: string, options?: bibtexParser.ParserOptions): Promise { - let ast = undefined - try { - ast = (await proxy).parseBibTeX(s, options) - } catch (err) { - logger.logError('Error when parsing bib file.', err) + const ast = await (await proxy).parseBibTeX(s, options) + if (ast instanceof Error) { + logger.logError('Error when parsing bib file.', ast) + return undefined + } else { + return ast } - return ast } function stringify(ast: Ast.Ast): string { diff --git a/src/parse/parser/latexlog.ts b/src/parse/parser/latexlog.ts index e8663dfb1..2991b17e6 100644 --- a/src/parse/parser/latexlog.ts +++ b/src/parse/parser/latexlog.ts @@ -262,6 +262,7 @@ function parseBadBox(line: string, filename: string, state: ParserState, type?: line: 1, text: result[2] ? `${result[1]} in page ${result[2]}` : result[1] } + parseLine(line.substring(result[0].length), state) } else { state.currentResult = { type: 'typesetting', @@ -270,9 +271,8 @@ function parseBadBox(line: string, filename: string, state: ParserState, type?: text: result[1] } state.insideBoxWarn = true + state.searchEmptyLine = false } - state.searchEmptyLine = false - parseLine(line.substring(result[0].length), state) return true } return false diff --git a/src/parse/parser/unified-defs.ts b/src/parse/parser/unified-defs.ts index 45df11c22..ed1d5cec1 100644 --- a/src/parse/parser/unified-defs.ts +++ b/src/parse/parser/unified-defs.ts @@ -16,7 +16,7 @@ const MACROS: MacroInfoRecord = { subinputfrom: { signature: 'm m' }, subincludefrom: { signature: 'm m' }, // \label{some-label} - linelabel: { signature: 'o m' }, + linelabel: { signature: 'd<> o m' }, // \newglossaryentry{vscode}{name=VSCode, description=Editor} newglossaryentry: { signature: 'm m' }, provideglossaryentry: { signature: 'm m' }, diff --git a/src/parse/parser/unified.ts b/src/parse/parser/unified.ts index fc67d74f1..01b449351 100644 --- a/src/parse/parser/unified.ts +++ b/src/parse/parser/unified.ts @@ -26,8 +26,15 @@ function reset(macros: Ast.MacroInfoRecord, environments: Ast.EnvInfoRecord) { unifiedParser = getParser({ macros, environments, flags: { autodetectExpl3AndAtLetter: true } }) } -function parseBibTeX(s: string, options?: bibtexParser.ParserOptions): bibtexParser.BibtexAst { - return bibtexParser.parse(s, options) +function parseBibTeX(s: string, options?: bibtexParser.ParserOptions): bibtexParser.BibtexAst | Error | undefined { + try { + return bibtexParser.parse(s, options) + } catch (err) { + if (err instanceof Error) { + return err + } + return undefined + } } const worker = { diff --git a/src/preview/server.ts b/src/preview/server.ts index 8586e0591..1eb50d101 100644 --- a/src/preview/server.ts +++ b/src/preview/server.ts @@ -2,7 +2,6 @@ import * as vscode from 'vscode' import * as http from 'http' import type { AddressInfo } from 'net' import ws from 'ws' -import * as fs from 'fs' import * as path from 'path' import { lw } from '../lw' @@ -74,7 +73,7 @@ function getPort(): number { async function getUrl(pdfUri?: vscode.Uri): Promise<{url: string, uri: vscode.Uri}> { // viewer/viewer.js automatically requests the file to server.ts, and server.ts decodes the encoded path of PDF file. - const origUrl = await vscode.env.asExternalUri(vscode.Uri.parse(`http://127.0.0.1:${lw.server.getPort()}`, true)) + const origUrl = await vscode.env.asExternalUri(vscode.Uri.parse(`http://127.0.0.1:${getPort()}`, true)) const url = (origUrl.toString().endsWith('/') ? origUrl.toString().slice(0, -1) : origUrl.toString()) + (pdfUri ? ('/viewer.html?file=' + encodePathWithPrefix(pdfUri)) : '') @@ -96,11 +95,14 @@ function getValidOrigin(): string { function initialize(hostname?: string): http.Server { if (hostname) { // We must have created one. state.httpServer.close() + state.address = undefined } const httpServer = http.createServer((request, response) => handler(request, response)) const configuration = vscode.workspace.getConfiguration('latex-workshop') - const viewerPort = configuration.get('viewer.pdf.internal.port') as number + const viewerPort = configuration.get('view.pdf.internal.port') as number httpServer.listen(viewerPort, hostname ?? '127.0.0.1', undefined, async () => { + // Double set state to ensure the server is set + state.httpServer = httpServer const address = state.httpServer.address() if (address && typeof address !== 'string') { state.address = address @@ -197,11 +199,13 @@ async function handler(request: http.IncomingMessage, response: http.ServerRespo const fileUri = decodePathWithPrefix(s) if (!lw.viewer.isViewing(fileUri)) { logger.log(`Invalid PDF request: ${fileUri.toString(true)}`) + response.writeHead(404) + response.end() return } try { - const buf: Buffer = Buffer.from(await vscode.workspace.fs.readFile(fileUri)) - sendOkResponse(response, buf, 'application/pdf') + const content = await vscode.workspace.fs.readFile(fileUri) + sendOkResponse(response, Buffer.from(content), 'application/pdf') logger.log(`Preview PDF file: ${fileUri.toString(true)}`) } catch (e) { logger.logError(`Error reading PDF ${fileUri.toString(true)}`, e) @@ -212,8 +216,7 @@ async function handler(request: http.IncomingMessage, response: http.ServerRespo } if (request.url.endsWith('/config.json')) { const params = lw.viewer.getParams() - const content = JSON.stringify(params) - sendOkResponse(response, Buffer.from(content), 'application/json') + sendOkResponse(response, Buffer.from(JSON.stringify(params)), 'application/json') return } let root: string @@ -283,18 +286,17 @@ async function handler(request: http.IncomingMessage, response: http.ServerRespo break } } - fs.readFile(fileName, (err, content) => { - if (err) { - if (err.code === 'ENOENT') { - response.writeHead(404) - } else { - response.writeHead(500) - } - response.end() + try { + const content = await vscode.workspace.fs.readFile(vscode.Uri.file(fileName)) + sendOkResponse(response, Buffer.from(content), contentType, false) + } catch (err) { + if (typeof (err as any).code === 'string' && (err as any).code === 'FileNotFound') { + response.writeHead(404) } else { - sendOkResponse(response, content, contentType, false) + response.writeHead(500) } - }) + response.end() + } } /** diff --git a/src/preview/viewer.ts b/src/preview/viewer.ts index a856a3322..f7633e24d 100644 --- a/src/preview/viewer.ts +++ b/src/preview/viewer.ts @@ -126,11 +126,7 @@ async function viewInCustomEditor(pdfUri: vscode.Uri): Promise { await vscode.commands.executeCommand('workbench.action.focusRightGroup') } else { await vscode.commands.executeCommand('vscode.openWith', pdfUri, 'latex-workshop-pdf-hook', showOptions) - if (currentColumn === vscode.ViewColumn.One) { - await moveActiveEditor('left', true) - } else { - await vscode.commands.executeCommand('workbench.action.focusRightGroup') - } + await moveActiveEditor('left', true) } } else if (editorGroup === 'right') { const currentColumn = vscode.window.activeTextEditor?.viewColumn @@ -241,7 +237,7 @@ function handler(websocket: ws, msg: string): void { if (clientSet === undefined) { break } - const client = new Client(data.viewer, websocket) + const client = new Client(websocket) clientSet.add(client) client.onDidDispose(() => { clientSet.delete(client) @@ -254,7 +250,7 @@ function handler(websocket: ws, msg: string): void { if (configuration.get('synctex.afterBuild.enabled') as boolean) { logger.log('SyncTex after build invoked.') const uri = vscode.Uri.parse(data.pdfFileUri, true) - lw.locate.synctex.toPDF(undefined, undefined, uri) + lw.locate.synctex.toPDF(uri) } break } @@ -264,24 +260,14 @@ function handler(websocket: ws, msg: string): void { break } case 'external_link': { - void vscode.env.clipboard.writeText(data.url) const uri = vscode.Uri.parse(data.url) if (['http', 'https'].includes(uri.scheme)) { void vscode.env.openExternal(uri) } else { - vscode.window.showInformationMessage(`The link ${data.url} has been copied to clipboard.`, 'Open link', 'Dismiss').then( - option => { - switch (option) { - case 'Open link': - void vscode.env.openExternal(uri) - break - default: - break - } - }, - reason => { - logger.log(`Unknown error when opening URI. Error: ${JSON.stringify(reason)}, URI: ${data.url}`) - }) + void vscode.window.showInputBox({ + prompt: 'For security reasons, please copy and visit this link manually.', + value: data.url + }) } break } @@ -310,9 +296,10 @@ function handler(websocket: ws, msg: string): void { function getParams(): PdfViewerParams { const configuration = vscode.workspace.getConfiguration('latex-workshop') const invertType = configuration.get('view.pdf.invertMode.enabled') as string - const invertEnabled = (invertType === 'auto' && vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark) || - invertType === 'always' || - (invertType === 'compat' && ((configuration.get('view.pdf.invert') as number) > 0)) + const invertEnabled = + (invertType === 'auto' && vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Dark) || + invertType === 'always' || + (invertType === 'compat' && (configuration.get('view.pdf.invert') as number) > 0) const pack: PdfViewerParams = { scale: configuration.get('view.pdf.zoom') as string, trim: configuration.get('view.pdf.trim') as number, @@ -332,19 +319,19 @@ function getParams(): PdfViewerParams { pageColorsForeground: configuration.get('view.pdf.color.light.pageColorsForeground') || 'CanvasText', pageColorsBackground: configuration.get('view.pdf.color.light.pageColorsBackground') || 'Canvas', backgroundColor: configuration.get('view.pdf.color.light.backgroundColor', '#ffffff'), - pageBorderColor: configuration.get('view.pdf.color.light.pageBorderColor', 'lightgrey') + pageBorderColor: configuration.get('view.pdf.color.light.pageBorderColor', 'lightgrey'), }, dark: { pageColorsForeground: configuration.get('view.pdf.color.dark.pageColorsForeground') || 'CanvasText', pageColorsBackground: configuration.get('view.pdf.color.dark.pageColorsBackground') || 'Canvas', backgroundColor: configuration.get('view.pdf.color.dark.backgroundColor', '#ffffff'), - pageBorderColor: configuration.get('view.pdf.color.dark.pageBorderColor', 'lightgrey') - } + pageBorderColor: configuration.get('view.pdf.color.dark.pageBorderColor', 'lightgrey'), + }, }, codeColorTheme: vscode.window.activeColorTheme.kind === vscode.ColorThemeKind.Light ? 'light' : 'dark', keybindings: { - synctex: configuration.get('view.pdf.internal.synctex.keybinding') as 'ctrl-click' | 'double-click' - } + synctex: configuration.get('view.pdf.internal.synctex.keybinding') as 'ctrl-click' | 'double-click', + }, } return pack } @@ -407,6 +394,7 @@ function showInvisibleWebviewPanel(pdfUri: vscode.Uri): boolean { } /** + * !! Test only * Returns the state of the internal PDF viewer of `pdfFilePath`. * * @param pdfUri The path of a PDF file. diff --git a/src/preview/viewer/client.ts b/src/preview/viewer/client.ts index 56274cd2b..abb91a747 100644 --- a/src/preview/viewer/client.ts +++ b/src/preview/viewer/client.ts @@ -3,12 +3,10 @@ import type ws from 'ws' import type { ServerResponse } from '../../../types/latex-workshop-protocol-types/index' export class Client { - readonly viewer: 'browser' | 'tab' readonly websocket: ws private readonly disposables = new Set() - constructor(viewer: 'browser' | 'tab', websocket: ws) { - this.viewer = viewer + constructor(websocket: ws) { this.websocket = websocket this.websocket.on('close', () => { this.disposeDisposables() diff --git a/src/preview/viewer/pdfviewerpanel.ts b/src/preview/viewer/pdfviewerpanel.ts index 03e51cf8e..bf61b8c7f 100644 --- a/src/preview/viewer/pdfviewerpanel.ts +++ b/src/preview/viewer/pdfviewerpanel.ts @@ -97,7 +97,7 @@ async function populate(pdfUri: vscode.Uri, panel: vscode.WebviewPanel): Promise function getKeyboardEventConfig(): boolean { const configuration = vscode.workspace.getConfiguration('latex-workshop') - const setting: 'auto' | 'force' | 'never' = configuration.get('viewer.pdf.internal.keyboardEvent', 'auto') + const setting: 'auto' | 'force' | 'never' = configuration.get('view.pdf.internal.keyboardEvent', 'auto') if (setting === 'auto') { return true } else if (setting === 'force') { diff --git a/src/types.ts b/src/types.ts index 24816594c..d26d33f1b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -124,7 +124,7 @@ export interface LaTeXLinter { } export interface LaTeXFormatter { - formatDocument(document: vscode.TextDocument, range?: vscode.Range): Promise + formatDocument(document: vscode.TextDocument, range?: vscode.Range): Promise } export enum TeXElementType { Environment, Macro, Section, SectionAst, SubFile, BibItem, BibField } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 213665c8b..7d3f79d07 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -127,6 +127,7 @@ function initStatusBarItem() { function clearCompilerMessage() { COMPILER_PANEL.clear() + CACHED_COMPILER.length = 0 } function showLog() { diff --git a/syntax/DocTeX.tmLanguage.json b/syntax/DocTeX.tmLanguage.json index 44e9faf3a..75fe5a5f4 100644 --- a/syntax/DocTeX.tmLanguage.json +++ b/syntax/DocTeX.tmLanguage.json @@ -2056,7 +2056,7 @@ ] } }, - "contentName": "punctuation.definition.comment.latex", + "contentName": "comment.line.percentage.latex", "end": "(\\\\end\\{\\2\\})", "name": "meta.function.verbatim.latex" }, diff --git a/syntax/LaTeX.tmLanguage.json b/syntax/LaTeX.tmLanguage.json index 3e938d4fd..72a764667 100644 --- a/syntax/LaTeX.tmLanguage.json +++ b/syntax/LaTeX.tmLanguage.json @@ -1995,7 +1995,7 @@ ] } }, - "contentName": "punctuation.definition.comment.latex", + "contentName": "comment.line.percentage.latex", "end": "(\\\\end\\{\\2\\})", "name": "meta.function.verbatim.latex" }, diff --git a/syntax/TeX.tmLanguage.json b/syntax/TeX.tmLanguage.json index 11a898677..6c4d47f4e 100644 --- a/syntax/TeX.tmLanguage.json +++ b/syntax/TeX.tmLanguage.json @@ -2,7 +2,7 @@ "name": "TeX", "patterns": [ { - "begin": "(?<=^\\s*)((\\\\)iffalse)", + "begin": "(?<=^\\s*)((\\\\)iffalse)(?!\\s*[{}]\\s*\\\\fi)", "beginCaptures": { "1": { "name": "keyword.control.tex" @@ -12,7 +12,7 @@ } }, "contentName": "comment.line.percentage.tex", - "end": "(?<=^\\s*)((\\\\)(?:else|fi))", + "end": "((\\\\)(?:else|fi))", "endCaptures": { "1": { "name": "keyword.control.tex" @@ -25,6 +25,9 @@ { "include": "#comment" }, + { + "include": "#braces" + }, { "include": "#conditionals" } diff --git a/test/fixtures/unittest/09_viewer_server/main.pdf b/test/fixtures/unittest/09_viewer_server/main.pdf new file mode 100644 index 000000000..80bc1e9e0 Binary files /dev/null and b/test/fixtures/unittest/09_viewer_server/main.pdf differ diff --git a/test/fixtures/unittest/10_viewer_pdf_server/main.pdf b/test/fixtures/unittest/10_viewer_pdf_server/main.pdf new file mode 100644 index 000000000..80bc1e9e0 Binary files /dev/null and b/test/fixtures/unittest/10_viewer_pdf_server/main.pdf differ diff --git a/test/suites/01_build.test.ts b/test/suites/01_build.test.ts index b0d6eee8f..cbd60d018 100644 --- a/test/suites/01_build.test.ts +++ b/test/suites/01_build.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert' import { lw } from '../../src/lw' import * as test from './utils' -suite('Build TeX files test suite', () => { +suite.skip('Build TeX files test suite', () => { test.suite.name = path.basename(__filename).replace('.test.js', '') test.suite.fixture = 'testground' diff --git a/test/suites/02_autobuild.test.ts b/test/suites/02_autobuild.test.ts index 78250af24..89444680e 100644 --- a/test/suites/02_autobuild.test.ts +++ b/test/suites/02_autobuild.test.ts @@ -3,7 +3,7 @@ import * as path from 'path' import * as test from './utils' import assert from 'assert' -suite('Auto-build test suite', () => { +suite.skip('Auto-build test suite', () => { test.suite.name = path.basename(__filename).replace('.test.js', '') test.suite.fixture = 'testground' diff --git a/test/suites/03_findroot.test.ts b/test/suites/03_findroot.test.ts index 977d3e549..fc3521d28 100644 --- a/test/suites/03_findroot.test.ts +++ b/test/suites/03_findroot.test.ts @@ -4,7 +4,7 @@ import * as assert from 'assert' import { lw } from '../../src/lw' import * as test from './utils' -suite('Find root file test suite', () => { +suite.skip('Find root file test suite', () => { test.suite.name = path.basename(__filename).replace('.test.js', '') test.suite.fixture = 'testground' diff --git a/test/suites/05_viewer.test.ts b/test/suites/05_viewer.test.ts index f709dc5bd..2eeab44cb 100644 --- a/test/suites/05_viewer.test.ts +++ b/test/suites/05_viewer.test.ts @@ -4,7 +4,7 @@ import * as assert from 'assert' import { lw } from '../../src/lw' import * as test from './utils' -suite('PDF viewer test suite', () => { +suite.skip('PDF viewer test suite', () => { test.suite.name = path.basename(__filename).replace('.test.js', '') test.suite.fixture = 'testground' diff --git a/test/suites/09_formatter.test.ts b/test/suites/09_formatter.test.ts index 20d40e1d8..9fa43b1b8 100644 --- a/test/suites/09_formatter.test.ts +++ b/test/suites/09_formatter.test.ts @@ -44,7 +44,7 @@ suite('Formatter test suite', () => { const original = readFileSync(path.resolve(fixture, 'main.tex')).toString() const formatted = await test.format() assert.notStrictEqual(original, formatted) - }) + }, ['win32', 'linux']) test.run('change formatting.latexindent.path on the fly', async (fixture: string) => { await vscode.workspace.getConfiguration('latex-workshop').update('formatting.latexindent.path', 'echo') @@ -63,7 +63,7 @@ suite('Formatter test suite', () => { await vscode.workspace.getConfiguration('latex-workshop').update('formatting.latexindent.args', ['-c', '%DIR%/', '%TMPFILE%', '-y=defaultIndent: \'%INDENT%\'']) const formatted = await test.format() assert.notStrictEqual(original, formatted) - }) + }, ['win32', 'linux']) test.run('test bibtex formatter', async (fixture: string) => { await test.load(fixture, [ diff --git a/test/suites/utils.ts b/test/suites/utils.ts index 165feafe3..6f51dbe51 100644 --- a/test/suites/utils.ts +++ b/test/suites/utils.ts @@ -79,7 +79,6 @@ export function sleep(ms: number) { export async function reset() { await vscode.commands.executeCommand('workbench.action.closeAllEditors') await Promise.all(Object.values(lw.cache.promises)) - lw.compile.lastSteps = [] lw.compile.lastAutoBuildTime = 0 lw.compile.compiledPDFPath = '' lw.compile.compiledPDFWriting = 0 diff --git a/test/units/01_core_file.test.ts b/test/units/01_core_file.test.ts index d80a29b38..a6ab7ddba 100644 --- a/test/units/01_core_file.test.ts +++ b/test/units/01_core_file.test.ts @@ -10,7 +10,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file') + mock.init(lw, 'file') }) after(() => { @@ -79,82 +79,82 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir) }) - it('should get output directory with absolute `latex.outDir` and root', async () => { - await set.config('latex.outDir', '/output') + it('should get output directory with absolute `latex.outDir` and root', () => { + set.config('latex.outDir', '/output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should get output directory with relative `latex.outDir` and root', async () => { - await set.config('latex.outDir', 'output') + it('should get output directory with relative `latex.outDir` and root', () => { + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) - it('should get output directory with relative `latex.outDir` with leading `./` and root', async () => { - await set.config('latex.outDir', './output') + it('should get output directory with relative `latex.outDir` with leading `./` and root', () => { + set.config('latex.outDir', './output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) - it('should get output directory with relative `latex.outDir`, root, and an input latex', async () => { + it('should get output directory with relative `latex.outDir`, root, and an input latex', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.outDir', 'output') + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(texPath), 'output') }) - it('should get output directory with placeholder in `latex.outDir` and root', async () => { - await set.config('latex.outDir', '%DIR%') + it('should get output directory with placeholder in `latex.outDir` and root', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path) }) - it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', async () => { + it('should get output directory with placeholder in `latex.outDir`, root, and an input latex', () => { const rootDir = get.path(fixture) const texPath = get.path(fixture, 'main.tex') - await set.config('latex.outDir', '%DIR%') + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(texPath), rootDir) }) - it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', async () => { - await set.config('latex.outDir', '%DIR%') + it('should get output directory from last compilation if `latex.outDir` is `%DIR%`', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '', '/output') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', async () => { - await set.config('latex.outDir', '/output') + it('should ignore output directory from last compilation if `latex.outDir` is not `%DIR%`', () => { + set.config('latex.outDir', '/output') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '', '/trap') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should ignore output directory from last compilation if no `outdir` is recorded', async () => { - await set.config('latex.outDir', '%DIR%') + it('should ignore output directory from last compilation if no `outdir` is recorded', () => { + set.config('latex.outDir', '%DIR%') set.root(fixture, 'main.tex') lw.file.setTeXDirs(lw.root.file.path ?? '') assert.pathStrictEqual(lw.file.getOutDir(), lw.root.dir.path) }) - it('should handle empty `latex.outDir` correctly', async () => { - await set.config('latex.outDir', '') + it('should handle empty `latex.outDir` correctly', () => { + set.config('latex.outDir', '') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), './') }) - it('should handle absolute `latex.outDir` with trailing slashes correctly', async () => { - await set.config('latex.outDir', '/output/') + it('should handle absolute `latex.outDir` with trailing slashes correctly', () => { + set.config('latex.outDir', '/output/') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), '/output') }) - it('should handle relative `latex.outDir` with trailing slashes correctly', async () => { - await set.config('latex.outDir', 'output/') + it('should handle relative `latex.outDir` with trailing slashes correctly', () => { + set.config('latex.outDir', 'output/') set.root(fixture, 'main.tex') assert.pathStrictEqual(lw.file.getOutDir(), 'output') }) @@ -184,7 +184,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'main.tex') const flsPath = get.path(fixture, 'output', 'main.fls') - await set.config('latex.outDir', 'output') + set.config('latex.outDir', 'output') assert.pathStrictEqual(await lw.file.getFlsPath(texPath), flsPath) }) @@ -233,18 +233,18 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { ]) }) - it('should correctly find BibTeX files in `latex.bibDirs`', async () => { + it('should correctly find BibTeX files in `latex.bibDirs`', () => { set.root(fixture, 'main.tex') - await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) + set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) const result = lw.file.getBibPath('sub.bib', lw.root.dir.path ?? '') assert.listStrictEqual(result, [ path.resolve(lw.root.dir.path ?? '', 'subdir', 'sub.bib') ]) }) - it('should return an empty array when no BibTeX file is found', async () => { + it('should return an empty array when no BibTeX file is found', () => { set.root(fixture, 'main.tex') - await set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) + set.config('latex.bibDirs', [ path.resolve(lw.root.dir.path ?? '', 'subdir') ]) const result = lw.file.getBibPath('nonexistent.bib', path.resolve(lw.root.dir.path ?? '', 'output')) assert.listStrictEqual(result, [ ]) }) @@ -258,29 +258,29 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { ]) }) - it('should handle case when kpsewhich is disabled and BibTeX file not found', async () => { + it('should handle case when kpsewhich is disabled and BibTeX file not found', () => { const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', false) + set.config('kpsewhich.bibtex.enabled', false) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '') stub.restore() assert.listStrictEqual(result, [ ]) }) - it('should handle case when kpsewhich is enabled and BibTeX file not found', async () => { + it('should handle case when kpsewhich is enabled and BibTeX file not found', () => { const nonPath = get.path(fixture, 'nonexistent.bib') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: get.path(fixture, 'nonexistent.bib'), output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', true) + set.config('kpsewhich.bibtex.enabled', true) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('nonexistent.bib', lw.root.dir.path ?? '') stub.restore() assert.listStrictEqual(result, [ nonPath ]) }) - it('should return an empty array when kpsewhich is enabled but file is not found', async () => { + it('should return an empty array when kpsewhich is enabled but file is not found', () => { const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) - await set.config('kpsewhich.bibtex.enabled', true) + set.config('kpsewhich.bibtex.enabled', true) set.root(fixture, 'main.tex') const result = lw.file.getBibPath('another-nonexistent.bib', lw.root.dir.path ?? '') stub.restore() @@ -330,40 +330,40 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.file.getJobname', () => { - it('should return the jobname if present in configuration', async () => { + it('should return the jobname if present in configuration', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', 'myJob') + set.config('latex.jobname', 'myJob') assert.strictEqual(lw.file.getJobname(texPath), 'myJob') }) - it('should return the name of the input texPath if jobname is empty', async () => { + it('should return the name of the input texPath if jobname is empty', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', '') + set.config('latex.jobname', '') const expectedJobname = path.parse(texPath).name assert.strictEqual(lw.file.getJobname(texPath), expectedJobname) }) - it('should return the name of the input texPath if configuration is not set', async () => { + it('should return the name of the input texPath if configuration is not set', () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.jobname', undefined) // Ensuring the jobname is not set + set.config('latex.jobname', undefined) // Ensuring the jobname is not set const expectedJobname = path.parse(texPath).name assert.strictEqual(lw.file.getJobname(texPath), expectedJobname) }) }) describe('lw.file.getPdfPath', () => { - it('should return the correct PDF path when outDir is empty', async () => { - await set.config('latex.outDir', '') + it('should return the correct PDF path when outDir is empty', () => { + set.config('latex.outDir', '') set.root(fixture, 'main.tex') const texpath = lw.root.file.path ?? '' assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('.tex', '.pdf')) }) - it('should return the correct PDF path when outDir is specified', async () => { - await set.config('latex.outDir', 'output') + it('should return the correct PDF path when outDir is specified', () => { + set.config('latex.outDir', 'output') set.root(fixture, 'main.tex') const texpath = lw.root.file.path ?? '' assert.pathStrictEqual(lw.file.getPdfPath(texpath), texpath.replaceAll('main.tex', 'output/main.pdf')) @@ -503,16 +503,16 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('kpsewhich', () => { - it('should call kpsewhich with correct arguments', async () => { - await set.config('kpsewhich.path', 'kpse') + it('should call kpsewhich with correct arguments', () => { + set.config('kpsewhich.path', 'kpse') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) lw.file.kpsewhich('article.cls') stub.restore() sinon.assert.calledWith(stub, 'kpse', ['article.cls'], sinon.match.any) }) - it('should handle isBib flag correctly', async () => { - await set.config('kpsewhich.path', 'kpse') + it('should handle isBib flag correctly', () => { + set.config('kpsewhich.path', 'kpse') const stub = sinon.stub(lw.external, 'sync').returns({ pid: 0, status: 0, stdout: '', output: [''], stderr: '', signal: 'SIGTERM' }) lw.file.kpsewhich('reference.bib', true) stub.restore() diff --git a/test/units/02_core_watcher.test.ts b/test/units/02_core_watcher.test.ts index c3c9a0c9e..ffaaf86f2 100644 --- a/test/units/02_core_watcher.test.ts +++ b/test/units/02_core_watcher.test.ts @@ -18,7 +18,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const getOnDeleteHandlers = () => _onDeleteHandlersSpy.call(lw.watcher.src) as Set<(uri: vscode.Uri) => void> before(() => { - mock.object(lw, 'file', 'watcher') + mock.init(lw, 'file', 'watcher') _onDidChangeSpy = sinon.spy(lw.watcher.src as any, 'onDidChange') _onDidDeleteSpy = sinon.spy(lw.watcher.src as any, 'onDidDelete') _watchersSpy = sinon.spy(lw.watcher.src as any, 'watchers', ['get']).get @@ -182,8 +182,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(stub.callCount, 0) }) - it('should call onChangeHandlers once when quickly changing watched binary file', async function (this: Mocha.Context) { - this.slow(1050) + it('should call onChangeHandlers once when quickly changing watched binary file', async () => { const binPath = get.path(fixture, 'main.bin') lw.watcher.src.add(vscode.Uri.file(binPath)) @@ -193,8 +192,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(stub.callCount, 1) }) - it('should call onChangeHandlers multiple times when slowly changing watched binary file', async function (this: Mocha.Context) { - this.slow(2050) + it('should call onChangeHandlers multiple times when slowly changing watched binary file', async () => { const binPath = get.path(fixture, 'main.bin') lw.watcher.src.add(vscode.Uri.file(binPath)) @@ -210,10 +208,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const stub = sinon.stub() const handler = (filePath: vscode.Uri) => { stub(filePath.fsPath) } - beforeEach(async () => { + beforeEach(() => { stub.reset() lw.watcher.src.onDelete(handler) - await set.config('latex.watch.delay', 100) + set.config('latex.watch.delay', 100) }) afterEach(() => { @@ -221,8 +219,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { getOnDeleteHandlers().delete(handler) }) - it('should call onDeleteHandlers when deleting watched file', async function (this: Mocha.Context) { - this.slow(250) + it('should call onDeleteHandlers when deleting watched file', async () => { const texPath = get.path(fixture, 'main.tex') lw.watcher.src.add(vscode.Uri.file(texPath)) @@ -231,8 +228,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.listStrictEqual(stub.getCall(0).args, [ texPath ]) }) - it('should not call onChangeHandlers when watched file is deleted then created in a short time', async function (this: Mocha.Context) { - this.slow(250) + it('should not call onChangeHandlers when watched file is deleted then created in a short time', async () => { const binPath = get.path(fixture, 'main.bin') lw.watcher.src.add(vscode.Uri.file(binPath)) diff --git a/test/units/03_core_cache.test.ts b/test/units/03_core_cache.test.ts index cf908a2d0..e068249f3 100644 --- a/test/units/03_core_cache.test.ts +++ b/test/units/03_core_cache.test.ts @@ -1,5 +1,4 @@ import * as vscode from 'vscode' -import * as Mocha from 'mocha' import * as path from 'path' import * as sinon from 'sinon' import { assert, get, log, mock, set, sleep } from './utils' @@ -9,7 +8,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file', 'watcher', 'cache') + mock.init(lw, 'file', 'watcher', 'cache') }) after(() => { @@ -38,7 +37,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should excluded files with config set ', async () => { - await set.config('latex.watch.files.ignore', ['**/*.bbl']) + set.config('latex.watch.files.ignore', ['**/*.bbl']) log.start() await lw.cache.refreshCache(bblPath) @@ -53,8 +52,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.cache.canCache', () => { - beforeEach(async () => { - await set.config('latex.watch.files.ignore', []) + beforeEach(() => { + set.config('latex.watch.files.ignore', []) }) it('should cache supported TeX files', async () => { @@ -284,13 +283,12 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.cache.refreshCacheAggressive', () => { - beforeEach(async () => { - await set.config('intellisense.update.aggressive.enabled', true) - await set.config('intellisense.update.delay', 100) + beforeEach(() => { + set.config('intellisense.update.aggressive.enabled', true) + set.config('intellisense.update.delay', 100) }) - it('should not aggressively cache non-cached files', async function (this: Mocha.Context) { - this.slow(350) + it('should not aggressively cache non-cached files', async () => { const texPath = get.path(fixture, 'main.tex') lw.cache.refreshCacheAggressive(texPath) @@ -298,8 +296,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.listStrictEqual(lw.cache.paths(), [ ]) }) - it('should aggressively cache cached files', async function (this: Mocha.Context) { - this.slow(350) + it('should aggressively cache cached files', async () => { const texPath = get.path(fixture, 'main.tex') lw.cache.add(texPath) @@ -317,8 +314,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(lw.cache.get(lw.cache.paths()[0])?.content, '') }) - it('should reload .fls file when aggressively caching cached files', async function (this: Mocha.Context) { - this.slow(350) + it('should reload .fls file when aggressively caching cached files', async () => { const texPath = get.path(fixture, 'main.tex') lw.cache.add(texPath) @@ -331,11 +327,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.hasLog('Parsing .fls ') }) - it('should not aggressively cache cached files without `intellisense.update.aggressive.enabled`', async function (this: Mocha.Context) { - this.slow(350) + it('should not aggressively cache cached files without `intellisense.update.aggressive.enabled`', async () => { const texPath = get.path(fixture, 'main.tex') - await set.config('intellisense.update.aggressive.enabled', false) + set.config('intellisense.update.aggressive.enabled', false) lw.cache.add(texPath) await lw.cache.refreshCache(texPath) const stub = mock.textDocument(texPath, '', { isDirty: true }) @@ -345,8 +340,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(lw.cache.get(lw.cache.paths()[0])?.content, '%') }) - it('should aggressively cache cached files once on quick changes', async function (this: Mocha.Context) { - this.slow(450) + it('should aggressively cache cached files once on quick changes', async () => { const texPath = get.path(fixture, 'main.tex') lw.cache.add(texPath) @@ -370,8 +364,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(lw.cache.get(lw.cache.paths()[0])?.content, '%%') }) - it('should aggressively cache cached files multiple times on slow changes', async function (this: Mocha.Context) { - this.slow(650) + it('should aggressively cache cached files multiple times on slow changes', async () => { const texPath = get.path(fixture, 'main.tex') lw.cache.add(texPath) @@ -550,7 +543,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should add a child if it is defined in `latex.texDirs`', async () => { const texPath = get.path(fixture, 'main.tex') - await set.config('latex.texDirs', [ get.path(fixture, 'update_children_xr', 'sub') ]) + set.config('latex.texDirs', [ get.path(fixture, 'update_children_xr', 'sub') ]) set.root(texPath) lw.cache.add(texPath) @@ -775,7 +768,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not add \\bibdata if the bib is excluded', async () => { - await set.config('latex.watch.files.ignore', ['**/main.bib']) + set.config('latex.watch.files.ignore', ['**/main.bib']) const toParse = get.path(fixture, 'load_aux_file', 'main.tex') set.root(fixture, 'load_aux_file', 'main.tex') await lw.cache.refreshCache(toParse) diff --git a/test/units/04_core_root.test.ts b/test/units/04_core_root.test.ts index 3abf24640..c3773a04d 100644 --- a/test/units/04_core_root.test.ts +++ b/test/units/04_core_root.test.ts @@ -8,7 +8,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const fixture = path.basename(__filename).split('.')[0] before(() => { - mock.object(lw, 'file', 'watcher', 'cache', 'root') + mock.init(lw, 'file', 'watcher', 'cache', 'root') }) after(() => { @@ -16,16 +16,15 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('on root file deletion', () => { - beforeEach(async () => { - await set.config('latex.watch.delay', 100) + beforeEach(() => { + set.config('latex.watch.delay', 100) }) afterEach(() => { lw.watcher.src.reset() }) - it('should remove root info and refind', async function (this: Mocha.Context) { - this.slow(250) + it('should remove root info and refind', async () => { const texPath = get.path(fixture, 'another.tex') lw.root.file.path = texPath @@ -271,8 +270,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.root.findFromActive', () => { - beforeEach(async () => { - await set.config('latex.rootFile.indicator', '\\documentclass[]{}') + beforeEach(() => { + set.config('latex.rootFile.indicator', '\\documentclass[]{}') }) it('should do nothing if there is no active editor', async () => { @@ -341,7 +340,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.root.getIndicator', () => { it('should use \\begin{document} indicator on selecting `\\begin{document}`', async () => { - await set.config('latex.rootFile.indicator', '\\begin{document}') + set.config('latex.rootFile.indicator', '\\begin{document}') const texPath = get.path(fixture, 'main.tex') @@ -354,7 +353,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return \\documentclass indicator on other values', async () => { - await set.config('latex.rootFile.indicator', 'invalid value') + set.config('latex.rootFile.indicator', 'invalid value') const texPath = get.path(fixture, 'main.tex') @@ -368,19 +367,19 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.root.findInWorkspace', () => { - beforeEach(async () => { - await set.config('latex.rootFile.indicator', '\\begin{document}') // avoid active editor check + beforeEach(() => { + set.config('latex.rootFile.indicator', '\\begin{document}') // avoid active editor check }) it('should follow `latex.search.rootFiles.include` config', async () => { - await set.config('latex.search.rootFiles.include', [ 'absolutely-nothing.tex' ]) + set.config('latex.search.rootFiles.include', [ 'absolutely-nothing.tex' ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) }) it('should follow `latex.search.rootFiles.exclude` config', async () => { - await set.config('latex.search.rootFiles.exclude', [ '**/*' ]) + set.config('latex.search.rootFiles.exclude', [ '**/*' ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) @@ -389,8 +388,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should find the correct root from workspace', async () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) - await set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/**/parent.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/**/parent.tex` ]) await lw.root.find() assert.hasLog('Try finding root from current workspaceRootDir:') @@ -398,7 +397,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should ignore root file indicators in comments', async () => { - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/comment.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/comment.tex` ]) await lw.root.find() assert.strictEqual(lw.root.file.path, undefined) @@ -408,7 +407,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() stub.restore() @@ -421,8 +420,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'parent.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) - await set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/main.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.exclude', [ `${fixture}/find_workspace/main.tex` ]) await lw.cache.refreshCache(texPath) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() @@ -436,7 +435,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const texPath = get.path(fixture, 'find_workspace', 'parent.tex') const texPathAnother = get.path(fixture, 'find_workspace', 'another.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) await lw.cache.refreshCache(texPath) const stub = mock.activeTextEditor(texPathAnother, '\\documentclass{article}\n') await lw.root.find() @@ -449,7 +448,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should find the correct root if current root is in the candidates', async () => { const texPath = get.path(fixture, 'find_workspace', 'main.tex') - await set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) + set.config('latex.search.rootFiles.include', [ `${fixture}/find_workspace/**/*.tex` ]) set.root(fixture, 'find_workspace', 'main.tex') const stub = mock.activeTextEditor(texPath, '\\documentclass{article}\n') await lw.root.find() @@ -468,7 +467,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not change root if no new root can be found, only refresh outline', async () => { - await set.config('latex.search.rootFiles.exclude', [ '**/*.*' ]) + set.config('latex.search.rootFiles.exclude', [ '**/*.*' ]) await lw.root.find() @@ -517,8 +516,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual((lw.lint.label.reset as sinon.SinonStub).callCount, 1) }) - it('should watch, cache, and parse fls of the new root', async function (this: Mocha.Context) { - this.slow(300) + it('should watch, cache, and parse fls of the new root', async () => { const texPath = get.path(fixture, 'main.tex') const stub = mock.activeTextEditor(texPath, '%!TeX root=main.tex') diff --git a/test/units/05_compile_queue.test.ts b/test/units/05_compile_queue.test.ts index 7548f962f..37dda6af3 100644 --- a/test/units/05_compile_queue.test.ts +++ b/test/units/05_compile_queue.test.ts @@ -6,7 +6,7 @@ import { queue } from '../../src/compile/queue' describe(path.basename(__filename).split('.')[0] + ':', () => { before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') }) after(() => { diff --git a/test/units/06_compile_recipe.test.ts b/test/units/06_compile_recipe.test.ts index b860c454b..068310829 100644 --- a/test/units/06_compile_recipe.test.ts +++ b/test/units/06_compile_recipe.test.ts @@ -7,22 +7,21 @@ import { build, initialize } from '../../src/compile/recipe' import { queue } from '../../src/compile/queue' describe(path.basename(__filename).split('.')[0] + ':', () => { - const fixture = path.basename(__filename).split('.')[0] let getOutDirStub: sinon.SinonStub let getIncludedTeXStub: sinon.SinonStub let mkdirStub: sinon.SinonStub before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') getOutDirStub = sinon.stub(lw.file, 'getOutDir').returns('.') getIncludedTeXStub = lw.cache.getIncludedTeX as sinon.SinonStub mkdirStub = sinon.stub(lw.external, 'mkdirSync').returns(undefined) }) - beforeEach(async () => { + beforeEach(() => { initialize() getIncludedTeXStub.returns([]) - await set.config('latex.recipe.default', 'first') + set.config('latex.recipe.default', 'first') }) afterEach(() => { @@ -38,21 +37,19 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.compile->recipe', () => { - it('should set the LATEXWORKSHOP_DOCKER_LATEX environment variable based on the configuration', async function (this: Mocha.Context) { - this.slow(500) + it('should set the LATEXWORKSHOP_DOCKER_LATEX environment variable based on the configuration', async () => { const expectedImageName = 'your-docker-image' - await set.config('docker.image.latex', expectedImageName) + await set.codeConfig('docker.image.latex', expectedImageName) await sleep(150) assert.strictEqual(process.env['LATEXWORKSHOP_DOCKER_LATEX'], expectedImageName) }) - it('should set the LATEXWORKSHOP_DOCKER_PATH environment variable based on the configuration', async function (this: Mocha.Context) { - this.slow(500) + it('should set the LATEXWORKSHOP_DOCKER_PATH environment variable based on the configuration', async () => { const expectedDockerPath = '/usr/local/bin/docker' - await set.config('docker.path', expectedDockerPath) + await set.codeConfig('docker.path', expectedDockerPath) await sleep(150) assert.strictEqual(process.env['LATEXWORKSHOP_DOCKER_PATH'], expectedDockerPath) @@ -62,7 +59,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.compile->recipe.build', () => { it('should call `saveAll` before building', async () => { const stub = sinon.stub(vscode.workspace, 'saveAll') as sinon.SinonStub - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) stub.restore() @@ -71,10 +68,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should call `createOutputSubFolders` with correct args', async () => { - const rootFile = set.root(fixture, 'main.tex') - const subPath = get.path(fixture, 'sub', 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + const subPath = get.path('sub', 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) lw.root.subfiles.path = subPath getIncludedTeXStub.returns([rootFile, subPath]) @@ -83,10 +80,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should call `createOutputSubFolders` with correct args with subfiles package', async () => { - const rootFile = set.root(fixture, 'main.tex') - const subPath = get.path(fixture, 'sub', 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + const subPath = get.path('sub', 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) lw.root.subfiles.path = subPath getIncludedTeXStub.returns([rootFile, subPath]) @@ -95,9 +92,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not call buildLoop if no tool is created', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', []) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', []) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) const stub = sinon.stub() await build(rootFile, 'latex', stub) @@ -106,9 +103,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should set lw.compile.compiledPDFPath', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) await build(rootFile, 'latex', async () => {}) @@ -128,8 +125,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error if no recipe is found', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.recipes', []) + const rootFile = set.root('main.tex') + set.config('latex.recipes', []) await build(rootFile, 'latex', async () => {}) @@ -137,11 +134,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create build tools based on magic comments when enabled', async () => { - const rootFile = set.root(fixture, 'magic.tex') + const rootFile = set.root('magic.tex') readStub.resolves('% !TEX program = pdflatex\n') - await set.config('latex.recipes', []) - await set.config('latex.build.forceRecipeUsage', false) - await set.config('latex.magic.args', ['--shell-escape']) + set.config('latex.recipes', []) + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--shell-escape']) await build(rootFile, 'latex', async () => {}) @@ -153,9 +150,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error with magic comments but disabled', async () => { - const rootFile = set.root(fixture, 'magic.tex') - await set.config('latex.recipes', []) - await set.config('latex.build.forceRecipeUsage', true) + const rootFile = set.root('magic.tex') + set.config('latex.recipes', []) + set.config('latex.build.forceRecipeUsage', true) await build(rootFile, 'latex', async () => {}) @@ -163,9 +160,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should skip undefined tools in the recipe and log an error', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', [{ name: 'existingTool', command: 'pdflatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool', 'existingTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', [{ name: 'existingTool', command: 'pdflatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool', 'existingTool'] }]) await build(rootFile, 'latex', async () => {}) @@ -178,9 +175,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should do nothing but log an error if no tools are prepared', async () => { - const rootFile = set.root(fixture, 'main.tex') - await set.config('latex.tools', []) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) + const rootFile = set.root('main.tex') + set.config('latex.tools', []) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['nonexistentTool'] }]) await build(rootFile, 'latex', async () => {}) @@ -191,7 +188,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { describe('lw.compile->recipe.createOutputSubFolders', () => { beforeEach(() => { - getIncludedTeXStub.returns([ set.root(fixture, 'main.tex') ]) + getIncludedTeXStub.returns([ set.root('main.tex') ]) }) afterEach(() => { @@ -199,7 +196,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should resolve the output directory relative to the root directory if not absolute', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const expectedOutDir = path.resolve(path.dirname(rootFile), relativeOutDir) getOutDirStub.returns(relativeOutDir) @@ -210,7 +207,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should use the absolute output directory as is', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const absoluteOutDir = '/absolute/output' getOutDirStub.returns(absoluteOutDir) @@ -220,7 +217,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create the output directory if it does not exist', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const expectedOutDir = path.resolve(path.dirname(rootFile), relativeOutDir) const stub = sinon.stub(lw.file, 'exists').resolves(false) @@ -234,7 +231,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not create the output directory if it already exists', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const relativeOutDir = 'output' const stub = sinon.stub(lw.file, 'exists').resolves({ type: vscode.FileType.Directory, ctime: 0, mtime: 0, size: 0 }) getOutDirStub.returns(relativeOutDir) @@ -257,8 +254,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { queue.clear() }) - beforeEach(async () => { - await set.config('latex.build.forceRecipeUsage', false) + beforeEach(() => { + set.config('latex.build.forceRecipeUsage', false) }) afterEach(() => { @@ -335,8 +332,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should detect only LW recipe comment', async () => { - await set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }]) + set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }]) readStub.resolves('% !LW recipe = Recipe2\n') @@ -405,10 +402,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { queue.clear() }) - beforeEach(async () => { - await set.config('latex.build.forceRecipeUsage', false) - await set.config('latex.magic.args', ['--shell-escape']) - await set.config('latex.magic.bib.args', ['--min-crossrefs=1000']) + beforeEach(() => { + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--shell-escape']) + set.config('latex.magic.bib.args', ['--min-crossrefs=1000']) }) afterEach(() => { @@ -482,13 +479,13 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) describe('lw.compile->recipe.findRecipe', () => { - beforeEach(async () => { - await set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }, { name: 'Tool3', command: 'lualatex' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }, { name: 'Recipe3', tools: ['Tool3'] }]) + beforeEach(() => { + set.config('latex.tools', [{ name: 'Tool1', command: 'pdflatex' }, { name: 'Tool2', command: 'xelatex' }, { name: 'Tool3', command: 'lualatex' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool2'] }, { name: 'Recipe3', tools: ['Tool3'] }]) }) it('should do nothing but log an error if no recipes are defined', async () => { - await set.config('latex.recipes', []) + set.config('latex.recipes', []) await build('dummy.tex', 'latex', async () => {}) @@ -496,7 +493,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should use the default recipe name if recipeName is undefined', async () => { - await set.config('latex.recipe.default', 'Recipe2') + set.config('latex.recipe.default', 'Recipe2') await build('dummy.tex', 'latex', async () => {}) @@ -512,7 +509,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return the last used recipe if defaultRecipeName is `lastUsed`', async () => { - await set.config('latex.recipe.default', 'lastUsed') + set.config('latex.recipe.default', 'lastUsed') await build('dummy.tex', 'latex', async () => {}, 'Recipe2') queue.clear() @@ -523,8 +520,22 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.strictEqual(step.name, 'Tool2') }) + it('should use the updated new tools in the last used recipe if defaultRecipeName is `lastUsed`', async () => { + set.config('latex.recipe.default', 'lastUsed') + + await build('dummy.tex', 'latex', async () => {}, 'Recipe2') + queue.clear() + + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['Tool1'] }, { name: 'Recipe2', tools: ['Tool3'] }, { name: 'Recipe3', tools: ['Tool3'] }]) + await build('dummy.tex', 'latex', async () => {}) + + const step = queue.getStep() + assert.ok(step) + assert.strictEqual(step.name, 'Tool3') + }) + it('should reset prevRecipe if the language ID changes', async () => { - await set.config('latex.recipe.default', 'lastUsed') + set.config('latex.recipe.default', 'lastUsed') await build('dummy.tex', 'latex', async () => {}, 'Recipe2') queue.clear() @@ -536,7 +547,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should return the first matching recipe based on langId if no recipe is found', async () => { - await set.config('latex.recipes', [ + set.config('latex.recipes', [ { name: 'recipe1', tools: [] }, { name: 'rsweave Recipe', tools: [] }, { name: 'weave.jl Recipe', tools: [] }, @@ -600,9 +611,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { extRoot = lw.extensionRoot }) - beforeEach(async () => { - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) - await set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) + beforeEach(() => { + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.recipes', [{ name: 'Recipe1', tools: ['latexmk'] }]) }) afterEach(() => { @@ -621,7 +632,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should modify command when Docker is enabled on Windows', async () => { - await set.config('docker.enabled', true) + set.config('docker.enabled', true) setPlatform('win32') lw.extensionRoot = '/path/to/extension' @@ -633,7 +644,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should modify command and chmod when Docker is enabled on non-Windows', async () => { - await set.config('docker.enabled', true) + set.config('docker.enabled', true) setPlatform('linux') lw.extensionRoot = '/path/to/extension' @@ -648,7 +659,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not modify command when Docker is disabled', async () => { - await set.config('docker.enabled', false) + set.config('docker.enabled', false) await build('dummy.tex', 'latex', async () => {}) @@ -658,8 +669,8 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should replace argument placeholders', async () => { - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['%DOC%', '%DOC%', '%DIR%'], env: {} }]) - const rootFile = set.root(fixture, 'main.tex') + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['%DOC%', '%DOC%', '%DIR%'], env: {} }]) + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -667,11 +678,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.pathStrictEqual(step.args?.[0], rootFile.replace('.tex', '')) assert.pathStrictEqual(step.args?.[1], rootFile.replace('.tex', '')) - assert.pathStrictEqual(step.args?.[2], get.path(fixture)) + assert.pathStrictEqual(step.args?.[2], get.path('')) }) it('should set TeX directories correctly', async () => { - await set.config('latex.tools', [ + set.config('latex.tools', [ { name: 'latexmk', command: 'latexmk', @@ -679,7 +690,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { env: {}, }, ]) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') const stub = sinon.stub(lw.file, 'setTeXDirs') await build(rootFile, 'latex', async () => {}) @@ -689,7 +700,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should process environment variables correctly', async () => { - await set.config('latex.tools', [ + set.config('latex.tools', [ { name: 'latexmk', command: 'latexmk', @@ -697,7 +708,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { env: { DOC: '%DOC%' }, }, ]) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -707,10 +718,10 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should append max print line arguments when enabled', async () => { - await set.config('latex.option.maxPrintLine.enabled', true) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + set.config('latex.option.maxPrintLine.enabled', true) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) syncStub.returns({ stdout: 'pdfTeX 3.14159265-2.6-1.40.21 (MiKTeX 2.9.7350 64-bit)' }) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -718,7 +729,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.ok(step.args?.includes('--max-print-line=' + lw.constant.MAX_PRINT_LINE), step.args?.join(' ')) - await set.config('latex.tools', [{ name: 'latexmk', command: 'pdflatex' }]) + set.config('latex.tools', [{ name: 'latexmk', command: 'pdflatex' }]) initialize() await build(rootFile, 'latex', async () => {}) @@ -726,7 +737,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { assert.ok(step) assert.ok(step.args?.includes('--max-print-line=' + lw.constant.MAX_PRINT_LINE), step.args?.join(' ')) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['--lualatex'] }]) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk', args: ['--lualatex'] }]) initialize() await build(rootFile, 'latex', async () => {}) @@ -759,9 +770,9 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { syncStub = sinon.stub(lw.external, 'sync') }) - beforeEach(async () => { - await set.config('latex.option.maxPrintLine.enabled', true) - await set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) + beforeEach(() => { + set.config('latex.option.maxPrintLine.enabled', true) + set.config('latex.tools', [{ name: 'latexmk', command: 'latexmk' }]) }) afterEach(() => { @@ -774,7 +785,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { it('should not consider MikTeX logic when pdflatex command fails', async () => { syncStub.throws(new Error('Command failed')) - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build(rootFile, 'latex', async () => {}) @@ -785,7 +796,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should not execute compile program again to determine MikTeX if already executed and cached', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') syncStub.returns({ stdout: 'pdfTeX 3.14159265-2.6-1.40.21 (MiKTeX 2.9.7350 64-bit)' }) await build(rootFile, 'latex', async () => {}) diff --git a/test/units/07_compile_external.test.ts b/test/units/07_compile_external.test.ts index 5b1fc9380..c1aab1ab2 100644 --- a/test/units/07_compile_external.test.ts +++ b/test/units/07_compile_external.test.ts @@ -9,10 +9,8 @@ import { build } from '../../src/compile/external' import type { ExternalStep } from '../../src/types' describe(path.basename(__filename).split('.')[0] + ':', () => { - const fixture = path.basename(__filename).split('.')[0] - before(() => { - mock.object(lw, 'file', 'root') + mock.init(lw, 'file', 'root') }) after(() => { @@ -33,7 +31,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should create a Tool object representing the build command and arguments', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) @@ -67,7 +65,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { const stub = sinon.stub().returnsArg(0) const replaceStub = sinon.stub(lwUtils, 'replaceArgumentPlaceholders').returns(stub) const pathStub = sinon.stub(lw.file, 'getPdfPath').returns('main.pdf') - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) replaceStub.restore() @@ -92,11 +90,11 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should set the compiledPDFPath if a root file is provided', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) - assert.pathStrictEqual(lw.compile.compiledPDFPath, get.path(fixture, 'main.pdf')) + assert.pathStrictEqual(lw.compile.compiledPDFPath, get.path('main.pdf')) }) it('should not set the compiledPDFPath if no root file is provided', async () => { @@ -114,7 +112,7 @@ describe(path.basename(__filename).split('.')[0] + ':', () => { }) it('should add the build tool to the queue for execution', async () => { - const rootFile = set.root(fixture, 'main.tex') + const rootFile = set.root('main.tex') await build('command', ['arg1', 'arg2'], '/cwd', sinon.stub(), rootFile) diff --git a/test/units/08_compile_build.test.ts b/test/units/08_compile_build.test.ts new file mode 100644 index 000000000..9fbcaf79f --- /dev/null +++ b/test/units/08_compile_build.test.ts @@ -0,0 +1,544 @@ +import * as vscode from 'vscode' +import type { SpawnOptions } from 'child_process' +import * as cs from 'cross-spawn' +import * as path from 'path' +import * as sinon from 'sinon' +import { assert, get, log, mock, set, sleep } from './utils' +import { lw } from '../../src/lw' +import { autoBuild, build } from '../../src/compile/build' +import * as pick from '../../src/utils/quick-pick' +import { terminate } from '../../src/compile/terminate' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + let activeStub: sinon.SinonStub + let findStub: sinon.SinonStub + + before(() => { + mock.init(lw, 'file', 'root') + ;(lw.cache.getIncludedTeX as sinon.SinonStub).returns([get.path('main.tex')]) + findStub = sinon.stub(lw.root, 'find') + ;(lw.extra.clean as sinon.SinonStub).resolves(Promise.resolve()) + }) + + beforeEach(() => { + activeStub = mock.activeTextEditor(get.path('main.tex'), '', { languageId: 'latex' }) + findStub.callsFake(() => { + set.root('main.tex') + return Promise.resolve(undefined) + }) + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'exit 0;'] }, + { name: 'bad', command: 'bash', args: ['-c', 'exit 1;'] }, + ]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool'] }]) + }) + + afterEach(() => { + activeStub.restore() + findStub.resetHistory() + }) + + after(() => { + sinon.restore() + }) + + describe('lw.compile->build.build', () => { + it('should do nothing if there is no active text editor', async () => { + activeStub.restore() + + await build() + + assert.hasLog('Cannot start to build because the active editor is undefined.') + }) + + it('should try find root if not given as an argument', async () => { + await build() + + assert.ok(findStub.called) + }) + + it('should skip finding root if given as an argument', async () => { + await build(false, get.path('alt.tex'), 'latex') + + assert.ok(!findStub.called) + }) + + it('should use the correct root file if not given as an argument', async () => { + set.root('main.tex') + + await build() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + }) + + it('should use external command to build project if set', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 0;#external']) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 0;#external"].') + }) + + it('should use the current pwd as external command cwd', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'echo $PWD']) + + await build() + + assert.pathStrictEqual( + get.compiler.log().split('\n')[0].trim(), + path.dirname(get.path('main.tex')).replace(/^([a-zA-Z]):/, (_, p1: string) => '\\' + p1.toLowerCase()) + ) + }) + + it('should do nothing if cannot find root and not external', async () => { + findStub.callsFake(() => { + lw.root.file.path = undefined + lw.root.file.langId = undefined + return Promise.resolve(undefined) + }) + + await build() + + assert.hasLog('Cannot find LaTeX root file. See') + }) + + it('should let use pick root file when subfile is detected', async () => { + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.file.langId = 'latex' + const stub = sinon.stub(pick, 'pickRootPath').resolves(get.path('subfile.tex')) + + await build() + + lw.root.subfiles.path = undefined + lw.root.file.langId = undefined + stub.restore() + + assert.hasLog(`Building root file: ${get.path('subfile.tex')}`) + }) + + it('should skip picking root file if `skipSelection` is `true`', async () => { + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.file.langId = 'latex' + const stub = sinon.stub(pick, 'pickRootPath').resolves(get.path('subfile.tex')) + + await build(true) + + lw.root.subfiles.path = undefined + lw.root.file.langId = undefined + stub.restore() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + }) + }) + + describe('lw.compile->build.buildLoop', () => { + it('should not loop a new build if another one is ongoing', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'sleep .5;exit 0;'] }]) + + const buildPromise = build() + await build() + await buildPromise + + assert.hasLog('Another build loop is already running.') + }) + + it('should increment `lw.compile.compiledPDFWriting` to avoid PDF refresh during compilation', async () => { + await build() + + assert.ok(lw.compile.compiledPDFWriting > 0, lw.compile.compiledPDFWriting.toString()) + + await new Promise((resolve) => + setTimeout( + resolve, + (vscode.workspace.getConfiguration('latex-workshop').get('latex.watch.pdf.delay') as number) + 100 + ) + ) + + assert.strictEqual(lw.compile.compiledPDFWriting, 0) + }) + + it('should handle multiple steps one by one', async () => { + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 0;"].') + assert.hasLog('Recipe step 2 The command is bash:["-c","exit 0;"].') + }) + + it('should early-terminate if a step returns non-zero code', async () => { + set.config('latex.recipes', [{ name: 'recipe', tools: ['bad', 'tool'] }]) + set.config('latex.autoBuild.cleanAndRetry.enabled', false) + + await build() + + assert.hasLog('Recipe step 1 The command is bash:["-c","exit 1;"].') + assert.notHasLog('Recipe step 2 The command is bash:["-c","exit 0;"].') + }) + + it('should correctly set `skipped` flag to `true` for skipped latexmk steps', async () => { + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'echo "Latexmk: All targets (build) are up-to-date";'] }, + ]) + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(!stub.called) + + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'exit 0;'] }]) + + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + }) + + describe('lw.compile->build.spawnProcess', () => { + let readStub: sinon.SinonStub + + before(() => { + readStub = sinon.stub(lw.file, 'read') + }) + + after(() => { + readStub.restore() + }) + + + it('should respect `latex.build.clearLog.everyRecipeStep.enabled` config', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'echo 1;'] }]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + set.config('latex.build.clearLog.everyRecipeStep.enabled', true) + + await build() + const stepLog = get.compiler.log() + + set.config('latex.build.clearLog.everyRecipeStep.enabled', false) + await build() + assert.strictEqual(stepLog + stepLog, get.compiler.log()) + }) + + it('should handle magic comment %!TEX program', async () => { + readStub.resolves('% !TEX program = echo\n') + set.config('latex.build.forceRecipeUsage', false) + set.config('latex.magic.args', ['--arg1', '--arg2']) + + await build() + assert.strictEqual(get.compiler.log().trim(), '--arg1 --arg2') + }) + + it('should handle magic comment % !TEX program with % !TEX options', async () => { + readStub.resolves('% !TEX program = echo\n% !TEX options = --arg1 --arg2\n') + set.config('latex.build.forceRecipeUsage', false) + + await build() + assert.strictEqual(get.compiler.log().trim(), '--arg1 --arg2') + }) + + it('should use the root file directory as cwd when building', async () => { + set.root('main.tex') + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.pathStrictEqual(spawnSpy.getCall(0)?.args?.[2].cwd?.toString(), path.dirname(get.path('main.tex'))) + }) + + it('should change current working directory to root when using subfiles to compile sub files', async () => { + set.root('main.tex') + set.config('latex.tools', [ + { name: 'tool', command: 'latexmk', args: [] }, + ]) + lw.root.subfiles.path = get.path('sub/subfile.tex') + + const spawnStub = sinon.stub(lw.external, 'spawn') + let lastSpawnArgs: [command: string, args: readonly string[], options: SpawnOptions] | undefined + spawnStub.callsFake((...args) => { + lastSpawnArgs = args + return cs.spawn('true') + }) + + await build(true, get.path('sub/subfile.tex'), 'latex') + spawnStub.restore() + + lw.root.subfiles.path = undefined + + assert.pathStrictEqual(lastSpawnArgs?.[2].cwd?.toString(), path.dirname(get.path('main.tex'))) + }) + + it('should set and use `max_print_line` envvar when building', async () => { + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.strictEqual(spawnSpy.getCall(0)?.args?.[2].env?.['max_print_line'], lw.constant.MAX_PRINT_LINE) + }) + + it('should spawn external commands with no env', async () => { + set.config('latex.external.build.command', 'echo') + const spawnSpy = sinon.spy(lw.external, 'spawn') + + await build() + spawnSpy.restore() + + assert.strictEqual(spawnSpy.getCall(0)?.args?.[2].env, undefined) + }) + }) + + describe('lw.compile->build.monitorProcess', () => { + it('should handle process error', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'absolutely-nonexistent', args: [] }]) + + await build() + + assert.hasLog('LaTeX fatal error on PID') + assert.hasLog('Error: spawn absolutely-nonexistent ENOENT') + }) + + it('should stop the recipe on process error', async () => { + set.config('latex.tools', [{ name: 'tool', command: 'absolutely-nonexistent', args: [] }]) + set.config('latex.recipes', [{ name: 'recipe', tools: ['tool', 'tool'] }]) + + await build() + + assert.notHasLog('Recipe step 2') + }) + + it('should retry building on non-zero exit code and `latex.autoBuild.cleanAndRetry.enabled`', async () => { + set.config('latex.autoBuild.cleanAndRetry.enabled', true) + set.config('latex.autoClean.run', 'onFailed') + set.config('latex.recipes', [{ name: 'recipe', tools: ['bad'] }]) + + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + + // First build + assert.hasLog('Cleaning auxiliary files and retrying build after toolchain error.') + // Second build + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 2) + }) + + it('should handle external command failure', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 1;#external']) + + await build() + + assert.hasLog('Build with external command returns error') + }) + + it('should silently omit user termination', async () => { + set.config('latex.autoBuild.cleanAndRetry.enabled', false) + set.config('latex.tools', [{ name: 'tool', command: 'bash', args: ['-c', 'sleep 10;exit 0;'] }]) + + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + const promise = build() + let spawned = false + while (!spawned) { + try { + assert.hasLog('Recipe step 1 The command is bash:["-c","sleep 10;exit 0;"].') + spawned = true + } catch (_) { + await sleep(10) + } + } + terminate() + await promise + + assert.notHasLog('Cleaning auxiliary files and retrying build after toolchain error.') + assert.notHasLog('Build with external command returns error') + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 0) + }) + }) + + describe('lw.compile->build.afterSuccessfulBuilt', () => { + it('should refresh the viewer after successful external command build, nothing else', async () => { + set.config('latex.external.build.command', 'bash') + set.config('latex.external.build.args', ['-c', 'exit 0;#external']) + set.root('main.tex') + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + assert.notHasLog(`Successfully built ${get.path('main.tex')}`) + }) + + it('should not refresh viewer if the build is a skipped latexmk one', async () => { + set.config('latex.tools', [ + { name: 'tool', command: 'bash', args: ['-c', 'echo "Latexmk: All targets (build) are up-to-date";'] }, + ]) + + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(!stub.called) + }) + + it('should refresh viewer on general successful builds', async () => { + const stub = lw.viewer.refresh as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should call `lw.completion.reference.setNumbersFromAuxFile` to set reference numbers', async () => { + const stub = lw.completion.reference.setNumbersFromAuxFile as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should load generated .fls file in cache', async () => { + const stub = lw.cache.loadFlsFile as sinon.SinonStub + stub.resetHistory() + await build() + + assert.ok(stub.called) + }) + + it('should call syncTeX only if the viewer is in `external` mode', async () => { + set.config('view.pdf.viewer', 'external') + set.config('synctex.afterBuild.enabled', true) + + await build() + + assert.hasLog('SyncTex after build invoked.') + }) + + it('should not call syncTeX if the viewer is not in `external` mode', async () => { + set.config('view.pdf.viewer', 'tab') + set.config('synctex.afterBuild.enabled', true) + + await build() + + assert.notHasLog('SyncTex after build invoked.') + }) + + it('should not call syncTeX if `synctex.afterBuild.enabled` is false', async () => { + set.config('view.pdf.viewer', 'external') + set.config('synctex.afterBuild.enabled', false) + + await build() + + assert.notHasLog('SyncTex after build invoked.') + }) + + it('should auto-clean if `latex.autoClean.run` is `onSucceeded` or `onBuilt`', async () => { + set.config('latex.autoClean.run', 'onSucceeded') + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 1) + + set.config('latex.autoClean.run', 'onBuilt') + ;(lw.extra.clean as sinon.SinonStub).resetHistory() + await build() + assert.strictEqual((lw.extra.clean as sinon.SinonStub).callCount, 1) + }) + }) + + describe('lw.compile->build.autoBuild', () => { + beforeEach(() => { + lw.compile.lastAutoBuildTime = 0 + }) + + it('should not trigger auto-build if invoking event is not set in config', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + log.start() + await autoBuild(get.path('main.tex'), 'onSave') + log.stop() + assert.notHasLog('Auto build started') + + set.config('latex.autoBuild.run', 'onSave') + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + assert.notHasLog('Auto build started') + }) + + it('should not trigger auto-build if the last auto-build is too soon', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + set.config('latex.autoBuild.delay', 10000) + + await autoBuild(get.path('main.tex'), 'onFileChange') + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + + assert.hasLog('Auto build started') + assert.hasLog('Autobuild temporarily disabled.') + + lw.compile.lastAutoBuildTime = 0 + log.start() + await autoBuild(get.path('main.tex'), 'onFileChange') + log.stop() + + assert.notHasLog('Autobuild temporarily disabled.') + }) + + it('should auto-build subfiles if `latex.rootFile.useSubFile` is true and no bib change is detected', async () => { + set.config('latex.autoBuild.run', 'onFileChange') + set.config('latex.rootFile.useSubFile', true) + set.root('main.tex') + lw.root.subfiles.path = get.path('subfile.tex') + lw.root.subfiles.langId = 'latex' + + log.start() + await autoBuild(get.path('subfile.tex'), 'onFileChange', false) + log.stop() + + lw.root.subfiles.path = undefined + lw.root.subfiles.langId = undefined + + assert.hasLog(`Building root file: ${get.path('subfile.tex')}`) + assert.notHasLog(`Building root file: ${get.path('main.tex')}`) + + lw.compile.lastAutoBuildTime = 0 + log.start() + await autoBuild(get.path('subfile.tex'), 'onFileChange', false) + log.stop() + + assert.hasLog(`Building root file: ${get.path('main.tex')}`) + assert.notHasLog(`Building root file: ${get.path('subfile.tex')}`) + }) + + it('should auto-build when watched source is changed', () => { + set.config('latex.autoBuild.run', 'onFileChange') + + log.start() + for (const handler of lw.watcher.src['onChangeHandlers']) { + try { + handler(vscode.Uri.file(get.path('main.tex'))) + } catch (_) { } + } + log.stop() + + assert.hasLog(`Auto build started detecting the change of a file: ${get.path('main.tex')}`) + }) + + it('should auto-build when watched bib file is changed', () => { + set.config('latex.autoBuild.run', 'onFileChange') + + log.start() + for (const handler of lw.watcher.bib['onChangeHandlers']) { + try { + handler(vscode.Uri.file(get.path('main.bib'))) + } catch (_) { } + } + log.stop() + + assert.hasLog(`Auto build started detecting the change of a file: ${get.path('main.bib')}`) + }) + }) +}) diff --git a/test/units/09_viewer_server.test.ts b/test/units/09_viewer_server.test.ts new file mode 100644 index 000000000..6a57d3b34 --- /dev/null +++ b/test/units/09_viewer_server.test.ts @@ -0,0 +1,188 @@ +import * as vscode from 'vscode' +import * as path from 'path' +import * as ws from 'ws' +import * as sinon from 'sinon' +import fetch from 'node-fetch' +import { lw } from '../../src/lw' +import { assert, get, mock, set, sleep } from './utils' +import type { ClientRequest, PdfViewerParams } from '../../types/latex-workshop-protocol-types' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + const fixture = path.basename(__filename).split('.')[0] + let handlerStub: sinon.SinonStub + let websocket: ws.WebSocket + + before(async () => { + mock.init(lw, 'file', 'root', 'server') + handlerStub = lw.viewer.handler as sinon.SinonStub + await connectWs() + }) + + after(() => { + sinon.restore() + }) + + async function connectWs() { + const serverPath = `ws://127.0.0.1:${lw.server.getPort()}` + websocket = new ws.WebSocket(serverPath) + + await new Promise((resolve) => { + websocket.on('open', resolve) + }) + } + + describe('lw.viewer->server.WsServer', () => { + it('should handle websocket messages', async () => { + handlerStub.resetHistory() + websocket.send(JSON.stringify({ type: 'ping' })) + let elapsed = 0 + while (true) { + if ( + handlerStub.called && + (JSON.parse((handlerStub.lastCall.args?.[1] as Uint8Array)?.toString()) as ClientRequest).type === 'ping' + ) { + break + } + await sleep(10) + elapsed += 10 + if (elapsed >= 1000) { + assert.fail('Timed out waiting for message "ping"') + } + } + }) + }) + + describe('lw.viewer->server.initialize', () => { + async function waitInitialize(hostname?: string, newPort?: number, timeout = 1000) { + const originalPort = lw.server.getPort() + if (newPort === undefined || newPort === originalPort) { + newPort = (newPort ?? originalPort) + 1 + } + set.config('view.pdf.internal.port', newPort) + lw.server.initialize(hostname) + + let elapsed = 0 + while(true) { + try { + const port = lw.server.getPort() + if (port !== undefined && port !== originalPort) { + break + } + } catch {} + await sleep(10) + elapsed += 10 + if (elapsed >= timeout) { + assert.fail('Timed out waiting for server initialization.') + } + } + } + + it('should create a server at port defined by `view.pdf.internal.port` ', async () => { + const newPort = 34567 !== lw.server.getPort() ? 34567 : 45678 + await waitInitialize(undefined, newPort) + + assert.strictEqual(lw.server.getPort(), newPort) + }) + + it('should warn in log that a hostname is set', async () => { + await waitInitialize('::1') + + assert.hasLog('BE AWARE: YOU ARE PUBLIC TO ::1 !') + }) + + after(async () => { + await waitInitialize() + await connectWs() + }) + }) + + describe('lw.viewer->server.handler', () => { + it('should be set up to the http server', async () => { + const url = await lw.server.getUrl() + const res = await fetch(url.url + '/non-existent-file.html') + assert.ok(res.status === 404 || res.status === 500) + }) + + it('should retrieve and return PDF content', async () => { + const stub = lw.viewer.isViewing as sinon.SinonStub + stub.returns(true) + const url = await lw.server.getUrl(vscode.Uri.file(get.path(fixture, 'main.pdf'))) + const res = await fetch(url.url.replaceAll('viewer.html?file=', '')) + stub.resetBehavior() + + assert.strictEqual(res.headers.get('Content-Type'), 'application/pdf', JSON.stringify(res)) + }) + + it('should 404 if the retrieved PDF is not curently viewed', async () => { + const stub = lw.viewer.isViewing as sinon.SinonStub + stub.returns(false) + const url = await lw.server.getUrl(vscode.Uri.file(get.path(fixture, 'main.pdf'))) + const res = await fetch(url.url.replaceAll('viewer.html?file=', '')) + stub.resetBehavior() + + assert.strictEqual(res.status, 404, JSON.stringify(res)) + }) + + it('should 404 if the retrieved PDF cannot be read', async () => { + const stub = lw.viewer.isViewing as sinon.SinonStub + stub.returns(true) + const url = await lw.server.getUrl(vscode.Uri.file(get.path(fixture, 'non-existent.pdf'))) + const res = await fetch(url.url.replaceAll('viewer.html?file=', '')) + stub.resetBehavior() + + assert.strictEqual(res.status, 404, JSON.stringify(res)) + }) + + it('should return the default config on /config.json', async () => { + const stub = lw.viewer.getParams as sinon.SinonStub + stub.restore() + const viewerConfig = lw.viewer.getParams() + + const url = await lw.server.getUrl() + const res = await fetch(url.url + '/config.json') + const config = await res.json() as PdfViewerParams + sinon.stub(lw.viewer, 'getParams') + + assert.deepStrictEqual(config, viewerConfig) + }) + + it('should get pdf.js files under /build', async () => { + const url = await lw.server.getUrl() + const res = await fetch(url.url + '/build/pdf.min.mjs') + + assert.strictEqual(res.status, 200) + }) + + it('should get pdf.js files under /cmaps', async () => { + const url = await lw.server.getUrl() + const res = await fetch(url.url + '/cmaps/Adobe-CNS1-0.bcmap') + + assert.strictEqual(res.status, 200) + }) + + it('should get pdf.js files under /standard_fonts', async () => { + const url = await lw.server.getUrl() + const res = await fetch(url.url + '/standard_fonts/LiberationSans-Regular.ttf') + + assert.strictEqual(res.status, 200) + }) + + it('should get viewer files', async () => { + const url = await lw.server.getUrl() + let res = await fetch(url.url + '/viewer/latexworkshop.css') + assert.strictEqual(res.status, 200) + + res = await fetch(url.url + '/latexworkshop.css') + assert.strictEqual(res.status, 200) + }) + + it('should prevent directory traversal attack', async () => { + const url = await lw.server.getUrl() + let res = await fetch(url.url + '/build/../../sinon/package.json') + assert.strictEqual(res.status, 404) + + res = await fetch(url.url + '/build/%2e%2e/%2e%2e/sinon/package.json') + assert.strictEqual(res.status, 404) + }) + }) +}) diff --git a/test/units/10_viewer_pdf_server.test.ts b/test/units/10_viewer_pdf_server.test.ts new file mode 100644 index 000000000..fab61c415 --- /dev/null +++ b/test/units/10_viewer_pdf_server.test.ts @@ -0,0 +1,594 @@ +import * as vscode from 'vscode' +import * as os from 'os' +import * as path from 'path' +import * as ws from 'ws' +import * as sinon from 'sinon' +import { lw } from '../../src/lw' +import { locate, view, viewInWebviewPanel } from '../../src/preview/viewer' +import * as manager from '../../src/preview/viewer/pdfviewermanager' +import { assert, get, mock, set, sleep } from './utils' +import type { ClientRequest } from '../../types/latex-workshop-protocol-types' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + const fixture = path.basename(__filename).split('.')[0] + const pdfPath = get.path(fixture, 'main.pdf') + const pdfUri = vscode.Uri.file(pdfPath) + let handlerSpy: sinon.SinonSpy + + before(() => { + mock.init(lw, 'file', 'root', 'server', 'viewer') + handlerSpy = sinon.spy(lw.viewer, 'handler') + }) + + afterEach(async () => { + await vscode.commands.executeCommand('workbench.action.closeAllEditors') + }) + + after(() => { + sinon.restore() + }) + + function waitMessage(type: ClientRequest['type'], timeout = 1000) { + return (async () => { + handlerSpy.resetHistory() + let elapsed = 0 + while (true) { + if ( + handlerSpy.called && + (JSON.parse((handlerSpy.lastCall.args?.[1] as Uint8Array)?.toString()) as ClientRequest).type === type + ) { + break + } + await sleep(10) + elapsed += 10 + if (elapsed >= timeout) { + assert.fail(`Timed out waiting for message "${type}"`) + } + } + })() + } + + let wsMsg = '' + async function waitMsg(target: string, timeout = 1000) { + return (async () => { + wsMsg = '' + let elapsed = 0 + while (true) { + await sleep(10) + if (wsMsg === target) { + await sleep(100) + if (wsMsg === target) { + break + } else { + assert.fail(`Message overflown: ${wsMsg}`) + } + } + elapsed += 10 + if (elapsed >= timeout) { + assert.fail(`Timed out waiting for message "${target}": ${wsMsg}`) + } + } + })() + } + + describe('lw.viewer->viewer.viewInCustomEditor', () => { + let execSpy: sinon.SinonSpy + + before(() => { + execSpy = sinon.spy(vscode.commands, 'executeCommand') + }) + + beforeEach(() => { + set.config('view.pdf.viewer', 'tab') + execSpy.resetHistory() + }) + + after(() => { + execSpy.restore() + }) + + it('should create a custom editor', async () => { + const promise = waitMessage('loaded') + await view(pdfPath) + await promise + + assert.hasLog(`Open PDF tab for ${pdfUri.toString(true)}`) + }) + + it('should register the created panel in the viewer manager', async () => { + const promise = waitMessage('loaded') + await view(pdfPath) + await promise + + assert.strictEqual(manager.getPanels(pdfUri)?.size, 1) + assert.strictEqual(manager.getClients(pdfUri)?.size, 1) + }) + + it('should create the custom editor at the left group if focused on right', async () => { + set.config('view.pdf.tab.editorGroup', 'left') + mock.activeTextEditor('main.tex', '', { viewColumn: vscode.ViewColumn.Two }) + + await view(pdfPath) + + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'vscode.openWith') + assert.strictEqual((execSpy.firstCall.args[3] as vscode.TextDocumentShowOptions).viewColumn, 1) + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusRightGroup') + }) + + it('should create the custom editor and move to the left group if focused on left', async () => { + set.config('view.pdf.tab.editorGroup', 'left') + mock.activeTextEditor('main.tex', '', { viewColumn: vscode.ViewColumn.One }) + + await view(pdfPath) + + assert.strictEqual(execSpy.callCount, 3) + assert.strictEqual(execSpy.firstCall.args[0], 'vscode.openWith') + assert.strictEqual((execSpy.firstCall.args[3] as vscode.TextDocumentShowOptions).viewColumn, -1) + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.moveEditorToLeftGroup') + assert.strictEqual(execSpy.thirdCall.args[0], 'workbench.action.focusRightGroup') + }) + + it('should create the custom editor to the right', async () => { + set.config('view.pdf.tab.editorGroup', 'right') + mock.activeTextEditor('main.tex', '', { viewColumn: vscode.ViewColumn.One }) + + await view(pdfPath) + + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'vscode.openWith') + assert.strictEqual((execSpy.firstCall.args[3] as vscode.TextDocumentShowOptions).viewColumn, 2) + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusLeftGroup') + }) + + it('should create the custom editor and move to above or below', async () => { + set.config('view.pdf.tab.editorGroup', 'above') + mock.activeTextEditor('main.tex', '', { viewColumn: vscode.ViewColumn.One }) + + await view(pdfPath) + + assert.strictEqual(execSpy.callCount, 3) + assert.strictEqual(execSpy.firstCall.args[0], 'vscode.openWith') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.moveEditorToAboveGroup') + assert.strictEqual(execSpy.thirdCall.args[0], 'workbench.action.focusBelowGroup') + + execSpy.resetHistory() + set.config('view.pdf.tab.editorGroup', 'below') + await view(pdfPath) + assert.strictEqual(execSpy.callCount, 3) + assert.strictEqual(execSpy.firstCall.args[0], 'vscode.openWith') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.moveEditorToBelowGroup') + assert.strictEqual(execSpy.thirdCall.args[0], 'workbench.action.focusAboveGroup') + }) + }) + + describe('lw.viewer->viewer.viewInBrowser', () => { + let openStub: sinon.SinonStub + + before(() => { + openStub = sinon.stub(vscode.env, 'openExternal') + }) + + beforeEach(() => { + set.config('view.pdf.viewer', 'browser') + openStub.reset() + }) + + after(() => { + openStub.restore() + }) + + it('should open the viewer link with vscode.env.openExternal', async () => { + await view(pdfPath) + + assert.strictEqual(openStub.callCount, 1) + }) + + it('should register the created client sets in the viewer manager', async () => { + await view(pdfPath) + + assert.notStrictEqual(manager.getPanels(pdfUri), undefined) + assert.notStrictEqual(manager.getClients(pdfUri), undefined) + }) + + it('should watch the opened pdf', async () => { + const stub = lw.watcher.pdf['add'] as sinon.SinonStub + stub.resetHistory() + await view(pdfPath) + + assert.strictEqual(stub.callCount, 1) + }) + + it('should prompt the viewer link for user to open if cannot directly open', async () => { + openStub.rejects(new Error('Failed to open')) + const stub = sinon.stub(vscode.window, 'showInputBox').resolves() + + await view(pdfPath) + stub.restore() + + assert.strictEqual(stub.callCount, 1) + assert.strictEqual((stub.firstCall.args?.[0] as vscode.InputBoxOptions)?.value, (await lw.server.getUrl(pdfUri)).url) + }) + }) + + describe('lw.viewer->viewer.viewInWebviewPanel', () => { + let execSpy: sinon.SinonSpy + + before(() => { + execSpy = sinon.spy(vscode.commands, 'executeCommand') + }) + + beforeEach(() => { + execSpy.resetHistory() + }) + + after(() => { + execSpy.restore() + }) + + it('should create a webview panel', async () => { + const promise = waitMessage('loaded') + await viewInWebviewPanel(pdfUri, 'current', true) + await promise + + assert.hasLog(`Open PDF tab for ${pdfUri.toString(true)}`) + }) + + it('should register the created panel in the viewer manager', async () => { + const promise = waitMessage('loaded') + await viewInWebviewPanel(pdfUri, 'current', true) + await promise + + assert.strictEqual(manager.getPanels(pdfUri)?.size, 1) + assert.strictEqual(manager.getClients(pdfUri)?.size, 1) + }) + + it('should move the webview panel to the specified editor group', async () => { + const activeEditorStub = mock.activeTextEditor('main.tex', '') + + execSpy.resetHistory() + await viewInWebviewPanel(pdfUri, 'left', true) + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'workbench.action.moveEditorToLeftGroup') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusRightGroup') + + execSpy.resetHistory() + await viewInWebviewPanel(pdfUri, 'right', true) + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'workbench.action.moveEditorToRightGroup') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusLeftGroup') + + execSpy.resetHistory() + await viewInWebviewPanel(pdfUri, 'above', true) + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'workbench.action.moveEditorToAboveGroup') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusBelowGroup') + + execSpy.resetHistory() + await viewInWebviewPanel(pdfUri, 'below', true) + assert.strictEqual(execSpy.callCount, 2) + assert.strictEqual(execSpy.firstCall.args[0], 'workbench.action.moveEditorToBelowGroup') + assert.strictEqual(execSpy.secondCall.args[0], 'workbench.action.focusAboveGroup') + + activeEditorStub.restore() + }) + + it('should not move the webview panel if there is no active editor', async () => { + const activeEditorStub = sinon.stub(vscode.window, 'activeTextEditor').value(undefined) + await viewInWebviewPanel(pdfUri, 'left', true) + activeEditorStub.restore() + + assert.strictEqual(execSpy.callCount, 0) + }) + + it('should only move the webview panel but not focus back if `preserveFocus` is `false`', async () => { + const activeEditorStub = mock.activeTextEditor('main.tex', '') + + await viewInWebviewPanel(pdfUri, 'left', false) + + activeEditorStub.restore() + assert.strictEqual(execSpy.callCount, 1) + assert.strictEqual(execSpy.firstCall.args[0], 'workbench.action.moveEditorToLeftGroup') + }) + }) + + describe('lw.viewer->viewer.viewInTab', () => { + it('should create a webview panel', async () => { + set.config('view.pdf.viewer', 'legacy') + const promise = waitMessage('loaded') + await view(pdfPath, 'tab') + await promise + + assert.hasLog(`Open PDF tab for ${pdfUri.toString(true)}`) + }) + }) + + describe('lw.viewer->viewer.handler', () => { + let websocket: ws.WebSocket + + before(async () => { + const serverPath = `ws://127.0.0.1:${lw.server.getPort()}` + websocket = new ws.WebSocket(serverPath) + + await new Promise((resolve) => { + websocket.on('open', resolve) + }) + }) + + after(() => { + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + }) + + it('should handle `open` message and create a Client', async () => { + manager.create(pdfUri) + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + + const promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + + assert.strictEqual(manager.getClients(pdfUri)?.size, 1) + }) + + it('should handle `loaded` message and perform SyncTeX if enabled', async () => { + set.config('synctex.afterBuild.enabled', true) + const stub = lw.locate.synctex.toPDF as sinon.SinonStub + stub.reset() + + const promise = waitMessage('loaded') + websocket.send(JSON.stringify({ type: 'loaded', pdfFileUri: pdfUri.toString(true) })) + await promise + + assert.strictEqual(stub.callCount, 1) + }) + + it('should handle `loaded` message but skip SyncTeX if disabled', async () => { + set.config('synctex.afterBuild.enabled', false) + const stub = lw.locate.synctex.toPDF as sinon.SinonStub + stub.reset() + + const promise = waitMessage('loaded') + websocket.send(JSON.stringify({ type: 'loaded', pdfFileUri: pdfUri.toString(true) })) + await promise + + assert.strictEqual(stub.callCount, 0) + }) + + it('should handle `reverse_synctex` message and perform reverse SyncTeX', async () => { + const stub = lw.locate.synctex.toTeX as sinon.SinonStub + stub.reset() + + const promise = waitMessage('reverse_synctex') + websocket.send(JSON.stringify({ type: 'reverse_synctex', pdfFileUri: pdfUri.toString(true) })) + await promise + + assert.strictEqual(stub.callCount, 1) + }) + + it('should handle `external_link` message and opens http/https link', async () => { + let stub = sinon.stub(vscode.env, 'openExternal') + let promise = waitMessage('external_link') + websocket.send(JSON.stringify({ type: 'external_link', url: 'http://google.com' })) + await promise + stub.restore() + assert.strictEqual(stub.callCount, 1) + + stub = sinon.stub(vscode.env, 'openExternal') + promise = waitMessage('external_link') + websocket.send(JSON.stringify({ type: 'external_link', url: 'https://google.com' })) + await promise + stub.restore() + assert.strictEqual(stub.callCount, 1) + }) + + it('should handle `external_link` message and prompts non-http/https link', async () => { + const stub = sinon.stub(vscode.window, 'showInputBox') + const openStub = sinon.stub(vscode.env, 'openExternal') + const promise = waitMessage('external_link') + websocket.send(JSON.stringify({ type: 'external_link', url: 'file://some/private/file.txt' })) + await promise + stub.restore() + openStub.restore() + assert.strictEqual(stub.callCount, 1) + assert.strictEqual(openStub.callCount, 0) + }) + + it('should accept `ping` message and do nothing', async () => { + const promise = waitMessage('ping') + websocket.send(JSON.stringify({ type: 'ping' })) + await promise + }) + + it('should accept `add_log` message and add the message to LW log', async () => { + const promise = waitMessage('add_log') + const message = `This is a test message. ${Math.random()}` + websocket.send(JSON.stringify({ type: 'add_log', message })) + await promise + + assert.hasLog(message) + }) + + it('should accept `copy` message and copy the content to clipboard', async () => { + const writeTextStub = sinon.stub().resolves() + const stub = sinon.stub(vscode.env, 'clipboard').value({ writeText: writeTextStub }) + + const promise = waitMessage('copy') + websocket.send(JSON.stringify({ type: 'copy', isMetaKey: os.platform() === 'darwin', content: '' })) + await promise + + stub.restore() + + assert.strictEqual(writeTextStub.callCount, 1) + }) + + it('should accept `copy` message but does not copy the content if the wrong ctrl/meta key is pressed', async () => { + const writeTextStub = sinon.stub().resolves() + const stub = sinon.stub(vscode.env, 'clipboard').value({ writeText: writeTextStub }) + + const promise = waitMessage('copy') + websocket.send(JSON.stringify({ type: 'copy', isMetaKey: os.platform() !== 'darwin', content: '' })) + await promise + + stub.restore() + + assert.strictEqual(writeTextStub.callCount, 0) + }) + }) + + describe('lw.viewer->viewer.refresh', () => { + const altUri = pdfUri.with({ path: get.path(fixture, 'alt.pdf') }) + + before(async () => { + manager.create(pdfUri) + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + + manager.create(altUri) + manager.getPanels(altUri)?.clear() + manager.getClients(altUri)?.clear() + + const serverPath = `ws://127.0.0.1:${lw.server.getPort()}` + const websocket = new ws.WebSocket(serverPath) + websocket.on('message', (msg) => { wsMsg += msg.toString() }) + + await new Promise((resolve) => { + websocket.on('open', resolve) + }) + + let promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + + promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + + promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: altUri.toString(true) })) + await promise + }) + + after(() => { + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + manager.getPanels(altUri)?.clear() + manager.getClients(altUri)?.clear() + }) + + it('should refresh all viewers if not provided with an uri', async () => { + const promise = waitMsg(JSON.stringify({ type: 'refresh' }).repeat(3)) + lw.viewer.refresh() + await promise + }) + + it('should refresh selected viewers if provided with an uri', async () => { + const promise = waitMsg(JSON.stringify({ type: 'refresh' }).repeat(2)) + lw.viewer.refresh(pdfUri) + await promise + }) + + it('should do nothing if provided uri is not viewed', async () => { + const promise = waitMsg(JSON.stringify({ type: 'refresh' }).repeat(0)) + lw.viewer.refresh(pdfUri.with({ path: 'nonexistent.pdf' })) + await promise + }) + }) + + describe('lw.viewer->viewer.reload', () => { + before(async () => { + manager.create(pdfUri) + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + + const serverPath = `ws://127.0.0.1:${lw.server.getPort()}` + const websocket = new ws.WebSocket(serverPath) + websocket.on('message', (msg) => { wsMsg += msg.toString() }) + + await new Promise((resolve) => { + websocket.on('open', resolve) + }) + + let promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + + promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + }) + + after(() => { + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + }) + + it('should reload all viewers if config `view.pdf.invert` is changed', async () => { + const promise = waitMsg(JSON.stringify({ type: 'reload' }).repeat(2)) + await set.codeConfig('view.pdf.invert', 1) + await promise + }) + + it('should reload all viewers if config `view.pdf.invertMode` and children are changed', async () => { + const promise = waitMsg(JSON.stringify({ type: 'reload' }).repeat(2)) + await set.codeConfig('view.pdf.invertMode.enabled', 'auto') + await promise + }) + + it('should reload all viewers if config `view.pdf.color` and children changed', async () => { + const promise = waitMsg(JSON.stringify({ type: 'reload' }).repeat(2)) + await set.codeConfig('view.pdf.color.light.pageColorsForeground', 'black') + await promise + }) + + it('should reload all viewers if config `view.pdf.internal` is changed', async () => { + const promise = waitMsg(JSON.stringify({ type: 'reload' }).repeat(2)) + await set.codeConfig('view.pdf.internal.port', 12345) + await promise + }) + }) + + describe('lw.viewer->viewer.locate', () => { + before(async () => { + manager.create(pdfUri) + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + + const serverPath = `ws://127.0.0.1:${lw.server.getPort()}` + const websocket = new ws.WebSocket(serverPath) + websocket.on('message', (msg) => { wsMsg += msg.toString() }) + + await new Promise((resolve) => { + websocket.on('open', resolve) + }) + + let promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + + promise = waitMessage('open') + websocket.send(JSON.stringify({ type: 'open', pdfFileUri: pdfUri.toString(true) })) + await promise + }) + + after(() => { + manager.getPanels(pdfUri)?.clear() + manager.getClients(pdfUri)?.clear() + }) + + it('should perform SyncTeX', async () => { + const promise = waitMsg(JSON.stringify({ type: 'synctex', data: [] }).repeat(2)) + await locate(pdfPath, []) + await promise + }) + + it('should try opening the PDF if not already viewing', async () => { + const altPath = pdfPath.replaceAll('main.pdf', 'alt.pdf') + await locate(altPath, []) + + assert.hasLog(`PDF is not opened: ${altPath} , try opening.`) + assert.hasLog(`PDF cannot be opened: ${altPath} .`) + }) + }) +}) diff --git a/test/units/11_parser_tex.test.ts b/test/units/11_parser_tex.test.ts new file mode 100644 index 000000000..1e687cea5 --- /dev/null +++ b/test/units/11_parser_tex.test.ts @@ -0,0 +1,224 @@ +import * as path from 'path' +import * as sinon from 'sinon' +import { lw } from '../../src/lw' +import { assert, mock, set } from './utils' +import { parser } from '../../src/parse/parser' +import type * as Ast from '@unified-latex/unified-latex-types' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + before(() => { + mock.init(lw, 'file', 'root', 'parser') + }) + + after(() => { + sinon.restore() + }) + + describe('lw.parser->tex', () => { + it('should parse LaTeX content', async () => { + const ast = await parser.tex('\\documentclass{article}') + + assert.strictEqual(ast.content[0].type, 'macro') + assert.strictEqual(ast.content[0].content, 'documentclass') + }) + }) + + describe('lw.parser->bib', () => { + it('should parse BibTeX content', async () => { + const ast = await parser.bib('@article{key, author = "author"}') + + assert.ok(ast) + assert.strictEqual(ast.content[0].entryType, 'article') + assert.strictEqual(ast.content[0].internalKey, 'key') + assert.strictEqual(ast.content[0].content[0].name, 'author') + assert.strictEqual(ast.content[0].content[0].value.kind, 'text_string') + assert.strictEqual(ast.content[0].content[0].value.content, 'author') + }) + + it('should log error when parsing BibTeX content fails', async () => { + const ast = await parser.bib('@article{key, author = "author",') + + assert.strictEqual(ast, undefined) + assert.hasLog('Error when parsing bib file.') + }) + }) + + describe('lw.parser->args', () => { + function emptyArg(arg: Ast.Argument) { + assert.strictEqual(arg.content.length, 0) + } + + function strArg(arg: Ast.Argument) { + assert.strictEqual(arg.content[0].type, 'string') + } + + async function hasArgs(tex: string, signature: string) { + const node = (await parser.tex(tex)).content[0] + const args: ((arg: Ast.Argument) => void)[] = [] + for (const arg of signature.replaceAll(' ', '').split('')) { + args.push(arg === 's' ? strArg : emptyArg) + } + assert.strictEqual(node.type, 'macro') + + if (signature.length === 0) { + assert.strictEqual(node.args, undefined) + } else { + assert.strictEqual(node.args?.length, args.length) + for (let i = 0; i < args.length; i++) { + args[i](node.args[i]) + } + } + } + + it('should correctly parse arguments of `InputIfFileExists`', async () => { + await hasArgs('\\InputIfFileExists{file}', 's') + }) + + it('should correctly parse arguments of `SweaveInput`', async () => { + await hasArgs('\\SweaveInput{file}', 's') + }) + + it('should correctly parse arguments of `subfile`', async () => { + await hasArgs('\\SweaveInput{file}', 's') + }) + + it('should correctly parse arguments of `loadglsentries`', async () => { + await hasArgs('\\SweaveInput{file}', 's') + }) + + it('should correctly parse arguments of `markdownInput`', async () => { + await hasArgs('\\SweaveInput{file}', 's') + }) + + it('should correctly parse arguments of `import`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `inputfrom`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `includefrom`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `subimport`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `subinputfrom`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `subincludefrom`', async () => { + await hasArgs('\\import{folder}{file}', 'ss') + }) + + it('should correctly parse arguments of `linelabel`', async () => { + await hasArgs('\\linelabel{tag}', 'ees') + await hasArgs('\\linelabel[short]{tag}', 'ess') + await hasArgs('\\linelabel[short]{tag}', 'sss') + }) + + it('should correctly parse arguments of `newglossaryentry`', async () => { + await hasArgs('\\newglossaryentry{arg1}{arg2}', 'ss') + }) + + it('should correctly parse arguments of `provideglossaryentry`', async () => { + await hasArgs('\\provideglossaryentry{arg1}{arg2}', 'ss') + }) + + it('should correctly parse arguments of `longnewglossaryentry`', async () => { + await hasArgs('\\longnewglossaryentry{arg1}{arg2}{arg3}', 'esss') + await hasArgs('\\longnewglossaryentry[opt]{arg1}{arg2}{arg3}', 'ssss') + }) + + it('should correctly parse arguments of `longprovideglossaryentry`', async () => { + await hasArgs('\\longprovideglossaryentry{arg1}{arg2}{arg3}', 'esss') + await hasArgs('\\longprovideglossaryentry[opt]{arg1}{arg2}{arg3}', 'ssss') + }) + + it('should correctly parse arguments of `newacronym`', async () => { + await hasArgs('\\newacronym{arg1}{arg2}{arg3}', 'esss') + await hasArgs('\\newacronym[opt]{arg1}{arg2}{arg3}', 'ssss') + }) + + it('should correctly parse arguments of `newabbreviation`', async () => { + await hasArgs('\\newabbreviation{arg1}{arg2}{arg3}', 'esss') + await hasArgs('\\newabbreviation[opt]{arg1}{arg2}{arg3}', 'ssss') + }) + + it('should correctly parse arguments of `newabbr`', async () => { + await hasArgs('\\newabbr{arg1}{arg2}{arg3}', 'esss') + await hasArgs('\\newabbr[opt]{arg1}{arg2}{arg3}', 'ssss') + }) + + it('should correctly parse arguments of `newrobustcmd`', async () => { + await hasArgs('\\newrobustcmd{arg1}{arg2}', 'eesees') + await hasArgs('\\newrobustcmd*{arg1}[opt2][opt3]{arg2}', 'ssssss') + }) + + it('should correctly parse arguments of `renewrobustcmd`', async () => { + await hasArgs('\\renewrobustcmd{arg1}{arg2}', 'eesees') + await hasArgs('\\renewrobustcmd*{arg1}[opt2][opt3]{arg2}', 'ssssss') + }) + + it('should correctly parse arguments of `providerobustcmd`', async () => { + await hasArgs('\\providerobustcmd{arg1}{arg2}', 'esees') + await hasArgs('\\providerobustcmd*{arg1}[opt1][opt2]{arg2}', 'sssss') + }) + + it('should correctly parse arguments of `DeclareRobustCommand`', async () => { + await hasArgs('\\DeclareRobustCommand{arg1}{arg2}', 'esees') + await hasArgs('\\DeclareRobustCommand*{arg1}[opt1][opt2]{arg2}', 'sssss') + }) + + it('should correctly parse arguments of `DeclareMathOperator`', async () => { + await hasArgs('\\DeclareMathOperator{arg1}{arg2}', 'ess') + await hasArgs('\\DeclareMathOperator*{arg1}{arg2}', 'sss') + }) + + it('should correctly parse arguments of `DeclarePairedDelimiter`', async () => { + await hasArgs('\\DeclarePairedDelimiter{arg1}{arg2}{arg3}', 'sss') + }) + + it('should correctly parse arguments of `DeclarePairedDelimiterX`', async () => { + await hasArgs('\\DeclarePairedDelimiterX{arg1}{arg2}{arg3}{arg4}', 'sesss') + await hasArgs('\\DeclarePairedDelimiterX{arg1}[opt]{arg2}{arg3}{arg4}', 'sssss') + }) + + it('should correctly parse arguments of `DeclarePairedDelimiterXPP`', async () => { + await hasArgs('\\DeclarePairedDelimiterXPP{arg1}{arg2}{arg3}{arg4}{arg5}{arg6}', 'sesssss') + await hasArgs('\\DeclarePairedDelimiterXPP{arg1}[opt]{arg2}{arg3}{arg4}{arg5}{arg6}', 'sssssss') + }) + + it('should register and parse macros defined in `view.outline.commands`', async () => { + await hasArgs('\\randommacro{arg}', '') + + await set.codeConfig('view.outline.commands', ['randommacro']) + await hasArgs('\\randommacro{tag}', 'ees') + await hasArgs('\\randommacro[short]{tag}', 'ess') + await hasArgs('\\randommacro[short]{tag}', 'sss') + + }) + + it('should register and parse macros defined in `intellisense.label.command`', async () => { + await hasArgs('\\randommacro{arg}', '') + + await set.codeConfig('intellisense.label.command', ['randommacro']) + await hasArgs('\\randommacro{tag}', 'ees') + await hasArgs('\\randommacro[short]{tag}', 'ess') + await hasArgs('\\randommacro[short]{tag}', 'sss') + + }) + + it('should register and parse macros defined in `view.outline.sections`', async () => { + await hasArgs('\\randommacro{arg}', '') + + await set.codeConfig('view.outline.sections', ['randommacro']) + await hasArgs('\\randommacro{tag}', 'ees') + await hasArgs('\\randommacro[short]{tag}', 'ess') + await hasArgs('\\randommacro*[short]{tag}', 'sss') + }) + }) +}) diff --git a/test/units/12_parser_log.test.ts b/test/units/12_parser_log.test.ts new file mode 100644 index 000000000..75ff5bcea --- /dev/null +++ b/test/units/12_parser_log.test.ts @@ -0,0 +1,450 @@ +import * as path from 'path' +import * as sinon from 'sinon' +import { lw } from '../../src/lw' +import { assert, get, mock, set } from './utils' +import { parser } from '../../src/parse/parser' +import { bibtexLogParser } from '../../src/parse/parser/bibtexlog' +import { biberLogParser } from '../../src/parse/parser/biberlog' +import { latexLogParser } from '../../src/parse/parser/latexlog' + +describe(path.basename(__filename).split('.')[0] + ':', () => { + before(() => { + mock.init(lw, 'file', 'root', 'parser') + }) + + after(() => { + sinon.restore() + }) + + describe('lw.parser->log', () => { + let bibtexParserSpy: sinon.SinonSpy + let biberParserSpy: sinon.SinonSpy + let latexParserSpy: sinon.SinonSpy + + before(() => { + bibtexParserSpy = sinon.spy(bibtexLogParser, 'parse') + biberParserSpy = sinon.spy(biberLogParser, 'parse') + latexParserSpy = sinon.spy(latexLogParser, 'parse') + }) + + after(() => { + bibtexParserSpy.restore() + biberParserSpy.restore() + latexParserSpy.restore() + }) + + function resetSpies() { + bibtexParserSpy.resetHistory() + biberParserSpy.resetHistory() + latexParserSpy.resetHistory() + } + + it('should parse bibtex log', () => { + const log = `This is BibTeX, Version 0.99d (MiKTeX 2.9) +The top-level auxiliary file: main.aux +The style file: plain.bst +Database file #1: main.bib +Warning--I didn't find a database entry for "test" +(There was 1 warning) +` + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.calledOnce) + assert.ok(biberParserSpy.notCalled) + assert.ok(latexParserSpy.notCalled) + }) + + it('should parse biber log', () => { + const log = `INFO - This is Biber 2.14 +INFO - Logfile is 'test.blg' +INFO - Reading 'test.bcf' +INFO - Using all citekeys in bib section 0 +INFO - Processing section 0 +INFO - Globbing data source 'test.bib' +INFO - Globbed data source 'test.bib' to test.bib +INFO - Looking for bibtex format file 'test.bib' for section 0 +INFO - LaTeX decoding ... +INFO - Found BibTeX data source 'test.bib' +` + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.notCalled) + assert.ok(biberParserSpy.calledOnce) + assert.ok(latexParserSpy.notCalled) + }) + + it('should parse bibtex log in its alternative format', () => { + const log = `Reason: Input/output error +The 8-bit codepage and sorting file: 88591lat.csf +The top-level auxiliary file: test.aux +I couldn't open style file noexist.bst +---line 2 of file test.aux + : \\bibstyle{noexist + : } +I'm skipping whatever remains of this command +I found no style file---while reading file test.aux +(There were 2 error messages)` + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.calledOnce) + assert.ok(biberParserSpy.notCalled) + assert.ok(latexParserSpy.notCalled) + }) + + it('should trim and parse the last steps of latexmk log', () => { + const log = `Rule 'pdflatex': +------------ +Run number 1 of rule 'pdflatex' +------------ +Latexmk: applying rule 'pdflatex'... +Output written on ./main.pdf (1 page, 10 bytes). +LATEXMK LOG +Rule 'bibtex main': +------------ +Run number 1 of rule 'bibtex main' +------------ +Latexmk: applying rule 'bibtex main'... +------------ +Running 'bibtex "main"' +------------ +This is BibTeX, Version 0.99d (MiKTeX 2.9) +LATEXMK LOG +Rule 'pdflatex': +------------ +Run number 2 of rule 'pdflatex' +------------ +Latexmk: applying rule 'pdflatex'... +Output written on ./main.pdf (2 page, 20 bytes). +LATEXMK LOG +` + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.calledOnce) + assert.ok((bibtexParserSpy.firstCall.args[0] as string).includes('This is BibTeX')) + + assert.ok(latexParserSpy.calledOnce) + assert.ok((latexParserSpy.firstCall.args[0] as string).includes('(2 page, 20 bytes)')) + }) + + it('should trim and parse the last steps of texify log', () => { + const log = `running pdflatex +Output written on ./main.pdf (1 page, 10 bytes). +TEXIFY LOG +running miktex-bibtex +This is BibTeX, Version 0.99d (MiKTeX 2.9) +TEXIFY LOG +running pdflatex +Output written on ./main.pdf (2 page, 20 bytes). +TEXIFY LOG +` + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.calledOnce) + assert.ok((bibtexParserSpy.firstCall.args[0] as string).includes('This is BibTeX')) + + assert.ok(latexParserSpy.calledOnce) + assert.ok((latexParserSpy.firstCall.args[0] as string).includes('(2 page, 20 bytes)')) + }) + + it('should parse latex log with fatal error', () => { + const log = 'Fatal error occurred, no output PDF file produced!' + + resetSpies() + parser.log(log, 'main.tex') + + assert.ok(bibtexParserSpy.notCalled) + assert.ok(biberParserSpy.notCalled) + assert.ok(latexParserSpy.calledOnce) + }) + + it('should return skipped `true` if latexmk is skipped', () => { + const log = 'Latexmk: All targets (test) are up-to-date' + + resetSpies() + assert.ok(parser.log(log, 'main.tex')) + }) + + it('should return skipped `false` if latexmk is skipped, but is after some steps', () => { + const log = "Latexmk: applying rule 'pdflatex'...\nLatexmk: All targets (test) are up-to-date" + + resetSpies() + assert.ok(!parser.log(log, 'main.tex')) + }) + }) + + describe('lw.parser->latex', () => { + beforeEach(() => { + set.config('message.badbox.show', 'both') + }) + + it('should parse general LaTeX errors', () => { + const errorLogs = [ + 'main.tex:12: Undefined control sequence', + 'document.cls:45: Package geometry Error: Invalid margin value', + 'main.tex:7: LaTeX Error: Missing $ inserted', + 'report.tex:102: File `input.tex\' not found', + '! LaTeX Error: This is an error message', + '! Package pdftex.def Error: File image.png not found', + '! Undefined control sequence (in main.tex)', + '! LaTeX Error: Too many }\'s', + 'main.tex:12: Package amsmath Error: Missing }', + 'document.cls:45: Class article Error: Missing mandatory argument', + 'report.tex:15: LaTeX Error: Overfull \\hbox', + '! File `image.png\' not found', + '! Missing $ inserted', + '! Too many }\'s', + ] + + for (const log of errorLogs) { + const error = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error', log) + } + }) + + it('should finish parsing a LaTeX error on empty line', () => { + const errors = latexLogParser.parse('main.tex:12: Undefined control sequence\n\ndocument.cls:45: Package geometry Error: Invalid margin value', get.path('main.tex')) + + assert.strictEqual(errors.length, 2) + }) + + it('should handle multi-line LaTeX errors with line number', () => { + const log = `! Undefined control sequence. +l.4 \\draw + [->-=0.5] (0,0) -- (3,-2); + ` + + const error = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.errorPosText, ' \\draw') + }) + + it('should handle multi-line LaTeX errors without line number', () => { + const log = `! Undefined control sequence. +Test message` + + const error = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.text, 'Undefined control sequence.\nTest message') + }) + + const badboxLogs = [ + 'Overfull \\hbox (some text) in paragraph at lines 10--20', + 'Overfull \\vbox (more text) in paragraph at lines 5--15', + 'Overfull \\hbox (some text) detected at line 12', + 'Overfull \\vbox (more text) detected at line 7', + 'Overfull \\hbox (some text) has occurred while \\output is active', + 'Overfull \\vbox (more text) has occurred while \\output is active [5]', + 'Underfull \\hbox (some text) in paragraph at lines 8--18', + 'Underfull \\vbox (more text) in paragraph at lines 3--13', + 'Underfull \\hbox (some text) detected at line 11', + 'Underfull \\vbox (more text) detected at line 6', + 'Underfull \\hbox (some text) has occurred while \\output is active', + 'Underfull \\vbox (more text) has occurred while \\output is active [8]' + ] + + it('should parse over/underfull hbox/vbox warnings', () => { + for (const log of badboxLogs) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'typesetting', log) + } + }) + + it('should not parse bad box warnings if disabled', () => { + set.config('message.badbox.show', 'none') + + for (const log of badboxLogs) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning, undefined) + } + }) + + it('should only parse overfull box warnings if set', () => { + set.config('message.badbox.show', 'overfull') + + for (const log of badboxLogs.slice(0, badboxLogs.length / 2)) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'typesetting') + } + + for (const log of badboxLogs.slice(badboxLogs.length / 2)) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning, undefined) + } + }) + + it('should only parse underfull box warnings if set', () => { + set.config('message.badbox.show', 'underfull') + + for (const log of badboxLogs.slice(0, badboxLogs.length / 2)) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning, undefined) + } + + for (const log of badboxLogs.slice(badboxLogs.length / 2)) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'typesetting') + } + }) + + it('should parse the line number of bad box if provided', () => { + assert.strictEqual(latexLogParser.parse(badboxLogs[0], get.path('main.tex'))?.[0]?.line, 10) + assert.strictEqual(latexLogParser.parse(badboxLogs[1], get.path('main.tex'))?.[0]?.line, 5) + assert.strictEqual(latexLogParser.parse(badboxLogs[2], get.path('main.tex'))?.[0]?.line, 12) + assert.strictEqual(latexLogParser.parse(badboxLogs[3], get.path('main.tex'))?.[0]?.line, 7) + assert.strictEqual(latexLogParser.parse(badboxLogs[4], get.path('main.tex'))?.[0]?.line, 1) + assert.strictEqual(latexLogParser.parse(badboxLogs[5], get.path('main.tex'))?.[0]?.line, 1) + assert.strictEqual(latexLogParser.parse(badboxLogs[6], get.path('main.tex'))?.[0]?.line, 8) + assert.strictEqual(latexLogParser.parse(badboxLogs[7], get.path('main.tex'))?.[0]?.line, 3) + assert.strictEqual(latexLogParser.parse(badboxLogs[8], get.path('main.tex'))?.[0]?.line, 11) + assert.strictEqual(latexLogParser.parse(badboxLogs[9], get.path('main.tex'))?.[0]?.line, 6) + assert.strictEqual(latexLogParser.parse(badboxLogs[10], get.path('main.tex'))?.[0]?.line, 1) + assert.strictEqual(latexLogParser.parse(badboxLogs[11], get.path('main.tex'))?.[0]?.line, 1) + }) + + it('should parse the page number of bad box if provided', () => { + assert.strictEqual(latexLogParser.parse(badboxLogs[0], get.path('main.tex'))?.[0]?.text, 'Overfull \\hbox (some text)') + assert.strictEqual(latexLogParser.parse(badboxLogs[5], get.path('main.tex'))?.[0]?.text, 'Overfull \\vbox (more text) in page 5') + assert.strictEqual(latexLogParser.parse(badboxLogs[11], get.path('main.tex'))?.[0]?.text, 'Underfull \\vbox (more text) in page 8') + }) + + it('should go on parsing after the bad box warning even without line break', () => { + const log = badboxLogs[5] + badboxLogs[1] + const warnings = latexLogParser.parse(log, get.path('main.tex')) + + assert.strictEqual(warnings.length, 2) + }) + + it('should skip the first line after bad box warning', () => { + const log = badboxLogs[0] + '\n' + badboxLogs[1] + const warnings = latexLogParser.parse(log, get.path('main.tex')) + + assert.strictEqual(warnings.length, 1) + }) + + it('should parse class/package/module/LaTeX3 warnings/infos', () => { + const logs = [ + 'Class MyClass Warning: This is a warning message on input line 42.', + 'Package MyPackage Info: All systems operational.', + 'Module MyModule Warning: Something went wrong.', + 'LaTeX Info: Compilation successful.', + 'LaTeX3 Warning: Deprecated feature used on line 10.', + 'LaTeX3 Info: No issues found.', + ] + + for (const log of logs) { + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'warning') + } + }) + + it('should parse line number of class/package/module/LaTeX3 warnings/infos', () => { + assert.strictEqual(latexLogParser.parse('Class MyClass Warning: This is a warning message on input line 42.', get.path('main.tex'))?.[0].line, 42) + assert.strictEqual(latexLogParser.parse('Module MyModule Warning: Something went wrong.', get.path('main.tex'))?.[0].line, 1) + }) + + it('should parse line number of class/package/module/LaTeX3 warnings/infos if appeared in the next line', () => { + assert.strictEqual(latexLogParser.parse('Package hyperref Warning: Token not allowed in a PDF string (PDFDocEncoding):\n(hyperref) removing `math shift\' on input line 42.', get.path('main.tex'))?.[0].line, 42) + }) + + it('should parse missing character warnings', () => { + const log = 'Missing character: There is no ⫫ (U+2AEB) in font [latinmodern-math.otf]:mode=base;script=math;language=dflt;mathfontdimen=xetex;!' + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'warning') + }) + + it('should ignore empty bibliography env warning', () => { + const log = 'Empty `thebibliography\' environment' + const warnings = latexLogParser.parse(log, get.path('main.tex')) + assert.strictEqual(warnings.length, 0) + }) + + it('should parse bib entry not found warning', () => { + const log = 'Biber warning: WARN - I didn\'t find a database entry for \'nonexisting\' (section 0)' + const warning = latexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'warning') + assert.strictEqual(warning?.text, 'No bib entry found for \'nonexisting\'') + }) + }) + + describe('lw.parser->bibtex', () => { + it('should parse multi-line bibtex warning', () => { + const log = 'Warning--duplicate entry\n--line 45 of file sample.bib' + const warning = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'warning') + }) + + it('should parse single line bibtex warning', () => { + const stub = lw.completion.citation.getItem as sinon.SinonStub + stub.returns({ file: 'main.bib', position: { line: 1 } }) + const log = 'Warning--empty field in article1.bib' + const warning = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + stub.reset() + assert.strictEqual(warning?.type, 'warning') + assert.strictEqual(warning?.line, 2) + }) + + it('should parse multi-line bibtex error', () => { + const log = 'Error---line 23 of file references.bib\nThere\'s a problem with the syntax\nI\'m skipping whatever remains of this entry' + const error = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error') + }) + + it('should parse bad cross-reference error', () => { + const log = 'A bad cross reference---entry "article1"\nrefers to entry "article2", which doesn\'t exist' + const error = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error') + }) + + it('should parse multi-line macro error', () => { + const log = 'Macro definition error\n---line 37 of file macros.bib\nUndefined macro \\cite\nI\'m skipping whatever remains of this command' + const error = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error') + assert.strictEqual(error?.line, 37) + }) + + it('should parse `.aux` file error', () => { + const log = 'Error in the bibliography file---while reading file citations.aux' + const error = bibtexLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error') + }) + }) + + describe('lw.parser->biber', () => { + it('should log the bibtex file discovered', () => { + const log = 'INFO - Found BibTeX data source \'ref.bib\'' + biberLogParser.parse(log, get.path('main.tex')) + assert.hasLog('Found BibTeX file ref.bib') + }) + + it('should parse biber line error', () => { + const log = 'ERROR - BibTeX subsystem encountered an unexpected token, line 42, missing closing brace' + const error = biberLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(error?.type, 'error') + assert.strictEqual(error?.line, 42) + }) + + it('should parse entry not found warning', () => { + const log = 'WARN - I didn\'t find a database entry for \'article123\'.' + const warning = biberLogParser.parse(log, get.path('main.tex'))?.[0] + assert.strictEqual(warning?.type, 'warning') + }) + + it('should parse other line error', () => { + const stub = lw.completion.citation.getItem as sinon.SinonStub + stub.returns({ file: 'main.bib', position: { line: 1 } }) + const log = 'WARN - Duplicate entry \'article456\' found in the database.' + const warning = biberLogParser.parse(log, get.path('main.tex'))?.[0] + stub.reset() + assert.strictEqual(warning?.type, 'warning') + assert.strictEqual(warning?.line, 2) + }) + }) +}) diff --git a/test/units/utils.ts b/test/units/utils.ts index 207e750fe..2dd58d99e 100644 --- a/test/units/utils.ts +++ b/test/units/utils.ts @@ -12,7 +12,8 @@ type ExtendedAssert = typeof nodeAssert & { pathStrictEqual: (actual: string | undefined, expected: string | undefined, message?: string | Error) => void, pathNotStrictEqual: (actual: string | undefined, expected: string | undefined, message?: string | Error) => void, hasLog: (message: string | RegExp) => void, - notHasLog: (message: string | RegExp) => void + notHasLog: (message: string | RegExp) => void, + hasCompilerLog: (message: string | RegExp) => void } export const assert: ExtendedAssert = nodeAssert as ExtendedAssert assert.listStrictEqual = (actual: T[] | undefined, expected: T[] | undefined, message?: string | Error) => { @@ -32,21 +33,31 @@ function getPaths(actual: string | undefined, expected: string | undefined): [st return [actual, expected] } assert.pathStrictEqual = (actual: string | undefined, expected: string | undefined, message?: string | Error) => { - assert.strictEqual(path.relative(...getPaths(actual, expected)), '', message) + [actual, expected] = getPaths(actual, expected) + assert.strictEqual(path.relative(actual, expected), '', message ?? `Paths are not equal: ${actual} !== ${expected} .`) } assert.pathNotStrictEqual = (actual: string | undefined, expected: string | undefined, message?: string | Error) => { - assert.notStrictEqual(path.relative(...getPaths(actual, expected)), '', message) + [actual, expected] = getPaths(actual, expected) + assert.notStrictEqual(path.relative(actual, expected), '', message ?? `Paths are equal: ${actual} === ${expected} .`) } function hasLog(message: string | RegExp) { return typeof message === 'string' ? log.all().some(logMessage => logMessage.includes(lwLog.applyPlaceholders(message))) : log.all().some(logMessage => message.exec(logMessage)) } +function hasCompilerLog(message: string | RegExp) { + return typeof message === 'string' + ? lwLog.getCachedLog().CACHED_COMPILER.some(logMessage => logMessage.includes(message)) + : lwLog.getCachedLog().CACHED_COMPILER.some(logMessage => message.exec(logMessage)) +} assert.hasLog = (message: string | RegExp) => { - assert.ok(hasLog(message), log.all().join('\n')) + assert.ok(hasLog(message), '\n' + log.all().join('\n')) } assert.notHasLog = (message: string | RegExp) => { - assert.ok(!hasLog(message), log.all().join('\n')) + assert.ok(!hasLog(message), '\n' + log.all().join('\n')) +} +assert.hasCompilerLog = (message: string | RegExp) => { + assert.ok(hasCompilerLog(message), '\n' + lwLog.getCachedLog().CACHED_COMPILER.join('\n')) } export const get = { @@ -60,20 +71,28 @@ export const get = { } else { return result } + }, + compiler: { + log: () => lwLog.getCachedLog().CACHED_COMPILER.join('') } } +const configs: Map = new Map() const changedConfigs: Set = new Set() export const set = { root: (...paths: string[]) => { const rootFile = get.path(...paths) lw.root.file.path = rootFile + lw.root.file.langId = 'latex' lw.root.dir.path = path.dirname(rootFile) return rootFile }, - config: async (section: string, value: any) => { - await vscode.workspace.getConfiguration('latex-workshop').update(section, value) + config: (section: string, value: any) => { + configs.set(section, value) + }, + codeConfig: async (section: string, value: any) => { changedConfigs.add(section) + await vscode.workspace.getConfiguration('latex-workshop').update(section, value) } } @@ -87,9 +106,10 @@ export const reset = { }, config: async () => { for (const section of changedConfigs.values()) { - await set.config(section, undefined) + await set.codeConfig(section, undefined) } changedConfigs.clear() + configs.clear() }, log: () => { lwLog.resetCachedLog() @@ -117,6 +137,10 @@ export function sleep(ms: number) { } export const mock = { + init: (obj: any, ...ignore: string[]) => { + mock.object(obj, ...ignore) + mock.config() + }, object: (obj: any, ...ignore: string[]) => { const items = Object.getPrototypeOf(obj) === Object.prototype ? Object.getOwnPropertyNames(obj) @@ -133,10 +157,29 @@ export const mock = { } }) }, + config: () => { + const original = vscode.workspace.getConfiguration + sinon.stub(vscode.workspace, 'getConfiguration').callsFake((section?: string, scope?: vscode.ConfigurationScope | null) => { + function getConfig(configName: string): T | undefined + function getConfig(configName: string, defaultValue: T): T + function getConfig(configName: string, defaultValue?: T): T | undefined { + if (configs.has(configName)) { + return configs.get(configName) as T + } + return originalConfig.get(configName, defaultValue) + } + const originalConfig = original(section, scope) + const configItem: vscode.WorkspaceConfiguration = { + ...originalConfig, + get: getConfig + } + return configItem + }) + }, textDocument: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string } = {}) => { return sinon.stub(vscode.workspace, 'textDocuments').value([ new TextDocument(filePath, content, params) ]) }, - activeTextEditor: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string } = {}) => { + activeTextEditor: (filePath: string, content: string, params: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string, viewColumn?: vscode.ViewColumn } = {}) => { return sinon.stub(vscode.window, 'activeTextEditor').value(new TextEditor(filePath, content, params)) } } @@ -212,8 +255,11 @@ class TextEditor implements vscode.TextEditor { options: vscode.TextEditorOptions = {} viewColumn: vscode.ViewColumn | undefined = vscode.ViewColumn.Active - constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false, scheme = 'file' }: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string }) { + constructor(filePath: string, content: string, { languageId = 'latex', isDirty = false, isClosed = false, scheme = 'file', viewColumn = undefined }: { languageId?: string, isDirty?: boolean, isClosed?: boolean, scheme?: string, viewColumn?: vscode.ViewColumn }) { this.document = new TextDocument(filePath, content, { languageId, isDirty, isClosed, scheme }) + if (viewColumn !== undefined) { + this.viewColumn = viewColumn + } } edit(_: (_: vscode.TextEditorEdit) => void): Thenable { throw new Error('Not implemented.') } diff --git a/types/latex-workshop-protocol-types/index.d.ts b/types/latex-workshop-protocol-types/index.d.ts index 8d29e59d7..a90844ec6 100644 --- a/types/latex-workshop-protocol-types/index.d.ts +++ b/types/latex-workshop-protocol-types/index.d.ts @@ -58,8 +58,7 @@ export type PdfViewerParams = { export type ClientRequest = { type: 'open', - pdfFileUri: string, - viewer: 'browser' | 'tab' + pdfFileUri: string } | { type: 'loaded', pdfFileUri: string diff --git a/viewer/components/connection.ts b/viewer/components/connection.ts index 304c16ba9..c112df18f 100644 --- a/viewer/components/connection.ts +++ b/viewer/components/connection.ts @@ -20,8 +20,7 @@ export function initConnect() { ws.addEventListener('close', reconnect) const openPack: ClientRequest = { type: 'open', - pdfFileUri: utils.parseURL().pdfFileUri, - viewer: (utils.isEmbedded() ? 'tab' : 'browser') + pdfFileUri: utils.parseURL().pdfFileUri } void send(openPack) }).catch((e) => console.error('Setting up connection port failed:', e)) diff --git a/viewer/components/gui.ts b/viewer/components/gui.ts index 4cca81bd6..d93638e4c 100644 --- a/viewer/components/gui.ts +++ b/viewer/components/gui.ts @@ -27,15 +27,15 @@ export function patchViewerUI() { const template = document.createElement('template') template.innerHTML = -` - -
` diff --git a/viewer/images/altText_disclaimer.svg b/viewer/images/altText_disclaimer.svg new file mode 100644 index 000000000..6fe79e710 --- /dev/null +++ b/viewer/images/altText_disclaimer.svg @@ -0,0 +1,3 @@ + + + diff --git a/viewer/images/altText_spinner.svg b/viewer/images/altText_spinner.svg new file mode 100644 index 000000000..fedb4724f --- /dev/null +++ b/viewer/images/altText_spinner.svg @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/viewer/images/altText_warning.svg b/viewer/images/altText_warning.svg new file mode 100644 index 000000000..03014ceab --- /dev/null +++ b/viewer/images/altText_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/viewer/images/messageBar_closingButton.svg b/viewer/images/messageBar_closingButton.svg new file mode 100644 index 000000000..8a40715de --- /dev/null +++ b/viewer/images/messageBar_closingButton.svg @@ -0,0 +1,3 @@ + + + diff --git a/viewer/images/messageBar_warning.svg b/viewer/images/messageBar_warning.svg new file mode 100644 index 000000000..011cfcf3e --- /dev/null +++ b/viewer/images/messageBar_warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/viewer/locale/be/viewer.ftl b/viewer/locale/be/viewer.ftl index ee1f43010..18a941862 100644 --- a/viewer/locale/be/viewer.ftl +++ b/viewer/locale/be/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Сцягнуць pdfjs-bookmark-button = .title = Дзейная старонка (паглядзець URL-адрас з дзейнай старонкі) pdfjs-bookmark-button-label = Цяперашняя старонка -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Адкрыць у праграме -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Адкрыць у праграме ## Secondary toolbar and context menu @@ -125,6 +119,9 @@ pdfjs-document-properties-keywords = Ключавыя словы: pdfjs-document-properties-creation-date = Дата стварэння: pdfjs-document-properties-modification-date = Дата змянення: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -283,6 +280,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -306,8 +306,6 @@ pdfjs-editor-stamp-button-label = Дадаць або змяніць выявы pdfjs-editor-highlight-button = .title = Вылучэнне pdfjs-editor-highlight-button-label = Вылучэнне -pdfjs-highlight-floating-button = - .title = Вылучэнне pdfjs-highlight-floating-button1 = .title = Падфарбаваць .aria-label = Падфарбаваць @@ -402,3 +400,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Паказаць усе pdfjs-editor-highlight-show-all-button = .title = Паказаць усе + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Рэдагаваць тэкст для атрыбута alt (апісанне выявы) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Дадаць тэкст для атрыбута alt (апісанне выявы) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Напішыце сваё апісанне тут… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Кароткае апісанне для людзей, якія не бачаць выяву, ці калі выява не загружаецца. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Гэты тэкст для атрыбута alt быў створаны аўтаматычна і можа быць недакладным +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Даведацца больш +pdfjs-editor-new-alt-text-create-automatically-button-label = Ствараць тэкст для атрыбута alt аўтаматычна +pdfjs-editor-new-alt-text-not-now-button = Не зараз +pdfjs-editor-new-alt-text-error-title = Не ўдалося аўтаматычна стварыць тэкст для атрыбута alt +pdfjs-editor-new-alt-text-error-description = Калі ласка, напішыце ўласны тэкст для атрыбута alt або паўтарыце спробу пазней. +pdfjs-editor-new-alt-text-error-close-button = Закрыць +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Сцягванне мадэлі ШІ для тэксту для атрыбута alt ({ $downloadedSize } з { $totalSize } МБ) + .aria-valuetext = Сцягванне мадэлі ШІ для тэксту для атрыбута alt ({ $downloadedSize } з { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Тэкст для атрыбута alt дададзены +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Адсутнічае тэкст для атрыбута alt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Водгук на тэкст для атрыбута alt +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створаны аўтаматычна: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Налады альтэрнатыўнага тэксту для выявы +pdfjs-image-alt-text-settings-button-label = Налады альтэрнатыўнага тэксту для выявы +pdfjs-editor-alt-text-settings-dialog-label = Налады альтэрнатыўнага тэксту для выявы +pdfjs-editor-alt-text-settings-automatic-title = Аўтаматычны тэкст для атрыбута alt +pdfjs-editor-alt-text-settings-create-model-button-label = Ствараць тэкст для атрыбута alt аўтаматычна +pdfjs-editor-alt-text-settings-create-model-description = Прапануе апісанні, каб дапамагчы людзям, якія не бачаць выяву, ці калі выява не загружаецца. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Мадэль ШІ для тэксту для атрыбута alt ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Працуе лакальна на вашай прыладзе, таму вашы звесткі застаюцца прыватнымі. Патрабуецца для аўтаматычнага альтэрнатыўнага тэксту. +pdfjs-editor-alt-text-settings-delete-model-button = Выдаліць +pdfjs-editor-alt-text-settings-download-model-button = Сцягнуць +pdfjs-editor-alt-text-settings-downloading-model-button = Сцягванне… +pdfjs-editor-alt-text-settings-editor-title = Рэдактар тэксту для атрыбута alt +pdfjs-editor-alt-text-settings-show-dialog-button-label = Адразу паказваць рэдактар тэксту для атрыбута alt пры даданні выявы +pdfjs-editor-alt-text-settings-show-dialog-description = Дапамагае пераканацца, што ўсе вашы выявы маюць альтэрнатыўны тэкст. +pdfjs-editor-alt-text-settings-close-button = Закрыць diff --git a/viewer/locale/bg/viewer.ftl b/viewer/locale/bg/viewer.ftl index 7522054c0..36c7b5189 100644 --- a/viewer/locale/bg/viewer.ftl +++ b/viewer/locale/bg/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Изтегляне pdfjs-bookmark-button = .title = Текуща страница (преглед на адреса на страницата) pdfjs-bookmark-button-label = Текуща страница -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Отваряне в приложение -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Отваряне в приложение ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Свойства на документ pdfjs-document-properties-file-name = Име на файл: pdfjs-document-properties-file-size = Големина на файл: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байта) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байта) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байта) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Ключови думи: pdfjs-document-properties-creation-date = Дата на създаване: pdfjs-document-properties-modification-date = Дата на промяна: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Анотация { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -301,8 +309,6 @@ pdfjs-editor-ink-button-label = Рисуване pdfjs-editor-stamp-button = .title = Добавяне или променяне на изображения pdfjs-editor-stamp-button-label = Добавяне или променяне на изображения -pdfjs-editor-remove-button = - .title = Премахване ## Remove button for the various kind of editor. @@ -382,3 +388,14 @@ pdfjs-editor-colorpicker-pink = .title = Розово pdfjs-editor-colorpicker-red = .title = Червено + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + + +## Image alt-text settings + diff --git a/viewer/locale/br/viewer.ftl b/viewer/locale/br/viewer.ftl index 9049f68f1..471b9a5df 100644 --- a/viewer/locale/br/viewer.ftl +++ b/viewer/locale/br/viewer.ftl @@ -49,12 +49,6 @@ pdfjs-download-button = # Length of the translation matters since we are in a mobile context, with limited screen estate. pdfjs-download-button-label = Pellgargañ pdfjs-bookmark-button-label = Pajenn a-vremañ -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Digeriñ en arload -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Digeriñ en arload ## Secondary toolbar and context menu @@ -214,6 +208,7 @@ pdfjs-find-next-button = pdfjs-find-next-button-label = War-lerc'h pdfjs-find-highlight-checkbox = Usskediñ pep tra pdfjs-find-match-case-checkbox-label = Teurel evezh ouzh ar pennlizherennoù +pdfjs-find-match-diacritics-checkbox-label = Doujañ d’an tiredoù pdfjs-find-entire-word-checkbox-label = Gerioù a-bezh pdfjs-find-reached-top = Tizhet eo bet derou ar bajenn, kenderc'hel diouzh an diaz pdfjs-find-reached-bottom = Tizhet eo bet dibenn ar bajenn, kenderc'hel diouzh ar c'hrec'h @@ -311,3 +306,7 @@ pdfjs-editor-alt-text-save-button = Enrollañ ## Color picker + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + diff --git a/viewer/locale/cs/viewer.ftl b/viewer/locale/cs/viewer.ftl index b05761b0a..7c7ff3d8d 100644 --- a/viewer/locale/cs/viewer.ftl +++ b/viewer/locale/cs/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Stáhnout pdfjs-bookmark-button = .title = Aktuální stránka (zobrazit URL od aktuální stránky) pdfjs-bookmark-button-label = Aktuální stránka -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Otevřít v aplikaci -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Otevřít v aplikaci ## Secondary toolbar and context menu @@ -125,6 +119,9 @@ pdfjs-document-properties-keywords = Klíčová slova: pdfjs-document-properties-creation-date = Datum vytvoření: pdfjs-document-properties-modification-date = Datum úpravy: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -285,6 +282,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Anotace typu { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -308,8 +308,6 @@ pdfjs-editor-stamp-button-label = Přidání či úprava obrázků pdfjs-editor-highlight-button = .title = Zvýraznění pdfjs-editor-highlight-button-label = Zvýraznění -pdfjs-highlight-floating-button = - .title = Zvýraznit pdfjs-highlight-floating-button1 = .title = Zvýraznit .aria-label = Zvýraznit @@ -404,3 +402,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Zobrazit vše pdfjs-editor-highlight-show-all-button = .title = Zobrazit vše + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Upravit alternativní text (popis obrázku) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Přidat alternativní text (popis obrázku) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Sem napište svůj popis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krátký popis pro lidi, kteří neuvidí obrázek nebo když se obrázek nenačítá. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tento alternativní text byl vytvořen automaticky a může být nepřesný. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Více informací +pdfjs-editor-new-alt-text-create-automatically-button-label = Vytvořit alternativní text automaticky +pdfjs-editor-new-alt-text-not-now-button = Teď ne +pdfjs-editor-new-alt-text-error-title = Nepodařilo se automaticky vytvořit alternativní text +pdfjs-editor-new-alt-text-error-description = Napište prosím vlastní alternativní text nebo to zkuste znovu později. +pdfjs-editor-new-alt-text-error-close-button = Zavřít +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Stahuje se model AI pro alternativní texty ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativní text byl přidán +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Chybí alternativní text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Zkontrolovat alternativní text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Vytvořeno automaticky: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavení alternativního textu obrázku +pdfjs-image-alt-text-settings-button-label = Nastavení alternativního textu obrázku +pdfjs-editor-alt-text-settings-dialog-label = Nastavení alternativního textu obrázku +pdfjs-editor-alt-text-settings-automatic-title = Automatický alternativní text +pdfjs-editor-alt-text-settings-create-model-button-label = Vytvořit alternativní text automaticky +pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, které pomohou lidem, kteří nevidí obrázek nebo když se obrázek nenačte. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI pro alternativní text ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běží lokálně na vašem zařízení, takže vaše data zůstávají v bezpečí. Vyžadováno pro automatický alternativní text. +pdfjs-editor-alt-text-settings-delete-model-button = Smazat +pdfjs-editor-alt-text-settings-download-model-button = Stáhnout +pdfjs-editor-alt-text-settings-downloading-model-button = Probíhá stahování... +pdfjs-editor-alt-text-settings-editor-title = Editor alternativního textu +pdfjs-editor-alt-text-settings-show-dialog-button-label = Při přidávání obrázku hned zobrazit editor alternativního textu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomůže vám zajistit, aby všechny vaše obrázky obsahovaly alternativní text. +pdfjs-editor-alt-text-settings-close-button = Zavřít diff --git a/viewer/locale/cy/viewer.ftl b/viewer/locale/cy/viewer.ftl index 061237aba..b6da47982 100644 --- a/viewer/locale/cy/viewer.ftl +++ b/viewer/locale/cy/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Llwytho i lawr pdfjs-bookmark-button = .title = Tudalen Gyfredol (Gweld URL o'r Dudalen Gyfredol) pdfjs-bookmark-button-label = Tudalen Gyfredol -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Agor yn yr ap -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Agor yn yr ap ## Secondary toolbar and context menu @@ -312,8 +306,6 @@ pdfjs-editor-stamp-button-label = Ychwanegu neu olygu delweddau pdfjs-editor-highlight-button = .title = Amlygu pdfjs-editor-highlight-button-label = Amlygu -pdfjs-highlight-floating-button = - .title = Amlygu pdfjs-highlight-floating-button1 = .title = Amlygu .aria-label = Amlygu @@ -408,3 +400,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Dangos y cyfan pdfjs-editor-highlight-show-all-button = .title = Dangos y cyfan + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Golygu testun amgen (disgrifiad o ddelwedd) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Ychwanegwch destun amgen (disgrifiad delwedd) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Ysgrifennwch eich disgrifiad yma… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Disgrifiad byr ar gyfer pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Cafodd y testun amgen hwn ei greu'n awtomatig a gall fod yn anghywir. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Dysgu rhagor +pdfjs-editor-new-alt-text-create-automatically-button-label = Creu testun amgen yn awtomatig +pdfjs-editor-new-alt-text-not-now-button = Nid nawr +pdfjs-editor-new-alt-text-error-title = Methu â chreu testun amgen yn awtomatig +pdfjs-editor-new-alt-text-error-description = Ysgrifennwch eich testun amgen eich hun neu ceisiwch eto yn nes ymlaen. +pdfjs-editor-new-alt-text-error-close-button = Cau +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB) + .aria-valuetext = Wrthi'n llwytho i lawr model AI testun amgen ( { $downloadedSize } o { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Ychwanegwyd testun amgen +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Testun amgen coll +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Adolygu'r testun amgen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Crëwyd yn awtomatig: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Gosodiadau testun amgen delwedd +pdfjs-image-alt-text-settings-button-label = Gosodiadau testun amgen delwedd +pdfjs-editor-alt-text-settings-dialog-label = Gosodiadau testun amgen delwedd +pdfjs-editor-alt-text-settings-automatic-title = Testun amgen awtomatig +pdfjs-editor-alt-text-settings-create-model-button-label = Creu testun amgen yn awtomatig +pdfjs-editor-alt-text-settings-create-model-description = Yn awgrymu disgrifiadau i helpu pobl sydd ddim yn gallu gweld y ddelwedd neu pan nad yw'r ddelwedd yn llwytho. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI testun amgen ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Yn rhedeg yn lleol ar eich dyfais fel bod eich data'n aros yn breifat. Yn ofynnol ar gyfer testun amgen awtomatig. +pdfjs-editor-alt-text-settings-delete-model-button = Dileu +pdfjs-editor-alt-text-settings-download-model-button = Llwytho i Lawr +pdfjs-editor-alt-text-settings-downloading-model-button = Wrthi'n llwytho i lawr… +pdfjs-editor-alt-text-settings-editor-title = Golygydd testun amgen +pdfjs-editor-alt-text-settings-show-dialog-button-label = Dangoswch y golygydd testun amgen yn syth wrth ychwanegu delwedd +pdfjs-editor-alt-text-settings-show-dialog-description = Yn eich helpu i wneud yn siŵr bod gan eich holl ddelweddau destun amgen. +pdfjs-editor-alt-text-settings-close-button = Cau diff --git a/viewer/locale/da/viewer.ftl b/viewer/locale/da/viewer.ftl index 968b22ffc..2c5f526da 100644 --- a/viewer/locale/da/viewer.ftl +++ b/viewer/locale/da/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Hent pdfjs-bookmark-button = .title = Aktuel side (vis URL fra den aktuelle side) pdfjs-bookmark-button-label = Aktuel side -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Åbn i app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Åbn i app ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Dokumentegenskaber… pdfjs-document-properties-file-name = Filnavn: pdfjs-document-properties-file-size = Filstørrelse: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Nøgleord: pdfjs-document-properties-creation-date = Oprettet: pdfjs-document-properties-modification-date = Redigeret: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type }kommentar] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Tilføj eller rediger billeder pdfjs-editor-highlight-button = .title = Fremhæv pdfjs-editor-highlight-button-label = Fremhæv -pdfjs-highlight-floating-button = - .title = Fremhæv pdfjs-highlight-floating-button1 = .title = Fremhæv .aria-label = Fremhæv @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Vis alle pdfjs-editor-highlight-show-all-button = .title = Vis alle + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (billedbeskrivelse) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Tilføj alternativ tekst (billedbeskrivelse) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv din beskrivelse her... +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort beskrivelse til personer, der ikke kan se billedet, eller når billedet ikke indlæses. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative tekst blev oprettet automatisk og kan være upræcis. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Læs mere +pdfjs-editor-new-alt-text-create-automatically-button-label = Opret alternativ tekst automatisk +pdfjs-editor-new-alt-text-not-now-button = Ikke nu +pdfjs-editor-new-alt-text-error-title = Kunne ikke oprette alternativ tekst automatisk +pdfjs-editor-new-alt-text-error-description = Skriv din egen alternative tekst, eller prøv igen senere. +pdfjs-editor-new-alt-text-error-close-button = Luk +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB) + .aria-valuetext = Henter alternativ tekst AI-model ({ $downloadedSize } af { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst tilføjet +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Mangler alternativ tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Gennemgå alternativ tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oprettet automatisk: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Indstillinger for alternativ tekst til billeder +pdfjs-image-alt-text-settings-button-label = Indstillinger for alternativ tekst til billeder +pdfjs-editor-alt-text-settings-dialog-label = Indstillinger for alternativ tekst til billeder +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Opret alternativ tekst automatisk +pdfjs-editor-alt-text-settings-create-model-description = Foreslår beskrivelser for at hjælpe folk, der ikke kan se billedet, eller når billedet ikke indlæses. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model til at oprette alternative tekster ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Kører lokalt på din enhed, så dine data forbliver private. Påkrævet for at anvende automatisk alternativ tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Slet +pdfjs-editor-alt-text-settings-download-model-button = Hent +pdfjs-editor-alt-text-settings-downloading-model-button = Henter… +pdfjs-editor-alt-text-settings-editor-title = Redigering af alternativ tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis redigering af alternativ tekst med det samme, når et billede tilføjes +pdfjs-editor-alt-text-settings-show-dialog-description = Hjælper dig med at sikre, at alle dine billeder har alternativ tekst. +pdfjs-editor-alt-text-settings-close-button = Luk diff --git a/viewer/locale/de/viewer.ftl b/viewer/locale/de/viewer.ftl index da073aa17..24fcb103f 100644 --- a/viewer/locale/de/viewer.ftl +++ b/viewer/locale/de/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Herunterladen pdfjs-bookmark-button = .title = Aktuelle Seite (URL von aktueller Seite anzeigen) pdfjs-bookmark-button-label = Aktuelle Seite -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Mit App öffnen -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Mit App öffnen ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Grafiken hinzufügen oder bearbeiten pdfjs-editor-highlight-button = .title = Hervorheben pdfjs-editor-highlight-button-label = Hervorheben -pdfjs-highlight-floating-button = - .title = Hervorheben pdfjs-highlight-floating-button1 = .title = Hervorheben .aria-label = Hervorheben @@ -400,3 +392,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Alle anzeigen pdfjs-editor-highlight-show-all-button = .title = Alle anzeigen + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternativ-Text (Grafikbeschreibung) bearbeiten +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternativ-Text (Grafikbeschreibung) hinzufügen +pdfjs-editor-new-alt-text-textarea = + .placeholder = Schreiben Sie Ihre Beschreibung hier… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kurze Beschreibung für Personen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Dieser Alternativ-Text wurde automatisch erstellt und könnte ungenau sein. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Dieser Alternativ-Text wurde automatisch erstellt. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Weitere Informationen +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternativ-Text automatisch erstellen +pdfjs-editor-new-alt-text-not-now-button = Nicht jetzt +pdfjs-editor-new-alt-text-error-title = Alternativ-Text konnte nicht automatisch erstellt werden +pdfjs-editor-new-alt-text-error-description = Bitte schreiben Sie Ihren eigenen Alternativ-Text oder versuchen Sie es später erneut. +pdfjs-editor-new-alt-text-error-close-button = Schließen +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB) + .aria-valuetext = Alternativ-Text-KI-Modell wird heruntergeladen ({ $downloadedSize } von { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativ-Text hinzugefügt +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Fehlender Alternativ-Text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternativ-Text überprüfen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch erstellt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternativ-Text-Einstellungen für Grafiken +pdfjs-image-alt-text-settings-button-label = Alternativ-Text-Einstellungen für Grafiken +pdfjs-editor-alt-text-settings-dialog-label = Alternativ-Text-Einstellungen für Grafiken +pdfjs-editor-alt-text-settings-automatic-title = Automatischer Alternativ-Text +pdfjs-editor-alt-text-settings-create-model-button-label = Alternativ-Text automatisch erstellen +pdfjs-editor-alt-text-settings-create-model-description = Schlägt Beschreibungen vor, um Personen zu helfen, die die Grafik nicht sehen können, oder wenn die Grafik nicht geladen wird. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternativ-Text-KI-Modell ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wird lokal auf Ihrem Gerät ausgeführt, sodass Ihre Daten privat bleiben. Erforderlich für automatischen Alternativ-Text. +pdfjs-editor-alt-text-settings-delete-model-button = Löschen +pdfjs-editor-alt-text-settings-download-model-button = Herunterladen +pdfjs-editor-alt-text-settings-downloading-model-button = Wird heruntergeladen… +pdfjs-editor-alt-text-settings-editor-title = Alternativ-Texteditor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternativ-Texteditor beim Hinzufügen einer Grafik anzeigen +pdfjs-editor-alt-text-settings-show-dialog-description = Hilft Ihnen, sicherzustellen, dass alle Ihre Grafiken Alternativ-Text haben. +pdfjs-editor-alt-text-settings-close-button = Schließen diff --git a/viewer/locale/dsb/viewer.ftl b/viewer/locale/dsb/viewer.ftl index e86f8153e..f78c3661c 100644 --- a/viewer/locale/dsb/viewer.ftl +++ b/viewer/locale/dsb/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Ześěgnuś pdfjs-bookmark-button = .title = Aktualny bok (URL z aktualnego boka pokazaś) pdfjs-bookmark-button-label = Aktualny bok -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = W nałoženju wócyniś -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = W nałoženju wócyniś ## Secondary toolbar and context menu @@ -308,8 +302,6 @@ pdfjs-editor-stamp-button-label = Wobraze pśidaś abo wobźěłaś pdfjs-editor-highlight-button = .title = Wuzwignuś pdfjs-editor-highlight-button-label = Wuzwignuś -pdfjs-highlight-floating-button = - .title = Wuzwignjenje pdfjs-highlight-floating-button1 = .title = Wuzwignuś .aria-label = Wuzwignuś @@ -404,3 +396,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Wšykne pokazaś pdfjs-editor-highlight-show-all-button = .title = Wšykne pokazaś + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobźěłaś (wobrazowe wopisanje) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst pśidaś (wobrazowe wopisanje) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Pišćo how swójo wopisanje… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krotke wopisanje za luźe, kótarež njamóžośo wobraz wiźeś abo gaž se wobraz njezacytajo. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Toś ten alternatiwny tekst jo se awtomatiski napórał a jo snaź njedokradny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Dalšne informacije +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatiski napóraś +pdfjs-editor-new-alt-text-not-now-button = Nic něnto +pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njedajo se awtomatiski napóraś +pdfjs-editor-new-alt-text-error-description = Pšosym pišćo swój alternatiwny tekst abo wopytajśo pózdźej hyšći raz. +pdfjs-editor-new-alt-text-error-close-button = Zacyniś +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Model KI za alternatiwny tekst se ześěgujo ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst jo se pśidał +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst felujo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst pśeglědowaś +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatiski napórany: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwnego wobrazowego teksta +pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatiski napóraś +pdfjs-editor-alt-text-settings-create-model-description = Naraźujo wopisanja, aby pomagał ludam, kótarež njamóžośo wobraz wiźeś abo gaž se wobraz njezacytajo. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwnego teksta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běžy lokalnje na wašom rěźe, aby waše daty priwatne wóstali. Za awtomatiski alternatiwny tekst trjebny. +pdfjs-editor-alt-text-settings-delete-model-button = Lašowaś +pdfjs-editor-alt-text-settings-download-model-button = Ześěgnuś +pdfjs-editor-alt-text-settings-downloading-model-button = Ześěgujo se… +pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwnego teksta ned pokazaś, gaž se wobraz pśidawa +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga, wam wšym swójim wobrazam alternatiwny tekst pśidaś. +pdfjs-editor-alt-text-settings-close-button = Zacyniś diff --git a/viewer/locale/el/viewer.ftl b/viewer/locale/el/viewer.ftl index 96d9dc36e..4c5df349b 100644 --- a/viewer/locale/el/viewer.ftl +++ b/viewer/locale/el/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Λήψη pdfjs-bookmark-button = .title = Τρέχουσα σελίδα (Προβολή URL από τρέχουσα σελίδα) pdfjs-bookmark-button-label = Τρέχουσα σελίδα -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Άνοιγμα σε εφαρμογή -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Άνοιγμα σε εφαρμογή ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Ιδιότητες εγγράφου… pdfjs-document-properties-file-name = Όνομα αρχείου: pdfjs-document-properties-file-size = Μέγεθος αρχείου: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Λέξεις-κλειδιά: pdfjs-document-properties-creation-date = Ημερομηνία δημιουργίας: pdfjs-document-properties-modification-date = Ημερομηνία τροποποίησης: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Σχόλιο «{ $type }»] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Προσθήκη ή επεξεργασία ε pdfjs-editor-highlight-button = .title = Επισήμανση pdfjs-editor-highlight-button-label = Επισήμανση -pdfjs-highlight-floating-button = - .title = Επισήμανση pdfjs-highlight-floating-button1 = .title = Επισήμανση .aria-label = Επισήμανση @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Εμφάνιση όλων pdfjs-editor-highlight-show-all-button = .title = Εμφάνιση όλων + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Επεξεργασία εναλλακτικού κειμένου (περιγραφή εικόνας) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Προσθήκη εναλλακτικού κειμένου (περιγραφή εικόνας) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Γράψτε την περιγραφή σας εδώ… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Σύντομη περιγραφή για άτομα που δεν μπορούν να δουν την εικόνα ή όταν η εικόνα δεν φορτώνεται. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Αυτό το εναλλακτικό κείμενο δημιουργήθηκε αυτόματα και ενδέχεται να είναι ανακριβές. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Μάθετε περισσότερα +pdfjs-editor-new-alt-text-create-automatically-button-label = Αυτόματη δημιουργία εναλλακτικού κειμένου +pdfjs-editor-new-alt-text-not-now-button = Όχι τώρα +pdfjs-editor-new-alt-text-error-title = Δεν ήταν δυνατή η αυτόματη δημιουργία εναλλακτικού κειμένου +pdfjs-editor-new-alt-text-error-description = Γράψτε το δικό σας εναλλακτικό κείμενο ή δοκιμάστε ξανά αργότερα. +pdfjs-editor-new-alt-text-error-close-button = Κλείσιμο +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Λήψη μοντέλου AI εναλλακτικού κειμένου ({ $downloadedSize } από { $totalSize } MB) + .aria-valuetext = Λήψη μοντέλου AI εναλλακτικού κειμένου ({ $downloadedSize } από { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Προστέθηκε εναλλακτικό κείμενο +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Απουσία εναλλακτικού κειμένου +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Έλεγχος εναλλακτικού κειμένου +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Αυτόματη δημιουργία: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ρυθμίσεις εναλλακτικού κειμένου εικόνας +pdfjs-image-alt-text-settings-button-label = Ρυθμίσεις εναλλακτικού κειμένου εικόνας +pdfjs-editor-alt-text-settings-dialog-label = Ρυθμίσεις εναλλακτικού κειμένου εικόνας +pdfjs-editor-alt-text-settings-automatic-title = Αυτόματο εναλλακτικό κείμενο +pdfjs-editor-alt-text-settings-create-model-button-label = Αυτόματη δημιουργία εναλλακτικού κειμένου +pdfjs-editor-alt-text-settings-create-model-description = Προτείνει περιγραφές για άτομα που δεν μπορούν να δουν την εικόνα ή όταν η εικόνα δεν φορτώνεται. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Μοντέλο AI εναλλακτικού κειμένου ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Εκτελείται τοπικά στη συσκευή σας, ώστε τα δεδομένα σας να παραμένουν ιδιωτικά. Απαιτείται για τη δημιουργία του αυτόματου εναλλακτικού κειμένου. +pdfjs-editor-alt-text-settings-delete-model-button = Διαγραφή +pdfjs-editor-alt-text-settings-download-model-button = Λήψη +pdfjs-editor-alt-text-settings-downloading-model-button = Λήψη… +pdfjs-editor-alt-text-settings-editor-title = Επεξεργασία εναλλακτικού κειμένου +pdfjs-editor-alt-text-settings-show-dialog-button-label = Άμεση εμφάνιση της επεξεργασίας εναλλακτικού κειμένου κατά την προσθήκη εικόνας +pdfjs-editor-alt-text-settings-show-dialog-description = Σας βοηθά να βεβαιωθείτε ότι όλες οι εικόνες σας έχουν εναλλακτικό κείμενο. +pdfjs-editor-alt-text-settings-close-button = Κλείσιμο diff --git a/viewer/locale/en-CA/viewer.ftl b/viewer/locale/en-CA/viewer.ftl index f87104e78..6068bd0bb 100644 --- a/viewer/locale/en-CA/viewer.ftl +++ b/viewer/locale/en-CA/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Download pdfjs-bookmark-button = .title = Current Page (View URL from Current Page) pdfjs-bookmark-button-label = Current Page -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Open in app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Open in app ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Add or edit images pdfjs-editor-highlight-button = .title = Highlight pdfjs-editor-highlight-button-label = Highlight -pdfjs-highlight-floating-button = - .title = Highlight pdfjs-highlight-floating-button1 = .title = Highlight .aria-label = Highlight @@ -400,3 +392,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Show all pdfjs-editor-highlight-show-all-button = .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = This alt text was created automatically. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alt text added +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close diff --git a/viewer/locale/en-GB/viewer.ftl b/viewer/locale/en-GB/viewer.ftl index 3b141aee1..9dedd14bd 100644 --- a/viewer/locale/en-GB/viewer.ftl +++ b/viewer/locale/en-GB/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Download pdfjs-bookmark-button = .title = Current Page (View URL from Current Page) pdfjs-bookmark-button-label = Current Page -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Open in app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Open in app ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Document Properties… pdfjs-document-properties-file-name = File name: pdfjs-document-properties-file-size = File size: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Keywords: pdfjs-document-properties-creation-date = Creation Date: pdfjs-document-properties-modification-date = Modification Date: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Add or edit images pdfjs-editor-highlight-button = .title = Highlight pdfjs-editor-highlight-button-label = Highlight -pdfjs-highlight-floating-button = - .title = Highlight pdfjs-highlight-floating-button1 = .title = Highlight .aria-label = Highlight @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Show all pdfjs-editor-highlight-show-all-button = .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alt text added +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close diff --git a/viewer/locale/en-US/viewer.ftl b/viewer/locale/en-US/viewer.ftl index 8aea43959..67be5cd58 100644 --- a/viewer/locale/en-US/viewer.ftl +++ b/viewer/locale/en-US/viewer.ftl @@ -113,14 +113,14 @@ pdfjs-document-properties-file-name = File name: pdfjs-document-properties-file-size = File size: # Variables: -# $size_kb (Number) - the PDF file size in kilobytes -# $size_b (Number) - the PDF file size in bytes -pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) # Variables: -# $size_mb (Number) - the PDF file size in megabytes -# $size_b (Number) - the PDF file size in bytes -pdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes) +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) pdfjs-document-properties-title = Title: pdfjs-document-properties-author = Author: @@ -130,9 +130,8 @@ pdfjs-document-properties-creation-date = Creation Date: pdfjs-document-properties-modification-date = Modification Date: # Variables: -# $date (Date) - the creation/modification date of the PDF file -# $time (Time) - the creation/modification time of the PDF file -pdfjs-document-properties-date-string = { $date }, { $time } +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } pdfjs-document-properties-creator = Creator: pdfjs-document-properties-producer = PDF Producer: @@ -284,9 +283,8 @@ pdfjs-rendering-error = An error occurred while rendering the page. ## Annotations # Variables: -# $date (Date) - the modification date of the annotation -# $time (Time) - the modification time of the annotation -pdfjs-annotation-date-string = { $date }, { $time } +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } # .alt: This is used as a tooltip. # Variables: @@ -381,14 +379,22 @@ pdfjs-editor-alt-text-textarea = ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. -pdfjs-editor-resizer-label-top-left = Top left corner — resize -pdfjs-editor-resizer-label-top-middle = Top middle — resize -pdfjs-editor-resizer-label-top-right = Top right corner — resize -pdfjs-editor-resizer-label-middle-right = Middle right — resize -pdfjs-editor-resizer-label-bottom-right = Bottom right corner — resize -pdfjs-editor-resizer-label-bottom-middle = Bottom middle — resize -pdfjs-editor-resizer-label-bottom-left = Bottom left corner — resize -pdfjs-editor-resizer-label-middle-left = Middle left — resize +pdfjs-editor-resizer-top-left = + .aria-label = Top left corner — resize +pdfjs-editor-resizer-top-middle = + .aria-label = Top middle — resize +pdfjs-editor-resizer-top-right = + .aria-label = Top right corner — resize +pdfjs-editor-resizer-middle-right = + .aria-label = Middle right — resize +pdfjs-editor-resizer-bottom-right = + .aria-label = Bottom right corner — resize +pdfjs-editor-resizer-bottom-middle = + .aria-label = Bottom middle — resize +pdfjs-editor-resizer-bottom-left = + .aria-label = Bottom left corner — resize +pdfjs-editor-resizer-middle-left = + .aria-label = Middle left — resize ## Color picker @@ -416,3 +422,74 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Show all pdfjs-editor-highlight-show-all-button = .title = Show all + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description) + +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description) + +pdfjs-editor-new-alt-text-textarea = + .placeholder = Write your description here… + +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load. + +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more + +pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically +pdfjs-editor-new-alt-text-not-now-button = Not now +pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically +pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later. +pdfjs-editor-new-alt-text-error-close-button = Close + +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB) + +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alt text added + +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Missing alt text + +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Review alt text + +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Image alt text settings +pdfjs-image-alt-text-settings-button-label = Image alt text settings + +pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings +pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text +pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically +pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load. + +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB) + +pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text. +pdfjs-editor-alt-text-settings-delete-model-button = Delete +pdfjs-editor-alt-text-settings-download-model-button = Download +pdfjs-editor-alt-text-settings-downloading-model-button = Downloading… + +pdfjs-editor-alt-text-settings-editor-title = Alt text editor +pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image +pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text. +pdfjs-editor-alt-text-settings-close-button = Close diff --git a/viewer/locale/eo/viewer.ftl b/viewer/locale/eo/viewer.ftl index 23c2b24fa..2b77231f3 100644 --- a/viewer/locale/eo/viewer.ftl +++ b/viewer/locale/eo/viewer.ftl @@ -298,8 +298,6 @@ pdfjs-editor-stamp-button-label = Aldoni aŭ modifi bildojn pdfjs-editor-highlight-button = .title = Elstarigi pdfjs-editor-highlight-button-label = Elstarigi -pdfjs-highlight-floating-button = - .title = Elstarigi pdfjs-highlight-floating-button1 = .title = Elstarigi .aria-label = Elstarigi @@ -394,3 +392,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Montri ĉiujn pdfjs-editor-highlight-show-all-button = .title = Montri ĉiujn + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifi alternativan tekston (priskribo de bildo) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Aldoni alternativan tekston (priskribo de bildo) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skribu vian priskribon ĉi tie… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Mallonga priskribo por personoj kiuj ne povas vidi la bildon kaj por montri kiam la bildo ne ŝargeblas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tiu ĉi alternativa teksto estis aŭtomate kreita kaj povus esti malĝusta. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pli da informo +pdfjs-editor-new-alt-text-create-automatically-button-label = Aŭtomate krei alternativan tekston +pdfjs-editor-new-alt-text-not-now-button = Ne nun +pdfjs-editor-new-alt-text-error-title = Ne eblis aŭtomate krei alternativan tekston +pdfjs-editor-new-alt-text-error-description = Bonvolu skribi vian propran alternativan tekston aŭ provi denove poste. +pdfjs-editor-new-alt-text-error-close-button = Fermi +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Elŝuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO) + .aria-valuetext = Elŝuto de modelo de artefarita intelekto por alternativa teksto ({ $downloadedSize } el { $totalSize } MO) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativa teksto aldonita +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Mankas alternativa teksto +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Kontroli alternativan tekston +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Aŭtomate kreita: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Agordoj por alternativa teksto de bildoj +pdfjs-image-alt-text-settings-button-label = Agordoj por alternativa teksto de bildoj +pdfjs-editor-alt-text-settings-dialog-label = Agordoj por alternativa teksto de bildoj +pdfjs-editor-alt-text-settings-automatic-title = Aŭtomata alternativa teksto +pdfjs-editor-alt-text-settings-create-model-button-label = Aŭtomate krei alternativan tekston +pdfjs-editor-alt-text-settings-create-model-description = Tio ĉi sugestas priskribojn por helpi personojn kiuj ne povas vidi aŭ ŝargi la bildon. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de artefarita intelekto por alternativa teksto ({ $totalSize } MO) +pdfjs-editor-alt-text-settings-ai-model-description = Ĝi funkcias en via aparato, do viaj datumoj restas privataj. Ĝi estas postulata por aŭtomata kreado de alternativa teksto. +pdfjs-editor-alt-text-settings-delete-model-button = Forigi +pdfjs-editor-alt-text-settings-download-model-button = Elŝuti +pdfjs-editor-alt-text-settings-downloading-model-button = Elŝuto… +pdfjs-editor-alt-text-settings-editor-title = Redaktilo de alternativa teksto +pdfjs-editor-alt-text-settings-show-dialog-button-label = Montri redaktilon de alternativa teksto tuj post aldono de bildo +pdfjs-editor-alt-text-settings-show-dialog-description = Tio ĉi helpas vin kontroli ĉu ĉiuj bildoj havas alternativan tekston. +pdfjs-editor-alt-text-settings-close-button = Fermi diff --git a/viewer/locale/es-AR/viewer.ftl b/viewer/locale/es-AR/viewer.ftl index 40610b24e..686781e5a 100644 --- a/viewer/locale/es-AR/viewer.ftl +++ b/viewer/locale/es-AR/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Descargar pdfjs-bookmark-button = .title = Página actual (Ver URL de la página actual) pdfjs-bookmark-button-label = Página actual -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Abrir en la aplicación -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Abrir en la aplicación ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Propiedades del documento… pdfjs-document-properties-file-name = Nombre de archivo: pdfjs-document-properties-file-size = Tamaño de archovo: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Palabras clave: pdfjs-document-properties-creation-date = Fecha de creación: pdfjs-document-properties-modification-date = Fecha de modificación: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Anotación] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Agregar o editar imágenes pdfjs-editor-highlight-button = .title = Resaltar pdfjs-editor-highlight-button-label = Resaltar -pdfjs-highlight-floating-button = - .title = Resaltar pdfjs-highlight-floating-button1 = .title = Resaltar .aria-label = Resaltar @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostrar todo pdfjs-editor-highlight-show-all-button = .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Agregar texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribir la descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descripción corta para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Conocer más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = No ahora +pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escriba su propio texto alternativo o pruebe nuevamente más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo agregado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Configuración de texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Configuración de texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Configuración de texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en el dispositivo para que los datos se mantengan privados. Requerido para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Borrar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Descargando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al agregar una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarse de que todas las imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar diff --git a/viewer/locale/es-CL/viewer.ftl b/viewer/locale/es-CL/viewer.ftl index c4507a3fa..ede3d2520 100644 --- a/viewer/locale/es-CL/viewer.ftl +++ b/viewer/locale/es-CL/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Descargar pdfjs-bookmark-button = .title = Página actual (Ver URL de la página actual) pdfjs-bookmark-button-label = Página actual -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Abrir en una aplicación -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Abrir en una aplicación ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Propiedades del documento… pdfjs-document-properties-file-name = Nombre de archivo: pdfjs-document-properties-file-size = Tamaño del archivo: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Palabras clave: pdfjs-document-properties-creation-date = Fecha de creación: pdfjs-document-properties-modification-date = Fecha de modificación: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Anotación] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Añadir o editar imágenes pdfjs-editor-highlight-button = .title = Destacar pdfjs-editor-highlight-button-label = Destacar -pdfjs-highlight-floating-button = - .title = Destacar pdfjs-highlight-floating-button1 = .title = Destacar .aria-label = Destacar @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostrar todo pdfjs-editor-highlight-show-all-button = .title = Mostrar todo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descripción de la imagen) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Añadir texto alternativo (descripción de la imagen) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escribe tu descripción aquí… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descripción para las personas que no pueden ver la imagen o cuando la imagen no se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo fue creado automáticamente y puede ser incorrecto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Aprender más +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternativo automáticamente +pdfjs-editor-new-alt-text-not-now-button = Ahora no +pdfjs-editor-new-alt-text-error-title = No se pudo crear el texto alternativo automáticamente +pdfjs-editor-new-alt-text-error-description = Escribe tu propio texto alternativo o vuelve a intentarlo más tarde. +pdfjs-editor-new-alt-text-error-close-button = Cerrar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Descargando el modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Se añadió el texto alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Falta el texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revisar el texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creado automáticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ajustes del texto alternativo de la imagen +pdfjs-image-alt-text-settings-button-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-dialog-label = Ajustes del texto alternativo de la imagen +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternativo automáticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugiere descripciones para ayudar a las personas que no pueden ver la imagen o cuando la imagen no se carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Se ejecuta localmente en tu dispositivo para que tus datos permanezcan privados. Necesario para el texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Descargar +pdfjs-editor-alt-text-settings-downloading-model-button = Bajando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar el editor de texto alternativo inmediatamente al añadir una imagen +pdfjs-editor-alt-text-settings-show-dialog-description = Te ayuda a asegurarte de que todas tus imágenes tengan texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Cerrar diff --git a/viewer/locale/es-MX/viewer.ftl b/viewer/locale/es-MX/viewer.ftl index 0069c6eb5..5ca090a9c 100644 --- a/viewer/locale/es-MX/viewer.ftl +++ b/viewer/locale/es-MX/viewer.ftl @@ -42,15 +42,15 @@ pdfjs-print-button-label = Imprimir pdfjs-save-button = .title = Guardar pdfjs-save-button-label = Guardar +# Used in Firefox for Android as a tooltip for the download button (“download” is a verb). +pdfjs-download-button = + .title = Descargar +# Used in Firefox for Android as a label for the download button (“download” is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Descargar pdfjs-bookmark-button = .title = Página actual (Ver URL de la página actual) pdfjs-bookmark-button-label = Página actual -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Abrir en la aplicación -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Abrir en la aplicación ## Secondary toolbar and context menu @@ -277,6 +277,12 @@ pdfjs-editor-free-text-button-label = Texto pdfjs-editor-ink-button = .title = Dibujar pdfjs-editor-ink-button-label = Dibujar + +## Remove button for the various kind of editor. + + +## + # Editor Parameters pdfjs-editor-free-text-color-input = Color pdfjs-editor-free-text-size-input = Tamaño @@ -297,3 +303,25 @@ pdfjs-ink-canvas = ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. + +## Color picker + +pdfjs-editor-colorpicker-button = + .title = Cambiar color +pdfjs-editor-colorpicker-yellow = + .title = Amarillo +pdfjs-editor-colorpicker-green = + .title = Verde +pdfjs-editor-colorpicker-blue = + .title = Azul +pdfjs-editor-colorpicker-pink = + .title = Rosa +pdfjs-editor-colorpicker-red = + .title = Rojo + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Mostrar todo +pdfjs-editor-highlight-show-all-button = + .title = Mostrar todo diff --git a/viewer/locale/fi/viewer.ftl b/viewer/locale/fi/viewer.ftl index 51667837c..ebec89165 100644 --- a/viewer/locale/fi/viewer.ftl +++ b/viewer/locale/fi/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Lataa pdfjs-bookmark-button = .title = Nykyinen sivu (Näytä URL-osoite nykyiseltä sivulta) pdfjs-bookmark-button-label = Nykyinen sivu -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Avaa sovelluksessa -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Avaa sovelluksessa ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Lisää tai muokkaa kuvia pdfjs-editor-highlight-button = .title = Korostus pdfjs-editor-highlight-button-label = Korostus -pdfjs-highlight-floating-button = - .title = Korostus pdfjs-highlight-floating-button1 = .title = Korostus .aria-label = Korostus @@ -400,3 +392,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Näytä kaikki pdfjs-editor-highlight-show-all-button = .title = Näytä kaikki + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Muokkaa vaihtoehtoista tekstiä (kuvan kuvaus) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Lisää vaihtoehtoinen teksti (kuvan kuvaus) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Kirjoita kuvaus tähän… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Lyhyt kuvaus ihmisille, jotka eivät näe kuvaa tai kun kuva ei lataudu. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tämä vaihtoehtoinen teksti luotiin automaattisesti, ja se voi olla epätarkka. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Tämä vaihtoehtoinen teksti luotiin automaattisesti. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Lue lisää +pdfjs-editor-new-alt-text-create-automatically-button-label = Luo vaihtoehtoinen teksti automaattisesti +pdfjs-editor-new-alt-text-not-now-button = Ei nyt +pdfjs-editor-new-alt-text-error-title = Vaihtoehtotekstiä ei voitu luoda automaattisesti +pdfjs-editor-new-alt-text-error-description = Kirjoita oma vaihtoehtoinen teksti tai yritä myöhemmin uudelleen. +pdfjs-editor-new-alt-text-error-close-button = Sulje +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt) + .aria-valuetext = Ladataan vaihtoehtoisen tekstin tekoälymallia ({ $downloadedSize } / { $totalSize } Mt) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Vaihtoehtoinen teksti lisätty +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Vaihtoehtoinen teksti puuttuu +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Tarkista vaihtoehtoinen teksti +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Luotu automaattisesti: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-image-alt-text-settings-button-label = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-editor-alt-text-settings-dialog-label = Kuvan vaihtoehtoisen tekstin asetukset +pdfjs-editor-alt-text-settings-automatic-title = Automaattinen vaihtoehtoinen teksti +pdfjs-editor-alt-text-settings-create-model-button-label = Luo vaihtoehtoinen teksti automaattisesti +pdfjs-editor-alt-text-settings-create-model-description = Ehdottaa kuvauksia, jotka auttavat ihmisiä, jotka eivät näe kuvaa tai kun kuva ei lataudu. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Vaihtoehtoisen tekstin tekoälymalli ({ $totalSize } Mt) +pdfjs-editor-alt-text-settings-ai-model-description = Toimii paikallisesti laitteellasi, joten tietosi pysyvät yksityisinä. Vaadittu automaattiselle vaihtoehtoiselle tekstille. +pdfjs-editor-alt-text-settings-delete-model-button = Poista +pdfjs-editor-alt-text-settings-download-model-button = Lataa +pdfjs-editor-alt-text-settings-downloading-model-button = Ladataan… +pdfjs-editor-alt-text-settings-editor-title = Vaihtoehtoisen tekstin muokkain +pdfjs-editor-alt-text-settings-show-dialog-button-label = Näytä vaihtoehtoisen tekstin muokkain heti, kun lisäät kuvan +pdfjs-editor-alt-text-settings-show-dialog-description = Auttaa varmistamaan, että kaikissa kuvissasi on vaihtoehtoinen teksti. +pdfjs-editor-alt-text-settings-close-button = Sulje diff --git a/viewer/locale/fr/viewer.ftl b/viewer/locale/fr/viewer.ftl index 54c06c22c..bfd483fd2 100644 --- a/viewer/locale/fr/viewer.ftl +++ b/viewer/locale/fr/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Télécharger pdfjs-bookmark-button = .title = Page courante (montrer l’adresse de la page courante) pdfjs-bookmark-button-label = Page courante -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Ouvrir dans une application -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Ouvrir dans une application ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Propriétés du document… pdfjs-document-properties-file-name = Nom du fichier : pdfjs-document-properties-file-size = Taille du fichier : # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } Ko ({ $b } octets) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } Mo ({ $b } octets) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } Ko ({ $size_b } octets) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Mots-clés : pdfjs-document-properties-creation-date = Date de création : pdfjs-document-properties-modification-date = Modifié le : # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date } à { $time } @@ -277,6 +282,9 @@ pdfjs-annotation-date-string = { $date } à { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Annotation { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -300,8 +308,6 @@ pdfjs-editor-stamp-button-label = Ajouter ou modifier des images pdfjs-editor-highlight-button = .title = Surligner pdfjs-editor-highlight-button-label = Surligner -pdfjs-highlight-floating-button = - .title = Surligner pdfjs-highlight-floating-button1 = .title = Surligner .aria-label = Surligner @@ -396,3 +402,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Tout afficher pdfjs-editor-highlight-show-all-button = .title = Tout afficher + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifier le texte alternatif (description de l’image) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Ajouter du texte alternatif (description de l’image) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Rédigez votre description ici… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Courte description pour les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ce texte alternatif a été créé automatiquement et peut être inexact. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = En savoir plus +pdfjs-editor-new-alt-text-create-automatically-button-label = Créer automatiquement le texte alternatif +pdfjs-editor-new-alt-text-not-now-button = Pas maintenant +pdfjs-editor-new-alt-text-error-title = Impossible de créer automatiquement le texte alternatif +pdfjs-editor-new-alt-text-error-description = Veuillez rédiger votre propre texte alternatif ou réessayer plus tard. +pdfjs-editor-new-alt-text-error-close-button = Fermer +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo) + .aria-valuetext = Téléchargement du modèle d’IA de texte alternatif ({ $downloadedSize } sur { $totalSize } Mo) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Texte alternatif ajouté +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Texte alternatif manquant +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Réviser le texte alternatif +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Créé automatiquement : { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Paramètres du texte alternatif des images +pdfjs-image-alt-text-settings-button-label = Paramètres du texte alternatif des images +pdfjs-editor-alt-text-settings-dialog-label = Paramètres du texte alternatif des images +pdfjs-editor-alt-text-settings-automatic-title = Texte alternatif automatique +pdfjs-editor-alt-text-settings-create-model-button-label = Créer automatiquement le texte alternatif +pdfjs-editor-alt-text-settings-create-model-description = Suggère des descriptions pour aider les personnes qui ne peuvent pas voir l’image ou lorsque l’image ne se charge pas. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modèle d’IA de texte alternatif ({ $totalSize } Mo) +pdfjs-editor-alt-text-settings-ai-model-description = Fonctionne localement sur votre appareil, vos données restent privées. Obligatoire pour la génération automatique de texte alternatif. +pdfjs-editor-alt-text-settings-delete-model-button = Supprimer +pdfjs-editor-alt-text-settings-download-model-button = Télécharger +pdfjs-editor-alt-text-settings-downloading-model-button = Téléchargement… +pdfjs-editor-alt-text-settings-editor-title = Éditeur de texte alternatif +pdfjs-editor-alt-text-settings-show-dialog-button-label = Afficher l’éditeur de texte alternatif immédiatement lors de l’ajout d’une image +pdfjs-editor-alt-text-settings-show-dialog-description = Vous aide à vous assurer que toutes vos images ont du texte alternatif. +pdfjs-editor-alt-text-settings-close-button = Fermer diff --git a/viewer/locale/fur/viewer.ftl b/viewer/locale/fur/viewer.ftl index 8e8c8a017..79688eea3 100644 --- a/viewer/locale/fur/viewer.ftl +++ b/viewer/locale/fur/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Discjame pdfjs-bookmark-button = .title = Pagjine corinte (mostre URL de pagjine atuâl) pdfjs-bookmark-button-label = Pagjine corinte -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Vierç te aplicazion -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Vierç te aplicazion ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Zonte o modifiche imagjins pdfjs-editor-highlight-button = .title = Evidenzie pdfjs-editor-highlight-button-label = Evidenzie -pdfjs-highlight-floating-button = - .title = Evidenzie pdfjs-highlight-floating-button1 = .title = Evidenzie .aria-label = Evidenzie @@ -400,3 +392,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostre dut pdfjs-editor-highlight-show-all-button = .title = Mostre dut + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifiche test alternatîf (descrizion de imagjin) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Zonte test alternatîf (descrizion de imagjin) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scrîf achì la tô descrizion… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Curte descrizion par personis che no rivin a viodi la imagjin, o che e ven mostrade cuant che no si rive a cjariâle. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Chest test alternatîf al è stât creât in automatic e al è pussibil che nol sedi cret. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Chest test alternatîf al è stât creât in automatic. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Plui informazions +pdfjs-editor-new-alt-text-create-automatically-button-label = Cree test alternatîf in automatic +pdfjs-editor-new-alt-text-not-now-button = No cumò +pdfjs-editor-new-alt-text-error-title = Impussibil creâ test alternatîf in automatic +pdfjs-editor-new-alt-text-error-description = Scrîf il to test alternatîf o prove plui tart. +pdfjs-editor-new-alt-text-error-close-button = Siere +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Daûr a discjariâil model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB) + .aria-valuetext = Daûr a discjariâ il model IA pal test alternatîf ({ $downloadedSize } di { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Test alternatîf zontât +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Al mancje il test alternatîf +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Verifiche test alternatîf +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creât in automatic: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Impostazions test alternatîf pes imagjins +pdfjs-image-alt-text-settings-button-label = Impostazions test alternatîf pes imagjins +pdfjs-editor-alt-text-settings-dialog-label = Impostazions test alternatîf pes imagjins +pdfjs-editor-alt-text-settings-automatic-title = Test alternatîf automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Cree test alternatîf in automatic +pdfjs-editor-alt-text-settings-create-model-description = Al sugjerìs descrizions par judâ lis personis che no rivin a viodi la imagjin o cuant che la imagjin no ven cjariade. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model IA pal test alternatîf ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Al ven eseguît in locâl sul to dispositîf, cussì che i tiei dâts a restin riservâts. Al è necessari pe gjenerazion automatiche dal test alternatîf. +pdfjs-editor-alt-text-settings-delete-model-button = Elimine +pdfjs-editor-alt-text-settings-download-model-button = Discjame +pdfjs-editor-alt-text-settings-downloading-model-button = Daûr a discjariâ… +pdfjs-editor-alt-text-settings-editor-title = Modifiche test alternatîf +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostre l'editôr dal test alternatîf a pene che e ven zontade une imagjin +pdfjs-editor-alt-text-settings-show-dialog-description = Ti jude a sigurâti che dutis lis tôs imagjins a vedin il test alternatîf. +pdfjs-editor-alt-text-settings-close-button = Siere diff --git a/viewer/locale/fy-NL/viewer.ftl b/viewer/locale/fy-NL/viewer.ftl index a67f9b9d3..d2af154ac 100644 --- a/viewer/locale/fy-NL/viewer.ftl +++ b/viewer/locale/fy-NL/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Downloade pdfjs-bookmark-button = .title = Aktuele side (URL fan aktuele side besjen) pdfjs-bookmark-button-label = Aktuele side -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Iepenje yn app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Iepenje yn app ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Dokuminteigenskippen… pdfjs-document-properties-file-name = Bestânsnamme: pdfjs-document-properties-file-size = Bestânsgrutte: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Kaaiwurden: pdfjs-document-properties-creation-date = Oanmaakdatum: pdfjs-document-properties-modification-date = Bewurkingsdatum: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type }-annotaasje] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Ofbyldingen tafoegje of bewurkje pdfjs-editor-highlight-button = .title = Markearje pdfjs-editor-highlight-button-label = Markearje -pdfjs-highlight-floating-button = - .title = Markearje pdfjs-highlight-floating-button1 = .title = Markearje .aria-label = Markearje @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Alles toane pdfjs-editor-highlight-show-all-button = .title = Alles toane + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternative tekst (ôfbyldingsbeskriuwing) bewurkje +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternative tekst (ôfbyldingsbeskriuwing) tafoegje +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriuw hjir jo beskriuwing... +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Koarte beskriuwing foar minsken dy’t de ôfbylding net sjen kinne of wannear’t de ôfbylding net laden wurdt. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Dizze alternative tekst is automatysk makke en is mooglik net korrekt. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mear ynfo +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternative tekst automatysk oanmeitsje +pdfjs-editor-new-alt-text-not-now-button = No net +pdfjs-editor-new-alt-text-error-title = Kin alternative tekst net automatysk oanmeitsje +pdfjs-editor-new-alt-text-error-description = Skriuw jo eigen alternative tekst of probearje it letter nochris. +pdfjs-editor-new-alt-text-error-close-button = Slute +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB) + .aria-valuetext = AI-model foar alternative tekst downloade ({ $downloadedSize } fan { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternative tekst tafoege +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Alternative tekst ûntbrekt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternative tekst beoardiele +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatysk oanmakke: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-image-alt-text-settings-button-label = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-editor-alt-text-settings-dialog-label = Ynstellingen foar alternative tekst fan ôfbyldingen +pdfjs-editor-alt-text-settings-automatic-title = Automatyske alternative tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternative tekst automatysk oanmeitsje +pdfjs-editor-alt-text-settings-create-model-description = Stelt beskriuwingen foar om minsken te helpen dy’t de ôfbylding net sjen kinne of foar wa’t de ôfbylding net laden wurdt. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model foar alternative tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wurdt lokaal op jo apparaat útfierd, sadat jo gegevens privee bliuwe. Fereaske foar automatyske alternative tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Fuortsmite +pdfjs-editor-alt-text-settings-download-model-button = Downloade +pdfjs-editor-alt-text-settings-downloading-model-button = Downloade… +pdfjs-editor-alt-text-settings-editor-title = Alternative-tekstbewurker +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternative-tekstbewurker daliks toane by tafoegjen fan in ôfbylding +pdfjs-editor-alt-text-settings-show-dialog-description = Helpt jo derfoar te soargjen dat al jo ôfbyldingen alternative tekst hawwe. +pdfjs-editor-alt-text-settings-close-button = Slute diff --git a/viewer/locale/gn/viewer.ftl b/viewer/locale/gn/viewer.ftl index 29ec18ae3..9f1493c37 100644 --- a/viewer/locale/gn/viewer.ftl +++ b/viewer/locale/gn/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Mboguejy pdfjs-bookmark-button = .title = Kuatiarogue ag̃agua (Ehecha URL kuatiarogue ag̃agua) pdfjs-bookmark-button-label = Kuatiarogue Ag̃agua -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Embojuruja tembiporu’ípe -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Embojuruja tembiporu’ípe ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Embojuaju térã embosako’i ta’ãnga pdfjs-editor-highlight-button = .title = Mbosa’y pdfjs-editor-highlight-button-label = Mbosa’y -pdfjs-highlight-floating-button = - .title = Mbosa’y pdfjs-highlight-floating-button1 = .title = Mbosa’y .aria-label = Mbosa’y @@ -400,3 +392,56 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Techaukapa pdfjs-editor-highlight-show-all-button = .title = Techaukapa + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Embosako’i moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Embojuaju moñe’ẽrã mokõiha (ta’ãngáre ñeñe’ẽ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Edescribi ko’ápe… +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Ko moñe’ẽrã mokõiha heñói ijeheguiete. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Eikuaave +pdfjs-editor-new-alt-text-create-automatically-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-new-alt-text-not-now-button = Ani ko’ág̃a +pdfjs-editor-new-alt-text-error-title = Noñemoheñói moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-new-alt-text-error-description = Ehai ne moñe’ẽrã mokõiha térã eha’ã jey ag̃amieve. +pdfjs-editor-new-alt-text-error-close-button = Mboty +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e + .aria-valuetext = Emboguejyhína IA moñe’ẽrã mokõiháva ({ $downloadedSize } { $totalSize } MB) mba’e +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Oñembojuaju moñe’ẽrã mokõiha +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Ndaipóri moñe’ẽrã mokõiha +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Ehechajey moñe’ẽrã mokõiha +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Heñóiva ijeheguiete: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-image-alt-text-settings-button-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-editor-alt-text-settings-dialog-label = Ta’ãnga moñe’ẽrã mokõiha ñemboheko +pdfjs-editor-alt-text-settings-automatic-title = Moñe’ẽrã mokõiha ijeheguíva +pdfjs-editor-alt-text-settings-create-model-button-label = Emoheñói moñe’ẽrã mokõiha ijeheguíva +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Peteĩva IA moñe’ẽrã mokõiha ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-delete-model-button = Mboguete +pdfjs-editor-alt-text-settings-download-model-button = Mboguejy +pdfjs-editor-alt-text-settings-downloading-model-button = Emboguejyhína… +pdfjs-editor-alt-text-settings-editor-title = Moñe’ẽrã mokõiha mbosako’iha +pdfjs-editor-alt-text-settings-show-dialog-button-label = Ehechauka moñe’ẽrã mokõiha mbosako’iha embojuajúvo ta’ãnga +pdfjs-editor-alt-text-settings-show-dialog-description = Nepytyvõta ta’ãngakuéra orekotaha moñe’ẽrã mokõiha. +pdfjs-editor-alt-text-settings-close-button = Mboty diff --git a/viewer/locale/he/viewer.ftl b/viewer/locale/he/viewer.ftl index 624d2083f..8824f624f 100644 --- a/viewer/locale/he/viewer.ftl +++ b/viewer/locale/he/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = הורדה pdfjs-bookmark-button = .title = עמוד נוכחי (הצגת כתובת האתר מהעמוד הנוכחי) pdfjs-bookmark-button-label = עמוד נוכחי -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = פתיחה ביישום -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = פתיחה ביישום ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = מאפייני מסמך… pdfjs-document-properties-file-name = שם קובץ: pdfjs-document-properties-file-size = גודל הקובץ: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } ק״ב ({ $b } בתים) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } מ״ב ({ $b } בתים) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } ק״ב ({ $size_b } בתים) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = מילות מפתח: pdfjs-document-properties-creation-date = תאריך יצירה: pdfjs-document-properties-modification-date = תאריך שינוי: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [הערת { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = הוספה או עריכת תמונות pdfjs-editor-highlight-button = .title = סימון pdfjs-editor-highlight-button-label = סימון -pdfjs-highlight-floating-button = - .title = סימון pdfjs-highlight-floating-button1 = .title = סימון .aria-label = סימון @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = הצגת הכול pdfjs-editor-highlight-show-all-button = .title = הצגת הכול + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = עריכת טקסט חלופי (תיאור תמונה) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = הוספת טקסט חלופי (תיאור תמונה) +pdfjs-editor-new-alt-text-textarea = + .placeholder = נא לכתוב את התיאור שלך כאן… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = תיאור קצר לאנשים שאינם יכולים לראות את התמונה או כאשר התמונה אינה נטענת. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = טקסט חלופי זה נוצר באופן אוטומטי ועשוי להיות לא מדויק. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = מידע נוסף +pdfjs-editor-new-alt-text-create-automatically-button-label = יצירת טקסט חלופי באופן אוטומטי +pdfjs-editor-new-alt-text-not-now-button = לא כעת +pdfjs-editor-new-alt-text-error-title = לא ניתן היה ליצור טקסט חלופי באופן אוטומטי +pdfjs-editor-new-alt-text-error-description = נא לכתוב טקסט חלופי משלך או לנסות שוב מאוחר יותר. +pdfjs-editor-new-alt-text-error-close-button = סגירה +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב) + .aria-valuetext = בתהליך הורדת מודל AI של טקסט חלופי ({ $downloadedSize } מתוך { $totalSize } מ״ב) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = טקסט חלופי נוסף +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = חסר טקסט חלופי +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = סקירת טקסט חלופי +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = נוצר באופן אוטומטי: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = הגדרות טקסט חלופי של תמונה +pdfjs-image-alt-text-settings-button-label = הגדרות טקסט חלופי של תמונה +pdfjs-editor-alt-text-settings-dialog-label = הגדרות טקסט חלופי של תמונה +pdfjs-editor-alt-text-settings-automatic-title = טקסט חלופי אוטומטי +pdfjs-editor-alt-text-settings-create-model-button-label = יצירת טקסט חלופי באופן אוטומטי +pdfjs-editor-alt-text-settings-create-model-description = הצעת תיאורים כדי לסייע לאנשים שאינם יכולים לראות את התמונה או כאשר התמונה אינה נטענת. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = מודל AI לטקסט חלופי ({ $totalSize } מ״ב) +pdfjs-editor-alt-text-settings-ai-model-description = פועל באופן מקומי במכשיר שלך כך שהנתונים שלך נשארים פרטיים. נדרש עבור טקסט חלופי אוטומטי. +pdfjs-editor-alt-text-settings-delete-model-button = מחיקה +pdfjs-editor-alt-text-settings-download-model-button = הורדה +pdfjs-editor-alt-text-settings-downloading-model-button = בהורדה… +pdfjs-editor-alt-text-settings-editor-title = עורך טקסט חלופי +pdfjs-editor-alt-text-settings-show-dialog-button-label = הצגת עורך טקסט חלופי מיד בעת הוספת תמונה +pdfjs-editor-alt-text-settings-show-dialog-description = מסייע לך לוודא שלכל התמונות שלך יש טקסט חלופי. +pdfjs-editor-alt-text-settings-close-button = סגירה diff --git a/viewer/locale/hr/viewer.ftl b/viewer/locale/hr/viewer.ftl index 23d88e76a..7867f5078 100644 --- a/viewer/locale/hr/viewer.ftl +++ b/viewer/locale/hr/viewer.ftl @@ -42,6 +42,15 @@ pdfjs-print-button-label = Ispiši pdfjs-save-button = .title = Spremi pdfjs-save-button-label = Spremi +# Used in Firefox for Android as a tooltip for the download button (“download” is a verb). +pdfjs-download-button = + .title = Preuzimanja +# Used in Firefox for Android as a label for the download button (“download” is a verb). +# Length of the translation matters since we are in a mobile context, with limited screen estate. +pdfjs-download-button-label = Preuzimanja +pdfjs-bookmark-button = + .title = Trenutna stranica (pogledajte URL s trenutne stranice) +pdfjs-bookmark-button-label = Trenutna stranica ## Secondary toolbar and context menu @@ -66,6 +75,9 @@ pdfjs-cursor-text-select-tool-button-label = Alat za označavanje teksta pdfjs-cursor-hand-tool-button = .title = Omogući ručni alat pdfjs-cursor-hand-tool-button-label = Ručni alat +pdfjs-scroll-page-button = + .title = Koristi klizanje stranice +pdfjs-scroll-page-button-label = Klizanje stranice pdfjs-scroll-vertical-button = .title = Koristi okomito pomicanje pdfjs-scroll-vertical-button-label = Okomito pomicanje @@ -90,7 +102,7 @@ pdfjs-spread-even-button-label = Parne duplerice pdfjs-document-properties-button = .title = Svojstva dokumenta … pdfjs-document-properties-button-label = Svojstva dokumenta … -pdfjs-document-properties-file-name = Naziv datoteke: +pdfjs-document-properties-file-name = Ime datoteke: pdfjs-document-properties-file-size = Veličina datoteke: # Variables: # $size_kb (Number) - the PDF file size in kilobytes @@ -112,7 +124,7 @@ pdfjs-document-properties-modification-date = Datum promjene: pdfjs-document-properties-date-string = { $date }, { $time } pdfjs-document-properties-creator = Stvaratelj: pdfjs-document-properties-producer = PDF stvaratelj: -pdfjs-document-properties-version = PDF verzija: +pdfjs-document-properties-version = PDF inačica: pdfjs-document-properties-page-count = Broj stranica: pdfjs-document-properties-page-size = Dimenzije stranice: pdfjs-document-properties-page-size-unit-inches = in @@ -201,12 +213,30 @@ pdfjs-find-previous-button = pdfjs-find-previous-button-label = Prethodno pdfjs-find-next-button = .title = Pronađi sljedeće pojavljivanje ovog izraza -pdfjs-find-next-button-label = Sljedeće +pdfjs-find-next-button-label = Dalje pdfjs-find-highlight-checkbox = Istankni sve pdfjs-find-match-case-checkbox-label = Razlikovanje velikih i malih slova +pdfjs-find-match-diacritics-checkbox-label = Razlikuj dijakritičke znakove pdfjs-find-entire-word-checkbox-label = Cijele riječi pdfjs-find-reached-top = Dosegnut početak dokumenta, nastavak s kraja pdfjs-find-reached-bottom = Dosegnut kraj dokumenta, nastavak s početka +# Variables: +# $current (Number) - the index of the currently active find result +# $total (Number) - the total number of matches in the document +pdfjs-find-match-count = + { NUMBER($total) -> + [one] { $current } od { $total } rezultata + [few] { $current } od { $total } rezultata + *[other] { $current } od { $total } rezultata + } +# Variables: +# $limit (Number) - the maximum number of matches +pdfjs-find-match-count-limit = + { NUMBER($limit) -> + [one] Više od { $limit } rezultat + [few] Više od { $limit } rezultata + *[other] Više od { $limit } rezultata + } pdfjs-find-not-found = Izraz nije pronađen ## Predefined zoom values @@ -231,7 +261,7 @@ pdfjs-page-landmark = pdfjs-loading-error = Došlo je do greške pri učitavanju PDF-a. pdfjs-invalid-file-error = Neispravna ili oštećena PDF datoteka. pdfjs-missing-file-error = Nedostaje PDF datoteka. -pdfjs-unexpected-response-error = Neočekivani odgovor poslužitelja. +pdfjs-unexpected-response-error = Neočekivani odgovor servera. pdfjs-rendering-error = Došlo je do greške prilikom iscrtavanja stranice. ## Annotations @@ -261,19 +291,128 @@ pdfjs-web-fonts-disabled = Web fontovi su deaktivirani: nije moguće koristiti u pdfjs-editor-free-text-button = .title = Tekst pdfjs-editor-free-text-button-label = Tekst +pdfjs-editor-ink-button = + .title = Crtanje +pdfjs-editor-ink-button-label = Crtanje +pdfjs-editor-stamp-button = + .title = Dodajte ili uredite slike +pdfjs-editor-stamp-button-label = Dodajte ili uredite slike +pdfjs-editor-highlight-button = + .title = Istakni +pdfjs-editor-highlight-button-label = Istakni +pdfjs-highlight-floating-button1 = + .title = Istakni + .aria-label = Istakni +pdfjs-highlight-floating-button-label = Istakni + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Ukloni crtež +pdfjs-editor-remove-freetext-button = + .title = Ukloni tekst +pdfjs-editor-remove-stamp-button = + .title = Ukloni sliku +pdfjs-editor-remove-highlight-button = + .title = Ukloni isticanje + +## + # Editor Parameters pdfjs-editor-free-text-color-input = Boja pdfjs-editor-free-text-size-input = Veličina pdfjs-editor-ink-color-input = Boja pdfjs-editor-ink-thickness-input = Debljina pdfjs-editor-ink-opacity-input = Neprozirnost +pdfjs-editor-stamp-add-image-button = + .title = Dodaj sliku +pdfjs-editor-stamp-add-image-button-label = Dodaj sliku +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Debljina +pdfjs-editor-free-highlight-thickness-title = + .title = Promjeni debljinu pri isticanju drugih stavki osim teksta pdfjs-free-text = .aria-label = Uređivač teksta pdfjs-free-text-default-content = Počni tipkati … +pdfjs-ink = + .aria-label = Uređivač crteža +pdfjs-ink-canvas = + .aria-label = Slika koju je izradio korisnik ## Alt-text dialog +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Alternativni tekst +pdfjs-editor-alt-text-edit-button-label = Uredi alternativni tekst +pdfjs-editor-alt-text-dialog-label = Odaberi jednu opciju +pdfjs-editor-alt-text-dialog-description = Alternativni tekst pomaže slijepim osobama ili kada se slika ne učita. +pdfjs-editor-alt-text-add-description-label = Dodaj opis +pdfjs-editor-alt-text-add-description-description = Sažmi sadržaj predmeta, okruženje ili radnje u jednoj ili dvije rečenice. +pdfjs-editor-alt-text-mark-decorative-label = Označi kao ukrasno +pdfjs-editor-alt-text-mark-decorative-description = Ovo se koristi za ukrasne slike, poput rubova ili vodenih žigova. +pdfjs-editor-alt-text-cancel-button = Odustani +pdfjs-editor-alt-text-save-button = Spremi +pdfjs-editor-alt-text-decorative-tooltip = Označeno kao ukrasno +# .placeholder: This is a placeholder for the alt text input area +pdfjs-editor-alt-text-textarea = + .placeholder = Na primjer, „Mladić sjeda za stol kako bi jeo” ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. +pdfjs-editor-resizer-label-top-left = Gornji lijevi kut — promijenite veličinu +pdfjs-editor-resizer-label-top-middle = Gore sredina — promijenite veličinu +pdfjs-editor-resizer-label-top-right = Gornji desni kut — promijenite veličinu +pdfjs-editor-resizer-label-middle-right = Sredina desno — promijenite veličinu +pdfjs-editor-resizer-label-bottom-right = Donji desni kut — promijenite veličinu +pdfjs-editor-resizer-label-bottom-middle = Dolje sredina — promjenite veličinu +pdfjs-editor-resizer-label-bottom-left = Donji lijevi kut — promijenite veličinu +pdfjs-editor-resizer-label-middle-left = Sredina lijevo — promijenite veličinu + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Boja isticanja +pdfjs-editor-colorpicker-button = + .title = Promjeni boju +pdfjs-editor-colorpicker-dropdown = + .aria-label = Izbor boja +pdfjs-editor-colorpicker-yellow = + .title = Žuta +pdfjs-editor-colorpicker-green = + .title = Zelena +pdfjs-editor-colorpicker-blue = + .title = Plava +pdfjs-editor-colorpicker-pink = + .title = Ružičasta +pdfjs-editor-colorpicker-red = + .title = Crvena + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Prikaži sve +pdfjs-editor-highlight-show-all-button = + .title = Prikaži sve + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saznaj više +pdfjs-editor-new-alt-text-create-automatically-button-label = Automatski stvori zamjenski tekst + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Postavke zamjenskog teksta slike +pdfjs-image-alt-text-settings-button-label = Postavke zamjenskog teksta slike +pdfjs-editor-alt-text-settings-dialog-label = Postavke zamjenskog teksta slike +pdfjs-editor-alt-text-settings-automatic-title = Automatski zamjenski tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Automatski stvori zamjenski tekst +pdfjs-editor-alt-text-settings-delete-model-button = Izbriši +pdfjs-editor-alt-text-settings-download-model-button = Preuzmi +pdfjs-editor-alt-text-settings-downloading-model-button = Preuzimanje … +pdfjs-editor-alt-text-settings-editor-title = Uređivač zamjenskog teksta +pdfjs-editor-alt-text-settings-show-dialog-button-label = Prikaži uređivač zamjenskog teksta odmah pri dodavanju slike +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaže osigurati da sve tvoje slike imaju zamjenski tekst. +pdfjs-editor-alt-text-settings-close-button = Zatvori diff --git a/viewer/locale/hsb/viewer.ftl b/viewer/locale/hsb/viewer.ftl index 46feaf1bd..2f8e920fe 100644 --- a/viewer/locale/hsb/viewer.ftl +++ b/viewer/locale/hsb/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Sćahnyć pdfjs-bookmark-button = .title = Aktualna strona (URL z aktualneje strony pokazać) pdfjs-bookmark-button-label = Aktualna strona -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = W nałoženju wočinić -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = W nałoženju wočinić ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Dokumentowe kajkosće… pdfjs-document-properties-file-name = Mjeno dataje: pdfjs-document-properties-file-size = Wulkosć dataje: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtow) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtow) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtow) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Klučowe słowa: pdfjs-document-properties-creation-date = Datum wutworjenja: pdfjs-document-properties-modification-date = Datum změny: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -285,6 +290,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Typ přispomnjenki: { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -308,8 +316,6 @@ pdfjs-editor-stamp-button-label = Wobrazy přidać abo wobdźěłać pdfjs-editor-highlight-button = .title = Wuzběhnyć pdfjs-editor-highlight-button-label = Wuzběhnyć -pdfjs-highlight-floating-button = - .title = Wuzběhnyć pdfjs-highlight-floating-button1 = .title = Wuzběhnjenje .aria-label = Wuzběhnjenje @@ -404,3 +410,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Wšě pokazać pdfjs-editor-highlight-show-all-button = .title = Wšě pokazać + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatiwny tekst wobdźěłać (wobrazowe wopisanje) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatiwny tekst přidać (wobrazowe wopisanje) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Pisajće tu swoje wopisanje… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krótke wopisanje za ludźi, kotřiž njemóžeće wobraz widźeć abo hdyž so wobraz njezačita. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tutón alternatiwny tekst je so awtomatisce wutworił a je snano njedokładny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Dalše informacije +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatiwny tekst awtomatisce wutworić +pdfjs-editor-new-alt-text-not-now-button = Nic nětko +pdfjs-editor-new-alt-text-error-title = Alternatiwny tekst njeda so awtomatisce wutworić +pdfjs-editor-new-alt-text-error-description = Prošu pisajće swój alternatiwny tekst abo spytajće pozdźišo hišće raz. +pdfjs-editor-new-alt-text-error-close-button = Začinić +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Model KI za alternatiwny tekst so sćahuje ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternatiwny tekst je so přidał +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Alternatiwny tekst faluje +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternatiwny tekst přepruwować +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Awtomatisce wutworjeny: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-image-alt-text-settings-button-label = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-editor-alt-text-settings-dialog-label = Nastajenja alternatiwneho wobrazoweho teksta +pdfjs-editor-alt-text-settings-automatic-title = Awtomatiski alternatiwny tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatiwny tekst awtomatisce wutworić +pdfjs-editor-alt-text-settings-create-model-description = Namjetuje wopisanja, zo by ludźom pomhał, kotřiž njemóžeće wobraz widźeć abo hdyž so wobraz njezačita. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model KI alternatiwneho teksta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Běži lokalnje na wašim graće, zo bychu waše daty priwatne wostali. Za awtomatiski alternatiwny tekst trěbny. +pdfjs-editor-alt-text-settings-delete-model-button = Zhašeć +pdfjs-editor-alt-text-settings-download-model-button = Sćahnyć +pdfjs-editor-alt-text-settings-downloading-model-button = Sćahuje so… +pdfjs-editor-alt-text-settings-editor-title = Editor za alternatiwny tekst +pdfjs-editor-alt-text-settings-show-dialog-button-label = Editor alternatiwneho teksta hnydom pokazać, hdyž so wobraz přidawa +pdfjs-editor-alt-text-settings-show-dialog-description = Pomha, wam wšěm swojim wobrazam alternatiwny tekst přidać. +pdfjs-editor-alt-text-settings-close-button = Začinić diff --git a/viewer/locale/hu/viewer.ftl b/viewer/locale/hu/viewer.ftl index 0c33e51be..5a1956a35 100644 --- a/viewer/locale/hu/viewer.ftl +++ b/viewer/locale/hu/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = Dokumentum tulajdonságai… pdfjs-document-properties-file-name = Fájlnév: pdfjs-document-properties-file-size = Fájlméret: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } bájt) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bájt) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bájt) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = Kulcsszavak: pdfjs-document-properties-creation-date = Létrehozás dátuma: pdfjs-document-properties-modification-date = Módosítás dátuma: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -275,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } megjegyzés] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -298,8 +312,6 @@ pdfjs-editor-stamp-button-label = Képek hozzáadása vagy szerkesztése pdfjs-editor-highlight-button = .title = Kiemelés pdfjs-editor-highlight-button-label = Kiemelés -pdfjs-highlight-floating-button = - .title = Kiemelés pdfjs-highlight-floating-button1 = .title = Kiemelés .aria-label = Kiemelés @@ -394,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Összes megjelenítése pdfjs-editor-highlight-show-all-button = .title = Összes megjelenítése + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatív szöveg szerkesztése (képleírás) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatív szöveg hozzáadása (képleírás) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Írja ide a leírását… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Rövid leírás azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ez az alternatív szöveg automatikusan lett létrehozva, és pontatlan lehet. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = További tudnivalók +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatív szöveg automatikus létrehozása +pdfjs-editor-new-alt-text-not-now-button = Most nem +pdfjs-editor-new-alt-text-error-title = Az alternatív szöveg automatikus létrehozása nem sikerült +pdfjs-editor-new-alt-text-error-description = Írja meg a saját alternatív szövegét, vagy próbálja újra később. +pdfjs-editor-new-alt-text-error-close-button = Bezárás +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = Alternatív szöveg MI modell letöltése ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternatív szöveg hozzáadva +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Hiányzó alternatív szöveg +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternatív szöveg szerkesztése +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikusan létrehozva: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Kép alternatív szövegének beállításai +pdfjs-image-alt-text-settings-button-label = Kép alternatív szövegének beállításai +pdfjs-editor-alt-text-settings-dialog-label = Kép alternatív szövegének beállításai +pdfjs-editor-alt-text-settings-automatic-title = Automatikus alternatív szöveg +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatív szöveg automatikus létrehozása +pdfjs-editor-alt-text-settings-create-model-description = Leírásokat javasol, hogy segítsen azoknak, akik nem látják a képet, vagy arra az esetre, ha a kép nem tölt be. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alternatív szöveg MI modellje ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Helyben fut az eszközén, így az adatai privátok maradnak. Az automatikus alternatív szövegekhez szükséges. +pdfjs-editor-alt-text-settings-delete-model-button = Törlés +pdfjs-editor-alt-text-settings-download-model-button = Letöltés +pdfjs-editor-alt-text-settings-downloading-model-button = Letöltés… +pdfjs-editor-alt-text-settings-editor-title = Alternatív szöveg szerkesztője +pdfjs-editor-alt-text-settings-show-dialog-button-label = Az alternatív szöveg szerkesztőjének azonnali megjelenítése egy kép hozzáadásakor +pdfjs-editor-alt-text-settings-show-dialog-description = Segít elérni, hogy az összes képén legyen alternatív szöveg. +pdfjs-editor-alt-text-settings-close-button = Bezárás diff --git a/viewer/locale/ia/viewer.ftl b/viewer/locale/ia/viewer.ftl index 4cddfa281..bf207ab41 100644 --- a/viewer/locale/ia/viewer.ftl +++ b/viewer/locale/ia/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = Proprietates del documento… pdfjs-document-properties-file-name = Nomine del file: pdfjs-document-properties-file-size = Dimension de file: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = Parolas clave: pdfjs-document-properties-creation-date = Data de creation: pdfjs-document-properties-modification-date = Data de modification: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -275,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Annotation] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -392,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Monstrar toto pdfjs-editor-highlight-show-all-button = .title = Monstrar toto + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger texto alternative (description del imagine) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adder texto alternative (description del imagine) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scribe tu description ci… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve description pro personas qui non pote vider le imagine o quando le imagine non se carga. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Iste texto alternative ha essite create automaticamente e pote esser inexacte. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Pro saper plus +pdfjs-editor-new-alt-text-create-automatically-button-label = Crear texto alternative automaticamente +pdfjs-editor-new-alt-text-not-now-button = Non ora +pdfjs-editor-new-alt-text-error-title = Impossibile crear texto alternative automaticamente +pdfjs-editor-new-alt-text-error-description = Scribe tu proprie texto alternative o retenta plus tarde. +pdfjs-editor-new-alt-text-error-close-button = Clauder +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Discargante modello de intelligentia artificial del texto alternative ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Texto alternative addite +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Texto alternative mancante +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revider texto alternative +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automaticamente create: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Parametros del texto alternative del imagine +pdfjs-image-alt-text-settings-button-label = Parametros del texto alternative del imagine +pdfjs-editor-alt-text-settings-dialog-label = Parametros del texto alternative del imagine +pdfjs-editor-alt-text-settings-automatic-title = Texto alternative automatic +pdfjs-editor-alt-text-settings-create-model-button-label = Crear texto alternative automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Suggere descriptiones pro adjutar le personas qui non pote vider le imagine o quando le imagine non carga. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modello de intelligentia artificial del texto alternative ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Flue localmente sur tu apparato assi tu datos remane private. Necessari pro texto alternative automatic. +pdfjs-editor-alt-text-settings-delete-model-button = Deler +pdfjs-editor-alt-text-settings-download-model-button = Discargar +pdfjs-editor-alt-text-settings-downloading-model-button = Discargante… +pdfjs-editor-alt-text-settings-editor-title = Rediger texto alternative +pdfjs-editor-alt-text-settings-show-dialog-button-label = Monstrar le redactor de texto alternative a pena on adde un imagine +pdfjs-editor-alt-text-settings-show-dialog-description = Te adjuta a verifica que tote tu imagines ha un texto alternative. +pdfjs-editor-alt-text-settings-close-button = Clauder diff --git a/viewer/locale/is/viewer.ftl b/viewer/locale/is/viewer.ftl index d3afef3ea..5bd3d61f8 100644 --- a/viewer/locale/is/viewer.ftl +++ b/viewer/locale/is/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Sækja pdfjs-bookmark-button = .title = Núverandi síða (Skoða vefslóð frá núverandi síðu) pdfjs-bookmark-button-label = Núverandi síða -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Opna í smáforriti -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Opna í smáforriti ## Secondary toolbar and context menu @@ -284,7 +278,7 @@ pdfjs-text-annotation-type = ## Password -pdfjs-password-label = Sláðu inn lykilorð til að opna þessa PDF skrá. +pdfjs-password-label = Settu inn lykilorð til að opna þessa PDF-skrá. pdfjs-password-invalid = Ógilt lykilorð. Reyndu aftur. pdfjs-password-ok-button = Í lagi pdfjs-password-cancel-button = Hætta við @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Bæta við eða breyta myndum pdfjs-editor-highlight-button = .title = Áherslulita pdfjs-editor-highlight-button-label = Áherslulita -pdfjs-highlight-floating-button = - .title = Áherslulita pdfjs-highlight-floating-button1 = .title = Áherslulita .aria-label = Áherslulita @@ -400,3 +392,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Birta allt pdfjs-editor-highlight-show-all-button = .title = Birta allt + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Breyta alt-myndatexta (lýsingu á mynd) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Bæta við alt-myndatexta (lýsingu á mynd) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skrifaðu lýsinguna þína hér… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Stutt lýsing fyrir fólk sem getur ekki séð myndina eða þegar myndin hleðst ekki inn. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Þessi alt-myndatexti var búinn til sjálfvirkt og gæti verið ónákvæmur. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Kanna nánar +pdfjs-editor-new-alt-text-create-automatically-button-label = Útbúa alt-myndatexta sjálfvirkt +pdfjs-editor-new-alt-text-not-now-button = Ekki núna +pdfjs-editor-new-alt-text-error-title = Gat ekki búið til alt-myndatexta sjálfkrafa +pdfjs-editor-new-alt-text-error-description = Skrifaðu þinn eiginn alt-myndatexta eða reyndu aftur síðar. +pdfjs-editor-new-alt-text-error-close-button = Loka +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB) + .aria-valuetext = Sækir gervigreindarlíkan með alt-myndatextum ({ $downloadedSize } af { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alt-myndatexta bætt við +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Vantar alt-myndatexta +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Yfirfara myndatexta +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Útbúið sjálfvirkt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Stillingar fyrir alt-texta myndar +pdfjs-image-alt-text-settings-button-label = Stillingar fyrir alt-texta myndar +pdfjs-editor-alt-text-settings-dialog-label = Stillingar fyrir alt-texta myndar +pdfjs-editor-alt-text-settings-automatic-title = Sjálfvirkur alt-myndatexti +pdfjs-editor-alt-text-settings-create-model-button-label = Útbúa alt-myndatexta sjálfvirkt +pdfjs-editor-alt-text-settings-create-model-description = Stingur upp á lýsingum til að hjálpa fólki sem getur ekki séð myndina eða þegar myndin hleðst ekki inn. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Gervigreindarlíkan alt-myndatexta ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Keyrir staðbundið á tækinu þínu svo gögnin þín haldast undir þinni stjórn. Nauðsynlegt fyrir sjálfvirka alt-myndatexta. +pdfjs-editor-alt-text-settings-delete-model-button = Eyða +pdfjs-editor-alt-text-settings-download-model-button = Sækja +pdfjs-editor-alt-text-settings-downloading-model-button = Sæki… +pdfjs-editor-alt-text-settings-editor-title = Ritill fyrir alt-myndatexta +pdfjs-editor-alt-text-settings-show-dialog-button-label = Sýna alt-myndatextaritil strax þegar mynd er bætt við +pdfjs-editor-alt-text-settings-show-dialog-description = Hjálpar þér að tryggja að allar myndirnar þínar séu með alt-myndatexta. +pdfjs-editor-alt-text-settings-close-button = Loka diff --git a/viewer/locale/it/viewer.ftl b/viewer/locale/it/viewer.ftl index fcdab36a9..bdbed4a35 100644 --- a/viewer/locale/it/viewer.ftl +++ b/viewer/locale/it/viewer.ftl @@ -104,6 +104,12 @@ pdfjs-document-properties-button = pdfjs-document-properties-button-label = Proprietà del documento… pdfjs-document-properties-file-name = Nome file: pdfjs-document-properties-file-size = Dimensione file: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } byte) +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) # Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes @@ -118,6 +124,7 @@ pdfjs-document-properties-subject = Oggetto: pdfjs-document-properties-keywords = Parole chiave: pdfjs-document-properties-creation-date = Data creazione: pdfjs-document-properties-modification-date = Data modifica: +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } # Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file @@ -220,7 +227,6 @@ pdfjs-find-match-diacritics-checkbox-label = Segni diacritici pdfjs-find-entire-word-checkbox-label = Parole intere pdfjs-find-reached-top = Raggiunto l’inizio della pagina, continua dalla fine pdfjs-find-reached-bottom = Raggiunta la fine della pagina, continua dall’inizio - # Variables: # $current (Number) - the index of the currently active find result # $total (Number) - the total number of matches in the document @@ -229,7 +235,6 @@ pdfjs-find-match-count = [one] { $current } di { $total } corrispondenza *[other] { $current } di { $total } corrispondenze } - # Variables: # $limit (Number) - the maximum number of matches pdfjs-find-match-count-limit = @@ -237,7 +242,6 @@ pdfjs-find-match-count-limit = [one] Più di una { $limit } corrispondenza *[other] Più di { $limit } corrispondenze } - pdfjs-find-not-found = Testo non trovato ## Predefined zoom values @@ -278,6 +282,7 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Annotazione: { $type }] +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -332,7 +337,6 @@ pdfjs-editor-stamp-add-image-button-label = Aggiungi immagine pdfjs-editor-free-highlight-thickness-input = Spessore pdfjs-editor-free-highlight-thickness-title = .title = Modifica lo spessore della selezione per elementi non testuali - pdfjs-free-text = .aria-label = Editor di testo pdfjs-free-text-default-content = Inizia a digitare… @@ -370,12 +374,27 @@ pdfjs-editor-resizer-label-bottom-right = Angolo in basso a destra — ridimensi pdfjs-editor-resizer-label-bottom-middle = Lato inferiore nel mezzo — ridimensiona pdfjs-editor-resizer-label-bottom-left = Angolo in basso a sinistra — ridimensiona pdfjs-editor-resizer-label-middle-left = Lato sinistro nel mezzo — ridimensiona +pdfjs-editor-resizer-top-left = + .aria-label = Angolo in alto a sinistra — ridimensiona +pdfjs-editor-resizer-top-middle = + .aria-label = Lato superiore nel mezzo — ridimensiona +pdfjs-editor-resizer-top-right = + .aria-label = Angolo in alto a destra — ridimensiona +pdfjs-editor-resizer-middle-right = + .aria-label = Lato destro nel mezzo — ridimensiona +pdfjs-editor-resizer-bottom-right = + .aria-label = Angolo in basso a destra — ridimensiona +pdfjs-editor-resizer-bottom-middle = + .aria-label = Lato inferiore nel mezzo — ridimensiona +pdfjs-editor-resizer-bottom-left = + .aria-label = Angolo in basso a sinistra — ridimensiona +pdfjs-editor-resizer-middle-left = + .aria-label = Lato sinistro nel mezzo — ridimensiona ## Color picker # This means "Color used to highlight text" pdfjs-editor-highlight-colorpicker-label = Colore evidenziatore - pdfjs-editor-colorpicker-button = .title = Cambia colore pdfjs-editor-colorpicker-dropdown = @@ -397,3 +416,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostra tutto pdfjs-editor-highlight-show-all-button = .title = Mostra tutto + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Modifica testo alternativo (descrizione dell’immagine) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Aggiungi testo alternativo (descrizione dell’immagine) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Scrivi qui la tua descrizione… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Breve descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Questo testo alternativo è stato creato automaticamente e potrebbe non essere accurato. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ulteriori informazioni +pdfjs-editor-new-alt-text-create-automatically-button-label = Crea automaticamente testo alternativo +pdfjs-editor-new-alt-text-not-now-button = Non adesso +pdfjs-editor-new-alt-text-error-title = Impossibile creare automaticamente il testo alternativo +pdfjs-editor-new-alt-text-error-description = Scrivi il testo alternativo o riprova più tardi. +pdfjs-editor-new-alt-text-error-close-button = Chiudi +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB) + .aria-valuetext = Download in corso del modello IA per il testo alternativo ({ $downloadedSize } di { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Aggiunto testo alternativo +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Testo alternativo mancante +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Verifica testo alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creato automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Impostazioni testo alternativo per le immagini +pdfjs-image-alt-text-settings-button-label = Impostazioni testo alternativo per le immagini +pdfjs-editor-alt-text-settings-dialog-label = Impostazioni testo alternativo per le immagini +pdfjs-editor-alt-text-settings-automatic-title = Testo alternativo automatico +pdfjs-editor-alt-text-settings-create-model-button-label = Crea testo alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Suggerisce una descrizione per le persone che non possono vedere l’immagine, o mostrata quando l’immagine non si carica. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modello IA per il testo alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Viene eseguito localmente sul tuo dispositivo in modo che i tuoi dati rimangano riservati. È richiesto per la generazione automatica del testo alternativo. +pdfjs-editor-alt-text-settings-delete-model-button = Elimina +pdfjs-editor-alt-text-settings-download-model-button = Scarica +pdfjs-editor-alt-text-settings-downloading-model-button = Download… +pdfjs-editor-alt-text-settings-editor-title = Modifica testo alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostra l’editor del testo alternativo non appena si aggiunge un’immagine +pdfjs-editor-alt-text-settings-show-dialog-description = Ti aiuta ad assicurarti che tutte le tue immagini abbiano il testo alternativo. +pdfjs-editor-alt-text-settings-close-button = Chiudi diff --git a/viewer/locale/ja/viewer.ftl b/viewer/locale/ja/viewer.ftl index 2246cfd49..5681d5e56 100644 --- a/viewer/locale/ja/viewer.ftl +++ b/viewer/locale/ja/viewer.ftl @@ -223,18 +223,10 @@ pdfjs-find-reached-bottom = 文書末尾に到達したので先頭から続け # Variables: # $current (Number) - the index of the currently active find result # $total (Number) - the total number of matches in the document -pdfjs-find-match-count = - { $total -> - [one] { $total } 件中 { $current } 件目 - *[other] { $total } 件中 { $current } 件目 - } +pdfjs-find-match-count = { $total } 件中 { $current } 件目 # Variables: # $limit (Number) - the maximum number of matches -pdfjs-find-match-count-limit = - { $limit -> - [one] { $limit } 件以上一致 - *[other] { $limit } 件以上一致 - } +pdfjs-find-match-count-limit = { $limit } 件以上一致 pdfjs-find-not-found = 見つかりませんでした ## Predefined zoom values @@ -279,7 +271,7 @@ pdfjs-text-annotation-type = ## Password pdfjs-password-label = この PDF ファイルを開くためのパスワードを入力してください。 -pdfjs-password-invalid = 無効なパスワードです。もう一度やり直してください。 +pdfjs-password-invalid = パスワードが正しくありません。もう一度試してください。 pdfjs-password-ok-button = OK pdfjs-password-cancel-button = キャンセル pdfjs-web-fonts-disabled = ウェブフォントが無効になっています: 埋め込まれた PDF のフォントを使用できません。 @@ -298,8 +290,6 @@ pdfjs-editor-stamp-button-label = 画像を追加または編集 pdfjs-editor-highlight-button = .title = 強調します pdfjs-editor-highlight-button-label = 強調 -pdfjs-highlight-floating-button = - .title = 強調 pdfjs-highlight-floating-button1 = .title = 強調 .aria-label = 強調します @@ -392,5 +382,63 @@ pdfjs-editor-colorpicker-red = ## This is a toggle button to show/hide all the highlights. pdfjs-editor-highlight-show-all-button-label = すべて表示 +# (^m^) en-US: .title = Show all pdfjs-editor-highlight-show-all-button = .title = 強調の表示を切り替えます + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 代替テキストを編集 (画像の説明) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 代替テキストを追加 (画像の説明) +pdfjs-editor-new-alt-text-textarea = + .placeholder = ここに説明を記入してください... +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 画像が読み込まれない場合や見えない人のための短い説明です。 +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = この代替テキストは自動的に生成されました。 +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 詳細情報 +pdfjs-editor-new-alt-text-create-automatically-button-label = 代替テキストを自動生成 +pdfjs-editor-new-alt-text-not-now-button = 後で +pdfjs-editor-new-alt-text-error-title = 代替テキストを自動生成できませんでした +pdfjs-editor-new-alt-text-error-description = ご自分で代替テキストを書くか後でもう一度試してください。 +pdfjs-editor-new-alt-text-error-close-button = 閉じる +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 代替テキスト AI モデルをダウンロードしています ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 代替テキスト AI モデルをダウンロードしています ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = 代替テキストを追加しました +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = 代替テキストがありません +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = 代替テキストをレビュー +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動生成されました: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = 画像の代替テキスト設定 +pdfjs-image-alt-text-settings-button-label = 画像の代替テキスト設定 +pdfjs-editor-alt-text-settings-dialog-label = 画像の代替テキスト設定 +pdfjs-editor-alt-text-settings-automatic-title = 自動代替テキスト +pdfjs-editor-alt-text-settings-create-model-button-label = 代替テキストを自動生成 +pdfjs-editor-alt-text-settings-create-model-description = 画像が読み込まれない場合や見えない人のために説明を提案します。 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 代替テキスト AI モデル ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ローカルの端末上で実行されるためデータは非公開になります。代替テキストの自動生成に必要です。 +pdfjs-editor-alt-text-settings-delete-model-button = 削除 +pdfjs-editor-alt-text-settings-download-model-button = ダウンロード +pdfjs-editor-alt-text-settings-downloading-model-button = ダウンロード中... +pdfjs-editor-alt-text-settings-editor-title = 代替テキストエディター +pdfjs-editor-alt-text-settings-show-dialog-button-label = 画像の追加時に代替テキストエディターを表示する +pdfjs-editor-alt-text-settings-show-dialog-description = すべての画像に代替テキストを追加する助けになります。 +pdfjs-editor-alt-text-settings-close-button = 閉じる diff --git a/viewer/locale/kab/viewer.ftl b/viewer/locale/kab/viewer.ftl index 5f16478e3..cfe0ba339 100644 --- a/viewer/locale/kab/viewer.ftl +++ b/viewer/locale/kab/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Sader pdfjs-bookmark-button = .title = Asebter amiran (Sken-d tansa URL seg usebter amiran) pdfjs-bookmark-button-label = Asebter amiran -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Ldi deg usnas -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Ldi deg usnas ## Secondary toolbar and context menu @@ -301,8 +295,27 @@ pdfjs-editor-ink-button-label = Suneɣ pdfjs-editor-stamp-button = .title = Rnu neɣ ẓreg tugniwin pdfjs-editor-stamp-button-label = Rnu neɣ ẓreg tugniwin -pdfjs-editor-remove-button = - .title = Kkes +pdfjs-editor-highlight-button = + .title = Derrer +pdfjs-editor-highlight-button-label = Derrer +pdfjs-highlight-floating-button1 = + .title = Derrer + .aria-label = Derrer +pdfjs-highlight-floating-button-label = Derrer + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Kkes asuneɣ +pdfjs-editor-remove-freetext-button = + .title = Kkes aḍris +pdfjs-editor-remove-stamp-button = + .title = Kkes tugna +pdfjs-editor-remove-highlight-button = + .title = Kkes aderrer + +## + # Editor Parameters pdfjs-editor-free-text-color-input = Initen pdfjs-editor-free-text-size-input = Teɣzi @@ -312,6 +325,8 @@ pdfjs-editor-ink-opacity-input = Tebrek pdfjs-editor-stamp-add-image-button = .title = Rnu tawlaft pdfjs-editor-stamp-add-image-button-label = Rnu tawlaft +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tuzert pdfjs-free-text = .aria-label = Amaẓrag n uḍris pdfjs-free-text-default-content = Bdu tira... @@ -335,3 +350,37 @@ pdfjs-editor-alt-text-decorative-tooltip = Yettwacreḍ d adlag ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. +pdfjs-editor-resizer-label-top-left = Tiɣmert n ufella n zelmeḍ — semsawi teɣzi +pdfjs-editor-resizer-label-top-middle = Talemmat n ufella — semsawi teɣzi +pdfjs-editor-resizer-label-top-right = Tiɣmert n ufella n yeffus — semsawi teɣzi +pdfjs-editor-resizer-label-middle-right = Talemmast tayeffust — semsawi teɣzi +pdfjs-editor-resizer-label-bottom-right = Tiɣmert n wadda n yeffus — semsawi teɣzi +pdfjs-editor-resizer-label-bottom-middle = Talemmat n wadda — semsawi teɣzi +pdfjs-editor-resizer-label-bottom-left = Tiɣmert n wadda n zelmeḍ — semsawi teɣzi +pdfjs-editor-resizer-label-middle-left = Talemmast tazelmdaḍt — semsawi teɣzi + +## Color picker + +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Ini n uderrer +pdfjs-editor-colorpicker-button = + .title = Senfel ini +pdfjs-editor-colorpicker-dropdown = + .aria-label = Afran n yiniten +pdfjs-editor-colorpicker-yellow = + .title = Awraɣ +pdfjs-editor-colorpicker-green = + .title = Azegzaw +pdfjs-editor-colorpicker-blue = + .title = Amidadi +pdfjs-editor-colorpicker-pink = + .title = Axuxi +pdfjs-editor-colorpicker-red = + .title = Azggaɣ + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Sken akk +pdfjs-editor-highlight-show-all-button = + .title = Sken akk diff --git a/viewer/locale/kk/viewer.ftl b/viewer/locale/kk/viewer.ftl index 57260a2da..f8abb1ba4 100644 --- a/viewer/locale/kk/viewer.ftl +++ b/viewer/locale/kk/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Жүктеп алу pdfjs-bookmark-button = .title = Ағымдағы бет (Ағымдағы беттен URL адресін көру) pdfjs-bookmark-button-label = Ағымдағы бет -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Қолданбада ашу -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Қолданбада ашу ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Құжат қасиеттері… pdfjs-document-properties-file-name = Файл аты: pdfjs-document-properties-file-size = Файл өлшемі: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Кілт сөздер: pdfjs-document-properties-creation-date = Жасалған күні: pdfjs-document-properties-modification-date = Түзету күні: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } аңдатпасы] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Суреттерді қосу немесе т pdfjs-editor-highlight-button = .title = Ерекшелеу pdfjs-editor-highlight-button-label = Ерекшелеу -pdfjs-highlight-floating-button = - .title = Ерекшелеу pdfjs-highlight-floating-button1 = .title = Ерекшелеу .aria-label = Ерекшелеу @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Барлығын көрсету pdfjs-editor-highlight-show-all-button = .title = Барлығын көрсету + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Балама мәтінді өңдеу (сурет сипаттамасы) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Балама мәтінді қосу (сурет сипаттамасы) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Сипаттамаңызды осында жазыңыз… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Суретті көре алмайтын адамдар үшін немесе сурет жүктелмеген кезіне арналған қысқаша сипаттама. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Бұл балама мәтін автоматты түрде жасалды және дәлсіз болуы мүмкін. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Көбірек білу +pdfjs-editor-new-alt-text-create-automatically-button-label = Балама мәтінді автоматты түрде жасау +pdfjs-editor-new-alt-text-not-now-button = Қазір емес +pdfjs-editor-new-alt-text-error-title = Балама мәтінді автоматты түрде жасау мүмкін болмады +pdfjs-editor-new-alt-text-error-description = Өзіңіздің балама мәтініңізді жазыңыз немесе кейінірек қайталап көріңіз. +pdfjs-editor-new-alt-text-error-close-button = Жабу +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ) + .aria-valuetext = Балама мәтін үшін ЖИ моделі жүктеп алынуда ({ $downloadedSize }/{ $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Балама мәтін қосылды +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Балама мәтін жоқ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Балама мәтінге пікір қалдыру +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Автоматты түрде жасалды: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Суреттің балама мәтінінің баптаулары +pdfjs-image-alt-text-settings-button-label = Суреттің балама мәтінінің баптаулары +pdfjs-editor-alt-text-settings-dialog-label = Суреттің балама мәтінінің баптаулары +pdfjs-editor-alt-text-settings-automatic-title = Автоматты балама мәтін +pdfjs-editor-alt-text-settings-create-model-button-label = Балама мәтінді автоматты түрде жасау +pdfjs-editor-alt-text-settings-create-model-description = Суретті көре алмайтын адамдар үшін немесе сурет жүктелмеген кезіне арналған сипаттамаларды ұсынады. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Баламалы мәтіннің ЖИ моделі ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Деректеріңіз жеке болып қалуы үшін құрылғыңызда жергілікті түрде жұмыс істейді. Автоматты балама мәтін үшін қажет. +pdfjs-editor-alt-text-settings-delete-model-button = Өшіру +pdfjs-editor-alt-text-settings-download-model-button = Жүктеп алу +pdfjs-editor-alt-text-settings-downloading-model-button = Жүктеліп алынуда… +pdfjs-editor-alt-text-settings-editor-title = Баламалы мәтін редакторы +pdfjs-editor-alt-text-settings-show-dialog-button-label = Суретті қосқанда балама мәтін редакторын бірден көрсету +pdfjs-editor-alt-text-settings-show-dialog-description = Барлық суреттерде балама мәтін бар екеніне көз жеткізуге көмектеседі. +pdfjs-editor-alt-text-settings-close-button = Жабу diff --git a/viewer/locale/ko/viewer.ftl b/viewer/locale/ko/viewer.ftl index 2afce1440..5c9b91f63 100644 --- a/viewer/locale/ko/viewer.ftl +++ b/viewer/locale/ko/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = 다운로드 pdfjs-bookmark-button = .title = 현재 페이지 (현재 페이지에서 URL 보기) pdfjs-bookmark-button-label = 현재 페이지 -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = 앱에서 열기 -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = 앱에서 열기 ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = 문서 속성… pdfjs-document-properties-file-name = 파일 이름: pdfjs-document-properties-file-size = 파일 크기: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } 바이트) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } 바이트) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b }바이트) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = 키워드: pdfjs-document-properties-creation-date = 작성 날짜: pdfjs-document-properties-modification-date = 수정 날짜: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -273,6 +278,9 @@ pdfjs-annotation-date-string = { $date } { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } 주석] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -296,8 +304,6 @@ pdfjs-editor-stamp-button-label = 이미지 추가 또는 편집 pdfjs-editor-highlight-button = .title = 강조 표시 pdfjs-editor-highlight-button-label = 강조 표시 -pdfjs-highlight-floating-button = - .title = 강조 표시 pdfjs-highlight-floating-button1 = .title = 강조 표시 .aria-label = 강조 표시 @@ -392,3 +398,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = 모두 보기 pdfjs-editor-highlight-show-all-button = .title = 모두 보기 + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 대체 텍스트 (이미지 설명) 편집 +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 대체 텍스트 (이미지 설명) 추가 +pdfjs-editor-new-alt-text-textarea = + .placeholder = 여기에 설명을 작성하세요… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 이미지가 보이지 않거나 이미지가 로딩되지 않는 경우를 위한 간단한 설명입니다. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = 이 대체 텍스트는 자동으로 생성되었으므로 정확하지 않을 수 있습니다. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 더 알아보기 +pdfjs-editor-new-alt-text-create-automatically-button-label = 자동으로 대체 텍스트 생성 +pdfjs-editor-new-alt-text-not-now-button = 나중에 +pdfjs-editor-new-alt-text-error-title = 대체 텍스트를 자동으로 생성할 수 없습니다. +pdfjs-editor-new-alt-text-error-description = 대체 텍스트를 직접 작성하거나 나중에 다시 시도하세요. +pdfjs-editor-new-alt-text-error-close-button = 닫기 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 대체 텍스트 AI 모델 다운로드 중 ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 대체 텍스트 AI 모델 다운로드 중 ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = 대체 텍스트 추가됨 +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = 대체 텍스트 누락 +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = 대체 텍스트 검토 +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 자동으로 생성됨: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = 이미지 대체 텍스트 설정 +pdfjs-image-alt-text-settings-button-label = 이미지 대체 텍스트 설정 +pdfjs-editor-alt-text-settings-dialog-label = 이미지 대체 텍스트 설정 +pdfjs-editor-alt-text-settings-automatic-title = 자동 대체 텍스트 +pdfjs-editor-alt-text-settings-create-model-button-label = 자동으로 대체 텍스트 생성 +pdfjs-editor-alt-text-settings-create-model-description = 이미지가 보이지 않거나 이미지가 로딩되지 않을 때 도움이 되는 설명을 제안합니다. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 대체 텍스트 AI 모델 ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 사용자의 장치에서 로컬로 실행되므로 데이터가 비공개로 유지됩니다. 자동 대체 텍스트에 필요합니다. +pdfjs-editor-alt-text-settings-delete-model-button = 삭제 +pdfjs-editor-alt-text-settings-download-model-button = 다운로드 +pdfjs-editor-alt-text-settings-downloading-model-button = 다운로드 중… +pdfjs-editor-alt-text-settings-editor-title = 대체 텍스트 편집기 +pdfjs-editor-alt-text-settings-show-dialog-button-label = 이미지 추가 시 바로 대체 텍스트 편집기 표시 +pdfjs-editor-alt-text-settings-show-dialog-description = 모든 이미지에 대체 텍스트가 있는지 확인하는 데 도움이 됩니다. +pdfjs-editor-alt-text-settings-close-button = 닫기 diff --git a/viewer/locale/nl/viewer.ftl b/viewer/locale/nl/viewer.ftl index a1dd47d3c..8a76d43ab 100644 --- a/viewer/locale/nl/viewer.ftl +++ b/viewer/locale/nl/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Downloaden pdfjs-bookmark-button = .title = Huidige pagina (URL van huidige pagina bekijken) pdfjs-bookmark-button-label = Huidige pagina -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Openen in app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Openen in app ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Documenteigenschappen… pdfjs-document-properties-file-name = Bestandsnaam: pdfjs-document-properties-file-size = Bestandsgrootte: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Sleutelwoorden: pdfjs-document-properties-creation-date = Aanmaakdatum: pdfjs-document-properties-modification-date = Wijzigingsdatum: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type }-aantekening] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Afbeeldingen toevoegen of bewerken pdfjs-editor-highlight-button = .title = Markeren pdfjs-editor-highlight-button-label = Markeren -pdfjs-highlight-floating-button = - .title = Markeren pdfjs-highlight-floating-button1 = .title = Markeren .aria-label = Markeren @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Alles tonen pdfjs-editor-highlight-show-all-button = .title = Alles tonen + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alternatieve tekst (afbeeldingsbeschrijving) bewerken +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alternatieve tekst (afbeeldingsbeschrijving) toevoegen +pdfjs-editor-new-alt-text-textarea = + .placeholder = Schrijf hier uw beschrijving… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Korte beschrijving voor mensen die de afbeelding niet kunnen zien of wanneer de afbeelding niet wordt geladen. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Deze alternatieve tekst is automatisch gemaakt en is mogelijk onjuist. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Meer info +pdfjs-editor-new-alt-text-create-automatically-button-label = Alternatieve tekst automatisch aanmaken +pdfjs-editor-new-alt-text-not-now-button = Niet nu +pdfjs-editor-new-alt-text-error-title = Kan alternatieve tekst niet automatisch aanmaken +pdfjs-editor-new-alt-text-error-description = Schrijf uw eigen alternatieve tekst of probeer het later nog eens. +pdfjs-editor-new-alt-text-error-close-button = Sluiten +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB) + .aria-valuetext = AI-model voor alternatieve tekst downloaden ({ $downloadedSize } van { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternatieve tekst toegevoegd +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Alternatieve tekst ontbreekt +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alternatieve tekst beoordelen +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatisch aangemaakt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-image-alt-text-settings-button-label = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-editor-alt-text-settings-dialog-label = Instellingen voor alternatieve tekst van afbeeldingen +pdfjs-editor-alt-text-settings-automatic-title = Automatische alternatieve tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Alternatieve tekst automatisch aanmaken +pdfjs-editor-alt-text-settings-create-model-description = Stelt beschrijvingen voor om mensen te helpen die de afbeelding niet kunnen zien of voor wie de afbeelding niet wordt geladen. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-model voor alternatieve tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Wordt lokaal op uw apparaat uitgevoerd, zodat uw gegevens privé blijven. Vereist voor automatische alternatieve tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Verwijderen +pdfjs-editor-alt-text-settings-download-model-button = Downloaden +pdfjs-editor-alt-text-settings-downloading-model-button = Downloaden… +pdfjs-editor-alt-text-settings-editor-title = Alternatieve-tekstbewerker +pdfjs-editor-alt-text-settings-show-dialog-button-label = Alternatieve-tekstbewerker meteen tonen bij toevoegen van een afbeelding +pdfjs-editor-alt-text-settings-show-dialog-description = Helpt u ervoor te zorgen dat al uw afbeeldingen alternatieve tekst hebben. +pdfjs-editor-alt-text-settings-close-button = Sluiten diff --git a/viewer/locale/nn-NO/viewer.ftl b/viewer/locale/nn-NO/viewer.ftl index 476e4c15b..fbee62c29 100644 --- a/viewer/locale/nn-NO/viewer.ftl +++ b/viewer/locale/nn-NO/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Last ned pdfjs-bookmark-button = .title = Gjeldande side (sjå URL frå gjeldande side) pdfjs-bookmark-button-label = Gjeldande side -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Opne i app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Opne i app ## Secondary toolbar and context menu @@ -135,8 +129,8 @@ pdfjs-document-properties-page-count = Sidetal: pdfjs-document-properties-page-size = Sidestørrelse: pdfjs-document-properties-page-size-unit-inches = in pdfjs-document-properties-page-size-unit-millimeters = mm -pdfjs-document-properties-page-size-orientation-portrait = ståande -pdfjs-document-properties-page-size-orientation-landscape = liggande +pdfjs-document-properties-page-size-orientation-portrait = ståande (portrait) +pdfjs-document-properties-page-size-orientation-landscape = liggande (landscape) pdfjs-document-properties-page-size-name-a-three = A3 pdfjs-document-properties-page-size-name-a-four = A4 pdfjs-document-properties-page-size-name-letter = Brev @@ -301,9 +295,24 @@ pdfjs-editor-ink-button-label = Teikne pdfjs-editor-stamp-button = .title = Legg til eller rediger bilde pdfjs-editor-stamp-button-label = Legg til eller rediger bilde +pdfjs-editor-highlight-button = + .title = Markere +pdfjs-editor-highlight-button-label = Markere +pdfjs-highlight-floating-button1 = + .title = Markere + .aria-label = Markere +pdfjs-highlight-floating-button-label = Markere ## Remove button for the various kind of editor. +pdfjs-editor-remove-ink-button = + .title = Fjern teikninga +pdfjs-editor-remove-freetext-button = + .title = Fjern tekst +pdfjs-editor-remove-stamp-button = + .title = Fjern bildet +pdfjs-editor-remove-highlight-button = + .title = Fjern utheving ## @@ -316,6 +325,10 @@ pdfjs-editor-ink-opacity-input = Ugjennomskinleg pdfjs-editor-stamp-add-image-button = .title = Legg til bilde pdfjs-editor-stamp-add-image-button-label = Legg til bilde +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Tjukkleik +pdfjs-editor-free-highlight-thickness-title = + .title = Endre tjukn når du markerer andre element enn tekst pdfjs-free-text = .aria-label = Tekstredigering pdfjs-free-text-default-content = Byrje å skrive… @@ -345,9 +358,23 @@ pdfjs-editor-alt-text-textarea = ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. +pdfjs-editor-resizer-label-top-left = Øvste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-top-middle = Øvst i midten — endre størrelse +pdfjs-editor-resizer-label-top-right = Øvste høgre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-right = Midt til høgre – endre størrelse +pdfjs-editor-resizer-label-bottom-right = Nedste høgre hjørne – endre størrelse +pdfjs-editor-resizer-label-bottom-middle = Nedst i midten — endre størrelse +pdfjs-editor-resizer-label-bottom-left = Nedste venstre hjørne – endre størrelse +pdfjs-editor-resizer-label-middle-left = Midt til venstre — endre størrelse ## Color picker +# This means "Color used to highlight text" +pdfjs-editor-highlight-colorpicker-label = Uthevingsfarge +pdfjs-editor-colorpicker-button = + .title = Endre farge +pdfjs-editor-colorpicker-dropdown = + .aria-label = Fargeval pdfjs-editor-colorpicker-yellow = .title = Gul pdfjs-editor-colorpicker-green = @@ -358,3 +385,65 @@ pdfjs-editor-colorpicker-pink = .title = Rosa pdfjs-editor-colorpicker-red = .title = Raud + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + +pdfjs-editor-highlight-show-all-button-label = Vis alle +pdfjs-editor-highlight-show-all-button = + .title = Vis alle + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Rediger alternativ tekst (bildeskildring) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Legg til alternativ tekst (bildeskildring) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv skildringa di her… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort skildring for personar som ikkje kan sjå bildet, eller når bildet ikkje lastar inn. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denne alternative teksten vart oppretta automatisk, og kan vere unøyaktig. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Les meir +pdfjs-editor-new-alt-text-create-automatically-button-label = Opprett alternativ tekt automatisk +pdfjs-editor-new-alt-text-not-now-button = Ikkje no +pdfjs-editor-new-alt-text-error-title = Klarte ikkje å opprette alternativ tekst automatisk +pdfjs-editor-new-alt-text-error-description = Skriv din eigen alternative tekst eller prøv igjen seinare. +pdfjs-editor-new-alt-text-error-close-button = Lat att +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB) + .aria-valuetext = Lastar ned AI-modell med alternativ tekst ({ $downloadedSize } av { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativ tekst lagt til +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Manglande alternativ tekst +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Vurder alternativ tekst +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Oppretta automatisk: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternative tekst-innstillingar for bilde +pdfjs-image-alt-text-settings-button-label = Alternative tekst-innstillingar for bilde +pdfjs-editor-alt-text-settings-dialog-label = Alternative tekst-innstillingar for bilde +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ tekst +pdfjs-editor-alt-text-settings-create-model-button-label = Opprett alternativ tekt automatisk +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-modell for alternativ tekst ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Køyrer lokalt på eininga di slik at dataa dine blir verande private. Påkravd for automatisk alternativ tekst. +pdfjs-editor-alt-text-settings-delete-model-button = Slett +pdfjs-editor-alt-text-settings-download-model-button = Last ned +pdfjs-editor-alt-text-settings-downloading-model-button = Lastar ned… +pdfjs-editor-alt-text-settings-editor-title = Alternativ tekst-redigerar +pdfjs-editor-alt-text-settings-show-dialog-button-label = Vis alternativ tekst-redigerar direkte når du legg til eit bilde +pdfjs-editor-alt-text-settings-close-button = Lat att diff --git a/viewer/locale/oc/viewer.ftl b/viewer/locale/oc/viewer.ftl index 68889798b..76bef4fed 100644 --- a/viewer/locale/oc/viewer.ftl +++ b/viewer/locale/oc/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Telecargar pdfjs-bookmark-button = .title = Pagina actuala (mostrar l’adreça de la pagina actuala) pdfjs-bookmark-button-label = Pagina actuala -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Dobrir amb l’aplicacion -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Dobrir amb l’aplicacion ## Secondary toolbar and context menu @@ -286,13 +280,24 @@ pdfjs-editor-ink-button-label = Dessenhar pdfjs-editor-stamp-button = .title = Apondre o modificar d’imatges pdfjs-editor-stamp-button-label = Apondre o modificar d’imatges +pdfjs-editor-highlight-button = + .title = Subrelinhar +pdfjs-editor-highlight-button-label = Subrelinhar +pdfjs-highlight-floating-button1 = + .title = Subrelinhar + .aria-label = Subrelinhar +pdfjs-highlight-floating-button-label = Subrelinhar ## Remove button for the various kind of editor. +pdfjs-editor-remove-ink-button = + .title = Levar lo dessenh pdfjs-editor-remove-freetext-button = .title = Suprimir lo tèxte pdfjs-editor-remove-stamp-button = .title = Suprimir l’imatge +pdfjs-editor-remove-highlight-button = + .title = Levar lo suslinhatge ## @@ -335,6 +340,8 @@ pdfjs-editor-alt-text-save-button = Enregistrar pdfjs-editor-highlight-colorpicker-label = Color de suslinhatge pdfjs-editor-colorpicker-button = .title = Cambiar de color +pdfjs-editor-colorpicker-dropdown = + .aria-label = Causida de colors pdfjs-editor-colorpicker-yellow = .title = Jaune pdfjs-editor-colorpicker-green = diff --git a/viewer/locale/pa-IN/viewer.ftl b/viewer/locale/pa-IN/viewer.ftl index eef67d354..6500b3441 100644 --- a/viewer/locale/pa-IN/viewer.ftl +++ b/viewer/locale/pa-IN/viewer.ftl @@ -298,8 +298,6 @@ pdfjs-editor-stamp-button-label = ਚਿੱਤਰ ਜੋੜੋ ਜਾਂ ਸੋ pdfjs-editor-highlight-button = .title = ਹਾਈਲਾਈਟ pdfjs-editor-highlight-button-label = ਹਾਈਲਾਈਟ -pdfjs-highlight-floating-button = - .title = ਹਾਈਲਾਈਟ pdfjs-highlight-floating-button1 = .title = ਹਾਈਲਾਈਟ .aria-label = ਹਾਈਲਾਈਟ @@ -394,3 +392,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = ਸਭ ਵੇਖੋ pdfjs-editor-highlight-show-all-button = .title = ਸਭ ਵੇਖੋ + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਸੋਧੋ +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = ਬਦਲਵੀਂ ਲਿਖਤ (ਚਿੱਤਰ ਦਾ ਵਰਣਨ) ਨੂੰ ਜੋੜੋ +pdfjs-editor-new-alt-text-textarea = + .placeholder = …ਆਪਣਾ ਵਰਣਨਾ ਇੱਥੇ ਲਿਖੋ +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = ਲੋਕ, ਜੋ ਕਿ ਚਿੱਤਰ ਨਹੀਂ ਵੇਖ ਸਕਦੇ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ। +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ਇਹ ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਤਿਆਰ ਕੀਤੀ ਗਈ ਸੀ ਅਤੇ ਗਲਤ ਵੀ ਹੋ ਸਕਦੀ ਹੈ। +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = ਇਹ ਬਦਲਵੀ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਤਿਆਰ ਕੀਤੀ ਗਈ ਸੀ। +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ਹੋਰ ਜਾਣੋ +pdfjs-editor-new-alt-text-create-automatically-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ +pdfjs-editor-new-alt-text-not-now-button = ਹੁਣੇ ਨਹੀਂ +pdfjs-editor-new-alt-text-error-title = ਬਦਲਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਈ ਨਹੀਂ ਜਾ ਸਕੀ +pdfjs-editor-new-alt-text-error-description = ਆਪਣਾ ਖੁਦ ਦੀ ਬਦਲਵੀਂ ਲਿਖਤ ਲਿਖੋ ਜਾਂ ਫੇਰ ਕੋਸ਼ਿਸ਼ ਕਰੋ। +pdfjs-editor-new-alt-text-error-close-button = ਬੰਦ ਕਰੋ +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize }) + .aria-valuetext = ਬਦਲਵਾਂ ਲਿਖਤ AI ਮਾਡਲ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ ({ $totalSize } MB ਵਿੱਚੋਂ { $downloadedSize }) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਜੋੜੀ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = ਬਦਲਵਾਂ ਲਿਖਤ ਗੁੰਮ ਹੈ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = ਬਦਲਵੀਂ ਲਿਖਤ ਦਾ ਰੀਵਿਊ ਕਰੋ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = ਆਪਣੇ-ਆਪ ਬਣਾਇਆ: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-image-alt-text-settings-button-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-editor-alt-text-settings-dialog-label = ਚਿੱਤਰ ਬਦਲਵੀਂ ਲਿਖਤ ਦੀਆਂ ਸੈਟਿੰਗਾਂ +pdfjs-editor-alt-text-settings-automatic-title = ਆਟੋਮਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ +pdfjs-editor-alt-text-settings-create-model-button-label = ਬਲਦਵੀਂ ਲਿਖਤ ਆਪਣੇ-ਆਪ ਬਣਾਓ +pdfjs-editor-alt-text-settings-create-model-description = ਚਿੱਤਰ ਨਾ ਵੇਖ ਸਕਣ ਵਾਲੇ ਲੋਕਾਂ ਦੀ ਮਦਦ ਜਾਂ ਜਦ ਵੀ ਚਿੱਤਰਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਜਾ ਸਕਦਾ, ਉਸ ਲਈ ਛੋਟਾ ਵੇਰਵਾ ਦਿਓ। +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = ਬਦਲਵੀ ਲਿਖਤ ਲਈ AI ਮਾਡਲ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ਤੁਹਾਡੇ ਡਿਵਾਈਸ ਉੱਤੇ ਲੋਕਲ ਹੀ ਚੱਲਦਾ ਹੋਣ ਕਰਕੇ ਤੁਹਾਡਾ ਡਾਟਾ ਪ੍ਰਾਈਵੇਟ ਹੀ ਰਹਿੰਦਾ ਹੈ। ਆਟੋਮੈਟਿਕ ਬਦਲਵੀਂ ਲਿਖਤ ਲਈ ਚਾਹੀਦਾ ਹੈ। +pdfjs-editor-alt-text-settings-delete-model-button = ਹਟਾਓ +pdfjs-editor-alt-text-settings-download-model-button = ਡਾਊਨਲੋਡ +pdfjs-editor-alt-text-settings-downloading-model-button = …ਨੂੰ ਡਾਊਨਲੋਡ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ +pdfjs-editor-alt-text-settings-editor-title = ਬਦਲਵੀਂ ਲਿਖਤ ਐਡੀਟਰ +pdfjs-editor-alt-text-settings-show-dialog-button-label = ਜਦੋਂ ਵਿੱਚ ਚਿੱਤਰ ਜੋੜਿਆ ਜਾਵੇ ਤਾਂ ਫ਼ੌਰਨ ਬਦਲਵੀ ਲਿਖਤ ਸੰਪਾਦਕ ਵੇਖਾਓ +pdfjs-editor-alt-text-settings-show-dialog-description = ਤੁਹਾਡੀ ਮਦਦ ਕਰਦਾ ਹੈ ਕਿ ਤੁਹਾਡੇ ਸਾਰੇ ਚਿੱਤਰਾਂ ਲਈ ਬਦਲਵੀਂ ਲਿਖਤ ਮੌਜੂਦ ਹੋਵੇ। +pdfjs-editor-alt-text-settings-close-button = ਬੰਦ ਕਰੋ diff --git a/viewer/locale/pl/viewer.ftl b/viewer/locale/pl/viewer.ftl index b34d60744..4f98f8708 100644 --- a/viewer/locale/pl/viewer.ftl +++ b/viewer/locale/pl/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Pobierz pdfjs-bookmark-button = .title = Bieżąca strona (adres do otwarcia na bieżącej stronie) pdfjs-bookmark-button-label = Bieżąca strona -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Otwórz w aplikacji -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Otwórz w aplikacji ## Secondary toolbar and context menu @@ -306,8 +300,6 @@ pdfjs-editor-stamp-button-label = Dodaj lub edytuj obrazy pdfjs-editor-highlight-button = .title = Wyróżnij pdfjs-editor-highlight-button-label = Wyróżnij -pdfjs-highlight-floating-button = - .title = Wyróżnij pdfjs-highlight-floating-button1 = .title = Wyróżnij .aria-label = Wyróżnij @@ -402,3 +394,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Pokaż wszystkie pdfjs-editor-highlight-show-all-button = .title = Pokaż wszystkie + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Edytuj tekst alternatywny (opis obrazu) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Dodaj tekst alternatywny (opis obrazu) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Napisz tutaj opis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krótki opis dla osób, które nie widzą obrazu lub kiedy obraz się nie wczytuje. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ten tekst alternatywny został utworzony automatycznie i może być niepoprawny. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Więcej informacji +pdfjs-editor-new-alt-text-create-automatically-button-label = Automatycznie utwórz tekst alternatywny +pdfjs-editor-new-alt-text-not-now-button = Nie teraz +pdfjs-editor-new-alt-text-error-title = Nie można automatycznie utworzyć tekstu alternatywnego +pdfjs-editor-new-alt-text-error-description = Proszę napisać własny tekst alternatywny lub spróbować ponownie później. +pdfjs-editor-new-alt-text-error-close-button = Zamknij +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Pobieranie modelu SI tekstu alternatywnego ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Dodano tekst alternatywny +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Brak tekstu alternatywnego +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Przejrzyj tekst alternatywny +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Utworzono automatycznie: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Ustawienia tekstu alternatywnego obrazów +pdfjs-image-alt-text-settings-button-label = Ustawienia tekstu alternatywnego obrazów +pdfjs-editor-alt-text-settings-dialog-label = Ustawienia tekstu alternatywnego obrazów +pdfjs-editor-alt-text-settings-automatic-title = Automatyczny tekst alternatywny +pdfjs-editor-alt-text-settings-create-model-button-label = Automatyczne tworzenie tekstu alternatywnego +pdfjs-editor-alt-text-settings-create-model-description = Podpowiada opisy, które mogą pomóc osobom, które nie widzą obrazu lub kiedy obraz się nie wczytuje. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model SI tekstu alternatywnego ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Działa lokalnie na urządzeniu użytkownika, więc Twoje dane pozostają prywatne. Wymagane do funkcji automatycznego tekstu alternatywnego. +pdfjs-editor-alt-text-settings-delete-model-button = Usuń +pdfjs-editor-alt-text-settings-download-model-button = Pobierz +pdfjs-editor-alt-text-settings-downloading-model-button = Pobieranie… +pdfjs-editor-alt-text-settings-editor-title = Edytor tekstu alternatywnego +pdfjs-editor-alt-text-settings-show-dialog-button-label = Wyświetlanie edytora tekstu alternatywnego od razu po dodaniu obrazu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga upewnić się, że wszystkie obrazy mają tekst alternatywny. +pdfjs-editor-alt-text-settings-close-button = Zamknij diff --git a/viewer/locale/pt-BR/viewer.ftl b/viewer/locale/pt-BR/viewer.ftl index 153f0426a..f3fc10766 100644 --- a/viewer/locale/pt-BR/viewer.ftl +++ b/viewer/locale/pt-BR/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = Propriedades do documento… pdfjs-document-properties-file-name = Nome do arquivo: pdfjs-document-properties-file-size = Tamanho do arquivo: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = Palavras-chave: pdfjs-document-properties-creation-date = Data da criação: pdfjs-document-properties-modification-date = Data da modificação: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -275,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Anotação { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -298,8 +312,6 @@ pdfjs-editor-stamp-button-label = Adicionar ou editar imagens pdfjs-editor-highlight-button = .title = Destaque pdfjs-editor-highlight-button-label = Destaque -pdfjs-highlight-floating-button = - .title = Destaque pdfjs-highlight-floating-button1 = .title = Destaque .aria-label = Destaque @@ -394,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostrar todos pdfjs-editor-highlight-show-all-button = .title = Mostrar todos + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escreva sua descrição aqui… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descrição curta para pessoas que não conseguem ver a imagem ou quando a imagem não é carregada. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente, pode não estar correto. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saiba mais +pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-not-now-button = Agora não +pdfjs-editor-new-alt-text-error-title = Não foi possível criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-error-description = Escreva seu próprio texto alternativo ou tente novamente mais tarde. +pdfjs-editor-new-alt-text-error-close-button = Fechar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = Baixando modelo de inteligência artificial de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Falta texto alternativo +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revisar texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Configurações de texto alternativo de imagens +pdfjs-image-alt-text-settings-button-label = Configurações de texto alternativo de imagens +pdfjs-editor-alt-text-settings-dialog-label = Configurações de texto alternativo de imagens +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugere uma descrição para ajudar pessoas que não conseguem ver a imagem ou quando a imagem não é carregada. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de inteligência artificial de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Funciona localmente no seu dispositivo para que seus dados permaneçam privativos. Necessário para texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Excluir +pdfjs-editor-alt-text-settings-download-model-button = Baixar +pdfjs-editor-alt-text-settings-downloading-model-button = Baixando… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar o editor de texto alternativo imediatamente ao adicionar uma imagem +pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a assegurar que todas as suas imagens tenham texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Fechar diff --git a/viewer/locale/pt-PT/viewer.ftl b/viewer/locale/pt-PT/viewer.ftl index 7fd8d3785..09fbef84c 100644 --- a/viewer/locale/pt-PT/viewer.ftl +++ b/viewer/locale/pt-PT/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Transferir pdfjs-bookmark-button = .title = Página atual (ver URL da página atual) pdfjs-bookmark-button-label = Pagina atual -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Abrir na aplicação -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Abrir na aplicação ## Secondary toolbar and context menu @@ -304,8 +298,6 @@ pdfjs-editor-stamp-button-label = Adicionar ou editar imagens pdfjs-editor-highlight-button = .title = Destaque pdfjs-editor-highlight-button-label = Destaque -pdfjs-highlight-floating-button = - .title = Destaque pdfjs-highlight-floating-button1 = .title = Realçar .aria-label = Realçar @@ -400,3 +392,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Mostrar tudo pdfjs-editor-highlight-show-all-button = .title = Mostrar tudo + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Editar texto alternativo (descrição da imagem) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Adicionar texto alternativo (descrição da imagem) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Escreva a sua descrição aqui… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Descrição curta para as pessoas que não podem visualizar a imagem ou quando a imagem não carrega. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Este texto alternativo foi criado automaticamente e pode ser impreciso. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Este texto alternativo foi criado automaticamente. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Saber mais +pdfjs-editor-new-alt-text-create-automatically-button-label = Criar texto alternativo automaticamente +pdfjs-editor-new-alt-text-not-now-button = Agora não +pdfjs-editor-new-alt-text-error-title = Não foi possível criar o texto alternativo automaticamente +pdfjs-editor-new-alt-text-error-description = Escreva o seu próprio texto alternativo ou tente novamente mais tarde. +pdfjs-editor-new-alt-text-error-close-button = Fechar +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) + .aria-valuetext = A transferir o modelo de IA de texto alternativo ({ $downloadedSize } de { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Texto alternativo adicionado +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Texto alternativo em falta +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Rever texto alternativo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Criado automaticamente: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Definições de texto alternativo da imagem +pdfjs-image-alt-text-settings-button-label = Definições de texto alternativo da imagem +pdfjs-editor-alt-text-settings-dialog-label = Definições de texto alternativo das imagens +pdfjs-editor-alt-text-settings-automatic-title = Texto alternativo automático +pdfjs-editor-alt-text-settings-create-model-button-label = Criar texto alternativo automaticamente +pdfjs-editor-alt-text-settings-create-model-description = Sugere descrições para ajudar as pessoas que não podem visualizar a imagem ou quando a imagem não carrega. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modelo de IA de texto alternativo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = É executado localmente no seu dispositivo para que os seus dados se mantenham privados. É necessário para o texto alternativo automático. +pdfjs-editor-alt-text-settings-delete-model-button = Eliminar +pdfjs-editor-alt-text-settings-download-model-button = Transferir +pdfjs-editor-alt-text-settings-downloading-model-button = A transferir… +pdfjs-editor-alt-text-settings-editor-title = Editor de texto alternativo +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mostrar editor de texto alternativo imediatamente ao adicionar uma imagem +pdfjs-editor-alt-text-settings-show-dialog-description = Ajuda a garantir que todas as suas imagens tenham um texto alternativo. +pdfjs-editor-alt-text-settings-close-button = Fechar diff --git a/viewer/locale/ru/viewer.ftl b/viewer/locale/ru/viewer.ftl index 6e3713ce4..dd2aa198c 100644 --- a/viewer/locale/ru/viewer.ftl +++ b/viewer/locale/ru/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Загрузить pdfjs-bookmark-button = .title = Текущая страница (просмотр URL-адреса с текущей страницы) pdfjs-bookmark-button-label = Текущая страница -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Открыть в приложении -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Открыть в программе ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Свойства документа… pdfjs-document-properties-file-name = Имя файла: pdfjs-document-properties-file-size = Размер файла: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } КБ ({ $b } байт) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } МБ ({ $b } байт) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } КБ ({ $size_b } байт) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Ключевые слова: pdfjs-document-properties-creation-date = Дата создания: pdfjs-document-properties-modification-date = Дата изменения: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -283,6 +288,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Аннотация { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -306,8 +314,6 @@ pdfjs-editor-stamp-button-label = Добавить или изменить из pdfjs-editor-highlight-button = .title = Выделение pdfjs-editor-highlight-button-label = Выделение -pdfjs-highlight-floating-button = - .title = Выделение pdfjs-highlight-floating-button1 = .title = Выделение .aria-label = Выделение @@ -402,3 +408,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Показать все pdfjs-editor-highlight-show-all-button = .title = Показать все + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Изменить альтернативный текст (описание изображения) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Добавить альтернативный текст (описание изображения) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Напишите здесь своё описание… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Короткое описание для людей, которые не видят изображение, или если изображение не загружается. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Этот альтернативный текст был создан автоматически и может быть неточным. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Подробнее +pdfjs-editor-new-alt-text-create-automatically-button-label = Автоматически создавать альтернативный текст +pdfjs-editor-new-alt-text-not-now-button = Не сейчас +pdfjs-editor-new-alt-text-error-title = Не удалось автоматически создать альтернативный текст +pdfjs-editor-new-alt-text-error-description = Пожалуйста, напишите свой альтернативный текст или попробуйте ещё раз позже. +pdfjs-editor-new-alt-text-error-close-button = Закрыть +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Загрузка модели ИИ для альтернативного текста ({ $downloadedSize } из { $totalSize } МБ) + .aria-valuetext = Загрузка модели ИИ для альтернативного текста ({ $downloadedSize } из { $totalSize } МБ) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Альтернативный текст добавлен +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Отсутствует альтернативный текст +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Отзыв на альтернативный текст +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Создано автоматически: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Настройки альтернативного текста для изображения +pdfjs-image-alt-text-settings-button-label = Настройки альтернативного текста для изображения +pdfjs-editor-alt-text-settings-dialog-label = Настройки альтернативного текста для изображения +pdfjs-editor-alt-text-settings-automatic-title = Автоматический альтернативный текст +pdfjs-editor-alt-text-settings-create-model-button-label = Автоматически создавать альтернативный текст +pdfjs-editor-alt-text-settings-create-model-description = Предлагает описания, чтобы помочь людям, которые не видят изображение, или если изображение не загружается. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = ИИ-модель альтернативного текста ({ $totalSize } МБ) +pdfjs-editor-alt-text-settings-ai-model-description = Запускается локально на вашем устройстве, поэтому ваши данные остаются конфиденциальными. Требуется для автоматического альтернативного текста. +pdfjs-editor-alt-text-settings-delete-model-button = Удалить +pdfjs-editor-alt-text-settings-download-model-button = Загрузить +pdfjs-editor-alt-text-settings-downloading-model-button = Загрузка… +pdfjs-editor-alt-text-settings-editor-title = Редактор альтернативного текста +pdfjs-editor-alt-text-settings-show-dialog-button-label = Сразу показывать редактор альтернативного текста при добавлении изображения +pdfjs-editor-alt-text-settings-show-dialog-description = Помогает вам убедиться, что все ваши изображения имеют альтернативный текст. +pdfjs-editor-alt-text-settings-close-button = Закрыть diff --git a/viewer/locale/sc/viewer.ftl b/viewer/locale/sc/viewer.ftl index a51943c90..1137c2bb0 100644 --- a/viewer/locale/sc/viewer.ftl +++ b/viewer/locale/sc/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Iscàrriga pdfjs-bookmark-button = .title = Pàgina atuale (ammustra s’URL de sa pàgina atuale) pdfjs-bookmark-button-label = Pàgina atuale -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Aberi in un’aplicatzione -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Aberi in un’aplicatzione ## Secondary toolbar and context menu @@ -266,6 +260,27 @@ pdfjs-editor-ink-button-label = Disinnu pdfjs-editor-stamp-button = .title = Agiunghe o modìfica immàgines pdfjs-editor-stamp-button-label = Agiunghe o modìfica immàgines +pdfjs-editor-highlight-button = + .title = Evidèntzia +pdfjs-editor-highlight-button-label = Evidèntzia +pdfjs-highlight-floating-button1 = + .title = Evidèntzia + .aria-label = Evidèntzia +pdfjs-highlight-floating-button-label = Evidèntzia + +## Remove button for the various kind of editor. + +pdfjs-editor-remove-ink-button = + .title = Boga su disinnu +pdfjs-editor-remove-freetext-button = + .title = Boga su testu +pdfjs-editor-remove-stamp-button = + .title = Boga s’immàgine +pdfjs-editor-remove-highlight-button = + .title = Boga s’evidèntzia + +## + # Editor Parameters pdfjs-editor-free-text-color-input = Colore pdfjs-editor-free-text-size-input = Mannària @@ -274,6 +289,8 @@ pdfjs-editor-ink-thickness-input = Grussària pdfjs-editor-stamp-add-image-button = .title = Agiunghe un’immàgine pdfjs-editor-stamp-add-image-button-label = Agiunghe un’immàgine +# This refers to the thickness of the line used for free highlighting (not bound to text) +pdfjs-editor-free-highlight-thickness-input = Grussària pdfjs-free-text = .aria-label = Editore de testu pdfjs-free-text-default-content = Cumintza a iscrìere… @@ -284,7 +301,67 @@ pdfjs-ink-canvas = ## Alt-text dialog +# Alternative text (alt text) helps when people can't see the image. +pdfjs-editor-alt-text-button-label = Testu alternativu +pdfjs-editor-alt-text-edit-button-label = Modifica su testu alternativu +pdfjs-editor-alt-text-dialog-label = Sèbera un’optzione +pdfjs-editor-alt-text-dialog-description = Su testu alternativu (“alt text”) est ùtile pro persones chi non podent bìdere s’immàgine o cando non benit carrigada. +pdfjs-editor-alt-text-add-description-label = Agiunghe una descritzione +pdfjs-editor-alt-text-cancel-button = Annulla +pdfjs-editor-alt-text-save-button = Sarva ## Editor resizers ## This is used in an aria label to help to understand the role of the resizer. + +## Color picker + +pdfjs-editor-colorpicker-button = + .title = Modifica su colore +pdfjs-editor-colorpicker-dropdown = + .aria-label = Colores a disponimentu +pdfjs-editor-colorpicker-yellow = + .title = Grogu +pdfjs-editor-colorpicker-green = + .title = Birde +pdfjs-editor-colorpicker-blue = + .title = Biaitu +pdfjs-editor-colorpicker-pink = + .title = Rosa + +## Show all highlights +## This is a toggle button to show/hide all the highlights. + + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Mancat su testu alternativu +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Revisiona su testu alternativu +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Creadu in automàticu: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-image-alt-text-settings-button-label = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-editor-alt-text-settings-dialog-label = Cunfiguratzione de su testu alternativu de is immàgines +pdfjs-editor-alt-text-settings-automatic-title = Testu alternativu automàticu +pdfjs-editor-alt-text-settings-create-model-button-label = Crea testu alternativu in automàticu +pdfjs-editor-alt-text-settings-create-model-description = Cussìgiat descritziones pro agiudare a gente chi non podet bìdere s’immàgine o cando non benit carrigada. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Modellu de IA pro su testu alternativu ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Est esecutadu in locale in manera chi is datos tuos abarrent in privadu. Rechestu pro sa generatzione automàtica de testu alternativu. +pdfjs-editor-alt-text-settings-delete-model-button = Cantzella +pdfjs-editor-alt-text-settings-download-model-button = Iscàrriga +pdfjs-editor-alt-text-settings-downloading-model-button = Iscarrighende… +pdfjs-editor-alt-text-settings-editor-title = Editore de testu alternativu +pdfjs-editor-alt-text-settings-show-dialog-button-label = Mustra deretu s’editore de testu alternativu cando siat agiunta un’immàgine +pdfjs-editor-alt-text-settings-show-dialog-description = T’agiudat a assegurare chi totu is immàgines tuas tèngiant unu testu alternativu. +pdfjs-editor-alt-text-settings-close-button = Serra diff --git a/viewer/locale/sk/viewer.ftl b/viewer/locale/sk/viewer.ftl index 07a0c5efc..59ffe736d 100644 --- a/viewer/locale/sk/viewer.ftl +++ b/viewer/locale/sk/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Stiahnuť pdfjs-bookmark-button = .title = Aktuálna stránka (zobraziť adresu URL z aktuálnej stránky) pdfjs-bookmark-button-label = Aktuálna stránka -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Otvoriť v aplikácii -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Otvoriť v aplikácii ## Secondary toolbar and context menu @@ -308,8 +302,6 @@ pdfjs-editor-stamp-button-label = Pridať alebo upraviť obrázky pdfjs-editor-highlight-button = .title = Zvýrazniť pdfjs-editor-highlight-button-label = Zvýrazniť -pdfjs-highlight-floating-button = - .title = Zvýrazniť pdfjs-highlight-floating-button1 = .title = Zvýrazniť .aria-label = Zvýrazniť @@ -404,3 +396,62 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Zobraziť všetko pdfjs-editor-highlight-show-all-button = .title = Zobraziť všetko + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Upraviť alternatívny text (popis obrázka) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Pridať alternatívny text (popis obrázka) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Sem napíšte svoj popis… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Krátky popis pre ľudí, ktorí nevidia obrázok alebo ak sa obrázok nenačíta. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Tento alternatívny text bol vytvorený automaticky a môže byť nepresný. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer = Tento alternatívny text bol vytvorený automaticky. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Ďalšie informácie +pdfjs-editor-new-alt-text-create-automatically-button-label = Automaticky vytvoriť alternatívny text +pdfjs-editor-new-alt-text-not-now-button = Teraz nie +pdfjs-editor-new-alt-text-error-title = Alternatívny text sa nepodarilo vytvoriť automaticky +pdfjs-editor-new-alt-text-error-description = Napíšte svoj vlastný alternatívny text alebo to skúste znova neskôr. +pdfjs-editor-new-alt-text-error-close-button = Zavrieť +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Sťahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB) + .aria-valuetext = Sťahuje sa model AI pre alternatívne texty ({ $downloadedSize } z { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternatívny text bol pridaný +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Chýbajúci alternatívny text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Skontrolovať alternatívny text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Vytvorené automaticky: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavenia alternatívneho textu obrázka +pdfjs-image-alt-text-settings-button-label = Nastavenia alternatívneho textu obrázka +pdfjs-editor-alt-text-settings-dialog-label = Nastavenia alternatívneho textu obrázka +pdfjs-editor-alt-text-settings-automatic-title = Automatický alternatívny text +pdfjs-editor-alt-text-settings-create-model-button-label = Automaticky vytvoriť alternatívny text +pdfjs-editor-alt-text-settings-create-model-description = Navrhuje popisy, ktoré pomôžu ľuďom, ktorým sa obrázok nezobrazuje alebo ak sa obrázok nenačíta. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model AI pre alternatívne texty ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Beží lokálne na vašom zariadení, takže vaše dáta zostanú súkromné. Vyžaduje sa pre automatický alternatívny text. +pdfjs-editor-alt-text-settings-delete-model-button = Odstrániť +pdfjs-editor-alt-text-settings-download-model-button = Stiahnuť +pdfjs-editor-alt-text-settings-downloading-model-button = Sťahuje sa… +pdfjs-editor-alt-text-settings-editor-title = Editor alternatívneho textu +pdfjs-editor-alt-text-settings-show-dialog-button-label = Pri pridávaní obrázka ihneď zobraziť editor alternatívneho textu +pdfjs-editor-alt-text-settings-show-dialog-description = Pomáha vám zabezpečiť, aby všetky vaše obrázky mali alternatívny text. +pdfjs-editor-alt-text-settings-close-button = Zavrieť diff --git a/viewer/locale/skr/viewer.ftl b/viewer/locale/skr/viewer.ftl index 72afe356c..f129ea861 100644 --- a/viewer/locale/skr/viewer.ftl +++ b/viewer/locale/skr/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = دستاویز خواص … pdfjs-document-properties-file-name = فائل دا ناں: pdfjs-document-properties-file-size = فائل دا سائز: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } بائٹاں) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } بائٹاں) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } کے بی ({ $size_b } بائٹس) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = کلیدی الفاظ: pdfjs-document-properties-creation-date = تخلیق دی تاریخ: pdfjs-document-properties-modification-date = ترمیم دی تاریخ: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -275,6 +286,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } تشریح] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -298,8 +312,6 @@ pdfjs-editor-stamp-button-label = تصویراں کوں شامل کرو یا ت pdfjs-editor-highlight-button = .title = نمایاں کرو pdfjs-editor-highlight-button-label = نمایاں کرو -pdfjs-highlight-floating-button = - .title = نمایاں کرو pdfjs-highlight-floating-button1 = .title = نمایاں کرو .aria-label = نمایاں کرو @@ -394,3 +406,38 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = سارے ݙکھاؤ pdfjs-editor-highlight-show-all-button = .title = سارے ݙکھاؤ + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-textarea = + .placeholder = اتھ آپݨی وضاحت لکھو۔۔۔ +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = ٻیا سِکھو +pdfjs-editor-new-alt-text-create-automatically-button-label = آلٹ عبارت خودکار بݨاؤ +pdfjs-editor-new-alt-text-not-now-button = ہݨ کائناں +pdfjs-editor-new-alt-text-error-title = آلٹ عبارت خودکار نہ بݨاؤ +pdfjs-editor-new-alt-text-error-close-button = بند کرو +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = آلٹ عبارت شامل تھی ڳئی +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = متبادل عبارت غائب ہے +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = alt متن تے نظرثانی کرو +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = خودکار تخلیق تھئی: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = تصویر آلٹ عبارت ترتیباں +pdfjs-image-alt-text-settings-button-label = تصویر آلٹ عبارت ترتیباں +pdfjs-editor-alt-text-settings-dialog-label = تصویر آلٹ عبارت ترتیباں +pdfjs-editor-alt-text-settings-automatic-title = خودکار آلٹ عبارت +pdfjs-editor-alt-text-settings-create-model-button-label = آلٹ عبارت خودکار بݨاؤ +pdfjs-editor-alt-text-settings-delete-model-button = مٹاؤ +pdfjs-editor-alt-text-settings-download-model-button = ڈاؤن لوڈ +pdfjs-editor-alt-text-settings-downloading-model-button = ڈاؤن لوڈ تھیندا پئے … +pdfjs-editor-alt-text-settings-editor-title = متبادل ٹیکسٹ ایڈیٹر +pdfjs-editor-alt-text-settings-close-button = بند کرو diff --git a/viewer/locale/sl/viewer.ftl b/viewer/locale/sl/viewer.ftl index 7cda4ec29..7c90021fa 100644 --- a/viewer/locale/sl/viewer.ftl +++ b/viewer/locale/sl/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = Lastnosti dokumenta … pdfjs-document-properties-file-name = Ime datoteke: pdfjs-document-properties-file-size = Velikost datoteke: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bajtov) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bajtov) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bajtov) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = Ključne besede: pdfjs-document-properties-creation-date = Datum nastanka: pdfjs-document-properties-modification-date = Datum spremembe: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -279,6 +290,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [Opomba vrste { $type }] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -302,6 +316,10 @@ pdfjs-editor-stamp-button-label = Dodajanje ali urejanje slik pdfjs-editor-highlight-button = .title = Označevalnik pdfjs-editor-highlight-button-label = Označevalnik +pdfjs-highlight-floating-button1 = + .title = Označi + .aria-label = Označi +pdfjs-highlight-floating-button-label = Označi ## Remove button for the various kind of editor. @@ -392,3 +410,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Prikaži vse pdfjs-editor-highlight-show-all-button = .title = Prikaži vse + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Uredi nadomestno besedilo (opis slike) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Dodaj nadomestno besedilo (opis slike) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Tukaj napišite svoj opis … +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kratek opis za ljudi, ki ne morejo videti slike, ali za primer, ko se slika ne naloži. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = To nadomestno besedilo je bilo ustvarjeno samodejno in je lahko netočno. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Več o tem +pdfjs-editor-new-alt-text-create-automatically-button-label = Samodejno ustvari nadomestno besedilo +pdfjs-editor-new-alt-text-not-now-button = Ne zdaj +pdfjs-editor-new-alt-text-error-title = Nadomestnega besedila ni bilo mogoče samodejno ustvariti +pdfjs-editor-new-alt-text-error-description = Sestavite svoje nadomestno besedilo ali poskusite znova pozneje. +pdfjs-editor-new-alt-text-error-close-button = Zapri +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Prenašanje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB) + .aria-valuetext = Prenašanje modela UI za nadomestno besedilo ({ $downloadedSize } od { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Nadomestno besedilo dodano +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Nadomestno besedilo manjka +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Oceni nadomestno besedilo +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Samodejno ustvarjeno: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Nastavitve nadomestnega besedila slike +pdfjs-image-alt-text-settings-button-label = Nastavitve nadomestnega besedila slike +pdfjs-editor-alt-text-settings-dialog-label = Nastavitve nadomestnega besedila slike +pdfjs-editor-alt-text-settings-automatic-title = Samodejno nadomestno besedilo +pdfjs-editor-alt-text-settings-create-model-button-label = Samodejno ustvari nadomestno besedilo +pdfjs-editor-alt-text-settings-create-model-description = Predlaga opise za pomoč ljudem, ki ne morejo videti slike, ali za primer, ko se slika ne naloži. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model UI za nadomestno besedilo ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Izvaja se lokalno na vaši napravi, tako da vaši podatki ostajajo zasebni. Zahtevano za samodejno nadomestno besedilo. +pdfjs-editor-alt-text-settings-delete-model-button = Izbriši +pdfjs-editor-alt-text-settings-download-model-button = Prenesi +pdfjs-editor-alt-text-settings-downloading-model-button = Prenašanje ... +pdfjs-editor-alt-text-settings-editor-title = Urejevalnik nadomestnega besedila +pdfjs-editor-alt-text-settings-show-dialog-button-label = Ob dodajanju slike takoj prikaži urejevalnik nadomestnega besedila +pdfjs-editor-alt-text-settings-show-dialog-description = Pomaga vam zagotoviti, da imajo vse vaše slike nadomestno besedilo. +pdfjs-editor-alt-text-settings-close-button = Zapri diff --git a/viewer/locale/sq/viewer.ftl b/viewer/locale/sq/viewer.ftl index be5b273dc..eb27d41d7 100644 --- a/viewer/locale/sq/viewer.ftl +++ b/viewer/locale/sq/viewer.ftl @@ -289,8 +289,6 @@ pdfjs-editor-stamp-button-label = Shtoni ose përpunoni figura pdfjs-editor-highlight-button = .title = Theksim pdfjs-editor-highlight-button-label = Theksoje -pdfjs-highlight-floating-button = - .title = Theksim pdfjs-highlight-floating-button1 = .title = Theksim .aria-label = Theksim @@ -385,3 +383,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Shfaqi krejt pdfjs-editor-highlight-show-all-button = .title = Shfaqi krejt + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Përpunoni tekst alternativ (përshkrim figure) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Shtoni tekst alternativ (përshkrim figure) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Shkruani këtu përshkrimin tuaj… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Përshkrim i shkurtër për persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Ky tekst alternativ qe krijuar automatikisht dhe mund të jetë i pasaktë. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Mësoni më tepër +pdfjs-editor-new-alt-text-create-automatically-button-label = Krijo automatikisht tekst alternativ +pdfjs-editor-new-alt-text-not-now-button = Jo tani +pdfjs-editor-new-alt-text-error-title = S’u krijua dot automatikisht tekst alternativ +pdfjs-editor-new-alt-text-error-description = Ju lutemi, shkruani tekstin tuaj alternativ, ose riprovoni më vonë. +pdfjs-editor-new-alt-text-error-close-button = Mbylle +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB) + .aria-valuetext = Po shkarkohet model IA teksti alternativ ({ $downloadedSize } nga { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = U shtua tekst alternativ +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Mungon teskt alternativ +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Shqyrtoni tekst alternativ +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Krijuar automatikisht: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Rregullime teksti alternativ figure +pdfjs-image-alt-text-settings-button-label = Rregullime teksti alternativ figure +pdfjs-editor-alt-text-settings-dialog-label = Rregullime teksti alternativ figure +pdfjs-editor-alt-text-settings-automatic-title = Tekst alternativ i automatizuar +pdfjs-editor-alt-text-settings-create-model-button-label = Krijo automatikisht tekst alternativ +pdfjs-editor-alt-text-settings-create-model-description = Sugjeron përshkrime, për të ndihmuar persona që s’munden të shohin figurën, ose për kur figura nuk ngarkohet dot. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Model IA teksti alternativ ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Xhiron lokalisht në pajisjen tuaj, pra të dhënat tuaja mbeten private. E domosdoshme për tekst të automatizuar alternativ. +pdfjs-editor-alt-text-settings-delete-model-button = Fshije +pdfjs-editor-alt-text-settings-download-model-button = Shkarkoje +pdfjs-editor-alt-text-settings-downloading-model-button = Po shkarkohet… +pdfjs-editor-alt-text-settings-editor-title = Përpunues teksti alternativ +pdfjs-editor-alt-text-settings-show-dialog-button-label = Shfaq menjëherë përpunues teksti alternativ, kur shtohet një figurë +pdfjs-editor-alt-text-settings-show-dialog-description = Ju ndihmon të siguroheni se krejt figurat tuaja kanë tekst alternativ. +pdfjs-editor-alt-text-settings-close-button = Mbylle diff --git a/viewer/locale/sv-SE/viewer.ftl b/viewer/locale/sv-SE/viewer.ftl index 61d69867d..2590ce8d9 100644 --- a/viewer/locale/sv-SE/viewer.ftl +++ b/viewer/locale/sv-SE/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Hämta pdfjs-bookmark-button = .title = Aktuell sida (Visa URL från aktuell sida) pdfjs-bookmark-button-label = Aktuell sida -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Öppna i app -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Öppna i app ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Dokumentegenskaper… pdfjs-document-properties-file-name = Filnamn: pdfjs-document-properties-file-size = Filstorlek: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } kB ({ $b } byte) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } byte) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } kB ({ $size_b } byte) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Nyckelord: pdfjs-document-properties-creation-date = Skapades: pdfjs-document-properties-modification-date = Ändrades: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -281,6 +286,9 @@ pdfjs-annotation-date-string = { $date } { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type }-annotering] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -304,8 +312,6 @@ pdfjs-editor-stamp-button-label = Lägg till eller redigera bilder pdfjs-editor-highlight-button = .title = Markera pdfjs-editor-highlight-button-label = Markera -pdfjs-highlight-floating-button = - .title = Markera pdfjs-highlight-floating-button1 = .title = Markera .aria-label = Markera @@ -400,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Visa alla pdfjs-editor-highlight-show-all-button = .title = Visa alla + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Redigera alternativ text (bildbeskrivning) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Lägg till alternativ text (bildbeskrivning) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Skriv din beskrivning här… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Kort beskrivning för personer som inte kan se bilden eller när bilden inte laddas. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Denna alternativa text skapades automatiskt och kan vara felaktig. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Läs mer +pdfjs-editor-new-alt-text-create-automatically-button-label = Skapa alternativ text automatiskt +pdfjs-editor-new-alt-text-not-now-button = Inte nu +pdfjs-editor-new-alt-text-error-title = Det gick inte att skapa alternativ text automatiskt +pdfjs-editor-new-alt-text-error-description = Skriv din egna alternativa text eller försök igen senare. +pdfjs-editor-new-alt-text-error-close-button = Stäng +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB) + .aria-valuetext = Hämtar AI-modell med alternativ text ({ $downloadedSize } av { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alternativ text tillagd +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Saknar alternativ text +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Granska alternativ text +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Skapas automatiskt: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Alternativ textinställningar för bild +pdfjs-image-alt-text-settings-button-label = Alternativ textinställningar för bild +pdfjs-editor-alt-text-settings-dialog-label = Alternativ textinställningar för bild +pdfjs-editor-alt-text-settings-automatic-title = Automatisk alternativ text +pdfjs-editor-alt-text-settings-create-model-button-label = Skapa alternativ text automatiskt +pdfjs-editor-alt-text-settings-create-model-description = Föreslår beskrivningar för att hjälpa personer som inte kan se bilden eller när bilden inte laddas. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = AI-modell för alternativ text ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Körs lokalt på din enhet så att din data förblir privat. Krävs för automatisk alternativ text. +pdfjs-editor-alt-text-settings-delete-model-button = Ta bort +pdfjs-editor-alt-text-settings-download-model-button = Hämta +pdfjs-editor-alt-text-settings-downloading-model-button = Hämtar… +pdfjs-editor-alt-text-settings-editor-title = Alternativ textredigerare +pdfjs-editor-alt-text-settings-show-dialog-button-label = Visa alternativ textredigerare direkt när du lägger till en bild +pdfjs-editor-alt-text-settings-show-dialog-description = Hjälper dig att se till att alla dina bilder har alternativ text. +pdfjs-editor-alt-text-settings-close-button = Stäng diff --git a/viewer/locale/tg/viewer.ftl b/viewer/locale/tg/viewer.ftl index 42964701c..8b3c03df1 100644 --- a/viewer/locale/tg/viewer.ftl +++ b/viewer/locale/tg/viewer.ftl @@ -298,8 +298,6 @@ pdfjs-editor-stamp-button-label = Илова ё таҳрир кардани та pdfjs-editor-highlight-button = .title = Ҷудокунӣ pdfjs-editor-highlight-button-label = Ҷудокунӣ -pdfjs-highlight-floating-button = - .title = Ҷудокунӣ pdfjs-highlight-floating-button1 = .title = Ҷудокунӣ .aria-label = Ҷудокунӣ @@ -394,3 +392,11 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Ҳамаро намоиш додан pdfjs-editor-highlight-show-all-button = .title = Ҳамаро намоиш додан + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Маълумоти бештар + +## Image alt-text settings + diff --git a/viewer/locale/th/viewer.ftl b/viewer/locale/th/viewer.ftl index 283b440cb..99787cfbc 100644 --- a/viewer/locale/th/viewer.ftl +++ b/viewer/locale/th/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = ดาวน์โหลด pdfjs-bookmark-button = .title = หน้าปัจจุบัน (ดู URL จากหน้าปัจจุบัน) pdfjs-bookmark-button-label = หน้าปัจจุบัน -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = เปิดในแอป -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = เปิดในแอป ## Secondary toolbar and context menu @@ -296,8 +290,6 @@ pdfjs-editor-stamp-button-label = เพิ่มหรือแก้ไขภ pdfjs-editor-highlight-button = .title = เน้น pdfjs-editor-highlight-button-label = เน้น -pdfjs-highlight-floating-button = - .title = เน้นสี pdfjs-highlight-floating-button1 = .title = เน้นสี .aria-label = เน้นสี @@ -392,3 +384,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = แสดงทั้งหมด pdfjs-editor-highlight-show-all-button = .title = แสดงทั้งหมด + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = แก้ไขข้อความทดแทน (คำอธิบายภาพ) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = เพิ่มข้อความทดแทน (คำอธิบายภาพ) +pdfjs-editor-new-alt-text-textarea = + .placeholder = เขียนคำอธิบายของคุณที่นี่… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = คำอธิบายสั้นๆ สำหรับผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = ข้อความทดแทนนี้ถูกสร้างขึ้นโดยอัตโนมัติและอาจไม่ถูกต้อง +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = เรียนรู้เพิ่มเติม +pdfjs-editor-new-alt-text-create-automatically-button-label = สร้างข้อความทดแทนโดยอัตโนมัติ +pdfjs-editor-new-alt-text-not-now-button = ไม่ใช่ตอนนี้ +pdfjs-editor-new-alt-text-error-title = ไม่สามารถสร้างข้อความทดแทนโดยอัตโนมัติได้ +pdfjs-editor-new-alt-text-error-description = กรุณาเขียนข้อความทดแทนด้วยตัวเองหรือลองใหม่อีกครั้งในภายหลัง +pdfjs-editor-new-alt-text-error-close-button = ปิด +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = กำลังดาวน์โหลดโมเดล AI สำหรับข้อความทดแทน ({ $downloadedSize } จาก { $totalSize } MB) + .aria-valuetext = กำลังดาวน์โหลดโมเดล AI สำหรับข้อความทดแทน ({ $downloadedSize } จาก { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = เพิ่มข้อความทดแทนแล้ว +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = ขาดข้อความทดแทน +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = ตรวจสอบข้อความทดแทน +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = สร้างขึ้นโดยอัตโนมัติ: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = ตั้งค่าข้อความทดแทนภาพ +pdfjs-image-alt-text-settings-button-label = ตั้งค่าข้อความทดแทนภาพ +pdfjs-editor-alt-text-settings-dialog-label = ตั้งค่าข้อความทดแทนภาพ +pdfjs-editor-alt-text-settings-automatic-title = การทดแทนด้วยข้อความอัตโนมัติ +pdfjs-editor-alt-text-settings-create-model-button-label = สร้างข้อความทดแทนอัตโนมัติ +pdfjs-editor-alt-text-settings-create-model-description = แนะนำคำอธิบายเพื่อช่วยเหลือผู้ที่ไม่สามารถมองเห็นภาพหรือเมื่อภาพไม่โหลด +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = โมเดล AI สำหรับข้อความทดแทน ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = ทำงานในเครื่องของคุณเพื่อให้ข้อมูลของคุณเป็นส่วนตัว จำเป็นสำหรับข้อความทดแทนอัตโนมัติ +pdfjs-editor-alt-text-settings-delete-model-button = ลบ +pdfjs-editor-alt-text-settings-download-model-button = ดาวน์โหลด +pdfjs-editor-alt-text-settings-downloading-model-button = กำลังดาวน์โหลด… +pdfjs-editor-alt-text-settings-editor-title = ตัวแก้ไขข้อความทดแทน +pdfjs-editor-alt-text-settings-show-dialog-button-label = แสดงตัวแก้ไขข้อความทดแทนทันทีเมื่อเพิ่มภาพ +pdfjs-editor-alt-text-settings-show-dialog-description = ช่วยให้คุณแน่ใจว่าภาพทั้งหมดของคุณมีข้อความทดแทน +pdfjs-editor-alt-text-settings-close-button = ปิด diff --git a/viewer/locale/tr/viewer.ftl b/viewer/locale/tr/viewer.ftl index 198022ebe..5d8c50760 100644 --- a/viewer/locale/tr/viewer.ftl +++ b/viewer/locale/tr/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = Belge özellikleri… pdfjs-document-properties-file-name = Dosya adı: pdfjs-document-properties-file-size = Dosya boyutu: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bayt) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bayt) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bayt) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = Anahtar kelimeler: pdfjs-document-properties-creation-date = Oluşturma tarihi: pdfjs-document-properties-modification-date = Değiştirme tarihi: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date } { $time } @@ -275,6 +286,9 @@ pdfjs-annotation-date-string = { $date } { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } işareti] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -298,8 +312,6 @@ pdfjs-editor-stamp-button-label = Resim ekle veya düzenle pdfjs-editor-highlight-button = .title = Vurgula pdfjs-editor-highlight-button-label = Vurgula -pdfjs-highlight-floating-button = - .title = Vurgula pdfjs-highlight-floating-button1 = .title = Vurgula .aria-label = Vurgula @@ -394,3 +406,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Tümünü göster pdfjs-editor-highlight-show-all-button = .title = Tümünü göster + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Alt metni düzenle (resim açıklaması) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Alt metin ekle (resim açıklaması) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Açıklamanızı buraya yazın… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Görme engelli kişilere gösterilecek veya resmin yüklenemediği durumlarda gösterilecek kısa açıklama. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Bu alt metin otomatik olarak oluşturulmuştur ve hatalı olabilir. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Daha fazla bilgi alın +pdfjs-editor-new-alt-text-create-automatically-button-label = Otomatik olarak alt metin oluştur +pdfjs-editor-new-alt-text-not-now-button = Şimdi değil +pdfjs-editor-new-alt-text-error-title = Alt metin otomatik olarak oluşturulamadı +pdfjs-editor-new-alt-text-error-description = Lütfen kendi alt metninizi yazın veya daha sonra yeniden deneyin. +pdfjs-editor-new-alt-text-error-close-button = Kapat +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = Alt metin yapay zekâ modeli indiriliyor ({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Alt metin eklendi +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Alt metin eksik +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Alt metni incele +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Otomatik olarak oluşturuldu: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Resim alt metni ayarları +pdfjs-image-alt-text-settings-button-label = Resim alt metni ayarları +pdfjs-editor-alt-text-settings-dialog-label = Resim alt metni ayarları +pdfjs-editor-alt-text-settings-automatic-title = Otomatik alt metin +pdfjs-editor-alt-text-settings-create-model-button-label = Otomatik olarak alt metin oluştur +pdfjs-editor-alt-text-settings-create-model-description = Görme engelli kişilere gösterilecek veya resmin yüklenemediği durumlarda gösterilecek açıklamalar önerir. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Alt metin yapay zekâ modeli ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Verilerinizin gizli kalması için cihazınızda yerel olarak çalışır. Otomatik alt metin için gereklidir. +pdfjs-editor-alt-text-settings-delete-model-button = Sil +pdfjs-editor-alt-text-settings-download-model-button = İndir +pdfjs-editor-alt-text-settings-downloading-model-button = İndiriliyor… +pdfjs-editor-alt-text-settings-editor-title = Alt metin düzenleyicisi +pdfjs-editor-alt-text-settings-show-dialog-button-label = Resim eklerken alt metin düzenleyicisini hemen göster +pdfjs-editor-alt-text-settings-show-dialog-description = Tüm resimlerinizin alt metne sahip olduğundan emin olmanızı sağlar. +pdfjs-editor-alt-text-settings-close-button = Kapat diff --git a/viewer/locale/uk/viewer.ftl b/viewer/locale/uk/viewer.ftl index d663e675c..070381199 100644 --- a/viewer/locale/uk/viewer.ftl +++ b/viewer/locale/uk/viewer.ftl @@ -300,8 +300,6 @@ pdfjs-editor-stamp-button-label = Додати чи редагувати зоб pdfjs-editor-highlight-button = .title = Підсвітити pdfjs-editor-highlight-button-label = Підсвітити -pdfjs-highlight-floating-button = - .title = Підсвітити pdfjs-highlight-floating-button1 = .title = Підсвітити .aria-label = Підсвітити @@ -396,3 +394,36 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Показати все pdfjs-editor-highlight-show-all-button = .title = Показати все + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Редагувати альтернативний текст (опис зображення) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Додати альтернативний текст (опис зображення) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Напишіть свій опис тут… +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Докладніше +pdfjs-editor-new-alt-text-create-automatically-button-label = Автоматично створювати альтернативний текст +pdfjs-editor-new-alt-text-not-now-button = Не зараз +pdfjs-editor-new-alt-text-error-close-button = Закрити +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Альтернативний текст додано +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Відсутній альтернативний текст +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Переглянути альтернативний текст +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Створено автоматично: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Налаштування альтернативного тексту зображення +pdfjs-image-alt-text-settings-button-label = Налаштування альтернативного тексту зображення +pdfjs-editor-alt-text-settings-dialog-label = Налаштування альтернативного тексту зображення +pdfjs-editor-alt-text-settings-automatic-title = Автоматичний альтернативний текст +pdfjs-editor-alt-text-settings-create-model-button-label = Автоматично створювати альтернативний текст diff --git a/viewer/locale/vi/viewer.ftl b/viewer/locale/vi/viewer.ftl index 4c53f75b2..98c1bb5b6 100644 --- a/viewer/locale/vi/viewer.ftl +++ b/viewer/locale/vi/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = Tải xuống pdfjs-bookmark-button = .title = Trang hiện tại (xem URL từ trang hiện tại) pdfjs-bookmark-button-label = Trang hiện tại -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = Mở trong ứng dụng -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = Mở trong ứng dụng ## Secondary toolbar and context menu @@ -111,6 +105,14 @@ pdfjs-document-properties-button-label = Thuộc tính của tài liệu… pdfjs-document-properties-file-name = Tên tập tin: pdfjs-document-properties-file-size = Kích thước: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } byte) @@ -125,6 +127,9 @@ pdfjs-document-properties-keywords = Từ khóa: pdfjs-document-properties-creation-date = Ngày tạo: pdfjs-document-properties-modification-date = Ngày sửa đổi: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -273,6 +278,9 @@ pdfjs-annotation-date-string = { $date }, { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } Chú thích] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -296,8 +304,6 @@ pdfjs-editor-stamp-button-label = Thêm hoặc chỉnh sửa hình ảnh pdfjs-editor-highlight-button = .title = Đánh dấu pdfjs-editor-highlight-button-label = Đánh dấu -pdfjs-highlight-floating-button = - .title = Đánh dấu pdfjs-highlight-floating-button1 = .title = Đánh dấu .aria-label = Đánh dấu @@ -392,3 +398,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = Hiện tất cả pdfjs-editor-highlight-show-all-button = .title = Hiện tất cả + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = Chỉnh sửa văn bản thay thế (mô tả hình ảnh) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = Thêm văn bản thay thế (mô tả hình ảnh) +pdfjs-editor-new-alt-text-textarea = + .placeholder = Viết mô tả của bạn ở đây… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = Mô tả ngắn gọn dành cho người không xem được ảnh hoặc khi không thể tải ảnh. +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = Văn bản thay thế này được tạo tự động và có thể không chính xác. +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Tìm hiểu thêm +pdfjs-editor-new-alt-text-create-automatically-button-label = Tạo văn bản thay thế tự động +pdfjs-editor-new-alt-text-not-now-button = Không phải bây giờ +pdfjs-editor-new-alt-text-error-title = Không thể tạo tự động văn bản thay thế +pdfjs-editor-new-alt-text-error-description = Vui lòng viết văn bản thay thế của riêng bạn hoặc thử lại sau. +pdfjs-editor-new-alt-text-error-close-button = Đóng +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = Đang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } trong số { $totalSize } MB) + .aria-valuetext = Đang tải xuống mô hình AI văn bản thay thế ({ $downloadedSize } trong số { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = Đã thêm văn bản thay thế +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = Thiếu văn bản thay thế +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = Xem lại văn bản thay thế +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Được tạo tự động: { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = Cài đặt văn bản thay thế của hình ảnh +pdfjs-image-alt-text-settings-button-label = Cài đặt văn bản thay thế của hình ảnh +pdfjs-editor-alt-text-settings-dialog-label = Cài đặt văn bản thay thế của hình ảnh +pdfjs-editor-alt-text-settings-automatic-title = Văn bản thay thế tự động +pdfjs-editor-alt-text-settings-create-model-button-label = Tạo văn bản thay thế tự động +pdfjs-editor-alt-text-settings-create-model-description = Đề xuất mô tả giúp ích cho những người không xem được ảnh hoặc khi không thể tải ảnh. +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = Mô hình AI văn bản khác ({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = Chạy cục bộ trên thiết bị của bạn để dữ liệu của bạn luôn ở chế độ riêng tư. Bắt buộc đối với văn bản thay thế tự động. +pdfjs-editor-alt-text-settings-delete-model-button = Xóa +pdfjs-editor-alt-text-settings-download-model-button = Tải xuống +pdfjs-editor-alt-text-settings-downloading-model-button = Đang tải xuống… +pdfjs-editor-alt-text-settings-editor-title = Trình soạn thảo văn bản thay thế +pdfjs-editor-alt-text-settings-show-dialog-button-label = Hiển thị ngay trình soạn thảo văn bản thay thế khi thêm hình ảnh +pdfjs-editor-alt-text-settings-show-dialog-description = Giúp bạn đảm bảo tất cả hình ảnh của bạn đều có văn bản thay thế. +pdfjs-editor-alt-text-settings-close-button = Đóng diff --git a/viewer/locale/zh-CN/viewer.ftl b/viewer/locale/zh-CN/viewer.ftl index d653d5c2f..03798c1a2 100644 --- a/viewer/locale/zh-CN/viewer.ftl +++ b/viewer/locale/zh-CN/viewer.ftl @@ -105,6 +105,14 @@ pdfjs-document-properties-button-label = 文档属性… pdfjs-document-properties-file-name = 文件名: pdfjs-document-properties-file-size = 文件大小: # Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } 字节) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } 字节) +# Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } 字节) @@ -119,6 +127,9 @@ pdfjs-document-properties-keywords = 关键词: pdfjs-document-properties-creation-date = 创建日期: pdfjs-document-properties-modification-date = 修改日期: # Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } +# Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date }, { $time } @@ -267,6 +278,9 @@ pdfjs-annotation-date-string = { $date },{ $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } 注释] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -290,8 +304,6 @@ pdfjs-editor-stamp-button-label = 添加或编辑图像 pdfjs-editor-highlight-button = .title = 高亮 pdfjs-editor-highlight-button-label = 高亮 -pdfjs-highlight-floating-button = - .title = 高亮 pdfjs-highlight-floating-button1 = .title = 高亮 .aria-label = 高亮 @@ -386,3 +398,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = 显示全部 pdfjs-editor-highlight-show-all-button = .title = 显示全部 + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 编辑替换文字(图像描述) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 添加替换文字(图像描述) +pdfjs-editor-new-alt-text-textarea = + .placeholder = 请在此处撰写描述… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 向无法看到或加载图像的用户提供的简短描述。 +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = 此段替换文字为自动创建,有可能不准确。 +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 详细了解 +pdfjs-editor-new-alt-text-create-automatically-button-label = 自动创建替换文字 +pdfjs-editor-new-alt-text-not-now-button = 暂时不要 +pdfjs-editor-new-alt-text-error-title = 无法自动创建替换文字 +pdfjs-editor-new-alt-text-error-description = 请自行撰写替换文字,或稍后再试。 +pdfjs-editor-new-alt-text-error-close-button = 关闭 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下载提供替换文字的 AI 模型({ $downloadedSize }/{ $totalSize } MB) + .aria-valuetext = 正在下载提供替换文字的 AI 模型({ $downloadedSize }/{ $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = 已添加替换文字 +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = 缺少替换文字 +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = 检查替换文字 +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = [自动创建] { $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = 图像替换文字设置 +pdfjs-image-alt-text-settings-button-label = 图像替换文字设置 +pdfjs-editor-alt-text-settings-dialog-label = 图像替换文字设置 +pdfjs-editor-alt-text-settings-automatic-title = 自动创建替换文字 +pdfjs-editor-alt-text-settings-create-model-button-label = 自动创建替换文字 +pdfjs-editor-alt-text-settings-create-model-description = 向无法看到或加载图像的用户提供描述。 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 提供替换文字的 AI 模型({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 在您的设备本地运行,可使数据保持私密。自动创建替换文字需要使用此模型。 +pdfjs-editor-alt-text-settings-delete-model-button = 删除 +pdfjs-editor-alt-text-settings-download-model-button = 下载 +pdfjs-editor-alt-text-settings-downloading-model-button = 正在下载… +pdfjs-editor-alt-text-settings-editor-title = 替换文字编辑器 +pdfjs-editor-alt-text-settings-show-dialog-button-label = 添加图像后立即显示替换文字编辑器 +pdfjs-editor-alt-text-settings-show-dialog-description = 帮助确保所有图像均拥有替换文字。 +pdfjs-editor-alt-text-settings-close-button = 关闭 diff --git a/viewer/locale/zh-TW/viewer.ftl b/viewer/locale/zh-TW/viewer.ftl index f8614a9f3..20ff406c6 100644 --- a/viewer/locale/zh-TW/viewer.ftl +++ b/viewer/locale/zh-TW/viewer.ftl @@ -51,12 +51,6 @@ pdfjs-download-button-label = 下載 pdfjs-bookmark-button = .title = 目前頁面(含目前檢視頁面的網址) pdfjs-bookmark-button-label = 目前頁面 -# Used in Firefox for Android. -pdfjs-open-in-app-button = - .title = 在應用程式中開啟 -# Used in Firefox for Android. -# Length of the translation matters since we are in a mobile context, with limited screen estate. -pdfjs-open-in-app-button-label = 用程式開啟 ## Secondary toolbar and context menu @@ -82,8 +76,8 @@ pdfjs-cursor-hand-tool-button = .title = 開啟頁面移動工具 pdfjs-cursor-hand-tool-button-label = 頁面移動工具 pdfjs-scroll-page-button = - .title = 使用頁面捲動功能 -pdfjs-scroll-page-button-label = 頁面捲動功能 + .title = 使用單頁捲動版面 +pdfjs-scroll-page-button-label = 單頁捲動 pdfjs-scroll-vertical-button = .title = 使用垂直捲動版面 pdfjs-scroll-vertical-button-label = 垂直捲動 @@ -108,8 +102,16 @@ pdfjs-spread-even-button-label = 偶數跨頁 pdfjs-document-properties-button = .title = 文件內容… pdfjs-document-properties-button-label = 文件內容… -pdfjs-document-properties-file-name = 檔案名稱: -pdfjs-document-properties-file-size = 檔案大小: +pdfjs-document-properties-file-name = 檔案名稱: +pdfjs-document-properties-file-size = 檔案大小: +# Variables: +# $kb (Number) - the PDF file size in kilobytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB({ $b } 位元組) +# Variables: +# $mb (Number) - the PDF file size in megabytes +# $b (Number) - the PDF file size in bytes +pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB({ $b } 位元組) # Variables: # $size_kb (Number) - the PDF file size in kilobytes # $size_b (Number) - the PDF file size in bytes @@ -118,21 +120,24 @@ pdfjs-document-properties-kb = { $size_kb } KB({ $size_b } 位元組) # $size_mb (Number) - the PDF file size in megabytes # $size_b (Number) - the PDF file size in bytes pdfjs-document-properties-mb = { $size_mb } MB({ $size_b } 位元組) -pdfjs-document-properties-title = 標題: -pdfjs-document-properties-author = 作者: -pdfjs-document-properties-subject = 主旨: -pdfjs-document-properties-keywords = 關鍵字: -pdfjs-document-properties-creation-date = 建立日期: -pdfjs-document-properties-modification-date = 修改日期: +pdfjs-document-properties-title = 標題: +pdfjs-document-properties-author = 作者: +pdfjs-document-properties-subject = 主旨: +pdfjs-document-properties-keywords = 關鍵字: +pdfjs-document-properties-creation-date = 建立日期: +pdfjs-document-properties-modification-date = 修改日期: +# Variables: +# $dateObj (Date) - the creation/modification date and time of the PDF file +pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } # Variables: # $date (Date) - the creation/modification date of the PDF file # $time (Time) - the creation/modification time of the PDF file pdfjs-document-properties-date-string = { $date } { $time } -pdfjs-document-properties-creator = 建立者: -pdfjs-document-properties-producer = PDF 產生器: -pdfjs-document-properties-version = PDF 版本: -pdfjs-document-properties-page-count = 頁數: -pdfjs-document-properties-page-size = 頁面大小: +pdfjs-document-properties-creator = 建立者: +pdfjs-document-properties-producer = PDF 產生器: +pdfjs-document-properties-version = PDF 版本: +pdfjs-document-properties-page-count = 頁數: +pdfjs-document-properties-page-size = 頁面大小: pdfjs-document-properties-page-size-unit-inches = in pdfjs-document-properties-page-size-unit-millimeters = mm pdfjs-document-properties-page-size-orientation-portrait = 垂直 @@ -156,7 +161,7 @@ pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $hei # The linearization status of the document; usually called "Fast Web View" in # English locales of Adobe software. -pdfjs-document-properties-linearized = 快速 Web 檢視: +pdfjs-document-properties-linearized = 快速 Web 檢視: pdfjs-document-properties-linearized-yes = 是 pdfjs-document-properties-linearized-no = 否 pdfjs-document-properties-close-button = 關閉 @@ -273,6 +278,9 @@ pdfjs-annotation-date-string = { $date } { $time } # Some common types are e.g.: "Check", "Text", "Comment", "Note" pdfjs-text-annotation-type = .alt = [{ $type } 註解] +# Variables: +# $dateObj (Date) - the modification date and time of the annotation +pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") } ## Password @@ -296,8 +304,6 @@ pdfjs-editor-stamp-button-label = 新增或編輯圖片 pdfjs-editor-highlight-button = .title = 強調 pdfjs-editor-highlight-button-label = 強調 -pdfjs-highlight-floating-button = - .title = 強調 pdfjs-highlight-floating-button1 = .title = 強調 .aria-label = 強調 @@ -331,7 +337,7 @@ pdfjs-editor-free-highlight-thickness-title = .title = 更改強調文字以外的項目時的線條粗細 pdfjs-free-text = .aria-label = 文本編輯器 -pdfjs-free-text-default-content = 開始打字… +pdfjs-free-text-default-content = 在此打字… pdfjs-ink = .aria-label = 圖形編輯器 pdfjs-ink-canvas = @@ -392,3 +398,60 @@ pdfjs-editor-colorpicker-red = pdfjs-editor-highlight-show-all-button-label = 顯示全部 pdfjs-editor-highlight-show-all-button = .title = 顯示全部 + +## New alt-text dialog +## Group note for entire feature: Alternative text (alt text) helps when people can't see the image. This feature includes a tool to create alt text automatically using an AI model that works locally on the user's device to preserve privacy. + +# Modal header positioned above a text box where users can edit the alt text. +pdfjs-editor-new-alt-text-dialog-edit-label = 編輯替代文字(圖片描述) +# Modal header positioned above a text box where users can add the alt text. +pdfjs-editor-new-alt-text-dialog-add-label = 新增替代文字(圖片描述) +pdfjs-editor-new-alt-text-textarea = + .placeholder = 在此寫下您的描述文字… +# This text refers to the alt text box above this description. It offers a definition of alt text. +pdfjs-editor-new-alt-text-description = 為看不到圖片的讀者,或圖片無法載入時顯示的簡短描述。 +# This is a required legal disclaimer that refers to the automatically created text inside the alt text box above this text. It disappears if the text is edited by a human. +pdfjs-editor-new-alt-text-disclaimer1 = 此替代文字是自動產生的,可能不夠精確。 +pdfjs-editor-new-alt-text-disclaimer-learn-more-url = 更多資訊 +pdfjs-editor-new-alt-text-create-automatically-button-label = 自動產生替代文字 +pdfjs-editor-new-alt-text-not-now-button = 暫時不要 +pdfjs-editor-new-alt-text-error-title = 無法自動產生替代文字 +pdfjs-editor-new-alt-text-error-description = 請自行填寫替代文字,或稍後再試一次。 +pdfjs-editor-new-alt-text-error-close-button = 關閉 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +# $downloadedSize (Number) - the downloaded size (in MB) of the AI model. +# $percent (Number) - the percentage of the downloaded size. +pdfjs-editor-new-alt-text-ai-model-downloading-progress = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB) + .aria-valuetext = 正在下載替代文字 AI 模型({ $downloadedSize } / { $totalSize } MB) +# This is a button that users can click to edit the alt text they have already added. +pdfjs-editor-new-alt-text-added-button-label = 已新增替代文字 +# This is a button that users can click to open the alt text editor and add alt text when it is not present. +pdfjs-editor-new-alt-text-missing-button-label = 缺少替代文字 +# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated. +pdfjs-editor-new-alt-text-to-review-button-label = 確認替代文字 +# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear. +# Variables: +# $generatedAltText (String) - the generated alt-text. +pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = 自動產生:{ $generatedAltText } + +## Image alt-text settings + +pdfjs-image-alt-text-settings-button = + .title = 圖片替代文字設定 +pdfjs-image-alt-text-settings-button-label = 圖片替代文字設定 +pdfjs-editor-alt-text-settings-dialog-label = 圖片替代文字設定 +pdfjs-editor-alt-text-settings-automatic-title = 自動化替代文字 +pdfjs-editor-alt-text-settings-create-model-button-label = 自動產生替代文字 +pdfjs-editor-alt-text-settings-create-model-description = 為您建議圖片描述,幫助看不到圖片的讀者,或於圖片無法載入時顯示。 +# Variables: +# $totalSize (Number) - the total size (in MB) of the AI model. +pdfjs-editor-alt-text-settings-download-model-label = 替代文字 AI 模型({ $totalSize } MB) +pdfjs-editor-alt-text-settings-ai-model-description = 在您的本機裝置上運作,以確保您的資料隱私。必須下載此模型才可以自動產生替代文字。 +pdfjs-editor-alt-text-settings-delete-model-button = 刪除 +pdfjs-editor-alt-text-settings-download-model-button = 下載 +pdfjs-editor-alt-text-settings-downloading-model-button = 下載中… +pdfjs-editor-alt-text-settings-editor-title = 替代文字編輯器 +pdfjs-editor-alt-text-settings-show-dialog-button-label = 新增圖片後立即顯示替代文字編輯器 +pdfjs-editor-alt-text-settings-show-dialog-description = 幫助您確保所有圖片都有替代文字。 +pdfjs-editor-alt-text-settings-close-button = 關閉 diff --git a/viewer/viewer.css b/viewer/viewer.css index 95d45b8f6..b9e2437cf 100644 --- a/viewer/viewer.css +++ b/viewer/viewer.css @@ -22,6 +22,9 @@ --hover-filter:brightness(0.9); --focus-ring-color:#0060df; --focus-ring-outline:2px solid var(--focus-ring-color); + --link-fg-color:#0060df; + --link-hover-fg-color:#0250bb; + --separator-color:#f0f0f4; --textarea-border-color:#8f8f9d; --textarea-bg-color:white; @@ -41,20 +44,10 @@ --button-primary-bg-color:#0060df; --button-primary-fg-color:#fbfbfe; + --button-primary-border-color:var(--button-primary-bg-color); --button-primary-hover-bg-color:var(--button-primary-bg-color); --button-primary-hover-fg-color:var(--button-primary-fg-color); --button-primary-hover-border-color:var(--button-primary-hover-bg-color); - - font:message-box; - font-size:13px; - font-weight:400; - line-height:150%; - border-radius:4px; - padding:12px 16px; - border:1px solid var(--dialog-border-color); - background:var(--dialog-bg-color); - color:var(--text-primary-color); - box-shadow:var(--dialog-shadow); } @media (prefers-color-scheme: dark){ @@ -67,6 +60,9 @@ --text-secondary-color:#cfcfd8; --focus-ring-color:#0df; --hover-filter:brightness(1.4); + --link-fg-color:#0df; + --link-hover-fg-color:#80ebff; + --separator-color:#52525e; --textarea-bg-color:#42414d; @@ -88,6 +84,9 @@ --text-secondary-color:#cfcfd8; --focus-ring-color:#0df; --hover-filter:brightness(1.4); + --link-fg-color:#0df; + --link-hover-fg-color:#80ebff; + --separator-color:#52525e; --textarea-bg-color:#42414d; @@ -110,6 +109,9 @@ --text-secondary-color:CanvasText; --hover-filter:none; --focus-ring-color:ButtonBorder; + --link-fg-color:LinkText; + --link-hover-fg-color:LinkText; + --separator-color:CanvasText; --textarea-border-color:ButtonBorder; --textarea-bg-color:Field; @@ -133,26 +135,69 @@ } } -.dialog .mainContainer *:focus-visible{ +.dialog{ + + font:message-box; + font-size:13px; + font-weight:400; + line-height:150%; + border-radius:4px; + padding:12px 16px; + border:1px solid var(--dialog-border-color); + background:var(--dialog-bg-color); + color:var(--text-primary-color); + box-shadow:var(--dialog-shadow); +} + +:is(.dialog .mainContainer) *:focus-visible{ outline:var(--focus-ring-outline); outline-offset:2px; } -.dialog .mainContainer .radio{ +:is(.dialog .mainContainer) .title{ + display:flex; + width:auto; + flex-direction:column; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + } + +:is(:is(.dialog .mainContainer) .title) > span{ + font-size:13px; + font-style:normal; + font-weight:590; + line-height:150%; + } + +:is(.dialog .mainContainer) .dialogSeparator{ + width:100%; + height:1px; + margin-block:4px; + background-color:var(--separator-color); + } + +:is(.dialog .mainContainer) .dialogButtonsGroup{ + display:flex; + gap:12px; + align-self:flex-end; + } + +:is(.dialog .mainContainer) .radio{ display:flex; flex-direction:column; align-items:flex-start; gap:4px; } -.dialog .mainContainer .radio > .radioButton{ +:is(:is(.dialog .mainContainer) .radio) > .radioButton{ display:flex; gap:8px; align-self:stretch; align-items:center; } -.dialog .mainContainer .radio > .radioButton input{ +:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input{ -webkit-appearance:none; -moz-appearance:none; appearance:none; @@ -164,16 +209,16 @@ border:1px solid var(--radio-border-color); } -.dialog .mainContainer .radio > .radioButton input:hover{ +:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):hover{ filter:var(--hover-filter); } -.dialog .mainContainer .radio > .radioButton input:checked{ +:is(:is(:is(:is(.dialog .mainContainer) .radio) > .radioButton) input):checked{ background-color:var(--radio-checked-bg-color); border:4px solid var(--radio-checked-border-color); } -.dialog .mainContainer .radio > .radioLabel{ +:is(:is(.dialog .mainContainer) .radio) > .radioLabel{ display:flex; padding-inline-start:24px; align-items:flex-start; @@ -181,13 +226,13 @@ align-self:stretch; } -.dialog .mainContainer .radio > .radioLabel > span{ +:is(:is(:is(.dialog .mainContainer) .radio) > .radioLabel) > span{ flex:1 0 0; font-size:11px; color:var(--text-secondary-color); } -.dialog .mainContainer button{ +:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton)){ border-radius:4px; border:1px solid; font:menu; @@ -197,37 +242,45 @@ height:32px; } -.dialog .mainContainer button:hover{ +:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton))):hover{ cursor:pointer; filter:var(--hover-filter); } -.dialog .mainContainer button.secondaryButton{ +.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton))){ color:var(--button-secondary-fg-color); background-color:var(--button-secondary-bg-color); border-color:var(--button-secondary-border-color); } -.dialog .mainContainer button.secondaryButton:hover{ +.secondaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton))):hover{ color:var(--button-secondary-hover-fg-color); background-color:var(--button-secondary-hover-bg-color); border-color:var(--button-secondary-hover-border-color); } -.dialog .mainContainer button.primaryButton{ - color:var(--button-primary-hover-fg-color); - background-color:var(--button-primary-hover-bg-color); - border-color:var(--button-primary-hover-border-color); +.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton))){ + color:var(--button-primary-fg-color); + background-color:var(--button-primary-bg-color); + border-color:var(--button-primary-border-color); opacity:1; } -.dialog .mainContainer button.primaryButton:hover{ +.primaryButton:is(:is(.dialog .mainContainer) button:not(:is(.toggle-button,.closeButton))):hover{ color:var(--button-primary-hover-fg-color); background-color:var(--button-primary-hover-bg-color); border-color:var(--button-primary-hover-border-color); } -.dialog .mainContainer textarea{ +:is(.dialog .mainContainer) a{ + color:var(--link-fg-color); + } + +:is(:is(.dialog .mainContainer) a):hover{ + color:var(--link-hover-fg-color); + } + +:is(.dialog .mainContainer) textarea{ font:inherit; padding:8px; resize:none; @@ -239,16 +292,185 @@ color:var(--textarea-fg-color); } -.dialog .mainContainer textarea:focus{ +:is(:is(.dialog .mainContainer) textarea):focus{ outline-offset:0; border-color:transparent; } -.dialog .mainContainer textarea:disabled{ +:is(:is(.dialog .mainContainer) textarea):disabled{ pointer-events:none; opacity:0.4; } +:is(.dialog .mainContainer) .messageBar{ + --message-bar-warning-icon:url(images/messageBar_warning.svg); + --closing-button-icon:url(images/messageBar_closingButton.svg); + + --message-bar-bg-color:#ffebcd; + --message-bar-fg-color:#15141a; + --message-bar-border-color:rgb(0 0 0 / 0.08); + --message-bar-icon-color:#cd411e; + --message-bar-close-button-border-radius:4px; + --message-bar-close-button-border:none; + --message-bar-close-button-color:var(--text-primary-color); + --message-bar-close-button-hover-bg-color:rgb(21 20 26 / 0.14); + --message-bar-close-button-active-bg-color:rgb(21 20 26 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(21 20 26 / 0.07); + --message-bar-close-button-color-hover:var(--text-primary-color); + } + +@media (prefers-color-scheme: dark){ + +:where(html:not(.is-light)) :is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:#5a3100; + --message-bar-fg-color:#fbfbfe; + --message-bar-border-color:rgb(255 255 255 / 0.08); + --message-bar-icon-color:#e49c49; + --message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14); + --message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07); + } + } + +:where(html.is-dark) :is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:#5a3100; + --message-bar-fg-color:#fbfbfe; + --message-bar-border-color:rgb(255 255 255 / 0.08); + --message-bar-icon-color:#e49c49; + --message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14); + --message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21); + --message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07); + } + +@media screen and (forced-colors: active){ + +:is(.dialog .mainContainer) .messageBar{ + --message-bar-bg-color:HighlightText; + --message-bar-fg-color:CanvasText; + --message-bar-border-color:CanvasText; + --message-bar-icon-color:CanvasText; + --message-bar-close-button-color:ButtonText; + --message-bar-close-button-border:1px solid ButtonText; + --message-bar-close-button-hover-bg-color:ButtonText; + --message-bar-close-button-active-bg-color:ButtonText; + --message-bar-close-button-focus-bg-color:ButtonText; + --message-bar-close-button-color-hover:HighlightText; + } + } + +:is(.dialog .mainContainer) .messageBar{ + + display:flex; + position:relative; + padding:12px 8px 12px 0; + flex-direction:column; + justify-content:center; + align-items:flex-start; + gap:8px; + align-self:stretch; + + border-radius:4px; + border:1px solid var(--message-bar-border-color); + background:var(--message-bar-bg-color); + color:var(--message-bar-fg-color); + } + +:is(:is(.dialog .mainContainer) .messageBar) > div{ + display:flex; + padding-inline-start:16px; + align-items:flex-start; + gap:8px; + align-self:stretch; + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) > div)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--message-bar-warning-icon); + mask-image:var(--message-bar-warning-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--message-bar-icon-color); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + flex:1 0 0; + } + +:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .title{ + font-size:13px; + font-weight:590; + } + +:is(:is(:is(:is(.dialog .mainContainer) .messageBar) > div) > div) .description{ + font-size:13px; + } + +:is(:is(.dialog .mainContainer) .messageBar) .closeButton{ + position:absolute; + width:32px; + height:32px; + inset-inline-end:8px; + inset-block-start:8px; + background:none; + border-radius:var(--message-bar-close-button-border-radius); + border:var(--message-bar-close-button-border); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--closing-button-icon); + mask-image:var(--closing-button-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--message-bar-close-button-color); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton):is(:hover,:active,:focus)::before{ + background-color:var(--message-bar-close-button-color-hover); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton):hover{ + background-color:var(--message-bar-close-button-hover-bg-color); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton):active{ + background-color:var(--message-bar-close-button-active-bg-color); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton):focus{ + background-color:var(--message-bar-close-button-focus-bg-color); + } + +:is(:is(:is(.dialog .mainContainer) .messageBar) .closeButton) > span{ + display:inline-block; + width:0; + height:0; + overflow:hidden; + } + +:is(.dialog .mainContainer) .toggler{ + display:flex; + align-items:center; + gap:8px; + align-self:stretch; + } + +:is(:is(.dialog .mainContainer) .toggler) > .togglerLabel{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + } + .textLayer{ position:absolute; text-align:initial; @@ -269,7 +491,7 @@ touch-action:none; } -.textLayer :is(span, br){ +.textLayer :is(span,br){ color:transparent; position:absolute; white-space:pre; @@ -277,8 +499,7 @@ transform-origin:0% 0%; } -.textLayer > :not(.markedContent), - .textLayer .markedContent span:not(.markedContent){ +.textLayer > :not(.markedContent),.textLayer .markedContent span:not(.markedContent){ z-index:1; } @@ -292,13 +513,6 @@ --highlight-selected-bg-color:rgb(0 100 0 / 0.25); --highlight-backdrop-filter:none; --highlight-selected-backdrop-filter:none; - - margin:-1px; - padding:1px; - background-color:var(--highlight-bg-color); - -webkit-backdrop-filter:var(--highlight-backdrop-filter); - backdrop-filter:var(--highlight-backdrop-filter); - border-radius:4px; } @media screen and (forced-colors: active){ @@ -313,23 +527,33 @@ } } -.textLayer .highlight.appended{ +.textLayer .highlight{ + + margin:-1px; + padding:1px; + background-color:var(--highlight-bg-color); + -webkit-backdrop-filter:var(--highlight-backdrop-filter); + backdrop-filter:var(--highlight-backdrop-filter); + border-radius:4px; + } + +.appended:is(.textLayer .highlight){ position:initial; } -.textLayer .highlight.begin{ +.begin:is(.textLayer .highlight){ border-radius:4px 0 0 4px; } -.textLayer .highlight.end{ +.end:is(.textLayer .highlight){ border-radius:0 4px 4px 0; } -.textLayer .highlight.middle{ +.middle:is(.textLayer .highlight){ border-radius:0; } -.textLayer .highlight.selected{ +.selected:is(.textLayer .highlight){ background-color:var(--highlight-selected-bg-color); -webkit-backdrop-filter:var(--highlight-selected-backdrop-filter); backdrop-filter:var(--highlight-selected-backdrop-filter); @@ -364,9 +588,9 @@ user-select:none; } -.textLayer .endOfContent.active{ - top:0; - } +.textLayer.selecting .endOfContent{ + top:0; + } .annotationLayer{ --annotation-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,"); @@ -376,12 +600,6 @@ --input-disabled-border-color:transparent; --input-hover-border-color:black; --link-outline:none; - - position:absolute; - top:0; - left:0; - pointer-events:none; - transform-origin:0 0; } @media screen and (forced-colors: active){ @@ -394,7 +612,7 @@ --link-outline:1.5px solid LinkText; } - .annotationLayer .textWidgetAnnotation :is(input, textarea):required, .annotationLayer .choiceWidgetAnnotation select:required, .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required{ + .annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{ outline:1.5px solid selectedItem; } @@ -402,12 +620,12 @@ outline:var(--link-outline); } - .annotationLayer .linkAnnotation:hover{ + :is(.annotationLayer .linkAnnotation):hover{ -webkit-backdrop-filter:var(--hcm-highlight-filter); backdrop-filter:var(--hcm-highlight-filter); } - .annotationLayer .linkAnnotation > a:hover{ + :is(.annotationLayer .linkAnnotation) > a:hover{ opacity:0 !important; background:none !important; box-shadow:none; @@ -436,6 +654,15 @@ } } +.annotationLayer{ + + position:absolute; + top:0; + left:0; + pointer-events:none; + transform-origin:0 0; +} + .annotationLayer[data-main-rotation="90"] .norotate{ transform:rotate(270deg) translateX(-100%); } @@ -448,8 +675,7 @@ transform:rotate(90deg) translateY(-100%); } -.annotationLayer.disabled section, - .annotationLayer.disabled .popup{ +.annotationLayer.disabled section,.annotationLayer.disabled .popup{ pointer-events:none; } @@ -460,7 +686,7 @@ pointer-events:none; } -.annotationLayer .annotationContent.freetext{ +.freetext:is(.annotationLayer .annotationContent){ background:transparent; border:none; inset:0; @@ -481,11 +707,15 @@ transform-origin:0 0; } -.annotationLayer section:has(div.annotationContent) canvas.annotationContent{ +:is(.annotationLayer section):has(div.annotationContent) canvas.annotationContent{ display:none; } -.annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a{ +.textLayer.selecting ~ .annotationLayer section{ + pointer-events:none; + } + +.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton) > a{ position:absolute; font-size:1em; top:0; @@ -494,8 +724,7 @@ height:100%; } -.annotationLayer :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) - > a:hover{ +.annotationLayer :is(.linkAnnotation,.buttonWidgetAnnotation.pushButton):not(.hasBorder) > a:hover{ opacity:0.2; background-color:rgb(255 255 0); box-shadow:0 2px 10px rgb(255 255 0); @@ -518,7 +747,7 @@ left:0; } -.annotationLayer .textWidgetAnnotation :is(input, textarea), .annotationLayer .choiceWidgetAnnotation select, .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input{ +.annotationLayer .textWidgetAnnotation :is(input,textarea),.annotationLayer .choiceWidgetAnnotation select,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{ background-image:var(--annotation-unfocused-field-background); border:2px solid var(--input-unfocused-border-color); box-sizing:border-box; @@ -529,7 +758,7 @@ width:100%; } -.annotationLayer .textWidgetAnnotation :is(input, textarea):required, .annotationLayer .choiceWidgetAnnotation select:required, .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required{ +.annotationLayer .textWidgetAnnotation :is(input,textarea):required,.annotationLayer .choiceWidgetAnnotation select:required,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:required{ outline:1.5px solid red; } @@ -545,28 +774,28 @@ resize:none; } -.annotationLayer .textWidgetAnnotation [disabled]:is(input, textarea), .annotationLayer .choiceWidgetAnnotation select[disabled], .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input[disabled]{ +.annotationLayer .textWidgetAnnotation [disabled]:is(input,textarea),.annotationLayer .choiceWidgetAnnotation select[disabled],.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input[disabled]{ background:none; border:2px solid var(--input-disabled-border-color); cursor:not-allowed; } -.annotationLayer .textWidgetAnnotation :is(input, textarea):hover, .annotationLayer .choiceWidgetAnnotation select:hover, .annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover{ +.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input:hover{ border:2px solid var(--input-hover-border-color); } -.annotationLayer .textWidgetAnnotation :is(input, textarea):hover, .annotationLayer .choiceWidgetAnnotation select:hover, .annotationLayer .buttonWidgetAnnotation.checkBox input:hover{ +.annotationLayer .textWidgetAnnotation :is(input,textarea):hover,.annotationLayer .choiceWidgetAnnotation select:hover,.annotationLayer .buttonWidgetAnnotation.checkBox input:hover{ border-radius:2px; } -.annotationLayer .textWidgetAnnotation :is(input, textarea):focus, .annotationLayer .choiceWidgetAnnotation select:focus{ +.annotationLayer .textWidgetAnnotation :is(input,textarea):focus,.annotationLayer .choiceWidgetAnnotation select:focus{ background:none; border:2px solid var(--input-focus-border-color); border-radius:2px; outline:var(--input-focus-outline); } -.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) :focus{ +.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) :focus{ background-image:none; background-color:transparent; } @@ -582,17 +811,14 @@ outline:var(--input-focus-outline); } -.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before, - .annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after, - .annotationLayer .buttonWidgetAnnotation.radioButton input:checked::before{ +.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{ +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::before,.annotationLayer .buttonWidgetAnnotation.checkBox input:checked::after{ height:80%; left:45%; width:1px; @@ -624,7 +850,7 @@ width:103%; } -.annotationLayer .buttonWidgetAnnotation:is(.checkBox, .radioButton) input{ +.annotationLayer .buttonWidgetAnnotation:is(.checkBox,.radioButton) input{ -webkit-appearance:none; -moz-appearance:none; appearance:none; @@ -716,7 +942,7 @@ pointer-events:none; } -.annotationLayer .annotationTextContent span{ +:is(.annotationLayer .annotationTextContent) span{ width:100%; display:inline-block; } @@ -1053,76 +1279,76 @@ transform:none; } -.canvasWrapper svg[data-main-rotation="90"] mask, - .canvasWrapper svg[data-main-rotation="90"] use:not(.clip, .mask){ +[data-main-rotation="90"]:is(.canvasWrapper svg) mask,[data-main-rotation="90"]:is(.canvasWrapper svg) use:not(.clip,.mask){ transform:matrix(0, 1, -1, 0, 1, 0); } -.canvasWrapper svg[data-main-rotation="180"] mask, - .canvasWrapper svg[data-main-rotation="180"] use:not(.clip, .mask){ +[data-main-rotation="180"]:is(.canvasWrapper svg) mask,[data-main-rotation="180"]:is(.canvasWrapper svg) use:not(.clip,.mask){ transform:matrix(-1, 0, 0, -1, 1, 1); } -.canvasWrapper svg[data-main-rotation="270"] mask, - .canvasWrapper svg[data-main-rotation="270"] use:not(.clip, .mask){ +[data-main-rotation="270"]:is(.canvasWrapper svg) mask,[data-main-rotation="270"]:is(.canvasWrapper svg) use:not(.clip,.mask){ transform:matrix(0, -1, 1, 0, 0, 1); } -.canvasWrapper svg.highlight{ +.highlight:is(.canvasWrapper svg){ --blend-mode:multiply; - - position:absolute; - mix-blend-mode:var(--blend-mode); } @media screen and (forced-colors: active){ -.canvasWrapper svg.highlight{ +.highlight:is(.canvasWrapper svg){ --blend-mode:difference; } } -.canvasWrapper svg.highlight:not(.free){ +.highlight:is(.canvasWrapper svg){ + + position:absolute; + mix-blend-mode:var(--blend-mode); + } + +.highlight:is(.canvasWrapper svg):not(.free){ fill-rule:evenodd; } -.canvasWrapper svg.highlightOutline{ +.highlightOutline:is(.canvasWrapper svg){ position:absolute; mix-blend-mode:normal; fill-rule:evenodd; fill:none; } -.canvasWrapper svg.highlightOutline.hovered:not(.free):not(.selected){ +.highlightOutline.hovered:is(.canvasWrapper svg):not(.free):not(.selected){ stroke:var(--hover-outline-color); stroke-width:var(--outline-width); } -.canvasWrapper svg.highlightOutline.selected:not(.free) .mainOutline{ +.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .mainOutline{ stroke:var(--outline-around-color); stroke-width:calc( var(--outline-width) + 2 * var(--outline-around-width) ); } -.canvasWrapper svg.highlightOutline.selected:not(.free) .secondaryOutline{ +.highlightOutline.selected:is(.canvasWrapper svg):not(.free) .secondaryOutline{ stroke:var(--outline-color); stroke-width:var(--outline-width); } -.canvasWrapper svg.highlightOutline.free.hovered:not(.selected){ +.highlightOutline.free.hovered:is(.canvasWrapper svg):not(.selected){ stroke:var(--hover-outline-color); stroke-width:calc(2 * var(--outline-width)); } -.canvasWrapper svg.highlightOutline.free.selected .mainOutline{ +.highlightOutline.free.selected:is(.canvasWrapper svg) .mainOutline{ stroke:var(--outline-around-color); stroke-width:calc( 2 * (var(--outline-width) + var(--outline-around-width)) ); } -.canvasWrapper svg.highlightOutline.free.selected .secondaryOutline{ +.highlightOutline.free.selected:is(.canvasWrapper svg) .secondaryOutline{ stroke:var(--outline-color); stroke-width:calc(2 * var(--outline-width)); } @@ -1140,48 +1366,6 @@ --size-item-small:16px; --size-item-large:32px; --color-canvas:white; - - --toggle-background-color:var(--button-background-color); - --toggle-background-color-hover:var(--button-background-color-hover); - --toggle-background-color-active:var(--button-background-color-active); - --toggle-background-color-pressed:var(--color-accent-primary); - --toggle-background-color-pressed-hover:var(--color-accent-primary-hover); - --toggle-background-color-pressed-active:var(--color-accent-primary-active); - --toggle-border-color:var(--border-interactive-color); - --toggle-border-color-hover:var(--toggle-border-color); - --toggle-border-color-active:var(--toggle-border-color); - --toggle-border-radius:var(--border-radius-circle); - --toggle-border-width:var(--border-width); - --toggle-height:var(--size-item-small); - --toggle-width:var(--size-item-large); - --toggle-dot-background-color:var(--toggle-border-color); - --toggle-dot-background-color-hover:var(--toggle-dot-background-color); - --toggle-dot-background-color-active:var(--toggle-dot-background-color); - --toggle-dot-background-color-on-pressed:var(--color-canvas); - --toggle-dot-margin:1px; - --toggle-dot-height:calc( - var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * - var(--toggle-border-width) - ); - --toggle-dot-width:var(--toggle-dot-height); - --toggle-dot-transform-x:calc( - var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width) - ); - - -webkit-appearance:none; - - -moz-appearance:none; - - appearance:none; - padding:0; - margin:0; - border:var(--toggle-border-width) solid var(--toggle-border-color); - height:var(--toggle-height); - width:var(--toggle-width); - border-radius:var(--toggle-border-radius); - background:var(--toggle-background-color); - box-sizing:border-box; - flex-shrink:0; } @media (prefers-color-scheme: dark){ @@ -1240,24 +1424,69 @@ } } -.toggle-button:focus-visible{ - outline:var(--focus-outline); - outline-offset:var(--focus-outline-offset); - } +.toggle-button{ -.toggle-button:enabled:hover{ - background:var(--toggle-background-color-hover); - border-color:var(--toggle-border-color); - } + --toggle-background-color:var(--button-background-color); + --toggle-background-color-hover:var(--button-background-color-hover); + --toggle-background-color-active:var(--button-background-color-active); + --toggle-background-color-pressed:var(--color-accent-primary); + --toggle-background-color-pressed-hover:var(--color-accent-primary-hover); + --toggle-background-color-pressed-active:var(--color-accent-primary-active); + --toggle-border-color:var(--border-interactive-color); + --toggle-border-color-hover:var(--toggle-border-color); + --toggle-border-color-active:var(--toggle-border-color); + --toggle-border-radius:var(--border-radius-circle); + --toggle-border-width:var(--border-width); + --toggle-height:var(--size-item-small); + --toggle-width:var(--size-item-large); + --toggle-dot-background-color:var(--toggle-border-color); + --toggle-dot-background-color-hover:var(--toggle-dot-background-color); + --toggle-dot-background-color-active:var(--toggle-dot-background-color); + --toggle-dot-background-color-on-pressed:var(--color-canvas); + --toggle-dot-margin:1px; + --toggle-dot-height:calc( + var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * + var(--toggle-border-width) + ); + --toggle-dot-width:var(--toggle-dot-height); + --toggle-dot-transform-x:calc( + var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width) + ); -.toggle-button:enabled:active{ - background:var(--toggle-background-color-active); - border-color:var(--toggle-border-color); - } + -webkit-appearance:none; -.toggle-button[aria-pressed="true"]{ - background:var(--toggle-background-color-pressed); - border-color:transparent; + -moz-appearance:none; + + appearance:none; + padding:0; + margin:0; + border:var(--toggle-border-width) solid var(--toggle-border-color); + height:var(--toggle-height); + width:var(--toggle-width); + border-radius:var(--toggle-border-radius); + background:var(--toggle-background-color); + box-sizing:border-box; + flex-shrink:0; +} + +.toggle-button:focus-visible{ + outline:var(--focus-outline); + outline-offset:var(--focus-outline-offset); + } + +.toggle-button:enabled:hover{ + background:var(--toggle-background-color-hover); + border-color:var(--toggle-border-color); + } + +.toggle-button:enabled:active{ + background:var(--toggle-background-color-active); + border-color:var(--toggle-border-color); + } + +.toggle-button[aria-pressed="true"]{ + background:var(--toggle-background-color-pressed); + border-color:transparent; } .toggle-button[aria-pressed="true"]:enabled:hover{ @@ -1286,8 +1515,7 @@ background-color:var(--toggle-dot-background-color-on-pressed); } -.toggle-button[aria-pressed="true"]:enabled:hover::before, - .toggle-button[aria-pressed="true"]:enabled:active::before{ +.toggle-button[aria-pressed="true"]:enabled:hover::before,.toggle-button[aria-pressed="true"]:enabled:active::before{ background-color:var(--toggle-dot-background-color-on-pressed); } @@ -1315,8 +1543,7 @@ position:relative; } - .toggle-button[aria-pressed="true"]:enabled:hover, - .toggle-button[aria-pressed="true"]:enabled:hover:active{ + .toggle-button[aria-pressed="true"]:enabled:hover,.toggle-button[aria-pressed="true"]:enabled:hover:active{ border-color:var(--toggle-border-color-hover); } @@ -1325,8 +1552,7 @@ border-color:var(--toggle-dot-background-color-hover); } - .toggle-button:hover::before, - .toggle-button:active::before{ + .toggle-button:hover::before,.toggle-button:active::before{ background-color:var(--toggle-dot-background-color-hover); } } @@ -1384,6 +1610,8 @@ --editorInk-editing-cursor:url(images/cursor-editorInk.svg) 0 16, pointer; --editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text; --editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer; + + --new-alt-text-warning-image:url(images/altText_warning.svg); } .visuallyHidden{ position:absolute; @@ -1411,6 +1639,10 @@ cursor:var(--editorFreeHighlight-editing-cursor); } +#viewerContainer.pdfPresentationMode:fullscreen .noAltTextBadge{ + display:none !important; + } + @media (min-resolution: 1.1dppx){ :root{ --editorFreeText-editing-cursor:url(images/cursor-editorFreeText.svg) 0 16, text; @@ -1448,6 +1680,14 @@ cursor:auto; } +.annotationEditorLayer .selectedEditor{ + z-index:100000 !important; + } + +.annotationEditorLayer.drawing *{ + pointer-events:none !important; + } + .annotationEditorLayer.waiting{ content:""; cursor:wait; @@ -1480,20 +1720,20 @@ border:var(--unfocus-outline); } -.annotationEditorLayer .draggable.selectedEditor:is(.freeTextEditor, .inkEditor, .stampEditor){ +.draggable.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)){ cursor:move; } -.annotationEditorLayer .moving:is(.freeTextEditor, .inkEditor, .stampEditor){ +.moving:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)){ touch-action:none; } -.annotationEditorLayer .selectedEditor:is(.freeTextEditor, .inkEditor, .stampEditor){ +.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)){ border:var(--focus-outline); outline:var(--focus-outline-around); } -.annotationEditorLayer .selectedEditor:is(.freeTextEditor, .inkEditor, .stampEditor)::before{ +.selectedEditor:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor))::before{ content:""; position:absolute; inset:0; @@ -1501,20 +1741,19 @@ pointer-events:none; } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor):hover:not(.selectedEditor){ +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)):hover:not(.selectedEditor){ border:var(--hover-outline); outline:var(--hover-outline-around); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor):hover:not(.selectedEditor)::before{ +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)):hover:not(.selectedEditor)::before{ content:""; position:absolute; inset:0; border:var(--focus-outline-around); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar{ --editor-toolbar-delete-image:url(images/editor-toolbar-delete.svg); --editor-toolbar-bg-color:#f0f0f4; --editor-toolbar-highlight-image:url(images/toolbarButton-editorHighlight.svg); @@ -1529,52 +1768,36 @@ --editor-toolbar-vert-offset:6px; --editor-toolbar-height:28px; --editor-toolbar-padding:2px; - - display:flex; - width:-moz-fit-content; - width:fit-content; - height:var(--editor-toolbar-height); - flex-direction:column; - justify-content:center; - align-items:center; - cursor:default; - pointer-events:auto; - box-sizing:content-box; - padding:var(--editor-toolbar-padding); - - position:absolute; - inset-inline-end:0; - inset-block-start:calc(100% + var(--editor-toolbar-vert-offset)); - - border-radius:6px; - background-color:var(--editor-toolbar-bg-color); - border:1px solid var(--editor-toolbar-border-color); - box-shadow:var(--editor-toolbar-shadow); + --alt-text-done-color:#2ac3a2; + --alt-text-warning-color:#0090ed; + --alt-text-hover-done-color:var(--alt-text-done-color); + --alt-text-hover-warning-color:var(--alt-text-warning-color); } @media (prefers-color-scheme: dark){ -:where(html:not(.is-light)) :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +:where(html:not(.is-light)) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar{ --editor-toolbar-bg-color:#2b2a33; --editor-toolbar-fg-color:#fbfbfe; --editor-toolbar-hover-bg-color:#52525e; --editor-toolbar-focus-outline-color:#0df; + --alt-text-done-color:#54ffbd; + --alt-text-warning-color:#80ebff; } } -:where(html.is-dark) :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +:where(html.is-dark) :is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar{ --editor-toolbar-bg-color:#2b2a33; --editor-toolbar-fg-color:#fbfbfe; --editor-toolbar-hover-bg-color:#52525e; --editor-toolbar-focus-outline-color:#0df; + --alt-text-done-color:#54ffbd; + --alt-text-warning-color:#80ebff; } @media screen and (forced-colors: active){ -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar{ --editor-toolbar-bg-color:ButtonFace; --editor-toolbar-fg-color:ButtonText; --editor-toolbar-border-color:ButtonText; @@ -1584,31 +1807,54 @@ --editor-toolbar-hover-outline:2px solid var(--editor-toolbar-hover-border-color); --editor-toolbar-focus-outline-color:ButtonBorder; --editor-toolbar-shadow:none; + --alt-text-done-color:var(--editor-toolbar-fg-color); + --alt-text-warning-color:var(--editor-toolbar-fg-color); + --alt-text-hover-done-color:var(--editor-toolbar-hover-fg-color); + --alt-text-hover-warning-color:var(--editor-toolbar-hover-fg-color); } } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar.hidden{ +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar{ + + display:flex; + width:-moz-fit-content; + width:fit-content; + height:var(--editor-toolbar-height); + flex-direction:column; + justify-content:center; + align-items:center; + cursor:default; + pointer-events:auto; + box-sizing:content-box; + padding:var(--editor-toolbar-padding); + + position:absolute; + inset-inline-end:0; + inset-block-start:calc(100% + var(--editor-toolbar-vert-offset)); + + border-radius:6px; + background-color:var(--editor-toolbar-bg-color); + border:1px solid var(--editor-toolbar-border-color); + box-shadow:var(--editor-toolbar-shadow); + } + +.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar){ display:none; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar:has(:focus-visible){ +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar):has(:focus-visible){ border-color:transparent; } -[dir="ltr"] :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +[dir="ltr"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar){ transform-origin:100% 0; } -[dir="rtl"] :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar{ +[dir="rtl"] :is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar){ transform-origin:0 0; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons{ +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons{ display:flex; justify-content:center; align-items:center; @@ -1616,8 +1862,7 @@ height:100%; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .divider{ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .divider{ width:1px; height:calc( 2 * var(--editor-toolbar-padding) + var(--editor-toolbar-height) @@ -1627,13 +1872,11 @@ margin-inline:2px; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .highlightButton{ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .highlightButton{ width:var(--editor-toolbar-height); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .highlightButton::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .highlightButton)::before{ content:""; -webkit-mask-image:var(--editor-toolbar-highlight-image); mask-image:var(--editor-toolbar-highlight-image); @@ -1647,18 +1890,15 @@ height:100%; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .highlightButton:hover::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .highlightButton):hover::before{ background-color:var(--editor-toolbar-hover-fg-color); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .delete{ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .delete{ width:var(--editor-toolbar-height); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .delete::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .delete)::before{ content:""; -webkit-mask-image:var(--editor-toolbar-delete-image); mask-image:var(--editor-toolbar-delete-image); @@ -1672,25 +1912,21 @@ height:100%; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .delete:hover::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .delete):hover::before{ background-color:var(--editor-toolbar-hover-fg-color); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons > *{ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) > *{ height:var(--editor-toolbar-height); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons > :not(.divider){ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) > :not(.divider){ border:none; background-color:transparent; cursor:pointer; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons > :not(.divider):hover{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover{ border-radius:2px; background-color:var(--editor-toolbar-hover-bg-color); color:var(--editor-toolbar-hover-fg-color); @@ -1698,19 +1934,16 @@ outline-offset:1px; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons > :not(.divider):hover:active{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):hover:active{ outline:none; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons > :not(.divider):focus-visible{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) > :not(.divider)):focus-visible{ border-radius:2px; outline:2px solid var(--editor-toolbar-focus-outline-color); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText{ +:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText{ --alt-text-add-image:url(images/altText_add.svg); --alt-text-done-image:url(images/altText_done.svg); @@ -1727,13 +1960,11 @@ color:var(--editor-toolbar-fg-color); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText:disabled{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText):disabled{ pointer-events:none; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ content:""; -webkit-mask-image:var(--alt-text-add-image); mask-image:var(--alt-text-add-image); @@ -1748,64 +1979,60 @@ margin-inline-end:4px; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText:hover::before{ +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ background-color:var(--editor-toolbar-hover-fg-color); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText.done::before{ +.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ -webkit-mask-image:var(--alt-text-done-image); mask-image:var(--alt-text-done-image); } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText .tooltip{ +.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-warning-image); + mask-image:var(--new-alt-text-warning-image); + background-color:var(--alt-text-warning-color); + -webkit-mask-size:cover; + mask-size:cover; + } + +.new:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ + background-color:var(--alt-text-hover-warning-color); + } + +.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText)::before{ + -webkit-mask-image:var(--alt-text-done-image); + mask-image:var(--alt-text-done-image); + background-color:var(--alt-text-done-color); + } + +.new.done:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText):hover::before{ + background-color:var(--alt-text-hover-done-color); + } + +:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip{ display:none; } -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText .tooltip.show{ +.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ --alt-text-tooltip-bg:#f0f0f4; --alt-text-tooltip-fg:#15141a; --alt-text-tooltip-border:#8f8f9d; --alt-text-tooltip-shadow:0px 2px 6px 0px rgb(58 57 68 / 0.2); - - display:inline-flex; - flex-direction:column; - align-items:center; - justify-content:center; - position:absolute; - top:calc(100% + 2px); - inset-inline-start:0; - padding-block:2px 3px; - padding-inline:3px; - max-width:300px; - width:-moz-max-content; - width:max-content; - height:auto; - font-size:12px; - - border:0.5px solid var(--alt-text-tooltip-border); - background:var(--alt-text-tooltip-bg); - box-shadow:var(--alt-text-tooltip-shadow); - color:var(--alt-text-tooltip-fg); - - pointer-events:none; } @media (prefers-color-scheme: dark){ -:where(html:not(.is-light)) :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText .tooltip.show{ +:where(html:not(.is-light)) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ --alt-text-tooltip-bg:#1c1b22; --alt-text-tooltip-fg:#fbfbfe; --alt-text-tooltip-shadow:0px 2px 6px 0px #15141a; } } -:where(html.is-dark) :is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText .tooltip.show{ +:where(html.is-dark) .show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ --alt-text-tooltip-bg:#1c1b22; --alt-text-tooltip-fg:#fbfbfe; --alt-text-tooltip-shadow:0px 2px 6px 0px #15141a; @@ -1813,8 +2040,7 @@ @media screen and (forced-colors: active){ -:is(.annotationEditorLayer - :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),.textLayer) .editToolbar .buttons .altText .tooltip.show{ +.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ --alt-text-tooltip-bg:Canvas; --alt-text-tooltip-fg:CanvasText; --alt-text-tooltip-border:CanvasText; @@ -1822,6 +2048,31 @@ } } +.show:is(:is(:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor,.highlightEditor),.textLayer) .editToolbar) .buttons) .altText) .tooltip){ + + display:inline-flex; + flex-direction:column; + align-items:center; + justify-content:center; + position:absolute; + top:calc(100% + 2px); + inset-inline-start:0; + padding-block:2px 3px; + padding-inline:3px; + max-width:300px; + width:-moz-max-content; + width:max-content; + height:auto; + font-size:12px; + + border:0.5px solid var(--alt-text-tooltip-border); + background:var(--alt-text-tooltip-bg); + box-shadow:var(--alt-text-tooltip-shadow); + color:var(--alt-text-tooltip-fg); + + pointer-events:none; + } + .annotationEditorLayer .freeTextEditor{ padding:calc(var(--freetext-padding) * var(--scale-factor)); width:auto; @@ -1889,23 +2140,86 @@ height:auto; } -.annotationEditorLayer .stampEditor canvas{ +:is(.annotationEditorLayer .stampEditor) canvas{ position:absolute; width:100%; height:100%; margin:0; + top:0; + left:0; + } + +:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#f0f0f4; + --no-alt-text-badge-bg-color:#cfcfd8; + --no-alt-text-badge-fg-color:#5b5b66; + } + +@media (prefers-color-scheme: dark){ + +:where(html:not(.is-light)) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#52525e; + --no-alt-text-badge-bg-color:#fbfbfe; + --no-alt-text-badge-fg-color:#15141a; + } + } + +:where(html.is-dark) :is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:#52525e; + --no-alt-text-badge-bg-color:#fbfbfe; + --no-alt-text-badge-fg-color:#15141a; + } + +@media screen and (forced-colors: active){ + +:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + --no-alt-text-badge-border-color:ButtonText; + --no-alt-text-badge-bg-color:ButtonFace; + --no-alt-text-badge-fg-color:ButtonText; + } + } + +:is(.annotationEditorLayer .stampEditor) .noAltTextBadge{ + + position:absolute; + inset-inline-end:5px; + inset-block-end:5px; + display:inline-flex; + width:32px; + height:32px; + padding:3px; + justify-content:center; + align-items:center; + pointer-events:none; + z-index:1; + + border-radius:2px; + border:1px solid var(--no-alt-text-badge-border-color); + background:var(--no-alt-text-badge-bg-color); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers{ +:is(:is(.annotationEditorLayer .stampEditor) .noAltTextBadge)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-warning-image); + mask-image:var(--new-alt-text-warning-image); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--no-alt-text-badge-fg-color); + } + +:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers{ position:absolute; inset:0; } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers.hidden{ +.hidden:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers){ display:none; } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer{ +:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer{ width:var(--resizer-size); height:var(--resizer-size); background:content-box var(--resizer-bg-color); @@ -1914,270 +2228,108 @@ position:absolute; } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.topLeft{ +.topLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ top:var(--resizer-shift); left:var(--resizer-shift); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.topMiddle{ +.topMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ top:var(--resizer-shift); left:calc(50% + var(--resizer-shift)); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.topRight{ +.topRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ top:var(--resizer-shift); right:var(--resizer-shift); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.middleRight{ +.middleRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ top:calc(50% + var(--resizer-shift)); right:var(--resizer-shift); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.bottomRight{ +.bottomRight:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ bottom:var(--resizer-shift); right:var(--resizer-shift); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.bottomMiddle{ +.bottomMiddle:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ bottom:var(--resizer-shift); left:calc(50% + var(--resizer-shift)); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.bottomLeft{ +.bottomLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ bottom:var(--resizer-shift); left:var(--resizer-shift); } -.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) > .resizers > .resizer.middleLeft{ +.middleLeft:is(:is(:is(.annotationEditorLayer :is(.freeTextEditor,.inkEditor,.stampEditor)) > .resizers) > .resizer){ top:calc(50% + var(--resizer-shift)); left:var(--resizer-shift); } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomRight{ +.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ cursor:nwse-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomMiddle{ +.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ cursor:ns-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomLeft{ +.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ cursor:nesw-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleLeft{ +.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="180"],[data-editor-rotation="0"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="90"],[data-editor-rotation="270"])) > .resizers > .resizer){ cursor:ew-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topLeft, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomRight{ +.topLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ cursor:nesw-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topMiddle, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomMiddle, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomMiddle{ +.topMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomMiddle:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ cursor:ew-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.topRight, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.bottomLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.bottomLeft{ +.topRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.bottomLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ cursor:nwse-resize; } -.annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleRight, - .annotationEditorLayer[data-main-rotation="0"] - :is([data-editor-rotation="90"], [data-editor-rotation="270"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="90"] - :is([data-editor-rotation="0"], [data-editor-rotation="180"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="180"] - :is([data-editor-rotation="270"], [data-editor-rotation="90"]) > .resizers > .resizer.middleLeft, - .annotationEditorLayer[data-main-rotation="270"] - :is([data-editor-rotation="180"], [data-editor-rotation="0"]) > .resizers > .resizer.middleLeft{ +.middleRight:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer),.middleLeft:is(:is(.annotationEditorLayer[data-main-rotation="0"] :is([data-editor-rotation="90"],[data-editor-rotation="270"]),.annotationEditorLayer[data-main-rotation="90"] :is([data-editor-rotation="0"],[data-editor-rotation="180"]),.annotationEditorLayer[data-main-rotation="180"] :is([data-editor-rotation="270"],[data-editor-rotation="90"]),.annotationEditorLayer[data-main-rotation="270"] :is([data-editor-rotation="180"],[data-editor-rotation="0"])) > .resizers > .resizer){ cursor:ns-resize; } -.annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="90"], - [data-main-rotation="90"] [data-editor-rotation="0"], - [data-main-rotation="180"] [data-editor-rotation="270"], - [data-main-rotation="270"] [data-editor-rotation="180"] - ) .editToolbar{ +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar{ rotate:270deg; } -[dir="ltr"] .annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="90"], - [data-main-rotation="90"] [data-editor-rotation="0"], - [data-main-rotation="180"] [data-editor-rotation="270"], - [data-main-rotation="270"] [data-editor-rotation="180"] - ) .editToolbar{ +[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){ inset-inline-end:calc(0px - var(--editor-toolbar-vert-offset)); inset-block-start:0; } -[dir="rtl"] .annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="90"], - [data-main-rotation="90"] [data-editor-rotation="0"], - [data-main-rotation="180"] [data-editor-rotation="270"], - [data-main-rotation="270"] [data-editor-rotation="180"] - ) .editToolbar{ +[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="90"],[data-main-rotation="90"] [data-editor-rotation="0"],[data-main-rotation="180"] [data-editor-rotation="270"],[data-main-rotation="270"] [data-editor-rotation="180"])) .editToolbar){ inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset)); inset-block-start:0; } -.annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="180"], - [data-main-rotation="90"] [data-editor-rotation="90"], - [data-main-rotation="180"] [data-editor-rotation="0"], - [data-main-rotation="270"] [data-editor-rotation="270"] - ) .editToolbar{ +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="180"],[data-main-rotation="90"] [data-editor-rotation="90"],[data-main-rotation="180"] [data-editor-rotation="0"],[data-main-rotation="270"] [data-editor-rotation="270"])) .editToolbar{ rotate:180deg; inset-inline-end:100%; inset-block-start:calc(0pc - var(--editor-toolbar-vert-offset)); } -.annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="270"], - [data-main-rotation="90"] [data-editor-rotation="180"], - [data-main-rotation="180"] [data-editor-rotation="90"], - [data-main-rotation="270"] [data-editor-rotation="0"] - ) .editToolbar{ +:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar{ rotate:90deg; } -[dir="ltr"] .annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="270"], - [data-main-rotation="90"] [data-editor-rotation="180"], - [data-main-rotation="180"] [data-editor-rotation="90"], - [data-main-rotation="270"] [data-editor-rotation="0"] - ) .editToolbar{ +[dir="ltr"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){ inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset)); inset-block-start:100%; } -[dir="rtl"] .annotationEditorLayer - :is( - [data-main-rotation="0"] [data-editor-rotation="270"], - [data-main-rotation="90"] [data-editor-rotation="180"], - [data-main-rotation="180"] [data-editor-rotation="90"], - [data-main-rotation="270"] [data-editor-rotation="0"] - ) .editToolbar{ +[dir="rtl"] :is(:is(.annotationEditorLayer :is([data-main-rotation="0"] [data-editor-rotation="270"],[data-main-rotation="90"] [data-editor-rotation="180"],[data-main-rotation="180"] [data-editor-rotation="90"],[data-main-rotation="270"] [data-editor-rotation="0"])) .editToolbar){ inset-inline-start:calc(0px - var(--editor-toolbar-vert-offset)); inset-block-start:0; } @@ -2201,7 +2353,7 @@ gap:16px; } -.dialog.altText #altTextContainer #overallDescription{ +:is(.dialog.altText #altTextContainer) #overallDescription{ display:flex; flex-direction:column; align-items:flex-start; @@ -2209,34 +2361,34 @@ align-self:stretch; } -.dialog.altText #altTextContainer #overallDescription span{ +:is(:is(.dialog.altText #altTextContainer) #overallDescription) span{ align-self:stretch; } -.dialog.altText #altTextContainer #overallDescription .title{ +:is(:is(.dialog.altText #altTextContainer) #overallDescription) .title{ font-size:13px; font-style:normal; font-weight:590; } -.dialog.altText #altTextContainer #addDescription{ +:is(.dialog.altText #altTextContainer) #addDescription{ display:flex; flex-direction:column; align-items:stretch; gap:8px; } -.dialog.altText #altTextContainer #addDescription .descriptionArea{ +:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea{ flex:1; padding-inline:24px 10px; } -.dialog.altText #altTextContainer #addDescription .descriptionArea textarea{ +:is(:is(:is(.dialog.altText #altTextContainer) #addDescription) .descriptionArea) textarea{ width:100%; min-height:75px; } -.dialog.altText #altTextContainer #buttons{ +:is(.dialog.altText #altTextContainer) #buttons{ display:flex; justify-content:flex-end; align-items:flex-start; @@ -2244,6 +2396,210 @@ align-self:stretch; } +.dialog.newAltText{ + --new-alt-text-ai-disclaimer-icon:url(images/altText_disclaimer.svg); + --new-alt-text-spinner-icon:url(images/altText_spinner.svg); + --preview-image-bg-color:#f0f0f4; + --preview-image-border:none; +} + +@media (prefers-color-scheme: dark){ + +:where(html:not(.is-light)) .dialog.newAltText{ + --preview-image-bg-color:#2b2a33; +} + } + +:where(html.is-dark) .dialog.newAltText{ + --preview-image-bg-color:#2b2a33; +} + +@media screen and (forced-colors: active){ + +.dialog.newAltText{ + --preview-image-bg-color:ButtonFace; + --preview-image-border:1px solid ButtonText; +} + } + +.dialog.newAltText{ + + width:80%; + max-width:570px; + min-width:300px; + padding:0; +} + +.dialog.newAltText.noAi #newAltTextDisclaimer,.dialog.newAltText.noAi #newAltTextCreateAutomatically{ + display:none !important; + } + +.dialog.newAltText.aiInstalling #newAltTextCreateAutomatically{ + display:none !important; + } + +.dialog.newAltText.aiInstalling #newAltTextDownloadModel{ + display:flex !important; + } + +.dialog.newAltText.error #newAltTextNotNow{ + display:none !important; + } + +.dialog.newAltText.error #newAltTextCancel{ + display:inline-block !important; + } + +.dialog.newAltText:not(.error) #newAltTextError{ + display:none !important; + } + +.dialog.newAltText #newAltTextContainer{ + display:flex; + width:auto; + padding:16px; + flex-direction:column; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + flex:0 1 auto; + line-height:normal; + } + +:is(.dialog.newAltText #newAltTextContainer) #mainContent{ + display:flex; + justify-content:flex-end; + align-items:flex-start; + gap:12px; + align-self:stretch; + flex:1 1 auto; + } + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionAndSettings{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:16px; + flex:1 0 0; + align-self:stretch; + } + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction{ + display:flex; + flex-direction:column; + align-items:flex-start; + gap:8px; + align-self:stretch; + flex:1 1 auto; + } + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer{ + width:100%; + height:70px; + position:relative; + } + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea{ + width:100%; + height:100%; + padding:8px; + } + +:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::-moz-placeholder{ + color:var(--text-secondary-color); + } + +:is(:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea)::placeholder{ + color:var(--text-secondary-color); + } + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{ + display:none; + position:absolute; + width:16px; + height:16px; + inset-inline-start:8px; + inset-block-start:8px; + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); + pointer-events:none; + } + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::-moz-placeholder{ + color:transparent; + } + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) textarea::placeholder{ + color:transparent; + } + +.loading:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescriptionContainer) .altTextSpinner{ + display:inline-block; + -webkit-mask-image:var(--new-alt-text-spinner-icon); + mask-image:var(--new-alt-text-spinner-icon); + } + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDescription{ + font-size:11px; + } + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer{ + display:flex; + flex-direction:row; + align-items:flex-start; + gap:4px; + font-size:11px; + } + +:is(:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #descriptionInstruction) #newAltTextDisclaimer)::before{ + content:""; + display:inline-block; + width:17px; + height:16px; + -webkit-mask-image:var(--new-alt-text-ai-disclaimer-icon); + mask-image:var(--new-alt-text-ai-disclaimer-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); + flex:1 0 auto; + } + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel{ + display:flex; + align-items:center; + gap:4px; + align-self:stretch; + } + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextDownloadModel)::before{ + content:""; + display:inline-block; + width:16px; + height:16px; + -webkit-mask-image:var(--new-alt-text-spinner-icon); + mask-image:var(--new-alt-text-spinner-icon); + -webkit-mask-size:cover; + mask-size:cover; + background-color:var(--text-secondary-color); + } + +:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview{ + width:180px; + aspect-ratio:1; + display:flex; + justify-content:center; + align-items:center; + flex:0 0 auto; + background-color:var(--preview-image-bg-color); + border:var(--preview-image-border); + } + +:is(:is(:is(.dialog.newAltText #newAltTextContainer) #mainContent) #newAltTextImagePreview) > canvas{ + max-width:100%; + max-height:100%; + } + .colorPicker{ --hover-outline-color:#0250bb; --selected-outline-color:#0060df; @@ -2284,7 +2640,7 @@ forced-color-adjust:none; } -.colorPicker button:is(:hover, .selected) > .swatch{ +.colorPicker button:is(:hover,.selected) > .swatch{ border:none; } @@ -2317,11 +2673,11 @@ transform-origin:0 0; } -.annotationEditorLayer .highlightEditor:not(.free){ +:is(.annotationEditorLayer .highlightEditor):not(.free){ transform:none; } -.annotationEditorLayer .highlightEditor .internal{ +:is(.annotationEditorLayer .highlightEditor) .internal{ position:absolute; top:0; left:0; @@ -2330,21 +2686,21 @@ pointer-events:auto; } -.annotationEditorLayer .highlightEditor.disabled .internal{ +.disabled:is(.annotationEditorLayer .highlightEditor) .internal{ pointer-events:none; } -.annotationEditorLayer .highlightEditor.selectedEditor .internal{ +.selectedEditor:is(.annotationEditorLayer .highlightEditor) .internal{ cursor:pointer; } -.annotationEditorLayer .highlightEditor .editToolbar{ +:is(.annotationEditorLayer .highlightEditor) .editToolbar{ --editor-toolbar-colorpicker-arrow-image:url(images/toolbarButton-menuArrow.svg); transform-origin:center !important; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker{ +:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker{ position:relative; width:auto; display:flex; @@ -2354,7 +2710,7 @@ padding:4px; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker::after{ +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker)::after{ content:""; -webkit-mask-image:var(--editor-toolbar-colorpicker-arrow-image); mask-image:var(--editor-toolbar-colorpicker-arrow-image); @@ -2368,19 +2724,19 @@ height:12px; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker:hover::after{ +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):hover::after{ background-color:var(--editor-toolbar-hover-fg-color); } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker:has(.dropdown:not(.hidden)){ +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden)){ background-color:var(--editor-toolbar-hover-bg-color); } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker:has(.dropdown:not(.hidden))::after{ +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker):has(.dropdown:not(.hidden))::after{ scale:-1; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown{ +:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown{ position:absolute; display:flex; justify-content:center; @@ -2396,7 +2752,7 @@ width:calc(100% + 2 * var(--editor-toolbar-padding)); } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown button{ +:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button{ width:100%; height:auto; border:none; @@ -2407,19 +2763,19 @@ background:none; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown button:is(:active, :focus-visible){ +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:active,:focus-visible){ outline:none; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown button > .swatch{ +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{ outline-offset:2px; } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown button[aria-selected="true"] > .swatch{ +[aria-selected="true"]:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button) > .swatch{ outline:2px solid var(--selected-outline-color); } -.annotationEditorLayer .highlightEditor .editToolbar .buttons .colorPicker .dropdown button:is(:hover, :active, :focus-visible) > .swatch{ +:is(:is(:is(:is(:is(:is(.annotationEditorLayer .highlightEditor) .editToolbar) .buttons) .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{ outline:2px solid var(--hover-outline-color); } @@ -2449,7 +2805,7 @@ gap:8px; } -#highlightParamsToolbarContainer .colorPicker .dropdown{ +:is(#highlightParamsToolbarContainer .colorPicker) .dropdown{ display:flex; justify-content:space-between; align-items:center; @@ -2457,7 +2813,7 @@ height:auto; } -#highlightParamsToolbarContainer .colorPicker .dropdown button{ +:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button{ width:auto; height:auto; border:none; @@ -2469,20 +2825,20 @@ flex:0 0 auto; } -#highlightParamsToolbarContainer .colorPicker .dropdown button .swatch{ +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) .swatch{ width:24px; height:24px; } -#highlightParamsToolbarContainer .colorPicker .dropdown button:is(:active, :focus-visible){ +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:active,:focus-visible){ outline:none; } -#highlightParamsToolbarContainer .colorPicker .dropdown button[aria-selected="true"] > .swatch{ +[aria-selected="true"]:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button) > .swatch{ outline:2px solid var(--selected-outline-color); } -#highlightParamsToolbarContainer .colorPicker .dropdown button:is(:hover, :active, :focus-visible) > .swatch{ +:is(:is(:is(#highlightParamsToolbarContainer .colorPicker) .dropdown) button):is(:hover,:active,:focus-visible) > .swatch{ outline:2px solid var(--hover-outline-color); } @@ -2494,13 +2850,13 @@ align-self:stretch; } -#highlightParamsToolbarContainer #editorHighlightThickness .editorParamsLabel{ +:is(#highlightParamsToolbarContainer #editorHighlightThickness) .editorParamsLabel{ width:100%; height:auto; align-self:stretch; } -#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker{ +:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ display:flex; justify-content:space-between; align-items:center; @@ -2511,28 +2867,27 @@ @media (prefers-color-scheme: dark){ -:where(html:not(.is-light)) #highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker{ +:where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ --example-color:#80808e; } } -:where(html.is-dark) #highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker{ +:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ --example-color:#80808e; } @media screen and (forced-colors: active){ -#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker{ +:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker{ --example-color:CanvasText; } } -:is(#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker > .editorParamsSlider[disabled]){ +:is(:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) > .editorParamsSlider[disabled]){ opacity:0.4; } -#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker::before, - #highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker::after{ +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::before,:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{ content:""; width:8px; aspect-ratio:1; @@ -2541,11 +2896,11 @@ background-color:var(--example-color); } -#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker::after{ +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker)::after{ width:24px; } -#highlightParamsToolbarContainer #editorHighlightThickness .thicknessPicker .editorParamsSlider{ +:is(:is(#highlightParamsToolbarContainer #editorHighlightThickness) .thicknessPicker) .editorParamsSlider{ width:unset; height:14px; } @@ -2558,40 +2913,97 @@ align-self:stretch; } -#highlightParamsToolbarContainer #editorHighlightVisibility .divider{ +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ --divider-color:#d7d7db; - - margin-block:4px; - width:100%; - height:1px; - background-color:var(--divider-color); } @media (prefers-color-scheme: dark){ -:where(html:not(.is-light)) #highlightParamsToolbarContainer #editorHighlightVisibility .divider{ +:where(html:not(.is-light)) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ --divider-color:#8f8f9d; } } -:where(html.is-dark) #highlightParamsToolbarContainer #editorHighlightVisibility .divider{ +:where(html.is-dark) :is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ --divider-color:#8f8f9d; } @media screen and (forced-colors: active){ -#highlightParamsToolbarContainer #editorHighlightVisibility .divider{ +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ --divider-color:CanvasText; } } -#highlightParamsToolbarContainer #editorHighlightVisibility .toggler{ +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .divider{ + + margin-block:4px; + width:100%; + height:1px; + background-color:var(--divider-color); + } + +:is(#highlightParamsToolbarContainer #editorHighlightVisibility) .toggler{ display:flex; justify-content:space-between; align-items:center; align-self:stretch; } +#altTextSettingsDialog{ + padding:16px; +} + +#altTextSettingsDialog #altTextSettingsContainer{ + display:flex; + width:573px; + flex-direction:column; + gap:16px; + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) .mainContainer{ + gap:16px; + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) .description{ + color:var(--text-secondary-color); + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings{ + display:flex; + flex-direction:column; + gap:12px; + } + +:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) button{ + width:-moz-fit-content; + width:fit-content; + } + +.download:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings) #deleteModelButton{ + display:none; + } + +:is(:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings):not(.download) #downloadModelButton{ + display:none; + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticAltText,:is(#altTextSettingsDialog #altTextSettingsContainer) #altTextEditor{ + display:flex; + flex-direction:column; + gap:8px; + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) #createModelDescription,:is(#altTextSettingsDialog #altTextSettingsContainer) #aiModelSettings,:is(#altTextSettingsDialog #altTextSettingsContainer) #showAltTextDialogDescription{ + padding-inline-start:40px; + } + +:is(#altTextSettingsDialog #altTextSettingsContainer) #automaticSettings{ + display:flex; + flex-direction:column; + gap:16px; + } + :root{ --viewer-container-height:0; --pdfViewer-padding-bottom:0; @@ -2646,27 +3058,31 @@ } } +.pdfViewer.copyAll{ + cursor:wait; + } + .pdfViewer .canvasWrapper{ overflow:hidden; width:100%; height:100%; } -.pdfViewer .canvasWrapper canvas{ +:is(.pdfViewer .canvasWrapper) canvas{ margin:0; display:block; } -.pdfViewer .canvasWrapper canvas[hidden]{ +[hidden]:is(:is(.pdfViewer .canvasWrapper) canvas){ display:none; } -.pdfViewer .canvasWrapper canvas[zooming]{ +[zooming]:is(:is(.pdfViewer .canvasWrapper) canvas){ width:100%; height:100%; } -.pdfViewer .canvasWrapper canvas .structTree{ +:is(:is(.pdfViewer .canvasWrapper) canvas) .structTree{ contain:strict; } @@ -2797,6 +3213,7 @@ --toolbar-border-color:rgb(184 184 184); --toolbar-box-shadow:0 1px 0 var(--toolbar-border-color); --toolbar-border-bottom:none; + --toolbar-height:32px; --toolbarSidebar-box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25), 0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1); --toolbarSidebar-border-bottom:none; --button-hover-color:rgb(221 222 223); @@ -2866,6 +3283,9 @@ --secondaryToolbarButton-spreadNone-icon:url(images/secondaryToolbarButton-spreadNone.svg); --secondaryToolbarButton-spreadOdd-icon:url(images/secondaryToolbarButton-spreadOdd.svg); --secondaryToolbarButton-spreadEven-icon:url(images/secondaryToolbarButton-spreadEven.svg); + --secondaryToolbarButton-imageAltTextSettings-icon:var( + --toolbarButton-editorStamp-icon + ); --secondaryToolbarButton-documentProperties-icon:url(images/secondaryToolbarButton-documentProperties.svg); --editorParams-stampAddImage-icon:url(images/toolbarButton-zoomIn.svg); } @@ -3010,6 +3430,15 @@ body{ scrollbar-color:var(--scrollbar-color) var(--scrollbar-bg-color); } +body.wait::before{ + content:""; + position:fixed; + width:100%; + height:100%; + z-index:100000; + cursor:wait; + } + .hidden, [hidden]{ display:none !important; @@ -3048,7 +3477,7 @@ body{ #sidebarContainer{ position:absolute; - inset-block:32px 0; + inset-block:var(--toolbar-height) 0; inset-inline-start:calc(-1 * var(--sidebar-width)); width:var(--sidebar-width); visibility:hidden; @@ -3075,7 +3504,7 @@ body{ } #sidebarContent{ - inset-block:32px 0; + inset-block:var(--toolbar-height) 0; inset-inline-start:0; overflow:auto; position:absolute; @@ -3086,7 +3515,7 @@ body{ #viewerContainer{ overflow:auto; position:absolute; - inset:32px 0 0; + inset:var(--toolbar-height) 0 0; outline:none; } #viewerContainer:not(.pdfPresentationMode){ @@ -3107,9 +3536,8 @@ body{ font:message-box; } -:is(.toolbar, .editorParamsToolbar, .findbar, #sidebarContainer) - :is(input, button, select), -.secondaryToolbar :is(input, button, a, select){ +:is(.toolbar, .editorParamsToolbar, #sidebarContainer) + :is(input, button, select){ outline:none; font:message-box; } @@ -3120,7 +3548,7 @@ body{ #toolbarSidebar{ width:100%; - height:32px; + height:var(--toolbar-height); background-color:var(--sidebar-toolbar-bg-color); box-shadow:var(--toolbarSidebar-box-shadow); border-bottom:var(--toolbarSidebar-border-bottom); @@ -3136,18 +3564,16 @@ body{ } #toolbarContainer, -.findbar, -.secondaryToolbar, .editorParamsToolbar{ position:relative; - height:32px; + height:var(--toolbar-height); background-color:var(--toolbar-bg-color); box-shadow:var(--toolbar-box-shadow); border-bottom:var(--toolbar-border-bottom); } #toolbarViewer{ - height:32px; + height:var(--toolbar-height); } #loadingBar{ @@ -3214,94 +3640,25 @@ body{ animation:progressIndeterminate 1s linear infinite; } -#outerContainer.sidebarResizing - :is(#sidebarContainer, #viewerContainer, #loadingBar){ - transition-duration:0s; -} - -.findbar, -.secondaryToolbar, -.editorParamsToolbar{ - top:32px; - position:absolute; - z-index:30000; - height:auto; - padding:0 4px; - margin:4px 2px; - font:message-box; - font-size:12px; - line-height:14px; - text-align:left; - cursor:default; -} - -.findbar{ - inset-inline-start:64px; - min-width:300px; - background-color:var(--toolbar-bg-color); -} -.findbar > div{ - height:32px; -} -.findbar > div#findbarInputContainer{ - margin-inline-end:4px; -} -.findbar.wrapContainers > div, -.findbar.wrapContainers > div#findbarMessageContainer > *{ - clear:both; -} -.findbar.wrapContainers > div#findbarMessageContainer{ - height:auto; -} - -.findbar input[type="checkbox"]{ - pointer-events:none; -} - -.findbar label{ - -webkit-user-select:none; - -moz-user-select:none; - user-select:none; -} - -.findbar label:hover, -.findbar input:focus-visible + label{ - color:var(--toggled-btn-color); - background-color:var(--button-hover-color); -} - -.findbar .toolbarField[type="checkbox"]:checked + .toolbarLabel{ - background-color:var(--toggled-btn-bg-color) !important; - color:var(--toggled-btn-color); -} - -#findInput{ - width:200px; -} - -#findInput::-moz-placeholder{ - font-style:normal; - } - -#findInput::placeholder{ - font-style:normal; - } - -.loadingInput:has(> #findInput[data-status="pending"])::after{ - display:block; - visibility:visible; - } - -#findInput[data-status="notFound"]{ - background-color:rgb(255 102 102); - } +#outerContainer.sidebarResizing + :is(#sidebarContainer, #viewerContainer, #loadingBar){ + transition-duration:0s; +} -.secondaryToolbar, .editorParamsToolbar{ - padding:6px 0 10px; - inset-inline-end:4px; - height:auto; background-color:var(--doorhanger-bg-color); + top:var(--toolbar-height); + position:absolute; + z-index:30000; + height:auto; + inset-inline-end:4px; + padding:6px 0 10px; + margin:4px 2px; + font:message-box; + font-size:12px; + line-height:14px; + text-align:left; + cursor:default; } .editorParamsToolbarContainer{ @@ -3354,14 +3711,6 @@ body{ background-color:white; } -#secondaryToolbarButtonContainer{ - max-width:220px; - min-height:26px; - max-height:calc(var(--viewer-container-height) - 40px); - overflow-y:auto; - margin-bottom:-4px; -} - #editorStampParamsToolbar{ inset-inline-end:calc(var(--editor-toolbar-base-offset) + 0px); } @@ -3423,22 +3772,6 @@ body{ margin-inline-end:-9px; } -#findResultsCount{ - background-color:rgb(217 217 217); - color:rgb(82 82 82); - text-align:center; - padding:4px 5px; - margin:5px; -} - -#findMsg[data-status="notFound"]{ - font-weight:bold; -} - -:is(#findResultsCount, #findMsg):empty{ - display:none; -} - #toolbarViewerMiddle{ position:absolute; left:50%; @@ -3458,8 +3791,7 @@ body{ #toolbarViewerMiddle > *, #toolbarViewerRight > *, #toolbarSidebarLeft *, -#toolbarSidebarRight *, -.findbar *{ +#toolbarSidebarRight *{ position:relative; float:var(--inline-start); } @@ -3483,7 +3815,6 @@ body{ } .toolbarButton, -.secondaryToolbarButton, .dialogButton{ border:none; background:none; @@ -3507,7 +3838,7 @@ body{ overflow:hidden; } -:is(.toolbarButton, .secondaryToolbarButton, .dialogButton)[disabled]{ +:is(.toolbarButton, .dialogButton)[disabled]{ opacity:0.5; } @@ -3533,7 +3864,6 @@ body{ .toolbarButton, .dropdownToolbarButton, -.secondaryToolbarButton, .dialogButton{ min-width:16px; margin:2px 1px; @@ -3553,27 +3883,23 @@ body{ .toolbarButton:is(:hover, :focus-visible){ background-color:var(--button-hover-color); } -.secondaryToolbarButton:is(:hover, :focus-visible){ - background-color:var(--doorhanger-hover-bg-color); - color:var(--doorhanger-hover-color); -} -:is(.toolbarButton, .secondaryToolbarButton).toggled, +.toolbarButton.toggled, .splitToolbarButton.toggled > .toolbarButton.toggled{ background-color:var(--toggled-btn-bg-color); color:var(--toggled-btn-color); } -:is(.toolbarButton, .secondaryToolbarButton).toggled:hover, +.toolbarButton.toggled:hover, .splitToolbarButton.toggled > .toolbarButton.toggled:hover{ outline:var(--toggled-hover-btn-outline) !important; } -:is(.toolbarButton, .secondaryToolbarButton).toggled::before{ +.toolbarButton.toggled::before{ background-color:var(--toggled-btn-color); } -:is(.toolbarButton, .secondaryToolbarButton).toggled:hover:active, +.toolbarButton.toggled:hover:active, .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active{ background-color:var(--toggled-hover-active-btn-color); } @@ -3625,7 +3951,7 @@ body{ height:1px; } -:is(.toolbarButton, .secondaryToolbarButton, .treeItemToggler)::before, +:is(.toolbarButton, .treeItemToggler)::before, .dropdownToolbarButton::after{ position:absolute; display:inline-block; @@ -3648,17 +3974,10 @@ body{ left:6px; } -.toolbarButton:is(:hover, :focus-visible)::before, -.secondaryToolbarButton:is(:hover, :focus-visible)::before{ +.toolbarButton:is(:hover, :focus-visible)::before{ background-color:var(--toolbar-icon-hover-bg-color); } -.secondaryToolbarButton::before{ - opacity:var(--doorhanger-icon-opacity); - top:5px; - inset-inline-start:12px; -} - #sidebarToggle::before{ -webkit-mask-image:var(--toolbarButton-sidebarToggle-icon); mask-image:var(--toolbarButton-sidebarToggle-icon); @@ -3671,16 +3990,6 @@ body{ transform:scaleX(var(--dir-factor)); } -#findPrevious::before{ - -webkit-mask-image:var(--findbarButton-previous-icon); - mask-image:var(--findbarButton-previous-icon); -} - -#findNext::before{ - -webkit-mask-image:var(--findbarButton-next-icon); - mask-image:var(--findbarButton-next-icon); -} - #previous::before{ -webkit-mask-image:var(--toolbarButton-pageUp-icon); mask-image:var(--toolbarButton-pageUp-icon); @@ -3701,11 +4010,6 @@ body{ mask-image:var(--toolbarButton-zoomIn-icon); } -#presentationMode::before{ - -webkit-mask-image:var(--toolbarButton-presentationMode-icon); - mask-image:var(--toolbarButton-presentationMode-icon); -} - #editorFreeText::before{ -webkit-mask-image:var(--toolbarButton-editorFreeText-icon); mask-image:var(--toolbarButton-editorFreeText-icon); @@ -3726,35 +4030,16 @@ body{ mask-image:var(--toolbarButton-editorStamp-icon); } -:is(#print, #secondaryPrint)::before{ +#print::before{ -webkit-mask-image:var(--toolbarButton-print-icon); mask-image:var(--toolbarButton-print-icon); } -#secondaryOpenFile::before{ - -webkit-mask-image:var(--toolbarButton-openFile-icon); - mask-image:var(--toolbarButton-openFile-icon); -} - -:is(#download, #secondaryDownload)::before{ +#download::before{ -webkit-mask-image:var(--toolbarButton-download-icon); mask-image:var(--toolbarButton-download-icon); } -a.secondaryToolbarButton{ - padding-top:5px; - text-decoration:none; -} -a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{ - opacity:0.5; - pointer-events:none; -} - -#viewBookmark::before{ - -webkit-mask-image:var(--toolbarButton-bookmark-icon); - mask-image:var(--toolbarButton-bookmark-icon); -} - #viewThumbnail::before{ -webkit-mask-image:var(--toolbarButton-viewThumbnail-icon); mask-image:var(--toolbarButton-viewThumbnail-icon); @@ -3799,95 +4084,6 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{ border-radius:50%; } -.secondaryToolbarButton{ - position:relative; - margin:0; - padding:0 0 1px; - padding-inline-start:36px; - height:auto; - min-height:26px; - width:auto; - min-width:100%; - text-align:start; - white-space:normal; - border-radius:0; - box-sizing:border-box; - display:inline-block; -} -.secondaryToolbarButton > span{ - padding-inline-end:4px; -} - -#firstPage::before{ - -webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon); - mask-image:var(--secondaryToolbarButton-firstPage-icon); -} - -#lastPage::before{ - -webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon); - mask-image:var(--secondaryToolbarButton-lastPage-icon); -} - -#pageRotateCcw::before{ - -webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon); - mask-image:var(--secondaryToolbarButton-rotateCcw-icon); -} - -#pageRotateCw::before{ - -webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon); - mask-image:var(--secondaryToolbarButton-rotateCw-icon); -} - -#cursorSelectTool::before{ - -webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon); - mask-image:var(--secondaryToolbarButton-selectTool-icon); -} - -#cursorHandTool::before{ - -webkit-mask-image:var(--secondaryToolbarButton-handTool-icon); - mask-image:var(--secondaryToolbarButton-handTool-icon); -} - -#scrollPage::before{ - -webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon); - mask-image:var(--secondaryToolbarButton-scrollPage-icon); -} - -#scrollVertical::before{ - -webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon); - mask-image:var(--secondaryToolbarButton-scrollVertical-icon); -} - -#scrollHorizontal::before{ - -webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); - mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); -} - -#scrollWrapped::before{ - -webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); - mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); -} - -#spreadNone::before{ - -webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon); - mask-image:var(--secondaryToolbarButton-spreadNone-icon); -} - -#spreadOdd::before{ - -webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon); - mask-image:var(--secondaryToolbarButton-spreadOdd-icon); -} - -#spreadEven::before{ - -webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon); - mask-image:var(--secondaryToolbarButton-spreadEven-icon); -} - -#documentProperties::before{ - -webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon); - mask-image:var(--secondaryToolbarButton-documentProperties-icon); -} - .verticalToolbarSeparator{ display:block; margin:5px 2px; @@ -3937,7 +4133,7 @@ a:is(.toolbarButton, .secondaryToolbarButton)[href="#"]{ -webkit-appearance:none; } -.loadingInput:has(> #pageNumber.loading)::after{ +.loadingInput:has( > .loading:is(#pageNumber))::after{ display:block; visibility:visible; @@ -4238,6 +4434,296 @@ dialog :link{ z-index:50000; } +.toolbarButton.labeled{ + border-radius:0; + display:inline-block; + height:auto; + margin:0; + padding:0 0 1px; + padding-inline-start:36px; + position:relative; + min-height:26px; + min-width:100%; + text-align:start; + white-space:normal; + width:auto; + } + +.toolbarButton.labeled:is(a){ + padding-top:5px; + text-decoration:none; + } + +.toolbarButton.labeled[href="#"]:is(a){ + opacity:0.5; + pointer-events:none; + } + +.toolbarButton.labeled::before{ + inset-inline-start:12px; + opacity:var(--doorhanger-icon-opacity); + top:5px; + } + +.toolbarButton.labeled:not(.toggled):is(:hover,:focus-visible){ + background-color:var(--doorhanger-hover-bg-color); + color:var(--doorhanger-hover-color); + } + +.toolbarButton.labeled > span{ + display:unset; + padding-inline-end:4px; + } + +#secondaryToolbar{ + background-color:var(--doorhanger-bg-color); + cursor:default; + font:message-box; + font-size:12px; + height:auto; + inset-inline-end:4px; + line-height:14px; + margin:4px 2px; + padding:6px 0 10px; + position:absolute; + text-align:left; + top:var(--toolbar-height); + z-index:30000; +} + +#secondaryToolbar :is(button,a){ + font:message-box; + outline:none; + } + +#secondaryToolbar #secondaryToolbarButtonContainer{ + margin-bottom:-4px; + max-height:calc(var(--viewer-container-height) - 40px); + max-width:220px; + min-height:26px; + overflow-y:auto; + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryOpenFile::before{ + -webkit-mask-image:var(--toolbarButton-openFile-icon); + mask-image:var(--toolbarButton-openFile-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryPrint::before{ + -webkit-mask-image:var(--toolbarButton-print-icon); + mask-image:var(--toolbarButton-print-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #secondaryDownload::before{ + -webkit-mask-image:var(--toolbarButton-download-icon); + mask-image:var(--toolbarButton-download-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #presentationMode::before{ + -webkit-mask-image:var(--toolbarButton-presentationMode-icon); + mask-image:var(--toolbarButton-presentationMode-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #viewBookmark::before{ + -webkit-mask-image:var(--toolbarButton-bookmark-icon); + mask-image:var(--toolbarButton-bookmark-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #firstPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-firstPage-icon); + mask-image:var(--secondaryToolbarButton-firstPage-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #lastPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-lastPage-icon); + mask-image:var(--secondaryToolbarButton-lastPage-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCcw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCcw-icon); + mask-image:var(--secondaryToolbarButton-rotateCcw-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #pageRotateCw::before{ + -webkit-mask-image:var(--secondaryToolbarButton-rotateCw-icon); + mask-image:var(--secondaryToolbarButton-rotateCw-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorSelectTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-selectTool-icon); + mask-image:var(--secondaryToolbarButton-selectTool-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #cursorHandTool::before{ + -webkit-mask-image:var(--secondaryToolbarButton-handTool-icon); + mask-image:var(--secondaryToolbarButton-handTool-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollPage::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollPage-icon); + mask-image:var(--secondaryToolbarButton-scrollPage-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollVertical::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollVertical-icon); + mask-image:var(--secondaryToolbarButton-scrollVertical-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollHorizontal::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); + mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #scrollWrapped::before{ + -webkit-mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); + mask-image:var(--secondaryToolbarButton-scrollWrapped-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadNone::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadNone-icon); + mask-image:var(--secondaryToolbarButton-spreadNone-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadOdd::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadOdd-icon); + mask-image:var(--secondaryToolbarButton-spreadOdd-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #spreadEven::before{ + -webkit-mask-image:var(--secondaryToolbarButton-spreadEven-icon); + mask-image:var(--secondaryToolbarButton-spreadEven-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #imageAltTextSettings::before{ + -webkit-mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon); + mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon); + } + +:is(#secondaryToolbar #secondaryToolbarButtonContainer) #documentProperties::before{ + -webkit-mask-image:var(--secondaryToolbarButton-documentProperties-icon); + mask-image:var(--secondaryToolbarButton-documentProperties-icon); + } + +#findbar{ + background-color:var(--toolbar-bg-color); + cursor:default; + font:message-box; + font-size:12px; + height:auto; + inset-inline-start:64px; + line-height:14px; + margin:4px 2px; + min-width:300px; + padding:0 4px; + position:absolute; + text-align:left; + top:var(--toolbar-height); + z-index:30000; +} + +#findbar *{ + float:var(--inline-start); + position:relative; + } + +#findbar > div{ + height:var(--toolbar-height); + } + +#findbar :is(button,input){ + font:message-box; + outline:none; + } + +[type="checkbox"]:is(#findbar input){ + pointer-events:none; + } + +[type="checkbox"]:is(#findbar input):checked + .toolbarLabel{ + background-color:var(--toggled-btn-bg-color) !important; + color:var(--toggled-btn-color); + } + +#findbar label{ + -webkit-user-select:none; + -moz-user-select:none; + user-select:none; + } + +#findbar :is(label:hover,input:focus-visible + label){ + background-color:var(--button-hover-color); + color:var(--toggled-btn-color); + } + +#findbar #findbarInputContainer{ + margin-inline-end:4px; + } + +:is(#findbar #findbarInputContainer) #findInput{ + width:200px; + } + +:is(:is(#findbar #findbarInputContainer) #findInput)::-moz-placeholder{ + font-style:normal; + } + +:is(:is(#findbar #findbarInputContainer) #findInput)::placeholder{ + font-style:normal; + } + +.loadingInput:has( > [data-status="pending"]:is(:is(#findbar #findbarInputContainer) #findInput))::after{ + display:block; + visibility:visible; + } + +[data-status="notFound"]:is(:is(#findbar #findbarInputContainer) #findInput){ + background-color:rgb(255 102 102); + } + +:is(#findbar #findbarInputContainer) #findPrevious::before{ + -webkit-mask-image:var(--findbarButton-previous-icon); + mask-image:var(--findbarButton-previous-icon); + } + +:is(#findbar #findbarInputContainer) #findNext::before{ + -webkit-mask-image:var(--findbarButton-next-icon); + mask-image:var(--findbarButton-next-icon); + } + +:is(#findbar #findbarMessageContainer) #findResultsCount{ + background-color:rgb(217 217 217); + color:rgb(82 82 82); + margin:5px; + padding:4px 5px; + text-align:center; + } + +[data-status="notFound"]:is(:is(#findbar #findbarMessageContainer) #findMsg){ + font-weight:bold; + } + +:is(#findbar #findbarMessageContainer) *:empty{ + display:none; + } + +#findbar.wrapContainers > div{ + clear:both; + } + +#findbar.wrapContainers > #findbarMessageContainer{ + height:auto; + } + +:is(#findbar.wrapContainers > #findbarMessageContainer) > *{ + clear:both; + } + +@media all and (max-width: 690px){ + #findbar{ + inset-inline-start:34px; + } + } + @page{ margin:0; } @@ -4293,7 +4779,7 @@ dialog :link{ } .visibleMediumView{ - display:none; + display:none !important; } @media all and (max-width: 900px){ @@ -4320,24 +4806,21 @@ dialog :link{ --editor-toolbar-base-offset:40px; } #outerContainer .hiddenMediumView{ - display:none; + display:none !important; } #outerContainer .visibleMediumView{ - display:inherit; + display:inherit !important; } } @media all and (max-width: 690px){ .hiddenSmallView, .hiddenSmallView *{ - display:none; + display:none !important; } .toolbarButtonSpacer{ width:0; } - .findbar{ - inset-inline-start:34px; - } } @media all and (max-width: 560px){ diff --git a/viewer/viewer.html b/viewer/viewer.html index bedfdd304..4369c642c 100644 --- a/viewer/viewer.html +++ b/viewer/viewer.html @@ -1,4 +1,4 @@ - + -
-
-
@@ -297,43 +302,43 @@
- - - -
- -
-
-
-
@@ -379,8 +384,8 @@
- - + +
@@ -444,7 +449,7 @@

-

- +
@@ -485,8 +490,97 @@
- - + + +
+ +
+ +
+
+ Edit alt text (image description) +
+
+
+
+
+
+ +
+ Short description for people who can’t see the image or when the image doesn’t load. +
This alt text was created automatically and may be inaccurate. Learn more
+
+
+ + +
+ +
+
+
+
+
+
+ Couldn’t create alt text automatically + Please write your own alt text or try again later. +
+ +
+
+
+ + + +
+
+
+ + +
+
+ Image alt text settings +
+
+ Automatic alt text +
+
+
+ + +
+
+ Suggests descriptions to help people who can’t see the image or when the image doesn’t load. Learn more +
+
+
+
+ Alt text AI model (180MB) +
+ Runs locally on your device so your data stays private. Required for automatic alt text. +
+
+ + +
+
+
+
+
+ Alt text editor +
+
+ + +
+
+ Helps you make sure all your images have alt text. +
+
+
+
+
@@ -499,7 +593,7 @@ 0%
- +
diff --git a/viewer/viewer.mjs b/viewer/viewer.mjs index 00c98d358..743ed5ce4 100644 --- a/viewer/viewer.mjs +++ b/viewer/viewer.mjs @@ -2,7 +2,7 @@ * @licstart The following is the entire license notice for the * JavaScript code in this page * - * Copyright 2023 Mozilla Foundation + * Copyright 2024 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -142,7 +142,7 @@ function scrollIntoView(element, spot, scrollMatches = false) { } parent.scrollTop = offsetY; } -function watchScroll(viewAreaElement, callback) { +function watchScroll(viewAreaElement, callback, abortSignal = undefined) { const debounceScroll = function (evt) { if (rAF) { return; @@ -172,7 +172,13 @@ function watchScroll(viewAreaElement, callback) { _eventHandler: debounceScroll }; let rAF = null; - viewAreaElement.addEventListener("scroll", debounceScroll, true); + viewAreaElement.addEventListener("scroll", debounceScroll, { + useCapture: true, + signal: abortSignal + }); + abortSignal?.addEventListener("abort", () => window.cancelAnimationFrame(rAF), { + once: true + }); return state; } function parseQueryString(query) { @@ -250,9 +256,8 @@ function approximateFraction(x) { } return result; } -function roundToDivide(x, div) { - const r = x % div; - return r === 0 ? x : Math.round(x - r + div); +function floorToDivide(x, div) { + return x - x % div; } function getPageSizeInches({ view, @@ -441,7 +446,7 @@ class ProgressBar { } } setDisableAutoFetch(delay = 5000) { - if (isNaN(this.#percent)) { + if (this.#percent === 100 || isNaN(this.#percent)) { return; } if (this.#disableAutoFetchTimeout) { @@ -530,15 +535,20 @@ function toggleExpandedBtn(button, toggle, view = null) { ;// CONCATENATED MODULE: ./web/app_options.js { - var compatibilityParams = Object.create(null); + var compatParams = new Map(); const userAgent = navigator.userAgent || ""; const platform = navigator.platform || ""; const maxTouchPoints = navigator.maxTouchPoints || 1; const isAndroid = /Android/.test(userAgent); const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent) || platform === "MacIntel" && maxTouchPoints > 1; - (function checkCanvasSizeLimitation() { + (function () { if (isIOS || isAndroid) { - compatibilityParams.maxCanvasPixels = 5242880; + compatParams.set("maxCanvasPixels", 5242880); + } + })(); + (function () { + if (isAndroid) { + compatParams.set("useSystemFonts", false); } })(); } @@ -547,9 +557,21 @@ const OptionKind = { VIEWER: 0x02, API: 0x04, WORKER: 0x08, + EVENT_DISPATCH: 0x10, PREFERENCE: 0x80 }; +const Type = { + BOOLEAN: 0x01, + NUMBER: 0x02, + OBJECT: 0x04, + STRING: 0x08, + UNDEFINED: 0x10 +}; const defaultOptions = { + allowedGlobalEvents: { + value: null, + kind: OptionKind.BROWSER + }, canvasMaxAreaInBytes: { value: -1, kind: OptionKind.BROWSER + OptionKind.API @@ -558,6 +580,16 @@ const defaultOptions = { value: false, kind: OptionKind.BROWSER }, + localeProperties: { + value: { + lang: navigator.language || "en-US" + }, + kind: OptionKind.BROWSER + }, + nimbusDataStr: { + value: "", + kind: OptionKind.BROWSER + }, supportsCaretBrowsingMode: { value: false, kind: OptionKind.BROWSER @@ -582,6 +614,14 @@ const defaultOptions = { value: true, kind: OptionKind.BROWSER }, + toolbarDensity: { + value: 0, + kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH + }, + altTextLearnMoreUrl: { + value: "", + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + }, annotationEditorMode: { value: 0, kind: OptionKind.VIEWER + OptionKind.PREFERENCE @@ -614,16 +654,24 @@ const defaultOptions = { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, - enableHighlightEditor: { + enableAltText: { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, + enableAltTextModelDownload: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH + }, + enableGuessAltText: { + value: true, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH + }, enableHighlightFloatingButton: { value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, - enableML: { - value: false, + enableNewAltTextWhenAddingImage: { + value: true, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, enablePermissions: { @@ -638,8 +686,8 @@ const defaultOptions = { value: true, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, - enableStampEditor: { - value: true, + enableUpdatedAddImage: { + value: false, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }, externalLinkRel: { @@ -738,6 +786,10 @@ const defaultOptions = { value: "", kind: OptionKind.API }, + enableHWA: { + value: true, + kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE + }, enableXfa: { value: true, kind: OptionKind.API + OptionKind.PREFERENCE @@ -766,6 +818,11 @@ const defaultOptions = { value: "../standard_fonts/", kind: OptionKind.API }, + useSystemFonts: { + value: undefined, + kind: OptionKind.API, + type: Type.BOOLEAN + Type.UNDEFINED + }, verbosity: { value: 1, kind: OptionKind.API @@ -792,66 +849,84 @@ const defaultOptions = { value: 0, kind: OptionKind.VIEWER + OptionKind.PREFERENCE }; + defaultOptions.enableFakeMLManager = { + value: true, + kind: OptionKind.VIEWER + }; } { defaultOptions.disablePreferences = { value: false, kind: OptionKind.VIEWER }; - defaultOptions.locale = { - value: navigator.language || "en-US", - kind: OptionKind.VIEWER - }; -} -const userOptions = Object.create(null); -{ - for (const name in compatibilityParams) { - userOptions[name] = compatibilityParams[name]; - } } class AppOptions { - constructor() { - throw new Error("Cannot initialize AppOptions."); + static eventBus; + static #opts = new Map(); + static { + for (const name in defaultOptions) { + this.#opts.set(name, defaultOptions[name].value); + } + for (const [name, value] of compatParams) { + this.#opts.set(name, value); + } + this._hasInvokedSet = false; + this._checkDisablePreferences = () => { + if (this.get("disablePreferences")) { + return true; + } + if (this._hasInvokedSet) { + console.warn("The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option to prevent that.'); + } + return false; + }; } static get(name) { - return userOptions[name] ?? defaultOptions[name]?.value ?? undefined; + return this.#opts.get(name); } static getAll(kind = null, defaultOnly = false) { const options = Object.create(null); for (const name in defaultOptions) { - const defaultOption = defaultOptions[name]; - if (kind && !(kind & defaultOption.kind)) { + const defaultOpt = defaultOptions[name]; + if (kind && !(kind & defaultOpt.kind)) { continue; } - options[name] = defaultOnly ? defaultOption.value : userOptions[name] ?? defaultOption.value; + options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value; } return options; } static set(name, value) { - userOptions[name] = value; + this.setAll({ + [name]: value + }); } - static setAll(options, init = false) { - if (init) { - if (this.get("disablePreferences")) { - return; + static setAll(options, prefs = false) { + this._hasInvokedSet ||= true; + let events; + for (const name in options) { + const defaultOpt = defaultOptions[name], + userOpt = options[name]; + if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) { + continue; } - for (const name in userOptions) { - if (compatibilityParams[name] !== undefined) { - continue; - } - console.warn("setAll: The Preferences may override manually set AppOptions; " + 'please use the "disablePreferences"-option in order to prevent that.'); - break; + const { + kind + } = defaultOpt; + if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) { + continue; } + if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) { + (events ||= new Map()).set(name, userOpt); + } + this.#opts.set(name, userOpt); } - for (const name in options) { - userOptions[name] = options[name]; - } - } - static remove(name) { - delete userOptions[name]; - const val = compatibilityParams[name]; - if (val !== undefined) { - userOptions[name] = val; + if (events) { + for (const [name, value] of events) { + this.eventBus.dispatch(name.toLowerCase(), { + source: this, + value + }); + } } } } @@ -1162,26 +1237,27 @@ class PDFLinkService { if (!(typeof zoom === "object" && typeof zoom?.name === "string")) { return false; } + const argsLen = args.length; let allowNull = true; switch (zoom.name) { case "XYZ": - if (args.length !== 3) { + if (argsLen < 2 || argsLen > 3) { return false; } break; case "Fit": case "FitB": - return args.length === 0; + return argsLen === 0; case "FitH": case "FitBH": case "FitV": case "FitBV": - if (args.length !== 1) { + if (argsLen > 1) { return false; } break; case "FitR": - if (args.length !== 4) { + if (argsLen !== 4) { return false; } allowNull = false; @@ -1231,7 +1307,6 @@ const { noContextMenu, normalizeUnicode, OPS, - Outliner, PasswordResponses, PDFDataRangeTransport, PDFDateString, @@ -1239,12 +1314,10 @@ const { PermissionFlag, PixelsPerInch, RenderingCancelledException, - renderTextLayer, setLayerDimensions, shadow, TextLayer, UnexpectedResponseException, - updateTextLayer, Util, VerbosityLevel, version, @@ -1357,19 +1430,23 @@ class EventBus { } } } -class AutomationEventBus extends EventBus { +class FirefoxEventBus extends EventBus { + #externalServices; + #globalEventNames; + #isInAutomation; + constructor(globalEventNames, externalServices, isInAutomation) { + super(); + this.#globalEventNames = globalEventNames; + this.#externalServices = externalServices; + this.#isInAutomation = isInAutomation; + } dispatch(eventName, data) { - throw new Error("Not implemented: AutomationEventBus.dispatch"); + throw new Error("Not implemented: FirefoxEventBus.dispatch"); } } ;// CONCATENATED MODULE: ./web/external_services.js class BaseExternalServices { - constructor() { - if (this.constructor === BaseExternalServices) { - throw new Error("Cannot initialize BaseExternalServices."); - } - } updateFindControlState(data) {} updateFindMatchesCount(data) {} initPassiveLoading() {} @@ -1383,36 +1460,29 @@ class BaseExternalServices { updateEditorStates(data) { throw new Error("Not implemented: updateEditorStates"); } - async getNimbusExperimentData() {} + dispatchGlobalEvent(_event) {} } ;// CONCATENATED MODULE: ./web/preferences.js class BasePreferences { - #browserDefaults = Object.freeze({ - canvasMaxAreaInBytes: -1, - isInAutomation: false, - supportsCaretBrowsingMode: false, - supportsDocumentFonts: true, - supportsIntegratedFind: false, - supportsMouseWheelZoomCtrlKey: true, - supportsMouseWheelZoomMetaKey: true, - supportsPinchToZoom: true - }); #defaults = Object.freeze({ + altTextLearnMoreUrl: "", annotationEditorMode: 0, annotationMode: 2, cursorToolOnLoad: 0, defaultZoomDelay: 400, defaultZoomValue: "", disablePageLabels: false, - enableHighlightEditor: false, + enableAltText: false, + enableAltTextModelDownload: true, + enableGuessAltText: true, enableHighlightFloatingButton: false, - enableML: false, + enableNewAltTextWhenAddingImage: true, enablePermissions: false, enablePrintAutoRotate: true, enableScripting: true, - enableStampEditor: true, + enableUpdatedAddImage: false, externalLinkTarget: 0, highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F", historyUpdateUrl: false, @@ -1430,29 +1500,23 @@ class BasePreferences { disableFontFace: false, disableRange: false, disableStream: false, + enableHWA: true, enableXfa: true, viewerCssTheme: 0 }); - #prefs = Object.create(null); #initializedPromise = null; constructor() { - if (this.constructor === BasePreferences) { - throw new Error("Cannot initialize BasePreferences."); - } this.#initializedPromise = this._readFromStorage(this.#defaults).then(({ browserPrefs, prefs }) => { - const options = Object.create(null); - for (const [name, val] of Object.entries(this.#browserDefaults)) { - const prefVal = browserPrefs?.[name]; - options[name] = typeof prefVal === typeof val ? prefVal : val; - } - for (const [name, val] of Object.entries(this.#defaults)) { - const prefVal = prefs?.[name]; - options[name] = this.#prefs[name] = typeof prefVal === typeof val ? prefVal : val; + if (AppOptions._checkDisablePreferences()) { + return; } - AppOptions.setAll(options, true); + AppOptions.setAll({ + ...browserPrefs, + ...prefs + }, true); }); } async _writeToStorage(prefObj) { @@ -1461,58 +1525,21 @@ class BasePreferences { async _readFromStorage(prefObj) { throw new Error("Not implemented: _readFromStorage"); } - #updatePref({ - name, - value - }) { - throw new Error("Not implemented: #updatePref"); - } async reset() { await this.#initializedPromise; - const oldPrefs = structuredClone(this.#prefs); - this.#prefs = Object.create(null); - try { - await this._writeToStorage(this.#defaults); - } catch (reason) { - this.#prefs = oldPrefs; - throw reason; - } + AppOptions.setAll(this.#defaults, true); + await this._writeToStorage(this.#defaults); } async set(name, value) { await this.#initializedPromise; - const defaultValue = this.#defaults[name], - oldPrefs = structuredClone(this.#prefs); - if (defaultValue === undefined) { - throw new Error(`Set preference: "${name}" is undefined.`); - } else if (value === undefined) { - throw new Error("Set preference: no value is specified."); - } - const valueType = typeof value, - defaultType = typeof defaultValue; - if (valueType !== defaultType) { - if (valueType === "number" && defaultType === "string") { - value = value.toString(); - } else { - throw new Error(`Set preference: "${value}" is a ${valueType}, expected a ${defaultType}.`); - } - } else if (valueType === "number" && !Number.isInteger(value)) { - throw new Error(`Set preference: "${value}" must be an integer.`); - } - this.#prefs[name] = value; - try { - await this._writeToStorage(this.#prefs); - } catch (reason) { - this.#prefs = oldPrefs; - throw reason; - } + AppOptions.setAll({ + [name]: value + }, true); + await this._writeToStorage(AppOptions.getAll(OptionKind.PREFERENCE)); } async get(name) { await this.#initializedPromise; - const defaultValue = this.#defaults[name]; - if (defaultValue === undefined) { - throw new Error(`Get preference: "${name}" is undefined.`); - } - return this.#prefs[name] ?? defaultValue; + return AppOptions.get(name); } get initializedPromise() { return this.#initializedPromise; @@ -2743,6 +2770,9 @@ class DOMLocalization extends Localization { this.pauseObserving(); if (this.roots.size === 0) { this.mutationObserver = null; + if (this.windowElement && this.pendingrAF) { + this.windowElement.cancelAnimationFrame(this.pendingrAF); + } this.windowElement = null; this.pendingrAF = null; this.pendingElements.clear(); @@ -2843,6 +2873,7 @@ class DOMLocalization extends Localization { ;// CONCATENATED MODULE: ./web/l10n.js class L10n { #dir; + #elements = new Set(); #lang; #l10n; constructor({ @@ -2874,14 +2905,29 @@ class L10n { id: ids, args }]); - return messages?.[0].value || fallback; + return messages[0]?.value || fallback; } async translate(element) { + this.#elements.add(element); try { this.#l10n.connectRoot(element); await this.#l10n.translateRoots(); } catch {} } + async translateOnce(element) { + try { + await this.#l10n.translateElements([element]); + } catch (ex) { + console.error(`translateOnce: "${ex}".`); + } + } + async destroy() { + for (const element of this.#elements) { + this.#l10n.disconnectRoot(element); + } + this.#elements.clear(); + this.#l10n.pauseObserving(); + } pause() { this.#l10n.pauseObserving(); } @@ -2954,8 +3000,7 @@ class genericl10n_GenericL10n extends L10n { const bundle = await this.#createBundle(lang, baseURL, paths); if (bundle) { yield bundle; - } - if (lang === "en-us") { + } else if (lang === "en-us") { yield this.#createBundleFallback(lang); } } @@ -2989,7 +3034,7 @@ class genericl10n_GenericL10n extends L10n { yield this.#createBundleFallback(lang); } static async #createBundleFallback(lang) { - const text = "pdfjs-previous-button =\n .title = Previous Page\npdfjs-previous-button-label = Previous\npdfjs-next-button =\n .title = Next Page\npdfjs-next-button-label = Next\npdfjs-page-input =\n .title = Page\npdfjs-of-pages = of { $pagesCount }\npdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })\npdfjs-zoom-out-button =\n .title = Zoom Out\npdfjs-zoom-out-button-label = Zoom Out\npdfjs-zoom-in-button =\n .title = Zoom In\npdfjs-zoom-in-button-label = Zoom In\npdfjs-zoom-select =\n .title = Zoom\npdfjs-presentation-mode-button =\n .title = Switch to Presentation Mode\npdfjs-presentation-mode-button-label = Presentation Mode\npdfjs-open-file-button =\n .title = Open File\npdfjs-open-file-button-label = Open\npdfjs-print-button =\n .title = Print\npdfjs-print-button-label = Print\npdfjs-save-button =\n .title = Save\npdfjs-save-button-label = Save\npdfjs-download-button =\n .title = Download\npdfjs-download-button-label = Download\npdfjs-bookmark-button =\n .title = Current Page (View URL from Current Page)\npdfjs-bookmark-button-label = Current Page\npdfjs-tools-button =\n .title = Tools\npdfjs-tools-button-label = Tools\npdfjs-first-page-button =\n .title = Go to First Page\npdfjs-first-page-button-label = Go to First Page\npdfjs-last-page-button =\n .title = Go to Last Page\npdfjs-last-page-button-label = Go to Last Page\npdfjs-page-rotate-cw-button =\n .title = Rotate Clockwise\npdfjs-page-rotate-cw-button-label = Rotate Clockwise\npdfjs-page-rotate-ccw-button =\n .title = Rotate Counterclockwise\npdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise\npdfjs-cursor-text-select-tool-button =\n .title = Enable Text Selection Tool\npdfjs-cursor-text-select-tool-button-label = Text Selection Tool\npdfjs-cursor-hand-tool-button =\n .title = Enable Hand Tool\npdfjs-cursor-hand-tool-button-label = Hand Tool\npdfjs-scroll-page-button =\n .title = Use Page Scrolling\npdfjs-scroll-page-button-label = Page Scrolling\npdfjs-scroll-vertical-button =\n .title = Use Vertical Scrolling\npdfjs-scroll-vertical-button-label = Vertical Scrolling\npdfjs-scroll-horizontal-button =\n .title = Use Horizontal Scrolling\npdfjs-scroll-horizontal-button-label = Horizontal Scrolling\npdfjs-scroll-wrapped-button =\n .title = Use Wrapped Scrolling\npdfjs-scroll-wrapped-button-label = Wrapped Scrolling\npdfjs-spread-none-button =\n .title = Do not join page spreads\npdfjs-spread-none-button-label = No Spreads\npdfjs-spread-odd-button =\n .title = Join page spreads starting with odd-numbered pages\npdfjs-spread-odd-button-label = Odd Spreads\npdfjs-spread-even-button =\n .title = Join page spreads starting with even-numbered pages\npdfjs-spread-even-button-label = Even Spreads\npdfjs-document-properties-button =\n .title = Document Properties\u2026\npdfjs-document-properties-button-label = Document Properties\u2026\npdfjs-document-properties-file-name = File name:\npdfjs-document-properties-file-size = File size:\npdfjs-document-properties-kb = { $size_kb } KB ({ $size_b } bytes)\npdfjs-document-properties-mb = { $size_mb } MB ({ $size_b } bytes)\npdfjs-document-properties-title = Title:\npdfjs-document-properties-author = Author:\npdfjs-document-properties-subject = Subject:\npdfjs-document-properties-keywords = Keywords:\npdfjs-document-properties-creation-date = Creation Date:\npdfjs-document-properties-modification-date = Modification Date:\npdfjs-document-properties-date-string = { $date }, { $time }\npdfjs-document-properties-creator = Creator:\npdfjs-document-properties-producer = PDF Producer:\npdfjs-document-properties-version = PDF Version:\npdfjs-document-properties-page-count = Page Count:\npdfjs-document-properties-page-size = Page Size:\npdfjs-document-properties-page-size-unit-inches = in\npdfjs-document-properties-page-size-unit-millimeters = mm\npdfjs-document-properties-page-size-orientation-portrait = portrait\npdfjs-document-properties-page-size-orientation-landscape = landscape\npdfjs-document-properties-page-size-name-a-three = A3\npdfjs-document-properties-page-size-name-a-four = A4\npdfjs-document-properties-page-size-name-letter = Letter\npdfjs-document-properties-page-size-name-legal = Legal\npdfjs-document-properties-page-size-dimension-string = { $width } \xD7 { $height } { $unit } ({ $orientation })\npdfjs-document-properties-page-size-dimension-name-string = { $width } \xD7 { $height } { $unit } ({ $name }, { $orientation })\npdfjs-document-properties-linearized = Fast Web View:\npdfjs-document-properties-linearized-yes = Yes\npdfjs-document-properties-linearized-no = No\npdfjs-document-properties-close-button = Close\npdfjs-print-progress-message = Preparing document for printing\u2026\npdfjs-print-progress-percent = { $progress }%\npdfjs-print-progress-close-button = Cancel\npdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.\npdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.\npdfjs-toggle-sidebar-button =\n .title = Toggle Sidebar\npdfjs-toggle-sidebar-notification-button =\n .title = Toggle Sidebar (document contains outline/attachments/layers)\npdfjs-toggle-sidebar-button-label = Toggle Sidebar\npdfjs-document-outline-button =\n .title = Show Document Outline (double-click to expand/collapse all items)\npdfjs-document-outline-button-label = Document Outline\npdfjs-attachments-button =\n .title = Show Attachments\npdfjs-attachments-button-label = Attachments\npdfjs-layers-button =\n .title = Show Layers (double-click to reset all layers to the default state)\npdfjs-layers-button-label = Layers\npdfjs-thumbs-button =\n .title = Show Thumbnails\npdfjs-thumbs-button-label = Thumbnails\npdfjs-current-outline-item-button =\n .title = Find Current Outline Item\npdfjs-current-outline-item-button-label = Current Outline Item\npdfjs-findbar-button =\n .title = Find in Document\npdfjs-findbar-button-label = Find\npdfjs-additional-layers = Additional Layers\npdfjs-thumb-page-title =\n .title = Page { $page }\npdfjs-thumb-page-canvas =\n .aria-label = Thumbnail of Page { $page }\npdfjs-find-input =\n .title = Find\n .placeholder = Find in document\u2026\npdfjs-find-previous-button =\n .title = Find the previous occurrence of the phrase\npdfjs-find-previous-button-label = Previous\npdfjs-find-next-button =\n .title = Find the next occurrence of the phrase\npdfjs-find-next-button-label = Next\npdfjs-find-highlight-checkbox = Highlight All\npdfjs-find-match-case-checkbox-label = Match Case\npdfjs-find-match-diacritics-checkbox-label = Match Diacritics\npdfjs-find-entire-word-checkbox-label = Whole Words\npdfjs-find-reached-top = Reached top of document, continued from bottom\npdfjs-find-reached-bottom = Reached end of document, continued from top\npdfjs-find-match-count =\n { $total ->\n [one] { $current } of { $total } match\n *[other] { $current } of { $total } matches\n }\npdfjs-find-match-count-limit =\n { $limit ->\n [one] More than { $limit } match\n *[other] More than { $limit } matches\n }\npdfjs-find-not-found = Phrase not found\npdfjs-page-scale-width = Page Width\npdfjs-page-scale-fit = Page Fit\npdfjs-page-scale-auto = Automatic Zoom\npdfjs-page-scale-actual = Actual Size\npdfjs-page-scale-percent = { $scale }%\npdfjs-page-landmark =\n .aria-label = Page { $page }\npdfjs-loading-error = An error occurred while loading the PDF.\npdfjs-invalid-file-error = Invalid or corrupted PDF file.\npdfjs-missing-file-error = Missing PDF file.\npdfjs-unexpected-response-error = Unexpected server response.\npdfjs-rendering-error = An error occurred while rendering the page.\npdfjs-annotation-date-string = { $date }, { $time }\npdfjs-text-annotation-type =\n .alt = [{ $type } Annotation]\npdfjs-password-label = Enter the password to open this PDF file.\npdfjs-password-invalid = Invalid password. Please try again.\npdfjs-password-ok-button = OK\npdfjs-password-cancel-button = Cancel\npdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.\npdfjs-editor-free-text-button =\n .title = Text\npdfjs-editor-free-text-button-label = Text\npdfjs-editor-ink-button =\n .title = Draw\npdfjs-editor-ink-button-label = Draw\npdfjs-editor-stamp-button =\n .title = Add or edit images\npdfjs-editor-stamp-button-label = Add or edit images\npdfjs-editor-highlight-button =\n .title = Highlight\npdfjs-editor-highlight-button-label = Highlight\npdfjs-highlight-floating-button1 =\n .title = Highlight\n .aria-label = Highlight\npdfjs-highlight-floating-button-label = Highlight\npdfjs-editor-remove-ink-button =\n .title = Remove drawing\npdfjs-editor-remove-freetext-button =\n .title = Remove text\npdfjs-editor-remove-stamp-button =\n .title = Remove image\npdfjs-editor-remove-highlight-button =\n .title = Remove highlight\npdfjs-editor-free-text-color-input = Color\npdfjs-editor-free-text-size-input = Size\npdfjs-editor-ink-color-input = Color\npdfjs-editor-ink-thickness-input = Thickness\npdfjs-editor-ink-opacity-input = Opacity\npdfjs-editor-stamp-add-image-button =\n .title = Add image\npdfjs-editor-stamp-add-image-button-label = Add image\npdfjs-editor-free-highlight-thickness-input = Thickness\npdfjs-editor-free-highlight-thickness-title =\n .title = Change thickness when highlighting items other than text\npdfjs-free-text =\n .aria-label = Text Editor\npdfjs-free-text-default-content = Start typing\u2026\npdfjs-ink =\n .aria-label = Draw Editor\npdfjs-ink-canvas =\n .aria-label = User-created image\npdfjs-editor-alt-text-button-label = Alt text\npdfjs-editor-alt-text-edit-button-label = Edit alt text\npdfjs-editor-alt-text-dialog-label = Choose an option\npdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can\u2019t see the image or when it doesn\u2019t load.\npdfjs-editor-alt-text-add-description-label = Add a description\npdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.\npdfjs-editor-alt-text-mark-decorative-label = Mark as decorative\npdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.\npdfjs-editor-alt-text-cancel-button = Cancel\npdfjs-editor-alt-text-save-button = Save\npdfjs-editor-alt-text-decorative-tooltip = Marked as decorative\npdfjs-editor-alt-text-textarea =\n .placeholder = For example, \u201CA young man sits down at a table to eat a meal\u201D\npdfjs-editor-resizer-label-top-left = Top left corner \u2014 resize\npdfjs-editor-resizer-label-top-middle = Top middle \u2014 resize\npdfjs-editor-resizer-label-top-right = Top right corner \u2014 resize\npdfjs-editor-resizer-label-middle-right = Middle right \u2014 resize\npdfjs-editor-resizer-label-bottom-right = Bottom right corner \u2014 resize\npdfjs-editor-resizer-label-bottom-middle = Bottom middle \u2014 resize\npdfjs-editor-resizer-label-bottom-left = Bottom left corner \u2014 resize\npdfjs-editor-resizer-label-middle-left = Middle left \u2014 resize\npdfjs-editor-highlight-colorpicker-label = Highlight color\npdfjs-editor-colorpicker-button =\n .title = Change color\npdfjs-editor-colorpicker-dropdown =\n .aria-label = Color choices\npdfjs-editor-colorpicker-yellow =\n .title = Yellow\npdfjs-editor-colorpicker-green =\n .title = Green\npdfjs-editor-colorpicker-blue =\n .title = Blue\npdfjs-editor-colorpicker-pink =\n .title = Pink\npdfjs-editor-colorpicker-red =\n .title = Red\npdfjs-editor-highlight-show-all-button-label = Show all\npdfjs-editor-highlight-show-all-button =\n .title = Show all"; + const text = "pdfjs-previous-button =\n .title = Previous Page\npdfjs-previous-button-label = Previous\npdfjs-next-button =\n .title = Next Page\npdfjs-next-button-label = Next\npdfjs-page-input =\n .title = Page\npdfjs-of-pages = of { $pagesCount }\npdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })\npdfjs-zoom-out-button =\n .title = Zoom Out\npdfjs-zoom-out-button-label = Zoom Out\npdfjs-zoom-in-button =\n .title = Zoom In\npdfjs-zoom-in-button-label = Zoom In\npdfjs-zoom-select =\n .title = Zoom\npdfjs-presentation-mode-button =\n .title = Switch to Presentation Mode\npdfjs-presentation-mode-button-label = Presentation Mode\npdfjs-open-file-button =\n .title = Open File\npdfjs-open-file-button-label = Open\npdfjs-print-button =\n .title = Print\npdfjs-print-button-label = Print\npdfjs-save-button =\n .title = Save\npdfjs-save-button-label = Save\npdfjs-download-button =\n .title = Download\npdfjs-download-button-label = Download\npdfjs-bookmark-button =\n .title = Current Page (View URL from Current Page)\npdfjs-bookmark-button-label = Current Page\npdfjs-tools-button =\n .title = Tools\npdfjs-tools-button-label = Tools\npdfjs-first-page-button =\n .title = Go to First Page\npdfjs-first-page-button-label = Go to First Page\npdfjs-last-page-button =\n .title = Go to Last Page\npdfjs-last-page-button-label = Go to Last Page\npdfjs-page-rotate-cw-button =\n .title = Rotate Clockwise\npdfjs-page-rotate-cw-button-label = Rotate Clockwise\npdfjs-page-rotate-ccw-button =\n .title = Rotate Counterclockwise\npdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise\npdfjs-cursor-text-select-tool-button =\n .title = Enable Text Selection Tool\npdfjs-cursor-text-select-tool-button-label = Text Selection Tool\npdfjs-cursor-hand-tool-button =\n .title = Enable Hand Tool\npdfjs-cursor-hand-tool-button-label = Hand Tool\npdfjs-scroll-page-button =\n .title = Use Page Scrolling\npdfjs-scroll-page-button-label = Page Scrolling\npdfjs-scroll-vertical-button =\n .title = Use Vertical Scrolling\npdfjs-scroll-vertical-button-label = Vertical Scrolling\npdfjs-scroll-horizontal-button =\n .title = Use Horizontal Scrolling\npdfjs-scroll-horizontal-button-label = Horizontal Scrolling\npdfjs-scroll-wrapped-button =\n .title = Use Wrapped Scrolling\npdfjs-scroll-wrapped-button-label = Wrapped Scrolling\npdfjs-spread-none-button =\n .title = Do not join page spreads\npdfjs-spread-none-button-label = No Spreads\npdfjs-spread-odd-button =\n .title = Join page spreads starting with odd-numbered pages\npdfjs-spread-odd-button-label = Odd Spreads\npdfjs-spread-even-button =\n .title = Join page spreads starting with even-numbered pages\npdfjs-spread-even-button-label = Even Spreads\npdfjs-document-properties-button =\n .title = Document Properties\u2026\npdfjs-document-properties-button-label = Document Properties\u2026\npdfjs-document-properties-file-name = File name:\npdfjs-document-properties-file-size = File size:\npdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)\npdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)\npdfjs-document-properties-title = Title:\npdfjs-document-properties-author = Author:\npdfjs-document-properties-subject = Subject:\npdfjs-document-properties-keywords = Keywords:\npdfjs-document-properties-creation-date = Creation Date:\npdfjs-document-properties-modification-date = Modification Date:\npdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: \"short\", timeStyle: \"medium\") }\npdfjs-document-properties-creator = Creator:\npdfjs-document-properties-producer = PDF Producer:\npdfjs-document-properties-version = PDF Version:\npdfjs-document-properties-page-count = Page Count:\npdfjs-document-properties-page-size = Page Size:\npdfjs-document-properties-page-size-unit-inches = in\npdfjs-document-properties-page-size-unit-millimeters = mm\npdfjs-document-properties-page-size-orientation-portrait = portrait\npdfjs-document-properties-page-size-orientation-landscape = landscape\npdfjs-document-properties-page-size-name-a-three = A3\npdfjs-document-properties-page-size-name-a-four = A4\npdfjs-document-properties-page-size-name-letter = Letter\npdfjs-document-properties-page-size-name-legal = Legal\npdfjs-document-properties-page-size-dimension-string = { $width } \xD7 { $height } { $unit } ({ $orientation })\npdfjs-document-properties-page-size-dimension-name-string = { $width } \xD7 { $height } { $unit } ({ $name }, { $orientation })\npdfjs-document-properties-linearized = Fast Web View:\npdfjs-document-properties-linearized-yes = Yes\npdfjs-document-properties-linearized-no = No\npdfjs-document-properties-close-button = Close\npdfjs-print-progress-message = Preparing document for printing\u2026\npdfjs-print-progress-percent = { $progress }%\npdfjs-print-progress-close-button = Cancel\npdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.\npdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.\npdfjs-toggle-sidebar-button =\n .title = Toggle Sidebar\npdfjs-toggle-sidebar-notification-button =\n .title = Toggle Sidebar (document contains outline/attachments/layers)\npdfjs-toggle-sidebar-button-label = Toggle Sidebar\npdfjs-document-outline-button =\n .title = Show Document Outline (double-click to expand/collapse all items)\npdfjs-document-outline-button-label = Document Outline\npdfjs-attachments-button =\n .title = Show Attachments\npdfjs-attachments-button-label = Attachments\npdfjs-layers-button =\n .title = Show Layers (double-click to reset all layers to the default state)\npdfjs-layers-button-label = Layers\npdfjs-thumbs-button =\n .title = Show Thumbnails\npdfjs-thumbs-button-label = Thumbnails\npdfjs-current-outline-item-button =\n .title = Find Current Outline Item\npdfjs-current-outline-item-button-label = Current Outline Item\npdfjs-findbar-button =\n .title = Find in Document\npdfjs-findbar-button-label = Find\npdfjs-additional-layers = Additional Layers\npdfjs-thumb-page-title =\n .title = Page { $page }\npdfjs-thumb-page-canvas =\n .aria-label = Thumbnail of Page { $page }\npdfjs-find-input =\n .title = Find\n .placeholder = Find in document\u2026\npdfjs-find-previous-button =\n .title = Find the previous occurrence of the phrase\npdfjs-find-previous-button-label = Previous\npdfjs-find-next-button =\n .title = Find the next occurrence of the phrase\npdfjs-find-next-button-label = Next\npdfjs-find-highlight-checkbox = Highlight All\npdfjs-find-match-case-checkbox-label = Match Case\npdfjs-find-match-diacritics-checkbox-label = Match Diacritics\npdfjs-find-entire-word-checkbox-label = Whole Words\npdfjs-find-reached-top = Reached top of document, continued from bottom\npdfjs-find-reached-bottom = Reached end of document, continued from top\npdfjs-find-match-count =\n { $total ->\n [one] { $current } of { $total } match\n *[other] { $current } of { $total } matches\n }\npdfjs-find-match-count-limit =\n { $limit ->\n [one] More than { $limit } match\n *[other] More than { $limit } matches\n }\npdfjs-find-not-found = Phrase not found\npdfjs-page-scale-width = Page Width\npdfjs-page-scale-fit = Page Fit\npdfjs-page-scale-auto = Automatic Zoom\npdfjs-page-scale-actual = Actual Size\npdfjs-page-scale-percent = { $scale }%\npdfjs-page-landmark =\n .aria-label = Page { $page }\npdfjs-loading-error = An error occurred while loading the PDF.\npdfjs-invalid-file-error = Invalid or corrupted PDF file.\npdfjs-missing-file-error = Missing PDF file.\npdfjs-unexpected-response-error = Unexpected server response.\npdfjs-rendering-error = An error occurred while rendering the page.\npdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: \"short\", timeStyle: \"medium\") }\npdfjs-text-annotation-type =\n .alt = [{ $type } Annotation]\npdfjs-password-label = Enter the password to open this PDF file.\npdfjs-password-invalid = Invalid password. Please try again.\npdfjs-password-ok-button = OK\npdfjs-password-cancel-button = Cancel\npdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.\npdfjs-editor-free-text-button =\n .title = Text\npdfjs-editor-free-text-button-label = Text\npdfjs-editor-ink-button =\n .title = Draw\npdfjs-editor-ink-button-label = Draw\npdfjs-editor-stamp-button =\n .title = Add or edit images\npdfjs-editor-stamp-button-label = Add or edit images\npdfjs-editor-highlight-button =\n .title = Highlight\npdfjs-editor-highlight-button-label = Highlight\npdfjs-highlight-floating-button1 =\n .title = Highlight\n .aria-label = Highlight\npdfjs-highlight-floating-button-label = Highlight\npdfjs-editor-remove-ink-button =\n .title = Remove drawing\npdfjs-editor-remove-freetext-button =\n .title = Remove text\npdfjs-editor-remove-stamp-button =\n .title = Remove image\npdfjs-editor-remove-highlight-button =\n .title = Remove highlight\npdfjs-editor-free-text-color-input = Color\npdfjs-editor-free-text-size-input = Size\npdfjs-editor-ink-color-input = Color\npdfjs-editor-ink-thickness-input = Thickness\npdfjs-editor-ink-opacity-input = Opacity\npdfjs-editor-stamp-add-image-button =\n .title = Add image\npdfjs-editor-stamp-add-image-button-label = Add image\npdfjs-editor-free-highlight-thickness-input = Thickness\npdfjs-editor-free-highlight-thickness-title =\n .title = Change thickness when highlighting items other than text\npdfjs-free-text =\n .aria-label = Text Editor\npdfjs-free-text-default-content = Start typing\u2026\npdfjs-ink =\n .aria-label = Draw Editor\npdfjs-ink-canvas =\n .aria-label = User-created image\npdfjs-editor-alt-text-button-label = Alt text\npdfjs-editor-alt-text-edit-button-label = Edit alt text\npdfjs-editor-alt-text-dialog-label = Choose an option\npdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can\u2019t see the image or when it doesn\u2019t load.\npdfjs-editor-alt-text-add-description-label = Add a description\npdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.\npdfjs-editor-alt-text-mark-decorative-label = Mark as decorative\npdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.\npdfjs-editor-alt-text-cancel-button = Cancel\npdfjs-editor-alt-text-save-button = Save\npdfjs-editor-alt-text-decorative-tooltip = Marked as decorative\npdfjs-editor-alt-text-textarea =\n .placeholder = For example, \u201CA young man sits down at a table to eat a meal\u201D\npdfjs-editor-resizer-top-left =\n .aria-label = Top left corner \u2014 resize\npdfjs-editor-resizer-top-middle =\n .aria-label = Top middle \u2014 resize\npdfjs-editor-resizer-top-right =\n .aria-label = Top right corner \u2014 resize\npdfjs-editor-resizer-middle-right =\n .aria-label = Middle right \u2014 resize\npdfjs-editor-resizer-bottom-right =\n .aria-label = Bottom right corner \u2014 resize\npdfjs-editor-resizer-bottom-middle =\n .aria-label = Bottom middle \u2014 resize\npdfjs-editor-resizer-bottom-left =\n .aria-label = Bottom left corner \u2014 resize\npdfjs-editor-resizer-middle-left =\n .aria-label = Middle left \u2014 resize\npdfjs-editor-highlight-colorpicker-label = Highlight color\npdfjs-editor-colorpicker-button =\n .title = Change color\npdfjs-editor-colorpicker-dropdown =\n .aria-label = Color choices\npdfjs-editor-colorpicker-yellow =\n .title = Yellow\npdfjs-editor-colorpicker-green =\n .title = Green\npdfjs-editor-colorpicker-blue =\n .title = Blue\npdfjs-editor-colorpicker-pink =\n .title = Pink\npdfjs-editor-colorpicker-red =\n .title = Red\npdfjs-editor-highlight-show-all-button-label = Show all\npdfjs-editor-highlight-show-all-button =\n .title = Show all\npdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)\npdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)\npdfjs-editor-new-alt-text-textarea =\n .placeholder = Write your description here\u2026\npdfjs-editor-new-alt-text-description = Short description for people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.\npdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more\npdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically\npdfjs-editor-new-alt-text-not-now-button = Not now\npdfjs-editor-new-alt-text-error-title = Couldn\u2019t create alt text automatically\npdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.\npdfjs-editor-new-alt-text-error-close-button = Close\npdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\n .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)\npdfjs-editor-new-alt-text-added-button-label = Alt text added\npdfjs-editor-new-alt-text-missing-button-label = Missing alt text\npdfjs-editor-new-alt-text-to-review-button-label = Review alt text\npdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }\npdfjs-image-alt-text-settings-button =\n .title = Image alt text settings\npdfjs-image-alt-text-settings-button-label = Image alt text settings\npdfjs-editor-alt-text-settings-dialog-label = Image alt text settings\npdfjs-editor-alt-text-settings-automatic-title = Automatic alt text\npdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically\npdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can\u2019t see the image or when the image doesn\u2019t load.\npdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)\npdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.\npdfjs-editor-alt-text-settings-delete-model-button = Delete\npdfjs-editor-alt-text-settings-download-model-button = Download\npdfjs-editor-alt-text-settings-downloading-model-button = Downloading\u2026\npdfjs-editor-alt-text-settings-editor-title = Alt text editor\npdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image\npdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.\npdfjs-editor-alt-text-settings-close-button = Close"; return createBundle(lang, text); } } @@ -3064,16 +3109,638 @@ class Preferences extends BasePreferences { } class ExternalServices extends BaseExternalServices { async createL10n() { - return new genericl10n_GenericL10n(AppOptions.get("locale")); + return new genericl10n_GenericL10n(AppOptions.get("localeProperties")?.lang); } createScripting() { return new GenericScripting(AppOptions.get("sandboxBundleSrc")); } } class MLManager { - async guess() { + async isEnabledFor(_name) { + return false; + } + async deleteModel(_service) { + return null; + } + isReady(_name) { + return false; + } + guess(_data) {} + toggleService(_name, _enabled) {} + static getFakeMLManager(options) { + return new FakeMLManager(options); + } +} +class FakeMLManager { + eventBus = null; + hasProgress = false; + constructor({ + enableGuessAltText, + enableAltTextModelDownload + }) { + this.enableGuessAltText = enableGuessAltText; + this.enableAltTextModelDownload = enableAltTextModelDownload; + } + setEventBus(eventBus, abortSignal) { + this.eventBus = eventBus; + } + async isEnabledFor(_name) { + return this.enableGuessAltText; + } + async deleteModel(_name) { + this.enableAltTextModelDownload = false; return null; } + async loadModel(_name) {} + async downloadModel(_name) { + this.hasProgress = true; + const { + promise, + resolve + } = Promise.withResolvers(); + const total = 1e8; + const end = 1.5 * total; + const increment = 5e6; + let loaded = 0; + const id = setInterval(() => { + loaded += increment; + if (loaded <= end) { + this.eventBus.dispatch("loadaiengineprogress", { + source: this, + detail: { + total, + totalLoaded: loaded, + finished: loaded + increment >= end + } + }); + return; + } + clearInterval(id); + this.hasProgress = false; + this.enableAltTextModelDownload = true; + resolve(true); + }, 900); + return promise; + } + isReady(_name) { + return this.enableAltTextModelDownload; + } + guess({ + request: { + data + } + }) { + return new Promise(resolve => { + setTimeout(() => { + resolve(data ? { + output: "Fake alt text" + } : { + error: true + }); + }, 3000); + }); + } + toggleService(_name, enabled) { + this.enableGuessAltText = enabled; + } +} + +;// CONCATENATED MODULE: ./web/new_alt_text_manager.js + +class NewAltTextManager { + #boundCancel = this.#cancel.bind(this); + #createAutomaticallyButton; + #currentEditor = null; + #cancelButton; + #descriptionContainer; + #dialog; + #disclaimer; + #downloadModel; + #downloadModelDescription; + #eventBus; + #firstTime = false; + #guessedAltText; + #isEditing = null; + #imagePreview; + #imageData; + #isAILoading = false; + #wasAILoading = false; + #learnMore; + #notNowButton; + #overlayManager; + #textarea; + #title; + #uiManager; + #previousAltText = null; + constructor({ + descriptionContainer, + dialog, + imagePreview, + cancelButton, + disclaimer, + notNowButton, + saveButton, + textarea, + learnMore, + errorCloseButton, + createAutomaticallyButton, + downloadModel, + downloadModelDescription, + title + }, overlayManager, eventBus) { + this.#cancelButton = cancelButton; + this.#createAutomaticallyButton = createAutomaticallyButton; + this.#descriptionContainer = descriptionContainer; + this.#dialog = dialog; + this.#disclaimer = disclaimer; + this.#notNowButton = notNowButton; + this.#imagePreview = imagePreview; + this.#textarea = textarea; + this.#learnMore = learnMore; + this.#title = title; + this.#downloadModel = downloadModel; + this.#downloadModelDescription = downloadModelDescription; + this.#overlayManager = overlayManager; + this.#eventBus = eventBus; + dialog.addEventListener("close", this.#close.bind(this)); + dialog.addEventListener("contextmenu", event => { + if (event.target !== this.#textarea) { + event.preventDefault(); + } + }); + cancelButton.addEventListener("click", this.#boundCancel); + notNowButton.addEventListener("click", this.#boundCancel); + saveButton.addEventListener("click", this.#save.bind(this)); + errorCloseButton.addEventListener("click", () => { + this.#toggleError(false); + }); + createAutomaticallyButton.addEventListener("click", async () => { + const checked = createAutomaticallyButton.getAttribute("aria-pressed") !== "true"; + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.ai_generation_check", + data: { + status: checked + } + }); + if (this.#uiManager) { + this.#uiManager.setPreference("enableGuessAltText", checked); + await this.#uiManager.mlManager.toggleService("altText", checked); + } + this.#toggleGuessAltText(checked, false); + }); + textarea.addEventListener("focus", () => { + this.#wasAILoading = this.#isAILoading; + this.#toggleLoading(false); + }); + textarea.addEventListener("blur", () => { + if (textarea.value) { + return; + } + this.#toggleLoading(this.#wasAILoading); + }); + textarea.addEventListener("input", () => { + this.#toggleTitle(); + this.#toggleDisclaimer(); + }); + eventBus._on("enableguessalttext", ({ + value + }) => { + this.#toggleGuessAltText(value, false); + }); + this.#overlayManager.register(dialog); + this.#learnMore.addEventListener("click", () => { + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.info", + data: { + topic: "alt_text" + } + }); + }); + } + #toggleLoading(value) { + if (!this.#uiManager || this.#isAILoading === value) { + return; + } + this.#isAILoading = value; + this.#descriptionContainer.classList.toggle("loading", value); + } + #toggleError(value) { + if (!this.#uiManager) { + return; + } + this.#dialog.classList.toggle("error", value); + } + #toggleTitle() { + const isEditing = this.#isAILoading || !!this.#textarea.value; + if (this.#isEditing === isEditing) { + return; + } + this.#isEditing = isEditing; + this.#title.setAttribute("data-l10n-id", isEditing ? "pdfjs-editor-new-alt-text-dialog-edit-label" : "pdfjs-editor-new-alt-text-dialog-add-label"); + } + async #toggleGuessAltText(value, isInitial = false) { + if (!this.#uiManager) { + return; + } + this.#dialog.classList.toggle("aiDisabled", !value); + this.#createAutomaticallyButton.setAttribute("aria-pressed", value); + if (value) { + const { + altTextLearnMoreUrl + } = this.#uiManager.mlManager; + if (altTextLearnMoreUrl) { + this.#learnMore.href = altTextLearnMoreUrl; + } + this.#mlGuessAltText(isInitial); + } else { + this.#toggleLoading(false); + this.#isAILoading = false; + this.#toggleTitle(); + this.#toggleDisclaimer(); + } + } + #toggleNotNow() { + this.#notNowButton.classList.toggle("hidden", !this.#firstTime); + this.#cancelButton.classList.toggle("hidden", this.#firstTime); + } + #toggleAI(value) { + this.#dialog.classList.toggle("noAi", !value); + this.#toggleTitle(); + } + #toggleDisclaimer(value = null) { + if (!this.#uiManager) { + return; + } + const hidden = value === null ? !this.#guessedAltText || this.#guessedAltText !== this.#textarea.value : !value; + this.#disclaimer.classList.toggle("hidden", hidden); + } + async #mlGuessAltText(isInitial) { + if (this.#isAILoading) { + return; + } + if (this.#textarea.value) { + return; + } + if (isInitial && this.#previousAltText !== null) { + return; + } + this.#guessedAltText = this.#currentEditor.guessedAltText; + if (this.#previousAltText === null && this.#guessedAltText) { + this.#addAltText(this.#guessedAltText); + this.#toggleDisclaimer(); + this.#toggleTitle(); + return; + } + this.#toggleLoading(true); + this.#toggleTitle(); + this.#toggleDisclaimer(true); + let hasError = false; + try { + const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false); + if (altText) { + this.#guessedAltText = altText; + this.#wasAILoading = this.#isAILoading; + if (this.#isAILoading) { + this.#addAltText(altText); + } + } + } catch (e) { + console.error(e); + hasError = true; + } + this.#toggleLoading(false); + if (hasError && this.#uiManager) { + this.#toggleError(true); + this.#toggleTitle(); + this.#toggleDisclaimer(); + } + } + #addAltText(altText) { + if (!this.#uiManager || this.#textarea.value) { + return; + } + this.#textarea.value = altText; + } + #setProgress() { + this.#downloadModel.classList.toggle("hidden", false); + const callback = async ({ + detail: { + finished, + total, + totalLoaded + } + }) => { + const ONE_MEGA_BYTES = 1e6; + totalLoaded = Math.min(0.99 * total, totalLoaded); + const totalSize = this.#downloadModelDescription.ariaValueMax = Math.round(total / ONE_MEGA_BYTES); + const downloadedSize = this.#downloadModelDescription.ariaValueNow = Math.round(totalLoaded / ONE_MEGA_BYTES); + this.#downloadModelDescription.setAttribute("data-l10n-args", JSON.stringify({ + totalSize, + downloadedSize + })); + if (!finished) { + return; + } + this.#eventBus._off("loadaiengineprogress", callback); + this.#downloadModel.classList.toggle("hidden", true); + this.#toggleAI(true); + if (!this.#uiManager) { + return; + } + const { + mlManager + } = this.#uiManager; + mlManager.toggleService("altText", true); + this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true); + }; + this.#eventBus._on("loadaiengineprogress", callback); + } + async editAltText(uiManager, editor, firstTime) { + if (this.#currentEditor || !editor) { + return; + } + if (firstTime && editor.hasAltTextData()) { + editor.altTextFinish(); + return; + } + this.#firstTime = firstTime; + let { + mlManager + } = uiManager; + let hasAI = !!mlManager; + if (mlManager && !mlManager.isReady("altText")) { + hasAI = false; + if (mlManager.hasProgress) { + this.#setProgress(); + } else { + mlManager = null; + } + } else { + this.#downloadModel.classList.toggle("hidden", true); + } + const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText"); + this.#currentEditor = editor; + this.#uiManager = uiManager; + this.#uiManager.removeEditListeners(); + ({ + altText: this.#previousAltText + } = editor.altTextData); + this.#textarea.value = this.#previousAltText ?? ""; + const AI_MAX_IMAGE_DIMENSION = 224; + let canvas; + if (mlManager) { + ({ + canvas, + imageData: this.#imageData + } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, true)); + if (hasAI) { + this.#toggleGuessAltText(await isAltTextEnabledPromise, true); + } + } else { + ({ + canvas + } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, false)); + } + canvas.setAttribute("role", "presentation"); + this.#imagePreview.append(canvas); + this.#toggleNotNow(); + this.#toggleAI(hasAI); + this.#toggleError(false); + try { + await this.#overlayManager.open(this.#dialog); + } catch (ex) { + this.#close(); + throw ex; + } + } + #cancel() { + this.#currentEditor.altTextData = { + cancel: true + }; + const altText = this.#textarea.value.trim(); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.dismiss", + data: { + alt_text_type: altText ? "present" : "empty", + flow: this.#firstTime ? "image_add" : "alt_text_edit" + } + }); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.image_added", + data: { + alt_text_modal: true, + alt_text_type: "skipped" + } + }); + this.#finish(); + } + #finish() { + if (this.#overlayManager.active === this.#dialog) { + this.#overlayManager.close(this.#dialog); + } + } + #close() { + const canvas = this.#imagePreview.firstChild; + canvas.remove(); + canvas.width = canvas.height = 0; + this.#imageData = null; + this.#toggleLoading(false); + this.#uiManager?.addEditListeners(); + this.#currentEditor.altTextFinish(); + this.#uiManager?.setSelected(this.#currentEditor); + this.#currentEditor = null; + this.#uiManager = null; + } + #save() { + const altText = this.#textarea.value.trim(); + this.#currentEditor.altTextData = { + altText, + decorative: false + }; + this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText; + if (this.#guessedAltText && this.#guessedAltText !== altText) { + const guessedWords = new Set(this.#guessedAltText.split(/\s+/)); + const words = new Set(altText.split(/\s+/)); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.user_edit", + data: { + total_words: guessedWords.size, + words_removed: guessedWords.difference(words).size, + words_added: words.difference(guessedWords).size + } + }); + } + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.image_added", + data: { + alt_text_modal: true, + alt_text_type: altText ? "present" : "empty" + } + }); + this.#currentEditor._reportTelemetry({ + action: "pdfjs.image.alt_text.save", + data: { + alt_text_type: altText ? "present" : "empty", + flow: this.#firstTime ? "image_add" : "alt_text_edit" + } + }); + this.#finish(); + } + destroy() { + this.#uiManager = null; + this.#finish(); + } +} +class ImageAltTextSettings { + #aiModelSettings; + #createModelButton; + #downloadModelButton; + #dialog; + #eventBus; + #mlManager; + #overlayManager; + #showAltTextDialogButton; + constructor({ + dialog, + createModelButton, + aiModelSettings, + learnMore, + closeButton, + deleteModelButton, + downloadModelButton, + showAltTextDialogButton + }, overlayManager, eventBus, mlManager) { + this.#dialog = dialog; + this.#aiModelSettings = aiModelSettings; + this.#createModelButton = createModelButton; + this.#downloadModelButton = downloadModelButton; + this.#showAltTextDialogButton = showAltTextDialogButton; + this.#overlayManager = overlayManager; + this.#eventBus = eventBus; + this.#mlManager = mlManager; + const { + altTextLearnMoreUrl + } = mlManager; + if (altTextLearnMoreUrl) { + learnMore.href = altTextLearnMoreUrl; + } + dialog.addEventListener("contextmenu", noContextMenu); + createModelButton.addEventListener("click", async e => { + const checked = this.#togglePref("enableGuessAltText", e); + await mlManager.toggleService("altText", checked); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_ai_generation_check", + data: { + status: checked + } + }); + }); + showAltTextDialogButton.addEventListener("click", e => { + const checked = this.#togglePref("enableNewAltTextWhenAddingImage", e); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_edit_alt_text_check", + data: { + status: checked + } + }); + }); + deleteModelButton.addEventListener("click", this.#delete.bind(this, true)); + downloadModelButton.addEventListener("click", this.#download.bind(this, true)); + closeButton.addEventListener("click", this.#finish.bind(this)); + learnMore.addEventListener("click", () => { + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.info", + data: { + topic: "ai_generation" + } + }); + }); + eventBus._on("enablealttextmodeldownload", ({ + value + }) => { + if (value) { + this.#download(false); + } else { + this.#delete(false); + } + }); + this.#overlayManager.register(dialog); + } + #reportTelemetry(data) { + this.#eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + data + } + }); + } + async #download(isFromUI = false) { + if (isFromUI) { + this.#downloadModelButton.disabled = true; + const span = this.#downloadModelButton.firstChild; + span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-downloading-model-button"); + await this.#mlManager.downloadModel("altText"); + span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-download-model-button"); + this.#createModelButton.disabled = false; + this.#setPref("enableGuessAltText", true); + this.#mlManager.toggleService("altText", true); + this.#setPref("enableAltTextModelDownload", true); + this.#downloadModelButton.disabled = false; + } + this.#aiModelSettings.classList.toggle("download", false); + this.#createModelButton.setAttribute("aria-pressed", true); + } + async #delete(isFromUI = false) { + if (isFromUI) { + await this.#mlManager.deleteModel("altText"); + this.#setPref("enableGuessAltText", false); + this.#setPref("enableAltTextModelDownload", false); + } + this.#aiModelSettings.classList.toggle("download", true); + this.#createModelButton.disabled = true; + this.#createModelButton.setAttribute("aria-pressed", false); + } + async open({ + enableGuessAltText, + enableNewAltTextWhenAddingImage + }) { + const { + enableAltTextModelDownload + } = this.#mlManager; + this.#createModelButton.disabled = !enableAltTextModelDownload; + this.#createModelButton.setAttribute("aria-pressed", enableAltTextModelDownload && enableGuessAltText); + this.#showAltTextDialogButton.setAttribute("aria-pressed", enableNewAltTextWhenAddingImage); + this.#aiModelSettings.classList.toggle("download", !enableAltTextModelDownload); + await this.#overlayManager.open(this.#dialog); + this.#reportTelemetry({ + type: "stamp", + action: "pdfjs.image.alt_text.settings_displayed" + }); + } + #togglePref(name, { + target + }) { + const checked = target.getAttribute("aria-pressed") !== "true"; + this.#setPref(name, checked); + target.setAttribute("aria-pressed", checked); + return checked; + } + #setPref(name, value) { + this.#eventBus.dispatch("setpreference", { + source: this, + name, + value + }); + } + #finish() { + if (this.#overlayManager.active === this.#dialog) { + this.#overlayManager.close(this.#dialog); + } + } } ;// CONCATENATED MODULE: ./web/alt_text_manager.js @@ -3363,6 +4030,15 @@ class AnnotationEditorParams { dispatchEvent("INK_OPACITY", this.valueAsNumber); }); editorStampAddImage.addEventListener("click", () => { + this.eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + data: { + action: "pdfjs.image.add_image_click" + } + } + }); dispatchEvent("CREATE"); }); editorFreeHighlightThickness.addEventListener("input", function () { @@ -3625,13 +4301,6 @@ function download(blobUrl, filename) { } class DownloadManager { #openBlobUrls = new WeakMap(); - downloadUrl(url, filename, _options) { - if (!createValidAbsoluteUrl(url, "http://example.com")) { - console.error(`downloadUrl - not a valid URL: ${url}`); - return; - } - download(url + "#pdfjs.action=download", filename); - } downloadData(data, filename, contentType) { const blobUrl = URL.createObjectURL(new Blob([data], { type: contentType @@ -3666,8 +4335,19 @@ class DownloadManager { this.downloadData(data, filename, contentType); return false; } - download(blob, url, filename, _options) { - const blobUrl = URL.createObjectURL(blob); + download(data, url, filename) { + let blobUrl; + if (data) { + blobUrl = URL.createObjectURL(new Blob([data], { + type: "application/pdf" + })); + } else { + if (!createValidAbsoluteUrl(url, "http://example.com")) { + console.error(`download - not a valid URL: ${url}`); + return; + } + blobUrl = url + "#pdfjs.action=download"; + } download(blobUrl, filename); } } @@ -3757,7 +4437,7 @@ class PasswordPrompt { if (!this._isViewerEmbedded || passwordIncorrect) { this.input.focus(); } - this.label.setAttribute("data-l10n-id", `pdfjs-password-${passwordIncorrect ? "invalid" : "label"}`); + this.label.setAttribute("data-l10n-id", passwordIncorrect ? "pdfjs-password-invalid" : "pdfjs-password-label"); } async close() { if (this.overlayManager.active === this.dialog) { @@ -3798,9 +4478,6 @@ const TREEITEM_OFFSET_TOP = -100; const TREEITEM_SELECTED_CLASS = "selected"; class BaseTreeViewer { constructor(options) { - if (this.constructor === BaseTreeViewer) { - throw new Error("Cannot initialize BaseTreeViewer."); - } this.container = options.container; this.eventBus = options.eventBus; this._l10n = options.l10n; @@ -4193,15 +4870,14 @@ class PDFCursorTools { ;// CONCATENATED MODULE: ./web/pdf_document_properties.js -const DEFAULT_FIELD_CONTENT = "-"; const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"]; const US_PAGE_NAMES = { - "8.5x11": "letter", - "8.5x14": "legal" + "8.5x11": "pdfjs-document-properties-page-size-name-letter", + "8.5x14": "pdfjs-document-properties-page-size-name-legal" }; const METRIC_PAGE_NAMES = { - "297x420": "a-three", - "210x297": "a-four" + "297x420": "pdfjs-document-properties-page-size-name-a-three", + "210x297": "pdfjs-document-properties-page-size-name-a-four" }; function getPageName(size, isPortrait, pageNames) { const width = isPortrait ? size.width : size.height; @@ -4229,7 +4905,6 @@ class PDFDocumentProperties { eventBus._on("rotationchanging", evt => { this._pagesRotation = evt.pagesRotation; }); - this._isNonMetricLocale = NON_METRIC_LOCALES.includes(l10n.getLanguage()); } async open() { await Promise.all([this.overlayManager.open(this.dialog), this._dataAvailableCapability.promise]); @@ -4282,7 +4957,7 @@ class PDFDocumentProperties { setDocument(pdfDocument) { if (this.pdfDocument) { this.#reset(); - this.#updateUI(true); + this.#updateUI(); } if (!pdfDocument) { return; @@ -4297,32 +4972,23 @@ class PDFDocumentProperties { this._currentPageNumber = 1; this._pagesRotation = 0; } - #updateUI(reset = false) { - if (reset || !this.#fieldData) { - for (const id in this.fields) { - this.fields[id].textContent = DEFAULT_FIELD_CONTENT; - } - return; - } - if (this.overlayManager.active !== this.dialog) { + #updateUI() { + if (this.#fieldData && this.overlayManager.active !== this.dialog) { return; } for (const id in this.fields) { - const content = this.#fieldData[id]; - this.fields[id].textContent = content || content === 0 ? content : DEFAULT_FIELD_CONTENT; + const content = this.#fieldData?.[id]; + this.fields[id].textContent = content || content === 0 ? content : "-"; } } - async #parseFileSize(fileSize = 0) { - const kb = fileSize / 1024, + async #parseFileSize(b = 0) { + const kb = b / 1024, mb = kb / 1024; - if (!kb) { - return undefined; - } - return this.l10n.get(`pdfjs-document-properties-${mb >= 1 ? "mb" : "kb"}`, { - size_mb: mb >= 1 && (+mb.toPrecision(3)).toLocaleString(), - size_kb: mb < 1 && (+kb.toPrecision(3)).toLocaleString(), - size_b: fileSize.toLocaleString() - }); + return kb ? this.l10n.get(mb >= 1 ? "pdfjs-document-properties-size-mb" : "pdfjs-document-properties-size-kb", { + mb, + kb, + b + }) : undefined; } async #parsePageSize(pageSizeInches, pagesRotation) { if (!pageSizeInches) { @@ -4334,7 +5000,8 @@ class PDFDocumentProperties { height: pageSizeInches.width }; } - const isPortrait = isPortraitOrientation(pageSizeInches); + const isPortrait = isPortraitOrientation(pageSizeInches), + nonMetric = NON_METRIC_LOCALES.includes(this.l10n.getLanguage()); let sizeInches = { width: Math.round(pageSizeInches.width * 100) / 100, height: Math.round(pageSizeInches.height * 100) / 100 @@ -4343,8 +5010,8 @@ class PDFDocumentProperties { width: Math.round(pageSizeInches.width * 25.4 * 10) / 10, height: Math.round(pageSizeInches.height * 25.4 * 10) / 10 }; - let rawName = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES); - if (!rawName && !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))) { + let nameId = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES); + if (!nameId && !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))) { const exactMillimeters = { width: pageSizeInches.width * 25.4, height: pageSizeInches.height * 25.4 @@ -4354,8 +5021,8 @@ class PDFDocumentProperties { height: Math.round(sizeMillimeters.height) }; if (Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 && Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1) { - rawName = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES); - if (rawName) { + nameId = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES); + if (nameId) { sizeInches = { width: Math.round(intMillimeters.width / 25.4 * 100) / 100, height: Math.round(intMillimeters.height / 25.4 * 100) / 100 @@ -4367,27 +5034,23 @@ class PDFDocumentProperties { const [{ width, height - }, unit, name, orientation] = await Promise.all([this._isNonMetricLocale ? sizeInches : sizeMillimeters, this.l10n.get(`pdfjs-document-properties-page-size-unit-${this._isNonMetricLocale ? "inches" : "millimeters"}`), rawName && this.l10n.get(`pdfjs-document-properties-page-size-name-${rawName}`), this.l10n.get(`pdfjs-document-properties-page-size-orientation-${isPortrait ? "portrait" : "landscape"}`)]); - return this.l10n.get(`pdfjs-document-properties-page-size-dimension-${name ? "name-" : ""}string`, { - width: width.toLocaleString(), - height: height.toLocaleString(), + }, unit, name, orientation] = await Promise.all([nonMetric ? sizeInches : sizeMillimeters, this.l10n.get(nonMetric ? "pdfjs-document-properties-page-size-unit-inches" : "pdfjs-document-properties-page-size-unit-millimeters"), nameId && this.l10n.get(nameId), this.l10n.get(isPortrait ? "pdfjs-document-properties-page-size-orientation-portrait" : "pdfjs-document-properties-page-size-orientation-landscape")]); + return this.l10n.get(name ? "pdfjs-document-properties-page-size-dimension-name-string" : "pdfjs-document-properties-page-size-dimension-string", { + width, + height, unit, name, orientation }); } async #parseDate(inputDate) { - const dateObject = PDFDateString.toDateObject(inputDate); - if (!dateObject) { - return undefined; - } - return this.l10n.get("pdfjs-document-properties-date-string", { - date: dateObject.toLocaleDateString(), - time: dateObject.toLocaleTimeString() - }); + const dateObj = PDFDateString.toDateObject(inputDate); + return dateObj ? this.l10n.get("pdfjs-document-properties-date-time-string", { + dateObj + }) : undefined; } #parseLinearization(isLinearized) { - return this.l10n.get(`pdfjs-document-properties-linearized-${isLinearized ? "yes" : "no"}`); + return this.l10n.get(isLinearized ? "pdfjs-document-properties-linearized-yes" : "pdfjs-document-properties-linearized-no"); } } @@ -4864,25 +5527,6 @@ class PDFFindController { } return true; } - #calculateRegExpMatch(query, entireWord, pageIndex, pageContent) { - const matches = this._pageMatches[pageIndex] = []; - const matchesLength = this._pageMatchesLength[pageIndex] = []; - if (!query) { - return; - } - const diffs = this._pageDiffs[pageIndex]; - let match; - while ((match = query.exec(pageContent)) !== null) { - if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) { - continue; - } - const [matchPos, matchLen] = getOriginalIndex(diffs, match.index, match[0].length); - if (matchLen) { - matches.push(matchPos); - matchesLength.push(matchLen); - } - } - } #convertToRegExpString(query, hasDiacritics) { const { matchDiacritics @@ -4924,29 +5568,25 @@ class PDFFindController { return [isUnicode, query]; } #calculateMatch(pageIndex) { - let query = this.#query; + const query = this.#query; if (query.length === 0) { return; } - const { - caseSensitive, - entireWord - } = this.#state; const pageContent = this._pageContents[pageIndex]; - const hasDiacritics = this._hasDiacritics[pageIndex]; - let isUnicode = false; - if (typeof query === "string") { - [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics); - } else { - query = query.sort().reverse().map(q => { - const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics); - isUnicode ||= isUnicodePart; - return `(${queryPart})`; - }).join("|"); - } - const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`; - query = query ? new RegExp(query, flags) : null; - this.#calculateRegExpMatch(query, entireWord, pageIndex, pageContent); + const matcherResult = this.match(query, pageContent, pageIndex); + const matches = this._pageMatches[pageIndex] = []; + const matchesLength = this._pageMatchesLength[pageIndex] = []; + const diffs = this._pageDiffs[pageIndex]; + matcherResult?.forEach(({ + index, + length + }) => { + const [matchPos, matchLen] = getOriginalIndex(diffs, index, length); + if (matchLen) { + matches.push(matchPos); + matchesLength.push(matchLen); + } + }); if (this.#state.highlightAll) { this.#updatePage(pageIndex); } @@ -4954,7 +5594,7 @@ class PDFFindController { this._resumePageIdx = null; this.#nextPageMatch(); } - const pageMatchesCount = this._pageMatches[pageIndex].length; + const pageMatchesCount = matches.length; this._matchesCountTotal += pageMatchesCount; if (this.#updateMatchesCountOnProgress) { if (pageMatchesCount > 0) { @@ -4964,6 +5604,40 @@ class PDFFindController { this.#updateUIResultsCount(); } } + match(query, pageContent, pageIndex) { + const hasDiacritics = this._hasDiacritics[pageIndex]; + let isUnicode = false; + if (typeof query === "string") { + [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics); + } else { + query = query.sort().reverse().map(q => { + const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics); + isUnicode ||= isUnicodePart; + return `(${queryPart})`; + }).join("|"); + } + if (!query) { + return undefined; + } + const { + caseSensitive, + entireWord + } = this.#state; + const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`; + query = new RegExp(query, flags); + const matches = []; + let match; + while ((match = query.exec(pageContent)) !== null) { + if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) { + continue; + } + matches.push({ + index: match.index, + length: match[0].length + }); + } + return matches; + } #extractText() { if (this._extractTextPromises.length > 0) { return; @@ -5180,6 +5854,7 @@ class PDFFindController { source: this, state, previous, + entireWord: this.#state?.entireWord ?? null, matchesCount: this.#requestMatchesCount(), rawQuery: this.#state?.query ?? null }); @@ -5276,7 +5951,7 @@ class PDFFindBar { status = "notFound"; break; case FindState.WRAPPED: - findMsgId = `pdfjs-find-reached-${previous ? "top" : "bottom"}`; + findMsgId = previous ? "pdfjs-find-reached-top" : "pdfjs-find-reached-bottom"; break; } findField.setAttribute("data-status", status); @@ -5299,7 +5974,7 @@ class PDFFindBar { } = this; if (total > 0) { const limit = MATCHES_COUNT_LIMIT; - findResultsCount.setAttribute("data-l10n-id", `pdfjs-find-match-count${total > limit ? "-limit" : ""}`); + findResultsCount.setAttribute("data-l10n-id", total > limit ? "pdfjs-find-match-count-limit" : "pdfjs-find-match-count"); findResultsCount.setAttribute("data-l10n-args", JSON.stringify({ limit, current, @@ -5866,15 +6541,16 @@ class PDFLayerViewer extends BaseTreeViewer { return false; }; } - async _setNestedName(element, { + _setNestedName(element, { name = null }) { if (typeof name === "string") { element.textContent = this._normalizeTextContent(name); return; } - element.textContent = await this._l10n.get("pdfjs-additional-layers"); + element.setAttribute("data-l10n-id", "pdfjs-additional-layers"); element.style.fontStyle = "italic"; + this._l10n.translateOnce(element); } _addToggleButton(div, { name = null @@ -6742,22 +7418,24 @@ class PDFPrintService { useRenderedPage() { this.throwIfInactive(); const img = document.createElement("img"); - const scratchCanvas = this.scratchCanvas; - if ("toBlob" in scratchCanvas) { - scratchCanvas.toBlob(function (blob) { - img.src = URL.createObjectURL(blob); - }); - } else { - img.src = scratchCanvas.toDataURL(); - } + this.scratchCanvas.toBlob(blob => { + img.src = URL.createObjectURL(blob); + }); const wrapper = document.createElement("div"); wrapper.className = "printedPage"; wrapper.append(img); this.printContainer.append(wrapper); - return new Promise(function (resolve, reject) { - img.onload = resolve; - img.onerror = reject; + const { + promise, + resolve, + reject + } = Promise.withResolvers(); + img.onload = resolve; + img.onerror = reject; + promise.catch(() => {}).then(() => { + URL.revokeObjectURL(img.src); }); + return promise; } performPrint() { this.throwIfInactive(); @@ -7374,8 +8052,7 @@ const SIDEBAR_RESIZING_CLASS = "sidebarResizing"; const UI_NOTIFICATION_CLASS = "pdfSidebarNotification"; class PDFSidebar { #isRTL = false; - #mouseMoveBound = this.#mouseMove.bind(this); - #mouseUpBound = this.#mouseUp.bind(this); + #mouseAC = null; #outerContainerWidth = null; #width = null; constructor({ @@ -7542,10 +8219,14 @@ class PDFSidebar { } } #addEventListeners() { + const { + eventBus, + outerContainer + } = this; this.sidebarContainer.addEventListener("transitionend", evt => { if (evt.target === this.sidebarContainer) { - this.outerContainer.classList.remove("sidebarMoving"); - this.eventBus.dispatch("resize", { + outerContainer.classList.remove("sidebarMoving"); + eventBus.dispatch("resize", { source: this }); } @@ -7560,7 +8241,7 @@ class PDFSidebar { this.switchView(SidebarView.OUTLINE); }); this.outlineButton.addEventListener("dblclick", () => { - this.eventBus.dispatch("toggleoutlinetree", { + eventBus.dispatch("toggleoutlinetree", { source: this }); }); @@ -7571,12 +8252,12 @@ class PDFSidebar { this.switchView(SidebarView.LAYERS); }); this.layersButton.addEventListener("dblclick", () => { - this.eventBus.dispatch("resetlayers", { + eventBus.dispatch("resetlayers", { source: this }); }); this._currentOutlineItemButton.addEventListener("click", () => { - this.eventBus.dispatch("currentoutlineitem", { + eventBus.dispatch("currentoutlineitem", { source: this }); }); @@ -7588,7 +8269,7 @@ class PDFSidebar { this.switchView(SidebarView.THUMBS); } }; - this.eventBus._on("outlineloaded", evt => { + eventBus._on("outlineloaded", evt => { onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE); evt.currentOutlineItemPromise.then(enabled => { if (!this.isInitialViewSet) { @@ -7597,13 +8278,13 @@ class PDFSidebar { this._currentOutlineItemButton.disabled = !enabled; }); }); - this.eventBus._on("attachmentsloaded", evt => { + eventBus._on("attachmentsloaded", evt => { onTreeLoaded(evt.attachmentsCount, this.attachmentsButton, SidebarView.ATTACHMENTS); }); - this.eventBus._on("layersloaded", evt => { + eventBus._on("layersloaded", evt => { onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS); }); - this.eventBus._on("presentationmodechanged", evt => { + eventBus._on("presentationmodechanged", evt => { if (evt.state === PresentationModeState.NORMAL && this.visibleView === SidebarView.THUMBS) { this.onUpdateThumbnails(); } @@ -7612,11 +8293,16 @@ class PDFSidebar { if (evt.button !== 0) { return; } - this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); - window.addEventListener("mousemove", this.#mouseMoveBound); - window.addEventListener("mouseup", this.#mouseUpBound); + outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); + this.#mouseAC = new AbortController(); + const opts = { + signal: this.#mouseAC.signal + }; + window.addEventListener("mousemove", this.#mouseMove.bind(this), opts); + window.addEventListener("mouseup", this.#mouseUp.bind(this), opts); + window.addEventListener("blur", this.#mouseUp.bind(this), opts); }); - this.eventBus._on("resize", evt => { + eventBus._on("resize", evt => { if (evt.source !== window) { return; } @@ -7628,12 +8314,12 @@ class PDFSidebar { this.#updateWidth(this.#width); return; } - this.outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); + outerContainer.classList.add(SIDEBAR_RESIZING_CLASS); const updated = this.#updateWidth(this.#width); Promise.resolve().then(() => { - this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS); + outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS); if (updated) { - this.eventBus.dispatch("resize", { + eventBus.dispatch("resize", { source: this }); } @@ -7670,8 +8356,8 @@ class PDFSidebar { this.eventBus.dispatch("resize", { source: this }); - window.removeEventListener("mousemove", this.#mouseMoveBound); - window.removeEventListener("mouseup", this.#mouseUpBound); + this.#mouseAC?.abort(); + this.#mouseAC = null; } } @@ -7714,7 +8400,8 @@ class PDFThumbnailView { optionalContentConfigPromise, linkService, renderingQueue, - pageColors + pageColors, + enableHWA }) { this.id = id; this.renderingId = "thumbnail" + id; @@ -7725,6 +8412,7 @@ class PDFThumbnailView { this.pdfPageRotate = defaultViewport.rotation; this._optionalContentConfigPromise = optionalContentConfigPromise || null; this.pageColors = pageColors || null; + this.enableHWA = enableHWA || false; this.eventBus = eventBus; this.linkService = linkService; this.renderingQueue = renderingQueue; @@ -7808,10 +8496,11 @@ class PDFThumbnailView { } this.resume = null; } - #getPageDrawContext(upscaleFactor = 1) { + #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d", { - alpha: false + alpha: false, + willReadFrequently: !enableHWA }); const outputScale = new OutputScale(); canvas.width = upscaleFactor * this.canvasWidth * outputScale.sx | 0; @@ -7930,7 +8619,7 @@ class PDFThumbnailView { const { ctx, canvas - } = this.#getPageDrawContext(); + } = this.#getPageDrawContext(1, true); if (img.width <= 2 * canvas.width) { ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height); return canvas; @@ -7977,14 +8666,17 @@ class PDFThumbnailViewer { eventBus, linkService, renderingQueue, - pageColors + pageColors, + abortSignal, + enableHWA }) { this.container = container; this.eventBus = eventBus; this.linkService = linkService; this.renderingQueue = renderingQueue; this.pageColors = pageColors || null; - this.scroll = watchScroll(this.container, this.#scrollUpdated.bind(this)); + this.enableHWA = enableHWA || false; + this.scroll = watchScroll(this.container, this.#scrollUpdated.bind(this), abortSignal); this.#resetView(); } #scrollUpdated() { @@ -8105,7 +8797,8 @@ class PDFThumbnailViewer { optionalContentConfigPromise, linkService: this.linkService, renderingQueue: this.renderingQueue, - pageColors: this.pageColors + pageColors: this.pageColors, + enableHWA: this.enableHWA }); this._thumbnails.push(thumbnail); } @@ -8368,6 +9061,9 @@ class AnnotationLayerBuilder { } this.div.hidden = true; } + hasEditableAnnotations() { + return !!this.annotationLayer?.hasEditableAnnotations(); + } #updatePresentationModeState(state) { if (!this.div) { return; @@ -8935,13 +9631,6 @@ class TextLayerBuilder { this.div.tabIndex = 0; this.div.className = "textLayer"; } - #finishRendering() { - this.#renderingDone = true; - const endOfContent = document.createElement("div"); - endOfContent.className = "endOfContent"; - this.div.append(endOfContent); - this.#bindMouse(endOfContent); - } async render(viewport, textContentParams = null) { if (this.#renderingDone && this.#textLayer) { this.#textLayer.update({ @@ -8967,7 +9656,11 @@ class TextLayerBuilder { this.highlighter?.setTextMapping(textDivs, textContentItemsStr); this.accessibilityManager?.setTextMapping(textDivs); await this.#textLayer.render(); - this.#finishRendering(); + this.#renderingDone = true; + const endOfContent = document.createElement("div"); + endOfContent.className = "endOfContent"; + this.div.append(endOfContent); + this.#bindMouse(endOfContent); this.#onAppend?.(this.div); this.highlighter?.enable(); this.accessibilityManager?.enable(); @@ -8995,8 +9688,8 @@ class TextLayerBuilder { const { div } = this; - div.addEventListener("mousedown", evt => { - end.classList.add("active"); + div.addEventListener("mousedown", () => { + div.classList.add("selecting"); }); div.addEventListener("copy", event => { if (!this.#enablePermissions) { @@ -9028,13 +9721,33 @@ class TextLayerBuilder { textLayer.append(end); end.style.width = ""; end.style.height = ""; - end.classList.remove("active"); + textLayer.classList.remove("selecting"); }; + let isPointerDown = false; + document.addEventListener("pointerdown", () => { + isPointerDown = true; + }, { + signal + }); document.addEventListener("pointerup", () => { + isPointerDown = false; + this.#textLayers.forEach(reset); + }, { + signal + }); + window.addEventListener("blur", () => { + isPointerDown = false; this.#textLayers.forEach(reset); }, { signal }); + document.addEventListener("keyup", () => { + if (!isPointerDown) { + this.#textLayers.forEach(reset); + } + }, { + signal + }); var isFirefox, prevRange; document.addEventListener("selectionchange", () => { const selection = document.getSelection(); @@ -9053,7 +9766,7 @@ class TextLayerBuilder { } for (const [textLayerDiv, endDiv] of this.#textLayers) { if (activeTextLayers.has(textLayerDiv)) { - endDiv.classList.add("active"); + textLayerDiv.classList.add("selecting"); } else { reset(endDiv, textLayerDiv); } @@ -9100,7 +9813,9 @@ const DEFAULT_LAYER_PROPERTIES = null; const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]); class PDFPageView { #annotationMode = AnnotationMode.ENABLE_FORMS; + #enableHWA = false; #hasRestrictedScaling = false; + #isEditing = false; #layerProperties = null; #loadingId = null; #previousRotation = null; @@ -9132,6 +9847,7 @@ class PDFPageView { this.imageResourcesPath = options.imageResourcesPath || ""; this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); this.pageColors = options.pageColors || null; + this.#enableHWA = options.enableHWA || false; this.eventBus = options.eventBus; this.renderingQueue = options.renderingQueue; this.l10n = options.l10n; @@ -9254,6 +9970,9 @@ class PDFPageView { this.reset(); this.pdfPage?.cleanup(); } + hasEditableAnnotations() { + return !!this.annotationLayer?.hasEditableAnnotations(); + } get _textHighlighter() { return shadow(this, "_textHighlighter", new TextHighlighter({ pageIndex: this.id - 1, @@ -9430,6 +10149,19 @@ class PDFPageView { this._resetZoomLayer(); } } + toggleEditingMode(isEditing) { + if (!this.hasEditableAnnotations()) { + return; + } + this.#isEditing = isEditing; + this.reset({ + keepZoomLayer: true, + keepAnnotationLayer: true, + keepAnnotationEditorLayer: true, + keepXfaLayer: true, + keepTextLayer: true + }); + } update({ scale = 0, rotation = null, @@ -9742,7 +10474,8 @@ class PDFPageView { canvasWrapper.append(canvas); this.canvas = canvas; const ctx = canvas.getContext("2d", { - alpha: false + alpha: false, + willReadFrequently: !this.#enableHWA }); const outputScale = this.outputScale = new OutputScale(); if (this.maxCanvasPixels === 0) { @@ -9763,13 +10496,13 @@ class PDFPageView { } const sfx = approximateFraction(outputScale.sx); const sfy = approximateFraction(outputScale.sy); - canvas.width = roundToDivide(width * outputScale.sx, sfx[0]); - canvas.height = roundToDivide(height * outputScale.sy, sfy[0]); + canvas.width = floorToDivide(width * outputScale.sx, sfx[0]); + canvas.height = floorToDivide(height * outputScale.sy, sfy[0]); const { style } = canvas; - style.width = roundToDivide(width, sfx[1]) + "px"; - style.height = roundToDivide(height, sfy[1]) + "px"; + style.width = floorToDivide(width, sfx[1]) + "px"; + style.height = floorToDivide(height, sfy[1]) + "px"; this.#viewportMap.set(canvas, viewport); const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null; const renderContext = { @@ -9779,7 +10512,8 @@ class PDFPageView { annotationMode: this.#annotationMode, optionalContentConfigPromise: this._optionalContentConfigPromise, annotationCanvasMap: this._annotationCanvasMap, - pageColors + pageColors, + isEditing: this.#isEditing }; const renderTask = this.renderTask = pdfPage.render(renderContext); renderTask.onContinue = renderContinueCallback; @@ -9801,20 +10535,18 @@ class PDFPageView { }); await this.#renderDrawLayer(); this.drawLayer.setParent(canvasWrapper); - if (!this.annotationEditorLayer) { - this.annotationEditorLayer = new AnnotationEditorLayerBuilder({ - uiManager: annotationEditorUIManager, - pdfPage, - l10n, - accessibilityManager: this._accessibilityManager, - annotationLayer: this.annotationLayer?.annotationLayer, - textLayer: this.textLayer, - drawLayer: this.drawLayer.getDrawLayer(), - onAppend: annotationEditorLayerDiv => { - this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer"); - } - }); - } + this.annotationEditorLayer ||= new AnnotationEditorLayerBuilder({ + uiManager: annotationEditorUIManager, + pdfPage, + l10n, + accessibilityManager: this._accessibilityManager, + annotationLayer: this.annotationLayer?.annotationLayer, + textLayer: this.textLayer, + drawLayer: this.drawLayer.getDrawLayer(), + onAppend: annotationEditorLayerDiv => { + this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer"); + } + }); this.#renderAnnotationEditorLayer(); }, error => { if (!(error instanceof RenderingCancelledException)) { @@ -9873,8 +10605,8 @@ class PDFPageView { const DEFAULT_CACHE_SIZE = 10; const PagesCountLimit = { - FORCE_SCROLL_MODE_PAGE: 15000, - FORCE_LAZY_PAGE_INIT: 7500, + FORCE_SCROLL_MODE_PAGE: 10000, + FORCE_LAZY_PAGE_INIT: 5000, PAUSE_EAGER_PAGE_INIT: 250 }; function isValidAnnotationEditorMode(mode) { @@ -9936,10 +10668,15 @@ class PDFViewer { #annotationEditorUIManager = null; #annotationMode = AnnotationMode.ENABLE_FORMS; #containerTopLeft = null; + #enableHWA = false; #enableHighlightFloatingButton = false; #enablePermissions = false; + #enableUpdatedAddImage = false; + #enableNewAltTextWhenAddingImage = false; #eventAbortController = null; #mlManager = null; + #onPageRenderedCallback = null; + #switchAnnotationEditorModeTimeoutId = null; #getAllTextInProgress = false; #hiddenCopyElement = null; #interruptCopyCondition = false; @@ -9949,7 +10686,7 @@ class PDFViewer { #scaleTimeoutId = null; #textLayerMode = TextLayerMode.ENABLE; constructor(options) { - const viewerVersion = "4.3.136"; + const viewerVersion = "4.6.82"; if (version !== viewerVersion) { throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`); } @@ -9976,6 +10713,8 @@ class PDFViewer { this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE; this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null; this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true; + this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true; + this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.removePageBorders = options.removePageBorders || true; @@ -9985,6 +10724,7 @@ class PDFViewer { this.#enablePermissions = options.enablePermissions || false; this.pageColors = options.pageColors || null; this.#mlManager = options.mlManager || null; + this.#enableHWA = options.enableHWA || false; this.defaultRenderingQueue = !options.renderingQueue; if (this.defaultRenderingQueue) { this.renderingQueue = new PDFRenderingQueue(); @@ -9992,7 +10732,16 @@ class PDFViewer { } else { this.renderingQueue = options.renderingQueue; } - this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this)); + const { + abortSignal + } = options; + abortSignal?.addEventListener("abort", () => { + this.#resizeObserver.disconnect(); + this.#resizeObserver = null; + }, { + once: true + }); + this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this), abortSignal); this.presentationModeState = PresentationModeState.UNKNOWN; this._resetView(); if (this.removePageBorders) { @@ -10257,10 +11006,14 @@ class PDFViewer { return; } this.#getAllTextInProgress = true; - const savedCursor = this.container.style.cursor; - this.container.style.cursor = "wait"; - const interruptCopy = ev => this.#interruptCopyCondition = ev.key === "Escape"; - window.addEventListener("keydown", interruptCopy); + const { + classList + } = this.viewer; + classList.add("copyAll"); + const ac = new AbortController(); + window.addEventListener("keydown", ev => this.#interruptCopyCondition = ev.key === "Escape", { + signal: ac.signal + }); this.getAllText().then(async text => { if (text !== null) { await navigator.clipboard.writeText(text); @@ -10270,8 +11023,8 @@ class PDFViewer { }).finally(() => { this.#getAllTextInProgress = false; this.#interruptCopyCondition = false; - window.removeEventListener("keydown", interruptCopy); - this.container.style.cursor = savedCursor; + ac.abort(); + classList.remove("copyAll"); }); event.preventDefault(); event.stopPropagation(); @@ -10287,10 +11040,8 @@ class PDFViewer { this._resetView(); this.findController?.setDocument(null); this._scriptingManager?.setDocument(null); - if (this.#annotationEditorUIManager) { - this.#annotationEditorUIManager.destroy(); - this.#annotationEditorUIManager = null; - } + this.#annotationEditorUIManager?.destroy(); + this.#annotationEditorUIManager = null; } this.pdfDocument = pdfDocument; if (!pdfDocument) { @@ -10363,17 +11114,20 @@ class PDFViewer { element.id = "hiddenCopyElement"; viewer.before(element); } - if (annotationEditorMode !== AnnotationEditorType.DISABLE) { + if (typeof AbortSignal.any === "function" && annotationEditorMode !== AnnotationEditorType.DISABLE) { const mode = annotationEditorMode; if (pdfDocument.isPureXfa) { console.warn("Warning: XFA-editing is not implemented."); } else if (isValidAnnotationEditorMode(mode)) { - this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#mlManager); + this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#enableUpdatedAddImage, this.#enableNewAltTextWhenAddingImage, this.#mlManager); eventBus.dispatch("annotationeditoruimanager", { source: this, uiManager: this.#annotationEditorUIManager }); if (mode !== AnnotationEditorType.NONE) { + if (mode === AnnotationEditorType.STAMP) { + this.#mlManager?.loadModel("altText"); + } this.#annotationEditorUIManager.updateMode(mode); } } else { @@ -10405,7 +11159,8 @@ class PDFViewer { maxCanvasPixels: this.maxCanvasPixels, pageColors, l10n: this.l10n, - layerProperties: this._layerProperties + layerProperties: this._layerProperties, + enableHWA: this.#enableHWA }); this._pages.push(pageView); } @@ -10527,6 +11282,7 @@ class PDFViewer { this.viewer.removeAttribute("lang"); this.#hiddenCopyElement?.remove(); this.#hiddenCopyElement = null; + this.#cleanupSwitchAnnotationEditorMode(); } #ensurePageViewVisible() { if (this._scrollMode !== ScrollMode.PAGE) { @@ -10899,6 +11655,34 @@ class PDFViewer { location: this._location }); } + #switchToEditAnnotationMode() { + const visible = this._getVisiblePages(); + const pagesToRefresh = []; + const { + ids, + views + } = visible; + for (const page of views) { + const { + view + } = page; + if (!view.hasEditableAnnotations()) { + ids.delete(view.id); + continue; + } + pagesToRefresh.push(page); + } + if (pagesToRefresh.length === 0) { + return null; + } + this.renderingQueue.renderHighestPriority({ + first: pagesToRefresh[0], + last: pagesToRefresh.at(-1), + views: pagesToRefresh, + ids + }); + return ids; + } containsElement(element) { return this.container.contains(element); } @@ -11331,6 +12115,16 @@ class PDFViewer { get containerTopLeft() { return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft]; } + #cleanupSwitchAnnotationEditorMode() { + if (this.#onPageRenderedCallback) { + this.eventBus._off("pagerendered", this.#onPageRenderedCallback); + this.#onPageRenderedCallback = null; + } + if (this.#switchAnnotationEditorModeTimeoutId !== null) { + clearTimeout(this.#switchAnnotationEditorModeTimeoutId); + this.#switchAnnotationEditorModeTimeoutId = null; + } + } get annotationEditorMode() { return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE; } @@ -11351,21 +12145,50 @@ class PDFViewer { if (!this.pdfDocument) { return; } - this.#annotationEditorMode = mode; - this.eventBus.dispatch("annotationeditormodechanged", { - source: this, - mode - }); - this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard); - } - set annotationEditorParams({ - type, - value - }) { - if (!this.#annotationEditorUIManager) { - throw new Error(`The AnnotationEditor is not enabled.`); + if (mode === AnnotationEditorType.STAMP) { + this.#mlManager?.loadModel("altText"); + } + const { + eventBus + } = this; + const updater = () => { + this.#cleanupSwitchAnnotationEditorMode(); + this.#annotationEditorMode = mode; + this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard); + eventBus.dispatch("annotationeditormodechanged", { + source: this, + mode + }); + }; + if (mode === AnnotationEditorType.NONE || this.#annotationEditorMode === AnnotationEditorType.NONE) { + const isEditing = mode !== AnnotationEditorType.NONE; + if (!isEditing) { + this.pdfDocument.annotationStorage.resetModifiedIds(); + } + for (const pageView of this._pages) { + pageView.toggleEditingMode(isEditing); + } + const idsToRefresh = this.#switchToEditAnnotationMode(); + if (isEditing && idsToRefresh) { + this.#cleanupSwitchAnnotationEditorMode(); + this.#onPageRenderedCallback = ({ + pageNumber + }) => { + idsToRefresh.delete(pageNumber); + if (idsToRefresh.size === 0) { + this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0); + } + }; + const { + signal + } = this.#eventAbortController; + eventBus._on("pagerendered", this.#onPageRenderedCallback, { + signal + }); + return; + } } - this.#annotationEditorUIManager.updateParams(type, value); + updater(); } refresh(noUpdate = false, updateArgs = Object.create(null)) { if (!this.pdfDocument) { @@ -11486,6 +12309,10 @@ class SecondaryToolbar { mode: SpreadMode.EVEN }, close: true + }, { + element: options.imageAltTextSettingsButton, + eventName: "imagealttextsettings", + close: true }, { element: options.documentPropertiesButton, eventName: "documentproperties", @@ -11662,7 +12489,7 @@ class SecondaryToolbar { class Toolbar { #opts; - constructor(options, eventBus) { + constructor(options, eventBus, toolbarDensity = 0) { this.#opts = options; this.eventBus = eventBus; const buttons = [{ @@ -11726,29 +12553,21 @@ class Toolbar { } = options.editorStampButton; return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.STAMP; } + }, + telemetry: { + type: "editing", + data: { + action: "pdfjs.image.icon_click" + } } }]; this.#bindListeners(buttons); - if (options.editorHighlightColorPicker) { - eventBus._on("annotationeditoruimanager", ({ - uiManager - }) => { - this.#setAnnotationEditorUIManager(uiManager, options.editorHighlightColorPicker); - }, { - once: true - }); - } - eventBus._on("showannotationeditorui", ({ - mode - }) => { - switch (mode) { - case AnnotationEditorType.HIGHLIGHT: - options.editorHighlightButton.click(); - break; - } + this.#updateToolbarDensity({ + value: toolbarDensity }); this.reset(); } + #updateToolbarDensity() {} #setAnnotationEditorUIManager(uiManager, parentContainer) { const colorPicker = new ColorPicker({ uiManager @@ -11789,6 +12608,8 @@ class Toolbar { eventBus } = this; const { + editorHighlightColorPicker, + editorHighlightButton, pageNumber, scaleSelect } = this.#opts; @@ -11796,7 +12617,8 @@ class Toolbar { for (const { element, eventName, - eventDetails + eventDetails, + telemetry } of buttons) { element.addEventListener("click", evt => { if (eventName !== null) { @@ -11806,6 +12628,12 @@ class Toolbar { isFromKeyboard: evt.detail === 0 }); } + if (telemetry) { + eventBus.dispatch("reporttelemetry", { + source: this, + details: telemetry + }); + } }); } pageNumber.addEventListener("click", function () { @@ -11835,6 +12663,25 @@ class Toolbar { }); scaleSelect.oncontextmenu = noContextMenu; eventBus._on("annotationeditormodechanged", this.#editorModeChanged.bind(this)); + eventBus._on("showannotationeditorui", ({ + mode + }) => { + switch (mode) { + case AnnotationEditorType.HIGHLIGHT: + editorHighlightButton.click(); + break; + } + }); + eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this)); + if (editorHighlightColorPicker) { + eventBus._on("annotationeditoruimanager", ({ + uiManager + }) => { + this.#setAnnotationEditorUIManager(uiManager, editorHighlightColorPicker); + }, { + once: true + }); + } } #editorModeChanged({ mode @@ -12014,10 +12861,10 @@ class ViewHistory { + const FORCE_PAGES_LOADED_TIMEOUT = 10000; -const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000; const ViewOnLoad = { UNKNOWN: -1, PREVIOUS: 0, @@ -12049,20 +12896,22 @@ const PDFViewerApplication = { store: null, downloadManager: null, overlayManager: null, - preferences: null, + preferences: new Preferences(), toolbar: null, secondaryToolbar: null, eventBus: null, l10n: null, annotationEditorParams: null, + imageAltTextSettings: null, isInitialViewSet: false, - downloadComplete: false, isViewerEmbedded: window.parent !== window, url: "", baseUrl: "", + mlManager: null, _downloadUrl: "", _eventBusAbortController: null, _windowAbortController: null, + _globalAbortController: new AbortController(), documentInfo: null, metadata: null, _contentDispositionFilename: null, @@ -12078,11 +12927,9 @@ const PDFViewerApplication = { _printAnnotationStoragePromise: null, _touchInfo: null, _isCtrlKeyDown: false, - _nimbusDataPromise: null, _caretBrowsing: null, _isScrolling: false, async initialize(appConfig) { - let l10nPromise; this.appConfig = appConfig; try { await this.preferences.initializedPromise; @@ -12104,8 +12951,7 @@ const PDFViewerApplication = { if (mode) { document.documentElement.classList.add(mode); } - l10nPromise = this.externalServices.createL10n(); - this.l10n = await l10nPromise; + this.l10n = await this.externalServices.createL10n(); document.getElementsByTagName("html")[0].dir = this.l10n.getDirection(); this.l10n.translate(appConfig.appContainer || document.documentElement); if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) { @@ -12144,24 +12990,6 @@ const PDFViewerApplication = { console.error(`_parseHashParams: "${ex.message}".`); } } - if (params.has("disablerange")) { - AppOptions.set("disableRange", params.get("disablerange") === "true"); - } - if (params.has("disablestream")) { - AppOptions.set("disableStream", params.get("disablestream") === "true"); - } - if (params.has("disableautofetch")) { - AppOptions.set("disableAutoFetch", params.get("disableautofetch") === "true"); - } - if (params.has("disablefontface")) { - AppOptions.set("disableFontFace", params.get("disablefontface") === "true"); - } - if (params.has("disablehistory")) { - AppOptions.set("disableHistory", params.get("disablehistory") === "true"); - } - if (params.has("verbosity")) { - AppOptions.set("verbosity", params.get("verbosity") | 0); - } if (params.has("textlayer")) { switch (params.get("textlayer")) { case "off": @@ -12194,7 +13022,24 @@ const PDFViewerApplication = { } } if (params.has("locale")) { - AppOptions.set("locale", params.get("locale")); + AppOptions.set("localeProperties", { + lang: params.get("locale") + }); + } + const opts = { + disableAutoFetch: x => x === "true", + disableFontFace: x => x === "true", + disableHistory: x => x === "true", + disableRange: x => x === "true", + disableStream: x => x === "true", + verbosity: x => x | 0 + }; + for (const name in opts) { + const check = opts[name], + key = name.toLowerCase(); + if (params.has(key)) { + AppOptions.set(name, check(params.get(key))); + } } }, async _initializeViewerComponents() { @@ -12203,7 +13048,9 @@ const PDFViewerApplication = { externalServices, l10n } = this; - const eventBus = AppOptions.get("isInAutomation") ? new AutomationEventBus() : new EventBus(); + let eventBus; + eventBus = new EventBus(); + this.mlManager?.setEventBus(eventBus, this._globalAbortController.signal); this.eventBus = eventBus; this.overlayManager = new OverlayManager(); const pdfRenderingQueue = new PDFRenderingQueue(); @@ -12236,7 +13083,13 @@ const PDFViewerApplication = { background: AppOptions.get("pageColorsBackground"), foreground: AppOptions.get("pageColorsForeground") } : null; - const altTextManager = appConfig.altTextDialog ? new AltTextManager(appConfig.altTextDialog, container, this.overlayManager, eventBus) : null; + let altTextManager; + if (AppOptions.get("enableUpdatedAddImage")) { + altTextManager = appConfig.newAltTextDialog ? new NewAltTextManager(appConfig.newAltTextDialog, this.overlayManager, eventBus) : null; + } else { + altTextManager = appConfig.altTextDialog ? new AltTextManager(appConfig.altTextDialog, container, this.overlayManager, eventBus) : null; + } + const enableHWA = AppOptions.get("enableHWA"); const pdfViewer = new PDFViewer({ container, viewer, @@ -12253,12 +13106,16 @@ const PDFViewerApplication = { annotationEditorMode, annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"), enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"), + enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"), + enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage"), imageResourcesPath: AppOptions.get("imageResourcesPath"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), maxCanvasPixels: AppOptions.get("maxCanvasPixels"), enablePermissions: AppOptions.get("enablePermissions"), pageColors, - mlManager: this.mlManager + mlManager: this.mlManager, + abortSignal: this._globalAbortController.signal, + enableHWA }); this.pdfViewer = pdfViewer; pdfRenderingQueue.setViewer(pdfViewer); @@ -12270,7 +13127,9 @@ const PDFViewerApplication = { eventBus, renderingQueue: pdfRenderingQueue, linkService: pdfLinkService, - pageColors + pageColors, + abortSignal: this._globalAbortController.signal, + enableHWA }); pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer); } @@ -12285,14 +13144,7 @@ const PDFViewerApplication = { this.findBar = new PDFFindBar(appConfig.findBar, eventBus); } if (appConfig.annotationEditorParams) { - if (annotationEditorMode !== AnnotationEditorType.DISABLE) { - if (AppOptions.get("enableStampEditor")) { - appConfig.toolbar?.editorStampButton?.classList.remove("hidden"); - } - const editorHighlightButton = appConfig.toolbar?.editorHighlightButton; - if (editorHighlightButton && AppOptions.get("enableHighlightEditor")) { - editorHighlightButton.hidden = false; - } + if (typeof AbortSignal.any === "function" && annotationEditorMode !== AnnotationEditorType.DISABLE) { this.annotationEditorParams = new AnnotationEditorParams(appConfig.annotationEditorParams, eventBus); } else { for (const id of ["editorModeButtons", "editorModeSeparator"]) { @@ -12300,6 +13152,9 @@ const PDFViewerApplication = { } } } + if (this.mlManager && appConfig.secondaryToolbar?.imageAltTextSettingsButton) { + this.imageAltTextSettings = new ImageAltTextSettings(appConfig.altTextSettingsDialog, this.overlayManager, eventBus, this.mlManager); + } if (appConfig.documentProperties) { this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties, this.overlayManager, eventBus, l10n, () => this._docFilename); } @@ -12311,9 +13166,13 @@ const PDFViewerApplication = { }); } if (appConfig.toolbar) { - this.toolbar = new Toolbar(appConfig.toolbar, eventBus); + this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get("toolbarDensity")); } if (appConfig.secondaryToolbar) { + if (AppOptions.get("enableAltText")) { + appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove("hidden"); + appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove("hidden"); + } this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus); } if (this.supportsFullscreen && appConfig.secondaryToolbar?.presentationModeButton) { @@ -12368,7 +13227,6 @@ const PDFViewerApplication = { } }, async run(config) { - this.preferences = new Preferences(); await this.initialize(config); const { appConfig, @@ -12398,17 +13256,21 @@ const PDFViewerApplication = { }); }); appConfig.mainContainer.addEventListener("dragover", function (evt) { - evt.preventDefault(); - evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === "copy" ? "copy" : "move"; + for (const item of evt.dataTransfer.items) { + if (item.type === "application/pdf") { + evt.dataTransfer.dropEffect = evt.dataTransfer.effectAllowed === "copy" ? "copy" : "move"; + evt.preventDefault(); + evt.stopPropagation(); + return; + } + } }); appConfig.mainContainer.addEventListener("drop", function (evt) { - evt.preventDefault(); - const { - files - } = evt.dataTransfer; - if (!files || files.length === 0) { + if (evt.dataTransfer.files?.[0].type !== "application/pdf") { return; } + evt.preventDefault(); + evt.stopPropagation(); eventBus.dispatch("fileinputchange", { source: this, fileInput: evt.dataTransfer @@ -12428,7 +13290,7 @@ const PDFViewerApplication = { appConfig.secondaryToolbar?.presentationModeButton.classList.add("hidden"); } if (this.supportsIntegratedFind) { - appConfig.toolbar?.viewFind?.classList.add("hidden"); + appConfig.findBar?.toggleButton?.classList.add("hidden"); } if (file) { this.open({ @@ -12441,9 +13303,6 @@ const PDFViewerApplication = { get externalServices() { return shadow(this, "externalServices", new ExternalServices()); }, - get mlManager() { - return shadow(this, "mlManager", AppOptions.get("enableML") === true ? new MLManager() : null); - }, get initialized() { return this._initializedCapability.settled; }, @@ -12524,12 +13383,10 @@ const PDFViewerApplication = { let title = pdfjs_getPdfFilenameFromUrl(url, ""); if (!title) { try { - title = decodeURIComponent(getFilenameFromUrl(url)) || url; - } catch { - title = url; - } + title = decodeURIComponent(getFilenameFromUrl(url)); + } catch {} } - // this.setTitle(title); + // this.setTitle(title || url); }, setTitle(title = this._title) { this._title = title; @@ -12575,7 +13432,6 @@ const PDFViewerApplication = { this.pdfLinkService.externalLinkEnabled = true; this.store = null; this.isInitialViewSet = false; - this.downloadComplete = false; this.url = ""; this.baseUrl = ""; this._downloadUrl = ""; @@ -12648,44 +13504,25 @@ const PDFViewerApplication = { }); }); }, - _ensureDownloadComplete() { - if (this.pdfDocument && this.downloadComplete) { - return; - } - throw new Error("PDF document not downloaded."); - }, - async download(options = {}) { - const url = this._downloadUrl, - filename = this._docFilename; + async download() { + let data; try { - this._ensureDownloadComplete(); - const data = await this.pdfDocument.getData(); - const blob = new Blob([data], { - type: "application/pdf" - }); - await this.downloadManager.download(blob, url, filename, options); - } catch { - await this.downloadManager.downloadUrl(url, filename, options); - } + data = await this.pdfDocument.getData(); + } catch {} + this.downloadManager.download(data, this._downloadUrl, this._docFilename); }, - async save(options = {}) { + async save() { if (this._saveInProgress) { return; } this._saveInProgress = true; await this.pdfScriptingManager.dispatchWillSave(); - const url = this._downloadUrl, - filename = this._docFilename; try { - this._ensureDownloadComplete(); const data = await this.pdfDocument.saveDocument(); - const blob = new Blob([data], { - type: "application/pdf" - }); - await this.downloadManager.download(blob, url, filename, options); + this.downloadManager.download(data, this._downloadUrl, this._docFilename); } catch (reason) { console.error(`Error when saving the document: ${reason.message}`); - await this.download(options); + await this.download(); } finally { await this.pdfScriptingManager.dispatchDidSave(); this._saveInProgress = false; @@ -12700,12 +13537,13 @@ const PDFViewerApplication = { }); } }, - downloadOrSave(options = {}) { - if (this.pdfDocument?.annotationStorage.size > 0) { - this.save(options); - } else { - this.download(options); - } + async downloadOrSave() { + const { + classList + } = this.appConfig.appContainer; + classList.add("wait"); + await (this.pdfDocument?.annotationStorage.size > 0 ? this.save() : this.download()); + classList.remove("wait"); }, async _documentError(key, moreInfo = null) { this._unblockDocumentLoadEvent(); @@ -12736,11 +13574,8 @@ const PDFViewerApplication = { return message; }, progress(level) { - if (!this.loadingBar || this.downloadComplete) { - return; - } const percent = Math.round(level * 100); - if (percent <= this.loadingBar.percent) { + if (!this.loadingBar || percent <= this.loadingBar.percent) { return; } this.loadingBar.percent = percent; @@ -12754,7 +13589,6 @@ const PDFViewerApplication = { length }) => { this._contentLength = length; - this.downloadComplete = true; this.loadingBar?.hide(); firstPagePromise.then(() => { this.eventBus.dispatch("documentloaded", { @@ -13191,10 +14025,9 @@ const PDFViewerApplication = { this.pdfPresentationMode?.request(); }, triggerPrinting() { - if (!this.supportsPrinting) { - return; + if (this.supportsPrinting) { + window.print(); } - window.print(); }, bindEvents() { if (this._eventBusAbortController) { @@ -13203,14 +14036,18 @@ const PDFViewerApplication = { this._eventBusAbortController = new AbortController(); const { eventBus, + externalServices, + pdfDocumentProperties, + pdfViewer, + preferences, _eventBusAbortController: { signal } } = this; - eventBus._on("resize", webViewerResize, { + eventBus._on("resize", onResize.bind(this), { signal }); - eventBus._on("hashchange", webViewerHashchange, { + eventBus._on("hashchange", onHashchange.bind(this), { signal }); eventBus._on("beforeprint", this.beforePrint.bind(this), { @@ -13219,115 +14056,115 @@ const PDFViewerApplication = { eventBus._on("afterprint", this.afterPrint.bind(this), { signal }); - eventBus._on("pagerender", webViewerPageRender, { + eventBus._on("pagerender", onPageRender.bind(this), { signal }); - eventBus._on("pagerendered", webViewerPageRendered, { + eventBus._on("pagerendered", onPageRendered.bind(this), { signal }); - eventBus._on("updateviewarea", webViewerUpdateViewarea, { + eventBus._on("updateviewarea", onUpdateViewarea.bind(this), { signal }); - eventBus._on("pagechanging", webViewerPageChanging, { + eventBus._on("pagechanging", onPageChanging.bind(this), { signal }); - eventBus._on("scalechanging", webViewerScaleChanging, { + eventBus._on("scalechanging", onScaleChanging.bind(this), { signal }); - eventBus._on("rotationchanging", webViewerRotationChanging, { + eventBus._on("rotationchanging", onRotationChanging.bind(this), { signal }); - eventBus._on("sidebarviewchanged", webViewerSidebarViewChanged, { + eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), { signal }); - eventBus._on("pagemode", webViewerPageMode, { + eventBus._on("pagemode", onPageMode.bind(this), { signal }); - eventBus._on("namedaction", webViewerNamedAction, { + eventBus._on("namedaction", onNamedAction.bind(this), { signal }); - eventBus._on("presentationmodechanged", webViewerPresentationModeChanged, { + eventBus._on("presentationmodechanged", evt => pdfViewer.presentationModeState = evt.state, { signal }); - eventBus._on("presentationmode", webViewerPresentationMode, { + eventBus._on("presentationmode", this.requestPresentationMode.bind(this), { signal }); - eventBus._on("switchannotationeditormode", webViewerSwitchAnnotationEditorMode, { + eventBus._on("switchannotationeditormode", evt => pdfViewer.annotationEditorMode = evt, { signal }); - eventBus._on("switchannotationeditorparams", webViewerSwitchAnnotationEditorParams, { + eventBus._on("print", this.triggerPrinting.bind(this), { signal }); - eventBus._on("print", webViewerPrint, { + eventBus._on("download", this.downloadOrSave.bind(this), { signal }); - eventBus._on("download", webViewerDownload, { + eventBus._on("firstpage", () => this.page = 1, { signal }); - eventBus._on("firstpage", webViewerFirstPage, { + eventBus._on("lastpage", () => this.page = this.pagesCount, { signal }); - eventBus._on("lastpage", webViewerLastPage, { + eventBus._on("nextpage", () => pdfViewer.nextPage(), { signal }); - eventBus._on("nextpage", webViewerNextPage, { + eventBus._on("previouspage", () => pdfViewer.previousPage(), { signal }); - eventBus._on("previouspage", webViewerPreviousPage, { + eventBus._on("zoomin", this.zoomIn.bind(this), { signal }); - eventBus._on("zoomin", webViewerZoomIn, { + eventBus._on("zoomout", this.zoomOut.bind(this), { signal }); - eventBus._on("zoomout", webViewerZoomOut, { + eventBus._on("zoomreset", this.zoomReset.bind(this), { signal }); - eventBus._on("zoomreset", webViewerZoomReset, { + eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), { signal }); - eventBus._on("pagenumberchanged", webViewerPageNumberChanged, { + eventBus._on("scalechanged", evt => pdfViewer.currentScaleValue = evt.value, { signal }); - eventBus._on("scalechanged", webViewerScaleChanged, { + eventBus._on("rotatecw", this.rotatePages.bind(this, 90), { signal }); - eventBus._on("rotatecw", webViewerRotateCw, { + eventBus._on("rotateccw", this.rotatePages.bind(this, -90), { signal }); - eventBus._on("rotateccw", webViewerRotateCcw, { + eventBus._on("optionalcontentconfig", evt => pdfViewer.optionalContentConfigPromise = evt.promise, { signal }); - eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig, { + eventBus._on("switchscrollmode", evt => pdfViewer.scrollMode = evt.mode, { signal }); - eventBus._on("switchscrollmode", webViewerSwitchScrollMode, { + eventBus._on("scrollmodechanged", onViewerModesChanged.bind(this, "scrollMode"), { signal }); - eventBus._on("scrollmodechanged", webViewerScrollModeChanged, { + eventBus._on("switchspreadmode", evt => pdfViewer.spreadMode = evt.mode, { signal }); - eventBus._on("switchspreadmode", webViewerSwitchSpreadMode, { + eventBus._on("spreadmodechanged", onViewerModesChanged.bind(this, "spreadMode"), { signal }); - eventBus._on("spreadmodechanged", webViewerSpreadModeChanged, { + eventBus._on("imagealttextsettings", onImageAltTextSettings.bind(this), { signal }); - eventBus._on("documentproperties", webViewerDocumentProperties, { + eventBus._on("documentproperties", () => pdfDocumentProperties?.open(), { signal }); - eventBus._on("findfromurlhash", webViewerFindFromUrlHash, { + eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), { signal }); - eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount, { + eventBus._on("updatefindmatchescount", onUpdateFindMatchesCount.bind(this), { signal }); - eventBus._on("updatefindcontrolstate", webViewerUpdateFindControlState, { + eventBus._on("updatefindcontrolstate", onUpdateFindControlState.bind(this), { signal }); - eventBus._on("fileinputchange", webViewerFileInputChange, { + eventBus._on("fileinputchange", onFileInputChange.bind(this), { signal }); - eventBus._on("openfile", webViewerOpenFile, { + eventBus._on("openfile", onOpenFile.bind(this), { signal }); }, @@ -13341,13 +14178,14 @@ const PDFViewerApplication = { appConfig: { mainContainer }, + pdfViewer, _windowAbortController: { signal } } = this; function addWindowResolutionChange(evt = null) { if (evt) { - webViewerResolutionChange(evt); + pdfViewer.refresh(); } const mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`); mediaQueryList.addEventListener("change", addWindowResolutionChange, { @@ -13356,39 +14194,34 @@ const PDFViewerApplication = { }); } addWindowResolutionChange(); - window.addEventListener("visibilitychange", webViewerVisibilityChange, { - signal - }); - window.addEventListener("wheel", webViewerWheel, { + window.addEventListener("wheel", onWheel.bind(this), { passive: false, signal }); - window.addEventListener("touchstart", webViewerTouchStart, { + window.addEventListener("touchstart", onTouchStart.bind(this), { passive: false, signal }); - window.addEventListener("touchmove", webViewerTouchMove, { + window.addEventListener("touchmove", onTouchMove.bind(this), { passive: false, signal }); - window.addEventListener("touchend", webViewerTouchEnd, { + window.addEventListener("touchend", onTouchEnd.bind(this), { passive: false, signal }); - window.addEventListener("click", webViewerClick, { + window.addEventListener("click", onClick.bind(this), { signal }); - window.addEventListener("keydown", webViewerKeyDown, { + window.addEventListener("keydown", onKeyDown.bind(this), { signal }); - window.addEventListener("keyup", webViewerKeyUp, { + window.addEventListener("keyup", onKeyUp.bind(this), { signal }); - window.addEventListener("resize", () => { - eventBus.dispatch("resize", { - source: window - }); - }, { + window.addEventListener("resize", () => eventBus.dispatch("resize", { + source: window + }), { signal }); window.addEventListener("hashchange", () => { @@ -13399,24 +14232,20 @@ const PDFViewerApplication = { }, { signal }); - window.addEventListener("beforeprint", () => { - eventBus.dispatch("beforeprint", { - source: window - }); - }, { + window.addEventListener("beforeprint", () => eventBus.dispatch("beforeprint", { + source: window + }), { signal }); - window.addEventListener("afterprint", () => { - eventBus.dispatch("afterprint", { - source: window - }); - }, { + window.addEventListener("afterprint", () => eventBus.dispatch("afterprint", { + source: window + }), { signal }); - window.addEventListener("updatefromsandbox", event => { + window.addEventListener("updatefromsandbox", evt => { eventBus.dispatch("updatefromsandbox", { source: window, - detail: event.detail + detail: evt.detail }); }, { signal @@ -13472,6 +14301,14 @@ const PDFViewerApplication = { this._windowAbortController?.abort(); this._windowAbortController = null; }, + async testingClose() { + this.unbindEvents(); + this.unbindWindowEvents(); + this._globalAbortController?.abort(); + this._globalAbortController = null; + this.findBar?.close(); + await Promise.all([this.l10n?.destroy(), this.close()]); + }, _accumulateTicks(ticks, prop) { if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) { this[prop] = 0; @@ -13526,33 +14363,46 @@ initCom(PDFViewerApplication); throw ex; } }; + var onFileInputChange = function (evt) { + if (this.pdfViewer?.isInPresentationMode) { + return; + } + const file = evt.fileInput.files[0]; + this.open({ + url: URL.createObjectURL(file), + originalUrl: file.name + }); + }; + var onOpenFile = function (evt) { + this._openFileInput?.click(); + }; } -function webViewerPageRender({ +function onPageRender({ pageNumber }) { - if (pageNumber === PDFViewerApplication.page) { - PDFViewerApplication.toolbar?.updateLoadingIndicatorState(true); + if (pageNumber === this.page) { + this.toolbar?.updateLoadingIndicatorState(true); } } -function webViewerPageRendered({ +function onPageRendered({ pageNumber, error }) { - if (pageNumber === PDFViewerApplication.page) { - PDFViewerApplication.toolbar?.updateLoadingIndicatorState(false); + if (pageNumber === this.page) { + this.toolbar?.updateLoadingIndicatorState(false); } - if (PDFViewerApplication.pdfSidebar?.visibleView === SidebarView.THUMBS) { - const pageView = PDFViewerApplication.pdfViewer.getPageView(pageNumber - 1); - const thumbnailView = PDFViewerApplication.pdfThumbnailViewer?.getThumbnail(pageNumber - 1); + if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) { + const pageView = this.pdfViewer.getPageView(pageNumber - 1); + const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1); if (pageView) { thumbnailView?.setImage(pageView); } } if (error) { - PDFViewerApplication._otherError("pdfjs-rendering-error", error); + this._otherError("pdfjs-rendering-error", error); } } -function webViewerPageMode({ +function onPageMode({ mode }) { let view; @@ -13577,42 +14427,39 @@ function webViewerPageMode({ console.error('Invalid "pagemode" hash parameter: ' + mode); return; } - PDFViewerApplication.pdfSidebar?.switchView(view, true); + this.pdfSidebar?.switchView(view, true); } -function webViewerNamedAction(evt) { +function onNamedAction(evt) { switch (evt.action) { case "GoToPage": - PDFViewerApplication.appConfig.toolbar?.pageNumber.select(); + this.appConfig.toolbar?.pageNumber.select(); break; case "Find": - if (!PDFViewerApplication.supportsIntegratedFind) { - PDFViewerApplication.findBar?.toggle(); + if (!this.supportsIntegratedFind) { + this.findBar?.toggle(); } break; case "Print": - PDFViewerApplication.triggerPrinting(); + this.triggerPrinting(); break; case "SaveAs": - PDFViewerApplication.downloadOrSave(); + this.downloadOrSave(); break; } } -function webViewerPresentationModeChanged(evt) { - PDFViewerApplication.pdfViewer.presentationModeState = evt.state; -} -function webViewerSidebarViewChanged({ +function onSidebarViewChanged({ view }) { - PDFViewerApplication.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS; - if (PDFViewerApplication.isInitialViewSet) { - PDFViewerApplication.store?.set("sidebarView", view).catch(() => {}); + this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS; + if (this.isInitialViewSet) { + this.store?.set("sidebarView", view).catch(() => {}); } } -function webViewerUpdateViewarea({ +function onUpdateViewarea({ location }) { - if (PDFViewerApplication.isInitialViewSet) { - PDFViewerApplication.store?.setMultiple({ + if (this.isInitialViewSet) { + this.store?.setMultiple({ page: location.pageNumber, zoom: location.scale, scrollLeft: location.left, @@ -13620,27 +14467,21 @@ function webViewerUpdateViewarea({ rotation: location.rotation }).catch(() => {}); } - if (PDFViewerApplication.appConfig.secondaryToolbar) { - const href = PDFViewerApplication.pdfLinkService.getAnchorUrl(location.pdfOpenParams); - PDFViewerApplication.appConfig.secondaryToolbar.viewBookmarkButton.href = href; - } -} -function webViewerScrollModeChanged(evt) { - if (PDFViewerApplication.isInitialViewSet && !PDFViewerApplication.pdfViewer.isInPresentationMode) { - PDFViewerApplication.store?.set("scrollMode", evt.mode).catch(() => {}); + if (this.appConfig.secondaryToolbar) { + this.appConfig.secondaryToolbar.viewBookmarkButton.href = this.pdfLinkService.getAnchorUrl(location.pdfOpenParams); } } -function webViewerSpreadModeChanged(evt) { - if (PDFViewerApplication.isInitialViewSet && !PDFViewerApplication.pdfViewer.isInPresentationMode) { - PDFViewerApplication.store?.set("spreadMode", evt.mode).catch(() => {}); +function onViewerModesChanged(name, evt) { + if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) { + this.store?.set(name, evt.mode).catch(() => {}); } } -function webViewerResize() { +function onResize() { const { pdfDocument, pdfViewer, pdfRenderingQueue - } = PDFViewerApplication; + } = this; if (pdfRenderingQueue.printing && window.matchMedia("print").matches) { return; } @@ -13653,100 +14494,36 @@ function webViewerResize() { } pdfViewer.update(); } -function webViewerHashchange(evt) { +function onHashchange(evt) { const hash = evt.hash; if (!hash) { return; } - if (!PDFViewerApplication.isInitialViewSet) { - PDFViewerApplication.initialBookmark = hash; - } else if (!PDFViewerApplication.pdfHistory?.popStateInProgress) { - PDFViewerApplication.pdfLinkService.setHash(hash); + if (!this.isInitialViewSet) { + this.initialBookmark = hash; + } else if (!this.pdfHistory?.popStateInProgress) { + this.pdfLinkService.setHash(hash); } } -{ - var webViewerFileInputChange = function (evt) { - if (PDFViewerApplication.pdfViewer?.isInPresentationMode) { - return; - } - const file = evt.fileInput.files[0]; - PDFViewerApplication.open({ - url: URL.createObjectURL(file), - originalUrl: file.name - }); - }; - var webViewerOpenFile = function (evt) { - PDFViewerApplication._openFileInput?.click(); - }; -} -function webViewerPresentationMode() { - PDFViewerApplication.requestPresentationMode(); -} -function webViewerSwitchAnnotationEditorMode(evt) { - PDFViewerApplication.pdfViewer.annotationEditorMode = evt; -} -function webViewerSwitchAnnotationEditorParams(evt) { - PDFViewerApplication.pdfViewer.annotationEditorParams = evt; -} -function webViewerPrint() { - PDFViewerApplication.triggerPrinting(); -} -function webViewerDownload() { - PDFViewerApplication.downloadOrSave(); -} -function webViewerFirstPage() { - PDFViewerApplication.page = 1; -} -function webViewerLastPage() { - PDFViewerApplication.page = PDFViewerApplication.pagesCount; -} -function webViewerNextPage() { - PDFViewerApplication.pdfViewer.nextPage(); -} -function webViewerPreviousPage() { - PDFViewerApplication.pdfViewer.previousPage(); -} -function webViewerZoomIn() { - PDFViewerApplication.zoomIn(); -} -function webViewerZoomOut() { - PDFViewerApplication.zoomOut(); -} -function webViewerZoomReset() { - PDFViewerApplication.zoomReset(); -} -function webViewerPageNumberChanged(evt) { - const pdfViewer = PDFViewerApplication.pdfViewer; +function onPageNumberChanged(evt) { + const { + pdfViewer + } = this; if (evt.value !== "") { - PDFViewerApplication.pdfLinkService.goToPage(evt.value); + this.pdfLinkService.goToPage(evt.value); } if (evt.value !== pdfViewer.currentPageNumber.toString() && evt.value !== pdfViewer.currentPageLabel) { - PDFViewerApplication.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel); + this.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel); } } -function webViewerScaleChanged(evt) { - PDFViewerApplication.pdfViewer.currentScaleValue = evt.value; -} -function webViewerRotateCw() { - PDFViewerApplication.rotatePages(90); -} -function webViewerRotateCcw() { - PDFViewerApplication.rotatePages(-90); -} -function webViewerOptionalContentConfig(evt) { - PDFViewerApplication.pdfViewer.optionalContentConfigPromise = evt.promise; -} -function webViewerSwitchScrollMode(evt) { - PDFViewerApplication.pdfViewer.scrollMode = evt.mode; -} -function webViewerSwitchSpreadMode(evt) { - PDFViewerApplication.pdfViewer.spreadMode = evt.mode; -} -function webViewerDocumentProperties() { - PDFViewerApplication.pdfDocumentProperties?.open(); +function onImageAltTextSettings() { + this.imageAltTextSettings?.open({ + enableGuessAltText: AppOptions.get("enableGuessAltText"), + enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage") + }); } -function webViewerFindFromUrlHash(evt) { - PDFViewerApplication.eventBus.dispatch("find", { +function onFindFromUrlHash(evt) { + this.eventBus.dispatch("find", { source: evt.source, type: "", query: evt.query, @@ -13757,141 +14534,122 @@ function webViewerFindFromUrlHash(evt) { matchDiacritics: true }); } -function webViewerUpdateFindMatchesCount({ +function onUpdateFindMatchesCount({ matchesCount }) { - if (PDFViewerApplication.supportsIntegratedFind) { - PDFViewerApplication.externalServices.updateFindMatchesCount(matchesCount); + if (this.supportsIntegratedFind) { + this.externalServices.updateFindMatchesCount(matchesCount); } else { - PDFViewerApplication.findBar?.updateResultsCount(matchesCount); + this.findBar?.updateResultsCount(matchesCount); } } -function webViewerUpdateFindControlState({ +function onUpdateFindControlState({ state, previous, + entireWord, matchesCount, rawQuery }) { - if (PDFViewerApplication.supportsIntegratedFind) { - PDFViewerApplication.externalServices.updateFindControlState({ + if (this.supportsIntegratedFind) { + this.externalServices.updateFindControlState({ result: state, findPrevious: previous, + entireWord, matchesCount, rawQuery }); } else { - PDFViewerApplication.findBar?.updateUIState(state, previous, matchesCount); + this.findBar?.updateUIState(state, previous, matchesCount); } } -function webViewerScaleChanging(evt) { - PDFViewerApplication.toolbar?.setPageScale(evt.presetValue, evt.scale); - PDFViewerApplication.pdfViewer.update(); +function onScaleChanging(evt) { + this.toolbar?.setPageScale(evt.presetValue, evt.scale); + this.pdfViewer.update(); } -function webViewerRotationChanging(evt) { - if (PDFViewerApplication.pdfThumbnailViewer) { - PDFViewerApplication.pdfThumbnailViewer.pagesRotation = evt.pagesRotation; +function onRotationChanging(evt) { + if (this.pdfThumbnailViewer) { + this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation; } - PDFViewerApplication.forceRendering(); - PDFViewerApplication.pdfViewer.currentPageNumber = evt.pageNumber; + this.forceRendering(); + this.pdfViewer.currentPageNumber = evt.pageNumber; } -function webViewerPageChanging({ +function onPageChanging({ pageNumber, pageLabel }) { - PDFViewerApplication.toolbar?.setPageNumber(pageNumber, pageLabel); - PDFViewerApplication.secondaryToolbar?.setPageNumber(pageNumber); - if (PDFViewerApplication.pdfSidebar?.visibleView === SidebarView.THUMBS) { - PDFViewerApplication.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber); - } - const currentPage = PDFViewerApplication.pdfViewer.getPageView(pageNumber - 1); - PDFViewerApplication.toolbar?.updateLoadingIndicatorState(currentPage?.renderingState === RenderingStates.RUNNING); -} -function webViewerResolutionChange(evt) { - PDFViewerApplication.pdfViewer.refresh(); -} -function webViewerVisibilityChange(evt) { - if (document.visibilityState === "visible") { - setZoomDisabledTimeout(); - } -} -let zoomDisabledTimeout = null; -function setZoomDisabledTimeout() { - if (zoomDisabledTimeout) { - clearTimeout(zoomDisabledTimeout); + this.toolbar?.setPageNumber(pageNumber, pageLabel); + this.secondaryToolbar?.setPageNumber(pageNumber); + if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) { + this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber); } - zoomDisabledTimeout = setTimeout(function () { - zoomDisabledTimeout = null; - }, WHEEL_ZOOM_DISABLED_TIMEOUT); + const currentPage = this.pdfViewer.getPageView(pageNumber - 1); + this.toolbar?.updateLoadingIndicatorState(currentPage?.renderingState === RenderingStates.RUNNING); } -function webViewerWheel(evt) { +function onWheel(evt) { const { pdfViewer, supportsMouseWheelZoomCtrlKey, supportsMouseWheelZoomMetaKey, supportsPinchToZoom - } = PDFViewerApplication; + } = this; if (pdfViewer.isInPresentationMode) { return; } const deltaMode = evt.deltaMode; let scaleFactor = Math.exp(-evt.deltaY / 100); const isBuiltInMac = false; - const isPinchToZoom = evt.ctrlKey && !PDFViewerApplication._isCtrlKeyDown && deltaMode === WheelEvent.DOM_DELTA_PIXEL && evt.deltaX === 0 && (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && evt.deltaZ === 0; + const isPinchToZoom = evt.ctrlKey && !this._isCtrlKeyDown && deltaMode === WheelEvent.DOM_DELTA_PIXEL && evt.deltaX === 0 && (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && evt.deltaZ === 0; const origin = [evt.clientX, evt.clientY]; if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) { evt.preventDefault(); - if (PDFViewerApplication._isScrolling || zoomDisabledTimeout || document.visibilityState === "hidden" || PDFViewerApplication.overlayManager.active) { + if (this._isScrolling || document.visibilityState === "hidden" || this.overlayManager.active) { return; } if (isPinchToZoom && supportsPinchToZoom) { - scaleFactor = PDFViewerApplication._accumulateFactor(pdfViewer.currentScale, scaleFactor, "_wheelUnusedFactor"); - PDFViewerApplication.updateZoom(null, scaleFactor, origin); + scaleFactor = this._accumulateFactor(pdfViewer.currentScale, scaleFactor, "_wheelUnusedFactor"); + this.updateZoom(null, scaleFactor, origin); } else { const delta = normalizeWheelEventDirection(evt); let ticks = 0; if (deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_PAGE) { - if (Math.abs(delta) >= 1) { - ticks = Math.sign(delta); - } else { - ticks = PDFViewerApplication._accumulateTicks(delta, "_wheelUnusedTicks"); - } + ticks = Math.abs(delta) >= 1 ? Math.sign(delta) : this._accumulateTicks(delta, "_wheelUnusedTicks"); } else { const PIXELS_PER_LINE_SCALE = 30; - ticks = PDFViewerApplication._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, "_wheelUnusedTicks"); + ticks = this._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, "_wheelUnusedTicks"); } - PDFViewerApplication.updateZoom(ticks, null, origin); + this.updateZoom(ticks, null, origin); } } } -function webViewerTouchStart(evt) { - if (PDFViewerApplication.pdfViewer.isInPresentationMode || evt.touches.length < 2) { +function onTouchStart(evt) { + if (this.pdfViewer.isInPresentationMode || evt.touches.length < 2) { return; } evt.preventDefault(); - if (evt.touches.length !== 2 || PDFViewerApplication.overlayManager.active) { - PDFViewerApplication._touchInfo = null; + if (evt.touches.length !== 2 || this.overlayManager.active) { + this._touchInfo = null; return; } let [touch0, touch1] = evt.touches; if (touch0.identifier > touch1.identifier) { [touch0, touch1] = [touch1, touch0]; } - PDFViewerApplication._touchInfo = { + this._touchInfo = { touch0X: touch0.pageX, touch0Y: touch0.pageY, touch1X: touch1.pageX, touch1Y: touch1.pageY }; } -function webViewerTouchMove(evt) { - if (!PDFViewerApplication._touchInfo || evt.touches.length !== 2) { +function onTouchMove(evt) { + if (!this._touchInfo || evt.touches.length !== 2) { return; } const { pdfViewer, _touchInfo, supportsPinchToZoom - } = PDFViewerApplication; + } = this; let [touch0, touch1] = evt.touches; if (touch0.identifier > touch1.identifier) { [touch0, touch1] = [touch1, touch0]; @@ -13950,46 +14708,46 @@ function webViewerTouchMove(evt) { const distance = Math.hypot(page0X - page1X, page0Y - page1Y) || 1; const pDistance = Math.hypot(pTouch0X - pTouch1X, pTouch0Y - pTouch1Y) || 1; if (supportsPinchToZoom) { - const newScaleFactor = PDFViewerApplication._accumulateFactor(pdfViewer.currentScale, distance / pDistance, "_touchUnusedFactor"); - PDFViewerApplication.updateZoom(null, newScaleFactor, origin); + const newScaleFactor = this._accumulateFactor(pdfViewer.currentScale, distance / pDistance, "_touchUnusedFactor"); + this.updateZoom(null, newScaleFactor, origin); } else { const PIXELS_PER_LINE_SCALE = 30; - const ticks = PDFViewerApplication._accumulateTicks((distance - pDistance) / PIXELS_PER_LINE_SCALE, "_touchUnusedTicks"); - PDFViewerApplication.updateZoom(ticks, null, origin); + const ticks = this._accumulateTicks((distance - pDistance) / PIXELS_PER_LINE_SCALE, "_touchUnusedTicks"); + this.updateZoom(ticks, null, origin); } } -function webViewerTouchEnd(evt) { - if (!PDFViewerApplication._touchInfo) { +function onTouchEnd(evt) { + if (!this._touchInfo) { return; } evt.preventDefault(); - PDFViewerApplication._touchInfo = null; - PDFViewerApplication._touchUnusedTicks = 0; - PDFViewerApplication._touchUnusedFactor = 1; + this._touchInfo = null; + this._touchUnusedTicks = 0; + this._touchUnusedFactor = 1; } -function webViewerClick(evt) { - if (!PDFViewerApplication.secondaryToolbar?.isOpen) { +function onClick(evt) { + if (!this.secondaryToolbar?.isOpen) { return; } - const appConfig = PDFViewerApplication.appConfig; - if (PDFViewerApplication.pdfViewer.containsElement(evt.target) || appConfig.toolbar?.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar?.toggleButton) { - PDFViewerApplication.secondaryToolbar.close(); + const appConfig = this.appConfig; + if (this.pdfViewer.containsElement(evt.target) || appConfig.toolbar?.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar?.toggleButton) { + this.secondaryToolbar.close(); } } -function webViewerKeyUp(evt) { +function onKeyUp(evt) { if (evt.key === "Control") { - PDFViewerApplication._isCtrlKeyDown = false; + this._isCtrlKeyDown = false; } } -function webViewerKeyDown(evt) { - PDFViewerApplication._isCtrlKeyDown = evt.key === "Control"; - if (PDFViewerApplication.overlayManager.active) { +function onKeyDown(evt) { + this._isCtrlKeyDown = evt.key === "Control"; + if (this.overlayManager.active) { return; } const { eventBus, pdfViewer - } = PDFViewerApplication; + } = this; const isViewerInPresentationMode = pdfViewer.isInPresentationMode; let handled = false, ensureViewerFocused = false; @@ -13997,16 +14755,16 @@ function webViewerKeyDown(evt) { if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) { switch (evt.keyCode) { case 70: - if (!PDFViewerApplication.supportsIntegratedFind && !evt.shiftKey) { - PDFViewerApplication.findBar?.open(); + if (!this.supportsIntegratedFind && !evt.shiftKey) { + this.findBar?.open(); handled = true; } break; case 71: - if (!PDFViewerApplication.supportsIntegratedFind) { + if (!this.supportsIntegratedFind) { const { state - } = PDFViewerApplication.findController; + } = this.findController; if (state) { const newState = { source: window, @@ -14025,34 +14783,34 @@ function webViewerKeyDown(evt) { case 107: case 187: case 171: - PDFViewerApplication.zoomIn(); + this.zoomIn(); handled = true; break; case 173: case 109: case 189: - PDFViewerApplication.zoomOut(); + this.zoomOut(); handled = true; break; case 48: case 96: if (!isViewerInPresentationMode) { - setTimeout(function () { - PDFViewerApplication.zoomReset(); + setTimeout(() => { + this.zoomReset(); }); handled = false; } break; case 38: - if (isViewerInPresentationMode || PDFViewerApplication.page > 1) { - PDFViewerApplication.page = 1; + if (isViewerInPresentationMode || this.page > 1) { + this.page = 1; handled = true; ensureViewerFocused = true; } break; case 40: - if (isViewerInPresentationMode || PDFViewerApplication.page < PDFViewerApplication.pagesCount) { - PDFViewerApplication.page = PDFViewerApplication.pagesCount; + if (isViewerInPresentationMode || this.page < this.pagesCount) { + this.page = this.pagesCount; handled = true; ensureViewerFocused = true; } @@ -14080,9 +14838,9 @@ function webViewerKeyDown(evt) { if (cmd === 3 || cmd === 10) { switch (evt.keyCode) { case 80: - PDFViewerApplication.requestPresentationMode(); + this.requestPresentationMode(); handled = true; - PDFViewerApplication.externalServices.reportTelemetry({ + this.externalServices.reportTelemetry({ type: "buttons", data: { id: "presentationModeKeyboard" @@ -14090,8 +14848,8 @@ function webViewerKeyDown(evt) { }); break; case 71: - if (PDFViewerApplication.appConfig.toolbar) { - PDFViewerApplication.appConfig.toolbar.pageNumber.select(); + if (this.appConfig.toolbar) { + this.appConfig.toolbar.pageNumber.select(); handled = true; } break; @@ -14116,8 +14874,8 @@ function webViewerKeyDown(evt) { turnOnlyIfPageFit = false; switch (evt.keyCode) { case 38: - if (PDFViewerApplication.supportsCaretBrowsingMode) { - PDFViewerApplication.moveCaret(true, false); + if (this.supportsCaretBrowsingMode) { + this.moveCaret(true, false); handled = true; break; } @@ -14134,7 +14892,7 @@ function webViewerKeyDown(evt) { turnPage = -1; break; case 37: - if (PDFViewerApplication.supportsCaretBrowsingMode) { + if (this.supportsCaretBrowsingMode) { return; } if (pdfViewer.isHorizontalScrollbarEnabled) { @@ -14145,18 +14903,18 @@ function webViewerKeyDown(evt) { turnPage = -1; break; case 27: - if (PDFViewerApplication.secondaryToolbar?.isOpen) { - PDFViewerApplication.secondaryToolbar.close(); + if (this.secondaryToolbar?.isOpen) { + this.secondaryToolbar.close(); handled = true; } - if (!PDFViewerApplication.supportsIntegratedFind && PDFViewerApplication.findBar?.opened) { - PDFViewerApplication.findBar.close(); + if (!this.supportsIntegratedFind && this.findBar?.opened) { + this.findBar.close(); handled = true; } break; case 40: - if (PDFViewerApplication.supportsCaretBrowsingMode) { - PDFViewerApplication.moveCaret(false, false); + if (this.supportsCaretBrowsingMode) { + this.moveCaret(false, false); handled = true; break; } @@ -14174,7 +14932,7 @@ function webViewerKeyDown(evt) { turnPage = 1; break; case 39: - if (PDFViewerApplication.supportsCaretBrowsingMode) { + if (this.supportsCaretBrowsingMode) { return; } if (pdfViewer.isHorizontalScrollbarEnabled) { @@ -14185,30 +14943,30 @@ function webViewerKeyDown(evt) { turnPage = 1; break; case 36: - if (isViewerInPresentationMode || PDFViewerApplication.page > 1) { - PDFViewerApplication.page = 1; + if (isViewerInPresentationMode || this.page > 1) { + this.page = 1; handled = true; ensureViewerFocused = true; } break; case 35: - if (isViewerInPresentationMode || PDFViewerApplication.page < PDFViewerApplication.pagesCount) { - PDFViewerApplication.page = PDFViewerApplication.pagesCount; + if (isViewerInPresentationMode || this.page < this.pagesCount) { + this.page = this.pagesCount; handled = true; ensureViewerFocused = true; } break; case 83: - PDFViewerApplication.pdfCursorTools?.switchTool(CursorTool.SELECT); + this.pdfCursorTools?.switchTool(CursorTool.SELECT); break; case 72: - PDFViewerApplication.pdfCursorTools?.switchTool(CursorTool.HAND); + this.pdfCursorTools?.switchTool(CursorTool.HAND); break; case 82: - PDFViewerApplication.rotatePages(90); + this.rotatePages(90); break; case 115: - PDFViewerApplication.pdfSidebar?.toggle(); + this.pdfSidebar?.toggle(); break; } if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")) { @@ -14231,15 +14989,15 @@ function webViewerKeyDown(evt) { handled = true; break; case 38: - PDFViewerApplication.moveCaret(true, true); + this.moveCaret(true, true); handled = true; break; case 40: - PDFViewerApplication.moveCaret(false, true); + this.moveCaret(false, true); handled = true; break; case 82: - PDFViewerApplication.rotatePages(-90); + this.rotatePages(-90); break; } } @@ -14260,22 +15018,14 @@ function beforeUnload(evt) { evt.returnValue = ""; return false; } -function webViewerAnnotationEditorStatesChanged(data) { - PDFViewerApplication.externalServices.updateEditorStates(data); -} -function webViewerReportTelemetry({ - details -}) { - PDFViewerApplication.externalServices.reportTelemetry(details); -} ;// CONCATENATED MODULE: ./web/viewer.js -const pdfjsVersion = "4.3.136"; -const pdfjsBuild = "0cec64437"; +const pdfjsVersion = "4.6.82"; +const pdfjsBuild = "9b541910f"; const AppConstants = { LinkTarget: LinkTarget, RenderingStates: RenderingStates, @@ -14300,7 +15050,6 @@ function getViewerConfiguration() { next: document.getElementById("next"), zoomIn: document.getElementById("zoomIn"), zoomOut: document.getElementById("zoomOut"), - viewFind: document.getElementById("viewFind"), print: document.getElementById("print"), editorFreeTextButton: document.getElementById("editorFreeText"), editorFreeTextParamsToolbar: document.getElementById("editorFreeTextParamsToolbar"), @@ -14334,6 +15083,8 @@ function getViewerConfiguration() { spreadNoneButton: document.getElementById("spreadNone"), spreadOddButton: document.getElementById("spreadOdd"), spreadEvenButton: document.getElementById("spreadEven"), + imageAltTextSettingsButton: document.getElementById("imageAltTextSettings"), + imageAltTextSettingsSeparator: document.getElementById("imageAltTextSettingsSeparator"), documentPropertiesButton: document.getElementById("documentProperties") }, sidebar: { @@ -14399,6 +15150,35 @@ function getViewerConfiguration() { cancelButton: document.getElementById("altTextCancel"), saveButton: document.getElementById("altTextSave") }, + newAltTextDialog: { + dialog: document.getElementById("newAltTextDialog"), + title: document.getElementById("newAltTextTitle"), + descriptionContainer: document.getElementById("newAltTextDescriptionContainer"), + textarea: document.getElementById("newAltTextDescriptionTextarea"), + disclaimer: document.getElementById("newAltTextDisclaimer"), + learnMore: document.getElementById("newAltTextLearnMore"), + imagePreview: document.getElementById("newAltTextImagePreview"), + createAutomatically: document.getElementById("newAltTextCreateAutomatically"), + createAutomaticallyButton: document.getElementById("newAltTextCreateAutomaticallyButton"), + downloadModel: document.getElementById("newAltTextDownloadModel"), + downloadModelDescription: document.getElementById("newAltTextDownloadModelDescription"), + error: document.getElementById("newAltTextError"), + errorCloseButton: document.getElementById("newAltTextCloseButton"), + cancelButton: document.getElementById("newAltTextCancel"), + notNowButton: document.getElementById("newAltTextNotNow"), + saveButton: document.getElementById("newAltTextSave") + }, + altTextSettingsDialog: { + dialog: document.getElementById("altTextSettingsDialog"), + createModelButton: document.getElementById("createModelButton"), + aiModelSettings: document.getElementById("aiModelSettings"), + learnMore: document.getElementById("altTextSettingsLearnMore"), + deleteModelButton: document.getElementById("deleteModelButton"), + downloadModelButton: document.getElementById("downloadModelButton"), + showAltTextDialogButton: document.getElementById("showAltTextDialogButton"), + altTextSettingsCloseButton: document.getElementById("altTextSettingsCloseButton"), + closeButton: document.getElementById("altTextSettingsCloseButton") + }, annotationEditorParams: { editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"), editorFreeTextColor: document.getElementById("editorFreeTextColor"),