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:
Tim van der Meij 2022-01-08 13:19:11 +01:00 committed by GitHub
commit 8ac0ccc227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 231 additions and 246 deletions

View File

@ -13,24 +13,21 @@
* limitations under the License. * limitations under the License.
*/ */
import {
assert,
BaseException,
isString,
removeNullCharacters,
shadow,
stringToBytes,
Util,
warn,
} from "../shared/util.js";
import { import {
BaseCanvasFactory, BaseCanvasFactory,
BaseCMapReaderFactory, BaseCMapReaderFactory,
BaseStandardFontDataFactory, BaseStandardFontDataFactory,
BaseSVGFactory, BaseSVGFactory,
} from "./base_factory.js"; } from "./base_factory.js";
import {
BaseException,
isString,
shadow,
stringToBytes,
Util,
warn,
} from "../shared/util.js";
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
const SVG_NS = "http://www.w3.org/2000/svg"; const SVG_NS = "http://www.w3.org/2000/svg";
const PixelsPerInch = { const PixelsPerInch = {
@ -316,70 +313,6 @@ class RenderingCancelledException extends BaseException {
} }
} }
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 } = {}) {
assert(
url && typeof url === "string",
'addLinkAttributes: 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;
}
function isDataScheme(url) { function isDataScheme(url) {
const ii = url.length; const ii = url.length;
let i = 0; let i = 0;
@ -632,7 +565,6 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) {
} }
export { export {
addLinkAttributes,
deprecated, deprecated,
DOMCanvasFactory, DOMCanvasFactory,
DOMCMapReaderFactory, DOMCMapReaderFactory,
@ -644,7 +576,6 @@ export {
isDataScheme, isDataScheme,
isPdfFile, isPdfFile,
isValidFetchUrl, isValidFetchUrl,
LinkTarget,
loadScript, loadScript,
PageViewport, PageViewport,
PDFDateString, PDFDateString,

View File

@ -12,7 +12,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* eslint-disable sort-exports/sort-exports */
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
/** @typedef {import("./display/api").PDFDocumentLoadingTask} PDFDocumentLoadingTask */ /** @typedef {import("./display/api").PDFDocumentLoadingTask} PDFDocumentLoadingTask */
@ -20,19 +19,6 @@
/** @typedef {import("./display/api").PDFPageProxy} PDFPageProxy */ /** @typedef {import("./display/api").PDFPageProxy} PDFPageProxy */
/** @typedef {import("./display/api").RenderTask} RenderTask */ /** @typedef {import("./display/api").RenderTask} RenderTask */
import {
addLinkAttributes,
getFilenameFromUrl,
getPdfFilenameFromUrl,
getXfaPageViewport,
isPdfFile,
isValidFetchUrl,
LinkTarget,
loadScript,
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
} from "./display/display_utils.js";
import { import {
AnnotationMode, AnnotationMode,
CMapCompressionType, CMapCompressionType,
@ -44,7 +30,6 @@ import {
OPS, OPS,
PasswordResponses, PasswordResponses,
PermissionFlag, PermissionFlag,
removeNullCharacters,
shadow, shadow,
UnexpectedResponseException, UnexpectedResponseException,
UNSUPPORTED_FEATURES, UNSUPPORTED_FEATURES,
@ -60,6 +45,17 @@ import {
setPDFNetworkStreamFactory, setPDFNetworkStreamFactory,
version, version,
} from "./display/api.js"; } from "./display/api.js";
import {
getFilenameFromUrl,
getPdfFilenameFromUrl,
getXfaPageViewport,
isPdfFile,
isValidFetchUrl,
loadScript,
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
} from "./display/display_utils.js";
import { AnnotationLayer } from "./display/annotation_layer.js"; import { AnnotationLayer } from "./display/annotation_layer.js";
import { GlobalWorkerOptions } from "./display/worker_options.js"; import { GlobalWorkerOptions } from "./display/worker_options.js";
import { isNodeJS } from "./shared/is_node.js"; import { isNodeJS } from "./shared/is_node.js";
@ -108,49 +104,38 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
} }
export { export {
// From "./display/display_utils.js": AnnotationLayer,
addLinkAttributes,
getFilenameFromUrl,
getPdfFilenameFromUrl,
isPdfFile,
LinkTarget,
loadScript,
PDFDateString,
PixelsPerInch,
RenderingCancelledException,
getXfaPageViewport,
// From "./shared/util.js":
AnnotationMode, AnnotationMode,
build,
CMapCompressionType, CMapCompressionType,
createObjectURL, createObjectURL,
createPromiseCapability, createPromiseCapability,
createValidAbsoluteUrl, createValidAbsoluteUrl,
getDocument,
getFilenameFromUrl,
getPdfFilenameFromUrl,
getXfaPageViewport,
GlobalWorkerOptions,
InvalidPDFException, InvalidPDFException,
isPdfFile,
loadScript,
LoopbackPort,
MissingPDFException, MissingPDFException,
OPS, OPS,
PasswordResponses, PasswordResponses,
PDFDataRangeTransport,
PDFDateString,
PDFWorker,
PermissionFlag, PermissionFlag,
removeNullCharacters, PixelsPerInch,
RenderingCancelledException,
renderTextLayer,
shadow, shadow,
SVGGraphics,
UnexpectedResponseException, UnexpectedResponseException,
UNSUPPORTED_FEATURES, UNSUPPORTED_FEATURES,
Util, Util,
VerbosityLevel, VerbosityLevel,
// From "./display/api.js":
build,
getDocument,
LoopbackPort,
PDFDataRangeTransport,
PDFWorker,
version, version,
// From "./display/annotation_layer.js":
AnnotationLayer,
// From "./display/worker_options.js":
GlobalWorkerOptions,
// From "./display/text_layer.js":
renderTextLayer,
// From "./display/svg.js":
SVGGraphics,
// From "./display/xfa_layer.js":
XfaLayer, XfaLayer,
}; };

View File

@ -575,23 +575,6 @@ class AbortException extends BaseException {
} }
} }
const NullCharactersRegExp = /\x00+/g;
const InvisibleCharactersRegExp = /[\x01-\x1F]/g;
/**
* @param {string} str
*/
function removeNullCharacters(str, replaceInvisible = false) {
if (typeof str !== "string") {
warn("The argument for removeNullCharacters must be a string.");
return str;
}
if (replaceInvisible) {
str = str.replace(InvisibleCharactersRegExp, " ");
}
return str.replace(NullCharactersRegExp, "");
}
function bytesToString(bytes) { function bytesToString(bytes) {
assert( assert(
bytes !== null && typeof bytes === "object" && bytes.length !== undefined, bytes !== null && typeof bytes === "object" && bytes.length !== undefined,
@ -1185,7 +1168,6 @@ export {
PasswordException, PasswordException,
PasswordResponses, PasswordResponses,
PermissionFlag, PermissionFlag,
removeNullCharacters,
RenderingIntentFlag, RenderingIntentFlag,
setVerbosityLevel, setVerbosityLevel,
shadow, shadow,

View File

@ -21,6 +21,7 @@ import {
isPortraitOrientation, isPortraitOrientation,
isValidRotation, isValidRotation,
parseQueryString, parseQueryString,
removeNullCharacters,
} from "../../web/ui_utils.js"; } from "../../web/ui_utils.js";
describe("ui_utils", function () { describe("ui_utils", function () {
@ -139,6 +140,30 @@ describe("ui_utils", function () {
}); });
}); });
describe("removeNullCharacters", function () {
it("should not modify string without null characters", function () {
const str = "string without null chars";
expect(removeNullCharacters(str)).toEqual("string without null chars");
});
it("should modify string with null characters", function () {
const str = "string\x00With\x00Null\x00Chars";
expect(removeNullCharacters(str)).toEqual("stringWithNullChars");
});
it("should modify string with non-displayable characters", function () {
const str = Array.from(Array(32).keys())
.map(x => String.fromCharCode(x) + "a")
.join("");
// \x00 is replaced by an empty string.
const expected =
"a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a";
expect(removeNullCharacters(str, /* replaceInvisible */ true)).toEqual(
expected
);
});
});
describe("getPageSizeInches", function () { describe("getPageSizeInches", function () {
it("gets page size (in inches)", function () { it("gets page size (in inches)", function () {
const page = { const page = {

View File

@ -25,7 +25,6 @@ import {
isNum, isNum,
isSameOrigin, isSameOrigin,
isString, isString,
removeNullCharacters,
string32, string32,
stringToBytes, stringToBytes,
stringToPDFString, stringToPDFString,
@ -175,30 +174,6 @@ describe("util", function () {
}); });
}); });
describe("removeNullCharacters", function () {
it("should not modify string without null characters", function () {
const str = "string without null chars";
expect(removeNullCharacters(str)).toEqual("string without null chars");
});
it("should modify string with null characters", function () {
const str = "string\x00With\x00Null\x00Chars";
expect(removeNullCharacters(str)).toEqual("stringWithNullChars");
});
it("should modify string with non-displayable characters", function () {
const str = Array.from(Array(32).keys())
.map(x => String.fromCharCode(x) + "a")
.join("");
// \x00 is replaced by an empty string.
const expected =
"a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a";
expect(removeNullCharacters(str, /* replaceInvisible */ true)).toEqual(
expected
);
});
});
describe("ReadableStream", function () { describe("ReadableStream", function () {
it("should return an Object", function () { it("should return an Object", function () {
const readable = new ReadableStream(); const readable = new ReadableStream();

View File

@ -46,7 +46,6 @@ import {
GlobalWorkerOptions, GlobalWorkerOptions,
InvalidPDFException, InvalidPDFException,
isPdfFile, isPdfFile,
LinkTarget,
loadScript, loadScript,
MissingPDFException, MissingPDFException,
OPS, OPS,
@ -57,6 +56,7 @@ import {
version, version,
} from "pdfjs-lib"; } from "pdfjs-lib";
import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js"; import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
import { LinkTarget, PDFLinkService } from "./pdf_link_service.js";
import { OverlayManager } from "./overlay_manager.js"; import { OverlayManager } from "./overlay_manager.js";
import { PasswordPrompt } from "./password_prompt.js"; import { PasswordPrompt } from "./password_prompt.js";
import { PDFAttachmentViewer } from "./pdf_attachment_viewer.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 { PDFFindController } from "./pdf_find_controller.js";
import { PDFHistory } from "./pdf_history.js"; import { PDFHistory } from "./pdf_history.js";
import { PDFLayerViewer } from "./pdf_layer_viewer.js"; import { PDFLayerViewer } from "./pdf_layer_viewer.js";
import { PDFLinkService } from "./pdf_link_service.js";
import { PDFOutlineViewer } from "./pdf_outline_viewer.js"; import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
import { PDFPresentationMode } from "./pdf_presentation_mode.js"; import { PDFPresentationMode } from "./pdf_presentation_mode.js";
import { PDFRenderingQueue } from "./pdf_rendering_queue.js"; import { PDFRenderingQueue } from "./pdf_rendering_queue.js";

View File

@ -13,7 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { removeNullCharacters } from "pdfjs-lib"; import { removeNullCharacters } from "./ui_utils.js";
const TREEITEM_OFFSET_TOP = -100; // px const TREEITEM_OFFSET_TOP = -100; // px
const TREEITEM_SELECTED_CLASS = "selected"; const TREEITEM_SELECTED_CLASS = "selected";

View File

@ -16,8 +16,72 @@
/** @typedef {import("./event_utils").EventBus} EventBus */ /** @typedef {import("./event_utils").EventBus} EventBus */
/** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */ /** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */
import { addLinkAttributes, LinkTarget } from "pdfjs-lib"; import { parseQueryString, removeNullCharacters } from "./ui_utils.js";
import { parseQueryString } 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 * @typedef {Object} PDFLinkServiceOptions
@ -38,6 +102,8 @@ import { parseQueryString } from "./ui_utils.js";
* @implements {IPDFLinkService} * @implements {IPDFLinkService}
*/ */
class PDFLinkService { class PDFLinkService {
#pagesRefCache = new Map();
/** /**
* @param {PDFLinkServiceOptions} options * @param {PDFLinkServiceOptions} options
*/ */
@ -57,14 +123,12 @@ class PDFLinkService {
this.pdfDocument = null; this.pdfDocument = null;
this.pdfViewer = null; this.pdfViewer = null;
this.pdfHistory = null; this.pdfHistory = null;
this._pagesRefCache = null;
} }
setDocument(pdfDocument, baseUrl = null) { setDocument(pdfDocument, baseUrl = null) {
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
this.pdfDocument = pdfDocument; this.pdfDocument = pdfDocument;
this._pagesRefCache = Object.create(null); this.#pagesRefCache.clear();
} }
setViewer(pdfViewer) { setViewer(pdfViewer) {
@ -110,10 +174,7 @@ class PDFLinkService {
this.pdfViewer.pagesRotation = value; this.pdfViewer.pagesRotation = value;
} }
/** #goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
* @private
*/
_goToDestinationHelper(rawDest, namedDest = null, explicitDest) {
// Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..> // Dest array looks like that: <page-ref> </XYZ|/FitXXX> <args..>
const destRef = explicitDest[0]; const destRef = explicitDest[0];
let pageNumber; let pageNumber;
@ -128,11 +189,11 @@ class PDFLinkService {
.getPageIndex(destRef) .getPageIndex(destRef)
.then(pageIndex => { .then(pageIndex => {
this.cachePageRef(pageIndex + 1, destRef); this.cachePageRef(pageIndex + 1, destRef);
this._goToDestinationHelper(rawDest, namedDest, explicitDest); this.#goToDestinationHelper(rawDest, namedDest, explicitDest);
}) })
.catch(() => { .catch(() => {
console.error( console.error(
`PDFLinkService._goToDestinationHelper: "${destRef}" is not ` + `PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
`a valid page reference, for dest="${rawDest}".` `a valid page reference, for dest="${rawDest}".`
); );
}); });
@ -142,14 +203,14 @@ class PDFLinkService {
pageNumber = destRef + 1; pageNumber = destRef + 1;
} else { } else {
console.error( console.error(
`PDFLinkService._goToDestinationHelper: "${destRef}" is not ` + `PDFLinkService.#goToDestinationHelper: "${destRef}" is not ` +
`a valid destination reference, for dest="${rawDest}".` `a valid destination reference, for dest="${rawDest}".`
); );
return; return;
} }
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) { if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
console.error( console.error(
`PDFLinkService._goToDestinationHelper: "${pageNumber}" is not ` + `PDFLinkService.#goToDestinationHelper: "${pageNumber}" is not ` +
`a valid page number, for dest="${rawDest}".` `a valid page number, for dest="${rawDest}".`
); );
return; return;
@ -193,7 +254,7 @@ class PDFLinkService {
); );
return; 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 {HTMLAnchorElement} link
* @param {string} url * @param {string} url
* @param {boolean} [newWindow] * @param {boolean} [newWindow]
@ -340,8 +401,7 @@ class PDFLinkService {
} }
} else { } else {
console.error( console.error(
`PDFLinkService.setHash: "${zoomArg}" is not ` + `PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`
"a valid zoom value."
); );
} }
} }
@ -379,13 +439,17 @@ class PDFLinkService {
} }
} catch (ex) {} } catch (ex) {}
if (typeof dest === "string" || isValidExplicitDestination(dest)) { if (
typeof dest === "string" ||
PDFLinkService.#isValidExplicitDestination(dest)
) {
this.goToDestination(dest); this.goToDestination(dest);
return; return;
} }
console.error( console.error(
`PDFLinkService.setHash: "${unescape(hash)}" is not ` + `PDFLinkService.setHash: "${unescape(
"a valid destination." hash
)}" is not a valid destination.`
); );
} }
} }
@ -440,11 +504,11 @@ class PDFLinkService {
} }
const refStr = const refStr =
pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`; pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
this._pagesRefCache[refStr] = pageNum; this.#pagesRefCache.set(refStr, pageNum);
} }
/** /**
* @private * @ignore
*/ */
_cachedPageNumber(pageRef) { _cachedPageNumber(pageRef) {
if (!pageRef) { if (!pageRef) {
@ -452,7 +516,7 @@ class PDFLinkService {
} }
const refStr = const refStr =
pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`; 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) { isPageCached(pageNumber) {
return this.pdfViewer.isPageCached(pageNumber); return this.pdfViewer.isPageCached(pageNumber);
} }
}
function isValidExplicitDestination(dest) { static #isValidExplicitDestination(dest) {
if (!Array.isArray(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 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 };

View File

@ -19,7 +19,11 @@ import {
DefaultTextLayerFactory, DefaultTextLayerFactory,
DefaultXfaLayerFactory, DefaultXfaLayerFactory,
} from "./default_factory.js"; } 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 { PDFSinglePageViewer, PDFViewer } from "./pdf_viewer.js";
import { AnnotationLayerBuilder } from "./annotation_layer_builder.js"; import { AnnotationLayerBuilder } from "./annotation_layer_builder.js";
import { DownloadManager } from "./download_manager.js"; import { DownloadManager } from "./download_manager.js";
@ -49,6 +53,7 @@ export {
DownloadManager, DownloadManager,
EventBus, EventBus,
GenericL10n, GenericL10n,
LinkTarget,
NullL10n, NullL10n,
PDFFindController, PDFFindController,
PDFHistory, PDFHistory,

View File

@ -200,6 +200,24 @@ function parseQueryString(query) {
return params; 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 * 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 * passes a given condition. The items are expected to be sorted in the sense
@ -838,6 +856,7 @@ export {
parseQueryString, parseQueryString,
PresentationModeState, PresentationModeState,
ProgressBar, ProgressBar,
removeNullCharacters,
RendererType, RendererType,
RenderingStates, RenderingStates,
roundToDivide, roundToDivide,