mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-23 08:38:06 +02:00
[Editor] Refactor the free highlight stuff in order to be able to use the code for more general drawing
One goal is to make the code for drawing with the Ink tool similar to the one to free highlighting: it doesn't really make sense to have so different ways to do almost the same thing. When the zoom level is high, it'll avoid to create a too big canvas covering all the page which consume more memory, makes the drawing very slow and the overall user xp pretty bad. A second goal is to be able to easily implement more drawing tools where we would just have to implement how to draw from the pointer coordinates.
This commit is contained in:
parent
9108848743
commit
5a9607b2ad
7 changed files with 633 additions and 476 deletions
|
@ -86,13 +86,11 @@ class DrawLayer {
|
|||
return clipPathId;
|
||||
}
|
||||
|
||||
highlight(outlines, color, opacity, isPathUpdatable = false) {
|
||||
draw(outlines, color, opacity, isPathUpdatable = false) {
|
||||
const id = this.#id++;
|
||||
const root = this.#createSVG(outlines.box);
|
||||
root.classList.add("highlight");
|
||||
if (outlines.free) {
|
||||
root.classList.add("free");
|
||||
}
|
||||
root.classList.add(...outlines.classNamesForDrawing);
|
||||
|
||||
const defs = DrawLayer._svgFactory.createElement("defs");
|
||||
root.append(defs);
|
||||
const path = DrawLayer._svgFactory.createElement("path");
|
||||
|
@ -119,14 +117,14 @@ class DrawLayer {
|
|||
return { id, clipPathId: `url(#${clipPathId})` };
|
||||
}
|
||||
|
||||
highlightOutline(outlines) {
|
||||
drawOutline(outlines) {
|
||||
// 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("highlightOutline");
|
||||
root.classList.add(...outlines.classNamesForOutlining);
|
||||
const defs = DrawLayer._svgFactory.createElement("defs");
|
||||
root.append(defs);
|
||||
const path = DrawLayer._svgFactory.createElement("path");
|
||||
|
@ -137,8 +135,7 @@ class DrawLayer {
|
|||
path.setAttribute("vector-effect", "non-scaling-stroke");
|
||||
|
||||
let maskId;
|
||||
if (outlines.free) {
|
||||
root.classList.add("free");
|
||||
if (outlines.mustRemoveSelfIntersections) {
|
||||
const mask = DrawLayer._svgFactory.createElement("mask");
|
||||
defs.append(mask);
|
||||
maskId = `mask_p${this.pageIndex}_${id}`;
|
||||
|
@ -188,11 +185,6 @@ class DrawLayer {
|
|||
path.setAttribute("d", line.toSVGPath());
|
||||
}
|
||||
|
||||
removeFreeHighlight(id) {
|
||||
this.remove(id);
|
||||
this.#toUpdate.delete(id);
|
||||
}
|
||||
|
||||
updatePath(id, line) {
|
||||
this.#toUpdate.get(id).setAttribute("d", line.toSVGPath());
|
||||
}
|
||||
|
@ -230,6 +222,7 @@ class DrawLayer {
|
|||
}
|
||||
|
||||
remove(id) {
|
||||
this.#toUpdate.delete(id);
|
||||
if (this.#parent === null) {
|
||||
return;
|
||||
}
|
||||
|
@ -243,6 +236,7 @@ class DrawLayer {
|
|||
root.remove();
|
||||
}
|
||||
this.#mapping.clear();
|
||||
this.#toUpdate.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright 2023 Mozilla Foundation
|
||||
/* Copyright 2024 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -13,337 +13,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Util } from "../../shared/util.js";
|
||||
import { Outline } from "./outline.js";
|
||||
import { Util } from "../../../shared/util.js";
|
||||
|
||||
class Outliner {
|
||||
#box;
|
||||
|
||||
#verticalEdges = [];
|
||||
|
||||
#intervals = [];
|
||||
|
||||
/**
|
||||
* Construct an outliner.
|
||||
* @param {Array<Object>} boxes - An array of axis-aligned rectangles.
|
||||
* @param {number} borderWidth - The width of the border of the boxes, it
|
||||
* allows to make the boxes bigger (or smaller).
|
||||
* @param {number} innerMargin - The margin between the boxes and the
|
||||
* outlines. It's important to not have a null innerMargin when we want to
|
||||
* draw the outline else the stroked outline could be clipped because of its
|
||||
* width.
|
||||
* @param {boolean} isLTR - true if we're in LTR mode. It's used to determine
|
||||
* the last point of the boxes.
|
||||
*/
|
||||
constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) {
|
||||
let minX = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let minY = Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
// We round the coordinates to slightly reduce the number of edges in the
|
||||
// final outlines.
|
||||
const NUMBER_OF_DIGITS = 4;
|
||||
const EPSILON = 10 ** -NUMBER_OF_DIGITS;
|
||||
|
||||
// The coordinates of the boxes are in the page coordinate system.
|
||||
for (const { x, y, width, height } of boxes) {
|
||||
const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON;
|
||||
const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON;
|
||||
const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON;
|
||||
const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON;
|
||||
const left = [x1, y1, y2, true];
|
||||
const right = [x2, y1, y2, false];
|
||||
this.#verticalEdges.push(left, right);
|
||||
|
||||
minX = Math.min(minX, x1);
|
||||
maxX = Math.max(maxX, x2);
|
||||
minY = Math.min(minY, y1);
|
||||
maxY = Math.max(maxY, y2);
|
||||
}
|
||||
|
||||
const bboxWidth = maxX - minX + 2 * innerMargin;
|
||||
const bboxHeight = maxY - minY + 2 * innerMargin;
|
||||
const shiftedMinX = minX - innerMargin;
|
||||
const shiftedMinY = minY - innerMargin;
|
||||
const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2);
|
||||
const lastPoint = [lastEdge[0], lastEdge[2]];
|
||||
|
||||
// Convert the coordinates of the edges into box coordinates.
|
||||
for (const edge of this.#verticalEdges) {
|
||||
const [x, y1, y2] = edge;
|
||||
edge[0] = (x - shiftedMinX) / bboxWidth;
|
||||
edge[1] = (y1 - shiftedMinY) / bboxHeight;
|
||||
edge[2] = (y2 - shiftedMinY) / bboxHeight;
|
||||
}
|
||||
|
||||
this.#box = {
|
||||
x: shiftedMinX,
|
||||
y: shiftedMinY,
|
||||
width: bboxWidth,
|
||||
height: bboxHeight,
|
||||
lastPoint,
|
||||
};
|
||||
}
|
||||
|
||||
getOutlines() {
|
||||
// We begin to sort lexicographically the vertical edges by their abscissa,
|
||||
// and then by their ordinate.
|
||||
this.#verticalEdges.sort(
|
||||
(a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]
|
||||
);
|
||||
|
||||
// We're now using a sweep line algorithm to find the outlines.
|
||||
// We start with the leftmost vertical edge, and we're going to iterate
|
||||
// over all the vertical edges from left to right.
|
||||
// Each time we encounter a left edge, we're going to insert the interval
|
||||
// [y1, y2] in the set of intervals.
|
||||
// This set of intervals is used to break the vertical edges into chunks:
|
||||
// we only take the part of the vertical edge that isn't in the union of
|
||||
// the intervals.
|
||||
const outlineVerticalEdges = [];
|
||||
for (const edge of this.#verticalEdges) {
|
||||
if (edge[3]) {
|
||||
// Left edge.
|
||||
outlineVerticalEdges.push(...this.#breakEdge(edge));
|
||||
this.#insert(edge);
|
||||
} else {
|
||||
// Right edge.
|
||||
this.#remove(edge);
|
||||
outlineVerticalEdges.push(...this.#breakEdge(edge));
|
||||
}
|
||||
}
|
||||
return this.#getOutlines(outlineVerticalEdges);
|
||||
}
|
||||
|
||||
#getOutlines(outlineVerticalEdges) {
|
||||
const edges = [];
|
||||
const allEdges = new Set();
|
||||
|
||||
for (const edge of outlineVerticalEdges) {
|
||||
const [x, y1, y2] = edge;
|
||||
edges.push([x, y1, edge], [x, y2, edge]);
|
||||
}
|
||||
|
||||
// We sort lexicographically the vertices of each edge by their ordinate and
|
||||
// by their abscissa.
|
||||
// Every pair (v_2i, v_{2i + 1}) of vertices defines a horizontal edge.
|
||||
// So for every vertical edge, we're going to add the two vertical edges
|
||||
// which are connected to it through a horizontal edge.
|
||||
edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
|
||||
for (let i = 0, ii = edges.length; i < ii; i += 2) {
|
||||
const edge1 = edges[i][2];
|
||||
const edge2 = edges[i + 1][2];
|
||||
edge1.push(edge2);
|
||||
edge2.push(edge1);
|
||||
allEdges.add(edge1);
|
||||
allEdges.add(edge2);
|
||||
}
|
||||
const outlines = [];
|
||||
let outline;
|
||||
|
||||
while (allEdges.size > 0) {
|
||||
const edge = allEdges.values().next().value;
|
||||
let [x, y1, y2, edge1, edge2] = edge;
|
||||
allEdges.delete(edge);
|
||||
let lastPointX = x;
|
||||
let lastPointY = y1;
|
||||
|
||||
outline = [x, y2];
|
||||
outlines.push(outline);
|
||||
|
||||
while (true) {
|
||||
let e;
|
||||
if (allEdges.has(edge1)) {
|
||||
e = edge1;
|
||||
} else if (allEdges.has(edge2)) {
|
||||
e = edge2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
allEdges.delete(e);
|
||||
[x, y1, y2, edge1, edge2] = e;
|
||||
|
||||
if (lastPointX !== x) {
|
||||
outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2);
|
||||
lastPointX = x;
|
||||
}
|
||||
lastPointY = lastPointY === y1 ? y2 : y1;
|
||||
}
|
||||
outline.push(lastPointX, lastPointY);
|
||||
}
|
||||
return new HighlightOutline(outlines, this.#box);
|
||||
}
|
||||
|
||||
#binarySearch(y) {
|
||||
const array = this.#intervals;
|
||||
let start = 0;
|
||||
let end = array.length - 1;
|
||||
|
||||
while (start <= end) {
|
||||
const middle = (start + end) >> 1;
|
||||
const y1 = array[middle][0];
|
||||
if (y1 === y) {
|
||||
return middle;
|
||||
}
|
||||
if (y1 < y) {
|
||||
start = middle + 1;
|
||||
} else {
|
||||
end = middle - 1;
|
||||
}
|
||||
}
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
#insert([, y1, y2]) {
|
||||
const index = this.#binarySearch(y1);
|
||||
this.#intervals.splice(index, 0, [y1, y2]);
|
||||
}
|
||||
|
||||
#remove([, y1, y2]) {
|
||||
const index = this.#binarySearch(y1);
|
||||
for (let i = index; i < this.#intervals.length; i++) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
if (start !== y1) {
|
||||
break;
|
||||
}
|
||||
if (start === y1 && end === y2) {
|
||||
this.#intervals.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
if (start !== y1) {
|
||||
break;
|
||||
}
|
||||
if (start === y1 && end === y2) {
|
||||
this.#intervals.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#breakEdge(edge) {
|
||||
const [x, y1, y2] = edge;
|
||||
const results = [[x, y1, y2]];
|
||||
const index = this.#binarySearch(y2);
|
||||
for (let i = 0; i < index; i++) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
for (let j = 0, jj = results.length; j < jj; j++) {
|
||||
const [, y3, y4] = results[j];
|
||||
if (end <= y3 || y4 <= start) {
|
||||
// There is no intersection between the interval and the edge, hence
|
||||
// we keep it as is.
|
||||
continue;
|
||||
}
|
||||
if (y3 >= start) {
|
||||
if (y4 > end) {
|
||||
results[j][1] = end;
|
||||
} else {
|
||||
if (jj === 1) {
|
||||
return [];
|
||||
}
|
||||
// The edge is included in the interval, hence we remove it.
|
||||
results.splice(j, 1);
|
||||
j--;
|
||||
jj--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
results[j][2] = start;
|
||||
if (y4 > end) {
|
||||
results.push([x, end, y4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class Outline {
|
||||
/**
|
||||
* @returns {string} The SVG path of the outline.
|
||||
*/
|
||||
toSVGPath() {
|
||||
throw new Error("Abstract method `toSVGPath` must be implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Object|null} The bounding box of the outline.
|
||||
*/
|
||||
get box() {
|
||||
throw new Error("Abstract getter `box` must be implemented.");
|
||||
}
|
||||
|
||||
serialize(_bbox, _rotation) {
|
||||
throw new Error("Abstract method `serialize` must be implemented.");
|
||||
}
|
||||
|
||||
get free() {
|
||||
return this instanceof FreeHighlightOutline;
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightOutline extends Outline {
|
||||
#box;
|
||||
|
||||
#outlines;
|
||||
|
||||
constructor(outlines, box) {
|
||||
super();
|
||||
this.#outlines = outlines;
|
||||
this.#box = box;
|
||||
}
|
||||
|
||||
toSVGPath() {
|
||||
const buffer = [];
|
||||
for (const polygon of this.#outlines) {
|
||||
let [prevX, prevY] = polygon;
|
||||
buffer.push(`M${prevX} ${prevY}`);
|
||||
for (let i = 2; i < polygon.length; i += 2) {
|
||||
const x = polygon[i];
|
||||
const y = polygon[i + 1];
|
||||
if (x === prevX) {
|
||||
buffer.push(`V${y}`);
|
||||
prevY = y;
|
||||
} else if (y === prevY) {
|
||||
buffer.push(`H${x}`);
|
||||
prevX = x;
|
||||
}
|
||||
}
|
||||
buffer.push("Z");
|
||||
}
|
||||
return buffer.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the outlines into the PDF page coordinate system.
|
||||
* @param {Array<number>} _bbox - the bounding box of the annotation.
|
||||
* @param {number} _rotation - the rotation of the annotation.
|
||||
* @returns {Array<Array<number>>}
|
||||
*/
|
||||
serialize([blX, blY, trX, trY], _rotation) {
|
||||
const outlines = [];
|
||||
const width = trX - blX;
|
||||
const height = trY - blY;
|
||||
for (const outline of this.#outlines) {
|
||||
const points = new Array(outline.length);
|
||||
for (let i = 0; i < outline.length; i += 2) {
|
||||
points[i] = blX + outline[i] * width;
|
||||
points[i + 1] = trY - outline[i + 1] * height;
|
||||
}
|
||||
outlines.push(points);
|
||||
}
|
||||
return outlines;
|
||||
}
|
||||
|
||||
get box() {
|
||||
return this.#box;
|
||||
}
|
||||
}
|
||||
|
||||
class FreeOutliner {
|
||||
class FreeDrawOutliner {
|
||||
#box;
|
||||
|
||||
#bottom = [];
|
||||
|
@ -381,7 +54,7 @@ class FreeOutliner {
|
|||
|
||||
static #MIN_DIFF = 2;
|
||||
|
||||
static #MIN = FreeOutliner.#MIN_DIST + FreeOutliner.#MIN_DIFF;
|
||||
static #MIN = FreeDrawOutliner.#MIN_DIST + FreeDrawOutliner.#MIN_DIFF;
|
||||
|
||||
constructor({ x, y }, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
|
||||
this.#box = box;
|
||||
|
@ -389,16 +62,12 @@ class FreeOutliner {
|
|||
this.#isLTR = isLTR;
|
||||
this.#last.set([NaN, NaN, NaN, NaN, x, y], 6);
|
||||
this.#innerMargin = innerMargin;
|
||||
this.#min_dist = FreeOutliner.#MIN_DIST * scaleFactor;
|
||||
this.#min = FreeOutliner.#MIN * scaleFactor;
|
||||
this.#min_dist = FreeDrawOutliner.#MIN_DIST * scaleFactor;
|
||||
this.#min = FreeDrawOutliner.#MIN * scaleFactor;
|
||||
this.#scaleFactor = scaleFactor;
|
||||
this.#points.push(x, y);
|
||||
}
|
||||
|
||||
get free() {
|
||||
return true;
|
||||
}
|
||||
|
||||
isEmpty() {
|
||||
// When we add a second point then this.#last.slice(6) will be something
|
||||
// like [NaN, NaN, firstX, firstY, secondX, secondY,...] so having a NaN
|
||||
|
@ -544,21 +213,10 @@ class FreeOutliner {
|
|||
}
|
||||
const top = this.#top;
|
||||
const bottom = this.#bottom;
|
||||
const lastTop = this.#last.subarray(4, 6);
|
||||
const lastBottom = this.#last.subarray(16, 18);
|
||||
const [x, y, width, height] = this.#box;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
|
||||
if (isNaN(this.#last[6]) && !this.isEmpty()) {
|
||||
// We've only two points.
|
||||
return `M${(this.#last[2] - x) / width} ${
|
||||
(this.#last[3] - y) / height
|
||||
} L${(this.#last[4] - x) / width} ${(this.#last[5] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${
|
||||
(this.#last[16] - x) / width
|
||||
} ${(this.#last[17] - y) / height} L${(this.#last[14] - x) / width} ${
|
||||
(this.#last[15] - y) / height
|
||||
} Z`;
|
||||
return this.#toSVGPathTwoPoints();
|
||||
}
|
||||
|
||||
const buffer = [];
|
||||
|
@ -575,11 +233,8 @@ class FreeOutliner {
|
|||
}
|
||||
}
|
||||
|
||||
buffer.push(
|
||||
`L${(lastTop[0] - x) / width} ${(lastTop[1] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${
|
||||
(lastBottom[0] - x) / width
|
||||
} ${(lastBottom[1] - y) / height}`
|
||||
);
|
||||
this.#toSVGPathEnd(buffer);
|
||||
|
||||
for (let i = bottom.length - 6; i >= 6; i -= 6) {
|
||||
if (isNaN(bottom[i])) {
|
||||
buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`);
|
||||
|
@ -591,17 +246,60 @@ class FreeOutliner {
|
|||
);
|
||||
}
|
||||
}
|
||||
buffer.push(`L${bottom[4]} ${bottom[5]} Z`);
|
||||
|
||||
this.#toSVGPathStart(buffer);
|
||||
|
||||
return buffer.join(" ");
|
||||
}
|
||||
|
||||
#toSVGPathTwoPoints() {
|
||||
const [x, y, width, height] = this.#box;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
|
||||
return `M${(this.#last[2] - x) / width} ${
|
||||
(this.#last[3] - y) / height
|
||||
} L${(this.#last[4] - x) / width} ${(this.#last[5] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${
|
||||
(this.#last[16] - x) / width
|
||||
} ${(this.#last[17] - y) / height} L${(this.#last[14] - x) / width} ${
|
||||
(this.#last[15] - y) / height
|
||||
} Z`;
|
||||
}
|
||||
|
||||
#toSVGPathStart(buffer) {
|
||||
const bottom = this.#bottom;
|
||||
buffer.push(`L${bottom[4]} ${bottom[5]} Z`);
|
||||
}
|
||||
|
||||
#toSVGPathEnd(buffer) {
|
||||
const [x, y, width, height] = this.#box;
|
||||
const lastTop = this.#last.subarray(4, 6);
|
||||
const lastBottom = this.#last.subarray(16, 18);
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
|
||||
buffer.push(
|
||||
`L${(lastTop[0] - x) / width} ${(lastTop[1] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${
|
||||
(lastBottom[0] - x) / width
|
||||
} ${(lastBottom[1] - y) / height}`
|
||||
);
|
||||
}
|
||||
|
||||
newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
|
||||
return new FreeDrawOutline(
|
||||
outline,
|
||||
points,
|
||||
box,
|
||||
scaleFactor,
|
||||
innerMargin,
|
||||
isLTR
|
||||
);
|
||||
}
|
||||
|
||||
getOutlines() {
|
||||
const top = this.#top;
|
||||
const bottom = this.#bottom;
|
||||
const last = this.#last;
|
||||
const lastTop = last.subarray(4, 6);
|
||||
const lastBottom = last.subarray(16, 18);
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
|
||||
const points = new Float64Array((this.#points?.length ?? 0) + 2);
|
||||
|
@ -611,61 +309,10 @@ class FreeOutliner {
|
|||
}
|
||||
points[points.length - 2] = (this.#lastX - layerX) / layerWidth;
|
||||
points[points.length - 1] = (this.#lastY - layerY) / layerHeight;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
|
||||
if (isNaN(last[6]) && !this.isEmpty()) {
|
||||
// We've only two points.
|
||||
const outline = new Float64Array(36);
|
||||
outline.set(
|
||||
[
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[2] - layerX) / layerWidth,
|
||||
(last[3] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[4] - layerX) / layerWidth,
|
||||
(last[5] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
lastTopX,
|
||||
lastTopY,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
lastBottomX,
|
||||
lastBottomY,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[16] - layerX) / layerWidth,
|
||||
(last[17] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[14] - layerX) / layerWidth,
|
||||
(last[15] - layerY) / layerHeight,
|
||||
],
|
||||
0
|
||||
);
|
||||
return new FreeHighlightOutline(
|
||||
outline,
|
||||
points,
|
||||
this.#box,
|
||||
this.#scaleFactor,
|
||||
this.#innerMargin,
|
||||
this.#isLTR
|
||||
);
|
||||
return this.#getOutlineTwoPoints(points);
|
||||
}
|
||||
|
||||
const outline = new Float64Array(
|
||||
|
@ -681,6 +328,102 @@ class FreeOutliner {
|
|||
outline[i + 1] = top[i + 1];
|
||||
}
|
||||
|
||||
N = this.#getOutlineEnd(outline, N);
|
||||
|
||||
for (let i = bottom.length - 6; i >= 6; i -= 6) {
|
||||
for (let j = 0; j < 6; j += 2) {
|
||||
if (isNaN(bottom[i + j])) {
|
||||
outline[N] = outline[N + 1] = NaN;
|
||||
N += 2;
|
||||
continue;
|
||||
}
|
||||
outline[N] = bottom[i + j];
|
||||
outline[N + 1] = bottom[i + j + 1];
|
||||
N += 2;
|
||||
}
|
||||
}
|
||||
|
||||
this.#getOutlineStart(outline, N);
|
||||
|
||||
return this.newFreeDrawOutline(
|
||||
outline,
|
||||
points,
|
||||
this.#box,
|
||||
this.#scaleFactor,
|
||||
this.#innerMargin,
|
||||
this.#isLTR
|
||||
);
|
||||
}
|
||||
|
||||
#getOutlineTwoPoints(points) {
|
||||
const last = this.#last;
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
const outline = new Float64Array(36);
|
||||
outline.set(
|
||||
[
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[2] - layerX) / layerWidth,
|
||||
(last[3] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[4] - layerX) / layerWidth,
|
||||
(last[5] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
lastTopX,
|
||||
lastTopY,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
lastBottomX,
|
||||
lastBottomY,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[16] - layerX) / layerWidth,
|
||||
(last[17] - layerY) / layerHeight,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
NaN,
|
||||
(last[14] - layerX) / layerWidth,
|
||||
(last[15] - layerY) / layerHeight,
|
||||
],
|
||||
0
|
||||
);
|
||||
return this.newFreeDrawOutline(
|
||||
outline,
|
||||
points,
|
||||
this.#box,
|
||||
this.#scaleFactor,
|
||||
this.#innerMargin,
|
||||
this.#isLTR
|
||||
);
|
||||
}
|
||||
|
||||
#getOutlineStart(outline, pos) {
|
||||
const bottom = this.#bottom;
|
||||
outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], pos);
|
||||
return (pos += 6);
|
||||
}
|
||||
|
||||
#getOutlineEnd(outline, pos) {
|
||||
const lastTop = this.#last.subarray(4, 6);
|
||||
const lastBottom = this.#last.subarray(16, 18);
|
||||
const [layerX, layerY, layerWidth, layerHeight] = this.#box;
|
||||
const [lastTopX, lastTopY, lastBottomX, lastBottomY] =
|
||||
this.#getLastCoords();
|
||||
outline.set(
|
||||
[
|
||||
NaN,
|
||||
|
@ -708,35 +451,13 @@ class FreeOutliner {
|
|||
(lastBottom[0] - layerX) / layerWidth,
|
||||
(lastBottom[1] - layerY) / layerHeight,
|
||||
],
|
||||
N
|
||||
);
|
||||
N += 24;
|
||||
|
||||
for (let i = bottom.length - 6; i >= 6; i -= 6) {
|
||||
for (let j = 0; j < 6; j += 2) {
|
||||
if (isNaN(bottom[i + j])) {
|
||||
outline[N] = outline[N + 1] = NaN;
|
||||
N += 2;
|
||||
continue;
|
||||
}
|
||||
outline[N] = bottom[i + j];
|
||||
outline[N + 1] = bottom[i + j + 1];
|
||||
N += 2;
|
||||
}
|
||||
}
|
||||
outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], N);
|
||||
return new FreeHighlightOutline(
|
||||
outline,
|
||||
points,
|
||||
this.#box,
|
||||
this.#scaleFactor,
|
||||
this.#innerMargin,
|
||||
this.#isLTR
|
||||
pos
|
||||
);
|
||||
return (pos += 24);
|
||||
}
|
||||
}
|
||||
|
||||
class FreeHighlightOutline extends Outline {
|
||||
class FreeDrawOutline extends Outline {
|
||||
#box;
|
||||
|
||||
#bbox = null;
|
||||
|
@ -895,6 +616,17 @@ class FreeHighlightOutline extends Outline {
|
|||
return this.#bbox;
|
||||
}
|
||||
|
||||
newOutliner(point, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
|
||||
return new FreeDrawOutliner(
|
||||
point,
|
||||
box,
|
||||
scaleFactor,
|
||||
thickness,
|
||||
isLTR,
|
||||
innerMargin
|
||||
);
|
||||
}
|
||||
|
||||
getNewOutline(thickness, innerMargin) {
|
||||
// Build the outline of the highlight to use as the focus outline.
|
||||
const { x, y, width, height } = this.#bbox;
|
||||
|
@ -903,7 +635,7 @@ class FreeHighlightOutline extends Outline {
|
|||
const sy = height * layerHeight;
|
||||
const tx = x * layerWidth + layerX;
|
||||
const ty = y * layerHeight + layerY;
|
||||
const outliner = new FreeOutliner(
|
||||
const outliner = this.newOutliner(
|
||||
{
|
||||
x: this.#points[0] * sx + tx,
|
||||
y: this.#points[1] * sy + ty,
|
||||
|
@ -922,6 +654,10 @@ class FreeHighlightOutline extends Outline {
|
|||
}
|
||||
return outliner.getOutlines();
|
||||
}
|
||||
|
||||
get mustRemoveSelfIntersections() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { FreeOutliner, Outliner };
|
||||
export { FreeDrawOutline, FreeDrawOutliner };
|
369
src/display/editor/drawers/highlight.js
Normal file
369
src/display/editor/drawers/highlight.js
Normal file
|
@ -0,0 +1,369 @@
|
|||
/* Copyright 2023 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 { FreeDrawOutline, FreeDrawOutliner } from "./freedraw.js";
|
||||
import { Outline } from "./outline.js";
|
||||
|
||||
class HighlightOutliner {
|
||||
#box;
|
||||
|
||||
#verticalEdges = [];
|
||||
|
||||
#intervals = [];
|
||||
|
||||
/**
|
||||
* Construct an outliner.
|
||||
* @param {Array<Object>} boxes - An array of axis-aligned rectangles.
|
||||
* @param {number} borderWidth - The width of the border of the boxes, it
|
||||
* allows to make the boxes bigger (or smaller).
|
||||
* @param {number} innerMargin - The margin between the boxes and the
|
||||
* outlines. It's important to not have a null innerMargin when we want to
|
||||
* draw the outline else the stroked outline could be clipped because of its
|
||||
* width.
|
||||
* @param {boolean} isLTR - true if we're in LTR mode. It's used to determine
|
||||
* the last point of the boxes.
|
||||
*/
|
||||
constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) {
|
||||
let minX = Infinity;
|
||||
let maxX = -Infinity;
|
||||
let minY = Infinity;
|
||||
let maxY = -Infinity;
|
||||
|
||||
// We round the coordinates to slightly reduce the number of edges in the
|
||||
// final outlines.
|
||||
const NUMBER_OF_DIGITS = 4;
|
||||
const EPSILON = 10 ** -NUMBER_OF_DIGITS;
|
||||
|
||||
// The coordinates of the boxes are in the page coordinate system.
|
||||
for (const { x, y, width, height } of boxes) {
|
||||
const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON;
|
||||
const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON;
|
||||
const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON;
|
||||
const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON;
|
||||
const left = [x1, y1, y2, true];
|
||||
const right = [x2, y1, y2, false];
|
||||
this.#verticalEdges.push(left, right);
|
||||
|
||||
minX = Math.min(minX, x1);
|
||||
maxX = Math.max(maxX, x2);
|
||||
minY = Math.min(minY, y1);
|
||||
maxY = Math.max(maxY, y2);
|
||||
}
|
||||
|
||||
const bboxWidth = maxX - minX + 2 * innerMargin;
|
||||
const bboxHeight = maxY - minY + 2 * innerMargin;
|
||||
const shiftedMinX = minX - innerMargin;
|
||||
const shiftedMinY = minY - innerMargin;
|
||||
const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2);
|
||||
const lastPoint = [lastEdge[0], lastEdge[2]];
|
||||
|
||||
// Convert the coordinates of the edges into box coordinates.
|
||||
for (const edge of this.#verticalEdges) {
|
||||
const [x, y1, y2] = edge;
|
||||
edge[0] = (x - shiftedMinX) / bboxWidth;
|
||||
edge[1] = (y1 - shiftedMinY) / bboxHeight;
|
||||
edge[2] = (y2 - shiftedMinY) / bboxHeight;
|
||||
}
|
||||
|
||||
this.#box = {
|
||||
x: shiftedMinX,
|
||||
y: shiftedMinY,
|
||||
width: bboxWidth,
|
||||
height: bboxHeight,
|
||||
lastPoint,
|
||||
};
|
||||
}
|
||||
|
||||
getOutlines() {
|
||||
// We begin to sort lexicographically the vertical edges by their abscissa,
|
||||
// and then by their ordinate.
|
||||
this.#verticalEdges.sort(
|
||||
(a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]
|
||||
);
|
||||
|
||||
// We're now using a sweep line algorithm to find the outlines.
|
||||
// We start with the leftmost vertical edge, and we're going to iterate
|
||||
// over all the vertical edges from left to right.
|
||||
// Each time we encounter a left edge, we're going to insert the interval
|
||||
// [y1, y2] in the set of intervals.
|
||||
// This set of intervals is used to break the vertical edges into chunks:
|
||||
// we only take the part of the vertical edge that isn't in the union of
|
||||
// the intervals.
|
||||
const outlineVerticalEdges = [];
|
||||
for (const edge of this.#verticalEdges) {
|
||||
if (edge[3]) {
|
||||
// Left edge.
|
||||
outlineVerticalEdges.push(...this.#breakEdge(edge));
|
||||
this.#insert(edge);
|
||||
} else {
|
||||
// Right edge.
|
||||
this.#remove(edge);
|
||||
outlineVerticalEdges.push(...this.#breakEdge(edge));
|
||||
}
|
||||
}
|
||||
return this.#getOutlines(outlineVerticalEdges);
|
||||
}
|
||||
|
||||
#getOutlines(outlineVerticalEdges) {
|
||||
const edges = [];
|
||||
const allEdges = new Set();
|
||||
|
||||
for (const edge of outlineVerticalEdges) {
|
||||
const [x, y1, y2] = edge;
|
||||
edges.push([x, y1, edge], [x, y2, edge]);
|
||||
}
|
||||
|
||||
// We sort lexicographically the vertices of each edge by their ordinate and
|
||||
// by their abscissa.
|
||||
// Every pair (v_2i, v_{2i + 1}) of vertices defines a horizontal edge.
|
||||
// So for every vertical edge, we're going to add the two vertical edges
|
||||
// which are connected to it through a horizontal edge.
|
||||
edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
|
||||
for (let i = 0, ii = edges.length; i < ii; i += 2) {
|
||||
const edge1 = edges[i][2];
|
||||
const edge2 = edges[i + 1][2];
|
||||
edge1.push(edge2);
|
||||
edge2.push(edge1);
|
||||
allEdges.add(edge1);
|
||||
allEdges.add(edge2);
|
||||
}
|
||||
const outlines = [];
|
||||
let outline;
|
||||
|
||||
while (allEdges.size > 0) {
|
||||
const edge = allEdges.values().next().value;
|
||||
let [x, y1, y2, edge1, edge2] = edge;
|
||||
allEdges.delete(edge);
|
||||
let lastPointX = x;
|
||||
let lastPointY = y1;
|
||||
|
||||
outline = [x, y2];
|
||||
outlines.push(outline);
|
||||
|
||||
while (true) {
|
||||
let e;
|
||||
if (allEdges.has(edge1)) {
|
||||
e = edge1;
|
||||
} else if (allEdges.has(edge2)) {
|
||||
e = edge2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
allEdges.delete(e);
|
||||
[x, y1, y2, edge1, edge2] = e;
|
||||
|
||||
if (lastPointX !== x) {
|
||||
outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2);
|
||||
lastPointX = x;
|
||||
}
|
||||
lastPointY = lastPointY === y1 ? y2 : y1;
|
||||
}
|
||||
outline.push(lastPointX, lastPointY);
|
||||
}
|
||||
return new HighlightOutline(outlines, this.#box);
|
||||
}
|
||||
|
||||
#binarySearch(y) {
|
||||
const array = this.#intervals;
|
||||
let start = 0;
|
||||
let end = array.length - 1;
|
||||
|
||||
while (start <= end) {
|
||||
const middle = (start + end) >> 1;
|
||||
const y1 = array[middle][0];
|
||||
if (y1 === y) {
|
||||
return middle;
|
||||
}
|
||||
if (y1 < y) {
|
||||
start = middle + 1;
|
||||
} else {
|
||||
end = middle - 1;
|
||||
}
|
||||
}
|
||||
return end + 1;
|
||||
}
|
||||
|
||||
#insert([, y1, y2]) {
|
||||
const index = this.#binarySearch(y1);
|
||||
this.#intervals.splice(index, 0, [y1, y2]);
|
||||
}
|
||||
|
||||
#remove([, y1, y2]) {
|
||||
const index = this.#binarySearch(y1);
|
||||
for (let i = index; i < this.#intervals.length; i++) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
if (start !== y1) {
|
||||
break;
|
||||
}
|
||||
if (start === y1 && end === y2) {
|
||||
this.#intervals.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (let i = index - 1; i >= 0; i--) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
if (start !== y1) {
|
||||
break;
|
||||
}
|
||||
if (start === y1 && end === y2) {
|
||||
this.#intervals.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#breakEdge(edge) {
|
||||
const [x, y1, y2] = edge;
|
||||
const results = [[x, y1, y2]];
|
||||
const index = this.#binarySearch(y2);
|
||||
for (let i = 0; i < index; i++) {
|
||||
const [start, end] = this.#intervals[i];
|
||||
for (let j = 0, jj = results.length; j < jj; j++) {
|
||||
const [, y3, y4] = results[j];
|
||||
if (end <= y3 || y4 <= start) {
|
||||
// There is no intersection between the interval and the edge, hence
|
||||
// we keep it as is.
|
||||
continue;
|
||||
}
|
||||
if (y3 >= start) {
|
||||
if (y4 > end) {
|
||||
results[j][1] = end;
|
||||
} else {
|
||||
if (jj === 1) {
|
||||
return [];
|
||||
}
|
||||
// The edge is included in the interval, hence we remove it.
|
||||
results.splice(j, 1);
|
||||
j--;
|
||||
jj--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
results[j][2] = start;
|
||||
if (y4 > end) {
|
||||
results.push([x, end, y4]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
class HighlightOutline extends Outline {
|
||||
#box;
|
||||
|
||||
#outlines;
|
||||
|
||||
constructor(outlines, box) {
|
||||
super();
|
||||
this.#outlines = outlines;
|
||||
this.#box = box;
|
||||
}
|
||||
|
||||
toSVGPath() {
|
||||
const buffer = [];
|
||||
for (const polygon of this.#outlines) {
|
||||
let [prevX, prevY] = polygon;
|
||||
buffer.push(`M${prevX} ${prevY}`);
|
||||
for (let i = 2; i < polygon.length; i += 2) {
|
||||
const x = polygon[i];
|
||||
const y = polygon[i + 1];
|
||||
if (x === prevX) {
|
||||
buffer.push(`V${y}`);
|
||||
prevY = y;
|
||||
} else if (y === prevY) {
|
||||
buffer.push(`H${x}`);
|
||||
prevX = x;
|
||||
}
|
||||
}
|
||||
buffer.push("Z");
|
||||
}
|
||||
return buffer.join(" ");
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the outlines into the PDF page coordinate system.
|
||||
* @param {Array<number>} _bbox - the bounding box of the annotation.
|
||||
* @param {number} _rotation - the rotation of the annotation.
|
||||
* @returns {Array<Array<number>>}
|
||||
*/
|
||||
serialize([blX, blY, trX, trY], _rotation) {
|
||||
const outlines = [];
|
||||
const width = trX - blX;
|
||||
const height = trY - blY;
|
||||
for (const outline of this.#outlines) {
|
||||
const points = new Array(outline.length);
|
||||
for (let i = 0; i < outline.length; i += 2) {
|
||||
points[i] = blX + outline[i] * width;
|
||||
points[i + 1] = trY - outline[i + 1] * height;
|
||||
}
|
||||
outlines.push(points);
|
||||
}
|
||||
return outlines;
|
||||
}
|
||||
|
||||
get box() {
|
||||
return this.#box;
|
||||
}
|
||||
|
||||
get classNamesForDrawing() {
|
||||
return ["highlight"];
|
||||
}
|
||||
|
||||
get classNamesForOutlining() {
|
||||
return ["highlightOutline"];
|
||||
}
|
||||
}
|
||||
|
||||
class FreeHighlightOutliner extends FreeDrawOutliner {
|
||||
newFreeDrawOutline(outline, points, box, scaleFactor, innerMargin, isLTR) {
|
||||
return new FreeHighlightOutline(
|
||||
outline,
|
||||
points,
|
||||
box,
|
||||
scaleFactor,
|
||||
innerMargin,
|
||||
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,
|
||||
box,
|
||||
scaleFactor,
|
||||
thickness,
|
||||
isLTR,
|
||||
innerMargin
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export { FreeHighlightOutliner, HighlightOutliner };
|
55
src/display/editor/drawers/outline.js
Normal file
55
src/display/editor/drawers/outline.js
Normal file
|
@ -0,0 +1,55 @@
|
|||
/* Copyright 2023 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 { unreachable } from "../../../shared/util.js";
|
||||
|
||||
class Outline {
|
||||
/**
|
||||
* @returns {string} The SVG path of the outline.
|
||||
*/
|
||||
toSVGPath() {
|
||||
unreachable("Abstract method `toSVGPath` must be implemented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Object|null} The bounding box of the outline.
|
||||
*/
|
||||
// eslint-disable-next-line getter-return
|
||||
get box() {
|
||||
unreachable("Abstract getter `box` must be implemented.");
|
||||
}
|
||||
|
||||
serialize(_bbox, _rotation) {
|
||||
unreachable("Abstract method `serialize` must be implemented.");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line getter-return
|
||||
get classNamesForDrawing() {
|
||||
unreachable("Abstract getter `classNamesForDrawing` must be implemented.");
|
||||
}
|
||||
|
||||
// eslint-disable-next-line getter-return
|
||||
get classNamesForOutlining() {
|
||||
unreachable(
|
||||
"Abstract getter `classNamesForOutlining` must be implemented."
|
||||
);
|
||||
}
|
||||
|
||||
get mustRemoveSelfIntersections() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export { Outline };
|
|
@ -20,7 +20,10 @@ import {
|
|||
Util,
|
||||
} from "../../shared/util.js";
|
||||
import { bindEvents, KeyboardManager } from "./tools.js";
|
||||
import { FreeOutliner, Outliner } from "./outliner.js";
|
||||
import {
|
||||
FreeHighlightOutliner,
|
||||
HighlightOutliner,
|
||||
} from "./drawers/highlight.js";
|
||||
import {
|
||||
HighlightAnnotationElement,
|
||||
InkAnnotationElement,
|
||||
|
@ -149,7 +152,10 @@ class HighlightEditor extends AnnotationEditor {
|
|||
}
|
||||
|
||||
#createOutlines() {
|
||||
const outliner = new Outliner(this.#boxes, /* borderWidth = */ 0.001);
|
||||
const outliner = new HighlightOutliner(
|
||||
this.#boxes,
|
||||
/* borderWidth = */ 0.001
|
||||
);
|
||||
this.#highlightOutlines = outliner.getOutlines();
|
||||
({
|
||||
x: this.x,
|
||||
|
@ -158,7 +164,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
height: this.height,
|
||||
} = this.#highlightOutlines.box);
|
||||
|
||||
const outlinerForOutline = new Outliner(
|
||||
const outlinerForOutline = new HighlightOutliner(
|
||||
this.#boxes,
|
||||
/* borderWidth = */ 0.0025,
|
||||
/* innerMargin = */ 0.001,
|
||||
|
@ -190,9 +196,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
// 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.highlightOutline(
|
||||
this.#focusOutlines
|
||||
);
|
||||
this.#outlineId = this.parent.drawLayer.drawOutline(this.#focusOutlines);
|
||||
} else if (this.parent) {
|
||||
const angle = this.parent.viewport.rotation;
|
||||
this.parent.drawLayer.updateLine(this.#id, highlightOutlines);
|
||||
|
@ -498,13 +502,12 @@ class HighlightEditor extends AnnotationEditor {
|
|||
if (this.#id !== null) {
|
||||
return;
|
||||
}
|
||||
({ id: this.#id, clipPathId: this.#clipPathId } =
|
||||
parent.drawLayer.highlight(
|
||||
this.#highlightOutlines,
|
||||
this.color,
|
||||
this.#opacity
|
||||
));
|
||||
this.#outlineId = parent.drawLayer.highlightOutline(this.#focusOutlines);
|
||||
({ id: this.#id, clipPathId: this.#clipPathId } = parent.drawLayer.draw(
|
||||
this.#highlightOutlines,
|
||||
this.color,
|
||||
this.#opacity
|
||||
));
|
||||
this.#outlineId = parent.drawLayer.drawOutline(this.#focusOutlines);
|
||||
if (this.#highlightDiv) {
|
||||
this.#highlightDiv.style.clipPath = this.#clipPathId;
|
||||
}
|
||||
|
@ -742,7 +745,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
this.#highlightMove.bind(this, parent),
|
||||
{ signal }
|
||||
);
|
||||
this._freeHighlight = new FreeOutliner(
|
||||
this._freeHighlight = new FreeHighlightOutliner(
|
||||
{ x, y },
|
||||
[layerX, layerY, parentWidth, parentHeight],
|
||||
parent.scale,
|
||||
|
@ -751,7 +754,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
/* innerMargin = */ 0.001
|
||||
);
|
||||
({ id: this._freeHighlightId, clipPathId: this._freeHighlightClipId } =
|
||||
parent.drawLayer.highlight(
|
||||
parent.drawLayer.draw(
|
||||
this._freeHighlight,
|
||||
this._defaultColor,
|
||||
this._defaultOpacity,
|
||||
|
@ -775,7 +778,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
methodOfCreation: "main_toolbar",
|
||||
});
|
||||
} else {
|
||||
parent.drawLayer.removeFreeHighlight(this._freeHighlightId);
|
||||
parent.drawLayer.remove(this._freeHighlightId);
|
||||
}
|
||||
this._freeHighlightId = -1;
|
||||
this._freeHighlight = null;
|
||||
|
@ -869,7 +872,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
x: points[0] - pageX,
|
||||
y: pageHeight - (points[1] - pageY),
|
||||
};
|
||||
const outliner = new FreeOutliner(
|
||||
const outliner = new FreeHighlightOutliner(
|
||||
point,
|
||||
[0, 0, pageWidth, pageHeight],
|
||||
1,
|
||||
|
@ -882,7 +885,7 @@ class HighlightEditor extends AnnotationEditor {
|
|||
point.y = pageHeight - (points[i + 1] - pageY);
|
||||
outliner.add(point);
|
||||
}
|
||||
const { id, clipPathId } = parent.drawLayer.highlight(
|
||||
const { id, clipPathId } = parent.drawLayer.draw(
|
||||
outliner,
|
||||
editor.color,
|
||||
editor._defaultOpacity,
|
||||
|
|
|
@ -69,7 +69,7 @@ import { AnnotationLayer } from "./display/annotation_layer.js";
|
|||
import { ColorPicker } from "./display/editor/color_picker.js";
|
||||
import { DrawLayer } from "./display/draw_layer.js";
|
||||
import { GlobalWorkerOptions } from "./display/worker_options.js";
|
||||
import { Outliner } from "./display/editor/outliner.js";
|
||||
import { HighlightOutliner } from "./display/editor/drawers/highlight.js";
|
||||
import { TextLayer } from "./display/text_layer.js";
|
||||
import { XfaLayer } from "./display/xfa_layer.js";
|
||||
|
||||
|
@ -82,7 +82,7 @@ const pdfjsBuild =
|
|||
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
|
||||
globalThis.pdfjsTestingUtils = {
|
||||
Outliner,
|
||||
HighlightOutliner,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue