@file-viewer/doc 2.2.4 → 2.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,7 @@ export declare const DOC_CONTROL: {
2
2
  paragraph: string;
3
3
  cellMark: string;
4
4
  picture: string;
5
+ textBoxPlaceholder: string;
5
6
  fieldStart: string;
6
7
  fieldSeparator: string;
7
8
  fieldEnd: string;
@@ -2,6 +2,7 @@ export const DOC_CONTROL = {
2
2
  paragraph: '\u000d',
3
3
  cellMark: '\u0007',
4
4
  picture: '\u0001',
5
+ textBoxPlaceholder: '\u0008',
5
6
  fieldStart: '\u0013',
6
7
  fieldSeparator: '\u0014',
7
8
  fieldEnd: '\u0015',
@@ -123,13 +123,68 @@ function normalizePlainTextChar(ch) {
123
123
  case DOC_CONTROL.nonBreakingHyphen:
124
124
  return '-';
125
125
  case DOC_CONTROL.nonRequiredHyphen:
126
- return '';
127
126
  case DOC_CONTROL.annotationRef:
127
+ case DOC_CONTROL.textBoxPlaceholder:
128
128
  return '';
129
129
  default:
130
130
  return ch;
131
131
  }
132
132
  }
133
+ function normalizeParagraphText(text) {
134
+ return text.replaceAll(DOC_CONTROL.textBoxPlaceholder, '');
135
+ }
136
+ function getDocumentStoryRanges(fib, documentLength) {
137
+ const storyLengths = [
138
+ ['main', fib.fibRgLw.ccpText],
139
+ ['footnote', fib.fibRgLw.ccpFtn],
140
+ ['header', fib.fibRgLw.ccpHdd],
141
+ ['macro', fib.fibRgLw.ccpMcr],
142
+ ['annotation', fib.fibRgLw.ccpAtn],
143
+ ['endnote', fib.fibRgLw.ccpEdn],
144
+ ['textbox', fib.fibRgLw.ccpTxbx],
145
+ ['header-textbox', fib.fibRgLw.ccpHdrTxbx],
146
+ ];
147
+ const hasDeclaredStories = storyLengths.some(([, length]) => length > 0);
148
+ if (!hasDeclaredStories)
149
+ return [{ kind: 'main', cpStart: 0, cpEnd: documentLength }];
150
+ const ranges = [];
151
+ let cp = 0;
152
+ for (const [kind, rawLength] of storyLengths) {
153
+ const length = Math.max(0, rawLength || 0);
154
+ const cpStart = Math.min(cp, documentLength);
155
+ cp += length;
156
+ const cpEnd = Math.min(cp, documentLength);
157
+ ranges.push({ kind, cpStart, cpEnd });
158
+ }
159
+ return ranges;
160
+ }
161
+ function buildStoryParagraphRanges(story, documentText, papxRuns) {
162
+ if (story.cpEnd <= story.cpStart)
163
+ return [];
164
+ const runs = papxRuns.filter((run) => run.cpStart < story.cpEnd && run.cpEnd > story.cpStart);
165
+ if (runs.length) {
166
+ return runs.map((run) => {
167
+ const cpStart = Math.max(story.cpStart, run.cpStart);
168
+ const cpEnd = Math.min(story.cpEnd, run.cpEnd);
169
+ return {
170
+ cpStart,
171
+ cpEnd,
172
+ terminator: documentText[cpEnd - 1] || '',
173
+ styleId: run.styleId,
174
+ properties: run.properties,
175
+ storyKind: story.kind,
176
+ };
177
+ }).filter((range) => range.cpEnd > range.cpStart);
178
+ }
179
+ return splitParagraphRanges(documentText.slice(story.cpStart, story.cpEnd)).map((range) => ({
180
+ ...range,
181
+ cpStart: range.cpStart + story.cpStart,
182
+ cpEnd: range.cpEnd + story.cpStart,
183
+ styleId: 0,
184
+ properties: [],
185
+ storyKind: story.kind,
186
+ }));
187
+ }
133
188
  function buildInlineNodes(segments, resolveAsset) {
134
189
  const output = [];
135
190
  const fieldStack = [];
@@ -207,7 +262,7 @@ function buildInlineNodes(segments, resolveAsset) {
207
262
  }
208
263
  return output;
209
264
  }
210
- function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef) {
265
+ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef, forceReadableColors = false) {
211
266
  const overlaps = getOverlappingRuns(chpxRuns, range.cpStart, range.cpEnd, cursorRef);
212
267
  const boundaries = new Set([range.cpStart, range.cpEnd]);
213
268
  for (const run of overlaps) {
@@ -227,6 +282,8 @@ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps
227
282
  const charStyleProps = directState.charStyleId != null ? styles.resolveStyle(directState.charStyleId).charProps : [];
228
283
  const finalProps = mergePropertyArrays(baseCharProps, charStyleProps, directProps);
229
284
  const state = charPropsToState(finalProps);
285
+ if (forceReadableColors && state.colorIndex === 8)
286
+ state.colorIndex = 1;
230
287
  const font = resolveFont(state.fontFamilyId);
231
288
  if (font)
232
289
  state.fontFamily = font.name || font.altName || undefined;
@@ -245,6 +302,8 @@ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps
245
302
  }
246
303
  if (!segments.length && paragraphText) {
247
304
  const state = charPropsToState(baseCharProps);
305
+ if (forceReadableColors && state.colorIndex === 8)
306
+ state.colorIndex = 1;
248
307
  const font = resolveFont(state.fontFamilyId);
249
308
  if (font)
250
309
  state.fontFamily = font.name || font.altName || undefined;
@@ -264,7 +323,7 @@ function buildParagraphModel(range, paragraphText, styles, fonts, chpxRuns, reso
264
323
  const tableState = tablePropsToState(tableProps);
265
324
  const baseCharProps = paraStyle.charProps;
266
325
  const resolveFont = (fontId) => fonts.byIndex(fontId);
267
- const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor);
326
+ const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor, range.storyKind === 'textbox' || range.storyKind === 'header-textbox');
268
327
  const inlines = buildInlineNodes(segments, resolveAsset);
269
328
  return {
270
329
  id: uniqueId('para'),
@@ -601,35 +660,41 @@ export function parseMsDoc(input, options = {}) {
601
660
  const clx = parseClx(tableBytes, fib.fibRgFcLcb);
602
661
  const pieceTexts = buildPieceTextCache(wordBytes, clx);
603
662
  const documentText = pieceTexts.join('');
604
- const mainStoryEnd = fib.fibRgLw.ccpText > 0 ? fib.fibRgLw.ccpText : documentText.length;
663
+ const stories = getDocumentStoryRanges(fib, documentText.length);
664
+ const mainStory = stories.find((story) => story.kind === 'main') || { kind: 'main', cpStart: 0, cpEnd: documentText.length };
665
+ const supplementalTextStories = stories.filter((story) => ((story.kind === 'textbox' || story.kind === 'header-textbox')
666
+ && normalizeParagraphText(documentText.slice(story.cpStart, story.cpEnd)).replace(/[\r\u0007]/g, '').trim().length > 0));
605
667
  const styles = parseStyles(tableBytes, fib.fibRgFcLcb);
606
668
  const fonts = parseFonts(tableBytes, fib.fibRgFcLcb);
607
- const chpxRuns = readChpxRuns(wordBytes, tableBytes, fib, clx).filter((run) => run.cpStart < mainStoryEnd);
669
+ const relevantStoryEnd = Math.max(mainStory.cpEnd, ...supplementalTextStories.map((story) => story.cpEnd));
670
+ const chpxRuns = readChpxRuns(wordBytes, tableBytes, fib, clx)
671
+ .filter((run) => run.cpStart < relevantStoryEnd)
672
+ .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, relevantStoryEnd) }));
608
673
  const papxRuns = readPapxRuns(wordBytes, tableBytes, fib, clx, dataBytes)
609
- .filter((run) => run.cpStart < mainStoryEnd)
610
- .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, mainStoryEnd) }));
611
- const ranges = papxRuns.length
612
- ? papxRuns.map((run) => ({
613
- cpStart: run.cpStart,
614
- cpEnd: run.cpEnd,
615
- terminator: documentText[run.cpEnd - 1] || '',
616
- styleId: run.styleId,
617
- properties: run.properties,
618
- }))
619
- : splitParagraphRanges(documentText.slice(0, mainStoryEnd)).map((range) => ({ ...range, styleId: 0, properties: [] }));
674
+ .filter((run) => run.cpStart < relevantStoryEnd)
675
+ .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, relevantStoryEnd) }));
676
+ const ranges = [mainStory, ...supplementalTextStories]
677
+ .flatMap((story) => buildStoryParagraphRanges(story, documentText, papxRuns));
678
+ if (supplementalTextStories.length) {
679
+ 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 });
680
+ }
620
681
  const objectPool = extractObjectPool(cfb);
621
682
  const assets = [];
622
683
  const usedAttachmentNames = new Set();
623
684
  const assetCache = new Map();
624
685
  const resolveAsset = createAssetResolver(dataBytes, objectPool, assets, usedAttachmentNames, assetCache, options);
625
686
  const chpxCursor = { index: 0 };
626
- const paragraphs = ranges.map((range) => {
687
+ const paragraphs = ranges.flatMap((range) => {
627
688
  const rawParagraphText = getTextByCp(wordBytes, clx, pieceTexts, range.cpStart, range.cpEnd);
628
689
  const terminator = range.terminator === DOC_CONTROL.paragraph || range.terminator === DOC_CONTROL.cellMark ? range.terminator : '';
629
- const paragraphText = terminator && rawParagraphText.endsWith(terminator)
690
+ const rawContent = terminator && rawParagraphText.endsWith(terminator)
630
691
  ? rawParagraphText.slice(0, -1)
631
692
  : rawParagraphText;
632
- return buildParagraphModel({ ...range, terminator }, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor);
693
+ const paragraphText = normalizeParagraphText(rawContent);
694
+ const isTextBoxStory = range.storyKind === 'textbox' || range.storyKind === 'header-textbox';
695
+ if ((rawContent.includes(DOC_CONTROL.textBoxPlaceholder) || isTextBoxStory) && !paragraphText.trim())
696
+ return [];
697
+ return [buildParagraphModel({ ...range, terminator }, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor)];
633
698
  });
634
699
  normalizeRowEndOnlyTables(paragraphs);
635
700
  const blocks = buildBlocks(paragraphs);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@file-viewer/doc",
3
- "version": "2.2.4",
3
+ "version": "2.2.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Browser-native legacy MS-DOC (.doc) parser and HTML renderer extracted from the public msdoc-viewer 0.2.0 line.",
@@ -70,6 +70,7 @@
70
70
  "type-check": "tsc -b tsconfig.json",
71
71
  "verify:table-spec": "pnpm build && node scripts/verify-table-spec.mjs",
72
72
  "verify:github-34": "pnpm build && node scripts/verify-github-34.mjs",
73
- "verify:github-87": "pnpm build && node scripts/verify-github-87.mjs"
73
+ "verify:github-87": "pnpm build && node scripts/verify-github-87.mjs",
74
+ "verify:github-171": "pnpm build && node scripts/verify-github-171.mjs"
74
75
  }
75
76
  }