288 lines
6.9 KiB
JavaScript

/* Copyright 2022 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
AnnotationEditorParamsType,
AnnotationEditorType,
shadow,
Util,
} from "../../shared/util.js";
import { DrawingEditor, DrawingOptions } from "./draw.js";
import { InkDrawOutline, InkDrawOutliner } from "./drawers/inkdraw.js";
import { AnnotationEditor } from "./editor.js";
import { InkAnnotationElement } from "../annotation_layer.js";
class InkDrawingOptions extends DrawingOptions {
constructor(viewerParameters) {
super();
this._viewParameters = viewerParameters;
super.updateProperties({
fill: "none",
stroke: AnnotationEditor._defaultLineColor,
"stroke-opacity": 1,
"stroke-width": 1,
"stroke-linecap": "round",
"stroke-linejoin": "round",
"stroke-miterlimit": 10,
});
}
updateSVGProperty(name, value) {
if (name === "stroke-width") {
value ??= this["stroke-width"];
value *= this._viewParameters.realScale;
}
super.updateSVGProperty(name, value);
}
clone() {
const clone = new InkDrawingOptions(this._viewParameters);
clone.updateAll(this);
return clone;
}
}
/**
* Basic draw editor in order to generate an Ink annotation.
*/
class InkEditor extends DrawingEditor {
static _type = "ink";
static _editorType = AnnotationEditorType.INK;
static _defaultDrawingOptions = null;
constructor(params) {
super({ ...params, name: "inkEditor" });
this._willKeepAspectRatio = true;
this.defaultL10nId = "pdfjs-editor-ink-editor";
}
/** @inheritdoc */
static initialize(l10n, uiManager) {
AnnotationEditor.initialize(l10n, uiManager);
this._defaultDrawingOptions = new InkDrawingOptions(
uiManager.viewParameters
);
}
/** @inheritdoc */
static getDefaultDrawingOptions(options) {
const clone = this._defaultDrawingOptions.clone();
clone.updateProperties(options);
return clone;
}
/** @inheritdoc */
static get supportMultipleDrawings() {
return true;
}
/** @inheritdoc */
static get typesMap() {
return shadow(
this,
"typesMap",
new Map([
[AnnotationEditorParamsType.INK_THICKNESS, "stroke-width"],
[AnnotationEditorParamsType.INK_COLOR, "stroke"],
[AnnotationEditorParamsType.INK_OPACITY, "stroke-opacity"],
])
);
}
/** @inheritdoc */
static createDrawerInstance(x, y, parentWidth, parentHeight, rotation) {
return new InkDrawOutliner(
x,
y,
parentWidth,
parentHeight,
rotation,
this._defaultDrawingOptions["stroke-width"]
);
}
/** @inheritdoc */
static deserializeDraw(
pageX,
pageY,
pageWidth,
pageHeight,
innerMargin,
data
) {
return InkDrawOutline.deserialize(
pageX,
pageY,
pageWidth,
pageHeight,
innerMargin,
data
);
}
/** @inheritdoc */
static async deserialize(data, parent, uiManager) {
let initialData = null;
if (data instanceof InkAnnotationElement) {
const {
data: {
inkLists,
rect,
rotation,
id,
color,
opacity,
borderStyle: { rawWidth: thickness },
popupRef,
},
parent: {
page: { pageNumber },
},
} = data;
initialData = data = {
annotationType: AnnotationEditorType.INK,
color: Array.from(color),
thickness,
opacity,
paths: { points: inkLists },
boxes: null,
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
popupRef,
};
}
const editor = await super.deserialize(data, parent, uiManager);
editor._initialData = initialData;
return editor;
}
/** @inheritdoc */
onScaleChanging() {
if (!this.parent) {
return;
}
super.onScaleChanging();
const { _drawId, _drawingOptions, parent } = this;
_drawingOptions.updateSVGProperty("stroke-width");
parent.drawLayer.updateProperties(
_drawId,
_drawingOptions.toSVGProperties()
);
}
static onScaleChangingWhenDrawing() {
const parent = this._currentParent;
if (!parent) {
return;
}
super.onScaleChangingWhenDrawing();
this._defaultDrawingOptions.updateSVGProperty("stroke-width");
parent.drawLayer.updateProperties(
this._currentDrawId,
this._defaultDrawingOptions.toSVGProperties()
);
}
/** @inheritdoc */
createDrawingOptions({ color, thickness, opacity }) {
this._drawingOptions = InkEditor.getDefaultDrawingOptions({
stroke: Util.makeHexColor(...color),
"stroke-width": thickness,
"stroke-opacity": opacity,
});
}
/** @inheritdoc */
serialize(isForCopying = false) {
if (this.isEmpty()) {
return null;
}
if (this.deleted) {
return this.serializeDeleted();
}
const { lines, points, rect } = this.serializeDraw(isForCopying);
const {
_drawingOptions: {
stroke,
"stroke-opacity": opacity,
"stroke-width": thickness,
},
} = this;
const serialized = {
annotationType: AnnotationEditorType.INK,
color: AnnotationEditor._colorManager.convert(stroke),
opacity,
thickness,
paths: {
lines,
points,
},
pageIndex: this.pageIndex,
rect,
rotation: this.rotation,
structTreeParentId: this._structTreeParentId,
};
if (isForCopying) {
serialized.isCopy = true;
return serialized;
}
if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
return null;
}
serialized.id = this.annotationElementId;
return serialized;
}
#hasElementChanged(serialized) {
const { color, thickness, opacity, pageIndex } = this._initialData;
return (
this._hasBeenMoved ||
this._hasBeenResized ||
serialized.color.some((c, i) => c !== color[i]) ||
serialized.thickness !== thickness ||
serialized.opacity !== opacity ||
serialized.pageIndex !== pageIndex
);
}
/** @inheritdoc */
renderAnnotationElement(annotation) {
const { points, rect } = this.serializeDraw(/* isForCopying = */ false);
annotation.updateEdited({
rect,
thickness: this._drawingOptions["stroke-width"],
points,
});
return null;
}
}
export { InkDrawingOptions, InkEditor };