diff --git a/src/core/evaluator.js b/src/core/evaluator.js index 3a1296f1dade63..acebd6d6ec42e3 100644 --- a/src/core/evaluator.js +++ b/src/core/evaluator.js @@ -422,8 +422,16 @@ class PartialEvaluator { smask, isolated: false, knockout: false, + optionalContent: null, }; + if (dict.has("OC")) { + groupOptions.optionalContent = await this.parseMarkedContentProps( + dict.get("OC"), + resources + ); + } + var groupSubtype = group.get("S"); var colorSpace = null; if (isName(groupSubtype, "Transparency")) { @@ -1224,6 +1232,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, @@ -1683,9 +1748,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 @@ -1695,6 +1757,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 diff --git a/src/core/obj.js b/src/core/obj.js index fdaa1bd178aae8..de951d5f91291d 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -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)) { diff --git a/src/core/worker.js b/src/core/worker.js index 31bd828de52cf3..d41e4a103591e5 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -485,6 +485,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"); }); diff --git a/src/display/api.js b/src/display/api.js index e88618a32f4fc7..a6668f492f7e50 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -54,6 +54,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"; @@ -709,6 +710,16 @@ class PDFDocumentProxy { return this._transport.getOutline(); } + /** + * @see {OptionalContentConfig} + * @returns {Promise} A promise that is resolved with an + * {OptionalContentConfig} that will have all the optional content groups (if + * the document has any). + */ + getOptionalContentConfig() { + return this._transport.getOptionalContentConfig(); + } + /** * @returns {Promise} A promise that is resolved with an {Array} that contains * the permission flags for the PDF document, or `null` when @@ -882,6 +893,11 @@ class PDFDocumentProxy { * CSS value, a CanvasGradient object (a linear or * radial gradient) or a CanvasPattern object (a repetitive * image). The default value is 'rgb(255,255,255)'. + * @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. */ /** @@ -1004,6 +1020,7 @@ class PDFPageProxy { imageLayer = null, canvasFactory = null, background = null, + optionalContentConfigPromise = null, }) { if (this._stats) { this._stats.time("Overall"); @@ -1014,6 +1031,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); @@ -1106,8 +1127,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; @@ -1115,7 +1139,10 @@ class PDFPageProxy { if (this._stats) { this._stats.time("Rendering"); } - internalRenderTask.initializeGraphics(transparency); + internalRenderTask.initializeGraphics({ + transparency, + optionalContentConfig, + }); internalRenderTask.operatorListChanged(); }) .catch(complete); @@ -2447,6 +2474,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); } @@ -2659,7 +2694,7 @@ const InternalRenderTask = (function InternalRenderTaskClosure() { }); } - initializeGraphics(transparency = false) { + initializeGraphics({ transparency = false, optionalContentConfig }) { if (this.cancelled) { return; } @@ -2697,7 +2732,8 @@ const InternalRenderTask = (function InternalRenderTaskClosure() { this.objs, this.canvasFactory, this.webGLContext, - imageLayer + imageLayer, + optionalContentConfig ); this.gfx.beginDrawing({ transform, diff --git a/src/display/canvas.js b/src/display/canvas.js index 9b9693c11ccee9..b9238023e42f3f 100644 --- a/src/display/canvas.js +++ b/src/display/canvas.js @@ -444,7 +444,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { objs, canvasFactory, webGLContext, - imageLayer + imageLayer, + optionalContentConfig ) { this.ctx = canvasCtx; this.current = new CanvasExtraState(); @@ -468,6 +469,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 @@ -1259,34 +1263,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(); @@ -1314,11 +1320,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) { @@ -1697,7 +1705,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); @@ -1779,12 +1787,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; @@ -1866,6 +1876,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, shadingFill: function CanvasGraphics_shadingFill(patternIR) { + if (!this.contentVisible) { + return; + } var ctx = this.ctx; this.save(); @@ -1914,6 +1927,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { matrix, bbox ) { + if (!this.contentVisible) { + return; + } this.save(); this.baseTransformStack.push(this.baseTransform); @@ -1933,11 +1949,24 @@ 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 (group.optionalContent) { + this.markedContentStack.push({ + visible: this.optionalContentConfig.isVisible(group.optionalContent), + }); + this.contentVisible = this.isContentVisible(); + if (!this.contentVisible) { + return; + } + } + this.save(); var currentCtx = this.ctx; // TODO non-isolated groups - according to Rik at adobe non-isolated @@ -2059,6 +2088,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, endGroup: function CanvasGraphics_endGroup(group) { + if (group.optionalContent) { + this.markedContentStack.pop(); + const previouslyVisible = this.contentVisible; + this.contentVisible = this.isContentVisible(); + if (!previouslyVisible) { + return; + } + } this.groupLevel--; var groupCtx = this.ctx; this.ctx = this.groupStack.pop(); @@ -2114,6 +2151,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; @@ -2165,6 +2205,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { scaleY, positions ) { + if (!this.contentVisible) { + return; + } var width = imgData.width; var height = imgData.height; var fillColor = this.current.fillColor; @@ -2209,6 +2252,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup( images ) { + if (!this.contentVisible) { + return; + } var ctx = this.ctx; var fillColor = this.current.fillColor; @@ -2246,6 +2292,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); @@ -2263,6 +2312,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { scaleY, positions ) { + if (!this.contentVisible) { + return; + } const imgData = objId.startsWith("g_") ? this.commonObjs.get(objId) : this.objs.get(objId); @@ -2289,6 +2341,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; @@ -2391,6 +2446,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { imgData, map ) { + if (!this.contentVisible) { + return; + } var ctx = this.ctx; var w = imgData.width; var h = imgData.height; @@ -2430,6 +2488,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { }, paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() { + if (!this.contentVisible) { + return; + } this.ctx.fillRect(0, 0, 1, 1); }, @@ -2448,10 +2509,20 @@ var CanvasGraphics = (function CanvasGraphicsClosure() { 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 @@ -2497,6 +2568,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) { diff --git a/src/display/optional_content_config.js b/src/display/optional_content_config.js new file mode 100644 index 00000000000000..2ff0a6c11cf724 --- /dev/null +++ b/src/display/optional_content_config.js @@ -0,0 +1,107 @@ +/* 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 { assert, 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") { + assert( + this.groups.has(group.id), + "Optional content group must be found." + ); + 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.get(id).visible) { + return true; + } + } + return false; + } else if (group.policy === "AllOn") { + for (const id of group.ids) { + if (!this.groups.get(id).visible) { + return false; + } + } + return true; + } else if (group.policy === "AnyOff") { + for (const id of group.ids) { + if (!this.groups.get(id).visible) { + return true; + } + } + return false; + } else if (group.policy === "AllOff") { + for (const id of group.ids) { + if (this.groups.get(id).visible) { + return false; + } + } + return true; + } + throw new Error(`Unknown optional content policy ${group.policy}.`); + } + throw new Error(`Unknown group type ${group.type}.`); + } +} + +export { OptionalContentConfig }; diff --git a/src/shared/util.js b/src/shared/util.js index c077f4957a77ed..cc6d0ec8dd5abb 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -302,6 +302,7 @@ const UNSUPPORTED_FEATURES = { errorFontToUnicode: "errorFontToUnicode", errorFontLoadNative: "errorFontLoadNative", errorFontGetPath: "errorFontGetPath", + errorMarkedContent: "errorMarkedContent", }; const PasswordResponses = { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 44c50058f7f29b..085de126c89f67 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -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 diff --git a/test/pdfs/issue11144_reduced.pdf b/test/pdfs/issue11144_reduced.pdf new file mode 100644 index 00000000000000..190d8fb7b68808 Binary files /dev/null and b/test/pdfs/issue11144_reduced.pdf differ diff --git a/test/pdfs/issue12007_reduced.pdf b/test/pdfs/issue12007_reduced.pdf new file mode 100644 index 00000000000000..30bec9eb80c75a Binary files /dev/null and b/test/pdfs/issue12007_reduced.pdf differ diff --git a/test/pdfs/issue269_1.pdf b/test/pdfs/issue269_1.pdf new file mode 100644 index 00000000000000..3e7e6bd8dfb746 Binary files /dev/null and b/test/pdfs/issue269_1.pdf differ diff --git a/test/pdfs/issue269_2.pdf b/test/pdfs/issue269_2.pdf new file mode 100644 index 00000000000000..7f8751559525d4 Binary files /dev/null and b/test/pdfs/issue269_2.pdf differ diff --git a/test/test_manifest.json b/test/test_manifest.json index 65c1b19dca3523..c13f77c34328d4 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -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", diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index 3a5c77ba92626e..e9ee35f6f9ae05 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -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();