mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-20 15:18:08 +02:00
Merge pull request #14424 from Snuffleupagus/mv-addLinkAttributes
[api-minor] Move `addLinkAttributes`, `LinkTarget`, and `removeNullCharacters` into the viewer (PR 14092 follow-up)
This commit is contained in:
commit
8ac0ccc227
10 changed files with 231 additions and 246 deletions
|
@ -46,7 +46,6 @@ import {
|
|||
GlobalWorkerOptions,
|
||||
InvalidPDFException,
|
||||
isPdfFile,
|
||||
LinkTarget,
|
||||
loadScript,
|
||||
MissingPDFException,
|
||||
OPS,
|
||||
|
@ -57,6 +56,7 @@ import {
|
|||
version,
|
||||
} from "pdfjs-lib";
|
||||
import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
|
||||
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
|
||||
import { OverlayManager } from "./overlay_manager.js";
|
||||
import { PasswordPrompt } from "./password_prompt.js";
|
||||
import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
|
||||
|
@ -65,7 +65,6 @@ import { PDFFindBar } from "./pdf_find_bar.js";
|
|||
import { PDFFindController } from "./pdf_find_controller.js";
|
||||
import { PDFHistory } from "./pdf_history.js";
|
||||
import { PDFLayerViewer } from "./pdf_layer_viewer.js";
|
||||
import { PDFLinkService } from "./pdf_link_service.js";
|
||||
import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
|
||||
import { PDFPresentationMode } from "./pdf_presentation_mode.js";
|
||||
import { PDFRenderingQueue } from "./pdf_rendering_queue.js";
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { removeNullCharacters } from "pdfjs-lib";
|
||||
import { removeNullCharacters } from "./ui_utils.js";
|
||||
|
||||
const TREEITEM_OFFSET_TOP = -100; // px
|
||||
const TREEITEM_SELECTED_CLASS = "selected";
|
||||
|
|
|
@ -16,8 +16,72 @@
|
|||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
|
||||
|
||||
import { addLinkAttributes, LinkTarget } from "pdfjs-lib";
|
||||
import { parseQueryString } from "./ui_utils.js";
|
||||
import { parseQueryString, removeNullCharacters } from "./ui_utils.js";
|
||||
|
||||
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
|
||||
|
||||
const LinkTarget = {
|
||||
NONE: 0, // Default value.
|
||||
SELF: 1,
|
||||
BLANK: 2,
|
||||
PARENT: 3,
|
||||
TOP: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef ExternalLinkParameters
|
||||
* @typedef {Object} ExternalLinkParameters
|
||||
* @property {string} url - An absolute URL.
|
||||
* @property {LinkTarget} [target] - The link target. The default value is
|
||||
* `LinkTarget.NONE`.
|
||||
* @property {string} [rel] - The link relationship. The default value is
|
||||
* `DEFAULT_LINK_REL`.
|
||||
* @property {boolean} [enabled] - Whether the link should be enabled. The
|
||||
* default value is true.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Adds various attributes (href, title, target, rel) to hyperlinks.
|
||||
* @param {HTMLAnchorElement} link - The link element.
|
||||
* @param {ExternalLinkParameters} params
|
||||
*/
|
||||
function addLinkAttributes(link, { url, target, rel, enabled = true } = {}) {
|
||||
if (!url || typeof url !== "string") {
|
||||
throw new Error('A valid "url" parameter must provided.');
|
||||
}
|
||||
|
||||
const urlNullRemoved = removeNullCharacters(url);
|
||||
if (enabled) {
|
||||
link.href = link.title = urlNullRemoved;
|
||||
} else {
|
||||
link.href = "";
|
||||
link.title = `Disabled: ${urlNullRemoved}`;
|
||||
link.onclick = () => {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
let targetStr = ""; // LinkTarget.NONE
|
||||
switch (target) {
|
||||
case LinkTarget.NONE:
|
||||
break;
|
||||
case LinkTarget.SELF:
|
||||
targetStr = "_self";
|
||||
break;
|
||||
case LinkTarget.BLANK:
|
||||
targetStr = "_blank";
|
||||
break;
|
||||
case LinkTarget.PARENT:
|
||||
targetStr = "_parent";
|
||||
break;
|
||||
case LinkTarget.TOP:
|
||||
targetStr = "_top";
|
||||
break;
|
||||
}
|
||||
link.target = targetStr;
|
||||
|
||||
link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @typedef {Object} PDFLinkServiceOptions
|
||||
|
@ -38,6 +102,8 @@ import { parseQueryString } from "./ui_utils.js";
|
|||
* @implements {IPDFLinkService}
|
||||
*/
|
||||
class PDFLinkService {
|
||||
#pagesRefCache = new Map();
|
||||
|
||||
/**
|
||||
* @param {PDFLinkServiceOptions} options
|
||||
*/
|
||||
|
@ -57,14 +123,12 @@ class PDFLinkService {
|
|||
this.pdfDocument = null;
|
||||
this.pdfViewer = null;
|
||||
this.pdfHistory = null;
|
||||
|
||||
this._pagesRefCache = null;
|
||||
}
|
||||
|
||||
setDocument(pdfDocument, baseUrl = null) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.pdfDocument = pdfDocument;
|
||||
this._pagesRefCache = Object.create(null);
|
||||
this.#pagesRefCache.clear();
|
||||
}
|
||||
|
||||
setViewer(pdfViewer) {
|
||||
|
@ -110,10 +174,7 @@ class PDFLinkService {
|
|||
this.pdfViewer.pagesRotation = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
|
||||
#goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
|
||||
// Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
|
||||
const destRef = explicitDest[0];
|
||||
let pageNumber;
|
||||
|
@ -128,11 +189,11 @@ class PDFLinkService {
|
|||
.getPageIndex(destRef)
|
||||
.then(pageIndex => {
|
||||
this.cachePageRef(pageIndex + 1, destRef);
|
||||
this._goToDestinationHelper(rawDest, namedDest, explicitDest);
|
||||
this.#goToDestinationHelper(rawDest, namedDest, explicitDest);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error(
|
||||
`PDFLinkService._goToDestinationHelper: "${destRef}" is not ` +
|
||||
`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
|
||||
`a valid page reference, for dest="${rawDest}".`
|
||||
);
|
||||
});
|
||||
|
@ -142,14 +203,14 @@ class PDFLinkService {
|
|||
pageNumber = destRef + 1;
|
||||
} else {
|
||||
console.error(
|
||||
`PDFLinkService._goToDestinationHelper: "${destRef}" is not ` +
|
||||
`PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
|
||||
`a valid destination reference, for dest="${rawDest}".`
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
|
||||
console.error(
|
||||
`PDFLinkService._goToDestinationHelper: "${pageNumber}" is not ` +
|
||||
`PDFLinkService.#goToDestinationHelper: "${pageNumber}" is not ` +
|
||||
`a valid page number, for dest="${rawDest}".`
|
||||
);
|
||||
return;
|
||||
|
@ -193,7 +254,7 @@ class PDFLinkService {
|
|||
);
|
||||
return;
|
||||
}
|
||||
this._goToDestinationHelper(dest, namedDest, explicitDest);
|
||||
this.#goToDestinationHelper(dest, namedDest, explicitDest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,7 +291,7 @@ class PDFLinkService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the `addLinkAttributes`-function in the API.
|
||||
* Wrapper around the `addLinkAttributes` helper function.
|
||||
* @param {HTMLAnchorElement} link
|
||||
* @param {string} url
|
||||
* @param {boolean} [newWindow]
|
||||
|
@ -340,8 +401,7 @@ class PDFLinkService {
|
|||
}
|
||||
} else {
|
||||
console.error(
|
||||
`PDFLinkService.setHash: "${zoomArg}" is not ` +
|
||||
"a valid zoom value."
|
||||
`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -379,13 +439,17 @@ class PDFLinkService {
|
|||
}
|
||||
} catch (ex) {}
|
||||
|
||||
if (typeof dest === "string" || isValidExplicitDestination(dest)) {
|
||||
if (
|
||||
typeof dest === "string" ||
|
||||
PDFLinkService.#isValidExplicitDestination(dest)
|
||||
) {
|
||||
this.goToDestination(dest);
|
||||
return;
|
||||
}
|
||||
console.error(
|
||||
`PDFLinkService.setHash: "${unescape(hash)}" is not ` +
|
||||
"a valid destination."
|
||||
`PDFLinkService.setHash: "${unescape(
|
||||
hash
|
||||
)}" is not a valid destination.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -440,11 +504,11 @@ class PDFLinkService {
|
|||
}
|
||||
const refStr =
|
||||
pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
|
||||
this._pagesRefCache[refStr] = pageNum;
|
||||
this.#pagesRefCache.set(refStr, pageNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @ignore
|
||||
*/
|
||||
_cachedPageNumber(pageRef) {
|
||||
if (!pageRef) {
|
||||
|
@ -452,7 +516,7 @@ class PDFLinkService {
|
|||
}
|
||||
const refStr =
|
||||
pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
|
||||
return this._pagesRefCache?.[refStr] || null;
|
||||
return this.#pagesRefCache.get(refStr) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -468,65 +532,65 @@ class PDFLinkService {
|
|||
isPageCached(pageNumber) {
|
||||
return this.pdfViewer.isPageCached(pageNumber);
|
||||
}
|
||||
}
|
||||
|
||||
function isValidExplicitDestination(dest) {
|
||||
if (!Array.isArray(dest)) {
|
||||
return false;
|
||||
}
|
||||
const destLength = dest.length;
|
||||
if (destLength < 2) {
|
||||
return false;
|
||||
}
|
||||
const page = dest[0];
|
||||
if (
|
||||
!(
|
||||
typeof page === "object" &&
|
||||
Number.isInteger(page.num) &&
|
||||
Number.isInteger(page.gen)
|
||||
) &&
|
||||
!(Number.isInteger(page) && page >= 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const zoom = dest[1];
|
||||
if (!(typeof zoom === "object" && typeof zoom.name === "string")) {
|
||||
return false;
|
||||
}
|
||||
let allowNull = true;
|
||||
switch (zoom.name) {
|
||||
case "XYZ":
|
||||
if (destLength !== 5) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "Fit":
|
||||
case "FitB":
|
||||
return destLength === 2;
|
||||
case "FitH":
|
||||
case "FitBH":
|
||||
case "FitV":
|
||||
case "FitBV":
|
||||
if (destLength !== 3) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "FitR":
|
||||
if (destLength !== 6) {
|
||||
return false;
|
||||
}
|
||||
allowNull = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
for (let i = 2; i < destLength; i++) {
|
||||
const param = dest[i];
|
||||
if (!(typeof param === "number" || (allowNull && param === null))) {
|
||||
static #isValidExplicitDestination(dest) {
|
||||
if (!Array.isArray(dest)) {
|
||||
return false;
|
||||
}
|
||||
const destLength = dest.length;
|
||||
if (destLength < 2) {
|
||||
return false;
|
||||
}
|
||||
const page = dest[0];
|
||||
if (
|
||||
!(
|
||||
typeof page === "object" &&
|
||||
Number.isInteger(page.num) &&
|
||||
Number.isInteger(page.gen)
|
||||
) &&
|
||||
!(Number.isInteger(page) && page >= 0)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const zoom = dest[1];
|
||||
if (!(typeof zoom === "object" && typeof zoom.name === "string")) {
|
||||
return false;
|
||||
}
|
||||
let allowNull = true;
|
||||
switch (zoom.name) {
|
||||
case "XYZ":
|
||||
if (destLength !== 5) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "Fit":
|
||||
case "FitB":
|
||||
return destLength === 2;
|
||||
case "FitH":
|
||||
case "FitBH":
|
||||
case "FitV":
|
||||
case "FitBV":
|
||||
if (destLength !== 3) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "FitR":
|
||||
if (destLength !== 6) {
|
||||
return false;
|
||||
}
|
||||
allowNull = false;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
for (let i = 2; i < destLength; i++) {
|
||||
const param = dest[i];
|
||||
if (!(typeof param === "number" || (allowNull && param === null))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -634,4 +698,4 @@ class SimpleLinkService {
|
|||
}
|
||||
}
|
||||
|
||||
export { PDFLinkService, SimpleLinkService };
|
||||
export { LinkTarget, PDFLinkService, SimpleLinkService };
|
||||
|
|
|
@ -19,7 +19,11 @@ import {
|
|||
DefaultTextLayerFactory,
|
||||
DefaultXfaLayerFactory,
|
||||
} from "./default_factory.js";
|
||||
import { PDFLinkService, SimpleLinkService } from "./pdf_link_service.js";
|
||||
import {
|
||||
LinkTarget,
|
||||
PDFLinkService,
|
||||
SimpleLinkService,
|
||||
} from "./pdf_link_service.js";
|
||||
import { PDFSinglePageViewer, PDFViewer } from "./pdf_viewer.js";
|
||||
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
|
||||
import { DownloadManager } from "./download_manager.js";
|
||||
|
@ -49,6 +53,7 @@ export {
|
|||
DownloadManager,
|
||||
EventBus,
|
||||
GenericL10n,
|
||||
LinkTarget,
|
||||
NullL10n,
|
||||
PDFFindController,
|
||||
PDFHistory,
|
||||
|
|
|
@ -200,6 +200,24 @@ function parseQueryString(query) {
|
|||
return params;
|
||||
}
|
||||
|
||||
const NullCharactersRegExp = /\x00/g;
|
||||
const InvisibleCharactersRegExp = /[\x01-\x1F]/g;
|
||||
|
||||
/**
|
||||
* @param {string} str
|
||||
* @param {boolean} [replaceInvisible]
|
||||
*/
|
||||
function removeNullCharacters(str, replaceInvisible = false) {
|
||||
if (typeof str !== "string") {
|
||||
console.error(`The argument must be a string.`);
|
||||
return str;
|
||||
}
|
||||
if (replaceInvisible) {
|
||||
str = str.replace(InvisibleCharactersRegExp, " ");
|
||||
}
|
||||
return str.replace(NullCharactersRegExp, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Use binary search to find the index of the first item in a given array which
|
||||
* passes a given condition. The items are expected to be sorted in the sense
|
||||
|
@ -838,6 +856,7 @@ export {
|
|||
parseQueryString,
|
||||
PresentationModeState,
|
||||
ProgressBar,
|
||||
removeNullCharacters,
|
||||
RendererType,
|
||||
RenderingStates,
|
||||
roundToDivide,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue