Skip to content

Commit c4d4367

Browse files
committed
[Annotation] Improve the performance of the code for getting glyphs which belongs to annotations bounding boxes (bug 1987914)
Instead of looking at every bbox, we use a grid (64x64) where each cell of the grid is associated with the bboxes touching it. In order to get the potential bboxes containing a point, we just have to compute the number of the cell containing it and in using the associated described above, we can quickly know if the point is contained. With the pdf in the mentioned bug, it's ~20 times faster.
1 parent 8ba1807 commit c4d4367

1 file changed

Lines changed: 69 additions & 48 deletions

File tree

src/core/intersector.js

Lines changed: 69 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,13 @@
1616
class SingleIntersector {
1717
#annotation;
1818

19-
#minX = Infinity;
19+
minX = Infinity;
2020

21-
#minY = Infinity;
21+
minY = Infinity;
2222

23-
#maxX = -Infinity;
23+
maxX = -Infinity;
2424

25-
#maxY = -Infinity;
25+
maxY = -Infinity;
2626

2727
#quadPoints = null;
2828

@@ -40,30 +40,21 @@ class SingleIntersector {
4040
if (!quadPoints) {
4141
// If there are no quad points, we use the rectangle to determine the
4242
// bounds of the annotation.
43-
[this.#minX, this.#minY, this.#maxX, this.#maxY] = annotation.data.rect;
43+
[this.minX, this.minY, this.maxX, this.maxY] = annotation.data.rect;
4444
return;
4545
}
4646

4747
for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
48-
this.#minX = Math.min(this.#minX, quadPoints[i]);
49-
this.#maxX = Math.max(this.#maxX, quadPoints[i + 2]);
50-
this.#minY = Math.min(this.#minY, quadPoints[i + 5]);
51-
this.#maxY = Math.max(this.#maxY, quadPoints[i + 1]);
48+
this.minX = Math.min(this.minX, quadPoints[i]);
49+
this.maxX = Math.max(this.maxX, quadPoints[i + 2]);
50+
this.minY = Math.min(this.minY, quadPoints[i + 5]);
51+
this.maxY = Math.max(this.maxY, quadPoints[i + 1]);
5252
}
5353
if (quadPoints.length > 8) {
5454
this.#quadPoints = quadPoints;
5555
}
5656
}
5757

58-
overlaps(other) {
59-
return !(
60-
this.#minX >= other.#maxX ||
61-
this.#maxX <= other.#minX ||
62-
this.#minY >= other.#maxY ||
63-
this.#maxY <= other.#minY
64-
);
65-
}
66-
6758
/**
6859
* Check if the given point intersects with the annotation's quad points.
6960
* The point (x, y) is supposed to be the center of the glyph.
@@ -72,12 +63,7 @@ class SingleIntersector {
7263
* @returns {boolean}
7364
*/
7465
#intersects(x, y) {
75-
if (
76-
this.#minX >= x ||
77-
this.#maxX <= x ||
78-
this.#minY >= y ||
79-
this.#maxY <= y
80-
) {
66+
if (this.minX >= x || this.maxX <= x || this.minY >= y || this.maxY <= y) {
8167
return false;
8268
}
8369

@@ -154,56 +140,91 @@ class SingleIntersector {
154140
}
155141
}
156142

143+
// The grid is STEPS x STEPS.
144+
const STEPS = 64;
145+
157146
class Intersector {
158-
#intersectors = new Map();
147+
#intersectors = [];
148+
149+
#grid = [];
150+
151+
#minX;
152+
153+
#minY;
154+
155+
#invXRatio;
156+
157+
#invYRatio;
159158

160159
constructor(annotations) {
160+
let minX = Infinity;
161+
let minY = Infinity;
162+
let maxX = -Infinity;
163+
let maxY = -Infinity;
164+
const intersectors = this.#intersectors;
161165
for (const annotation of annotations) {
162166
if (!annotation.data.quadPoints && !annotation.data.rect) {
163167
continue;
164168
}
165169
const intersector = new SingleIntersector(annotation);
166-
for (const [otherIntersector, overlapping] of this.#intersectors) {
167-
if (otherIntersector.overlaps(intersector)) {
168-
if (!overlapping) {
169-
this.#intersectors.set(otherIntersector, new Set([intersector]));
170-
} else {
171-
overlapping.add(intersector);
170+
intersectors.push(intersector);
171+
minX = Math.min(minX, intersector.minX);
172+
minY = Math.min(minY, intersector.minY);
173+
maxX = Math.max(maxX, intersector.maxX);
174+
maxY = Math.max(maxY, intersector.maxY);
175+
}
176+
this.#minX = minX;
177+
this.#minY = minY;
178+
this.#invXRatio = (STEPS - 1) / (maxX - minX);
179+
this.#invYRatio = (STEPS - 1) / (maxY - minY);
180+
for (const intersector of intersectors) {
181+
const iMin = this.#getGridIndex(intersector.minX, intersector.minY);
182+
const iMax = this.#getGridIndex(intersector.maxX, intersector.maxY);
183+
const w = (iMax - iMin) % STEPS;
184+
const h = Math.floor((iMax - iMin) / STEPS);
185+
for (let i = iMin; i <= iMin + h * STEPS; i += STEPS) {
186+
for (let j = 0; j <= w; j++) {
187+
let existing = this.#grid[i + j];
188+
if (!existing) {
189+
this.#grid[i + j] = existing = [];
172190
}
191+
existing.push(intersector);
173192
}
174193
}
175-
this.#intersectors.set(intersector, null);
176194
}
177195
}
178196

197+
#getGridIndex(x, y) {
198+
const i = Math.floor((x - this.#minX) * this.#invXRatio);
199+
const j = Math.floor((y - this.#minY) * this.#invYRatio);
200+
return i >= 0 && i < STEPS && j >= 0 && j < STEPS ? i + j * STEPS : -1;
201+
}
202+
179203
addGlyph(transform, width, height, glyph) {
180204
const x = transform[4] + width / 2;
181205
const y = transform[5] + height / 2;
182-
let overlappingIntersectors;
183-
for (const [intersector, overlapping] of this.#intersectors) {
184-
if (overlappingIntersectors) {
185-
if (overlappingIntersectors.has(intersector)) {
186-
intersector.addGlyph(x, y, glyph);
187-
} else {
188-
intersector.disableExtraChars();
189-
}
190-
continue;
191-
}
192-
if (!intersector.addGlyph(x, y, glyph)) {
193-
continue;
194-
}
195-
overlappingIntersectors = overlapping;
206+
const index = this.#getGridIndex(x, y);
207+
if (index < 0) {
208+
return;
209+
}
210+
const intersectors = this.#grid[index];
211+
if (!intersectors) {
212+
return;
213+
}
214+
215+
for (const intersector of intersectors) {
216+
intersector.addGlyph(x, y, glyph);
196217
}
197218
}
198219

199220
addExtraChar(char) {
200-
for (const intersector of this.#intersectors.keys()) {
221+
for (const intersector of this.#intersectors) {
201222
intersector.addExtraChar(char);
202223
}
203224
}
204225

205226
setText() {
206-
for (const intersector of this.#intersectors.keys()) {
227+
for (const intersector of this.#intersectors) {
207228
intersector.setText();
208229
}
209230
}

0 commit comments

Comments
 (0)