Skip to content

Commit b213c46

Browse files
committed
[editor] Add some UI elements in order to set font size & color, and ink thickness & color
1 parent 4e025e1 commit b213c46

20 files changed

+625
-66
lines changed

l10n/en-US/viewer.properties

+6
Original file line numberDiff line numberDiff line change
@@ -259,3 +259,9 @@ editor_ink.title=Add Ink Annotation
259259
editor_ink_label=Ink Annotation
260260

261261
freetext_default_content=Enter some text…
262+
263+
# Editor Parameters
264+
editor_free_text_font_color=Font Color
265+
editor_free_text_font_size=Font Size
266+
editor_ink_line_color=Line Color
267+
editor_ink_line_thickness=Line Thickness

src/core/annotation.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3767,7 +3767,7 @@ class InkAnnotation extends MarkupAnnotation {
37673767
}
37683768

37693769
const appearanceBuffer = [
3770-
`${thickness} w`,
3770+
`${thickness} w 1 J 1 j`,
37713771
`${getPdfColor(color, /* isFill */ false)}`,
37723772
];
37733773
const buffer = [];

src/display/canvas.js

+2-5
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ import {
2626
Util,
2727
warn,
2828
} from "../shared/util.js";
29+
import { getRGB, PixelsPerInch } from "./display_utils.js";
2930
import {
3031
getShadingPattern,
3132
PathType,
3233
TilingPattern,
3334
} from "./pattern_helper.js";
3435
import { applyMaskImageData } from "../shared/image_utils.js";
3536
import { isNodeJS } from "../shared/is_node.js";
36-
import { PixelsPerInch } from "./display_utils.js";
3737

3838
// <canvas> contexts store most of the state we need natively.
3939
// However, PDF needs a bit more state, which we store here.
@@ -1326,10 +1326,7 @@ class CanvasGraphics {
13261326
// Then for every color in the pdf, if its rounded luminance is the
13271327
// same as the background one then it's replaced by the new
13281328
// background color else by the foreground one.
1329-
const cB = parseInt(defaultBg.slice(1), 16);
1330-
const rB = (cB && 0xff0000) >> 16;
1331-
const gB = (cB && 0x00ff00) >> 8;
1332-
const bB = cB && 0x0000ff;
1329+
const [rB, gB, bB] = getRGB(defaultBg);
13331330
const newComp = x => {
13341331
x /= 255;
13351332
return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;

src/display/display_utils.js

+23
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,28 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) {
567567
});
568568
}
569569

570+
function getRGB(color) {
571+
if (color.startsWith("#")) {
572+
const colorRGB = parseInt(color.slice(1), 16);
573+
return [
574+
(colorRGB & 0xff0000) >> 16,
575+
(colorRGB & 0x00ff00) >> 8,
576+
colorRGB & 0x0000ff,
577+
];
578+
}
579+
580+
if (color.startsWith("rgb(")) {
581+
// getComputedStyle(...).color returns a `rgb(R, G, B)` color.
582+
return color
583+
.slice(/* "rgb(".length */ 4, -1) // Strip out "rgb(" and ")".
584+
.split(",")
585+
.map(x => parseInt(x));
586+
}
587+
588+
warn(`Not a valid color format: "${color}"`);
589+
return [0, 0, 0];
590+
}
591+
570592
export {
571593
deprecated,
572594
DOMCanvasFactory,
@@ -575,6 +597,7 @@ export {
575597
DOMSVGFactory,
576598
getFilenameFromUrl,
577599
getPdfFilenameFromUrl,
600+
getRGB,
578601
getXfaPageViewport,
579602
isDataScheme,
580603
isPdfFile,

src/display/editor/annotation_editor_layer.js

+26-16
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class AnnotationEditorLayer {
7878
if (!AnnotationEditorLayer._initialized) {
7979
AnnotationEditorLayer._initialized = true;
8080
FreeTextEditor.initialize(options.l10n);
81+
82+
options.uiManager.registerEditorTypes([FreeTextEditor, InkEditor]);
8183
}
8284
this.#uiManager = options.uiManager;
8385
this.annotationStorage = options.annotationStorage;
@@ -98,14 +100,22 @@ class AnnotationEditorLayer {
98100
* @param {number} mode
99101
*/
100102
updateMode(mode) {
101-
if (mode === AnnotationEditorType.INK) {
102-
// We want to have the ink editor covering all of the page without having
103-
// to click to create it: it must be here when we start to draw.
104-
this.div.addEventListener("mouseover", this.#boundMouseover);
105-
this.div.removeEventListener("click", this.#boundClick);
106-
} else {
107-
this.div.removeEventListener("mouseover", this.#boundMouseover);
103+
switch (mode) {
104+
case AnnotationEditorType.INK:
105+
// We want to have the ink editor covering all of the page without
106+
// having to click to create it: it must be here when we start to draw.
107+
this.div.addEventListener("mouseover", this.#boundMouseover);
108+
this.div.removeEventListener("click", this.#boundClick);
109+
break;
110+
case AnnotationEditorType.FREETEXT:
111+
this.div.removeEventListener("mouseover", this.#boundMouseover);
112+
this.div.addEventListener("click", this.#boundClick);
113+
break;
114+
default:
115+
this.div.removeEventListener("mouseover", this.#boundMouseover);
116+
this.div.removeEventListener("click", this.#boundClick);
108117
}
118+
109119
this.setActiveEditor(null);
110120
}
111121

@@ -130,13 +140,10 @@ class AnnotationEditorLayer {
130140

131141
/**
132142
* Add some commands into the CommandManager (undo/redo stuff).
133-
* @param {function} cmd
134-
* @param {function} undo
135-
* @param {boolean} mustExec - If true the command is executed after having
136-
* been added.
143+
* @param {Object} params
137144
*/
138-
addCommands(cmd, undo, mustExec) {
139-
this.#uiManager.addCommands(cmd, undo, mustExec);
145+
addCommands(params) {
146+
this.#uiManager.addCommands(params);
140147
}
141148

142149
/**
@@ -232,7 +239,10 @@ class AnnotationEditorLayer {
232239
this.unselectAll();
233240
this.div.removeEventListener("click", this.#boundClick);
234241
} else {
235-
this.#uiManager.allowClick = false;
242+
// When in Ink mode, setting the editor to null allows the
243+
// user to have to make one click in order to start drawing.
244+
this.#uiManager.allowClick =
245+
this.#uiManager.getMode() === AnnotationEditorType.INK;
236246
this.div.addEventListener("click", this.#boundClick);
237247
}
238248
}
@@ -332,7 +342,7 @@ class AnnotationEditorLayer {
332342
editor.remove();
333343
};
334344

335-
this.addCommands(cmd, undo, true);
345+
this.addCommands({ cmd, undo, mustExec: true });
336346
}
337347

338348
/**
@@ -347,7 +357,7 @@ class AnnotationEditorLayer {
347357
editor.remove();
348358
};
349359

350-
this.addCommands(cmd, undo, false);
360+
this.addCommands({ cmd, undo, mustExec: false });
351361
}
352362

353363
/**

src/display/editor/editor.js

+15
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,21 @@ class AnnotationEditor {
372372
this.div.classList.remove("selectedEditor");
373373
}
374374
}
375+
376+
/**
377+
* Update some parameters which have been changed through the UI.
378+
* @param {number} type
379+
* @param {*} value
380+
*/
381+
updateParams(type, value) {}
382+
383+
/**
384+
* Get some properties to update in the UI.
385+
* @returns {Object}
386+
*/
387+
get propertiesToUpdate() {
388+
return {};
389+
}
375390
}
376391

377392
export { AnnotationEditor };

src/display/editor/freetext.js

+104-4
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
*/
1515

1616
import {
17+
AnnotationEditorParamsType,
1718
AnnotationEditorType,
1819
assert,
1920
LINE_FACTOR,
2021
} from "../../shared/util.js";
2122
import { AnnotationEditor } from "./editor.js";
2223
import { bindEvents } from "./tools.js";
24+
import { getRGB } from "../display_utils.js";
2325

2426
/**
2527
* Basic text editor in order to create a FreeTex annotation.
@@ -41,10 +43,14 @@ class FreeTextEditor extends AnnotationEditor {
4143

4244
static _internalPadding = 0;
4345

46+
static _defaultFontSize = 10;
47+
48+
static _defaultColor = "CanvasText";
49+
4450
constructor(params) {
4551
super({ ...params, name: "freeTextEditor" });
46-
this.#color = params.color || "CanvasText";
47-
this.#fontSize = params.fontSize || 10;
52+
this.#color = params.color || FreeTextEditor._defaultColor;
53+
this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
4854
}
4955

5056
static initialize(l10n) {
@@ -89,6 +95,94 @@ class FreeTextEditor extends AnnotationEditor {
8995
return editor;
9096
}
9197

98+
static updateDefaultParams(type, value) {
99+
switch (type) {
100+
case AnnotationEditorParamsType.FREETEXT_SIZE:
101+
FreeTextEditor._defaultFontSize = value;
102+
break;
103+
case AnnotationEditorParamsType.FREETEXT_COLOR:
104+
FreeTextEditor._defaultColor = value;
105+
break;
106+
}
107+
}
108+
109+
/** @inheritdoc */
110+
updateParams(type, value) {
111+
switch (type) {
112+
case AnnotationEditorParamsType.FREETEXT_SIZE:
113+
this.#updateFontSize(value);
114+
break;
115+
case AnnotationEditorParamsType.FREETEXT_COLOR:
116+
this.#updateColor(value);
117+
break;
118+
}
119+
}
120+
121+
static get defaultPropertiesToUpdate() {
122+
return [
123+
[
124+
AnnotationEditorParamsType.FREETEXT_SIZE,
125+
FreeTextEditor._defaultFontSize,
126+
],
127+
[AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor],
128+
];
129+
}
130+
131+
/** @inheritdoc */
132+
get propertiesToUpdate() {
133+
return [
134+
[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize],
135+
[AnnotationEditorParamsType.FREETEXT_COLOR, this.#color],
136+
];
137+
}
138+
139+
/**
140+
* Update the font size and make this action as undoable.
141+
* @param {number} fontSize
142+
*/
143+
#updateFontSize(fontSize) {
144+
const setFontsize = size => {
145+
this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`;
146+
this.translate(0, -(size - this.#fontSize) * this.parent.scaleFactor);
147+
this.#fontSize = size;
148+
};
149+
const savedFontsize = this.#fontSize;
150+
this.parent.addCommands({
151+
cmd: () => {
152+
setFontsize(fontSize);
153+
},
154+
undo: () => {
155+
setFontsize(savedFontsize);
156+
},
157+
mustExec: true,
158+
type: AnnotationEditorParamsType.FREETEXT_SIZE,
159+
overwriteIfSameType: true,
160+
keepUndo: true,
161+
});
162+
}
163+
164+
/**
165+
* Update the color and make this action undoable.
166+
* @param {string} color
167+
*/
168+
#updateColor(color) {
169+
const savedColor = this.#color;
170+
this.parent.addCommands({
171+
cmd: () => {
172+
this.#color = color;
173+
this.editorDiv.style.color = color;
174+
},
175+
undo: () => {
176+
this.#color = savedColor;
177+
this.editorDiv.style.color = savedColor;
178+
},
179+
mustExec: true,
180+
type: AnnotationEditorParamsType.FREETEXT_COLOR,
181+
overwriteIfSameType: true,
182+
keepUndo: true,
183+
});
184+
}
185+
92186
/** @inheritdoc */
93187
getInitialTranslation() {
94188
// The start of the base line is where the user clicked.
@@ -116,13 +210,15 @@ class FreeTextEditor extends AnnotationEditor {
116210
enableEditMode() {
117211
super.enableEditMode();
118212
this.overlayDiv.classList.remove("enabled");
213+
this.editorDiv.contentEditable = true;
119214
this.div.draggable = false;
120215
}
121216

122217
/** @inheritdoc */
123218
disableEditMode() {
124219
super.disableEditMode();
125220
this.overlayDiv.classList.add("enabled");
221+
this.editorDiv.contentEditable = false;
126222
this.div.draggable = true;
127223
}
128224

@@ -223,7 +319,7 @@ class FreeTextEditor extends AnnotationEditor {
223319
this.editorDiv.contentEditable = true;
224320

225321
const { style } = this.editorDiv;
226-
style.fontSize = `${this.#fontSize}%`;
322+
style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
227323
style.color = this.#color;
228324

229325
this.div.append(this.editorDiv);
@@ -248,6 +344,7 @@ class FreeTextEditor extends AnnotationEditor {
248344
);
249345
// eslint-disable-next-line no-unsanitized/property
250346
this.editorDiv.innerHTML = this.#contentHTML;
347+
this.div.draggable = true;
251348
}
252349

253350
return this.div;
@@ -258,9 +355,12 @@ class FreeTextEditor extends AnnotationEditor {
258355
const padding = FreeTextEditor._internalPadding * this.parent.scaleFactor;
259356
const rect = this.getRect(padding, padding);
260357

358+
// We don't use this.#color directly because it can be CanvasText.
359+
const color = getRGB(getComputedStyle(this.editorDiv).color);
360+
261361
return {
262362
annotationType: AnnotationEditorType.FREETEXT,
263-
color: [0, 0, 0],
363+
color,
264364
fontSize: this.#fontSize,
265365
value: this.#content,
266366
pageIndex: this.parent.pageIndex,

0 commit comments

Comments
 (0)