From 2d2b6463b8a9ac26b72d8b7de7a47f78cfdfd940 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 3 Jan 2022 13:36:49 +0100 Subject: [PATCH 1/4] [api-minor] Move `addLinkAttributes` and `LinkTarget` into the viewer As part of the changes/improvement in PR 14092, we're no longer using the `addLinkAttributes` directly in e.g. the AnnotationLayer-code. Given that the helper function is now *only* used in the viewer, hence it no longer seems necessary to expose it through the official API. *Please note:* It seems somewhat unlikely that third-party users were relying *directly* on the helper function, which is why it's not being exported as part of the viewer components. (If necessary, we can always change this later on.) --- src/display/display_utils.js | 85 ++++-------------------------------- src/pdf.js | 69 ++++++++++++----------------- web/app.js | 3 +- web/pdf_link_service.js | 71 ++++++++++++++++++++++++++++-- web/pdf_viewer.component.js | 7 ++- 5 files changed, 111 insertions(+), 124 deletions(-) diff --git a/src/display/display_utils.js b/src/display/display_utils.js index c34035f2c..be4b20c7c 100644 --- a/src/display/display_utils.js +++ b/src/display/display_utils.js @@ -13,24 +13,21 @@ * limitations under the License. */ -import { - assert, - BaseException, - isString, - removeNullCharacters, - shadow, - stringToBytes, - Util, - warn, -} from "../shared/util.js"; import { BaseCanvasFactory, BaseCMapReaderFactory, BaseStandardFontDataFactory, BaseSVGFactory, } 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 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) { const ii = url.length; let i = 0; @@ -632,7 +565,6 @@ function getXfaPageViewport(xfaPage, { scale = 1, rotation = 0 }) { } export { - addLinkAttributes, deprecated, DOMCanvasFactory, DOMCMapReaderFactory, @@ -644,7 +576,6 @@ export { isDataScheme, isPdfFile, isValidFetchUrl, - LinkTarget, loadScript, PageViewport, PDFDateString, diff --git a/src/pdf.js b/src/pdf.js index 3a4b38e0f..a69baeba9 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable sort-exports/sort-exports */ // eslint-disable-next-line max-len /** @typedef {import("./display/api").PDFDocumentLoadingTask} PDFDocumentLoadingTask */ @@ -20,19 +19,6 @@ /** @typedef {import("./display/api").PDFPageProxy} PDFPageProxy */ /** @typedef {import("./display/api").RenderTask} RenderTask */ -import { - addLinkAttributes, - getFilenameFromUrl, - getPdfFilenameFromUrl, - getXfaPageViewport, - isPdfFile, - isValidFetchUrl, - LinkTarget, - loadScript, - PDFDateString, - PixelsPerInch, - RenderingCancelledException, -} from "./display/display_utils.js"; import { AnnotationMode, CMapCompressionType, @@ -60,6 +46,17 @@ import { setPDFNetworkStreamFactory, version, } 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 { GlobalWorkerOptions } from "./display/worker_options.js"; import { isNodeJS } from "./shared/is_node.js"; @@ -108,49 +105,39 @@ if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) { } export { - // From "./display/display_utils.js": - addLinkAttributes, - getFilenameFromUrl, - getPdfFilenameFromUrl, - isPdfFile, - LinkTarget, - loadScript, - PDFDateString, - PixelsPerInch, - RenderingCancelledException, - getXfaPageViewport, - // From "./shared/util.js": + AnnotationLayer, AnnotationMode, + build, CMapCompressionType, createObjectURL, createPromiseCapability, createValidAbsoluteUrl, + getDocument, + getFilenameFromUrl, + getPdfFilenameFromUrl, + getXfaPageViewport, + GlobalWorkerOptions, InvalidPDFException, + isPdfFile, + loadScript, + LoopbackPort, MissingPDFException, OPS, PasswordResponses, + PDFDataRangeTransport, + PDFDateString, + PDFWorker, PermissionFlag, + PixelsPerInch, removeNullCharacters, + RenderingCancelledException, + renderTextLayer, shadow, + SVGGraphics, UnexpectedResponseException, UNSUPPORTED_FEATURES, Util, VerbosityLevel, - // From "./display/api.js": - build, - getDocument, - LoopbackPort, - PDFDataRangeTransport, - PDFWorker, 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, }; diff --git a/web/app.js b/web/app.js index 209ad0360..07402bd0d 100644 --- a/web/app.js +++ b/web/app.js @@ -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"; diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 3f6e4eb25..d74741beb 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -16,8 +16,73 @@ /** @typedef {import("./event_utils").EventBus} EventBus */ /** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */ -import { addLinkAttributes, LinkTarget } from "pdfjs-lib"; import { parseQueryString } from "./ui_utils.js"; +import { removeNullCharacters } from "pdfjs-lib"; + +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 @@ -230,7 +295,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] @@ -634,4 +699,4 @@ class SimpleLinkService { } } -export { PDFLinkService, SimpleLinkService }; +export { LinkTarget, PDFLinkService, SimpleLinkService }; diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js index dce7d9b93..a7a918cf1 100644 --- a/web/pdf_viewer.component.js +++ b/web/pdf_viewer.component.js @@ -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, From fc31e1ba874d3495f3229e80f8c115f4cab9bd15 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 3 Jan 2022 13:44:08 +0100 Subject: [PATCH 2/4] Convert the `isValidExplicitDestination` helper to a private static method on `PDFLinkService` This patch also changes a previously "private" method, on `PDFLinkService`, to be *properly* private since that's now supported. --- web/pdf_link_service.js | 140 ++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index d74741beb..846bbd669 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -175,10 +175,7 @@ class PDFLinkService { this.pdfViewer.pagesRotation = value; } - /** - * @private - */ - _goToDestinationHelper(rawDest, namedDest = null, explicitDest) { + #goToDestinationHelper(rawDest, namedDest = null, explicitDest) { // Dest array looks like that: const destRef = explicitDest[0]; let pageNumber; @@ -193,11 +190,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}".` ); }); @@ -207,14 +204,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; @@ -258,7 +255,7 @@ class PDFLinkService { ); return; } - this._goToDestinationHelper(dest, namedDest, explicitDest); + this.#goToDestinationHelper(dest, namedDest, explicitDest); } /** @@ -405,8 +402,7 @@ class PDFLinkService { } } else { console.error( - `PDFLinkService.setHash: "${zoomArg}" is not ` + - "a valid zoom value." + `PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.` ); } } @@ -444,13 +440,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.` ); } } @@ -509,7 +509,7 @@ class PDFLinkService { } /** - * @private + * @ignore */ _cachedPageNumber(pageRef) { if (!pageRef) { @@ -533,65 +533,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; } /** From 00aa9811e6cfb6a6200d2dc5b2e81eef4a5f033e Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 3 Jan 2022 13:57:53 +0100 Subject: [PATCH 3/4] Convert the `pagesRefCache`, on `PDFLinkService`, from an Object to a Map This seems like a more appropriate data structure, and as part of these changes the property was also converted to a *private* one. --- web/pdf_link_service.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 846bbd669..0913993de 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -103,6 +103,8 @@ function addLinkAttributes(link, { url, target, rel, enabled = true } = {}) { * @implements {IPDFLinkService} */ class PDFLinkService { + #pagesRefCache = new Map(); + /** * @param {PDFLinkServiceOptions} options */ @@ -122,14 +124,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) { @@ -505,7 +505,7 @@ class PDFLinkService { } const refStr = pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`; - this._pagesRefCache[refStr] = pageNum; + this.#pagesRefCache.set(refStr, pageNum); } /** @@ -517,7 +517,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; } /** From 7b8794b37e3e4bfc25474d17289faea98918e841 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 3 Jan 2022 14:11:12 +0100 Subject: [PATCH 4/4] [api-minor] Move `removeNullCharacters` into the viewer This helper function has never been used in e.g. the worker-thread, hence its placement in `src/shared/util.js` led to a *small* amount of unnecessary duplication. After the previous patches this helper function is now *only* used in the viewer, hence it no longer seems necessary to expose it through the official API. *Please note:* It seems somewhat unlikely that third-party users were relying *directly* on this helper function, which is why it's not being exported as part of the viewer components. (If necessary, we can always change this later on.) --- src/pdf.js | 2 -- src/shared/util.js | 18 ------------------ test/unit/ui_utils_spec.js | 25 +++++++++++++++++++++++++ test/unit/util_spec.js | 25 ------------------------- web/base_tree_viewer.js | 2 +- web/pdf_link_service.js | 3 +-- web/ui_utils.js | 19 +++++++++++++++++++ 7 files changed, 46 insertions(+), 48 deletions(-) diff --git a/src/pdf.js b/src/pdf.js index a69baeba9..d89ac3af8 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -30,7 +30,6 @@ import { OPS, PasswordResponses, PermissionFlag, - removeNullCharacters, shadow, UnexpectedResponseException, UNSUPPORTED_FEATURES, @@ -129,7 +128,6 @@ export { PDFWorker, PermissionFlag, PixelsPerInch, - removeNullCharacters, RenderingCancelledException, renderTextLayer, shadow, diff --git a/src/shared/util.js b/src/shared/util.js index 7211254dd..214624236 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -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) { assert( bytes !== null && typeof bytes === "object" && bytes.length !== undefined, @@ -1185,7 +1168,6 @@ export { PasswordException, PasswordResponses, PermissionFlag, - removeNullCharacters, RenderingIntentFlag, setVerbosityLevel, shadow, diff --git a/test/unit/ui_utils_spec.js b/test/unit/ui_utils_spec.js index d262f1079..c83e2f056 100644 --- a/test/unit/ui_utils_spec.js +++ b/test/unit/ui_utils_spec.js @@ -21,6 +21,7 @@ import { isPortraitOrientation, isValidRotation, parseQueryString, + removeNullCharacters, } from "../../web/ui_utils.js"; 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 () { it("gets page size (in inches)", function () { const page = { diff --git a/test/unit/util_spec.js b/test/unit/util_spec.js index b86d287c4..941e2542c 100644 --- a/test/unit/util_spec.js +++ b/test/unit/util_spec.js @@ -25,7 +25,6 @@ import { isNum, isSameOrigin, isString, - removeNullCharacters, string32, stringToBytes, 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 () { it("should return an Object", function () { const readable = new ReadableStream(); diff --git a/web/base_tree_viewer.js b/web/base_tree_viewer.js index 7a4f1e030..7a3168128 100644 --- a/web/base_tree_viewer.js +++ b/web/base_tree_viewer.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"; diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 0913993de..6e2a5f989 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -16,8 +16,7 @@ /** @typedef {import("./event_utils").EventBus} EventBus */ /** @typedef {import("./interfaces").IPDFLinkService} IPDFLinkService */ -import { parseQueryString } from "./ui_utils.js"; -import { removeNullCharacters } from "pdfjs-lib"; +import { parseQueryString, removeNullCharacters } from "./ui_utils.js"; const DEFAULT_LINK_REL = "noopener noreferrer nofollow"; diff --git a/web/ui_utils.js b/web/ui_utils.js index d446cda3d..54593573a 100644 --- a/web/ui_utils.js +++ b/web/ui_utils.js @@ -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,