mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-25 09:35:48 +02:00
Compare commits
16 Commits
ea18e73de2
...
e6cb600896
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6cb600896 | ||
|
|
adcde1175e | ||
|
|
143a7244a3 | ||
|
|
13a61b1f72 | ||
|
|
941e17296e | ||
|
|
1f8eed020f | ||
|
|
bbfbe5159c | ||
|
|
4daca805d6 | ||
|
|
d37e0b40a5 | ||
|
|
46b16bd42e | ||
|
|
59086fa582 | ||
|
|
057507f6ce | ||
|
|
31c6561b91 | ||
|
|
05de3c8a88 | ||
|
|
bd14524536 | ||
|
|
fb9758303b |
@ -76,6 +76,7 @@ import { FileSpec } from "./file_spec.js";
|
||||
import { JpegStream } from "./jpeg_stream.js";
|
||||
import { ObjectLoader } from "./object_loader.js";
|
||||
import { OperatorList } from "./operator_list.js";
|
||||
import { parseMarkedContentProps } from "./evaluator_utils.js";
|
||||
import { XFAFactory } from "./xfa/factory.js";
|
||||
|
||||
class AnnotationFactory {
|
||||
@ -663,6 +664,8 @@ function getTransformMatrix(rect, bbox, matrix) {
|
||||
}
|
||||
|
||||
class Annotation {
|
||||
_oc = undefined;
|
||||
|
||||
constructor(params) {
|
||||
const { annotationGlobals, dict, orphanFields, ref, subtype, xref } =
|
||||
params;
|
||||
@ -679,7 +682,7 @@ class Annotation {
|
||||
this.setColor(dict.getArray("C"));
|
||||
this.setBorderStyle(dict);
|
||||
this.setAppearance(dict);
|
||||
this.setOptionalContent(dict);
|
||||
this.#setOptionalContent(xref, dict);
|
||||
|
||||
const MK = dict.get("MK");
|
||||
this.setBorderAndBackgroundColors(MK);
|
||||
@ -710,6 +713,7 @@ class Annotation {
|
||||
hasAppearance: !!this.appearance,
|
||||
id: params.id,
|
||||
modificationDate: this.modificationDate,
|
||||
oc: this._oc,
|
||||
rect: this.rectangle,
|
||||
subtype,
|
||||
hasOwnCanvas: false,
|
||||
@ -1169,14 +1173,17 @@ class Annotation {
|
||||
}
|
||||
}
|
||||
|
||||
setOptionalContent(dict) {
|
||||
this.oc = null;
|
||||
|
||||
const oc = dict.get("OC");
|
||||
if (oc instanceof Name) {
|
||||
warn("setOptionalContent: Support for /Name-entry is not implemented.");
|
||||
} else if (oc instanceof Dict) {
|
||||
this.oc = oc;
|
||||
#setOptionalContent(xref, dict) {
|
||||
if (dict.has("OC")) {
|
||||
try {
|
||||
this._oc = parseMarkedContentProps(
|
||||
xref,
|
||||
dict.get("OC"),
|
||||
/* resources = */ null
|
||||
);
|
||||
} catch (ex) {
|
||||
warn(`#setOptionalContent: ${ex}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1229,13 +1236,7 @@ class Annotation {
|
||||
|
||||
const opList = new OperatorList();
|
||||
|
||||
let optionalContent;
|
||||
if (this.oc) {
|
||||
optionalContent = await evaluator.parseMarkedContentProps(
|
||||
this.oc,
|
||||
/* resources = */ null
|
||||
);
|
||||
}
|
||||
const optionalContent = this._oc;
|
||||
if (optionalContent !== undefined) {
|
||||
opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
|
||||
}
|
||||
@ -2110,13 +2111,7 @@ class WidgetAnnotation extends Annotation {
|
||||
const bbox = [0, 0, this.width, this.height];
|
||||
const transform = getTransformMatrix(this.data.rect, bbox, matrix);
|
||||
|
||||
let optionalContent;
|
||||
if (this.oc) {
|
||||
optionalContent = await evaluator.parseMarkedContentProps(
|
||||
this.oc,
|
||||
/* resources = */ null
|
||||
);
|
||||
}
|
||||
const optionalContent = this._oc;
|
||||
if (optionalContent !== undefined) {
|
||||
opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
|
||||
}
|
||||
|
||||
@ -787,6 +787,12 @@ class CFFParser {
|
||||
this.emptyPrivateDictionary(parentDict);
|
||||
return;
|
||||
}
|
||||
// The Private DICT extends past the end of the font data, which means
|
||||
// the embedded font is truncated; abort so the caller can substitute a
|
||||
// system font instead of rendering blank glyphs (issue 7625).
|
||||
if (offset + size > this.bytes.length) {
|
||||
throw new FormatError("CFF Private DICT extends past end of font");
|
||||
}
|
||||
|
||||
const privateDictEnd = offset + size;
|
||||
const dictData = this.bytes.subarray(offset, privateDictEnd);
|
||||
|
||||
@ -87,25 +87,28 @@ class XRefWrapper {
|
||||
this._getNewRef = getNewRef;
|
||||
}
|
||||
|
||||
fetch(ref) {
|
||||
return ref instanceof Ref ? this.entries[ref.num] : ref;
|
||||
}
|
||||
|
||||
fetchIfRefAsync(ref) {
|
||||
return Promise.resolve(this.fetch(ref));
|
||||
}
|
||||
|
||||
fetchIfRef(ref) {
|
||||
return this.fetch(ref);
|
||||
}
|
||||
|
||||
fetchAsync(ref) {
|
||||
return Promise.resolve(this.fetch(ref));
|
||||
}
|
||||
|
||||
getNewTemporaryRef() {
|
||||
return this._getNewRef();
|
||||
}
|
||||
|
||||
fetchIfRef(obj) {
|
||||
return obj instanceof Ref ? this.fetch(obj) : obj;
|
||||
}
|
||||
|
||||
fetch(ref) {
|
||||
if (!(ref instanceof Ref)) {
|
||||
throw new Error("ref object is not a reference");
|
||||
}
|
||||
return this.entries[ref.num];
|
||||
}
|
||||
|
||||
async fetchIfRefAsync(obj) {
|
||||
return obj instanceof Ref ? this.fetchAsync(obj) : obj;
|
||||
}
|
||||
|
||||
async fetchAsync(ref) {
|
||||
return this.fetch(ref);
|
||||
}
|
||||
}
|
||||
|
||||
class PDFEditor {
|
||||
@ -193,8 +196,7 @@ class PDFEditor {
|
||||
* @returns {Ref}
|
||||
*/
|
||||
get newRef() {
|
||||
const ref = Ref.get(this.newRefCount++, 0);
|
||||
return ref;
|
||||
return Ref.get(this.newRefCount++, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -518,9 +520,9 @@ class PDFEditor {
|
||||
attributes = [attributes];
|
||||
}
|
||||
for (let attr of attributes) {
|
||||
attr = this.xrefWrapper.fetch(attr);
|
||||
attr = this.xrefWrapper.fetchIfRef(attr);
|
||||
if (isName(attr.get("O"), "Table") && attr.has("Headers")) {
|
||||
const headers = this.xrefWrapper.fetch(attr.getRaw("Headers"));
|
||||
const headers = this.xrefWrapper.fetchIfRef(attr.getRaw("Headers"));
|
||||
if (Array.isArray(headers)) {
|
||||
for (let i = 0, ii = headers.length; i < ii; i++) {
|
||||
const newId = dedupIDs.get(
|
||||
|
||||
@ -87,6 +87,7 @@ import { getGlyphsUnicode } from "./glyphlist.js";
|
||||
import { getMetrics } from "./metrics.js";
|
||||
import { getUnicodeForGlyph } from "./unicode.js";
|
||||
import { MurmurHash3_64 } from "../shared/murmurhash3.js";
|
||||
import { parseMarkedContentProps } from "./evaluator_utils.js";
|
||||
import { PDFImage } from "./image.js";
|
||||
import { Stream } from "./stream.js";
|
||||
import { stringToPDFString } from "./string_utils.js";
|
||||
@ -1651,105 +1652,8 @@ class PartialEvaluator {
|
||||
throw new FormatError(`Unknown PatternName: ${patternName}`);
|
||||
}
|
||||
|
||||
_parseVisibilityExpression(array, nestingCounter, currentResult) {
|
||||
const MAX_NESTING = 10;
|
||||
if (++nestingCounter > MAX_NESTING) {
|
||||
warn("Visibility expression is too deeply nested");
|
||||
return;
|
||||
}
|
||||
const length = array.length;
|
||||
const operator = this.xref.fetchIfRef(array[0]);
|
||||
if (length < 2 || !(operator instanceof Name)) {
|
||||
warn("Invalid visibility expression");
|
||||
return;
|
||||
}
|
||||
switch (operator.name) {
|
||||
case "And":
|
||||
case "Or":
|
||||
case "Not":
|
||||
currentResult.push(operator.name);
|
||||
break;
|
||||
default:
|
||||
warn(`Invalid operator ${operator.name} in visibility expression`);
|
||||
return;
|
||||
}
|
||||
for (let i = 1; i < length; i++) {
|
||||
const raw = array[i];
|
||||
const object = this.xref.fetchIfRef(raw);
|
||||
if (Array.isArray(object)) {
|
||||
const nestedResult = [];
|
||||
currentResult.push(nestedResult);
|
||||
// Recursively parse a subarray.
|
||||
this._parseVisibilityExpression(object, nestingCounter, nestedResult);
|
||||
} else if (raw instanceof Ref) {
|
||||
// Reference to an OCG dictionary.
|
||||
currentResult.push(raw.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async parseMarkedContentProps(contentProperties, resources) {
|
||||
let optionalContent;
|
||||
if (contentProperties instanceof Name) {
|
||||
const properties = resources.get("Properties");
|
||||
optionalContent = properties.get(contentProperties.name);
|
||||
} else if (contentProperties instanceof Dict) {
|
||||
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 expression = optionalContent.get("VE");
|
||||
if (Array.isArray(expression)) {
|
||||
const result = [];
|
||||
this._parseVisibilityExpression(expression, 0, result);
|
||||
if (result.length > 0) {
|
||||
return {
|
||||
type: "OCMD",
|
||||
expression: result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const optionalContentGroups = optionalContent.get("OCGs");
|
||||
if (
|
||||
Array.isArray(optionalContentGroups) ||
|
||||
optionalContentGroups instanceof Dict
|
||||
) {
|
||||
const groupIds = [];
|
||||
if (Array.isArray(optionalContentGroups)) {
|
||||
for (const ocg of optionalContentGroups) {
|
||||
groupIds.push(ocg.toString());
|
||||
}
|
||||
} else {
|
||||
// Dictionary, just use the obj id.
|
||||
groupIds.push(optionalContentGroups.objId);
|
||||
}
|
||||
|
||||
return {
|
||||
type: optionalContentType,
|
||||
ids: groupIds,
|
||||
policy:
|
||||
optionalContent.get("P") instanceof Name
|
||||
? optionalContent.get("P").name
|
||||
: null,
|
||||
expression: null,
|
||||
};
|
||||
} else if (optionalContentGroups instanceof Ref) {
|
||||
return {
|
||||
type: optionalContentType,
|
||||
id: optionalContentGroups.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return parseMarkedContentProps(this.xref, contentProperties, resources);
|
||||
}
|
||||
|
||||
async getOperatorList({
|
||||
@ -4666,18 +4570,11 @@ class PartialEvaluator {
|
||||
|
||||
let fontFile, fontFileN, subtype, length1, length2, length3;
|
||||
try {
|
||||
fontFile = descriptor.get("FontFile");
|
||||
if (fontFile) {
|
||||
fontFileN = 1;
|
||||
} else {
|
||||
fontFile = descriptor.get("FontFile2");
|
||||
for (const n of ["FontFile", "FontFile2", "FontFile3"]) {
|
||||
fontFile = descriptor.get(n);
|
||||
if (fontFile) {
|
||||
fontFileN = 2;
|
||||
} else {
|
||||
fontFile = descriptor.get("FontFile3");
|
||||
if (fontFile) {
|
||||
fontFileN = 3;
|
||||
}
|
||||
fontFileN = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -4831,7 +4728,35 @@ class PartialEvaluator {
|
||||
const newProperties = await this.extractDataStructures(dict, properties);
|
||||
this.extractWidths(dict, descriptor, newProperties);
|
||||
|
||||
return new Font(fontName.name, fontFile, newProperties, this.options);
|
||||
const font = new Font(fontName.name, fontFile, newProperties, this.options);
|
||||
// The embedded font may have been too corrupt to parse, in which case
|
||||
// we ended up in the fallback path without a substitution selected.
|
||||
// Try the substitution map now so text renders in a font close to what
|
||||
// the document asked for (issue 7625).
|
||||
if (
|
||||
font.missingFile &&
|
||||
!font.systemFontInfo &&
|
||||
!isType3Font &&
|
||||
this.options.useSystemFonts
|
||||
) {
|
||||
const standardFontName = getStandardFontName(fontName.name);
|
||||
const substitution = getFontSubstitution(
|
||||
this.systemFontCache,
|
||||
this.idFactory,
|
||||
this.options.standardFontDataUrl,
|
||||
fontName.name,
|
||||
standardFontName,
|
||||
type
|
||||
);
|
||||
if (substitution) {
|
||||
if (substitution.guessFallback) {
|
||||
substitution.guessFallback = false;
|
||||
substitution.css += `,${font.fallbackName}`;
|
||||
}
|
||||
font.systemFontInfo = substitution;
|
||||
}
|
||||
}
|
||||
return font;
|
||||
}
|
||||
|
||||
static buildFontPaths(font, glyphs, handler, evaluatorOptions) {
|
||||
|
||||
123
src/core/evaluator_utils.js
Normal file
123
src/core/evaluator_utils.js
Normal file
@ -0,0 +1,123 @@
|
||||
/* Copyright 2012 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 { Dict, Name, Ref } from "./primitives.js";
|
||||
import { FormatError, warn } from "../shared/util.js";
|
||||
|
||||
function _parseVisibilityExpression(
|
||||
xref,
|
||||
array,
|
||||
nestingCounter,
|
||||
currentResult
|
||||
) {
|
||||
const MAX_NESTING = 10;
|
||||
if (++nestingCounter > MAX_NESTING) {
|
||||
warn("Visibility expression is too deeply nested");
|
||||
return;
|
||||
}
|
||||
const length = array.length;
|
||||
const operator = xref.fetchIfRef(array[0]);
|
||||
if (length < 2 || !(operator instanceof Name)) {
|
||||
warn("Invalid visibility expression");
|
||||
return;
|
||||
}
|
||||
switch (operator.name) {
|
||||
case "And":
|
||||
case "Or":
|
||||
case "Not":
|
||||
currentResult.push(operator.name);
|
||||
break;
|
||||
default:
|
||||
warn(`Invalid operator ${operator.name} in visibility expression`);
|
||||
return;
|
||||
}
|
||||
for (let i = 1; i < length; i++) {
|
||||
const raw = array[i];
|
||||
const object = xref.fetchIfRef(raw);
|
||||
if (Array.isArray(object)) {
|
||||
const nestedResult = [];
|
||||
currentResult.push(nestedResult);
|
||||
// Recursively parse a subarray.
|
||||
_parseVisibilityExpression(xref, object, nestingCounter, nestedResult);
|
||||
} else if (raw instanceof Ref) {
|
||||
// Reference to an OCG dictionary.
|
||||
currentResult.push(raw.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseMarkedContentProps(xref, contentProperties, resources) {
|
||||
let optionalContent;
|
||||
if (contentProperties instanceof Name) {
|
||||
const properties = resources.get("Properties");
|
||||
optionalContent = properties.get(contentProperties.name);
|
||||
} else if (contentProperties instanceof Dict) {
|
||||
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 expression = optionalContent.get("VE");
|
||||
if (Array.isArray(expression)) {
|
||||
const result = [];
|
||||
_parseVisibilityExpression(xref, expression, 0, result);
|
||||
if (result.length > 0) {
|
||||
return {
|
||||
type: "OCMD",
|
||||
expression: result,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const optionalContentGroups = optionalContent.get("OCGs");
|
||||
if (
|
||||
Array.isArray(optionalContentGroups) ||
|
||||
optionalContentGroups instanceof Dict
|
||||
) {
|
||||
const groupIds = [];
|
||||
if (Array.isArray(optionalContentGroups)) {
|
||||
for (const ocg of optionalContentGroups) {
|
||||
groupIds.push(ocg.toString());
|
||||
}
|
||||
} else {
|
||||
// Dictionary, just use the obj id.
|
||||
groupIds.push(optionalContentGroups.objId);
|
||||
}
|
||||
const p = optionalContent.get("P");
|
||||
|
||||
return {
|
||||
type: optionalContentType,
|
||||
ids: groupIds,
|
||||
policy: p instanceof Name ? p.name : null,
|
||||
expression: null,
|
||||
};
|
||||
} else if (optionalContentGroups instanceof Ref) {
|
||||
return {
|
||||
type: optionalContentType,
|
||||
id: optionalContentGroups.toString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export { parseMarkedContentProps };
|
||||
@ -2698,7 +2698,7 @@ class Font {
|
||||
if (
|
||||
(header.version === "OTTO" &&
|
||||
(!properties.composite ||
|
||||
(properties.fontFileN === 3 && parsedCff?.isCIDFont))) ||
|
||||
(properties.fontFileN === "FontFile3" && parsedCff?.isCIDFont))) ||
|
||||
!tables.head ||
|
||||
!tables.hhea ||
|
||||
!tables.maxp ||
|
||||
|
||||
@ -566,6 +566,13 @@ class Type1Parser {
|
||||
},
|
||||
};
|
||||
let token, length, data, lenIV;
|
||||
// Some fonts (e.g. those embedded in issue18548.pdf) define a second
|
||||
// `/Subrs` and `/CharStrings` block that the PostScript runtime selects
|
||||
// conditionally (e.g. high-resolution variants). Testing with other
|
||||
// viewers shows that none of them actually use these conditional blocks,
|
||||
// so we can "safely" ignore them.
|
||||
let subrsParsed = false;
|
||||
let charStringsParsed = false;
|
||||
while ((token = this.getToken()) !== null) {
|
||||
if (token !== "/") {
|
||||
continue;
|
||||
@ -573,6 +580,10 @@ class Type1Parser {
|
||||
token = this.getToken();
|
||||
switch (token) {
|
||||
case "CharStrings":
|
||||
if (charStringsParsed) {
|
||||
break;
|
||||
}
|
||||
charStringsParsed = true;
|
||||
// The number immediately following CharStrings must be greater or
|
||||
// equal to the number of CharStrings.
|
||||
this.getToken();
|
||||
@ -610,6 +621,10 @@ class Type1Parser {
|
||||
}
|
||||
break;
|
||||
case "Subrs":
|
||||
if (subrsParsed) {
|
||||
break;
|
||||
}
|
||||
subrsParsed = true;
|
||||
this.readInt(); // num
|
||||
this.getToken(); // read in 'array'
|
||||
while (this.getToken() === "dup") {
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
/** @typedef {import("./api").PDFPageProxy} PDFPageProxy */
|
||||
/** @typedef {import("./page_viewport").PageViewport} PageViewport */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||
@ -886,6 +888,18 @@ class AnnotationElement {
|
||||
});
|
||||
}
|
||||
|
||||
updateOC(optionalContentConfig) {
|
||||
if (!this.data.oc || !optionalContentConfig) {
|
||||
return;
|
||||
}
|
||||
const isVisible = optionalContentConfig.isVisible(this.data.oc);
|
||||
if (isVisible) {
|
||||
this.show();
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
get width() {
|
||||
return this.data.rect[2] - this.data.rect[0];
|
||||
}
|
||||
@ -3755,7 +3769,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
|
||||
* @property {TextAccessibilityManager} [accessibilityManager]
|
||||
* @property {AnnotationEditorUIManager} [annotationEditorUIManager]
|
||||
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
||||
* @property {CommentManager} [commentManager] - The comment manager instance.
|
||||
* @property {OptionalContentConfig} [optionalContentConfig]
|
||||
*/
|
||||
|
||||
/**
|
||||
@ -3827,7 +3841,7 @@ class AnnotationLayer {
|
||||
* @memberof AnnotationLayer
|
||||
*/
|
||||
async render(params) {
|
||||
const { annotations } = params;
|
||||
const { annotations, optionalContentConfig } = params;
|
||||
const layer = this.div;
|
||||
setLayerDimensions(layer, this.viewport);
|
||||
|
||||
@ -3892,6 +3906,7 @@ class AnnotationLayer {
|
||||
if (data.hidden) {
|
||||
rendered.style.visibility = "hidden";
|
||||
}
|
||||
element.updateOC(optionalContentConfig);
|
||||
|
||||
if (element._isEditable) {
|
||||
this.#editableAnnotations.set(element.data.id, element);
|
||||
@ -4052,11 +4067,14 @@ class AnnotationLayer {
|
||||
* @param {AnnotationLayerParameters} viewport
|
||||
* @memberof AnnotationLayer
|
||||
*/
|
||||
update({ viewport }) {
|
||||
update({ viewport, optionalContentConfig }) {
|
||||
const layer = this.div;
|
||||
this.viewport = viewport;
|
||||
setLayerDimensions(layer, { rotation: viewport.rotation });
|
||||
|
||||
for (const element of this.#elements) {
|
||||
element.updateOC(optionalContentConfig);
|
||||
}
|
||||
this.#setAnnotationCanvasMap();
|
||||
layer.hidden = false;
|
||||
}
|
||||
|
||||
@ -28,54 +28,32 @@ function expandBBox(array, index, minX, minY, maxX, maxY) {
|
||||
array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY);
|
||||
}
|
||||
|
||||
// Apply a scaling matrix to some min/max values.
|
||||
// If a scaling factor is negative then min and max must be swapped.
|
||||
function scaleMinMax(transform, minMax) {
|
||||
function scaleCharBBox(scaleX, scaleY, x, y, bbox) {
|
||||
let temp;
|
||||
if (transform[0]) {
|
||||
if (transform[0] < 0) {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[2];
|
||||
minMax[2] = temp;
|
||||
if (scaleX) {
|
||||
if (scaleX < 0) {
|
||||
temp = bbox[0];
|
||||
bbox[0] = bbox[2];
|
||||
bbox[2] = temp;
|
||||
}
|
||||
minMax[0] *= transform[0];
|
||||
minMax[2] *= transform[0];
|
||||
bbox[0] *= scaleX;
|
||||
bbox[2] *= scaleX;
|
||||
|
||||
if (transform[3] < 0) {
|
||||
temp = minMax[1];
|
||||
minMax[1] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
if (scaleY < 0) {
|
||||
temp = bbox[1];
|
||||
bbox[1] = bbox[3];
|
||||
bbox[3] = temp;
|
||||
}
|
||||
minMax[1] *= transform[3];
|
||||
minMax[3] *= transform[3];
|
||||
bbox[1] *= scaleY;
|
||||
bbox[3] *= scaleY;
|
||||
} else {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[1];
|
||||
minMax[1] = temp;
|
||||
temp = minMax[2];
|
||||
minMax[2] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
|
||||
if (transform[1] < 0) {
|
||||
temp = minMax[1];
|
||||
minMax[1] = minMax[3];
|
||||
minMax[3] = temp;
|
||||
}
|
||||
minMax[1] *= transform[1];
|
||||
minMax[3] *= transform[1];
|
||||
|
||||
if (transform[2] < 0) {
|
||||
temp = minMax[0];
|
||||
minMax[0] = minMax[2];
|
||||
minMax[2] = temp;
|
||||
}
|
||||
minMax[0] *= transform[2];
|
||||
minMax[2] *= transform[2];
|
||||
bbox.fill(0);
|
||||
}
|
||||
minMax[0] += transform[4];
|
||||
minMax[1] += transform[5];
|
||||
minMax[2] += transform[4];
|
||||
minMax[3] += transform[5];
|
||||
bbox[0] += x;
|
||||
bbox[1] += y;
|
||||
bbox[2] += x;
|
||||
bbox[3] += y;
|
||||
}
|
||||
|
||||
// This is computed rathter than hard-coded to keep into
|
||||
@ -663,7 +641,7 @@ class CanvasDependencyTracker {
|
||||
computedBBox = [0, 0, 0, 0];
|
||||
Util.axialAlignedBoundingBox(fontBBox, font.fontMatrix, computedBBox);
|
||||
if (scale !== 1 || x !== 0 || y !== 0) {
|
||||
scaleMinMax([scale, 0, 0, -scale, x, y], computedBBox);
|
||||
scaleCharBBox(scale, -scale, x, y, computedBBox);
|
||||
}
|
||||
|
||||
if (isBBoxTrustworthy) {
|
||||
|
||||
@ -242,7 +242,8 @@ class Rasterize {
|
||||
fieldObjects,
|
||||
page,
|
||||
imageResourcesPath,
|
||||
renderForms = false
|
||||
renderForms = false,
|
||||
optionalContentConfigPromise = null
|
||||
) {
|
||||
try {
|
||||
const { svg, foreignObject, style, div } = this.createContainer(viewport);
|
||||
@ -263,6 +264,7 @@ class Rasterize {
|
||||
imageResourcesPath,
|
||||
renderForms,
|
||||
fieldObjects,
|
||||
optionalContentConfig: await optionalContentConfigPromise,
|
||||
};
|
||||
|
||||
// Ensure that the annotationLayer gets translated.
|
||||
@ -1355,7 +1357,8 @@ class Driver {
|
||||
task.fieldObjects,
|
||||
page,
|
||||
IMAGE_RESOURCES_PATH,
|
||||
renderForms
|
||||
renderForms,
|
||||
task.optionalContentConfigPromise
|
||||
).then(() => {
|
||||
completeRender(false);
|
||||
});
|
||||
|
||||
@ -2271,9 +2271,7 @@ describe("Highlight Editor", () => {
|
||||
const pdfData = fs.readFileSync(pdfPath).toString("base64");
|
||||
const dataTransfer = await page.evaluateHandle(data => {
|
||||
const transfer = new DataTransfer();
|
||||
const view = Uint8Array.from(atob(data), code =>
|
||||
code.charCodeAt(0)
|
||||
);
|
||||
const view = Uint8Array.fromBase64(data);
|
||||
const file = new File([view], "basicapi.pdf", {
|
||||
type: "application/pdf",
|
||||
});
|
||||
|
||||
1
test/pdfs/.gitignore
vendored
1
test/pdfs/.gitignore
vendored
@ -922,3 +922,4 @@
|
||||
!knockout_groups_test.pdf
|
||||
!issue18032.pdf
|
||||
!Embedded_font.pdf
|
||||
!issue18548_reduced.pdf
|
||||
|
||||
BIN
test/pdfs/issue18548_reduced.pdf
Normal file
BIN
test/pdfs/issue18548_reduced.pdf
Normal file
Binary file not shown.
1
test/pdfs/issue20433.pdf.link
Normal file
1
test/pdfs/issue20433.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
https://web.archive.org/web/20251107005559/https://octopdf.com/octopdf-sample.pdf
|
||||
1
test/pdfs/issue7625.pdf.link
Normal file
1
test/pdfs/issue7625.pdf.link
Normal file
@ -0,0 +1 @@
|
||||
https://github.com/mozilla/pdf.js/files/467169/Er.aestetik.en.loftestang.for.laering.pdf
|
||||
@ -1956,6 +1956,42 @@
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue20433-initial",
|
||||
"file": "pdfs/issue20433.pdf",
|
||||
"md5": "3a550da7807540982ed457397667db79",
|
||||
"link": true,
|
||||
"rounds": 1,
|
||||
"firstPage": 2,
|
||||
"type": "eq",
|
||||
"forms": true
|
||||
},
|
||||
{
|
||||
"id": "issue20433-no-form",
|
||||
"file": "pdfs/issue20433.pdf",
|
||||
"md5": "3a550da7807540982ed457397667db79",
|
||||
"link": true,
|
||||
"rounds": 1,
|
||||
"firstPage": 2,
|
||||
"type": "eq",
|
||||
"forms": true,
|
||||
"optionalContent": {
|
||||
"73R": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "issue20433-no-mathml",
|
||||
"file": "pdfs/issue20433.pdf",
|
||||
"md5": "3a550da7807540982ed457397667db79",
|
||||
"link": true,
|
||||
"rounds": 1,
|
||||
"firstPage": 2,
|
||||
"type": "eq",
|
||||
"forms": true,
|
||||
"optionalContent": {
|
||||
"115R": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "issue13845",
|
||||
"file": "pdfs/issue13845.pdf",
|
||||
@ -14267,5 +14303,22 @@
|
||||
"md5": "b68dd5a3e6833d1af94e295fe1d60285",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue18548_reduced",
|
||||
"file": "pdfs/issue18548_reduced.pdf",
|
||||
"md5": "39d15f7f810bd89a4e5858df9c75ca4e",
|
||||
"rounds": 1,
|
||||
"type": "eq"
|
||||
},
|
||||
{
|
||||
"id": "issue7625",
|
||||
"file": "pdfs/issue7625.pdf",
|
||||
"md5": "77ca0a41da767dca31ca45219e2ae202",
|
||||
"link": true,
|
||||
"rounds": 1,
|
||||
"firstPage": 1,
|
||||
"lastPage": 1,
|
||||
"type": "eq"
|
||||
}
|
||||
]
|
||||
|
||||
@ -63,6 +63,7 @@ import { PresentationModeState } from "./ui_utils.js";
|
||||
* @property {PageViewport} viewport
|
||||
* @property {string} [intent] - The default value is "display".
|
||||
* @property {StructTreeLayerBuilder} [structTreeLayer]
|
||||
* @property {Promise} [optionalContentConfigPromise]
|
||||
*/
|
||||
|
||||
class AnnotationLayerBuilder {
|
||||
@ -125,8 +126,15 @@ class AnnotationLayerBuilder {
|
||||
* @returns {Promise<void>} A promise that is resolved when rendering of the
|
||||
* annotations is complete.
|
||||
*/
|
||||
async render({ viewport, intent = "display", structTreeLayer = null }) {
|
||||
async render({
|
||||
viewport,
|
||||
intent = "display",
|
||||
structTreeLayer = null,
|
||||
optionalContentConfigPromise = null,
|
||||
}) {
|
||||
if (this.div) {
|
||||
const optionalContentConfig = await optionalContentConfigPromise;
|
||||
|
||||
if (this._cancelled || !this.annotationLayer) {
|
||||
return;
|
||||
}
|
||||
@ -134,15 +142,18 @@ class AnnotationLayerBuilder {
|
||||
// transformation matrices.
|
||||
this.annotationLayer.update({
|
||||
viewport: viewport.clone({ dontFlip: true }),
|
||||
optionalContentConfig,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const [annotations, hasJSActions, fieldObjects] = await Promise.all([
|
||||
this.pdfPage.getAnnotations({ intent }),
|
||||
this._hasJSActionsPromise,
|
||||
this._fieldObjectsPromise,
|
||||
]);
|
||||
const [annotations, hasJSActions, fieldObjects, optionalContentConfig] =
|
||||
await Promise.all([
|
||||
this.pdfPage.getAnnotations({ intent }),
|
||||
this._hasJSActionsPromise,
|
||||
this._fieldObjectsPromise,
|
||||
optionalContentConfigPromise,
|
||||
]);
|
||||
if (this._cancelled) {
|
||||
return;
|
||||
}
|
||||
@ -169,6 +180,7 @@ class AnnotationLayerBuilder {
|
||||
enableScripting: this.enableScripting,
|
||||
hasJSActions,
|
||||
fieldObjects,
|
||||
optionalContentConfig,
|
||||
});
|
||||
|
||||
this.#annotations = annotations;
|
||||
|
||||
@ -464,6 +464,7 @@ class PDFPageView extends BasePDFPageView {
|
||||
viewport: this.viewport,
|
||||
intent: "display",
|
||||
structTreeLayer: this.structTreeLayer,
|
||||
optionalContentConfigPromise: this._optionalContentConfigPromise,
|
||||
});
|
||||
} catch (ex) {
|
||||
console.error("#renderAnnotationLayer:", ex);
|
||||
|
||||
@ -37,6 +37,10 @@ import { XfaLayer } from "pdfjs-lib";
|
||||
*/
|
||||
|
||||
class XfaLayerBuilder {
|
||||
#cancelled = false;
|
||||
|
||||
div = null;
|
||||
|
||||
/**
|
||||
* @param {XfaLayerBuilderOptions} options
|
||||
*/
|
||||
@ -50,9 +54,6 @@ class XfaLayerBuilder {
|
||||
this.annotationStorage = annotationStorage;
|
||||
this.linkService = linkService;
|
||||
this.xfaHtml = xfaHtml;
|
||||
|
||||
this.div = null;
|
||||
this._cancelled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -62,50 +63,33 @@ class XfaLayerBuilder {
|
||||
* with a `textDivs` property that can be used with the TextHighlighter.
|
||||
*/
|
||||
async render({ viewport, intent = "display" }) {
|
||||
let xfaHtml;
|
||||
if (intent === "print") {
|
||||
const parameters = {
|
||||
viewport: viewport.clone({ dontFlip: true }),
|
||||
div: this.div,
|
||||
xfaHtml: this.xfaHtml,
|
||||
annotationStorage: this.annotationStorage,
|
||||
linkService: this.linkService,
|
||||
intent,
|
||||
};
|
||||
xfaHtml = this.xfaHtml;
|
||||
} else {
|
||||
xfaHtml = await this.pdfPage.getXfa();
|
||||
|
||||
// Create an xfa layer div and render the form
|
||||
this.div = document.createElement("div");
|
||||
parameters.div = this.div;
|
||||
|
||||
return XfaLayer.render(parameters);
|
||||
if (this.#cancelled || !xfaHtml) {
|
||||
return { textDivs: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// intent === "display"
|
||||
const xfaHtml = await this.pdfPage.getXfa();
|
||||
if (this._cancelled || !xfaHtml) {
|
||||
return { textDivs: [] };
|
||||
}
|
||||
|
||||
const parameters = {
|
||||
// Create an xfa layer div and render the form
|
||||
const hasDiv = !!this.div;
|
||||
const params = {
|
||||
viewport: viewport.clone({ dontFlip: true }),
|
||||
div: this.div,
|
||||
div: (this.div ??= document.createElement("div")),
|
||||
xfaHtml,
|
||||
annotationStorage: this.annotationStorage,
|
||||
linkService: this.linkService,
|
||||
intent,
|
||||
};
|
||||
|
||||
if (this.div) {
|
||||
return XfaLayer.update(parameters);
|
||||
}
|
||||
// Create an xfa layer div and render the form
|
||||
this.div = document.createElement("div");
|
||||
parameters.div = this.div;
|
||||
|
||||
return XfaLayer.render(parameters);
|
||||
return hasDiv ? XfaLayer.update(params) : XfaLayer.render(params);
|
||||
}
|
||||
|
||||
cancel() {
|
||||
this._cancelled = true;
|
||||
this.#cancelled = true;
|
||||
}
|
||||
|
||||
hide() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user