Add support for optional marked content.
Add a new method to the API to get the optional content configuration. Add a new render task param that accepts the above configuration. For now, the optional content is not controllable by the user in the viewer, but renders with the default configuration in the PDF. All of the test files added exhibit different uses of optional content. Fixes #269. Fix test to work with optional content. - Change the stopAtErrors test to ensure the operator list has something, instead of asserting the exact number of operators.
This commit is contained in:
parent
e68ac05f18
commit
ac494a2278
@ -392,6 +392,14 @@ class PartialEvaluator {
|
|||||||
} else {
|
} else {
|
||||||
bbox = null;
|
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");
|
var group = dict.get("Group");
|
||||||
if (group) {
|
if (group) {
|
||||||
var groupOptions = {
|
var groupOptions = {
|
||||||
@ -449,6 +457,10 @@ class PartialEvaluator {
|
|||||||
if (group) {
|
if (group) {
|
||||||
operatorList.addOp(OPS.endGroup, [groupOptions]);
|
operatorList.addOp(OPS.endGroup, [groupOptions]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (optionalContent) {
|
||||||
|
operatorList.addOp(OPS.endMarkedContent, []);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1202,6 +1214,63 @@ class PartialEvaluator {
|
|||||||
throw new FormatError(`Unknown PatternName: ${patternName}`);
|
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({
|
getOperatorList({
|
||||||
stream,
|
stream,
|
||||||
task,
|
task,
|
||||||
@ -1704,9 +1773,6 @@ class PartialEvaluator {
|
|||||||
continue;
|
continue;
|
||||||
case OPS.markPoint:
|
case OPS.markPoint:
|
||||||
case OPS.markPointProps:
|
case OPS.markPointProps:
|
||||||
case OPS.beginMarkedContent:
|
|
||||||
case OPS.beginMarkedContentProps:
|
|
||||||
case OPS.endMarkedContent:
|
|
||||||
case OPS.beginCompat:
|
case OPS.beginCompat:
|
||||||
case OPS.endCompat:
|
case OPS.endCompat:
|
||||||
// Ignore operators where the corresponding handlers are known to
|
// 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,
|
// e.g. as done in https://github.com/mozilla/pdf.js/pull/6266,
|
||||||
// but doing so is meaningless without knowing the semantics.
|
// but doing so is meaningless without knowing the semantics.
|
||||||
continue;
|
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:
|
default:
|
||||||
// Note: Ignore the operator if it has `Dict` arguments, since
|
// Note: Ignore the operator if it has `Dict` arguments, since
|
||||||
// those are non-serializable, otherwise postMessage will throw
|
// those are non-serializable, otherwise postMessage will throw
|
||||||
|
@ -254,6 +254,82 @@ class Catalog {
|
|||||||
return permissions;
|
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() {
|
get numPages() {
|
||||||
const obj = this.toplevelPagesDict.get("Count");
|
const obj = this.toplevelPagesDict.get("Count");
|
||||||
if (!Number.isInteger(obj)) {
|
if (!Number.isInteger(obj)) {
|
||||||
|
@ -481,6 +481,10 @@ class WorkerMessageHandler {
|
|||||||
return pdfManager.ensureCatalog("documentOutline");
|
return pdfManager.ensureCatalog("documentOutline");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
handler.on("GetOptionalContentConfig", function (data) {
|
||||||
|
return pdfManager.ensureCatalog("optionalContentConfig");
|
||||||
|
});
|
||||||
|
|
||||||
handler.on("GetPermissions", function (data) {
|
handler.on("GetPermissions", function (data) {
|
||||||
return pdfManager.ensureCatalog("permissions");
|
return pdfManager.ensureCatalog("permissions");
|
||||||
});
|
});
|
||||||
|
@ -55,6 +55,7 @@ import { GlobalWorkerOptions } from "./worker_options.js";
|
|||||||
import { isNodeJS } from "../shared/is_node.js";
|
import { isNodeJS } from "../shared/is_node.js";
|
||||||
import { MessageHandler } from "../shared/message_handler.js";
|
import { MessageHandler } from "../shared/message_handler.js";
|
||||||
import { Metadata } from "./metadata.js";
|
import { Metadata } from "./metadata.js";
|
||||||
|
import { OptionalContentConfig } from "./optional_content_config.js";
|
||||||
import { PDFDataTransportStream } from "./transport_stream.js";
|
import { PDFDataTransportStream } from "./transport_stream.js";
|
||||||
import { WebGLContext } from "./webgl.js";
|
import { WebGLContext } from "./webgl.js";
|
||||||
|
|
||||||
@ -788,6 +789,15 @@ class PDFDocumentProxy {
|
|||||||
return this._transport.getOutline();
|
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
|
* @returns {Promise<Array<string | null>>} A promise that is resolved with
|
||||||
* an {Array} that contains the permission flags for the PDF document, or
|
* 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)'.
|
* image). The default value is 'rgb(255,255,255)'.
|
||||||
* @property {Object} [annotationStorage] - Storage for annotation data in
|
* @property {Object} [annotationStorage] - Storage for annotation data in
|
||||||
* forms.
|
* 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,
|
canvasFactory = null,
|
||||||
background = null,
|
background = null,
|
||||||
annotationStorage = null,
|
annotationStorage = null,
|
||||||
|
optionalContentConfigPromise = null,
|
||||||
}) {
|
}) {
|
||||||
if (this._stats) {
|
if (this._stats) {
|
||||||
this._stats.time("Overall");
|
this._stats.time("Overall");
|
||||||
@ -1098,6 +1114,10 @@ class PDFPageProxy {
|
|||||||
// this call to render.
|
// this call to render.
|
||||||
this.pendingCleanup = false;
|
this.pendingCleanup = false;
|
||||||
|
|
||||||
|
if (!optionalContentConfigPromise) {
|
||||||
|
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
|
||||||
|
}
|
||||||
|
|
||||||
let intentState = this._intentStates.get(renderingIntent);
|
let intentState = this._intentStates.get(renderingIntent);
|
||||||
if (!intentState) {
|
if (!intentState) {
|
||||||
intentState = Object.create(null);
|
intentState = Object.create(null);
|
||||||
@ -1191,8 +1211,11 @@ class PDFPageProxy {
|
|||||||
intentState.renderTasks.push(internalRenderTask);
|
intentState.renderTasks.push(internalRenderTask);
|
||||||
const renderTask = internalRenderTask.task;
|
const renderTask = internalRenderTask.task;
|
||||||
|
|
||||||
intentState.displayReadyCapability.promise
|
Promise.all([
|
||||||
.then(transparency => {
|
intentState.displayReadyCapability.promise,
|
||||||
|
optionalContentConfigPromise,
|
||||||
|
])
|
||||||
|
.then(([transparency, optionalContentConfig]) => {
|
||||||
if (this.pendingCleanup) {
|
if (this.pendingCleanup) {
|
||||||
complete();
|
complete();
|
||||||
return;
|
return;
|
||||||
@ -1200,7 +1223,10 @@ class PDFPageProxy {
|
|||||||
if (this._stats) {
|
if (this._stats) {
|
||||||
this._stats.time("Rendering");
|
this._stats.time("Rendering");
|
||||||
}
|
}
|
||||||
internalRenderTask.initializeGraphics(transparency);
|
internalRenderTask.initializeGraphics({
|
||||||
|
transparency,
|
||||||
|
optionalContentConfig,
|
||||||
|
});
|
||||||
internalRenderTask.operatorListChanged();
|
internalRenderTask.operatorListChanged();
|
||||||
})
|
})
|
||||||
.catch(complete);
|
.catch(complete);
|
||||||
@ -2546,6 +2572,14 @@ class WorkerTransport {
|
|||||||
return this.messageHandler.sendWithPromise("GetOutline", null);
|
return this.messageHandler.sendWithPromise("GetOutline", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOptionalContentConfig() {
|
||||||
|
return this.messageHandler
|
||||||
|
.sendWithPromise("GetOptionalContentConfig", null)
|
||||||
|
.then(results => {
|
||||||
|
return new OptionalContentConfig(results);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
getPermissions() {
|
getPermissions() {
|
||||||
return this.messageHandler.sendWithPromise("GetPermissions", null);
|
return this.messageHandler.sendWithPromise("GetPermissions", null);
|
||||||
}
|
}
|
||||||
@ -2759,7 +2793,7 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeGraphics(transparency = false) {
|
initializeGraphics({ transparency = false, optionalContentConfig }) {
|
||||||
if (this.cancelled) {
|
if (this.cancelled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2797,7 +2831,8 @@ const InternalRenderTask = (function InternalRenderTaskClosure() {
|
|||||||
this.objs,
|
this.objs,
|
||||||
this.canvasFactory,
|
this.canvasFactory,
|
||||||
this.webGLContext,
|
this.webGLContext,
|
||||||
imageLayer
|
imageLayer,
|
||||||
|
optionalContentConfig
|
||||||
);
|
);
|
||||||
this.gfx.beginDrawing({
|
this.gfx.beginDrawing({
|
||||||
transform,
|
transform,
|
||||||
|
@ -447,7 +447,8 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
objs,
|
objs,
|
||||||
canvasFactory,
|
canvasFactory,
|
||||||
webGLContext,
|
webGLContext,
|
||||||
imageLayer
|
imageLayer,
|
||||||
|
optionalContentConfig
|
||||||
) {
|
) {
|
||||||
this.ctx = canvasCtx;
|
this.ctx = canvasCtx;
|
||||||
this.current = new CanvasExtraState();
|
this.current = new CanvasExtraState();
|
||||||
@ -471,6 +472,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
this.smaskStack = [];
|
this.smaskStack = [];
|
||||||
this.smaskCounter = 0;
|
this.smaskCounter = 0;
|
||||||
this.tempSMask = null;
|
this.tempSMask = null;
|
||||||
|
this.contentVisible = true;
|
||||||
|
this.markedContentStack = [];
|
||||||
|
this.optionalContentConfig = optionalContentConfig;
|
||||||
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
this.cachedCanvases = new CachedCanvases(this.canvasFactory);
|
||||||
if (canvasCtx) {
|
if (canvasCtx) {
|
||||||
// NOTE: if mozCurrentTransform is polyfilled, then the current state of
|
// 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
|
// For stroke we want to temporarily change the global alpha to the
|
||||||
// stroking alpha.
|
// stroking alpha.
|
||||||
ctx.globalAlpha = this.current.strokeAlpha;
|
ctx.globalAlpha = this.current.strokeAlpha;
|
||||||
if (
|
if (this.contentVisible) {
|
||||||
strokeColor &&
|
if (
|
||||||
strokeColor.hasOwnProperty("type") &&
|
strokeColor &&
|
||||||
strokeColor.type === "Pattern"
|
strokeColor.hasOwnProperty("type") &&
|
||||||
) {
|
strokeColor.type === "Pattern"
|
||||||
// for patterns, we transform to pattern space, calculate
|
) {
|
||||||
// the pattern, call stroke, and restore to user space
|
// for patterns, we transform to pattern space, calculate
|
||||||
ctx.save();
|
// the pattern, call stroke, and restore to user space
|
||||||
// The current transform will be replaced while building the pattern,
|
ctx.save();
|
||||||
// but the line width needs to be adjusted by the current transform, so
|
// The current transform will be replaced while building the pattern,
|
||||||
// we must scale it. To properly fix this we should be using a pattern
|
// but the line width needs to be adjusted by the current transform,
|
||||||
// transform instead (see #10955).
|
// so we must scale it. To properly fix this we should be using a
|
||||||
const transform = ctx.mozCurrentTransform;
|
// pattern transform instead (see #10955).
|
||||||
const scale = Util.singularValueDecompose2dScale(transform)[0];
|
const transform = ctx.mozCurrentTransform;
|
||||||
ctx.strokeStyle = strokeColor.getPattern(ctx, this);
|
const scale = Util.singularValueDecompose2dScale(transform)[0];
|
||||||
ctx.lineWidth = Math.max(
|
ctx.strokeStyle = strokeColor.getPattern(ctx, this);
|
||||||
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
|
ctx.lineWidth = Math.max(
|
||||||
this.current.lineWidth * scale
|
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
|
||||||
);
|
this.current.lineWidth * scale
|
||||||
ctx.stroke();
|
);
|
||||||
ctx.restore();
|
ctx.stroke();
|
||||||
} else {
|
ctx.restore();
|
||||||
// Prevent drawing too thin lines by enforcing a minimum line width.
|
} else {
|
||||||
ctx.lineWidth = Math.max(
|
// Prevent drawing too thin lines by enforcing a minimum line width.
|
||||||
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
|
ctx.lineWidth = Math.max(
|
||||||
this.current.lineWidth
|
this.getSinglePixelWidth() * MIN_WIDTH_FACTOR,
|
||||||
);
|
this.current.lineWidth
|
||||||
ctx.stroke();
|
);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (consumePath) {
|
if (consumePath) {
|
||||||
this.consumePath();
|
this.consumePath();
|
||||||
@ -1317,11 +1323,13 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
needRestore = true;
|
needRestore = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.pendingEOFill) {
|
if (this.contentVisible) {
|
||||||
ctx.fill("evenodd");
|
if (this.pendingEOFill) {
|
||||||
this.pendingEOFill = false;
|
ctx.fill("evenodd");
|
||||||
} else {
|
this.pendingEOFill = false;
|
||||||
ctx.fill();
|
} else {
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needRestore) {
|
if (needRestore) {
|
||||||
@ -1700,7 +1708,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
|
|
||||||
// Only attempt to draw the glyph if it is actually in the embedded font
|
// 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.
|
// 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) {
|
if (simpleFillText && !accent) {
|
||||||
// common case
|
// common case
|
||||||
ctx.fillText(character, scaledX, scaledY);
|
ctx.fillText(character, scaledX, scaledY);
|
||||||
@ -1782,12 +1790,14 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
warn(`Type3 character "${glyph.operatorListId}" is not available.`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.processingType3 = glyph;
|
if (this.contentVisible) {
|
||||||
this.save();
|
this.processingType3 = glyph;
|
||||||
ctx.scale(fontSize, fontSize);
|
this.save();
|
||||||
ctx.transform.apply(ctx, fontMatrix);
|
ctx.scale(fontSize, fontSize);
|
||||||
this.executeOperatorList(operatorList);
|
ctx.transform.apply(ctx, fontMatrix);
|
||||||
this.restore();
|
this.executeOperatorList(operatorList);
|
||||||
|
this.restore();
|
||||||
|
}
|
||||||
|
|
||||||
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
|
||||||
width = transformed[0] * fontSize + spacing;
|
width = transformed[0] * fontSize + spacing;
|
||||||
@ -1869,6 +1879,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
shadingFill: function CanvasGraphics_shadingFill(patternIR) {
|
shadingFill: function CanvasGraphics_shadingFill(patternIR) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
@ -1917,6 +1930,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
matrix,
|
matrix,
|
||||||
bbox
|
bbox
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.save();
|
this.save();
|
||||||
this.baseTransformStack.push(this.baseTransform);
|
this.baseTransformStack.push(this.baseTransform);
|
||||||
|
|
||||||
@ -1936,11 +1952,18 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
|
paintFormXObjectEnd: function CanvasGraphics_paintFormXObjectEnd() {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.restore();
|
this.restore();
|
||||||
this.baseTransform = this.baseTransformStack.pop();
|
this.baseTransform = this.baseTransformStack.pop();
|
||||||
},
|
},
|
||||||
|
|
||||||
beginGroup: function CanvasGraphics_beginGroup(group) {
|
beginGroup: function CanvasGraphics_beginGroup(group) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.save();
|
this.save();
|
||||||
var currentCtx = this.ctx;
|
var currentCtx = this.ctx;
|
||||||
// TODO non-isolated groups - according to Rik at adobe non-isolated
|
// TODO non-isolated groups - according to Rik at adobe non-isolated
|
||||||
@ -2062,6 +2085,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
endGroup: function CanvasGraphics_endGroup(group) {
|
endGroup: function CanvasGraphics_endGroup(group) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.groupLevel--;
|
this.groupLevel--;
|
||||||
var groupCtx = this.ctx;
|
var groupCtx = this.ctx;
|
||||||
this.ctx = this.groupStack.pop();
|
this.ctx = this.groupStack.pop();
|
||||||
@ -2117,6 +2143,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
|
paintImageMaskXObject: function CanvasGraphics_paintImageMaskXObject(img) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
var width = img.width,
|
var width = img.width,
|
||||||
height = img.height;
|
height = img.height;
|
||||||
@ -2168,6 +2197,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
scaleY,
|
scaleY,
|
||||||
positions
|
positions
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var width = imgData.width;
|
var width = imgData.width;
|
||||||
var height = imgData.height;
|
var height = imgData.height;
|
||||||
var fillColor = this.current.fillColor;
|
var fillColor = this.current.fillColor;
|
||||||
@ -2212,6 +2244,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup(
|
paintImageMaskXObjectGroup: function CanvasGraphics_paintImageMaskXObjectGroup(
|
||||||
images
|
images
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
|
|
||||||
var fillColor = this.current.fillColor;
|
var fillColor = this.current.fillColor;
|
||||||
@ -2249,6 +2284,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
|
paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const imgData = objId.startsWith("g_")
|
const imgData = objId.startsWith("g_")
|
||||||
? this.commonObjs.get(objId)
|
? this.commonObjs.get(objId)
|
||||||
: this.objs.get(objId);
|
: this.objs.get(objId);
|
||||||
@ -2266,6 +2304,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
scaleY,
|
scaleY,
|
||||||
positions
|
positions
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const imgData = objId.startsWith("g_")
|
const imgData = objId.startsWith("g_")
|
||||||
? this.commonObjs.get(objId)
|
? this.commonObjs.get(objId)
|
||||||
: this.objs.get(objId);
|
: this.objs.get(objId);
|
||||||
@ -2292,6 +2333,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
paintInlineImageXObject: function CanvasGraphics_paintInlineImageXObject(
|
paintInlineImageXObject: function CanvasGraphics_paintInlineImageXObject(
|
||||||
imgData
|
imgData
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var width = imgData.width;
|
var width = imgData.width;
|
||||||
var height = imgData.height;
|
var height = imgData.height;
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
@ -2394,6 +2438,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
imgData,
|
imgData,
|
||||||
map
|
map
|
||||||
) {
|
) {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var ctx = this.ctx;
|
var ctx = this.ctx;
|
||||||
var w = imgData.width;
|
var w = imgData.width;
|
||||||
var h = imgData.height;
|
var h = imgData.height;
|
||||||
@ -2433,6 +2480,9 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() {
|
paintSolidColorImageMask: function CanvasGraphics_paintSolidColorImageMask() {
|
||||||
|
if (!this.contentVisible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.ctx.fillRect(0, 0, 1, 1);
|
this.ctx.fillRect(0, 0, 1, 1);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -2445,16 +2495,28 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
// TODO Marked content.
|
// TODO Marked content.
|
||||||
},
|
},
|
||||||
beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
|
beginMarkedContent: function CanvasGraphics_beginMarkedContent(tag) {
|
||||||
// TODO Marked content.
|
this.markedContentStack.push({
|
||||||
|
visible: true,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
|
beginMarkedContentProps: function CanvasGraphics_beginMarkedContentProps(
|
||||||
tag,
|
tag,
|
||||||
properties
|
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() {
|
endMarkedContent: function CanvasGraphics_endMarkedContent() {
|
||||||
// TODO Marked content.
|
this.markedContentStack.pop();
|
||||||
|
this.contentVisible = this.isContentVisible();
|
||||||
},
|
},
|
||||||
|
|
||||||
// Compatibility
|
// Compatibility
|
||||||
@ -2500,6 +2562,15 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
|
|||||||
transform[1] * x + transform[3] * y + transform[5],
|
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) {
|
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",
|
errorFontToUnicode: "errorFontToUnicode",
|
||||||
errorFontLoadNative: "errorFontLoadNative",
|
errorFontLoadNative: "errorFontLoadNative",
|
||||||
errorFontGetPath: "errorFontGetPath",
|
errorFontGetPath: "errorFontGetPath",
|
||||||
|
errorMarkedContent: "errorMarkedContent",
|
||||||
};
|
};
|
||||||
|
|
||||||
const PasswordResponses = {
|
const PasswordResponses = {
|
||||||
|
4
test/pdfs/.gitignore
vendored
4
test/pdfs/.gitignore
vendored
@ -52,6 +52,7 @@
|
|||||||
!issue7835.pdf
|
!issue7835.pdf
|
||||||
!issue11922_reduced.pdf
|
!issue11922_reduced.pdf
|
||||||
!issue7855.pdf
|
!issue7855.pdf
|
||||||
|
!issue11144_reduced.pdf
|
||||||
!issue7872.pdf
|
!issue7872.pdf
|
||||||
!issue7901.pdf
|
!issue7901.pdf
|
||||||
!issue8061.pdf
|
!issue8061.pdf
|
||||||
@ -296,6 +297,7 @@
|
|||||||
!issue3371.pdf
|
!issue3371.pdf
|
||||||
!issue2956.pdf
|
!issue2956.pdf
|
||||||
!issue2537r.pdf
|
!issue2537r.pdf
|
||||||
|
!issue269_1.pdf
|
||||||
!bug946506.pdf
|
!bug946506.pdf
|
||||||
!issue3885.pdf
|
!issue3885.pdf
|
||||||
!issue11697_reduced.pdf
|
!issue11697_reduced.pdf
|
||||||
@ -331,6 +333,7 @@
|
|||||||
!issue5481.pdf
|
!issue5481.pdf
|
||||||
!issue5567.pdf
|
!issue5567.pdf
|
||||||
!issue5701.pdf
|
!issue5701.pdf
|
||||||
|
!issue12007_reduced.pdf
|
||||||
!issue5896.pdf
|
!issue5896.pdf
|
||||||
!issue6010_1.pdf
|
!issue6010_1.pdf
|
||||||
!issue6010_2.pdf
|
!issue6010_2.pdf
|
||||||
@ -352,6 +355,7 @@
|
|||||||
!issue9278.pdf
|
!issue9278.pdf
|
||||||
!annotation-text-without-popup.pdf
|
!annotation-text-without-popup.pdf
|
||||||
!annotation-underline.pdf
|
!annotation-underline.pdf
|
||||||
|
!issue269_2.pdf
|
||||||
!annotation-strikeout.pdf
|
!annotation-strikeout.pdf
|
||||||
!annotation-squiggly.pdf
|
!annotation-squiggly.pdf
|
||||||
!annotation-highlight.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,
|
"link": false,
|
||||||
"type": "eq"
|
"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",
|
{ "id": "issue10438",
|
||||||
"file": "pdfs/issue10438_reduced.pdf",
|
"file": "pdfs/issue10438_reduced.pdf",
|
||||||
"md5": "bb26f68493e33af17b256a6ffe777a24",
|
"md5": "bb26f68493e33af17b256a6ffe777a24",
|
||||||
|
@ -1636,8 +1636,8 @@ describe("api", function () {
|
|||||||
const result1 = loadingTask1.promise.then(pdfDoc => {
|
const result1 = loadingTask1.promise.then(pdfDoc => {
|
||||||
return pdfDoc.getPage(1).then(pdfPage => {
|
return pdfDoc.getPage(1).then(pdfPage => {
|
||||||
return pdfPage.getOperatorList().then(opList => {
|
return pdfPage.getOperatorList().then(opList => {
|
||||||
expect(opList.fnArray.length).toEqual(722);
|
expect(opList.fnArray.length).toBeGreaterThan(100);
|
||||||
expect(opList.argsArray.length).toEqual(722);
|
expect(opList.argsArray.length).toBeGreaterThan(100);
|
||||||
expect(opList.lastChunk).toEqual(true);
|
expect(opList.lastChunk).toEqual(true);
|
||||||
|
|
||||||
return loadingTask1.destroy();
|
return loadingTask1.destroy();
|
||||||
|
Loading…
Reference in New Issue
Block a user