Skip to content

Commit

Permalink
Hook up the ops bbox logic to the pdf debugger
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicolo-ribaudo committed Nov 14, 2024
1 parent 54be55a commit 5a6a877
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 3 deletions.
16 changes: 15 additions & 1 deletion src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
NodeStandardFontDataFactory,
} from "display-node_utils";
import { CanvasGraphics } from "./canvas.js";
import { CanvasRecorder } from "./canvas_recorder.js";
import { DOMCanvasFactory } from "./canvas_factory.js";
import { DOMCMapReaderFactory } from "display-cmap_reader_factory";
import { DOMFilterFactory } from "./filter_factory.js";
Expand Down Expand Up @@ -1517,9 +1518,22 @@ class PDFPageProxy {
this._pumpOperatorList(intentArgs);
}

const recordingContext =
this._pdfBug &&
globalThis.StepperManager?.enabled &&
!this._recordedGroups
? new CanvasRecorder(canvasContext)
: null;

const complete = error => {
intentState.renderTasks.delete(internalRenderTask);

if (recordingContext) {
this._recordedGroups =
CanvasRecorder.getFinishedGroups(recordingContext);
internalRenderTask.stepper.setOperatorGroups(this._recordedGroups);
}

// Attempt to reduce memory usage during *printing*, by always running
// cleanup immediately once rendering has finished.
if (this._maybeCleanupAfterRender || intentPrint) {
Expand Down Expand Up @@ -1552,7 +1566,7 @@ class PDFPageProxy {
callback: complete,
// Only include the required properties, and *not* the entire object.
params: {
canvasContext,
canvasContext: recordingContext ?? canvasContext,
viewport,
transform,
background,
Expand Down
31 changes: 31 additions & 0 deletions web/debugger.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,34 @@
background-color: rgb(255 255 255 / 0.6);
color: rgb(0 0 0);
}

.pdfBugGroupsLayer {
position: absolute;
inset: 0;
pointer-events: none;

> * {
position: absolute;
outline-color: red;
outline-width: 2px;

--hover-outline-style: solid !important;
--hover-background-color: rgb(255 0 0 / 0.2);

&:hover {
outline-style: var(--hover-outline-style);
background-color: var(--hover-background-color);
cursor: pointer;
}

.showDebugBoxes & {
outline-style: dashed;
}
}
}

.showDebugBoxes {
.pdfBugGroupsLayer {
pointer-events: all;
}
}
144 changes: 142 additions & 2 deletions web/debugger.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ const StepperManager = (function StepperManagerClosure() {
active: false,
// Stepper specific functions.
create(pageIndex) {
const pageContainer = document.querySelector(
`#viewer div[data-page-number="${pageIndex + 1}"]`
);

const debug = document.createElement("div");
debug.id = "stepper" + pageIndex;
debug.hidden = true;
Expand All @@ -210,7 +214,12 @@ const StepperManager = (function StepperManagerClosure() {
b.value = pageIndex;
stepperChooser.append(b);
const initBreakPoints = breakPoints[pageIndex] || [];
const stepper = new Stepper(debug, pageIndex, initBreakPoints);
const stepper = new Stepper(
debug,
pageIndex,
initBreakPoints,
pageContainer
);
steppers.push(stepper);
if (steppers.length === 1) {
this.selectStepper(pageIndex, false);
Expand Down Expand Up @@ -277,7 +286,7 @@ class Stepper {
return simpleObj;
}

constructor(panel, pageIndex, initialBreakPoints) {
constructor(panel, pageIndex, initialBreakPoints, pageContainer) {
this.panel = panel;
this.breakPoint = 0;
this.nextBreakPoint = null;
Expand All @@ -286,11 +295,20 @@ class Stepper {
this.currentIdx = -1;
this.operatorListIdx = 0;
this.indentLevel = 0;
this.operatorGroups = null;
this.pageContainer = pageContainer;
}

init(operatorList) {
const panel = this.panel;
const content = this.#c("div", "c=continue, s=step");

const showBoxesToggle = this.#c("label", "Show bounding boxes");
const showBoxesCheckbox = this.#c("input");
showBoxesCheckbox.type = "checkbox";
showBoxesToggle.prepend(showBoxesCheckbox);
content.append(this.#c("br"), showBoxesToggle);

const table = this.#c("table");
content.append(table);
table.cellSpacing = 0;
Expand All @@ -305,6 +323,22 @@ class Stepper {
panel.append(content);
this.table = table;
this.updateOperatorList(operatorList);

const hoverStyle = this.#c("style");
this.hoverStyle = hoverStyle;
content.prepend(hoverStyle);
table.addEventListener("mouseover", this.#handleStepHover.bind(this));
table.addEventListener("mouseleave", e => {
hoverStyle.innerText = "";
});

showBoxesCheckbox.addEventListener("change", () => {
if (showBoxesCheckbox.checked) {
this.pageContainer.classList.add("showDebugBoxes");
} else {
this.pageContainer.classList.remove("showDebugBoxes");
}
});
}

updateOperatorList(operatorList) {
Expand Down Expand Up @@ -397,6 +431,112 @@ class Stepper {
this.table.append(chunk);
}

setOperatorGroups(groups) {
this.operatorGroups = groups;

let boxesContainer = this.pageContainer.querySelector(".pdfBugGroupsLayer");
if (!boxesContainer) {
boxesContainer = this.#c("div");
boxesContainer.classList.add("pdfBugGroupsLayer");
this.pageContainer.append(boxesContainer);

boxesContainer.addEventListener(
"click",
this.#handleDebugBoxClick.bind(this)
);
boxesContainer.addEventListener(
"mouseover",
this.#handleDebugBoxHover.bind(this)
);
}
boxesContainer.innerHTML = "";

for (let i = 0; i < groups.length; i++) {
const el = this.#c("div");
el.style.left = `${groups[i].minX * 100}%`;
el.style.top = `${groups[i].minY * 100}%`;
el.style.width = `${(groups[i].maxX - groups[i].minX) * 100}%`;
el.style.height = `${(groups[i].maxY - groups[i].minY) * 100}%`;
el.dataset.groupIdx = i;
boxesContainer.append(el);
}
}

#handleStepHover(e) {
const tr = e.target.closest("tr");
if (!tr || tr.dataset.idx === undefined) {
return;
}

const index = +tr.dataset.idx;

const closestGroupIndex =
this.operatorGroups?.findIndex(({ data }) => {
if ("idx" in data) {
return data.idx === index;
}
if ("startIdx" in data) {
return data.startIdx <= index && index <= data.endIdx;
}
return false;
}) ?? -1;
if (closestGroupIndex === -1) {
this.hoverStyle.innerText = "";
return;
}

this.#highlightStepsGroup(closestGroupIndex);
}

#handleDebugBoxHover(e) {
if (e.target.dataset.groupIdx === undefined) {
return;
}

const groupIdx = Number(e.target.dataset.groupIdx);
this.#highlightStepsGroup(groupIdx);
}

#handleDebugBoxClick(e) {
if (e.target.dataset.groupIdx === undefined) {
return;
}

const groupIdx = Number(e.target.dataset.groupIdx);
const group = this.operatorGroups[groupIdx];

const firstOp = "idx" in group.data ? group.data.idx : group.data.startIdx;

this.table.childNodes[firstOp].scrollIntoView();
}

#highlightStepsGroup(groupIndex) {
const group = this.operatorGroups[groupIndex];

let cssSelector;
if ("idx" in group.data) {
cssSelector = `tr[data-idx="${group.data.idx}"]`;
} else if ("startIdx" in group.data) {
cssSelector = `:nth-child(n+${group.data.startIdx + 1} of tr[data-idx]):nth-child(-n+${group.data.endIdx + 1} of tr[data-idx])`;
}

this.hoverStyle.innerText = `#${this.panel.id} ${cssSelector} { background-color: rgba(0, 0, 0, 0.1); }`;

if (group.data.dependencies) {
const selector = group.data.dependencies
.map(idx => `#${this.panel.id} tr[data-idx="${idx}"]`)
.join(", ");
this.hoverStyle.innerText += `${selector} { background-color: rgba(0, 255, 255, 0.1); }`;
}

this.hoverStyle.innerText += `
#viewer [data-page-number="${this.pageIndex + 1}"] .pdfBugGroupsLayer :nth-child(${groupIndex + 1}) {
background-color: var(--hover-background-color);
outline-style: var(--hover-outline-style);
}
`;
}

getNextBreakPoint() {
this.breakPoints.sort(function (a, b) {
return a - b;
Expand Down
5 changes: 5 additions & 0 deletions web/pdf_page_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,8 @@ class PDFPageView {
keepXfaLayer = false,
keepTextLayer = false,
} = {}) {
const keepPdfBugGroups = this.pdfPage?._pdfBug ?? false;

this.cancelRendering({
keepAnnotationLayer,
keepAnnotationEditorLayer,
Expand Down Expand Up @@ -564,6 +566,9 @@ class PDFPageView {
case textLayerNode:
continue;
}
if (keepPdfBugGroups && node.classList.contains("pdfBugGroupsLayer")) {
continue;
}
node.remove();
const layerIndex = this.#layers.indexOf(node);
if (layerIndex >= 0) {
Expand Down

0 comments on commit 5a6a877

Please sign in to comment.