From 2151be2b776c92f4ef6a89c96847a423dd36eb05 Mon Sep 17 00:00:00 2001 From: MarsLuay <70299537+MarsLuay@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:26:37 +0000 Subject: [PATCH 1/2] perf: optimize find search index filtering loops and allocations Pre-compute lowercased shape and fallback texts during search index slide creation from OOXML and lazily populate them if missing. Replace array .filter() with direct loop iteration in collectFindMatchesFromSearchIndex to eliminate intermediate array allocations and repeated toLocaleLowerCase() string allocations on every query. --- src/powerpoint/findSearchIndex.ts | 45 ++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/src/powerpoint/findSearchIndex.ts b/src/powerpoint/findSearchIndex.ts index 6a01000..f26d53e 100644 --- a/src/powerpoint/findSearchIndex.ts +++ b/src/powerpoint/findSearchIndex.ts @@ -16,6 +16,8 @@ export interface PowerPointFindSearchIndexSlide { slideIndex: number; shapeMatches: PowerPointFindMatch[]; fallbackText: string; + lowerShapeTexts?: string[]; + lowerFallbackText?: string; } /** @@ -29,11 +31,15 @@ export function createFindSearchIndexSlideFromOoxml( ): PowerPointFindSearchIndexSlide { const slideDocument = parseXml(slideXml, `slide ${slideIndex + 1}`); const shapeMatches: PowerPointFindMatch[] = []; + const lowerShapeTexts: string[] = []; const addShape = (shape: Element, shapeIndex: number): void => { const text = normalizeSearchText( getDescendants(shape, 't').map((element) => element.textContent ?? '').join(''), ); - if (text) shapeMatches.push({ slideIndex, shapeIndex, text }); + if (text) { + shapeMatches.push({ slideIndex, shapeIndex, text }); + lowerShapeTexts.push(text.toLocaleLowerCase()); + } }; const shapes = getShapeChildren(getShapeTree(slideDocument)); @@ -46,10 +52,14 @@ export function createFindSearchIndexSlideFromOoxml( .forEach((child, childIndex) => addShape(child, (shapeIndex * 1000) + childIndex)); }); + const fallbackText = normalizeSearchText(slideDocument.documentElement.textContent ?? ''); + return { slideIndex, shapeMatches, - fallbackText: normalizeSearchText(slideDocument.documentElement.textContent ?? ''), + fallbackText, + lowerShapeTexts, + lowerFallbackText: fallbackText.toLocaleLowerCase(), }; } @@ -66,13 +76,30 @@ export function collectFindMatchesFromSearchIndex( const matches: PowerPointFindMatch[] = []; for (const slide of searchIndex) { - const shapeMatches = slide.shapeMatches.filter((match) => ( - match.text.toLocaleLowerCase().includes(normalizedQuery) - )); - if (shapeMatches.length > 0) { - matches.push(...shapeMatches); - } else if (slide.fallbackText.toLocaleLowerCase().includes(normalizedQuery)) { - matches.push({ slideIndex: slide.slideIndex, shapeIndex: null, text: slide.fallbackText }); + let slideHasMatch = false; + const shapeMatches = slide.shapeMatches; + let lowerShapeTexts = slide.lowerShapeTexts; + if (!lowerShapeTexts || lowerShapeTexts.length !== shapeMatches.length) { + lowerShapeTexts = shapeMatches.map((match) => match.text.toLocaleLowerCase()); + (slide as PowerPointFindSearchIndexSlide).lowerShapeTexts = lowerShapeTexts; + } + + for (let i = 0; i < shapeMatches.length; i++) { + const match = shapeMatches[i]; + const lowerText = lowerShapeTexts[i]; + if (match && lowerText !== undefined && lowerText.includes(normalizedQuery)) { + matches.push(match); + slideHasMatch = true; + } + } + + if (!slideHasMatch && slide.fallbackText) { + const lowerFallback = slide.lowerFallbackText ?? ( + (slide as PowerPointFindSearchIndexSlide).lowerFallbackText = slide.fallbackText.toLocaleLowerCase() + ); + if (lowerFallback.includes(normalizedQuery)) { + matches.push({ slideIndex: slide.slideIndex, shapeIndex: null, text: slide.fallbackText }); + } } } return matches; From 51134dca58319b4c2f4297bf7a3283b3daf830a5 Mon Sep 17 00:00:00 2001 From: MarsLuay <70299537+MarsLuay@users.noreply.github.com> Date: Tue, 25 Aug 2026 09:31:03 +0000 Subject: [PATCH 2/2] perf: optimize find search index filtering loops and allocations Pre-compute lowercased shape and fallback texts during search index slide creation from OOXML and lazily populate them if missing. Replace array .filter() with direct loop iteration in collectFindMatchesFromSearchIndex to eliminate intermediate array allocations and repeated toLocaleLowerCase() string allocations on every query. --- src/powerpoint/findSearchIndex.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/powerpoint/findSearchIndex.ts b/src/powerpoint/findSearchIndex.ts index f26d53e..faedfdb 100644 --- a/src/powerpoint/findSearchIndex.ts +++ b/src/powerpoint/findSearchIndex.ts @@ -81,7 +81,7 @@ export function collectFindMatchesFromSearchIndex( let lowerShapeTexts = slide.lowerShapeTexts; if (!lowerShapeTexts || lowerShapeTexts.length !== shapeMatches.length) { lowerShapeTexts = shapeMatches.map((match) => match.text.toLocaleLowerCase()); - (slide as PowerPointFindSearchIndexSlide).lowerShapeTexts = lowerShapeTexts; + slide.lowerShapeTexts = lowerShapeTexts; } for (let i = 0; i < shapeMatches.length; i++) { @@ -95,7 +95,7 @@ export function collectFindMatchesFromSearchIndex( if (!slideHasMatch && slide.fallbackText) { const lowerFallback = slide.lowerFallbackText ?? ( - (slide as PowerPointFindSearchIndexSlide).lowerFallbackText = slide.fallbackText.toLocaleLowerCase() + slide.lowerFallbackText = slide.fallbackText.toLocaleLowerCase() ); if (lowerFallback.includes(normalizedQuery)) { matches.push({ slideIndex: slide.slideIndex, shapeIndex: null, text: slide.fallbackText });