mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-04 21:37:23 +02:00
Compare commits
No commits in common. "master" and "v6.2.108" have entirely different histories.
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@ -24,13 +24,13 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
- name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/init@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
|
||||||
with:
|
with:
|
||||||
languages: ${{ matrix.language }}
|
languages: ${{ matrix.language }}
|
||||||
queries: security-and-quality
|
queries: security-and-quality
|
||||||
|
|
||||||
- name: Autobuild CodeQL
|
- name: Autobuild CodeQL
|
||||||
uses: github/codeql-action/autobuild@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/autobuild@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
|
||||||
|
|
||||||
- name: Perform CodeQL analysis
|
- name: Perform CodeQL analysis
|
||||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
uses: github/codeql-action/analyze@e0647621c2984b5ed2f768cb892365bf2a616ad1 # v4.37.2
|
||||||
|
|||||||
@ -30,9 +30,6 @@ latest JavaScript features; please also see [this wiki page](https://github.com/
|
|||||||
|
|
||||||
+ Older browsers: https://mozilla.github.io/pdf.js/legacy/web/viewer.html
|
+ Older browsers: https://mozilla.github.io/pdf.js/legacy/web/viewer.html
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Open new files via the menu (the ">>" icon) or by dragging and dropping.
|
|
||||||
|
|
||||||
### Browser Extensions
|
### Browser Extensions
|
||||||
|
|
||||||
#### Firefox
|
#### Firefox
|
||||||
|
|||||||
@ -62,13 +62,6 @@ export default [
|
|||||||
rules: {
|
rules: {
|
||||||
...regexpPlugin.configs["flat/recommended"].rules,
|
...regexpPlugin.configs["flat/recommended"].rules,
|
||||||
"regexp/no-legacy-features": "off",
|
"regexp/no-legacy-features": "off",
|
||||||
"regexp/no-octal": "error",
|
|
||||||
"regexp/no-potentially-useless-backreference": "error",
|
|
||||||
"regexp/no-standalone-backslash": "error",
|
|
||||||
"regexp/no-super-linear-move": "error",
|
|
||||||
"regexp/optimal-lookaround-quantifier": "error",
|
|
||||||
"regexp/prefer-escape-replacement-dollar-char": "error",
|
|
||||||
"regexp/prefer-regexp-exec": "error",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
12
external/builder/builder.mjs
vendored
12
external/builder/builder.mjs
vendored
@ -53,7 +53,7 @@ function preprocess(inFilename, outFilename, defines) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return content.replaceAll(
|
return content.replaceAll(
|
||||||
/^[ \t]*@import\s+url\(([^)]+)\);[ \t]*$/gm,
|
/^\s*@import\s+url\(([^)]+)\);\s*$/gm,
|
||||||
function (all, url) {
|
function (all, url) {
|
||||||
const file = path.join(path.dirname(baseUrl), url);
|
const file = path.join(path.dirname(baseUrl), url);
|
||||||
const imported = fs.readFileSync(file, "utf8").toString();
|
const imported = fs.readFileSync(file, "utf8").toString();
|
||||||
@ -73,7 +73,10 @@ function preprocess(inFilename, outFilename, defines) {
|
|||||||
const out = [];
|
const out = [];
|
||||||
let i = 0;
|
let i = 0;
|
||||||
function readLine() {
|
function readLine() {
|
||||||
return i < totalLines ? lines[i++] : null;
|
if (i < totalLines) {
|
||||||
|
return lines[i++];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
const writeLine =
|
const writeLine =
|
||||||
typeof outFilename === "function"
|
typeof outFilename === "function"
|
||||||
@ -124,7 +127,10 @@ function preprocess(inFilename, outFilename, defines) {
|
|||||||
function expand(line) {
|
function expand(line) {
|
||||||
line = line.replaceAll(/__\w+__/g, function (variable) {
|
line = line.replaceAll(/__\w+__/g, function (variable) {
|
||||||
variable = variable.substring(2, variable.length - 2);
|
variable = variable.substring(2, variable.length - 2);
|
||||||
return variable in defines ? defines[variable] : "";
|
if (variable in defines) {
|
||||||
|
return defines[variable];
|
||||||
|
}
|
||||||
|
return "";
|
||||||
});
|
});
|
||||||
writeLine(line);
|
writeLine(line);
|
||||||
}
|
}
|
||||||
|
|||||||
2
external/cmapscompress/parse.mjs
vendored
2
external/cmapscompress/parse.mjs
vendored
@ -37,7 +37,7 @@ function parseAdobeCMap(content) {
|
|||||||
result.usecmap = m[1];
|
result.usecmap = m[1];
|
||||||
}
|
}
|
||||||
const re =
|
const re =
|
||||||
/\b(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(?:endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
|
/(\d+)\s+(begincodespacerange|beginnotdefrange|begincidchar|begincidrange|beginbfchar|beginbfrange)\n([\s\S]*?)\n(?:endcodespacerange|endnotdefrange|endcidchar|endcidrange|endbfchar|endbfrange)/g;
|
||||||
while ((m = re.exec(body))) {
|
while ((m = re.exec(body))) {
|
||||||
const lines = m[3].toLowerCase().split("\n");
|
const lines = m[3].toLowerCase().split("\n");
|
||||||
|
|
||||||
|
|||||||
@ -52,61 +52,21 @@ pdfjs-bookmark-button-label = بلگه هیم سکویی
|
|||||||
pdfjs-tools-button =
|
pdfjs-tools-button =
|
||||||
.title = ٱوزارا
|
.title = ٱوزارا
|
||||||
pdfjs-tools-button-label = ٱوزارا
|
pdfjs-tools-button-label = ٱوزارا
|
||||||
pdfjs-first-page-button =
|
|
||||||
.title = رئڌن و بلگه نیایی
|
|
||||||
pdfjs-first-page-button-label = رئڌن و بلگه نیایی
|
|
||||||
pdfjs-last-page-button =
|
|
||||||
.title = رئڌن و بلگه دیندایی
|
|
||||||
pdfjs-last-page-button-label = رئڌن و بلگه دیندایی
|
|
||||||
pdfjs-page-rotate-cw-button =
|
|
||||||
.title = لر خردن ساعتگرد
|
|
||||||
pdfjs-page-rotate-cw-button-label = لر خردن ساعتگرد
|
|
||||||
pdfjs-page-rotate-ccw-button =
|
|
||||||
.title = لر خردن پاد ساعتگرد
|
|
||||||
pdfjs-page-rotate-ccw-button-label = لر خردن پاد ساعتگرد
|
|
||||||
pdfjs-cursor-text-select-tool-button =
|
|
||||||
.title = فعال کردن ٱوزار پسند هؽل
|
|
||||||
pdfjs-cursor-text-select-tool-button-label = ٱوزار پسند هؽل
|
pdfjs-cursor-text-select-tool-button-label = ٱوزار پسند هؽل
|
||||||
pdfjs-cursor-hand-tool-button =
|
pdfjs-cursor-hand-tool-button =
|
||||||
.title = فعال کردن ٱوزار دست
|
.title = فعال کردن ٱوزار دست
|
||||||
pdfjs-cursor-hand-tool-button-label = ٱوزار دست
|
pdfjs-cursor-hand-tool-button-label = ٱوزار دست
|
||||||
pdfjs-scroll-page-button =
|
|
||||||
.title = و کار گرؽڌن اسکرۊل بلگه
|
|
||||||
pdfjs-scroll-page-button-label = اسکرۊل بلگه
|
|
||||||
pdfjs-scroll-vertical-button =
|
|
||||||
.title = و کار گرؽڌن اسکرۊل عمۊدی
|
|
||||||
pdfjs-scroll-vertical-button-label = اسکرۊل عمۊدی
|
|
||||||
pdfjs-scroll-horizontal-button =
|
|
||||||
.title = و کار گرؽڌن اسکرۊل اوفوقی
|
|
||||||
pdfjs-scroll-horizontal-button-label = اسکرۊل اوفوقی
|
|
||||||
pdfjs-scroll-wrapped-button =
|
|
||||||
.title = و کار گرؽڌن اسکرۊل پؽچسته
|
|
||||||
pdfjs-scroll-wrapped-button-label = اسکرۊل پؽچسته
|
|
||||||
|
|
||||||
## Document properties dialog
|
## Document properties dialog
|
||||||
|
|
||||||
pdfjs-document-properties-button =
|
|
||||||
.title = خۊسۊسیات سند…
|
|
||||||
pdfjs-document-properties-button-label = خۊسۊسیات سند…
|
|
||||||
pdfjs-document-properties-file-name = نوم فایل:
|
pdfjs-document-properties-file-name = نوم فایل:
|
||||||
pdfjs-document-properties-file-size = هندا فایل:
|
pdfjs-document-properties-file-size = هندا فایل:
|
||||||
pdfjs-document-properties-title = عونوان:
|
pdfjs-document-properties-title = عونوان:
|
||||||
pdfjs-document-properties-author = هؽل کوݩ:
|
pdfjs-document-properties-author = هؽل کوݩ:
|
||||||
pdfjs-document-properties-subject = سرتال:
|
pdfjs-document-properties-subject = سرتال:
|
||||||
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") }
|
|
||||||
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 = هندا بلگه:
|
||||||
pdfjs-document-properties-page-size-unit-inches = اینچ
|
pdfjs-document-properties-page-size-unit-inches = اینچ
|
||||||
pdfjs-document-properties-page-size-unit-millimeters = میلی متر
|
pdfjs-document-properties-page-size-unit-millimeters = میلی متر
|
||||||
pdfjs-document-properties-page-size-orientation-portrait = portrait
|
|
||||||
pdfjs-document-properties-page-size-orientation-landscape = landscape
|
|
||||||
pdfjs-document-properties-page-size-name-a-three = A3
|
pdfjs-document-properties-page-size-name-a-three = A3
|
||||||
pdfjs-document-properties-page-size-name-a-four = A4
|
pdfjs-document-properties-page-size-name-a-four = A4
|
||||||
pdfjs-document-properties-page-size-name-letter = نامه
|
pdfjs-document-properties-page-size-name-letter = نامه
|
||||||
@ -124,16 +84,12 @@ 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 = نیشتن زل وب:
|
|
||||||
pdfjs-document-properties-linearized-yes = هری
|
pdfjs-document-properties-linearized-yes = هری
|
||||||
pdfjs-document-properties-linearized-no = ن
|
pdfjs-document-properties-linearized-no = ن
|
||||||
pdfjs-document-properties-close-button = بستن
|
pdfjs-document-properties-close-button = بستن
|
||||||
|
|
||||||
## Print
|
## Print
|
||||||
|
|
||||||
pdfjs-print-progress-message = ٱماڌه کردن سند سی چاپ کردن…
|
|
||||||
# Variables:
|
# Variables:
|
||||||
# $progress (Number) - percent value
|
# $progress (Number) - percent value
|
||||||
pdfjs-print-progress-percent = { $progress }%
|
pdfjs-print-progress-percent = { $progress }%
|
||||||
@ -143,55 +99,6 @@ pdfjs-print-progress-close-button = لقو
|
|||||||
|
|
||||||
pdfjs-toggle-sidebar-button =
|
pdfjs-toggle-sidebar-button =
|
||||||
.title = آلشت هالت نوار کلی
|
.title = آلشت هالت نوار کلی
|
||||||
pdfjs-toggle-sidebar-button-label = آلشت هالت نوار کلی
|
|
||||||
pdfjs-document-outline-button-label = تئر سند
|
|
||||||
pdfjs-attachments-button =
|
|
||||||
.title = نشووݩ داڌن پیوستا
|
|
||||||
pdfjs-attachments-button-label = پیوستا
|
|
||||||
pdfjs-layers-button-label = لایه یل
|
|
||||||
pdfjs-thumbs-button =
|
|
||||||
.title = نشووݩ داڌن شؽواتا کۊچیر
|
|
||||||
pdfjs-thumbs-button-label = شؽواتا کۊچیر
|
|
||||||
pdfjs-findbar-button =
|
|
||||||
.title = جوستن من سند
|
|
||||||
pdfjs-findbar-button-label = جوستن
|
|
||||||
pdfjs-additional-layers = لایه یل ازافه
|
|
||||||
|
|
||||||
## Thumbnails panel item (tooltip and alt text for images)
|
|
||||||
|
|
||||||
# Variables:
|
|
||||||
# $page (Number) - the page number
|
|
||||||
pdfjs-thumb-page-title =
|
|
||||||
.title = بلگه { $page }
|
|
||||||
|
|
||||||
## Find panel button title and messages
|
|
||||||
|
|
||||||
pdfjs-find-previous-button-label = دیندایی
|
|
||||||
pdfjs-find-next-button-label = بئڌی
|
|
||||||
pdfjs-find-highlight-checkbox = هایلایت کردن پوی
|
|
||||||
|
|
||||||
## Predefined zoom values
|
|
||||||
|
|
||||||
pdfjs-page-scale-width = پئنا بلگه
|
|
||||||
pdfjs-page-scale-fit = هندا کردن بلگه
|
|
||||||
pdfjs-page-scale-auto = زۊم کردن خوتکار
|
|
||||||
pdfjs-page-scale-actual = هندا واقعی
|
|
||||||
# Variables:
|
|
||||||
# $scale (Number) - percent value for page scale
|
|
||||||
pdfjs-page-scale-percent = { $scale }%
|
|
||||||
|
|
||||||
## PDF page
|
|
||||||
|
|
||||||
# Variables:
|
|
||||||
# $page (Number) - the page number
|
|
||||||
pdfjs-page-landmark =
|
|
||||||
.aria-label = بلگه { $page }
|
|
||||||
|
|
||||||
## Annotations
|
|
||||||
|
|
||||||
# Variables:
|
|
||||||
# $dateObj (Date) - the modification date and time of the annotation
|
|
||||||
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
|
|
||||||
|
|
||||||
## Password
|
## Password
|
||||||
|
|
||||||
@ -200,12 +107,6 @@ pdfjs-password-cancel-button = لقو
|
|||||||
|
|
||||||
## Editing
|
## Editing
|
||||||
|
|
||||||
pdfjs-editor-free-text-button =
|
|
||||||
.title = هؽل
|
|
||||||
pdfjs-editor-free-text-button-label = هؽل
|
|
||||||
pdfjs-editor-ink-button =
|
|
||||||
.title = کشیڌن
|
|
||||||
pdfjs-editor-ink-button-label = کشیڌن
|
|
||||||
pdfjs-editor-stamp-button =
|
pdfjs-editor-stamp-button =
|
||||||
.title = ٱووردن یا آلشت شؽواتا
|
.title = ٱووردن یا آلشت شؽواتا
|
||||||
pdfjs-editor-stamp-button-label = ٱووردن یا آلشت شؽواتا
|
pdfjs-editor-stamp-button-label = ٱووردن یا آلشت شؽواتا
|
||||||
@ -217,14 +118,6 @@ pdfjs-editor-stamp-editor =
|
|||||||
|
|
||||||
##
|
##
|
||||||
|
|
||||||
# Editor Parameters
|
|
||||||
pdfjs-editor-free-text-color-input = رنگ
|
|
||||||
pdfjs-editor-free-text-size-input = هندا
|
|
||||||
pdfjs-editor-ink-color-input = رنگ
|
|
||||||
pdfjs-editor-ink-thickness-input = کۊلۊفتی
|
|
||||||
pdfjs-editor-ink-opacity-input = کر بیڌن
|
|
||||||
# This refers to the thickness of the line used for free highlighting (not bound to text)
|
|
||||||
pdfjs-editor-free-highlight-thickness-input = کۊلۊفتی
|
|
||||||
# .default-content is used as a placeholder in an empty text editor.
|
# .default-content is used as a placeholder in an empty text editor.
|
||||||
pdfjs-free-text2 =
|
pdfjs-free-text2 =
|
||||||
.aria-label = آلشتگر هؽل
|
.aria-label = آلشتگر هؽل
|
||||||
@ -234,55 +127,15 @@ pdfjs-editor-comments-sidebar-no-comments-link = قلوه دووسته بۊین
|
|||||||
## Alt-text dialog
|
## Alt-text dialog
|
||||||
|
|
||||||
pdfjs-editor-alt-text-cancel-button = لقو
|
pdfjs-editor-alt-text-cancel-button = لقو
|
||||||
pdfjs-editor-alt-text-save-button = زفت
|
|
||||||
|
|
||||||
## Color picker
|
|
||||||
|
|
||||||
pdfjs-editor-colorpicker-yellow =
|
|
||||||
.title = هیل
|
|
||||||
pdfjs-editor-colorpicker-green =
|
|
||||||
.title = ساوز
|
|
||||||
pdfjs-editor-colorpicker-blue =
|
|
||||||
.title = کوۊ
|
|
||||||
pdfjs-editor-colorpicker-pink =
|
|
||||||
.title = آل
|
|
||||||
pdfjs-editor-colorpicker-red =
|
|
||||||
.title = سوئر
|
|
||||||
|
|
||||||
## Show all highlights
|
|
||||||
## This is a toggle button to show/hide all the highlights.
|
|
||||||
|
|
||||||
pdfjs-editor-highlight-show-all-button-label = نشووݩ داڌن پوی
|
|
||||||
pdfjs-editor-highlight-show-all-button =
|
|
||||||
.title = نشووݩ داڌن پوی
|
|
||||||
|
|
||||||
## New alt-text dialog
|
## 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.
|
## 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 = قلوه دووسته بۊین
|
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = قلوه دووسته بۊین
|
||||||
pdfjs-editor-new-alt-text-not-now-button = سکو ن
|
|
||||||
pdfjs-editor-new-alt-text-error-close-button = بستن
|
|
||||||
|
|
||||||
## "Annotations removed" bar
|
|
||||||
|
|
||||||
pdfjs-editor-undo-bar-undo-button-label = وورگندن
|
|
||||||
pdfjs-editor-undo-bar-close-button =
|
|
||||||
.title = بستن
|
|
||||||
pdfjs-editor-undo-bar-close-button-label = بستن
|
|
||||||
|
|
||||||
## Tab panels
|
|
||||||
|
|
||||||
pdfjs-editor-add-signature-draw-thickness-range-label = کۊلۊفتی
|
|
||||||
|
|
||||||
## Controls
|
|
||||||
|
|
||||||
pdfjs-editor-add-signature-error-close-button = بستن
|
|
||||||
|
|
||||||
## Dialog buttons
|
## Dialog buttons
|
||||||
|
|
||||||
pdfjs-editor-add-signature-cancel-button = لقو
|
pdfjs-editor-add-signature-cancel-button = لقو
|
||||||
pdfjs-editor-add-signature-add-button = ٱووردن
|
|
||||||
pdfjs-editor-edit-signature-update-button = ورۊ رسۊوی
|
|
||||||
|
|
||||||
## Comment popup
|
## Comment popup
|
||||||
|
|
||||||
@ -294,32 +147,4 @@ pdfjs-editor-edit-comment-popup-button =
|
|||||||
|
|
||||||
# An existing comment is edited
|
# An existing comment is edited
|
||||||
pdfjs-editor-edit-comment-dialog-title-when-editing = آلشت منشڌ
|
pdfjs-editor-edit-comment-dialog-title-when-editing = آلشت منشڌ
|
||||||
pdfjs-editor-edit-comment-dialog-save-button-when-editing = ورۊ رسۊوی
|
|
||||||
pdfjs-editor-edit-comment-dialog-save-button-when-adding = ٱووردن
|
|
||||||
pdfjs-editor-edit-comment-dialog-text-input =
|
|
||||||
.placeholder = ناهاڌن پا هؽل کردن…
|
|
||||||
pdfjs-editor-edit-comment-dialog-cancel-button = لقو
|
pdfjs-editor-edit-comment-dialog-cancel-button = لقو
|
||||||
|
|
||||||
## The view manager is a sidebar displaying different views:
|
|
||||||
## - thumbnails;
|
|
||||||
## - outline;
|
|
||||||
## - attachments;
|
|
||||||
## - layers.
|
|
||||||
## The thumbnails view is used to edit the pdf: remove/insert pages, ...
|
|
||||||
|
|
||||||
pdfjs-views-manager-sidebar =
|
|
||||||
.aria-label = نوار کلی
|
|
||||||
pdfjs-views-manager-layers-option-label = لایه یل
|
|
||||||
pdfjs-views-manager-pages-status-action-button-label = دؽوۉداری
|
|
||||||
pdfjs-views-manager-pages-status-copy-button-label = لف گیری
|
|
||||||
pdfjs-views-manager-pages-status-cut-button-label = بۊریڌن
|
|
||||||
pdfjs-views-manager-pages-status-delete-button-label = پاک کردن
|
|
||||||
pdfjs-views-manager-status-undo-button-label = وورگندن
|
|
||||||
pdfjs-views-manager-status-done-button-label = ٱنجوم وابی
|
|
||||||
pdfjs-views-manager-status-close-button =
|
|
||||||
.title = بستن
|
|
||||||
pdfjs-views-manager-status-close-button-label = بستن
|
|
||||||
pdfjs-views-manager-paste-button-label = جا وندن
|
|
||||||
# Badge used to promote a new feature in the UI, keep it as short as possible.
|
|
||||||
# It's spelled uppercase for English, but it can be translated as usual.
|
|
||||||
pdfjs-new-badge-content = نۊ
|
|
||||||
|
|||||||
@ -67,8 +67,8 @@ pdfjs-page-rotate-cw-button =
|
|||||||
.title = Im Uhrzeigersinn drehen
|
.title = Im Uhrzeigersinn drehen
|
||||||
pdfjs-page-rotate-cw-button-label = Im Uhrzeigersinn drehen
|
pdfjs-page-rotate-cw-button-label = Im Uhrzeigersinn drehen
|
||||||
pdfjs-page-rotate-ccw-button =
|
pdfjs-page-rotate-ccw-button =
|
||||||
.title = Gegen den Uhrzeigersinn drehen
|
.title = Gegen Uhrzeigersinn drehen
|
||||||
pdfjs-page-rotate-ccw-button-label = Gegen den Uhrzeigersinn drehen
|
pdfjs-page-rotate-ccw-button-label = Gegen Uhrzeigersinn drehen
|
||||||
pdfjs-cursor-text-select-tool-button =
|
pdfjs-cursor-text-select-tool-button =
|
||||||
.title = Textauswahl-Werkzeug aktivieren
|
.title = Textauswahl-Werkzeug aktivieren
|
||||||
pdfjs-cursor-text-select-tool-button-label = Textauswahl-Werkzeug
|
pdfjs-cursor-text-select-tool-button-label = Textauswahl-Werkzeug
|
||||||
@ -299,7 +299,7 @@ pdfjs-rendering-error = Beim Darstellen der Seite trat ein Fehler auf.
|
|||||||
# (32000-1:2008 Table 169 – Annotation types).
|
# (32000-1:2008 Table 169 – Annotation types).
|
||||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||||
pdfjs-text-annotation-type =
|
pdfjs-text-annotation-type =
|
||||||
.alt = [{ $type } Anmerkung]
|
.alt = [Anlage: { $type }]
|
||||||
# Variables:
|
# Variables:
|
||||||
# $dateObj (Date) - the modification date and time of the annotation
|
# $dateObj (Date) - the modification date and time of the annotation
|
||||||
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
|
pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }
|
||||||
@ -626,7 +626,7 @@ pdfjs-editor-add-signature-save-checkbox = Unterschrift speichern
|
|||||||
pdfjs-editor-add-signature-save-warning-message = Sie haben die Grenze von 5 gespeicherten Unterschriften erreicht. Entfernen Sie eine, um weitere zu speichern.
|
pdfjs-editor-add-signature-save-warning-message = Sie haben die Grenze von 5 gespeicherten Unterschriften erreicht. Entfernen Sie eine, um weitere zu speichern.
|
||||||
pdfjs-editor-add-signature-image-upload-error-title = Grafik konnte nicht hochgeladen werden
|
pdfjs-editor-add-signature-image-upload-error-title = Grafik konnte nicht hochgeladen werden
|
||||||
pdfjs-editor-add-signature-image-upload-error-description = Überprüfen Sie Ihre Netzwerkverbindung, oder versuchen Sie es mit einer anderen Grafik.
|
pdfjs-editor-add-signature-image-upload-error-description = Überprüfen Sie Ihre Netzwerkverbindung, oder versuchen Sie es mit einer anderen Grafik.
|
||||||
pdfjs-editor-add-signature-image-no-data-error-title = Kann Grafik nicht in eine Unterschrift umwandeln
|
pdfjs-editor-add-signature-image-no-data-error-title = Kann Grafik nicht in eine Signatur umwandeln
|
||||||
pdfjs-editor-add-signature-image-no-data-error-description = Bitte versuchen Sie, eine andere Grafik hochzuladen.
|
pdfjs-editor-add-signature-image-no-data-error-description = Bitte versuchen Sie, eine andere Grafik hochzuladen.
|
||||||
pdfjs-editor-add-signature-error-close-button = Schließen
|
pdfjs-editor-add-signature-error-close-button = Schließen
|
||||||
|
|
||||||
@ -730,9 +730,9 @@ pdfjs-views-manager-pages-status-undo-delete-label =
|
|||||||
}
|
}
|
||||||
pdfjs-views-manager-pages-status-waiting-ready-label = Ihre Datei wird vorbereitet…
|
pdfjs-views-manager-pages-status-waiting-ready-label = Ihre Datei wird vorbereitet…
|
||||||
pdfjs-views-manager-pages-status-waiting-uploading-label = Datei wird hochgeladen…
|
pdfjs-views-manager-pages-status-waiting-uploading-label = Datei wird hochgeladen…
|
||||||
pdfjs-views-manager-status-warning-cut-label = Ausschneiden nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
pdfjs-views-manager-status-warning-cut-label = Ausschneiden war nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
||||||
pdfjs-views-manager-status-warning-copy-label = Kopieren nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
pdfjs-views-manager-status-warning-copy-label = Kopieren nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
||||||
pdfjs-views-manager-status-warning-delete-label = Löschen nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
pdfjs-views-manager-status-warning-delete-label = Löschen war nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
||||||
pdfjs-views-manager-status-warning-save-label = Speichern nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
pdfjs-views-manager-status-warning-save-label = Speichern nicht möglich. Aktualisieren Sie die Seite und versuchen Sie es erneut.
|
||||||
pdfjs-views-manager-status-undo-button-label = Rückgängig
|
pdfjs-views-manager-status-undo-button-label = Rückgängig
|
||||||
pdfjs-views-manager-status-done-button-label = Fertig
|
pdfjs-views-manager-status-done-button-label = Fertig
|
||||||
@ -824,8 +824,8 @@ pdfjs-digital-signature-properties-certificate-revoked = Zertifikat: Widerrufen
|
|||||||
## Main menu for adding/removing signatures
|
## Main menu for adding/removing signatures
|
||||||
|
|
||||||
pdfjs-editor-delete-signature-button1 =
|
pdfjs-editor-delete-signature-button1 =
|
||||||
.title = Gespeicherte Unterschrift entfernen
|
.title = Gespeicherte Signatur entfernen
|
||||||
pdfjs-editor-delete-signature-button-label1 = Gespeicherte Unterschrift entfernen
|
pdfjs-editor-delete-signature-button-label1 = Gespeicherte Signatur entfernen
|
||||||
|
|
||||||
## Editor toolbar
|
## Editor toolbar
|
||||||
|
|
||||||
|
|||||||
@ -162,8 +162,8 @@ pdfjs-print-progress-message = Preparando documento para impresión…
|
|||||||
# $progress (Number) - percent value
|
# $progress (Number) - percent value
|
||||||
pdfjs-print-progress-percent = { $progress }%
|
pdfjs-print-progress-percent = { $progress }%
|
||||||
pdfjs-print-progress-close-button = Cancelar
|
pdfjs-print-progress-close-button = Cancelar
|
||||||
pdfjs-printing-not-supported = Advertencia: La impresión no está completamente soportada por este navegador.
|
pdfjs-printing-not-supported = Advertencia: La impresión no esta completamente soportada por este navegador.
|
||||||
pdfjs-printing-not-ready = Advertencia: El PDF no cargó completamente para impresión.
|
pdfjs-printing-not-ready = Advertencia: El PDF no cargo completamente para impresión.
|
||||||
|
|
||||||
## Tooltips and alt text for side panel toolbar buttons
|
## Tooltips and alt text for side panel toolbar buttons
|
||||||
|
|
||||||
@ -266,7 +266,7 @@ pdfjs-page-landmark =
|
|||||||
## Loading indicator messages
|
## Loading indicator messages
|
||||||
|
|
||||||
pdfjs-loading-error = Un error ocurrió al cargar el PDF.
|
pdfjs-loading-error = Un error ocurrió al cargar el PDF.
|
||||||
pdfjs-invalid-file-error = Archivo PDF inválido o dañado.
|
pdfjs-invalid-file-error = Archivo PDF invalido o dañado.
|
||||||
pdfjs-missing-file-error = Archivo PDF no encontrado.
|
pdfjs-missing-file-error = Archivo PDF no encontrado.
|
||||||
pdfjs-unexpected-response-error = Respuesta inesperada del servidor.
|
pdfjs-unexpected-response-error = Respuesta inesperada del servidor.
|
||||||
pdfjs-rendering-error = Un error ocurrió al renderizar la página.
|
pdfjs-rendering-error = Un error ocurrió al renderizar la página.
|
||||||
@ -361,7 +361,7 @@ pdfjs-editor-remove-signature-button =
|
|||||||
pdfjs-editor-free-text-color-input = Color
|
pdfjs-editor-free-text-color-input = Color
|
||||||
pdfjs-editor-free-text-size-input = Tamaño
|
pdfjs-editor-free-text-size-input = Tamaño
|
||||||
pdfjs-editor-ink-color-input = Color
|
pdfjs-editor-ink-color-input = Color
|
||||||
pdfjs-editor-ink-thickness-input = Grosor
|
pdfjs-editor-ink-thickness-input = Grossor
|
||||||
pdfjs-editor-ink-opacity-input = Opacidad
|
pdfjs-editor-ink-opacity-input = Opacidad
|
||||||
pdfjs-editor-stamp-add-image-button =
|
pdfjs-editor-stamp-add-image-button =
|
||||||
.title = Agregar imagen
|
.title = Agregar imagen
|
||||||
@ -581,7 +581,7 @@ pdfjs-editor-add-signature-type-input =
|
|||||||
.aria-label = Escribe tu firma
|
.aria-label = Escribe tu firma
|
||||||
.placeholder = Escribe tu firma
|
.placeholder = Escribe tu firma
|
||||||
pdfjs-editor-add-signature-draw-placeholder = Dibuja tu firma
|
pdfjs-editor-add-signature-draw-placeholder = Dibuja tu firma
|
||||||
pdfjs-editor-add-signature-draw-thickness-range-label = Grosor
|
pdfjs-editor-add-signature-draw-thickness-range-label = Grossor
|
||||||
# Variables:
|
# Variables:
|
||||||
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
|
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
|
||||||
pdfjs-editor-add-signature-draw-thickness-range =
|
pdfjs-editor-add-signature-draw-thickness-range =
|
||||||
@ -698,15 +698,15 @@ pdfjs-views-manager-status-undo-cut-label =
|
|||||||
# $count (Number) - the number of selected pages to be copied.
|
# $count (Number) - the number of selected pages to be copied.
|
||||||
pdfjs-views-manager-pages-status-undo-copy-label =
|
pdfjs-views-manager-pages-status-undo-copy-label =
|
||||||
{ $count ->
|
{ $count ->
|
||||||
[one] 1 página copiada
|
[one] 1 pagina copiada
|
||||||
*[other] { $count } páginas copiadas
|
*[other] { $count } paginas copiadas
|
||||||
}
|
}
|
||||||
# Variables:
|
# Variables:
|
||||||
# $count (Number) - the number of selected pages to be deleted.
|
# $count (Number) - the number of selected pages to be deleted.
|
||||||
pdfjs-views-manager-pages-status-undo-delete-label =
|
pdfjs-views-manager-pages-status-undo-delete-label =
|
||||||
{ $count ->
|
{ $count ->
|
||||||
[one] 1 página eliminada
|
[one] 1 pagina eliminada
|
||||||
*[other] { $count } páginas eliminadas
|
*[other] { $count } paginas eliminadas
|
||||||
}
|
}
|
||||||
pdfjs-views-manager-pages-status-waiting-ready-label = Preparando tu archivo…
|
pdfjs-views-manager-pages-status-waiting-ready-label = Preparando tu archivo…
|
||||||
pdfjs-views-manager-pages-status-waiting-uploading-label = Subiendo archivo…
|
pdfjs-views-manager-pages-status-waiting-uploading-label = Subiendo archivo…
|
||||||
|
|||||||
@ -422,11 +422,11 @@ pdfjs-editor-comments-sidebar-no-comments-link = Argibide gehiago
|
|||||||
|
|
||||||
## Alt-text dialog
|
## Alt-text dialog
|
||||||
|
|
||||||
pdfjs-editor-alt-text-button-label = Ordezko testua
|
pdfjs-editor-alt-text-button-label = Testu alternatiboa
|
||||||
pdfjs-editor-alt-text-edit-button =
|
pdfjs-editor-alt-text-edit-button =
|
||||||
.aria-label = Editatu ordezko testua
|
.aria-label = Editatu testu alternatiboa
|
||||||
pdfjs-editor-alt-text-dialog-label = Aukeratu aukera
|
pdfjs-editor-alt-text-dialog-label = Aukeratu aukera
|
||||||
pdfjs-editor-alt-text-dialog-description = Ordezko testuak laguntzen du jendeak ezin duenean irudia ikusi edo ez denean kargatzen.
|
pdfjs-editor-alt-text-dialog-description = Testu alternatiboak laguntzen du jendeak ezin duenean irudia ikusi edo ez denean kargatzen.
|
||||||
pdfjs-editor-alt-text-add-description-label = Gehitu azalpena
|
pdfjs-editor-alt-text-add-description-label = Gehitu azalpena
|
||||||
pdfjs-editor-alt-text-add-description-description = Saiatu idazten gaia, ezarpena edo ekintzak deskribatzen dituen esaldi 1 edo 2.
|
pdfjs-editor-alt-text-add-description-description = Saiatu idazten gaia, ezarpena edo ekintzak deskribatzen dituen esaldi 1 edo 2.
|
||||||
pdfjs-editor-alt-text-mark-decorative-label = Markatu apaingarri gisa
|
pdfjs-editor-alt-text-mark-decorative-label = Markatu apaingarri gisa
|
||||||
@ -439,7 +439,7 @@ pdfjs-editor-alt-text-textarea =
|
|||||||
.placeholder = Adibidez, "gizon gaztea mahaian eserita dago bazkaltzeko"
|
.placeholder = Adibidez, "gizon gaztea mahaian eserita dago bazkaltzeko"
|
||||||
# Alternative text (alt text) helps when people can't see the image.
|
# Alternative text (alt text) helps when people can't see the image.
|
||||||
pdfjs-editor-alt-text-button =
|
pdfjs-editor-alt-text-button =
|
||||||
.aria-label = Ordezko testua
|
.aria-label = Testu alternatiboa
|
||||||
|
|
||||||
## Editor resizers
|
## Editor resizers
|
||||||
## This is used in an aria label to help to understand the role of the resizer.
|
## This is used in an aria label to help to understand the role of the resizer.
|
||||||
@ -491,38 +491,38 @@ pdfjs-editor-highlight-show-all-button =
|
|||||||
## 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.
|
## 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.
|
# Modal header positioned above a text box where users can edit the alt text.
|
||||||
pdfjs-editor-new-alt-text-dialog-edit-label = Editatu ordezko testua (irudiaren azalpena)
|
pdfjs-editor-new-alt-text-dialog-edit-label = Editatu testu alternatiboa (irudiaren azalpena)
|
||||||
# Modal header positioned above a text box where users can add the alt text.
|
# Modal header positioned above a text box where users can add the alt text.
|
||||||
pdfjs-editor-new-alt-text-dialog-add-label = Gehitu ordezko testua (irudiaren azalpena)
|
pdfjs-editor-new-alt-text-dialog-add-label = Gehitu testu alternatiboa (irudiaren azalpena)
|
||||||
pdfjs-editor-new-alt-text-textarea =
|
pdfjs-editor-new-alt-text-textarea =
|
||||||
.placeholder = Idatzi zure azalpena hemen…
|
.placeholder = Idatzi zure azalpena hemen…
|
||||||
# This text refers to the alt text box above this description. It offers a definition of alt text.
|
# This text refers to the alt text box above this description. It offers a definition of alt text.
|
||||||
pdfjs-editor-new-alt-text-description = Azalpen laburra irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
|
pdfjs-editor-new-alt-text-description = Azalpen laburra irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
|
||||||
# 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.
|
# 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 = Ordezko testu hau automatikoki sortu da eta okerra izan liteke.
|
pdfjs-editor-new-alt-text-disclaimer1 = Testu alternatibo hau automatikoki sortu da eta okerra izan liteke.
|
||||||
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Argibide gehiago
|
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Argibide gehiago
|
||||||
pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu ordezko testua automatikoki
|
pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu testu alternatiboa automatikoki
|
||||||
pdfjs-editor-new-alt-text-not-now-button = Une honetan ez
|
pdfjs-editor-new-alt-text-not-now-button = Une honetan ez
|
||||||
pdfjs-editor-new-alt-text-error-title = Ezin da ordezko testua automatikoki sortu
|
pdfjs-editor-new-alt-text-error-title = Ezin da testu alternatiboa automatikoki sortu
|
||||||
pdfjs-editor-new-alt-text-error-description = Idatzi zure ordezko testu propioa edo saiatu berriro geroago.
|
pdfjs-editor-new-alt-text-error-description = Idatzi zure testu alternatibo propioa edo saiatu berriro geroago.
|
||||||
pdfjs-editor-new-alt-text-error-close-button = Itxi
|
pdfjs-editor-new-alt-text-error-close-button = Itxi
|
||||||
# Variables:
|
# Variables:
|
||||||
# $totalSize (Number) - the total size (in MB) of the AI model.
|
# $totalSize (Number) - the total size (in MB) of the AI model.
|
||||||
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
|
# $downloadedSize (Number) - the downloaded size (in MB) of the AI model.
|
||||||
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ordezko testuaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Testu alternatiboaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||||
.aria-valuetext = Ordezko testuaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
.aria-valuetext = Testu alternatiboaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||||
# This is a button that users can click to edit the alt text they have already added.
|
# This is a button that users can click to edit the alt text they have already added.
|
||||||
pdfjs-editor-new-alt-text-added-button =
|
pdfjs-editor-new-alt-text-added-button =
|
||||||
.aria-label = Ordezko testua gehituta
|
.aria-label = Testu alternatiboa gehituta
|
||||||
pdfjs-editor-new-alt-text-added-button-label = Ordezko testua gehituta
|
pdfjs-editor-new-alt-text-added-button-label = Testu alternatiboa gehituta
|
||||||
# This is a button that users can click to open the alt text editor and add alt text when it is not present.
|
# 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 =
|
pdfjs-editor-new-alt-text-missing-button =
|
||||||
.aria-label = Ordezko testua falta da
|
.aria-label = Testu alternatiboa falta da
|
||||||
pdfjs-editor-new-alt-text-missing-button-label = Ordezko testua falta da
|
pdfjs-editor-new-alt-text-missing-button-label = Testu alternatiboa falta da
|
||||||
# This is a button that opens up the alt text modal where users should review the alt text that was automatically generated.
|
# 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 =
|
pdfjs-editor-new-alt-text-to-review-button =
|
||||||
.aria-label = Berrikusi ordezko testua
|
.aria-label = Berrikusi testu alternatiboa
|
||||||
pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi ordezko testua
|
pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi testu alternatiboa
|
||||||
# "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.
|
# "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:
|
# Variables:
|
||||||
# $generatedAltText (String) - the generated alt-text.
|
# $generatedAltText (String) - the generated alt-text.
|
||||||
@ -531,22 +531,22 @@ pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikoki sort
|
|||||||
## Image alt-text settings
|
## Image alt-text settings
|
||||||
|
|
||||||
pdfjs-image-alt-text-settings-button =
|
pdfjs-image-alt-text-settings-button =
|
||||||
.title = Irudien ordezko testuaren ezarpenak
|
.title = Irudiaren testu alternatiboaren ezarpenak
|
||||||
pdfjs-image-alt-text-settings-button-label = Irudien ordezko testuaren ezarpenak
|
pdfjs-image-alt-text-settings-button-label = Irudiaren testu alternatiboaren ezarpenak
|
||||||
pdfjs-editor-alt-text-settings-dialog-label = Irudien ordezko testuaren ezarpenak
|
pdfjs-editor-alt-text-settings-dialog-label = Irudiaren testu alternatiboaren ezarpenak
|
||||||
pdfjs-editor-alt-text-settings-automatic-title = Ordezko testu automatikoa
|
pdfjs-editor-alt-text-settings-automatic-title = Testu alternatibo automatikoa
|
||||||
pdfjs-editor-alt-text-settings-create-model-button-label = Sortu ordezko testua automatikoki
|
pdfjs-editor-alt-text-settings-create-model-button-label = Sortu testu alternatiboa automatikoki
|
||||||
pdfjs-editor-alt-text-settings-create-model-description = Azalpenak iradokitzen ditu irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
|
pdfjs-editor-alt-text-settings-create-model-description = Azalpenak iradokitzen ditu irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
|
||||||
# Variables:
|
# Variables:
|
||||||
# $totalSize (Number) - the total size (in MB) of the AI model.
|
# $totalSize (Number) - the total size (in MB) of the AI model.
|
||||||
pdfjs-editor-alt-text-settings-download-model-label = Ordezko testuaren AA modeloa ({ $totalSize } MB)
|
pdfjs-editor-alt-text-settings-download-model-label = Testu alternatiboaren AA modeloa ({ $totalSize } MB)
|
||||||
pdfjs-editor-alt-text-settings-ai-model-description = Zure gailuan modu lokalean exekutatzen da eta zure datuak pribatu mantentzen dira. Ordezko testu automatikorako beharrezkoa.
|
pdfjs-editor-alt-text-settings-ai-model-description = Zure gailuan modu lokalean exekutatzen da eta zure datuak pribatu mantentzen dira. Testu alternatibo automatikorako beharrezkoa.
|
||||||
pdfjs-editor-alt-text-settings-delete-model-button = Ezabatu
|
pdfjs-editor-alt-text-settings-delete-model-button = Ezabatu
|
||||||
pdfjs-editor-alt-text-settings-download-model-button = Deskargatu
|
pdfjs-editor-alt-text-settings-download-model-button = Deskargatu
|
||||||
pdfjs-editor-alt-text-settings-downloading-model-button = Deskargatzen…
|
pdfjs-editor-alt-text-settings-downloading-model-button = Deskargatzen…
|
||||||
pdfjs-editor-alt-text-settings-editor-title = Ordezko testuaren editorea
|
pdfjs-editor-alt-text-settings-editor-title = Testu alternatiboaren editorea
|
||||||
pdfjs-editor-alt-text-settings-show-dialog-button-label = Erakutsi ordezko testua irudi bat gehitzean berehala
|
pdfjs-editor-alt-text-settings-show-dialog-button-label = Erakutsi testu alternatiboa irudi bat gehitzean berehala
|
||||||
pdfjs-editor-alt-text-settings-show-dialog-description = Zure irudi guztiek ordezko testua dutela ziurtatzen laguntzen dizu.
|
pdfjs-editor-alt-text-settings-show-dialog-description = Zure irudiek testu alternatiboa duela ziurtatzen laguntzen dizu.
|
||||||
pdfjs-editor-alt-text-settings-close-button = Itxi
|
pdfjs-editor-alt-text-settings-close-button = Itxi
|
||||||
|
|
||||||
## Accessibility labels (announced by screen readers) for objects added to the editor.
|
## Accessibility labels (announced by screen readers) for objects added to the editor.
|
||||||
@ -584,7 +584,7 @@ pdfjs-editor-undo-bar-close-button-label = Itxi
|
|||||||
pdfjs-editor-add-signature-dialog-label =
|
pdfjs-editor-add-signature-dialog-label =
|
||||||
Leiho modal honek PDF dokumentu batera gehitzeko sinadurak
|
Leiho modal honek PDF dokumentu batera gehitzeko sinadurak
|
||||||
sortzea ahalbidetzen dio erabiltzaileari. Erabiltzaileak izena edita
|
sortzea ahalbidetzen dio erabiltzaileari. Erabiltzaileak izena edita
|
||||||
dezake (ordezko testu modura ere erabiltzen dena) eta sinadura
|
dezake (testu alternatibo modura ere erabiltzen dena) eta sinadura
|
||||||
gordetzeko aukera du gehiagotan erabili ahal izateko.
|
gordetzeko aukera du gehiagotan erabili ahal izateko.
|
||||||
pdfjs-editor-add-signature-dialog-title = Gehitu sinadura
|
pdfjs-editor-add-signature-dialog-title = Gehitu sinadura
|
||||||
|
|
||||||
@ -619,9 +619,9 @@ pdfjs-editor-add-signature-image-browse-link =
|
|||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
pdfjs-editor-add-signature-description-label = Azalpena (ordezko testua)
|
pdfjs-editor-add-signature-description-label = Azalpena (testu alternatiboa)
|
||||||
pdfjs-editor-add-signature-description-input =
|
pdfjs-editor-add-signature-description-input =
|
||||||
.title = Azalpena (ordezko testua)
|
.title = Azalpena (testu alternatiboa)
|
||||||
pdfjs-editor-add-signature-description-default-when-drawing = Sinadura
|
pdfjs-editor-add-signature-description-default-when-drawing = Sinadura
|
||||||
pdfjs-editor-add-signature-clear-button-label = Garbitu sinadura
|
pdfjs-editor-add-signature-clear-button-label = Garbitu sinadura
|
||||||
pdfjs-editor-add-signature-clear-button =
|
pdfjs-editor-add-signature-clear-button =
|
||||||
|
|||||||
@ -257,7 +257,7 @@ pdfjs-find-match-count = Occurrence { $current } sur { $total }
|
|||||||
# $limit (Number) - the maximum number of matches
|
# $limit (Number) - the maximum number of matches
|
||||||
pdfjs-find-match-count-limit =
|
pdfjs-find-match-count-limit =
|
||||||
{ $limit ->
|
{ $limit ->
|
||||||
[1] Plus d’une occurrence
|
[one] Plus d’{ $limit } occurrence
|
||||||
*[other] Plus de { $limit } occurrences
|
*[other] Plus de { $limit } occurrences
|
||||||
}
|
}
|
||||||
pdfjs-find-not-found = Expression non trouvée
|
pdfjs-find-not-found = Expression non trouvée
|
||||||
|
|||||||
@ -261,7 +261,7 @@ pdfjs-find-match-count =
|
|||||||
# $limit (Number) - the maximum number of matches
|
# $limit (Number) - the maximum number of matches
|
||||||
pdfjs-find-match-count-limit =
|
pdfjs-find-match-count-limit =
|
||||||
{ $limit ->
|
{ $limit ->
|
||||||
[one] Più di { $limit } corrispondenza
|
[one] Più di una { $limit } corrispondenza
|
||||||
*[other] Più di { $limit } corrispondenze
|
*[other] Più di { $limit } corrispondenze
|
||||||
}
|
}
|
||||||
pdfjs-find-not-found = Testo non trovato
|
pdfjs-find-not-found = Testo non trovato
|
||||||
|
|||||||
@ -132,8 +132,8 @@ pdfjs-document-properties-page-size-orientation-portrait = pokončno
|
|||||||
pdfjs-document-properties-page-size-orientation-landscape = ležeče
|
pdfjs-document-properties-page-size-orientation-landscape = ležeče
|
||||||
pdfjs-document-properties-page-size-name-a-three = A3
|
pdfjs-document-properties-page-size-name-a-three = A3
|
||||||
pdfjs-document-properties-page-size-name-a-four = A4
|
pdfjs-document-properties-page-size-name-a-four = A4
|
||||||
pdfjs-document-properties-page-size-name-letter = Letter
|
pdfjs-document-properties-page-size-name-letter = Pismo
|
||||||
pdfjs-document-properties-page-size-name-legal = Legal
|
pdfjs-document-properties-page-size-name-legal = Pravno
|
||||||
|
|
||||||
## Variables:
|
## Variables:
|
||||||
## $width (Number) - the width of the (current) page
|
## $width (Number) - the width of the (current) page
|
||||||
|
|||||||
@ -486,7 +486,7 @@ pdfjs-editor-new-alt-text-description = Mô tả ngắn gọn dành cho người
|
|||||||
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-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-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-create-automatically-button-label = Tạo văn bản thay thế tự động
|
||||||
pdfjs-editor-new-alt-text-not-now-button = Để sau
|
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-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-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
|
pdfjs-editor-new-alt-text-error-close-button = Đóng
|
||||||
|
|||||||
250
package-lock.json
generated
250
package-lock.json
generated
@ -15,8 +15,8 @@
|
|||||||
"@fluent/dom": "^0.10.2",
|
"@fluent/dom": "^0.10.2",
|
||||||
"@metalsmith/layouts": "^3.0.0",
|
"@metalsmith/layouts": "^3.0.0",
|
||||||
"@metalsmith/markdown": "^1.10.0",
|
"@metalsmith/markdown": "^1.10.0",
|
||||||
"@napi-rs/canvas": "^1.0.3",
|
"@napi-rs/canvas": "^1.0.2",
|
||||||
"@types/node": "^26.1.2",
|
"@types/node": "^26.1.1",
|
||||||
"autoprefixer": "^10.5.4",
|
"autoprefixer": "^10.5.4",
|
||||||
"babel-loader": "^10.1.1",
|
"babel-loader": "^10.1.1",
|
||||||
"babel-plugin-istanbul": "^8.0.2",
|
"babel-plugin-istanbul": "^8.0.2",
|
||||||
@ -33,7 +33,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.5.6",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"eslint-plugin-regexp": "^3.1.1",
|
"eslint-plugin-regexp": "^3.1.1",
|
||||||
"eslint-plugin-unicorn": "^72.0.0",
|
"eslint-plugin-unicorn": "^72.0.0",
|
||||||
"globals": "^17.8.0",
|
"globals": "^17.7.0",
|
||||||
"gulp": "^5.0.1",
|
"gulp": "^5.0.1",
|
||||||
"gulp-cli": "^3.1.0",
|
"gulp-cli": "^3.1.0",
|
||||||
"gulp-postcss": "^10.0.0",
|
"gulp-postcss": "^10.0.0",
|
||||||
@ -49,14 +49,14 @@
|
|||||||
"jstransformer-nunjucks": "^1.2.0",
|
"jstransformer-nunjucks": "^1.2.0",
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
"metalsmith": "^2.7.0",
|
"metalsmith": "^2.7.0",
|
||||||
"metalsmith-html-relative": "^2.0.13",
|
"metalsmith-html-relative": "^2.0.12",
|
||||||
"ordered-read-streams": "^2.0.0",
|
"ordered-read-streams": "^2.0.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.5.25",
|
"postcss": "^8.5.23",
|
||||||
"postcss-discard-comments": "^8.0.1",
|
"postcss-discard-comments": "^8.0.1",
|
||||||
"postcss-values-parser": "^8.0.0",
|
"postcss-values-parser": "^8.0.0",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"puppeteer": "^25.4.0",
|
"puppeteer": "^25.3.0",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.14.1",
|
||||||
"stylelint-prettier": "^5.0.3",
|
"stylelint-prettier": "^5.0.3",
|
||||||
"svglint": "^4.2.1",
|
"svglint": "^4.2.1",
|
||||||
@ -65,7 +65,7 @@
|
|||||||
"ttest": "^4.0.0",
|
"ttest": "^4.0.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vinyl": "^3.0.1",
|
"vinyl": "^3.0.1",
|
||||||
"webpack": "^5.109.2",
|
"webpack": "^5.109.0",
|
||||||
"webpack-stream": "^7.0.0"
|
"webpack-stream": "^7.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -3669,9 +3669,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas": {
|
"node_modules/@napi-rs/canvas": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.2.tgz",
|
||||||
"integrity": "sha512-OlI657a5XXvKGFX7kNeIzJ8rO7IXt87Mqu2H8rXE46viAuOfum/JA7ysX7+eBhxNKznT+RCZh418mndlcFX3+w==",
|
"integrity": "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
@ -3685,23 +3685,23 @@
|
|||||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@napi-rs/canvas-android-arm64": "1.0.3",
|
"@napi-rs/canvas-android-arm64": "1.0.2",
|
||||||
"@napi-rs/canvas-darwin-arm64": "1.0.3",
|
"@napi-rs/canvas-darwin-arm64": "1.0.2",
|
||||||
"@napi-rs/canvas-darwin-x64": "1.0.3",
|
"@napi-rs/canvas-darwin-x64": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.3",
|
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-arm64-gnu": "1.0.3",
|
"@napi-rs/canvas-linux-arm64-gnu": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-arm64-musl": "1.0.3",
|
"@napi-rs/canvas-linux-arm64-musl": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.3",
|
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-x64-gnu": "1.0.3",
|
"@napi-rs/canvas-linux-x64-gnu": "1.0.2",
|
||||||
"@napi-rs/canvas-linux-x64-musl": "1.0.3",
|
"@napi-rs/canvas-linux-x64-musl": "1.0.2",
|
||||||
"@napi-rs/canvas-win32-arm64-msvc": "1.0.3",
|
"@napi-rs/canvas-win32-arm64-msvc": "1.0.2",
|
||||||
"@napi-rs/canvas-win32-x64-msvc": "1.0.3"
|
"@napi-rs/canvas-win32-x64-msvc": "1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.2.tgz",
|
||||||
"integrity": "sha512-7kSCdUhoXiO+AaIMXdBGdtp6EctZNkmF62Rea/BmVQlwKaM3bBhOzyGUzxyxz9dv5vdBfpyAaxhSRSJF4kqK4A==",
|
"integrity": "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3720,9 +3720,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.2.tgz",
|
||||||
"integrity": "sha512-ds14V1BPagLszQyaDTeggny5fNeTCqsUQ5QhFj9VDxSEfzrVxXtdbR0LoFyKa0Siaaw8KvqSk4t7k/WoZJwvbg==",
|
"integrity": "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3741,9 +3741,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.2.tgz",
|
||||||
"integrity": "sha512-qof3LRAAycmkV2I1izZo9RoSHF8kCQr5O05sFwv0jK8rSdYV6KHVwimo6Qb7RxZj40WHKbLHm5JDaUF0o5XUAA==",
|
"integrity": "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3762,9 +3762,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.2.tgz",
|
||||||
"integrity": "sha512-FU2kKZLmolHA9+KcUA+l1+xH3WTLUUTQDU/kLv9SEUr2TrRPu94aytOeizFJDHPs/QBcw4QL1mCQhetQXYBbag==",
|
"integrity": "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
@ -3783,9 +3783,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-GVSjntxKeA+/y/ZKf1F+cmUw1WeIkE5aMRPqnZUlBTBvBcrvgWccJAWuYCKPX4QJQwZILIIwhgdAbl51yj6fpA==",
|
"integrity": "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3807,9 +3807,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.2.tgz",
|
||||||
"integrity": "sha512-J51oK/axyZ13kxycumSMfLiDZMdWdOVvqDFI28BpuViZHE3A0bQfr8B5vg8YnPEnqLD3BSn1hkdlh2buspEcNQ==",
|
"integrity": "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3831,9 +3831,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-CtQgQjoVTX67jS9XuCTtJ40Sl7wRLMguoFnnGnfDmCWf7kzKFZVwj5ynqUOIGKFMSB61ZCuQlwPvVNxYTTseaw==",
|
"integrity": "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
@ -3855,9 +3855,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.2.tgz",
|
||||||
"integrity": "sha512-jtfzAHFp+FRaR7zGT4jyCe6wUgAG/dVb5A4Apd8FY9jKarntDfUAlJXscugiH7ZF5kKnu7/lHFk9LaDPcrGEVQ==",
|
"integrity": "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3879,9 +3879,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.2.tgz",
|
||||||
"integrity": "sha512-xTzaUCKUHTY4bCGadeeRZggbRVbGUT1petg7Z8r9AJR2+D9Bqu6nQAgqBGC6D47tA70LjaaaLTrJ7wNY1T74dg==",
|
"integrity": "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -3903,9 +3903,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.2.tgz",
|
||||||
"integrity": "sha512-ktVLuBkI6QVOm5BwO/WbdGwxgeetAMJa7TTmR8qBarXF0OU2NKjvjUtPJAl2y8t+zBRczJl/1VOl9gua6WcK2g==",
|
"integrity": "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -3924,9 +3924,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.2.tgz",
|
||||||
"integrity": "sha512-SGhlQ8bDjL1Cz2KnsKMasr/5sTcwG/SZkB6WCJxLsmSm/3aS2C+3p39bA7iZ2/94+NkVDySZfbiGoaSZSFHYxA==",
|
"integrity": "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4160,9 +4160,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "26.1.2",
|
"version": "26.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||||
"integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
|
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -5647,9 +5647,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chromium-bidi": {
|
"node_modules/chromium-bidi": {
|
||||||
"version": "17.0.2",
|
"version": "16.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz",
|
||||||
"integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==",
|
"integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6132,9 +6132,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/devtools-protocol": {
|
"node_modules/devtools-protocol": {
|
||||||
"version": "0.0.1653615",
|
"version": "0.0.1638949",
|
||||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1653615.tgz",
|
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1638949.tgz",
|
||||||
"integrity": "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA==",
|
"integrity": "sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
@ -6320,9 +6320,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
"version": "5.24.5",
|
"version": "5.24.3",
|
||||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz",
|
||||||
"integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
|
"integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -6956,9 +6956,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/fast-uri": {
|
"node_modules/fast-uri": {
|
||||||
"version": "3.1.5",
|
"version": "3.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||||
"integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
|
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -7448,9 +7448,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/globals": {
|
"node_modules/globals": {
|
||||||
"version": "17.8.0",
|
"version": "17.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
|
||||||
"integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==",
|
"integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -9279,15 +9279,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/metalsmith-html-relative": {
|
"node_modules/metalsmith-html-relative": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.12",
|
||||||
"resolved": "https://registry.npmjs.org/metalsmith-html-relative/-/metalsmith-html-relative-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/metalsmith-html-relative/-/metalsmith-html-relative-2.0.12.tgz",
|
||||||
"integrity": "sha512-jG6aVx5v1cL9/mY72qBknPPF+RZluOSim3zETDxJkQt6+Ejb/AyABmNUResNFwyfh4ZjBcAKxSXgid3PKANsfA==",
|
"integrity": "sha512-VYZ0OsUbhTk/ktaDilMbgjAZowHaYXBbAd9O3zz66+iNtcWEDgrrrw2TLjxxHQPB4OCa7KqfRRFgInykTI9aUQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.2.0",
|
"cheerio": "^1.2.0",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"minimatch": "^10.2.6"
|
"minimatch": "^10.2.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.18.1"
|
"node": ">=20.18.1"
|
||||||
@ -9364,13 +9364,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "10.2.6",
|
"version": "10.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||||
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
|
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^5.0.8"
|
"brace-expansion": "^5.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18 || 20 || >=22"
|
"node": "18 || 20 || >=22"
|
||||||
@ -9471,9 +9471,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/modern-tar": {
|
"node_modules/modern-tar": {
|
||||||
"version": "0.7.7",
|
"version": "0.7.6",
|
||||||
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz",
|
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz",
|
||||||
"integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==",
|
"integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -10104,9 +10104,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.5.25",
|
"version": "8.5.23",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
|
||||||
"integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
|
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -10339,18 +10339,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer": {
|
"node_modules/puppeteer": {
|
||||||
"version": "25.4.0",
|
"version": "25.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.3.0.tgz",
|
||||||
"integrity": "sha512-xfQp8dFBcGaLc1hEMaVr7s+oW4ZkAurr8Y9H81ilKhu6QoLfSTkZjU7IavnyJ/VWpB9ni3KNJUQHUatslLWyGw==",
|
"integrity": "sha512-O1tx8S315aw8eI99HZ5ZNcVEzJ9+jKF//eO5UvfZ3cXJ6okZ5sX3Y50u7DJaM+ewEK4LqXP068tBhfRaWikj+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "3.0.6",
|
"@puppeteer/browsers": "3.0.6",
|
||||||
"chromium-bidi": "17.0.2",
|
"chromium-bidi": "16.0.1",
|
||||||
"devtools-protocol": "0.0.1653615",
|
"devtools-protocol": "0.0.1638949",
|
||||||
"lilconfig": "^3.1.3",
|
"lilconfig": "^3.1.3",
|
||||||
"puppeteer-core": "25.4.0",
|
"puppeteer-core": "25.3.0",
|
||||||
"typed-query-selector": "^2.12.2"
|
"typed-query-selector": "^2.12.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -10361,18 +10361,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/puppeteer-core": {
|
"node_modules/puppeteer-core": {
|
||||||
"version": "25.4.0",
|
"version": "25.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.3.0.tgz",
|
||||||
"integrity": "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA==",
|
"integrity": "sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@puppeteer/browsers": "3.0.6",
|
"@puppeteer/browsers": "3.0.6",
|
||||||
"chromium-bidi": "17.0.2",
|
"chromium-bidi": "16.0.1",
|
||||||
"devtools-protocol": "0.0.1653615",
|
"devtools-protocol": "0.0.1638949",
|
||||||
"typed-query-selector": "^2.12.2",
|
"typed-query-selector": "^2.12.2",
|
||||||
"webdriver-bidi-protocol": "0.4.2",
|
"webdriver-bidi-protocol": "0.4.2",
|
||||||
"ws": "^8.21.1"
|
"ws": "^8.21.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
@ -11974,9 +11974,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||||
"version": "2.1.4",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -12453,9 +12453,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici": {
|
"node_modules/undici": {
|
||||||
"version": "7.29.0",
|
"version": "7.28.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz",
|
||||||
"integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
|
"integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -12761,9 +12761,9 @@
|
|||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.109.2",
|
"version": "5.109.0",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.2.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.0.tgz",
|
||||||
"integrity": "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==",
|
"integrity": "sha512-vomrngskVVXEZF9sMZfYAd4pXZUnfaWdJGlF+BTNF+gJBCKYCQBnOeVPlrh39Ewl7nlCsirDplMy6o5g9xJHBg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -12775,7 +12775,7 @@
|
|||||||
"acorn": "^8.16.0",
|
"acorn": "^8.16.0",
|
||||||
"browserslist": "^4.28.1",
|
"browserslist": "^4.28.1",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
"enhanced-resolve": "^5.24.4",
|
"enhanced-resolve": "^5.24.2",
|
||||||
"es-module-lexer": "^2.1.0",
|
"es-module-lexer": "^2.1.0",
|
||||||
"eslint-scope": "5.1.1",
|
"eslint-scope": "5.1.1",
|
||||||
"events": "^3.2.0",
|
"events": "^3.2.0",
|
||||||
@ -13162,9 +13162,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.21.1",
|
"version": "8.21.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||||
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -13240,16 +13240,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yargs": {
|
"node_modules/yargs": {
|
||||||
"version": "18.1.0",
|
"version": "18.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||||
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
|
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cliui": "^9.0.1",
|
"cliui": "^9.0.1",
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.1",
|
||||||
"get-caller-file": "^2.0.5",
|
"get-caller-file": "^2.0.5",
|
||||||
"string-width": "^8.2.1",
|
"string-width": "^7.2.0",
|
||||||
"y18n": "^5.0.5",
|
"y18n": "^5.0.5",
|
||||||
"yargs-parser": "^22.0.0"
|
"yargs-parser": "^22.0.0"
|
||||||
},
|
},
|
||||||
@ -13280,18 +13280,26 @@
|
|||||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs/node_modules/emoji-regex": {
|
||||||
|
"version": "10.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||||
|
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/yargs/node_modules/string-width": {
|
"node_modules/yargs/node_modules/string-width": {
|
||||||
"version": "8.2.2",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||||
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
|
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"get-east-asian-width": "^1.5.0",
|
"emoji-regex": "^10.3.0",
|
||||||
"strip-ansi": "^7.1.2"
|
"get-east-asian-width": "^1.0.0",
|
||||||
|
"strip-ansi": "^7.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
|||||||
14
package.json
14
package.json
@ -10,8 +10,8 @@
|
|||||||
"@fluent/dom": "^0.10.2",
|
"@fluent/dom": "^0.10.2",
|
||||||
"@metalsmith/layouts": "^3.0.0",
|
"@metalsmith/layouts": "^3.0.0",
|
||||||
"@metalsmith/markdown": "^1.10.0",
|
"@metalsmith/markdown": "^1.10.0",
|
||||||
"@napi-rs/canvas": "^1.0.3",
|
"@napi-rs/canvas": "^1.0.2",
|
||||||
"@types/node": "^26.1.2",
|
"@types/node": "^26.1.1",
|
||||||
"autoprefixer": "^10.5.4",
|
"autoprefixer": "^10.5.4",
|
||||||
"babel-loader": "^10.1.1",
|
"babel-loader": "^10.1.1",
|
||||||
"babel-plugin-istanbul": "^8.0.2",
|
"babel-plugin-istanbul": "^8.0.2",
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"eslint-plugin-prettier": "^5.5.6",
|
"eslint-plugin-prettier": "^5.5.6",
|
||||||
"eslint-plugin-regexp": "^3.1.1",
|
"eslint-plugin-regexp": "^3.1.1",
|
||||||
"eslint-plugin-unicorn": "^72.0.0",
|
"eslint-plugin-unicorn": "^72.0.0",
|
||||||
"globals": "^17.8.0",
|
"globals": "^17.7.0",
|
||||||
"gulp": "^5.0.1",
|
"gulp": "^5.0.1",
|
||||||
"gulp-cli": "^3.1.0",
|
"gulp-cli": "^3.1.0",
|
||||||
"gulp-postcss": "^10.0.0",
|
"gulp-postcss": "^10.0.0",
|
||||||
@ -44,14 +44,14 @@
|
|||||||
"jstransformer-nunjucks": "^1.2.0",
|
"jstransformer-nunjucks": "^1.2.0",
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
"metalsmith": "^2.7.0",
|
"metalsmith": "^2.7.0",
|
||||||
"metalsmith-html-relative": "^2.0.13",
|
"metalsmith-html-relative": "^2.0.12",
|
||||||
"ordered-read-streams": "^2.0.0",
|
"ordered-read-streams": "^2.0.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"postcss": "^8.5.25",
|
"postcss": "^8.5.23",
|
||||||
"postcss-discard-comments": "^8.0.1",
|
"postcss-discard-comments": "^8.0.1",
|
||||||
"postcss-values-parser": "^8.0.0",
|
"postcss-values-parser": "^8.0.0",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"puppeteer": "^25.4.0",
|
"puppeteer": "^25.3.0",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.14.1",
|
||||||
"stylelint-prettier": "^5.0.3",
|
"stylelint-prettier": "^5.0.3",
|
||||||
"svglint": "^4.2.1",
|
"svglint": "^4.2.1",
|
||||||
@ -60,7 +60,7 @@
|
|||||||
"ttest": "^4.0.0",
|
"ttest": "^4.0.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"vinyl": "^3.0.1",
|
"vinyl": "^3.0.1",
|
||||||
"webpack": "^5.109.2",
|
"webpack": "^5.109.0",
|
||||||
"webpack-stream": "^7.0.0"
|
"webpack-stream": "^7.0.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"stableVersion": "6.2.108",
|
"stableVersion": "6.1.200",
|
||||||
"baseVersion": "ce4ff55faaa83b39b0137dc458af6eea6f96235f",
|
"baseVersion": "eddd70a2ca1054ad2e0792972c3f2774b89f0cd2",
|
||||||
"versionPrefix": "6.3."
|
"versionPrefix": "6.2."
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2887,7 +2887,10 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
|||||||
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
|
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
|
||||||
|
|
||||||
// Check if we have a date or time.
|
// Check if we have a date or time.
|
||||||
const { actions } = this.data;
|
const {
|
||||||
|
data: { actions },
|
||||||
|
} = this;
|
||||||
|
|
||||||
if (!actions) {
|
if (!actions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2895,35 +2898,32 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
|||||||
const AFDateTime =
|
const AFDateTime =
|
||||||
/^AF(Date|Time)_(?:Keystroke|Format)(?:Ex)?\(['"]?([^'"]+)['"]?\);$/;
|
/^AF(Date|Time)_(?:Keystroke|Format)(?:Ex)?\(['"]?([^'"]+)['"]?\);$/;
|
||||||
let canUseHTMLDateTime = false;
|
let canUseHTMLDateTime = false;
|
||||||
|
|
||||||
const aFormat = actions.get("Format"),
|
|
||||||
aKeystroke = actions.get("Keystroke");
|
|
||||||
if (
|
if (
|
||||||
(aFormat?.length === 1 &&
|
(actions.Format?.length === 1 &&
|
||||||
aKeystroke?.length === 1 &&
|
actions.Keystroke?.length === 1 &&
|
||||||
AFDateTime.test(aFormat[0]) &&
|
AFDateTime.test(actions.Format[0]) &&
|
||||||
AFDateTime.test(aKeystroke[0])) ||
|
AFDateTime.test(actions.Keystroke[0])) ||
|
||||||
(aFormat?.length === 0 &&
|
(actions.Format?.length === 0 &&
|
||||||
aKeystroke?.length === 1 &&
|
actions.Keystroke?.length === 1 &&
|
||||||
AFDateTime.test(aKeystroke[0])) ||
|
AFDateTime.test(actions.Keystroke[0])) ||
|
||||||
(aKeystroke?.length === 0 &&
|
(actions.Keystroke?.length === 0 &&
|
||||||
aFormat?.length === 1 &&
|
actions.Format?.length === 1 &&
|
||||||
AFDateTime.test(aFormat[0]))
|
AFDateTime.test(actions.Format[0]))
|
||||||
) {
|
) {
|
||||||
// If the Format and Keystroke actions are the same, we can just use
|
// If the Format and Keystroke actions are the same, we can just use
|
||||||
// the Format action.
|
// the Format action.
|
||||||
canUseHTMLDateTime = true;
|
canUseHTMLDateTime = true;
|
||||||
}
|
}
|
||||||
const actionsToVisit = [];
|
const actionsToVisit = [];
|
||||||
if (aFormat) {
|
if (actions.Format) {
|
||||||
actionsToVisit.push(...aFormat);
|
actionsToVisit.push(...actions.Format);
|
||||||
}
|
}
|
||||||
if (aKeystroke) {
|
if (actions.Keystroke) {
|
||||||
actionsToVisit.push(...aKeystroke);
|
actionsToVisit.push(...actions.Keystroke);
|
||||||
}
|
}
|
||||||
if (canUseHTMLDateTime) {
|
if (canUseHTMLDateTime) {
|
||||||
actions.delete("Keystroke");
|
delete actions.Keystroke;
|
||||||
actions.set("Format", actionsToVisit);
|
actions.Format = actionsToVisit;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const formatAction of actionsToVisit) {
|
for (const formatAction of actionsToVisit) {
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import {
|
|||||||
DocumentActionEventType,
|
DocumentActionEventType,
|
||||||
FormatError,
|
FormatError,
|
||||||
info,
|
info,
|
||||||
makeArr,
|
|
||||||
PermissionFlag,
|
PermissionFlag,
|
||||||
shadow,
|
shadow,
|
||||||
stringToUTF8String,
|
stringToUTF8String,
|
||||||
@ -1288,10 +1287,10 @@ class Catalog {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (javaScript) {
|
if (javaScript) {
|
||||||
actions ??= new Map();
|
actions ??= Object.create(null);
|
||||||
|
|
||||||
for (const [key, val] of javaScript) {
|
for (const [key, val] of javaScript) {
|
||||||
actions.getOrInsertComputed(key, makeArr).push(val);
|
(actions[key] ??= []).push(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return shadow(this, "jsActions", actions);
|
return shadow(this, "jsActions", actions);
|
||||||
|
|||||||
@ -1428,9 +1428,10 @@ class CFFFDSelect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getFDIndex(glyphIndex) {
|
getFDIndex(glyphIndex) {
|
||||||
return glyphIndex < 0 || glyphIndex >= this.fdSelect.length
|
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
|
||||||
? -1
|
return -1;
|
||||||
: this.fdSelect[glyphIndex];
|
}
|
||||||
|
return this.fdSelect[glyphIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -239,10 +239,10 @@ class ChunkedStream extends Stream {
|
|||||||
};
|
};
|
||||||
Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
|
Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
|
||||||
get() {
|
get() {
|
||||||
return (
|
if (this.numChunksLoaded === this.numChunks) {
|
||||||
this.numChunksLoaded === this.numChunks ||
|
return true;
|
||||||
this.getMissingChunks().length === 0
|
}
|
||||||
);
|
return this.getMissingChunks().length === 0;
|
||||||
},
|
},
|
||||||
configurable: true,
|
configurable: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
|
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
|
||||||
import { BaseStream } from "./base_stream.js";
|
import { BaseStream } from "./base_stream.js";
|
||||||
import { CONTROL_CHAR_REGEXP } from "../shared/css_utils.js";
|
|
||||||
import { stringToPDFString } from "./string_utils.js";
|
import { stringToPDFString } from "./string_utils.js";
|
||||||
|
|
||||||
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
|
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
|
||||||
@ -342,8 +341,7 @@ function lookupNormalRect(arr, fallback) {
|
|||||||
* each part of the path.
|
* each part of the path.
|
||||||
*/
|
*/
|
||||||
function parseXFAPath(path) {
|
function parseXFAPath(path) {
|
||||||
// Anchoring prevents retrying the match at every character.
|
const positionPattern = /(.+)\[(\d+)\]$/;
|
||||||
const positionPattern = /^(.+)\[(\d+)\]$/;
|
|
||||||
return path.split(".").map(component => {
|
return path.split(".").map(component => {
|
||||||
const m = component.match(positionPattern);
|
const m = component.match(positionPattern);
|
||||||
if (m) {
|
if (m) {
|
||||||
@ -451,7 +449,7 @@ function _collectJS(entry, xref, list, parents) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function collectActions(xref, dict, eventType) {
|
function collectActions(xref, dict, eventType) {
|
||||||
const actions = new Map();
|
const actions = Object.create(null);
|
||||||
const additionalActionsDicts = getInheritableProperty({
|
const additionalActionsDicts = getInheritableProperty({
|
||||||
dict,
|
dict,
|
||||||
key: "AA",
|
key: "AA",
|
||||||
@ -477,7 +475,7 @@ function collectActions(xref, dict, eventType) {
|
|||||||
const list = [];
|
const list = [];
|
||||||
_collectJS(rawActionDict, xref, list, parents);
|
_collectJS(rawActionDict, xref, list, parents);
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
actions.set(action, list);
|
actions[action] = list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -489,10 +487,10 @@ function collectActions(xref, dict, eventType) {
|
|||||||
const list = [];
|
const list = [];
|
||||||
_collectJS(actionDict, xref, list, parents);
|
_collectJS(actionDict, xref, list, parents);
|
||||||
if (list.length > 0) {
|
if (list.length > 0) {
|
||||||
actions.set("Action", list);
|
actions.Action = list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return actions.size ? actions : null;
|
return Object.keys(actions).length ? actions : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const XMLEntities = {
|
const XMLEntities = {
|
||||||
@ -562,17 +560,6 @@ function validateFontName(fontFamily, mustWarn = false) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// A <string> is terminated by a newline, which for CSS also includes the
|
|
||||||
// form feed character; see https://drafts.csswg.org/css-syntax/#newline.
|
|
||||||
// The font family is escaped before being used, see `serializeFontFamily`,
|
|
||||||
// hence this only prevents values that cannot sensibly name a font from
|
|
||||||
// being used at all (the unquoted case below is already this strict).
|
|
||||||
if (CONTROL_CHAR_REGEXP.test(fontFamily)) {
|
|
||||||
if (mustWarn) {
|
|
||||||
warn(`FontFamily contains control characters: ${fontFamily}.`);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
|
||||||
for (const ident of fontFamily.split(/[ \t]+/)) {
|
for (const ident of fontFamily.split(/[ \t]+/)) {
|
||||||
@ -587,18 +574,6 @@ function validateFontName(fontFamily, mustWarn = false) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strip the spaces preceding a digit, since e.g. "Wingdings 3" is not a valid
|
|
||||||
// font name in the css specs.
|
|
||||||
// The optional trailing digit is matched as part of the space run, so that a
|
|
||||||
// failing match cannot backtrack over the spaces; otherwise the replacement
|
|
||||||
// would be quadratic in the number of consecutive spaces.
|
|
||||||
function normalizeCSSFontFamily(fontFamily) {
|
|
||||||
return fontFamily.replaceAll(
|
|
||||||
/( +)(\d)?/g,
|
|
||||||
(_, spaces, digit) => digit ?? " "
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function validateCSSFont(cssFontInfo) {
|
function validateCSSFont(cssFontInfo) {
|
||||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
|
// See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
|
||||||
const DEFAULT_CSS_FONT_OBLIQUE = "14";
|
const DEFAULT_CSS_FONT_OBLIQUE = "14";
|
||||||
@ -775,7 +750,6 @@ export {
|
|||||||
lookupRect,
|
lookupRect,
|
||||||
MAX_INT_32,
|
MAX_INT_32,
|
||||||
MissingDataException,
|
MissingDataException,
|
||||||
normalizeCSSFontFamily,
|
|
||||||
numberToString,
|
numberToString,
|
||||||
ParserEOFException,
|
ParserEOFException,
|
||||||
parseXFAPath,
|
parseXFAPath,
|
||||||
|
|||||||
@ -111,9 +111,10 @@ class DecodeStream extends BaseStream {
|
|||||||
|
|
||||||
async getImageData(length, decoderOptions) {
|
async getImageData(length, decoderOptions) {
|
||||||
if (!this.canAsyncDecodeImageFromBuffer) {
|
if (!this.canAsyncDecodeImageFromBuffer) {
|
||||||
return this.isAsyncDecoder
|
if (this.isAsyncDecoder) {
|
||||||
? this.decodeImage(null, length, decoderOptions)
|
return this.decodeImage(null, length, decoderOptions);
|
||||||
: this.getBytes(length, decoderOptions);
|
}
|
||||||
|
return this.getBytes(length, decoderOptions);
|
||||||
}
|
}
|
||||||
const data = await this.stream.asyncGetBytes();
|
const data = await this.stream.asyncGetBytes();
|
||||||
return this.decodeImage(data, length, decoderOptions);
|
return this.decodeImage(data, length, decoderOptions);
|
||||||
|
|||||||
@ -42,7 +42,6 @@ import {
|
|||||||
isWhiteSpace,
|
isWhiteSpace,
|
||||||
lookupNormalRect,
|
lookupNormalRect,
|
||||||
MissingDataException,
|
MissingDataException,
|
||||||
normalizeCSSFontFamily,
|
|
||||||
PDF_VERSION_REGEXP,
|
PDF_VERSION_REGEXP,
|
||||||
RESOURCES_KEYS_OPERATOR_LIST,
|
RESOURCES_KEYS_OPERATOR_LIST,
|
||||||
RESOURCES_KEYS_TEXT_CONTENT,
|
RESOURCES_KEYS_TEXT_CONTENT,
|
||||||
@ -1397,7 +1396,9 @@ class PDFDocument {
|
|||||||
if (!(descriptor instanceof Dict)) {
|
if (!(descriptor instanceof Dict)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const fontFamily = normalizeCSSFontFamily(descriptor.get("FontFamily"));
|
let fontFamily = descriptor.get("FontFamily");
|
||||||
|
// For example, "Wingdings 3" is not a valid font name in the css specs.
|
||||||
|
fontFamily = fontFamily.replaceAll(/ +(\d)/g, "$1");
|
||||||
const fontWeight = descriptor.get("FontWeight");
|
const fontWeight = descriptor.get("FontWeight");
|
||||||
|
|
||||||
// Angle is expressed in degrees counterclockwise in PDF
|
// Angle is expressed in degrees counterclockwise in PDF
|
||||||
@ -1961,7 +1962,7 @@ class PDFDocument {
|
|||||||
const { acroForm } = annotationGlobals;
|
const { acroForm } = annotationGlobals;
|
||||||
|
|
||||||
const visitedRefs = new RefSet();
|
const visitedRefs = new RefSet();
|
||||||
const allFields = new Map();
|
const allFields = Object.create(null);
|
||||||
const fieldPromises = new Map();
|
const fieldPromises = new Map();
|
||||||
const orphanFields = new RefSetCache();
|
const orphanFields = new RefSetCache();
|
||||||
for (const fieldRef of acroForm.get("Fields")) {
|
for (const fieldRef of acroForm.get("Fields")) {
|
||||||
@ -1982,7 +1983,7 @@ class PDFDocument {
|
|||||||
Promise.all(promises).then(fields => {
|
Promise.all(promises).then(fields => {
|
||||||
fields = fields.filter(field => !!field);
|
fields = fields.filter(field => !!field);
|
||||||
if (fields.length > 0) {
|
if (fields.length > 0) {
|
||||||
allFields.set(name, fields);
|
allFields[name] = fields;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -1990,7 +1991,7 @@ class PDFDocument {
|
|||||||
await Promise.all(allPromises);
|
await Promise.all(allPromises);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allFields: allFields.size ? allFields : null,
|
allFields: Object.keys(allFields).length ? allFields : null,
|
||||||
orphanFields,
|
orphanFields,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -2268,9 +2269,9 @@ class PDFDocument {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (fieldObjects?.allFields) {
|
if (fieldObjects?.allFields) {
|
||||||
return fieldObjects.allFields
|
return Object.values(fieldObjects.allFields).some(fieldObject =>
|
||||||
.values()
|
fieldObject.some(object => object.actions !== null)
|
||||||
.some(fieldObj => fieldObj.some(obj => obj.actions !== null));
|
);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,8 +57,6 @@ class PageData {
|
|||||||
this.annotations = null;
|
this.annotations = null;
|
||||||
// Named destinations which points to this page.
|
// Named destinations which points to this page.
|
||||||
this.pointingNamedDestinations = null;
|
this.pointingNamedDestinations = null;
|
||||||
// Rank of this page among the output pages sharing the same source page.
|
|
||||||
this.copyLevel = 0;
|
|
||||||
|
|
||||||
documentData.pagesMap.put(page.ref, this);
|
documentData.pagesMap.put(page.ref, this);
|
||||||
}
|
}
|
||||||
@ -265,7 +263,7 @@ class PDFEditor {
|
|||||||
) {
|
) {
|
||||||
if (obj instanceof Ref) {
|
if (obj instanceof Ref) {
|
||||||
const {
|
const {
|
||||||
currentDocument: { fieldToParent, oldRefMapping },
|
currentDocument: { oldRefMapping },
|
||||||
} = this;
|
} = this;
|
||||||
const existingRef = oldRefMapping.get(obj);
|
const existingRef = oldRefMapping.get(obj);
|
||||||
if (existingRef) {
|
if (existingRef) {
|
||||||
@ -273,12 +271,6 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
const oldRef = obj;
|
const oldRef = obj;
|
||||||
obj = await xref.fetchAsync(oldRef);
|
obj = await xref.fetchAsync(oldRef);
|
||||||
const mappedRef = oldRefMapping.get(oldRef);
|
|
||||||
if (mappedRef) {
|
|
||||||
// Another concurrent traversal may have allocated the clone while the
|
|
||||||
// source object was being fetched.
|
|
||||||
return mappedRef;
|
|
||||||
}
|
|
||||||
if (typeof obj === "number") {
|
if (typeof obj === "number") {
|
||||||
// Simple value; no need to create a new reference.
|
// Simple value; no need to create a new reference.
|
||||||
return obj;
|
return obj;
|
||||||
@ -307,17 +299,9 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cloneSource = true;
|
|
||||||
if (fieldToParent.has(oldRef) && obj instanceof Dict) {
|
|
||||||
// Avoid following a widget's field hierarchy while cloning the page,
|
|
||||||
// without mutating the source dictionary cached by its XRef.
|
|
||||||
obj = this.cloneDict(obj);
|
|
||||||
obj.delete("Parent");
|
|
||||||
cloneSource = false;
|
|
||||||
}
|
|
||||||
this.xref[newRef.num] = await this.#collectDependencies(
|
this.xref[newRef.num] = await this.#collectDependencies(
|
||||||
obj,
|
obj,
|
||||||
cloneSource,
|
true,
|
||||||
xref,
|
xref,
|
||||||
resourceStreamPath
|
resourceStreamPath
|
||||||
);
|
);
|
||||||
@ -1099,18 +1083,10 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
const copyCounts = new Map();
|
|
||||||
for (let i = 0, ii = this.oldPages.length; i < ii; i++) {
|
for (let i = 0, ii = this.oldPages.length; i < ii; i++) {
|
||||||
const pageData = this.oldPages[i];
|
if (this.oldPages[i] === undefined) {
|
||||||
if (pageData === undefined) {
|
|
||||||
throw new Error("extractPages: sparse pageIndices.");
|
throw new Error("extractPages: sparse pageIndices.");
|
||||||
}
|
}
|
||||||
if (pageData) {
|
|
||||||
const { page } = pageData;
|
|
||||||
const copyLevel = copyCounts.get(page) ?? 0;
|
|
||||||
copyCounts.set(page, copyLevel + 1);
|
|
||||||
pageData.copyLevel = copyLevel;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
promises.length = 0;
|
promises.length = 0;
|
||||||
|
|
||||||
@ -1247,8 +1223,11 @@ class PDFEditor {
|
|||||||
"Sig"
|
"Sig"
|
||||||
);
|
);
|
||||||
const parentRef = annotationDict.getRaw("Parent") || null;
|
const parentRef = annotationDict.getRaw("Parent") || null;
|
||||||
// The parent will be omitted from the annotation clone to avoid
|
// We remove the parent to avoid visiting it when cloning the
|
||||||
// visiting it, then restored by #mergeAcroForms.
|
// annotation.
|
||||||
|
// It'll be fixed later in #mergeAcroForms when merging the
|
||||||
|
// AcroForms.
|
||||||
|
annotationDict.delete("Parent");
|
||||||
fieldToParent.put(annotationRef, parentRef);
|
fieldToParent.put(annotationRef, parentRef);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2157,20 +2136,8 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
let parent = parentRef;
|
let parent = parentRef;
|
||||||
let lastNonNullParent = parentRef;
|
let lastNonNullParent = parentRef;
|
||||||
const visited = new RefSet();
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (parent instanceof Ref) {
|
parent = xref.fetchIfRef(parent)?.getRaw("Parent") || null;
|
||||||
if (visited.has(parent)) {
|
|
||||||
// Cyclic Parent chain: stop on the field closing the cycle.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
visited.put(parent);
|
|
||||||
}
|
|
||||||
const parentDict = xref.fetchIfRef(parent);
|
|
||||||
if (!(parentDict instanceof Dict)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
parent = parentDict.getRaw("Parent") || null;
|
|
||||||
if (!parent) {
|
if (!parent) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -2243,9 +2210,6 @@ class PDFEditor {
|
|||||||
}
|
}
|
||||||
processed.put(oldKidRef);
|
processed.put(oldKidRef);
|
||||||
const kid = xref.fetchIfRef(oldKidRef);
|
const kid = xref.fetchIfRef(oldKidRef);
|
||||||
if (!(kid instanceof Dict)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (kid.has("Kids")) {
|
if (kid.has("Kids")) {
|
||||||
const kidsArray = kid.get("Kids");
|
const kidsArray = kid.get("Kids");
|
||||||
if (!Array.isArray(kidsArray)) {
|
if (!Array.isArray(kidsArray)) {
|
||||||
@ -2423,18 +2387,12 @@ class PDFEditor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a copy of a page.
|
* Create a copy of a page.
|
||||||
* @param {number} pageIndex - Index of the page slot in the new document
|
* @param {number} pageIndex
|
||||||
* (the index of the source page is `page.pageIndex`).
|
|
||||||
* @returns {Promise<Ref>} the page reference in the new PDF document.
|
* @returns {Promise<Ref>} the page reference in the new PDF document.
|
||||||
*/
|
*/
|
||||||
async #makePageCopy(pageIndex) {
|
async #makePageCopy(pageIndex) {
|
||||||
const {
|
const { page, documentData, annotations, pointingNamedDestinations } =
|
||||||
page,
|
this.oldPages[pageIndex];
|
||||||
documentData,
|
|
||||||
annotations,
|
|
||||||
pointingNamedDestinations,
|
|
||||||
copyLevel,
|
|
||||||
} = this.oldPages[pageIndex];
|
|
||||||
this.currentDocument = documentData;
|
this.currentDocument = documentData;
|
||||||
const { dedupNamedDestinations, oldRefMapping } = documentData;
|
const { dedupNamedDestinations, oldRefMapping } = documentData;
|
||||||
const { xref, rotate, mediaBox, resources, ref: oldPageRef } = page;
|
const { xref, rotate, mediaBox, resources, ref: oldPageRef } = page;
|
||||||
@ -2503,11 +2461,9 @@ class PDFEditor {
|
|||||||
|
|
||||||
const newAnnotations =
|
const newAnnotations =
|
||||||
documentData.document === this.#primaryDocument
|
documentData.document === this.#primaryDocument
|
||||||
? this.#newAnnotationsParams?.newAnnotationsByPage
|
? this.#newAnnotationsParams?.newAnnotationsByPage?.get(page.pageIndex)
|
||||||
?.get(page.pageIndex)
|
|
||||||
?.filter(({ copyLevel: level }) => (level ?? 0) === copyLevel)
|
|
||||||
: null;
|
: null;
|
||||||
if (newAnnotations?.length) {
|
if (newAnnotations) {
|
||||||
const { handler, task, imagesPromises } = this.#newAnnotationsParams;
|
const { handler, task, imagesPromises } = this.#newAnnotationsParams;
|
||||||
const changes = new RefSetCache();
|
const changes = new RefSetCache();
|
||||||
const newData = await AnnotationFactory.saveNewAnnotations(
|
const newData = await AnnotationFactory.saveNewAnnotations(
|
||||||
|
|||||||
@ -186,7 +186,10 @@ class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof buf1 === "string") {
|
if (typeof buf1 === "string") {
|
||||||
return cipherTransform ? cipherTransform.decryptString(buf1) : buf1;
|
if (cipherTransform) {
|
||||||
|
return cipherTransform.decryptString(buf1);
|
||||||
|
}
|
||||||
|
return buf1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// simple object
|
// simple object
|
||||||
|
|||||||
@ -132,7 +132,7 @@ class Lexer {
|
|||||||
this.pos = 0;
|
this.pos = 0;
|
||||||
this.len = data.length;
|
this.len = data.length;
|
||||||
// Sticky regexes: set lastIndex before exec() to match at an exact offset.
|
// Sticky regexes: set lastIndex before exec() to match at an exact offset.
|
||||||
this._numberPattern = /[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/iy;
|
this._numberPattern = /[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/iy;
|
||||||
this._identifierPattern = /[a-z]+/y;
|
this._identifierPattern = /[a-z]+/y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,10 @@ class Stream extends BaseStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getByte() {
|
getByte() {
|
||||||
return this.pos >= this.end ? -1 : this.bytes[this.pos++];
|
if (this.pos >= this.end) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return this.bytes[this.pos++];
|
||||||
}
|
}
|
||||||
|
|
||||||
getBytes(length) {
|
getBytes(length) {
|
||||||
|
|||||||
@ -16,15 +16,19 @@
|
|||||||
import { stringToBytes, Util, warn } from "../shared/util.js";
|
import { stringToBytes, Util, warn } from "../shared/util.js";
|
||||||
|
|
||||||
function isAscii(str) {
|
function isAscii(str) {
|
||||||
// eslint-disable-next-line no-control-regex
|
return (
|
||||||
return typeof str === "string" && (!str || /^[\x00-\x7F]*$/.test(str));
|
typeof str === "string" &&
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
(!str || /^[\x00-\x7F]*$/.test(str))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the string is null or undefined then it is returned as is.
|
// If the string is null or undefined then it is returned as is.
|
||||||
function stringToAsciiOrUTF16BE(str) {
|
function stringToAsciiOrUTF16BE(str) {
|
||||||
return str === null || str === undefined || isAscii(str)
|
if (str === null || str === undefined) {
|
||||||
? str
|
return str;
|
||||||
: stringToUTF16String(str, /* bigEndian = */ true);
|
}
|
||||||
|
return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function stringToUTF16HexString(str) {
|
function stringToUTF16HexString(str) {
|
||||||
|
|||||||
@ -83,9 +83,10 @@ class IdentityToUnicodeMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get(i) {
|
get(i) {
|
||||||
return this.firstChar <= i && i <= this.lastChar
|
if (this.firstChar <= i && i <= this.lastChar) {
|
||||||
? String.fromCharCode(i)
|
return String.fromCharCode(i);
|
||||||
: undefined;
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
charCodeOf(v) {
|
charCodeOf(v) {
|
||||||
|
|||||||
@ -148,31 +148,6 @@ async function writeArray(array, buffer, transform) {
|
|||||||
buffer.push("]");
|
buffer.push("]");
|
||||||
}
|
}
|
||||||
|
|
||||||
// The exponential notation isn't valid in a PDF, hence a number is always
|
|
||||||
// written with all its digits.
|
|
||||||
function numberToPDFString(value) {
|
|
||||||
// `toFixed` uses the exponential notation from 1e21 on, so such a number is
|
|
||||||
// written thanks to BigInt: it's necessarily an integer, and `isInteger` also
|
|
||||||
// rules out NaN and ±Infinity for which BigInt would throw.
|
|
||||||
if (Number.isInteger(value) && Math.abs(value) >= 1e21) {
|
|
||||||
return BigInt(value).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Below that limit `toFixed(10)` never uses the exponential notation (unlike
|
|
||||||
// `toString` which uses it under 1e-6) and it rounds the value: it always
|
|
||||||
// adds 10 decimals, hence scan them backwards to remove the trailing zeros,
|
|
||||||
// and then the dot itself when none of the decimals is left.
|
|
||||||
const str = value.toFixed(10);
|
|
||||||
let end = str.length;
|
|
||||||
while (str[end - 1] === "0") {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
if (str[end - 1] === ".") {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
return str.slice(0, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function writeValue(value, buffer, transform) {
|
async function writeValue(value, buffer, transform) {
|
||||||
if (value instanceof Name) {
|
if (value instanceof Name) {
|
||||||
buffer.push(`/${escapePDFName(value.name)}`);
|
buffer.push(`/${escapePDFName(value.name)}`);
|
||||||
@ -190,7 +165,9 @@ async function writeValue(value, buffer, transform) {
|
|||||||
// matrices (e.g. [0.000008 0 0 0.000008 0 0]).
|
// matrices (e.g. [0.000008 0 0 0.000008 0 0]).
|
||||||
// The numbers must be "rounded" only when pdf.js is producing them and the
|
// The numbers must be "rounded" only when pdf.js is producing them and the
|
||||||
// current transformation matrix is well known.
|
// current transformation matrix is well known.
|
||||||
buffer.push(numberToPDFString(value));
|
// toFixed(10) avoids scientific notation and rounds; the replace removes
|
||||||
|
// trailing zeros (and a trailing dot for integers).
|
||||||
|
buffer.push(value.toFixed(10).replace(/\.?0+$/, ""));
|
||||||
} else if (typeof value === "boolean") {
|
} else if (typeof value === "boolean") {
|
||||||
buffer.push(value.toString());
|
buffer.push(value.toString());
|
||||||
} else if (value instanceof Dict) {
|
} else if (value instanceof Dict) {
|
||||||
|
|||||||
@ -190,9 +190,11 @@ class Builder {
|
|||||||
if (hasNamespace) {
|
if (hasNamespace) {
|
||||||
this._currentNamespace = this._namespaceStack.pop();
|
this._currentNamespace = this._namespaceStack.pop();
|
||||||
}
|
}
|
||||||
prefixes?.forEach(({ prefix }) => {
|
if (prefixes) {
|
||||||
this._namespacePrefixes.get(prefix).pop();
|
prefixes.forEach(({ prefix }) => {
|
||||||
});
|
this._namespacePrefixes.get(prefix).pop();
|
||||||
|
});
|
||||||
|
}
|
||||||
if (nsAgnostic) {
|
if (nsAgnostic) {
|
||||||
this._nsAgnosticLevel--;
|
this._nsAgnosticLevel--;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -154,7 +154,10 @@ class FontFinder {
|
|||||||
|
|
||||||
function selectFont(xfaFont, typeface) {
|
function selectFont(xfaFont, typeface) {
|
||||||
if (xfaFont.posture === "italic") {
|
if (xfaFont.posture === "italic") {
|
||||||
return xfaFont.weight === "bold" ? typeface.bolditalic : typeface.italic;
|
if (xfaFont.weight === "bold") {
|
||||||
|
return typeface.bolditalic;
|
||||||
|
}
|
||||||
|
return typeface.italic;
|
||||||
} else if (xfaFont.weight === "bold") {
|
} else if (xfaFont.weight === "bold") {
|
||||||
return typeface.bold;
|
return typeface.bold;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import {
|
|||||||
import { createValidAbsoluteUrl, warn } from "../../shared/util.js";
|
import { createValidAbsoluteUrl, warn } from "../../shared/util.js";
|
||||||
import { getMeasurement, stripQuotes } from "./utils.js";
|
import { getMeasurement, stripQuotes } from "./utils.js";
|
||||||
import { selectFont } from "./fonts.js";
|
import { selectFont } from "./fonts.js";
|
||||||
import { serializeFontFamily } from "../../shared/css_utils.js";
|
|
||||||
import { TextMeasure } from "./text.js";
|
import { TextMeasure } from "./text.js";
|
||||||
import { XFAObject } from "./xfa_object.js";
|
import { XFAObject } from "./xfa_object.js";
|
||||||
|
|
||||||
@ -598,16 +597,13 @@ function setFontFamily(xfaFont, node, fontFinder, style) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const name = stripQuotes(xfaFont.typeface);
|
const name = stripQuotes(xfaFont.typeface);
|
||||||
// Use the same serialization as the `@font-face` rule, resp. the `FontFace`
|
style.fontFamily = `"${name}"`;
|
||||||
// instance, that the font is registered with; see `createFontFaceRule` and
|
|
||||||
// `createNativeFontFace` in `src/display/font_loader.js`.
|
|
||||||
style.fontFamily = serializeFontFamily(name);
|
|
||||||
|
|
||||||
const typeface = fontFinder.find(name);
|
const typeface = fontFinder.find(name);
|
||||||
if (typeface) {
|
if (typeface) {
|
||||||
const { fontFamily } = typeface.regular.cssFontInfo;
|
const { fontFamily } = typeface.regular.cssFontInfo;
|
||||||
if (fontFamily !== name) {
|
if (fontFamily !== name) {
|
||||||
style.fontFamily = serializeFontFamily(fontFamily);
|
style.fontFamily = `"${fontFamily}"`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const para = getCurrentPara(node);
|
const para = getCurrentPara(node);
|
||||||
|
|||||||
@ -6073,9 +6073,10 @@ class Value extends XFAObject {
|
|||||||
|
|
||||||
[$text]() {
|
[$text]() {
|
||||||
if (this.exData) {
|
if (this.exData) {
|
||||||
return typeof this.exData[$content] === "string"
|
if (typeof this.exData[$content] === "string") {
|
||||||
? this.exData[$content].trim()
|
return this.exData[$content].trim();
|
||||||
: this.exData[$content][$text]().trim();
|
}
|
||||||
|
return this.exData[$content][$text]().trim();
|
||||||
}
|
}
|
||||||
for (const name of Object.getOwnPropertyNames(this)) {
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
if (name === "image") {
|
if (name === "image") {
|
||||||
|
|||||||
@ -284,9 +284,10 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$text]() {
|
[$text]() {
|
||||||
return this[_children].length === 0
|
if (this[_children].length === 0) {
|
||||||
? this[$content]
|
return this[$content];
|
||||||
: this[_children].map(c => c[$text]()).join("");
|
}
|
||||||
|
return this[_children].map(c => c[$text]()).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
get [_attributeNames]() {
|
get [_attributeNames]() {
|
||||||
@ -328,7 +329,11 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$getChildren](name = null) {
|
[$getChildren](name = null) {
|
||||||
return !name ? this[_children] : this[name];
|
if (!name) {
|
||||||
|
return this[_children];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
[$dump]() {
|
[$dump]() {
|
||||||
@ -675,9 +680,11 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$getChildren](name = null) {
|
[$getChildren](name = null) {
|
||||||
return !name
|
if (!name) {
|
||||||
? this[_children]
|
return this[_children];
|
||||||
: this[_children].filter(c => c[$nodeName] === name);
|
}
|
||||||
|
|
||||||
|
return this[_children].filter(c => c[$nodeName] === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[$getChildrenByClass](name) {
|
[$getChildrenByClass](name) {
|
||||||
@ -902,9 +909,11 @@ class XmlObject extends XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$getChildren](name = null) {
|
[$getChildren](name = null) {
|
||||||
return !name
|
if (!name) {
|
||||||
? this[_children]
|
return this[_children];
|
||||||
: this[_children].filter(c => c[$nodeName] === name);
|
}
|
||||||
|
|
||||||
|
return this[_children].filter(c => c[$nodeName] === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
[$getAttributes]() {
|
[$getAttributes]() {
|
||||||
|
|||||||
@ -49,17 +49,16 @@ function isWhitespaceString(s) {
|
|||||||
|
|
||||||
class XMLParserBase {
|
class XMLParserBase {
|
||||||
static get _entityRegex() {
|
static get _entityRegex() {
|
||||||
// Entity references cannot contain "&", keeping the scan linear.
|
return shadow(this, "_entityRegex", /&(?:#x([^;]+)|#([^;]+)|([^;]+));/g);
|
||||||
return shadow(this, "_entityRegex", /&(?:#x([^;&]+)|#([^;&]+)|([^;&]+));/g);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_resolveEntities(s) {
|
_resolveEntities(s) {
|
||||||
return s.replaceAll(XMLParserBase._entityRegex, (all, hex, dec, entity) => {
|
return s.replaceAll(XMLParserBase._entityRegex, (_, hex, dec, entity) => {
|
||||||
if (hex || dec) {
|
if (hex) {
|
||||||
const code = hex ? parseInt(hex, 16) : parseInt(dec, 10);
|
return String.fromCodePoint(parseInt(hex, 16));
|
||||||
// An out-of-range or unparsable code point is kept as-is, since
|
}
|
||||||
// `String.fromCodePoint` would throw on it.
|
if (dec) {
|
||||||
return code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : all;
|
return String.fromCodePoint(parseInt(dec, 10));
|
||||||
}
|
}
|
||||||
switch (entity) {
|
switch (entity) {
|
||||||
case "lt":
|
case "lt":
|
||||||
@ -325,9 +324,10 @@ class SimpleDOMNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get textContent() {
|
get textContent() {
|
||||||
return !this.childNodes
|
if (!this.childNodes) {
|
||||||
? this.nodeValue || ""
|
return this.nodeValue || "";
|
||||||
: this.childNodes.map(child => child.textContent).join("");
|
}
|
||||||
|
return this.childNodes.map(child => child.textContent).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
get children() {
|
get children() {
|
||||||
|
|||||||
@ -75,7 +75,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
|||||||
* @property {Object} svgFactory
|
* @property {Object} svgFactory
|
||||||
* @property {boolean} [enableScripting]
|
* @property {boolean} [enableScripting]
|
||||||
* @property {boolean} [hasJSActions]
|
* @property {boolean} [hasJSActions]
|
||||||
* @property {Map} [fieldObjects]
|
* @property {Object} [fieldObjects]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class AnnotationElementFactory {
|
class AnnotationElementFactory {
|
||||||
@ -803,7 +803,7 @@ class AnnotationElement {
|
|||||||
const fields = [];
|
const fields = [];
|
||||||
|
|
||||||
if (this._fieldObjects) {
|
if (this._fieldObjects) {
|
||||||
const fieldObj = this._fieldObjects.get(name) || [];
|
const fieldObj = this._fieldObjects[name] || [];
|
||||||
|
|
||||||
for (const { page, id, exportValues } of fieldObj) {
|
for (const { page, id, exportValues } of fieldObj) {
|
||||||
if (page === -1) {
|
if (page === -1) {
|
||||||
@ -1012,9 +1012,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
} else {
|
} else {
|
||||||
if (
|
if (
|
||||||
data.actions &&
|
data.actions &&
|
||||||
(data.actions.has("Action") ||
|
(data.actions.Action ||
|
||||||
data.actions.has("Mouse Up") ||
|
data.actions["Mouse Up"] ||
|
||||||
data.actions.has("Mouse Down")) &&
|
data.actions["Mouse Down"]) &&
|
||||||
this.enableScripting &&
|
this.enableScripting &&
|
||||||
this.hasJSActions
|
this.hasJSActions
|
||||||
) {
|
) {
|
||||||
@ -1158,14 +1158,14 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @memberof LinkAnnotationElement
|
* @memberof LinkAnnotationElement
|
||||||
*/
|
*/
|
||||||
_bindJSAction(link, { actions, id, overlaidText }) {
|
_bindJSAction(link, data) {
|
||||||
link.href = this.linkService.getAnchorUrl("");
|
link.href = this.linkService.getAnchorUrl("");
|
||||||
const map = new Map([
|
const map = new Map([
|
||||||
["Action", "onclick"],
|
["Action", "onclick"],
|
||||||
["Mouse Up", "onmouseup"],
|
["Mouse Up", "onmouseup"],
|
||||||
["Mouse Down", "onmousedown"],
|
["Mouse Down", "onmousedown"],
|
||||||
]);
|
]);
|
||||||
for (const name of actions.keys()) {
|
for (const name of Object.keys(data.actions)) {
|
||||||
const jsName = map.get(name);
|
const jsName = map.get(name);
|
||||||
if (!jsName) {
|
if (!jsName) {
|
||||||
continue;
|
continue;
|
||||||
@ -1173,13 +1173,16 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
link[jsName] = () => {
|
link[jsName] = () => {
|
||||||
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
||||||
source: this,
|
source: this,
|
||||||
detail: { id, name },
|
detail: {
|
||||||
|
id: data.id,
|
||||||
|
name,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (overlaidText) {
|
if (data.overlaidText) {
|
||||||
link.title = overlaidText;
|
link.title = data.overlaidText;
|
||||||
}
|
}
|
||||||
link.onclick ||= () => false;
|
link.onclick ||= () => false;
|
||||||
|
|
||||||
@ -1217,12 +1220,12 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
|
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
|
||||||
const fieldIds = new Set(resetFormRefs);
|
const fieldIds = new Set(resetFormRefs);
|
||||||
for (const fieldName of resetFormFields) {
|
for (const fieldName of resetFormFields) {
|
||||||
const fields = this._fieldObjects.get(fieldName) || [];
|
const fields = this._fieldObjects[fieldName] || [];
|
||||||
for (const { id } of fields) {
|
for (const { id } of fields) {
|
||||||
fieldIds.add(id);
|
fieldIds.add(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const fields of this._fieldObjects.values()) {
|
for (const fields of Object.values(this._fieldObjects)) {
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (fieldIds.has(field.id) === include) {
|
if (fieldIds.has(field.id) === include) {
|
||||||
allFields.push(field);
|
allFields.push(field);
|
||||||
@ -1230,7 +1233,7 @@ class LinkAnnotationElement extends AnnotationElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const fields of this._fieldObjects.values()) {
|
for (const fields of Object.values(this._fieldObjects)) {
|
||||||
allFields.push(...fields);
|
allFields.push(...fields);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1376,10 +1379,8 @@ class WidgetAnnotationElement extends AnnotationElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_setEventListeners(element, elementData, names, getter) {
|
_setEventListeners(element, elementData, names, getter) {
|
||||||
const { actions } = this.data;
|
|
||||||
|
|
||||||
for (const [baseName, eventName] of names) {
|
for (const [baseName, eventName] of names) {
|
||||||
if (eventName === "Action" || actions?.has(eventName)) {
|
if (eventName === "Action" || this.data.actions?.[eventName]) {
|
||||||
if (eventName === "Focus" || eventName === "Blur") {
|
if (eventName === "Focus" || eventName === "Blur") {
|
||||||
elementData ||= { focused: false };
|
elementData ||= { focused: false };
|
||||||
}
|
}
|
||||||
@ -1390,10 +1391,10 @@ class WidgetAnnotationElement extends AnnotationElement {
|
|||||||
eventName,
|
eventName,
|
||||||
getter
|
getter
|
||||||
);
|
);
|
||||||
if (eventName === "Focus" && !actions?.has("Blur")) {
|
if (eventName === "Focus" && !this.data.actions?.Blur) {
|
||||||
// Ensure that elementData will have the correct value.
|
// Ensure that elementData will have the correct value.
|
||||||
this._setEventListener(element, elementData, "blur", "Blur", null);
|
this._setEventListener(element, elementData, "blur", "Blur", null);
|
||||||
} else if (eventName === "Blur" && !actions?.has("Focus")) {
|
} else if (eventName === "Blur" && !this.data.actions?.Focus) {
|
||||||
this._setEventListener(element, elementData, "focus", "Focus", null);
|
this._setEventListener(element, elementData, "focus", "Focus", null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1629,7 +1630,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
}
|
}
|
||||||
elementData.lastCommittedValue = target.value;
|
elementData.lastCommittedValue = target.value;
|
||||||
elementData.commitKey = 1;
|
elementData.commitKey = 1;
|
||||||
if (!this.data.actions?.has("Focus")) {
|
if (!this.data.actions?.Focus) {
|
||||||
elementData.focused = true;
|
elementData.focused = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -1751,7 +1752,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
if (!elementData.focused || !event.relatedTarget) {
|
if (!elementData.focused || !event.relatedTarget) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.data.actions?.has("Blur")) {
|
if (!this.data.actions?.Blur) {
|
||||||
elementData.focused = false;
|
elementData.focused = false;
|
||||||
}
|
}
|
||||||
const { target } = event;
|
const { target } = event;
|
||||||
@ -1799,7 +1800,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
_blurListener(event);
|
_blurListener(event);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.data.actions?.has("Keystroke")) {
|
if (this.data.actions?.Keystroke) {
|
||||||
element.addEventListener("beforeinput", event => {
|
element.addEventListener("beforeinput", event => {
|
||||||
elementData.lastCommittedValue = null;
|
elementData.lastCommittedValue = null;
|
||||||
const { data, target } = event;
|
const { data, target } = event;
|
||||||
@ -1811,21 +1812,11 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
|||||||
switch (event.inputType) {
|
switch (event.inputType) {
|
||||||
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
|
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
|
||||||
case "deleteWordBackward": {
|
case "deleteWordBackward": {
|
||||||
// The previous unanchored regex could take quadratic time, so
|
const match = value
|
||||||
// scan backwards over the trailing non-word characters and
|
.substring(0, selectionStart)
|
||||||
// then the word.
|
.match(/\w*\W*$/);
|
||||||
const wordCharPattern = /\w/;
|
if (match) {
|
||||||
while (
|
selStart -= match[0].length;
|
||||||
selStart > 0 &&
|
|
||||||
!wordCharPattern.test(value[selStart - 1])
|
|
||||||
) {
|
|
||||||
selStart--;
|
|
||||||
}
|
|
||||||
while (
|
|
||||||
selStart > 0 &&
|
|
||||||
wordCharPattern.test(value[selStart - 1])
|
|
||||||
) {
|
|
||||||
selStart--;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -3132,7 +3123,10 @@ class PopupElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get isVisible() {
|
get isVisible() {
|
||||||
return !this.#commentManager && this.#container.hidden === false;
|
if (this.#commentManager) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.#container.hidden === false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3953,7 +3947,7 @@ class MediaAnnotationElement extends AnnotationElement {
|
|||||||
* @property {boolean} [enableScripting] - Enable embedded script execution.
|
* @property {boolean} [enableScripting] - Enable embedded script execution.
|
||||||
* @property {boolean} [hasJSActions] - Some fields have JS actions.
|
* @property {boolean} [hasJSActions] - Some fields have JS actions.
|
||||||
* The default value is `false`.
|
* The default value is `false`.
|
||||||
* @property {Map<string, Array<Object>> | null} [fieldObjects]
|
* @property {Object<string, Array<Object>> | null} [fieldObjects]
|
||||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||||
|
|||||||
@ -867,8 +867,8 @@ class PDFDocumentProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||||
* the JavaScript actions:
|
* an {Object} with the JavaScript actions:
|
||||||
* - from the name tree.
|
* - from the name tree.
|
||||||
* - from A or AA entries in the catalog dictionary.
|
* - from A or AA entries in the catalog dictionary.
|
||||||
* , or `null` if no JavaScript exists.
|
* , or `null` if no JavaScript exists.
|
||||||
@ -1009,15 +1009,11 @@ class PDFDocumentProxy {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<PageInfo>} pageInfos - The pages to extract.
|
* @param {Array<PageInfo>} pageInfos - The pages to extract.
|
||||||
* @param {Int32Array} [copyLevels] - For each viewer page, its rank among the
|
|
||||||
* extracted pages sharing the same source page, or -1 if it isn't extracted.
|
|
||||||
* This routes editor annotations when the viewer contains multiple copies
|
|
||||||
* of a source page.
|
|
||||||
* @returns {Promise<Uint8Array>} A promise that is resolved with a
|
* @returns {Promise<Uint8Array>} A promise that is resolved with a
|
||||||
* {Uint8Array} containing the full data of the saved document.
|
* {Uint8Array} containing the full data of the saved document.
|
||||||
*/
|
*/
|
||||||
extractPages(pageInfos, copyLevels = null) {
|
extractPages(pageInfos) {
|
||||||
return this._transport.extractPages(pageInfos, copyLevels);
|
return this._transport.extractPages(pageInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1073,9 +1069,9 @@ class PDFDocumentProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<Map<string, Array<Object>> | null>} A promise that is
|
* @returns {Promise<Object<string, Array<Object>> | null>} A promise that is
|
||||||
* resolved with a {Map} containing /AcroForm field data for the JS sandbox,
|
* resolved with an {Object} containing /AcroForm field data for the JS
|
||||||
* or `null` when no field data is present in the PDF file.
|
* sandbox, or `null` when no field data is present in the PDF file.
|
||||||
*/
|
*/
|
||||||
getFieldObjects() {
|
getFieldObjects() {
|
||||||
return this._transport.getFieldObjects();
|
return this._transport.getFieldObjects();
|
||||||
@ -1433,8 +1429,8 @@ class PDFPageProxy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
* @returns {Promise<Object>} A promise that is resolved with an
|
||||||
* the JavaScript actions, or `null` if no JavaScript exists.
|
* {Object} with JS actions.
|
||||||
*/
|
*/
|
||||||
getJSActions() {
|
getJSActions() {
|
||||||
return this._transport.getPageJSActions(this._pageIndex);
|
return this._transport.getPageJSActions(this._pageIndex);
|
||||||
@ -2940,7 +2936,7 @@ class WorkerTransport {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
extractPages(pageInfos, copyLevels = null) {
|
extractPages(pageInfos) {
|
||||||
const params = {
|
const params = {
|
||||||
pageInfos,
|
pageInfos,
|
||||||
};
|
};
|
||||||
@ -2967,8 +2963,6 @@ class WorkerTransport {
|
|||||||
// Annotation pageIndex tracks the editor's current viewer position; the
|
// Annotation pageIndex tracks the editor's current viewer position; the
|
||||||
// worker keys lookups by source index. Remap UI -> source via pagesMapper
|
// worker keys lookups by source index. Remap UI -> source via pagesMapper
|
||||||
// so reorganized pages still receive their annotations after extraction.
|
// so reorganized pages still receive their annotations after extraction.
|
||||||
// Multiple viewer pages can share a source page. The copy level routes
|
|
||||||
// each editor annotation to the corresponding extracted copy.
|
|
||||||
const mapping = this.pagesMapper.getMapping();
|
const mapping = this.pagesMapper.getMapping();
|
||||||
if (mapping) {
|
if (mapping) {
|
||||||
const remapped = new Map();
|
const remapped = new Map();
|
||||||
@ -2978,13 +2972,9 @@ class WorkerTransport {
|
|||||||
v.pageIndex >= 0 &&
|
v.pageIndex >= 0 &&
|
||||||
v.pageIndex < mapping.length
|
v.pageIndex < mapping.length
|
||||||
) {
|
) {
|
||||||
// copyLevels uses -1 for non-extracted pages. Keep their entries
|
|
||||||
// because an extracted stamp may share their bitmapId; the worker
|
|
||||||
// uses the negative level to skip the annotation itself.
|
|
||||||
const copyLevel = copyLevels?.[v.pageIndex] ?? 0;
|
|
||||||
const sourceIdx = mapping[v.pageIndex] - 1;
|
const sourceIdx = mapping[v.pageIndex] - 1;
|
||||||
if (sourceIdx !== v.pageIndex || copyLevel !== 0) {
|
if (sourceIdx !== v.pageIndex) {
|
||||||
remapped.set(k, { ...v, pageIndex: sourceIdx, copyLevel });
|
remapped.set(k, { ...v, pageIndex: sourceIdx });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,7 +188,10 @@ class CanvasBBoxTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOpenMarker() {
|
getOpenMarker() {
|
||||||
return this._savesStack.length === 0 ? null : this._savesStack.at(-1);
|
if (this._savesStack.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._savesStack.at(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
recordCloseMarker(opIdx, onSavePopped) {
|
recordCloseMarker(opIdx, onSavePopped) {
|
||||||
|
|||||||
@ -188,22 +188,10 @@ function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (newURL.hash) {
|
if (newURL.hash) {
|
||||||
// Locate the last ".pdf" and then extend it to the left, up to the closest
|
const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
|
||||||
// separator. Both steps are linear, whereas a single pattern starting with
|
const hashFilename = reFilename.exec(newURL.hash);
|
||||||
// `[^/?#=]+` is quadratic on a hash which contains no ".pdf" at all.
|
if (hashFilename) {
|
||||||
const { hash } = newURL;
|
return decode(hashFilename[0]);
|
||||||
let extensionStart = -1;
|
|
||||||
for (const { index } of hash.matchAll(/\.pdf\b/gi)) {
|
|
||||||
extensionStart = index;
|
|
||||||
}
|
|
||||||
if (extensionStart > 0) {
|
|
||||||
let filenameStart = extensionStart;
|
|
||||||
while (filenameStart > 0 && !"/?#=".includes(hash[filenameStart - 1])) {
|
|
||||||
filenameStart--;
|
|
||||||
}
|
|
||||||
if (filenameStart < extensionStart) {
|
|
||||||
return decode(hash.slice(filenameStart, extensionStart + 4));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -641,8 +641,7 @@ class DrawLayer {
|
|||||||
textLayerData.selectionDiv = div;
|
textLayerData.selectionDiv = div;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawLayer.#parent && div.parentNode !== drawLayer.#parent) {
|
if (!div.parentNode && drawLayer.#parent) {
|
||||||
// The div can still be in a canvas wrapper which has been removed.
|
|
||||||
drawLayer.#parent.append(div);
|
drawLayer.#parent.append(div);
|
||||||
this.#selections.add(div);
|
this.#selections.add(div);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -131,15 +131,17 @@ class AltText {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return this.#useNewAltTextFlow
|
if (this.#useNewAltTextFlow) {
|
||||||
? this.#altText === null
|
return this.#altText === null;
|
||||||
: !this.#altText && !this.#altTextDecorative;
|
}
|
||||||
|
return !this.#altText && !this.#altTextDecorative;
|
||||||
}
|
}
|
||||||
|
|
||||||
hasData() {
|
hasData() {
|
||||||
return this.#useNewAltTextFlow
|
if (this.#useNewAltTextFlow) {
|
||||||
? this.#altText !== null || !!this.#guessedText
|
return this.#altText !== null || !!this.#guessedText;
|
||||||
: this.isEmpty();
|
}
|
||||||
|
return this.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
get guessedText() {
|
get guessedText() {
|
||||||
|
|||||||
@ -622,7 +622,10 @@ class InkDrawOutline extends Outline {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateProperty(name, value) {
|
updateProperty(name, value) {
|
||||||
return name === "stroke-width" ? this.#updateThickness(value) : null;
|
if (name === "stroke-width") {
|
||||||
|
return this.#updateThickness(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#updateThickness(thickness) {
|
#updateThickness(thickness) {
|
||||||
|
|||||||
@ -1359,7 +1359,16 @@ class AnnotationEditor {
|
|||||||
|
|
||||||
bindEvents(this, div, ["keydown", "pointerdown", "dblclick"]);
|
bindEvents(this, div, ["keydown", "pointerdown", "dblclick"]);
|
||||||
|
|
||||||
this.#addTouchManager();
|
if (this.isResizable && this._uiManager._supportsPinchToZoom) {
|
||||||
|
this.#touchManager ||= new TouchManager({
|
||||||
|
container: div,
|
||||||
|
isPinchingDisabled: () => !this.isSelected,
|
||||||
|
onPinchStart: this.#touchPinchStartCallback.bind(this),
|
||||||
|
onPinching: this.#touchPinchCallback.bind(this),
|
||||||
|
onPinchEnd: this.#touchPinchEndCallback.bind(this),
|
||||||
|
signal: this._uiManager._signal,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.addStandaloneCommentButton();
|
this.addStandaloneCommentButton();
|
||||||
this._uiManager._editorUndoBar?.hide();
|
this._uiManager._editorUndoBar?.hide();
|
||||||
@ -1794,25 +1803,6 @@ class AnnotationEditor {
|
|||||||
this.div.addEventListener("focusout", this.focusout.bind(this), { signal });
|
this.div.addEventListener("focusout", this.focusout.bind(this), { signal });
|
||||||
}
|
}
|
||||||
|
|
||||||
#addTouchManager() {
|
|
||||||
if (
|
|
||||||
this.#touchManager ||
|
|
||||||
!this.div ||
|
|
||||||
!this.isResizable ||
|
|
||||||
!this._uiManager._supportsPinchToZoom
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.#touchManager = new TouchManager({
|
|
||||||
container: this.div,
|
|
||||||
isPinchingDisabled: () => !this.isSelected,
|
|
||||||
onPinchStart: this.#touchPinchStartCallback.bind(this),
|
|
||||||
onPinching: this.#touchPinchCallback.bind(this),
|
|
||||||
onPinchEnd: this.#touchPinchEndCallback.bind(this),
|
|
||||||
signal: this._uiManager._signal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rebuild the editor in case it has been removed on undo.
|
* Rebuild the editor in case it has been removed on undo.
|
||||||
*
|
*
|
||||||
@ -1820,7 +1810,6 @@ class AnnotationEditor {
|
|||||||
*/
|
*/
|
||||||
rebuild() {
|
rebuild() {
|
||||||
this.#addFocusListeners();
|
this.#addFocusListeners();
|
||||||
this.#addTouchManager();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -254,9 +254,10 @@ class SignatureEditor extends DrawingEditor {
|
|||||||
|
|
||||||
/** @inheritdoc */
|
/** @inheritdoc */
|
||||||
get toolbarButtons() {
|
get toolbarButtons() {
|
||||||
return this._uiManager.signatureManager
|
if (this._uiManager.signatureManager) {
|
||||||
? [["editSignature", this._uiManager.signatureManager]]
|
return [["editSignature", this._uiManager.signatureManager]];
|
||||||
: super.toolbarButtons;
|
}
|
||||||
|
return super.toolbarButtons;
|
||||||
}
|
}
|
||||||
|
|
||||||
addSignature(data, heightInPage, description, uuid) {
|
addSignature(data, heightInPage, description, uuid) {
|
||||||
|
|||||||
@ -748,8 +748,6 @@ class AnnotationEditorUIManager {
|
|||||||
|
|
||||||
#savedAllLayers = null;
|
#savedAllLayers = null;
|
||||||
|
|
||||||
#savedEditorsByPage = null;
|
|
||||||
|
|
||||||
#altTextManager = null;
|
#altTextManager = null;
|
||||||
|
|
||||||
#annotationStorage = null;
|
#annotationStorage = null;
|
||||||
@ -1915,7 +1913,7 @@ class AnnotationEditorUIManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updatePageIndex(oldPageIndex, newPageIndex) {
|
updatePageIndex(oldPageIndex, newPageIndex) {
|
||||||
for (const editor of this.#savedEditorsByPage.get(oldPageIndex) || []) {
|
for (const editor of this.getEditors(oldPageIndex)) {
|
||||||
editor.pageIndex = newPageIndex;
|
editor.pageIndex = newPageIndex;
|
||||||
}
|
}
|
||||||
const layer = this.#savedAllLayers.get(oldPageIndex);
|
const layer = this.#savedAllLayers.get(oldPageIndex);
|
||||||
@ -1933,32 +1931,10 @@ class AnnotationEditorUIManager {
|
|||||||
startUpdatePages() {
|
startUpdatePages() {
|
||||||
this.#savedAllLayers = new Map(this.#allLayers);
|
this.#savedAllLayers = new Map(this.#allLayers);
|
||||||
this.#allLayers.clear();
|
this.#allLayers.clear();
|
||||||
|
|
||||||
const savedEditorsByPage = (this.#savedEditorsByPage = new Map());
|
|
||||||
const saveEditor = editor => {
|
|
||||||
savedEditorsByPage
|
|
||||||
.getOrInsertComputed(editor.pageIndex, makeArr)
|
|
||||||
.push(editor);
|
|
||||||
};
|
|
||||||
for (const editor of this.#allEditors.values()) {
|
|
||||||
saveEditor(editor);
|
|
||||||
}
|
|
||||||
// Clones are initially kept serialized until their editor layer is
|
|
||||||
// rendered, hence they're not present in #allEditors yet.
|
|
||||||
for (const [id, editor] of this.#annotationStorage) {
|
|
||||||
if (
|
|
||||||
id.startsWith(AnnotationEditorPrefix) &&
|
|
||||||
!this.#allEditors.has(id) &&
|
|
||||||
Number.isInteger(editor?.pageIndex)
|
|
||||||
) {
|
|
||||||
saveEditor(editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
endUpdatePages() {
|
endUpdatePages() {
|
||||||
this.#savedAllLayers = null;
|
this.#savedAllLayers = null;
|
||||||
this.#savedEditorsByPage = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clonePage(pageIndex, newPageIndex) {
|
clonePage(pageIndex, newPageIndex) {
|
||||||
|
|||||||
@ -22,7 +22,6 @@ import {
|
|||||||
warn,
|
warn,
|
||||||
} from "../shared/util.js";
|
} from "../shared/util.js";
|
||||||
import { makePathFromDrawOPS } from "./display_utils.js";
|
import { makePathFromDrawOPS } from "./display_utils.js";
|
||||||
import { serializeFontFamily } from "../shared/css_utils.js";
|
|
||||||
|
|
||||||
class FontLoader {
|
class FontLoader {
|
||||||
#systemFonts = new Set();
|
#systemFonts = new Set();
|
||||||
@ -440,7 +439,7 @@ class FontFaceObject {
|
|||||||
css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
|
css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
|
||||||
}
|
}
|
||||||
nativeFontFace = new FontFace(
|
nativeFontFace = new FontFace(
|
||||||
serializeFontFamily(this.cssFontInfo.fontFamily),
|
this.cssFontInfo.fontFamily,
|
||||||
this.data,
|
this.data,
|
||||||
css
|
css
|
||||||
);
|
);
|
||||||
@ -464,10 +463,7 @@ class FontFaceObject {
|
|||||||
if (this.cssFontInfo.italicAngle) {
|
if (this.cssFontInfo.italicAngle) {
|
||||||
css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
|
css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
|
||||||
}
|
}
|
||||||
// The font family originates from the PDF document, hence it must be
|
rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`;
|
||||||
// serialized as a <string> to prevent arbitrary rule injection.
|
|
||||||
const fontFamily = serializeFontFamily(this.cssFontInfo.fontFamily);
|
|
||||||
rule = `@font-face {font-family:${fontFamily};${css}src:${url}}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._inspectFont?.(this, url);
|
this._inspectFont?.(this, url);
|
||||||
|
|||||||
@ -25,7 +25,6 @@ import {
|
|||||||
ensureResponseOrigin,
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
trimHeadersEnd,
|
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
import { endRequests } from "./transport_stream.js";
|
import { endRequests } from "./transport_stream.js";
|
||||||
@ -209,7 +208,9 @@ class PDFNetworkStreamReader extends BasePDFStreamReader {
|
|||||||
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
||||||
const responseHeaders = new Headers(
|
const responseHeaders = new Headers(
|
||||||
rawResponseHeaders
|
rawResponseHeaders
|
||||||
? trimHeadersEnd(rawResponseHeaders.trimStart())
|
? rawResponseHeaders
|
||||||
|
.trimStart()
|
||||||
|
.replace(/[^\S ]+$/, "") // Not `trimEnd`, to keep regular spaces.
|
||||||
.split(/[\r\n]+/)
|
.split(/[\r\n]+/)
|
||||||
.map(x => {
|
.map(x => {
|
||||||
const [key, ...val] = x.split(": ");
|
const [key, ...val] = x.split(": ");
|
||||||
|
|||||||
@ -32,17 +32,6 @@ function createHeaders(isHttp, httpHeaders) {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trim the trailing whitespace of the raw response headers, but keep the
|
|
||||||
// regular spaces (hence no `trimEnd`). Scanning backwards keeps this linear,
|
|
||||||
// whereas a `$`-anchored regex is quadratic in the length of the run.
|
|
||||||
function trimHeadersEnd(str) {
|
|
||||||
let end = str.length;
|
|
||||||
while (end > 0 && str[end - 1] !== " " && /\s/.test(str[end - 1])) {
|
|
||||||
end--;
|
|
||||||
}
|
|
||||||
return str.slice(0, end);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getResponseOrigin(url) {
|
function getResponseOrigin(url) {
|
||||||
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
||||||
return URL.parse(url)?.origin ?? null;
|
return URL.parse(url)?.origin ?? null;
|
||||||
@ -128,6 +117,5 @@ export {
|
|||||||
ensureResponseOrigin,
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
trimHeadersEnd,
|
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -111,6 +111,7 @@ class PagesMapper {
|
|||||||
movePages(selectedPages, pagesToMove, index) {
|
movePages(selectedPages, pagesToMove, index) {
|
||||||
this.#ensureInit();
|
this.#ensureInit();
|
||||||
const pageNumberToId = this.#pageNumberToId;
|
const pageNumberToId = this.#pageNumberToId;
|
||||||
|
const prevIdToPageNumber = this.#buildIdToPageNumber();
|
||||||
const movedCount = pagesToMove.length;
|
const movedCount = pagesToMove.length;
|
||||||
const mappedPagesToMove = new Uint32Array(movedCount);
|
const mappedPagesToMove = new Uint32Array(movedCount);
|
||||||
let removedBeforeTarget = 0;
|
let removedBeforeTarget = 0;
|
||||||
@ -125,7 +126,6 @@ class PagesMapper {
|
|||||||
|
|
||||||
const pagesNumber = this.#pagesNumber;
|
const pagesNumber = this.#pagesNumber;
|
||||||
const remainingLen = pagesNumber - movedCount;
|
const remainingLen = pagesNumber - movedCount;
|
||||||
const prevPageNumbers = new Int32Array(pagesNumber);
|
|
||||||
const adjustedTarget = MathClamp(
|
const adjustedTarget = MathClamp(
|
||||||
index - removedBeforeTarget,
|
index - removedBeforeTarget,
|
||||||
0,
|
0,
|
||||||
@ -135,8 +135,7 @@ class PagesMapper {
|
|||||||
// Compact: keep only non-moved pages.
|
// Compact: keep only non-moved pages.
|
||||||
for (let i = 0, r = 0; i < pagesNumber; i++) {
|
for (let i = 0, r = 0; i < pagesNumber; i++) {
|
||||||
if (!selectedPages.has(i + 1)) {
|
if (!selectedPages.has(i + 1)) {
|
||||||
pageNumberToId[r] = pageNumberToId[i];
|
pageNumberToId[r++] = pageNumberToId[i];
|
||||||
prevPageNumbers[r++] = i + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,13 +146,8 @@ class PagesMapper {
|
|||||||
remainingLen
|
remainingLen
|
||||||
);
|
);
|
||||||
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
pageNumberToId.set(mappedPagesToMove, adjustedTarget);
|
||||||
prevPageNumbers.copyWithin(
|
|
||||||
adjustedTarget + movedCount,
|
this.#updatePrevPageNumbers(prevIdToPageNumber);
|
||||||
adjustedTarget,
|
|
||||||
remainingLen
|
|
||||||
);
|
|
||||||
prevPageNumbers.set(pagesToMove, adjustedTarget);
|
|
||||||
this.#prevPageNumbers = prevPageNumbers;
|
|
||||||
|
|
||||||
if (pageNumberToId.every((id, i) => id === i + 1)) {
|
if (pageNumberToId.every((id, i) => id === i + 1)) {
|
||||||
this.#pageNumberToId = null;
|
this.#pageNumberToId = null;
|
||||||
@ -316,48 +310,12 @@ class PagesMapper {
|
|||||||
return this.#pageNumberToId !== null;
|
return this.#pageNumberToId !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds the copy level of every page. Extracted pages are ranked, in page
|
|
||||||
* order, among the extracted pages sharing their page ID. Pages which aren't
|
|
||||||
* extracted get -1.
|
|
||||||
* @param {Array<number>} [extractedPageNumbers] - Sorted 1-based page
|
|
||||||
* numbers to extract, or null to extract everything.
|
|
||||||
* @returns {Int32Array|null} null when the page mapping is the identity.
|
|
||||||
*/
|
|
||||||
#buildCopyLevels(extractedPageNumbers = null) {
|
|
||||||
if (!this.#pageNumberToId) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const copyLevels = new Int32Array(this.#pagesNumber).fill(-1);
|
|
||||||
const counts = new Map();
|
|
||||||
if (extractedPageNumbers) {
|
|
||||||
for (const pageNumber of extractedPageNumbers) {
|
|
||||||
const id = this.getPageId(pageNumber);
|
|
||||||
const level = counts.get(id) ?? 0;
|
|
||||||
counts.set(id, level + 1);
|
|
||||||
copyLevels[pageNumber - 1] = level;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0, ii = this.#pagesNumber; i < ii; i++) {
|
|
||||||
const id = this.#pageNumberToId[i];
|
|
||||||
const level = counts.get(id) ?? 0;
|
|
||||||
counts.set(id, level + 1);
|
|
||||||
copyLevels[i] = level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return copyLevels;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current page mapping suitable for saving.
|
* Gets the current page mapping suitable for saving.
|
||||||
* @param {Map<number, Array<number>>} [idToPageNumber]
|
* @param {Map<number, Array<number>>} [idToPageNumber]
|
||||||
* @param {Int32Array} [copyLevels]
|
* @returns {Array<Object>}
|
||||||
* @returns {{pageInfos: Array<Object>, copyLevels: Int32Array|null}}
|
|
||||||
*/
|
*/
|
||||||
getPageMappingForSaving(
|
getPageMappingForSaving(idToPageNumber = null) {
|
||||||
idToPageNumber = null,
|
|
||||||
copyLevels = this.#buildCopyLevels()
|
|
||||||
) {
|
|
||||||
idToPageNumber ??= this.#buildIdToPageNumber();
|
idToPageNumber ??= this.#buildIdToPageNumber();
|
||||||
// idToPageNumber maps used 1-based IDs to 1-based page numbers.
|
// idToPageNumber maps used 1-based IDs to 1-based page numbers.
|
||||||
// For example if the final pdf contains page 3 twice and they are moved at
|
// For example if the final pdf contains page 3 twice and they are moved at
|
||||||
@ -406,7 +364,7 @@ class PagesMapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { pageInfos: extractParams, copyLevels };
|
return extractParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractPages(extractedPageNumbers) {
|
extractPages(extractedPageNumbers) {
|
||||||
@ -419,10 +377,7 @@ class PagesMapper {
|
|||||||
const usedPageNumbers = usedIds.getOrInsertComputed(id, makeArr);
|
const usedPageNumbers = usedIds.getOrInsertComputed(id, makeArr);
|
||||||
usedPageNumbers.push(i + 1);
|
usedPageNumbers.push(i + 1);
|
||||||
}
|
}
|
||||||
return this.getPageMappingForSaving(
|
return this.getPageMappingForSaving(usedIds);
|
||||||
usedIds,
|
|
||||||
this.#buildCopyLevels(extractedPageNumbers)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -15,10 +15,6 @@
|
|||||||
|
|
||||||
import { OutputScale, stopEvent } from "./display_utils.js";
|
import { OutputScale, stopEvent } from "./display_utils.js";
|
||||||
|
|
||||||
function preventDefault(evt) {
|
|
||||||
evt.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
class TouchManager {
|
class TouchManager {
|
||||||
#container;
|
#container;
|
||||||
|
|
||||||
@ -139,13 +135,8 @@ class TouchManager {
|
|||||||
opt.capture = true;
|
opt.capture = true;
|
||||||
container.addEventListener("pointerdown", stopEvent, opt);
|
container.addEventListener("pointerdown", stopEvent, opt);
|
||||||
container.addEventListener("pointermove", stopEvent, opt);
|
container.addEventListener("pointermove", stopEvent, opt);
|
||||||
// `pointerup` and `pointercancel` are only default-prevented: a
|
container.addEventListener("pointercancel", stopEvent, opt);
|
||||||
// `stopPropagation` in the capture phase also skips the bubble-phase
|
container.addEventListener("pointerup", stopEvent, opt);
|
||||||
// listeners of the very node it's called on, hence swallowing them here
|
|
||||||
// would prevent any session in flight, e.g. an editor being resized, from
|
|
||||||
// ever being ended.
|
|
||||||
container.addEventListener("pointercancel", preventDefault, opt);
|
|
||||||
container.addEventListener("pointerup", preventDefault, opt);
|
|
||||||
this.#onPinchStart?.();
|
this.#onPinchStart?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,7 +189,7 @@ class TouchManager {
|
|||||||
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
|
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
|
||||||
if (
|
if (
|
||||||
!this.#isPinching &&
|
!this.#isPinching &&
|
||||||
Math.abs(pDistance - distance) <= this.MIN_TOUCH_DISTANCE_TO_PINCH
|
Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -216,12 +207,7 @@ class TouchManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The distances are in screen CSS pixels, but the origin must be in client
|
const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
|
||||||
// coordinates, like the one coming from a wheel event.
|
|
||||||
const origin = [
|
|
||||||
(touch0.clientX + touch1.clientX) / 2,
|
|
||||||
(touch0.clientY + touch1.clientY) / 2,
|
|
||||||
];
|
|
||||||
this.#onPinching?.(origin, pDistance, distance);
|
this.#onPinching?.(origin, pDistance, distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -130,12 +130,18 @@ export class SandboxSupportBase {
|
|||||||
}
|
}
|
||||||
this.win.alert(cMsg);
|
this.win.alert(cMsg);
|
||||||
},
|
},
|
||||||
confirm: cMsg =>
|
confirm: cMsg => {
|
||||||
typeof cMsg !== "string" ? false : this.win.confirm(cMsg),
|
if (typeof cMsg !== "string") {
|
||||||
prompt: (cQuestion, cDefault) =>
|
return false;
|
||||||
typeof cQuestion !== "string" || typeof cDefault !== "string"
|
}
|
||||||
? null
|
return this.win.confirm(cMsg);
|
||||||
: this.win.prompt(cQuestion, cDefault),
|
},
|
||||||
|
prompt: (cQuestion, cDefault) => {
|
||||||
|
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.win.prompt(cQuestion, cDefault);
|
||||||
|
},
|
||||||
parseURL: cUrl => {
|
parseURL: cUrl => {
|
||||||
const url = new this.win.URL(cUrl);
|
const url = new this.win.URL(cUrl);
|
||||||
const props = [
|
const props = [
|
||||||
|
|||||||
@ -19,9 +19,7 @@ class SandboxSupport extends SandboxSupportBase {
|
|||||||
exportValueToSandbox(val) {
|
exportValueToSandbox(val) {
|
||||||
// The communication with the Quickjs sandbox is based on strings
|
// The communication with the Quickjs sandbox is based on strings
|
||||||
// So we use JSON.stringfy to serialize
|
// So we use JSON.stringfy to serialize
|
||||||
return JSON.stringify(val, (k, v) =>
|
return JSON.stringify(val);
|
||||||
v instanceof Map ? Object.fromEntries(v) : v
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
importValueFromSandbox(val) {
|
importValueFromSandbox(val) {
|
||||||
@ -67,7 +65,7 @@ class Sandbox {
|
|||||||
let success = false;
|
let success = false;
|
||||||
let buf = 0;
|
let buf = 0;
|
||||||
try {
|
try {
|
||||||
const sandboxData = this.support.exportValueToSandbox(data);
|
const sandboxData = JSON.stringify(data);
|
||||||
// "pdfjsScripting.initSandbox..." MUST be the last line to be evaluated
|
// "pdfjsScripting.initSandbox..." MUST be the last line to be evaluated
|
||||||
// since the returned value is used for the communication.
|
// since the returned value is used for the communication.
|
||||||
code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`);
|
code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`);
|
||||||
|
|||||||
@ -52,9 +52,11 @@ class AForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AFMergeChange(event = globalThis.event) {
|
AFMergeChange(event = globalThis.event) {
|
||||||
return event.willCommit
|
if (event.willCommit) {
|
||||||
? event.value.toString()
|
return event.value.toString();
|
||||||
: this._app._eventDispatcher.mergeChange(event);
|
}
|
||||||
|
|
||||||
|
return this._app._eventDispatcher.mergeChange(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
AFParseDateEx(cString, cOrder) {
|
AFParseDateEx(cString, cOrder) {
|
||||||
@ -100,7 +102,10 @@ class AForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AFMakeArrayFromList(string) {
|
AFMakeArrayFromList(string) {
|
||||||
return typeof string === "string" ? string.split(/, ?/g) : string;
|
if (typeof string === "string") {
|
||||||
|
return string.split(/, ?/g);
|
||||||
|
}
|
||||||
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
AFNumber_Format(
|
AFNumber_Format(
|
||||||
@ -611,9 +616,11 @@ class AForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AFExactMatch(rePatterns, str) {
|
AFExactMatch(rePatterns, str) {
|
||||||
return rePatterns instanceof RegExp
|
if (rePatterns instanceof RegExp) {
|
||||||
? str.match(rePatterns)?.[0] === str || 0
|
return str.match(rePatterns)?.[0] === str || 0;
|
||||||
: rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;
|
}
|
||||||
|
|
||||||
|
return rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,8 +21,8 @@ const FieldType = {
|
|||||||
time: 4,
|
time: 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMap(val) {
|
function createActionsMap(actions) {
|
||||||
return val instanceof Map ? val : new Map(val ? Object.entries(val) : null);
|
return new Map(actions ? Object.entries(actions) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getFieldType(actions) {
|
function getFieldType(actions) {
|
||||||
@ -30,8 +30,10 @@ function getFieldType(actions) {
|
|||||||
if (!format) {
|
if (!format) {
|
||||||
return FieldType.none;
|
return FieldType.none;
|
||||||
}
|
}
|
||||||
format = format[0].trim();
|
|
||||||
|
|
||||||
|
format = format[0];
|
||||||
|
|
||||||
|
format = format.trim();
|
||||||
if (format.startsWith("AFNumber_")) {
|
if (format.startsWith("AFNumber_")) {
|
||||||
return FieldType.number;
|
return FieldType.number;
|
||||||
}
|
}
|
||||||
@ -47,4 +49,4 @@ function getFieldType(actions) {
|
|||||||
return FieldType.none;
|
return FieldType.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { createMap, FieldType, getFieldType };
|
export { createActionsMap, FieldType, getFieldType };
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { makeArr, makeMap, serializeError } from "./app_utils.js";
|
import { makeArr, makeMap, serializeError } from "./app_utils.js";
|
||||||
import { createMap } from "./common.js";
|
import { createActionsMap } from "./common.js";
|
||||||
import { PDFObject } from "./pdf_object.js";
|
import { PDFObject } from "./pdf_object.js";
|
||||||
import { PrintParams } from "./print_params.js";
|
import { PrintParams } from "./print_params.js";
|
||||||
import { ZoomType } from "./constants.js";
|
import { ZoomType } from "./constants.js";
|
||||||
@ -98,7 +98,7 @@ class Doc extends PDFObject {
|
|||||||
|
|
||||||
this._zoomType = ZoomType.none;
|
this._zoomType = ZoomType.none;
|
||||||
this._zoom = data.zoom || 100;
|
this._zoom = data.zoom || 100;
|
||||||
this._actions = createMap(data.actions);
|
this._actions = createActionsMap(data.actions);
|
||||||
this._globalEval = data.globalEval;
|
this._globalEval = data.globalEval;
|
||||||
this._userActivation = false;
|
this._userActivation = false;
|
||||||
this._disablePrinting = false;
|
this._disablePrinting = false;
|
||||||
@ -173,9 +173,9 @@ class Doc extends PDFObject {
|
|||||||
_dispatchPageEvent(name, actions, pageNumber) {
|
_dispatchPageEvent(name, actions, pageNumber) {
|
||||||
if (name === "PageOpen") {
|
if (name === "PageOpen") {
|
||||||
this.#pageActions ??= new Map();
|
this.#pageActions ??= new Map();
|
||||||
this.#pageActions.getOrInsertComputed(pageNumber, () =>
|
if (!this.#pageActions.has(pageNumber)) {
|
||||||
createMap(actions)
|
this.#pageActions.set(pageNumber, createActionsMap(actions));
|
||||||
);
|
}
|
||||||
this._pageNum = pageNumber - 1;
|
this._pageNum = pageNumber - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,10 +13,10 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createMap, FieldType, getFieldType } from "./common.js";
|
import { createActionsMap, FieldType, getFieldType } from "./common.js";
|
||||||
import { makeArr, serializeError } from "./app_utils.js";
|
|
||||||
import { Color } from "./color.js";
|
import { Color } from "./color.js";
|
||||||
import { PDFObject } from "./pdf_object.js";
|
import { PDFObject } from "./pdf_object.js";
|
||||||
|
import { serializeError } from "./app_utils.js";
|
||||||
|
|
||||||
class Field extends PDFObject {
|
class Field extends PDFObject {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
@ -65,7 +65,7 @@ class Field extends PDFObject {
|
|||||||
this.userName = data.userName;
|
this.userName = data.userName;
|
||||||
|
|
||||||
// Private
|
// Private
|
||||||
this._actions = createMap(data.actions);
|
this._actions = createActionsMap(data.actions);
|
||||||
this._browseForFileToSubmit = data.browseForFileToSubmit || null;
|
this._browseForFileToSubmit = data.browseForFileToSubmit || null;
|
||||||
this._buttonCaption = null;
|
this._buttonCaption = null;
|
||||||
this._buttonIcon = null;
|
this._buttonIcon = null;
|
||||||
@ -98,7 +98,10 @@ class Field extends PDFObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get currentValueIndices() {
|
get currentValueIndices() {
|
||||||
return !this._isChoice ? 0 : this._currentValueIndices;
|
if (!this._isChoice) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this._currentValueIndices;
|
||||||
}
|
}
|
||||||
|
|
||||||
set currentValueIndices(indices) {
|
set currentValueIndices(indices) {
|
||||||
@ -496,7 +499,10 @@ class Field extends PDFObject {
|
|||||||
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
|
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._actions.getOrInsertComputed(cTrigger, makeArr).push(cScript);
|
if (!(cTrigger in this._actions)) {
|
||||||
|
this._actions[cTrigger] = [];
|
||||||
|
}
|
||||||
|
this._actions[cTrigger].push(cScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
setFocus() {
|
setFocus() {
|
||||||
@ -578,7 +584,7 @@ class RadioButtonField extends Field {
|
|||||||
for (const radioData of otherButtons) {
|
for (const radioData of otherButtons) {
|
||||||
this.exportValues.push(radioData.exportValues);
|
this.exportValues.push(radioData.exportValues);
|
||||||
this._radioIds.push(radioData.id);
|
this._radioIds.push(radioData.id);
|
||||||
this._radioActions.push(createMap(radioData.actions));
|
this._radioActions.push(createActionsMap(radioData.actions));
|
||||||
if (this._value === radioData.exportValues) {
|
if (this._value === radioData.exportValues) {
|
||||||
this._id = radioData.id;
|
this._id = radioData.id;
|
||||||
}
|
}
|
||||||
@ -677,13 +683,17 @@ class CheckboxField extends RadioButtonField {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isBoxChecked(nWidget) {
|
isBoxChecked(nWidget) {
|
||||||
return this._value === "Off" ? false : super.isBoxChecked(nWidget);
|
if (this._value === "Off") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.isBoxChecked(nWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
isDefaultChecked(nWidget) {
|
isDefaultChecked(nWidget) {
|
||||||
return this.defaultValue === "Off"
|
if (this.defaultValue === "Off") {
|
||||||
? this._value === "Off"
|
return this._value === "Off";
|
||||||
: super.isDefaultChecked(nWidget);
|
}
|
||||||
|
return super.isDefaultChecked(nWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkThisBox(nWidget, bCheckIt = true) {
|
checkThisBox(nWidget, bCheckIt = true) {
|
||||||
|
|||||||
@ -32,7 +32,6 @@ import { AForm } from "./aform.js";
|
|||||||
import { App } from "./app.js";
|
import { App } from "./app.js";
|
||||||
import { Color } from "./color.js";
|
import { Color } from "./color.js";
|
||||||
import { Console } from "./console.js";
|
import { Console } from "./console.js";
|
||||||
import { createMap } from "./common.js";
|
|
||||||
import { Doc } from "./doc.js";
|
import { Doc } from "./doc.js";
|
||||||
import { ProxyHandler } from "./proxy.js";
|
import { ProxyHandler } from "./proxy.js";
|
||||||
import { serializeError } from "./app_utils.js";
|
import { serializeError } from "./app_utils.js";
|
||||||
@ -71,57 +70,61 @@ function initSandbox(params) {
|
|||||||
const util = new Util({ externalCall });
|
const util = new Util({ externalCall });
|
||||||
const appObjects = app._objects;
|
const appObjects = app._objects;
|
||||||
|
|
||||||
for (const [name, objs] of createMap(data.objects)) {
|
if (data.objects) {
|
||||||
const annotations = [];
|
const annotations = [];
|
||||||
let container = null;
|
|
||||||
|
|
||||||
for (const obj of objs) {
|
for (const [name, objs] of Object.entries(data.objects)) {
|
||||||
if (obj.type !== "") {
|
annotations.length = 0;
|
||||||
annotations.push(obj);
|
let container = null;
|
||||||
} else {
|
|
||||||
container = obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let obj = container;
|
for (const obj of objs) {
|
||||||
if (annotations.length > 0) {
|
if (obj.type !== "") {
|
||||||
obj = annotations[0];
|
annotations.push(obj);
|
||||||
obj.send = send;
|
} else {
|
||||||
}
|
container = obj;
|
||||||
|
|
||||||
obj.globalEval = globalEval;
|
|
||||||
obj.doc = _document;
|
|
||||||
obj.fieldPath = name;
|
|
||||||
obj.appObjects = appObjects;
|
|
||||||
obj.util = util;
|
|
||||||
|
|
||||||
const otherFields = annotations.slice(1);
|
|
||||||
|
|
||||||
let field;
|
|
||||||
switch (obj.type) {
|
|
||||||
case "radiobutton": {
|
|
||||||
field = new RadioButtonField(otherFields, obj);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "checkbox": {
|
|
||||||
field = new CheckboxField(otherFields, obj);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (otherFields.length > 0) {
|
|
||||||
obj.siblings = otherFields.map(x => x.id);
|
|
||||||
}
|
}
|
||||||
field = new Field(obj);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const wrapped = new Proxy(field, proxyHandler);
|
let obj = container;
|
||||||
const _object = { obj: field, wrapped };
|
if (annotations.length > 0) {
|
||||||
doc._addField(name, _object);
|
obj = annotations[0];
|
||||||
for (const object of objs) {
|
obj.send = send;
|
||||||
appObjects[object.id] = _object;
|
}
|
||||||
}
|
|
||||||
if (container) {
|
obj.globalEval = globalEval;
|
||||||
appObjects[container.id] = _object;
|
obj.doc = _document;
|
||||||
|
obj.fieldPath = name;
|
||||||
|
obj.appObjects = appObjects;
|
||||||
|
obj.util = util;
|
||||||
|
|
||||||
|
const otherFields = annotations.slice(1);
|
||||||
|
|
||||||
|
let field;
|
||||||
|
switch (obj.type) {
|
||||||
|
case "radiobutton": {
|
||||||
|
field = new RadioButtonField(otherFields, obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "checkbox": {
|
||||||
|
field = new CheckboxField(otherFields, obj);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (otherFields.length > 0) {
|
||||||
|
obj.siblings = otherFields.map(x => x.id);
|
||||||
|
}
|
||||||
|
field = new Field(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped = new Proxy(field, proxyHandler);
|
||||||
|
const _object = { obj: field, wrapped };
|
||||||
|
doc._addField(name, _object);
|
||||||
|
for (const object of objs) {
|
||||||
|
appObjects[object.id] = _object;
|
||||||
|
}
|
||||||
|
if (container) {
|
||||||
|
appObjects[container.id] = _object;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -43,10 +43,13 @@ class ProxyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
set(obj, prop, value) {
|
set(obj, prop, value) {
|
||||||
// If the field is a container for other fields then dispatch the kids.
|
if (obj._kidIds) {
|
||||||
obj._kidIds?.forEach(id => {
|
// If the field is a container for other fields then
|
||||||
obj._appObjects[id].wrapped[prop] = value;
|
// dispatch the kids.
|
||||||
});
|
obj._kidIds.forEach(id => {
|
||||||
|
obj._appObjects[id].wrapped[prop] = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||||
const old = obj[prop];
|
const old = obj[prop];
|
||||||
|
|||||||
@ -252,9 +252,10 @@ class Util extends PDFObject {
|
|||||||
const patterns =
|
const patterns =
|
||||||
/(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
|
/(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
|
||||||
return cFormat.replaceAll(patterns, function (match, pattern) {
|
return cFormat.replaceAll(patterns, function (match, pattern) {
|
||||||
return pattern in handlers
|
if (pattern in handlers) {
|
||||||
? handlers[pattern](data)
|
return handlers[pattern](data);
|
||||||
: pattern.charCodeAt(1);
|
}
|
||||||
|
return pattern.charCodeAt(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
/* Copyright 2026 Mozilla Foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const CONTROL_CHAR_REGEXP = /\p{Cc}/u;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the given value already is a well-formed CSS <string>, i.e. a
|
|
||||||
* value which can be used verbatim since it cannot introduce delimiters.
|
|
||||||
* See https://drafts.csswg.org/css-syntax/#string-token-diagram.
|
|
||||||
* @param {string} str
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function isCSSString(str) {
|
|
||||||
const quote = str[0];
|
|
||||||
if (
|
|
||||||
str.length < 2 ||
|
|
||||||
(quote !== `"` && quote !== `'`) ||
|
|
||||||
str.at(-1) !== quote
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const end = str.length - 1;
|
|
||||||
for (let i = 1; i < end; i++) {
|
|
||||||
const char = str[i];
|
|
||||||
if (char === quote || CONTROL_CHAR_REGEXP.test(char)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (char === "\\") {
|
|
||||||
// Skip the escaped character. A trailing backslash would instead escape
|
|
||||||
// the closing quote, and control characters must not occur in a CSS
|
|
||||||
// <string> even when escaped this way.
|
|
||||||
if (++i >= end || CONTROL_CHAR_REGEXP.test(str[i])) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes a font family, originating from the PDF document, such that it
|
|
||||||
* can safely be interpolated into CSS.
|
|
||||||
* @param {string} fontFamily
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function serializeFontFamily(fontFamily) {
|
|
||||||
if (isCSSString(fontFamily)) {
|
|
||||||
return fontFamily;
|
|
||||||
}
|
|
||||||
// Always emit a <string>, rather than a <custom-ident> sequence, since both
|
|
||||||
// denote the same family name but only the former cannot be mistaken for a
|
|
||||||
// generic family (e.g. `serif`) or a CSS-wide keyword (e.g. `inherit`);
|
|
||||||
// those are not valid font family names and would be ignored.
|
|
||||||
// Control characters use hexadecimal escapes, since CSS line terminators
|
|
||||||
// cannot be escaped by simply prefixing them with a backslash.
|
|
||||||
const escaped = fontFamily.replaceAll(/["\\\p{Cc}]/gu, char =>
|
|
||||||
char === `"` || char === "\\"
|
|
||||||
? `\\${char}`
|
|
||||||
: `\\${char.codePointAt(0).toString(16)} `
|
|
||||||
);
|
|
||||||
return `"${escaped}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export { CONTROL_CHAR_REGEXP, serializeFontFamily };
|
|
||||||
@ -1007,9 +1007,10 @@ class Driver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getLastPageNumber(task) {
|
_getLastPageNumber(task) {
|
||||||
return !task.pdfDoc
|
if (!task.pdfDoc) {
|
||||||
? task.firstPage || 1
|
return task.firstPage || 1;
|
||||||
: task.lastPage || task.pdfDoc.numPages;
|
}
|
||||||
|
return task.lastPage || task.pdfDoc.numPages;
|
||||||
}
|
}
|
||||||
|
|
||||||
_nextPage(task, loadError) {
|
_nextPage(task, loadError) {
|
||||||
|
|||||||
@ -31,12 +31,10 @@ import {
|
|||||||
kbUndo,
|
kbUndo,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
moveEditor,
|
moveEditor,
|
||||||
pinch,
|
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
selectEditor,
|
selectEditor,
|
||||||
selectEditors,
|
selectEditors,
|
||||||
switchToEditor,
|
switchToEditor,
|
||||||
unselectEditor,
|
|
||||||
waitForAnnotationModeChanged,
|
waitForAnnotationModeChanged,
|
||||||
waitForNoElement,
|
waitForNoElement,
|
||||||
waitForPointerUp,
|
waitForPointerUp,
|
||||||
@ -67,26 +65,6 @@ const drawLine = async (page, x0, y0, x1, y1) => {
|
|||||||
await awaitPromise(clickHandle);
|
await awaitPromise(clickHandle);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Draw an editor large enough to have room for two fingers on it, and leave it
|
|
||||||
// selected since that's what makes it resizable with a touchscreen.
|
|
||||||
const drawAndSelectEditor = async page => {
|
|
||||||
await switchToInk(page);
|
|
||||||
const { x, y, width, height } = await getRect(page, ".annotationEditorLayer");
|
|
||||||
await drawLine(
|
|
||||||
page,
|
|
||||||
x + 0.15 * width,
|
|
||||||
y + 0.1 * height,
|
|
||||||
x + 0.75 * width,
|
|
||||||
y + 0.35 * height
|
|
||||||
);
|
|
||||||
await commit(page);
|
|
||||||
|
|
||||||
return {
|
|
||||||
layer: { x, y, width, height },
|
|
||||||
editor: await getRect(page, getEditorSelector(0)),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("Ink Editor", () => {
|
describe("Ink Editor", () => {
|
||||||
describe("Basic operations", () => {
|
describe("Basic operations", () => {
|
||||||
let pages;
|
let pages;
|
||||||
@ -1429,148 +1407,3 @@ describe("Ink must be committed when the document is saved", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Pinch to resize a drawing", () => {
|
|
||||||
let pages;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await closePages(pages);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("must keep resizing a drawing which came back with an undo", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
await switchToInk(page);
|
|
||||||
|
|
||||||
const rect = await getRect(page, ".annotationEditorLayer");
|
|
||||||
await drawLine(
|
|
||||||
page,
|
|
||||||
rect.x + 200,
|
|
||||||
rect.y + 200,
|
|
||||||
rect.x + 300,
|
|
||||||
rect.y + 260
|
|
||||||
);
|
|
||||||
await commit(page);
|
|
||||||
|
|
||||||
const editorSelector = getEditorSelector(0);
|
|
||||||
await clearAll(page);
|
|
||||||
await waitForNoElement(page, editorSelector);
|
|
||||||
await kbUndo(page);
|
|
||||||
await page.waitForSelector(editorSelector);
|
|
||||||
await selectEditor(page, editorSelector);
|
|
||||||
|
|
||||||
const before = await getRect(page, editorSelector);
|
|
||||||
const startGap = Math.min(before.width, before.height) * 0.2;
|
|
||||||
const endGap = Math.max(before.width, before.height) * 1.8;
|
|
||||||
await pinch(page, {
|
|
||||||
centerX: before.x + before.width / 2,
|
|
||||||
centerY: before.y + before.height / 2,
|
|
||||||
startGap,
|
|
||||||
endGap,
|
|
||||||
});
|
|
||||||
const { width: after } = await getRect(page, editorSelector);
|
|
||||||
|
|
||||||
expect(after)
|
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toBeGreaterThan(before.width);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Resize with a touchscreen", () => {
|
|
||||||
let pages;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await closePages(pages);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("must check that the resize session is ended when the finger is lifted while another one is down", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
const { layer } = await drawAndSelectEditor(page);
|
|
||||||
|
|
||||||
// Grabbing a resizer starts a resize session, which disables the
|
|
||||||
// pointer events of the editor layer until the finger is lifted.
|
|
||||||
const resizer = await getRect(
|
|
||||||
page,
|
|
||||||
`${getEditorSelector(0)} .resizer.bottomRight`
|
|
||||||
);
|
|
||||||
await pinch(page, {
|
|
||||||
steps: 0,
|
|
||||||
startPoints: [
|
|
||||||
{
|
|
||||||
x: resizer.x + resizer.width / 2,
|
|
||||||
y: resizer.y + resizer.height / 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: layer.x + 0.3 * layer.width,
|
|
||||||
y: layer.y + 0.9 * layer.height,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
afterFirstStart: () =>
|
|
||||||
page.waitForSelector(".annotationEditorLayer.disabled"),
|
|
||||||
afterFirstEnd: () =>
|
|
||||||
// The `pointerup` of the resizing finger must still be dispatched,
|
|
||||||
// else the resize session would never end and the editor would keep
|
|
||||||
// being resized by the plain mouse moves coming afterwards.
|
|
||||||
page.waitForSelector(".annotationEditorLayer:not(.disabled)"),
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("Tap after a two-finger gesture", () => {
|
|
||||||
let pages;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await closePages(pages);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("must check that the tap following a two-finger gesture selects the editor", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
const { layer, editor } = await drawAndSelectEditor(page);
|
|
||||||
const editorSelector = getEditorSelector(0);
|
|
||||||
|
|
||||||
// One finger on the editor, which starts a drag session, and a second
|
|
||||||
// one elsewhere on the page, which turns it into a two-finger gesture.
|
|
||||||
await pinch(page, {
|
|
||||||
steps: 0,
|
|
||||||
startPoints: [
|
|
||||||
{
|
|
||||||
x: editor.x + 0.5 * editor.width,
|
|
||||||
y: editor.y + 0.8 * editor.height,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: layer.x + 0.3 * layer.width,
|
|
||||||
y: layer.y + 0.9 * layer.height,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
// Once every finger is up, no listener may be left behind to swallow
|
|
||||||
// the `pointerdown` of the next tap on the editor.
|
|
||||||
await unselectEditor(page, editorSelector);
|
|
||||||
await page.touchscreen.tap(
|
|
||||||
editor.x + 0.5 * editor.width,
|
|
||||||
editor.y + 0.8 * editor.height
|
|
||||||
);
|
|
||||||
await waitForSelectedEditor(page, editorSelector);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@ -815,13 +815,19 @@ describe("Reorganize Pages View", () => {
|
|||||||
await page.waitForSelector("#viewsManagerStatusActionButton", {
|
await page.waitForSelector("#viewsManagerStatusActionButton", {
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
// Drag-and-drop behavior is covered separately. Use an exact move
|
const rect1 = await getRect(page, getThumbnailSelector(1));
|
||||||
// here so this test only checks the save payload.
|
const rect2 = await getRect(page, getThumbnailSelector(2));
|
||||||
await movePages(page, [1], 2);
|
|
||||||
|
await dragAndDrop(
|
||||||
|
page,
|
||||||
|
getThumbnailSelector(1),
|
||||||
|
[[0, rect2.y - rect1.y + rect2.height / 2]],
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
const handleSave = await createPromise(page, resolve => {
|
const handleSave = await createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.onSavePages = async ({ data }) => {
|
window.PDFViewerApplication.onSavePages = async ({ data }) => {
|
||||||
resolve(Array.from(data.pageInfos[0].pageIndices));
|
resolve(Array.from(data[0].pageIndices));
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2026,12 +2032,9 @@ describe("Reorganize Pages View", () => {
|
|||||||
const pagesData = await awaitPromise(handleExport);
|
const pagesData = await awaitPromise(handleExport);
|
||||||
expect(pagesData)
|
expect(pagesData)
|
||||||
.withContext(`In ${browserName}`)
|
.withContext(`In ${browserName}`)
|
||||||
.toEqual({
|
.toEqual([
|
||||||
pageInfos: [
|
{ document: null, pageIndices: [0, 1], includePages: [0, 2] },
|
||||||
{ document: null, pageIndices: [0, 1], includePages: [0, 2] },
|
]);
|
||||||
],
|
|
||||||
copyLevels: null,
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitForTextToBe(page, labelSelector, "Select pages");
|
await waitForTextToBe(page, labelSelector, "Select pages");
|
||||||
// All checkboxes should be unchecked.
|
// All checkboxes should be unchecked.
|
||||||
@ -2862,7 +2865,7 @@ describe("Reorganize Pages View", () => {
|
|||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should keep an ink annotation on a pasted and moved page", async () => {
|
it("should check that the pasted page has an ink annotation in the DOM", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
// Enable ink editor mode and draw a line on page 1.
|
// Enable ink editor mode and draw a line on page 1.
|
||||||
@ -2896,43 +2899,20 @@ describe("Reorganize Pages View", () => {
|
|||||||
// Both the original and the cloned annotation must now be in storage.
|
// Both the original and the cloned annotation must now be in storage.
|
||||||
await waitForStorageEntries(page, 2);
|
await waitForStorageEntries(page, 2);
|
||||||
|
|
||||||
const editorIds = await page.evaluate(() => {
|
// Close the reorganize view and navigate to page 3 (the pasted copy)
|
||||||
const entries = Array.from(
|
// to trigger rendering of its annotation editor layer.
|
||||||
window.PDFViewerApplication.pdfDocument.annotationStorage
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
original: entries.find(([, editor]) => editor.pageIndex === 0)[0],
|
|
||||||
clone: entries.find(([, editor]) => editor.pageIndex === 2)[0],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move the pasted copy before the original and verify that both
|
|
||||||
// stored page indices follow the new page order.
|
|
||||||
await movePages(page, [3], 0);
|
|
||||||
const editorPageIndices = await page.evaluate(ids => {
|
|
||||||
const storage =
|
|
||||||
window.PDFViewerApplication.pdfDocument.annotationStorage;
|
|
||||||
return {
|
|
||||||
original: storage.getRawValue(ids.original).pageIndex,
|
|
||||||
clone: storage.getRawValue(ids.clone).pageIndex,
|
|
||||||
};
|
|
||||||
}, editorIds);
|
|
||||||
expect(editorPageIndices)
|
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toEqual({ original: 1, clone: 0 });
|
|
||||||
|
|
||||||
// Show the moved copy and verify that its cloned editor survived.
|
|
||||||
await page.click("#viewsManagerToggleButton");
|
await page.click("#viewsManagerToggleButton");
|
||||||
await page.waitForSelector("#viewsManager", { hidden: true });
|
await page.waitForSelector("#viewsManager", { hidden: true });
|
||||||
await page.evaluate(() => {
|
await page.evaluate(() => {
|
||||||
window.PDFViewerApplication.pdfViewer.currentPageNumber = 1;
|
window.PDFViewerApplication.pdfViewer.currentPageNumber = 3;
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.waitForSelector(`.page[data-page-number="1"] .inkEditor`, {
|
// The cloned ink annotation must appear in the DOM of page 3.
|
||||||
|
await page.waitForSelector(`.page[data-page-number="3"] .inkEditor`, {
|
||||||
visible: true,
|
visible: true,
|
||||||
});
|
});
|
||||||
const inkEditors = await page.$$(
|
const inkEditors = await page.$$(
|
||||||
`.page[data-page-number="1"] .inkEditor`
|
`.page[data-page-number="3"] .inkEditor`
|
||||||
);
|
);
|
||||||
expect(inkEditors.length).withContext(`In ${browserName}`).toBe(1);
|
expect(inkEditors.length).withContext(`In ${browserName}`).toBe(1);
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1204,69 +1204,6 @@ describe("Interaction", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must efficiently delete a word from a large field", async () => {
|
|
||||||
const nonWordLength = 200000;
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
await waitForScripting(page);
|
|
||||||
|
|
||||||
const result = await page.$eval(
|
|
||||||
getSelector("27R"),
|
|
||||||
(element, length) => {
|
|
||||||
element.value = `${"!".repeat(length)}a`;
|
|
||||||
element.setSelectionRange(
|
|
||||||
element.value.length,
|
|
||||||
element.value.length
|
|
||||||
);
|
|
||||||
|
|
||||||
const eventBus = window.PDFViewerApplication.eventBus;
|
|
||||||
const eventBusPrototype = Object.getPrototypeOf(eventBus);
|
|
||||||
const originalDispatch = eventBusPrototype.dispatch;
|
|
||||||
let selection;
|
|
||||||
eventBusPrototype.dispatch = function (eventName, data) {
|
|
||||||
if (
|
|
||||||
this === eventBus &&
|
|
||||||
eventName === "dispatcheventinsandbox"
|
|
||||||
) {
|
|
||||||
const { selEnd, selStart } = data.detail;
|
|
||||||
selection = { selEnd, selStart };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
originalDispatch.call(this, eventName, data);
|
|
||||||
};
|
|
||||||
|
|
||||||
const event = new InputEvent("beforeinput", {
|
|
||||||
bubbles: true,
|
|
||||||
cancelable: true,
|
|
||||||
inputType: "deleteWordBackward",
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const startTime = performance.now();
|
|
||||||
element.dispatchEvent(event);
|
|
||||||
return {
|
|
||||||
duration: performance.now() - startTime,
|
|
||||||
selection,
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
eventBusPrototype.dispatch = originalDispatch;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nonWordLength
|
|
||||||
);
|
|
||||||
expect(result.selection)
|
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toEqual({
|
|
||||||
selEnd: nonWordLength + 1,
|
|
||||||
selStart: nonWordLength,
|
|
||||||
});
|
|
||||||
expect(result.duration)
|
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toBeLessThan(1000);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("must check that an infinite loop is not triggered", async () => {
|
it("must check that an infinite loop is not triggered", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
|
|||||||
@ -600,77 +600,6 @@ async function dragAndDrop(page, selector, translations, steps = 1) {
|
|||||||
await page.waitForSelector("#viewer:not(.noUserSelect)");
|
await page.waitForSelector("#viewer:not(.noUserSelect)");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move two fingers, horizontally centered on (centerX, centerY), from startGap
|
|
||||||
// to endGap: it's a pinch out when endGap is larger than startGap.
|
|
||||||
// Keep in mind that `TouchManager` starts to pinch only once the distance
|
|
||||||
// between the two fingers changed by more than `MIN_TOUCH_DISTANCE_TO_PINCH`
|
|
||||||
// (35 CSS pixels), hence the first moves are swallowed and the resulting zoom
|
|
||||||
// factor is smaller than endGap / startGap.
|
|
||||||
// Explicit start/end points can be used for tests which need an asymmetric
|
|
||||||
// gesture, or hooks around each touch lifetime.
|
|
||||||
async function pinch(
|
|
||||||
page,
|
|
||||||
{
|
|
||||||
afterEnd = null,
|
|
||||||
afterFirstEnd = null,
|
|
||||||
afterFirstStart = null,
|
|
||||||
afterStart = null,
|
|
||||||
beforeEnd = null,
|
|
||||||
centerX = 0,
|
|
||||||
centerY = 0,
|
|
||||||
centerDeltaX = 0,
|
|
||||||
centerDeltaY = 0,
|
|
||||||
startGap = 0,
|
|
||||||
endGap = startGap,
|
|
||||||
endPoints = null,
|
|
||||||
startPoints = null,
|
|
||||||
steps = 12,
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const normalizePoint = point =>
|
|
||||||
Array.isArray(point) ? { x: point[0], y: point[1] } : point;
|
|
||||||
const start = (
|
|
||||||
startPoints || [
|
|
||||||
{ x: centerX - startGap, y: centerY },
|
|
||||||
{ x: centerX + startGap, y: centerY },
|
|
||||||
]
|
|
||||||
).map(normalizePoint);
|
|
||||||
let end;
|
|
||||||
if (endPoints) {
|
|
||||||
end = endPoints.map(normalizePoint);
|
|
||||||
} else if (startPoints) {
|
|
||||||
end = start;
|
|
||||||
} else {
|
|
||||||
end = [
|
|
||||||
{ x: centerX + centerDeltaX - endGap, y: centerY + centerDeltaY },
|
|
||||||
{ x: centerX + centerDeltaX + endGap, y: centerY + centerDeltaY },
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const finger0 = await page.touchscreen.touchStart(start[0].x, start[0].y);
|
|
||||||
await afterFirstStart?.(finger0);
|
|
||||||
const finger1 = await page.touchscreen.touchStart(start[1].x, start[1].y);
|
|
||||||
await afterStart?.([finger0, finger1]);
|
|
||||||
|
|
||||||
for (let i = 1; i <= steps; i++) {
|
|
||||||
const t = i / steps;
|
|
||||||
await finger0.move(
|
|
||||||
start[0].x + (end[0].x - start[0].x) * t,
|
|
||||||
start[0].y + (end[0].y - start[0].y) * t
|
|
||||||
);
|
|
||||||
await finger1.move(
|
|
||||||
start[1].x + (end[1].x - start[1].x) * t,
|
|
||||||
start[1].y + (end[1].y - start[1].y) * t
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await beforeEnd?.([finger0, finger1]);
|
|
||||||
await finger0.end();
|
|
||||||
await afterFirstEnd?.([finger0, finger1]);
|
|
||||||
await finger1.end();
|
|
||||||
await afterEnd?.([finger0, finger1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForPageChanging(page) {
|
function waitForPageChanging(page) {
|
||||||
return createPromise(page, resolve => {
|
return createPromise(page, resolve => {
|
||||||
window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
|
window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
|
||||||
@ -1234,7 +1163,6 @@ export {
|
|||||||
paste,
|
paste,
|
||||||
pasteFromClipboard,
|
pasteFromClipboard,
|
||||||
PDI,
|
PDI,
|
||||||
pinch,
|
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
selectEditor,
|
selectEditor,
|
||||||
selectEditors,
|
selectEditors,
|
||||||
|
|||||||
@ -20,11 +20,9 @@
|
|||||||
import {
|
import {
|
||||||
closePages,
|
closePages,
|
||||||
closeSinglePage,
|
closeSinglePage,
|
||||||
firstPageOnTop,
|
|
||||||
getSpanRectFromText,
|
getSpanRectFromText,
|
||||||
kbSelectAll,
|
kbSelectAll,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
scrollIntoView,
|
|
||||||
waitForEvent,
|
waitForEvent,
|
||||||
} from "./test_utils.mjs";
|
} from "./test_utils.mjs";
|
||||||
import { MathClamp } from "../../src/shared/math_clamp.js";
|
import { MathClamp } from "../../src/shared/math_clamp.js";
|
||||||
@ -1150,94 +1148,6 @@ describe("Text layer", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("when the page has been destroyed and rendered again", () => {
|
|
||||||
const selectionSelector = ".canvasWrapper .selection svg path[d]";
|
|
||||||
const timeout = 5000;
|
|
||||||
|
|
||||||
let pages;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
pages = await loadAndWait(
|
|
||||||
"tracemonkey.pdf",
|
|
||||||
`.page[data-page-number = "1"] .endOfContent`,
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
{ annotationEditorMode: -1 }
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
await closePages(pages);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function selectSomeText(page) {
|
|
||||||
const [positionStart, positionEnd] = await Promise.all([
|
|
||||||
getSpanRectFromText(
|
|
||||||
page,
|
|
||||||
1,
|
|
||||||
"(frequently executed) bytecode sequences, records"
|
|
||||||
).then(middlePosition),
|
|
||||||
getSpanRectFromText(
|
|
||||||
page,
|
|
||||||
1,
|
|
||||||
"them, and compiles them to fast native code. We call such a se-"
|
|
||||||
).then(belowEndPosition),
|
|
||||||
]);
|
|
||||||
|
|
||||||
await page.mouse.move(positionStart.x, positionStart.y);
|
|
||||||
await page.mouse.down();
|
|
||||||
await moveInSteps(page, positionStart, positionEnd, 20);
|
|
||||||
await page.mouse.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
it("must draw the selection", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
await selectSomeText(page);
|
|
||||||
await page.waitForSelector(selectionSelector, { timeout });
|
|
||||||
|
|
||||||
await page.evaluate(() => {
|
|
||||||
document.getSelection().removeAllRanges();
|
|
||||||
});
|
|
||||||
await page.waitForSelector(selectionSelector, {
|
|
||||||
hidden: true,
|
|
||||||
timeout,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Scroll down, page by page, until the first page view is
|
|
||||||
// evicted from the buffer: its canvas wrapper is then removed.
|
|
||||||
const pagesCount = await page.evaluate(
|
|
||||||
() => window.PDFViewerApplication.pagesCount
|
|
||||||
);
|
|
||||||
let isDestroyed = false;
|
|
||||||
for (let i = 2; i <= pagesCount && !isDestroyed; i++) {
|
|
||||||
const selector = `.page[data-page-number = "${i}"]`;
|
|
||||||
await scrollIntoView(page, selector);
|
|
||||||
await page.waitForSelector(
|
|
||||||
`${selector} .canvasWrapper canvas`,
|
|
||||||
{ timeout: 0 }
|
|
||||||
);
|
|
||||||
isDestroyed = !(await page.$(
|
|
||||||
`.page[data-page-number = "1"] .canvasWrapper`
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(isDestroyed)
|
|
||||||
.withContext(`In ${browserName}, first page destroyed`)
|
|
||||||
.toBeTrue();
|
|
||||||
|
|
||||||
await firstPageOnTop(page);
|
|
||||||
await page.waitForSelector(
|
|
||||||
`.page[data-page-number = "1"] .canvasWrapper canvas`,
|
|
||||||
{ timeout: 0 }
|
|
||||||
);
|
|
||||||
await selectSomeText(page);
|
|
||||||
await page.waitForSelector(selectionSelector, { timeout });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("using selection carets", () => {
|
describe("using selection carets", () => {
|
||||||
|
|||||||
@ -327,80 +327,6 @@ describe("PDF Thumbnail View", () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("must move to the previous item after pressing the End key", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
await showViewsManager(page);
|
|
||||||
await waitForThumbnailVisible(page, 1);
|
|
||||||
|
|
||||||
await enableMenuItems(page);
|
|
||||||
|
|
||||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
|
||||||
await page.keyboard.press("Enter");
|
|
||||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Move to the second menu item.
|
|
||||||
await page.keyboard.press("ArrowDown");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Jump to the last menu item.
|
|
||||||
await page.keyboard.press("End");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionExport:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The focus must move relative to the last menu item, and not
|
|
||||||
// relative to the item that was focused before pressing End.
|
|
||||||
await page.keyboard.press("ArrowUp");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionDelete:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("must move to the next item after pressing the Home key", async () => {
|
|
||||||
await Promise.all(
|
|
||||||
pages.map(async ([browserName, page]) => {
|
|
||||||
await showViewsManager(page);
|
|
||||||
await waitForThumbnailVisible(page, 1);
|
|
||||||
|
|
||||||
await enableMenuItems(page);
|
|
||||||
|
|
||||||
await kbFocusNext(page, "#viewsManagerStatusActionButton");
|
|
||||||
await page.keyboard.press("Enter");
|
|
||||||
await waitForMenu(page, "#viewsManagerStatusActionButton");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wrap around to the last menu item.
|
|
||||||
await page.keyboard.press("ArrowUp");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionExport:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Jump to the first menu item.
|
|
||||||
await page.keyboard.press("Home");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionCopy:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// The focus must move relative to the first menu item, and not
|
|
||||||
// relative to the item that was focused before pressing Home.
|
|
||||||
await page.keyboard.press("ArrowDown");
|
|
||||||
await page.waitForSelector("#viewsManagerStatusActionCut:focus", {
|
|
||||||
visible: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Checkbox accessibility", () => {
|
describe("Checkbox accessibility", () => {
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import {
|
|||||||
getRect,
|
getRect,
|
||||||
getSpanRectFromText,
|
getSpanRectFromText,
|
||||||
loadAndWait,
|
loadAndWait,
|
||||||
pinch,
|
|
||||||
scrollIntoView,
|
scrollIntoView,
|
||||||
showViewsManager,
|
showViewsManager,
|
||||||
waitAndClick,
|
waitAndClick,
|
||||||
@ -1528,11 +1527,7 @@ describe("PDF viewer", () => {
|
|||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
pages = await loadAndWait(
|
pages = await loadAndWait(
|
||||||
"tracemonkey.pdf",
|
"tracemonkey.pdf",
|
||||||
`.page[data-page-number = "1"] .endOfContent`,
|
`.page[data-page-number = "1"] .endOfContent`
|
||||||
// Pin the zoom: the drift checked below is proportional to the zoom
|
|
||||||
// level reached at the end of the pinch, and the default `page-fit`
|
|
||||||
// depends on the size of the window.
|
|
||||||
50
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1540,9 +1535,20 @@ describe("PDF viewer", () => {
|
|||||||
await closePages(pages);
|
await closePages(pages);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the content under the pinch center fixed on the screen", async () => {
|
it("keeps the content under the pinch centre fixed on the screen", async () => {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pages.map(async ([browserName, page]) => {
|
pages.map(async ([browserName, page]) => {
|
||||||
|
if (browserName === "firefox") {
|
||||||
|
pending(
|
||||||
|
"Touch events are not supported on devices without touch screen in Firefox."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (browserName === "chrome") {
|
||||||
|
pending(
|
||||||
|
"Pinch zoom emulation is not supported for WebDriver BiDi in Chrome."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const rect = await getSpanRectFromText(page, 1, "type-stable");
|
const rect = await getSpanRectFromText(page, 1, "type-stable");
|
||||||
const originX = rect.x + rect.width / 2;
|
const originX = rect.x + rect.width / 2;
|
||||||
const originY = rect.y + rect.height / 2;
|
const originY = rect.y + rect.height / 2;
|
||||||
@ -1558,17 +1564,14 @@ describe("PDF viewer", () => {
|
|||||||
};
|
};
|
||||||
window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
|
window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
|
||||||
});
|
});
|
||||||
// Spread the two fingers from 50 to 200 pixels apart: the first
|
const client = await page.target().createCDPSession();
|
||||||
// moves are swallowed until the distance between them changed by
|
await client.send("Input.synthesizePinchGesture", {
|
||||||
// more than 35 pixels, hence a zoom factor of about 200/85 = 2.4.
|
x: originX,
|
||||||
await pinch(page, {
|
y: originY,
|
||||||
centerX: originX,
|
scaleFactor: 3,
|
||||||
centerY: originY,
|
gestureSourceType: "touch",
|
||||||
startGap: 25,
|
|
||||||
endGap: 100,
|
|
||||||
});
|
});
|
||||||
await awaitPromise(rendered);
|
await awaitPromise(rendered);
|
||||||
|
|
||||||
const spanHandle = await page.evaluateHandle(() =>
|
const spanHandle = await page.evaluateHandle(() =>
|
||||||
Array.from(
|
Array.from(
|
||||||
document.querySelectorAll(
|
document.querySelectorAll(
|
||||||
@ -1576,21 +1579,7 @@ describe("PDF viewer", () => {
|
|||||||
)
|
)
|
||||||
).find(span => span.textContent.includes("type-stable"))
|
).find(span => span.textContent.includes("type-stable"))
|
||||||
);
|
);
|
||||||
expect(await spanHandle.isIntersectingViewport())
|
expect(await spanHandle.isIntersectingViewport()).toBeTrue();
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toBeTrue();
|
|
||||||
|
|
||||||
// The text which was under the fingers must still be at the same
|
|
||||||
// height: only vertically because a page which is larger than its
|
|
||||||
// container isn't centered in it anymore.
|
|
||||||
// A few pixels are tolerated because the origin is preserved by
|
|
||||||
// scrolling: Chrome snaps the scroll offsets to the device pixels and
|
|
||||||
// the discarded fractions show up as a small drift. It's exact in
|
|
||||||
// Firefox, which keeps them.
|
|
||||||
const newRect = await getSpanRectFromText(page, 1, "type-stable");
|
|
||||||
expect(Math.abs(newRect.y + newRect.height / 2 - originY))
|
|
||||||
.withContext(`In ${browserName}`)
|
|
||||||
.toBeLessThan(5);
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2460,14 +2460,17 @@ describe("annotation", function () {
|
|||||||
annotationGlobalsMock,
|
annotationGlobalsMock,
|
||||||
idFactoryMock
|
idFactoryMock
|
||||||
);
|
);
|
||||||
const { actions } = await annotation.getFieldObject();
|
const fieldObject = await annotation.getFieldObject();
|
||||||
expect(actions).toEqual(
|
const actions = fieldObject.actions;
|
||||||
new Map([
|
expect(actions["Mouse Enter"]).toEqual(["hello()"]);
|
||||||
["Mouse Enter", ["hello()"]],
|
expect(actions["Mouse Exit"]).toEqual([
|
||||||
["Mouse Exit", ["world()", "olleh()", "foo()", "dlrow()", "oof()"]],
|
"world()",
|
||||||
["Mouse Down", ["bar()"]],
|
"olleh()",
|
||||||
])
|
"foo()",
|
||||||
);
|
"dlrow()",
|
||||||
|
"oof()",
|
||||||
|
]);
|
||||||
|
expect(actions["Mouse Down"]).toEqual(["bar()"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should save Japanese text", async function () {
|
it("should save Japanese text", async function () {
|
||||||
@ -3725,7 +3728,7 @@ describe("annotation", function () {
|
|||||||
);
|
);
|
||||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||||
expect(data.pushButton).toBeTrue();
|
expect(data.pushButton).toBeTrue();
|
||||||
expect(data.actions.get("Action")).toEqual(["do_something();"]);
|
expect(data.actions.Action).toEqual(["do_something();"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle push buttons that act as a tooltip only", async function () {
|
it("should handle push buttons that act as a tooltip only", async function () {
|
||||||
|
|||||||
@ -986,9 +986,9 @@ describe("api", function () {
|
|||||||
expect(pdfDocument.numPages).toEqual(1);
|
expect(pdfDocument.numPages).toEqual(1);
|
||||||
|
|
||||||
const jsActions = await pdfDocument.getJSActions();
|
const jsActions = await pdfDocument.getJSActions();
|
||||||
expect(jsActions).toEqual(
|
expect(jsActions).toEqual({
|
||||||
new Map([["OpenAction", ["func=function(){app.alert(1)};func();"]]])
|
OpenAction: ["func=function(){app.alert(1)};func();"],
|
||||||
);
|
});
|
||||||
|
|
||||||
const page = await pdfDocument.getPage(1);
|
const page = await pdfDocument.getPage(1);
|
||||||
expect(page).toBeInstanceOf(PDFPageProxy);
|
expect(page).toBeInstanceOf(PDFPageProxy);
|
||||||
@ -1943,17 +1943,12 @@ describe("api", function () {
|
|||||||
// PDF document with "JavaScript" action in the OpenAction dictionary.
|
// PDF document with "JavaScript" action in the OpenAction dictionary.
|
||||||
const loadingTask = getDocument(buildGetDocumentParams("issue6106.pdf"));
|
const loadingTask = getDocument(buildGetDocumentParams("issue6106.pdf"));
|
||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
const jsActions = await pdfDoc.getJSActions();
|
const { OpenAction } = await pdfDoc.getJSActions();
|
||||||
|
|
||||||
expect(jsActions).toEqual(
|
expect(OpenAction).toEqual([
|
||||||
new Map([
|
"this.print({bUI:true,bSilent:false,bShrinkToFit:true});",
|
||||||
[
|
]);
|
||||||
"OpenAction",
|
expect(OpenAction[0]).toMatch(AutoPrintRegExp);
|
||||||
["this.print({bUI:true,bSilent:false,bShrinkToFit:true});"],
|
|
||||||
],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
expect(jsActions.get("OpenAction")[0]).toMatch(AutoPrintRegExp);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -1993,27 +1988,21 @@ describe("api", function () {
|
|||||||
const page3 = await pdfDoc.getPage(3);
|
const page3 = await pdfDoc.getPage(3);
|
||||||
const page3Actions = await page3.getJSActions();
|
const page3Actions = await page3.getJSActions();
|
||||||
|
|
||||||
expect(docActions).toEqual(
|
expect(docActions).toEqual({
|
||||||
new Map([
|
DidPrint: [`this.getField("Text2").value = "DidPrint";`],
|
||||||
["DidPrint", [`this.getField("Text2").value = "DidPrint";`]],
|
DidSave: [`this.getField("Text2").value = "DidSave";`],
|
||||||
["DidSave", [`this.getField("Text2").value = "DidSave";`]],
|
WillClose: [`this.getField("Text1").value = "WillClose";`],
|
||||||
["WillClose", [`this.getField("Text1").value = "WillClose";`]],
|
WillPrint: [`this.getField("Text1").value = "WillPrint";`],
|
||||||
["WillPrint", [`this.getField("Text1").value = "WillPrint";`]],
|
WillSave: [`this.getField("Text1").value = "WillSave";`],
|
||||||
["WillSave", [`this.getField("Text1").value = "WillSave";`]],
|
});
|
||||||
])
|
expect(page1Actions).toEqual({
|
||||||
);
|
PageOpen: [`this.getField("Text1").value = "PageOpen 1";`],
|
||||||
expect(page1Actions).toEqual(
|
PageClose: [`this.getField("Text2").value = "PageClose 1";`],
|
||||||
new Map([
|
});
|
||||||
["PageOpen", [`this.getField("Text1").value = "PageOpen 1";`]],
|
expect(page3Actions).toEqual({
|
||||||
["PageClose", [`this.getField("Text2").value = "PageClose 1";`]],
|
PageOpen: [`this.getField("Text5").value = "PageOpen 3";`],
|
||||||
])
|
PageClose: [`this.getField("Text6").value = "PageClose 3";`],
|
||||||
);
|
});
|
||||||
expect(page3Actions).toEqual(
|
|
||||||
new Map([
|
|
||||||
["PageOpen", [`this.getField("Text5").value = "PageOpen 3";`]],
|
|
||||||
["PageClose", [`this.getField("Text6").value = "PageClose 3";`]],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -2028,64 +2017,53 @@ describe("api", function () {
|
|||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||||
|
|
||||||
expect(fieldObjects).toEqual(
|
expect(fieldObjects).toEqual({
|
||||||
new Map([
|
Text1: [
|
||||||
[
|
{
|
||||||
"Text1",
|
id: "25R",
|
||||||
[
|
value: "",
|
||||||
{
|
defaultValue: "",
|
||||||
id: "25R",
|
multiline: false,
|
||||||
value: "",
|
password: false,
|
||||||
defaultValue: "",
|
charLimit: 0,
|
||||||
multiline: false,
|
comb: false,
|
||||||
password: false,
|
editable: true,
|
||||||
charLimit: 0,
|
hidden: false,
|
||||||
comb: false,
|
name: "Text1",
|
||||||
editable: true,
|
rect: [24.1789, 719.66, 432.22, 741.66],
|
||||||
hidden: false,
|
actions: null,
|
||||||
name: "Text1",
|
page: 0,
|
||||||
rect: [24.1789, 719.66, 432.22, 741.66],
|
strokeColor: null,
|
||||||
actions: null,
|
fillColor: null,
|
||||||
page: 0,
|
rotation: 0,
|
||||||
strokeColor: null,
|
datetimeFormat: undefined,
|
||||||
fillColor: null,
|
hasDatetimeHTML: false,
|
||||||
rotation: 0,
|
type: "text",
|
||||||
datetimeFormat: undefined,
|
},
|
||||||
hasDatetimeHTML: false,
|
],
|
||||||
type: "text",
|
Button1: [
|
||||||
},
|
{
|
||||||
],
|
id: "26R",
|
||||||
],
|
value: "Off",
|
||||||
[
|
defaultValue: null,
|
||||||
"Button1",
|
exportValues: undefined,
|
||||||
[
|
editable: true,
|
||||||
{
|
name: "Button1",
|
||||||
id: "26R",
|
rect: [455.436, 719.678, 527.436, 739.678],
|
||||||
value: "Off",
|
hidden: false,
|
||||||
defaultValue: null,
|
actions: {
|
||||||
exportValues: undefined,
|
Action: [
|
||||||
editable: true,
|
`this.getField("Text1").value = this.info.authors.join("::");`,
|
||||||
name: "Button1",
|
],
|
||||||
rect: [455.436, 719.678, 527.436, 739.678],
|
},
|
||||||
hidden: false,
|
page: 0,
|
||||||
actions: new Map([
|
strokeColor: null,
|
||||||
[
|
fillColor: new Uint8ClampedArray([192, 192, 192]),
|
||||||
"Action",
|
rotation: 0,
|
||||||
[
|
type: "button",
|
||||||
`this.getField("Text1").value = this.info.authors.join("::");`,
|
},
|
||||||
],
|
],
|
||||||
],
|
});
|
||||||
]),
|
|
||||||
page: 0,
|
|
||||||
strokeColor: null,
|
|
||||||
fillColor: new Uint8ClampedArray([192, 192, 192]),
|
|
||||||
rotation: 0,
|
|
||||||
type: "button",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
])
|
|
||||||
);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -2095,8 +2073,8 @@ describe("api", function () {
|
|||||||
const pdfDoc = await loadingTask.promise;
|
const pdfDoc = await loadingTask.promise;
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||||
|
|
||||||
for (const [name, objs] of fieldObjects) {
|
for (const name in fieldObjects) {
|
||||||
const pageIndexes = objs.map(o => o.page);
|
const pageIndexes = fieldObjects[name].map(o => o.page);
|
||||||
let expected;
|
let expected;
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
@ -6308,29 +6286,6 @@ small scripts as well as for`);
|
|||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clones shared indirect objects reached concurrently only once", async function () {
|
|
||||||
const pdfData = assemblePdf([
|
|
||||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n",
|
|
||||||
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
|
||||||
"3 0 obj\n<< /Type /Page /Parent 2 0 R " +
|
|
||||||
"/MediaBox [0 0 100 100] /Resources << /ExtGState " +
|
|
||||||
"<< /GS1 4 0 R /GS2 4 0 R >> >> >>\nendobj\n",
|
|
||||||
"4 0 obj\n<< /Type /ExtGState /CA 0.5 >>\nendobj\n",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const loadingTask = getDocument({ data: pdfData });
|
|
||||||
const pdfDoc = await loadingTask.promise;
|
|
||||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
|
||||||
expect(countMarker(data, "/Type /ExtGState")).toEqual(1);
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
const newLoadingTask = getDocument({ data });
|
|
||||||
const newPdfDoc = await newLoadingTask.promise;
|
|
||||||
expect(newPdfDoc.numPages).toEqual(1);
|
|
||||||
await newPdfDoc.getPage(1);
|
|
||||||
await newLoadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should merge two PDFs with page included ranges", async function () {
|
it("should merge two PDFs with page included ranges", async function () {
|
||||||
const loadingTask = getDocument(
|
const loadingTask = getDocument(
|
||||||
buildGetDocumentParams("tracemonkey.pdf")
|
buildGetDocumentParams("tracemonkey.pdf")
|
||||||
@ -7302,14 +7257,9 @@ small scripts as well as for`);
|
|||||||
let loadingTask = getDocument(buildGetDocumentParams("empty.pdf"));
|
let loadingTask = getDocument(buildGetDocumentParams("empty.pdf"));
|
||||||
let pdfDoc = await loadingTask.promise;
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
|
||||||
// Mirror the viewer: copy page 1 and paste the copy right after it.
|
// Simulate what clonePage() puts in annotationStorage when a page is
|
||||||
pdfDoc.pagesMapper.copyPages(new Uint32Array([1]));
|
// copied: the original annotation stays on pageIndex 0 and the clone
|
||||||
pdfDoc.pagesMapper.pastePages(1);
|
// is placed on pageIndex 1 (the new position of the pasted copy).
|
||||||
expect(pdfDoc.pagesMapper.pagesNumber).toEqual(2);
|
|
||||||
|
|
||||||
// Model the annotationStorage entries after clonePage(): the original
|
|
||||||
// stays at viewer page index 0 and the copy gets an entry at index 1.
|
|
||||||
// Both viewer pages map to source page 0.
|
|
||||||
const inkAnnotation = {
|
const inkAnnotation = {
|
||||||
annotationType: AnnotationEditorType.INK,
|
annotationType: AnnotationEditorType.INK,
|
||||||
rect: [50, 50, 200, 200],
|
rect: [50, 50, 200, 200],
|
||||||
@ -7347,15 +7297,15 @@ small scripts as well as for`);
|
|||||||
});
|
});
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
|
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
|
||||||
...inkAnnotation,
|
...inkAnnotation,
|
||||||
color: [255, 0, 0],
|
|
||||||
pageIndex: 1,
|
pageIndex: 1,
|
||||||
isClone: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const { pageInfos, copyLevels } = pdfDoc.pagesMapper.extractPages(
|
// Extract page 0 twice: once at output position 0 (original) and once
|
||||||
new Set([1, 2])
|
// at output position 1 (clone), mirroring copy+paste in the UI.
|
||||||
);
|
const data = await pdfDoc.extractPages([
|
||||||
const data = await pdfDoc.extractPages(pageInfos, copyLevels);
|
{ document: null, includePages: [0], pageIndices: [0] },
|
||||||
|
{ document: null, includePages: [0], pageIndices: [1] },
|
||||||
|
]);
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
|
|
||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
@ -7363,8 +7313,7 @@ small scripts as well as for`);
|
|||||||
|
|
||||||
expect(pdfDoc.numPages).toEqual(2);
|
expect(pdfDoc.numPages).toEqual(2);
|
||||||
|
|
||||||
// Each page must have the expected color and a distinct annotation ID.
|
// Both pages should carry the ink annotation.
|
||||||
const annotationIds = [];
|
|
||||||
for (let i = 1; i <= 2; i++) {
|
for (let i = 1; i <= 2; i++) {
|
||||||
const pdfPage = await pdfDoc.getPage(i);
|
const pdfPage = await pdfDoc.getPage(i);
|
||||||
const annotations = await pdfPage.getAnnotations();
|
const annotations = await pdfPage.getAnnotations();
|
||||||
@ -7372,174 +7321,7 @@ small scripts as well as for`);
|
|||||||
expect(annotations[0].annotationType)
|
expect(annotations[0].annotationType)
|
||||||
.withContext(`Page ${i}`)
|
.withContext(`Page ${i}`)
|
||||||
.toEqual(AnnotationType.INK);
|
.toEqual(AnnotationType.INK);
|
||||||
expect(Array.from(annotations[0].color))
|
|
||||||
.withContext(`Page ${i}`)
|
|
||||||
.toEqual(i === 1 ? [0, 0, 255] : [255, 0, 0]);
|
|
||||||
annotationIds.push(annotations[0].id);
|
|
||||||
}
|
}
|
||||||
expect(new Set(annotationIds).size).toEqual(2);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps distinct annotations when a clone moves before its original", async function () {
|
|
||||||
let loadingTask = getDocument(buildGetDocumentParams("empty.pdf"));
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
|
|
||||||
pdfDoc.pagesMapper.copyPages(new Uint32Array([1]));
|
|
||||||
pdfDoc.pagesMapper.pastePages(1);
|
|
||||||
|
|
||||||
const freeText = {
|
|
||||||
annotationType: AnnotationEditorType.FREETEXT,
|
|
||||||
rect: [12, 34, 56, 78],
|
|
||||||
rotation: 0,
|
|
||||||
fontSize: 10,
|
|
||||||
color: [0, 0, 0],
|
|
||||||
};
|
|
||||||
const original = {
|
|
||||||
...freeText,
|
|
||||||
value: "on the original",
|
|
||||||
pageIndex: 0,
|
|
||||||
};
|
|
||||||
const clone = {
|
|
||||||
...freeText,
|
|
||||||
value: "on the clone",
|
|
||||||
pageIndex: 1,
|
|
||||||
isClone: true,
|
|
||||||
};
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", original);
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", clone);
|
|
||||||
|
|
||||||
// Move the clone before the original and update both editor page
|
|
||||||
// indices to match.
|
|
||||||
pdfDoc.pagesMapper.movePages(new Set([2]), [2], 0);
|
|
||||||
expect(pdfDoc.pagesMapper.getPrevPageNumber(1)).toEqual(2);
|
|
||||||
expect(pdfDoc.pagesMapper.getPrevPageNumber(2)).toEqual(1);
|
|
||||||
clone.pageIndex = 0;
|
|
||||||
original.pageIndex = 1;
|
|
||||||
|
|
||||||
const { pageInfos, copyLevels } =
|
|
||||||
pdfDoc.pagesMapper.getPageMappingForSaving();
|
|
||||||
const data = await pdfDoc.extractPages(pageInfos, copyLevels);
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
expect(pdfDoc.numPages).toEqual(2);
|
|
||||||
|
|
||||||
for (let i = 1; i <= 2; i++) {
|
|
||||||
const pdfPage = await pdfDoc.getPage(i);
|
|
||||||
const annotations = await pdfPage.getAnnotations();
|
|
||||||
expect(annotations.map(a => a.contentsObj?.str)).toEqual([
|
|
||||||
i === 1 ? "on the clone" : "on the original",
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("only keeps the annotations of the extracted copy of a page", async function () {
|
|
||||||
let loadingTask = getDocument(
|
|
||||||
buildGetDocumentParams("three_pages_with_number.pdf")
|
|
||||||
);
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
|
|
||||||
// Copy page 1 and paste it at the end: source page 0 is now at the
|
|
||||||
// viewer positions 1 and 4.
|
|
||||||
pdfDoc.pagesMapper.copyPages(new Uint32Array([1]));
|
|
||||||
pdfDoc.pagesMapper.pastePages(3);
|
|
||||||
expect(pdfDoc.pagesMapper.pagesNumber).toEqual(4);
|
|
||||||
|
|
||||||
const freeText = {
|
|
||||||
annotationType: AnnotationEditorType.FREETEXT,
|
|
||||||
rect: [12, 34, 56, 78],
|
|
||||||
rotation: 0,
|
|
||||||
fontSize: 10,
|
|
||||||
color: [0, 0, 0],
|
|
||||||
};
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
|
|
||||||
...freeText,
|
|
||||||
value: "on the original",
|
|
||||||
pageIndex: 0,
|
|
||||||
});
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
|
|
||||||
...freeText,
|
|
||||||
value: "on the clone",
|
|
||||||
pageIndex: 3,
|
|
||||||
isClone: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extract the clone only: the annotation of the original must not be
|
|
||||||
// written on it.
|
|
||||||
const { pageInfos, copyLevels } = pdfDoc.pagesMapper.extractPages(
|
|
||||||
new Set([4])
|
|
||||||
);
|
|
||||||
const data = await pdfDoc.extractPages(pageInfos, copyLevels);
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
expect(pdfDoc.numPages).toEqual(1);
|
|
||||||
|
|
||||||
const pdfPage = await pdfDoc.getPage(1);
|
|
||||||
const annotations = await pdfPage.getAnnotations();
|
|
||||||
expect(annotations.map(a => a.contentsObj?.str)).toEqual([
|
|
||||||
"on the clone",
|
|
||||||
]);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps a shared stamp image when extracting only a copied page", async function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Cannot create a bitmap from Node.js.");
|
|
||||||
}
|
|
||||||
const bitmap = await getImageBitmap("firefox_logo.png");
|
|
||||||
|
|
||||||
let loadingTask = getDocument(buildGetDocumentParams("empty.pdf"));
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
|
|
||||||
pdfDoc.pagesMapper.copyPages(new Uint32Array([1]));
|
|
||||||
pdfDoc.pagesMapper.pastePages(1);
|
|
||||||
|
|
||||||
const stamp = {
|
|
||||||
annotationType: AnnotationEditorType.STAMP,
|
|
||||||
rect: [12, 34, 56, 78],
|
|
||||||
rotation: 0,
|
|
||||||
bitmapId: "im1",
|
|
||||||
};
|
|
||||||
// Model stamp serialization: both entries share a bitmapId, but only
|
|
||||||
// one carries the bitmap data.
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_0", {
|
|
||||||
...stamp,
|
|
||||||
bitmap,
|
|
||||||
pageIndex: 0,
|
|
||||||
});
|
|
||||||
pdfDoc.annotationStorage.setValue("pdfjs_internal_editor_1", {
|
|
||||||
...stamp,
|
|
||||||
pageIndex: 1,
|
|
||||||
isClone: true,
|
|
||||||
isCopy: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { pageInfos, copyLevels } = pdfDoc.pagesMapper.extractPages(
|
|
||||||
new Set([2])
|
|
||||||
);
|
|
||||||
const data = await pdfDoc.extractPages(pageInfos, copyLevels);
|
|
||||||
expect(data).not.toBeNull();
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
expect(pdfDoc.numPages).toEqual(1);
|
|
||||||
|
|
||||||
const pdfPage = await pdfDoc.getPage(1);
|
|
||||||
const annotations = await pdfPage.getAnnotations();
|
|
||||||
expect(annotations.length).toEqual(1);
|
|
||||||
expect(annotations[0].annotationType).toEqual(AnnotationType.STAMP);
|
|
||||||
|
|
||||||
const opList = await pdfPage.getOperatorList();
|
|
||||||
expect(opList.fnArray).toContain(OPS.paintImageXObject);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -7889,36 +7671,6 @@ small scripts as well as for`);
|
|||||||
return fontIndex < 0 ? null : operatorList.argsArray[fontIndex][0];
|
return fontIndex < 0 ? null : operatorList.argsArray[fontIndex][0];
|
||||||
};
|
};
|
||||||
|
|
||||||
it("does not mutate source widget parents", async function () {
|
|
||||||
const pdfData = assemblePdf([
|
|
||||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R " +
|
|
||||||
"/AcroForm 6 0 R >>\nendobj\n",
|
|
||||||
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
|
||||||
"3 0 obj\n<< /Type /Page /Parent 2 0 R " +
|
|
||||||
"/MediaBox [0 0 100 100] /Annots [4 0 R] >>\nendobj\n",
|
|
||||||
"4 0 obj\n<< /Type /Annot /Subtype /Widget /Rect [0 0 20 10] " +
|
|
||||||
"/Parent 5 0 R >>\nendobj\n",
|
|
||||||
"5 0 obj\n<< /FT /Tx /T (group) /Kids [4 0 R] >>\nendobj\n",
|
|
||||||
"6 0 obj\n<< /Fields [] /DA (/Helv 10 Tf) >>\nendobj\n",
|
|
||||||
]);
|
|
||||||
|
|
||||||
let loadingTask = getDocument({ data: pdfData });
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
|
||||||
|
|
||||||
const sourceAnnotations = await (
|
|
||||||
await pdfDoc.getPage(1)
|
|
||||||
).getAnnotations();
|
|
||||||
expect(sourceAnnotations[0].fieldName).toEqual("group");
|
|
||||||
expect(sourceAnnotations[0].fieldType).toEqual("Tx");
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
expect(pdfDoc.numPages).toEqual(1);
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rebuilds a missing AcroForm Fields array", async function () {
|
it("rebuilds a missing AcroForm Fields array", async function () {
|
||||||
const data = assemblePdf([
|
const data = assemblePdf([
|
||||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",
|
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",
|
||||||
@ -7939,73 +7691,9 @@ small scripts as well as for`);
|
|||||||
|
|
||||||
loadingTask = getDocument({ data: extracted });
|
loadingTask = getDocument({ data: extracted });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual(["group"]);
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
|
||||||
expect([...fieldObjects.keys()]).toEqual(["group"]);
|
|
||||||
const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
|
const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
|
||||||
expect(annotations[0].fieldName).toEqual("group");
|
expect(annotations[0].fieldName).toEqual("group");
|
||||||
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rebuilds a missing AcroForm Fields array with a Parent cycle", async function () {
|
|
||||||
const data = assemblePdf([
|
|
||||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",
|
|
||||||
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
|
||||||
"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] " +
|
|
||||||
"/Annots [4 0 R] >>\nendobj\n",
|
|
||||||
"4 0 obj\n<< /Type /Annot /Subtype /Widget /Rect [0 0 20 10] " +
|
|
||||||
"/Parent 5 0 R >>\nendobj\n",
|
|
||||||
// The Parent chain 5 -> 7 -> 5 is cyclic.
|
|
||||||
"5 0 obj\n<< /FT /Tx /T (group) /Kids [4 0 R] /Parent 7 0 R >>\n" +
|
|
||||||
"endobj\n",
|
|
||||||
"6 0 obj\n<< /DA (/Helv 10 Tf) >>\nendobj\n",
|
|
||||||
"7 0 obj\n<< /T (cycle) /Kids [5 0 R] /Parent 5 0 R >>\nendobj\n",
|
|
||||||
]);
|
|
||||||
|
|
||||||
let loadingTask = getDocument({ data });
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
const extracted = await pdfDoc.extractPages([{ document: null }]);
|
|
||||||
expect(extracted).not.toBeNull();
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data: extracted });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
|
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
|
||||||
expect([...fieldObjects.keys()]).toEqual(["group"]);
|
|
||||||
const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
|
|
||||||
expect(annotations[0].fieldName).toEqual("group");
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rebuilds a missing AcroForm Fields array with a bogus Parent", async function () {
|
|
||||||
const data = assemblePdf([
|
|
||||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",
|
|
||||||
"2 0 obj\n<< /Type /Pages /Kids [3 0 R] /Count 1 >>\nendobj\n",
|
|
||||||
"3 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 100 100] " +
|
|
||||||
"/Annots [4 0 R] >>\nendobj\n",
|
|
||||||
"4 0 obj\n<< /Type /Annot /Subtype /Widget /Rect [0 0 20 10] " +
|
|
||||||
"/FT /Tx /T (widget) /Parent 5 0 R >>\nendobj\n",
|
|
||||||
// The Parent isn't a dictionary.
|
|
||||||
"5 0 obj\n[1 2 3]\nendobj\n",
|
|
||||||
"6 0 obj\n<< /DA (/Helv 10 Tf) >>\nendobj\n",
|
|
||||||
]);
|
|
||||||
|
|
||||||
let loadingTask = getDocument({ data });
|
|
||||||
let pdfDoc = await loadingTask.promise;
|
|
||||||
const extracted = await pdfDoc.extractPages([{ document: null }]);
|
|
||||||
expect(extracted).not.toBeNull();
|
|
||||||
await loadingTask.destroy();
|
|
||||||
|
|
||||||
loadingTask = getDocument({ data: extracted });
|
|
||||||
pdfDoc = await loadingTask.promise;
|
|
||||||
|
|
||||||
const annotations = await (await pdfDoc.getPage(1)).getAnnotations();
|
|
||||||
expect(annotations.length).toEqual(1);
|
|
||||||
expect(annotations[0].fieldName).toEqual("widget");
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -8033,10 +7721,7 @@ small scripts as well as for`);
|
|||||||
|
|
||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual(["field"]);
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
|
||||||
expect([...fieldObjects.keys()]).toEqual(["field"]);
|
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -8094,10 +7779,9 @@ small scripts as well as for`);
|
|||||||
|
|
||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
|
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual([
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
"signature",
|
||||||
expect([...fieldObjects.keys()]).toEqual(["signature"]);
|
]);
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -8143,7 +7827,7 @@ small scripts as well as for`);
|
|||||||
// reflects the T entries of the fields in the AcroForm dictionary.
|
// reflects the T entries of the fields in the AcroForm dictionary.
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||||
expect(fieldObjects).not.toBeNull();
|
expect(fieldObjects).not.toBeNull();
|
||||||
expect([...fieldObjects.keys()].sort()).toEqual(origFieldNames);
|
expect(Object.keys(fieldObjects).sort()).toEqual(origFieldNames);
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -8201,7 +7885,7 @@ small scripts as well as for`);
|
|||||||
const allOrigFieldNames = [
|
const allOrigFieldNames = [
|
||||||
...new Set([...origPage1FieldNames, ...origPage2FieldNames]),
|
...new Set([...origPage1FieldNames, ...origPage2FieldNames]),
|
||||||
].sort();
|
].sort();
|
||||||
expect([...fieldObjects.keys()].sort()).toEqual(allOrigFieldNames);
|
expect(Object.keys(fieldObjects).sort()).toEqual(allOrigFieldNames);
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -8213,8 +7897,9 @@ small scripts as well as for`);
|
|||||||
let pdfDoc = await loadingTask.promise;
|
let pdfDoc = await loadingTask.promise;
|
||||||
|
|
||||||
expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
|
expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
|
||||||
const fieldObjects1 = await pdfDoc.getFieldObjects();
|
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||||
expect([...fieldObjects1.keys()]).toEqual(["group"]);
|
"group",
|
||||||
|
]);
|
||||||
|
|
||||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
@ -8226,8 +7911,9 @@ small scripts as well as for`);
|
|||||||
expect(Array.isArray(calculationOrder)).toBeTrue();
|
expect(Array.isArray(calculationOrder)).toBeTrue();
|
||||||
expect(calculationOrder.length).toEqual(1);
|
expect(calculationOrder.length).toEqual(1);
|
||||||
expect(calculationOrder[0]).not.toEqual("6R");
|
expect(calculationOrder[0]).not.toEqual("6R");
|
||||||
const fieldObjects2 = await pdfDoc.getFieldObjects();
|
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||||
expect([...fieldObjects2.keys()]).toEqual(["group"]);
|
"group",
|
||||||
|
]);
|
||||||
|
|
||||||
await loadingTask.destroy();
|
await loadingTask.destroy();
|
||||||
});
|
});
|
||||||
@ -8270,9 +7956,9 @@ small scripts as well as for`);
|
|||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
expect(pdfDoc.numPages).toEqual(2);
|
expect(pdfDoc.numPages).toEqual(2);
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
expect(
|
||||||
expect([...fieldObjects.keys()].sort()).toEqual(["first", "second"]);
|
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||||
|
).toEqual(["first", "second"]);
|
||||||
for (const pageNumber of [1, 2]) {
|
for (const pageNumber of [1, 2]) {
|
||||||
const fontName = await getAppearanceFontName(pdfDoc, pageNumber);
|
const fontName = await getAppearanceFontName(pdfDoc, pageNumber);
|
||||||
expect(fontName).not.toBeNull();
|
expect(fontName).not.toBeNull();
|
||||||
@ -8311,9 +7997,9 @@ small scripts as well as for`);
|
|||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
expect(pdfDoc.numPages).toEqual(2);
|
expect(pdfDoc.numPages).toEqual(2);
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
expect(
|
||||||
expect([...fieldObjects.keys()].sort()).toEqual(["broken", "main"]);
|
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||||
|
).toEqual(["broken", "main"]);
|
||||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||||
expect(fontName).not.toBeNull();
|
expect(fontName).not.toBeNull();
|
||||||
expect(fontName).not.toEqual("g_font_error");
|
expect(fontName).not.toEqual("g_font_error");
|
||||||
@ -8356,9 +8042,9 @@ small scripts as well as for`);
|
|||||||
loadingTask = getDocument({ data });
|
loadingTask = getDocument({ data });
|
||||||
pdfDoc = await loadingTask.promise;
|
pdfDoc = await loadingTask.promise;
|
||||||
expect(pdfDoc.numPages).toEqual(2);
|
expect(pdfDoc.numPages).toEqual(2);
|
||||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
expect(
|
||||||
expect([...fieldObjects.keys()].sort()).toEqual(["check", "main"]);
|
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||||
|
).toEqual(["check", "main"]);
|
||||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||||
expect(fontName).not.toBeNull();
|
expect(fontName).not.toBeNull();
|
||||||
expect(fontName).not.toEqual("g_font_error");
|
expect(fontName).not.toEqual("g_font_error");
|
||||||
|
|||||||
@ -220,24 +220,4 @@ describe("autolinker", function () {
|
|||||||
["john.doe@uni-cityname.tld", "mailto:john.doe@uni-cityname.tld"],
|
["john.doe@uni-cityname.tld", "mailto:john.doe@uni-cityname.tld"],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find emails with the longest parts allowed by the RFCs", function () {
|
|
||||||
const local = "a".repeat(64);
|
|
||||||
const label = "b".repeat(63);
|
|
||||||
testLinks([[`${local}@${label}.com`, `mailto:${local}@${label}.com`]]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("shouldn't find emails with parts longer than allowed by the RFCs", function () {
|
|
||||||
expect(
|
|
||||||
Autolinker.findLinks(`${"a".repeat(107)}@${"a".repeat(80)}.com`)
|
|
||||||
).toEqual([]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle a long run of characters before an @ efficiently", function () {
|
|
||||||
const text = `${"a".repeat(50000)}@`;
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
expect(Autolinker.findLinks(text)).toEqual([]);
|
|
||||||
expect(performance.now() - startTime).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -26,7 +26,6 @@
|
|||||||
"evaluator_spec.js",
|
"evaluator_spec.js",
|
||||||
"event_utils_spec.js",
|
"event_utils_spec.js",
|
||||||
"fetch_stream_spec.js",
|
"fetch_stream_spec.js",
|
||||||
"font_loader_spec.js",
|
|
||||||
"font_substitutions_spec.js",
|
"font_substitutions_spec.js",
|
||||||
"fonts_spec.js",
|
"fonts_spec.js",
|
||||||
"image_utils_spec.js",
|
"image_utils_spec.js",
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import {
|
|||||||
getRotationMatrix,
|
getRotationMatrix,
|
||||||
getSizeInBytes,
|
getSizeInBytes,
|
||||||
isWhiteSpace,
|
isWhiteSpace,
|
||||||
normalizeCSSFontFamily,
|
|
||||||
numberToString,
|
numberToString,
|
||||||
parseXFAPath,
|
parseXFAPath,
|
||||||
recoverJsURL,
|
recoverJsURL,
|
||||||
@ -244,32 +243,6 @@ describe("core_utils", function () {
|
|||||||
{ name: "BAR", pos: 456 },
|
{ name: "BAR", pos: 456 },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should ignore a malformed position", function () {
|
|
||||||
expect(parseXFAPath("foo[].bar[1x].oof[].[3]")).toEqual([
|
|
||||||
{ name: "foo[]", pos: 0 },
|
|
||||||
{ name: "bar[1x]", pos: 0 },
|
|
||||||
{ name: "oof[]", pos: 0 },
|
|
||||||
{ name: "[3]", pos: 0 },
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep the longest name when a component has several brackets", function () {
|
|
||||||
expect(parseXFAPath("foo[1][2]")).toEqual([{ name: "foo[1]", pos: 2 }]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle a long component efficiently", function () {
|
|
||||||
// Looking for the position with a leading `.+` is quadratic in the
|
|
||||||
// length of a component which doesn't end with one.
|
|
||||||
const name = "a".repeat(200000);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const parsedPath = parseXFAPath(name);
|
|
||||||
const duration = performance.now() - startTime;
|
|
||||||
|
|
||||||
expect(parsedPath).toEqual([{ name, pos: 0 }]);
|
|
||||||
expect(duration).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("recoverJsURL", function () {
|
describe("recoverJsURL", function () {
|
||||||
@ -352,41 +325,6 @@ describe("core_utils", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("normalizeCSSFontFamily", function () {
|
|
||||||
it("should strip the spaces preceding a digit", function () {
|
|
||||||
expect(normalizeCSSFontFamily("Wingdings 3")).toEqual("Wingdings3");
|
|
||||||
expect(normalizeCSSFontFamily("Wingdings 3")).toEqual("Wingdings3");
|
|
||||||
expect(normalizeCSSFontFamily(" 1 2 3")).toEqual("123");
|
|
||||||
expect(normalizeCSSFontFamily("MS Gothic 2 Bold 7")).toEqual(
|
|
||||||
"MS Gothic2 Bold7"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep the spaces which don't precede a digit", function () {
|
|
||||||
expect(normalizeCSSFontFamily("")).toEqual("");
|
|
||||||
expect(normalizeCSSFontFamily("Times New Roman")).toEqual(
|
|
||||||
"Times New Roman"
|
|
||||||
);
|
|
||||||
// The runs of spaces must be preserved as-is.
|
|
||||||
expect(normalizeCSSFontFamily(" Times New Roman ")).toEqual(
|
|
||||||
" Times New Roman "
|
|
||||||
);
|
|
||||||
// A digit which isn't preceded by a space is left alone.
|
|
||||||
expect(normalizeCSSFontFamily("Wingdings3")).toEqual("Wingdings3");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle long runs of spaces efficiently", function () {
|
|
||||||
// Guard against a regular expression that backtracks over the spaces,
|
|
||||||
// which makes the replacement quadratic: that needs several seconds
|
|
||||||
// here, whereas a linear one needs well under a millisecond.
|
|
||||||
const fontFamily = `Wingdings${" ".repeat(100000)}`;
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
expect(normalizeCSSFontFamily(fontFamily)).toEqual("Wingdings ");
|
|
||||||
expect(performance.now() - startTime).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("validateCSSFont", function () {
|
describe("validateCSSFont", function () {
|
||||||
it("Check font family", function () {
|
it("Check font family", function () {
|
||||||
const cssFontInfo = {
|
const cssFontInfo = {
|
||||||
@ -440,30 +378,6 @@ describe("core_utils", function () {
|
|||||||
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Check font family containing control characters", function () {
|
|
||||||
const cssFontInfo = {
|
|
||||||
fontFamily: "",
|
|
||||||
fontWeight: 0,
|
|
||||||
italicAngle: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
// A form feed is a newline in CSS, hence it terminates the <string>.
|
|
||||||
cssFontInfo.fontFamily = `"blah\fblah"`;
|
|
||||||
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
|
||||||
|
|
||||||
cssFontInfo.fontFamily = `"blah\x00blah"`;
|
|
||||||
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
|
||||||
|
|
||||||
cssFontInfo.fontFamily = `"blah\tblah"`;
|
|
||||||
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
|
||||||
|
|
||||||
cssFontInfo.fontFamily = `"blah\nblah"`;
|
|
||||||
expect(validateCSSFont(cssFontInfo)).toBeFalse();
|
|
||||||
|
|
||||||
cssFontInfo.fontFamily = `"blah blah"`;
|
|
||||||
expect(validateCSSFont(cssFontInfo)).toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Check font weight", function () {
|
it("Check font weight", function () {
|
||||||
const cssFontInfo = {
|
const cssFontInfo = {
|
||||||
fontFamily: "blah",
|
fontFamily: "blah",
|
||||||
|
|||||||
@ -122,30 +122,6 @@ describe("display_utils", function () {
|
|||||||
expect(
|
expect(
|
||||||
getPdfFilenameFromUrl("http://www.example.com/pdfs/pdf.html#file2.pdf")
|
getPdfFilenameFromUrl("http://www.example.com/pdfs/pdf.html#file2.pdf")
|
||||||
).toEqual("file2.pdf");
|
).toEqual("file2.pdf");
|
||||||
// Only the last ".pdf" of the hash is used.
|
|
||||||
expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#a.pdf/b.pdf")).toEqual(
|
|
||||||
"b.pdf"
|
|
||||||
);
|
|
||||||
// A ".pdf" which isn't preceded by a name is ignored.
|
|
||||||
expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#=.pdf")).toEqual(
|
|
||||||
"document.pdf"
|
|
||||||
);
|
|
||||||
// An invalid last ".pdf" prevents an earlier valid one from being used.
|
|
||||||
expect(getPdfFilenameFromUrl("/pdfs/pdfs.html#a.pdf/=.pdf")).toEqual(
|
|
||||||
"document.pdf"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("gets PDF filename from a long hash string efficiently", function () {
|
|
||||||
// Scanning the hash for a name is quadratic when it contains no ".pdf".
|
|
||||||
const url = `/pdfs/pdfs.html#${"a".repeat(200000)}`;
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
const filename = getPdfFilenameFromUrl(url);
|
|
||||||
const duration = performance.now() - startTime;
|
|
||||||
|
|
||||||
expect(filename).toEqual("document.pdf");
|
|
||||||
expect(duration).toBeLessThan(1000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("gets correct PDF filename when multiple ones are present", function () {
|
it("gets correct PDF filename when multiple ones are present", function () {
|
||||||
|
|||||||
@ -628,43 +628,39 @@ describe("document", function () {
|
|||||||
const kid2BisRef = Ref.get(266, 0);
|
const kid2BisRef = Ref.get(266, 0);
|
||||||
const parentRef = Ref.get(358, 0);
|
const parentRef = Ref.get(358, 0);
|
||||||
|
|
||||||
const allFieldsObj = Object.create(null);
|
const allFields = Object.create(null);
|
||||||
for (const name of ["parent", "kid1", "kid2", "kid11"]) {
|
for (const name of ["parent", "kid1", "kid2", "kid11"]) {
|
||||||
const buttonWidgetDict = new Dict();
|
const buttonWidgetDict = new Dict();
|
||||||
buttonWidgetDict.set("Type", Name.get("Annot"));
|
buttonWidgetDict.set("Type", Name.get("Annot"));
|
||||||
buttonWidgetDict.set("Subtype", Name.get("Widget"));
|
buttonWidgetDict.set("Subtype", Name.get("Widget"));
|
||||||
buttonWidgetDict.set("FT", Name.get("Btn"));
|
buttonWidgetDict.set("FT", Name.get("Btn"));
|
||||||
buttonWidgetDict.set("T", name);
|
buttonWidgetDict.set("T", name);
|
||||||
allFieldsObj[name] = buttonWidgetDict;
|
allFields[name] = buttonWidgetDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
allFieldsObj.kid1.set("Kids", [kid11Ref]);
|
allFields.kid1.set("Kids", [kid11Ref]);
|
||||||
allFieldsObj.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
allFields.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
||||||
|
|
||||||
const xref = new XRefMock([
|
const xref = new XRefMock([
|
||||||
{ ref: parentRef, data: allFieldsObj.parent },
|
{ ref: parentRef, data: allFields.parent },
|
||||||
{ ref: kid1Ref, data: allFieldsObj.kid1 },
|
{ ref: kid1Ref, data: allFields.kid1 },
|
||||||
{ ref: kid11Ref, data: allFieldsObj.kid11 },
|
{ ref: kid11Ref, data: allFields.kid11 },
|
||||||
{ ref: kid2Ref, data: allFieldsObj.kid2 },
|
{ ref: kid2Ref, data: allFields.kid2 },
|
||||||
{ ref: kid2BisRef, data: allFieldsObj.kid2 },
|
{ ref: kid2BisRef, data: allFields.kid2 },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
acroForm.set("Fields", [parentRef]);
|
acroForm.set("Fields", [parentRef]);
|
||||||
pdfDocument = getDocument(acroForm, xref);
|
pdfDocument = getDocument(acroForm, xref);
|
||||||
|
fields = (await pdfDocument.fieldObjects).allFields;
|
||||||
|
|
||||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
for (const [name, objs] of Object.entries(fields)) {
|
||||||
|
fields[name] = objs.map(obj => obj.id);
|
||||||
|
}
|
||||||
|
|
||||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
expect(fields["parent.kid1"]).toEqual(["314R"]);
|
||||||
name,
|
expect(fields["parent.kid1.kid11"]).toEqual(["159R"]);
|
||||||
objs.map(obj => obj.id),
|
expect(fields["parent.kid2"]).toEqual(["265R", "266R"]);
|
||||||
]);
|
expect(fields.parent).toEqual(["358R"]);
|
||||||
expect(objIds).toEqual([
|
|
||||||
["parent", ["358R"]],
|
|
||||||
["parent.kid1", ["314R"]],
|
|
||||||
["parent.kid1.kid11", ["159R"]],
|
|
||||||
["parent.kid2", ["265R", "266R"]],
|
|
||||||
]);
|
|
||||||
expect(orphanFields.size).toEqual(3);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should get field objects with a circular `Parent` chain", async function () {
|
it("should get field objects with a circular `Parent` chain", async function () {
|
||||||
@ -697,14 +693,9 @@ describe("document", function () {
|
|||||||
acroForm.set("Fields", [widgetRef]);
|
acroForm.set("Fields", [widgetRef]);
|
||||||
const pdfDocument = getDocument(acroForm, xref);
|
const pdfDocument = getDocument(acroForm, xref);
|
||||||
|
|
||||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
const { allFields } = await pdfDocument.fieldObjects;
|
||||||
|
expect(Object.keys(allFields)).toEqual([""]);
|
||||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
expect(allFields[""].map(obj => obj.id)).toEqual(["1R"]);
|
||||||
name,
|
|
||||||
objs.map(obj => obj.id),
|
|
||||||
]);
|
|
||||||
expect(objIds).toEqual([["", ["1R"]]]);
|
|
||||||
expect(orphanFields.size).toEqual(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should check if fields have any actions", async function () {
|
it("should check if fields have any actions", async function () {
|
||||||
|
|||||||
@ -1,151 +0,0 @@
|
|||||||
/* Copyright 2026 Mozilla Foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { FontFaceObject } from "../../src/display/font_loader.js";
|
|
||||||
import { isNodeJS } from "../../src/shared/util.js";
|
|
||||||
|
|
||||||
describe("font_loader", function () {
|
|
||||||
describe("FontFaceObject", function () {
|
|
||||||
function createFontFaceObject(fontFamily) {
|
|
||||||
return new FontFaceObject({
|
|
||||||
cssFontInfo: { fontFamily, fontWeight: "400", italicAngle: "0" },
|
|
||||||
data: new Uint8Array([0x00]),
|
|
||||||
disableFontFace: false,
|
|
||||||
fontExtraProperties: false,
|
|
||||||
loadedName: "g_d0_f1",
|
|
||||||
mimetype: "font/opentype",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFontFamily(rule) {
|
|
||||||
const start = rule.indexOf("font-family:") + "font-family:".length;
|
|
||||||
return rule.slice(start, rule.indexOf(";font-weight:", start));
|
|
||||||
}
|
|
||||||
|
|
||||||
it("creates a font-face rule", function () {
|
|
||||||
expect(
|
|
||||||
getFontFamily(createFontFaceObject("Foo-Bar").createFontFaceRule())
|
|
||||||
).toEqual(`"Foo-Bar"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps an injected rule inside the font family <string> (issue GHSA-wxrh-xgw3-3wqf)", function () {
|
|
||||||
const fontFamily = `"};body{background-image:url(https://example.com/)};a{x:"`;
|
|
||||||
const rule = createFontFaceObject(fontFamily).createFontFaceRule();
|
|
||||||
|
|
||||||
// The value already is a well-formed <string>, hence it's kept as-is.
|
|
||||||
expect(getFontFamily(rule)).toEqual(fontFamily);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("serializes a font family which isn't a well-formed <string>", function () {
|
|
||||||
expect(
|
|
||||||
getFontFamily(
|
|
||||||
createFontFaceObject(String.raw`Foo"Bar\Baz`).createFontFaceRule()
|
|
||||||
)
|
|
||||||
).toEqual(String.raw`"Foo\"Bar\\Baz"`);
|
|
||||||
|
|
||||||
// A trailing backslash would otherwise escape the closing quote.
|
|
||||||
expect(
|
|
||||||
getFontFamily(
|
|
||||||
createFontFaceObject(
|
|
||||||
String.raw`"};body{background-image:url(https://example.com/)};a{x:\"`
|
|
||||||
).createFontFaceRule()
|
|
||||||
)
|
|
||||||
).toEqual(
|
|
||||||
String.raw`"\"};body{background-image:url(https://example.com/)};a{x:\\\""`
|
|
||||||
);
|
|
||||||
|
|
||||||
// A trailing backslash would otherwise escape the following semi-colon,
|
|
||||||
// thus swallowing the `font-weight` declaration.
|
|
||||||
expect(
|
|
||||||
getFontFamily(createFontFaceObject("Foo\\").createFontFaceRule())
|
|
||||||
).toEqual(String.raw`"Foo\\"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("escapes CSS line terminators", function () {
|
|
||||||
const rule = createFontFaceObject(
|
|
||||||
"safe\f}body{background-image:url(https://example.com/)}/*"
|
|
||||||
).createFontFaceRule();
|
|
||||||
|
|
||||||
expect(rule).not.toContain("\f");
|
|
||||||
expect(getFontFamily(rule)).toEqual(
|
|
||||||
String.raw`"safe\c }body{background-image:url(https://example.com/)}/*"`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("quotes generic families and CSS-wide keywords", function () {
|
|
||||||
// Those are not valid font family names, hence the `font-family`
|
|
||||||
// descriptor would be ignored if they were emitted unquoted.
|
|
||||||
for (const fontFamily of ["serif", "monospace", "inherit", "initial"]) {
|
|
||||||
expect(
|
|
||||||
getFontFamily(createFontFaceObject(fontFamily).createFontFaceRule())
|
|
||||||
).toEqual(`"${fontFamily}"`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("uses the same font family in both font loading paths", function () {
|
|
||||||
const NativeFontFace = globalThis.FontFace;
|
|
||||||
globalThis.FontFace = function MockFontFace(family) {
|
|
||||||
this.family = family;
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
for (const fontFamily of [`"Foo Bar"`, "Foo-Bar", "serif"]) {
|
|
||||||
const font = createFontFaceObject(fontFamily);
|
|
||||||
|
|
||||||
expect(font.createNativeFontFace().family).toEqual(
|
|
||||||
getFontFamily(font.createFontFaceRule())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
globalThis.FontFace = NativeFontFace;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it("cannot escape the @font-face rule", function () {
|
|
||||||
if (isNodeJS) {
|
|
||||||
pending("Document is not supported in Node.js.");
|
|
||||||
}
|
|
||||||
const style = document.createElement("style");
|
|
||||||
document.head.append(style);
|
|
||||||
|
|
||||||
try {
|
|
||||||
for (const fontFamily of [
|
|
||||||
`"};body{background-image:url(https://example.com/)};a{x:"`,
|
|
||||||
String.raw`"};body{background-image:url(https://example.com/)};a{x:\"`,
|
|
||||||
"safe\f}body{background-image:url(https://example.com/)}/*",
|
|
||||||
String.raw`Foo"Bar\Baz`,
|
|
||||||
"Foo\\",
|
|
||||||
"serif",
|
|
||||||
]) {
|
|
||||||
const rule = createFontFaceObject(fontFamily).createFontFaceRule();
|
|
||||||
style.sheet.insertRule(rule, style.sheet.cssRules.length);
|
|
||||||
|
|
||||||
const cssRule = [...style.sheet.cssRules].at(-1);
|
|
||||||
expect(cssRule.constructor.name)
|
|
||||||
.withContext(fontFamily)
|
|
||||||
.toEqual("CSSFontFaceRule");
|
|
||||||
// The `font-family` descriptor must be both present and complete,
|
|
||||||
// i.e. the value must not have been truncated nor dropped.
|
|
||||||
expect(cssRule.style.getPropertyValue("font-family"))
|
|
||||||
.withContext(fontFamily)
|
|
||||||
.not.toEqual("");
|
|
||||||
}
|
|
||||||
// No additional rules were injected.
|
|
||||||
expect(style.sheet.cssRules.length).toEqual(6);
|
|
||||||
} finally {
|
|
||||||
style.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -72,7 +72,6 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/evaluator_spec.js",
|
"pdfjs-test/unit/evaluator_spec.js",
|
||||||
"pdfjs-test/unit/event_utils_spec.js",
|
"pdfjs-test/unit/event_utils_spec.js",
|
||||||
"pdfjs-test/unit/fetch_stream_spec.js",
|
"pdfjs-test/unit/fetch_stream_spec.js",
|
||||||
"pdfjs-test/unit/font_loader_spec.js",
|
|
||||||
"pdfjs-test/unit/font_substitutions_spec.js",
|
"pdfjs-test/unit/font_substitutions_spec.js",
|
||||||
"pdfjs-test/unit/fonts_spec.js",
|
"pdfjs-test/unit/fonts_spec.js",
|
||||||
"pdfjs-test/unit/image_utils_spec.js",
|
"pdfjs-test/unit/image_utils_spec.js",
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import {
|
|||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
trimHeadersEnd,
|
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
} from "../../src/display/network_utils.js";
|
} from "../../src/display/network_utils.js";
|
||||||
import { ResponseException } from "../../src/shared/util.js";
|
import { ResponseException } from "../../src/shared/util.js";
|
||||||
@ -387,38 +386,4 @@ describe("network_utils", function () {
|
|||||||
testCreateResponseError(new URL("https://foo.com/bar.pdf"), 0, false);
|
testCreateResponseError(new URL("https://foo.com/bar.pdf"), 0, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("trimHeadersEnd", function () {
|
|
||||||
it("removes the trailing whitespace", function () {
|
|
||||||
expect(trimHeadersEnd("a: 1\r\nb: 2\r\n")).toEqual("a: 1\r\nb: 2");
|
|
||||||
expect(trimHeadersEnd("a: 1\n\n")).toEqual("a: 1");
|
|
||||||
expect(trimHeadersEnd("a: 1\t\r\n")).toEqual("a: 1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("keeps the regular spaces", function () {
|
|
||||||
expect(trimHeadersEnd("a: 1 ")).toEqual("a: 1 ");
|
|
||||||
expect(trimHeadersEnd("a: 1\r\n ")).toEqual("a: 1\r\n ");
|
|
||||||
expect(trimHeadersEnd(" ")).toEqual(" ");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles strings without trailing whitespace", function () {
|
|
||||||
expect(trimHeadersEnd("")).toEqual("");
|
|
||||||
expect(trimHeadersEnd("a: 1")).toEqual("a: 1");
|
|
||||||
expect(trimHeadersEnd("\r\na: 1")).toEqual("\r\na: 1");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("handles a long run of whitespace efficiently", function () {
|
|
||||||
// Removing the run with a `$`-anchored regex is quadratic in its length,
|
|
||||||
// and a server controls how long the headers are.
|
|
||||||
const run = "\t".repeat(100000);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
// The run is trailing, hence removed.
|
|
||||||
expect(trimHeadersEnd(`a: 1${run}`)).toEqual("a: 1");
|
|
||||||
// The run is followed by a non-whitespace, hence kept: this is the case
|
|
||||||
// which a regex has to backtrack over.
|
|
||||||
expect(trimHeadersEnd(`a: 1${run}b`)).toEqual(`a: 1${run}b`);
|
|
||||||
expect(performance.now() - startTime).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -363,80 +363,6 @@ describe("Scripting", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should trigger an event added with setAction", async () => {
|
|
||||||
const refId = getId();
|
|
||||||
const data = {
|
|
||||||
objects: {
|
|
||||||
field: [
|
|
||||||
{
|
|
||||||
id: refId,
|
|
||||||
value: "",
|
|
||||||
actions: {},
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
|
||||||
calculationOrder: [],
|
|
||||||
};
|
|
||||||
sandbox.createSandbox(data);
|
|
||||||
|
|
||||||
await myeval(
|
|
||||||
`(this.getField("field").setAction("test", 'event.source.value = "abc";'), 0)`
|
|
||||||
);
|
|
||||||
|
|
||||||
await sandbox.dispatchEventInSandbox({
|
|
||||||
id: refId,
|
|
||||||
value: "",
|
|
||||||
name: "test",
|
|
||||||
willCommit: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(send_queue.has(refId)).toBeTrue();
|
|
||||||
expect(send_queue.get(refId)).toEqual({
|
|
||||||
id: refId,
|
|
||||||
value: "abc",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should trigger an event appended with setAction", async () => {
|
|
||||||
const refId = getId();
|
|
||||||
const data = {
|
|
||||||
objects: {
|
|
||||||
field: [
|
|
||||||
{
|
|
||||||
id: refId,
|
|
||||||
value: "",
|
|
||||||
actions: {
|
|
||||||
test: [`event.source.value = "a";`],
|
|
||||||
},
|
|
||||||
type: "text",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
appInfo: { language: "en-US", platform: "Linux x86_64" },
|
|
||||||
calculationOrder: [],
|
|
||||||
};
|
|
||||||
sandbox.createSandbox(data);
|
|
||||||
|
|
||||||
await myeval(
|
|
||||||
`(this.getField("field").setAction("test", 'event.source.value += "b";'), 0)`
|
|
||||||
);
|
|
||||||
|
|
||||||
await sandbox.dispatchEventInSandbox({
|
|
||||||
id: refId,
|
|
||||||
value: "",
|
|
||||||
name: "test",
|
|
||||||
willCommit: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(send_queue.has(refId)).toBeTrue();
|
|
||||||
expect(send_queue.get(refId)).toEqual({
|
|
||||||
id: refId,
|
|
||||||
value: "ab",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should trigger a Keystroke event and invalidate it", async () => {
|
it("should trigger a Keystroke event and invalidate it", async () => {
|
||||||
const refId = getId();
|
const refId = getId();
|
||||||
const data = {
|
const data = {
|
||||||
@ -753,10 +679,10 @@ describe("Scripting", function () {
|
|||||||
it("should parse a date with a format", async () => {
|
it("should parse a date with a format", async () => {
|
||||||
const check = async (date, format, expected) => {
|
const check = async (date, format, expected) => {
|
||||||
const value = await myeval(
|
const value = await myeval(
|
||||||
`AFParseDateEx("${date}", "${format}").toISOString().replace(/T.*/, "")`
|
`AFParseDateEx("${date}", "${format}").toISOString().replace(/T.*$/, "")`
|
||||||
);
|
);
|
||||||
expect(value).toEqual(
|
expect(value).toEqual(
|
||||||
new Date(expected).toISOString().replace(/T.*/, "")
|
new Date(expected).toISOString().replace(/T.*$/, "")
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -38,9 +38,10 @@ const WASM_URL = isNodeJS
|
|||||||
|
|
||||||
class DefaultFileReaderFactory {
|
class DefaultFileReaderFactory {
|
||||||
static async fetch(params) {
|
static async fetch(params) {
|
||||||
return isNodeJS
|
if (isNodeJS) {
|
||||||
? fetchDataNode(params.path)
|
return fetchDataNode(params.path);
|
||||||
: fetchDataDOM(params.path, /* type = */ "bytes");
|
}
|
||||||
|
return fetchDataDOM(params.path, /* type = */ "bytes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -295,20 +295,10 @@ describe("Writer", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should not use scientific notation for very large numbers", async function () {
|
it("should not use scientific notation for very large numbers", async function () {
|
||||||
// JavaScript's toString() and toFixed() produce scientific notation from
|
// JavaScript produces scientific notation above ~1e21 but such values
|
||||||
// 1e21 on, which is invalid PDF: such a number must be written with all
|
// are unlikely in PDFs; values below that threshold must be plain.
|
||||||
// its digits, which are the exact ones of the underlying double.
|
|
||||||
expect(await serialize(1e10)).toEqual("10000000000");
|
expect(await serialize(1e10)).toEqual("10000000000");
|
||||||
expect(await serialize(1.5e6)).toEqual("1500000");
|
expect(await serialize(1.5e6)).toEqual("1500000");
|
||||||
expect(await serialize(1e20)).toEqual("100000000000000000000");
|
|
||||||
expect(await serialize(1e21)).toEqual("1000000000000000000000");
|
|
||||||
// Removing the trailing zeros of the exponent used to change the value:
|
|
||||||
// "1e+30" was written "1e+3" and "1e+100" was written "1e+1".
|
|
||||||
expect(await serialize(1e30)).toEqual("1000000000000000019884624838656");
|
|
||||||
expect(await serialize(-1e30)).toEqual(
|
|
||||||
"-1000000000000000019884624838656"
|
|
||||||
);
|
|
||||||
expect((await serialize(1e100)).length).toEqual(101);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should round to at most 10 decimal places", async function () {
|
it("should round to at most 10 decimal places", async function () {
|
||||||
|
|||||||
@ -109,27 +109,6 @@ describe("XML", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("character references", function () {
|
|
||||||
const parseText = xml =>
|
|
||||||
new SimpleXMLParser({}).parseFromString(xml).documentElement.textContent;
|
|
||||||
|
|
||||||
it("should resolve the valid ones", function () {
|
|
||||||
expect(parseText("<a>AB😂�</a>")).toEqual(
|
|
||||||
"AB\u{1F602}\0\u{10FFFF}"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep the invalid ones as-is", function () {
|
|
||||||
// These must not throw: `String.fromCodePoint` rejects anything which
|
|
||||||
// isn't a code point.
|
|
||||||
expect(parseText("<a>&#xZZ;</a>")).toEqual("&#xZZ;");
|
|
||||||
expect(parseText("<a>&#zz;</a>")).toEqual("&#zz;");
|
|
||||||
expect(parseText("<a>�</a>")).toEqual("�");
|
|
||||||
expect(parseText("<a>�</a>")).toEqual("�");
|
|
||||||
expect(parseText("<a>&#-1;</a>")).toEqual("&#-1;");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should parse processing instructions", function () {
|
it("should parse processing instructions", function () {
|
||||||
const xml = `
|
const xml = `
|
||||||
<a>
|
<a>
|
||||||
@ -153,33 +132,4 @@ describe("XML", function () {
|
|||||||
["foo", ""],
|
["foo", ""],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("entities", function () {
|
|
||||||
const parseText = xml =>
|
|
||||||
new SimpleXMLParser({}).parseFromString(xml).documentElement.textContent;
|
|
||||||
|
|
||||||
it("should resolve the entities", function () {
|
|
||||||
expect(
|
|
||||||
parseText("<a><b> & "c" 'd'</a>")
|
|
||||||
).toEqual(`<b> & "c" 'd'`);
|
|
||||||
expect(parseText("<a>AB</a>")).toEqual("AB");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should keep the unresolved entities as-is", function () {
|
|
||||||
expect(parseText("<a>&unknown; a&b;c</a>")).toEqual("&unknown; a&b;c");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should resolve an entity preceded by a bare ampersand", function () {
|
|
||||||
expect(parseText("<a>AT&T & Co</a>")).toEqual("AT&T & Co");
|
|
||||||
expect(parseText("<a>&&</a>")).toEqual("&&");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should handle a long run of ampersands efficiently", function () {
|
|
||||||
const text = "&".repeat(100000);
|
|
||||||
|
|
||||||
const startTime = performance.now();
|
|
||||||
expect(parseText(`<a>${text}</a>`)).toEqual(text);
|
|
||||||
expect(performance.now() - startTime).toBeLessThan(1000);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@ -21,18 +21,15 @@
|
|||||||
:root {
|
:root {
|
||||||
--editor-toolbar-vert-offset: 6px;
|
--editor-toolbar-vert-offset: 6px;
|
||||||
--outline-width: 2px;
|
--outline-width: 2px;
|
||||||
/* Palette primitives, not the semantic light-dark tokens: these are drawn on
|
--outline-color: #0060df;
|
||||||
the always-light PDF page, where --color-accent-primary and friends would
|
|
||||||
resolve to their dark value. */
|
|
||||||
--outline-color: var(--color-blue-60, #0060df);
|
|
||||||
--outline-around-width: 1px;
|
--outline-around-width: 1px;
|
||||||
--outline-around-color: var(--color-gray-20, #f0f0f4);
|
--outline-around-color: #f0f0f4;
|
||||||
--hover-outline-around-color: var(--outline-around-color);
|
--hover-outline-around-color: var(--outline-around-color);
|
||||||
--editor-selection-outline: solid var(--outline-width) var(--outline-color);
|
--focus-outline: solid var(--outline-width) var(--outline-color);
|
||||||
--unfocus-outline: solid var(--outline-width) transparent;
|
--unfocus-outline: solid var(--outline-width) transparent;
|
||||||
--focus-outline-around: solid var(--outline-around-width)
|
--focus-outline-around: solid var(--outline-around-width)
|
||||||
var(--outline-around-color);
|
var(--outline-around-color);
|
||||||
--hover-outline-color: var(--color-gray-60, #8f8f9d);
|
--hover-outline-color: #8f8f9d;
|
||||||
--hover-outline: solid var(--outline-width) var(--hover-outline-color);
|
--hover-outline: solid var(--outline-width) var(--hover-outline-color);
|
||||||
--hover-outline-around: solid var(--outline-around-width)
|
--hover-outline-around: solid var(--outline-around-width)
|
||||||
var(--hover-outline-around-color);
|
var(--hover-outline-around-color);
|
||||||
@ -98,7 +95,7 @@
|
|||||||
--outline-color: CanvasText;
|
--outline-color: CanvasText;
|
||||||
--outline-around-color: ButtonFace;
|
--outline-around-color: ButtonFace;
|
||||||
--resizer-bg-color: ButtonText;
|
--resizer-bg-color: ButtonText;
|
||||||
--hover-outline-color: var(--color-accent-primary-hover, Highlight);
|
--hover-outline-color: Highlight;
|
||||||
--hover-outline-around-color: SelectedItemText;
|
--hover-outline-around-color: SelectedItemText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -191,7 +188,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.selectedEditor {
|
&.selectedEditor {
|
||||||
border: var(--editor-selection-outline);
|
border: var(--focus-outline);
|
||||||
outline: var(--focus-outline-around);
|
outline: var(--focus-outline-around);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@ -247,51 +244,30 @@
|
|||||||
--editor-toolbar-bg-color: light-dark(#f0f0f4, #2b2a33);
|
--editor-toolbar-bg-color: light-dark(#f0f0f4, #2b2a33);
|
||||||
--editor-toolbar-highlight-image: url(images/toolbarButton-editorHighlight.svg);
|
--editor-toolbar-highlight-image: url(images/toolbarButton-editorHighlight.svg);
|
||||||
--editor-toolbar-fg-color: light-dark(#2e2e56, #fbfbfe);
|
--editor-toolbar-fg-color: light-dark(#2e2e56, #fbfbfe);
|
||||||
--editor-toolbar-border-color: var(--border-color-interactive, #8f8f9d);
|
--editor-toolbar-border-color: #8f8f9d;
|
||||||
--editor-toolbar-hover-border-color: var(--editor-toolbar-border-color);
|
--editor-toolbar-hover-border-color: var(--editor-toolbar-border-color);
|
||||||
--editor-toolbar-hover-bg-color: light-dark(#e0e0e6, #52525e);
|
--editor-toolbar-hover-bg-color: light-dark(#e0e0e6, #52525e);
|
||||||
--editor-toolbar-hover-fg-color: var(--editor-toolbar-fg-color);
|
--editor-toolbar-hover-fg-color: var(--editor-toolbar-fg-color);
|
||||||
--editor-toolbar-hover-outline: none;
|
--editor-toolbar-hover-outline: none;
|
||||||
--editor-toolbar-focus-outline-color: var(
|
--editor-toolbar-focus-outline-color: light-dark(#0060df, #0df);
|
||||||
--focus-outline-color,
|
|
||||||
light-dark(#0060df, #0df)
|
|
||||||
);
|
|
||||||
--editor-toolbar-shadow: 0 2px 6px 0 rgb(58 57 68 / 0.2);
|
--editor-toolbar-shadow: 0 2px 6px 0 rgb(58 57 68 / 0.2);
|
||||||
--editor-toolbar-height: 28px;
|
--editor-toolbar-height: 28px;
|
||||||
--editor-toolbar-padding: 2px;
|
--editor-toolbar-padding: 2px;
|
||||||
--alt-text-done-color: var(
|
--alt-text-done-color: light-dark(#2ac3a2, #54ffbd);
|
||||||
--color-accent-attention,
|
|
||||||
light-dark(#2ac3a2, #54ffbd)
|
|
||||||
);
|
|
||||||
--alt-text-warning-color: light-dark(#0090ed, #80ebff);
|
--alt-text-warning-color: light-dark(#0090ed, #80ebff);
|
||||||
--alt-text-hover-done-color: var(--alt-text-done-color);
|
--alt-text-hover-done-color: var(--alt-text-done-color);
|
||||||
--alt-text-hover-warning-color: var(--alt-text-warning-color);
|
--alt-text-hover-warning-color: var(--alt-text-warning-color);
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--editor-toolbar-bg-color: var(--button-background-color, ButtonFace);
|
--editor-toolbar-bg-color: ButtonFace;
|
||||||
--editor-toolbar-fg-color: var(--button-text-color, ButtonText);
|
--editor-toolbar-fg-color: ButtonText;
|
||||||
--editor-toolbar-border-color: var(--button-border-color, ButtonText);
|
--editor-toolbar-border-color: ButtonText;
|
||||||
--editor-toolbar-hover-border-color: var(
|
--editor-toolbar-hover-border-color: AccentColor;
|
||||||
--button-border-color-hover,
|
--editor-toolbar-hover-bg-color: ButtonFace;
|
||||||
AccentColor
|
--editor-toolbar-hover-fg-color: AccentColor;
|
||||||
);
|
|
||||||
--editor-toolbar-hover-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
ButtonFace
|
|
||||||
);
|
|
||||||
--editor-toolbar-hover-fg-color: var(
|
|
||||||
--button-text-color-hover,
|
|
||||||
AccentColor
|
|
||||||
);
|
|
||||||
--editor-toolbar-hover-outline: 2px solid
|
--editor-toolbar-hover-outline: 2px solid
|
||||||
var(--editor-toolbar-hover-border-color);
|
var(--editor-toolbar-hover-border-color);
|
||||||
/* --button-border-color, not --focus-outline-color: the ring is painted
|
--editor-toolbar-focus-outline-color: ButtonBorder;
|
||||||
on the toolbar's own ButtonFace surface, and --focus-outline-color is
|
|
||||||
CanvasText in Firefox HCM. */
|
|
||||||
--editor-toolbar-focus-outline-color: var(
|
|
||||||
--button-border-color,
|
|
||||||
ButtonBorder
|
|
||||||
);
|
|
||||||
--editor-toolbar-shadow: none;
|
--editor-toolbar-shadow: none;
|
||||||
--alt-text-done-color: var(--editor-toolbar-fg-color);
|
--alt-text-done-color: var(--editor-toolbar-fg-color);
|
||||||
--alt-text-warning-color: var(--editor-toolbar-fg-color);
|
--alt-text-warning-color: var(--editor-toolbar-fg-color);
|
||||||
@ -485,18 +461,15 @@
|
|||||||
|
|
||||||
&.show {
|
&.show {
|
||||||
--alt-text-tooltip-bg: light-dark(#f0f0f4, #1c1b22);
|
--alt-text-tooltip-bg: light-dark(#f0f0f4, #1c1b22);
|
||||||
--alt-text-tooltip-fg: var(
|
--alt-text-tooltip-fg: light-dark(#15141a, #fbfbfe);
|
||||||
--text-color,
|
--alt-text-tooltip-border: #8f8f9d;
|
||||||
light-dark(#15141a, #fbfbfe)
|
|
||||||
);
|
|
||||||
--alt-text-tooltip-border: var(--border-color-interactive, #8f8f9d);
|
|
||||||
--alt-text-tooltip-shadow: 0 2px 6px 0
|
--alt-text-tooltip-shadow: 0 2px 6px 0
|
||||||
light-dark(rgb(58 57 68 / 0.2), #15141a);
|
light-dark(rgb(58 57 68 / 0.2), #15141a);
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--alt-text-tooltip-bg: Canvas;
|
--alt-text-tooltip-bg: Canvas;
|
||||||
--alt-text-tooltip-fg: var(--text-color, CanvasText);
|
--alt-text-tooltip-fg: CanvasText;
|
||||||
--alt-text-tooltip-border: var(--border-color, CanvasText);
|
--alt-text-tooltip-border: CanvasText;
|
||||||
--alt-text-tooltip-shadow: none;
|
--alt-text-tooltip-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1082,10 +1055,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.colorPicker {
|
.colorPicker {
|
||||||
--hover-outline-color: var(
|
--hover-outline-color: light-dark(#0250bb, #80ebff);
|
||||||
--color-accent-primary-hover,
|
|
||||||
light-dark(#0250bb, #80ebff)
|
|
||||||
);
|
|
||||||
--selected-outline-color: light-dark(#0060df, #aaf2ff);
|
--selected-outline-color: light-dark(#0060df, #aaf2ff);
|
||||||
--swatch-border-color: light-dark(#cfcfd8, #52525e);
|
--swatch-border-color: light-dark(#cfcfd8, #52525e);
|
||||||
|
|
||||||
|
|||||||
15
web/app.js
15
web/app.js
@ -370,7 +370,6 @@ const PDFViewerApplication = {
|
|||||||
// Set some specific preferences for tests.
|
// Set some specific preferences for tests.
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
Object.assign(opts, {
|
Object.assign(opts, {
|
||||||
annotationEditorMode: x => parseInt(x, 10),
|
|
||||||
capCanvasAreaFactor: x => parseInt(x, 10),
|
capCanvasAreaFactor: x => parseInt(x, 10),
|
||||||
docBaseUrl: x => x,
|
docBaseUrl: x => x,
|
||||||
enableAltText: x => x === "true",
|
enableAltText: x => x === "true",
|
||||||
@ -2563,7 +2562,7 @@ const PDFViewerApplication = {
|
|||||||
this.pdfViewer.onPagesEdited(data);
|
this.pdfViewer.onPagesEdited(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
async onSavePages({ data: { pageInfos, copyLevels } }) {
|
async onSavePages({ data: extractParams }) {
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2573,10 +2572,7 @@ const PDFViewerApplication = {
|
|||||||
if (!this.pdfDocument) {
|
if (!this.pdfDocument) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const modifiedPdfBytes = await this.pdfDocument.extractPages(
|
const modifiedPdfBytes = await this.pdfDocument.extractPages(extractParams);
|
||||||
pageInfos,
|
|
||||||
copyLevels
|
|
||||||
);
|
|
||||||
if (!modifiedPdfBytes) {
|
if (!modifiedPdfBytes) {
|
||||||
console.error(
|
console.error(
|
||||||
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
||||||
@ -2590,14 +2586,11 @@ const PDFViewerApplication = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
async onSaveAndLoad({ data: { pageInfos, copyLevels } }) {
|
async onSaveAndLoad({ data: extractParams }) {
|
||||||
if (!this.pdfDocument) {
|
if (!this.pdfDocument) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const modifiedPdfBytes = await this.pdfDocument.extractPages(
|
const modifiedPdfBytes = await this.pdfDocument.extractPages(extractParams);
|
||||||
pageInfos,
|
|
||||||
copyLevels
|
|
||||||
);
|
|
||||||
if (!modifiedPdfBytes) {
|
if (!modifiedPdfBytes) {
|
||||||
console.error(
|
console.error(
|
||||||
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
"Something wrong happened when saving the edited PDF.\nPlease file a bug."
|
||||||
|
|||||||
@ -416,18 +416,6 @@ const defaultOptions = new Map([
|
|||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
|
||||||
// Whether the viewer follows the Firefox design system (see the pref-gated
|
|
||||||
// @import of tokens-brand.css in viewer.css). Read from CSS via
|
|
||||||
// -moz-pref(), not from JS, so it is Firefox-only and has no effect
|
|
||||||
// elsewhere.
|
|
||||||
"enableNova",
|
|
||||||
{
|
|
||||||
/** @type {boolean} */
|
|
||||||
value: true,
|
|
||||||
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"enableOptimizedPartialRendering",
|
"enableOptimizedPartialRendering",
|
||||||
{
|
{
|
||||||
|
|||||||
@ -136,13 +136,10 @@ class Autolinker {
|
|||||||
static #numericTLDRegex;
|
static #numericTLDRegex;
|
||||||
|
|
||||||
static findLinks(text) {
|
static findLinks(text) {
|
||||||
// Regex can be tested and verified at https://regex101.com/r/riHjvK/1.
|
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
|
||||||
// The email parts are bounded to keep the scan linear: a local part can't
|
|
||||||
// exceed 64 characters (RFC 5321, section 4.5.3.1.1) and a domain label
|
|
||||||
// can't exceed 63 (RFC 1035, section 2.3.4).
|
|
||||||
this.#regex ??=
|
this.#regex ??=
|
||||||
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
// eslint-disable-next-line regexp/no-super-linear-backtracking
|
||||||
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]{1,64}@([\S--[[\p{P}--\-]<>]]{1,63}(?:\.[\S--[[\p{P}--\-]<>]]{1,63})+)/gv;
|
/\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gv;
|
||||||
|
|
||||||
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
|
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
|
||||||
const matches = normalizedText.matchAll(this.#regex);
|
const matches = normalizedText.matchAll(this.#regex);
|
||||||
|
|||||||
247
web/buttons.css
247
web/buttons.css
@ -1,247 +0,0 @@
|
|||||||
/* Copyright 2026 Mozilla Foundation
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Shared button primitive: .primaryButton / .secondaryButton, for any button in
|
|
||||||
* the viewer and not only those inside a .dialog. The colors consume Firefox
|
|
||||||
* design tokens with the shipped literal as a fallback (see pdf_viewer.css).
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--button-primary-bg-color: var(
|
|
||||||
--button-background-color-primary,
|
|
||||||
light-dark(#0060df, #0df)
|
|
||||||
);
|
|
||||||
--button-primary-fg-color: var(
|
|
||||||
--button-text-color-primary,
|
|
||||||
light-dark(#fbfbfe, #15141a)
|
|
||||||
);
|
|
||||||
--button-primary-border-color: var(--button-primary-bg-color);
|
|
||||||
--button-primary-active-bg-color: var(
|
|
||||||
--button-background-color-primary-active,
|
|
||||||
light-dark(#054096, #aaf2ff)
|
|
||||||
);
|
|
||||||
--button-primary-active-fg-color: var(--button-primary-fg-color);
|
|
||||||
--button-primary-active-border-color: var(--button-primary-active-bg-color);
|
|
||||||
--button-primary-hover-bg-color: var(
|
|
||||||
--button-background-color-primary-hover,
|
|
||||||
light-dark(#0250bb, #80ebff)
|
|
||||||
);
|
|
||||||
--button-primary-hover-fg-color: var(--button-primary-fg-color);
|
|
||||||
--button-primary-hover-border-color: var(--button-primary-hover-bg-color);
|
|
||||||
--button-primary-disabled-bg-color: var(--button-primary-bg-color);
|
|
||||||
--button-primary-disabled-border-color: var(--button-primary-border-color);
|
|
||||||
--button-primary-disabled-fg-color: var(--button-primary-fg-color);
|
|
||||||
|
|
||||||
--button-secondary-bg-color: var(
|
|
||||||
--button-background-color,
|
|
||||||
light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07))
|
|
||||||
);
|
|
||||||
--button-secondary-fg-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
|
||||||
/* Firefox's outline-style secondary button has a transparent fill and carries
|
|
||||||
the edge on --button-border-color; the fallback matches the fill instead,
|
|
||||||
since the shipped one is a translucent overlay. */
|
|
||||||
--button-secondary-border-color: var(
|
|
||||||
--button-border-color,
|
|
||||||
var(--button-secondary-bg-color)
|
|
||||||
);
|
|
||||||
--button-secondary-active-bg-color: var(
|
|
||||||
--button-background-color-active,
|
|
||||||
light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21))
|
|
||||||
);
|
|
||||||
--button-secondary-active-fg-color: var(--button-secondary-fg-color);
|
|
||||||
--button-secondary-active-border-color: var(--button-secondary-bg-color);
|
|
||||||
--button-secondary-hover-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14))
|
|
||||||
);
|
|
||||||
--button-secondary-hover-fg-color: var(--button-secondary-fg-color);
|
|
||||||
--button-secondary-hover-border-color: var(--button-secondary-hover-bg-color);
|
|
||||||
--button-secondary-disabled-bg-color: var(--button-secondary-bg-color);
|
|
||||||
--button-secondary-disabled-border-color: var(
|
|
||||||
--button-secondary-border-color
|
|
||||||
);
|
|
||||||
--button-secondary-disabled-fg-color: var(--button-secondary-fg-color);
|
|
||||||
--button-disabled-opacity: 0.4;
|
|
||||||
--hover-filter: brightness(0.9);
|
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
--button-disabled-opacity: 0.6;
|
|
||||||
--hover-filter: brightness(1.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
|
||||||
--button-primary-bg-color: var(
|
|
||||||
--button-background-color-primary,
|
|
||||||
ButtonText
|
|
||||||
);
|
|
||||||
--button-primary-fg-color: var(--button-text-color-primary, ButtonFace);
|
|
||||||
--button-primary-border-color: var(
|
|
||||||
--button-border-color-primary,
|
|
||||||
ButtonText
|
|
||||||
);
|
|
||||||
--button-primary-active-bg-color: var(
|
|
||||||
--button-background-color-primary-active,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-primary-active-fg-color: var(
|
|
||||||
--button-text-color-primary-active,
|
|
||||||
HighlightText
|
|
||||||
);
|
|
||||||
--button-primary-active-border-color: var(
|
|
||||||
--button-border-color-primary-active,
|
|
||||||
ButtonText
|
|
||||||
);
|
|
||||||
--button-primary-hover-bg-color: var(
|
|
||||||
--button-background-color-primary-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-primary-hover-fg-color: var(
|
|
||||||
--button-text-color-primary-hover,
|
|
||||||
HighlightText
|
|
||||||
);
|
|
||||||
--button-primary-hover-border-color: var(
|
|
||||||
--button-border-color-primary-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-primary-disabled-bg-color: var(
|
|
||||||
--button-background-color-primary-disabled,
|
|
||||||
GrayText
|
|
||||||
);
|
|
||||||
--button-primary-disabled-fg-color: var(
|
|
||||||
--button-text-color-primary-disabled,
|
|
||||||
ButtonFace
|
|
||||||
);
|
|
||||||
--button-primary-disabled-border-color: var(
|
|
||||||
--button-border-color-primary-disabled,
|
|
||||||
GrayText
|
|
||||||
);
|
|
||||||
|
|
||||||
--button-secondary-bg-color: var(--button-background-color, ButtonFace);
|
|
||||||
--button-secondary-fg-color: var(--button-text-color, ButtonText);
|
|
||||||
--button-secondary-border-color: var(--button-border-color, ButtonText);
|
|
||||||
--button-secondary-active-bg-color: var(
|
|
||||||
--button-background-color-active,
|
|
||||||
HighlightText
|
|
||||||
);
|
|
||||||
--button-secondary-active-fg-color: var(
|
|
||||||
--button-text-color-active,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-secondary-active-border-color: var(
|
|
||||||
--button-border-color-active,
|
|
||||||
ButtonText
|
|
||||||
);
|
|
||||||
--button-secondary-hover-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
HighlightText
|
|
||||||
);
|
|
||||||
--button-secondary-hover-fg-color: var(
|
|
||||||
--button-text-color-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-secondary-hover-border-color: var(
|
|
||||||
--button-border-color-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--button-secondary-disabled-fg-color: var(
|
|
||||||
--button-text-color-disabled,
|
|
||||||
GrayText
|
|
||||||
);
|
|
||||||
--button-secondary-disabled-border-color: var(
|
|
||||||
--button-border-color-disabled,
|
|
||||||
GrayText
|
|
||||||
);
|
|
||||||
--button-disabled-opacity: 1;
|
|
||||||
--hover-filter: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.primaryButton,
|
|
||||||
.secondaryButton {
|
|
||||||
border-radius: var(--button-border-radius, 4px);
|
|
||||||
border: 1px solid;
|
|
||||||
font: menu;
|
|
||||||
font-weight: var(--button-font-weight, 590);
|
|
||||||
font-size: var(--button-font-size, 13px);
|
|
||||||
padding: var(--button-padding, 4px 16px);
|
|
||||||
width: auto;
|
|
||||||
min-height: var(--button-min-height, 32px);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
filter: var(--hover-filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
color: inherit;
|
|
||||||
font: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.primaryButton {
|
|
||||||
color: var(--button-primary-fg-color);
|
|
||||||
background-color: var(--button-primary-bg-color);
|
|
||||||
border-color: var(--button-primary-border-color);
|
|
||||||
opacity: 1;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--button-primary-hover-fg-color);
|
|
||||||
background-color: var(--button-primary-hover-bg-color);
|
|
||||||
border-color: var(--button-primary-hover-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: var(--button-primary-active-fg-color);
|
|
||||||
background-color: var(--button-primary-active-bg-color);
|
|
||||||
border-color: var(--button-primary-active-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background-color: var(--button-primary-disabled-bg-color);
|
|
||||||
border-color: var(--button-primary-disabled-border-color);
|
|
||||||
color: var(--button-primary-disabled-fg-color);
|
|
||||||
opacity: var(--button-disabled-opacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondaryButton {
|
|
||||||
color: var(--button-secondary-fg-color);
|
|
||||||
background-color: var(--button-secondary-bg-color);
|
|
||||||
border-color: var(--button-secondary-border-color);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--button-secondary-hover-fg-color);
|
|
||||||
background-color: var(--button-secondary-hover-bg-color);
|
|
||||||
border-color: var(--button-secondary-hover-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active {
|
|
||||||
color: var(--button-secondary-active-fg-color);
|
|
||||||
background-color: var(--button-secondary-active-bg-color);
|
|
||||||
border-color: var(--button-secondary-active-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
background-color: var(--button-secondary-disabled-bg-color);
|
|
||||||
border-color: var(--button-secondary-disabled-border-color);
|
|
||||||
color: var(--button-secondary-disabled-fg-color);
|
|
||||||
opacity: var(--button-disabled-opacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -65,86 +65,39 @@
|
|||||||
:is(.annotationLayer, .annotationEditorLayer) {
|
:is(.annotationLayer, .annotationEditorLayer) {
|
||||||
.annotationCommentButton {
|
.annotationCommentButton {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
--comment-button-bg: var(
|
--comment-button-bg: light-dark(white, #1c1b22);
|
||||||
--background-color-canvas,
|
|
||||||
light-dark(white, #1c1b22)
|
|
||||||
);
|
|
||||||
--comment-button-fg: light-dark(#5b5b66, #fbfbfe);
|
--comment-button-fg: light-dark(#5b5b66, #fbfbfe);
|
||||||
--comment-button-active-bg: light-dark(#0041a4, #a6ecf4);
|
--comment-button-active-bg: light-dark(#0041a4, #a6ecf4);
|
||||||
--comment-button-active-fg: var(
|
--comment-button-active-fg: light-dark(white, #15141a);
|
||||||
--button-text-color-primary,
|
--comment-button-hover-bg: light-dark(#0053cb, #61dce9);
|
||||||
light-dark(white, #15141a)
|
--comment-button-hover-fg: light-dark(white, #15141a);
|
||||||
);
|
--comment-button-selected-bg: light-dark(#0062fa, #00cadb);
|
||||||
--comment-button-hover-bg: var(
|
--comment-button-border-color: light-dark(#8f8f9d, #bfbfc9);
|
||||||
--color-accent-primary-hover,
|
|
||||||
light-dark(#0053cb, #61dce9)
|
|
||||||
);
|
|
||||||
--comment-button-hover-fg: var(
|
|
||||||
--button-text-color-primary,
|
|
||||||
light-dark(white, #15141a)
|
|
||||||
);
|
|
||||||
--comment-button-selected-bg: var(
|
|
||||||
--color-accent-primary,
|
|
||||||
light-dark(#0062fa, #00cadb)
|
|
||||||
);
|
|
||||||
--comment-button-border-color: var(
|
|
||||||
--border-color-interactive,
|
|
||||||
light-dark(#8f8f9d, #bfbfc9)
|
|
||||||
);
|
|
||||||
--comment-button-active-border-color: var(--comment-button-active-bg);
|
--comment-button-active-border-color: var(--comment-button-active-bg);
|
||||||
--comment-button-focus-border-color: light-dark(#cfcfd8, #3a3944);
|
--comment-button-focus-border-color: light-dark(#cfcfd8, #3a3944);
|
||||||
--comment-button-hover-border-color: var(--comment-button-hover-bg);
|
--comment-button-hover-border-color: var(--comment-button-hover-bg);
|
||||||
--comment-button-selected-border-color: var(--comment-button-selected-bg);
|
--comment-button-selected-border-color: var(--comment-button-selected-bg);
|
||||||
--comment-button-selected-fg: var(
|
--comment-button-selected-fg: light-dark(white, #15141a);
|
||||||
--button-text-color-primary,
|
|
||||||
light-dark(white, #15141a)
|
|
||||||
);
|
|
||||||
--comment-button-dim: 24px;
|
--comment-button-dim: 24px;
|
||||||
--comment-button-box-shadow:
|
--comment-button-box-shadow:
|
||||||
0 0.25px 0.75px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
0 0.25px 0.75px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||||
0 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
0 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||||
--comment-button-focus-outline-color: var(
|
--comment-button-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||||
--focus-outline-color,
|
|
||||||
light-dark(#0062fa, #00cadb)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Each state reads the *matching* pair of Firefox button tokens, so the
|
|
||||||
background and the icon can never resolve to the same system colour. */
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--comment-button-bg: var(--button-background-color, ButtonFace);
|
--comment-button-bg: ButtonFace;
|
||||||
--comment-button-fg: var(--button-text-color, ButtonText);
|
--comment-button-fg: ButtonText;
|
||||||
--comment-button-hover-bg: var(
|
--comment-button-hover-bg: SelectedItemText;
|
||||||
--button-background-color-hover,
|
--comment-button-hover-fg: SelectedItem;
|
||||||
SelectedItemText
|
--comment-button-active-bg: SelectedItemText;
|
||||||
);
|
--comment-button-active-fg: SelectedItem;
|
||||||
--comment-button-hover-fg: var(--button-text-color-hover, SelectedItem);
|
--comment-button-border-color: ButtonBorder;
|
||||||
--comment-button-active-bg: var(
|
--comment-button-active-border-color: ButtonBorder;
|
||||||
--button-background-color-active,
|
--comment-button-hover-border-color: SelectedItem;
|
||||||
SelectedItemText
|
|
||||||
);
|
|
||||||
--comment-button-active-fg: var(--button-text-color-active, SelectedItem);
|
|
||||||
--comment-button-border-color: var(--button-border-color, ButtonBorder);
|
|
||||||
--comment-button-active-border-color: var(
|
|
||||||
--button-border-color-active,
|
|
||||||
ButtonBorder
|
|
||||||
);
|
|
||||||
--comment-button-hover-border-color: var(
|
|
||||||
--button-border-color-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
--comment-button-box-shadow: none;
|
--comment-button-box-shadow: none;
|
||||||
--comment-button-focus-outline-color: var(
|
--comment-button-focus-outline-color: CanvasText;
|
||||||
--focus-outline-color,
|
--comment-button-selected-bg: ButtonBorder;
|
||||||
CanvasText
|
--comment-button-selected-fg: ButtonFace;
|
||||||
);
|
|
||||||
--comment-button-selected-bg: var(
|
|
||||||
--button-background-color-primary,
|
|
||||||
ButtonBorder
|
|
||||||
);
|
|
||||||
--comment-button-selected-fg: var(
|
|
||||||
--button-text-color-primary,
|
|
||||||
ButtonFace
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -232,28 +185,19 @@
|
|||||||
--comment-active-brightness: 0.825;
|
--comment-active-brightness: 0.825;
|
||||||
--comment-active-filter: brightness(var(--comment-active-brightness));
|
--comment-active-filter: brightness(var(--comment-active-brightness));
|
||||||
--comment-border-color: light-dark(#f0f0f4, #52525e);
|
--comment-border-color: light-dark(#f0f0f4, #52525e);
|
||||||
--comment-focus-outline-color: var(
|
--comment-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||||
--focus-outline-color,
|
--comment-fg-color: light-dark(#15141a, #fbfbfe);
|
||||||
light-dark(#0062fa, #00cadb)
|
|
||||||
);
|
|
||||||
--comment-fg-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
|
||||||
--comment-count-bg-color: light-dark(#e2f7ff, #00317e);
|
--comment-count-bg-color: light-dark(#e2f7ff, #00317e);
|
||||||
--comment-indicator-active-fg-color: light-dark(#0041a4, #a6ecf4);
|
--comment-indicator-active-fg-color: light-dark(#0041a4, #a6ecf4);
|
||||||
--comment-indicator-active-filter: brightness(
|
--comment-indicator-active-filter: brightness(
|
||||||
calc(1 / var(--comment-active-brightness))
|
calc(1 / var(--comment-active-brightness))
|
||||||
);
|
);
|
||||||
--comment-indicator-focus-fg-color: light-dark(#5b5b66, #fbfbfe);
|
--comment-indicator-focus-fg-color: light-dark(#5b5b66, #fbfbfe);
|
||||||
--comment-indicator-hover-fg-color: var(
|
--comment-indicator-hover-fg-color: light-dark(#0053cb, #61dce9);
|
||||||
--color-accent-primary-hover,
|
|
||||||
light-dark(#0053cb, #61dce9)
|
|
||||||
);
|
|
||||||
--comment-indicator-hover-filter: brightness(
|
--comment-indicator-hover-filter: brightness(
|
||||||
calc(1 / var(--comment-hover-brightness))
|
calc(1 / var(--comment-hover-brightness))
|
||||||
);
|
);
|
||||||
--comment-indicator-selected-fg-color: var(
|
--comment-indicator-selected-fg-color: light-dark(#0062fa, #00cadb);
|
||||||
--color-accent-primary,
|
|
||||||
light-dark(#0062fa, #00cadb)
|
|
||||||
);
|
|
||||||
|
|
||||||
--button-comment-bg: transparent;
|
--button-comment-bg: transparent;
|
||||||
--button-comment-color: var(--main-color);
|
--button-comment-color: var(--main-color);
|
||||||
@ -264,6 +208,9 @@
|
|||||||
--button-comment-hover-bg: light-dark(#e0e0e6, #52525e);
|
--button-comment-hover-bg: light-dark(#e0e0e6, #52525e);
|
||||||
--button-comment-hover-color: var(--button-comment-color);
|
--button-comment-hover-color: var(--button-comment-color);
|
||||||
|
|
||||||
|
--link-fg-color: light-dark(#0060df, #0df);
|
||||||
|
--link-hover-fg-color: light-dark(#0250bb, #80ebff);
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--comment-date-fg-color: CanvasText;
|
--comment-date-fg-color: CanvasText;
|
||||||
--comment-bg-color: Canvas;
|
--comment-bg-color: Canvas;
|
||||||
@ -272,13 +219,11 @@
|
|||||||
--comment-active-bg-color: Canvas;
|
--comment-active-bg-color: Canvas;
|
||||||
--comment-active-filter: none;
|
--comment-active-filter: none;
|
||||||
--comment-border-color: CanvasText;
|
--comment-border-color: CanvasText;
|
||||||
--comment-fg-color: var(--text-color, CanvasText);
|
--comment-fg-color: CanvasText;
|
||||||
--comment-count-bg-color: Canvas;
|
--comment-count-bg-color: Canvas;
|
||||||
--comment-indicator-active-fg-color: SelectedItem;
|
--comment-indicator-active-fg-color: SelectedItem;
|
||||||
--comment-indicator-focus-fg-color: var(--text-color, CanvasText);
|
--comment-indicator-focus-fg-color: CanvasText;
|
||||||
/* On the Canvas comment surface, so paired with --text-color rather than a
|
--comment-indicator-hover-fg-color: CanvasText;
|
||||||
button/accent token. */
|
|
||||||
--comment-indicator-hover-fg-color: var(--text-color, CanvasText);
|
|
||||||
--comment-indicator-selected-fg-color: SelectedItem;
|
--comment-indicator-selected-fg-color: SelectedItem;
|
||||||
--button-comment-bg: ButtonFace;
|
--button-comment-bg: ButtonFace;
|
||||||
--button-comment-color: ButtonText;
|
--button-comment-color: ButtonText;
|
||||||
@ -287,6 +232,8 @@
|
|||||||
--button-comment-border: 1px solid ButtonText;
|
--button-comment-border: 1px solid ButtonText;
|
||||||
--button-comment-hover-bg: Highlight;
|
--button-comment-hover-bg: Highlight;
|
||||||
--button-comment-hover-color: HighlightText;
|
--button-comment-hover-color: HighlightText;
|
||||||
|
--link-fg-color: LinkText;
|
||||||
|
--link-hover-fg-color: LinkText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
206
web/dialog.css
206
web/dialog.css
@ -14,42 +14,117 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
--dialog-bg-color: var(--background-color-canvas, light-dark(white, #1c1b22));
|
--dialog-bg-color: light-dark(white, #1c1b22);
|
||||||
--dialog-border-color: var(
|
--dialog-border-color: light-dark(white, #1c1b22);
|
||||||
--background-color-canvas,
|
|
||||||
light-dark(white, #1c1b22)
|
|
||||||
);
|
|
||||||
--dialog-shadow: 0 2px 14px 0 light-dark(rgb(58 57 68 / 0.2), #15141a);
|
--dialog-shadow: 0 2px 14px 0 light-dark(rgb(58 57 68 / 0.2), #15141a);
|
||||||
|
--text-primary-color: light-dark(#15141a, #fbfbfe);
|
||||||
--text-secondary-color: light-dark(#5b5b66, #cfcfd8);
|
--text-secondary-color: light-dark(#5b5b66, #cfcfd8);
|
||||||
|
--hover-filter: brightness(0.9);
|
||||||
|
--link-fg-color: light-dark(#0060df, #0df);
|
||||||
|
--link-hover-fg-color: light-dark(#0250bb, #80ebff);
|
||||||
--separator-color: light-dark(#f0f0f4, #52525e);
|
--separator-color: light-dark(#f0f0f4, #52525e);
|
||||||
|
|
||||||
/* A textarea is an interactive control, so it takes the interactive border
|
--textarea-border-color: #8f8f9d;
|
||||||
token (whose light value is the shipped literal) and not --border-color,
|
|
||||||
which is the low-contrast decorative one. */
|
|
||||||
--textarea-border-color: var(--border-color-interactive, #8f8f9d);
|
|
||||||
--textarea-bg-color: light-dark(white, #42414d);
|
--textarea-bg-color: light-dark(white, #42414d);
|
||||||
--textarea-fg-color: var(--text-secondary-color);
|
--textarea-fg-color: var(--text-secondary-color);
|
||||||
|
|
||||||
/* .primaryButton / .secondaryButton button tokens are defined once, at :root
|
--radio-bg-color: light-dark(#f0f0f4, #2b2a33);
|
||||||
in buttons.css (the shared primitive), and inherit into dialogs. */
|
--radio-checked-bg-color: light-dark(#fbfbfe, #15141a);
|
||||||
|
--radio-border-color: #8f8f9d;
|
||||||
|
--radio-checked-border-color: light-dark(#0060df, #0df);
|
||||||
|
|
||||||
|
--button-secondary-bg-color: light-dark(
|
||||||
|
rgb(21 20 26 / 0.07),
|
||||||
|
rgb(251 251 254 / 0.07)
|
||||||
|
);
|
||||||
|
--button-secondary-fg-color: var(--text-primary-color);
|
||||||
|
--button-secondary-border-color: var(--button-secondary-bg-color);
|
||||||
|
--button-secondary-active-bg-color: light-dark(
|
||||||
|
rgb(21 20 26 / 0.21),
|
||||||
|
rgb(251 251 254 / 0.21)
|
||||||
|
);
|
||||||
|
--button-secondary-active-fg-color: var(--button-secondary-fg-color);
|
||||||
|
--button-secondary-active-border-color: var(--button-secondary-bg-color);
|
||||||
|
--button-secondary-hover-bg-color: light-dark(
|
||||||
|
rgb(21 20 26 / 0.14),
|
||||||
|
rgb(251 251 254 / 0.14)
|
||||||
|
);
|
||||||
|
--button-secondary-hover-fg-color: var(--button-secondary-fg-color);
|
||||||
|
--button-secondary-hover-border-color: var(--button-secondary-hover-bg-color);
|
||||||
|
--button-secondary-disabled-bg-color: var(--button-secondary-bg-color);
|
||||||
|
--button-secondary-disabled-border-color: var(
|
||||||
|
--button-secondary-border-color
|
||||||
|
);
|
||||||
|
--button-secondary-disabled-fg-color: var(--button-secondary-fg-color);
|
||||||
|
|
||||||
|
--button-primary-bg-color: light-dark(#0060df, #0df);
|
||||||
|
--button-primary-fg-color: light-dark(#fbfbfe, #15141a);
|
||||||
|
--button-primary-border-color: var(--button-primary-bg-color);
|
||||||
|
--button-primary-active-bg-color: light-dark(#054096, #aaf2ff);
|
||||||
|
--button-primary-active-fg-color: var(--button-primary-fg-color);
|
||||||
|
--button-primary-active-border-color: var(--button-primary-active-bg-color);
|
||||||
|
--button-primary-hover-bg-color: light-dark(#0250bb, #80ebff);
|
||||||
|
--button-primary-hover-fg-color: var(--button-primary-fg-color);
|
||||||
|
--button-primary-hover-border-color: var(--button-primary-hover-bg-color);
|
||||||
|
--button-primary-disabled-bg-color: var(--button-primary-bg-color);
|
||||||
|
--button-primary-disabled-border-color: var(--button-primary-border-color);
|
||||||
|
--button-primary-disabled-fg-color: var(--button-primary-fg-color);
|
||||||
|
--button-disabled-opacity: 0.4;
|
||||||
|
|
||||||
--input-text-bg-color: light-dark(white, #42414d);
|
--input-text-bg-color: light-dark(white, #42414d);
|
||||||
--input-text-fg-color: var(--text-primary-color);
|
--input-text-fg-color: var(--text-primary-color);
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
--hover-filter: brightness(1.4);
|
||||||
|
--button-disabled-opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--dialog-bg-color: var(--background-color-canvas, Canvas);
|
--dialog-bg-color: Canvas;
|
||||||
/* border-only: --background-color-canvas would resolve to Canvas in FF HCM
|
--dialog-border-color: CanvasText;
|
||||||
(= the dialog bg) and the shadow is none here, leaving no boundary; route
|
|
||||||
through --border-color (CanvasText in HCM) to keep a visible border. */
|
|
||||||
--dialog-border-color: var(--border-color, CanvasText);
|
|
||||||
--dialog-shadow: none;
|
--dialog-shadow: none;
|
||||||
|
--text-primary-color: CanvasText;
|
||||||
--text-secondary-color: CanvasText;
|
--text-secondary-color: CanvasText;
|
||||||
|
--hover-filter: none;
|
||||||
|
--link-fg-color: LinkText;
|
||||||
|
--link-hover-fg-color: LinkText;
|
||||||
--separator-color: CanvasText;
|
--separator-color: CanvasText;
|
||||||
|
|
||||||
--textarea-border-color: ButtonBorder;
|
--textarea-border-color: ButtonBorder;
|
||||||
--textarea-bg-color: Field;
|
--textarea-bg-color: Field;
|
||||||
--textarea-fg-color: ButtonText;
|
--textarea-fg-color: ButtonText;
|
||||||
|
|
||||||
|
--radio-bg-color: ButtonFace;
|
||||||
|
--radio-checked-bg-color: ButtonFace;
|
||||||
|
--radio-border-color: ButtonText;
|
||||||
|
--radio-checked-border-color: ButtonText;
|
||||||
|
|
||||||
|
--button-secondary-bg-color: ButtonFace;
|
||||||
|
--button-secondary-fg-color: ButtonText;
|
||||||
|
--button-secondary-border-color: ButtonText;
|
||||||
|
--button-secondary-active-bg-color: HighlightText;
|
||||||
|
--button-secondary-active-fg-color: SelectedItem;
|
||||||
|
--button-secondary-active-border-color: ButtonText;
|
||||||
|
--button-secondary-hover-bg-color: HighlightText;
|
||||||
|
--button-secondary-hover-fg-color: SelectedItem;
|
||||||
|
--button-secondary-hover-border-color: SelectedItem;
|
||||||
|
--button-secondary-disabled-fg-color: GrayText;
|
||||||
|
--button-secondary-disabled-border-color: GrayText;
|
||||||
|
|
||||||
|
--button-primary-bg-color: ButtonText;
|
||||||
|
--button-primary-fg-color: ButtonFace;
|
||||||
|
--button-primary-border-color: ButtonText;
|
||||||
|
--button-primary-active-bg-color: SelectedItem;
|
||||||
|
--button-primary-active-fg-color: HighlightText;
|
||||||
|
--button-primary-active-border-color: ButtonText;
|
||||||
|
--button-primary-hover-bg-color: SelectedItem;
|
||||||
|
--button-primary-hover-fg-color: HighlightText;
|
||||||
|
--button-primary-hover-border-color: SelectedItem;
|
||||||
|
--button-primary-disabled-bg-color: GrayText;
|
||||||
|
--button-primary-disabled-fg-color: ButtonFace;
|
||||||
|
--button-primary-disabled-border-color: GrayText;
|
||||||
|
--button-disabled-opacity: 1;
|
||||||
|
|
||||||
--input-text-bg-color: Field;
|
--input-text-bg-color: Field;
|
||||||
--input-text-fg-color: FieldText;
|
--input-text-fg-color: FieldText;
|
||||||
}
|
}
|
||||||
@ -58,7 +133,7 @@
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 150%;
|
line-height: 150%;
|
||||||
border-radius: var(--border-radius-medium, 4px);
|
border-radius: 4px;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border: 1px solid var(--dialog-border-color);
|
border: 1px solid var(--dialog-border-color);
|
||||||
background: var(--dialog-bg-color);
|
background: var(--dialog-bg-color);
|
||||||
@ -101,15 +176,6 @@
|
|||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Native checkbox/radio tinted with the accent at Firefox's control size,
|
|
||||||
matching Firefox's default form controls (the custom appearance is
|
|
||||||
Nova-only in Firefox, so the default look is native + accent-color). */
|
|
||||||
input:is([type="checkbox"], [type="radio"]) {
|
|
||||||
accent-color: var(--color-accent-primary, light-dark(#0060df, #0df));
|
|
||||||
width: var(--checkbox-size, 16px);
|
|
||||||
height: var(--checkbox-size, 16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio {
|
.radio {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -121,6 +187,25 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
input {
|
||||||
|
appearance: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--radio-bg-color);
|
||||||
|
border: 1px solid var(--radio-border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: var(--hover-filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: var(--radio-checked-bg-color);
|
||||||
|
border: 4px solid var(--radio-checked-border-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> .radioLabel {
|
> .radioLabel {
|
||||||
@ -139,15 +224,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
button:not(:is(.toggle-button, .closeButton, .clearInputButton)) {
|
button:not(:is(.toggle-button, .closeButton, .clearInputButton)) {
|
||||||
border-radius: var(--button-border-radius, 4px);
|
border-radius: 4px;
|
||||||
border-width: 1px;
|
border: 1px solid;
|
||||||
border-style: solid;
|
|
||||||
font: menu;
|
font: menu;
|
||||||
font-weight: var(--button-font-weight, 590);
|
font-weight: 590;
|
||||||
font-size: var(--button-font-size, 13px);
|
font-size: 13px;
|
||||||
padding: var(--button-padding, 4px 16px);
|
padding: 4px 16px;
|
||||||
width: auto;
|
width: auto;
|
||||||
min-height: var(--button-min-height, 32px);
|
height: 32px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -159,9 +243,57 @@
|
|||||||
font: inherit;
|
font: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .primaryButton / .secondaryButton colours and states come from the
|
&.secondaryButton {
|
||||||
shared buttons.css primitive; only the dialog-specific base geometry
|
color: var(--button-secondary-fg-color);
|
||||||
(above) lives here, so it also covers unclassed dialog buttons. */
|
background-color: var(--button-secondary-bg-color);
|
||||||
|
border-color: var(--button-secondary-border-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--button-secondary-hover-fg-color);
|
||||||
|
background-color: var(--button-secondary-hover-bg-color);
|
||||||
|
border-color: var(--button-secondary-hover-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--button-secondary-active-fg-color);
|
||||||
|
background-color: var(--button-secondary-active-bg-color);
|
||||||
|
border-color: var(--button-secondary-active-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: var(--button-secondary-disabled-bg-color);
|
||||||
|
border-color: var(--button-secondary-disabled-border-color);
|
||||||
|
color: var(--button-secondary-disabled-fg-color);
|
||||||
|
opacity: var(--button-disabled-opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.primaryButton {
|
||||||
|
color: var(--button-primary-fg-color);
|
||||||
|
background-color: var(--button-primary-bg-color);
|
||||||
|
border-color: var(--button-primary-border-color);
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--button-primary-hover-fg-color);
|
||||||
|
background-color: var(--button-primary-hover-bg-color);
|
||||||
|
border-color: var(--button-primary-hover-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: var(--button-primary-active-fg-color);
|
||||||
|
background-color: var(--button-primary-active-bg-color);
|
||||||
|
border-color: var(--button-primary-active-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: var(--button-primary-disabled-bg-color);
|
||||||
|
border-color: var(--button-primary-disabled-border-color);
|
||||||
|
color: var(--button-primary-disabled-fg-color);
|
||||||
|
opacity: var(--button-disabled-opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@ -181,7 +313,7 @@
|
|||||||
resize: none;
|
resize: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: var(--border-radius-small, 4px);
|
border-radius: 4px;
|
||||||
border: 1px solid var(--textarea-border-color);
|
border: 1px solid var(--textarea-border-color);
|
||||||
background: var(--textarea-bg-color);
|
background: var(--textarea-bg-color);
|
||||||
color: var(--textarea-fg-color);
|
color: var(--textarea-fg-color);
|
||||||
|
|||||||
@ -28,28 +28,16 @@
|
|||||||
--sig-detail-color: light-dark(rgb(96 96 96), rgb(180 180 184));
|
--sig-detail-color: light-dark(rgb(96 96 96), rgb(180 180 184));
|
||||||
--sig-divider-color: light-dark(rgb(228 228 232), rgb(82 82 86));
|
--sig-divider-color: light-dark(rgb(228 228 232), rgb(82 82 86));
|
||||||
--sig-summary-hover-color: light-dark(rgb(28 67 138), rgb(126 169 255));
|
--sig-summary-hover-color: light-dark(rgb(28 67 138), rgb(126 169 255));
|
||||||
--sig-link-color: var(
|
--sig-link-color: light-dark(rgb(28 113 216), rgb(126 169 255));
|
||||||
--link-color,
|
|
||||||
light-dark(rgb(28 113 216), rgb(126 169 255))
|
|
||||||
);
|
|
||||||
--sig-link-hover-bg: light-dark(
|
--sig-link-hover-bg: light-dark(
|
||||||
rgb(28 113 216 / 0.1),
|
rgb(28 113 216 / 0.1),
|
||||||
rgb(126 169 255 / 0.15)
|
rgb(126 169 255 / 0.15)
|
||||||
);
|
);
|
||||||
--sig-banner-verified-bg: var(
|
--sig-banner-verified-bg: light-dark(rgb(228 247 235), rgb(28 84 49));
|
||||||
--background-color-success,
|
|
||||||
light-dark(rgb(228 247 235), rgb(28 84 49))
|
|
||||||
);
|
|
||||||
--sig-banner-verified-color: light-dark(rgb(16 92 47), rgb(176 232 196));
|
--sig-banner-verified-color: light-dark(rgb(16 92 47), rgb(176 232 196));
|
||||||
--sig-banner-warn-bg: var(
|
--sig-banner-warn-bg: light-dark(rgb(255 247 217), rgb(95 67 9));
|
||||||
--background-color-warning,
|
|
||||||
light-dark(rgb(255 247 217), rgb(95 67 9))
|
|
||||||
);
|
|
||||||
--sig-banner-warn-color: light-dark(rgb(124 84 9), rgb(255 222 153));
|
--sig-banner-warn-color: light-dark(rgb(124 84 9), rgb(255 222 153));
|
||||||
--sig-banner-error-bg: var(
|
--sig-banner-error-bg: light-dark(rgb(254 226 235), rgb(122 21 51));
|
||||||
--background-color-critical,
|
|
||||||
light-dark(rgb(254 226 235), rgb(122 21 51))
|
|
||||||
);
|
|
||||||
--sig-banner-error-color: light-dark(rgb(167 26 70), rgb(255 188 207));
|
--sig-banner-error-color: light-dark(rgb(167 26 70), rgb(255 188 207));
|
||||||
|
|
||||||
/* Tint colours for the row / toolbar icons. These are paired with
|
/* Tint colours for the row / toolbar icons. These are paired with
|
||||||
@ -60,18 +48,9 @@
|
|||||||
* verified = green (only used for the top-level "everything fine"
|
* verified = green (only used for the top-level "everything fine"
|
||||||
* row and the toolbar's verified badge). */
|
* row and the toolbar's verified badge). */
|
||||||
--sig-icon-default: light-dark(rgb(150 150 150), rgb(180 180 184));
|
--sig-icon-default: light-dark(rgb(150 150 150), rgb(180 180 184));
|
||||||
--sig-icon-warn: var(
|
--sig-icon-warn: light-dark(rgb(217 142 27), rgb(255 178 77));
|
||||||
--icon-color-warning,
|
--sig-icon-error: light-dark(rgb(196 31 71), rgb(255 117 145));
|
||||||
light-dark(rgb(217 142 27), rgb(255 178 77))
|
--sig-icon-verified: light-dark(rgb(29 142 61), rgb(106 210 126));
|
||||||
);
|
|
||||||
--sig-icon-error: var(
|
|
||||||
--icon-color-critical,
|
|
||||||
light-dark(rgb(196 31 71), rgb(255 117 145))
|
|
||||||
);
|
|
||||||
--sig-icon-verified: var(
|
|
||||||
--icon-color-success,
|
|
||||||
light-dark(rgb(29 142 61), rgb(106 210 126))
|
|
||||||
);
|
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
/* HCM keywords are picked by *semantic role*, not by hue — the
|
/* HCM keywords are picked by *semantic role*, not by hue — the
|
||||||
@ -98,26 +77,23 @@
|
|||||||
--sig-detail-color: GrayText;
|
--sig-detail-color: GrayText;
|
||||||
--sig-divider-color: GrayText;
|
--sig-divider-color: GrayText;
|
||||||
--sig-summary-hover-color: AccentColor;
|
--sig-summary-hover-color: AccentColor;
|
||||||
--sig-link-color: var(--link-color, LinkText);
|
--sig-link-color: LinkText;
|
||||||
--sig-link-hover-bg: transparent;
|
--sig-link-hover-bg: transparent;
|
||||||
/* Firefox collapses the status backgrounds onto --background-color-canvas
|
--sig-banner-verified-bg: ButtonFace;
|
||||||
in HCM, so the foregrounds have to follow --text-color to stay a pair
|
--sig-banner-verified-color: ButtonText;
|
||||||
(ButtonText over that Canvas surface is not guaranteed to contrast). */
|
--sig-banner-warn-bg: ButtonFace;
|
||||||
--sig-banner-verified-bg: var(--background-color-success, ButtonFace);
|
--sig-banner-warn-color: ButtonText;
|
||||||
--sig-banner-verified-color: var(--text-color, ButtonText);
|
--sig-banner-error-bg: ButtonFace;
|
||||||
--sig-banner-warn-bg: var(--background-color-warning, ButtonFace);
|
--sig-banner-error-color: ButtonText;
|
||||||
--sig-banner-warn-color: var(--text-color, ButtonText);
|
|
||||||
--sig-banner-error-bg: var(--background-color-critical, ButtonFace);
|
|
||||||
--sig-banner-error-color: var(--text-color, ButtonText);
|
|
||||||
/* Severities collapse to the same emphasis keyword (AccentColor)
|
/* Severities collapse to the same emphasis keyword (AccentColor)
|
||||||
* in HCM — same trick as alt-text where `done` and `warning`
|
* in HCM — same trick as alt-text where `done` and `warning`
|
||||||
* share their hover colour. The glyph shape (check vs `!` vs `✕`)
|
* share their hover colour. The glyph shape (check vs `!` vs `✕`)
|
||||||
* carries the remaining distinction. The neutral "row crypto
|
* carries the remaining distinction. The neutral "row crypto
|
||||||
* verified" check stays muted (GrayText). */
|
* verified" check stays muted (GrayText). */
|
||||||
--sig-icon-default: GrayText;
|
--sig-icon-default: GrayText;
|
||||||
--sig-icon-warn: var(--icon-color-warning, AccentColor);
|
--sig-icon-warn: AccentColor;
|
||||||
--sig-icon-error: var(--icon-color-critical, AccentColor);
|
--sig-icon-error: AccentColor;
|
||||||
--sig-icon-verified: var(--icon-color-success, AccentColor);
|
--sig-icon-verified: AccentColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { INTERNAL_EVT, internalOpt } from "./internal_evt.js";
|
import { INTERNAL_EVT, internalOpt } from "./internal_evt.js";
|
||||||
import { makeSet } from "pdfjs-lib";
|
|
||||||
|
|
||||||
const WaitOnType = {
|
const WaitOnType = {
|
||||||
EVENT: "event",
|
EVENT: "event",
|
||||||
@ -73,7 +72,7 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) {
|
|||||||
* and `off` methods. To raise an event, the `dispatch` method shall be used.
|
* and `off` methods. To raise an event, the `dispatch` method shall be used.
|
||||||
*/
|
*/
|
||||||
class EventBus {
|
class EventBus {
|
||||||
#listeners = new Map();
|
#listeners = Object.create(null);
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||||
@ -102,7 +101,8 @@ class EventBus {
|
|||||||
signal.addEventListener("abort", onAbort);
|
signal.addEventListener("abort", onAbort);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.#listeners.getOrInsertComputed(eventName, makeSet).add({
|
const eventListeners = (this.#listeners[eventName] ??= []);
|
||||||
|
eventListeners.push({
|
||||||
listener,
|
listener,
|
||||||
internal: options?.internal === INTERNAL_EVT,
|
internal: options?.internal === INTERNAL_EVT,
|
||||||
once: options?.once === true,
|
once: options?.once === true,
|
||||||
@ -116,11 +116,17 @@ class EventBus {
|
|||||||
* @param {Object} [options]
|
* @param {Object} [options]
|
||||||
*/
|
*/
|
||||||
off(eventName, listener, options = null) {
|
off(eventName, listener, options = null) {
|
||||||
const eventListeners = this.#listeners.get(eventName);
|
const eventListeners = this.#listeners[eventName];
|
||||||
const evt = eventListeners?.keys().find(e => e.listener === listener);
|
if (!eventListeners) {
|
||||||
if (evt) {
|
return;
|
||||||
evt.rmAbort?.(); // Ensure that the `AbortSignal` listener is removed.
|
}
|
||||||
eventListeners.delete(evt);
|
for (let i = 0, ii = eventListeners.length; i < ii; i++) {
|
||||||
|
const evt = eventListeners[i];
|
||||||
|
if (evt.listener === listener) {
|
||||||
|
evt.rmAbort?.(); // Ensure that the `AbortSignal` listener is removed.
|
||||||
|
eventListeners.splice(i, 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,14 +135,14 @@ class EventBus {
|
|||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
*/
|
*/
|
||||||
dispatch(eventName, data) {
|
dispatch(eventName, data) {
|
||||||
const eventListeners = this.#listeners.get(eventName);
|
const eventListeners = this.#listeners[eventName];
|
||||||
if (!eventListeners?.size) {
|
if (!eventListeners?.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let extListeners;
|
let extListeners;
|
||||||
// Always create a copy of the listeners in case they are modified
|
// Making copy of the listeners array in case if it will be modified
|
||||||
// during dispatch.
|
// during dispatch.
|
||||||
for (const { listener, internal, once } of new Set(eventListeners)) {
|
for (const { listener, internal, once } of eventListeners.slice(0)) {
|
||||||
if (once) {
|
if (once) {
|
||||||
this.off(eventName, listener);
|
this.off(eventName, listener);
|
||||||
}
|
}
|
||||||
|
|||||||
15
web/menu.css
15
web/menu.css
@ -41,13 +41,10 @@ button.hasPopupMenu {
|
|||||||
var(--menu-text-color),
|
var(--menu-text-color),
|
||||||
transparent 93%
|
transparent 93%
|
||||||
);
|
);
|
||||||
--menuitem-focus-outline-color: var(
|
--menuitem-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||||
--focus-outline-color,
|
|
||||||
light-dark(#0062fa, #00cadb)
|
|
||||||
);
|
|
||||||
--menuitem-focus-border-color: light-dark(white, black);
|
--menuitem-focus-border-color: light-dark(white, black);
|
||||||
|
|
||||||
--menu-bg: var(--background-color-box, light-dark(white, #23222b));
|
--menu-bg: light-dark(white, #23222b);
|
||||||
--menu-background-blend-mode: normal;
|
--menu-background-blend-mode: normal;
|
||||||
--menu-box-shadow:
|
--menu-box-shadow:
|
||||||
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||||
@ -55,7 +52,7 @@ button.hasPopupMenu {
|
|||||||
--menu-border-color: light-dark(rgb(21 20 26 / 0.1), rgb(251 251 254 / 0.1));
|
--menu-border-color: light-dark(rgb(21 20 26 / 0.1), rgb(251 251 254 / 0.1));
|
||||||
--menuitem-border-radius: 8px;
|
--menuitem-border-radius: 8px;
|
||||||
--menu-backdrop-filter: none;
|
--menu-backdrop-filter: none;
|
||||||
--menu-text-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
--menu-text-color: light-dark(#15141a, #fbfbfe);
|
||||||
--menu-text-disabled-color: var(--menu-text-color);
|
--menu-text-disabled-color: var(--menu-text-color);
|
||||||
--menuitem-text-hover-fg: var(--menu-text-color);
|
--menuitem-text-hover-fg: var(--menu-text-color);
|
||||||
--menuitem-hover-bg: color-mix(
|
--menuitem-hover-bg: color-mix(
|
||||||
@ -67,11 +64,11 @@ button.hasPopupMenu {
|
|||||||
--disabled-opacity: 0.62;
|
--disabled-opacity: 0.62;
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--menu-bg: var(--background-color-box, Canvas);
|
--menu-bg: Canvas;
|
||||||
--menu-background-blend-mode: normal;
|
--menu-background-blend-mode: normal;
|
||||||
--menu-box-shadow: none;
|
--menu-box-shadow: none;
|
||||||
--menu-backdrop-filter: none;
|
--menu-backdrop-filter: none;
|
||||||
--menu-text-color: var(--text-color, ButtonText);
|
--menu-text-color: ButtonText;
|
||||||
--menu-text-disabled-color: GrayText;
|
--menu-text-disabled-color: GrayText;
|
||||||
--menu-border-color: CanvasText;
|
--menu-border-color: CanvasText;
|
||||||
--menuitem-border-color: none;
|
--menuitem-border-color: none;
|
||||||
@ -81,7 +78,7 @@ button.hasPopupMenu {
|
|||||||
--menuitem-active-border-color: ButtonText;
|
--menuitem-active-border-color: ButtonText;
|
||||||
--menuitem-text-active-fg: SelectedItem;
|
--menuitem-text-active-fg: SelectedItem;
|
||||||
--menuitem-focus-bg: ButtonFace;
|
--menuitem-focus-bg: ButtonFace;
|
||||||
--menuitem-focus-outline-color: var(--focus-outline-color, CanvasText);
|
--menuitem-focus-outline-color: CanvasText;
|
||||||
--menuitem-focus-border-color: none;
|
--menuitem-focus-border-color: none;
|
||||||
--disabled-opacity: 1;
|
--disabled-opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
49
web/menu.js
49
web/menu.js
@ -39,9 +39,14 @@ class Menu {
|
|||||||
constructor(menuContainer, triggeringButton, menuItems) {
|
constructor(menuContainer, triggeringButton, menuItems) {
|
||||||
this.#menu = menuContainer;
|
this.#menu = menuContainer;
|
||||||
this.#triggeringButton = triggeringButton;
|
this.#triggeringButton = triggeringButton;
|
||||||
this.#menuItems = Array.isArray(menuItems)
|
if (Array.isArray(menuItems)) {
|
||||||
? menuItems
|
this.#menuItems = menuItems;
|
||||||
: [...this.#menu.querySelectorAll("button")];
|
} else {
|
||||||
|
this.#menuItems = [];
|
||||||
|
for (const button of this.#menu.querySelectorAll("button")) {
|
||||||
|
this.#menuItems.push(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.#setUpMenu();
|
this.#setUpMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,11 +146,19 @@ class Menu {
|
|||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
break;
|
break;
|
||||||
case "Home":
|
case "Home":
|
||||||
this.#goToFirstLast(false);
|
this.#menuItems
|
||||||
|
.find(
|
||||||
|
item => !item.disabled && !item.classList.contains("hidden")
|
||||||
|
)
|
||||||
|
?.focus();
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
break;
|
break;
|
||||||
case "End":
|
case "End":
|
||||||
this.#goToFirstLast(true);
|
this.#menuItems
|
||||||
|
.findLast(
|
||||||
|
item => !item.disabled && !item.classList.contains("hidden")
|
||||||
|
)
|
||||||
|
?.focus();
|
||||||
stopEvent(e);
|
stopEvent(e);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -181,7 +194,11 @@ class Menu {
|
|||||||
if (!this.#openMenuAC) {
|
if (!this.#openMenuAC) {
|
||||||
this.#openMenu();
|
this.#openMenu();
|
||||||
}
|
}
|
||||||
this.#goToFirstLast(false);
|
this.#menuItems
|
||||||
|
.find(
|
||||||
|
item => !item.disabled && !item.classList.contains("hidden")
|
||||||
|
)
|
||||||
|
?.focus();
|
||||||
break;
|
break;
|
||||||
case "ArrowUp":
|
case "ArrowUp":
|
||||||
case "End":
|
case "End":
|
||||||
@ -189,7 +206,11 @@ class Menu {
|
|||||||
if (!this.#openMenuAC) {
|
if (!this.#openMenuAC) {
|
||||||
this.#openMenu();
|
this.#openMenu();
|
||||||
}
|
}
|
||||||
this.#goToFirstLast(true);
|
this.#menuItems
|
||||||
|
.findLast(
|
||||||
|
item => !item.disabled && !item.classList.contains("hidden")
|
||||||
|
)
|
||||||
|
?.focus();
|
||||||
break;
|
break;
|
||||||
case "Escape":
|
case "Escape":
|
||||||
this.#closeMenu();
|
this.#closeMenu();
|
||||||
@ -231,20 +252,6 @@ class Menu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Go to the first/last menu item.
|
|
||||||
* @param {boolean} [last]
|
|
||||||
*/
|
|
||||||
#goToFirstLast(last = false) {
|
|
||||||
const i = this.#menuItems[last ? "findLastIndex" : "findIndex"](
|
|
||||||
item => !item.disabled && !item.classList.contains("hidden")
|
|
||||||
);
|
|
||||||
if (i >= 0) {
|
|
||||||
this.#menuItems[i].focus();
|
|
||||||
this.#lastIndex = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
this.#closeMenu();
|
this.#closeMenu();
|
||||||
this.#menuAC?.abort();
|
this.#menuAC?.abort();
|
||||||
|
|||||||
@ -17,46 +17,28 @@
|
|||||||
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
||||||
--message-bar-close-button-color: var(--text-primary-color);
|
--message-bar-close-button-color: var(--text-primary-color);
|
||||||
--message-bar-close-button-color-hover: var(--text-primary-color);
|
--message-bar-close-button-color-hover: var(--text-primary-color);
|
||||||
--message-bar-close-button-border-radius: var(--border-radius-small, 4px);
|
--message-bar-close-button-border-radius: 4px;
|
||||||
--message-bar-close-button-border: none;
|
--message-bar-close-button-border: none;
|
||||||
--message-bar-close-button-hover-bg-color: var(
|
--message-bar-close-button-hover-bg-color: light-dark(
|
||||||
--button-background-color-hover,
|
rgb(21 20 26 / 0.14),
|
||||||
light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14))
|
rgb(251 251 254 / 0.14)
|
||||||
);
|
);
|
||||||
--message-bar-close-button-active-bg-color: var(
|
--message-bar-close-button-active-bg-color: light-dark(
|
||||||
--button-background-color-active,
|
rgb(21 20 26 / 0.21),
|
||||||
light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21))
|
rgb(251 251 254 / 0.21)
|
||||||
);
|
);
|
||||||
--message-bar-close-button-focus-bg-color: var(
|
--message-bar-close-button-focus-bg-color: light-dark(
|
||||||
--button-background-color,
|
rgb(21 20 26 / 0.07),
|
||||||
light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07))
|
rgb(251 251 254 / 0.07)
|
||||||
);
|
);
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
--message-bar-close-button-color: var(--button-text-color, ButtonText);
|
--message-bar-close-button-color: ButtonText;
|
||||||
--message-bar-close-button-border: 1px solid
|
--message-bar-close-button-border: 1px solid ButtonText;
|
||||||
var(--button-border-color, ButtonText);
|
--message-bar-close-button-hover-bg-color: ButtonText;
|
||||||
/* One icon colour is shared by hover/active/focus, so all three backgrounds
|
--message-bar-close-button-active-bg-color: ButtonText;
|
||||||
read the -hover token and pair with --button-text-color-hover. The
|
--message-bar-close-button-focus-bg-color: ButtonText;
|
||||||
fallbacks are SelectedItemText/SelectedItem for the same reason: the
|
--message-bar-close-button-color-hover: HighlightText;
|
||||||
previous ButtonText-behind-HighlightText hid the icon outright in any
|
|
||||||
theme whose button ink and highlight text are both light. */
|
|
||||||
--message-bar-close-button-hover-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
SelectedItemText
|
|
||||||
);
|
|
||||||
--message-bar-close-button-active-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
SelectedItemText
|
|
||||||
);
|
|
||||||
--message-bar-close-button-focus-bg-color: var(
|
|
||||||
--button-background-color-hover,
|
|
||||||
SelectedItemText
|
|
||||||
);
|
|
||||||
--message-bar-close-button-color-hover: var(
|
|
||||||
--button-text-color-hover,
|
|
||||||
SelectedItem
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -68,9 +50,7 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
/* Nests inside .dialog, whose radius is --border-radius-medium, so it has to
|
border-radius: 4px;
|
||||||
scale with it to keep the inner corner tighter than the outer one. */
|
|
||||||
border-radius: var(--border-radius-small, 4px);
|
|
||||||
|
|
||||||
border: 1px solid var(--message-bar-border-color);
|
border: 1px solid var(--message-bar-border-color);
|
||||||
background: var(--message-bar-bg-color);
|
background: var(--message-bar-bg-color);
|
||||||
@ -150,6 +130,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#editorUndoBar {
|
#editorUndoBar {
|
||||||
|
--text-primary-color: light-dark(#15141a, #fbfbfe);
|
||||||
|
|
||||||
--message-bar-icon: url(images/messageBar_info.svg);
|
--message-bar-icon: url(images/messageBar_info.svg);
|
||||||
--message-bar-icon-color: light-dark(#0060df, #73a7f3);
|
--message-bar-icon-color: light-dark(#0060df, #73a7f3);
|
||||||
--message-bar-bg-color: light-dark(#deeafc, #003070);
|
--message-bar-bg-color: light-dark(#deeafc, #003070);
|
||||||
@ -159,10 +141,41 @@
|
|||||||
rgb(255 255 255 / 0.08)
|
rgb(255 255 255 / 0.08)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
--undo-button-bg-color: light-dark(
|
||||||
|
rgb(21 20 26 / 0.07),
|
||||||
|
rgb(255 255 255 / 0.08)
|
||||||
|
);
|
||||||
|
--undo-button-bg-color-hover: light-dark(
|
||||||
|
rgb(21 20 26 / 0.14),
|
||||||
|
rgb(255 255 255 / 0.14)
|
||||||
|
);
|
||||||
|
--undo-button-bg-color-active: light-dark(
|
||||||
|
rgb(21 20 26 / 0.21),
|
||||||
|
rgb(255 255 255 / 0.21)
|
||||||
|
);
|
||||||
|
|
||||||
|
--undo-button-border: 1px solid light-dark(#0060df, #0df);
|
||||||
|
|
||||||
|
--undo-button-fg-color: var(--message-bar-fg-color);
|
||||||
|
--undo-button-fg-color-hover: var(--undo-button-fg-color);
|
||||||
|
--undo-button-fg-color-active: var(--undo-button-fg-color);
|
||||||
|
|
||||||
@media screen and (forced-colors: active) {
|
@media screen and (forced-colors: active) {
|
||||||
|
--text-primary-color: CanvasText;
|
||||||
|
|
||||||
--message-bar-icon-color: CanvasText;
|
--message-bar-icon-color: CanvasText;
|
||||||
--message-bar-bg-color: Canvas;
|
--message-bar-bg-color: Canvas;
|
||||||
--message-bar-border-color: CanvasText;
|
--message-bar-border-color: CanvasText;
|
||||||
|
|
||||||
|
--undo-button-bg-color: ButtonText;
|
||||||
|
--undo-button-bg-color-hover: SelectedItem;
|
||||||
|
--undo-button-bg-color-active: SelectedItem;
|
||||||
|
|
||||||
|
--undo-button-fg-color: ButtonFace;
|
||||||
|
--undo-button-fg-color-hover: SelectedItemText;
|
||||||
|
--undo-button-fg-color-active: SelectedItemText;
|
||||||
|
|
||||||
|
--undo-button-border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@ -184,10 +197,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#editorUndoBarUndoButton {
|
#editorUndoBarUndoButton {
|
||||||
|
border-radius: 4px;
|
||||||
|
font-weight: 590;
|
||||||
|
line-height: 19.5px;
|
||||||
|
color: var(--undo-button-fg-color);
|
||||||
|
border: var(--undo-button-border);
|
||||||
|
padding: 4px 16px;
|
||||||
margin-inline-start: 8px;
|
margin-inline-start: 8px;
|
||||||
/* The generic secondary border is the same translucent wash as its fill,
|
height: 32px;
|
||||||
which disappears on the tinted bar, so keep an explicit accent edge. */
|
|
||||||
border-color: var(--color-accent-primary, light-dark(#0060df, #0df));
|
background-color: var(--undo-button-bg-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--undo-button-bg-color-hover);
|
||||||
|
color: var(--undo-button-fg-color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background-color: var(--undo-button-bg-color-active);
|
||||||
|
color: var(--undo-button-fg-color-active);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
> div {
|
||||||
|
|||||||
@ -936,14 +936,9 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
if (this.structTreeLayer && !this.textLayer) {
|
if (this.structTreeLayer && !this.textLayer) {
|
||||||
this.structTreeLayer = null;
|
this.structTreeLayer = null;
|
||||||
}
|
}
|
||||||
// The annotation editor layer and the draw layer keep references on the
|
|
||||||
// text layer (the latter uses its div in order to render the selection),
|
|
||||||
// hence they must be recreated too.
|
|
||||||
if (
|
if (
|
||||||
this.annotationEditorLayer &&
|
this.annotationEditorLayer &&
|
||||||
(!keepAnnotationEditorLayer ||
|
(!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
|
||||||
!this.annotationEditorLayer.div ||
|
|
||||||
!this.textLayer)
|
|
||||||
) {
|
) {
|
||||||
if (this.drawLayer) {
|
if (this.drawLayer) {
|
||||||
this.drawLayer.cancel();
|
this.drawLayer.cancel();
|
||||||
@ -952,10 +947,6 @@ class PDFPageView extends BasePDFPageView {
|
|||||||
this.annotationEditorLayer.cancel();
|
this.annotationEditorLayer.cancel();
|
||||||
this.annotationEditorLayer = null;
|
this.annotationEditorLayer = null;
|
||||||
}
|
}
|
||||||
if (this.drawLayer && !this.textLayer) {
|
|
||||||
this.drawLayer.cancel();
|
|
||||||
this.drawLayer = null;
|
|
||||||
}
|
|
||||||
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
|
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
|
||||||
this.xfaLayer.cancel();
|
this.xfaLayer.cancel();
|
||||||
this.xfaLayer = null;
|
this.xfaLayer = null;
|
||||||
|
|||||||
@ -111,7 +111,7 @@ class PDFScriptingManager {
|
|||||||
// targeting an unknown id can be ignored.
|
// targeting an unknown id can be ignored.
|
||||||
if (objects) {
|
if (objects) {
|
||||||
this.#objectIds = new Set();
|
this.#objectIds = new Set();
|
||||||
for (const fields of objects.values()) {
|
for (const fields of Object.values(objects)) {
|
||||||
for (const { id } of fields) {
|
for (const { id } of fields) {
|
||||||
this.#objectIds.add(id);
|
this.#objectIds.add(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -386,8 +386,8 @@ class PDFThumbnailViewer {
|
|||||||
const pagesCount = this.#pagesMapper.pagesNumber;
|
const pagesCount = this.#pagesMapper.pagesNumber;
|
||||||
const data = this.hasStructuralChanges()
|
const data = this.hasStructuralChanges()
|
||||||
? this.getStructuralChanges()
|
? this.getStructuralChanges()
|
||||||
: { pageInfos: [{ document: null }], copyLevels: null };
|
: [{ document: null }];
|
||||||
data.pageInfos.push(...entries);
|
data.push(...entries);
|
||||||
this.eventBus.on(
|
this.eventBus.on(
|
||||||
"pagesloaded",
|
"pagesloaded",
|
||||||
() => {
|
() => {
|
||||||
|
|||||||
@ -12,22 +12,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
/*
|
|
||||||
* Firefox design-system bridge.
|
|
||||||
*
|
|
||||||
* The viewer chrome is authored against Firefox design-token names with the
|
|
||||||
* shipped literal as the fallback: `var(--fx-token, <literal>)`. GENERIC and
|
|
||||||
* the components build resolve the fallback and stay self-contained; the
|
|
||||||
* MOZCENTRAL build imports tokens-brand.css when pdfjs.enableNova is set (see
|
|
||||||
* viewer.css), so those names resolve to live Firefox values and the theme
|
|
||||||
* never has to be re-synced by hand.
|
|
||||||
*
|
|
||||||
* Firefox declares its tokens inside `@layer tokens-foundation-brand`, so any
|
|
||||||
* unlayered declaration wins over them. To *consume* a Firefox value,
|
|
||||||
* reference it through var() (with a fallback) and do NOT redeclare the same
|
|
||||||
* name: that would shadow Firefox's for the whole subtree.
|
|
||||||
*/
|
|
||||||
@import url(buttons.css);
|
|
||||||
@import url(message_bar.css);
|
@import url(message_bar.css);
|
||||||
@import url(dialog.css);
|
@import url(dialog.css);
|
||||||
@import url(text_layer_builder.css);
|
@import url(text_layer_builder.css);
|
||||||
@ -50,12 +34,8 @@
|
|||||||
--page-border: 9px solid transparent;
|
--page-border: 9px solid transparent;
|
||||||
--spreadHorizontalWrapped-margin-LR: -3.5px;
|
--spreadHorizontalWrapped-margin-LR: -3.5px;
|
||||||
--loading-icon-delay: 400ms;
|
--loading-icon-delay: 400ms;
|
||||||
--focus-ring-color: var(--focus-outline-color, light-dark(#0060df, #0df));
|
--focus-ring-color: light-dark(#0060df, #0df);
|
||||||
--focus-ring-outline: var(--focus-outline-width, 2px) solid
|
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||||
var(--focus-ring-color);
|
|
||||||
--text-primary-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
|
||||||
--link-fg-color: var(--link-color, light-dark(#0060df, #0df));
|
|
||||||
--link-hover-fg-color: var(--link-color-hover, light-dark(#0250bb, #80ebff));
|
|
||||||
--new-badge-bg: light-dark(#070, #37b847);
|
--new-badge-bg: light-dark(#070, #37b847);
|
||||||
--new-badge-color: light-dark(#fff, #15141a);
|
--new-badge-color: light-dark(#fff, #15141a);
|
||||||
--new-badge-border-color: light-dark(#fbfbfe / 40%, #15141a / 40%);
|
--new-badge-border-color: light-dark(#fbfbfe / 40%, #15141a / 40%);
|
||||||
@ -65,10 +45,7 @@
|
|||||||
--page-margin: 8px auto -1px;
|
--page-margin: 8px auto -1px;
|
||||||
--page-border: 1px solid CanvasText;
|
--page-border: 1px solid CanvasText;
|
||||||
--spreadHorizontalWrapped-margin-LR: 3.5px;
|
--spreadHorizontalWrapped-margin-LR: 3.5px;
|
||||||
--focus-ring-color: var(--focus-outline-color, CanvasText);
|
--focus-ring-color: CanvasText;
|
||||||
--text-primary-color: var(--text-color, CanvasText);
|
|
||||||
--link-fg-color: var(--link-color, LinkText);
|
|
||||||
--link-hover-fg-color: var(--link-color-hover, LinkText);
|
|
||||||
--new-badge-bg: AccentColor;
|
--new-badge-bg: AccentColor;
|
||||||
--new-badge-color: ButtonFace;
|
--new-badge-color: ButtonFace;
|
||||||
--new-badge-border-color: CanvasText;
|
--new-badge-border-color: CanvasText;
|
||||||
|
|||||||
@ -1600,10 +1600,13 @@ class PDFViewer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get #pageWidthScaleFactor() {
|
get #pageWidthScaleFactor() {
|
||||||
return this._spreadMode !== SpreadMode.NONE &&
|
if (
|
||||||
|
this._spreadMode !== SpreadMode.NONE &&
|
||||||
this._scrollMode !== ScrollMode.HORIZONTAL
|
this._scrollMode !== ScrollMode.HORIZONTAL
|
||||||
? 2
|
) {
|
||||||
: 1;
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
#setScale(value, options) {
|
#setScale(value, options) {
|
||||||
@ -1887,20 +1890,19 @@ class PDFViewer {
|
|||||||
container.scrollLeft - firstPage.x,
|
container.scrollLeft - firstPage.x,
|
||||||
container.scrollTop - firstPage.y
|
container.scrollTop - firstPage.y
|
||||||
);
|
);
|
||||||
const [left, top] = topLeft;
|
const intLeft = Math.round(topLeft[0]);
|
||||||
|
const intTop = Math.round(topLeft[1]);
|
||||||
|
|
||||||
let pdfOpenParams = `#page=${pageNumber}`;
|
let pdfOpenParams = `#page=${pageNumber}`;
|
||||||
if (!this.isInPresentationMode) {
|
if (!this.isInPresentationMode) {
|
||||||
pdfOpenParams +=
|
pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
|
||||||
`&zoom=${normalizedScaleValue},` +
|
|
||||||
`${Math.round(left)},${Math.round(top)}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._location = {
|
this._location = {
|
||||||
pageNumber,
|
pageNumber,
|
||||||
scale: normalizedScaleValue,
|
scale: normalizedScaleValue,
|
||||||
top,
|
top: intTop,
|
||||||
left,
|
left: intLeft,
|
||||||
rotation: this._pagesRotation,
|
rotation: this._pagesRotation,
|
||||||
pdfOpenParams,
|
pdfOpenParams,
|
||||||
};
|
};
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user