added ranges to operators
Some checks failed
CI / Test (20) (push) Has been cancelled
CI / Test (22) (push) Has been cancelled
CI / Test (23) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Lint / Lint (lts/*) (push) Has been cancelled
Types tests / Test (lts/*) (push) Has been cancelled

This commit is contained in:
Kilian Schuettler 2025-03-16 19:08:20 +01:00
parent 88a38b6c5b
commit 2305c6416e
9 changed files with 248 additions and 83 deletions

View File

@ -24,8 +24,9 @@ const loadingTask = getDocument({
try { try {
const pdfDocument = await loadingTask.promise; const pdfDocument = await loadingTask.promise;
console.log("# PDF document loaded."); console.log("# PDF document loaded.");
const table = await pdfDocument.getStreamAsString("/Page2/Contents/0/Data"); const page = await pdfDocument.getPage(1);
console.log(table); const opList = await page.getOperatorList();
console.log(opList);
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@ -1,9 +1,13 @@
import { EvaluatorPreprocessor } from "../../src/core/evaluator.js";
import { import {
getImageAsBlob, getImageAsBlob,
getPrim, getPrim,
getPrimitive,
getPrimTree, getPrimTree,
getStreamAsString, getStreamAsString,
} from "../../src/core/obj_walker.js"; } from "../../src/core/obj_walker.js";
import { Lexer, Parser } from "../../src/core/parser.js";
import { EOF } from "../../src/core/primitives.js";
import fs from "fs"; import fs from "fs";
import { PDFDocument } from "../../src/core/document.js"; import { PDFDocument } from "../../src/core/document.js";
import { Stream } from "../../src/core/stream.js"; import { Stream } from "../../src/core/stream.js";
@ -32,12 +36,58 @@ fs.readFile(filePath, (err, data) => {
} }
}); });
function bytesToString(bytes) {
let string = "";
for (let i = 0; i < bytes.length; i++) {
string += String.fromCharCode(bytes[i]);
}
return string;
}
async function parse(doc) { async function parse(doc) {
const path = "/Page2/Contents/2";
let [stream] = await getPrimitive(path, doc);
const lexer = new Lexer(stream);
const parser = new Parser({ lexer, xref: doc.xref, trackRanges: true });
const objs = [];
let [obj, start, end] = parser.getObjWithRange();
while (obj !== EOF) {
if (obj.cmd) {
objs.push([obj.cmd, start, end]);
} else {
objs.push([obj, start, end]);
}
[obj, start, end] = parser.getObjWithRange();
}
[stream] = await getPrimitive(path, doc);
const bytes = stream.getBytes();
const classes = new Set();
for (const o of objs) {
console.log(o[0].constructor.name);
classes.add(o[0].constructor.name);
const lexemmeBytes = bytes.slice(o[1], o[2]);
console.log(bytesToString(lexemmeBytes));
}
console.log("unique classes", classes);
[stream] = await getPrimitive(path, doc);
const preprocessor = new EvaluatorPreprocessor(stream, doc.xref);
const operation = {};
operation.args = null;
while (preprocessor.read(operation)) {
const args = operation.args;
const fn = operation.fn;
const range = operation.range;
const op = bytesToString(bytes.slice(range[0], range[1]));
console.log(args, fn);
console.log(`${range[0]}------------------------------------`);
console.log(op);
console.log(`${range[1]}------------------------------------`);
}
// console.time("xref"); // console.time("xref");
// let table = await retrieveXref(doc); // let table = await retrieveXref(doc);
// console.timeEnd("xref"); // console.timeEnd("xref");
// console.time("get prim"); // console.time("get prim");
// const prim = await getPrim("/Page2/Resources/XObject/Im0/Length", doc); // const prim = await getPrim("/Trailer/Root/Names/Dests/Names/1", doc);
// console.timeEnd("get prim"); // console.timeEnd("get prim");
// console.log(prim); // console.log(prim);
// const request = { // const request = {
@ -55,20 +105,40 @@ async function parse(doc) {
// const tree = await getPrimTree([request], doc); // const tree = await getPrimTree([request], doc);
// console.timeEnd("get tree"); // console.timeEnd("get tree");
// logTree(tree); // logTree(tree);
console.time("string"); // console.time("string");
const string = await getStreamAsString("/Page2/Contents/0/Data", doc); // const string = await getStreamAsString("/Page2/Contents/0/Data", doc);
console.timeEnd("string"); // console.timeEnd("string");
console.log(string); // console.log(string);
// const handler = { send: undefined };
// console.time("image"); // console.time("image");
// // const image = await getStreamAsImage("/Page2/Contents/2/Data", doc); // // const image = await getStreamAsImage("/Page2/Contents/2/Data", doc);
// const image = await getImageAsBlob( // const blob = await getImageAsBlob(
// "/Page2/Resources/XObject/Im0/Data", // "/Page2/Resources/XObject/Im0/Data",
// doc, // doc
// handler
// ); // );
// console.timeEnd("image"); // console.timeEnd("image");
// console.log(image); // console.log(blob);
// saveBlobToFile(blob, "image.png");
}
async function saveBlobToFile(blob, imagePath) {
const buffer = await blobToBuffer(blob);
fs.writeFile(imagePath, buffer, err => {
if (err) {
console.error("Error saving file:", err);
} else {
console.log("File saved successfully!");
}
});
}
function blobToBuffer(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(Buffer.from(reader.result));
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
} }
function logTree(tree) { function logTree(tree) {

View File

@ -177,11 +177,17 @@ function normalizeBlendMode(value, parsingArray = false) {
return "source-over"; return "source-over";
} }
function addLocallyCachedImageOps(opList, data) { function addLocallyCachedImageOps(opList, data, range) {
if (data.objId) { if (data.objId) {
opList.addDependency(data.objId); opList.addDependency(data.objId);
} }
opList.addImageOps(data.fn, data.args, data.optionalContent, data.hasMask); opList.addImageOps(
data.fn,
data.args,
range,
data.optionalContent,
data.hasMask
);
if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) { if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
data.args[0].count++; data.args[0].count++;
@ -455,7 +461,8 @@ class PartialEvaluator {
operatorList, operatorList,
task, task,
initialState, initialState,
localColorSpaceCache localColorSpaceCache,
range = null
) { ) {
const dict = xobj.dict; const dict = xobj.dict;
const matrix = lookupMatrix(dict.getArray("Matrix"), null); const matrix = lookupMatrix(dict.getArray("Matrix"), null);
@ -469,7 +476,11 @@ class PartialEvaluator {
); );
} }
if (optionalContent !== undefined) { if (optionalContent !== undefined) {
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); operatorList.addOp(
OPS.beginMarkedContentProps,
["OC", optionalContent],
range
);
} }
const group = dict.get("Group"); const group = dict.get("Group");
if (group) { if (group) {
@ -511,14 +522,14 @@ class PartialEvaluator {
smask.backdrop = colorSpace.getRgb(smask.backdrop, 0); smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
} }
operatorList.addOp(OPS.beginGroup, [groupOptions]); operatorList.addOp(OPS.beginGroup, [groupOptions], range);
} }
// If it's a group, a new canvas will be created that is the size of the // If it's a group, a new canvas will be created that is the size of the
// bounding box and translated to the correct position so we don't need to // bounding box and translated to the correct position so we don't need to
// apply the bounding box to it. // apply the bounding box to it.
const args = group ? [matrix, null] : [matrix, bbox]; const args = group ? [matrix, null] : [matrix, bbox];
operatorList.addOp(OPS.paintFormXObjectBegin, args); operatorList.addOp(OPS.paintFormXObjectBegin, args, range);
await this.getOperatorList({ await this.getOperatorList({
stream: xobj, stream: xobj,
@ -527,14 +538,14 @@ class PartialEvaluator {
operatorList, operatorList,
initialState, initialState,
}); });
operatorList.addOp(OPS.paintFormXObjectEnd, []); operatorList.addOp(OPS.paintFormXObjectEnd, [], range);
if (group) { if (group) {
operatorList.addOp(OPS.endGroup, [groupOptions]); operatorList.addOp(OPS.endGroup, [groupOptions], range);
} }
if (optionalContent !== undefined) { if (optionalContent !== undefined) {
operatorList.addOp(OPS.endMarkedContent, []); operatorList.addOp(OPS.endMarkedContent, [], range);
} }
} }
@ -569,6 +580,7 @@ class PartialEvaluator {
cacheKey, cacheKey,
localImageCache, localImageCache,
localColorSpaceCache, localColorSpaceCache,
range,
}) { }) {
const dict = image.dict; const dict = image.dict;
const imageRef = dict.objId; const imageRef = dict.objId;
@ -627,6 +639,7 @@ class PartialEvaluator {
operatorList.addImageOps( operatorList.addImageOps(
OPS.paintImageMaskXObject, OPS.paintImageMaskXObject,
args, args,
range,
optionalContent optionalContent
); );
@ -899,7 +912,8 @@ class PartialEvaluator {
operatorList, operatorList,
task, task,
stateManager, stateManager,
localColorSpaceCache localColorSpaceCache,
range
) { ) {
const smaskContent = smask.get("G"); const smaskContent = smask.get("G");
const smaskOptions = { const smaskOptions = {
@ -929,7 +943,8 @@ class PartialEvaluator {
operatorList, operatorList,
task, task,
stateManager.state.clone(), stateManager.state.clone(),
localColorSpaceCache localColorSpaceCache,
range
); );
} }
@ -1013,7 +1028,7 @@ class PartialEvaluator {
// Add the dependencies to the parent operator list so they are // Add the dependencies to the parent operator list so they are
// resolved before the sub operator list is executed synchronously. // resolved before the sub operator list is executed synchronously.
operatorList.addDependencies(tilingOpList.dependencies); operatorList.addDependencies(tilingOpList.dependencies);
operatorList.addOp(fn, tilingPatternIR); operatorList.addOp(fn, tilingPatternIR, range);
if (patternDict.objId) { if (patternDict.objId) {
localTilingPatternCache.set(/* name = */ null, patternDict.objId, { localTilingPatternCache.set(/* name = */ null, patternDict.objId, {
@ -1124,6 +1139,7 @@ class PartialEvaluator {
stateManager, stateManager,
localGStateCache, localGStateCache,
localColorSpaceCache, localColorSpaceCache,
range,
}) { }) {
const gStateRef = gState.objId; const gStateRef = gState.objId;
let isSimpleGState = true; let isSimpleGState = true;
@ -1180,7 +1196,8 @@ class PartialEvaluator {
operatorList, operatorList,
task, task,
stateManager, stateManager,
localColorSpaceCache localColorSpaceCache,
range
) )
); );
gStateObj.push([key, true]); gStateObj.push([key, true]);
@ -1218,7 +1235,7 @@ class PartialEvaluator {
await promise; await promise;
if (gStateObj.length > 0) { if (gStateObj.length > 0) {
operatorList.addOp(OPS.setGState, [gStateObj]); operatorList.addOp(OPS.setGState, [gStateObj], range);
} }
if (isSimpleGState) { if (isSimpleGState) {
@ -1387,7 +1404,7 @@ class PartialEvaluator {
return promise; return promise;
} }
buildPath(operatorList, fn, args, parsingText = false) { buildPath(operatorList, fn, args, parsingText = false, range = null) {
const lastIndex = operatorList.length - 1; const lastIndex = operatorList.length - 1;
if (!args) { if (!args) {
args = []; args = [];
@ -1405,7 +1422,7 @@ class PartialEvaluator {
// *extremely* rare that shouldn't really matter much in practice. // *extremely* rare that shouldn't really matter much in practice.
if (parsingText) { if (parsingText) {
warn(`Encountered path operator "${fn}" inside of a text object.`); warn(`Encountered path operator "${fn}" inside of a text object.`);
operatorList.addOp(OPS.save, null); operatorList.addOp(OPS.save, null, range);
} }
let minMax; let minMax;
@ -1428,10 +1445,10 @@ class PartialEvaluator {
minMax = [Infinity, Infinity, -Infinity, -Infinity]; minMax = [Infinity, Infinity, -Infinity, -Infinity];
break; break;
} }
operatorList.addOp(OPS.constructPath, [[fn], args, minMax]); operatorList.addOp(OPS.constructPath, [[fn], args, minMax], range);
if (parsingText) { if (parsingText) {
operatorList.addOp(OPS.restore, null); operatorList.addOp(OPS.restore, null, range);
} }
} else { } else {
const opArgs = operatorList.argsArray[lastIndex]; const opArgs = operatorList.argsArray[lastIndex];
@ -1439,6 +1456,9 @@ class PartialEvaluator {
opArgs[1].push(...args); opArgs[1].push(...args);
const minMax = opArgs[2]; const minMax = opArgs[2];
const opRange = operatorList.rangeArray[lastIndex];
opRange[1] = range[1];
// Compute min/max in the worker instead of the main thread. // Compute min/max in the worker instead of the main thread.
// If the current matrix (when drawing) is a scaling one // If the current matrix (when drawing) is a scaling one
// then min/max can be easily computed in using those values. // then min/max can be easily computed in using those values.
@ -1543,7 +1563,8 @@ class PartialEvaluator {
task, task,
localColorSpaceCache, localColorSpaceCache,
localTilingPatternCache, localTilingPatternCache,
localShadingPatternCache localShadingPatternCache,
range
) { ) {
// compile tiling patterns // compile tiling patterns
const patternName = args.pop(); const patternName = args.pop();
@ -1562,7 +1583,7 @@ class PartialEvaluator {
localTilingPattern.dict, localTilingPattern.dict,
color color
); );
operatorList.addOp(fn, tilingPatternIR); operatorList.addOp(fn, tilingPatternIR, range);
return undefined; return undefined;
} catch { } catch {
// Handle any errors during normal TilingPattern parsing. // Handle any errors during normal TilingPattern parsing.
@ -1596,7 +1617,7 @@ class PartialEvaluator {
}); });
if (objId) { if (objId) {
const matrix = lookupMatrix(dict.getArray("Matrix"), null); const matrix = lookupMatrix(dict.getArray("Matrix"), null);
operatorList.addOp(fn, ["Shading", objId, matrix]); operatorList.addOp(fn, ["Shading", objId, matrix], range);
} }
return undefined; return undefined;
} }
@ -1771,6 +1792,7 @@ class PartialEvaluator {
} }
let args = operation.args; let args = operation.args;
let fn = operation.fn; let fn = operation.fn;
const range = operation.range;
switch (fn | 0) { switch (fn | 0) {
case OPS.paintXObject: case OPS.paintXObject:
@ -1781,7 +1803,7 @@ class PartialEvaluator {
if (isValidName) { if (isValidName) {
const localImage = localImageCache.getByName(name); const localImage = localImageCache.getByName(name);
if (localImage) { if (localImage) {
addLocallyCachedImageOps(operatorList, localImage); addLocallyCachedImageOps(operatorList, localImage, range);
args = null; args = null;
continue; continue;
} }
@ -1859,6 +1881,7 @@ class PartialEvaluator {
cacheKey: name, cacheKey: name,
localImageCache, localImageCache,
localColorSpaceCache, localColorSpaceCache,
range,
}) })
.then(resolveXObject, rejectXObject); .then(resolveXObject, rejectXObject);
return; return;
@ -1900,7 +1923,11 @@ class PartialEvaluator {
) )
.then(function (loadedName) { .then(function (loadedName) {
operatorList.addDependency(loadedName); operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]); operatorList.addOp(
OPS.setFont,
[loadedName, fontSize],
range
);
}) })
); );
return; return;
@ -1929,6 +1956,7 @@ class PartialEvaluator {
cacheKey, cacheKey,
localImageCache, localImageCache,
localColorSpaceCache, localColorSpaceCache,
range,
}) })
); );
return; return;
@ -1961,7 +1989,7 @@ class PartialEvaluator {
self.ensureStateFont(stateManager.state); self.ensureStateFont(stateManager.state);
continue; continue;
} }
operatorList.addOp(OPS.nextLine); operatorList.addOp(OPS.nextLine, null, range);
args[0] = self.handleText(args[0], stateManager.state); args[0] = self.handleText(args[0], stateManager.state);
fn = OPS.showText; fn = OPS.showText;
break; break;
@ -1970,9 +1998,9 @@ class PartialEvaluator {
self.ensureStateFont(stateManager.state); self.ensureStateFont(stateManager.state);
continue; continue;
} }
operatorList.addOp(OPS.nextLine); operatorList.addOp(OPS.nextLine, null, range);
operatorList.addOp(OPS.setWordSpacing, [args.shift()]); operatorList.addOp(OPS.setWordSpacing, [args.shift()], range);
operatorList.addOp(OPS.setCharSpacing, [args.shift()]); operatorList.addOp(OPS.setCharSpacing, [args.shift()], range);
args[0] = self.handleText(args[0], stateManager.state); args[0] = self.handleText(args[0], stateManager.state);
fn = OPS.showText; fn = OPS.showText;
break; break;
@ -2092,7 +2120,8 @@ class PartialEvaluator {
task, task,
localColorSpaceCache, localColorSpaceCache,
localTilingPatternCache, localTilingPatternCache,
localShadingPatternCache localShadingPatternCache,
range
) )
); );
return; return;
@ -2124,7 +2153,8 @@ class PartialEvaluator {
task, task,
localColorSpaceCache, localColorSpaceCache,
localTilingPatternCache, localTilingPatternCache,
localShadingPatternCache localShadingPatternCache,
range
) )
); );
return; return;
@ -2175,7 +2205,7 @@ class PartialEvaluator {
const localGStateObj = localGStateCache.getByName(name); const localGStateObj = localGStateCache.getByName(name);
if (localGStateObj) { if (localGStateObj) {
if (localGStateObj.length > 0) { if (localGStateObj.length > 0) {
operatorList.addOp(OPS.setGState, [localGStateObj]); operatorList.addOp(OPS.setGState, [localGStateObj], range);
} }
args = null; args = null;
continue; continue;
@ -2211,6 +2241,7 @@ class PartialEvaluator {
stateManager, stateManager,
localGStateCache, localGStateCache,
localColorSpaceCache, localColorSpaceCache,
range,
}) })
.then(resolveGState, rejectGState); .then(resolveGState, rejectGState);
}).catch(function (reason) { }).catch(function (reason) {
@ -2232,7 +2263,7 @@ class PartialEvaluator {
case OPS.curveTo3: case OPS.curveTo3:
case OPS.closePath: case OPS.closePath:
case OPS.rectangle: case OPS.rectangle:
self.buildPath(operatorList, fn, args, parsingText); self.buildPath(operatorList, fn, args, parsingText, range);
continue; continue;
case OPS.markPoint: case OPS.markPoint:
case OPS.markPointProps: case OPS.markPointProps:
@ -2248,7 +2279,11 @@ class PartialEvaluator {
case OPS.beginMarkedContentProps: case OPS.beginMarkedContentProps:
if (!(args[0] instanceof Name)) { if (!(args[0] instanceof Name)) {
warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`); warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`);
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]); operatorList.addOp(
OPS.beginMarkedContentProps,
["OC", null],
range
);
continue; continue;
} }
if (args[0].name === "OC") { if (args[0].name === "OC") {
@ -2256,10 +2291,11 @@ class PartialEvaluator {
self self
.parseMarkedContentProps(args[1], resources) .parseMarkedContentProps(args[1], resources)
.then(data => { .then(data => {
operatorList.addOp(OPS.beginMarkedContentProps, [ operatorList.addOp(
"OC", OPS.beginMarkedContentProps,
data, ["OC", data],
]); range
);
}) })
.catch(reason => { .catch(reason => {
if (reason instanceof AbortException) { if (reason instanceof AbortException) {
@ -2269,10 +2305,11 @@ class PartialEvaluator {
warn( warn(
`getOperatorList - ignoring beginMarkedContentProps: "${reason}".` `getOperatorList - ignoring beginMarkedContentProps: "${reason}".`
); );
operatorList.addOp(OPS.beginMarkedContentProps, [ operatorList.addOp(
"OC", OPS.beginMarkedContentProps,
null, ["OC", null],
]); range
);
return; return;
} }
throw reason; throw reason;
@ -2305,7 +2342,7 @@ class PartialEvaluator {
} }
} }
} }
operatorList.addOp(fn, args); operatorList.addOp(fn, args, range);
} }
if (stop) { if (stop) {
next(deferred); next(deferred);
@ -5101,6 +5138,7 @@ class EvaluatorPreprocessor {
// avoiding allocations where possible is worthwhile. // avoiding allocations where possible is worthwhile.
// //
read(operation) { read(operation) {
const start = this.parser.getPosition();
let args = operation.args; let args = operation.args;
while (true) { while (true) {
const obj = this.parser.getObj(); const obj = this.parser.getObj();
@ -5177,6 +5215,8 @@ class EvaluatorPreprocessor {
operation.fn = fn; operation.fn = fn;
operation.args = args; operation.args = args;
const end = this.parser.getPosition();
operation.range = [start, end];
return true; return true;
} }
if (obj === EOF) { if (obj === EOF) {

View File

@ -47,8 +47,7 @@ async function getImageAsBlob(path, doc) {
pdfFunctionFactory, pdfFunctionFactory,
localColorSpaceCache: new LocalColorSpaceCache(), localColorSpaceCache: new LocalColorSpaceCache(),
}); });
const imageData = await pdfImage.createImageData(true, false); return pdfImage.createImageData(true, false);
return new Blob([imageData.data], { type: "image/png" });
} }
async function getPrimitive(path, doc) { async function getPrimitive(path, doc) {
@ -254,6 +253,8 @@ function toType(prim) {
return ["String", "-"]; return ["String", "-"];
} else if (isRef(prim)) { } else if (isRef(prim)) {
return ["Reference", "-"]; return ["Reference", "-"];
} else if (prim === null) {
return ["Null", "-"];
} }
throw new Error("Unknown prim"); throw new Error("Unknown prim");
} }
@ -305,6 +306,8 @@ function primToString(prim) {
return prim; return prim;
} else if (isRef(prim)) { } else if (isRef(prim)) {
return "XRef(" + prim.num + ", " + prim.gen + ")"; return "XRef(" + prim.num + ", " + prim.gen + ")";
} else if (prim === null) {
return "Null";
} }
throw new Error("Unknown prim"); throw new Error("Unknown prim");
} }
@ -376,5 +379,6 @@ export {
getPrimTree, getPrimTree,
getStreamAsString, getStreamAsString,
PrimitiveModel, PrimitiveModel,
toType,
TreeViewModel, TreeViewModel,
}; };

View File

@ -477,9 +477,10 @@ class NullOptimizer {
_optimize() {} _optimize() {}
push(fn, args) { push(fn, args, range) {
this.queue.fnArray.push(fn); this.queue.fnArray.push(fn);
this.queue.argsArray.push(args); this.queue.argsArray.push(args);
this.queue.rangeArray.push(range);
this._optimize(); this._optimize();
} }
@ -589,6 +590,7 @@ class OperatorList {
this._streamSink = streamSink; this._streamSink = streamSink;
this.fnArray = []; this.fnArray = [];
this.argsArray = []; this.argsArray = [];
this.rangeArray = [];
this.optimizer = this.optimizer =
streamSink && !(intent & RenderingIntentFlag.OPLIST) streamSink && !(intent & RenderingIntentFlag.OPLIST)
? new QueueOptimizer(this) ? new QueueOptimizer(this)
@ -620,8 +622,8 @@ class OperatorList {
return this._totalLength + this.length; return this._totalLength + this.length;
} }
addOp(fn, args) { addOp(fn, args, range = null) {
this.optimizer.push(fn, args); this.optimizer.push(fn, args, range);
this.weight++; this.weight++;
if (this._streamSink) { if (this._streamSink) {
if (this.weight >= OperatorList.CHUNK_SIZE) { if (this.weight >= OperatorList.CHUNK_SIZE) {
@ -636,7 +638,7 @@ class OperatorList {
} }
} }
addImageOps(fn, args, optionalContent, hasMask = false) { addImageOps(fn, args, range, optionalContent, hasMask = false) {
if (hasMask) { if (hasMask) {
this.addOp(OPS.save); this.addOp(OPS.save);
this.addOp(OPS.setGState, [[["SMask", false]]]); this.addOp(OPS.setGState, [[["SMask", false]]]);
@ -645,7 +647,7 @@ class OperatorList {
this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]); this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
} }
this.addOp(fn, args); this.addOp(fn, args, range);
if (optionalContent !== undefined) { if (optionalContent !== undefined) {
this.addOp(OPS.endMarkedContent, []); this.addOp(OPS.endMarkedContent, []);
@ -678,7 +680,7 @@ class OperatorList {
this.dependencies.add(dependency); this.dependencies.add(dependency);
} }
for (let i = 0, ii = opList.length; i < ii; i++) { for (let i = 0, ii = opList.length; i < ii; i++) {
this.addOp(opList.fnArray[i], opList.argsArray[i]); this.addOp(opList.fnArray[i], opList.argsArray[i], opList.rangeArray[i]);
} }
} }
@ -686,6 +688,7 @@ class OperatorList {
return { return {
fnArray: this.fnArray, fnArray: this.fnArray,
argsArray: this.argsArray, argsArray: this.argsArray,
rangeArray: this.rangeArray,
length: this.length, length: this.length,
}; };
} }
@ -717,6 +720,7 @@ class OperatorList {
{ {
fnArray: this.fnArray, fnArray: this.fnArray,
argsArray: this.argsArray, argsArray: this.argsArray,
rangeArray: this.rangeArray,
lastChunk, lastChunk,
separateAnnots, separateAnnots,
length, length,
@ -728,6 +732,7 @@ class OperatorList {
this.dependencies.clear(); this.dependencies.clear();
this.fnArray.length = 0; this.fnArray.length = 0;
this.argsArray.length = 0; this.argsArray.length = 0;
this.rangeArray.length = 0;
this.weight = 0; this.weight = 0;
this.optimizer.reset(); this.optimizer.reset();
} }

View File

@ -59,26 +59,51 @@ function getInlineImageCacheKey(bytes) {
} }
class Parser { class Parser {
constructor({ lexer, xref, allowStreams = false, recoveryMode = false }) { constructor({
lexer,
xref,
allowStreams = false,
recoveryMode = false,
trackRanges = true,
}) {
this.lexer = lexer; this.lexer = lexer;
this.xref = xref; this.xref = xref;
this.allowStreams = allowStreams; this.allowStreams = allowStreams;
this.recoveryMode = recoveryMode; this.recoveryMode = recoveryMode;
this.withRange = trackRanges;
this.imageCache = Object.create(null); this.imageCache = Object.create(null);
this._imageId = 0; this._imageId = 0;
this.refill(); this.refill();
} }
refill() { refill() {
if (this.withRange) {
const [buf1, start1, end1] = this.lexer.getObjWithRange();
const [buf2, start2, end2] = this.lexer.getObjWithRange();
this.buf1 = buf1;
this.range1 = [start1, end1];
this.buf2 = buf2;
this.range2 = [start2, end2];
} else {
this.buf1 = this.lexer.getObj(); this.buf1 = this.lexer.getObj();
this.buf2 = this.lexer.getObj(); this.buf2 = this.lexer.getObj();
} }
}
shift() { shift() {
if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") { if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") {
this.buf1 = this.buf2; this.buf1 = this.buf2;
this.buf2 = null; this.buf2 = null;
if (this.withRange) {
this.range1 = this.range2;
this.range2 = null;
}
} else if (this.withRange) {
this.buf1 = this.buf2;
this.range1 = this.range2;
const [buf2, start2, end2] = this.lexer.getObjWithRange();
this.buf2 = buf2;
this.range2 = [start2, end2];
} else { } else {
this.buf1 = this.buf2; this.buf1 = this.buf2;
this.buf2 = this.lexer.getObj(); this.buf2 = this.lexer.getObj();
@ -99,6 +124,17 @@ class Parser {
} }
} }
getPosition() {
return this.range1 ? this.range1[0] : 0;
}
getObjWithRange(cipherTransform = null) {
const start = this.range1[0];
const obj = this.getObj(cipherTransform);
const end = this.range1[0];
return [obj, start, end];
}
getObj(cipherTransform = null) { getObj(cipherTransform = null) {
const buf1 = this.buf1; const buf1 = this.buf1;
this.shift(); this.shift();
@ -1204,6 +1240,14 @@ class Lexer {
return strBuf.join(""); return strBuf.join("");
} }
getObjWithRange() {
// at the start of getObj() the stream has stepped beyond currentChar by one
const start = this.stream.pos - 1;
const obj = this.getObj();
const end = this.stream.pos;
return [obj, start, end];
}
getObj() { getObj() {
// Skip whitespace and comments. // Skip whitespace and comments.
let comment = false; let comment = false;

View File

@ -1,8 +1,8 @@
import {Dict, Ref} from "./primitives.js"; import { Ref } from "./primitives.js";
import {BaseStream} from "./base_stream.js"; import { toType } from "./obj_walker.js";
async function retrieveXref(doc) { async function retrieveXref(doc) {
let result = new XRefTable(doc.xref.entries.length); const result = new XRefTable(doc.xref.entries.length);
for (let i = 0; i < doc.xref.entries.length; i++) { for (let i = 0; i < doc.xref.entries.length; i++) {
result.entries.push(to_model(i, doc.xref.entries[i], doc.xref)); result.entries.push(to_model(i, doc.xref.entries[i], doc.xref));
} }
@ -13,13 +13,8 @@ function to_model(i, entry, xref) {
if (entry.free) { if (entry.free) {
return new XRefEntry("Free", i, entry.gen, entry.offset); return new XRefEntry("Free", i, entry.gen, entry.offset);
} }
const fetched = xref.fetch(new Ref(i, entry.gen)); const fetched = xref.fetch(Ref.get(i, entry.gen));
let type = "Unknown"; const [type] = toType(fetched);
if (fetched instanceof Dict) {
type = "Dictionary";
} else if (fetched instanceof BaseStream) {
type = "Stream";
}
return new XRefEntry(type, i, entry.gen, entry.offset); return new XRefEntry(type, i, entry.gen, entry.offset);
} }
@ -39,4 +34,4 @@ class XRefEntry {
} }
} }
export { XRefEntry, XRefTable, retrieveXref }; export { retrieveXref, XRefEntry, XRefTable };

View File

@ -503,7 +503,7 @@ class WorkerMessageHandler {
return getPrimTree(request, pdfManager.pdfDocument); return getPrimTree(request, pdfManager.pdfDocument);
}); });
handler.on("GetImageAsBlob", function (path) { handler.on("GetImageData", function (path) {
return getImageAsBlob(path, pdfManager.pdfDocument); return getImageAsBlob(path, pdfManager.pdfDocument);
}); });

View File

@ -1133,11 +1133,11 @@ class PDFDocumentProxy {
} }
/** /**
* @returns {Promise<Blob>} A promise that is resolved to a blob representing * @returns {Promise<ImageData>} A promise that is resolved to ImageData.
* the image as png. * Throws an Error if the path does not lead to an image!
*/ */
getImageByPath(path) { getImageDataByPath(path) {
return this._transport.getImageAsBlob(path); return this._transport.getImageDataByPath(path);
} }
/** /**
@ -1705,6 +1705,7 @@ class PDFPageProxy {
intentState.operatorList = { intentState.operatorList = {
fnArray: [], fnArray: [],
argsArray: [], argsArray: [],
rangeArray: [],
lastChunk: false, lastChunk: false,
separateAnnots: null, separateAnnots: null,
}; };
@ -1882,6 +1883,11 @@ class PDFPageProxy {
for (let i = 0, ii = operatorListChunk.length; i < ii; i++) { for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]); intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]); intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
if (intentState.operatorList.rangeArray) {
intentState.operatorList.rangeArray.push(
operatorListChunk.rangeArray[i]
);
}
} }
intentState.operatorList.lastChunk = operatorListChunk.lastChunk; intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots; intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
@ -2956,8 +2962,8 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetPrimTree", request); return this.messageHandler.sendWithPromise("GetPrimTree", request);
} }
getImageAsBlob(path) { getImageDataByPath(path) {
return this.messageHandler.sendWithPromise("GetImageAsBlob", path); return this.messageHandler.sendWithPromise("GetImageData", path);
} }
getStreamAsString(path) { getStreamAsString(path) {