[PDF Ink Signatures] Make annotation brush type selectable
Implement the annotation brush type selection in the frontend of the PDF viewer. It will send a setAnnotationBrush message to the backend to tell it what type, color, and size of brush to set the annotation brush to. This uses the existing annotation bar UI from CrOS's annotation mode. Some types of brushes shouldn't need a color or size, such as the eraser. This is reflected in AnnotationBrushParams, where either all values must be set or the params should be missing completely. There is a slight difference in behavior from the new annotation mode and the existing CrOS annotation mode: normally, the annotation tool would be updated every time the annotation menu was clicked, even if the values didn't change. The new annotation mode shouldn't do this, otherwise it will send many messages to the backend. Bug: 335521186 Change-Id: Ib909927d2d8e6a132c867eafc3d3feadd2cd7c6a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5540210 Reviewed-by: Demetrios Papadopoulos <dpapad@chromium.org> Commit-Queue: Andy Phan <andyphan@chromium.org> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Reviewed-by: Lei Zhang <thestig@chromium.org> Cr-Commit-Position: refs/heads/main@{#1301653}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
a816fd8edd
commit
449dbfe12a
chrome
browser
resources
test
data
pdf
@ -2,6 +2,30 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
// Some brushes don't need colors and a size, but the brushes that do should
|
||||
// have values for the red, green, and blue colors and a value for the size.
|
||||
export interface AnnotationBrush {
|
||||
type: AnnotationBrushType;
|
||||
params?: AnnotationBrushParams;
|
||||
}
|
||||
|
||||
// The annotation brush params, required for certain brush types.
|
||||
export interface AnnotationBrushParams {
|
||||
colorR: number;
|
||||
colorG: number;
|
||||
colorB: number;
|
||||
size: number;
|
||||
}
|
||||
|
||||
// The different types of annotation brushes.
|
||||
export enum AnnotationBrushType {
|
||||
ERASER = 'eraser',
|
||||
HIGHLIGHTER = 'highlighter',
|
||||
PEN = 'pen',
|
||||
}
|
||||
// </if>
|
||||
|
||||
export interface Attachment {
|
||||
name: string;
|
||||
size: number;
|
||||
|
@ -5,6 +5,9 @@
|
||||
import {assert} from 'chrome://resources/js/assert.js';
|
||||
import {PromiseResolver} from 'chrome://resources/js/promise_resolver.js';
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
import type {AnnotationBrush, AnnotationBrushParams, AnnotationBrushType} from './constants.js';
|
||||
// </if>
|
||||
import type {NamedDestinationMessageData, Rect, SaveRequestType} from './constants.js';
|
||||
import type {PdfPluginElement} from './internal_plugin.js';
|
||||
import type {Viewport} from './viewport.js';
|
||||
@ -41,6 +44,14 @@ interface ThumbnailMessageData {
|
||||
height: number;
|
||||
}
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
// The message sent to the backend to set the annotation brush.
|
||||
interface AnnotationBrushMessage extends Partial<AnnotationBrushParams> {
|
||||
type: string;
|
||||
brushType: AnnotationBrushType;
|
||||
}
|
||||
// </if>
|
||||
|
||||
/**
|
||||
* Creates a cryptographically secure pseudorandom 128-bit token.
|
||||
* @return The generated token as a hex string.
|
||||
@ -184,6 +195,16 @@ export class PluginController implements ContentController {
|
||||
enable,
|
||||
});
|
||||
}
|
||||
|
||||
setAnnotationBrush(brush: AnnotationBrush) {
|
||||
const message: AnnotationBrushMessage = {
|
||||
type: 'setAnnotationBrush',
|
||||
brushType: brush.type,
|
||||
...brush.params,
|
||||
};
|
||||
|
||||
this.postMessage_(message);
|
||||
}
|
||||
// </if>
|
||||
|
||||
redo() {}
|
||||
|
@ -17,7 +17,10 @@ import {EventTracker} from 'chrome://resources/js/event_tracker.js';
|
||||
import {PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
|
||||
|
||||
import type {AnnotationTool} from '../annotation_tool.js';
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
import type {AnnotationBrush, AnnotationBrushType} from '../constants.js';
|
||||
import {PluginController} from '../controller.js';
|
||||
// </if>
|
||||
// <if expr="enable_ink">
|
||||
import {InkController, InkControllerEventType} from '../ink_controller.js';
|
||||
// </if>
|
||||
@ -63,6 +66,10 @@ export class ViewerAnnotationsBarElement extends PolymerElement {
|
||||
type: Boolean,
|
||||
value: false,
|
||||
},
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
pdfInk2Enabled: Boolean,
|
||||
// </if>
|
||||
};
|
||||
}
|
||||
|
||||
@ -74,6 +81,10 @@ export class ViewerAnnotationsBarElement extends PolymerElement {
|
||||
private inkController_: InkController = InkController.getInstance();
|
||||
private tracker_: EventTracker = new EventTracker();
|
||||
// </if>
|
||||
// <if expr="enable_pdf_ink2">
|
||||
pdfInk2Enabled: boolean;
|
||||
private pluginController_: PluginController = PluginController.getInstance();
|
||||
// </if>
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@ -146,16 +157,75 @@ export class ViewerAnnotationsBarElement extends PolymerElement {
|
||||
'--pen-tip-border',
|
||||
options.selectedColor === '#000000' ? 'currentcolor' :
|
||||
options.selectedColor);
|
||||
this.annotationTool_ = {
|
||||
const newAnnotationTool: AnnotationTool = {
|
||||
tool: tool,
|
||||
size: options.selectedSize,
|
||||
color: options.selectedColor ? options.selectedColor : undefined,
|
||||
};
|
||||
// <if expr="enable_pdf_ink2">
|
||||
if (this.pdfInk2Enabled) {
|
||||
// Only set the annotation brush and `this.annotationTool_` if the values
|
||||
// have changed.
|
||||
if (this.isNewAnnotationTool_(newAnnotationTool)) {
|
||||
this.pluginController_.setAnnotationBrush(
|
||||
this.getAnnotationBrush_(newAnnotationTool));
|
||||
this.annotationTool_ = newAnnotationTool;
|
||||
}
|
||||
return;
|
||||
}
|
||||
// </if>
|
||||
|
||||
this.annotationTool_ = newAnnotationTool;
|
||||
|
||||
// <if expr="enable_ink">
|
||||
this.inkController_.setAnnotationTool(this.annotationTool_);
|
||||
// </if>
|
||||
}
|
||||
|
||||
// <if expr="enable_pdf_ink2">
|
||||
/**
|
||||
* Returns whether `annotationTool` is different from `this.annotationTool_`.
|
||||
* @param annotationTool The `AnnotationTool` to check.
|
||||
* @returns True if `this.annotationTool_` is null or doesn't have the same
|
||||
* values as `annotationTool`, false otherwise.
|
||||
*/
|
||||
private isNewAnnotationTool_(annotationTool: AnnotationTool): boolean {
|
||||
if (!this.annotationTool_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.annotationTool_.tool !== annotationTool.tool ||
|
||||
this.annotationTool_.size !== annotationTool.size ||
|
||||
this.annotationTool_.color !== annotationTool.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The `AnnotationBrush` constructed using the values in
|
||||
* `annotationTool`.
|
||||
*/
|
||||
private getAnnotationBrush_(annotationTool: AnnotationTool): AnnotationBrush {
|
||||
const brush: AnnotationBrush = {
|
||||
type: annotationTool.tool as AnnotationBrushType,
|
||||
};
|
||||
|
||||
if (annotationTool.color !== undefined) {
|
||||
// `AnnotationTool`'s color is a hex-coded color string, formatted as
|
||||
// '#ffffff'.
|
||||
const hexColor = annotationTool.color;
|
||||
assert(/^#[0-9a-f]{6}$/.test(hexColor));
|
||||
|
||||
brush.params = {
|
||||
colorR: parseInt(hexColor.substring(1, 3), 16),
|
||||
colorG: parseInt(hexColor.substring(3, 5), 16),
|
||||
colorB: parseInt(hexColor.substring(5, 7), 16),
|
||||
size: annotationTool.size,
|
||||
};
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
// </if>
|
||||
|
||||
/** @return Whether the annotation tool is using tool `toolName`. */
|
||||
private isAnnotationTool_(toolName: string): boolean {
|
||||
return !!this.annotationTool_ && this.annotationTool_.tool === toolName;
|
||||
|
@ -342,7 +342,11 @@
|
||||
</if>
|
||||
<if expr="enable_ink or enable_pdf_ink2">
|
||||
<template is="dom-if" if="[[showAnnotationsBar_]]">
|
||||
<viewer-annotations-bar annotation-mode="[[annotationMode]]">
|
||||
<viewer-annotations-bar
|
||||
<if expr="enable_pdf_ink2">
|
||||
pdf-ink2-enabled="[[pdfInk2Enabled]]"
|
||||
</if>
|
||||
annotation-mode="[[annotationMode]]">
|
||||
</viewer-annotations-bar>
|
||||
</template>
|
||||
</if>
|
||||
|
@ -9,6 +9,9 @@ export {AnnotationTool} from './annotation_tool.js';
|
||||
// </if>
|
||||
export {Bookmark} from './bookmark_type.js';
|
||||
export {BrowserApi, ZoomBehavior} from './browser_api.js';
|
||||
// <if expr="enable_pdf_ink2">
|
||||
export {AnnotationBrush, AnnotationBrushParams, AnnotationBrushType} from './constants.js';
|
||||
// </if>
|
||||
export {Attachment, FittingType, Point, Rect, SaveRequestType} from './constants.js';
|
||||
export {PluginController} from './controller.js';
|
||||
export {ViewerAttachmentBarElement} from './elements/viewer-attachment-bar.js';
|
||||
|
@ -2,11 +2,59 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import type {AnnotationBrushParams} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
|
||||
import {AnnotationBrushType, PluginController} from 'chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/pdf_viewer_wrapper.js';
|
||||
import {assert} from 'chrome://resources/js/assert.js';
|
||||
import {waitAfterNextRender} from 'chrome://webui-test/polymer_test_util.js';
|
||||
import {isVisible} from 'chrome://webui-test/test_util.js';
|
||||
|
||||
import {createMockPdfPluginForTest} from './test_util.js';
|
||||
|
||||
const viewer = document.body.querySelector('pdf-viewer')!;
|
||||
const viewerToolbar = viewer.$.toolbar;
|
||||
const controller = PluginController.getInstance();
|
||||
const mockPlugin = createMockPdfPluginForTest();
|
||||
controller.setPluginForTesting(mockPlugin);
|
||||
|
||||
/**
|
||||
* Test that the annotation brush is of type `type`, and has the `params`, if
|
||||
* necessary. Clears all messages from `mockPlugin` after, otherwise subsequent
|
||||
* calls would continue to find and use the same message.
|
||||
* @param type The type of the annotation brush.
|
||||
* @param params The params for the annotation brush, if necessary for the type
|
||||
* of brush.
|
||||
*/
|
||||
function assertAnnotationBrush(
|
||||
type: AnnotationBrushType, params?: AnnotationBrushParams) {
|
||||
const setAnnotationBrushMessage =
|
||||
mockPlugin.findMessage('setAnnotationBrush');
|
||||
chrome.test.assertTrue(setAnnotationBrushMessage !== undefined);
|
||||
chrome.test.assertEq('setAnnotationBrush', setAnnotationBrushMessage.type);
|
||||
chrome.test.assertEq(type, setAnnotationBrushMessage.brushType);
|
||||
const hasParams = params !== undefined;
|
||||
chrome.test.assertEq(
|
||||
hasParams ? params.colorR : undefined, setAnnotationBrushMessage.colorR);
|
||||
chrome.test.assertEq(
|
||||
hasParams ? params.colorG : undefined, setAnnotationBrushMessage.colorG);
|
||||
chrome.test.assertEq(
|
||||
hasParams ? params.colorB : undefined, setAnnotationBrushMessage.colorB);
|
||||
chrome.test.assertEq(
|
||||
hasParams ? params.size : undefined, setAnnotationBrushMessage.size);
|
||||
|
||||
mockPlugin.clearMessages();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to always got a non-null annotation bar. The annotation bar must
|
||||
* exist.
|
||||
* @returns The annotation bar.
|
||||
*/
|
||||
function getAnnotationsBar() {
|
||||
const annotationsBar =
|
||||
viewerToolbar.shadowRoot!.querySelector('viewer-annotations-bar');
|
||||
assert(annotationsBar);
|
||||
return annotationsBar;
|
||||
}
|
||||
|
||||
chrome.test.runTests([
|
||||
// Test that the annotations bar is shown when annotation mode is enabled and
|
||||
@ -16,8 +64,7 @@ chrome.test.runTests([
|
||||
|
||||
viewerToolbar.toggleAnnotation();
|
||||
await waitAfterNextRender(viewerToolbar);
|
||||
const annotationsBar =
|
||||
viewerToolbar.shadowRoot!.querySelector('viewer-annotations-bar');
|
||||
const annotationsBar = getAnnotationsBar();
|
||||
|
||||
// Annotations bar should be visible when annotation mode is enabled.
|
||||
chrome.test.assertTrue(viewerToolbar.annotationMode);
|
||||
@ -31,4 +78,115 @@ chrome.test.runTests([
|
||||
chrome.test.assertFalse(isVisible(annotationsBar));
|
||||
chrome.test.succeed();
|
||||
},
|
||||
// Test that the pen can be selected. Test that its size and color can be
|
||||
// selected.
|
||||
async function testSelectPen() {
|
||||
viewerToolbar.toggleAnnotation();
|
||||
await waitAfterNextRender(viewerToolbar);
|
||||
chrome.test.assertTrue(viewerToolbar.annotationMode);
|
||||
|
||||
const annotationBar = getAnnotationsBar();
|
||||
|
||||
// Default to pen.
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.PEN,
|
||||
{colorR: 0, colorG: 0, colorB: 0, size: 0.1429});
|
||||
|
||||
// Change the pen size.
|
||||
const penOptions =
|
||||
annotationBar.shadowRoot!.querySelector('#pen viewer-pen-options');
|
||||
assert(penOptions);
|
||||
const sizeButton =
|
||||
penOptions.shadowRoot!.querySelector<HTMLElement>('#sizes [value="1"]');
|
||||
assert(sizeButton);
|
||||
sizeButton.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.PEN, {colorR: 0, colorG: 0, colorB: 0, size: 1});
|
||||
|
||||
// Change the pen color.
|
||||
const colorButton = penOptions.shadowRoot!.querySelector<HTMLElement>(
|
||||
'#colors [value="#00b0ff"]');
|
||||
assert(colorButton);
|
||||
colorButton.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.PEN,
|
||||
{colorR: 0, colorG: 176, colorB: 255, size: 1});
|
||||
chrome.test.succeed();
|
||||
},
|
||||
// Test that the eraser can be selected.
|
||||
function testSelectEraser() {
|
||||
const annotationBar = getAnnotationsBar();
|
||||
|
||||
// Switch to eraser. It shouldn't have any params.
|
||||
annotationBar.$.eraser.click();
|
||||
|
||||
assertAnnotationBrush(AnnotationBrushType.ERASER);
|
||||
chrome.test.succeed();
|
||||
},
|
||||
// Test that the pen can be selected again, and should have the same settings
|
||||
// as last set in `testSelectPen()`.
|
||||
function testGoBackToPenWithPreviousSettings() {
|
||||
const annotationBar = getAnnotationsBar();
|
||||
|
||||
// Switch back to pen. It should have the previous settings.
|
||||
annotationBar.$.pen.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.PEN,
|
||||
{colorR: 0, colorG: 176, colorB: 255, size: 1});
|
||||
chrome.test.succeed();
|
||||
},
|
||||
// Test that the highlighter can be selected. Test that the dropdown menu can
|
||||
// be opened to select a color. Test that the size can be selected.
|
||||
function testSelectHighlighterWithDropdownColor() {
|
||||
const annotationBar = getAnnotationsBar();
|
||||
|
||||
// Switch to highlighter.
|
||||
const highlighter = annotationBar.$.highlighter;
|
||||
highlighter.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.HIGHLIGHTER,
|
||||
{colorR: 255, colorG: 188, colorB: 0, size: 0.7143});
|
||||
|
||||
// Fail to change the highlighter color to a hidden color. The
|
||||
// highlighter dropdown needs to be expanded to change to the color, so
|
||||
// without the dropdown, changing the color should fail.
|
||||
const highlighterOptions = highlighter.querySelector('viewer-pen-options');
|
||||
assert(highlighterOptions);
|
||||
const collapsedColorButton =
|
||||
highlighterOptions.shadowRoot!.querySelector<HTMLInputElement>(
|
||||
'#colors [value="#d1c4e9"]');
|
||||
assert(collapsedColorButton);
|
||||
chrome.test.assertTrue(collapsedColorButton.disabled);
|
||||
collapsedColorButton.click();
|
||||
|
||||
// Open the dropdown menu and change the highlighter color.
|
||||
const dropdownMenu =
|
||||
highlighterOptions.shadowRoot!.querySelector<HTMLElement>(
|
||||
'#colors #expand');
|
||||
assert(dropdownMenu);
|
||||
dropdownMenu.click();
|
||||
|
||||
chrome.test.assertFalse(collapsedColorButton.disabled);
|
||||
collapsedColorButton.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.HIGHLIGHTER,
|
||||
{colorR: 209, colorG: 196, colorB: 233, size: 0.7143});
|
||||
|
||||
// Change the highlighter size.
|
||||
const sizeButton =
|
||||
highlighterOptions.shadowRoot!.querySelector<HTMLElement>(
|
||||
'#sizes [value="1"]');
|
||||
assert(sizeButton);
|
||||
sizeButton.click();
|
||||
|
||||
assertAnnotationBrush(
|
||||
AnnotationBrushType.HIGHLIGHTER,
|
||||
{colorR: 209, colorG: 196, colorB: 233, size: 1});
|
||||
chrome.test.succeed();
|
||||
},
|
||||
]);
|
||||
|
@ -120,6 +120,7 @@ bool InkModule::OnMessage(const base::Value::Dict& message) {
|
||||
|
||||
static constexpr auto kMessageHandlers =
|
||||
base::MakeFixedFlatMap<std::string_view, MessageHandler>({
|
||||
{"setAnnotationBrush", &InkModule::HandleSetAnnotationBrushMessage},
|
||||
{"setAnnotationMode", &InkModule::HandleSetAnnotationModeMessage},
|
||||
});
|
||||
|
||||
@ -206,6 +207,11 @@ bool InkModule::OnMouseMove(const blink::WebMouseEvent& event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void InkModule::HandleSetAnnotationBrushMessage(
|
||||
const base::Value::Dict& message) {
|
||||
// TODO(crbug.com/335524382): Implement setting the brush for Ink2.
|
||||
}
|
||||
|
||||
void InkModule::HandleSetAnnotationModeMessage(
|
||||
const base::Value::Dict& message) {
|
||||
enabled_ = message.FindBool("enable").value();
|
||||
|
@ -50,6 +50,7 @@ class InkModule {
|
||||
bool OnMouseUp(const blink::WebMouseEvent& event);
|
||||
bool OnMouseMove(const blink::WebMouseEvent& event);
|
||||
|
||||
void HandleSetAnnotationBrushMessage(const base::Value::Dict& message);
|
||||
void HandleSetAnnotationModeMessage(const base::Value::Dict& message);
|
||||
|
||||
bool enabled_ = false;
|
||||
|
Reference in New Issue
Block a user