@file-viewer/doc 2.1.10

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.
@@ -0,0 +1,549 @@
1
+ import { parseCFB } from '../core/cfb.js';
2
+ import { cleanTextControlChars, pushWarning, shallowEqual, uniqueId } from '../core/utils.js';
3
+ import { parseClx, buildPieceTextCache, getTextByCp, splitParagraphRanges } from './clx.js';
4
+ import { DOC_CONTROL } from './constants.js';
5
+ import { parseFib } from './fib.js';
6
+ import { readChpxRuns, readPapxRuns } from './fkp.js';
7
+ import { parseFonts } from './fonts.js';
8
+ import { extractObjectPool, extractPictureAsset } from './objects.js';
9
+ import { applyTableStateToCells, charPropsToState, getTableDepth, paraPropsToState, tablePropsToState, } from './properties.js';
10
+ import { mergePropertyArrays, parseStyles, splitPropertiesByKind } from './styles.js';
11
+ function getOverlappingRuns(runs, cpStart, cpEnd, cursorRef) {
12
+ let cursor = cursorRef?.index || 0;
13
+ while (cursor < runs.length && runs[cursor].cpEnd <= cpStart)
14
+ cursor += 1;
15
+ if (cursorRef)
16
+ cursorRef.index = cursor;
17
+ const list = [];
18
+ let i = cursor;
19
+ while (i < runs.length && runs[i].cpStart < cpEnd) {
20
+ if (runs[i].cpEnd > cpStart)
21
+ list.push(runs[i]);
22
+ i += 1;
23
+ }
24
+ return list;
25
+ }
26
+ function normalizeTextStyleName(name) {
27
+ return String(name || '').trim();
28
+ }
29
+ function decodeFieldInstruction(instruction) {
30
+ const normalized = String(instruction || '').replace(/[\r\n\t]+/g, ' ').replace(/\s+/g, ' ').trim();
31
+ if (!normalized)
32
+ return null;
33
+ const upper = normalized.toUpperCase();
34
+ if (upper.startsWith('HYPERLINK')) {
35
+ const body = normalized.slice(9).trim();
36
+ const quoted = body.match(/"([^"]+)"/);
37
+ const href = quoted?.[1] || body.split(/\s+/)[0] || '';
38
+ return href ? { type: 'hyperlink', href } : null;
39
+ }
40
+ if (upper.startsWith('INCLUDEPICTURE')) {
41
+ const body = normalized.slice('INCLUDEPICTURE'.length).trim();
42
+ const quoted = body.match(/"([^"]+)"/);
43
+ return { type: 'includePicture', target: quoted?.[1] || body.split(/\s+/)[0] || '' };
44
+ }
45
+ if (upper.startsWith('EMBED')) {
46
+ return { type: 'embed', raw: normalized };
47
+ }
48
+ if (upper.startsWith('LINK')) {
49
+ return { type: 'link', raw: normalized };
50
+ }
51
+ return { type: 'unknown', raw: normalized };
52
+ }
53
+ function mergeTextNode(target, node) {
54
+ if (!node.text)
55
+ return;
56
+ const last = target[target.length - 1];
57
+ if (last && last.type === 'text' && last.href === node.href && shallowEqual(last.style, node.style)) {
58
+ last.text += node.text;
59
+ return;
60
+ }
61
+ target.push(node);
62
+ }
63
+ function emitInline(targetStack, output, node) {
64
+ if (!node)
65
+ return;
66
+ const target = targetStack.length ? targetStack[targetStack.length - 1].nodes : output;
67
+ if (node.type === 'text')
68
+ mergeTextNode(target, node);
69
+ else
70
+ target.push(node);
71
+ }
72
+ function getObjectPoolInfo(objectPool, pictureOffset) {
73
+ const candidates = [
74
+ `_${pictureOffset}`,
75
+ `_${String(pictureOffset)}`,
76
+ `_${pictureOffset.toString(16)}`,
77
+ `_${pictureOffset.toString(16).toUpperCase()}`,
78
+ ];
79
+ for (const key of candidates) {
80
+ if (objectPool.has(key))
81
+ return objectPool.get(key) || null;
82
+ }
83
+ return null;
84
+ }
85
+ function createAssetResolver(dataBytes, objectPool, assets, usedAttachmentNames, assetCache, options = {}) {
86
+ return function resolveAsset(charState) {
87
+ const pictureOffset = charState?.pictureOffset;
88
+ if (pictureOffset == null)
89
+ return null;
90
+ if (assetCache.has(pictureOffset))
91
+ return assetCache.get(pictureOffset) || null;
92
+ let asset = null;
93
+ const objectInfo = getObjectPoolInfo(objectPool, pictureOffset);
94
+ if ((charState.ole2 || charState.object || charState.data) && objectInfo?.attachment) {
95
+ asset = objectInfo.attachment;
96
+ usedAttachmentNames.add(objectInfo.entry.name);
97
+ }
98
+ if (!asset && dataBytes?.length) {
99
+ const extracted = extractPictureAsset(dataBytes, pictureOffset, options);
100
+ if (extracted && extracted.mime !== 'application/octet-stream') {
101
+ asset = extracted;
102
+ }
103
+ else if (!asset && objectInfo?.attachment) {
104
+ asset = objectInfo.attachment;
105
+ usedAttachmentNames.add(objectInfo.entry.name);
106
+ }
107
+ else if (extracted) {
108
+ asset = extracted;
109
+ }
110
+ }
111
+ if (!asset && objectInfo?.attachment) {
112
+ asset = objectInfo.attachment;
113
+ usedAttachmentNames.add(objectInfo.entry.name);
114
+ }
115
+ if (asset)
116
+ assets.push(asset);
117
+ assetCache.set(pictureOffset, asset);
118
+ return asset;
119
+ };
120
+ }
121
+ function normalizePlainTextChar(ch) {
122
+ switch (ch) {
123
+ case DOC_CONTROL.nonBreakingHyphen:
124
+ return '-';
125
+ case DOC_CONTROL.nonRequiredHyphen:
126
+ return '';
127
+ case DOC_CONTROL.annotationRef:
128
+ return '';
129
+ default:
130
+ return ch;
131
+ }
132
+ }
133
+ function buildInlineNodes(segments, resolveAsset) {
134
+ const output = [];
135
+ const fieldStack = [];
136
+ for (const segment of segments) {
137
+ if (!segment.text || segment.state.hidden)
138
+ continue;
139
+ for (const rawCh of segment.text) {
140
+ const ch = normalizePlainTextChar(rawCh);
141
+ if (ch === DOC_CONTROL.fieldStart) {
142
+ fieldStack.push({ instruction: '', parsed: null, readingInstruction: true, nodes: [] });
143
+ continue;
144
+ }
145
+ if (ch === DOC_CONTROL.fieldSeparator) {
146
+ const current = fieldStack[fieldStack.length - 1];
147
+ if (current) {
148
+ current.parsed = decodeFieldInstruction(current.instruction);
149
+ current.readingInstruction = false;
150
+ }
151
+ continue;
152
+ }
153
+ if (ch === DOC_CONTROL.fieldEnd) {
154
+ const current = fieldStack.pop();
155
+ if (!current)
156
+ continue;
157
+ let nodes = current.nodes;
158
+ if (current.readingInstruction) {
159
+ current.parsed = decodeFieldInstruction(current.instruction);
160
+ }
161
+ if (current.parsed?.type === 'hyperlink') {
162
+ const href = current.parsed.href;
163
+ nodes = nodes.map((node) => {
164
+ if (node.type === 'lineBreak' || node.type === 'pageBreak')
165
+ return node;
166
+ return { ...node, href };
167
+ });
168
+ }
169
+ for (const node of nodes)
170
+ emitInline(fieldStack, output, node);
171
+ continue;
172
+ }
173
+ const currentField = fieldStack[fieldStack.length - 1];
174
+ if (currentField?.readingInstruction) {
175
+ currentField.instruction += ch;
176
+ continue;
177
+ }
178
+ if (ch === DOC_CONTROL.hardLineBreak) {
179
+ emitInline(fieldStack, output, { type: 'lineBreak' });
180
+ continue;
181
+ }
182
+ if (ch === DOC_CONTROL.pageBreak) {
183
+ emitInline(fieldStack, output, { type: 'pageBreak' });
184
+ continue;
185
+ }
186
+ if (ch === DOC_CONTROL.picture) {
187
+ const asset = resolveAsset(segment.state);
188
+ if (asset?.type === 'image') {
189
+ emitInline(fieldStack, output, { type: 'image', asset, style: segment.state });
190
+ }
191
+ else if (asset?.type === 'attachment') {
192
+ emitInline(fieldStack, output, { type: 'attachment', asset, style: segment.state });
193
+ }
194
+ continue;
195
+ }
196
+ emitInline(fieldStack, output, {
197
+ type: 'text',
198
+ text: cleanTextControlChars(ch),
199
+ style: segment.state,
200
+ });
201
+ }
202
+ }
203
+ while (fieldStack.length) {
204
+ const current = fieldStack.pop();
205
+ for (const node of current.nodes)
206
+ emitInline(fieldStack, output, node);
207
+ }
208
+ return output;
209
+ }
210
+ function buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, cursorRef) {
211
+ const overlaps = getOverlappingRuns(chpxRuns, range.cpStart, range.cpEnd, cursorRef);
212
+ const boundaries = new Set([range.cpStart, range.cpEnd]);
213
+ for (const run of overlaps) {
214
+ boundaries.add(Math.max(range.cpStart, run.cpStart));
215
+ boundaries.add(Math.min(range.cpEnd, run.cpEnd));
216
+ }
217
+ const points = Array.from(boundaries).sort((a, b) => a - b);
218
+ const segments = [];
219
+ for (let i = 0; i < points.length - 1; i += 1) {
220
+ const cpStart = points[i];
221
+ const cpEnd = points[i + 1];
222
+ if (cpEnd <= cpStart)
223
+ continue;
224
+ const coveringRun = overlaps.find((run) => run.cpStart <= cpStart && run.cpEnd >= cpEnd);
225
+ const directProps = coveringRun?.properties || [];
226
+ const directState = charPropsToState(directProps);
227
+ const charStyleProps = directState.charStyleId != null ? styles.resolveStyle(directState.charStyleId).charProps : [];
228
+ const finalProps = mergePropertyArrays(baseCharProps, charStyleProps, directProps);
229
+ const state = charPropsToState(finalProps);
230
+ const font = resolveFont(state.fontFamilyId);
231
+ if (font)
232
+ state.fontFamily = font.name || font.altName || undefined;
233
+ const localStart = cpStart - range.cpStart;
234
+ const localEnd = cpEnd - range.cpStart;
235
+ const text = paragraphText.slice(localStart, localEnd);
236
+ if (!text)
237
+ continue;
238
+ const last = segments[segments.length - 1];
239
+ if (last && shallowEqual(last.state, state)) {
240
+ last.text += text;
241
+ last.cpEnd = cpEnd;
242
+ continue;
243
+ }
244
+ segments.push({ cpStart, cpEnd, text, state });
245
+ }
246
+ if (!segments.length && paragraphText) {
247
+ const state = charPropsToState(baseCharProps);
248
+ const font = resolveFont(state.fontFamilyId);
249
+ if (font)
250
+ state.fontFamily = font.name || font.altName || undefined;
251
+ segments.push({ cpStart: range.cpStart, cpEnd: range.cpEnd, text: paragraphText, state });
252
+ }
253
+ return segments;
254
+ }
255
+ function buildParagraphModel(range, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor) {
256
+ const directSplit = splitPropertiesByKind(range.properties || []);
257
+ const paraStyleId = range.styleId || directSplit.para.find((item) => item.name === 'styleId')?.value || 0;
258
+ const paraStyle = styles.resolveStyle(paraStyleId);
259
+ const paraProps = mergePropertyArrays(paraStyle.paraProps, directSplit.para);
260
+ const paraState = paraPropsToState(paraProps);
261
+ const directTableState = tablePropsToState(directSplit.table);
262
+ const tableStyleProps = directTableState.styleId != null ? styles.resolveStyle(directTableState.styleId).tableProps : [];
263
+ const tableProps = mergePropertyArrays(tableStyleProps, directSplit.table);
264
+ const tableState = tablePropsToState(tableProps);
265
+ const baseCharProps = paraStyle.charProps;
266
+ const resolveFont = (fontId) => fonts.byIndex(fontId);
267
+ const segments = buildCharSegments(range, paragraphText, chpxRuns, styles, baseCharProps, resolveFont, chpxCursor);
268
+ const inlines = buildInlineNodes(segments, resolveAsset);
269
+ return {
270
+ id: uniqueId('para'),
271
+ cpStart: range.cpStart,
272
+ cpEnd: range.cpEnd,
273
+ terminator: range.terminator || '',
274
+ text: paragraphText,
275
+ rawProperties: range.properties || [],
276
+ styleId: paraStyleId,
277
+ styleName: normalizeTextStyleName(styles.styles.get(paraStyleId)?.name),
278
+ paraProps,
279
+ paraState,
280
+ tableProps,
281
+ tableState,
282
+ segments,
283
+ inlines,
284
+ };
285
+ }
286
+ function finalizeTableGrid(rows) {
287
+ for (const row of rows) {
288
+ for (let i = 0; i < row.cells.length; i += 1) {
289
+ const cell = row.cells[i];
290
+ const merge = cell.meta?.merge || 0;
291
+ cell.colIndex = i;
292
+ cell.colspan = 1;
293
+ cell.rowspan = 1;
294
+ cell.hidden = false;
295
+ if (merge === 1) {
296
+ cell.hidden = true;
297
+ continue;
298
+ }
299
+ if (merge > 1) {
300
+ let j = i + 1;
301
+ while (j < row.cells.length && (row.cells[j].meta?.merge || 0) === 1) {
302
+ row.cells[j].hidden = true;
303
+ cell.colspan += 1;
304
+ j += 1;
305
+ }
306
+ }
307
+ }
308
+ }
309
+ for (let rowIndex = 0; rowIndex < rows.length; rowIndex += 1) {
310
+ const row = rows[rowIndex];
311
+ for (const cell of row.cells) {
312
+ if (cell.hidden)
313
+ continue;
314
+ const vertMerge = cell.meta?.vertMerge || 0;
315
+ if (vertMerge === 1) {
316
+ cell.hidden = true;
317
+ continue;
318
+ }
319
+ if (vertMerge > 1) {
320
+ let nextIndex = rowIndex + 1;
321
+ while (nextIndex < rows.length) {
322
+ let canMerge = true;
323
+ for (let col = cell.colIndex || 0; col < (cell.colIndex || 0) + (cell.colspan || 1); col += 1) {
324
+ const nextCell = rows[nextIndex].cells[col];
325
+ if (!nextCell || (nextCell.meta?.vertMerge || 0) !== 1) {
326
+ canMerge = false;
327
+ break;
328
+ }
329
+ }
330
+ if (!canMerge)
331
+ break;
332
+ for (let col = cell.colIndex || 0; col < (cell.colIndex || 0) + (cell.colspan || 1); col += 1) {
333
+ rows[nextIndex].cells[col].hidden = true;
334
+ }
335
+ cell.rowspan = (cell.rowspan || 1) + 1;
336
+ nextIndex += 1;
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+ function paragraphToBlock(paragraph) {
343
+ return {
344
+ type: 'paragraph',
345
+ id: paragraph.id,
346
+ styleId: paragraph.styleId,
347
+ styleName: paragraph.styleName,
348
+ paraState: paragraph.paraState,
349
+ inlines: paragraph.inlines,
350
+ text: paragraph.text,
351
+ };
352
+ }
353
+ function normalizeRowEndOnlyTables(paragraphs) {
354
+ for (let index = 0; index < paragraphs.length; index += 1) {
355
+ const rowEnd = paragraphs[index];
356
+ const cellCount = rowEnd.tableState.defTable?.cells?.length || 0;
357
+ if (!rowEnd.paraState.tableRowEnd || !cellCount)
358
+ continue;
359
+ rowEnd.paraState.inTable = true;
360
+ rowEnd.paraState.itap = Math.max(1, rowEnd.paraState.itap || 0);
361
+ let remainingCells = cellCount;
362
+ for (let cursor = index - 1; cursor >= 0 && remainingCells > 0; cursor -= 1) {
363
+ const paragraph = paragraphs[cursor];
364
+ if (paragraph.paraState.tableRowEnd || paragraph.paraState.innerTableRowEnd)
365
+ break;
366
+ if (paragraph.terminator !== DOC_CONTROL.cellMark)
367
+ break;
368
+ paragraph.paraState.inTable = true;
369
+ paragraph.paraState.itap = Math.max(rowEnd.paraState.itap || 1, paragraph.paraState.itap || 0);
370
+ paragraph.tableState = rowEnd.tableState;
371
+ paragraph.tableProps = rowEnd.tableProps;
372
+ remainingCells -= 1;
373
+ }
374
+ }
375
+ }
376
+ function buildTableBlock(tableParagraphs) {
377
+ const rows = [];
378
+ let pendingRow = { cells: [] };
379
+ let pendingCellParagraphs = [];
380
+ for (const paragraph of tableParagraphs) {
381
+ pendingCellParagraphs.push(paragraph);
382
+ if (paragraph.terminator === DOC_CONTROL.cellMark) {
383
+ pendingRow.cells.push({
384
+ id: uniqueId('cell'),
385
+ paragraphs: pendingCellParagraphs.map(paragraphToBlock),
386
+ meta: null,
387
+ });
388
+ pendingCellParagraphs = [];
389
+ if (paragraph.paraState.tableRowEnd || paragraph.paraState.innerTableRowEnd) {
390
+ const cellDefs = applyTableStateToCells(paragraph.tableState);
391
+ while (cellDefs.length &&
392
+ pendingRow.cells.length > cellDefs.length &&
393
+ pendingRow.cells[pendingRow.cells.length - 1].paragraphs.every((block) => !block.text && !(block.inlines || []).length)) {
394
+ pendingRow.cells.pop();
395
+ }
396
+ pendingRow.cells.forEach((cell, index) => {
397
+ cell.meta = cellDefs[index] || { index };
398
+ });
399
+ const gridWidthTwips = cellDefs.length
400
+ ? ((cellDefs[cellDefs.length - 1].rightBoundary || 0) - (cellDefs[0].leftBoundary || 0))
401
+ : 0;
402
+ rows.push({
403
+ id: uniqueId('row'),
404
+ cells: pendingRow.cells,
405
+ state: paragraph.tableState,
406
+ gridWidthTwips,
407
+ });
408
+ pendingRow = { cells: [] };
409
+ }
410
+ }
411
+ }
412
+ if (pendingCellParagraphs.length) {
413
+ pendingRow.cells.push({ id: uniqueId('cell'), paragraphs: pendingCellParagraphs.map(paragraphToBlock), meta: null });
414
+ }
415
+ if (pendingRow.cells.length) {
416
+ rows.push({ id: uniqueId('row'), cells: pendingRow.cells, state: tableParagraphs[0]?.tableState || tablePropsToState([]), gridWidthTwips: 0 });
417
+ }
418
+ finalizeTableGrid(rows);
419
+ const gridWidthTwips = rows.find((row) => row.gridWidthTwips)?.gridWidthTwips || 0;
420
+ const depth = Math.max(...tableParagraphs.map((paragraph) => getTableDepth(paragraph.paraState)), 1);
421
+ return {
422
+ type: 'table',
423
+ id: uniqueId('table'),
424
+ depth,
425
+ rows,
426
+ state: rows[0]?.state || tablePropsToState([]),
427
+ gridWidthTwips,
428
+ };
429
+ }
430
+ function buildBlocks(paragraphs) {
431
+ const blocks = [];
432
+ let index = 0;
433
+ while (index < paragraphs.length) {
434
+ const paragraph = paragraphs[index];
435
+ const depth = getTableDepth(paragraph.paraState);
436
+ if (depth <= 0) {
437
+ blocks.push(paragraphToBlock(paragraph));
438
+ index += 1;
439
+ continue;
440
+ }
441
+ const tableParagraphs = [];
442
+ while (index < paragraphs.length && getTableDepth(paragraphs[index].paraState) > 0) {
443
+ tableParagraphs.push(paragraphs[index]);
444
+ index += 1;
445
+ }
446
+ blocks.push(buildTableBlock(tableParagraphs));
447
+ }
448
+ return blocks;
449
+ }
450
+ /**
451
+ * Main MS-DOC entry point.
452
+ * It parses the OLE container, restores text through the piece table, resolves
453
+ * paragraph/character/table properties, and finally produces a normalized AST
454
+ * that the HTML renderer can consume.
455
+ */
456
+ export function parseMsDoc(input, options = {}) {
457
+ const warnings = [];
458
+ const cfb = parseCFB(input, options);
459
+ warnings.push(...(cfb.warnings || []));
460
+ const wordBytes = cfb.getStream('/WordDocument');
461
+ if (!wordBytes)
462
+ throw new Error('Missing WordDocument stream');
463
+ const fib = parseFib(wordBytes);
464
+ if (fib.base.wIdent !== 0xA5EC) {
465
+ pushWarning(warnings, `Unexpected FIB identifier: 0x${fib.base.wIdent.toString(16)}`);
466
+ }
467
+ if (fib.base.fEncrypted) {
468
+ throw new Error('Encrypted .doc files are not supported yet');
469
+ }
470
+ const tableBytes = cfb.getStream(fib.base.fWhichTblStm ? '/1Table' : '/0Table');
471
+ if (!tableBytes)
472
+ throw new Error('Missing table stream');
473
+ const dataBytes = cfb.getStream('/Data') || new Uint8Array(0);
474
+ const clx = parseClx(tableBytes, fib.fibRgFcLcb);
475
+ const pieceTexts = buildPieceTextCache(wordBytes, clx);
476
+ const documentText = pieceTexts.join('');
477
+ const mainStoryEnd = fib.fibRgLw.ccpText > 0 ? fib.fibRgLw.ccpText : documentText.length;
478
+ const styles = parseStyles(tableBytes, fib.fibRgFcLcb);
479
+ const fonts = parseFonts(tableBytes, fib.fibRgFcLcb);
480
+ const chpxRuns = readChpxRuns(wordBytes, tableBytes, fib, clx).filter((run) => run.cpStart < mainStoryEnd);
481
+ const papxRuns = readPapxRuns(wordBytes, tableBytes, fib, clx, dataBytes)
482
+ .filter((run) => run.cpStart < mainStoryEnd)
483
+ .map((run) => ({ ...run, cpEnd: Math.min(run.cpEnd, mainStoryEnd) }));
484
+ const ranges = papxRuns.length
485
+ ? papxRuns.map((run) => ({
486
+ cpStart: run.cpStart,
487
+ cpEnd: run.cpEnd,
488
+ terminator: documentText[run.cpEnd - 1] || '',
489
+ styleId: run.styleId,
490
+ properties: run.properties,
491
+ }))
492
+ : splitParagraphRanges(documentText.slice(0, mainStoryEnd)).map((range) => ({ ...range, styleId: 0, properties: [] }));
493
+ const objectPool = extractObjectPool(cfb);
494
+ const assets = [];
495
+ const usedAttachmentNames = new Set();
496
+ const assetCache = new Map();
497
+ const resolveAsset = createAssetResolver(dataBytes, objectPool, assets, usedAttachmentNames, assetCache, options);
498
+ const chpxCursor = { index: 0 };
499
+ const paragraphs = ranges.map((range) => {
500
+ const rawParagraphText = getTextByCp(wordBytes, clx, pieceTexts, range.cpStart, range.cpEnd);
501
+ const terminator = range.terminator === DOC_CONTROL.paragraph || range.terminator === DOC_CONTROL.cellMark ? range.terminator : '';
502
+ const paragraphText = terminator && rawParagraphText.endsWith(terminator)
503
+ ? rawParagraphText.slice(0, -1)
504
+ : rawParagraphText;
505
+ return buildParagraphModel({ ...range, terminator }, paragraphText, styles, fonts, chpxRuns, resolveAsset, chpxCursor);
506
+ });
507
+ normalizeRowEndOnlyTables(paragraphs);
508
+ const blocks = buildBlocks(paragraphs);
509
+ const trailingAttachments = Array.from(objectPool.values())
510
+ .filter((item) => item?.attachment && !usedAttachmentNames.has(item.entry.name))
511
+ .map((item) => item.attachment);
512
+ for (const attachment of trailingAttachments)
513
+ assets.push(attachment);
514
+ if (trailingAttachments.length) {
515
+ blocks.push({ type: 'attachments', id: uniqueId('attachments'), items: trailingAttachments });
516
+ }
517
+ return {
518
+ kind: 'msdoc',
519
+ version: 1,
520
+ warnings,
521
+ meta: {
522
+ fib: {
523
+ wIdent: fib.base.wIdent,
524
+ nFib: fib.base.nFib,
525
+ fWhichTblStm: fib.base.fWhichTblStm,
526
+ fComplex: fib.base.fComplex,
527
+ fEncrypted: fib.base.fEncrypted,
528
+ ccpText: fib.fibRgLw.ccpText,
529
+ },
530
+ counts: {
531
+ paragraphs: paragraphs.length,
532
+ blocks: blocks.length,
533
+ assets: assets.length,
534
+ styles: styles.styles.size,
535
+ fonts: fonts.fonts.length,
536
+ },
537
+ },
538
+ fonts: fonts.fonts,
539
+ styles: Array.from(styles.styles.values()).map((style) => ({
540
+ istd: style.istd,
541
+ name: style.name,
542
+ type: style.stdfBase?.stk,
543
+ basedOn: style.stdfBase?.istdBase,
544
+ next: style.stdfBase?.istdNext,
545
+ })),
546
+ blocks,
547
+ assets,
548
+ };
549
+ }
@@ -0,0 +1,18 @@
1
+ import type { CharState, DecodedProperty, ParaState, TableCellMeta, TableState } from '../types.js';
2
+ export declare function propertyArrayToMaps(properties: DecodedProperty[]): {
3
+ char: Record<string, unknown>;
4
+ para: Record<string, unknown>;
5
+ table: Record<string, unknown>;
6
+ };
7
+ export declare function charPropsToState(properties: DecodedProperty[]): CharState;
8
+ export declare function paraPropsToState(properties: DecodedProperty[]): ParaState;
9
+ export declare function tablePropsToState(properties: DecodedProperty[]): TableState;
10
+ export declare function getTableDepth(paraState: ParaState): number;
11
+ export declare function cssTextAlign(value: number | undefined): string;
12
+ export declare function cssUnderline(value: number | undefined): string;
13
+ export declare function cssVerticalAlign(value: number | undefined): string;
14
+ export declare function rangeApply<T>(list: T[], range: {
15
+ first: number;
16
+ lim: number;
17
+ } | undefined, callback: (item: T, index: number) => void): void;
18
+ export declare function applyTableStateToCells(tableState: TableState): TableCellMeta[];