blob: 99eed15d900ca614441d7f818e088fa96cbdd915 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @typedef {{
* canUndo: boolean,
* canRedo: boolean,
* }}
*/
let UndoState;
/**
* Wraps the Ink component with an API that can be called
* across an IFrame boundary.
*/
class InkAPI {
/** @param {!ink.embed.EmbedComponent} embed */
constructor(embed) {
this.embed_ = embed;
this.brush_ = ink.BrushModel.getInstance(embed);
this.camera_ = null;
}
/** @param {function(!UndoState)} listener */
addUndoStateListener(listener) {
/** @param {!ink.UndoStateChangeEvent} e */
function wrapper(e) {
listener({
canUndo: e.getCanUndo(),
canRedo: e.getCanRedo(),
});
}
this.embed_.addEventListener(ink.UndoStateChangeEvent.EVENT_TYPE, wrapper);
}
/**
* @param {!ArrayBuffer} buffer
*/
setPDF(buffer) {
// We change the type from ArrayBuffer to Uint8Array due to the consequences
// of the buffer being passed across the iframe boundary. This realm has a
// different ArrayBuffer constructor than `buffer`.
// TODO(dstockwell): Update Ink to allow Uint8Array here.
this.embed_.setPDF(
/** @type {!ArrayBuffer} */ (
/** @type {!*} */ (new Uint8Array(buffer))));
}
/**
* @return {!Promise<Uint8Array>}
*/
getPDF() {
return this.embed_.getPDF();
}
/**
* @return {!Uint8Array}
*/
getPDFDestructive() {
return this.embed_.getPDFDestructive();
}
async setCamera(camera) {
this.camera_ = camera;
this.embed_.setCamera(camera);
// Wait for the next task to avoid a race where Ink drops the camera value
// when the canvas is rotated in low-latency mode.
setTimeout(() => this.embed_.setCamera(this.camera_), 0);
}
/** @param {AnnotationTool} tool */
setAnnotationTool(tool) {
const shape = {
eraser: 'MAGIC_ERASE',
pen: 'INKPEN',
highlighter: 'SMART_HIGHLIGHTER_TOOL',
}[tool.tool];
this.brush_.setShape(shape);
if (tool.tool != 'eraser') {
this.brush_.setColor(/** @type {string} */ (tool.color));
}
this.brush_.setStrokeWidth(tool.size);
}
flush() {
return new Promise(resolve => this.embed_.flush(resolve));
}
/** @param {string} hexColor */
setOutOfBoundsColor(hexColor) {
this.embed_.setOutOfBoundsColor(ink.Color.fromString(hexColor));
}
/** @param {string} url */
setBorderImage(url) {
this.embed_.setBorderImage(url);
}
/** @param {number} spacing in points */
setPageSpacing(spacing) {
this.embed_.setVerticalPageLayout(spacing);
}
dispatchPointerEvent(type, init) {
const engine = document.querySelector('#ink-engine');
const match = engine.style.transform.match(/(\d+)deg/);
const rotation = match ? Number(match[1]) : 0;
let offsetX = init.clientX;
let offsetY = init.clientY;
// If Ink's canvas has been re-orientated away from 0, we must transform
// the event's offsetX and offsetY to correspond with the rotation and
// offset applied.
if ([90, 180, 270].includes(rotation)) {
const width = window.innerWidth;
const height = window.innerHeight;
const matrix = new DOMMatrix();
matrix.translateSelf(width / 2, height / 2);
matrix.rotateSelf(0, 0, -rotation);
matrix.translateSelf(-width / 2, -height / 2);
const result = matrix.transformPoint({x: offsetX, y: offsetY});
offsetX = result.x - engine.offsetLeft;
offsetY = result.y - engine.offsetTop;
}
const event = new PointerEvent(type, init);
// Ink uses offsetX and offsetY, but we can only override them, not pass
// as part of the init.
Object.defineProperty(event, 'offsetX', {value: offsetX});
Object.defineProperty(event, 'offsetY', {value: offsetY});
engine.dispatchEvent(event);
}
undo() {
this.embed_.undo();
}
redo() {
this.embed_.redo();
}
}
/** @return {Promise<InkAPI>} */
window.initInk = async function() {
const config = new ink.embed.Config();
const embed = await ink.embed.EmbedComponent.execute(config);
embed.assignFlag(ink.proto.Flag.ENABLE_HOST_CAMERA_CONTROL, true);
return new InkAPI(embed);
};