mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-23 08:38:06 +02:00
Merge pull request #19085 from calixteman/simplify_drawer
[Editor] Simplify the draw layer code
This commit is contained in:
commit
5133e6b666
7 changed files with 322 additions and 234 deletions
|
@ -55,7 +55,7 @@ class DrawLayer {
|
|||
return shadow(this, "_svgFactory", new DOMSVGFactory());
|
||||
}
|
||||
|
||||
static #setBox(element, { x = 0, y = 0, width = 1, height = 1 } = {}) {
|
||||
static #setBox(element, [x, y, width, height]) {
|
||||
const { style } = element;
|
||||
style.top = `${100 * y}%`;
|
||||
style.left = `${100 * x}%`;
|
||||
|
@ -63,11 +63,10 @@ class DrawLayer {
|
|||
style.height = `${100 * height}%`;
|
||||
}
|
||||
|
||||
#createSVG(box) {
|
||||
#createSVG() {
|
||||
const svg = DrawLayer._svgFactory.create(1, 1, /* skipDimensions = */ true);
|
||||
this.#parent.append(svg);
|
||||
svg.setAttribute("aria-hidden", true);
|
||||
DrawLayer.#setBox(svg, box);
|
||||
|
||||
return svg;
|
||||
}
|
||||
|
@ -86,10 +85,19 @@ class DrawLayer {
|
|||
return clipPathId;
|
||||
}
|
||||
|
||||
draw(outlines, color, opacity, isPathUpdatable = false) {
|
||||
#updateProperties(element, properties) {
|
||||
for (const [key, value] of Object.entries(properties)) {
|
||||
if (value === null) {
|
||||
element.removeAttribute(key);
|
||||
} else {
|
||||
element.setAttribute(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
draw(properties, isPathUpdatable = false, hasClip = false) {
|
||||
const id = this.#id++;
|
||||
const root = this.#createSVG(outlines.box);
|
||||
root.classList.add(...outlines.classNamesForDrawing);
|
||||
const root = this.#createSVG();
|
||||
|
||||
const defs = DrawLayer._svgFactory.createElement("defs");
|
||||
root.append(defs);
|
||||
|
@ -97,45 +105,42 @@ class DrawLayer {
|
|||
defs.append(path);
|
||||
const pathId = `path_p${this.pageIndex}_${id}`;
|
||||
path.setAttribute("id", pathId);
|
||||
path.setAttribute("d", outlines.toSVGPath());
|
||||
path.setAttribute("vector-effect", "non-scaling-stroke");
|
||||
|
||||
if (isPathUpdatable) {
|
||||
this.#toUpdate.set(id, path);
|
||||
}
|
||||
|
||||
// Create the clipping path for the editor div.
|
||||
const clipPathId = this.#createClipPath(defs, pathId);
|
||||
const clipPathId = hasClip ? this.#createClipPath(defs, pathId) : null;
|
||||
|
||||
const use = DrawLayer._svgFactory.createElement("use");
|
||||
root.append(use);
|
||||
root.setAttribute("fill", color);
|
||||
root.setAttribute("fill-opacity", opacity);
|
||||
use.setAttribute("href", `#${pathId}`);
|
||||
this.updateProperties(root, properties);
|
||||
|
||||
this.#mapping.set(id, root);
|
||||
|
||||
return { id, clipPathId: `url(#${clipPathId})` };
|
||||
}
|
||||
|
||||
drawOutline(outlines) {
|
||||
drawOutline(properties, mustRemoveSelfIntersections) {
|
||||
// We cannot draw the outline directly in the SVG for highlights because
|
||||
// it composes with its parent with mix-blend-mode: multiply.
|
||||
// But the outline has a different mix-blend-mode, so we need to draw it in
|
||||
// its own SVG.
|
||||
const id = this.#id++;
|
||||
const root = this.#createSVG(outlines.box);
|
||||
root.classList.add(...outlines.classNamesForOutlining);
|
||||
const root = this.#createSVG();
|
||||
const defs = DrawLayer._svgFactory.createElement("defs");
|
||||
root.append(defs);
|
||||
const path = DrawLayer._svgFactory.createElement("path");
|
||||
defs.append(path);
|
||||
const pathId = `path_p${this.pageIndex}_${id}`;
|
||||
path.setAttribute("id", pathId);
|
||||
path.setAttribute("d", outlines.toSVGPath());
|
||||
path.setAttribute("vector-effect", "non-scaling-stroke");
|
||||
|
||||
let maskId;
|
||||
if (outlines.mustRemoveSelfIntersections) {
|
||||
if (mustRemoveSelfIntersections) {
|
||||
const mask = DrawLayer._svgFactory.createElement("mask");
|
||||
defs.append(mask);
|
||||
maskId = `mask_p${this.pageIndex}_${id}`;
|
||||
|
@ -166,59 +171,40 @@ class DrawLayer {
|
|||
use1.classList.add("mainOutline");
|
||||
use2.classList.add("secondaryOutline");
|
||||
|
||||
this.updateProperties(root, properties);
|
||||
|
||||
this.#mapping.set(id, root);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
finalizeLine(id, line) {
|
||||
const path = this.#toUpdate.get(id);
|
||||
finalizeDraw(id, properties) {
|
||||
this.#toUpdate.delete(id);
|
||||
this.updateBox(id, line.box);
|
||||
path.setAttribute("d", line.toSVGPath());
|
||||
this.updateProperties(id, properties);
|
||||
}
|
||||
|
||||
updateLine(id, line) {
|
||||
const root = this.#mapping.get(id);
|
||||
const defs = root.firstChild;
|
||||
const path = defs.firstChild;
|
||||
path.setAttribute("d", line.toSVGPath());
|
||||
}
|
||||
|
||||
updatePath(id, line) {
|
||||
this.#toUpdate.get(id).setAttribute("d", line.toSVGPath());
|
||||
}
|
||||
|
||||
updateBox(id, box) {
|
||||
DrawLayer.#setBox(this.#mapping.get(id), box);
|
||||
}
|
||||
|
||||
show(id, visible) {
|
||||
this.#mapping.get(id).classList.toggle("hidden", !visible);
|
||||
}
|
||||
|
||||
rotate(id, angle) {
|
||||
this.#mapping.get(id).setAttribute("data-main-rotation", angle);
|
||||
}
|
||||
|
||||
changeColor(id, color) {
|
||||
this.#mapping.get(id).setAttribute("fill", color);
|
||||
}
|
||||
|
||||
changeOpacity(id, opacity) {
|
||||
this.#mapping.get(id).setAttribute("fill-opacity", opacity);
|
||||
}
|
||||
|
||||
addClass(id, className) {
|
||||
this.#mapping.get(id).classList.add(className);
|
||||
}
|
||||
|
||||
removeClass(id, className) {
|
||||
this.#mapping.get(id).classList.remove(className);
|
||||
}
|
||||
|
||||
getSVGRoot(id) {
|
||||
return this.#mapping.get(id);
|
||||
updateProperties(elementOrId, { root, bbox, rootClass, path }) {
|
||||
const element =
|
||||
typeof elementOrId === "number"
|
||||
? this.#mapping.get(elementOrId)
|
||||
: elementOrId;
|
||||
if (root) {
|
||||
this.#updateProperties(element, root);
|
||||
}
|
||||
if (bbox) {
|
||||
DrawLayer.#setBox(element, bbox);
|
||||
}
|
||||
if (rootClass) {
|
||||
const { classList } = element;
|
||||
for (const [className, value] of Object.entries(rootClass)) {
|
||||
classList.toggle(className, value);
|
||||
}
|
||||
}
|
||||
if (path) {
|
||||
const defs = element.firstChild;
|
||||
const pathElement = defs.firstChild;
|
||||
this.#updateProperties(pathElement, path);
|
||||
}
|
||||
}
|
||||
|
||||
remove(id) {
|
||||
|
|
|
@ -34,7 +34,7 @@ class FreeDrawOutliner {
|
|||
// We track the last 3 points in order to be able to:
|
||||
// - compute the normal of the line,
|
||||
// - compute the control points of the quadratic Bézier curve.
|
||||
#last = new Float64Array(18);
|
||||
#last = new Float32Array(18);
|
||||
|
||||
#lastX;
|
||||
|
||||
|
@ -302,7 +302,7 @@ class FreeDrawOutliner {
|
|||
const last = this.#last;
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
|
||||
const points = new Float64Array((this.#points?.length ?? 0) + 2);
|
||||
const points = new Float32Array((this.#points?.length ?? 0) + 2);
|
||||
for (let i = 0, ii = points.length - 2; i < ii; i += 2) {
|
||||
points[i] = (this.#points[i] - layerX) / layerWidth;
|
||||
points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
|
||||
|
@ -315,7 +315,7 @@ class FreeDrawOutliner {
|
|||
return this.#getOutlineTwoPoints(points);
|
||||
}
|
||||
|
||||
const outline = new Float64Array(
|
||||
const outline = new Float32Array(
|
||||
this.#top.length + 24 + this.#bottom.length
|
||||
);
|
||||
let N = top.length;
|
||||
|
@ -360,7 +360,7 @@ class FreeDrawOutliner {
|
|||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
const outline = new Float64Array(36);
|
||||
const outline = new Float32Array(36);
|
||||
outline.set(
|
||||
[
|
||||
NaN,
|
||||
|
@ -460,7 +460,7 @@ class FreeDrawOutliner {
|
|||
class FreeDrawOutline extends Outline {
|
||||
#box;
|
||||
|
||||
#bbox = null;
|
||||
#bbox = new Float32Array(4);
|
||||
|
||||
#innerMargin;
|
||||
|
||||
|
@ -480,9 +480,10 @@ class FreeDrawOutline extends Outline {
|
|||
this.#scaleFactor = scaleFactor;
|
||||
this.#innerMargin = innerMargin;
|
||||
this.#isLTR = isLTR;
|
||||
this.lastPoint = [NaN, NaN];
|
||||
this.#computeMinMax(isLTR);
|
||||
|
||||
const { x, y, width, height } = this.#bbox;
|
||||
const [x, y, width, height] = this.#bbox;
|
||||
for (let i = 0, ii = outline.length; i < ii; i += 2) {
|
||||
outline[i] = (outline[i] - x) / width;
|
||||
outline[i + 1] = (outline[i + 1] - y) / height;
|
||||
|
@ -517,49 +518,43 @@ class FreeDrawOutline extends Outline {
|
|||
let points;
|
||||
switch (rotation) {
|
||||
case 0:
|
||||
outline = this.#rescale(this.#outline, blX, trY, width, -height);
|
||||
points = this.#rescale(this.#points, blX, trY, width, -height);
|
||||
outline = Outline._rescale(this.#outline, blX, trY, width, -height);
|
||||
points = Outline._rescale(this.#points, blX, trY, width, -height);
|
||||
break;
|
||||
case 90:
|
||||
outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height);
|
||||
points = this.#rescaleAndSwap(this.#points, blX, blY, width, height);
|
||||
outline = Outline._rescaleAndSwap(
|
||||
this.#outline,
|
||||
blX,
|
||||
blY,
|
||||
width,
|
||||
height
|
||||
);
|
||||
points = Outline._rescaleAndSwap(this.#points, blX, blY, width, height);
|
||||
break;
|
||||
case 180:
|
||||
outline = this.#rescale(this.#outline, trX, blY, -width, height);
|
||||
points = this.#rescale(this.#points, trX, blY, -width, height);
|
||||
outline = Outline._rescale(this.#outline, trX, blY, -width, height);
|
||||
points = Outline._rescale(this.#points, trX, blY, -width, height);
|
||||
break;
|
||||
case 270:
|
||||
outline = this.#rescaleAndSwap(
|
||||
outline = Outline._rescaleAndSwap(
|
||||
this.#outline,
|
||||
trX,
|
||||
trY,
|
||||
-width,
|
||||
-height
|
||||
);
|
||||
points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height);
|
||||
points = Outline._rescaleAndSwap(
|
||||
this.#points,
|
||||
trX,
|
||||
trY,
|
||||
-width,
|
||||
-height
|
||||
);
|
||||
break;
|
||||
}
|
||||
return { outline: Array.from(outline), points: [Array.from(points)] };
|
||||
}
|
||||
|
||||
#rescale(src, tx, ty, sx, sy) {
|
||||
const dest = new Float64Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i] * sx;
|
||||
dest[i + 1] = ty + src[i + 1] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
#rescaleAndSwap(src, tx, ty, sx, sy) {
|
||||
const dest = new Float64Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i + 1] * sx;
|
||||
dest[i + 1] = ty + src[i] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
#computeMinMax(isLTR) {
|
||||
const outline = this.#outline;
|
||||
let lastX = outline[4];
|
||||
|
@ -605,11 +600,12 @@ class FreeDrawOutline extends Outline {
|
|||
lastY = outline[i + 5];
|
||||
}
|
||||
|
||||
const x = minX - this.#innerMargin,
|
||||
y = minY - this.#innerMargin,
|
||||
width = maxX - minX + 2 * this.#innerMargin,
|
||||
height = maxY - minY + 2 * this.#innerMargin;
|
||||
this.#bbox = { x, y, width, height, lastPoint: [lastPointX, lastPointY] };
|
||||
const bbox = this.#bbox;
|
||||
bbox[0] = minX - this.#innerMargin;
|
||||
bbox[1] = minY - this.#innerMargin;
|
||||
bbox[2] = maxX - minX + 2 * this.#innerMargin;
|
||||
bbox[3] = maxY - minY + 2 * this.#innerMargin;
|
||||
this.lastPoint = [lastPointX, lastPointY];
|
||||
}
|
||||
|
||||
get box() {
|
||||
|
@ -629,7 +625,7 @@ class FreeDrawOutline extends Outline {
|
|||
|
||||
getNewOutline(thickness, innerMargin) {
|
||||
// Build the outline of the highlight to use as the focus outline.
|
||||
const { x, y, width, height } = this.#bbox;
|
||||
const [x, y, width, height] = this.#bbox;
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
const sx = width * layerWidth;
|
||||
const sy = height * layerHeight;
|
||||
|
@ -654,10 +650,6 @@ class FreeDrawOutline extends Outline {
|
|||
}
|
||||
return outliner.getOutlines();
|
||||
}
|
||||
|
||||
get mustRemoveSelfIntersections() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { FreeDrawOutline, FreeDrawOutliner };
|
||||
|
|
|
@ -19,6 +19,8 @@ import { Outline } from "./outline.js";
|
|||
class HighlightOutliner {
|
||||
#box;
|
||||
|
||||
#lastPoint;
|
||||
|
||||
#verticalEdges = [];
|
||||
|
||||
#intervals = [];
|
||||
|
@ -77,13 +79,13 @@ class HighlightOutliner {
|
|||
edge[2] = (y2 - shiftedMinY) / bboxHeight;
|
||||
}
|
||||
|
||||
this.#box = {
|
||||
x: shiftedMinX,
|
||||
y: shiftedMinY,
|
||||
width: bboxWidth,
|
||||
height: bboxHeight,
|
||||
lastPoint,
|
||||
};
|
||||
this.#box = new Float32Array([
|
||||
shiftedMinX,
|
||||
shiftedMinY,
|
||||
bboxWidth,
|
||||
bboxHeight,
|
||||
]);
|
||||
this.#lastPoint = lastPoint;
|
||||
}
|
||||
|
||||
getOutlines() {
|
||||
|
@ -173,7 +175,7 @@ class HighlightOutliner {
|
|||
}
|
||||
outline.push(lastPointX, lastPointY);
|
||||
}
|
||||
return new HighlightOutline(outlines, this.#box);
|
||||
return new HighlightOutline(outlines, this.#box, this.#lastPoint);
|
||||
}
|
||||
|
||||
#binarySearch(y) {
|
||||
|
@ -267,10 +269,11 @@ class HighlightOutline extends Outline {
|
|||
|
||||
#outlines;
|
||||
|
||||
constructor(outlines, box) {
|
||||
constructor(outlines, box, lastPoint) {
|
||||
super();
|
||||
this.#outlines = outlines;
|
||||
this.#box = box;
|
||||
this.lastPoint = lastPoint;
|
||||
}
|
||||
|
||||
toSVGPath() {
|
||||
|
@ -319,10 +322,6 @@ class HighlightOutline extends Outline {
|
|||
return this.#box;
|
||||
}
|
||||
|
||||
get classNamesForDrawing() {
|
||||
return ["highlight"];
|
||||
}
|
||||
|
||||
get classNamesForOutlining() {
|
||||
return ["highlightOutline"];
|
||||
}
|
||||
|
@ -339,21 +338,9 @@ class FreeHighlightOutliner extends FreeDrawOutliner {
|
|||
isLTR
|
||||
);
|
||||
}
|
||||
|
||||
get classNamesForDrawing() {
|
||||
return ["highlight", "free"];
|
||||
}
|
||||
}
|
||||
|
||||
class FreeHighlightOutline extends FreeDrawOutline {
|
||||
get classNamesForDrawing() {
|
||||
return ["highlight", "free"];
|
||||
}
|
||||
|
||||
get classNamesForOutlining() {
|
||||
return ["highlightOutline", "free"];
|
||||
}
|
||||
|
||||
newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
|
||||
return new FreeHighlightOutliner(
|
||||
point,
|
||||
|
|
|
@ -35,20 +35,22 @@ class Outline {
|
|||
unreachable("Abstract method `serialize` must be implemented.");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line getter-return
|
||||
get classNamesForDrawing() {
|
||||
unreachable("Abstract getter `classNamesForDrawing` must be implemented.");
|
||||
static _rescale(src, tx, ty, sx, sy, dest) {
|
||||
dest ||= new Float32Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i] * sx;
|
||||
dest[i + 1] = ty + src[i + 1] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line getter-return
|
||||
get classNamesForOutlining() {
|
||||
unreachable(
|
||||
"Abstract getter `classNamesForOutlining` must be implemented."
|
||||
);
|
||||
}
|
||||
|
||||
get mustRemoveSelfIntersections() {
|
||||
return false;
|
||||
static _rescaleAndSwap(src, tx, ty, sx, sy, dest) {
|
||||
dest ||= new Float32Array(src.length);
|
||||
for (let i = 0, ii = src.length; i < ii; i += 2) {
|
||||
dest[i] = tx + src[i + 1] * sx;
|
||||
dest[i + 1] = ty + src[i] * sy;
|
||||
}
|
||||
return dest;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -157,12 +157,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
/* borderWidth = */ 0.001
|
||||
);
|
||||
this.#highlightOutlines = outliner.getOutlines();
|
||||
({
|
||||
x: this.x,
|
||||
y: this.y,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
} = this.#highlightOutlines.box);
|
||||
[this.x, this.y, this.width, this.height] = this.#highlightOutlines.box;
|
||||
|
||||
const outlinerForOutline = new HighlightOutliner(
|
||||
this.#boxes,
|
||||
|
@ -173,7 +168,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
this.#focusOutlines = outlinerForOutline.getOutlines();
|
||||
|
||||
// The last point is in the pages coordinate system.
|
||||
const { lastPoint } = this.#focusOutlines.box;
|
||||
const { lastPoint } = this.#focusOutlines;
|
||||
this.#lastPoint = [
|
||||
(lastPoint[0] - this.x) / this.width,
|
||||
(lastPoint[1] - this.y) / this.height,
|
||||
|
@ -195,26 +190,44 @@ class HighlightEditor extends AnnotationEditor {
|
|||
this.#clipPathId = clipPathId;
|
||||
// We need to redraw the highlight because we change the coordinates to be
|
||||
// in the box coordinate system.
|
||||
this.parent.drawLayer.finalizeLine(highlightId, highlightOutlines);
|
||||
this.#outlineId = this.parent.drawLayer.drawOutline(this.#focusOutlines);
|
||||
this.parent.drawLayer.finalizeDraw(highlightId, {
|
||||
bbox: highlightOutlines.box,
|
||||
path: {
|
||||
d: highlightOutlines.toSVGPath(),
|
||||
},
|
||||
});
|
||||
this.#outlineId = this.parent.drawLayer.drawOutline(
|
||||
{
|
||||
rootClass: {
|
||||
highlightOutline: true,
|
||||
free: true,
|
||||
},
|
||||
bbox: this.#focusOutlines.box,
|
||||
path: {
|
||||
d: this.#focusOutlines.toSVGPath(),
|
||||
},
|
||||
},
|
||||
/* mustRemoveSelfIntersections = */ true
|
||||
);
|
||||
} else if (this.parent) {
|
||||
const angle = this.parent.viewport.rotation;
|
||||
this.parent.drawLayer.updateLine(this.#id, highlightOutlines);
|
||||
this.parent.drawLayer.updateBox(
|
||||
this.#id,
|
||||
HighlightEditor.#rotateBbox(
|
||||
this.parent.drawLayer.updateProperties(this.#id, {
|
||||
bbox: HighlightEditor.#rotateBbox(
|
||||
this.#highlightOutlines.box,
|
||||
(angle - this.rotation + 360) % 360
|
||||
)
|
||||
);
|
||||
|
||||
this.parent.drawLayer.updateLine(this.#outlineId, this.#focusOutlines);
|
||||
this.parent.drawLayer.updateBox(
|
||||
this.#outlineId,
|
||||
HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle)
|
||||
);
|
||||
),
|
||||
path: {
|
||||
d: highlightOutlines.toSVGPath(),
|
||||
},
|
||||
});
|
||||
this.parent.drawLayer.updateProperties(this.#outlineId, {
|
||||
bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
|
||||
path: {
|
||||
d: this.#focusOutlines.toSVGPath(),
|
||||
},
|
||||
});
|
||||
}
|
||||
const { x, y, width, height } = highlightOutlines.box;
|
||||
const [x, y, width, height] = highlightOutlines.box;
|
||||
switch (this.rotation) {
|
||||
case 0:
|
||||
this.x = x;
|
||||
|
@ -246,7 +259,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
}
|
||||
}
|
||||
|
||||
const { lastPoint } = this.#focusOutlines.box;
|
||||
const { lastPoint } = this.#focusOutlines;
|
||||
this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height];
|
||||
}
|
||||
|
||||
|
@ -324,10 +337,14 @@ class HighlightEditor extends AnnotationEditor {
|
|||
#updateColor(color) {
|
||||
const setColorAndOpacity = (col, opa) => {
|
||||
this.color = col;
|
||||
this.parent?.drawLayer.changeColor(this.#id, col);
|
||||
this.#colorPicker?.updateColor(col);
|
||||
this.#opacity = opa;
|
||||
this.parent?.drawLayer.changeOpacity(this.#id, opa);
|
||||
this.parent?.drawLayer.updateProperties(this.#id, {
|
||||
root: {
|
||||
fill: col,
|
||||
"fill-opacity": opa,
|
||||
},
|
||||
});
|
||||
this.#colorPicker?.updateColor(col);
|
||||
};
|
||||
const savedColor = this.color;
|
||||
const savedOpacity = this.#opacity;
|
||||
|
@ -503,46 +520,53 @@ class HighlightEditor extends AnnotationEditor {
|
|||
return;
|
||||
}
|
||||
({ id: this.#id, clipPathId: this.#clipPathId } = parent.drawLayer.draw(
|
||||
this.#highlightOutlines,
|
||||
this.color,
|
||||
this.#opacity
|
||||
{
|
||||
bbox: this.#highlightOutlines.box,
|
||||
root: {
|
||||
viewBox: "0 0 1 1",
|
||||
fill: this.color,
|
||||
"fill-opacity": this.#opacity,
|
||||
},
|
||||
rootClass: {
|
||||
highlight: true,
|
||||
free: this.#isFreeHighlight,
|
||||
},
|
||||
path: {
|
||||
d: this.#highlightOutlines.toSVGPath(),
|
||||
},
|
||||
},
|
||||
/* isPathUpdatable = */ false,
|
||||
/* hasClip = */ true
|
||||
));
|
||||
this.#outlineId = parent.drawLayer.drawOutline(this.#focusOutlines);
|
||||
this.#outlineId = parent.drawLayer.drawOutline(
|
||||
{
|
||||
rootClass: {
|
||||
highlightOutline: true,
|
||||
free: this.#isFreeHighlight,
|
||||
},
|
||||
bbox: this.#focusOutlines.box,
|
||||
path: {
|
||||
d: this.#focusOutlines.toSVGPath(),
|
||||
},
|
||||
},
|
||||
/* mustRemoveSelfIntersections = */ this.#isFreeHighlight
|
||||
);
|
||||
|
||||
if (this.#highlightDiv) {
|
||||
this.#highlightDiv.style.clipPath = this.#clipPathId;
|
||||
}
|
||||
}
|
||||
|
||||
static #rotateBbox({ x, y, width, height }, angle) {
|
||||
static #rotateBbox([x, y, width, height], angle) {
|
||||
switch (angle) {
|
||||
case 90:
|
||||
return {
|
||||
x: 1 - y - height,
|
||||
y: x,
|
||||
width: height,
|
||||
height: width,
|
||||
};
|
||||
return [1 - y - height, x, height, width];
|
||||
case 180:
|
||||
return {
|
||||
x: 1 - x - width,
|
||||
y: 1 - y - height,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return [1 - x - width, 1 - y - height, width, height];
|
||||
case 270:
|
||||
return {
|
||||
x: y,
|
||||
y: 1 - x - width,
|
||||
width: height,
|
||||
height: width,
|
||||
};
|
||||
return [y, 1 - x - width, height, width];
|
||||
}
|
||||
return {
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
};
|
||||
return [x, y, width, height];
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -555,15 +579,23 @@ class HighlightEditor extends AnnotationEditor {
|
|||
box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle);
|
||||
} else {
|
||||
// An highlight annotation is always drawn horizontally.
|
||||
box = HighlightEditor.#rotateBbox(this, angle);
|
||||
box = HighlightEditor.#rotateBbox(
|
||||
[this.x, this.y, this.width, this.height],
|
||||
angle
|
||||
);
|
||||
}
|
||||
drawLayer.rotate(this.#id, angle);
|
||||
drawLayer.rotate(this.#outlineId, angle);
|
||||
drawLayer.updateBox(this.#id, box);
|
||||
drawLayer.updateBox(
|
||||
this.#outlineId,
|
||||
HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle)
|
||||
);
|
||||
drawLayer.updateProperties(this.#id, {
|
||||
bbox: box,
|
||||
root: {
|
||||
"data-main-rotation": angle,
|
||||
},
|
||||
});
|
||||
drawLayer.updateProperties(this.#outlineId, {
|
||||
bbox: HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle),
|
||||
root: {
|
||||
"data-main-rotation": angle,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -600,13 +632,21 @@ class HighlightEditor extends AnnotationEditor {
|
|||
|
||||
pointerover() {
|
||||
if (!this.isSelected) {
|
||||
this.parent.drawLayer.addClass(this.#outlineId, "hovered");
|
||||
this.parent?.drawLayer.updateProperties(this.#outlineId, {
|
||||
rootClass: {
|
||||
hovered: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pointerleave() {
|
||||
if (!this.isSelected) {
|
||||
this.parent.drawLayer.removeClass(this.#outlineId, "hovered");
|
||||
this.parent?.drawLayer.updateProperties(this.#outlineId, {
|
||||
rootClass: {
|
||||
hovered: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,8 +686,12 @@ class HighlightEditor extends AnnotationEditor {
|
|||
if (!this.#outlineId) {
|
||||
return;
|
||||
}
|
||||
this.parent?.drawLayer.removeClass(this.#outlineId, "hovered");
|
||||
this.parent?.drawLayer.addClass(this.#outlineId, "selected");
|
||||
this.parent?.drawLayer.updateProperties(this.#outlineId, {
|
||||
rootClass: {
|
||||
hovered: false,
|
||||
selected: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
|
@ -656,7 +700,11 @@ class HighlightEditor extends AnnotationEditor {
|
|||
if (!this.#outlineId) {
|
||||
return;
|
||||
}
|
||||
this.parent?.drawLayer.removeClass(this.#outlineId, "selected");
|
||||
this.parent?.drawLayer.updateProperties(this.#outlineId, {
|
||||
rootClass: {
|
||||
selected: false,
|
||||
},
|
||||
});
|
||||
if (!this.#isFreeHighlight) {
|
||||
this.#setCaret(/* start = */ false);
|
||||
}
|
||||
|
@ -671,8 +719,16 @@ class HighlightEditor extends AnnotationEditor {
|
|||
show(visible = this._isVisible) {
|
||||
super.show(visible);
|
||||
if (this.parent) {
|
||||
this.parent.drawLayer.show(this.#id, visible);
|
||||
this.parent.drawLayer.show(this.#outlineId, visible);
|
||||
this.parent.drawLayer.updateProperties(this.#id, {
|
||||
rootClass: {
|
||||
hidden: !visible,
|
||||
},
|
||||
});
|
||||
this.parent.drawLayer.updateProperties(this.#outlineId, {
|
||||
rootClass: {
|
||||
hidden: !visible,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -755,17 +811,34 @@ class HighlightEditor extends AnnotationEditor {
|
|||
);
|
||||
({ id: this._freeHighlightId, clipPathId: this._freeHighlightClipId } =
|
||||
parent.drawLayer.draw(
|
||||
this._freeHighlight,
|
||||
this._defaultColor,
|
||||
this._defaultOpacity,
|
||||
/* isPathUpdatable = */ true
|
||||
{
|
||||
bbox: [0, 0, 1, 1],
|
||||
root: {
|
||||
viewBox: "0 0 1 1",
|
||||
fill: this._defaultColor,
|
||||
"fill-opacity": this._defaultOpacity,
|
||||
},
|
||||
rootClass: {
|
||||
highlight: true,
|
||||
free: true,
|
||||
},
|
||||
path: {
|
||||
d: this._freeHighlight.toSVGPath(),
|
||||
},
|
||||
},
|
||||
/* isPathUpdatable = */ true,
|
||||
/* hasClip = */ true
|
||||
));
|
||||
}
|
||||
|
||||
static #highlightMove(parent, event) {
|
||||
if (this._freeHighlight.add(event)) {
|
||||
// Redraw only if the point has been added.
|
||||
parent.drawLayer.updatePath(this._freeHighlightId, this._freeHighlight);
|
||||
parent.drawLayer.updateProperties(this._freeHighlightId, {
|
||||
path: {
|
||||
d: this._freeHighlight.toSVGPath(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -886,10 +959,23 @@ class HighlightEditor extends AnnotationEditor {
|
|||
outliner.add(point);
|
||||
}
|
||||
const { id, clipPathId } = parent.drawLayer.draw(
|
||||
outliner,
|
||||
editor.color,
|
||||
editor._defaultOpacity,
|
||||
/* isPathUpdatable = */ true
|
||||
{
|
||||
bbox: [0, 0, 1, 1],
|
||||
root: {
|
||||
viewBox: "0 0 1 1",
|
||||
fill: editor.color,
|
||||
"fill-opacity": editor._defaultOpacity,
|
||||
},
|
||||
rootClass: {
|
||||
highlight: true,
|
||||
free: true,
|
||||
},
|
||||
path: {
|
||||
d: outliner.toSVGPath(),
|
||||
},
|
||||
},
|
||||
/* isPathUpdatable = */ true,
|
||||
/* hasClip = */ true
|
||||
);
|
||||
editor.#createFreeOutlines({
|
||||
highlightOutlines: outliner.getOutlines(),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue