213 lines
5.1 KiB
JavaScript
213 lines
5.1 KiB
JavaScript
/* Copyright 2025 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.
|
|
*/
|
|
|
|
class SingleIntersector {
|
|
#annotation;
|
|
|
|
#minX = Infinity;
|
|
|
|
#minY = Infinity;
|
|
|
|
#maxX = -Infinity;
|
|
|
|
#maxY = -Infinity;
|
|
|
|
#quadPoints = null;
|
|
|
|
#text = [];
|
|
|
|
#extraChars = [];
|
|
|
|
#lastIntersectingQuadIndex = -1;
|
|
|
|
#canTakeExtraChars = false;
|
|
|
|
constructor(annotation) {
|
|
this.#annotation = annotation;
|
|
const quadPoints = annotation.data.quadPoints;
|
|
if (!quadPoints) {
|
|
// If there are no quad points, we use the rectangle to determine the
|
|
// bounds of the annotation.
|
|
[this.#minX, this.#minY, this.#maxX, this.#maxY] = annotation.data.rect;
|
|
return;
|
|
}
|
|
|
|
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
|
|
this.#minX = Math.min(this.#minX, quadPoints[i]);
|
|
this.#maxX = Math.max(this.#maxX, quadPoints[i + 2]);
|
|
this.#minY = Math.min(this.#minY, quadPoints[i + 5]);
|
|
this.#maxY = Math.max(this.#maxY, quadPoints[i + 1]);
|
|
}
|
|
if (quadPoints.length > 8) {
|
|
this.#quadPoints = quadPoints;
|
|
}
|
|
}
|
|
|
|
overlaps(other) {
|
|
return !(
|
|
this.#minX >= other.#maxX ||
|
|
this.#maxX <= other.#minX ||
|
|
this.#minY >= other.#maxY ||
|
|
this.#maxY <= other.#minY
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Check if the given point intersects with the annotation's quad points.
|
|
* The point (x, y) is supposed to be the center of the glyph.
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @returns {boolean}
|
|
*/
|
|
#intersects(x, y) {
|
|
if (
|
|
this.#minX >= x ||
|
|
this.#maxX <= x ||
|
|
this.#minY >= y ||
|
|
this.#maxY <= y
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
const quadPoints = this.#quadPoints;
|
|
if (!quadPoints) {
|
|
// We've only one quad, so if we intersect min/max bounds then we
|
|
// intersect the quad.
|
|
return true;
|
|
}
|
|
|
|
if (this.#lastIntersectingQuadIndex >= 0) {
|
|
const i = this.#lastIntersectingQuadIndex;
|
|
if (
|
|
!(
|
|
quadPoints[i] >= x ||
|
|
quadPoints[i + 2] <= x ||
|
|
quadPoints[i + 5] >= y ||
|
|
quadPoints[i + 1] <= y
|
|
)
|
|
) {
|
|
return true;
|
|
}
|
|
this.#lastIntersectingQuadIndex = -1;
|
|
}
|
|
|
|
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
|
|
if (
|
|
!(
|
|
quadPoints[i] >= x ||
|
|
quadPoints[i + 2] <= x ||
|
|
quadPoints[i + 5] >= y ||
|
|
quadPoints[i + 1] <= y
|
|
)
|
|
) {
|
|
this.#lastIntersectingQuadIndex = i;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
addGlyph(x, y, glyph) {
|
|
if (!this.#intersects(x, y)) {
|
|
this.disableExtraChars();
|
|
return false;
|
|
}
|
|
|
|
if (this.#extraChars.length > 0) {
|
|
this.#text.push(this.#extraChars.join(""));
|
|
this.#extraChars.length = 0;
|
|
}
|
|
this.#text.push(glyph);
|
|
this.#canTakeExtraChars = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
addExtraChar(char) {
|
|
if (this.#canTakeExtraChars) {
|
|
this.#extraChars.push(char);
|
|
}
|
|
}
|
|
|
|
disableExtraChars() {
|
|
if (!this.#canTakeExtraChars) {
|
|
return;
|
|
}
|
|
this.#canTakeExtraChars = false;
|
|
this.#extraChars.length = 0;
|
|
}
|
|
|
|
setText() {
|
|
this.#annotation.data.overlaidText = this.#text.join("");
|
|
}
|
|
}
|
|
|
|
class Intersector {
|
|
#intersectors = new Map();
|
|
|
|
constructor(annotations) {
|
|
for (const annotation of annotations) {
|
|
if (!annotation.data.quadPoints && !annotation.data.rect) {
|
|
continue;
|
|
}
|
|
const intersector = new SingleIntersector(annotation);
|
|
for (const [otherIntersector, overlapping] of this.#intersectors) {
|
|
if (otherIntersector.overlaps(intersector)) {
|
|
if (!overlapping) {
|
|
this.#intersectors.set(otherIntersector, new Set([intersector]));
|
|
} else {
|
|
overlapping.add(intersector);
|
|
}
|
|
}
|
|
}
|
|
this.#intersectors.set(intersector, null);
|
|
}
|
|
}
|
|
|
|
addGlyph(transform, width, height, glyph) {
|
|
const x = transform[4] + width / 2;
|
|
const y = transform[5] + height / 2;
|
|
let overlappingIntersectors;
|
|
for (const [intersector, overlapping] of this.#intersectors) {
|
|
if (overlappingIntersectors) {
|
|
if (overlappingIntersectors.has(intersector)) {
|
|
intersector.addGlyph(x, y, glyph);
|
|
} else {
|
|
intersector.disableExtraChars();
|
|
}
|
|
continue;
|
|
}
|
|
if (!intersector.addGlyph(x, y, glyph)) {
|
|
continue;
|
|
}
|
|
overlappingIntersectors = overlapping;
|
|
}
|
|
}
|
|
|
|
addExtraChar(char) {
|
|
for (const intersector of this.#intersectors.keys()) {
|
|
intersector.addExtraChar(char);
|
|
}
|
|
}
|
|
|
|
setText() {
|
|
for (const intersector of this.#intersectors.keys()) {
|
|
intersector.setText();
|
|
}
|
|
}
|
|
}
|
|
|
|
export { Intersector };
|