mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-04 05:17:24 +02:00
Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4ba666b0c | ||
|
|
4c4e8feafd | ||
|
|
f21fe34747 | ||
|
|
ba7bf7b26c | ||
|
|
1da36fb6bf | ||
|
|
92d027eaeb | ||
|
|
5375bff642 | ||
|
|
b1f51818ae | ||
|
|
ae976b924b | ||
|
|
ec691130e6 | ||
|
|
88716313d6 | ||
|
|
82624a5e50 | ||
|
|
7590ad5312 | ||
|
|
923d48ead4 | ||
|
|
1f9fbc764e | ||
|
|
f7f30dd844 | ||
|
|
d2ebbd10de | ||
|
|
09ad726a3f | ||
|
|
d0779c411e | ||
|
|
89852881b8 | ||
|
|
eb8f6af8cc | ||
|
|
f73978083d | ||
|
|
5dc1d0c5d5 | ||
|
|
2475c4ec91 | ||
|
|
08f6769390 | ||
|
|
7862875438 | ||
|
|
cba911df86 | ||
|
|
cec8d2eed6 | ||
|
|
491792f0e2 | ||
|
|
664526ee65 | ||
|
|
7fc7072f9c | ||
|
|
bb1a948397 | ||
|
|
4b4c784a85 | ||
|
|
0f999e27b5 | ||
|
|
af38789499 | ||
|
|
0d5df9d96e | ||
|
|
8960eddfc1 | ||
|
|
ee59db5018 | ||
|
|
ce4ff55faa | ||
|
|
40f6492997 | ||
|
|
613e004b2e | ||
|
|
734b21e61e | ||
|
|
b4d028132b | ||
|
|
a80897dc9a | ||
|
|
0163438e1a | ||
|
|
f70f55d743 | ||
|
|
9e02ceed09 | ||
|
|
803c9d7d21 | ||
|
|
73707b62e9 | ||
|
|
1813ab0774 | ||
|
|
aa812b531a | ||
|
|
61acf93172 | ||
|
|
822d3ea809 | ||
|
|
3a19a20d50 | ||
|
|
4d9007e6f9 | ||
|
|
7f5fd4eba4 |
10
external/builder/builder.mjs
vendored
10
external/builder/builder.mjs
vendored
@ -73,10 +73,7 @@ function preprocess(inFilename, outFilename, defines) {
|
||||
const out = [];
|
||||
let i = 0;
|
||||
function readLine() {
|
||||
if (i < totalLines) {
|
||||
return lines[i++];
|
||||
}
|
||||
return null;
|
||||
return i < totalLines ? lines[i++] : null;
|
||||
}
|
||||
const writeLine =
|
||||
typeof outFilename === "function"
|
||||
@ -127,10 +124,7 @@ function preprocess(inFilename, outFilename, defines) {
|
||||
function expand(line) {
|
||||
line = line.replaceAll(/__\w+__/g, function (variable) {
|
||||
variable = variable.substring(2, variable.length - 2);
|
||||
if (variable in defines) {
|
||||
return defines[variable];
|
||||
}
|
||||
return "";
|
||||
return variable in defines ? defines[variable] : "";
|
||||
});
|
||||
writeLine(line);
|
||||
}
|
||||
|
||||
@ -52,21 +52,61 @@ pdfjs-bookmark-button-label = بلگه هیم سکویی
|
||||
pdfjs-tools-button =
|
||||
.title = ٱوزارا
|
||||
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-hand-tool-button =
|
||||
.title = فعال کردن ٱوزار دست
|
||||
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
|
||||
|
||||
pdfjs-document-properties-button =
|
||||
.title = خۊسۊسیات سند…
|
||||
pdfjs-document-properties-button-label = خۊسۊسیات سند…
|
||||
pdfjs-document-properties-file-name = نوم فایل:
|
||||
pdfjs-document-properties-file-size = هندا فایل:
|
||||
pdfjs-document-properties-title = عونوان:
|
||||
pdfjs-document-properties-author = هؽل کوݩ:
|
||||
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-unit-inches = اینچ
|
||||
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-four = A4
|
||||
pdfjs-document-properties-page-size-name-letter = نامه
|
||||
@ -84,12 +124,16 @@ 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-no = ن
|
||||
pdfjs-document-properties-close-button = بستن
|
||||
|
||||
## Print
|
||||
|
||||
pdfjs-print-progress-message = ٱماڌه کردن سند سی چاپ کردن…
|
||||
# Variables:
|
||||
# $progress (Number) - percent value
|
||||
pdfjs-print-progress-percent = { $progress }%
|
||||
@ -99,6 +143,55 @@ pdfjs-print-progress-close-button = لقو
|
||||
|
||||
pdfjs-toggle-sidebar-button =
|
||||
.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
|
||||
|
||||
@ -107,6 +200,12 @@ pdfjs-password-cancel-button = لقو
|
||||
|
||||
## 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 =
|
||||
.title = ٱووردن یا آلشت شؽواتا
|
||||
pdfjs-editor-stamp-button-label = ٱووردن یا آلشت شؽواتا
|
||||
@ -118,6 +217,14 @@ 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.
|
||||
pdfjs-free-text2 =
|
||||
.aria-label = آلشتگر هؽل
|
||||
@ -127,15 +234,55 @@ pdfjs-editor-comments-sidebar-no-comments-link = قلوه دووسته بۊین
|
||||
## Alt-text dialog
|
||||
|
||||
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
|
||||
## 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-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
|
||||
|
||||
pdfjs-editor-add-signature-cancel-button = لقو
|
||||
pdfjs-editor-add-signature-add-button = ٱووردن
|
||||
pdfjs-editor-edit-signature-update-button = ورۊ رسۊوی
|
||||
|
||||
## Comment popup
|
||||
|
||||
@ -147,4 +294,32 @@ pdfjs-editor-edit-comment-popup-button =
|
||||
|
||||
# An existing comment is edited
|
||||
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 = لقو
|
||||
|
||||
## 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
|
||||
pdfjs-page-rotate-cw-button-label = Im Uhrzeigersinn drehen
|
||||
pdfjs-page-rotate-ccw-button =
|
||||
.title = Gegen Uhrzeigersinn drehen
|
||||
pdfjs-page-rotate-ccw-button-label = Gegen Uhrzeigersinn drehen
|
||||
.title = Gegen den Uhrzeigersinn drehen
|
||||
pdfjs-page-rotate-ccw-button-label = Gegen den Uhrzeigersinn drehen
|
||||
pdfjs-cursor-text-select-tool-button =
|
||||
.title = Textauswahl-Werkzeug aktivieren
|
||||
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).
|
||||
# Some common types are e.g.: "Check", "Text", "Comment", "Note"
|
||||
pdfjs-text-annotation-type =
|
||||
.alt = [Anlage: { $type }]
|
||||
.alt = [{ $type } Anmerkung]
|
||||
# Variables:
|
||||
# $dateObj (Date) - the modification date and time of the annotation
|
||||
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-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-no-data-error-title = Kann Grafik nicht in eine Signatur umwandeln
|
||||
pdfjs-editor-add-signature-image-no-data-error-title = Kann Grafik nicht in eine Unterschrift umwandeln
|
||||
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
|
||||
|
||||
@ -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-uploading-label = Datei wird hochgeladen…
|
||||
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-cut-label = Ausschneiden 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 war 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-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-done-button-label = Fertig
|
||||
@ -824,8 +824,8 @@ pdfjs-digital-signature-properties-certificate-revoked = Zertifikat: Widerrufen
|
||||
## Main menu for adding/removing signatures
|
||||
|
||||
pdfjs-editor-delete-signature-button1 =
|
||||
.title = Gespeicherte Signatur entfernen
|
||||
pdfjs-editor-delete-signature-button-label1 = Gespeicherte Signatur entfernen
|
||||
.title = Gespeicherte Unterschrift entfernen
|
||||
pdfjs-editor-delete-signature-button-label1 = Gespeicherte Unterschrift entfernen
|
||||
|
||||
## Editor toolbar
|
||||
|
||||
|
||||
@ -162,8 +162,8 @@ pdfjs-print-progress-message = Preparando documento para impresión…
|
||||
# $progress (Number) - percent value
|
||||
pdfjs-print-progress-percent = { $progress }%
|
||||
pdfjs-print-progress-close-button = Cancelar
|
||||
pdfjs-printing-not-supported = Advertencia: La impresión no esta completamente soportada por este navegador.
|
||||
pdfjs-printing-not-ready = Advertencia: El PDF no cargo completamente para impresión.
|
||||
pdfjs-printing-not-supported = Advertencia: La impresión no está completamente soportada por este navegador.
|
||||
pdfjs-printing-not-ready = Advertencia: El PDF no cargó completamente para impresión.
|
||||
|
||||
## Tooltips and alt text for side panel toolbar buttons
|
||||
|
||||
@ -266,7 +266,7 @@ pdfjs-page-landmark =
|
||||
## Loading indicator messages
|
||||
|
||||
pdfjs-loading-error = Un error ocurrió al cargar el PDF.
|
||||
pdfjs-invalid-file-error = Archivo PDF invalido o dañado.
|
||||
pdfjs-invalid-file-error = Archivo PDF inválido o dañado.
|
||||
pdfjs-missing-file-error = Archivo PDF no encontrado.
|
||||
pdfjs-unexpected-response-error = Respuesta inesperada del servidor.
|
||||
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-size-input = Tamaño
|
||||
pdfjs-editor-ink-color-input = Color
|
||||
pdfjs-editor-ink-thickness-input = Grossor
|
||||
pdfjs-editor-ink-thickness-input = Grosor
|
||||
pdfjs-editor-ink-opacity-input = Opacidad
|
||||
pdfjs-editor-stamp-add-image-button =
|
||||
.title = Agregar imagen
|
||||
@ -581,7 +581,7 @@ pdfjs-editor-add-signature-type-input =
|
||||
.aria-label = Escribe tu firma
|
||||
.placeholder = Escribe tu firma
|
||||
pdfjs-editor-add-signature-draw-placeholder = Dibuja tu firma
|
||||
pdfjs-editor-add-signature-draw-thickness-range-label = Grossor
|
||||
pdfjs-editor-add-signature-draw-thickness-range-label = Grosor
|
||||
# Variables:
|
||||
# $thickness (Number) - the thickness (in pixels) of the line used to draw a signature.
|
||||
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.
|
||||
pdfjs-views-manager-pages-status-undo-copy-label =
|
||||
{ $count ->
|
||||
[one] 1 pagina copiada
|
||||
*[other] { $count } paginas copiadas
|
||||
[one] 1 página copiada
|
||||
*[other] { $count } páginas copiadas
|
||||
}
|
||||
# Variables:
|
||||
# $count (Number) - the number of selected pages to be deleted.
|
||||
pdfjs-views-manager-pages-status-undo-delete-label =
|
||||
{ $count ->
|
||||
[one] 1 pagina eliminada
|
||||
*[other] { $count } paginas eliminadas
|
||||
[one] 1 página eliminada
|
||||
*[other] { $count } páginas eliminadas
|
||||
}
|
||||
pdfjs-views-manager-pages-status-waiting-ready-label = Preparando tu 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
|
||||
|
||||
pdfjs-editor-alt-text-button-label = Testu alternatiboa
|
||||
pdfjs-editor-alt-text-button-label = Ordezko testua
|
||||
pdfjs-editor-alt-text-edit-button =
|
||||
.aria-label = Editatu testu alternatiboa
|
||||
.aria-label = Editatu ordezko testua
|
||||
pdfjs-editor-alt-text-dialog-label = Aukeratu aukera
|
||||
pdfjs-editor-alt-text-dialog-description = Testu alternatiboak laguntzen du jendeak ezin duenean irudia ikusi edo ez denean kargatzen.
|
||||
pdfjs-editor-alt-text-dialog-description = Ordezko testuak 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-description = Saiatu idazten gaia, ezarpena edo ekintzak deskribatzen dituen esaldi 1 edo 2.
|
||||
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"
|
||||
# Alternative text (alt text) helps when people can't see the image.
|
||||
pdfjs-editor-alt-text-button =
|
||||
.aria-label = Testu alternatiboa
|
||||
.aria-label = Ordezko testua
|
||||
|
||||
## Editor resizers
|
||||
## 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.
|
||||
|
||||
# Modal header positioned above a text box where users can edit the alt text.
|
||||
pdfjs-editor-new-alt-text-dialog-edit-label = Editatu testu alternatiboa (irudiaren azalpena)
|
||||
pdfjs-editor-new-alt-text-dialog-edit-label = Editatu ordezko testua (irudiaren azalpena)
|
||||
# Modal header positioned above a text box where users can add the alt text.
|
||||
pdfjs-editor-new-alt-text-dialog-add-label = Gehitu testu alternatiboa (irudiaren azalpena)
|
||||
pdfjs-editor-new-alt-text-dialog-add-label = Gehitu ordezko testua (irudiaren azalpena)
|
||||
pdfjs-editor-new-alt-text-textarea =
|
||||
.placeholder = Idatzi zure azalpena hemen…
|
||||
# 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.
|
||||
# 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 = Testu alternatibo hau automatikoki sortu da eta okerra izan liteke.
|
||||
pdfjs-editor-new-alt-text-disclaimer1 = Ordezko testu hau automatikoki sortu da eta okerra izan liteke.
|
||||
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Argibide gehiago
|
||||
pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu testu alternatiboa automatikoki
|
||||
pdfjs-editor-new-alt-text-create-automatically-button-label = Sortu ordezko testua automatikoki
|
||||
pdfjs-editor-new-alt-text-not-now-button = Une honetan ez
|
||||
pdfjs-editor-new-alt-text-error-title = Ezin da testu alternatiboa automatikoki sortu
|
||||
pdfjs-editor-new-alt-text-error-description = Idatzi zure testu alternatibo propioa edo saiatu berriro geroago.
|
||||
pdfjs-editor-new-alt-text-error-title = Ezin da ordezko testua automatikoki sortu
|
||||
pdfjs-editor-new-alt-text-error-description = Idatzi zure ordezko testu propioa edo saiatu berriro geroago.
|
||||
pdfjs-editor-new-alt-text-error-close-button = Itxi
|
||||
# Variables:
|
||||
# $totalSize (Number) - the total 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 = Testu alternatiboaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||
.aria-valuetext = Testu alternatiboaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||
pdfjs-editor-new-alt-text-ai-model-downloading-progress = Ordezko testuaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||
.aria-valuetext = Ordezko testuaren AA modeloa deskargatzen ({ $downloadedSize }/{ $totalSize } MB)
|
||||
# This is a button that users can click to edit the alt text they have already added.
|
||||
pdfjs-editor-new-alt-text-added-button =
|
||||
.aria-label = Testu alternatiboa gehituta
|
||||
pdfjs-editor-new-alt-text-added-button-label = Testu alternatiboa gehituta
|
||||
.aria-label = Ordezko testua gehituta
|
||||
pdfjs-editor-new-alt-text-added-button-label = Ordezko testua gehituta
|
||||
# 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 =
|
||||
.aria-label = Testu alternatiboa falta da
|
||||
pdfjs-editor-new-alt-text-missing-button-label = Testu alternatiboa falta da
|
||||
.aria-label = Ordezko testua falta da
|
||||
pdfjs-editor-new-alt-text-missing-button-label = Ordezko testua falta da
|
||||
# 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 =
|
||||
.aria-label = Berrikusi testu alternatiboa
|
||||
pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi testu alternatiboa
|
||||
.aria-label = Berrikusi ordezko testua
|
||||
pdfjs-editor-new-alt-text-to-review-button-label = Berrikusi ordezko testua
|
||||
# "Created automatically" is a prefix that will be added to the beginning of any alt text that has been automatically generated. After the colon, the user will see/hear the actual alt text description. If the alt text has been edited by a human, this prefix will not appear.
|
||||
# Variables:
|
||||
# $generatedAltText (String) - the generated alt-text.
|
||||
@ -531,22 +531,22 @@ pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Automatikoki sort
|
||||
## Image alt-text settings
|
||||
|
||||
pdfjs-image-alt-text-settings-button =
|
||||
.title = Irudiaren testu alternatiboaren ezarpenak
|
||||
pdfjs-image-alt-text-settings-button-label = Irudiaren testu alternatiboaren ezarpenak
|
||||
pdfjs-editor-alt-text-settings-dialog-label = Irudiaren testu alternatiboaren ezarpenak
|
||||
pdfjs-editor-alt-text-settings-automatic-title = Testu alternatibo automatikoa
|
||||
pdfjs-editor-alt-text-settings-create-model-button-label = Sortu testu alternatiboa automatikoki
|
||||
.title = Irudien ordezko testuaren ezarpenak
|
||||
pdfjs-image-alt-text-settings-button-label = Irudien ordezko testuaren ezarpenak
|
||||
pdfjs-editor-alt-text-settings-dialog-label = Irudien ordezko testuaren ezarpenak
|
||||
pdfjs-editor-alt-text-settings-automatic-title = Ordezko testu automatikoa
|
||||
pdfjs-editor-alt-text-settings-create-model-button-label = Sortu ordezko testua automatikoki
|
||||
pdfjs-editor-alt-text-settings-create-model-description = Azalpenak iradokitzen ditu irudia ikusi ezin duen jendearentzat edo irudia kargatu ezin denerako.
|
||||
# Variables:
|
||||
# $totalSize (Number) - the total size (in MB) of the AI model.
|
||||
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. Testu alternatibo automatikorako beharrezkoa.
|
||||
pdfjs-editor-alt-text-settings-download-model-label = Ordezko testuaren 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-delete-model-button = Ezabatu
|
||||
pdfjs-editor-alt-text-settings-download-model-button = Deskargatu
|
||||
pdfjs-editor-alt-text-settings-downloading-model-button = Deskargatzen…
|
||||
pdfjs-editor-alt-text-settings-editor-title = Testu alternatiboaren editorea
|
||||
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 irudiek testu alternatiboa duela ziurtatzen laguntzen dizu.
|
||||
pdfjs-editor-alt-text-settings-editor-title = Ordezko testuaren editorea
|
||||
pdfjs-editor-alt-text-settings-show-dialog-button-label = Erakutsi ordezko testua 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-close-button = Itxi
|
||||
|
||||
## 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 =
|
||||
Leiho modal honek PDF dokumentu batera gehitzeko sinadurak
|
||||
sortzea ahalbidetzen dio erabiltzaileari. Erabiltzaileak izena edita
|
||||
dezake (testu alternatibo modura ere erabiltzen dena) eta sinadura
|
||||
dezake (ordezko testu modura ere erabiltzen dena) eta sinadura
|
||||
gordetzeko aukera du gehiagotan erabili ahal izateko.
|
||||
pdfjs-editor-add-signature-dialog-title = Gehitu sinadura
|
||||
|
||||
@ -619,9 +619,9 @@ pdfjs-editor-add-signature-image-browse-link =
|
||||
|
||||
## Controls
|
||||
|
||||
pdfjs-editor-add-signature-description-label = Azalpena (testu alternatiboa)
|
||||
pdfjs-editor-add-signature-description-label = Azalpena (ordezko testua)
|
||||
pdfjs-editor-add-signature-description-input =
|
||||
.title = Azalpena (testu alternatiboa)
|
||||
.title = Azalpena (ordezko testua)
|
||||
pdfjs-editor-add-signature-description-default-when-drawing = Sinadura
|
||||
pdfjs-editor-add-signature-clear-button-label = Garbitu sinadura
|
||||
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
|
||||
pdfjs-find-match-count-limit =
|
||||
{ $limit ->
|
||||
[one] Plus d’{ $limit } occurrence
|
||||
[1] Plus d’une occurrence
|
||||
*[other] Plus de { $limit } occurrences
|
||||
}
|
||||
pdfjs-find-not-found = Expression non trouvée
|
||||
|
||||
@ -261,7 +261,7 @@ pdfjs-find-match-count =
|
||||
# $limit (Number) - the maximum number of matches
|
||||
pdfjs-find-match-count-limit =
|
||||
{ $limit ->
|
||||
[one] Più di una { $limit } corrispondenza
|
||||
[one] Più di { $limit } corrispondenza
|
||||
*[other] Più di { $limit } corrispondenze
|
||||
}
|
||||
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-name-a-three = A3
|
||||
pdfjs-document-properties-page-size-name-a-four = A4
|
||||
pdfjs-document-properties-page-size-name-letter = Pismo
|
||||
pdfjs-document-properties-page-size-name-legal = Pravno
|
||||
pdfjs-document-properties-page-size-name-letter = Letter
|
||||
pdfjs-document-properties-page-size-name-legal = Legal
|
||||
|
||||
## Variables:
|
||||
## $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-disclaimer-learn-more-url = Tìm hiểu thêm
|
||||
pdfjs-editor-new-alt-text-create-automatically-button-label = Tạo văn bản thay thế tự động
|
||||
pdfjs-editor-new-alt-text-not-now-button = Không phải bây giờ
|
||||
pdfjs-editor-new-alt-text-not-now-button = Để sau
|
||||
pdfjs-editor-new-alt-text-error-title = Không thể tạo tự động văn bản thay thế
|
||||
pdfjs-editor-new-alt-text-error-description = Vui lòng viết văn bản thay thế của riêng bạn hoặc thử lại sau.
|
||||
pdfjs-editor-new-alt-text-error-close-button = Đóng
|
||||
|
||||
238
package-lock.json
generated
238
package-lock.json
generated
@ -15,8 +15,8 @@
|
||||
"@fluent/dom": "^0.10.2",
|
||||
"@metalsmith/layouts": "^3.0.0",
|
||||
"@metalsmith/markdown": "^1.10.0",
|
||||
"@napi-rs/canvas": "^1.0.2",
|
||||
"@types/node": "^26.1.1",
|
||||
"@napi-rs/canvas": "^1.0.3",
|
||||
"@types/node": "^26.1.2",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-istanbul": "^8.0.2",
|
||||
@ -33,7 +33,7 @@
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-regexp": "^3.1.1",
|
||||
"eslint-plugin-unicorn": "^72.0.0",
|
||||
"globals": "^17.7.0",
|
||||
"globals": "^17.8.0",
|
||||
"gulp": "^5.0.1",
|
||||
"gulp-cli": "^3.1.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
@ -49,14 +49,14 @@
|
||||
"jstransformer-nunjucks": "^1.2.0",
|
||||
"kleur": "^4.1.5",
|
||||
"metalsmith": "^2.7.0",
|
||||
"metalsmith-html-relative": "^2.0.12",
|
||||
"metalsmith-html-relative": "^2.0.13",
|
||||
"ordered-read-streams": "^2.0.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.5.23",
|
||||
"postcss": "^8.5.25",
|
||||
"postcss-discard-comments": "^8.0.1",
|
||||
"postcss-values-parser": "^8.0.0",
|
||||
"prettier": "^3.9.6",
|
||||
"puppeteer": "^25.3.0",
|
||||
"puppeteer": "^25.4.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"svglint": "^4.2.1",
|
||||
@ -65,7 +65,7 @@
|
||||
"ttest": "^4.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vinyl": "^3.0.1",
|
||||
"webpack": "^5.109.0",
|
||||
"webpack": "^5.109.2",
|
||||
"webpack-stream": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -3669,9 +3669,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.2.tgz",
|
||||
"integrity": "sha512-EYEqlMYaCbpZDz+IgDH5xp9MTd3ui4dmGqbQYryhMLnSRxrhHKq5KQWHHKxFUcEP4Hp8/BWgvqXocX4j7iSbOQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-1.0.3.tgz",
|
||||
"integrity": "sha512-OlI657a5XXvKGFX7kNeIzJ8rO7IXt87Mqu2H8rXE46viAuOfum/JA7ysX7+eBhxNKznT+RCZh418mndlcFX3+w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@ -3685,23 +3685,23 @@
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@napi-rs/canvas-android-arm64": "1.0.2",
|
||||
"@napi-rs/canvas-darwin-arm64": "1.0.2",
|
||||
"@napi-rs/canvas-darwin-x64": "1.0.2",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.2",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "1.0.2",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "1.0.2",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.2",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "1.0.2",
|
||||
"@napi-rs/canvas-linux-x64-musl": "1.0.2",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "1.0.2",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "1.0.2"
|
||||
"@napi-rs/canvas-android-arm64": "1.0.3",
|
||||
"@napi-rs/canvas-darwin-arm64": "1.0.3",
|
||||
"@napi-rs/canvas-darwin-x64": "1.0.3",
|
||||
"@napi-rs/canvas-linux-arm-gnueabihf": "1.0.3",
|
||||
"@napi-rs/canvas-linux-arm64-gnu": "1.0.3",
|
||||
"@napi-rs/canvas-linux-arm64-musl": "1.0.3",
|
||||
"@napi-rs/canvas-linux-riscv64-gnu": "1.0.3",
|
||||
"@napi-rs/canvas-linux-x64-gnu": "1.0.3",
|
||||
"@napi-rs/canvas-linux-x64-musl": "1.0.3",
|
||||
"@napi-rs/canvas-win32-arm64-msvc": "1.0.3",
|
||||
"@napi-rs/canvas-win32-x64-msvc": "1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-android-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-IMXKVQod0ol4vt3gmClUfXz4JAgHYESGPCUqmH3lQxBoL0K/2greJaQE1HVBVxWWFKfLc4OLZVdxg7kXVyXv+g==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-7kSCdUhoXiO+AaIMXdBGdtp6EctZNkmF62Rea/BmVQlwKaM3bBhOzyGUzxyxz9dv5vdBfpyAaxhSRSJF4kqK4A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3720,9 +3720,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-arm64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.2.tgz",
|
||||
"integrity": "sha512-Sc8tPi6cF+5lqOzCCKFALJHhDiRwyMzTPYm3bbhdXsOunU0lQO5f05ucyOzN2r55I23Hg5bsjH63uSCvWp3EgQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-1.0.3.tgz",
|
||||
"integrity": "sha512-ds14V1BPagLszQyaDTeggny5fNeTCqsUQ5QhFj9VDxSEfzrVxXtdbR0LoFyKa0Siaaw8KvqSk4t7k/WoZJwvbg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3741,9 +3741,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-darwin-x64": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.2.tgz",
|
||||
"integrity": "sha512-niDXZ9LhKB1zLrUdYB64RHQFDGz9rr0eGx061qtJJU3U20EMMIx28ADF5fVYbhtOgkWQrBjFicfaye1yM0U62A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-1.0.3.tgz",
|
||||
"integrity": "sha512-qof3LRAAycmkV2I1izZo9RoSHF8kCQr5O05sFwv0jK8rSdYV6KHVwimo6Qb7RxZj40WHKbLHm5JDaUF0o5XUAA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3762,9 +3762,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm-gnueabihf": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.2.tgz",
|
||||
"integrity": "sha512-sgatQL9JxGRH/Amzcvu0P3t8Am3duou74CisfuJ41Dwt8cWy723z/9KZ8LlgmxfypEwEZxSTNFJtU8d281lmhQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-1.0.3.tgz",
|
||||
"integrity": "sha512-FU2kKZLmolHA9+KcUA+l1+xH3WTLUUTQDU/kLv9SEUr2TrRPu94aytOeizFJDHPs/QBcw4QL1mCQhetQXYBbag==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -3783,9 +3783,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-dgKuX0peF3xwY6ZF5QxGS4wbfDqpoFAJYXiLSp+guZKARQUKMkRqZSDrXKj7nfrec3UCMzC0PFCPte0ES98AiA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-GVSjntxKeA+/y/ZKf1F+cmUw1WeIkE5aMRPqnZUlBTBvBcrvgWccJAWuYCKPX4QJQwZILIIwhgdAbl51yj6fpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3807,9 +3807,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-arm64-musl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.2.tgz",
|
||||
"integrity": "sha512-qwROoDIC9upfvDoRLuPn2aNg9CGW1x0Ygr4k2Or+8paA9d0qBLwk87U+g8KQpoOviKoPoiwl97kvBYuYD7qZoA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-J51oK/axyZ13kxycumSMfLiDZMdWdOVvqDFI28BpuViZHE3A0bQfr8B5vg8YnPEnqLD3BSn1hkdlh2buspEcNQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3831,9 +3831,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-riscv64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-fXRjnPihdnbO6qy1QQOgxAonb68A0TCEG7rj1x7v7rxNElsE8EVIKIEUTvyDtU+sthYSbX+8e7g3oZiLGnOmxw==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-CtQgQjoVTX67jS9XuCTtJ40Sl7wRLMguoFnnGnfDmCWf7kzKFZVwj5ynqUOIGKFMSB61ZCuQlwPvVNxYTTseaw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -3855,9 +3855,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-gnu": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.2.tgz",
|
||||
"integrity": "sha512-nPR97DXhbWIAy7yazF3jc06kEPMqYMLmPzFOVNlwKPfIoSChnI+x7dc0hTLaihz3jxrjL6j4BbA7earxfx4X3g==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-1.0.3.tgz",
|
||||
"integrity": "sha512-jtfzAHFp+FRaR7zGT4jyCe6wUgAG/dVb5A4Apd8FY9jKarntDfUAlJXscugiH7ZF5kKnu7/lHFk9LaDPcrGEVQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3879,9 +3879,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-linux-x64-musl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.2.tgz",
|
||||
"integrity": "sha512-l7zZY5+jL5qnBZtDz7CoBtY6p7EkHu422g/0zWwrOrzIwWyWxZFRfZZORY1UG7YApymPLx+UbOkN206xXn/c1Q==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-1.0.3.tgz",
|
||||
"integrity": "sha512-xTzaUCKUHTY4bCGadeeRZggbRVbGUT1petg7Z8r9AJR2+D9Bqu6nQAgqBGC6D47tA70LjaaaLTrJ7wNY1T74dg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -3903,9 +3903,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-arm64-msvc": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.2.tgz",
|
||||
"integrity": "sha512-yE0koHCFF4PIbMc2o2SEALhnipz7WBISh5glLvQiomtIoCcW0np3H4Lw93ceJAfJttTTeIIWFbwH84F7EVzjMQ==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-arm64-msvc/-/canvas-win32-arm64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-ktVLuBkI6QVOm5BwO/WbdGwxgeetAMJa7TTmR8qBarXF0OU2NKjvjUtPJAl2y8t+zBRczJl/1VOl9gua6WcK2g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -3924,9 +3924,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/canvas-win32-x64-msvc": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.2.tgz",
|
||||
"integrity": "sha512-okU8/t2foV6C31n0GtvEMbfD5rOFc70+/6xUNME9Guld29sgSOIGUEDScAWFlcP3k5TYQRl9TNkwJEEjh15w8A==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-1.0.3.tgz",
|
||||
"integrity": "sha512-SGhlQ8bDjL1Cz2KnsKMasr/5sTcwG/SZkB6WCJxLsmSm/3aS2C+3p39bA7iZ2/94+NkVDySZfbiGoaSZSFHYxA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -4160,9 +4160,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "26.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
|
||||
"integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
|
||||
"version": "26.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.2.tgz",
|
||||
"integrity": "sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -5647,9 +5647,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/chromium-bidi": {
|
||||
"version": "16.0.1",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-16.0.1.tgz",
|
||||
"integrity": "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA==",
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-17.0.2.tgz",
|
||||
"integrity": "sha512-5v9GQFhTktFvotn/OFNJBmKLKRAb6n9r0bVCwf7sHgWc3/JryK0bj1nn93L3pHFrfgcsu6Be6EWsDi+1XHTGDg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -6132,9 +6132,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/devtools-protocol": {
|
||||
"version": "0.0.1638949",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1638949.tgz",
|
||||
"integrity": "sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA==",
|
||||
"version": "0.0.1653615",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1653615.tgz",
|
||||
"integrity": "sha512-pGVkY3T/qXxAp2nFPodwYqOevk6ncNMSmvL8QfRCx5ZWGd6Vor7AFNmyaA8Zs6uJyP1QAfjuLandCgvSix1BNA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
@ -6320,9 +6320,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.24.3",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.3.tgz",
|
||||
"integrity": "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==",
|
||||
"version": "5.24.5",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
|
||||
"integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -7448,9 +7448,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
|
||||
"integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==",
|
||||
"version": "17.8.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.8.0.tgz",
|
||||
"integrity": "sha512-Zz/LMDZScFmkakeL2cTHzf+PbWKdpU3uclqkZT7TjDG58j5WPt0PpA+n9uPI24fZtlw07q0OtEi84K+umsRzqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -9279,15 +9279,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/metalsmith-html-relative": {
|
||||
"version": "2.0.12",
|
||||
"resolved": "https://registry.npmjs.org/metalsmith-html-relative/-/metalsmith-html-relative-2.0.12.tgz",
|
||||
"integrity": "sha512-VYZ0OsUbhTk/ktaDilMbgjAZowHaYXBbAd9O3zz66+iNtcWEDgrrrw2TLjxxHQPB4OCa7KqfRRFgInykTI9aUQ==",
|
||||
"version": "2.0.13",
|
||||
"resolved": "https://registry.npmjs.org/metalsmith-html-relative/-/metalsmith-html-relative-2.0.13.tgz",
|
||||
"integrity": "sha512-jG6aVx5v1cL9/mY72qBknPPF+RZluOSim3zETDxJkQt6+Ejb/AyABmNUResNFwyfh4ZjBcAKxSXgid3PKANsfA==",
|
||||
"dev": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"minimatch": "^10.2.5"
|
||||
"minimatch": "^10.2.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
@ -9364,13 +9364,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"version": "10.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
|
||||
"integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
"brace-expansion": "^5.0.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
@ -9471,9 +9471,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/modern-tar": {
|
||||
"version": "0.7.6",
|
||||
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.6.tgz",
|
||||
"integrity": "sha512-sweCIVXzx1aIGTCdzcMlSZt1h8k5Tmk08VNAuRk3IU28XamGiOH5ypi11g6De2CH7PhYqSSnGy2A/EFhbWnVKg==",
|
||||
"version": "0.7.7",
|
||||
"resolved": "https://registry.npmjs.org/modern-tar/-/modern-tar-0.7.7.tgz",
|
||||
"integrity": "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -10104,9 +10104,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.23",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz",
|
||||
"integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==",
|
||||
"version": "8.5.25",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz",
|
||||
"integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -10339,18 +10339,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer": {
|
||||
"version": "25.3.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.3.0.tgz",
|
||||
"integrity": "sha512-O1tx8S315aw8eI99HZ5ZNcVEzJ9+jKF//eO5UvfZ3cXJ6okZ5sX3Y50u7DJaM+ewEK4LqXP068tBhfRaWikj+g==",
|
||||
"version": "25.4.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-25.4.0.tgz",
|
||||
"integrity": "sha512-xfQp8dFBcGaLc1hEMaVr7s+oW4ZkAurr8Y9H81ilKhu6QoLfSTkZjU7IavnyJ/VWpB9ni3KNJUQHUatslLWyGw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "3.0.6",
|
||||
"chromium-bidi": "16.0.1",
|
||||
"devtools-protocol": "0.0.1638949",
|
||||
"chromium-bidi": "17.0.2",
|
||||
"devtools-protocol": "0.0.1653615",
|
||||
"lilconfig": "^3.1.3",
|
||||
"puppeteer-core": "25.3.0",
|
||||
"puppeteer-core": "25.4.0",
|
||||
"typed-query-selector": "^2.12.2"
|
||||
},
|
||||
"bin": {
|
||||
@ -10361,18 +10361,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer-core": {
|
||||
"version": "25.3.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.3.0.tgz",
|
||||
"integrity": "sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA==",
|
||||
"version": "25.4.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-25.4.0.tgz",
|
||||
"integrity": "sha512-K1plkLOdeoUnGeT1OvdqF3qxl33v+Ra/uH5VyPEhXdMcpvGiEskHzxxEU3fgpccJpJLIipB/rPUsvkZRWeKqOA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "3.0.6",
|
||||
"chromium-bidi": "16.0.1",
|
||||
"devtools-protocol": "0.0.1638949",
|
||||
"chromium-bidi": "17.0.2",
|
||||
"devtools-protocol": "0.0.1653615",
|
||||
"typed-query-selector": "^2.12.2",
|
||||
"webdriver-bidi-protocol": "0.4.2",
|
||||
"ws": "^8.21.0"
|
||||
"ws": "^8.21.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.12.0"
|
||||
@ -11974,9 +11974,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/test-exclude/node_modules/brace-expansion": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
|
||||
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz",
|
||||
"integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -12761,9 +12761,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.109.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.0.tgz",
|
||||
"integrity": "sha512-vomrngskVVXEZF9sMZfYAd4pXZUnfaWdJGlF+BTNF+gJBCKYCQBnOeVPlrh39Ewl7nlCsirDplMy6o5g9xJHBg==",
|
||||
"version": "5.109.2",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.2.tgz",
|
||||
"integrity": "sha512-U9/cvLzxObKNEZ9+TtdqrHM5/9z3lgl2c+c4BzbqGxFQvQvBAq87yql5A8pQ+rrMbS496MZJeF5enVBndIy2hw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -12775,7 +12775,7 @@
|
||||
"acorn": "^8.16.0",
|
||||
"browserslist": "^4.28.1",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.24.2",
|
||||
"enhanced-resolve": "^5.24.4",
|
||||
"es-module-lexer": "^2.1.0",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
@ -13162,9 +13162,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
|
||||
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
|
||||
"version": "8.21.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz",
|
||||
"integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -13240,16 +13240,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "18.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz",
|
||||
"integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==",
|
||||
"version": "18.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
|
||||
"integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^9.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"string-width": "^7.2.0",
|
||||
"string-width": "^8.2.1",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^22.0.0"
|
||||
},
|
||||
@ -13280,26 +13280,18 @@
|
||||
"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": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
|
||||
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
"get-east-asian-width": "^1.5.0",
|
||||
"strip-ansi": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"node": ">=20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
|
||||
14
package.json
14
package.json
@ -10,8 +10,8 @@
|
||||
"@fluent/dom": "^0.10.2",
|
||||
"@metalsmith/layouts": "^3.0.0",
|
||||
"@metalsmith/markdown": "^1.10.0",
|
||||
"@napi-rs/canvas": "^1.0.2",
|
||||
"@types/node": "^26.1.1",
|
||||
"@napi-rs/canvas": "^1.0.3",
|
||||
"@types/node": "^26.1.2",
|
||||
"autoprefixer": "^10.5.4",
|
||||
"babel-loader": "^10.1.1",
|
||||
"babel-plugin-istanbul": "^8.0.2",
|
||||
@ -28,7 +28,7 @@
|
||||
"eslint-plugin-prettier": "^5.5.6",
|
||||
"eslint-plugin-regexp": "^3.1.1",
|
||||
"eslint-plugin-unicorn": "^72.0.0",
|
||||
"globals": "^17.7.0",
|
||||
"globals": "^17.8.0",
|
||||
"gulp": "^5.0.1",
|
||||
"gulp-cli": "^3.1.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
@ -44,14 +44,14 @@
|
||||
"jstransformer-nunjucks": "^1.2.0",
|
||||
"kleur": "^4.1.5",
|
||||
"metalsmith": "^2.7.0",
|
||||
"metalsmith-html-relative": "^2.0.12",
|
||||
"metalsmith-html-relative": "^2.0.13",
|
||||
"ordered-read-streams": "^2.0.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postcss": "^8.5.23",
|
||||
"postcss": "^8.5.25",
|
||||
"postcss-discard-comments": "^8.0.1",
|
||||
"postcss-values-parser": "^8.0.0",
|
||||
"prettier": "^3.9.6",
|
||||
"puppeteer": "^25.3.0",
|
||||
"puppeteer": "^25.4.0",
|
||||
"stylelint": "^17.14.1",
|
||||
"stylelint-prettier": "^5.0.3",
|
||||
"svglint": "^4.2.1",
|
||||
@ -60,7 +60,7 @@
|
||||
"ttest": "^4.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"vinyl": "^3.0.1",
|
||||
"webpack": "^5.109.0",
|
||||
"webpack": "^5.109.2",
|
||||
"webpack-stream": "^7.0.0"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"stableVersion": "6.1.200",
|
||||
"baseVersion": "eddd70a2ca1054ad2e0792972c3f2774b89f0cd2",
|
||||
"versionPrefix": "6.2."
|
||||
"stableVersion": "6.2.108",
|
||||
"baseVersion": "ce4ff55faaa83b39b0137dc458af6eea6f96235f",
|
||||
"versionPrefix": "6.3."
|
||||
}
|
||||
|
||||
@ -2887,10 +2887,7 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
||||
this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
|
||||
|
||||
// Check if we have a date or time.
|
||||
const {
|
||||
data: { actions },
|
||||
} = this;
|
||||
|
||||
const { actions } = this.data;
|
||||
if (!actions) {
|
||||
return;
|
||||
}
|
||||
@ -2898,32 +2895,35 @@ class TextWidgetAnnotation extends WidgetAnnotation {
|
||||
const AFDateTime =
|
||||
/^AF(Date|Time)_(?:Keystroke|Format)(?:Ex)?\(['"]?([^'"]+)['"]?\);$/;
|
||||
let canUseHTMLDateTime = false;
|
||||
|
||||
const aFormat = actions.get("Format"),
|
||||
aKeystroke = actions.get("Keystroke");
|
||||
if (
|
||||
(actions.Format?.length === 1 &&
|
||||
actions.Keystroke?.length === 1 &&
|
||||
AFDateTime.test(actions.Format[0]) &&
|
||||
AFDateTime.test(actions.Keystroke[0])) ||
|
||||
(actions.Format?.length === 0 &&
|
||||
actions.Keystroke?.length === 1 &&
|
||||
AFDateTime.test(actions.Keystroke[0])) ||
|
||||
(actions.Keystroke?.length === 0 &&
|
||||
actions.Format?.length === 1 &&
|
||||
AFDateTime.test(actions.Format[0]))
|
||||
(aFormat?.length === 1 &&
|
||||
aKeystroke?.length === 1 &&
|
||||
AFDateTime.test(aFormat[0]) &&
|
||||
AFDateTime.test(aKeystroke[0])) ||
|
||||
(aFormat?.length === 0 &&
|
||||
aKeystroke?.length === 1 &&
|
||||
AFDateTime.test(aKeystroke[0])) ||
|
||||
(aKeystroke?.length === 0 &&
|
||||
aFormat?.length === 1 &&
|
||||
AFDateTime.test(aFormat[0]))
|
||||
) {
|
||||
// If the Format and Keystroke actions are the same, we can just use
|
||||
// the Format action.
|
||||
canUseHTMLDateTime = true;
|
||||
}
|
||||
const actionsToVisit = [];
|
||||
if (actions.Format) {
|
||||
actionsToVisit.push(...actions.Format);
|
||||
if (aFormat) {
|
||||
actionsToVisit.push(...aFormat);
|
||||
}
|
||||
if (actions.Keystroke) {
|
||||
actionsToVisit.push(...actions.Keystroke);
|
||||
if (aKeystroke) {
|
||||
actionsToVisit.push(...aKeystroke);
|
||||
}
|
||||
if (canUseHTMLDateTime) {
|
||||
delete actions.Keystroke;
|
||||
actions.Format = actionsToVisit;
|
||||
actions.delete("Keystroke");
|
||||
actions.set("Format", actionsToVisit);
|
||||
}
|
||||
|
||||
for (const formatAction of actionsToVisit) {
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
DocumentActionEventType,
|
||||
FormatError,
|
||||
info,
|
||||
makeArr,
|
||||
PermissionFlag,
|
||||
shadow,
|
||||
stringToUTF8String,
|
||||
@ -1287,10 +1288,10 @@ class Catalog {
|
||||
);
|
||||
|
||||
if (javaScript) {
|
||||
actions ??= Object.create(null);
|
||||
actions ??= new Map();
|
||||
|
||||
for (const [key, val] of javaScript) {
|
||||
(actions[key] ??= []).push(val);
|
||||
actions.getOrInsertComputed(key, makeArr).push(val);
|
||||
}
|
||||
}
|
||||
return shadow(this, "jsActions", actions);
|
||||
|
||||
@ -1428,10 +1428,9 @@ class CFFFDSelect {
|
||||
}
|
||||
|
||||
getFDIndex(glyphIndex) {
|
||||
if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
|
||||
return -1;
|
||||
}
|
||||
return this.fdSelect[glyphIndex];
|
||||
return glyphIndex < 0 || glyphIndex >= this.fdSelect.length
|
||||
? -1
|
||||
: this.fdSelect[glyphIndex];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -239,10 +239,10 @@ class ChunkedStream extends Stream {
|
||||
};
|
||||
Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
|
||||
get() {
|
||||
if (this.numChunksLoaded === this.numChunks) {
|
||||
return true;
|
||||
}
|
||||
return this.getMissingChunks().length === 0;
|
||||
return (
|
||||
this.numChunksLoaded === this.numChunks ||
|
||||
this.getMissingChunks().length === 0
|
||||
);
|
||||
},
|
||||
configurable: true,
|
||||
});
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
} from "../shared/util.js";
|
||||
import { Dict, isName, isRefsEqual, Name, Ref, RefSet } from "./primitives.js";
|
||||
import { BaseStream } from "./base_stream.js";
|
||||
import { CONTROL_CHAR_REGEXP } from "../shared/css_utils.js";
|
||||
import { stringToPDFString } from "./string_utils.js";
|
||||
|
||||
const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
|
||||
@ -341,7 +342,8 @@ function lookupNormalRect(arr, fallback) {
|
||||
* each part of the path.
|
||||
*/
|
||||
function parseXFAPath(path) {
|
||||
const positionPattern = /(.+)\[(\d+)\]$/;
|
||||
// Anchoring prevents retrying the match at every character.
|
||||
const positionPattern = /^(.+)\[(\d+)\]$/;
|
||||
return path.split(".").map(component => {
|
||||
const m = component.match(positionPattern);
|
||||
if (m) {
|
||||
@ -449,7 +451,7 @@ function _collectJS(entry, xref, list, parents) {
|
||||
}
|
||||
|
||||
function collectActions(xref, dict, eventType) {
|
||||
const actions = Object.create(null);
|
||||
const actions = new Map();
|
||||
const additionalActionsDicts = getInheritableProperty({
|
||||
dict,
|
||||
key: "AA",
|
||||
@ -475,7 +477,7 @@ function collectActions(xref, dict, eventType) {
|
||||
const list = [];
|
||||
_collectJS(rawActionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions[action] = list;
|
||||
actions.set(action, list);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -487,10 +489,10 @@ function collectActions(xref, dict, eventType) {
|
||||
const list = [];
|
||||
_collectJS(actionDict, xref, list, parents);
|
||||
if (list.length > 0) {
|
||||
actions.Action = list;
|
||||
actions.set("Action", list);
|
||||
}
|
||||
}
|
||||
return Object.keys(actions).length ? actions : null;
|
||||
return actions.size ? actions : null;
|
||||
}
|
||||
|
||||
const XMLEntities = {
|
||||
@ -560,6 +562,17 @@ function validateFontName(fontFamily, mustWarn = 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 {
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/custom-ident.
|
||||
for (const ident of fontFamily.split(/[ \t]+/)) {
|
||||
@ -574,6 +587,18 @@ function validateFontName(fontFamily, mustWarn = false) {
|
||||
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) {
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/CSS/font-style.
|
||||
const DEFAULT_CSS_FONT_OBLIQUE = "14";
|
||||
@ -750,6 +775,7 @@ export {
|
||||
lookupRect,
|
||||
MAX_INT_32,
|
||||
MissingDataException,
|
||||
normalizeCSSFontFamily,
|
||||
numberToString,
|
||||
ParserEOFException,
|
||||
parseXFAPath,
|
||||
|
||||
@ -111,10 +111,9 @@ class DecodeStream extends BaseStream {
|
||||
|
||||
async getImageData(length, decoderOptions) {
|
||||
if (!this.canAsyncDecodeImageFromBuffer) {
|
||||
if (this.isAsyncDecoder) {
|
||||
return this.decodeImage(null, length, decoderOptions);
|
||||
}
|
||||
return this.getBytes(length, decoderOptions);
|
||||
return this.isAsyncDecoder
|
||||
? this.decodeImage(null, length, decoderOptions)
|
||||
: this.getBytes(length, decoderOptions);
|
||||
}
|
||||
const data = await this.stream.asyncGetBytes();
|
||||
return this.decodeImage(data, length, decoderOptions);
|
||||
|
||||
@ -42,6 +42,7 @@ import {
|
||||
isWhiteSpace,
|
||||
lookupNormalRect,
|
||||
MissingDataException,
|
||||
normalizeCSSFontFamily,
|
||||
PDF_VERSION_REGEXP,
|
||||
RESOURCES_KEYS_OPERATOR_LIST,
|
||||
RESOURCES_KEYS_TEXT_CONTENT,
|
||||
@ -1396,9 +1397,7 @@ class PDFDocument {
|
||||
if (!(descriptor instanceof Dict)) {
|
||||
continue;
|
||||
}
|
||||
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 fontFamily = normalizeCSSFontFamily(descriptor.get("FontFamily"));
|
||||
const fontWeight = descriptor.get("FontWeight");
|
||||
|
||||
// Angle is expressed in degrees counterclockwise in PDF
|
||||
@ -1962,7 +1961,7 @@ class PDFDocument {
|
||||
const { acroForm } = annotationGlobals;
|
||||
|
||||
const visitedRefs = new RefSet();
|
||||
const allFields = Object.create(null);
|
||||
const allFields = new Map();
|
||||
const fieldPromises = new Map();
|
||||
const orphanFields = new RefSetCache();
|
||||
for (const fieldRef of acroForm.get("Fields")) {
|
||||
@ -1983,7 +1982,7 @@ class PDFDocument {
|
||||
Promise.all(promises).then(fields => {
|
||||
fields = fields.filter(field => !!field);
|
||||
if (fields.length > 0) {
|
||||
allFields[name] = fields;
|
||||
allFields.set(name, fields);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -1991,7 +1990,7 @@ class PDFDocument {
|
||||
await Promise.all(allPromises);
|
||||
|
||||
return {
|
||||
allFields: Object.keys(allFields).length ? allFields : null,
|
||||
allFields: allFields.size ? allFields : null,
|
||||
orphanFields,
|
||||
};
|
||||
});
|
||||
@ -2269,9 +2268,9 @@ class PDFDocument {
|
||||
return true;
|
||||
}
|
||||
if (fieldObjects?.allFields) {
|
||||
return Object.values(fieldObjects.allFields).some(fieldObject =>
|
||||
fieldObject.some(object => object.actions !== null)
|
||||
);
|
||||
return fieldObjects.allFields
|
||||
.values()
|
||||
.some(fieldObj => fieldObj.some(obj => obj.actions !== null));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -263,7 +263,7 @@ class PDFEditor {
|
||||
) {
|
||||
if (obj instanceof Ref) {
|
||||
const {
|
||||
currentDocument: { oldRefMapping },
|
||||
currentDocument: { fieldToParent, oldRefMapping },
|
||||
} = this;
|
||||
const existingRef = oldRefMapping.get(obj);
|
||||
if (existingRef) {
|
||||
@ -271,6 +271,12 @@ class PDFEditor {
|
||||
}
|
||||
const oldRef = obj;
|
||||
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") {
|
||||
// Simple value; no need to create a new reference.
|
||||
return obj;
|
||||
@ -299,9 +305,17 @@ 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(
|
||||
obj,
|
||||
true,
|
||||
cloneSource,
|
||||
xref,
|
||||
resourceStreamPath
|
||||
);
|
||||
@ -1223,11 +1237,8 @@ class PDFEditor {
|
||||
"Sig"
|
||||
);
|
||||
const parentRef = annotationDict.getRaw("Parent") || null;
|
||||
// We remove the parent to avoid visiting it when cloning the
|
||||
// annotation.
|
||||
// It'll be fixed later in #mergeAcroForms when merging the
|
||||
// AcroForms.
|
||||
annotationDict.delete("Parent");
|
||||
// The parent will be omitted from the annotation clone to avoid
|
||||
// visiting it, then restored by #mergeAcroForms.
|
||||
fieldToParent.put(annotationRef, parentRef);
|
||||
}
|
||||
|
||||
|
||||
@ -186,10 +186,7 @@ class Parser {
|
||||
}
|
||||
|
||||
if (typeof buf1 === "string") {
|
||||
if (cipherTransform) {
|
||||
return cipherTransform.decryptString(buf1);
|
||||
}
|
||||
return buf1;
|
||||
return cipherTransform ? cipherTransform.decryptString(buf1) : buf1;
|
||||
}
|
||||
|
||||
// simple object
|
||||
|
||||
@ -39,10 +39,7 @@ class Stream extends BaseStream {
|
||||
}
|
||||
|
||||
getByte() {
|
||||
if (this.pos >= this.end) {
|
||||
return -1;
|
||||
}
|
||||
return this.bytes[this.pos++];
|
||||
return this.pos >= this.end ? -1 : this.bytes[this.pos++];
|
||||
}
|
||||
|
||||
getBytes(length) {
|
||||
|
||||
@ -16,19 +16,15 @@
|
||||
import { stringToBytes, Util, warn } from "../shared/util.js";
|
||||
|
||||
function isAscii(str) {
|
||||
return (
|
||||
typeof str === "string" &&
|
||||
// eslint-disable-next-line no-control-regex
|
||||
(!str || /^[\x00-\x7F]*$/.test(str))
|
||||
);
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return typeof str === "string" && (!str || /^[\x00-\x7F]*$/.test(str));
|
||||
}
|
||||
|
||||
// If the string is null or undefined then it is returned as is.
|
||||
function stringToAsciiOrUTF16BE(str) {
|
||||
if (str === null || str === undefined) {
|
||||
return str;
|
||||
}
|
||||
return isAscii(str) ? str : stringToUTF16String(str, /* bigEndian = */ true);
|
||||
return str === null || str === undefined || isAscii(str)
|
||||
? str
|
||||
: stringToUTF16String(str, /* bigEndian = */ true);
|
||||
}
|
||||
|
||||
function stringToUTF16HexString(str) {
|
||||
|
||||
@ -83,10 +83,9 @@ class IdentityToUnicodeMap {
|
||||
}
|
||||
|
||||
get(i) {
|
||||
if (this.firstChar <= i && i <= this.lastChar) {
|
||||
return String.fromCharCode(i);
|
||||
}
|
||||
return undefined;
|
||||
return this.firstChar <= i && i <= this.lastChar
|
||||
? String.fromCharCode(i)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
charCodeOf(v) {
|
||||
|
||||
@ -148,6 +148,31 @@ async function writeArray(array, buffer, transform) {
|
||||
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) {
|
||||
if (value instanceof Name) {
|
||||
buffer.push(`/${escapePDFName(value.name)}`);
|
||||
@ -165,9 +190,7 @@ async function writeValue(value, buffer, transform) {
|
||||
// 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
|
||||
// current transformation matrix is well known.
|
||||
// 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+$/, ""));
|
||||
buffer.push(numberToPDFString(value));
|
||||
} else if (typeof value === "boolean") {
|
||||
buffer.push(value.toString());
|
||||
} else if (value instanceof Dict) {
|
||||
|
||||
@ -190,11 +190,9 @@ class Builder {
|
||||
if (hasNamespace) {
|
||||
this._currentNamespace = this._namespaceStack.pop();
|
||||
}
|
||||
if (prefixes) {
|
||||
prefixes.forEach(({ prefix }) => {
|
||||
this._namespacePrefixes.get(prefix).pop();
|
||||
});
|
||||
}
|
||||
prefixes?.forEach(({ prefix }) => {
|
||||
this._namespacePrefixes.get(prefix).pop();
|
||||
});
|
||||
if (nsAgnostic) {
|
||||
this._nsAgnosticLevel--;
|
||||
}
|
||||
|
||||
@ -154,10 +154,7 @@ class FontFinder {
|
||||
|
||||
function selectFont(xfaFont, typeface) {
|
||||
if (xfaFont.posture === "italic") {
|
||||
if (xfaFont.weight === "bold") {
|
||||
return typeface.bolditalic;
|
||||
}
|
||||
return typeface.italic;
|
||||
return xfaFont.weight === "bold" ? typeface.bolditalic : typeface.italic;
|
||||
} else if (xfaFont.weight === "bold") {
|
||||
return typeface.bold;
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ import {
|
||||
import { createValidAbsoluteUrl, warn } from "../../shared/util.js";
|
||||
import { getMeasurement, stripQuotes } from "./utils.js";
|
||||
import { selectFont } from "./fonts.js";
|
||||
import { serializeFontFamily } from "../../shared/css_utils.js";
|
||||
import { TextMeasure } from "./text.js";
|
||||
import { XFAObject } from "./xfa_object.js";
|
||||
|
||||
@ -597,13 +598,16 @@ function setFontFamily(xfaFont, node, fontFinder, style) {
|
||||
}
|
||||
|
||||
const name = stripQuotes(xfaFont.typeface);
|
||||
style.fontFamily = `"${name}"`;
|
||||
// Use the same serialization as the `@font-face` rule, resp. the `FontFace`
|
||||
// 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);
|
||||
if (typeface) {
|
||||
const { fontFamily } = typeface.regular.cssFontInfo;
|
||||
if (fontFamily !== name) {
|
||||
style.fontFamily = `"${fontFamily}"`;
|
||||
style.fontFamily = serializeFontFamily(fontFamily);
|
||||
}
|
||||
|
||||
const para = getCurrentPara(node);
|
||||
|
||||
@ -6073,10 +6073,9 @@ class Value extends XFAObject {
|
||||
|
||||
[$text]() {
|
||||
if (this.exData) {
|
||||
if (typeof this.exData[$content] === "string") {
|
||||
return this.exData[$content].trim();
|
||||
}
|
||||
return this.exData[$content][$text]().trim();
|
||||
return typeof this.exData[$content] === "string"
|
||||
? this.exData[$content].trim()
|
||||
: this.exData[$content][$text]().trim();
|
||||
}
|
||||
for (const name of Object.getOwnPropertyNames(this)) {
|
||||
if (name === "image") {
|
||||
|
||||
@ -284,10 +284,9 @@ class XFAObject {
|
||||
}
|
||||
|
||||
[$text]() {
|
||||
if (this[_children].length === 0) {
|
||||
return this[$content];
|
||||
}
|
||||
return this[_children].map(c => c[$text]()).join("");
|
||||
return this[_children].length === 0
|
||||
? this[$content]
|
||||
: this[_children].map(c => c[$text]()).join("");
|
||||
}
|
||||
|
||||
get [_attributeNames]() {
|
||||
@ -329,11 +328,7 @@ class XFAObject {
|
||||
}
|
||||
|
||||
[$getChildren](name = null) {
|
||||
if (!name) {
|
||||
return this[_children];
|
||||
}
|
||||
|
||||
return this[name];
|
||||
return !name ? this[_children] : this[name];
|
||||
}
|
||||
|
||||
[$dump]() {
|
||||
@ -680,11 +675,9 @@ class XFAObject {
|
||||
}
|
||||
|
||||
[$getChildren](name = null) {
|
||||
if (!name) {
|
||||
return this[_children];
|
||||
}
|
||||
|
||||
return this[_children].filter(c => c[$nodeName] === name);
|
||||
return !name
|
||||
? this[_children]
|
||||
: this[_children].filter(c => c[$nodeName] === name);
|
||||
}
|
||||
|
||||
[$getChildrenByClass](name) {
|
||||
@ -909,11 +902,9 @@ class XmlObject extends XFAObject {
|
||||
}
|
||||
|
||||
[$getChildren](name = null) {
|
||||
if (!name) {
|
||||
return this[_children];
|
||||
}
|
||||
|
||||
return this[_children].filter(c => c[$nodeName] === name);
|
||||
return !name
|
||||
? this[_children]
|
||||
: this[_children].filter(c => c[$nodeName] === name);
|
||||
}
|
||||
|
||||
[$getAttributes]() {
|
||||
|
||||
@ -49,16 +49,17 @@ function isWhitespaceString(s) {
|
||||
|
||||
class XMLParserBase {
|
||||
static get _entityRegex() {
|
||||
return shadow(this, "_entityRegex", /&(?:#x([^;]+)|#([^;]+)|([^;]+));/g);
|
||||
// Entity references cannot contain "&", keeping the scan linear.
|
||||
return shadow(this, "_entityRegex", /&(?:#x([^;&]+)|#([^;&]+)|([^;&]+));/g);
|
||||
}
|
||||
|
||||
_resolveEntities(s) {
|
||||
return s.replaceAll(XMLParserBase._entityRegex, (_, hex, dec, entity) => {
|
||||
if (hex) {
|
||||
return String.fromCodePoint(parseInt(hex, 16));
|
||||
}
|
||||
if (dec) {
|
||||
return String.fromCodePoint(parseInt(dec, 10));
|
||||
return s.replaceAll(XMLParserBase._entityRegex, (all, hex, dec, entity) => {
|
||||
if (hex || dec) {
|
||||
const code = hex ? parseInt(hex, 16) : parseInt(dec, 10);
|
||||
// An out-of-range or unparsable code point is kept as-is, since
|
||||
// `String.fromCodePoint` would throw on it.
|
||||
return code >= 0 && code <= 0x10ffff ? String.fromCodePoint(code) : all;
|
||||
}
|
||||
switch (entity) {
|
||||
case "lt":
|
||||
@ -324,10 +325,9 @@ class SimpleDOMNode {
|
||||
}
|
||||
|
||||
get textContent() {
|
||||
if (!this.childNodes) {
|
||||
return this.nodeValue || "";
|
||||
}
|
||||
return this.childNodes.map(child => child.textContent).join("");
|
||||
return !this.childNodes
|
||||
? this.nodeValue || ""
|
||||
: this.childNodes.map(child => child.textContent).join("");
|
||||
}
|
||||
|
||||
get children() {
|
||||
|
||||
@ -75,7 +75,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
* @property {Object} svgFactory
|
||||
* @property {boolean} [enableScripting]
|
||||
* @property {boolean} [hasJSActions]
|
||||
* @property {Object} [fieldObjects]
|
||||
* @property {Map} [fieldObjects]
|
||||
*/
|
||||
|
||||
class AnnotationElementFactory {
|
||||
@ -803,7 +803,7 @@ class AnnotationElement {
|
||||
const fields = [];
|
||||
|
||||
if (this._fieldObjects) {
|
||||
const fieldObj = this._fieldObjects[name] || [];
|
||||
const fieldObj = this._fieldObjects.get(name) || [];
|
||||
|
||||
for (const { page, id, exportValues } of fieldObj) {
|
||||
if (page === -1) {
|
||||
@ -1012,9 +1012,9 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
} else {
|
||||
if (
|
||||
data.actions &&
|
||||
(data.actions.Action ||
|
||||
data.actions["Mouse Up"] ||
|
||||
data.actions["Mouse Down"]) &&
|
||||
(data.actions.has("Action") ||
|
||||
data.actions.has("Mouse Up") ||
|
||||
data.actions.has("Mouse Down")) &&
|
||||
this.enableScripting &&
|
||||
this.hasJSActions
|
||||
) {
|
||||
@ -1158,14 +1158,14 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
* @param {Object} data
|
||||
* @memberof LinkAnnotationElement
|
||||
*/
|
||||
_bindJSAction(link, data) {
|
||||
_bindJSAction(link, { actions, id, overlaidText }) {
|
||||
link.href = this.linkService.getAnchorUrl("");
|
||||
const map = new Map([
|
||||
["Action", "onclick"],
|
||||
["Mouse Up", "onmouseup"],
|
||||
["Mouse Down", "onmousedown"],
|
||||
]);
|
||||
for (const name of Object.keys(data.actions)) {
|
||||
for (const name of actions.keys()) {
|
||||
const jsName = map.get(name);
|
||||
if (!jsName) {
|
||||
continue;
|
||||
@ -1173,16 +1173,13 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
link[jsName] = () => {
|
||||
this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
|
||||
source: this,
|
||||
detail: {
|
||||
id: data.id,
|
||||
name,
|
||||
},
|
||||
detail: { id, name },
|
||||
});
|
||||
return false;
|
||||
};
|
||||
}
|
||||
if (data.overlaidText) {
|
||||
link.title = data.overlaidText;
|
||||
if (overlaidText) {
|
||||
link.title = overlaidText;
|
||||
}
|
||||
link.onclick ||= () => false;
|
||||
|
||||
@ -1220,12 +1217,12 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
|
||||
const fieldIds = new Set(resetFormRefs);
|
||||
for (const fieldName of resetFormFields) {
|
||||
const fields = this._fieldObjects[fieldName] || [];
|
||||
const fields = this._fieldObjects.get(fieldName) || [];
|
||||
for (const { id } of fields) {
|
||||
fieldIds.add(id);
|
||||
}
|
||||
}
|
||||
for (const fields of Object.values(this._fieldObjects)) {
|
||||
for (const fields of this._fieldObjects.values()) {
|
||||
for (const field of fields) {
|
||||
if (fieldIds.has(field.id) === include) {
|
||||
allFields.push(field);
|
||||
@ -1233,7 +1230,7 @@ class LinkAnnotationElement extends AnnotationElement {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const fields of Object.values(this._fieldObjects)) {
|
||||
for (const fields of this._fieldObjects.values()) {
|
||||
allFields.push(...fields);
|
||||
}
|
||||
}
|
||||
@ -1379,8 +1376,10 @@ class WidgetAnnotationElement extends AnnotationElement {
|
||||
}
|
||||
|
||||
_setEventListeners(element, elementData, names, getter) {
|
||||
const { actions } = this.data;
|
||||
|
||||
for (const [baseName, eventName] of names) {
|
||||
if (eventName === "Action" || this.data.actions?.[eventName]) {
|
||||
if (eventName === "Action" || actions?.has(eventName)) {
|
||||
if (eventName === "Focus" || eventName === "Blur") {
|
||||
elementData ||= { focused: false };
|
||||
}
|
||||
@ -1391,10 +1390,10 @@ class WidgetAnnotationElement extends AnnotationElement {
|
||||
eventName,
|
||||
getter
|
||||
);
|
||||
if (eventName === "Focus" && !this.data.actions?.Blur) {
|
||||
if (eventName === "Focus" && !actions?.has("Blur")) {
|
||||
// Ensure that elementData will have the correct value.
|
||||
this._setEventListener(element, elementData, "blur", "Blur", null);
|
||||
} else if (eventName === "Blur" && !this.data.actions?.Focus) {
|
||||
} else if (eventName === "Blur" && !actions?.has("Focus")) {
|
||||
this._setEventListener(element, elementData, "focus", "Focus", null);
|
||||
}
|
||||
}
|
||||
@ -1630,7 +1629,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
}
|
||||
elementData.lastCommittedValue = target.value;
|
||||
elementData.commitKey = 1;
|
||||
if (!this.data.actions?.Focus) {
|
||||
if (!this.data.actions?.has("Focus")) {
|
||||
elementData.focused = true;
|
||||
}
|
||||
});
|
||||
@ -1752,7 +1751,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
if (!elementData.focused || !event.relatedTarget) {
|
||||
return;
|
||||
}
|
||||
if (!this.data.actions?.Blur) {
|
||||
if (!this.data.actions?.has("Blur")) {
|
||||
elementData.focused = false;
|
||||
}
|
||||
const { target } = event;
|
||||
@ -1800,7 +1799,7 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
_blurListener(event);
|
||||
});
|
||||
|
||||
if (this.data.actions?.Keystroke) {
|
||||
if (this.data.actions?.has("Keystroke")) {
|
||||
element.addEventListener("beforeinput", event => {
|
||||
elementData.lastCommittedValue = null;
|
||||
const { data, target } = event;
|
||||
@ -1812,11 +1811,21 @@ class TextWidgetAnnotationElement extends WidgetAnnotationElement {
|
||||
switch (event.inputType) {
|
||||
// https://rawgit.com/w3c/input-events/v1/index.html#interface-InputEvent-Attributes
|
||||
case "deleteWordBackward": {
|
||||
const match = value
|
||||
.substring(0, selectionStart)
|
||||
.match(/\w*\W*$/);
|
||||
if (match) {
|
||||
selStart -= match[0].length;
|
||||
// The previous unanchored regex could take quadratic time, so
|
||||
// scan backwards over the trailing non-word characters and
|
||||
// then the word.
|
||||
const wordCharPattern = /\w/;
|
||||
while (
|
||||
selStart > 0 &&
|
||||
!wordCharPattern.test(value[selStart - 1])
|
||||
) {
|
||||
selStart--;
|
||||
}
|
||||
while (
|
||||
selStart > 0 &&
|
||||
wordCharPattern.test(value[selStart - 1])
|
||||
) {
|
||||
selStart--;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -3123,10 +3132,7 @@ class PopupElement {
|
||||
}
|
||||
|
||||
get isVisible() {
|
||||
if (this.#commentManager) {
|
||||
return false;
|
||||
}
|
||||
return this.#container.hidden === false;
|
||||
return !this.#commentManager && this.#container.hidden === false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -3947,7 +3953,7 @@ class MediaAnnotationElement extends AnnotationElement {
|
||||
* @property {boolean} [enableScripting] - Enable embedded script execution.
|
||||
* @property {boolean} [hasJSActions] - Some fields have JS actions.
|
||||
* The default value is `false`.
|
||||
* @property {Object<string, Array<Object>> | null} [fieldObjects]
|
||||
* @property {Map<string, Array<Object>> | null} [fieldObjects]
|
||||
* @property {Map<string, HTMLCanvasElement>} [annotationCanvasMap]
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||
|
||||
@ -867,8 +867,8 @@ class PDFDocumentProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object | null>} A promise that is resolved with
|
||||
* an {Object} with the JavaScript actions:
|
||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
||||
* the JavaScript actions:
|
||||
* - from the name tree.
|
||||
* - from A or AA entries in the catalog dictionary.
|
||||
* , or `null` if no JavaScript exists.
|
||||
@ -1069,9 +1069,9 @@ class PDFDocumentProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object<string, Array<Object>> | null>} A promise that is
|
||||
* resolved with an {Object} containing /AcroForm field data for the JS
|
||||
* sandbox, or `null` when no field data is present in the PDF file.
|
||||
* @returns {Promise<Map<string, Array<Object>> | null>} A promise that is
|
||||
* resolved with a {Map} containing /AcroForm field data for the JS sandbox,
|
||||
* or `null` when no field data is present in the PDF file.
|
||||
*/
|
||||
getFieldObjects() {
|
||||
return this._transport.getFieldObjects();
|
||||
@ -1429,8 +1429,8 @@ class PDFPageProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<Object>} A promise that is resolved with an
|
||||
* {Object} with JS actions.
|
||||
* @returns {Promise<Map | null>} A promise that is resolved with a {Map} with
|
||||
* the JavaScript actions, or `null` if no JavaScript exists.
|
||||
*/
|
||||
getJSActions() {
|
||||
return this._transport.getPageJSActions(this._pageIndex);
|
||||
|
||||
@ -188,10 +188,7 @@ class CanvasBBoxTracker {
|
||||
}
|
||||
|
||||
getOpenMarker() {
|
||||
if (this._savesStack.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return this._savesStack.at(-1);
|
||||
return this._savesStack.length === 0 ? null : this._savesStack.at(-1);
|
||||
}
|
||||
|
||||
recordCloseMarker(opIdx, onSavePopped) {
|
||||
|
||||
@ -188,10 +188,22 @@ function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
|
||||
}
|
||||
|
||||
if (newURL.hash) {
|
||||
const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
|
||||
const hashFilename = reFilename.exec(newURL.hash);
|
||||
if (hashFilename) {
|
||||
return decode(hashFilename[0]);
|
||||
// Locate the last ".pdf" and then extend it to the left, up to the closest
|
||||
// separator. Both steps are linear, whereas a single pattern starting with
|
||||
// `[^/?#=]+` is quadratic on a hash which contains no ".pdf" at all.
|
||||
const { hash } = newURL;
|
||||
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,7 +641,8 @@ class DrawLayer {
|
||||
textLayerData.selectionDiv = div;
|
||||
}
|
||||
|
||||
if (!div.parentNode && drawLayer.#parent) {
|
||||
if (drawLayer.#parent && div.parentNode !== drawLayer.#parent) {
|
||||
// The div can still be in a canvas wrapper which has been removed.
|
||||
drawLayer.#parent.append(div);
|
||||
this.#selections.add(div);
|
||||
}
|
||||
|
||||
@ -131,17 +131,15 @@ class AltText {
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
if (this.#useNewAltTextFlow) {
|
||||
return this.#altText === null;
|
||||
}
|
||||
return !this.#altText && !this.#altTextDecorative;
|
||||
return this.#useNewAltTextFlow
|
||||
? this.#altText === null
|
||||
: !this.#altText && !this.#altTextDecorative;
|
||||
}
|
||||
|
||||
hasData() {
|
||||
if (this.#useNewAltTextFlow) {
|
||||
return this.#altText !== null || !!this.#guessedText;
|
||||
}
|
||||
return this.isEmpty();
|
||||
return this.#useNewAltTextFlow
|
||||
? this.#altText !== null || !!this.#guessedText
|
||||
: this.isEmpty();
|
||||
}
|
||||
|
||||
get guessedText() {
|
||||
|
||||
@ -622,10 +622,7 @@ class InkDrawOutline extends Outline {
|
||||
}
|
||||
|
||||
updateProperty(name, value) {
|
||||
if (name === "stroke-width") {
|
||||
return this.#updateThickness(value);
|
||||
}
|
||||
return null;
|
||||
return name === "stroke-width" ? this.#updateThickness(value) : null;
|
||||
}
|
||||
|
||||
#updateThickness(thickness) {
|
||||
|
||||
@ -1359,16 +1359,7 @@ class AnnotationEditor {
|
||||
|
||||
bindEvents(this, div, ["keydown", "pointerdown", "dblclick"]);
|
||||
|
||||
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.#addTouchManager();
|
||||
|
||||
this.addStandaloneCommentButton();
|
||||
this._uiManager._editorUndoBar?.hide();
|
||||
@ -1803,6 +1794,25 @@ class AnnotationEditor {
|
||||
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.
|
||||
*
|
||||
@ -1810,6 +1820,7 @@ class AnnotationEditor {
|
||||
*/
|
||||
rebuild() {
|
||||
this.#addFocusListeners();
|
||||
this.#addTouchManager();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -254,10 +254,9 @@ class SignatureEditor extends DrawingEditor {
|
||||
|
||||
/** @inheritdoc */
|
||||
get toolbarButtons() {
|
||||
if (this._uiManager.signatureManager) {
|
||||
return [["editSignature", this._uiManager.signatureManager]];
|
||||
}
|
||||
return super.toolbarButtons;
|
||||
return this._uiManager.signatureManager
|
||||
? [["editSignature", this._uiManager.signatureManager]]
|
||||
: super.toolbarButtons;
|
||||
}
|
||||
|
||||
addSignature(data, heightInPage, description, uuid) {
|
||||
|
||||
@ -22,6 +22,7 @@ import {
|
||||
warn,
|
||||
} from "../shared/util.js";
|
||||
import { makePathFromDrawOPS } from "./display_utils.js";
|
||||
import { serializeFontFamily } from "../shared/css_utils.js";
|
||||
|
||||
class FontLoader {
|
||||
#systemFonts = new Set();
|
||||
@ -439,7 +440,7 @@ class FontFaceObject {
|
||||
css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
|
||||
}
|
||||
nativeFontFace = new FontFace(
|
||||
this.cssFontInfo.fontFamily,
|
||||
serializeFontFamily(this.cssFontInfo.fontFamily),
|
||||
this.data,
|
||||
css
|
||||
);
|
||||
@ -463,7 +464,10 @@ class FontFaceObject {
|
||||
if (this.cssFontInfo.italicAngle) {
|
||||
css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
|
||||
}
|
||||
rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`;
|
||||
// The font family originates from the PDF document, hence it must be
|
||||
// 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);
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
ensureResponseOrigin,
|
||||
extractFilenameFromHeader,
|
||||
getResponseOrigin,
|
||||
trimHeadersEnd,
|
||||
validateRangeRequestCapabilities,
|
||||
} from "./network_utils.js";
|
||||
import { endRequests } from "./transport_stream.js";
|
||||
@ -208,9 +209,7 @@ class PDFNetworkStreamReader extends BasePDFStreamReader {
|
||||
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
|
||||
const responseHeaders = new Headers(
|
||||
rawResponseHeaders
|
||||
? rawResponseHeaders
|
||||
.trimStart()
|
||||
.replace(/[^\S ]+$/, "") // Not `trimEnd`, to keep regular spaces.
|
||||
? trimHeadersEnd(rawResponseHeaders.trimStart())
|
||||
.split(/[\r\n]+/)
|
||||
.map(x => {
|
||||
const [key, ...val] = x.split(": ");
|
||||
|
||||
@ -32,6 +32,17 @@ function createHeaders(isHttp, httpHeaders) {
|
||||
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) {
|
||||
// Notably, null is distinct from "null" string (e.g. from file:-URLs).
|
||||
return URL.parse(url)?.origin ?? null;
|
||||
@ -117,5 +128,6 @@ export {
|
||||
ensureResponseOrigin,
|
||||
extractFilenameFromHeader,
|
||||
getResponseOrigin,
|
||||
trimHeadersEnd,
|
||||
validateRangeRequestCapabilities,
|
||||
};
|
||||
|
||||
@ -15,6 +15,10 @@
|
||||
|
||||
import { OutputScale, stopEvent } from "./display_utils.js";
|
||||
|
||||
function preventDefault(evt) {
|
||||
evt.preventDefault();
|
||||
}
|
||||
|
||||
class TouchManager {
|
||||
#container;
|
||||
|
||||
@ -135,8 +139,13 @@ class TouchManager {
|
||||
opt.capture = true;
|
||||
container.addEventListener("pointerdown", stopEvent, opt);
|
||||
container.addEventListener("pointermove", stopEvent, opt);
|
||||
container.addEventListener("pointercancel", stopEvent, opt);
|
||||
container.addEventListener("pointerup", stopEvent, opt);
|
||||
// `pointerup` and `pointercancel` are only default-prevented: a
|
||||
// `stopPropagation` in the capture phase also skips the bubble-phase
|
||||
// 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?.();
|
||||
}
|
||||
|
||||
@ -189,7 +198,7 @@ class TouchManager {
|
||||
const pDistance = Math.hypot(prevGapX, prevGapY) || 1;
|
||||
if (
|
||||
!this.#isPinching &&
|
||||
Math.abs(pDistance - distance) <= TouchManager.MIN_TOUCH_DISTANCE_TO_PINCH
|
||||
Math.abs(pDistance - distance) <= this.MIN_TOUCH_DISTANCE_TO_PINCH
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -207,7 +216,12 @@ class TouchManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const origin = [(screen0X + screen1X) / 2, (screen0Y + screen1Y) / 2];
|
||||
// The distances are in screen CSS pixels, but the origin must be in client
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
@ -130,18 +130,12 @@ export class SandboxSupportBase {
|
||||
}
|
||||
this.win.alert(cMsg);
|
||||
},
|
||||
confirm: cMsg => {
|
||||
if (typeof cMsg !== "string") {
|
||||
return false;
|
||||
}
|
||||
return this.win.confirm(cMsg);
|
||||
},
|
||||
prompt: (cQuestion, cDefault) => {
|
||||
if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
|
||||
return null;
|
||||
}
|
||||
return this.win.prompt(cQuestion, cDefault);
|
||||
},
|
||||
confirm: cMsg =>
|
||||
typeof cMsg !== "string" ? false : this.win.confirm(cMsg),
|
||||
prompt: (cQuestion, cDefault) =>
|
||||
typeof cQuestion !== "string" || typeof cDefault !== "string"
|
||||
? null
|
||||
: this.win.prompt(cQuestion, cDefault),
|
||||
parseURL: cUrl => {
|
||||
const url = new this.win.URL(cUrl);
|
||||
const props = [
|
||||
|
||||
@ -19,7 +19,9 @@ class SandboxSupport extends SandboxSupportBase {
|
||||
exportValueToSandbox(val) {
|
||||
// The communication with the Quickjs sandbox is based on strings
|
||||
// So we use JSON.stringfy to serialize
|
||||
return JSON.stringify(val);
|
||||
return JSON.stringify(val, (k, v) =>
|
||||
v instanceof Map ? Object.fromEntries(v) : v
|
||||
);
|
||||
}
|
||||
|
||||
importValueFromSandbox(val) {
|
||||
@ -65,7 +67,7 @@ class Sandbox {
|
||||
let success = false;
|
||||
let buf = 0;
|
||||
try {
|
||||
const sandboxData = JSON.stringify(data);
|
||||
const sandboxData = this.support.exportValueToSandbox(data);
|
||||
// "pdfjsScripting.initSandbox..." MUST be the last line to be evaluated
|
||||
// since the returned value is used for the communication.
|
||||
code.push(`pdfjsScripting.initSandbox({ data: ${sandboxData} })`);
|
||||
|
||||
@ -52,11 +52,9 @@ class AForm {
|
||||
}
|
||||
|
||||
AFMergeChange(event = globalThis.event) {
|
||||
if (event.willCommit) {
|
||||
return event.value.toString();
|
||||
}
|
||||
|
||||
return this._app._eventDispatcher.mergeChange(event);
|
||||
return event.willCommit
|
||||
? event.value.toString()
|
||||
: this._app._eventDispatcher.mergeChange(event);
|
||||
}
|
||||
|
||||
AFParseDateEx(cString, cOrder) {
|
||||
@ -102,10 +100,7 @@ class AForm {
|
||||
}
|
||||
|
||||
AFMakeArrayFromList(string) {
|
||||
if (typeof string === "string") {
|
||||
return string.split(/, ?/g);
|
||||
}
|
||||
return string;
|
||||
return typeof string === "string" ? string.split(/, ?/g) : string;
|
||||
}
|
||||
|
||||
AFNumber_Format(
|
||||
@ -616,11 +611,9 @@ class AForm {
|
||||
}
|
||||
|
||||
AFExactMatch(rePatterns, str) {
|
||||
if (rePatterns instanceof RegExp) {
|
||||
return str.match(rePatterns)?.[0] === str || 0;
|
||||
}
|
||||
|
||||
return rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;
|
||||
return rePatterns instanceof RegExp
|
||||
? str.match(rePatterns)?.[0] === str || 0
|
||||
: rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,8 +21,8 @@ const FieldType = {
|
||||
time: 4,
|
||||
};
|
||||
|
||||
function createActionsMap(actions) {
|
||||
return new Map(actions ? Object.entries(actions) : null);
|
||||
function createMap(val) {
|
||||
return val instanceof Map ? val : new Map(val ? Object.entries(val) : null);
|
||||
}
|
||||
|
||||
function getFieldType(actions) {
|
||||
@ -30,10 +30,8 @@ function getFieldType(actions) {
|
||||
if (!format) {
|
||||
return FieldType.none;
|
||||
}
|
||||
format = format[0].trim();
|
||||
|
||||
format = format[0];
|
||||
|
||||
format = format.trim();
|
||||
if (format.startsWith("AFNumber_")) {
|
||||
return FieldType.number;
|
||||
}
|
||||
@ -49,4 +47,4 @@ function getFieldType(actions) {
|
||||
return FieldType.none;
|
||||
}
|
||||
|
||||
export { createActionsMap, FieldType, getFieldType };
|
||||
export { createMap, FieldType, getFieldType };
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
import { makeArr, makeMap, serializeError } from "./app_utils.js";
|
||||
import { createActionsMap } from "./common.js";
|
||||
import { createMap } from "./common.js";
|
||||
import { PDFObject } from "./pdf_object.js";
|
||||
import { PrintParams } from "./print_params.js";
|
||||
import { ZoomType } from "./constants.js";
|
||||
@ -98,7 +98,7 @@ class Doc extends PDFObject {
|
||||
|
||||
this._zoomType = ZoomType.none;
|
||||
this._zoom = data.zoom || 100;
|
||||
this._actions = createActionsMap(data.actions);
|
||||
this._actions = createMap(data.actions);
|
||||
this._globalEval = data.globalEval;
|
||||
this._userActivation = false;
|
||||
this._disablePrinting = false;
|
||||
@ -173,9 +173,9 @@ class Doc extends PDFObject {
|
||||
_dispatchPageEvent(name, actions, pageNumber) {
|
||||
if (name === "PageOpen") {
|
||||
this.#pageActions ??= new Map();
|
||||
if (!this.#pageActions.has(pageNumber)) {
|
||||
this.#pageActions.set(pageNumber, createActionsMap(actions));
|
||||
}
|
||||
this.#pageActions.getOrInsertComputed(pageNumber, () =>
|
||||
createMap(actions)
|
||||
);
|
||||
this._pageNum = pageNumber - 1;
|
||||
}
|
||||
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createActionsMap, FieldType, getFieldType } from "./common.js";
|
||||
import { createMap, FieldType, getFieldType } from "./common.js";
|
||||
import { makeArr, serializeError } from "./app_utils.js";
|
||||
import { Color } from "./color.js";
|
||||
import { PDFObject } from "./pdf_object.js";
|
||||
import { serializeError } from "./app_utils.js";
|
||||
|
||||
class Field extends PDFObject {
|
||||
constructor(data) {
|
||||
@ -65,7 +65,7 @@ class Field extends PDFObject {
|
||||
this.userName = data.userName;
|
||||
|
||||
// Private
|
||||
this._actions = createActionsMap(data.actions);
|
||||
this._actions = createMap(data.actions);
|
||||
this._browseForFileToSubmit = data.browseForFileToSubmit || null;
|
||||
this._buttonCaption = null;
|
||||
this._buttonIcon = null;
|
||||
@ -98,10 +98,7 @@ class Field extends PDFObject {
|
||||
}
|
||||
|
||||
get currentValueIndices() {
|
||||
if (!this._isChoice) {
|
||||
return 0;
|
||||
}
|
||||
return this._currentValueIndices;
|
||||
return !this._isChoice ? 0 : this._currentValueIndices;
|
||||
}
|
||||
|
||||
set currentValueIndices(indices) {
|
||||
@ -499,10 +496,7 @@ class Field extends PDFObject {
|
||||
if (typeof cTrigger !== "string" || typeof cScript !== "string") {
|
||||
return;
|
||||
}
|
||||
if (!(cTrigger in this._actions)) {
|
||||
this._actions[cTrigger] = [];
|
||||
}
|
||||
this._actions[cTrigger].push(cScript);
|
||||
this._actions.getOrInsertComputed(cTrigger, makeArr).push(cScript);
|
||||
}
|
||||
|
||||
setFocus() {
|
||||
@ -584,7 +578,7 @@ class RadioButtonField extends Field {
|
||||
for (const radioData of otherButtons) {
|
||||
this.exportValues.push(radioData.exportValues);
|
||||
this._radioIds.push(radioData.id);
|
||||
this._radioActions.push(createActionsMap(radioData.actions));
|
||||
this._radioActions.push(createMap(radioData.actions));
|
||||
if (this._value === radioData.exportValues) {
|
||||
this._id = radioData.id;
|
||||
}
|
||||
@ -683,17 +677,13 @@ class CheckboxField extends RadioButtonField {
|
||||
}
|
||||
|
||||
isBoxChecked(nWidget) {
|
||||
if (this._value === "Off") {
|
||||
return false;
|
||||
}
|
||||
return super.isBoxChecked(nWidget);
|
||||
return this._value === "Off" ? false : super.isBoxChecked(nWidget);
|
||||
}
|
||||
|
||||
isDefaultChecked(nWidget) {
|
||||
if (this.defaultValue === "Off") {
|
||||
return this._value === "Off";
|
||||
}
|
||||
return super.isDefaultChecked(nWidget);
|
||||
return this.defaultValue === "Off"
|
||||
? this._value === "Off"
|
||||
: super.isDefaultChecked(nWidget);
|
||||
}
|
||||
|
||||
checkThisBox(nWidget, bCheckIt = true) {
|
||||
|
||||
@ -32,6 +32,7 @@ import { AForm } from "./aform.js";
|
||||
import { App } from "./app.js";
|
||||
import { Color } from "./color.js";
|
||||
import { Console } from "./console.js";
|
||||
import { createMap } from "./common.js";
|
||||
import { Doc } from "./doc.js";
|
||||
import { ProxyHandler } from "./proxy.js";
|
||||
import { serializeError } from "./app_utils.js";
|
||||
@ -70,61 +71,57 @@ function initSandbox(params) {
|
||||
const util = new Util({ externalCall });
|
||||
const appObjects = app._objects;
|
||||
|
||||
if (data.objects) {
|
||||
for (const [name, objs] of createMap(data.objects)) {
|
||||
const annotations = [];
|
||||
let container = null;
|
||||
|
||||
for (const [name, objs] of Object.entries(data.objects)) {
|
||||
annotations.length = 0;
|
||||
let container = null;
|
||||
for (const obj of objs) {
|
||||
if (obj.type !== "") {
|
||||
annotations.push(obj);
|
||||
} else {
|
||||
container = obj;
|
||||
}
|
||||
}
|
||||
|
||||
for (const obj of objs) {
|
||||
if (obj.type !== "") {
|
||||
annotations.push(obj);
|
||||
} else {
|
||||
container = obj;
|
||||
let obj = container;
|
||||
if (annotations.length > 0) {
|
||||
obj = annotations[0];
|
||||
obj.send = send;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
let obj = container;
|
||||
if (annotations.length > 0) {
|
||||
obj = annotations[0];
|
||||
obj.send = send;
|
||||
}
|
||||
|
||||
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);
|
||||
const _object = { obj: field, wrapped };
|
||||
doc._addField(name, _object);
|
||||
for (const object of objs) {
|
||||
appObjects[object.id] = _object;
|
||||
}
|
||||
if (container) {
|
||||
appObjects[container.id] = _object;
|
||||
}
|
||||
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,13 +43,10 @@ class ProxyHandler {
|
||||
}
|
||||
|
||||
set(obj, prop, value) {
|
||||
if (obj._kidIds) {
|
||||
// If the field is a container for other fields then
|
||||
// dispatch the kids.
|
||||
obj._kidIds.forEach(id => {
|
||||
obj._appObjects[id].wrapped[prop] = value;
|
||||
});
|
||||
}
|
||||
// If the field is a container for other fields then dispatch the kids.
|
||||
obj._kidIds?.forEach(id => {
|
||||
obj._appObjects[id].wrapped[prop] = value;
|
||||
});
|
||||
|
||||
if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
|
||||
const old = obj[prop];
|
||||
|
||||
@ -252,10 +252,9 @@ class Util extends PDFObject {
|
||||
const patterns =
|
||||
/(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) {
|
||||
if (pattern in handlers) {
|
||||
return handlers[pattern](data);
|
||||
}
|
||||
return pattern.charCodeAt(1);
|
||||
return pattern in handlers
|
||||
? handlers[pattern](data)
|
||||
: pattern.charCodeAt(1);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
76
src/shared/css_utils.js
Normal file
76
src/shared/css_utils.js
Normal file
@ -0,0 +1,76 @@
|
||||
/* 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,10 +1007,9 @@ class Driver {
|
||||
}
|
||||
|
||||
_getLastPageNumber(task) {
|
||||
if (!task.pdfDoc) {
|
||||
return task.firstPage || 1;
|
||||
}
|
||||
return task.lastPage || task.pdfDoc.numPages;
|
||||
return !task.pdfDoc
|
||||
? task.firstPage || 1
|
||||
: task.lastPage || task.pdfDoc.numPages;
|
||||
}
|
||||
|
||||
_nextPage(task, loadError) {
|
||||
|
||||
@ -31,10 +31,12 @@ import {
|
||||
kbUndo,
|
||||
loadAndWait,
|
||||
moveEditor,
|
||||
pinch,
|
||||
scrollIntoView,
|
||||
selectEditor,
|
||||
selectEditors,
|
||||
switchToEditor,
|
||||
unselectEditor,
|
||||
waitForAnnotationModeChanged,
|
||||
waitForNoElement,
|
||||
waitForPointerUp,
|
||||
@ -65,6 +67,26 @@ const drawLine = async (page, x0, y0, x1, y1) => {
|
||||
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("Basic operations", () => {
|
||||
let pages;
|
||||
@ -1407,3 +1429,148 @@ 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);
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1204,6 +1204,69 @@ 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 () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
|
||||
@ -600,6 +600,77 @@ async function dragAndDrop(page, selector, translations, steps = 1) {
|
||||
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) {
|
||||
return createPromise(page, resolve => {
|
||||
window.PDFViewerApplication.eventBus.on("pagechanging", resolve, {
|
||||
@ -1163,6 +1234,7 @@ export {
|
||||
paste,
|
||||
pasteFromClipboard,
|
||||
PDI,
|
||||
pinch,
|
||||
scrollIntoView,
|
||||
selectEditor,
|
||||
selectEditors,
|
||||
|
||||
@ -20,9 +20,11 @@
|
||||
import {
|
||||
closePages,
|
||||
closeSinglePage,
|
||||
firstPageOnTop,
|
||||
getSpanRectFromText,
|
||||
kbSelectAll,
|
||||
loadAndWait,
|
||||
scrollIntoView,
|
||||
waitForEvent,
|
||||
} from "./test_utils.mjs";
|
||||
import { MathClamp } from "../../src/shared/math_clamp.js";
|
||||
@ -1148,6 +1150,94 @@ 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", () => {
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
getRect,
|
||||
getSpanRectFromText,
|
||||
loadAndWait,
|
||||
pinch,
|
||||
scrollIntoView,
|
||||
showViewsManager,
|
||||
waitAndClick,
|
||||
@ -1527,7 +1528,11 @@ describe("PDF viewer", () => {
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"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
|
||||
);
|
||||
});
|
||||
|
||||
@ -1535,20 +1540,9 @@ describe("PDF viewer", () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("keeps the content under the pinch centre fixed on the screen", async () => {
|
||||
it("keeps the content under the pinch center fixed on the screen", async () => {
|
||||
await Promise.all(
|
||||
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 originX = rect.x + rect.width / 2;
|
||||
const originY = rect.y + rect.height / 2;
|
||||
@ -1564,14 +1558,17 @@ describe("PDF viewer", () => {
|
||||
};
|
||||
window.PDFViewerApplication.eventBus.on("textlayerrendered", cb);
|
||||
});
|
||||
const client = await page.target().createCDPSession();
|
||||
await client.send("Input.synthesizePinchGesture", {
|
||||
x: originX,
|
||||
y: originY,
|
||||
scaleFactor: 3,
|
||||
gestureSourceType: "touch",
|
||||
// Spread the two fingers from 50 to 200 pixels apart: the first
|
||||
// moves are swallowed until the distance between them changed by
|
||||
// more than 35 pixels, hence a zoom factor of about 200/85 = 2.4.
|
||||
await pinch(page, {
|
||||
centerX: originX,
|
||||
centerY: originY,
|
||||
startGap: 25,
|
||||
endGap: 100,
|
||||
});
|
||||
await awaitPromise(rendered);
|
||||
|
||||
const spanHandle = await page.evaluateHandle(() =>
|
||||
Array.from(
|
||||
document.querySelectorAll(
|
||||
@ -1579,7 +1576,21 @@ describe("PDF viewer", () => {
|
||||
)
|
||||
).find(span => span.textContent.includes("type-stable"))
|
||||
);
|
||||
expect(await spanHandle.isIntersectingViewport()).toBeTrue();
|
||||
expect(await spanHandle.isIntersectingViewport())
|
||||
.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,17 +2460,14 @@ describe("annotation", function () {
|
||||
annotationGlobalsMock,
|
||||
idFactoryMock
|
||||
);
|
||||
const fieldObject = await annotation.getFieldObject();
|
||||
const actions = fieldObject.actions;
|
||||
expect(actions["Mouse Enter"]).toEqual(["hello()"]);
|
||||
expect(actions["Mouse Exit"]).toEqual([
|
||||
"world()",
|
||||
"olleh()",
|
||||
"foo()",
|
||||
"dlrow()",
|
||||
"oof()",
|
||||
]);
|
||||
expect(actions["Mouse Down"]).toEqual(["bar()"]);
|
||||
const { actions } = await annotation.getFieldObject();
|
||||
expect(actions).toEqual(
|
||||
new Map([
|
||||
["Mouse Enter", ["hello()"]],
|
||||
["Mouse Exit", ["world()", "olleh()", "foo()", "dlrow()", "oof()"]],
|
||||
["Mouse Down", ["bar()"]],
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
it("should save Japanese text", async function () {
|
||||
@ -3728,7 +3725,7 @@ describe("annotation", function () {
|
||||
);
|
||||
expect(data.annotationType).toEqual(AnnotationType.WIDGET);
|
||||
expect(data.pushButton).toBeTrue();
|
||||
expect(data.actions.Action).toEqual(["do_something();"]);
|
||||
expect(data.actions.get("Action")).toEqual(["do_something();"]);
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
const jsActions = await pdfDocument.getJSActions();
|
||||
expect(jsActions).toEqual({
|
||||
OpenAction: ["func=function(){app.alert(1)};func();"],
|
||||
});
|
||||
expect(jsActions).toEqual(
|
||||
new Map([["OpenAction", ["func=function(){app.alert(1)};func();"]]])
|
||||
);
|
||||
|
||||
const page = await pdfDocument.getPage(1);
|
||||
expect(page).toBeInstanceOf(PDFPageProxy);
|
||||
@ -1943,12 +1943,17 @@ describe("api", function () {
|
||||
// PDF document with "JavaScript" action in the OpenAction dictionary.
|
||||
const loadingTask = getDocument(buildGetDocumentParams("issue6106.pdf"));
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const { OpenAction } = await pdfDoc.getJSActions();
|
||||
const jsActions = await pdfDoc.getJSActions();
|
||||
|
||||
expect(OpenAction).toEqual([
|
||||
"this.print({bUI:true,bSilent:false,bShrinkToFit:true});",
|
||||
]);
|
||||
expect(OpenAction[0]).toMatch(AutoPrintRegExp);
|
||||
expect(jsActions).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"OpenAction",
|
||||
["this.print({bUI:true,bSilent:false,bShrinkToFit:true});"],
|
||||
],
|
||||
])
|
||||
);
|
||||
expect(jsActions.get("OpenAction")[0]).toMatch(AutoPrintRegExp);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -1988,21 +1993,27 @@ describe("api", function () {
|
||||
const page3 = await pdfDoc.getPage(3);
|
||||
const page3Actions = await page3.getJSActions();
|
||||
|
||||
expect(docActions).toEqual({
|
||||
DidPrint: [`this.getField("Text2").value = "DidPrint";`],
|
||||
DidSave: [`this.getField("Text2").value = "DidSave";`],
|
||||
WillClose: [`this.getField("Text1").value = "WillClose";`],
|
||||
WillPrint: [`this.getField("Text1").value = "WillPrint";`],
|
||||
WillSave: [`this.getField("Text1").value = "WillSave";`],
|
||||
});
|
||||
expect(page1Actions).toEqual({
|
||||
PageOpen: [`this.getField("Text1").value = "PageOpen 1";`],
|
||||
PageClose: [`this.getField("Text2").value = "PageClose 1";`],
|
||||
});
|
||||
expect(page3Actions).toEqual({
|
||||
PageOpen: [`this.getField("Text5").value = "PageOpen 3";`],
|
||||
PageClose: [`this.getField("Text6").value = "PageClose 3";`],
|
||||
});
|
||||
expect(docActions).toEqual(
|
||||
new Map([
|
||||
["DidPrint", [`this.getField("Text2").value = "DidPrint";`]],
|
||||
["DidSave", [`this.getField("Text2").value = "DidSave";`]],
|
||||
["WillClose", [`this.getField("Text1").value = "WillClose";`]],
|
||||
["WillPrint", [`this.getField("Text1").value = "WillPrint";`]],
|
||||
["WillSave", [`this.getField("Text1").value = "WillSave";`]],
|
||||
])
|
||||
);
|
||||
expect(page1Actions).toEqual(
|
||||
new Map([
|
||||
["PageOpen", [`this.getField("Text1").value = "PageOpen 1";`]],
|
||||
["PageClose", [`this.getField("Text2").value = "PageClose 1";`]],
|
||||
])
|
||||
);
|
||||
expect(page3Actions).toEqual(
|
||||
new Map([
|
||||
["PageOpen", [`this.getField("Text5").value = "PageOpen 3";`]],
|
||||
["PageClose", [`this.getField("Text6").value = "PageClose 3";`]],
|
||||
])
|
||||
);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -2017,53 +2028,64 @@ describe("api", function () {
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
|
||||
expect(fieldObjects).toEqual({
|
||||
Text1: [
|
||||
{
|
||||
id: "25R",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
multiline: false,
|
||||
password: false,
|
||||
charLimit: 0,
|
||||
comb: false,
|
||||
editable: true,
|
||||
hidden: false,
|
||||
name: "Text1",
|
||||
rect: [24.1789, 719.66, 432.22, 741.66],
|
||||
actions: null,
|
||||
page: 0,
|
||||
strokeColor: null,
|
||||
fillColor: null,
|
||||
rotation: 0,
|
||||
datetimeFormat: undefined,
|
||||
hasDatetimeHTML: false,
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
Button1: [
|
||||
{
|
||||
id: "26R",
|
||||
value: "Off",
|
||||
defaultValue: null,
|
||||
exportValues: undefined,
|
||||
editable: true,
|
||||
name: "Button1",
|
||||
rect: [455.436, 719.678, 527.436, 739.678],
|
||||
hidden: false,
|
||||
actions: {
|
||||
Action: [
|
||||
`this.getField("Text1").value = this.info.authors.join("::");`,
|
||||
],
|
||||
},
|
||||
page: 0,
|
||||
strokeColor: null,
|
||||
fillColor: new Uint8ClampedArray([192, 192, 192]),
|
||||
rotation: 0,
|
||||
type: "button",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(fieldObjects).toEqual(
|
||||
new Map([
|
||||
[
|
||||
"Text1",
|
||||
[
|
||||
{
|
||||
id: "25R",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
multiline: false,
|
||||
password: false,
|
||||
charLimit: 0,
|
||||
comb: false,
|
||||
editable: true,
|
||||
hidden: false,
|
||||
name: "Text1",
|
||||
rect: [24.1789, 719.66, 432.22, 741.66],
|
||||
actions: null,
|
||||
page: 0,
|
||||
strokeColor: null,
|
||||
fillColor: null,
|
||||
rotation: 0,
|
||||
datetimeFormat: undefined,
|
||||
hasDatetimeHTML: false,
|
||||
type: "text",
|
||||
},
|
||||
],
|
||||
],
|
||||
[
|
||||
"Button1",
|
||||
[
|
||||
{
|
||||
id: "26R",
|
||||
value: "Off",
|
||||
defaultValue: null,
|
||||
exportValues: undefined,
|
||||
editable: true,
|
||||
name: "Button1",
|
||||
rect: [455.436, 719.678, 527.436, 739.678],
|
||||
hidden: false,
|
||||
actions: new Map([
|
||||
[
|
||||
"Action",
|
||||
[
|
||||
`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();
|
||||
});
|
||||
@ -2073,8 +2095,8 @@ describe("api", function () {
|
||||
const pdfDoc = await loadingTask.promise;
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
|
||||
for (const name in fieldObjects) {
|
||||
const pageIndexes = fieldObjects[name].map(o => o.page);
|
||||
for (const [name, objs] of fieldObjects) {
|
||||
const pageIndexes = objs.map(o => o.page);
|
||||
let expected;
|
||||
|
||||
switch (name) {
|
||||
@ -6286,6 +6308,29 @@ small scripts as well as for`);
|
||||
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 () {
|
||||
const loadingTask = getDocument(
|
||||
buildGetDocumentParams("tracemonkey.pdf")
|
||||
@ -7671,6 +7716,36 @@ small scripts as well as for`);
|
||||
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 () {
|
||||
const data = assemblePdf([
|
||||
"1 0 obj\n<< /Type /Catalog /Pages 2 0 R /AcroForm 6 0 R >>\nendobj\n",
|
||||
@ -7691,9 +7766,12 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data: extracted });
|
||||
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();
|
||||
expect(annotations[0].fieldName).toEqual("group");
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
@ -7721,7 +7799,10 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
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();
|
||||
});
|
||||
|
||||
@ -7779,9 +7860,10 @@ small scripts as well as for`);
|
||||
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(Object.keys(await pdfDoc.getFieldObjects())).toEqual([
|
||||
"signature",
|
||||
]);
|
||||
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()]).toEqual(["signature"]);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
|
||||
@ -7827,7 +7909,7 @@ small scripts as well as for`);
|
||||
// reflects the T entries of the fields in the AcroForm dictionary.
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect(fieldObjects).not.toBeNull();
|
||||
expect(Object.keys(fieldObjects).sort()).toEqual(origFieldNames);
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(origFieldNames);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -7885,7 +7967,7 @@ small scripts as well as for`);
|
||||
const allOrigFieldNames = [
|
||||
...new Set([...origPage1FieldNames, ...origPage2FieldNames]),
|
||||
].sort();
|
||||
expect(Object.keys(fieldObjects).sort()).toEqual(allOrigFieldNames);
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(allOrigFieldNames);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -7897,9 +7979,8 @@ small scripts as well as for`);
|
||||
let pdfDoc = await loadingTask.promise;
|
||||
|
||||
expect(await pdfDoc.getCalculationOrderIds()).toEqual(["6R"]);
|
||||
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||
"group",
|
||||
]);
|
||||
const fieldObjects1 = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects1.keys()]).toEqual(["group"]);
|
||||
|
||||
const data = await pdfDoc.extractPages([{ document: null }]);
|
||||
await loadingTask.destroy();
|
||||
@ -7911,9 +7992,8 @@ small scripts as well as for`);
|
||||
expect(Array.isArray(calculationOrder)).toBeTrue();
|
||||
expect(calculationOrder.length).toEqual(1);
|
||||
expect(calculationOrder[0]).not.toEqual("6R");
|
||||
expect(Object.keys((await pdfDoc.getFieldObjects()) || {})).toEqual([
|
||||
"group",
|
||||
]);
|
||||
const fieldObjects2 = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects2.keys()]).toEqual(["group"]);
|
||||
|
||||
await loadingTask.destroy();
|
||||
});
|
||||
@ -7956,9 +8036,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["first", "second"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["first", "second"]);
|
||||
|
||||
for (const pageNumber of [1, 2]) {
|
||||
const fontName = await getAppearanceFontName(pdfDoc, pageNumber);
|
||||
expect(fontName).not.toBeNull();
|
||||
@ -7997,9 +8077,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["broken", "main"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["broken", "main"]);
|
||||
|
||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||
expect(fontName).not.toBeNull();
|
||||
expect(fontName).not.toEqual("g_font_error");
|
||||
@ -8042,9 +8122,9 @@ small scripts as well as for`);
|
||||
loadingTask = getDocument({ data });
|
||||
pdfDoc = await loadingTask.promise;
|
||||
expect(pdfDoc.numPages).toEqual(2);
|
||||
expect(
|
||||
Object.keys((await pdfDoc.getFieldObjects()) ?? {}).sort()
|
||||
).toEqual(["check", "main"]);
|
||||
const fieldObjects = await pdfDoc.getFieldObjects();
|
||||
expect([...fieldObjects.keys()].sort()).toEqual(["check", "main"]);
|
||||
|
||||
const fontName = await getAppearanceFontName(pdfDoc, 2);
|
||||
expect(fontName).not.toBeNull();
|
||||
expect(fontName).not.toEqual("g_font_error");
|
||||
|
||||
@ -220,4 +220,24 @@ describe("autolinker", function () {
|
||||
["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,6 +26,7 @@
|
||||
"evaluator_spec.js",
|
||||
"event_utils_spec.js",
|
||||
"fetch_stream_spec.js",
|
||||
"font_loader_spec.js",
|
||||
"font_substitutions_spec.js",
|
||||
"fonts_spec.js",
|
||||
"image_utils_spec.js",
|
||||
|
||||
@ -24,6 +24,7 @@ import {
|
||||
getRotationMatrix,
|
||||
getSizeInBytes,
|
||||
isWhiteSpace,
|
||||
normalizeCSSFontFamily,
|
||||
numberToString,
|
||||
parseXFAPath,
|
||||
recoverJsURL,
|
||||
@ -243,6 +244,32 @@ describe("core_utils", function () {
|
||||
{ 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 () {
|
||||
@ -325,6 +352,41 @@ 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 () {
|
||||
it("Check font family", function () {
|
||||
const cssFontInfo = {
|
||||
@ -378,6 +440,30 @@ describe("core_utils", function () {
|
||||
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 () {
|
||||
const cssFontInfo = {
|
||||
fontFamily: "blah",
|
||||
|
||||
@ -122,6 +122,30 @@ describe("display_utils", function () {
|
||||
expect(
|
||||
getPdfFilenameFromUrl("http://www.example.com/pdfs/pdf.html#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 () {
|
||||
|
||||
@ -628,39 +628,43 @@ describe("document", function () {
|
||||
const kid2BisRef = Ref.get(266, 0);
|
||||
const parentRef = Ref.get(358, 0);
|
||||
|
||||
const allFields = Object.create(null);
|
||||
const allFieldsObj = Object.create(null);
|
||||
for (const name of ["parent", "kid1", "kid2", "kid11"]) {
|
||||
const buttonWidgetDict = new Dict();
|
||||
buttonWidgetDict.set("Type", Name.get("Annot"));
|
||||
buttonWidgetDict.set("Subtype", Name.get("Widget"));
|
||||
buttonWidgetDict.set("FT", Name.get("Btn"));
|
||||
buttonWidgetDict.set("T", name);
|
||||
allFields[name] = buttonWidgetDict;
|
||||
allFieldsObj[name] = buttonWidgetDict;
|
||||
}
|
||||
|
||||
allFields.kid1.set("Kids", [kid11Ref]);
|
||||
allFields.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
||||
allFieldsObj.kid1.set("Kids", [kid11Ref]);
|
||||
allFieldsObj.parent.set("Kids", [kid1Ref, kid2Ref, kid2BisRef]);
|
||||
|
||||
const xref = new XRefMock([
|
||||
{ ref: parentRef, data: allFields.parent },
|
||||
{ ref: kid1Ref, data: allFields.kid1 },
|
||||
{ ref: kid11Ref, data: allFields.kid11 },
|
||||
{ ref: kid2Ref, data: allFields.kid2 },
|
||||
{ ref: kid2BisRef, data: allFields.kid2 },
|
||||
{ ref: parentRef, data: allFieldsObj.parent },
|
||||
{ ref: kid1Ref, data: allFieldsObj.kid1 },
|
||||
{ ref: kid11Ref, data: allFieldsObj.kid11 },
|
||||
{ ref: kid2Ref, data: allFieldsObj.kid2 },
|
||||
{ ref: kid2BisRef, data: allFieldsObj.kid2 },
|
||||
]);
|
||||
|
||||
acroForm.set("Fields", [parentRef]);
|
||||
pdfDocument = getDocument(acroForm, xref);
|
||||
fields = (await pdfDocument.fieldObjects).allFields;
|
||||
|
||||
for (const [name, objs] of Object.entries(fields)) {
|
||||
fields[name] = objs.map(obj => obj.id);
|
||||
}
|
||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
||||
|
||||
expect(fields["parent.kid1"]).toEqual(["314R"]);
|
||||
expect(fields["parent.kid1.kid11"]).toEqual(["159R"]);
|
||||
expect(fields["parent.kid2"]).toEqual(["265R", "266R"]);
|
||||
expect(fields.parent).toEqual(["358R"]);
|
||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
||||
name,
|
||||
objs.map(obj => obj.id),
|
||||
]);
|
||||
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 () {
|
||||
@ -693,9 +697,14 @@ describe("document", function () {
|
||||
acroForm.set("Fields", [widgetRef]);
|
||||
const pdfDocument = getDocument(acroForm, xref);
|
||||
|
||||
const { allFields } = await pdfDocument.fieldObjects;
|
||||
expect(Object.keys(allFields)).toEqual([""]);
|
||||
expect(allFields[""].map(obj => obj.id)).toEqual(["1R"]);
|
||||
const { allFields, orphanFields } = await pdfDocument.fieldObjects;
|
||||
|
||||
const objIds = Array.from(allFields.entries(), ([name, objs]) => [
|
||||
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 () {
|
||||
|
||||
151
test/unit/font_loader_spec.js
Normal file
151
test/unit/font_loader_spec.js
Normal file
@ -0,0 +1,151 @@
|
||||
/* 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,6 +72,7 @@ async function initializePDFJS(callback) {
|
||||
"pdfjs-test/unit/evaluator_spec.js",
|
||||
"pdfjs-test/unit/event_utils_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/fonts_spec.js",
|
||||
"pdfjs-test/unit/image_utils_spec.js",
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
createHeaders,
|
||||
createResponseError,
|
||||
extractFilenameFromHeader,
|
||||
trimHeadersEnd,
|
||||
validateRangeRequestCapabilities,
|
||||
} from "../../src/display/network_utils.js";
|
||||
import { ResponseException } from "../../src/shared/util.js";
|
||||
@ -386,4 +387,38 @@ describe("network_utils", function () {
|
||||
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,6 +363,80 @@ 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 () => {
|
||||
const refId = getId();
|
||||
const data = {
|
||||
|
||||
@ -38,10 +38,9 @@ const WASM_URL = isNodeJS
|
||||
|
||||
class DefaultFileReaderFactory {
|
||||
static async fetch(params) {
|
||||
if (isNodeJS) {
|
||||
return fetchDataNode(params.path);
|
||||
}
|
||||
return fetchDataDOM(params.path, /* type = */ "bytes");
|
||||
return isNodeJS
|
||||
? fetchDataNode(params.path)
|
||||
: fetchDataDOM(params.path, /* type = */ "bytes");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -295,10 +295,20 @@ describe("Writer", function () {
|
||||
});
|
||||
|
||||
it("should not use scientific notation for very large numbers", async function () {
|
||||
// JavaScript produces scientific notation above ~1e21 but such values
|
||||
// are unlikely in PDFs; values below that threshold must be plain.
|
||||
// JavaScript's toString() and toFixed() produce scientific notation from
|
||||
// 1e21 on, which is invalid PDF: such a number must be written with all
|
||||
// its digits, which are the exact ones of the underlying double.
|
||||
expect(await serialize(1e10)).toEqual("10000000000");
|
||||
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 () {
|
||||
|
||||
@ -109,6 +109,27 @@ 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 () {
|
||||
const xml = `
|
||||
<a>
|
||||
@ -132,4 +153,33 @@ describe("XML", function () {
|
||||
["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,15 +21,18 @@
|
||||
:root {
|
||||
--editor-toolbar-vert-offset: 6px;
|
||||
--outline-width: 2px;
|
||||
--outline-color: #0060df;
|
||||
/* Palette primitives, not the semantic light-dark tokens: these are drawn on
|
||||
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-color: #f0f0f4;
|
||||
--outline-around-color: var(--color-gray-20, #f0f0f4);
|
||||
--hover-outline-around-color: var(--outline-around-color);
|
||||
--focus-outline: solid var(--outline-width) var(--outline-color);
|
||||
--editor-selection-outline: solid var(--outline-width) var(--outline-color);
|
||||
--unfocus-outline: solid var(--outline-width) transparent;
|
||||
--focus-outline-around: solid var(--outline-around-width)
|
||||
var(--outline-around-color);
|
||||
--hover-outline-color: #8f8f9d;
|
||||
--hover-outline-color: var(--color-gray-60, #8f8f9d);
|
||||
--hover-outline: solid var(--outline-width) var(--hover-outline-color);
|
||||
--hover-outline-around: solid var(--outline-around-width)
|
||||
var(--hover-outline-around-color);
|
||||
@ -95,7 +98,7 @@
|
||||
--outline-color: CanvasText;
|
||||
--outline-around-color: ButtonFace;
|
||||
--resizer-bg-color: ButtonText;
|
||||
--hover-outline-color: Highlight;
|
||||
--hover-outline-color: var(--color-accent-primary-hover, Highlight);
|
||||
--hover-outline-around-color: SelectedItemText;
|
||||
}
|
||||
}
|
||||
@ -188,7 +191,7 @@
|
||||
}
|
||||
|
||||
&.selectedEditor {
|
||||
border: var(--focus-outline);
|
||||
border: var(--editor-selection-outline);
|
||||
outline: var(--focus-outline-around);
|
||||
|
||||
&::before {
|
||||
@ -244,30 +247,51 @@
|
||||
--editor-toolbar-bg-color: light-dark(#f0f0f4, #2b2a33);
|
||||
--editor-toolbar-highlight-image: url(images/toolbarButton-editorHighlight.svg);
|
||||
--editor-toolbar-fg-color: light-dark(#2e2e56, #fbfbfe);
|
||||
--editor-toolbar-border-color: #8f8f9d;
|
||||
--editor-toolbar-border-color: var(--border-color-interactive, #8f8f9d);
|
||||
--editor-toolbar-hover-border-color: var(--editor-toolbar-border-color);
|
||||
--editor-toolbar-hover-bg-color: light-dark(#e0e0e6, #52525e);
|
||||
--editor-toolbar-hover-fg-color: var(--editor-toolbar-fg-color);
|
||||
--editor-toolbar-hover-outline: none;
|
||||
--editor-toolbar-focus-outline-color: light-dark(#0060df, #0df);
|
||||
--editor-toolbar-focus-outline-color: var(
|
||||
--focus-outline-color,
|
||||
light-dark(#0060df, #0df)
|
||||
);
|
||||
--editor-toolbar-shadow: 0 2px 6px 0 rgb(58 57 68 / 0.2);
|
||||
--editor-toolbar-height: 28px;
|
||||
--editor-toolbar-padding: 2px;
|
||||
--alt-text-done-color: light-dark(#2ac3a2, #54ffbd);
|
||||
--alt-text-done-color: var(
|
||||
--color-accent-attention,
|
||||
light-dark(#2ac3a2, #54ffbd)
|
||||
);
|
||||
--alt-text-warning-color: light-dark(#0090ed, #80ebff);
|
||||
--alt-text-hover-done-color: var(--alt-text-done-color);
|
||||
--alt-text-hover-warning-color: var(--alt-text-warning-color);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--editor-toolbar-bg-color: ButtonFace;
|
||||
--editor-toolbar-fg-color: ButtonText;
|
||||
--editor-toolbar-border-color: ButtonText;
|
||||
--editor-toolbar-hover-border-color: AccentColor;
|
||||
--editor-toolbar-hover-bg-color: ButtonFace;
|
||||
--editor-toolbar-hover-fg-color: AccentColor;
|
||||
--editor-toolbar-bg-color: var(--button-background-color, ButtonFace);
|
||||
--editor-toolbar-fg-color: var(--button-text-color, ButtonText);
|
||||
--editor-toolbar-border-color: var(--button-border-color, ButtonText);
|
||||
--editor-toolbar-hover-border-color: var(
|
||||
--button-border-color-hover,
|
||||
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
|
||||
var(--editor-toolbar-hover-border-color);
|
||||
--editor-toolbar-focus-outline-color: ButtonBorder;
|
||||
/* --button-border-color, not --focus-outline-color: the ring is painted
|
||||
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;
|
||||
--alt-text-done-color: var(--editor-toolbar-fg-color);
|
||||
--alt-text-warning-color: var(--editor-toolbar-fg-color);
|
||||
@ -461,15 +485,18 @@
|
||||
|
||||
&.show {
|
||||
--alt-text-tooltip-bg: light-dark(#f0f0f4, #1c1b22);
|
||||
--alt-text-tooltip-fg: light-dark(#15141a, #fbfbfe);
|
||||
--alt-text-tooltip-border: #8f8f9d;
|
||||
--alt-text-tooltip-fg: var(
|
||||
--text-color,
|
||||
light-dark(#15141a, #fbfbfe)
|
||||
);
|
||||
--alt-text-tooltip-border: var(--border-color-interactive, #8f8f9d);
|
||||
--alt-text-tooltip-shadow: 0 2px 6px 0
|
||||
light-dark(rgb(58 57 68 / 0.2), #15141a);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--alt-text-tooltip-bg: Canvas;
|
||||
--alt-text-tooltip-fg: CanvasText;
|
||||
--alt-text-tooltip-border: CanvasText;
|
||||
--alt-text-tooltip-fg: var(--text-color, CanvasText);
|
||||
--alt-text-tooltip-border: var(--border-color, CanvasText);
|
||||
--alt-text-tooltip-shadow: none;
|
||||
}
|
||||
|
||||
@ -1055,7 +1082,10 @@
|
||||
}
|
||||
|
||||
.colorPicker {
|
||||
--hover-outline-color: light-dark(#0250bb, #80ebff);
|
||||
--hover-outline-color: var(
|
||||
--color-accent-primary-hover,
|
||||
light-dark(#0250bb, #80ebff)
|
||||
);
|
||||
--selected-outline-color: light-dark(#0060df, #aaf2ff);
|
||||
--swatch-border-color: light-dark(#cfcfd8, #52525e);
|
||||
|
||||
|
||||
@ -370,6 +370,7 @@ const PDFViewerApplication = {
|
||||
// Set some specific preferences for tests.
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
Object.assign(opts, {
|
||||
annotationEditorMode: x => parseInt(x, 10),
|
||||
capCanvasAreaFactor: x => parseInt(x, 10),
|
||||
docBaseUrl: x => x,
|
||||
enableAltText: x => x === "true",
|
||||
|
||||
@ -416,6 +416,18 @@ const defaultOptions = new Map([
|
||||
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",
|
||||
{
|
||||
|
||||
@ -136,10 +136,13 @@ class Autolinker {
|
||||
static #numericTLDRegex;
|
||||
|
||||
static findLinks(text) {
|
||||
// Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
|
||||
// Regex can be tested and verified at https://regex101.com/r/riHjvK/1.
|
||||
// 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 ??=
|
||||
// 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}<>]]+@([\S--[[\p{P}--\-]<>]]+(?:\.[\S--[[\p{P}--\-]<>]]+)+)/gv;
|
||||
/\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;
|
||||
|
||||
const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
|
||||
const matches = normalizedText.matchAll(this.#regex);
|
||||
|
||||
247
web/buttons.css
Normal file
247
web/buttons.css
Normal file
@ -0,0 +1,247 @@
|
||||
/* 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,39 +65,86 @@
|
||||
:is(.annotationLayer, .annotationEditorLayer) {
|
||||
.annotationCommentButton {
|
||||
color-scheme: light dark;
|
||||
--comment-button-bg: light-dark(white, #1c1b22);
|
||||
--comment-button-bg: var(
|
||||
--background-color-canvas,
|
||||
light-dark(white, #1c1b22)
|
||||
);
|
||||
--comment-button-fg: light-dark(#5b5b66, #fbfbfe);
|
||||
--comment-button-active-bg: light-dark(#0041a4, #a6ecf4);
|
||||
--comment-button-active-fg: light-dark(white, #15141a);
|
||||
--comment-button-hover-bg: light-dark(#0053cb, #61dce9);
|
||||
--comment-button-hover-fg: light-dark(white, #15141a);
|
||||
--comment-button-selected-bg: light-dark(#0062fa, #00cadb);
|
||||
--comment-button-border-color: light-dark(#8f8f9d, #bfbfc9);
|
||||
--comment-button-active-fg: var(
|
||||
--button-text-color-primary,
|
||||
light-dark(white, #15141a)
|
||||
);
|
||||
--comment-button-hover-bg: var(
|
||||
--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-focus-border-color: light-dark(#cfcfd8, #3a3944);
|
||||
--comment-button-hover-border-color: var(--comment-button-hover-bg);
|
||||
--comment-button-selected-border-color: var(--comment-button-selected-bg);
|
||||
--comment-button-selected-fg: light-dark(white, #15141a);
|
||||
--comment-button-selected-fg: var(
|
||||
--button-text-color-primary,
|
||||
light-dark(white, #15141a)
|
||||
);
|
||||
--comment-button-dim: 24px;
|
||||
--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 2px 6px 0 light-dark(rgb(0 0 0 / 0.1), rgb(0 0 0 / 0.4));
|
||||
--comment-button-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||
--comment-button-focus-outline-color: var(
|
||||
--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) {
|
||||
--comment-button-bg: ButtonFace;
|
||||
--comment-button-fg: ButtonText;
|
||||
--comment-button-hover-bg: SelectedItemText;
|
||||
--comment-button-hover-fg: SelectedItem;
|
||||
--comment-button-active-bg: SelectedItemText;
|
||||
--comment-button-active-fg: SelectedItem;
|
||||
--comment-button-border-color: ButtonBorder;
|
||||
--comment-button-active-border-color: ButtonBorder;
|
||||
--comment-button-hover-border-color: SelectedItem;
|
||||
--comment-button-bg: var(--button-background-color, ButtonFace);
|
||||
--comment-button-fg: var(--button-text-color, ButtonText);
|
||||
--comment-button-hover-bg: var(
|
||||
--button-background-color-hover,
|
||||
SelectedItemText
|
||||
);
|
||||
--comment-button-hover-fg: var(--button-text-color-hover, SelectedItem);
|
||||
--comment-button-active-bg: var(
|
||||
--button-background-color-active,
|
||||
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-focus-outline-color: CanvasText;
|
||||
--comment-button-selected-bg: ButtonBorder;
|
||||
--comment-button-selected-fg: ButtonFace;
|
||||
--comment-button-focus-outline-color: var(
|
||||
--focus-outline-color,
|
||||
CanvasText
|
||||
);
|
||||
--comment-button-selected-bg: var(
|
||||
--button-background-color-primary,
|
||||
ButtonBorder
|
||||
);
|
||||
--comment-button-selected-fg: var(
|
||||
--button-text-color-primary,
|
||||
ButtonFace
|
||||
);
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
@ -185,19 +232,28 @@
|
||||
--comment-active-brightness: 0.825;
|
||||
--comment-active-filter: brightness(var(--comment-active-brightness));
|
||||
--comment-border-color: light-dark(#f0f0f4, #52525e);
|
||||
--comment-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||
--comment-fg-color: light-dark(#15141a, #fbfbfe);
|
||||
--comment-focus-outline-color: var(
|
||||
--focus-outline-color,
|
||||
light-dark(#0062fa, #00cadb)
|
||||
);
|
||||
--comment-fg-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
||||
--comment-count-bg-color: light-dark(#e2f7ff, #00317e);
|
||||
--comment-indicator-active-fg-color: light-dark(#0041a4, #a6ecf4);
|
||||
--comment-indicator-active-filter: brightness(
|
||||
calc(1 / var(--comment-active-brightness))
|
||||
);
|
||||
--comment-indicator-focus-fg-color: light-dark(#5b5b66, #fbfbfe);
|
||||
--comment-indicator-hover-fg-color: light-dark(#0053cb, #61dce9);
|
||||
--comment-indicator-hover-fg-color: var(
|
||||
--color-accent-primary-hover,
|
||||
light-dark(#0053cb, #61dce9)
|
||||
);
|
||||
--comment-indicator-hover-filter: brightness(
|
||||
calc(1 / var(--comment-hover-brightness))
|
||||
);
|
||||
--comment-indicator-selected-fg-color: light-dark(#0062fa, #00cadb);
|
||||
--comment-indicator-selected-fg-color: var(
|
||||
--color-accent-primary,
|
||||
light-dark(#0062fa, #00cadb)
|
||||
);
|
||||
|
||||
--button-comment-bg: transparent;
|
||||
--button-comment-color: var(--main-color);
|
||||
@ -208,9 +264,6 @@
|
||||
--button-comment-hover-bg: light-dark(#e0e0e6, #52525e);
|
||||
--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) {
|
||||
--comment-date-fg-color: CanvasText;
|
||||
--comment-bg-color: Canvas;
|
||||
@ -219,11 +272,13 @@
|
||||
--comment-active-bg-color: Canvas;
|
||||
--comment-active-filter: none;
|
||||
--comment-border-color: CanvasText;
|
||||
--comment-fg-color: CanvasText;
|
||||
--comment-fg-color: var(--text-color, CanvasText);
|
||||
--comment-count-bg-color: Canvas;
|
||||
--comment-indicator-active-fg-color: SelectedItem;
|
||||
--comment-indicator-focus-fg-color: CanvasText;
|
||||
--comment-indicator-hover-fg-color: CanvasText;
|
||||
--comment-indicator-focus-fg-color: var(--text-color, CanvasText);
|
||||
/* On the Canvas comment surface, so paired with --text-color rather than a
|
||||
button/accent token. */
|
||||
--comment-indicator-hover-fg-color: var(--text-color, CanvasText);
|
||||
--comment-indicator-selected-fg-color: SelectedItem;
|
||||
--button-comment-bg: ButtonFace;
|
||||
--button-comment-color: ButtonText;
|
||||
@ -232,8 +287,6 @@
|
||||
--button-comment-border: 1px solid ButtonText;
|
||||
--button-comment-hover-bg: Highlight;
|
||||
--button-comment-hover-color: HighlightText;
|
||||
--link-fg-color: LinkText;
|
||||
--link-hover-fg-color: LinkText;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
206
web/dialog.css
206
web/dialog.css
@ -14,117 +14,42 @@
|
||||
*/
|
||||
|
||||
.dialog {
|
||||
--dialog-bg-color: light-dark(white, #1c1b22);
|
||||
--dialog-border-color: light-dark(white, #1c1b22);
|
||||
--dialog-bg-color: var(--background-color-canvas, light-dark(white, #1c1b22));
|
||||
--dialog-border-color: var(
|
||||
--background-color-canvas,
|
||||
light-dark(white, #1c1b22)
|
||||
);
|
||||
--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);
|
||||
--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);
|
||||
|
||||
--textarea-border-color: #8f8f9d;
|
||||
/* A textarea is an interactive control, so it takes the interactive border
|
||||
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-fg-color: var(--text-secondary-color);
|
||||
|
||||
--radio-bg-color: light-dark(#f0f0f4, #2b2a33);
|
||||
--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;
|
||||
/* .primaryButton / .secondaryButton button tokens are defined once, at :root
|
||||
in buttons.css (the shared primitive), and inherit into dialogs. */
|
||||
|
||||
--input-text-bg-color: light-dark(white, #42414d);
|
||||
--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) {
|
||||
--dialog-bg-color: Canvas;
|
||||
--dialog-border-color: CanvasText;
|
||||
--dialog-bg-color: var(--background-color-canvas, Canvas);
|
||||
/* border-only: --background-color-canvas would resolve to Canvas in FF HCM
|
||||
(= 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;
|
||||
--text-primary-color: CanvasText;
|
||||
--text-secondary-color: CanvasText;
|
||||
--hover-filter: none;
|
||||
--link-fg-color: LinkText;
|
||||
--link-hover-fg-color: LinkText;
|
||||
--separator-color: CanvasText;
|
||||
|
||||
--textarea-border-color: ButtonBorder;
|
||||
--textarea-bg-color: Field;
|
||||
--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-fg-color: FieldText;
|
||||
}
|
||||
@ -133,7 +58,7 @@
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
line-height: 150%;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius-medium, 4px);
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--dialog-border-color);
|
||||
background: var(--dialog-bg-color);
|
||||
@ -176,6 +101,15 @@
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -187,25 +121,6 @@
|
||||
gap: 8px;
|
||||
align-self: stretch;
|
||||
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 {
|
||||
@ -224,14 +139,15 @@
|
||||
}
|
||||
|
||||
button:not(:is(.toggle-button, .closeButton, .clearInputButton)) {
|
||||
border-radius: 4px;
|
||||
border: 1px solid;
|
||||
border-radius: var(--button-border-radius, 4px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
font: menu;
|
||||
font-weight: 590;
|
||||
font-size: 13px;
|
||||
padding: 4px 16px;
|
||||
font-weight: var(--button-font-weight, 590);
|
||||
font-size: var(--button-font-size, 13px);
|
||||
padding: var(--button-padding, 4px 16px);
|
||||
width: auto;
|
||||
height: 32px;
|
||||
min-height: var(--button-min-height, 32px);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
@ -243,57 +159,9 @@
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
&.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);
|
||||
}
|
||||
}
|
||||
|
||||
&.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);
|
||||
}
|
||||
}
|
||||
|
||||
/* .primaryButton / .secondaryButton colours and states come from the
|
||||
shared buttons.css primitive; only the dialog-specific base geometry
|
||||
(above) lives here, so it also covers unclassed dialog buttons. */
|
||||
&:disabled {
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -313,7 +181,7 @@
|
||||
resize: none;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius-small, 4px);
|
||||
border: 1px solid var(--textarea-border-color);
|
||||
background: var(--textarea-bg-color);
|
||||
color: var(--textarea-fg-color);
|
||||
|
||||
@ -28,16 +28,28 @@
|
||||
--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-summary-hover-color: light-dark(rgb(28 67 138), rgb(126 169 255));
|
||||
--sig-link-color: light-dark(rgb(28 113 216), rgb(126 169 255));
|
||||
--sig-link-color: var(
|
||||
--link-color,
|
||||
light-dark(rgb(28 113 216), rgb(126 169 255))
|
||||
);
|
||||
--sig-link-hover-bg: light-dark(
|
||||
rgb(28 113 216 / 0.1),
|
||||
rgb(126 169 255 / 0.15)
|
||||
);
|
||||
--sig-banner-verified-bg: light-dark(rgb(228 247 235), rgb(28 84 49));
|
||||
--sig-banner-verified-bg: var(
|
||||
--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-warn-bg: light-dark(rgb(255 247 217), rgb(95 67 9));
|
||||
--sig-banner-warn-bg: var(
|
||||
--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-error-bg: light-dark(rgb(254 226 235), rgb(122 21 51));
|
||||
--sig-banner-error-bg: var(
|
||||
--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));
|
||||
|
||||
/* Tint colours for the row / toolbar icons. These are paired with
|
||||
@ -48,9 +60,18 @@
|
||||
* verified = green (only used for the top-level "everything fine"
|
||||
* row and the toolbar's verified badge). */
|
||||
--sig-icon-default: light-dark(rgb(150 150 150), rgb(180 180 184));
|
||||
--sig-icon-warn: light-dark(rgb(217 142 27), rgb(255 178 77));
|
||||
--sig-icon-error: light-dark(rgb(196 31 71), rgb(255 117 145));
|
||||
--sig-icon-verified: light-dark(rgb(29 142 61), rgb(106 210 126));
|
||||
--sig-icon-warn: var(
|
||||
--icon-color-warning,
|
||||
light-dark(rgb(217 142 27), rgb(255 178 77))
|
||||
);
|
||||
--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) {
|
||||
/* HCM keywords are picked by *semantic role*, not by hue — the
|
||||
@ -77,23 +98,26 @@
|
||||
--sig-detail-color: GrayText;
|
||||
--sig-divider-color: GrayText;
|
||||
--sig-summary-hover-color: AccentColor;
|
||||
--sig-link-color: LinkText;
|
||||
--sig-link-color: var(--link-color, LinkText);
|
||||
--sig-link-hover-bg: transparent;
|
||||
--sig-banner-verified-bg: ButtonFace;
|
||||
--sig-banner-verified-color: ButtonText;
|
||||
--sig-banner-warn-bg: ButtonFace;
|
||||
--sig-banner-warn-color: ButtonText;
|
||||
--sig-banner-error-bg: ButtonFace;
|
||||
--sig-banner-error-color: ButtonText;
|
||||
/* Firefox collapses the status backgrounds onto --background-color-canvas
|
||||
in HCM, so the foregrounds have to follow --text-color to stay a pair
|
||||
(ButtonText over that Canvas surface is not guaranteed to contrast). */
|
||||
--sig-banner-verified-bg: var(--background-color-success, ButtonFace);
|
||||
--sig-banner-verified-color: var(--text-color, ButtonText);
|
||||
--sig-banner-warn-bg: var(--background-color-warning, ButtonFace);
|
||||
--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)
|
||||
* in HCM — same trick as alt-text where `done` and `warning`
|
||||
* share their hover colour. The glyph shape (check vs `!` vs `✕`)
|
||||
* carries the remaining distinction. The neutral "row crypto
|
||||
* verified" check stays muted (GrayText). */
|
||||
--sig-icon-default: GrayText;
|
||||
--sig-icon-warn: AccentColor;
|
||||
--sig-icon-error: AccentColor;
|
||||
--sig-icon-verified: AccentColor;
|
||||
--sig-icon-warn: var(--icon-color-warning, AccentColor);
|
||||
--sig-icon-error: var(--icon-color-critical, AccentColor);
|
||||
--sig-icon-verified: var(--icon-color-success, AccentColor);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
import { INTERNAL_EVT, internalOpt } from "./internal_evt.js";
|
||||
import { makeSet } from "pdfjs-lib";
|
||||
|
||||
const WaitOnType = {
|
||||
EVENT: "event",
|
||||
@ -72,7 +73,7 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) {
|
||||
* and `off` methods. To raise an event, the `dispatch` method shall be used.
|
||||
*/
|
||||
class EventBus {
|
||||
#listeners = Object.create(null);
|
||||
#listeners = new Map();
|
||||
|
||||
constructor() {
|
||||
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
|
||||
@ -101,8 +102,7 @@ class EventBus {
|
||||
signal.addEventListener("abort", onAbort);
|
||||
}
|
||||
|
||||
const eventListeners = (this.#listeners[eventName] ??= []);
|
||||
eventListeners.push({
|
||||
this.#listeners.getOrInsertComputed(eventName, makeSet).add({
|
||||
listener,
|
||||
internal: options?.internal === INTERNAL_EVT,
|
||||
once: options?.once === true,
|
||||
@ -116,17 +116,11 @@ class EventBus {
|
||||
* @param {Object} [options]
|
||||
*/
|
||||
off(eventName, listener, options = null) {
|
||||
const eventListeners = this.#listeners[eventName];
|
||||
if (!eventListeners) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
}
|
||||
const eventListeners = this.#listeners.get(eventName);
|
||||
const evt = eventListeners?.keys().find(e => e.listener === listener);
|
||||
if (evt) {
|
||||
evt.rmAbort?.(); // Ensure that the `AbortSignal` listener is removed.
|
||||
eventListeners.delete(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,14 +129,14 @@ class EventBus {
|
||||
* @param {Object} data
|
||||
*/
|
||||
dispatch(eventName, data) {
|
||||
const eventListeners = this.#listeners[eventName];
|
||||
if (!eventListeners?.length) {
|
||||
const eventListeners = this.#listeners.get(eventName);
|
||||
if (!eventListeners?.size) {
|
||||
return;
|
||||
}
|
||||
let extListeners;
|
||||
// Making copy of the listeners array in case if it will be modified
|
||||
// Always create a copy of the listeners in case they are modified
|
||||
// during dispatch.
|
||||
for (const { listener, internal, once } of eventListeners.slice(0)) {
|
||||
for (const { listener, internal, once } of new Set(eventListeners)) {
|
||||
if (once) {
|
||||
this.off(eventName, listener);
|
||||
}
|
||||
|
||||
15
web/menu.css
15
web/menu.css
@ -41,10 +41,13 @@ button.hasPopupMenu {
|
||||
var(--menu-text-color),
|
||||
transparent 93%
|
||||
);
|
||||
--menuitem-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||
--menuitem-focus-outline-color: var(
|
||||
--focus-outline-color,
|
||||
light-dark(#0062fa, #00cadb)
|
||||
);
|
||||
--menuitem-focus-border-color: light-dark(white, black);
|
||||
|
||||
--menu-bg: light-dark(white, #23222b);
|
||||
--menu-bg: var(--background-color-box, light-dark(white, #23222b));
|
||||
--menu-background-blend-mode: normal;
|
||||
--menu-box-shadow:
|
||||
0 0.375px 1.5px 0 light-dark(rgb(0 0 0 / 0.05), rgb(0 0 0 / 0.2)),
|
||||
@ -52,7 +55,7 @@ button.hasPopupMenu {
|
||||
--menu-border-color: light-dark(rgb(21 20 26 / 0.1), rgb(251 251 254 / 0.1));
|
||||
--menuitem-border-radius: 8px;
|
||||
--menu-backdrop-filter: none;
|
||||
--menu-text-color: light-dark(#15141a, #fbfbfe);
|
||||
--menu-text-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
||||
--menu-text-disabled-color: var(--menu-text-color);
|
||||
--menuitem-text-hover-fg: var(--menu-text-color);
|
||||
--menuitem-hover-bg: color-mix(
|
||||
@ -64,11 +67,11 @@ button.hasPopupMenu {
|
||||
--disabled-opacity: 0.62;
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--menu-bg: Canvas;
|
||||
--menu-bg: var(--background-color-box, Canvas);
|
||||
--menu-background-blend-mode: normal;
|
||||
--menu-box-shadow: none;
|
||||
--menu-backdrop-filter: none;
|
||||
--menu-text-color: ButtonText;
|
||||
--menu-text-color: var(--text-color, ButtonText);
|
||||
--menu-text-disabled-color: GrayText;
|
||||
--menu-border-color: CanvasText;
|
||||
--menuitem-border-color: none;
|
||||
@ -78,7 +81,7 @@ button.hasPopupMenu {
|
||||
--menuitem-active-border-color: ButtonText;
|
||||
--menuitem-text-active-fg: SelectedItem;
|
||||
--menuitem-focus-bg: ButtonFace;
|
||||
--menuitem-focus-outline-color: CanvasText;
|
||||
--menuitem-focus-outline-color: var(--focus-outline-color, CanvasText);
|
||||
--menuitem-focus-border-color: none;
|
||||
--disabled-opacity: 1;
|
||||
}
|
||||
|
||||
@ -17,28 +17,46 @@
|
||||
--closing-button-icon: url(images/messageBar_closingButton.svg);
|
||||
--message-bar-close-button-color: var(--text-primary-color);
|
||||
--message-bar-close-button-color-hover: var(--text-primary-color);
|
||||
--message-bar-close-button-border-radius: 4px;
|
||||
--message-bar-close-button-border-radius: var(--border-radius-small, 4px);
|
||||
--message-bar-close-button-border: none;
|
||||
--message-bar-close-button-hover-bg-color: light-dark(
|
||||
rgb(21 20 26 / 0.14),
|
||||
rgb(251 251 254 / 0.14)
|
||||
--message-bar-close-button-hover-bg-color: var(
|
||||
--button-background-color-hover,
|
||||
light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14))
|
||||
);
|
||||
--message-bar-close-button-active-bg-color: light-dark(
|
||||
rgb(21 20 26 / 0.21),
|
||||
rgb(251 251 254 / 0.21)
|
||||
--message-bar-close-button-active-bg-color: var(
|
||||
--button-background-color-active,
|
||||
light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21))
|
||||
);
|
||||
--message-bar-close-button-focus-bg-color: light-dark(
|
||||
rgb(21 20 26 / 0.07),
|
||||
rgb(251 251 254 / 0.07)
|
||||
--message-bar-close-button-focus-bg-color: var(
|
||||
--button-background-color,
|
||||
light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07))
|
||||
);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--message-bar-close-button-color: ButtonText;
|
||||
--message-bar-close-button-border: 1px solid ButtonText;
|
||||
--message-bar-close-button-hover-bg-color: ButtonText;
|
||||
--message-bar-close-button-active-bg-color: ButtonText;
|
||||
--message-bar-close-button-focus-bg-color: ButtonText;
|
||||
--message-bar-close-button-color-hover: HighlightText;
|
||||
--message-bar-close-button-color: var(--button-text-color, ButtonText);
|
||||
--message-bar-close-button-border: 1px solid
|
||||
var(--button-border-color, ButtonText);
|
||||
/* One icon colour is shared by hover/active/focus, so all three backgrounds
|
||||
read the -hover token and pair with --button-text-color-hover. The
|
||||
fallbacks are SelectedItemText/SelectedItem for the same reason: the
|
||||
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;
|
||||
@ -50,7 +68,9 @@
|
||||
gap: 8px;
|
||||
user-select: none;
|
||||
|
||||
border-radius: 4px;
|
||||
/* Nests inside .dialog, whose radius is --border-radius-medium, so it has to
|
||||
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);
|
||||
background: var(--message-bar-bg-color);
|
||||
@ -130,8 +150,6 @@
|
||||
}
|
||||
|
||||
#editorUndoBar {
|
||||
--text-primary-color: light-dark(#15141a, #fbfbfe);
|
||||
|
||||
--message-bar-icon: url(images/messageBar_info.svg);
|
||||
--message-bar-icon-color: light-dark(#0060df, #73a7f3);
|
||||
--message-bar-bg-color: light-dark(#deeafc, #003070);
|
||||
@ -141,41 +159,10 @@
|
||||
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) {
|
||||
--text-primary-color: CanvasText;
|
||||
|
||||
--message-bar-icon-color: CanvasText;
|
||||
--message-bar-bg-color: Canvas;
|
||||
--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;
|
||||
@ -197,26 +184,10 @@
|
||||
}
|
||||
|
||||
#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;
|
||||
height: 32px;
|
||||
|
||||
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);
|
||||
}
|
||||
/* The generic secondary border is the same translucent wash as its fill,
|
||||
which disappears on the tinted bar, so keep an explicit accent edge. */
|
||||
border-color: var(--color-accent-primary, light-dark(#0060df, #0df));
|
||||
}
|
||||
|
||||
> div {
|
||||
|
||||
@ -936,9 +936,14 @@ class PDFPageView extends BasePDFPageView {
|
||||
if (this.structTreeLayer && !this.textLayer) {
|
||||
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 (
|
||||
this.annotationEditorLayer &&
|
||||
(!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)
|
||||
(!keepAnnotationEditorLayer ||
|
||||
!this.annotationEditorLayer.div ||
|
||||
!this.textLayer)
|
||||
) {
|
||||
if (this.drawLayer) {
|
||||
this.drawLayer.cancel();
|
||||
@ -947,6 +952,10 @@ class PDFPageView extends BasePDFPageView {
|
||||
this.annotationEditorLayer.cancel();
|
||||
this.annotationEditorLayer = null;
|
||||
}
|
||||
if (this.drawLayer && !this.textLayer) {
|
||||
this.drawLayer.cancel();
|
||||
this.drawLayer = null;
|
||||
}
|
||||
if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
|
||||
this.xfaLayer.cancel();
|
||||
this.xfaLayer = null;
|
||||
|
||||
@ -111,7 +111,7 @@ class PDFScriptingManager {
|
||||
// targeting an unknown id can be ignored.
|
||||
if (objects) {
|
||||
this.#objectIds = new Set();
|
||||
for (const fields of Object.values(objects)) {
|
||||
for (const fields of objects.values()) {
|
||||
for (const { id } of fields) {
|
||||
this.#objectIds.add(id);
|
||||
}
|
||||
|
||||
@ -12,6 +12,22 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* 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(dialog.css);
|
||||
@import url(text_layer_builder.css);
|
||||
@ -34,8 +50,12 @@
|
||||
--page-border: 9px solid transparent;
|
||||
--spreadHorizontalWrapped-margin-LR: -3.5px;
|
||||
--loading-icon-delay: 400ms;
|
||||
--focus-ring-color: light-dark(#0060df, #0df);
|
||||
--focus-ring-outline: 2px solid var(--focus-ring-color);
|
||||
--focus-ring-color: var(--focus-outline-color, light-dark(#0060df, #0df));
|
||||
--focus-ring-outline: var(--focus-outline-width, 2px) solid
|
||||
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-color: light-dark(#fff, #15141a);
|
||||
--new-badge-border-color: light-dark(#fbfbfe / 40%, #15141a / 40%);
|
||||
@ -45,7 +65,10 @@
|
||||
--page-margin: 8px auto -1px;
|
||||
--page-border: 1px solid CanvasText;
|
||||
--spreadHorizontalWrapped-margin-LR: 3.5px;
|
||||
--focus-ring-color: CanvasText;
|
||||
--focus-ring-color: var(--focus-outline-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-color: ButtonFace;
|
||||
--new-badge-border-color: CanvasText;
|
||||
|
||||
@ -1600,13 +1600,10 @@ class PDFViewer {
|
||||
}
|
||||
|
||||
get #pageWidthScaleFactor() {
|
||||
if (
|
||||
this._spreadMode !== SpreadMode.NONE &&
|
||||
return this._spreadMode !== SpreadMode.NONE &&
|
||||
this._scrollMode !== ScrollMode.HORIZONTAL
|
||||
) {
|
||||
return 2;
|
||||
}
|
||||
return 1;
|
||||
? 2
|
||||
: 1;
|
||||
}
|
||||
|
||||
#setScale(value, options) {
|
||||
@ -1890,19 +1887,20 @@ class PDFViewer {
|
||||
container.scrollLeft - firstPage.x,
|
||||
container.scrollTop - firstPage.y
|
||||
);
|
||||
const intLeft = Math.round(topLeft[0]);
|
||||
const intTop = Math.round(topLeft[1]);
|
||||
const [left, top] = topLeft;
|
||||
|
||||
let pdfOpenParams = `#page=${pageNumber}`;
|
||||
if (!this.isInPresentationMode) {
|
||||
pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
|
||||
pdfOpenParams +=
|
||||
`&zoom=${normalizedScaleValue},` +
|
||||
`${Math.round(left)},${Math.round(top)}`;
|
||||
}
|
||||
|
||||
this._location = {
|
||||
pageNumber,
|
||||
scale: normalizedScaleValue,
|
||||
top: intTop,
|
||||
left: intLeft,
|
||||
top,
|
||||
left,
|
||||
rotation: this._pagesRotation,
|
||||
pdfOpenParams,
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
--sidebar-bg-color: light-dark(#fff, #23222b);
|
||||
--sidebar-bg-color: var(--background-color-box, light-dark(#fff, #23222b));
|
||||
--sidebar-border-color: light-dark(
|
||||
rgb(21 20 26 / 0.1),
|
||||
rgb(251 251 254 / 0.1)
|
||||
@ -30,13 +30,18 @@
|
||||
--sidebar-width: 239px;
|
||||
--resizer-width: 4px;
|
||||
--resizer-shift: calc(0px - var(--resizer-width) - 2px);
|
||||
--resizer-hover-bg-color: light-dark(#0062fa, #00cadb);
|
||||
--resizer-hover-bg-color: var(
|
||||
--color-accent-primary,
|
||||
light-dark(#0062fa, #00cadb)
|
||||
);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--sidebar-bg-color: Canvas;
|
||||
--sidebar-bg-color: var(--background-color-box, Canvas);
|
||||
--sidebar-border-color: CanvasText;
|
||||
--sidebar-box-shadow: none;
|
||||
--resizer-hover-bg-color: CanvasText;
|
||||
/* Painted on the Canvas sidebar, so --text-color rather than the accent
|
||||
(which is ButtonText in Firefox HCM). */
|
||||
--resizer-hover-bg-color: var(--text-color, CanvasText);
|
||||
}
|
||||
|
||||
border-radius: var(--sidebar-border-radius);
|
||||
|
||||
@ -15,8 +15,16 @@
|
||||
|
||||
:root {
|
||||
--clear-signature-button-icon: url(images/editor-toolbar-delete.svg);
|
||||
--signature-bg: light-dark(#f9f9fb, #2b2a33);
|
||||
--signature-hover-bg: light-dark(#f0f0f4, var(--signature-bg));
|
||||
/* The signature wells are boxes nested in the dialog, i.e. Firefox's
|
||||
.info-box-container pattern. */
|
||||
--signature-bg: var(
|
||||
--background-color-box-info,
|
||||
light-dark(#f9f9fb, #2b2a33)
|
||||
);
|
||||
--signature-hover-bg: var(
|
||||
--button-background-color-hover,
|
||||
light-dark(#f0f0f4, var(--signature-bg))
|
||||
);
|
||||
--button-signature-bg: transparent;
|
||||
--button-signature-color: var(--main-color);
|
||||
--button-signature-active-bg: light-dark(#cfcfd8, #5b5b66);
|
||||
@ -41,13 +49,13 @@
|
||||
|
||||
.signatureDialog {
|
||||
--primary-color: var(--text-primary-color);
|
||||
--border-color: #8f8f9d;
|
||||
--signature-border-color: var(--border-color-interactive, #8f8f9d);
|
||||
--open-link-fg: var(--link-fg-color);
|
||||
--open-link-hover-fg: var(--link-hover-fg-color);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--primary-color: ButtonText;
|
||||
--border-color: ButtonText;
|
||||
--signature-border-color: var(--border-color-interactive, ButtonText);
|
||||
--open-link-fg: ButtonText;
|
||||
--open-link-hover-fg: ButtonText;
|
||||
}
|
||||
@ -88,11 +96,11 @@
|
||||
|
||||
> input {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
height: var(--size-item-large, 32px);
|
||||
padding-inline: 8px calc(4px + var(--button-dimension));
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-small, 4px);
|
||||
border: 1px solid var(--signature-border-color);
|
||||
}
|
||||
|
||||
.clearInputButton {
|
||||
@ -113,11 +121,17 @@
|
||||
|
||||
#addSignatureDialog {
|
||||
--secondary-color: var(--text-secondary-color);
|
||||
--bg-hover: #e0e0e6;
|
||||
--tab-top-line-active-color: #0060df;
|
||||
--bg-hover: var(
|
||||
--button-background-color-hover,
|
||||
light-dark(#e0e0e6, #52525e)
|
||||
);
|
||||
--tab-top-line-active-color: var(
|
||||
--color-accent-primary,
|
||||
light-dark(#0060df, #0df)
|
||||
);
|
||||
--tab-top-line-active-hover-color: var(--tab-text-hover-color);
|
||||
--tab-top-line-hover-color: #8f8f9d;
|
||||
--tab-top-line-inactive-color: #cfcfd8;
|
||||
--tab-top-line-hover-color: var(--border-color-interactive, #8f8f9d);
|
||||
--tab-top-line-inactive-color: light-dark(#cfcfd8, #8f8f9d);
|
||||
--tab-bottom-line-active-color: var(--tab-top-line-inactive-color);
|
||||
--tab-bottom-line-hover-color: var(--tab-top-line-inactive-color);
|
||||
--tab-bottom-line-inactive-color: var(--tab-top-line-inactive-color);
|
||||
@ -126,7 +140,7 @@
|
||||
--tab-bg-active-hover-color: var(--bg-hover);
|
||||
--tab-bg-hover: var(--bg-hover);
|
||||
--tab-panel-border: none;
|
||||
--tab-panel-border-radius: 4px;
|
||||
--tab-panel-border-radius: var(--border-radius-small, 4px);
|
||||
--tab-text-color: var(--primary-color);
|
||||
--tab-text-active-color: var(--tab-top-line-active-color);
|
||||
--tab-text-active-hover-color: var(--tab-text-hover-color);
|
||||
@ -134,24 +148,6 @@
|
||||
--signature-placeholder-color: var(--secondary-color);
|
||||
--signature-draw-placeholder-color: var(--primary-color);
|
||||
--signature-color: var(--primary-color);
|
||||
--clear-signature-button-border-width: 0;
|
||||
--clear-signature-button-border-style: solid;
|
||||
--clear-signature-button-border-color: transparent;
|
||||
--clear-signature-button-border-disabled-color: transparent;
|
||||
--clear-signature-button-color: var(--primary-color);
|
||||
--clear-signature-button-hover-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-active-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-disabled-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-focus-color: var(--clear-signature-button-color);
|
||||
--clear-signature-button-bg: var(--dialog-bg-color);
|
||||
--clear-signature-button-bg-hover: var(--bg-hover);
|
||||
--clear-signature-button-bg-active: #cfcfd8;
|
||||
--clear-signature-button-bg-focus: #f0f0f4;
|
||||
--clear-signature-button-bg-disabled: color-mix(
|
||||
in srgb,
|
||||
#f0f0f4,
|
||||
transparent 40%
|
||||
);
|
||||
--save-warning-color: var(--secondary-color);
|
||||
--thickness-bg: var(--dialog-bg-color);
|
||||
--thickness-label-color: var(--primary-color);
|
||||
@ -159,30 +155,13 @@
|
||||
--thickness-border: none;
|
||||
--draw-cursor: url(images/cursor-editorInk.svg) 0 16, pointer;
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
/* TODO: Update the dialog colors for dark mode but in dialog.css */
|
||||
--dialog-bg-color: #42414d;
|
||||
--bg-hover: #52525e;
|
||||
--primary-color: #fbfbfe;
|
||||
--secondary-color: #cfcfd8;
|
||||
--tab-top-line-active-color: #0df;
|
||||
--tab-top-line-inactive-color: #8f8f9d;
|
||||
--clear-signature-button-bg-active: #5b5b66;
|
||||
--clear-signature-button-bg-focus: #2b2a33;
|
||||
--clear-signature-button-bg-disabled: color-mix(
|
||||
in srgb,
|
||||
#2b2a33,
|
||||
transparent 40%
|
||||
);
|
||||
}
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--secondary-color: ButtonText;
|
||||
--bg: HighlightText;
|
||||
--bg-hover: var(--bg);
|
||||
--tab-top-line-active-color: ButtonText;
|
||||
--tab-top-line-active-color: var(--color-accent-primary, ButtonText);
|
||||
--tab-top-line-active-hover-color: HighlightText;
|
||||
--tab-top-line-hover-color: SelectedItem;
|
||||
--tab-top-line-hover-color: var(--border-color-interactive, SelectedItem);
|
||||
--tab-top-line-inactive-color: ButtonText;
|
||||
--tab-bottom-line-active-color: var(--tab-top-line-active-color);
|
||||
--tab-bottom-line-hover-color: var(--tab-top-line-hover-color);
|
||||
@ -196,24 +175,10 @@
|
||||
--tab-text-active-hover-color: HighlightText;
|
||||
--tab-text-hover-color: SelectedItem;
|
||||
--signature-color: ButtonText;
|
||||
--clear-signature-button-border-width: 1px;
|
||||
--clear-signature-button-border-style: solid;
|
||||
--clear-signature-button-border-color: ButtonText;
|
||||
--clear-signature-button-border-disabled-color: GrayText;
|
||||
--clear-signature-button-color: ButtonText;
|
||||
--clear-signature-button-hover-color: HighlightText;
|
||||
--clear-signature-button-active-color: SelectedItem;
|
||||
--clear-signature-button-focus-color: CanvasText;
|
||||
--clear-signature-button-disabled-color: GrayText;
|
||||
--clear-signature-button-bg: var(--bg);
|
||||
--clear-signature-button-bg-hover: SelectedItem;
|
||||
--clear-signature-button-bg-active: var(--bg);
|
||||
--clear-signature-button-bg-focus: var(--bg);
|
||||
--clear-signature-button-bg-disabled: var(--bg);
|
||||
--thickness-bg: Canvas;
|
||||
--thickness-label-color: CanvasText;
|
||||
--thickness-slider-color: ButtonText;
|
||||
--thickness-border: 1px solid var(--border-color);
|
||||
--thickness-border: 1px solid var(--signature-border-color);
|
||||
}
|
||||
|
||||
#addSignatureDialogLabel {
|
||||
@ -533,14 +498,7 @@
|
||||
|
||||
#clearSignatureButton {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
padding: 4px 8px;
|
||||
align-items: center;
|
||||
background-color: var(--clear-signature-button-bg);
|
||||
border-width: var(--clear-signature-button-border-width);
|
||||
border-style: var(--clear-signature-button-border-style);
|
||||
border-color: var(--clear-signature-button-border-color);
|
||||
border-radius: 4px;
|
||||
|
||||
> span {
|
||||
display: flex;
|
||||
@ -549,8 +507,6 @@
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
|
||||
color: var(--clear-signature-button-color);
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
@ -558,57 +514,10 @@
|
||||
height: 16px;
|
||||
mask-image: var(--clear-signature-button-icon);
|
||||
mask-size: cover;
|
||||
background-color: var(--clear-signature-button-color);
|
||||
background-color: currentColor;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--clear-signature-button-bg-hover);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-hover-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-hover-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--clear-signature-button-bg-active);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-active-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background-color: var(--clear-signature-button-bg-focus);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-focus-color);
|
||||
&::after {
|
||||
background-color: var(--clear-signature-button-focus-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: var(--clear-signature-button-bg-disabled);
|
||||
border-color: var(--clear-signature-button-border-disabled-color);
|
||||
|
||||
> span {
|
||||
color: var(--clear-signature-button-disabled-color);
|
||||
&::after {
|
||||
background-color: var(
|
||||
--clear-signature-button-disabled-color
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,44 +2,13 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
.toggle-button {
|
||||
--button-background-color: color-mix(in srgb, currentColor 7%, transparent);
|
||||
--button-background-color-hover: color-mix(
|
||||
in srgb,
|
||||
currentColor 14%,
|
||||
transparent
|
||||
);
|
||||
--button-background-color-active: color-mix(
|
||||
in srgb,
|
||||
currentColor 21%,
|
||||
transparent
|
||||
);
|
||||
--color-accent-primary: light-dark(#0060df, #0df);
|
||||
--color-accent-primary-hover: light-dark(#0250bb, #80ebff);
|
||||
--color-accent-primary-active: light-dark(#054096, #aaf2ff);
|
||||
--border-radius-circle: 9999px;
|
||||
--border-width: 1px;
|
||||
--size-item-small: 16px;
|
||||
--size-item-large: 32px;
|
||||
--color-canvas: light-dark(white, #1c1b22);
|
||||
--background-color-canvas: var(--color-canvas);
|
||||
--border-color-interactive: light-dark(#8f8f9d, #f9f9fa);
|
||||
--border-color-interactive-hover: var(--border-color-interactive);
|
||||
--border-color-interactive-active: var(--border-color-interactive);
|
||||
--focus-outline-offset: 2px;
|
||||
|
||||
@media (forced-colors: active) {
|
||||
--color-accent-primary: ButtonText;
|
||||
--color-accent-primary-hover: SelectedItem;
|
||||
--color-accent-primary-active: SelectedItem;
|
||||
--button-background-color: ButtonFace;
|
||||
--border-color-interactive: ButtonText;
|
||||
--border-color-interactive-hover: SelectedItem;
|
||||
--border-color-interactive-active: ButtonText;
|
||||
--color-canvas: ButtonText;
|
||||
--background-color-canvas: Canvas;
|
||||
}
|
||||
}
|
||||
/* The Firefox foundation tokens below are read with the shipped literal as the
|
||||
fallback, so MOZCENTRAL tracks the design system (tokens-brand.css, linked
|
||||
from viewer.html) while every other build keeps this self-contained
|
||||
moz-toggle copy. Declaring those token names here instead would shadow
|
||||
Firefox's (unlayered beats @layer), and a bare var() would both trip
|
||||
stylelint's no-unknown-custom-properties and collapse the toggle to a
|
||||
zero-size transparent element if the chrome sheet ever failed to load. */
|
||||
|
||||
/*
|
||||
The original file is located at:
|
||||
@ -55,23 +24,47 @@
|
||||
*/
|
||||
|
||||
.toggle-button {
|
||||
--toggle-background-color: var(--button-background-color);
|
||||
--toggle-background-color-hover: var(--button-background-color-hover);
|
||||
--toggle-background-color-active: var(--button-background-color-active);
|
||||
--toggle-background-color-pressed: var(--color-accent-primary);
|
||||
--toggle-background-color-pressed-hover: var(--color-accent-primary-hover);
|
||||
--toggle-background-color-pressed-active: var(--color-accent-primary-active);
|
||||
--toggle-border-color: var(--border-color-interactive);
|
||||
--toggle-background-color: var(
|
||||
--button-background-color,
|
||||
color-mix(in srgb, currentColor 7%, transparent)
|
||||
);
|
||||
--toggle-background-color-hover: var(
|
||||
--button-background-color-hover,
|
||||
color-mix(in srgb, currentColor 14%, transparent)
|
||||
);
|
||||
--toggle-background-color-active: var(
|
||||
--button-background-color-active,
|
||||
color-mix(in srgb, currentColor 21%, transparent)
|
||||
);
|
||||
--toggle-background-color-pressed: var(
|
||||
--color-accent-primary,
|
||||
light-dark(#0060df, #0df)
|
||||
);
|
||||
--toggle-background-color-pressed-hover: var(
|
||||
--color-accent-primary-hover,
|
||||
light-dark(#0250bb, #80ebff)
|
||||
);
|
||||
--toggle-background-color-pressed-active: var(
|
||||
--color-accent-primary-active,
|
||||
light-dark(#054096, #aaf2ff)
|
||||
);
|
||||
--toggle-border-color: var(
|
||||
--border-color-interactive,
|
||||
light-dark(#8f8f9d, #f9f9fa)
|
||||
);
|
||||
--toggle-border-color-hover: var(--toggle-border-color);
|
||||
--toggle-border-color-active: var(--toggle-border-color);
|
||||
--toggle-border-radius: var(--border-radius-circle);
|
||||
--toggle-border-width: var(--border-width);
|
||||
--toggle-height: var(--size-item-small);
|
||||
--toggle-width: var(--size-item-large);
|
||||
--toggle-border-radius: var(--border-radius-circle, 9999px);
|
||||
--toggle-border-width: var(--border-width, 1px);
|
||||
--toggle-height: var(--size-item-small, 16px);
|
||||
--toggle-width: var(--size-item-large, 32px);
|
||||
--toggle-dot-background-color: var(--toggle-border-color);
|
||||
--toggle-dot-background-color-hover: var(--toggle-dot-background-color);
|
||||
--toggle-dot-background-color-active: var(--toggle-dot-background-color);
|
||||
--toggle-dot-background-color-on-pressed: var(--background-color-canvas);
|
||||
--toggle-dot-background-color-on-pressed: var(
|
||||
--background-color-canvas,
|
||||
light-dark(white, #1c1b22)
|
||||
);
|
||||
--toggle-dot-margin: 1px;
|
||||
--toggle-dot-height: calc(
|
||||
var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 *
|
||||
@ -93,8 +86,8 @@
|
||||
box-sizing: border-box;
|
||||
|
||||
&:focus-visible {
|
||||
outline: var(--focus-outline);
|
||||
outline-offset: var(--focus-outline-offset);
|
||||
outline: var(--focus-ring-outline);
|
||||
outline-offset: var(--focus-outline-offset, 2px);
|
||||
}
|
||||
|
||||
&:enabled:hover {
|
||||
@ -186,16 +179,42 @@
|
||||
|
||||
@media (forced-colors) {
|
||||
.toggle-button {
|
||||
--toggle-dot-background-color: var(--color-accent-primary);
|
||||
--toggle-dot-background-color-hover: var(--color-accent-primary-hover);
|
||||
--toggle-dot-background-color-active: var(--color-accent-primary-active);
|
||||
--toggle-dot-background-color-on-pressed: var(--button-background-color);
|
||||
--toggle-border-color-hover: var(--border-color-interactive-hover);
|
||||
--toggle-border-color-active: var(--border-color-interactive-active);
|
||||
--toggle-background-color: var(--button-background-color, ButtonFace);
|
||||
--toggle-background-color-pressed: var(--color-accent-primary, ButtonText);
|
||||
--toggle-background-color-pressed-hover: var(
|
||||
--color-accent-primary-hover,
|
||||
SelectedItem
|
||||
);
|
||||
--toggle-background-color-pressed-active: var(
|
||||
--color-accent-primary-active,
|
||||
SelectedItem
|
||||
);
|
||||
--toggle-border-color: var(--border-color-interactive, ButtonText);
|
||||
--toggle-dot-background-color: var(--color-accent-primary, ButtonText);
|
||||
--toggle-dot-background-color-hover: var(
|
||||
--color-accent-primary-hover,
|
||||
SelectedItem
|
||||
);
|
||||
--toggle-dot-background-color-active: var(
|
||||
--color-accent-primary-active,
|
||||
SelectedItem
|
||||
);
|
||||
--toggle-dot-background-color-on-pressed: var(
|
||||
--button-background-color,
|
||||
ButtonFace
|
||||
);
|
||||
--toggle-border-color-hover: var(
|
||||
--border-color-interactive-hover,
|
||||
SelectedItem
|
||||
);
|
||||
--toggle-border-color-active: var(
|
||||
--border-color-interactive-active,
|
||||
ButtonText
|
||||
);
|
||||
}
|
||||
|
||||
.toggle-button[aria-pressed="true"]:enabled::after {
|
||||
border: 1px solid var(--button-background-color);
|
||||
border: 1px solid var(--button-background-color, ButtonFace);
|
||||
content: "";
|
||||
position: absolute;
|
||||
height: var(--toggle-height);
|
||||
|
||||
@ -26,29 +26,22 @@ const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
|
||||
*/
|
||||
class ViewHistory {
|
||||
constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) {
|
||||
this.fingerprint = fingerprint;
|
||||
this.cacheSize = cacheSize;
|
||||
|
||||
this._initializedPromise = this._readFromStorage().then(databaseStr => {
|
||||
const database = JSON.parse(databaseStr || "{}");
|
||||
let index = -1;
|
||||
if (!Array.isArray(database.files)) {
|
||||
database.files = [];
|
||||
} else {
|
||||
while (database.files.length >= this.cacheSize) {
|
||||
while (database.files.length >= cacheSize) {
|
||||
database.files.shift();
|
||||
}
|
||||
|
||||
for (let i = 0, ii = database.files.length; i < ii; i++) {
|
||||
const branch = database.files[i];
|
||||
if (branch.fingerprint === this.fingerprint) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
index = database.files.findIndex(
|
||||
branch => branch.fingerprint === fingerprint
|
||||
);
|
||||
}
|
||||
if (index === -1) {
|
||||
index = database.files.push({ fingerprint: this.fingerprint }) - 1;
|
||||
index = database.files.push({ fingerprint }) - 1;
|
||||
}
|
||||
this.file = database.files[index];
|
||||
this.database = database;
|
||||
|
||||
@ -24,12 +24,6 @@
|
||||
--field-bg-color: light-dark(rgb(255 255 255), rgb(64 64 68));
|
||||
--field-border-color: light-dark(rgb(187 187 188), rgb(115 115 115));
|
||||
--doorhanger-bg-color: light-dark(rgb(255 255 255), rgb(74 74 79));
|
||||
--dialog-button-border: none;
|
||||
--dialog-button-bg-color: light-dark(rgb(12 12 13 / 0.1), rgb(92 92 97));
|
||||
--dialog-button-hover-bg-color: light-dark(
|
||||
rgb(12 12 13 / 0.3),
|
||||
rgb(115 115 115)
|
||||
);
|
||||
|
||||
--toolbar-bg-color: light-dark(#f9f9fb, #2b2a33);
|
||||
--toolbar-divider-color: light-dark(#e0e0e6, #5b5b66);
|
||||
@ -42,9 +36,6 @@
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
:root {
|
||||
--dialog-button-border: 1px solid Highlight;
|
||||
--dialog-button-hover-bg-color: Highlight;
|
||||
--dialog-button-hover-color: ButtonFace;
|
||||
--field-border-color: ButtonText;
|
||||
--main-color: CanvasText;
|
||||
}
|
||||
@ -94,40 +85,6 @@ body {
|
||||
inset-block-start: 0;
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
border: none;
|
||||
background: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dialogButton:is(:hover, :focus-visible) {
|
||||
background-color: var(--dialog-button-hover-bg-color);
|
||||
}
|
||||
|
||||
.dialogButton:is(:hover, :focus-visible) > span {
|
||||
color: var(--dialog-button-hover-color);
|
||||
}
|
||||
|
||||
.dialogButton[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
min-width: 16px;
|
||||
margin: 2px 1px;
|
||||
padding: 2px 6px 0;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
color: var(--main-color);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.toolbarField {
|
||||
padding: 4px 7px;
|
||||
margin: 3px 0;
|
||||
@ -204,19 +161,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
:is(.toolbarButton .dialogButton)[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
width: auto;
|
||||
margin: 3px 4px 2px !important;
|
||||
padding: 2px 11px;
|
||||
color: var(--main-color);
|
||||
background-color: var(--dialog-button-bg-color);
|
||||
border: var(--dialog-button-border) !important;
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
|
||||
@ -124,10 +124,10 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<input type="password" id="password" class="toolbarField" />
|
||||
</div>
|
||||
<div class="buttonRow">
|
||||
<button id="passwordCancel" class="dialogButton" type="button">
|
||||
<button id="passwordCancel" class="secondaryButton" type="button">
|
||||
<span data-l10n-id="pdfjs-password-cancel-button"></span>
|
||||
</button>
|
||||
<button id="passwordSubmit" class="dialogButton" type="button">
|
||||
<button id="passwordSubmit" class="primaryButton" type="button">
|
||||
<span data-l10n-id="pdfjs-password-ok-button"></span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -13,6 +13,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* Pref-gated, so the Firefox tokens are opt-in (and with them the Nova theme,
|
||||
which tokens-brand.css gates on browser.nova.enabled): with the pref off
|
||||
every var() falls back to its shipped literal. -moz-pref() works here
|
||||
because chrome rules are enabled for a resource:// stylesheet, and it only
|
||||
sees pdfjs.* prefs. */
|
||||
/*#if MOZCENTRAL*/
|
||||
@import url(chrome://global/skin/design-system/tokens-brand.css) -moz-pref(
|
||||
"pdfjs.enableNova"
|
||||
);
|
||||
/*#endif*/
|
||||
@import url(pdf_viewer.css);
|
||||
@import url(digital_signature_properties.css);
|
||||
|
||||
@ -55,7 +65,10 @@
|
||||
inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25),
|
||||
0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1);
|
||||
--toolbarSidebar-border-bottom: none;
|
||||
--button-hover-color: color-mix(in srgb, currentColor 17%, transparent);
|
||||
--button-hover-color: var(
|
||||
--button-background-color-toolbar-hover,
|
||||
color-mix(in srgb, currentColor 17%, transparent)
|
||||
);
|
||||
--toggled-btn-color: light-dark(rgb(0 0 0), rgb(255 255 255));
|
||||
--toggled-btn-bg-color: rgb(0 0 0 / 0.3);
|
||||
--toggled-hover-active-btn-color: rgb(0 0 0 / 0.4);
|
||||
@ -70,12 +83,6 @@
|
||||
--doorhanger-border-color: light-dark(rgb(12 12 13 / 0.2), rgb(39 39 43));
|
||||
--doorhanger-hover-color: light-dark(rgb(12 12 13), rgb(249 249 250));
|
||||
--doorhanger-separator-color: light-dark(rgb(222 222 222), rgb(92 92 97));
|
||||
--dialog-button-border: none;
|
||||
--dialog-button-bg-color: light-dark(rgb(12 12 13 / 0.1), rgb(92 92 97));
|
||||
--dialog-button-hover-bg-color: light-dark(
|
||||
rgb(12 12 13 / 0.3),
|
||||
rgb(115 115 115)
|
||||
);
|
||||
|
||||
--loading-icon: url(images/loading.svg);
|
||||
--toolbarButton-editorComment-icon: url(images/comment-editButton.svg);
|
||||
@ -136,6 +143,10 @@
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
:root {
|
||||
/* Must stay a system color in HCM: --button-background-color-toolbar-hover
|
||||
is NOT overridden in the dist's forced-colors layer (it stays a
|
||||
color-mix(currentColor 17%, transparent)), which would make the hover
|
||||
background a faint translucent overlay and hide the ButtonFace icon. */
|
||||
--button-hover-color: Highlight;
|
||||
--toolbar-icon-opacity: 1;
|
||||
--toolbar-icon-bg-color: ButtonText;
|
||||
@ -150,9 +161,6 @@
|
||||
--doorhanger-hover-color: ButtonFace;
|
||||
--doorhanger-border-color-whcm: 1px solid ButtonText;
|
||||
--doorhanger-triangle-opacity-whcm: 0;
|
||||
--dialog-button-border: 1px solid Highlight;
|
||||
--dialog-button-hover-bg-color: Highlight;
|
||||
--dialog-button-hover-color: ButtonFace;
|
||||
--dropdown-btn-border: 1px solid ButtonText;
|
||||
--field-border-color: ButtonText;
|
||||
--main-color: CanvasText;
|
||||
@ -413,22 +421,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
border: none;
|
||||
background: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.dialogButton:is(:hover, :focus-visible) {
|
||||
background-color: var(--dialog-button-hover-bg-color);
|
||||
}
|
||||
|
||||
.dialogButton:is(:hover, :focus-visible) > span {
|
||||
color: var(--dialog-button-hover-color);
|
||||
}
|
||||
|
||||
.splitToolbarButtonSeparator {
|
||||
float: inline-start;
|
||||
width: 0;
|
||||
@ -437,20 +429,6 @@ body {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
min-width: 16px;
|
||||
margin: 2px 1px;
|
||||
padding: 2px 6px 0;
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
color: var(--main-color);
|
||||
font-size: 12px;
|
||||
line-height: 14px;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#viewsManagerToggleButton::before {
|
||||
mask-image: var(--toolbarButton-viewsManagerToggle-icon);
|
||||
transform: scaleX(var(--dir-factor));
|
||||
@ -646,15 +624,6 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.dialogButton {
|
||||
width: auto;
|
||||
margin: 3px 4px 2px !important;
|
||||
padding: 2px 11px;
|
||||
color: var(--main-color);
|
||||
background-color: var(--dialog-button-bg-color);
|
||||
border: var(--dialog-button-border) !important;
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: auto;
|
||||
padding: 15px;
|
||||
@ -665,7 +634,7 @@ dialog {
|
||||
line-height: 14px;
|
||||
background-color: var(--doorhanger-bg-color);
|
||||
border: 1px solid rgb(0 0 0 / 0.5);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--border-radius-medium, 4px);
|
||||
box-shadow: 0 1px 4px rgb(0 0 0 / 0.3);
|
||||
}
|
||||
|
||||
@ -695,8 +664,10 @@ dialog .separator {
|
||||
}
|
||||
|
||||
dialog .buttonRow {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
dialog :link {
|
||||
|
||||
@ -33,7 +33,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<!--<link rel="icon" type="image/svg+xml" href="chrome://global/skin/icons/pdf.svg" />-->
|
||||
<!--<meta
|
||||
http-equiv="Content-Security-Policy"
|
||||
content="default-src 'none'; script-src resource: 'wasm-unsafe-eval'; worker-src resource:; style-src resource:; img-src resource: blob: data:; media-src blob:; font-src resource:; connect-src resource:; base-uri 'none'; form-action 'none';"
|
||||
content="default-src 'none'; script-src resource: 'wasm-unsafe-eval'; worker-src resource:; style-src resource: chrome:; img-src resource: blob: data:; media-src blob:; font-src resource:; connect-src resource:; base-uri 'none'; form-action 'none';"
|
||||
/>-->
|
||||
<!--#elif TESTING-->
|
||||
<!--<meta
|
||||
@ -901,8 +901,8 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<input type="password" id="password" class="toolbarField" />
|
||||
</div>
|
||||
<div class="buttonRow">
|
||||
<button id="passwordCancel" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-cancel-button"></span></button>
|
||||
<button id="passwordSubmit" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-ok-button"></span></button>
|
||||
<button id="passwordCancel" class="secondaryButton" type="button"><span data-l10n-id="pdfjs-password-cancel-button"></span></button>
|
||||
<button id="passwordSubmit" class="primaryButton" type="button"><span data-l10n-id="pdfjs-password-ok-button"></span></button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog id="documentPropertiesDialog">
|
||||
@ -966,7 +966,9 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
|
||||
</div>
|
||||
<div class="buttonRow">
|
||||
<button id="documentPropertiesClose" class="dialogButton" type="button"><span data-l10n-id="pdfjs-document-properties-close-button"></span></button>
|
||||
<button id="documentPropertiesClose" class="secondaryButton" type="button">
|
||||
<span data-l10n-id="pdfjs-document-properties-close-button"></span>
|
||||
</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel" aria-describedby="dialogDescription">
|
||||
@ -1210,7 +1212,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<button class="clearInputButton" type="button" tabindex="0" aria-hidden="true"></button>
|
||||
</span>
|
||||
</div>
|
||||
<button id="clearSignatureButton" type="button" data-l10n-id="pdfjs-editor-add-signature-clear-button" tabindex="0">
|
||||
<button id="clearSignatureButton" class="secondaryButton" type="button" data-l10n-id="pdfjs-editor-add-signature-clear-button" tabindex="0">
|
||||
<span data-l10n-id="pdfjs-editor-add-signature-clear-button-label"></span>
|
||||
</button>
|
||||
</div>
|
||||
@ -1297,7 +1299,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<span data-l10n-id="pdfjs-print-progress-percent" data-l10n-args='{ "progress": 0 }' class="relative-progress">0%</span>
|
||||
</div>
|
||||
<div class="buttonRow">
|
||||
<button id="printCancel" class="dialogButton" type="button"><span data-l10n-id="pdfjs-print-progress-close-button"></span></button>
|
||||
<button id="printCancel" class="secondaryButton" type="button"><span data-l10n-id="pdfjs-print-progress-close-button"></span></button>
|
||||
</div>
|
||||
</dialog>
|
||||
<!--#endif-->
|
||||
@ -1312,7 +1314,7 @@ See https://github.com/adobe-type-tools/cmap-resources
|
||||
<div>
|
||||
<span id="editorUndoBarMessage" class="description"></span>
|
||||
</div>
|
||||
<button id="editorUndoBarUndoButton" class="undoButton" type="button" tabindex="0" data-l10n-id="pdfjs-editor-undo-bar-undo-button">
|
||||
<button id="editorUndoBarUndoButton" class="secondaryButton" type="button" tabindex="0" data-l10n-id="pdfjs-editor-undo-bar-undo-button">
|
||||
<span data-l10n-id="pdfjs-editor-undo-bar-undo-button-label"></span>
|
||||
</button>
|
||||
<button id="editorUndoBarCloseButton" class="closeButton" type="button" tabindex="0" data-l10n-id="pdfjs-editor-undo-bar-close-button">
|
||||
|
||||
@ -60,19 +60,35 @@
|
||||
--sidebar-max-width: 50vw;
|
||||
--sidebar-block-padding: 8px;
|
||||
|
||||
--text-color: light-dark(#15141a, #fbfbfe);
|
||||
--button-fg: var(--text-color);
|
||||
--button-no-bg: transparent;
|
||||
--button-bg: light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07));
|
||||
--button-border-color: transparent;
|
||||
--button-hover-bg: light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14));
|
||||
--button-hover-fg: var(--text-color);
|
||||
--button-hover-border-color: var(--button-border-color);
|
||||
--button-active-bg: light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21));
|
||||
--button-active-fg: var(--text-color);
|
||||
--button-active-border-color: var(--button-border-color);
|
||||
--button-focus-no-bg: color-mix(in srgb, var(--text-color), transparent 93%);
|
||||
--button-focus-outline-color: light-dark(#0062fa, #00cadb);
|
||||
--views-text-color: var(--text-color, light-dark(#15141a, #fbfbfe));
|
||||
--button-fg: var(--views-text-color);
|
||||
--button-no-bg: var(--button-background-color-toolbar, transparent);
|
||||
--button-bg: var(
|
||||
--button-background-color,
|
||||
light-dark(rgb(21 20 26 / 0.07), rgb(251 251 254 / 0.07))
|
||||
);
|
||||
--views-button-border-color: var(--button-border-color, transparent);
|
||||
--button-hover-bg: var(
|
||||
--button-background-color-hover,
|
||||
light-dark(rgb(21 20 26 / 0.14), rgb(251 251 254 / 0.14))
|
||||
);
|
||||
--button-hover-fg: var(--views-text-color);
|
||||
--button-hover-border-color: var(--views-button-border-color);
|
||||
--button-active-bg: var(
|
||||
--button-background-color-active,
|
||||
light-dark(rgb(21 20 26 / 0.21), rgb(251 251 254 / 0.21))
|
||||
);
|
||||
--button-active-fg: var(--views-text-color);
|
||||
--button-active-border-color: var(--views-button-border-color);
|
||||
--button-focus-no-bg: color-mix(
|
||||
in srgb,
|
||||
var(--views-text-color),
|
||||
transparent 93%
|
||||
);
|
||||
--button-focus-outline-color: var(
|
||||
--focus-outline-color,
|
||||
light-dark(#0062fa, #00cadb)
|
||||
);
|
||||
--button-focus-border-color: light-dark(white, black);
|
||||
--status-border-color: transparent;
|
||||
--status-actions-bg: light-dark(
|
||||
@ -81,7 +97,7 @@
|
||||
);
|
||||
--status-undo-bg: light-dark(rgb(0 98 250 / 0.08), rgb(0 202 219 / 0.08));
|
||||
--status-waiting-bg: var(--status-undo-bg);
|
||||
--indicator-color: light-dark(#0062fa, #00cadb);
|
||||
--indicator-color: var(--color-accent-primary, light-dark(#0062fa, #00cadb));
|
||||
--status-warning-bg: light-dark(#ffe8ea, #6e001f);
|
||||
--indicator-warning-color: light-dark(#b20037, #ffa0aa);
|
||||
--header-shadow:
|
||||
@ -95,7 +111,7 @@
|
||||
--image-current-border-color: var(--button-focus-outline-color);
|
||||
--image-current-focused-outline-color: var(--image-hover-border-color);
|
||||
--image-page-number-bg: light-dark(#f0f0f4, #42414d);
|
||||
--image-page-number-fg: var(--text-color);
|
||||
--image-page-number-fg: var(--views-text-color);
|
||||
--image-page-number-border-color: transparent;
|
||||
--image-hover-page-number-bg: var(--image-page-number-bg);
|
||||
--image-hover-page-number-fg: var(--image-page-number-fg);
|
||||
@ -134,23 +150,25 @@
|
||||
--multiple-dragging-text-color: light-dark(#fbfbfe, #15141a);
|
||||
|
||||
@media screen and (forced-colors: active) {
|
||||
--text-color: CanvasText;
|
||||
--button-fg: ButtonText;
|
||||
--button-bg: ButtonFace;
|
||||
--button-no-bg: ButtonFace;
|
||||
--button-border-color: ButtonText;
|
||||
--button-hover-bg: SelectedItemText;
|
||||
--button-hover-fg: SelectedItem;
|
||||
--button-hover-border-color: SelectedItem;
|
||||
--button-active-bg: SelectedItemText;
|
||||
--button-active-fg: SelectedItem;
|
||||
--button-active-border-color: ButtonText;
|
||||
--button-focus-no-bg: ButtonFace;
|
||||
--button-focus-outline-color: CanvasText;
|
||||
--views-text-color: var(--text-color, CanvasText);
|
||||
--button-fg: var(--button-text-color, ButtonText);
|
||||
--button-bg: var(--button-background-color, ButtonFace);
|
||||
--button-no-bg: var(--button-background-color, ButtonFace);
|
||||
--views-button-border-color: var(--button-border-color, ButtonText);
|
||||
--button-hover-bg: var(--button-background-color-hover, SelectedItemText);
|
||||
--button-hover-fg: var(--button-text-color-hover, SelectedItem);
|
||||
--button-hover-border-color: var(--button-border-color-hover, SelectedItem);
|
||||
--button-active-bg: var(--button-background-color-active, SelectedItemText);
|
||||
--button-active-fg: var(--button-text-color-active, SelectedItem);
|
||||
--button-active-border-color: var(--button-border-color-active, ButtonText);
|
||||
--button-focus-no-bg: var(--button-background-color, ButtonFace);
|
||||
--button-focus-outline-color: var(--focus-outline-color, CanvasText);
|
||||
--button-focus-border-color: none;
|
||||
--status-border-color: CanvasText;
|
||||
--status-undo-bg: none;
|
||||
--indicator-color: CanvasText;
|
||||
/* Painted on Canvas panels, so --text-color rather than the accent (which
|
||||
is ButtonText in Firefox HCM). */
|
||||
--indicator-color: var(--text-color, CanvasText);
|
||||
--status-warning-bg: none;
|
||||
--indicator-warning-color: CanvasText;
|
||||
--header-shadow: none;
|
||||
@ -162,7 +180,7 @@
|
||||
--image-hover-page-number-fg: SelectedItem;
|
||||
--image-current-page-number-bg: ButtonText;
|
||||
--image-current-page-number-fg: ButtonFace;
|
||||
--image-current-border-color: ButtonText;
|
||||
--image-current-border-color: var(--border-color, ButtonText);
|
||||
--image-current-focused-outline-color: var(--image-hover-border-color);
|
||||
--image-current-hover-page-number-bg: SelectedItem;
|
||||
--image-current-hover-page-number-fg: SelectedItemText;
|
||||
@ -170,7 +188,7 @@
|
||||
--image-page-number-fg: ButtonText;
|
||||
--image-page-number-border-color: var(--image-page-number-fg);
|
||||
--multiple-dragging-bg: Canvas;
|
||||
--multiple-dragging-indicator-bg: ButtonBorder;
|
||||
--multiple-dragging-indicator-bg: var(--text-color, ButtonBorder);
|
||||
--multiple-dragging-text-color: Canvas;
|
||||
--image-dragging-placeholder-bg: Canvas;
|
||||
--image-dragging-placeholder-border: 1px GrayText solid;
|
||||
@ -200,8 +218,8 @@
|
||||
.viewsManagerButton {
|
||||
width: auto;
|
||||
color: var(--button-fg);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
border-radius: var(--button-border-radius, 8px);
|
||||
border: 1px solid var(--views-button-border-color);
|
||||
background: var(--button-bg);
|
||||
|
||||
&:hover {
|
||||
@ -229,13 +247,17 @@
|
||||
}
|
||||
|
||||
&.viewsCloseButton {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
width: var(--size-item-large, 26px);
|
||||
height: var(--size-item-large, 26px);
|
||||
border-radius: var(--button-border-radius, 8px);
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::before {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-image: var(--close-button-icon);
|
||||
}
|
||||
}
|
||||
@ -253,7 +275,7 @@
|
||||
|
||||
.viewsManagerLabel {
|
||||
flex: 1 1 auto;
|
||||
color: var(--text-color);
|
||||
color: var(--views-text-color);
|
||||
text-align: center;
|
||||
height: fit-content;
|
||||
width: fit-content;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user