Merge pull request #12095 from brendandahl/oc

Add support for optional marked content.
This commit is contained in:
Brendan Dahl 2020-08-04 12:00:20 -07:00 committed by GitHub
commit 9989879458
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 1179 additions and 54 deletions

View File

@ -392,6 +392,14 @@ class PartialEvaluator {
} else {
bbox = null;
}
let optionalContent = null;
if (dict.has("OC")) {
optionalContent = await this.parseMarkedContentProps(
dict.get("OC"),
resources
);
operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
}
var group = dict.get("Group");
if (group) {
var groupOptions = {
@ -449,6 +457,10 @@ class PartialEvaluator {
if (group) {
operatorList.addOp(OPS.endGroup, [groupOptions]);
}
if (optionalContent) {
operatorList.addOp(OPS.endMarkedContent, []);
}
});
}
@ -1202,6 +1214,63 @@ class PartialEvaluator {
throw new FormatError(`Unknown PatternName: ${patternName}`);
}
async parseMarkedContentProps(contentProperties, resources) {
let optionalContent;
if (isName(contentProperties)) {
const properties = resources.get("Properties");
optionalContent = properties.get(contentProperties.name);
} else if (isDict(contentProperties)) {
optionalContent = contentProperties;
} else {
throw new FormatError("Optional content properties malformed.");
}
const optionalContentType = optionalContent.get("Type").name;
if (optionalContentType === "OCG") {
return {
type: optionalContentType,
id: optionalContent.objId,
};
} else if (optionalContentType === "OCMD") {
const optionalContentGroups = optionalContent.get("OCGs");
if (
Array.isArray(optionalContentGroups) ||
isDict(optionalContentGroups)
) {
const groupIds = [];
if (Array.isArray(optionalContentGroups)) {
optionalContent.get("OCGs").forEach(ocg => {
groupIds.push(ocg.toString());
});
} else {
// Dictionary, just use the obj id.
groupIds.push(optionalContentGroups.objId);
}
let expression = null;
if (optionalContent.get("VE")) {
// TODO support visibility expression.
expression = true;
}
return {
type: optionalContentType,
ids: groupIds,
policy: isName(optionalContent.get("P"))
? optionalContent.get("P").name
: null,
expression,
};
} else if (isRef(optionalContentGroups)) {
return {
type: optionalContentType,
id: optionalContentGroups.toString(),
};
}
}
return null;
}
getOperatorList({
stream,
task,
@ -1704,9 +1773,6 @@ class PartialEvaluator {
continue;
case OPS.markPoint:
case OPS.markPointProps:
case OPS.beginMarkedContent:
case OPS.beginMarkedContentProps:
case OPS.endMarkedContent:
case OPS.beginCompat:
case OPS.endCompat:
// Ignore operators where the corresponding handlers are known to
@ -1716,6 +1782,45 @@ class PartialEvaluator {
// e.g. as done in https://github.com/mozilla/pdf.js/pull/6266,
// but doing so is meaningless without knowing the semantics.
continue;
case OPS.beginMarkedContentProps:
if (!isName(args[0])) {
warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`);
continue;
}
if (args[0].name === "OC") {
next(
self
.parseMarkedContentProps(args[1], resources)
.then(data => {
operatorList.addOp(OPS.beginMarkedContentProps, [
"OC",
data,
]);
})
.catch(reason => {
if (reason instanceof AbortException) {
return;
}
if (self.options.ignoreErrors) {
self.handler.send("UnsupportedFeature", {
featureId: UNSUPPORTED_FEATURES.errorMarkedContent,
});
warn(
`getOperatorList - ignoring beginMarkedContentProps: "${reason}".`
);
return;
}
throw reason;
})
);
return;
}
// Other marked content types aren't supported yet.
args = [args[0].name];
break;
case OPS.beginMarkedContent:
case OPS.endMarkedContent:
default:
// Note: Ignore the operator if it has `Dict` arguments, since
// those are non-serializable, otherwise postMessage will throw

View File

@ -254,6 +254,82 @@ class Catalog {
return permissions;
}
get optionalContentConfig() {
let config = null;
try {
const properties = this.catDict.get("OCProperties");
if (!properties) {
return shadow(this, "optionalContentConfig", null);
}
const defaultConfig = properties.get("D");
if (!defaultConfig) {
return shadow(this, "optionalContentConfig", null);
}
const groupsData = properties.get("OCGs");
if (!Array.isArray(groupsData)) {
return shadow(this, "optionalContentConfig", null);
}
const groups = [];
const groupRefs = [];
// Ensure all the optional content groups are valid.
for (const groupRef of groupsData) {
if (!isRef(groupRef)) {
continue;
}
groupRefs.push(groupRef);
const group = this.xref.fetchIfRef(groupRef);
groups.push({
id: groupRef.toString(),
name: isString(group.get("Name"))
? stringToPDFString(group.get("Name"))
: null,
intent: isString(group.get("Intent"))
? stringToPDFString(group.get("Intent"))
: null,
});
}
config = this._readOptionalContentConfig(defaultConfig, groupRefs);
config.groups = groups;
} catch (ex) {
if (ex instanceof MissingDataException) {
throw ex;
}
warn(`Unable to read optional content config: ${ex}`);
}
return shadow(this, "optionalContentConfig", config);
}
_readOptionalContentConfig(config, contentGroupRefs) {
function parseOnOff(refs) {
const onParsed = [];
if (Array.isArray(refs)) {
for (const value of refs) {
if (!isRef(value)) {
continue;
}
if (contentGroupRefs.includes(value)) {
onParsed.push(value.toString());
}
}
}
return onParsed;
}
return {
name: isString(config.get("Name"))
? stringToPDFString(config.get("Name"))
: null,
creator: isString(config.get("Creator"))
? stringToPDFString(config.get("Creator"))
: null,
baseState: isName(config.get("BaseState"))
? config.get("BaseState").name
: null,
on: parseOnOff(config.get("ON")),
off: parseOnOff(config.get("OFF")),
};
}
get numPages() {
const obj = this.toplevelPagesDict.get("Count");
if (!Number.isInteger(obj)) {

View File

@ -481,6 +481,10 @@ class WorkerMessageHandler {
return pdfManager.ensureCatalog("documentOutline");
});
handler.on("GetOptionalContentConfig", function (data) {
return pdfManager.ensureCatalog("optionalContentConfig");
});
handler.on("GetPermissions", function (data) {
return pdfManager.ensureCatalog("permissions");
});

View File

@ -55,6 +55,7 @@ import { GlobalWorkerOptions } from "./worker_options.js";
import { isNodeJS } from "../shared/is_node.js";
import { MessageHandler } from "../shared/message_handler.js";
import { Metadata } from "./metadata.js";
import { OptionalContentConfig } from "./optional_content_config.js";
import { PDFDataTransportStream } from "./transport_stream.js";
import { WebGLContext } from "./webgl.js";
@ -788,6 +789,15 @@ class PDFDocumentProxy {
return this._transport.getOutline();
}
/**
* @returns {Promise<OptionalContentConfig | null>} A promise that is resolved
* with an {@link OptionalContentConfig} that will have all the optional
* content groups (if the document has any).
*/
getOptionalContentConfig() {
return this._transport.getOptionalContentConfig();
}
/**
* @returns {Promise<Array<string | null>>} A promise that is resolved with
* an {Array} that contains the permission flags for the PDF document, or
@ -965,6 +975,11 @@ class PDFDocumentProxy {
* image). The default value is 'rgb(255,255,255)'.
* @property {Object} [annotationStorage] - Storage for annotation data in
* forms.
* @property {Promise} [optionalContentConfigPromise] - A promise that should
* resolve with an {OptionalContentConfig} created from
* PDFDocumentProxy.getOptionalContentConfig. If null, the
* config will be automatically fetched with the default
* visibility states set.
*/
/**
@ -1088,6 +1103,7 @@ class PDFPageProxy {
canvasFactory = null,
background = null,
annotationStorage = null,
optionalContentConfigPromise = null,
}) {
if (this._stats) {
this._stats.time("Overall");
@ -1098,6 +1114,10 @@ class PDFPageProxy {
// this call to render.
this.pendingCleanup = false;
if (!optionalContentConfigPromise) {
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
}
let intentState = this._intentStates.get(renderingIntent);
if (!intentState) {
intentState = Object.create(null);
@ -1191,8 +1211,11 @@ class PDFPageProxy {
intentState.renderTasks.push(internalRenderTask);
const renderTask = internalRenderTask.task;
intentState.displayReadyCapability.promise
.then(transparency => {
Promise.all([
intentState.displayReadyCapability.promise,
optionalContentConfigPromise,
])
.then(([transparency, optionalContentConfig]) => {
if (this.pendingCleanup) {
complete();
return;
@ -1200,7 +1223,10 @@ class PDFPageProxy {
if (this._stats) {
this._stats.time("Rendering");
}
internalRenderTask.initializeGraphics(transparency);
internalRenderTask.initializeGraphics({
transparency,
optionalContentConfig,
});
internalRenderTask.operatorListChanged();
})
.catch(complete);
@ -2546,6 +2572,14 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetOutline", null);
}
getOptionalContentConfig() {
return this.messageHandler
.sendWithPromise("GetOptionalContentConfig", null)
.then(results => {
return new OptionalContentConfig(results);
});
}
getPermissions() {
return this.messageHandler.sendWithPromise("GetPermissions", null);
}
@ -2759,7 +2793,7 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
});
}
initializeGraphics(transparency = false) {
initializeGraphics({ transparency = false, optionalContentConfig }) {
if (this.cancelled) {
return;
}
@ -2797,7 +2831,8 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
this.objs,
this.canvasFactory,
this.webGLContext,
imageLayer
imageLayer,
optionalContentConfig
);
this.gfx.beginDrawing({
transform,

View File

@ -447,7 +447,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
objs,
canvasFactory,
webGLContext,
imageLayer
imageLayer,
optionalContentConfig
) {
this.ctx = canvasCtx;
this.current = new CanvasExtraState();
@ -471,6 +472,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
this.smaskStack = [];
this.smaskCounter = 0;
this.tempSMask = null;
this.contentVisible = true;
this.markedContentStack = [];
this.optionalContentConfig = optionalContentConfig;
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
if (canvasCtx) {
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
@ -1262,34 +1266,36 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// For stroke we want to temporarily change the global alpha to the
// stroking alpha.
ctx.globalAlpha = this.current.strokeAlpha;
if (
strokeColor &&
strokeColor.hasOwnProperty("type") &&
strokeColor.type === "Pattern"
) {
// for patterns, we transform to pattern space, calculate
// the pattern, call stroke, and restore to user space
ctx.save();
// The current transform will be replaced while building the pattern,
// but the line width needs to be adjusted by the current transform, so
// we must scale it. To properly fix this we should be using a pattern
// transform instead (see #10955).
const transform = ctx.mozCurrentTransform;
const scale = Util.singularValueDecompose2dScale(transform)[0];
ctx.strokeStyle = strokeColor.getPattern(ctx, this);
ctx.lineWidth = Math.max(
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
this.current.lineWidth * scale
);
ctx.stroke();
ctx.restore();
} else {
// Prevent drawing too thin lines by enforcing a minimum line width.
ctx.lineWidth = Math.max(
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
this.current.lineWidth
);
ctx.stroke();
if (this.contentVisible) {
if (
strokeColor &&
strokeColor.hasOwnProperty("type") &&
strokeColor.type === "Pattern"
) {
// for patterns, we transform to pattern space, calculate
// the pattern, call stroke, and restore to user space
ctx.save();
// The current transform will be replaced while building the pattern,
// but the line width needs to be adjusted by the current transform,
// so we must scale it. To properly fix this we should be using a
// pattern transform instead (see #10955).
const transform = ctx.mozCurrentTransform;
const scale = Util.singularValueDecompose2dScale(transform)[0];
ctx.strokeStyle = strokeColor.getPattern(ctx, this);
ctx.lineWidth = Math.max(
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
this.current.lineWidth * scale
);
ctx.stroke();
ctx.restore();
} else {
// Prevent drawing too thin lines by enforcing a minimum line width.
ctx.lineWidth = Math.max(
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
this.current.lineWidth
);
ctx.stroke();
}
}
if (consumePath) {
this.consumePath();
@ -1317,11 +1323,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
needRestore = true;
}
if (this.pendingEOFill) {
ctx.fill("evenodd");
this.pendingEOFill = false;
} else {
ctx.fill();
if (this.contentVisible) {
if (this.pendingEOFill) {
ctx.fill("evenodd");
this.pendingEOFill = false;
} else {
ctx.fill();
}
}
if (needRestore) {
@ -1700,7 +1708,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// Only attempt to draw the glyph if it is actually in the embedded font
// file or if there isn't a font file so the fallback font is shown.
if (glyph.isInFont || font.missingFile) {
if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
if (simpleFillText && !accent) {
// common case
ctx.fillText(character, scaledX, scaledY);
@ -1782,12 +1790,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
continue;
}
this.processingType3 = glyph;
this.save();
ctx.scale(fontSize, fontSize);
ctx.transform.apply(ctx, fontMatrix);
this.executeOperatorList(operatorList);
this.restore();
if (this.contentVisible) {
this.processingType3 = glyph;
this.save();
ctx.scale(fontSize, fontSize);
ctx.transform.apply(ctx, fontMatrix);
this.executeOperatorList(operatorList);
this.restore();
}
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
width = transformed[0] * fontSize + spacing;
@ -1869,6 +1879,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
shadingFill: function CanvasGraphics_shadingFill(patternIR) {
if (!this.contentVisible) {
return;
}
var ctx = this.ctx;
this.save();
@ -1917,6 +1930,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
matrix,
bbox
) {
if (!this.contentVisible) {
return;
}
this.save();
this.baseTransformStack.push(this.baseTransform);
@ -1936,11 +1952,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
if (!this.contentVisible) {
return;
}
this.restore();
this.baseTransform = this.baseTransformStack.pop();
},
beginGroup: function CanvasGraphics_beginGroup(group) {
if (!this.contentVisible) {
return;
}
this.save();
var currentCtx = this.ctx;
// TODO non-isolated groups - according to Rik at adobe non-isolated
@ -2062,6 +2085,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
endGroup: function CanvasGraphics_endGroup(group) {
if (!this.contentVisible) {
return;
}
this.groupLevel--;
var groupCtx = this.ctx;
this.ctx = this.groupStack.pop();
@ -2117,6 +2143,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
if (!this.contentVisible) {
return;
}
var ctx = this.ctx;
var width = img.width,
height = img.height;
@ -2168,6 +2197,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
scaleY,
positions
) {
if (!this.contentVisible) {
return;
}
var width = imgData.width;
var height = imgData.height;
var fillColor = this.current.fillColor;
@ -2212,6 +2244,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup(
images
) {
if (!this.contentVisible) {
return;
}
var ctx = this.ctx;
var fillColor = this.current.fillColor;
@ -2249,6 +2284,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
if (!this.contentVisible) {
return;
}
const imgData = objId.startsWith("g_")
? this.commonObjs.get(objId)
: this.objs.get(objId);
@ -2266,6 +2304,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
scaleY,
positions
) {
if (!this.contentVisible) {
return;
}
const imgData = objId.startsWith("g_")
? this.commonObjs.get(objId)
: this.objs.get(objId);
@ -2292,6 +2333,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
paintInlineImageXObject: function CanvasGraphics_paintInlineImageXObject(
imgData
) {
if (!this.contentVisible) {
return;
}
var width = imgData.width;
var height = imgData.height;
var ctx = this.ctx;
@ -2394,6 +2438,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
imgData,
map
) {
if (!this.contentVisible) {
return;
}
var ctx = this.ctx;
var w = imgData.width;
var h = imgData.height;
@ -2433,6 +2480,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
},
paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() {
if (!this.contentVisible) {
return;
}
this.ctx.fillRect(0, 0, 1, 1);
},
@ -2445,16 +2495,28 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
// TODO Marked content.
},
beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
// TODO Marked content.
this.markedContentStack.push({
visible: true,
});
},
beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
tag,
properties
) {
// TODO Marked content.
if (tag === "OC") {
this.markedContentStack.push({
visible: this.optionalContentConfig.isVisible(properties),
});
} else {
this.markedContentStack.push({
visible: true,
});
}
this.contentVisible = this.isContentVisible();
},
endMarkedContent: function CanvasGraphics_endMarkedContent() {
// TODO Marked content.
this.markedContentStack.pop();
this.contentVisible = this.isContentVisible();
},
// Compatibility
@ -2500,6 +2562,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
transform[1] * x + transform[3] * y + transform[5],
];
},
isContentVisible: function CanvasGraphics_isContentVisible() {
for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
if (!this.markedContentStack[i].visible) {
return false;
}
}
return true;
},
};
for (var op in OPS) {

View File

@ -0,0 +1,125 @@
/* Copyright 2020 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { warn } from "../shared/util.js";
class OptionalContentGroup {
constructor(name, intent) {
this.visible = true;
this.name = name;
this.intent = intent;
}
}
class OptionalContentConfig {
constructor(data) {
this.name = null;
this.creator = null;
this.groups = new Map();
if (data === null) {
return;
}
this.name = data.name;
this.creator = data.creator;
for (const group of data.groups) {
this.groups.set(
group.id,
new OptionalContentGroup(group.name, group.intent)
);
}
if (data.baseState === "OFF") {
for (const group of this.groups) {
group.visible = false;
}
}
for (const on of data.on) {
this.groups.get(on).visible = true;
}
for (const off of data.off) {
this.groups.get(off).visible = false;
}
}
isVisible(group) {
if (group.type === "OCG") {
if (!this.groups.has(group.id)) {
warn(`Optional content group not found: ${group.id}`);
return true;
}
return this.groups.get(group.id).visible;
} else if (group.type === "OCMD") {
// Per the spec, the expression should be preferred if available. Until
// we implement this, just fallback to using the group policy for now.
if (group.expression) {
warn("Visibility expression not supported yet.");
}
if (!group.policy || group.policy === "AnyOn") {
// Default
for (const id of group.ids) {
if (!this.groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (this.groups.get(id).visible) {
return true;
}
}
return false;
} else if (group.policy === "AllOn") {
for (const id of group.ids) {
if (!this.groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (!this.groups.get(id).visible) {
return false;
}
}
return true;
} else if (group.policy === "AnyOff") {
for (const id of group.ids) {
if (!this.groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (!this.groups.get(id).visible) {
return true;
}
}
return false;
} else if (group.policy === "AllOff") {
for (const id of group.ids) {
if (!this.groups.has(id)) {
warn(`Optional content group not found: ${id}`);
return true;
}
if (this.groups.get(id).visible) {
return false;
}
}
return true;
}
warn(`Unknown optional content policy ${group.policy}.`);
return true;
}
warn(`Unknown group type ${group.type}.`);
return true;
}
}
export { OptionalContentConfig };

View File

@ -302,6 +302,7 @@ const UNSUPPORTED_FEATURES = {
errorFontToUnicode: "errorFontToUnicode",
errorFontLoadNative: "errorFontLoadNative",
errorFontGetPath: "errorFontGetPath",
errorMarkedContent: "errorMarkedContent",
};
const PasswordResponses = {

View File

@ -52,6 +52,7 @@
!issue7835.pdf
!issue11922_reduced.pdf
!issue7855.pdf
!issue11144_reduced.pdf
!issue7872.pdf
!issue7901.pdf
!issue8061.pdf
@ -296,6 +297,7 @@
!issue3371.pdf
!issue2956.pdf
!issue2537r.pdf
!issue269_1.pdf
!bug946506.pdf
!issue3885.pdf
!issue11697_reduced.pdf
@ -331,6 +333,7 @@
!issue5481.pdf
!issue5567.pdf
!issue5701.pdf
!issue12007_reduced.pdf
!issue5896.pdf
!issue6010_1.pdf
!issue6010_2.pdf
@ -352,6 +355,7 @@
!issue9278.pdf
!annotation-text-without-popup.pdf
!annotation-underline.pdf
!issue269_2.pdf
!annotation-strikeout.pdf
!annotation-squiggly.pdf
!annotation-highlight.pdf

Binary file not shown.

Binary file not shown.

676
test/pdfs/issue269_1.pdf Normal file

File diff suppressed because one or more lines are too long

BIN
test/pdfs/issue269_2.pdf Normal file

Binary file not shown.

View File

@ -926,6 +926,34 @@
"link": false,
"type": "eq"
},
{ "id": "issue269_1",
"file": "pdfs/issue269_1.pdf",
"md5": "ab932f697b4d2e2bf700de15a8efea9c",
"rounds": 1,
"type": "eq",
"about": "Optional marked content."
},
{ "id": "issue269_2",
"file": "pdfs/issue269_2.pdf",
"md5": "0f553510850ee17c87fbab3fac564165",
"rounds": 1,
"type": "eq",
"about": "Optional marked content."
},
{ "id": "issue11144_reduced",
"file": "pdfs/issue11144_reduced.pdf",
"md5": "09e3e771ebd6867558074e900adb54b9",
"rounds": 1,
"type": "eq",
"about": "Optional marked content."
},
{ "id": "issue12007_reduced",
"file": "pdfs/issue12007_reduced.pdf",
"md5": "3aa9d8a0c5ff8594245149f9c7379613",
"rounds": 1,
"type": "eq",
"about": "Optional marked content."
},
{ "id": "issue10438",
"file": "pdfs/issue10438_reduced.pdf",
"md5": "bb26f68493e33af17b256a6ffe777a24",

View File

@ -1636,8 +1636,8 @@ describe("api", function () {
const result1 = loadingTask1.promise.then(pdfDoc => {
return pdfDoc.getPage(1).then(pdfPage => {
return pdfPage.getOperatorList().then(opList => {
expect(opList.fnArray.length).toEqual(722);
expect(opList.argsArray.length).toEqual(722);
expect(opList.fnArray.length).toBeGreaterThan(100);
expect(opList.argsArray.length).toBeGreaterThan(100);
expect(opList.lastChunk).toEqual(true);
return loadingTask1.destroy();