From e85c30e08a342f0518393ce98ee392c2a4fd7a4c Mon Sep 17 00:00:00 2001 From: calixteman Date: Sun, 15 Mar 2026 22:25:39 +0100 Subject: [PATCH] Add the possibility to skip some ops in the debug view The user has to click in the space before an op to add a breakpoint and click again in order to skip it. --- src/display/canvas.js | 12 ++++++--- web/debugger.mjs | 4 +++ web/internal/draw_ops_view.css | 21 ++++++++++++--- web/internal/draw_ops_view.js | 47 ++++++++++++++++++++++++---------- web/internal/page_view.js | 16 +++++++++--- 5 files changed, 77 insertions(+), 23 deletions(-) diff --git a/src/display/canvas.js b/src/display/canvas.js index eb3a11d14..7cf64d640 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -787,9 +787,15 @@ class CanvasGraphics { let fnId, fnArgs; while (true) { - if (stepper !== undefined && i === stepper.nextBreakPoint) { - stepper.breakIt(i, continueCallback); - return i; + if (stepper !== undefined) { + if (i === stepper.nextBreakPoint) { + stepper.breakIt(i, continueCallback); + return i; + } + if (stepper.shouldSkip(i)) { + i++; + continue; + } } if (!operationsFilter || operationsFilter(i)) { diff --git a/web/debugger.mjs b/web/debugger.mjs index efd36aea4..5953b1791 100644 --- a/web/debugger.mjs +++ b/web/debugger.mjs @@ -659,6 +659,10 @@ class Stepper { this.goTo(idx); } + shouldSkip(idx) { + return false; + } + goTo(idx) { const allRows = this.panel.getElementsByClassName("line"); for (const row of allRows) { diff --git a/web/internal/draw_ops_view.css b/web/internal/draw_ops_view.css index af20c0d94..d412c23b9 100644 --- a/web/internal/draw_ops_view.css +++ b/web/internal/draw_ops_view.css @@ -159,7 +159,7 @@ &::before { content: "●"; color: var(--changed-color); - font-size: 0.75em; + font-size: 0.9em; opacity: 0; } @@ -167,9 +167,17 @@ opacity: 0.4; } - &.active::before { + &[data-bp="pause"]::before { opacity: 1; } + + &[data-bp="skip"]::before { + content: "✕"; + opacity: 1; + } +} +.op-line.op-skipped > :not(.bp-gutter) { + opacity: 0.4; } .op-line.paused { background: var(--paused-bg); @@ -194,9 +202,16 @@ .bp-gutter:hover::before { color: ButtonBorder; } - .bp-gutter.active::before { + .bp-gutter[data-bp="pause"]::before { color: ButtonText; } + .bp-gutter[data-bp="skip"]::before { + color: ButtonText; + } + .op-line.op-skipped > :not(.bp-gutter) { + opacity: 1; + color: GrayText; + } /* Color swatch preserves the actual PDF color value. */ .color-swatch { diff --git a/web/internal/draw_ops_view.js b/web/internal/draw_ops_view.js index c6b1fd25c..5d1d500c2 100644 --- a/web/internal/draw_ops_view.js +++ b/web/internal/draw_ops_view.js @@ -23,6 +23,11 @@ for (const [name, id] of Object.entries(OPS)) { OPS_TO_NAME[id] = name; } +const BreakpointType = { + PAUSE: 0, + SKIP: 1, +}; + // Single hidden color input reused for all swatch pickers. const colorPickerInput = document.createElement("input"); colorPickerInput.type = "color"; @@ -408,7 +413,8 @@ class DrawOpsView { #selectedLine = null; - #breakpoints = new Set(); + // Map + #breakpoints = new Map(); #originalColors = new Map(); @@ -553,27 +559,40 @@ class DrawOpsView { line.ariaSelected = "false"; line.tabIndex = i === 0 ? 0 : -1; - // Breakpoint gutter — click to toggle a red-bullet breakpoint. + // Breakpoint gutter — click cycles: none → pause (●) → skip (✕) → none. const gutter = document.createElement("span"); gutter.className = "bp-gutter"; gutter.role = "checkbox"; gutter.tabIndex = 0; gutter.ariaLabel = "Breakpoint"; - const isInitiallyActive = this.#breakpoints.has(i); - gutter.ariaChecked = String(isInitiallyActive); - if (isInitiallyActive) { - gutter.classList.add("active"); + const initBpType = this.#breakpoints.get(i); + if (initBpType === BreakpointType.PAUSE) { + gutter.dataset.bp = "pause"; + gutter.ariaChecked = "true"; + } else if (initBpType === BreakpointType.SKIP) { + gutter.dataset.bp = "skip"; + gutter.ariaChecked = "mixed"; + line.classList.add("op-skipped"); + } else { + gutter.ariaChecked = "false"; } gutter.addEventListener("click", e => { e.stopPropagation(); - if (this.#breakpoints.has(i)) { - this.#breakpoints.delete(i); - gutter.classList.remove("active"); - gutter.ariaChecked = "false"; - } else { - this.#breakpoints.add(i); - gutter.classList.add("active"); + const current = this.#breakpoints.get(i); + if (current === undefined) { + this.#breakpoints.set(i, BreakpointType.PAUSE); + gutter.dataset.bp = "pause"; gutter.ariaChecked = "true"; + } else if (current === BreakpointType.PAUSE) { + this.#breakpoints.set(i, BreakpointType.SKIP); + gutter.dataset.bp = "skip"; + gutter.ariaChecked = "mixed"; + line.classList.add("op-skipped"); + } else { + this.#breakpoints.delete(i); + delete gutter.dataset.bp; + gutter.ariaChecked = "false"; + line.classList.remove("op-skipped"); } }); gutter.addEventListener("keydown", e => { @@ -656,4 +675,4 @@ class DrawOpsView { } } -export { DrawOpsView }; +export { BreakpointType, DrawOpsView }; diff --git a/web/internal/page_view.js b/web/internal/page_view.js index 5f294e3a8..ad179da92 100644 --- a/web/internal/page_view.js +++ b/web/internal/page_view.js @@ -13,9 +13,9 @@ * limitations under the License. */ +import { BreakpointType, DrawOpsView } from "./draw_ops_view.js"; import { CanvasContextDetailsView } from "./canvas_context_details_view.js"; import { DOMCanvasFactory } from "pdfjs/display/canvas_factory.js"; -import { DrawOpsView } from "./draw_ops_view.js"; import { SplitView } from "./split_view.js"; // Stepper for pausing/stepping through op list rendering. @@ -61,10 +61,20 @@ class ViewerStepper { cb(); } + shouldSkip(i) { + return ( + globalThis.StepperManager._breakpoints.get(i) === BreakpointType.SKIP + ); + } + #findNextAfter(idx) { let next = null; - for (const bp of globalThis.StepperManager._breakpoints) { - if (bp > idx && (next === null || bp < next)) { + for (const [bp, type] of globalThis.StepperManager._breakpoints) { + if ( + type === BreakpointType.PAUSE && + bp > idx && + (next === null || bp < next) + ) { next = bp; } }