[Editor] Use the color of the annotation for the background of the comment button

This commit is contained in:
Calixte Denizet 2025-08-31 22:32:11 +02:00
parent c8d49db624
commit ca280399c2
13 changed files with 114 additions and 51 deletions

View File

@ -38,6 +38,7 @@ import {
warn, warn,
} from "../shared/util.js"; } from "../shared/util.js";
import { import {
applyOpacity,
changeLightness, changeLightness,
PDFDateString, PDFDateString,
setLayerDimensions, setLayerDimensions,
@ -232,15 +233,8 @@ class AnnotationElement {
if (!this.data.color) { if (!this.data.color) {
return null; return null;
} }
const [r, g, b] = this.data.color; const [r, g, b] = applyOpacity(...this.data.color, this.data.opacity);
const opacity = this.data.opacity ?? 1; return changeLightness(r, g, b);
const oppositeOpacity = 255 * (1 - opacity);
return changeLightness(
Math.min(r + oppositeOpacity, 255),
Math.min(g + oppositeOpacity, 255),
Math.min(b + oppositeOpacity, 255)
);
} }
_normalizePoint(point) { _normalizePoint(point) {

View File

@ -770,7 +770,24 @@ const SupportedImageMimeTypes = [
"image/x-icon", "image/x-icon",
]; ];
function changeLightness(r, g, b, lumCallback = l => (1 + Math.sqrt(l)) / 2) { class ColorScheme {
static get isDarkMode() {
return shadow(
this,
"isDarkMode",
!!window?.matchMedia?.("(prefers-color-scheme: dark)").matches
);
}
}
function changeLightness(
r,
g,
b,
lumCallback = ColorScheme.isDarkMode
? l => (1 - Math.sqrt(1 - l)) / 2
: l => (1 + Math.sqrt(l)) / 2
) {
r /= 255; r /= 255;
g /= 255; g /= 255;
b /= 255; b /= 255;
@ -803,8 +820,19 @@ function changeLightness(r, g, b, lumCallback = l => (1 + Math.sqrt(l)) / 2) {
return `hsl(${h}, ${s}%, ${newL}%)`; return `hsl(${h}, ${s}%, ${newL}%)`;
} }
function applyOpacity(r, g, b, opacity) {
opacity = Math.min(Math.max(opacity ?? 1, 0), 1);
const white = 255 * (1 - opacity);
r = Math.round(r * opacity + white);
g = Math.round(g * opacity + white);
b = Math.round(b * opacity + white);
return [r, g, b];
}
export { export {
applyOpacity,
changeLightness, changeLightness,
ColorScheme,
deprecated, deprecated,
fetchData, fetchData,
getColorValues, getColorValues,

View File

@ -58,6 +58,10 @@ class Comment {
: position[0]) : position[0])
}% - var(--comment-button-dim))`; }% - var(--comment-button-dim))`;
style.top = `calc(${100 * position[1]}% - var(--comment-button-dim))`; style.top = `calc(${100 * position[1]}% - var(--comment-button-dim))`;
const color = this.#editor.commentButtonColor;
if (color) {
style.backgroundColor = color;
}
} }
return this.#render(button); return this.#render(button);

View File

@ -22,13 +22,18 @@ import {
ColorManager, ColorManager,
KeyboardManager, KeyboardManager,
} from "./tools.js"; } from "./tools.js";
import {
applyOpacity,
changeLightness,
noContextMenu,
stopEvent,
} from "../display_utils.js";
import { import {
FeatureTest, FeatureTest,
MathClamp, MathClamp,
shadow, shadow,
unreachable, unreachable,
} from "../../shared/util.js"; } from "../../shared/util.js";
import { noContextMenu, stopEvent } from "../display_utils.js";
import { AltText } from "./alt_text.js"; import { AltText } from "./alt_text.js";
import { Comment } from "./comment.js"; import { Comment } from "./comment.js";
import { EditorToolbar } from "./toolbar.js"; import { EditorToolbar } from "./toolbar.js";
@ -1857,6 +1862,17 @@ class AnnotationEditor {
return this._uiManager.direction === "ltr" ? [1, 0] : [0, 0]; return this._uiManager.direction === "ltr" ? [1, 0] : [0, 0];
} }
get commentButtonColor() {
if (!this.color) {
return null;
}
let [r, g, b] = AnnotationEditor._colorManager.convert(
this._uiManager.getNonHCMColor(this.color)
);
[r, g, b] = applyOpacity(r, g, b, this.opacity);
return changeLightness(r, g, b);
}
/** /**
* onkeydown callback. * onkeydown callback.
* @param {KeyboardEvent} event * @param {KeyboardEvent} event

View File

@ -35,8 +35,6 @@ const EOL_PATTERN = /\r\n?|\n/g;
* Basic text editor in order to create a FreeTex annotation. * Basic text editor in order to create a FreeTex annotation.
*/ */
class FreeTextEditor extends AnnotationEditor { class FreeTextEditor extends AnnotationEditor {
#color;
#content = ""; #content = "";
#editorDivId = `${this.id}-editor`; #editorDivId = `${this.id}-editor`;
@ -129,7 +127,7 @@ class FreeTextEditor extends AnnotationEditor {
constructor(params) { constructor(params) {
super({ ...params, name: "freeTextEditor" }); super({ ...params, name: "freeTextEditor" });
this.#color = this.color =
params.color || params.color ||
FreeTextEditor._defaultColor || FreeTextEditor._defaultColor ||
AnnotationEditor._defaultLineColor; AnnotationEditor._defaultLineColor;
@ -201,7 +199,7 @@ class FreeTextEditor extends AnnotationEditor {
get propertiesToUpdate() { get propertiesToUpdate() {
return [ return [
[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize],
[AnnotationEditorParamsType.FREETEXT_COLOR, this.#color], [AnnotationEditorParamsType.FREETEXT_COLOR, this.color],
]; ];
} }
@ -215,10 +213,6 @@ class FreeTextEditor extends AnnotationEditor {
return AnnotationEditorParamsType.FREETEXT_COLOR; return AnnotationEditorParamsType.FREETEXT_COLOR;
} }
get colorValue() {
return this.#color;
}
/** /**
* Update the font size and make this action as undoable. * Update the font size and make this action as undoable.
* @param {number} fontSize * @param {number} fontSize
@ -248,10 +242,10 @@ class FreeTextEditor extends AnnotationEditor {
*/ */
#updateColor(color) { #updateColor(color) {
const setColor = col => { const setColor = col => {
this.#color = this.editorDiv.style.color = col; this.color = this.editorDiv.style.color = col;
this._colorPicker?.update(col); this._colorPicker?.update(col);
}; };
const savedColor = this.#color; const savedColor = this.color;
this.addCommands({ this.addCommands({
cmd: setColor.bind(this, color), cmd: setColor.bind(this, color),
undo: setColor.bind(this, savedColor), undo: setColor.bind(this, savedColor),
@ -581,7 +575,7 @@ class FreeTextEditor extends AnnotationEditor {
const { style } = this.editorDiv; const { style } = this.editorDiv;
style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`; style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color; style.color = this.color;
this.div.append(this.editorDiv); this.div.append(this.editorDiv);
@ -819,7 +813,7 @@ class FreeTextEditor extends AnnotationEditor {
} }
const editor = await super.deserialize(data, parent, uiManager); const editor = await super.deserialize(data, parent, uiManager);
editor.#fontSize = data.fontSize; editor.#fontSize = data.fontSize;
editor.#color = Util.makeHexColor(...data.color); editor.color = Util.makeHexColor(...data.color);
editor.#content = FreeTextEditor.#deserializeContent(data.value); editor.#content = FreeTextEditor.#deserializeContent(data.value);
editor._initialData = initialData; editor._initialData = initialData;
if (data.comment) { if (data.comment) {
@ -841,9 +835,7 @@ class FreeTextEditor extends AnnotationEditor {
const rect = this.getPDFRect(); const rect = this.getPDFRect();
const color = AnnotationEditor._colorManager.convert( const color = AnnotationEditor._colorManager.convert(
this.isAttachedToDOM this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.color
? getComputedStyle(this.editorDiv).color
: this.#color
); );
const serialized = { const serialized = {
@ -895,7 +887,7 @@ class FreeTextEditor extends AnnotationEditor {
} }
const { style } = content; const { style } = content;
style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`; style.fontSize = `calc(${this.#fontSize}px * var(--total-scale-factor))`;
style.color = this.#color; style.color = this.color;
content.replaceChildren(); content.replaceChildren();
for (const line of this.#content.split("\n")) { for (const line of this.#content.split("\n")) {

View File

@ -64,8 +64,6 @@ class HighlightEditor extends AnnotationEditor {
#lastPoint = null; #lastPoint = null;
#opacity;
#outlineId = null; #outlineId = null;
#text = ""; #text = "";
@ -108,7 +106,7 @@ class HighlightEditor extends AnnotationEditor {
super({ ...params, name: "highlightEditor" }); super({ ...params, name: "highlightEditor" });
this.color = params.color || HighlightEditor._defaultColor; this.color = params.color || HighlightEditor._defaultColor;
this.#thickness = params.thickness || HighlightEditor._defaultThickness; this.#thickness = params.thickness || HighlightEditor._defaultThickness;
this.#opacity = params.opacity || HighlightEditor._defaultOpacity; this.opacity = params.opacity || HighlightEditor._defaultOpacity;
this.#boxes = params.boxes || null; this.#boxes = params.boxes || null;
this.#methodOfCreation = params.methodOfCreation || ""; this.#methodOfCreation = params.methodOfCreation || "";
this.#text = params.text || ""; this.#text = params.text || "";
@ -361,7 +359,7 @@ class HighlightEditor extends AnnotationEditor {
#updateColor(color) { #updateColor(color) {
const setColorAndOpacity = (col, opa) => { const setColorAndOpacity = (col, opa) => {
this.color = col; this.color = col;
this.#opacity = opa; this.opacity = opa;
this.parent?.drawLayer.updateProperties(this.#id, { this.parent?.drawLayer.updateProperties(this.#id, {
root: { root: {
fill: col, fill: col,
@ -371,7 +369,7 @@ class HighlightEditor extends AnnotationEditor {
this.#colorPicker?.updateColor(col); this.#colorPicker?.updateColor(col);
}; };
const savedColor = this.color; const savedColor = this.color;
const savedOpacity = this.#opacity; const savedOpacity = this.opacity;
this.addCommands({ this.addCommands({
cmd: setColorAndOpacity.bind( cmd: setColorAndOpacity.bind(
this, this,
@ -549,7 +547,7 @@ class HighlightEditor extends AnnotationEditor {
root: { root: {
viewBox: "0 0 1 1", viewBox: "0 0 1 1",
fill: this.color, fill: this.color,
"fill-opacity": this.#opacity, "fill-opacity": this.opacity,
}, },
rootClass: { rootClass: {
highlight: true, highlight: true,
@ -951,7 +949,7 @@ class HighlightEditor extends AnnotationEditor {
const editor = await super.deserialize(data, parent, uiManager); const editor = await super.deserialize(data, parent, uiManager);
editor.color = Util.makeHexColor(...color); editor.color = Util.makeHexColor(...color);
editor.#opacity = opacity || 1; editor.opacity = opacity || 1;
if (inkLists) { if (inkLists) {
editor.#thickness = data.thickness; editor.#thickness = data.thickness;
} }
@ -1046,7 +1044,7 @@ class HighlightEditor extends AnnotationEditor {
const serialized = { const serialized = {
annotationType: AnnotationEditorType.HIGHLIGHT, annotationType: AnnotationEditorType.HIGHLIGHT,
color, color,
opacity: this.#opacity, opacity: this.opacity,
thickness: this.#thickness, thickness: this.#thickness,
quadPoints: this.#serializeBoxes(), quadPoints: this.#serializeBoxes(),
outlines: this.#serializeOutlines(rect), outlines: this.#serializeOutlines(rect),

View File

@ -193,10 +193,14 @@ class InkEditor extends DrawingEditor {
return AnnotationEditorParamsType.INK_COLOR; return AnnotationEditorParamsType.INK_COLOR;
} }
get colorValue() { get color() {
return this._drawingOptions.stroke; return this._drawingOptions.stroke;
} }
get opacity() {
return this._drawingOptions["stroke-opacity"];
}
/** @inheritdoc */ /** @inheritdoc */
onScaleChanging() { onScaleChanging() {
if (!this.parent) { if (!this.parent) {

View File

@ -15,6 +15,7 @@
import { AnnotationEditorType, AnnotationPrefix } from "../../shared/util.js"; import { AnnotationEditorType, AnnotationPrefix } from "../../shared/util.js";
import { import {
ColorScheme,
OutputScale, OutputScale,
PixelsPerInch, PixelsPerInch,
SupportedImageMimeTypes, SupportedImageMimeTypes,
@ -535,7 +536,7 @@ class StampEditor extends AnnotationEditor {
black = "#cfcfd8"; black = "#cfcfd8";
if (this._uiManager.hcmFilter !== "none") { if (this._uiManager.hcmFilter !== "none") {
black = "black"; black = "black";
} else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) { } else if (ColorScheme.isDarkMode) {
white = "#8f8f9d"; white = "#8f8f9d";
black = "#42414d"; black = "#42414d";
} }

View File

@ -45,13 +45,7 @@ import {
VerbosityLevel, VerbosityLevel,
} from "./shared/util.js"; } from "./shared/util.js";
import { import {
build, applyOpacity,
getDocument,
PDFDataRangeTransport,
PDFWorker,
version,
} from "./display/api.js";
import {
changeLightness, changeLightness,
fetchData, fetchData,
getFilenameFromUrl, getFilenameFromUrl,
@ -69,6 +63,13 @@ import {
stopEvent, stopEvent,
SupportedImageMimeTypes, SupportedImageMimeTypes,
} from "./display/display_utils.js"; } from "./display/display_utils.js";
import {
build,
getDocument,
PDFDataRangeTransport,
PDFWorker,
version,
} from "./display/api.js";
import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js"; import { AnnotationEditorLayer } from "./display/editor/annotation_editor_layer.js";
import { AnnotationEditorUIManager } from "./display/editor/tools.js"; import { AnnotationEditorUIManager } from "./display/editor/tools.js";
import { AnnotationLayer } from "./display/annotation_layer.js"; import { AnnotationLayer } from "./display/annotation_layer.js";
@ -98,6 +99,7 @@ globalThis.pdfjsLib = {
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
AnnotationType, AnnotationType,
applyOpacity,
build, build,
changeLightness, changeLightness,
ColorPicker, ColorPicker,
@ -154,6 +156,7 @@ export {
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
AnnotationType, AnnotationType,
applyOpacity,
build, build,
changeLightness, changeLightness,
ColorPicker, ColorPicker,

View File

@ -14,6 +14,7 @@
*/ */
import { import {
applyOpacity,
changeLightness, changeLightness,
getFilenameFromUrl, getFilenameFromUrl,
getPdfFilenameFromUrl, getPdfFilenameFromUrl,
@ -324,4 +325,21 @@ describe("display_utils", function () {
); );
}); });
}); });
describe("applyOpacity", function () {
it("Check that the opacity is applied correctly", function () {
if (isNodeJS) {
pending("OffscreenCanvas is not supported in Node.js.");
}
const canvas = new OffscreenCanvas(1, 1);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "white";
ctx.fillRect(0, 0, 1, 1);
ctx.fillStyle = "rgb(123, 45, 67)";
ctx.globalAlpha = 0.8;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
expect(applyOpacity(123, 45, 67, ctx.globalAlpha)).toEqual([r, g, b]);
});
});
}); });

View File

@ -36,13 +36,7 @@ import {
VerbosityLevel, VerbosityLevel,
} from "../../src/shared/util.js"; } from "../../src/shared/util.js";
import { import {
build, applyOpacity,
getDocument,
PDFDataRangeTransport,
PDFWorker,
version,
} from "../../src/display/api.js";
import {
changeLightness, changeLightness,
fetchData, fetchData,
getFilenameFromUrl, getFilenameFromUrl,
@ -60,6 +54,13 @@ import {
stopEvent, stopEvent,
SupportedImageMimeTypes, SupportedImageMimeTypes,
} from "../../src/display/display_utils.js"; } from "../../src/display/display_utils.js";
import {
build,
getDocument,
PDFDataRangeTransport,
PDFWorker,
version,
} from "../../src/display/api.js";
import { AnnotationEditorLayer } from "../../src/display/editor/annotation_editor_layer.js"; import { AnnotationEditorLayer } from "../../src/display/editor/annotation_editor_layer.js";
import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js"; import { AnnotationEditorUIManager } from "../../src/display/editor/tools.js";
import { AnnotationLayer } from "../../src/display/annotation_layer.js"; import { AnnotationLayer } from "../../src/display/annotation_layer.js";
@ -82,6 +83,7 @@ const expectedAPI = Object.freeze({
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
AnnotationType, AnnotationType,
applyOpacity,
build, build,
changeLightness, changeLightness,
ColorPicker, ColorPicker,

View File

@ -257,6 +257,7 @@
:is(.annotationLayer, .annotationEditorLayer) { :is(.annotationLayer, .annotationEditorLayer) {
.annotationCommentButton { .annotationCommentButton {
color-scheme: light dark;
--comment-button-bg: light-dark(white, #1c1b22); --comment-button-bg: light-dark(white, #1c1b22);
--comment-button-fg: light-dark(#5b5b66, #fbfbfe); --comment-button-fg: light-dark(#5b5b66, #fbfbfe);
--comment-button-active-bg: light-dark(#0041a4, #a6ecf4); --comment-button-active-bg: light-dark(#0041a4, #a6ecf4);

View File

@ -22,6 +22,7 @@ const {
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
AnnotationType, AnnotationType,
applyOpacity,
build, build,
changeLightness, changeLightness,
ColorPicker, ColorPicker,
@ -78,6 +79,7 @@ export {
AnnotationLayer, AnnotationLayer,
AnnotationMode, AnnotationMode,
AnnotationType, AnnotationType,
applyOpacity,
build, build,
changeLightness, changeLightness,
ColorPicker, ColorPicker,