Skip to content

Commit 2475b16

Browse files
Hook up the ops bbox logic to the pdf debugger
When using the pdf debugger, when hovering over a step now: - it highlights the steps in the same groups - it highlights the steps that they depend on - it highlights on the PDF itself the bounding box
1 parent e507c36 commit 2475b16

File tree

4 files changed

+193
-3
lines changed

4 files changed

+193
-3
lines changed

src/display/api.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import {
5959
NodeStandardFontDataFactory,
6060
} from "display-node_utils";
6161
import { CanvasGraphics } from "./canvas.js";
62+
import { CanvasRecorder } from "./canvas_recorder.js";
6263
import { DOMCanvasFactory } from "./canvas_factory.js";
6364
import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
6465
import { DOMFilterFactory } from "./filter_factory.js";
@@ -1517,9 +1518,22 @@ class PDFPageProxy {
15171518
this._pumpOperatorList(intentArgs);
15181519
}
15191520

1521+
const recordingContext =
1522+
this._pdfBug &&
1523+
globalThis.StepperManager?.enabled &&
1524+
!this._recordedGroups
1525+
? new CanvasRecorder(canvasContext)
1526+
: null;
1527+
15201528
const complete = error => {
15211529
intentState.renderTasks.delete(internalRenderTask);
15221530

1531+
if (recordingContext) {
1532+
this._recordedGroups =
1533+
CanvasRecorder.getFinishedGroups(recordingContext);
1534+
internalRenderTask.stepper.setOperatorGroups(this._recordedGroups);
1535+
}
1536+
15231537
// Attempt to reduce memory usage during *printing*, by always running
15241538
// cleanup immediately once rendering has finished.
15251539
if (this._maybeCleanupAfterRender || intentPrint) {
@@ -1552,7 +1566,7 @@ class PDFPageProxy {
15521566
callback: complete,
15531567
// Only include the required properties, and *not* the entire object.
15541568
params: {
1555-
canvasContext,
1569+
canvasContext: recordingContext ?? canvasContext,
15561570
viewport,
15571571
transform,
15581572
background,

web/debugger.css

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,34 @@
109109
background-color: rgb(255 255 255 / 0.6);
110110
color: rgb(0 0 0);
111111
}
112+
113+
.pdfBugGroupsLayer {
114+
position: absolute;
115+
inset: 0;
116+
pointer-events: none;
117+
118+
> * {
119+
position: absolute;
120+
outline-color: red;
121+
outline-width: 2px;
122+
123+
--hover-outline-style: solid !important;
124+
--hover-background-color: rgb(255 0 0 / 0.2);
125+
126+
&:hover {
127+
outline-style: var(--hover-outline-style);
128+
background-color: var(--hover-background-color);
129+
cursor: pointer;
130+
}
131+
132+
.showDebugBoxes & {
133+
outline-style: dashed;
134+
}
135+
}
136+
}
137+
138+
.showDebugBoxes {
139+
.pdfBugGroupsLayer {
140+
pointer-events: all;
141+
}
142+
}

web/debugger.mjs

Lines changed: 142 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,10 @@ const StepperManager = (function StepperManagerClosure() {
200200
active: false,
201201
// Stepper specific functions.
202202
create(pageIndex) {
203+
const pageContainer = document.querySelector(
204+
`#viewer div[data-page-number="${pageIndex + 1}"]`
205+
);
206+
203207
const debug = document.createElement("div");
204208
debug.id = "stepper" + pageIndex;
205209
debug.hidden = true;
@@ -210,7 +214,12 @@ const StepperManager = (function StepperManagerClosure() {
210214
b.value = pageIndex;
211215
stepperChooser.append(b);
212216
const initBreakPoints = breakPoints[pageIndex] || [];
213-
const stepper = new Stepper(debug, pageIndex, initBreakPoints);
217+
const stepper = new Stepper(
218+
debug,
219+
pageIndex,
220+
initBreakPoints,
221+
pageContainer
222+
);
214223
steppers.push(stepper);
215224
if (steppers.length === 1) {
216225
this.selectStepper(pageIndex, false);
@@ -277,7 +286,7 @@ class Stepper {
277286
return simpleObj;
278287
}
279288

280-
constructor(panel, pageIndex, initialBreakPoints) {
289+
constructor(panel, pageIndex, initialBreakPoints, pageContainer) {
281290
this.panel = panel;
282291
this.breakPoint = 0;
283292
this.nextBreakPoint = null;
@@ -286,11 +295,20 @@ class Stepper {
286295
this.currentIdx = -1;
287296
this.operatorListIdx = 0;
288297
this.indentLevel = 0;
298+
this.operatorGroups = null;
299+
this.pageContainer = pageContainer;
289300
}
290301

291302
init(operatorList) {
292303
const panel = this.panel;
293304
const content = this.#c("div", "c=continue, s=step");
305+
306+
const showBoxesToggle = this.#c("label", "Show bounding boxes");
307+
const showBoxesCheckbox = this.#c("input");
308+
showBoxesCheckbox.type = "checkbox";
309+
showBoxesToggle.prepend(showBoxesCheckbox);
310+
content.append(this.#c("br"), showBoxesToggle);
311+
294312
const table = this.#c("table");
295313
content.append(table);
296314
table.cellSpacing = 0;
@@ -305,6 +323,22 @@ class Stepper {
305323
panel.append(content);
306324
this.table = table;
307325
this.updateOperatorList(operatorList);
326+
327+
const hoverStyle = this.#c("style");
328+
this.hoverStyle = hoverStyle;
329+
content.prepend(hoverStyle);
330+
table.addEventListener("mouseover", this.#handleStepHover.bind(this));
331+
table.addEventListener("mouseleave", e => {
332+
hoverStyle.innerText = "";
333+
});
334+
335+
showBoxesCheckbox.addEventListener("change", () => {
336+
if (showBoxesCheckbox.checked) {
337+
this.pageContainer.classList.add("showDebugBoxes");
338+
} else {
339+
this.pageContainer.classList.remove("showDebugBoxes");
340+
}
341+
});
308342
}
309343

310344
updateOperatorList(operatorList) {
@@ -397,6 +431,112 @@ class Stepper {
397431
this.table.append(chunk);
398432
}
399433

434+
setOperatorGroups(groups) {
435+
this.operatorGroups = groups;
436+
437+
let boxesContainer = this.pageContainer.querySelector(".pdfBugGroupsLayer");
438+
if (!boxesContainer) {
439+
boxesContainer = this.#c("div");
440+
boxesContainer.classList.add("pdfBugGroupsLayer");
441+
this.pageContainer.append(boxesContainer);
442+
443+
boxesContainer.addEventListener(
444+
"click",
445+
this.#handleDebugBoxClick.bind(this)
446+
);
447+
boxesContainer.addEventListener(
448+
"mouseover",
449+
this.#handleDebugBoxHover.bind(this)
450+
);
451+
}
452+
boxesContainer.innerHTML = "";
453+
454+
for (let i = 0; i < groups.length; i++) {
455+
const el = this.#c("div");
456+
el.style.left = `${groups[i].minX * 100}%`;
457+
el.style.top = `${groups[i].minY * 100}%`;
458+
el.style.width = `${(groups[i].maxX - groups[i].minX) * 100}%`;
459+
el.style.height = `${(groups[i].maxY - groups[i].minY) * 100}%`;
460+
el.dataset.groupIdx = i;
461+
boxesContainer.append(el);
462+
}
463+
}
464+
465+
#handleStepHover(e) {
466+
const tr = e.target.closest("tr");
467+
if (!tr || tr.dataset.idx === undefined) {
468+
return;
469+
}
470+
471+
const index = +tr.dataset.idx;
472+
473+
const closestGroupIndex =
474+
this.operatorGroups?.findIndex(({ data }) => {
475+
if ("idx" in data) {
476+
return data.idx === index;
477+
}
478+
if ("startIdx" in data) {
479+
return data.startIdx <= index && index <= data.endIdx;
480+
}
481+
return false;
482+
}) ?? -1;
483+
if (closestGroupIndex === -1) {
484+
this.hoverStyle.innerText = "";
485+
return;
486+
}
487+
488+
this.#highlightStepsGroup(closestGroupIndex);
489+
}
490+
491+
#handleDebugBoxHover(e) {
492+
if (e.target.dataset.groupIdx === undefined) {
493+
return;
494+
}
495+
496+
const groupIdx = Number(e.target.dataset.groupIdx);
497+
this.#highlightStepsGroup(groupIdx);
498+
}
499+
500+
#handleDebugBoxClick(e) {
501+
if (e.target.dataset.groupIdx === undefined) {
502+
return;
503+
}
504+
505+
const groupIdx = Number(e.target.dataset.groupIdx);
506+
const group = this.operatorGroups[groupIdx];
507+
508+
const firstOp = "idx" in group.data ? group.data.idx : group.data.startIdx;
509+
510+
this.table.childNodes[firstOp].scrollIntoView();
511+
}
512+
513+
#highlightStepsGroup(groupIndex) {
514+
const group = this.operatorGroups[groupIndex];
515+
516+
let cssSelector;
517+
if ("idx" in group.data) {
518+
cssSelector = `tr[data-idx="${group.data.idx}"]`;
519+
} else if ("startIdx" in group.data) {
520+
cssSelector = `:nth-child(n+${group.data.startIdx + 1} of tr[data-idx]):nth-child(-n+${group.data.endIdx + 1} of tr[data-idx])`;
521+
}
522+
523+
this.hoverStyle.innerText = `#${this.panel.id} ${cssSelector} { background-color: rgba(0, 0, 0, 0.1); }`;
524+
525+
if (group.data.dependencies) {
526+
const selector = group.data.dependencies
527+
.map(idx => `#${this.panel.id} tr[data-idx="${idx}"]`)
528+
.join(", ");
529+
this.hoverStyle.innerText += `${selector} { background-color: rgba(0, 255, 255, 0.1); }`;
530+
}
531+
532+
this.hoverStyle.innerText += `
533+
#viewer [data-page-number="${this.pageIndex + 1}"] .pdfBugGroupsLayer :nth-child(${groupIndex + 1}) {
534+
background-color: var(--hover-background-color);
535+
outline-style: var(--hover-outline-style);
536+
}
537+
`;
538+
}
539+
400540
getNextBreakPoint() {
401541
this.breakPoints.sort(function (a, b) {
402542
return a - b;

web/pdf_page_view.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,8 @@ class PDFPageView {
536536
keepXfaLayer = false,
537537
keepTextLayer = false,
538538
} = {}) {
539+
const keepPdfBugGroups = this.pdfPage?._pdfBug ?? false;
540+
539541
this.cancelRendering({
540542
keepAnnotationLayer,
541543
keepAnnotationEditorLayer,
@@ -564,6 +566,9 @@ class PDFPageView {
564566
case textLayerNode:
565567
continue;
566568
}
569+
if (keepPdfBugGroups && node.classList.contains("pdfBugGroupsLayer")) {
570+
continue;
571+
}
567572
node.remove();
568573
const layerIndex = this.#layers.indexOf(node);
569574
if (layerIndex >= 0) {

0 commit comments

Comments
 (0)