@file-viewer/doc
Advanced tools
@@ -5,2 +5,3 @@ export declare const DOC_CONTROL: { | ||
| picture: string; | ||
| textBoxPlaceholder: string; | ||
| fieldStart: string; | ||
@@ -7,0 +8,0 @@ fieldSeparator: string; |
@@ -5,2 +5,3 @@ export const DOC_CONTROL = { | ||
| picture: '\u0001', | ||
| textBoxPlaceholder: '\u0008', | ||
| fieldStart: '\u0013', | ||
@@ -7,0 +8,0 @@ fieldSeparator: '\u0014', |
+84
-19
@@ -126,4 +126,4 @@ import { parseCFB } from '../core/cfb.js'; | ||
| case DOC_CONTROL.nonRequiredHyphen: | ||
| return ''; | ||
| case DOC_CONTROL.annotationRef: | ||
| case DOC_CONTROL.textBoxPlaceholder: | ||
| return ''; | ||
@@ -134,2 +134,57 @@ default: | ||
| } | ||
| function normalizeParagraphText(text) { | ||
| return text.replaceAll(DOC_CONTROL.textBoxPlaceholder, ''); | ||
| } | ||
| function getDocumentStoryRanges(fib, documentLength) { | ||
| const storyLengths = [ | ||
| ['main', fib.fibRgLw.ccpText], | ||
| ['footnote', fib.fibRgLw.ccpFtn], | ||
| ['header', fib.fibRgLw.ccpHdd], | ||
| ['macro', fib.fibRgLw.ccpMcr], | ||
| ['annotation', fib.fibRgLw.ccpAtn], | ||
| ['endnote', fib.fibRgLw.ccpEdn], | ||
| ['textbox', fib.fibRgLw.ccpTxbx], | ||
| ['header-textbox', fib.fibRgLw.ccpHdrTxbx], | ||
| ]; | ||
| const hasDeclaredStories = storyLengths.some(([, length]) => length > 0); | ||
| if (!hasDeclaredStories) | ||
| return [{ kind: 'main', cpStart: 0, cpEnd: documentLength }]; | ||
| const ranges = []; | ||
| let cp = 0; | ||
| for (const [kind, rawLength] of storyLengths) { | ||
| const length = Math.max(0, rawLength || 0); | ||
| const cpStart = Math.min(cp, documentLength); | ||
| cp += length; | ||
| const cpEnd = Math.min(cp, documentLength); | ||
| ranges.push({ kind, cpStart, cpEnd }); | ||
| } | ||
| return ranges; | ||
| } | ||
| function buildStoryParagraphRanges(story, documentText, papxRuns) { | ||
| if (story.cpEnd <= story.cpStart) | ||
| return []; | ||
| const runs = papxRuns.filter((run) => run.cpStart < story.cpEnd && run.cpEnd > story.cpStart); | ||
| if (runs.length) { | ||
| return runs.map((run) => { | ||
| const cpStart = Math.max(story.cpStart, run.cpStart); | ||
| const cpEnd = Math.min(story.cpEnd, run.cpEnd); | ||
| return { | ||
| cpStart, | ||
| cpEnd, | ||
| terminator: documentText[cpEnd - 1] || '', | ||
| styleId: run.styleId, | ||
| properties: run.properties, | ||
| storyKind: story.kind, | ||
| }; | ||
| }).filter((range) => range.cpEnd > range.cpStart); | ||
| } | ||
| return splitParagraphRanges(documentText.slice(story.cpStart, story.cpEnd)).map((range) => ({ | ||
| ...range, | ||
| cpStart: range.cpStart + story.cpStart, | ||
| cpEnd: range.cpEnd + story.cpStart, | ||
| styleId: 0, | ||
| properties: [], | ||
| storyKind: story.kind, | ||
| })); | ||
| } | ||
| function buildInlineNodes(segments, resolveAsset) { | ||
@@ -212,3 +267,3 @@ const output = []; | ||
| } | ||
| function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef) { | ||
| function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef, forceReadableColors = false) { | ||
| const overlaps = getOverlappingRuns(chpxRuns, range.cpStart, range.cpEnd, cursorRef); | ||
@@ -233,2 +288,4 @@ const boundaries = new Set([range.cpStart, range.cpEnd]); | ||
| const state = charPropsToState(finalProps); | ||
| if (forceReadableColors && state.colorIndex === 8) | ||
| state.colorIndex = 1; | ||
| const font = resolveFont(state.fontFamilyId); | ||
@@ -252,2 +309,4 @@ if (font) | ||
| const state = charPropsToState(baseCharProps); | ||
| if (forceReadableColors && state.colorIndex === 8) | ||
| state.colorIndex = 1; | ||
| const font = resolveFont(state.fontFamilyId); | ||
@@ -272,3 +331,3 @@ if (font) | ||
| const resolveFont = (fontId) => fonts.byIndex(fontId); | ||
| const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor); | ||
| const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor, range.storyKind === 'textbox' || range.storyKind === 'header-textbox'); | ||
| const inlines = buildInlineNodes(segments, resolveAsset); | ||
@@ -610,18 +669,20 @@ return { | ||
| const documentText = pieceTexts.join(''); | ||
| const mainStoryEnd = fib.fibRgLw.ccpText > 0 ? fib.fibRgLw.ccpText : documentText.length; | ||
| const stories = getDocumentStoryRanges(fib, documentText.length); | ||
| const mainStory = stories.find((story) => story.kind === 'main') || { kind: 'main', cpStart: 0, cpEnd: documentText.length }; | ||
| const supplementalTextStories = stories.filter((story) => ((story.kind === 'textbox' || story.kind === 'header-textbox') | ||
| && normalizeParagraphText(documentText.slice(story.cpStart, story.cpEnd)).replace(/[\r\u0007]/g, '').trim().length > 0)); | ||
| const styles = parseStyles(tableBytes, fib.fibRgFcLcb); | ||
| const fonts = parseFonts(tableBytes, fib.fibRgFcLcb); | ||
| const chpxRuns = readChpxRuns(wordBytes, tableBytes, fib, clx).filter((run) => run.cpStart < mainStoryEnd); | ||
| const relevantStoryEnd = Math.max(mainStory.cpEnd, ...supplementalTextStories.map((story) => story.cpEnd)); | ||
| const chpxRuns = readChpxRuns(wordBytes, tableBytes, fib, clx) | ||
| .filter((run) => run.cpStart < relevantStoryEnd) | ||
| .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, relevantStoryEnd) })); | ||
| const papxRuns = readPapxRuns(wordBytes, tableBytes, fib, clx, dataBytes) | ||
| .filter((run) => run.cpStart < mainStoryEnd) | ||
| .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, mainStoryEnd) })); | ||
| const ranges = papxRuns.length | ||
| ? papxRuns.map((run) => ({ | ||
| cpStart: run.cpStart, | ||
| cpEnd: run.cpEnd, | ||
| terminator: documentText[run.cpEnd - 1] || '', | ||
| styleId: run.styleId, | ||
| properties: run.properties, | ||
| })) | ||
| : splitParagraphRanges(documentText.slice(0, mainStoryEnd)).map((range) => ({ ...range, styleId: 0, properties: [] })); | ||
| .filter((run) => run.cpStart < relevantStoryEnd) | ||
| .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, relevantStoryEnd) })); | ||
| const ranges = [mainStory, ...supplementalTextStories] | ||
| .flatMap((story) => buildStoryParagraphRanges(story, documentText, papxRuns)); | ||
| if (supplementalTextStories.length) { | ||
| pushWarning(warnings, 'Recovered text from a legacy Word text-box story; text-box positioning is unavailable, so the content was linearized after the main story.', { code: 'MSDOC_TEXTBOX_STORY_LINEARIZED', storyCount: supplementalTextStories.length }); | ||
| } | ||
| const objectPool = extractObjectPool(cfb); | ||
@@ -633,9 +694,13 @@ const assets = []; | ||
| const chpxCursor = { index: 0 }; | ||
| const paragraphs = ranges.map((range) => { | ||
| const paragraphs = ranges.flatMap((range) => { | ||
| const rawParagraphText = getTextByCp(wordBytes, clx, pieceTexts, range.cpStart, range.cpEnd); | ||
| const terminator = range.terminator === DOC_CONTROL.paragraph || range.terminator === DOC_CONTROL.cellMark ? range.terminator : ''; | ||
| const paragraphText = terminator && rawParagraphText.endsWith(terminator) | ||
| const rawContent = terminator && rawParagraphText.endsWith(terminator) | ||
| ? rawParagraphText.slice(0, -1) | ||
| : rawParagraphText; | ||
| return buildParagraphModel({ ...range, terminator }, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor); | ||
| const paragraphText = normalizeParagraphText(rawContent); | ||
| const isTextBoxStory = range.storyKind === 'textbox' || range.storyKind === 'header-textbox'; | ||
| if ((rawContent.includes(DOC_CONTROL.textBoxPlaceholder) || isTextBoxStory) && !paragraphText.trim()) | ||
| return []; | ||
| return [buildParagraphModel({ ...range, terminator }, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor)]; | ||
| }); | ||
@@ -642,0 +707,0 @@ normalizeRowEndOnlyTables(paragraphs); |
+3
-2
| { | ||
| "name": "@file-viewer/doc", | ||
| "version": "2.2.5", | ||
| "version": "2.2.6", | ||
| "private": false, | ||
@@ -73,4 +73,5 @@ "type": "module", | ||
| "verify:github-34": "pnpm build && node scripts/verify-github-34.mjs", | ||
| "verify:github-87": "pnpm build && node scripts/verify-github-87.mjs" | ||
| "verify:github-87": "pnpm build && node scripts/verify-github-87.mjs", | ||
| "verify:github-171": "pnpm build && node scripts/verify-github-171.mjs" | ||
| } | ||
| } |
304474
1.15%7573
0.89%