Compare commits

..

9 Commits

Author SHA1 Message Date
Jonas Jenwald
c6b9338f6f
Merge pull request #19719 from Snuffleupagus/structTree-xref
Pass the `XRef`-instance explicitly to the `StructTreeRoot` class, and simplify the `StructElementNode.prototype.role` getter
2025-03-25 19:41:20 +01:00
Jonas Jenwald
74ab29544e
Merge pull request #19701 from Snuffleupagus/enableAutoLinking-true
[api-minor] Enable `enableAutoLinking` by default
2025-03-25 18:18:08 +01:00
Jonas Jenwald
22107145df
Merge pull request #19691 from Snuffleupagus/xfa-template-rm-reduce
Remove `Array.prototype.reduce` usage from the `src/core/xfa/template.js` file
2025-03-25 18:17:12 +01:00
Jonas Jenwald
532406427b Simplify the StructElementNode.prototype.role getter a tiny bit
Given that the `roleMap` only contains `Name`-strings, the lookup can be simplified thanks to modern JavaScript features.
2025-03-25 18:04:56 +01:00
Jonas Jenwald
3c93d63731 Pass the XRef-instance explicitly to the StructTreeRoot class
This avoids the current situation where we're accessing it through various dictionaries, since that's a somewhat brittle solution given that in the general case a `Dict`-instance may not have the `xref`-field set (see e.g. the empty-Dict).
2025-03-25 18:04:51 +01:00
calixteman
d4c7aaa9d3
Merge pull request #19702 from calixteman/issue19694
In the struct tree a kid can be a reference to an MCID entry (issue #19694)
2025-03-25 18:02:46 +01:00
Calixte Denizet
dee80cb082 In the struct tree a kid can be a reference to an MCID entry (issue #19694) 2025-03-25 15:01:52 +01:00
Jonas Jenwald
9456b88f6b Remove Array.prototype.reduce usage from the src/core/xfa/template.js file
Using `Array.prototype.reduce` often leads to less readable code, and in these cases we can replace that with other Array-methods instead.
2025-03-24 16:40:41 +01:00
Jonas Jenwald
abcddb51e0 [api-minor] Enable enableAutoLinking by default
It's already enabled by default in Firefox, and since there's no open issues regarding auto-linking I suppose that we can attempt to enable it unconditionally.
2025-03-21 11:49:03 +01:00
8 changed files with 44 additions and 64 deletions

View File

@ -228,7 +228,7 @@
"enableAutoLinking": { "enableAutoLinking": {
"description": "Enable creation of hyperlinks from text that look like URLs.", "description": "Enable creation of hyperlinks from text that look like URLs.",
"type": "boolean", "type": "boolean",
"default": false "default": true
} }
} }
} }

View File

@ -262,7 +262,7 @@ class Catalog {
get structTreeRoot() { get structTreeRoot() {
let structTree = null; let structTree = null;
try { try {
structTree = this._readStructTreeRoot(); structTree = this.#readStructTreeRoot();
} catch (ex) { } catch (ex) {
if (ex instanceof MissingDataException) { if (ex instanceof MissingDataException) {
throw ex; throw ex;
@ -272,17 +272,14 @@ class Catalog {
return shadow(this, "structTreeRoot", structTree); return shadow(this, "structTreeRoot", structTree);
} }
/** #readStructTreeRoot() {
* @private
*/
_readStructTreeRoot() {
const rawObj = this._catDict.getRaw("StructTreeRoot"); const rawObj = this._catDict.getRaw("StructTreeRoot");
const obj = this.xref.fetchIfRef(rawObj); const obj = this.xref.fetchIfRef(rawObj);
if (!(obj instanceof Dict)) { if (!(obj instanceof Dict)) {
return null; return null;
} }
const root = new StructTreeRoot(obj, rawObj); const root = new StructTreeRoot(this.xref, obj, rawObj);
root.init(); root.init();
return root; return root;
} }

View File

@ -29,7 +29,8 @@ const StructElementType = {
}; };
class StructTreeRoot { class StructTreeRoot {
constructor(rootDict, rootRef) { constructor(xref, rootDict, rootRef) {
this.xref = xref;
this.dict = rootDict; this.dict = rootDict;
this.ref = rootRef instanceof Ref ? rootRef : null; this.ref = rootRef instanceof Ref ? rootRef : null;
this.roleMap = new Map(); this.roleMap = new Map();
@ -158,7 +159,7 @@ class StructTreeRoot {
} }
} }
async canUpdateStructTree({ pdfManager, xref, newAnnotationsByPage }) { async canUpdateStructTree({ pdfManager, newAnnotationsByPage }) {
if (!this.ref) { if (!this.ref) {
warn("Cannot update the struct tree: no root reference."); warn("Cannot update the struct tree: no root reference.");
return false; return false;
@ -180,7 +181,7 @@ class StructTreeRoot {
warn("Cannot update the struct tree: nums isn't an array."); warn("Cannot update the struct tree: nums isn't an array.");
return false; return false;
} }
const numberTree = new NumberTree(parentTree, xref); const numberTree = new NumberTree(parentTree, this.xref);
for (const pageIndex of newAnnotationsByPage.keys()) { for (const pageIndex of newAnnotationsByPage.keys()) {
const { pageDict } = await pdfManager.getPage(pageIndex); const { pageDict } = await pdfManager.getPage(pageIndex);
@ -201,7 +202,7 @@ class StructTreeRoot {
const { pageDict } = await pdfManager.getPage(pageIndex); const { pageDict } = await pdfManager.getPage(pageIndex);
StructTreeRoot.#collectParents({ StructTreeRoot.#collectParents({
elements, elements,
xref: this.dict.xref, xref: this.xref,
pageDict, pageDict,
numberTree, numberTree,
}); });
@ -233,9 +234,8 @@ class StructTreeRoot {
} }
async updateStructureTree({ newAnnotationsByPage, pdfManager, changes }) { async updateStructureTree({ newAnnotationsByPage, pdfManager, changes }) {
const xref = this.dict.xref; const { ref: structTreeRootRef, xref } = this;
const structTreeRoot = this.dict.clone(); const structTreeRoot = this.dict.clone();
const structTreeRootRef = this.ref;
const cache = new RefSetCache(); const cache = new RefSetCache();
cache.put(structTreeRootRef, structTreeRoot); cache.put(structTreeRootRef, structTreeRoot);
@ -541,6 +541,7 @@ class StructTreeRoot {
class StructElementNode { class StructElementNode {
constructor(tree, dict) { constructor(tree, dict) {
this.tree = tree; this.tree = tree;
this.xref = tree.xref;
this.dict = dict; this.dict = dict;
this.kids = []; this.kids = [];
this.parseKids(); this.parseKids();
@ -550,10 +551,7 @@ class StructElementNode {
const nameObj = this.dict.get("S"); const nameObj = this.dict.get("S");
const name = nameObj instanceof Name ? nameObj.name : ""; const name = nameObj instanceof Name ? nameObj.name : "";
const { root } = this.tree; const { root } = this.tree;
if (root.roleMap.has(name)) { return root.roleMap.get(name) ?? name;
return root.roleMap.get(name);
}
return name;
} }
parseKids() { parseKids() {
@ -565,7 +563,7 @@ class StructElementNode {
const kids = this.dict.get("K"); const kids = this.dict.get("K");
if (Array.isArray(kids)) { if (Array.isArray(kids)) {
for (const kid of kids) { for (const kid of kids) {
const element = this.parseKid(pageObjId, kid); const element = this.parseKid(pageObjId, this.xref.fetchIfRef(kid));
if (element) { if (element) {
this.kids.push(element); this.kids.push(element);
} }
@ -592,33 +590,26 @@ class StructElementNode {
}); });
} }
// Find the dictionary for the kid. if (!(kid instanceof Dict)) {
let kidDict = null;
if (kid instanceof Ref) {
kidDict = this.dict.xref.fetch(kid);
} else if (kid instanceof Dict) {
kidDict = kid;
}
if (!kidDict) {
return null; return null;
} }
const pageRef = kidDict.getRaw("Pg");
const pageRef = kid.getRaw("Pg");
if (pageRef instanceof Ref) { if (pageRef instanceof Ref) {
pageObjId = pageRef.toString(); pageObjId = pageRef.toString();
} }
const type = const type = kid.get("Type") instanceof Name ? kid.get("Type").name : null;
kidDict.get("Type") instanceof Name ? kidDict.get("Type").name : null;
if (type === "MCR") { if (type === "MCR") {
if (this.tree.pageDict.objId !== pageObjId) { if (this.tree.pageDict.objId !== pageObjId) {
return null; return null;
} }
const kidRef = kidDict.getRaw("Stm"); const kidRef = kid.getRaw("Stm");
return new StructElement({ return new StructElement({
type: StructElementType.STREAM_CONTENT, type: StructElementType.STREAM_CONTENT,
refObjId: kidRef instanceof Ref ? kidRef.toString() : null, refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
pageObjId, pageObjId,
mcid: kidDict.get("MCID"), mcid: kid.get("MCID"),
}); });
} }
@ -626,7 +617,7 @@ class StructElementNode {
if (this.tree.pageDict.objId !== pageObjId) { if (this.tree.pageDict.objId !== pageObjId) {
return null; return null;
} }
const kidRef = kidDict.getRaw("Obj"); const kidRef = kid.getRaw("Obj");
return new StructElement({ return new StructElement({
type: StructElementType.OBJECT, type: StructElementType.OBJECT,
refObjId: kidRef instanceof Ref ? kidRef.toString() : null, refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
@ -636,7 +627,7 @@ class StructElementNode {
return new StructElement({ return new StructElement({
type: StructElementType.ELEMENT, type: StructElementType.ELEMENT,
dict: kidDict, dict: kid,
}); });
} }
} }
@ -661,7 +652,8 @@ class StructElement {
class StructTreePage { class StructTreePage {
constructor(structTreeRoot, pageDict) { constructor(structTreeRoot, pageDict) {
this.root = structTreeRoot; this.root = structTreeRoot;
this.rootDict = structTreeRoot ? structTreeRoot.dict : null; this.xref = structTreeRoot?.xref ?? null;
this.rootDict = structTreeRoot?.dict ?? null;
this.pageDict = pageDict; this.pageDict = pageDict;
this.nodes = []; this.nodes = [];
} }
@ -687,7 +679,7 @@ class StructTreePage {
} }
const map = new Map(); const map = new Map();
const numberTree = new NumberTree(parentTree, this.rootDict.xref); const numberTree = new NumberTree(parentTree, this.xref);
for (const [elemId] of ids) { for (const [elemId] of ids) {
const obj = numberTree.getRaw(elemId); const obj = numberTree.getRaw(elemId);
@ -714,14 +706,14 @@ class StructTreePage {
} }
const map = new Map(); const map = new Map();
const numberTree = new NumberTree(parentTree, this.rootDict.xref); const numberTree = new NumberTree(parentTree, this.xref);
if (Number.isInteger(id)) { if (Number.isInteger(id)) {
const parentArray = numberTree.get(id); const parentArray = numberTree.get(id);
if (Array.isArray(parentArray)) { if (Array.isArray(parentArray)) {
for (const ref of parentArray) { for (const ref of parentArray) {
if (ref instanceof Ref) { if (ref instanceof Ref) {
this.addNode(this.rootDict.xref.fetch(ref), map); this.addNode(this.xref.fetch(ref), map);
} }
} }
} }
@ -733,7 +725,7 @@ class StructTreePage {
for (const [elemId, type] of ids) { for (const [elemId, type] of ids) {
const obj = numberTree.get(elemId); const obj = numberTree.get(elemId);
if (obj) { if (obj) {
const elem = this.addNode(this.rootDict.xref.fetchIfRef(obj), map); const elem = this.addNode(this.xref.fetchIfRef(obj), map);
if ( if (
elem?.kids?.length === 1 && elem?.kids?.length === 1 &&
elem.kids[0].type === StructElementType.OBJECT elem.kids[0].type === StructElementType.OBJECT

View File

@ -558,7 +558,6 @@ class WorkerMessageHandler {
} else if ( } else if (
await _structTreeRoot.canUpdateStructTree({ await _structTreeRoot.canUpdateStructTree({
pdfManager, pdfManager,
xref,
newAnnotationsByPage, newAnnotationsByPage,
}) })
) { ) {

View File

@ -5721,12 +5721,7 @@ class Text extends ContentObject {
if (typeof this[$content] === "string") { if (typeof this[$content] === "string") {
return this[$content] return this[$content]
.split(/[\u2029\u2028\n]/) .split(/[\u2029\u2028\n]/)
.reduce((acc, line) => { .filter(line => !!line)
if (line) {
acc.push(line);
}
return acc;
}, [])
.join("\n"); .join("\n");
} }
return this[$content][$text](); return this[$content][$text]();
@ -5748,18 +5743,15 @@ class Text extends ContentObject {
.map(para => .map(para =>
// Convert a paragraph into a set of <span> (for lines) // Convert a paragraph into a set of <span> (for lines)
// separated by <br>. // separated by <br>.
para.split(/[\u2028\n]/).reduce((acc, line) => { para.split(/[\u2028\n]/).flatMap(line => [
acc.push( {
{ name: "span",
name: "span", value: line,
value: line, },
}, {
{ name: "br",
name: "br", },
} ])
);
return acc;
}, [])
) )
.forEach(lines => { .forEach(lines => {
html.children.push({ html.children.push({

View File

@ -202,7 +202,7 @@ const defaultOptions = {
}, },
enableAutoLinking: { enableAutoLinking: {
/** @type {boolean} */ /** @type {boolean} */
value: typeof PDFJSDev === "undefined" || PDFJSDev.test("MOZCENTRAL"), value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE, kind: OptionKind.VIEWER + OptionKind.PREFERENCE,
}, },
enableDetailCanvas: { enableDetailCanvas: {

View File

@ -95,7 +95,7 @@ import { XfaLayerBuilder } from "./xfa_layer_builder.js";
* @property {boolean} [enableHWA] - Enables hardware acceleration for * @property {boolean} [enableHWA] - Enables hardware acceleration for
* rendering. The default value is `false`. * rendering. The default value is `false`.
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
* text that look like URLs. The default value is `false`. * text that look like URLs. The default value is `true`.
*/ */
const DEFAULT_LAYER_PROPERTIES = const DEFAULT_LAYER_PROPERTIES =
@ -130,7 +130,7 @@ class PDFPageView extends BasePDFPageView {
#canvasWrapper = null; #canvasWrapper = null;
#enableAutoLinking = false; #enableAutoLinking = true;
#hasRestrictedScaling = false; #hasRestrictedScaling = false;
@ -188,7 +188,7 @@ class PDFPageView extends BasePDFPageView {
this.maxCanvasPixels = this.maxCanvasPixels =
options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels"); options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
this.maxCanvasDim = options.maxCanvasDim || AppOptions.get("maxCanvasDim"); this.maxCanvasDim = options.maxCanvasDim || AppOptions.get("maxCanvasDim");
this.#enableAutoLinking = options.enableAutoLinking || false; this.#enableAutoLinking = options.enableAutoLinking !== false;
this.l10n = options.l10n; this.l10n = options.l10n;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {

View File

@ -137,7 +137,7 @@ function isValidAnnotationEditorMode(mode) {
* @property {boolean} [supportsPinchToZoom] - Enable zooming on pinch gesture. * @property {boolean} [supportsPinchToZoom] - Enable zooming on pinch gesture.
* The default value is `true`. * The default value is `true`.
* @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from * @property {boolean} [enableAutoLinking] - Enable creation of hyperlinks from
* text that look like URLs. The default value is `false`. * text that look like URLs. The default value is `true`.
*/ */
class PDFPageViewBuffer { class PDFPageViewBuffer {
@ -238,7 +238,7 @@ class PDFViewer {
#enableNewAltTextWhenAddingImage = false; #enableNewAltTextWhenAddingImage = false;
#enableAutoLinking = false; #enableAutoLinking = true;
#eventAbortController = null; #eventAbortController = null;
@ -340,7 +340,7 @@ class PDFViewer {
this.#mlManager = options.mlManager || null; this.#mlManager = options.mlManager || null;
this.#enableHWA = options.enableHWA || false; this.#enableHWA = options.enableHWA || false;
this.#supportsPinchToZoom = options.supportsPinchToZoom !== false; this.#supportsPinchToZoom = options.supportsPinchToZoom !== false;
this.#enableAutoLinking = options.enableAutoLinking || false; this.#enableAutoLinking = options.enableAutoLinking !== false;
this.defaultRenderingQueue = !options.renderingQueue; this.defaultRenderingQueue = !options.renderingQueue;
if ( if (