// ==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"], }); }