added filter toggle button

- new `string-template` and `renderStringPref` (regex pattern matching) in chromium extension options
- new `cssInvertPage`, `cssInvertThumb`, and `cssInvertFilter` settings (with defaults; off by default)
  - available filters: `brightness()`, `contrast()`, `grayscale()`, `invert()`, `sepia()`, `saturate()`, and `hue-rotate()` via regex (valid CSS syntax)
- translations for `en-US`, `en-GB`, `en-CA`, and `de`
- filter influences PDF view container and PDF thmbnail container (thus also custom text/drawings/highlights)
- new svg as icon (modified `toolbarButton-editorStamp.svg`)
- button toggle is added next to zoom/scale controls in the middle of the toolbar
This commit is contained in:
MAZ 2025-07-29 18:33:33 +02:00
parent 8460d903e2
commit 6cb11e420a
14 changed files with 150 additions and 0 deletions

View File

@ -43,6 +43,15 @@ input:invalid {
<div id="settings-boxes"></div>
<button id="reset-button" type="button">Restore default settings</button>
<template id="string-template">
<div class="settings-row">
<label>
<span></span>
<input type="text" value="" pattern="" placeholder="">
</label>
</div>
</template>
<template id="number-template">
<div class="settings-row">
<label>

View File

@ -105,6 +105,14 @@ Promise.all([
"1",
String(prefSchema.default)
);
} else if (prefSchema.type === "string") {
renderPreference = renderStringPref(
prefSchema.title,
prefSchema.description,
prefName,
prefSchema.pattern,
prefSchema.default
);
} else {
// Should NEVER be reached. Only happens if a new type of preference is
// added to the storage manifest.
@ -212,6 +220,42 @@ function renderNumberPref(
return renderPreference;
}
function renderStringPref(
shortDescription,
description,
prefName,
pattern,
placeholder
) {
var wrapper = importTemplate("string-template");
var textInput = wrapper.querySelector("input");
textInput.pattern = pattern;
textInput.placeholder = placeholder;
textInput.oninput = function () {
textInput.setCustomValidity(
textInput.validity.patternMismatch
? "Invalid value will not be saved!"
: ""
);
};
textInput.onchange = function () {
if (!textInput.reportValidity()) {
return;
}
var pref = {};
pref[prefName] = this.value;
storageArea.set(pref);
};
wrapper.querySelector("span").textContent = shortDescription;
wrapper.querySelector("label").title = description;
document.getElementById("settings-boxes").append(wrapper);
function renderPreference(value) {
textInput.value = value;
}
return renderPreference;
}
function renderBooleanPref(shortDescription, description, prefName) {
var wrapper = importTemplate("checkbox-template");
var checkbox = wrapper.querySelector('input[type="checkbox"]');

View File

@ -8,6 +8,25 @@
"enum": [0, 1, 2],
"default": 2
},
"cssInvertPage": {
"title": "Default brightness inversion",
"description": "Toggle on brightness inversion by default.",
"type": "boolean",
"default": false
},
"cssInvertThumb": {
"title": "Brightness invert thubnails",
"description": "Also apply brightness inversion for the page thubnails in the sidebar.",
"type": "boolean",
"default": true
},
"cssInvertFilter": {
"title": "Brightness inversion CSS filter",
"description": "The CSS filter used for the toggle. Available filters: 'brightness()', 'contrast()', 'grayscale()', 'invert()', 'sepia()', 'saturate()', and 'hue-rotate()'.",
"type": "string",
"pattern": "(?: *(?:brightness|contrast|grayscale|invert|sepia|saturate)\\( *(?:[+\\-]?(?:[0-9]+(?:\\.[0-9]+)?|[0-9]*\\.[0-9]+)(?:[eE][+\\-]?[0-9]+)?%? *)?\\)| *hue-rotate\\( *(?:0 *|[+\\-]?(?:[0-9]+(?:\\.[0-9]+)?|[0-9]*\\.[0-9]+)(?:[eE][+\\-]?[0-9]+)?(?:deg|g?rad|turn) *)?\\))+ *",
"default": "invert(90%) hue-rotate(180deg)"
},
"showPreviousViewOnLoad": {
"description": "DEPRECATED. Set viewOnLoad to 1 to disable showing the last page/position on load.",
"type": "boolean",

View File

@ -30,6 +30,10 @@ pdfjs-zoom-in-button =
pdfjs-zoom-in-button-label = Vergrößern
pdfjs-zoom-select =
.title = Zoom
pdfjs-invert-button =
.title = Invertierte Helligkeit umschalten
pdfjs-invert-button-label = Helligkeit umkehren
.title = Invert brightness
pdfjs-presentation-mode-button =
.title = In Präsentationsmodus wechseln
pdfjs-presentation-mode-button-label = Präsentationsmodus

View File

@ -30,6 +30,10 @@ pdfjs-zoom-in-button =
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-invert-button =
.title = Toggle inverted brightness
pdfjs-invert-button-label = Invert brightness
.title = Invert brightness
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode

View File

@ -30,6 +30,10 @@ pdfjs-zoom-in-button =
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-invert-button =
.title = Toggle inverted brightness
pdfjs-invert-button-label = Invert brightness
.title = Invert brightness
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode

View File

@ -33,6 +33,10 @@ pdfjs-zoom-in-button =
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
.title = Zoom
pdfjs-invert-button =
.title = Toggle inverted brightness
pdfjs-invert-button-label = Invert brightness
.title = Invert brightness
pdfjs-presentation-mode-button =
.title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode

View File

@ -219,6 +219,8 @@ const PDFViewerApplication = {
docStyle.setProperty("color-scheme", mode);
}
onInvert.call(this);
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) {
if (AppOptions.get("enableFakeMLManager")) {
this.mlManager =
@ -2019,6 +2021,7 @@ const PDFViewerApplication = {
eventBus._on("zoomin", this.zoomIn.bind(this), opts);
eventBus._on("zoomout", this.zoomOut.bind(this), opts);
eventBus._on("zoomreset", this.zoomReset.bind(this), opts);
eventBus._on("invert", onInvert.bind(this), opts);
eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), opts);
eventBus._on(
"scalechanged",
@ -2445,6 +2448,17 @@ function onNamedAction(evt) {
}
}
function onInvert(evt) {
// Handle brightness inversion (CSS filter) toggle
const active = evt?.state ?? AppOptions.get("cssInvertPage");
const filter = AppOptions.get("cssInvertFilter");
this.appConfig.toolbar.invert.classList.toggle("toggled", active);
this.appConfig.toolbar.invert.ariaChecked = String(active);
this.appConfig.viewerContainer.style.filter = active ? filter : "";
this.appConfig.sidebar.thumbnailView.style.filter =
active && AppOptions.get("cssInvertThumb") ? filter : "";
}
function onSidebarViewChanged({ view }) {
this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS;

View File

@ -178,6 +178,21 @@ const defaultOptions = {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
cssInvertPage: {
/** @type {boolean} */
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
cssInvertThumb: {
/** @type {boolean} */
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
cssInvertFilter: {
/** @type {string} */
value: "invert(90%) hue-rotate(180deg)",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
},
debuggerSrc: {
/** @type {string} */
value: "./debugger.mjs",

View File

@ -0,0 +1,12 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="black">
<path d="
M3 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2v-10a2 2 0 0 0-2-2z
M5 12a1 1 0 0 1-1-1v-1.321a.75.75 0 0 1 .218-.529L6.35 7.005a.75.75 0 0 1 1.061-.003l2.112 2.102.612-.577a.75.75 0 0 1 1.047.017l.6.605a.75.75 0 0 1 .218.529L12 11a1 1 0 0 1-1 1z
M11.6 5m.4.4v1.2l-.4.4h-1.2l-.4-.4v-1.2l.4-.4h1.2z
M14 13l-1 1h-10l-1-1v-10l1-1h10l1 1z
M13.5 12.75l-.75.75h-9.5l-.75-.75v-9.5l.75-.75h9.5l.75.75z
" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 772 B

View File

@ -38,6 +38,8 @@ import {
* @property {HTMLButtonElement} next - Button to go to the next page.
* @property {HTMLButtonElement} zoomIn - Button to zoom in the pages.
* @property {HTMLButtonElement} zoomOut - Button to zoom out the pages.
* @property {HTMLButtonElement} invert - Button to toggle brightness inversion
* (CSS filter).
* @property {HTMLButtonElement} editorFreeTextButton - Button to switch to
* FreeText editing.
* @property {HTMLButtonElement} download - Button to download the document.
@ -67,6 +69,16 @@ class Toolbar {
{ element: options.zoomOut, eventName: "zoomout" },
{ element: options.print, eventName: "print" },
{ element: options.download, eventName: "download" },
{
element: options.invert,
eventName: "invert",
eventDetails: {
get state() {
const { classList } = options.invert;
return !classList.contains("toggled");
},
},
},
{
element: options.editorFreeTextButton,
eventName: "switchannotationeditormode",

View File

@ -111,6 +111,7 @@
--toolbarButton-pageDown-icon: url(images/toolbarButton-pageDown.svg);
--toolbarButton-zoomOut-icon: url(images/toolbarButton-zoomOut.svg);
--toolbarButton-zoomIn-icon: url(images/toolbarButton-zoomIn.svg);
--toolbarButton-invert-icon: url(images/toolbarButton-invert.svg);
--toolbarButton-presentationMode-icon: url(images/toolbarButton-presentationMode.svg);
--toolbarButton-print-icon: url(images/toolbarButton-print.svg);
/*#if GENERIC*/
@ -540,6 +541,10 @@ body {
mask-image: var(--toolbarButton-zoomIn-icon);
}
#invertButton::before {
mask-image: var(--toolbarButton-invert-icon);
}
#editorFreeTextButton::before {
mask-image: var(--toolbarButton-editorFreeText-icon);
}

View File

@ -242,6 +242,9 @@ See https://github.com/adobe-type-tools/cmap-resources
<option value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'></option>
</select>
</span>
<button id="invertButton" class="toolbarButton" type="button" tabindex="0" data-l10n-id="pdfjs-invert-button" role="checkbox" aria-checked="false">
<span data-l10n-id="pdfjs-invert-button-label"></span>
</button>
</div>
<div id="toolbarViewerRight" class="toolbarHorizontalGroup">
<div id="editorModeButtons" class="toolbarHorizontalGroup" role="radiogroup">

View File

@ -44,6 +44,7 @@ function getViewerConfiguration() {
next: document.getElementById("next"),
zoomIn: document.getElementById("zoomInButton"),
zoomOut: document.getElementById("zoomOutButton"),
invert: document.getElementById("invertButton"),
print: document.getElementById("printButton"),
editorFreeTextButton: document.getElementById("editorFreeTextButton"),
editorFreeTextParamsToolbar: document.getElementById(