Merge pull request #20353 from calixteman/improve_intersector

[Annotation] Improve the performance of the code for getting glyphs which belongs to annotations bounding boxes (bug 1987914)
This commit is contained in:
calixteman 2025-10-10 13:31:03 +02:00 committed by GitHub
commit 0d8a300777
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -16,13 +16,13 @@
class SingleIntersector { class SingleIntersector {
#annotation; #annotation;
#minX = Infinity; minX = Infinity;
#minY = Infinity; minY = Infinity;
#maxX = -Infinity; maxX = -Infinity;
#maxY = -Infinity; maxY = -Infinity;
#quadPoints = null; #quadPoints = null;
@ -40,30 +40,21 @@ class SingleIntersector {
if (!quadPoints) { if (!quadPoints) {
// If there are no quad points, we use the rectangle to determine the // If there are no quad points, we use the rectangle to determine the
// bounds of the annotation. // bounds of the annotation.
[this.#minX, this.#minY, this.#maxX, this.#maxY] = annotation.data.rect; [this.minX, this.minY, this.maxX, this.maxY] = annotation.data.rect;
return; return;
} }
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) { for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
this.#minX = Math.min(this.#minX, quadPoints[i]); this.minX = Math.min(this.minX, quadPoints[i]);
this.#maxX = Math.max(this.#maxX, quadPoints[i + 2]); this.maxX = Math.max(this.maxX, quadPoints[i + 2]);
this.#minY = Math.min(this.#minY, quadPoints[i + 5]); this.minY = Math.min(this.minY, quadPoints[i + 5]);
this.#maxY = Math.max(this.#maxY, quadPoints[i + 1]); this.maxY = Math.max(this.maxY, quadPoints[i + 1]);
} }
if (quadPoints.length > 8) { if (quadPoints.length > 8) {
this.#quadPoints = quadPoints; 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. * 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. * The point (x, y) is supposed to be the center of the glyph.
@ -72,12 +63,7 @@ class SingleIntersector {
* @returns {boolean} * @returns {boolean}
*/ */
#intersects(x, y) { #intersects(x, y) {
if ( if (this.minX >= x || this.maxX <= x || this.minY >= y || this.maxY <= y) {
this.#minX >= x ||
this.#maxX <= x ||
this.#minY >= y ||
this.#maxY <= y
) {
return false; return false;
} }
@ -154,56 +140,91 @@ class SingleIntersector {
} }
} }
// The grid is STEPS x STEPS.
const STEPS = 64;
class Intersector { class Intersector {
#intersectors = new Map(); #intersectors = [];
#grid = [];
#minX;
#minY;
#invXRatio;
#invYRatio;
constructor(annotations) { constructor(annotations) {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
const intersectors = this.#intersectors;
for (const annotation of annotations) { for (const annotation of annotations) {
if (!annotation.data.quadPoints && !annotation.data.rect) { if (!annotation.data.quadPoints && !annotation.data.rect) {
continue; continue;
} }
const intersector = new SingleIntersector(annotation); const intersector = new SingleIntersector(annotation);
for (const [otherIntersector, overlapping] of this.#intersectors) { intersectors.push(intersector);
if (otherIntersector.overlaps(intersector)) { minX = Math.min(minX, intersector.minX);
if (!overlapping) { minY = Math.min(minY, intersector.minY);
this.#intersectors.set(otherIntersector, new Set([intersector])); maxX = Math.max(maxX, intersector.maxX);
} else { maxY = Math.max(maxY, intersector.maxY);
overlapping.add(intersector); }
this.#minX = minX;
this.#minY = minY;
this.#invXRatio = (STEPS - 1) / (maxX - minX);
this.#invYRatio = (STEPS - 1) / (maxY - minY);
for (const intersector of intersectors) {
const iMin = this.#getGridIndex(intersector.minX, intersector.minY);
const iMax = this.#getGridIndex(intersector.maxX, intersector.maxY);
const w = (iMax - iMin) % STEPS;
const h = Math.floor((iMax - iMin) / STEPS);
for (let i = iMin; i <= iMin + h * STEPS; i += STEPS) {
for (let j = 0; j <= w; j++) {
let existing = this.#grid[i + j];
if (!existing) {
this.#grid[i + j] = existing = [];
}
existing.push(intersector);
} }
} }
} }
this.#intersectors.set(intersector, null);
} }
#getGridIndex(x, y) {
const i = Math.floor((x - this.#minX) * this.#invXRatio);
const j = Math.floor((y - this.#minY) * this.#invYRatio);
return i >= 0 && i < STEPS && j >= 0 && j < STEPS ? i + j * STEPS : -1;
} }
addGlyph(transform, width, height, glyph) { addGlyph(transform, width, height, glyph) {
const x = transform[4] + width / 2; const x = transform[4] + width / 2;
const y = transform[5] + height / 2; const y = transform[5] + height / 2;
let overlappingIntersectors; const index = this.#getGridIndex(x, y);
for (const [intersector, overlapping] of this.#intersectors) { if (index < 0) {
if (overlappingIntersectors) { return;
if (overlappingIntersectors.has(intersector)) { }
const intersectors = this.#grid[index];
if (!intersectors) {
return;
}
for (const intersector of intersectors) {
intersector.addGlyph(x, y, glyph); intersector.addGlyph(x, y, glyph);
} else {
intersector.disableExtraChars();
}
continue;
}
if (!intersector.addGlyph(x, y, glyph)) {
continue;
}
overlappingIntersectors = overlapping;
} }
} }
addExtraChar(char) { addExtraChar(char) {
for (const intersector of this.#intersectors.keys()) { for (const intersector of this.#intersectors) {
intersector.addExtraChar(char); intersector.addExtraChar(char);
} }
} }
setText() { setText() {
for (const intersector of this.#intersectors.keys()) { for (const intersector of this.#intersectors) {
intersector.setText(); intersector.setText();
} }
} }