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