Skip to content

Commit

Permalink
Merge pull request #12095 from brendandahl/oc
Browse files Browse the repository at this point in the history
Add support for optional marked content.
  • Loading branch information
brendandahl authored Aug 4, 2020
2 parents e68ac05 + ac494a2 commit 9989879
Show file tree
Hide file tree
Showing 14 changed files with 503 additions and 54 deletions.
111 changes: 108 additions & 3 deletions src/core/evaluator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -449,6 +457,10 @@ class PartialEvaluator {
if (group) {
operatorList.addOp(OPS.endGroup, [groupOptions]);
}

if (optionalContent) {
operatorList.addOp(OPS.endMarkedContent, []);
}
});
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
76 changes: 76 additions & 0 deletions src/core/obj.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
4 changes: 4 additions & 0 deletions src/core/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
Expand Down
45 changes: 40 additions & 5 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
*/

/**
Expand Down Expand Up @@ -1088,6 +1103,7 @@ class PDFPageProxy {
canvasFactory = null,
background = null,
annotationStorage = null,
optionalContentConfigPromise = null,
}) {
if (this._stats) {
this._stats.time("Overall");
Expand All @@ -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);
Expand Down Expand Up @@ -1191,16 +1211,22 @@ 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;
}
if (this._stats) {
this._stats.time("Rendering");
}
internalRenderTask.initializeGraphics(transparency);
internalRenderTask.initializeGraphics({
transparency,
optionalContentConfig,
});
internalRenderTask.operatorListChanged();
})
.catch(complete);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -2759,7 +2793,7 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
});
}

initializeGraphics(transparency = false) {
initializeGraphics({ transparency = false, optionalContentConfig }) {
if (this.cancelled) {
return;
}
Expand Down Expand Up @@ -2797,7 +2831,8 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
this.objs,
this.canvasFactory,
this.webGLContext,
imageLayer
imageLayer,
optionalContentConfig
);
this.gfx.beginDrawing({
transform,
Expand Down
Loading

0 comments on commit 9989879

Please sign in to comment.