: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
const DIACRITICS_EXCEPTION = new Set([0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74]);
let DIACRITICS_EXCEPTION_STR;
const DIACRITICS_REG_EXP = /\p{M}+/gu;
const SPECIAL_CHARS_REG_EXP = /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu;
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
const SYLLABLES_LENGTHS = new Map();
const FIRST_CHAR_SYLLABLES_REG_EXP = "[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]";
const NFKC_CHARS_TO_NORMALIZE = new Map();
let noSyllablesRegExp = null;
let withSyllablesRegExp = null;
function normalize(text) {
const syllablePositions = [];
while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
for (const char of m[0]) {
let len = SYLLABLES_LENGTHS.get(char);
len = char.normalize("NFD").length;
SYLLABLES_LENGTHS.set(char, len);
syllablePositions.push([len, index++]);
if (syllablePositions.length === 0 && noSyllablesRegExp) {
normalizationRegex = noSyllablesRegExp;
} else if (syllablePositions.length > 0 && withSyllablesRegExp) {
normalizationRegex = withSyllablesRegExp;
const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
const toNormalizeWithNFKC = getNormalizeWithNFKC();
const CJK = "(?:\\p{Ideographic}|[\u3040-\u30FF])";
const HKDiacritics = "(?:\u3099|\u309A)";
const regexp = `([${replace}])|([${toNormalizeWithNFKC}])|(${HKDiacritics}\\n)|(\\p{M}+(?:-\\n)?)|(\\S-\\n)|(${CJK}\\n)|(\\n)`;
if (syllablePositions.length === 0) {
normalizationRegex = noSyllablesRegExp = new RegExp(regexp + "|(\\u0000)", "gum");
normalizationRegex = withSyllablesRegExp = new RegExp(regexp + `|(${FIRST_CHAR_SYLLABLES_REG_EXP})`, "gum");
const rawDiacriticsPositions = [];
while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
rawDiacriticsPositions.push([m[0].length, m.index]);
let normalized = text.normalize("NFD");
const positions = [[0, 0]];
let rawDiacriticsIndex = 0;
let hasDiacritics = false;
normalized = normalized.replace(normalizationRegex, (match, p1, p2, p3, p4, p5, p6, p7, p8, i) => {
const replacement = CHARACTERS_TO_NORMALIZE[p1];
const jj = replacement.length;
for (let j = 1; j < jj; j++) {
positions.push([i - shift + j, shift - j]);
let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2);
replacement = p2.normalize("NFKC");
NFKC_CHARS_TO_NORMALIZE.set(p2, replacement);
const jj = replacement.length;
for (let j = 1; j < jj; j++) {
positions.push([i - shift + j, shift - j]);
if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
positions.push([i - 1 - shift + 1, shift - 1]);
positions.push([i - shift + 1, shift]);
const hasTrailingDashEOL = p4.endsWith("\n");
const len = hasTrailingDashEOL ? p4.length - 2 : p4.length;
if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
for (let j = 1; j <= jj; j++) {
positions.push([i - 1 - shift + j, shift - j]);
if (hasTrailingDashEOL) {
positions.push([i - shift + 1, 1 + shift]);
const len = p5.length - 2;
positions.push([i - shift + len, 1 + shift]);
const len = p6.length - 1;
positions.push([i - shift + len, shift]);
positions.push([i - shift + 1, shift - 1]);
if (i + eol === syllablePositions[syllableIndex]?.[1]) {
const newCharLen = syllablePositions[syllableIndex][0] - 1;
for (let j = 1; j <= newCharLen; j++) {
positions.push([i - (shift - j), shift - j]);
shiftOrigin += newCharLen;
positions.push([normalized.length, shift]);
return [normalized, positions, hasDiacritics];
function getOriginalIndex(diffs, pos, len) {
const end = pos + len - 1;
let i = binarySearchFirstItem(diffs, x => x[0] >= start);
if (diffs[i][0] > start) {
let j = binarySearchFirstItem(diffs, x => x[0] >= end, i);
const oldStart = start + diffs[i][1];
const oldEnd = end + diffs[j][1];
const oldLen = oldEnd + 1 - oldStart;
return [oldStart, oldLen];
class PDFFindController {
#updateMatchesCountOnProgress = true;
updateMatchesCountOnProgress = true
this._linkService = linkService;
this._eventBus = eventBus;
this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress;
this.onIsPageVisible = null;
eventBus._on("find", this.#onFind.bind(this));
eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
return this._highlightMatches;
return this._pageMatches;
get pageMatchesLength() {
return this._pageMatchesLength;
setDocument(pdfDocument) {
this._pdfDocument = pdfDocument;
this._firstPageCapability.resolve();
const pdfDocument = this._pdfDocument;
if (this.#state === null || this.#shouldDirtyMatch(state)) {
if (type !== "highlightallchange") {
this.#updateUIState(FindState.PENDING);
this._firstPageCapability.promise.then(() => {
if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
const findbarClosed = !this._highlightMatches;
const pendingTimeout = !!this._findTimeout;
clearTimeout(this._findTimeout);
this._findTimeout = null;
this._findTimeout = setTimeout(() => {
this._findTimeout = null;
} else if (this._dirtyMatch) {
} else if (type === "again") {
if (findbarClosed && this.#state.highlightAll) {
} else if (type === "highlightallchange") {
this._highlightMatches = true;
if (!this._scrollMatches || !element) {
} else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
} else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
this._scrollMatches = false;
top: MATCH_SCROLL_OFFSET_TOP,
left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT
scrollIntoView(element, spot, true);
this._highlightMatches = false;
this._scrollMatches = false;
this._pdfDocument = null;
this._pageMatchesLength = [];
this.#visitedPagesCount = 0;
this._extractTextPromises = [];
this._hasDiacritics = [];
this._matchesCountTotal = 0;
this._pagesToSearch = null;
this._pendingFindMatches = new Set();
this._resumePageIdx = null;
this._dirtyMatch = false;
clearTimeout(this._findTimeout);
this._findTimeout = null;
this._firstPageCapability = Promise.withResolvers();
if (typeof query === "string") {
if (query !== this._rawQuery) {
[this._normalizedQuery] = normalize(query);
return this._normalizedQuery;
return (query || []).filter(q => !!q).map(q => normalize(q)[0]);
#shouldDirtyMatch(state) {
const newQuery = state.query,
prevQuery = this.#state.query;
const newType = typeof newQuery,
prevType = typeof prevQuery;
if (newType !== prevType) {
if (newType === "string") {
if (newQuery !== prevQuery) {
} else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
const pageNumber = this._selected.pageIdx + 1;
const linkService = this._linkService;
return pageNumber >= 1 && pageNumber <= linkService.pagesCount && pageNumber !== linkService.page && !(this.onIsPageVisible?.(pageNumber) ?? true);
case "highlightallchange":
#isEntireWord(content, startIdx, length) {
let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
const first = content.charCodeAt(startIdx);
const limit = match[1].charCodeAt(0);
if (getCharacterType(first) === getCharacterType(limit)) {
match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
const last = content.charCodeAt(startIdx + length - 1);
const limit = match[1].charCodeAt(0);
if (getCharacterType(last) === getCharacterType(limit)) {
#calculateRegExpMatch(query, entireWord, pageIndex, pageContent) {
const matches = this._pageMatches[pageIndex] = [];
const matchesLength = this._pageMatchesLength[pageIndex] = [];
const diffs = this._pageDiffs[pageIndex];
while ((match = query.exec(pageContent)) !== null) {
if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
const [matchPos, matchLen] = getOriginalIndex(diffs, match.index, match[0].length);
matchesLength.push(matchLen);
#convertToRegExpString(query, hasDiacritics) {
query = query.replaceAll(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
return `[ ]*\\${p1}[ ]*`;
return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : "";
const trailingSpaces = "[ ]*";
if (query.endsWith(trailingSpaces)) {
query = query.slice(0, query.length - trailingSpaces.length);
DIACRITICS_EXCEPTION_STR ||= String.fromCharCode(...DIACRITICS_EXCEPTION);
query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
return [isUnicode, query];
#calculateMatch(pageIndex) {
if (query.length === 0) {
const pageContent = this._pageContents[pageIndex];
const hasDiacritics = this._hasDiacritics[pageIndex];
if (typeof query === "string") {
[isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
query = query.sort().reverse().map(q => {
const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
isUnicode ||= isUnicodePart;
const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
query = query ? new RegExp(query, flags) : null;
this.#calculateRegExpMatch(query, entireWord, pageIndex, pageContent);
if (this.#state.highlightAll) {
this.#updatePage(pageIndex);
if (this._resumePageIdx === pageIndex) {
this._resumePageIdx = null;
const pageMatchesCount = this._pageMatches[pageIndex].length;
this._matchesCountTotal += pageMatchesCount;
if (this.#updateMatchesCountOnProgress) {
if (pageMatchesCount > 0) {
this.#updateUIResultsCount();
} else if (++this.#visitedPagesCount === this._linkService.pagesCount) {
this.#updateUIResultsCount();
if (this._extractTextPromises.length > 0) {
let deferred = Promise.resolve();
disableNormalization: true
for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
} = Promise.withResolvers();
this._extractTextPromises[i] = promise;
deferred = deferred.then(() => {
return this._pdfDocument.getPage(i + 1).then(pdfPage => pdfPage.getTextContent(textOptions)).then(textContent => {
for (const textItem of textContent.items) {
strBuf.push(textItem.str);