// ==UserScript==
// @name Invert PDF Button
// @version 1.1.2
// @author aminomancer
// @homepageURL https://github.com/aminomancer/uc.css.js
// @description Add a new button to Firefox's PDF.js viewer toolbar. It inverts the PDF colors to provide a dark mode.
// @downloadURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/invertPDFButton.sys.mjs
// @updateURL https://cdn.jsdelivr.net/gh/aminomancer/uc.css.js@master/JS/invertPDFButton.sys.mjs
// @license This Source Code Form is subject to the terms of the Creative Commons Attribution-NonCommercial-ShareAlike International License, v. 4.0. If a copy of the CC BY-NC-SA 4.0 was not distributed with this file, You can obtain one at http://creativecommons.org/licenses/by-nc-sa/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
// @backgroundmodule
// ==/UserScript==
export class InvertPDFButtonChild extends JSWindowActorChild {
static config = {
filter: "invert(98%) hue-rotate(176.4deg)",
icons: {
invert:
'data:image/svg+xml;utf8,',
uninvert:
'data:image/svg+xml;utf8,',
},
// Highlight colors are inverted along with everything else, which doesn't
// look good with all colors. So we change them to colors that look better
// under the filter. PDF.js still uses the default colors when not inverted.
highlightColors: {
yellow: "hsl(64, 70%, 50%)",
green: "hsl(156.6, 100%, 66.3%)",
blue: "hsl(189.4, 100%, 75.1%)",
pink: "hsl(300, 70%, 85%)",
red: "hsl(350, 80%, 85%)",
},
l10n: {
invert: "Invert PDF",
uninvert: "Uninvert PDF",
},
};
handleEvent(event) {
if (this.document?.nodePrincipal.originNoSuffix !== "resource://pdf.js") {
return;
}
this.setup();
}
setup() {
let container = this.document.getElementById("toolbarViewerRight");
if (container && !this.document.getElementById("editorInvert")) {
this.addButton(container);
}
let inverted = Services.prefs.getBoolPref(
"userChrome.invertPDFButton.inverted",
false
);
this.filter = Services.prefs.getStringPref(
"userChrome.invertPDFButton.filter",
InvertPDFButtonChild.config.filter
);
if (inverted) {
this.invertPDF();
} else {
this.uninvertPDF();
}
this.addStyles();
}
addButton(container) {
let separator = this.document.createElement("div");
separator.className = "verticalToolbarSeparator";
separator.id = "editorInvertSeparator";
container.prepend(separator);
let button = this.document.createElement("button");
button.id = "editorInvert";
button.className = "toolbarButton";
const { invert: label } = InvertPDFButtonChild.config.l10n;
button.title = label;
button.onclick = () => {
if (this.document.documentElement.dataset.inverted) {
this.uninvertPDF();
this.sendAsyncMessage("UninvertPDF");
} else {
this.invertPDF();
this.sendAsyncMessage("InvertPDF");
}
};
let altText = this.document.createElement("span");
altText.textContent = label;
button.appendChild(altText);
container.prepend(button);
}
addStyles() {
if (!this.document.getElementById("editorInvertStyles")) {
const {
icons: { invert, uninvert },
highlightColors,
} = InvertPDFButtonChild.config;
let stylesheet = this.document.createElement("style");
stylesheet.id = "editorInvertStyles";
stylesheet.textContent = /* css */ `#editorInvert::before {
mask-image: url('${invert}');
}
:root[data-inverted] {
#editorInvert::before {
mask-image: url('${uninvert}');
}
.pdfViewer,
#thumbnailView .thumbnailImage,
.editToolbar,
.freeTextEditor,
.inkEditorCanvas {
filter: ${this.filter};
}
.highlight {
&[fill="#FFFF98"] {
fill: ${highlightColors.yellow};
}
&[fill="#53FFBC"] {
fill: ${highlightColors.green};
}
&[fill="#80EBFF"] {
fill: ${highlightColors.blue};
}
&[fill="#FFCBE6"] {
fill: ${highlightColors.pink};
}
&[fill="#FF4F5F"] {
fill: ${highlightColors.red};
}
}
.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor) {
&.selectedEditor::before,
& > .resizers {
filter: ${this.filter};
}
}
}`;
this.document.head.appendChild(stylesheet);
}
}
invertPDF() {
this.document.documentElement.dataset.inverted = true;
let button = this.document.getElementById("editorInvert");
if (button) {
const { uninvert } = InvertPDFButtonChild.config.l10n;
button.title = uninvert;
button.querySelector("span").textContent = uninvert;
}
}
uninvertPDF() {
delete this.document.documentElement.dataset.inverted;
let button = this.document.getElementById("editorInvert");
if (button) {
const { invert } = InvertPDFButtonChild.config.l10n;
button.title = invert;
button.querySelector("span").textContent = invert;
}
}
}
export class InvertPDFButtonParent extends JSWindowActorParent {
async receiveMessage({ name } = {}) {
switch (name) {
case "InvertPDF":
Services.prefs.setBoolPref("userChrome.invertPDFButton.inverted", true);
break;
case "UninvertPDF":
Services.prefs.clearUserPref("userChrome.invertPDFButton.inverted");
break;
}
}
}
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT) {
Services.prefs
.getDefaultBranch("")
.setBoolPref("userChrome.invertPDFButton.inverted", false);
Services.prefs
.getDefaultBranch("")
.setStringPref(
"userChrome.invertPDFButton.filter",
InvertPDFButtonChild.config.filter
);
let esModuleURI = import.meta.url;
ChromeUtils.registerWindowActor("InvertPDFButton", {
child: { esModuleURI, events: { DOMContentLoaded: {} } },
parent: { esModuleURI },
allFrames: true,
messageManagerGroups: ["browsers"],
});
}