Skip to content

Commit dbd6f8c

Browse files
committed
[Editor] Add a color picker in the toolbar of Ink and Freetext annotations
1 parent e0783cd commit dbd6f8c

10 files changed

Lines changed: 207 additions & 8 deletions

File tree

l10n/en-US/viewer.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,9 +306,13 @@ pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fo
306306

307307
pdfjs-editor-free-text-button =
308308
.title = Text
309+
pdfjs-editor-color-picker-free-text-input =
310+
.title = Change text color
309311
pdfjs-editor-free-text-button-label = Text
310312
pdfjs-editor-ink-button =
311313
.title = Draw
314+
pdfjs-editor-color-picker-ink-input =
315+
.title = Change drawing color
312316
pdfjs-editor-ink-button-label = Draw
313317
pdfjs-editor-stamp-button =
314318
.title = Add or edit images

src/display/editor/color_picker.js

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import { AnnotationEditorParamsType, shadow } from "../../shared/util.js";
1717
import { KeyboardManager } from "./tools.js";
1818
import { noContextMenu } from "../display_utils.js";
1919

20+
/**
21+
* ColorPicker class provides a color picker for the annotation editor.
22+
* It displays a dropdown with some predefined colors and allows the user
23+
* to select a color for the annotation.
24+
*/
2025
class ColorPicker {
2126
#button = null;
2227

@@ -304,4 +309,64 @@ class ColorPicker {
304309
}
305310
}
306311

307-
export { ColorPicker };
312+
/**
313+
* BasicColorPicker class provides a simple color picker.
314+
* It displays an input element (with type="color") that allows the user
315+
* to select a color for the annotation.
316+
*/
317+
class BasicColorPicker {
318+
#input = null;
319+
320+
#editor = null;
321+
322+
#uiManager = null;
323+
324+
static #l10nColor = null;
325+
326+
constructor(editor) {
327+
this.#editor = editor;
328+
this.#uiManager = editor._uiManager;
329+
330+
BasicColorPicker.#l10nColor ||= Object.freeze({
331+
freetext: "pdfjs-editor-color-picker-free-text-input",
332+
ink: "pdfjs-editor-color-picker-ink-input",
333+
});
334+
}
335+
336+
renderButton() {
337+
if (this.#input) {
338+
return this.#input;
339+
}
340+
const { editorType, colorType, colorValue } = this.#editor;
341+
const input = (this.#input = document.createElement("input"));
342+
input.type = "color";
343+
input.value = colorValue || "#000000";
344+
input.className = "basicColorPicker";
345+
input.tabIndex = 0;
346+
input.setAttribute("data-l10n-id", BasicColorPicker.#l10nColor[editorType]);
347+
input.addEventListener(
348+
"input",
349+
() => {
350+
this.#uiManager.updateParams(colorType, input.value);
351+
},
352+
{ signal: this.#uiManager._signal }
353+
);
354+
return input;
355+
}
356+
357+
update(value) {
358+
if (!this.#input) {
359+
return;
360+
}
361+
this.#input.value = value;
362+
}
363+
364+
destroy() {
365+
this.#input?.remove();
366+
this.#input = null;
367+
}
368+
369+
hideDropdown() {}
370+
}
371+
372+
export { BasicColorPicker, ColorPicker };

src/display/editor/draw.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ class DrawingEditor extends AnnotationEditor {
6767

6868
#mustBeCommitted;
6969

70+
_colorPicker = null;
71+
7072
_drawId = null;
7173

7274
static _currentDrawId = -1;
@@ -240,6 +242,9 @@ class DrawingEditor extends AnnotationEditor {
240242
this._drawId,
241243
options.toSVGProperties()
242244
);
245+
if (type === this.colorType) {
246+
this._colorPicker?.update(val);
247+
}
243248
};
244249
this.addCommands({
245250
cmd: setter.bind(this, value),

src/display/editor/editor.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1059,7 +1059,7 @@ class AnnotationEditor {
10591059

10601060
/**
10611061
* Get the toolbar buttons for this editor.
1062-
* @returns {Array<Array<string|object>>|null}
1062+
* @returns {Array<Array<string|object|null>>|null}
10631063
*/
10641064
get toolbarButtons() {
10651065
return null;

src/display/editor/freetext.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
} from "../../shared/util.js";
2727
import { AnnotationEditorUIManager, KeyboardManager } from "./tools.js";
2828
import { AnnotationEditor } from "./editor.js";
29+
import { BasicColorPicker } from "./color_picker.js";
2930
import { FreeTextAnnotationElement } from "../annotation_layer.js";
3031

3132
const EOL_PATTERN = /\r\n?|\n/g;
@@ -44,6 +45,8 @@ class FreeTextEditor extends AnnotationEditor {
4445

4546
#fontSize;
4647

48+
_colorPicker = null;
49+
4750
static _freeTextDefaultContent = "";
4851

4952
static _internalPadding = 0;
@@ -202,6 +205,20 @@ class FreeTextEditor extends AnnotationEditor {
202205
];
203206
}
204207

208+
/** @inheritdoc */
209+
get toolbarButtons() {
210+
this._colorPicker ||= new BasicColorPicker(this);
211+
return [["colorPicker", this._colorPicker]];
212+
}
213+
214+
get colorType() {
215+
return AnnotationEditorParamsType.FREETEXT_COLOR;
216+
}
217+
218+
get colorValue() {
219+
return this.#color;
220+
}
221+
205222
/**
206223
* Update the font size and make this action as undoable.
207224
* @param {number} fontSize
@@ -232,6 +249,7 @@ class FreeTextEditor extends AnnotationEditor {
232249
#updateColor(color) {
233250
const setColor = col => {
234251
this.#color = this.editorDiv.style.color = col;
252+
this._colorPicker?.update(col);
235253
};
236254
const savedColor = this.#color;
237255
this.addCommands({

src/display/editor/ink.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { DrawingEditor, DrawingOptions } from "./draw.js";
2323
import { InkDrawOutline, InkDrawOutliner } from "./drawers/inkdraw.js";
2424
import { AnnotationEditor } from "./editor.js";
25+
import { BasicColorPicker } from "./color_picker.js";
2526
import { InkAnnotationElement } from "../annotation_layer.js";
2627

2728
class InkDrawingOptions extends DrawingOptions {
@@ -177,6 +178,20 @@ class InkEditor extends DrawingEditor {
177178
return editor;
178179
}
179180

181+
/** @inheritdoc */
182+
get toolbarButtons() {
183+
this._colorPicker ||= new BasicColorPicker(this);
184+
return [["colorPicker", this._colorPicker]];
185+
}
186+
187+
get colorType() {
188+
return AnnotationEditorParamsType.INK_COLOR;
189+
}
190+
191+
get colorValue() {
192+
return this._drawingOptions.stroke;
193+
}
194+
180195
/** @inheritdoc */
181196
onScaleChanging() {
182197
if (!this.parent) {

src/display/editor/tools.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1805,12 +1805,14 @@ class AnnotationEditorUIManager {
18051805
break;
18061806
}
18071807

1808-
for (const editor of this.#selectedEditors) {
1809-
editor.updateParams(type, value);
1810-
}
1811-
1812-
for (const editorType of this.#editorTypes) {
1813-
editorType.updateDefaultParams(type, value);
1808+
if (this.hasSelection) {
1809+
for (const editor of this.#selectedEditors) {
1810+
editor.updateParams(type, value);
1811+
}
1812+
} else {
1813+
for (const editorType of this.#editorTypes) {
1814+
editorType.updateDefaultParams(type, value);
1815+
}
18141816
}
18151817
}
18161818

test/integration/freetext_editor_spec.mjs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3502,4 +3502,48 @@ describe("FreeText Editor", () => {
35023502
);
35033503
});
35043504
});
3505+
3506+
describe("FreeText must update its color", () => {
3507+
let pages;
3508+
3509+
beforeEach(async () => {
3510+
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
3511+
});
3512+
3513+
afterEach(async () => {
3514+
await closePages(pages);
3515+
});
3516+
3517+
it("must check that the text color is the one chosen from the color picker", async () => {
3518+
await Promise.all(
3519+
pages.map(async ([_, page]) => {
3520+
await switchToFreeText(page);
3521+
3522+
const rect = await getRect(page, ".annotationEditorLayer");
3523+
const editorSelector = getEditorSelector(0);
3524+
const data = "Hello PDF.js World !!";
3525+
await page.mouse.click(
3526+
rect.x + rect.width / 2,
3527+
rect.y + rect.height / 2
3528+
);
3529+
await page.waitForSelector(editorSelector, { visible: true });
3530+
await page.type(`${editorSelector} .internal`, data);
3531+
await commit(page);
3532+
3533+
const colorPickerSelector = `${editorSelector} input.basicColorPicker`;
3534+
await page.waitForSelector(colorPickerSelector, { visible: true });
3535+
await page.locator(colorPickerSelector).fill("#ff0000");
3536+
3537+
await page.waitForFunction(
3538+
sel => {
3539+
const el = document.querySelector(sel);
3540+
return getComputedStyle(el).color === "rgb(255, 0, 0)";
3541+
},
3542+
{},
3543+
`${editorSelector} .internal`
3544+
);
3545+
})
3546+
);
3547+
});
3548+
});
35053549
});

test/integration/ink_editor_spec.mjs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1294,3 +1294,45 @@ describe("Should switch from an editor and mode to others by double clicking", (
12941294
);
12951295
});
12961296
});
1297+
1298+
describe("Ink must update its color", () => {
1299+
let pages;
1300+
1301+
beforeEach(async () => {
1302+
pages = await loadAndWait("empty.pdf", ".annotationEditorLayer");
1303+
});
1304+
1305+
afterEach(async () => {
1306+
await closePages(pages);
1307+
});
1308+
1309+
it("must check that the stroke color is the one chosen from the color picker", async () => {
1310+
await Promise.all(
1311+
pages.map(async ([_, page]) => {
1312+
await switchToInk(page);
1313+
1314+
const rect = await getRect(page, ".annotationEditorLayer");
1315+
1316+
const x = rect.x + 20;
1317+
const y = rect.y + 20;
1318+
const clickHandle = await waitForPointerUp(page);
1319+
await page.mouse.move(x, y);
1320+
await page.mouse.down();
1321+
await page.mouse.move(x + 50, y + 50);
1322+
await page.mouse.up();
1323+
await awaitPromise(clickHandle);
1324+
await commit(page);
1325+
1326+
const editorSelector = getEditorSelector(0);
1327+
const colorPickerSelector = `${editorSelector} input.basicColorPicker`;
1328+
await page.waitForSelector(colorPickerSelector, { visible: true });
1329+
await page.locator(colorPickerSelector).fill("#ff0000");
1330+
1331+
await page.waitForSelector(
1332+
".canvasWrapper svg.draw[stroke='#ff0000']",
1333+
{ visible: true }
1334+
);
1335+
})
1336+
);
1337+
});
1338+
});

web/annotation_editor_layer_builder.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,10 @@
10571057
}
10581058
}
10591059

1060+
.basicColorPicker {
1061+
width: 28px;
1062+
}
1063+
10601064
.annotationEditorLayer {
10611065
&[data-main-rotation="0"] {
10621066
.highlightEditor:not(.free) > .editToolbar {

0 commit comments

Comments
 (0)