Merge pull request #12095 from brendandahl/oc
Add support for optional marked content.
This commit is contained in:
commit
9989879458
@ -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
|
||||
|
@ -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)) {
|
||||
|
@ -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");
|
||||
});
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
125
src/display/optional_content_config.js
Normal file
125
src/display/optional_content_config.js
Normal 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 };
|
@ -302,6 +302,7 @@ const UNSUPPORTED_FEATURES = {
|
||||
errorFontToUnicode: "errorFontToUnicode",
|
||||
errorFontLoadNative: "errorFontLoadNative",
|
||||
errorFontGetPath: "errorFontGetPath",
|
||||
errorMarkedContent: "errorMarkedContent",
|
||||
};
|
||||
|
||||
const PasswordResponses = {
|
||||
|
4
test/pdfs/.gitignore
vendored
4
test/pdfs/.gitignore
vendored
@ -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
|
||||
|
BIN
test/pdfs/issue11144_reduced.pdf
Normal file
BIN
test/pdfs/issue11144_reduced.pdf
Normal file
Binary file not shown.
BIN
test/pdfs/issue12007_reduced.pdf
Normal file
BIN
test/pdfs/issue12007_reduced.pdf
Normal file
Binary file not shown.
676
test/pdfs/issue269_1.pdf
Normal file
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
BIN
test/pdfs/issue269_2.pdf
Normal file
Binary file not shown.
@ -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",
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user