: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
const HEADING_PATTERN = /^H(\d+)$/;
class StructTreeLayerBuilder {
return this.#treeDom !== undefined;
if (this.#treeDom !== undefined) {
const treeDom = this.#walk(structTree);
treeDom?.classList.add("structTree");
return this.#treeDom = treeDom;
if (this.#treeDom && !this.#treeDom.hidden) {
this.#treeDom.hidden = true;
if (this.#treeDom?.hidden) {
this.#treeDom.hidden = false;
#setAttributes(structElement, htmlElement) {
htmlElement.setAttribute("aria-label", removeNullCharacters(alt));
htmlElement.setAttribute("aria-owns", id);
if (lang !== undefined) {
htmlElement.setAttribute("lang", removeNullCharacters(lang, true));
const element = document.createElement("span");
const match = role.match(HEADING_PATTERN);
element.setAttribute("role", "heading");
element.setAttribute("aria-level", match[1]);
} else if (PDF_ROLE_TO_HTML_ROLE[role]) {
element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
this.#setAttributes(node, element);
if (node.children.length === 1 && "id" in node.children[0]) {
this.#setAttributes(node.children[0], element);
for (const kid of node.children) {
element.append(this.#walk(kid));
;// CONCATENATED MODULE: ./web/text_accessibility.js
class TextAccessibilityManager {
#waitingElements = new Map();
setTextMapping(textDivs) {
this.#textChildren = textDivs;
static #compareElementPositions(e1, e2) {
const rect1 = e1.getBoundingClientRect();
const rect2 = e2.getBoundingClientRect();
if (rect1.width === 0 && rect1.height === 0) {
if (rect2.width === 0 && rect2.height === 0) {
const bot1 = rect1.y + rect1.height;
const mid1 = rect1.y + rect1.height / 2;
const bot2 = rect2.y + rect2.height;
const mid2 = rect2.y + rect2.height / 2;
if (mid1 <= top2 && mid2 >= bot1) {
if (mid2 <= top1 && mid1 >= bot2) {
const centerX1 = rect1.x + rect1.width / 2;
const centerX2 = rect2.x + rect2.width / 2;
return centerX1 - centerX2;
throw new Error("TextAccessibilityManager is already enabled.");
if (!this.#textChildren) {
throw new Error("Text divs and strings have not been set.");
this.#textChildren = this.#textChildren.slice();
this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
if (this.#textNodes.size > 0) {
const textChildren = this.#textChildren;
for (const [id, nodeIndex] of this.#textNodes) {
const element = document.getElementById(id);
this.#textNodes.delete(id);
this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
for (const [element, isRemovable] of this.#waitingElements) {
this.addPointerInTextLayer(element, isRemovable);
this.#waitingElements.clear();
this.#waitingElements.clear();
this.#textChildren = null;
removePointerInTextLayer(element) {
this.#waitingElements.delete(element);
const children = this.#textChildren;
if (!children || children.length === 0) {
const nodeIndex = this.#textNodes.get(id);
if (nodeIndex === undefined) {
const node = children[nodeIndex];
this.#textNodes.delete(id);
let owns = node.getAttribute("aria-owns");
if (owns?.includes(id)) {
owns = owns.split(" ").filter(x => x !== id).join(" ");
node.setAttribute("aria-owns", owns);
node.removeAttribute("aria-owns");
node.setAttribute("role", "presentation");
#addIdToAriaOwns(id, node) {
const owns = node.getAttribute("aria-owns");
if (!owns?.includes(id)) {
node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
node.removeAttribute("role");
addPointerInTextLayer(element, isRemovable) {
this.#waitingElements.set(element, isRemovable);
this.removePointerInTextLayer(element);
const children = this.#textChildren;
if (!children || children.length === 0) {
const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(element, node) < 0);
const nodeIndex = Math.max(0, index - 1);
const child = children[nodeIndex];
this.#addIdToAriaOwns(id, child);
this.#textNodes.set(id, nodeIndex);
const parent = child.parentNode;
return parent?.classList.contains("markedContent") ? parent.id : null;
moveElementInDOM(container, element, contentElement, isRemovable) {
const id = this.addPointerInTextLayer(contentElement, isRemovable);
if (!container.hasChildNodes()) {
container.append(element);
const children = Array.from(container.childNodes).filter(node => node !== element);
if (children.length === 0) {
const elementToCompare = contentElement || element;
const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(elementToCompare, node) < 0);
children[0].before(element);
children[index - 1].after(element);
;// CONCATENATED MODULE: ./web/text_highlighter.js
#eventAbortController = null;
this.findController = findController;
this.eventBus = eventBus;
this.pageIdx = pageIndex;
this.textContentItemsStr = null;
setTextMapping(divs, texts) {
this.textContentItemsStr = texts;
if (!this.textDivs || !this.textContentItemsStr) {
throw new Error("Text divs and strings have not been set.");
throw new Error("TextHighlighter is already enabled.");
if (!this.#eventAbortController) {
this.#eventAbortController = new AbortController();
this.eventBus._on("updatetextlayermatches", evt => {
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
signal: this.#eventAbortController.signal
this.#eventAbortController?.abort();
this.#eventAbortController = null;
this._updateMatches(true);
_convertMatches(matches, matchesLength) {
const end = textContentItemsStr.length - 1;
for (let m = 0, mm = matches.length; m < mm; m++) {
let matchIdx = matches[m];
while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
iIndex += textContentItemsStr[i].length;
if (i === textContentItemsStr.length) {
console.error("Could not find a matching mapping");
offset: matchIdx - iIndex
matchIdx += matchesLength[m];
while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
iIndex += textContentItemsStr[i].length;
offset: matchIdx - iIndex
_renderMatches(matches) {
if (matches.length === 0) {
const isSelectedPage = pageIdx === findController.selected.pageIdx;
const selectedMatchIdx = findController.selected.matchIdx;
const highlightAll = findController.state.highlightAll;
function beginText(begin, className) {
const divIdx = begin.divIdx;
textDivs[divIdx].textContent = "";
return appendTextToDiv(divIdx, 0, begin.offset, className);
function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
let div = textDivs[divIdx];
if (div.nodeType === Node.TEXT_NODE) {
const span = document.createElement("span");
const content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
const node = document.createTextNode(content);
const span = document.createElement("span");
span.className = `${className} appended`;
return className.includes("selected") ? span.offsetLeft : 0;
let i0 = selectedMatchIdx,
} else if (!isSelectedPage) {
for (let i = i0; i < i1; i++) {
const match = matches[i];
const begin = match.begin;
if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) {
lastDivIdx = begin.divIdx;
lastOffset = begin.offset;
const isSelected = isSelectedPage && i === selectedMatchIdx;
const highlightSuffix = isSelected ? " selected" : "";
if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
if (begin.divIdx === end.divIdx) {
selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, end.offset, "highlight" + highlightSuffix);
selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, "highlight begin" + highlightSuffix);
for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
textDivs[n0].className = "highlight middle" + highlightSuffix;
beginText(end, "highlight end" + highlightSuffix);
findController.scrollMatchIntoView({
element: textDivs[begin.divIdx],
matchIndex: selectedMatchIdx
appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
_updateMatches(reset = false) {
if (!this.enabled && !reset) {
let clearedUntilDivIdx = -1;
for (const match of matches) {
const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
for (let n = begin, end = match.end.divIdx; n <= end; n++) {
div.textContent = textContentItemsStr[n];
clearedUntilDivIdx = match.end.divIdx + 1;
if (!findController?.highlightMatches || reset) {
const pageMatches = findController.pageMatches[pageIdx] || null;
const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
this.matches = this._convertMatches(pageMatches, pageMatchesLength);
this._renderMatches(this.matches);
;// CONCATENATED MODULE: ./web/text_layer_builder.js
#enablePermissions = false;
static #textLayers = new Map();
static #selectionChangeAbortController = null;
accessibilityManager = null,
enablePermissions = false,
this.highlighter = highlighter;
this.accessibilityManager = accessibilityManager;
this.#enablePermissions = enablePermissions === true;
this.#onAppend = onAppend;
this.div = document.createElement("div");
this.div.className = "textLayer";
this.#renderingDone = true;
const endOfContent = document.createElement("div");
endOfContent.className = "endOfContent";
this.div.append(endOfContent);
this.#bindMouse(endOfContent);
async render(viewport, textContentParams = null) {