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
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:
parent
88a38b6c5b
commit
2305c6416e
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 };
|
||||||
|
|||||||
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user