Merge pull request #20208 from nicolo-ribaudo/bboxes-typedarray
Store ops bboxes in a linear Uint8Array
This commit is contained in:
commit
beb5f5ca85
@ -1242,8 +1242,8 @@ class PDFDocumentProxy {
|
||||
* @property {boolean} [isEditing] - Render the page in editing mode.
|
||||
* @property {boolean} [recordOperations] - Record the dependencies and bounding
|
||||
* boxes of all PDF operations that render onto the canvas.
|
||||
* @property {Set<number>} [filteredOperationIndexes] - If provided, only run
|
||||
* the PDF operations that are included in this set.
|
||||
* @property {(index: number) => boolean} [operationsFilter] - If provided, only
|
||||
* run for which this function returns `true`.
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -1314,7 +1314,7 @@ class PDFPageProxy {
|
||||
|
||||
this._intentStates = new Map();
|
||||
this.destroyed = false;
|
||||
this.recordedGroups = null;
|
||||
this.recordedBBoxes = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1440,7 +1440,7 @@ class PDFPageProxy {
|
||||
printAnnotationStorage = null,
|
||||
isEditing = false,
|
||||
recordOperations = false,
|
||||
filteredOperationIndexes = null,
|
||||
operationsFilter = null,
|
||||
}) {
|
||||
this._stats?.time("Overall");
|
||||
|
||||
@ -1487,23 +1487,28 @@ class PDFPageProxy {
|
||||
this._pumpOperatorList(intentArgs);
|
||||
}
|
||||
|
||||
const recordForDebugger = Boolean(
|
||||
this._pdfBug && globalThis.StepperManager?.enabled
|
||||
);
|
||||
|
||||
const shouldRecordOperations =
|
||||
!this.recordedGroups &&
|
||||
(recordOperations ||
|
||||
(this._pdfBug && globalThis.StepperManager?.enabled));
|
||||
!this.recordedBBoxes && (recordOperations || recordForDebugger);
|
||||
|
||||
const complete = error => {
|
||||
intentState.renderTasks.delete(internalRenderTask);
|
||||
|
||||
if (shouldRecordOperations) {
|
||||
const recordedGroups = internalRenderTask.gfx?.dependencyTracker.take();
|
||||
if (recordedGroups) {
|
||||
internalRenderTask.stepper?.setOperatorGroups(recordedGroups);
|
||||
if (recordOperations) {
|
||||
this.recordedGroups = recordedGroups;
|
||||
const recordedBBoxes = internalRenderTask.gfx?.dependencyTracker.take();
|
||||
if (recordedBBoxes) {
|
||||
if (internalRenderTask.stepper) {
|
||||
internalRenderTask.stepper.setOperatorBBoxes(
|
||||
recordedBBoxes,
|
||||
internalRenderTask.gfx.dependencyTracker.takeDebugMetadata()
|
||||
);
|
||||
}
|
||||
if (recordOperations) {
|
||||
this.recordedBBoxes = recordedBBoxes;
|
||||
}
|
||||
} else if (recordOperations) {
|
||||
this.recordedGroups = [];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1542,7 +1547,11 @@ class PDFPageProxy {
|
||||
canvas,
|
||||
canvasContext,
|
||||
dependencyTracker: shouldRecordOperations
|
||||
? new CanvasDependencyTracker(canvas)
|
||||
? new CanvasDependencyTracker(
|
||||
canvas,
|
||||
intentState.operatorList.length,
|
||||
recordForDebugger
|
||||
)
|
||||
: null,
|
||||
viewport,
|
||||
transform,
|
||||
@ -1559,7 +1568,7 @@ class PDFPageProxy {
|
||||
pdfBug: this._pdfBug,
|
||||
pageColors,
|
||||
enableHWA: this._transport.enableHWA,
|
||||
filteredOperationIndexes,
|
||||
operationsFilter,
|
||||
});
|
||||
|
||||
(intentState.renderTasks ||= new Set()).add(internalRenderTask);
|
||||
@ -3169,7 +3178,7 @@ class InternalRenderTask {
|
||||
pdfBug = false,
|
||||
pageColors = null,
|
||||
enableHWA = false,
|
||||
filteredOperationIndexes = null,
|
||||
operationsFilter = null,
|
||||
}) {
|
||||
this.callback = callback;
|
||||
this.params = params;
|
||||
@ -3201,7 +3210,7 @@ class InternalRenderTask {
|
||||
this._canvasContext = params.canvas ? null : params.canvasContext;
|
||||
this._enableHWA = enableHWA;
|
||||
this._dependencyTracker = params.dependencyTracker;
|
||||
this._filteredOperationIndexes = filteredOperationIndexes;
|
||||
this._operationsFilter = operationsFilter;
|
||||
}
|
||||
|
||||
get completed() {
|
||||
@ -3288,6 +3297,9 @@ class InternalRenderTask {
|
||||
this.graphicsReadyCallback ||= this._continueBound;
|
||||
return;
|
||||
}
|
||||
this.gfx.dependencyTracker?.growOperationsCount(
|
||||
this.operatorList.fnArray.length
|
||||
);
|
||||
this.stepper?.updateOperatorList(this.operatorList);
|
||||
|
||||
if (this.running) {
|
||||
@ -3328,7 +3340,7 @@ class InternalRenderTask {
|
||||
this.operatorListIdx,
|
||||
this._continueBound,
|
||||
this.stepper,
|
||||
this._filteredOperationIndexes
|
||||
this._operationsFilter
|
||||
);
|
||||
if (this.operatorListIdx === this.operatorList.argsArray.length) {
|
||||
this.running = false;
|
||||
|
||||
@ -763,7 +763,7 @@ class CanvasGraphics {
|
||||
executionStartIdx,
|
||||
continueCallback,
|
||||
stepper,
|
||||
filteredOperationIndexes
|
||||
operationsFilter
|
||||
) {
|
||||
const argsArray = operatorList.argsArray;
|
||||
const fnArray = operatorList.fnArray;
|
||||
@ -791,7 +791,7 @@ class CanvasGraphics {
|
||||
return i;
|
||||
}
|
||||
|
||||
if (!filteredOperationIndexes || filteredOperationIndexes.has(i)) {
|
||||
if (!operationsFilter || operationsFilter(i)) {
|
||||
fnId = fnArray[i];
|
||||
// TODO: There is a `undefined` coming from somewhere.
|
||||
fnArgs = argsArray[i] ?? null;
|
||||
@ -1100,7 +1100,7 @@ class CanvasGraphics {
|
||||
-offsetY,
|
||||
]);
|
||||
fillCtx.fillStyle = isPatternFill
|
||||
? fillColor.getPattern(ctx, this, inverse, PathType.FILL)
|
||||
? fillColor.getPattern(ctx, this, inverse, PathType.FILL, opIdx)
|
||||
: fillColor;
|
||||
|
||||
fillCtx.fillRect(0, 0, width, height);
|
||||
@ -1549,7 +1549,8 @@ class CanvasGraphics {
|
||||
ctx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.STROKE
|
||||
PathType.STROKE,
|
||||
opIdx
|
||||
);
|
||||
if (baseTransform) {
|
||||
const newPath = new Path2D();
|
||||
@ -1603,7 +1604,8 @@ class CanvasGraphics {
|
||||
ctx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.FILL
|
||||
PathType.FILL,
|
||||
opIdx
|
||||
);
|
||||
if (baseTransform) {
|
||||
const newPath = new Path2D();
|
||||
@ -1759,7 +1761,7 @@ class CanvasGraphics {
|
||||
setFont(opIdx, fontRefName, size) {
|
||||
this.dependencyTracker
|
||||
?.recordSimpleData("font", opIdx)
|
||||
.recordNamedDependency(opIdx, fontRefName);
|
||||
.recordSimpleDataFromNamed("fontObj", fontRefName, opIdx);
|
||||
const fontObj = this.commonObjs.get(fontRefName);
|
||||
const current = this.current;
|
||||
|
||||
@ -2034,7 +2036,6 @@ class CanvasGraphics {
|
||||
if (this.dependencyTracker) {
|
||||
this.dependencyTracker
|
||||
.recordDependencies(opIdx, Dependencies.showText)
|
||||
.copyDependenciesFromIncrementalOperation(opIdx, "sameLineText")
|
||||
.resetBBox(opIdx);
|
||||
if (this.current.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG) {
|
||||
this.dependencyTracker
|
||||
@ -2047,9 +2048,7 @@ class CanvasGraphics {
|
||||
const font = current.font;
|
||||
if (font.isType3Font) {
|
||||
this.showType3Text(opIdx, glyphs);
|
||||
this.dependencyTracker
|
||||
?.recordOperation(opIdx)
|
||||
.recordIncrementalData("sameLineText", opIdx);
|
||||
this.dependencyTracker?.recordShowTextOperation(opIdx);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -2095,7 +2094,8 @@ class CanvasGraphics {
|
||||
ctx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.FILL
|
||||
PathType.FILL,
|
||||
opIdx
|
||||
);
|
||||
patternFillTransform = getCurrentTransform(ctx);
|
||||
ctx.restore();
|
||||
@ -2108,7 +2108,8 @@ class CanvasGraphics {
|
||||
ctx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.STROKE
|
||||
PathType.STROKE,
|
||||
opIdx
|
||||
);
|
||||
patternStrokeTransform = getCurrentTransform(ctx);
|
||||
ctx.restore();
|
||||
@ -2157,8 +2158,7 @@ class CanvasGraphics {
|
||||
-measure.actualBoundingBoxAscent,
|
||||
measure.actualBoundingBoxDescent
|
||||
)
|
||||
.recordOperation(opIdx)
|
||||
.recordIncrementalData("sameLineText", opIdx);
|
||||
.recordShowTextOperation(opIdx);
|
||||
}
|
||||
current.x += width * widthAdvanceScale * textHScale;
|
||||
ctx.restore();
|
||||
@ -2277,9 +2277,7 @@ class CanvasGraphics {
|
||||
ctx.restore();
|
||||
this.compose();
|
||||
|
||||
this.dependencyTracker
|
||||
?.recordOperation(opIdx)
|
||||
.recordIncrementalData("sameLineText", opIdx);
|
||||
this.dependencyTracker?.recordShowTextOperation(opIdx);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -2351,7 +2349,6 @@ class CanvasGraphics {
|
||||
}
|
||||
ctx.restore();
|
||||
if (dependencyTracker) {
|
||||
this.dependencyTracker.recordNestedDependencies();
|
||||
this.dependencyTracker = dependencyTracker;
|
||||
}
|
||||
}
|
||||
@ -2378,7 +2375,7 @@ class CanvasGraphics {
|
||||
if (IR[0] === "TilingPattern") {
|
||||
const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
|
||||
const canvasGraphicsFactory = {
|
||||
createCanvasGraphics: ctx =>
|
||||
createCanvasGraphics: (ctx, renderingOpIdx) =>
|
||||
new CanvasGraphics(
|
||||
ctx,
|
||||
this.commonObjs,
|
||||
@ -2392,7 +2389,11 @@ class CanvasGraphics {
|
||||
undefined,
|
||||
undefined,
|
||||
this.dependencyTracker
|
||||
? new CanvasNestedDependencyTracker(this.dependencyTracker, opIdx)
|
||||
? new CanvasNestedDependencyTracker(
|
||||
this.dependencyTracker,
|
||||
renderingOpIdx,
|
||||
/* ignoreBBoxes */ true
|
||||
)
|
||||
: null
|
||||
),
|
||||
};
|
||||
@ -2470,7 +2471,8 @@ class CanvasGraphics {
|
||||
ctx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.SHADING
|
||||
PathType.SHADING,
|
||||
opIdx
|
||||
);
|
||||
|
||||
const inv = getCurrentTransformInverse(ctx);
|
||||
@ -2937,7 +2939,8 @@ class CanvasGraphics {
|
||||
maskCtx,
|
||||
this,
|
||||
getCurrentTransformInverse(ctx),
|
||||
PathType.FILL
|
||||
PathType.FILL,
|
||||
opIdx
|
||||
)
|
||||
: fillColor;
|
||||
maskCtx.fillRect(0, 0, width, height);
|
||||
|
||||
@ -2,10 +2,70 @@ import { Util } from "../shared/util.js";
|
||||
|
||||
const FORCED_DEPENDENCY_LABEL = "__forcedDependency";
|
||||
|
||||
const { floor, ceil } = Math;
|
||||
|
||||
function expandBBox(array, index, minX, minY, maxX, maxY) {
|
||||
array[index * 4 + 0] = Math.min(array[index * 4 + 0], minX);
|
||||
array[index * 4 + 1] = Math.min(array[index * 4 + 1], minY);
|
||||
array[index * 4 + 2] = Math.max(array[index * 4 + 2], maxX);
|
||||
array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY);
|
||||
}
|
||||
|
||||
// This is computed rathter than hard-coded to keep into
|
||||
// account the platform's endianess.
|
||||
const EMPTY_BBOX = new Uint32Array(new Uint8Array([255, 255, 0, 0]).buffer)[0];
|
||||
|
||||
class BBoxReader {
|
||||
#bboxes;
|
||||
|
||||
#coords;
|
||||
|
||||
constructor(bboxes, coords) {
|
||||
this.#bboxes = bboxes;
|
||||
this.#coords = coords;
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this.#bboxes.length;
|
||||
}
|
||||
|
||||
isEmpty(i) {
|
||||
return this.#bboxes[i] === EMPTY_BBOX;
|
||||
}
|
||||
|
||||
minX(i) {
|
||||
return this.#coords[i * 4 + 0] / 256;
|
||||
}
|
||||
|
||||
minY(i) {
|
||||
return this.#coords[i * 4 + 1] / 256;
|
||||
}
|
||||
|
||||
maxX(i) {
|
||||
return (this.#coords[i * 4 + 2] + 1) / 256;
|
||||
}
|
||||
|
||||
maxY(i) {
|
||||
return (this.#coords[i * 4 + 3] + 1) / 256;
|
||||
}
|
||||
}
|
||||
|
||||
const ensureDebugMetadata = (map, key) => {
|
||||
if (!map) {
|
||||
return undefined;
|
||||
}
|
||||
let value = map.get(key);
|
||||
if (!value) {
|
||||
value = { dependencies: new Set(), isRenderingOperation: false };
|
||||
map.set(key, value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {"lineWidth" | "lineCap" | "lineJoin" | "miterLimit" | "dash" |
|
||||
* "strokeAlpha" | "fillColor" | "fillAlpha" | "globalCompositeOperation" |
|
||||
* "path" | "filter"} SimpleDependency
|
||||
* "path" | "filter" | "font" | "fontObj"} SimpleDependency
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -54,9 +114,38 @@ class CanvasDependencyTracker {
|
||||
|
||||
#canvasHeight;
|
||||
|
||||
constructor(canvas) {
|
||||
// Uint8ClampedArray<minX, minY, maxX, maxY>
|
||||
#bboxesCoords;
|
||||
|
||||
#bboxes;
|
||||
|
||||
#debugMetadata;
|
||||
|
||||
constructor(canvas, operationsCount, recordDebugMetadata = false) {
|
||||
this.#canvasWidth = canvas.width;
|
||||
this.#canvasHeight = canvas.height;
|
||||
this.#initializeBBoxes(operationsCount);
|
||||
if (recordDebugMetadata) {
|
||||
this.#debugMetadata = new Map();
|
||||
}
|
||||
}
|
||||
|
||||
growOperationsCount(operationsCount) {
|
||||
if (operationsCount >= this.#bboxes.length) {
|
||||
this.#initializeBBoxes(operationsCount, this.#bboxes);
|
||||
}
|
||||
}
|
||||
|
||||
#initializeBBoxes(operationsCount, oldBBoxes) {
|
||||
const buffer = new ArrayBuffer(operationsCount * 4);
|
||||
this.#bboxesCoords = new Uint8ClampedArray(buffer);
|
||||
this.#bboxes = new Uint32Array(buffer);
|
||||
if (oldBBoxes && oldBBoxes.length > 0) {
|
||||
this.#bboxes.set(oldBBoxes);
|
||||
this.#bboxes.fill(EMPTY_BBOX, oldBBoxes.length);
|
||||
} else {
|
||||
this.#bboxes.fill(EMPTY_BBOX);
|
||||
}
|
||||
}
|
||||
|
||||
save(opIdx) {
|
||||
@ -71,7 +160,7 @@ class CanvasDependencyTracker {
|
||||
},
|
||||
};
|
||||
this.#clipBox = { __proto__: this.#clipBox };
|
||||
this.#savesStack.push([opIdx, null]);
|
||||
this.#savesStack.push(opIdx);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -87,9 +176,12 @@ class CanvasDependencyTracker {
|
||||
this.#incremental = Object.getPrototypeOf(this.#incremental);
|
||||
this.#clipBox = Object.getPrototypeOf(this.#clipBox);
|
||||
|
||||
const lastPair = this.#savesStack.pop();
|
||||
if (lastPair !== undefined) {
|
||||
lastPair[1] = opIdx;
|
||||
const lastSave = this.#savesStack.pop();
|
||||
if (lastSave !== undefined) {
|
||||
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
|
||||
lastSave
|
||||
);
|
||||
this.#bboxes[opIdx] = this.#bboxes[lastSave];
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -99,7 +191,7 @@ class CanvasDependencyTracker {
|
||||
* @param {number} idx
|
||||
*/
|
||||
recordOpenMarker(idx) {
|
||||
this.#savesStack.push([idx, null]);
|
||||
this.#savesStack.push(idx);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -107,13 +199,16 @@ class CanvasDependencyTracker {
|
||||
if (this.#savesStack.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return this.#savesStack.at(-1)[0];
|
||||
return this.#savesStack.at(-1);
|
||||
}
|
||||
|
||||
recordCloseMarker(idx) {
|
||||
const lastPair = this.#savesStack.pop();
|
||||
if (lastPair !== undefined) {
|
||||
lastPair[1] = idx;
|
||||
recordCloseMarker(opIdx) {
|
||||
const lastSave = this.#savesStack.pop();
|
||||
if (lastSave !== undefined) {
|
||||
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
|
||||
lastSave
|
||||
);
|
||||
this.#bboxes[opIdx] = this.#bboxes[lastSave];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -121,14 +216,17 @@ class CanvasDependencyTracker {
|
||||
// Marked content needs a separate stack from save/restore, because they
|
||||
// form two independent trees.
|
||||
beginMarkedContent(opIdx) {
|
||||
this.#markedContentStack.push([opIdx, null]);
|
||||
this.#markedContentStack.push(opIdx);
|
||||
return this;
|
||||
}
|
||||
|
||||
endMarkedContent(opIdx) {
|
||||
const lastPair = this.#markedContentStack.pop();
|
||||
if (lastPair !== undefined) {
|
||||
lastPair[1] = opIdx;
|
||||
const lastSave = this.#markedContentStack.pop();
|
||||
if (lastSave !== undefined) {
|
||||
ensureDebugMetadata(this.#debugMetadata, opIdx)?.dependencies.add(
|
||||
lastSave
|
||||
);
|
||||
this.#bboxes[opIdx] = this.#bboxes[lastSave];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -182,6 +280,15 @@ class CanvasDependencyTracker {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SimpleDependency} name
|
||||
* @param {string} depName
|
||||
* @param {number} fallbackIdx
|
||||
*/
|
||||
recordSimpleDataFromNamed(name, depName, fallbackIdx) {
|
||||
this.#simple[name] = this.#namedDependencies.get(depName) ?? fallbackIdx;
|
||||
}
|
||||
|
||||
// All next operations, until the next .restore(), will depend on this
|
||||
recordFutureForcedDependency(name, idx) {
|
||||
this.recordIncrementalData(FORCED_DEPENDENCY_LABEL, idx);
|
||||
@ -207,16 +314,14 @@ class CanvasDependencyTracker {
|
||||
}
|
||||
|
||||
resetBBox(idx) {
|
||||
if (this.#pendingBBoxIdx !== idx) {
|
||||
this.#pendingBBoxIdx = idx;
|
||||
this.#pendingBBox[0] = Infinity;
|
||||
this.#pendingBBox[1] = Infinity;
|
||||
this.#pendingBBox[2] = -Infinity;
|
||||
this.#pendingBBox[3] = -Infinity;
|
||||
return this;
|
||||
}
|
||||
|
||||
get hasPendingBBox() {
|
||||
return this.#pendingBBoxIdx !== -1;
|
||||
return this;
|
||||
}
|
||||
|
||||
recordClipBox(idx, ctx, minX, maxX, minY, maxY) {
|
||||
@ -384,20 +489,6 @@ class CanvasDependencyTracker {
|
||||
return this;
|
||||
}
|
||||
|
||||
copyDependenciesFromIncrementalOperation(idx, name) {
|
||||
const operations = this.#operations;
|
||||
const pendingDependencies = this.#pendingDependencies;
|
||||
for (const depIdx of this.#incremental[name]) {
|
||||
operations
|
||||
.get(depIdx)
|
||||
.dependencies.forEach(
|
||||
pendingDependencies.add,
|
||||
pendingDependencies.add(depIdx)
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
recordNamedDependency(idx, name) {
|
||||
if (this.#namedDependencies.has(name)) {
|
||||
this.#pendingDependencies.add(this.#namedDependencies.get(name));
|
||||
@ -409,38 +500,74 @@ class CanvasDependencyTracker {
|
||||
/**
|
||||
* @param {number} idx
|
||||
*/
|
||||
recordOperation(idx, preserveBbox = false) {
|
||||
recordOperation(idx, preserve = false) {
|
||||
this.recordDependencies(idx, [FORCED_DEPENDENCY_LABEL]);
|
||||
const dependencies = new Set(this.#pendingDependencies);
|
||||
const pairs = this.#savesStack.concat(this.#markedContentStack);
|
||||
const bbox =
|
||||
this.#pendingBBoxIdx === idx
|
||||
? {
|
||||
minX: this.#pendingBBox[0],
|
||||
minY: this.#pendingBBox[1],
|
||||
maxX: this.#pendingBBox[2],
|
||||
maxY: this.#pendingBBox[3],
|
||||
|
||||
if (this.#debugMetadata) {
|
||||
const metadata = ensureDebugMetadata(this.#debugMetadata, idx);
|
||||
const { dependencies } = metadata;
|
||||
this.#pendingDependencies.forEach(dependencies.add, dependencies);
|
||||
this.#savesStack.forEach(dependencies.add, dependencies);
|
||||
this.#markedContentStack.forEach(dependencies.add, dependencies);
|
||||
dependencies.delete(idx);
|
||||
metadata.isRenderingOperation = true;
|
||||
}
|
||||
: null;
|
||||
this.#operations.set(idx, { bbox, pairs, dependencies });
|
||||
if (!preserveBbox) {
|
||||
|
||||
if (this.#pendingBBoxIdx === idx) {
|
||||
const minX = floor((this.#pendingBBox[0] * 256) / this.#canvasWidth);
|
||||
const minY = floor((this.#pendingBBox[1] * 256) / this.#canvasHeight);
|
||||
const maxX = ceil((this.#pendingBBox[2] * 256) / this.#canvasWidth);
|
||||
const maxY = ceil((this.#pendingBBox[3] * 256) / this.#canvasHeight);
|
||||
|
||||
expandBBox(this.#bboxesCoords, idx, minX, minY, maxX, maxY);
|
||||
for (const depIdx of this.#pendingDependencies) {
|
||||
if (depIdx !== idx) {
|
||||
expandBBox(this.#bboxesCoords, depIdx, minX, minY, maxX, maxY);
|
||||
}
|
||||
}
|
||||
for (const saveIdx of this.#savesStack) {
|
||||
if (saveIdx !== idx) {
|
||||
expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY);
|
||||
}
|
||||
}
|
||||
for (const saveIdx of this.#markedContentStack) {
|
||||
if (saveIdx !== idx) {
|
||||
expandBBox(this.#bboxesCoords, saveIdx, minX, minY, maxX, maxY);
|
||||
}
|
||||
}
|
||||
|
||||
if (!preserve) {
|
||||
this.#pendingDependencies.clear();
|
||||
this.#pendingBBoxIdx = -1;
|
||||
}
|
||||
this.#pendingDependencies.clear();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bboxToClipBoxDropOperation(idx) {
|
||||
if (this.#pendingBBoxIdx !== -1) {
|
||||
recordShowTextOperation(idx, preserve = false) {
|
||||
const deps = Array.from(this.#pendingDependencies);
|
||||
this.recordOperation(idx, preserve);
|
||||
this.recordIncrementalData("sameLineText", idx);
|
||||
for (const dep of deps) {
|
||||
this.recordIncrementalData("sameLineText", dep);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
bboxToClipBoxDropOperation(idx, preserve = false) {
|
||||
if (this.#pendingBBoxIdx === idx) {
|
||||
this.#pendingBBoxIdx = -1;
|
||||
|
||||
this.#clipBox[0] = Math.max(this.#clipBox[0], this.#pendingBBox[0]);
|
||||
this.#clipBox[1] = Math.max(this.#clipBox[1], this.#pendingBBox[1]);
|
||||
this.#clipBox[2] = Math.min(this.#clipBox[2], this.#pendingBBox[2]);
|
||||
this.#clipBox[3] = Math.min(this.#clipBox[3], this.#pendingBBox[3]);
|
||||
}
|
||||
|
||||
if (!preserve) {
|
||||
this.#pendingDependencies.clear();
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -464,21 +591,11 @@ class CanvasDependencyTracker {
|
||||
|
||||
take() {
|
||||
this.#fontBBoxTrustworthy.clear();
|
||||
return Array.from(
|
||||
this.#operations,
|
||||
([idx, { bbox, pairs, dependencies }]) => {
|
||||
pairs.forEach(pair => pair.forEach(dependencies.add, dependencies));
|
||||
dependencies.delete(idx);
|
||||
return {
|
||||
minX: (bbox?.minX ?? 0) / this.#canvasWidth,
|
||||
maxX: (bbox?.maxX ?? this.#canvasWidth) / this.#canvasWidth,
|
||||
minY: (bbox?.minY ?? 0) / this.#canvasHeight,
|
||||
maxY: (bbox?.maxY ?? this.#canvasHeight) / this.#canvasHeight,
|
||||
dependencies: Array.from(dependencies).sort((a, b) => a - b),
|
||||
idx,
|
||||
};
|
||||
return new BBoxReader(this.#bboxes, this.#bboxesCoords);
|
||||
}
|
||||
);
|
||||
|
||||
takeDebugMetadata() {
|
||||
return this.#debugMetadata;
|
||||
}
|
||||
}
|
||||
|
||||
@ -496,14 +613,17 @@ class CanvasNestedDependencyTracker {
|
||||
/** @type {number} */
|
||||
#opIdx;
|
||||
|
||||
#nestingLevel = 0;
|
||||
#ignoreBBoxes;
|
||||
|
||||
#outerDependencies;
|
||||
#nestingLevel = 0;
|
||||
|
||||
#savesLevel = 0;
|
||||
|
||||
constructor(dependencyTracker, opIdx) {
|
||||
if (dependencyTracker instanceof CanvasNestedDependencyTracker) {
|
||||
constructor(dependencyTracker, opIdx, ignoreBBoxes) {
|
||||
if (
|
||||
dependencyTracker instanceof CanvasNestedDependencyTracker &&
|
||||
dependencyTracker.#ignoreBBoxes === !!ignoreBBoxes
|
||||
) {
|
||||
// The goal of CanvasNestedDependencyTracker is to collapse all operations
|
||||
// into a single one. If we are already in a
|
||||
// CanvasNestedDependencyTracker, that is already happening.
|
||||
@ -511,8 +631,12 @@ class CanvasNestedDependencyTracker {
|
||||
}
|
||||
|
||||
this.#dependencyTracker = dependencyTracker;
|
||||
this.#outerDependencies = dependencyTracker._takePendingDependencies();
|
||||
this.#opIdx = opIdx;
|
||||
this.#ignoreBBoxes = !!ignoreBBoxes;
|
||||
}
|
||||
|
||||
growOperationsCount() {
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
|
||||
save(opIdx) {
|
||||
@ -595,6 +719,20 @@ class CanvasNestedDependencyTracker {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {SimpleDependency} name
|
||||
* @param {string} depName
|
||||
* @param {number} fallbackIdx
|
||||
*/
|
||||
recordSimpleDataFromNamed(name, depName, fallbackIdx) {
|
||||
this.#dependencyTracker.recordSimpleDataFromNamed(
|
||||
name,
|
||||
depName,
|
||||
this.#opIdx
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
// All next operations, until the next .restore(), will depend on this
|
||||
recordFutureForcedDependency(name, idx) {
|
||||
this.#dependencyTracker.recordFutureForcedDependency(name, this.#opIdx);
|
||||
@ -614,17 +752,14 @@ class CanvasNestedDependencyTracker {
|
||||
}
|
||||
|
||||
resetBBox(idx) {
|
||||
if (!this.#dependencyTracker.hasPendingBBox) {
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.resetBBox(this.#opIdx);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
get hasPendingBBox() {
|
||||
return this.#dependencyTracker.hasPendingBBox;
|
||||
}
|
||||
|
||||
recordClipBox(idx, ctx, minX, maxX, minY, maxY) {
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.recordClipBox(
|
||||
this.#opIdx,
|
||||
ctx,
|
||||
@ -633,10 +768,12 @@ class CanvasNestedDependencyTracker {
|
||||
minY,
|
||||
maxY
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
recordBBox(idx, ctx, minX, maxX, minY, maxY) {
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.recordBBox(
|
||||
this.#opIdx,
|
||||
ctx,
|
||||
@ -645,10 +782,12 @@ class CanvasNestedDependencyTracker {
|
||||
minY,
|
||||
maxY
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
recordCharacterBBox(idx, ctx, font, scale, x, y, getMeasure) {
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.recordCharacterBBox(
|
||||
this.#opIdx,
|
||||
ctx,
|
||||
@ -658,11 +797,14 @@ class CanvasNestedDependencyTracker {
|
||||
y,
|
||||
getMeasure
|
||||
);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
recordFullPageBBox(idx) {
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.recordFullPageBBox(this.#opIdx);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -675,14 +817,6 @@ class CanvasNestedDependencyTracker {
|
||||
return this;
|
||||
}
|
||||
|
||||
copyDependenciesFromIncrementalOperation(idx, name) {
|
||||
this.#dependencyTracker.copyDependenciesFromIncrementalOperation(
|
||||
this.#opIdx,
|
||||
name
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
recordNamedDependency(idx, name) {
|
||||
this.#dependencyTracker.recordNamedDependency(this.#opIdx, name);
|
||||
return this;
|
||||
@ -694,25 +828,26 @@ class CanvasNestedDependencyTracker {
|
||||
*/
|
||||
recordOperation(idx) {
|
||||
this.#dependencyTracker.recordOperation(this.#opIdx, true);
|
||||
const operation = this.#dependencyTracker._extractOperation(this.#opIdx);
|
||||
for (const depIdx of operation.dependencies) {
|
||||
this.#outerDependencies.add(depIdx);
|
||||
return this;
|
||||
}
|
||||
this.#outerDependencies.delete(this.#opIdx);
|
||||
this.#outerDependencies.delete(null);
|
||||
|
||||
recordShowTextOperation(idx) {
|
||||
this.#dependencyTracker.recordShowTextOperation(this.#opIdx, true);
|
||||
return this;
|
||||
}
|
||||
|
||||
bboxToClipBoxDropOperation(idx) {
|
||||
this.#dependencyTracker.bboxToClipBoxDropOperation(this.#opIdx);
|
||||
if (!this.#ignoreBBoxes) {
|
||||
this.#dependencyTracker.bboxToClipBoxDropOperation(this.#opIdx, true);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
recordNestedDependencies() {
|
||||
this.#dependencyTracker._pushPendingDependencies(this.#outerDependencies);
|
||||
take() {
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
|
||||
take() {
|
||||
takeDebugMetadata() {
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
}
|
||||
@ -759,6 +894,7 @@ const Dependencies = {
|
||||
"moveText",
|
||||
"textMatrix",
|
||||
"font",
|
||||
"fontObj",
|
||||
"filter",
|
||||
"fillColor",
|
||||
"textRenderingMode",
|
||||
@ -766,7 +902,8 @@ const Dependencies = {
|
||||
"fillAlpha",
|
||||
"strokeAlpha",
|
||||
"globalCompositeOperation",
|
||||
// TODO: More
|
||||
|
||||
"sameLineText",
|
||||
],
|
||||
transform: ["transform"],
|
||||
transformAndFill: ["transform", "fillColor"],
|
||||
|
||||
@ -478,7 +478,7 @@ class TilingPattern {
|
||||
this.baseTransform = baseTransform;
|
||||
}
|
||||
|
||||
createPatternCanvas(owner) {
|
||||
createPatternCanvas(owner, opIdx) {
|
||||
const {
|
||||
bbox,
|
||||
operatorList,
|
||||
@ -567,7 +567,7 @@ class TilingPattern {
|
||||
dimy.size
|
||||
);
|
||||
const tmpCtx = tmpCanvas.context;
|
||||
const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
|
||||
const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx, opIdx);
|
||||
graphics.groupLevel = owner.groupLevel;
|
||||
|
||||
this.setFillAndStrokeStyleToContext(graphics, paintType, color);
|
||||
@ -600,7 +600,7 @@ class TilingPattern {
|
||||
|
||||
graphics.endDrawing();
|
||||
|
||||
graphics.dependencyTracker?.restore().recordNestedDependencies?.();
|
||||
graphics.dependencyTracker?.restore();
|
||||
tmpCtx.restore();
|
||||
|
||||
if (redrawHorizontally || redrawVertically) {
|
||||
@ -726,7 +726,7 @@ class TilingPattern {
|
||||
return false;
|
||||
}
|
||||
|
||||
getPattern(ctx, owner, inverse, pathType) {
|
||||
getPattern(ctx, owner, inverse, pathType, opIdx) {
|
||||
// PDF spec 8.7.2 NOTE 1: pattern's matrix is relative to initial matrix.
|
||||
let matrix = inverse;
|
||||
if (pathType !== PathType.SHADING) {
|
||||
@ -736,7 +736,7 @@ class TilingPattern {
|
||||
}
|
||||
}
|
||||
|
||||
const temporaryPatternCanvas = this.createPatternCanvas(owner);
|
||||
const temporaryPatternCanvas = this.createPatternCanvas(owner, opIdx);
|
||||
|
||||
let domMatrix = new DOMMatrix(matrix);
|
||||
// Rescale and so that the ctx.createPattern call generates a pattern with
|
||||
|
||||
@ -1117,28 +1117,7 @@ class Driver {
|
||||
const baseline = ctx.canvas.toDataURL("image/png");
|
||||
this._clearCanvas();
|
||||
|
||||
const filteredIndexes = new Set();
|
||||
|
||||
// TODO: This logic is copy-psated from PDFPageDetailView.
|
||||
// We should export it instead, because even though it's
|
||||
// not the core logic of partial rendering it is still
|
||||
// relevant
|
||||
const recordedGroups = page.recordedGroups;
|
||||
for (let i = 0, ii = recordedGroups.length; i < ii; i++) {
|
||||
const group = recordedGroups[i];
|
||||
if (
|
||||
group.minX <= partialCrop.maxX &&
|
||||
group.maxX >= partialCrop.minX &&
|
||||
group.minY <= partialCrop.maxY &&
|
||||
group.maxY >= partialCrop.minY
|
||||
) {
|
||||
filteredIndexes.add(group.idx);
|
||||
group.dependencies.forEach(
|
||||
filteredIndexes.add,
|
||||
filteredIndexes
|
||||
);
|
||||
}
|
||||
}
|
||||
const recordedBBoxes = page.recordedBBoxes;
|
||||
|
||||
const partialRenderContext = {
|
||||
canvasContext: ctx,
|
||||
@ -1149,7 +1128,17 @@ class Driver {
|
||||
pageColors,
|
||||
transform,
|
||||
recordOperations: false,
|
||||
filteredOperationIndexes: filteredIndexes,
|
||||
operationsFilter(index) {
|
||||
if (recordedBBoxes.isEmpty(index)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
recordedBBoxes.minX(index) <= partialCrop.maxX &&
|
||||
recordedBBoxes.maxX(index) >= partialCrop.minX &&
|
||||
recordedBBoxes.minY(index) <= partialCrop.maxY &&
|
||||
recordedBBoxes.maxY(index) >= partialCrop.minY
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const partialRenderTask = page.render(partialRenderContext);
|
||||
|
||||
@ -541,8 +541,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bug1753983",
|
||||
@ -673,8 +672,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bug1473809",
|
||||
@ -780,8 +778,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": "Gives slightly different results in Firefox when running in headless mode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "issue2128",
|
||||
@ -1556,8 +1553,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": "Gives slightly different results in Firefox when running in headless mode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bug1811668",
|
||||
@ -2904,8 +2900,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bug1245391-text",
|
||||
@ -3250,8 +3245,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bug1337429",
|
||||
@ -8080,8 +8074,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": "Half pixel rendering at the edge of the image"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "issue16454",
|
||||
@ -11270,8 +11263,7 @@
|
||||
"maxX": 0.5,
|
||||
"minY": 0.25,
|
||||
"maxY": 0.5
|
||||
},
|
||||
"knownPartialMismatch": "One-bit difference in two pixels at the edge"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "issue14724",
|
||||
|
||||
@ -44,7 +44,7 @@ class BasePDFPageView {
|
||||
|
||||
pageColors = null;
|
||||
|
||||
recordedGroups = null;
|
||||
recordedBBoxes = null;
|
||||
|
||||
renderingQueue = null;
|
||||
|
||||
@ -235,7 +235,7 @@ class BasePDFPageView {
|
||||
if (renderTask === this.renderTask) {
|
||||
this.renderTask = null;
|
||||
if (this.enableOptimizedPartialRendering) {
|
||||
this.recordedGroups ??= renderTask.recordedGroups;
|
||||
this.recordedBBoxes ??= renderTask.recordedBBoxes;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
151
web/debugger.mjs
151
web/debugger.mjs
@ -295,7 +295,7 @@ class Stepper {
|
||||
this.currentIdx = -1;
|
||||
this.operatorListIdx = 0;
|
||||
this.indentLevel = 0;
|
||||
this.operatorGroups = null;
|
||||
this.operatorsGroups = null;
|
||||
this.pageContainer = pageContainer;
|
||||
}
|
||||
|
||||
@ -439,7 +439,7 @@ class Stepper {
|
||||
this.table.append(chunk);
|
||||
}
|
||||
|
||||
setOperatorGroups(groups) {
|
||||
setOperatorBBoxes(bboxes, metadata) {
|
||||
let boxesContainer = this.pageContainer.querySelector(".pdfBugGroupsLayer");
|
||||
if (!boxesContainer) {
|
||||
boxesContainer = this.#c("div");
|
||||
@ -457,12 +457,55 @@ class Stepper {
|
||||
}
|
||||
boxesContainer.innerHTML = "";
|
||||
|
||||
groups = groups.toSorted((a, b) => {
|
||||
const dependents = new Map();
|
||||
for (const [dependentIdx, { dependencies: ownDependencies }] of metadata) {
|
||||
for (const dependencyIdx of ownDependencies) {
|
||||
let ownDependents = dependents.get(dependencyIdx);
|
||||
if (!ownDependents) {
|
||||
dependents.set(dependencyIdx, (ownDependents = new Set()));
|
||||
}
|
||||
ownDependents.add(dependentIdx);
|
||||
}
|
||||
}
|
||||
|
||||
const groups = Array.from({ length: bboxes.length }, (_, i) => {
|
||||
let minX = null;
|
||||
let minY = null;
|
||||
let maxX = null;
|
||||
let maxY = null;
|
||||
if (!bboxes.isEmpty(i)) {
|
||||
minX = bboxes.minX(i);
|
||||
minY = bboxes.minY(i);
|
||||
maxX = bboxes.maxX(i);
|
||||
maxY = bboxes.maxY(i);
|
||||
}
|
||||
|
||||
return {
|
||||
minX,
|
||||
minY,
|
||||
maxX,
|
||||
maxY,
|
||||
dependencies: Array.from(metadata.get(i)?.dependencies ?? []).sort(),
|
||||
dependents: Array.from(dependents.get(i) ?? []).sort(),
|
||||
isRenderingOperation: metadata.get(i)?.isRenderingOperation ?? false,
|
||||
idx: i,
|
||||
};
|
||||
});
|
||||
this.operatorsGroups = groups;
|
||||
|
||||
const operatorsGroupsByZindex = groups.toSorted((a, b) => {
|
||||
if (a.minX === null) {
|
||||
return b.minX === null ? 0 : 1;
|
||||
}
|
||||
if (b.minX === null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const diffs = [
|
||||
a.minX - b.minX,
|
||||
a.minY - b.minY,
|
||||
b.maxX - a.maxX,
|
||||
a.minX - b.minX,
|
||||
b.maxY - a.maxY,
|
||||
b.maxX - a.maxX,
|
||||
];
|
||||
for (const diff of diffs) {
|
||||
if (Math.abs(diff) > 0.01) {
|
||||
@ -476,18 +519,20 @@ class Stepper {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
this.operatorGroups = groups;
|
||||
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
for (let i = 0; i < operatorsGroupsByZindex.length; i++) {
|
||||
const group = operatorsGroupsByZindex[i];
|
||||
if (group.minX !== null) {
|
||||
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;
|
||||
el.style.left = `${group.minX * 100}%`;
|
||||
el.style.top = `${group.minY * 100}%`;
|
||||
el.style.width = `${(group.maxX - group.minX) * 100}%`;
|
||||
el.style.height = `${(group.maxY - group.minY) * 100}%`;
|
||||
el.dataset.idx = group.idx;
|
||||
boxesContainer.append(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#handleStepHover(e) {
|
||||
const tr = e.target.closest("tr");
|
||||
@ -496,51 +541,85 @@ class Stepper {
|
||||
}
|
||||
|
||||
const index = +tr.dataset.idx;
|
||||
|
||||
const closestGroupIndex =
|
||||
this.operatorGroups?.findIndex(({ idx }) => idx === index) ?? -1;
|
||||
if (closestGroupIndex === -1) {
|
||||
this.hoverStyle.innerText = "";
|
||||
return;
|
||||
}
|
||||
|
||||
this.#highlightStepsGroup(closestGroupIndex);
|
||||
this.#highlightStepsGroup(index);
|
||||
}
|
||||
|
||||
#handleDebugBoxHover(e) {
|
||||
if (e.target.dataset.groupIdx === undefined) {
|
||||
if (e.target.dataset.idx === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupIdx = Number(e.target.dataset.groupIdx);
|
||||
this.#highlightStepsGroup(groupIdx);
|
||||
const idx = Number(e.target.dataset.idx);
|
||||
this.#highlightStepsGroup(idx);
|
||||
}
|
||||
|
||||
#handleDebugBoxClick(e) {
|
||||
if (e.target.dataset.groupIdx === undefined) {
|
||||
if (e.target.dataset.idx === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const groupIdx = Number(e.target.dataset.groupIdx);
|
||||
const group = this.operatorGroups[groupIdx];
|
||||
const idx = Number(e.target.dataset.idx);
|
||||
|
||||
this.table.childNodes[group.idx].scrollIntoView();
|
||||
this.table.childNodes[idx].scrollIntoView();
|
||||
}
|
||||
|
||||
#highlightStepsGroup(groupIndex) {
|
||||
const group = this.operatorGroups[groupIndex];
|
||||
#highlightStepsGroup(index) {
|
||||
const group = this.operatorsGroups?.[index];
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hoverStyle.innerText = `#${this.panel.id} tr[data-idx="${group.idx}"] { background-color: rgba(0, 0, 0, 0.1); }`;
|
||||
const renderingColor = `rgba(0, 0, 0, 0.1)`;
|
||||
const dependencyColor = `rgba(0, 255, 255, 0.1)`;
|
||||
const dependentColor = `rgba(255, 0, 0, 0.1)`;
|
||||
|
||||
const solid = color => `background-color: ${color}`;
|
||||
// Used for operations that have an empty bounding box
|
||||
const striped = color => `
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
white,
|
||||
white 10px,
|
||||
${color} 10px,
|
||||
${color} 20px
|
||||
)
|
||||
`;
|
||||
|
||||
const select = idx => `#${this.panel.id} tr[data-idx="${idx}"]`;
|
||||
const selectN = idxs =>
|
||||
idxs.length === 0 ? "q:not(q)" : idxs.map(select).join(", ");
|
||||
|
||||
const isEmpty = idx =>
|
||||
!this.operatorsGroups[idx] || this.operatorsGroups[idx].minX === null;
|
||||
|
||||
const selfColor = group.isRenderingOperation
|
||||
? renderingColor
|
||||
: dependentColor;
|
||||
|
||||
this.hoverStyle.innerText = `${select(index)} {
|
||||
${group.minX === null ? striped(selfColor) : solid(selfColor)}
|
||||
}`;
|
||||
if (group.dependencies.length > 0) {
|
||||
const selector = group.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 += `
|
||||
${selectN(group.dependencies.filter(idx => !isEmpty(idx)))} {
|
||||
${solid(dependencyColor)}
|
||||
}
|
||||
${selectN(group.dependencies.filter(isEmpty))} {
|
||||
${striped(dependencyColor)}
|
||||
}`;
|
||||
}
|
||||
if (group.dependents.length > 0) {
|
||||
this.hoverStyle.innerText += `
|
||||
${selectN(group.dependents.filter(idx => !isEmpty(idx)))} {
|
||||
${solid(dependentColor)}
|
||||
}
|
||||
${selectN(group.dependents.filter(isEmpty))} {
|
||||
${striped(dependentColor)}
|
||||
}`;
|
||||
}
|
||||
|
||||
this.hoverStyle.innerText += `
|
||||
#viewer [data-page-number="${this.pageIndex + 1}"] .pdfBugGroupsLayer :nth-child(${groupIndex + 1}) {
|
||||
#viewer [data-page-number="${this.pageIndex + 1}"] .pdfBugGroupsLayer [data-idx="${index}"] {
|
||||
background-color: var(--hover-background-color);
|
||||
outline-style: var(--hover-outline-style);
|
||||
}
|
||||
|
||||
@ -188,18 +188,12 @@ class PDFPageDetailView extends BasePDFPageView {
|
||||
|
||||
_getRenderingContext(canvas, transform) {
|
||||
const baseContext = this.pageView._getRenderingContext(canvas, transform);
|
||||
const recordedGroups = this.pdfPage.recordedGroups;
|
||||
const recordedBBoxes = this.pdfPage.recordedBBoxes;
|
||||
|
||||
if (!recordedGroups || !this.enableOptimizedPartialRendering) {
|
||||
if (!recordedBBoxes || !this.enableOptimizedPartialRendering) {
|
||||
return { ...baseContext, recordOperations: false };
|
||||
}
|
||||
|
||||
// TODO: There is probably a better data structure for this.
|
||||
// The indexes are always checked in increasing order, so we can just try
|
||||
// to build a pre-sorted array which should have faster lookups.
|
||||
// Needs benchmarking.
|
||||
const filteredIndexes = new Set();
|
||||
|
||||
const {
|
||||
viewport: { width: vWidth, height: vHeight },
|
||||
} = this.pageView;
|
||||
@ -215,23 +209,20 @@ class PDFPageDetailView extends BasePDFPageView {
|
||||
const detailMaxX = (aMinX + aWidth) / vWidth;
|
||||
const detailMaxY = (aMinY + aHeight) / vHeight;
|
||||
|
||||
for (let i = 0, ii = recordedGroups.length; i < ii; i++) {
|
||||
const group = recordedGroups[i];
|
||||
if (
|
||||
group.minX <= detailMaxX &&
|
||||
group.maxX >= detailMinX &&
|
||||
group.minY <= detailMaxY &&
|
||||
group.maxY >= detailMinY
|
||||
) {
|
||||
filteredIndexes.add(group.idx);
|
||||
group.dependencies.forEach(filteredIndexes.add, filteredIndexes);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...baseContext,
|
||||
recordOperations: false,
|
||||
filteredOperationIndexes: filteredIndexes,
|
||||
operationsFilter(index) {
|
||||
if (recordedBBoxes.isEmpty(index)) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
recordedBBoxes.minX(index) <= detailMaxX &&
|
||||
recordedBBoxes.maxX(index) >= detailMinX &&
|
||||
recordedBBoxes.minY(index) <= detailMaxY &&
|
||||
recordedBBoxes.maxY(index) >= detailMinY
|
||||
);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -940,7 +940,7 @@ class PDFPageView extends BasePDFPageView {
|
||||
pageColors: this.pageColors,
|
||||
isEditing: this.#isEditing,
|
||||
recordOperations:
|
||||
this.enableOptimizedPartialRendering && !this.recordedGroups,
|
||||
this.enableOptimizedPartialRendering && !this.recordedBBoxes,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user