diff --git a/packages/layout-engine/pm-adapter/src/converters/paragraph.test.ts b/packages/layout-engine/pm-adapter/src/converters/paragraph.test.ts index 01dfcd79a..77e1c294f 100644 --- a/packages/layout-engine/pm-adapter/src/converters/paragraph.test.ts +++ b/packages/layout-engine/pm-adapter/src/converters/paragraph.test.ts @@ -21,7 +21,6 @@ import type { PositionMap, TrackedChangesConfig, HyperlinkConfig, - StyleContext, ThemeColorPalette, NestedConverters, } from '../types.js'; @@ -82,6 +81,10 @@ import { } from '../tracked-changes.js'; const DEFAULT_HYPERLINK_CONFIG: HyperlinkConfig = { enableRichHyperlinks: false }; +const DEFAULT_TEST_FONT_FAMILY = 'Arial, sans-serif'; +const DEFAULT_TEST_FONT_SIZE_PX = (16 * 96) / 72; +const FALLBACK_FONT_FAMILY = 'Times New Roman, sans-serif'; +const FALLBACK_FONT_SIZE_PX = 12; let defaultConverterContext: ConverterContext = { translatedNumbering: {}, translatedLinkedStyles: { @@ -113,7 +116,6 @@ const paragraphToFlowBlocks = ( positions: PositionMap, defaultFont: string, defaultSize: number, - styleContext: StyleContext, trackedChangesConfig?: TrackedChangesConfig, bookmarks?: Map, hyperlinkConfig?: HyperlinkConfig, @@ -126,6 +128,8 @@ const paragraphToFlowBlocks = ( if (isConverters(maybeConverters)) { converters = maybeConverters; + } else if (maybeConverters) { + converterContext = maybeConverters as ConverterContext; } if (isConverters(converterContextOrConverters)) { @@ -134,19 +138,36 @@ const paragraphToFlowBlocks = ( converterContext = converterContextOrConverters as ConverterContext; } + const effectiveConverterContext = + converterContext ?? + ({ + ...defaultConverterContext, + translatedLinkedStyles: { + ...defaultConverterContext.translatedLinkedStyles, + docDefaults: { + ...defaultConverterContext.translatedLinkedStyles.docDefaults, + runProperties: { + ...(defaultConverterContext.translatedLinkedStyles.docDefaults?.runProperties ?? {}), + fontFamily: { + ...(defaultConverterContext.translatedLinkedStyles.docDefaults?.runProperties?.fontFamily ?? {}), + ascii: defaultFont, + }, + fontSize: defaultSize * 2, + }, + }, + }, + } as ConverterContext); + return baseParagraphToFlowBlocks({ para, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig: hyperlinkConfig ?? DEFAULT_HYPERLINK_CONFIG, themeColors, converters: converters as NestedConverters, - converterContext: converterContext ?? defaultConverterContext, + converterContext: effectiveConverterContext, enableComments: true, }); }; @@ -673,7 +694,6 @@ describe('paragraph converters', () => { describe('paragraphToFlowBlocks', () => { let nextBlockId: BlockIdGenerator; let positions: PositionMap; - let styleContext: StyleContext; let converterContext: ConverterContext; beforeEach(() => { @@ -687,7 +707,6 @@ describe('paragraph converters', () => { positions = new WeakMap(); // Setup style context (mock) - styleContext = {}; converterContext = { translatedNumbering: {}, translatedLinkedStyles: { @@ -739,7 +758,7 @@ describe('paragraph converters', () => { content: [], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); expect(blocks[0].kind).toBe('paragraph'); @@ -764,7 +783,7 @@ describe('paragraph converters', () => { }, }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); const paraBlock = blocks[0] as ParagraphBlock; @@ -784,7 +803,7 @@ describe('paragraph converters', () => { content: [], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(0); }); @@ -802,7 +821,7 @@ describe('paragraph converters', () => { content: [{ type: 'text', text: 'Visible text' }], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); const paraBlock = blocks[0] as ParagraphBlock; @@ -815,7 +834,7 @@ describe('paragraph converters', () => { type: 'paragraph', }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); expect(blocks[0].kind).toBe('paragraph'); @@ -834,7 +853,7 @@ describe('paragraph converters', () => { fontSize: 16, }); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); expect(blocks[0].kind).toBe('paragraph'); @@ -844,8 +863,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( textNode, positions, - 'Arial', - 16, + DEFAULT_TEST_FONT_FAMILY, + DEFAULT_TEST_FONT_SIZE_PX, [], undefined, expect.any(Object), @@ -864,7 +883,7 @@ describe('paragraph converters', () => { resolvedParagraphProperties: {}, }); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(2); expect(blocks[0].kind).toBe('pageBreak'); @@ -885,7 +904,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); const paraBlock = blocks[0] as ParagraphBlock; @@ -913,7 +932,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -928,8 +946,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( { type: 'text', text: 'Bold text' }, positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], // Empty marks - marks applied separately after linked styles undefined, expect.any(Object), @@ -968,7 +986,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1005,7 +1022,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1018,8 +1034,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( { type: 'text', text: 'Bold italic' }, positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], // Empty marks - marks applied separately after linked styles undefined, { enableRichHyperlinks: false }, @@ -1056,7 +1072,7 @@ describe('paragraph converters', () => { }; vi.mocked(tabNodeToRun).mockReturnValue(mockTabRun); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(vi.mocked(tabNodeToRun)).toHaveBeenCalledWith(tabNode, positions, 0, {}, []); const paraBlock = blocks[0] as ParagraphBlock; @@ -1069,7 +1085,7 @@ describe('paragraph converters', () => { content: [{ type: 'tab' }, { type: 'tab' }, { type: 'tab' }], }; - paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(vi.mocked(tabNodeToRun)).toHaveBeenNthCalledWith(1, expect.any(Object), positions, 0, {}, []); expect(vi.mocked(tabNodeToRun)).toHaveBeenNthCalledWith(2, expect.any(Object), positions, 1, {}, []); @@ -1084,7 +1100,7 @@ describe('paragraph converters', () => { vi.mocked(tabNodeToRun).mockReturnValue(null); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); const paraBlock = blocks[0] as ParagraphBlock; // Empty paragraph created because no runs were added @@ -1109,13 +1125,13 @@ describe('paragraph converters', () => { }; vi.mocked(tokenNodeToRun).mockReturnValue(mockTokenRun); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(vi.mocked(tokenNodeToRun)).toHaveBeenCalledWith( tokenNode, positions, - 'Arial', - 16, + DEFAULT_TEST_FONT_FAMILY, + DEFAULT_TEST_FONT_SIZE_PX, [], 'pageNumber', expect.any(Object), @@ -1137,14 +1153,13 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(vi.mocked(tokenNodeToRun)).toHaveBeenCalledWith( tokenNode, positions, - 'Arial', - 16, + DEFAULT_TEST_FONT_FAMILY, + DEFAULT_TEST_FONT_SIZE_PX, [], 'totalPageCount', expect.any(Object), @@ -1173,7 +1188,7 @@ describe('paragraph converters', () => { }; vi.mocked(tokenNodeToRun).mockReturnValue(mockTokenRun); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); const paraBlock = blocks[0] as ParagraphBlock; const tokenRun = paraBlock.runs[0] as TextRun; @@ -1193,7 +1208,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(vi.mocked(textNodeToRun)).toHaveBeenCalled(); const paraBlock = blocks[0] as ParagraphBlock; @@ -1219,14 +1234,13 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( expect.any(Object), positions, - 'Arial', - 16, + DEFAULT_TEST_FONT_FAMILY, + DEFAULT_TEST_FONT_SIZE_PX, [], sdtMetadata, expect.any(Object), @@ -1249,7 +1263,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(blocks).toHaveLength(1); @@ -1279,7 +1292,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(blocks).toHaveLength(1); @@ -1309,7 +1321,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(blocks).toHaveLength(1); @@ -1339,7 +1350,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(blocks).toHaveLength(1); @@ -1376,7 +1386,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(blocks).toHaveLength(1); @@ -1411,7 +1420,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1445,7 +1453,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1477,7 +1484,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1489,8 +1495,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( { type: 'text', text: '42' }, positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], undefined, expect.any(Object), @@ -1510,7 +1516,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1522,8 +1527,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( { type: 'text', text: '??' }, positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], undefined, expect.any(Object), @@ -1549,7 +1554,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1561,8 +1565,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( { type: 'text', text: 'fallback' }, positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], undefined, expect.any(Object), @@ -1589,7 +1593,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1602,8 +1605,8 @@ describe('paragraph converters', () => { expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( expect.any(Object), positions, - 'Arial', - 16, + FALLBACK_FONT_FAMILY, + FALLBACK_FONT_SIZE_PX, [], // Empty marks - applied separately to honor enableComments undefined, expect.any(Object), @@ -1636,7 +1639,7 @@ describe('paragraph converters', () => { positions.set(bookmarkNode, { start: 100, end: 100 }); const bookmarks = new Map(); - paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext, undefined, bookmarks); + paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, undefined, bookmarks); expect(bookmarks.get('MyBookmark')).toBe(100); }); @@ -1654,7 +1657,7 @@ describe('paragraph converters', () => { // Should not throw expect(() => { - paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); }).not.toThrow(); }); @@ -1674,7 +1677,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, ); expect(vi.mocked(textNodeToRun)).toHaveBeenCalled(); @@ -1708,7 +1710,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1757,7 +1758,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, trackedChanges, undefined, undefined, @@ -1792,7 +1792,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, { mode: 'final', enabled: true }, undefined, undefined, @@ -1827,7 +1826,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1860,7 +1858,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1892,7 +1889,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1924,7 +1920,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -1963,7 +1958,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, trackedChanges, bookmarks, hyperlinkConfig, @@ -1976,9 +1970,6 @@ describe('paragraph converters', () => { node: tableNode, nextBlockId, positions, - defaultFont: 'Arial', - defaultSize: 16, - styleContext, trackedChangesConfig: trackedChanges, bookmarks, hyperlinkConfig, @@ -1997,7 +1988,7 @@ describe('paragraph converters', () => { content: [{ type: 'text', text: 'Before' }, hardBreakNode, { type: 'text', text: 'After' }], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(3); expect(blocks[1].kind).toBe('pageBreak'); @@ -2014,7 +2005,7 @@ describe('paragraph converters', () => { content: [lineBreakNode], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks.some((b) => b.kind === 'columnBreak')).toBe(true); }); @@ -2029,7 +2020,7 @@ describe('paragraph converters', () => { content: [{ type: 'text', text: 'Text' }, lineBreakNode], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); expect(blocks[0].kind).toBe('paragraph'); @@ -2067,7 +2058,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, trackedChanges, undefined, undefined, @@ -2100,7 +2090,7 @@ describe('paragraph converters', () => { vi.mocked(applyTrackedChangesModeToRuns).mockReturnValue([]); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext, trackedChanges); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, trackedChanges); expect(blocks).toHaveLength(0); }); @@ -2111,7 +2101,7 @@ describe('paragraph converters', () => { content: [{ type: 'text', text: 'Test' }], }; - paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(vi.mocked(applyTrackedChangesModeToRuns)).not.toHaveBeenCalled(); }); @@ -2130,7 +2120,7 @@ describe('paragraph converters', () => { vi.mocked(applyTrackedChangesModeToRuns).mockReturnValue([]); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext, trackedChanges); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, trackedChanges); expect(blocks.some((b) => b.kind === 'pageBreak')).toBe(true); }); @@ -2164,7 +2154,7 @@ describe('paragraph converters', () => { vi.mocked(trackedChangesCompatible).mockReturnValue(true); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); const paraBlock = blocks[0] as ParagraphBlock; expect(paraBlock.runs).toHaveLength(1); @@ -2181,7 +2171,7 @@ describe('paragraph converters', () => { content: [{ type: 'hardBreak', attrs: { pageBreakType: 'page' } }], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks.some((b) => b.kind === 'paragraph')).toBe(true); const paraBlock = blocks.find((b) => b.kind === 'paragraph') as ParagraphBlock; @@ -2199,7 +2189,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks.length).toBeGreaterThan(1); expect(blocks.some((b) => b.kind === 'pageBreak')).toBe(true); @@ -2216,7 +2206,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); const paraBlocks = blocks.filter((b) => b.kind === 'paragraph'); expect(paraBlocks[0].id).not.toBe(paraBlocks[1].id); @@ -2239,7 +2229,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -2272,7 +2261,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -2292,7 +2280,7 @@ describe('paragraph converters', () => { }; // No converters provided - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); // Should create empty paragraph expect(blocks).toHaveLength(1); @@ -2309,23 +2297,13 @@ describe('paragraph converters', () => { enableRichHyperlinks: true, }; - paragraphToFlowBlocks( - para, - nextBlockId, - positions, - 'Arial', - 16, - styleContext, - undefined, - undefined, - customHyperlinkConfig, - ); + paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, undefined, undefined, customHyperlinkConfig); expect(vi.mocked(textNodeToRun)).toHaveBeenCalledWith( expect.any(Object), positions, - 'Arial', - 16, + DEFAULT_TEST_FONT_FAMILY, + DEFAULT_TEST_FONT_SIZE_PX, [], undefined, customHyperlinkConfig, @@ -2345,7 +2323,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -2374,7 +2351,7 @@ describe('paragraph converters', () => { }); vi.mocked(deepClone).mockImplementation((attrs) => ({ ...attrs })); - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); const paraBlocks = blocks.filter((b) => b.kind === 'paragraph'); // Should be called once per paragraph block (2 blocks in this case) @@ -2961,7 +2938,6 @@ describe('paragraph converters', () => { describe('Integration: Inline images in paragraphs', () => { let nextBlockId: BlockIdGenerator; let positions: PositionMap; - let styleContext: StyleContext; beforeEach(() => { vi.clearAllMocks(); @@ -2969,7 +2945,6 @@ describe('paragraph converters', () => { let counter = 0; nextBlockId = vi.fn((kind: string) => `${kind}-${counter++}`); positions = new WeakMap(); - styleContext = {}; vi.mocked(computeParagraphAttrs).mockReturnValue({ paragraphAttrs: {}, resolvedParagraphProperties: {} }); vi.mocked(cloneParagraphAttrs).mockReturnValue({}); @@ -3003,7 +2978,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -3057,7 +3031,6 @@ describe('paragraph converters', () => { positions, 'Arial', 16, - styleContext, undefined, undefined, undefined, @@ -3097,7 +3070,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); const paraBlock = blocks[0] as ParagraphBlock; @@ -3131,7 +3104,7 @@ describe('paragraph converters', () => { ], }; - const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16, styleContext); + const blocks = paragraphToFlowBlocks(para, nextBlockId, positions, 'Arial', 16); expect(blocks).toHaveLength(1); const paraBlock = blocks[0] as ParagraphBlock; diff --git a/packages/layout-engine/pm-adapter/src/converters/paragraph.ts b/packages/layout-engine/pm-adapter/src/converters/paragraph.ts index 111be4564..4a1e6e088 100644 --- a/packages/layout-engine/pm-adapter/src/converters/paragraph.ts +++ b/packages/layout-engine/pm-adapter/src/converters/paragraph.ts @@ -496,6 +496,34 @@ const applyInlineRunProperties = ( return { ...run, ...runAttrs }; }; +/** + * Extracts the default font family and size from paragraph properties. + * Used for creating default runs in empty paragraphs. + * @param converterContext - Converter context with document styles + * @param paragraphProperties - Resolved paragraph properties + * @returns Object with defaultFont and defaultSize + */ +function extractDefaultFontProperties( + converterContext: ConverterContext, + paragraphProperties: ParagraphProperties, +): { defaultFont: string; defaultSize: number } { + const defaultRunAttrs = computeRunAttrs( + resolveRunProperties( + converterContext, + paragraphProperties.runProperties, + paragraphProperties, + converterContext.tableInfo, + false, + false, + ), + converterContext, + ); + return { + defaultFont: defaultRunAttrs.fontFamily!, + defaultSize: defaultRunAttrs.fontSize!, + }; +} + /** * Converts a paragraph PM node to an array of FlowBlocks. * @@ -509,9 +537,6 @@ const applyInlineRunProperties = ( * @param para - Paragraph PM node to convert * @param nextBlockId - Block ID generator * @param positions - Position map for PM node tracking - * @param defaultFont - Default font family - * @param defaultSize - Default font size - * @param styleContext - Style resolution context * @param trackedChanges - Optional tracked changes configuration * @param bookmarks - Optional bookmark position map * @param hyperlinkConfig - Hyperlink configuration @@ -525,9 +550,6 @@ export function paragraphToFlowBlocks({ para, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig = DEFAULT_HYPERLINK_CONFIG, @@ -551,6 +573,7 @@ export function paragraphToFlowBlocks({ : undefined; const hasSectPr = Boolean(rawParagraphProps?.sectPr); const isSectPrMarker = hasSectPr || paraAttrs.pageBreakSource === 'sectPr'; + const { defaultFont, defaultSize } = extractDefaultFontProperties(converterContext, resolvedParagraphProperties); if (paragraphAttrs.pageBreakBefore) { blocks.push({ @@ -936,6 +959,7 @@ export function paragraphToFlowBlocks({ enableComments, ); } + applyInlineRunProperties(tokenRun as TextRun, activeRunProperties, converterContext); console.debug('[token-debug] paragraph-token-run', { token: (tokenRun as TextRun).token, fontFamily: (tokenRun as TextRun).fontFamily, @@ -1102,9 +1126,6 @@ export function paragraphToFlowBlocks({ node, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -1248,9 +1269,6 @@ export function handleParagraphNode(node: PMNode, context: NodeHandlerContext): recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -1283,9 +1301,6 @@ export function handleParagraphNode(node: PMNode, context: NodeHandlerContext): para: node, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/converters/table.test.ts b/packages/layout-engine/pm-adapter/src/converters/table.test.ts index 8352be2d3..761b49e29 100644 --- a/packages/layout-engine/pm-adapter/src/converters/table.test.ts +++ b/packages/layout-engine/pm-adapter/src/converters/table.test.ts @@ -8,7 +8,6 @@ import type { PMNode, BlockIdGenerator, PositionMap, - StyleContext, TrackedChangesConfig, HyperlinkConfig, ThemeColorPalette, @@ -34,7 +33,6 @@ const tableNodeToBlock = ( positions: PositionMap, defaultFont: string, defaultSize: number, - styleContext: StyleContext, trackedChangesConfig?: TrackedChangesConfig, bookmarks?: Map, hyperlinkConfig?: HyperlinkConfig, @@ -43,33 +41,41 @@ const tableNodeToBlock = ( converterContext?: ConverterContext, ) => { const converters = paragraphToFlowBlocks ? ({ paragraphToFlowBlocks } as NestedConverters) : ({} as NestedConverters); + const effectiveConverterContext = + converterContext ?? + ({ + ...DEFAULT_CONVERTER_CONTEXT, + translatedLinkedStyles: { + ...DEFAULT_CONVERTER_CONTEXT.translatedLinkedStyles, + docDefaults: { + ...DEFAULT_CONVERTER_CONTEXT.translatedLinkedStyles.docDefaults, + runProperties: { + ...(DEFAULT_CONVERTER_CONTEXT.translatedLinkedStyles.docDefaults?.runProperties ?? {}), + fontFamily: { + ...(DEFAULT_CONVERTER_CONTEXT.translatedLinkedStyles.docDefaults?.runProperties?.fontFamily ?? {}), + ascii: defaultFont, + }, + fontSize: defaultSize * 2, + }, + }, + }, + } as ConverterContext); return baseTableNodeToBlock({ node, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig: hyperlinkConfig ?? DEFAULT_HYPERLINK_CONFIG, themeColors, - converterContext: converterContext ?? DEFAULT_CONVERTER_CONTEXT, + converterContext: effectiveConverterContext, converters, enableComments: true, }); }; describe('table converter', () => { - const mockStyleContext: StyleContext = { - defaults: { - paragraphFont: 'Arial', - fontSize: 12, - decimalSeparator: '.', - }, - }; - describe('tableNodeToBlock', () => { const mockBlockIdGenerator: BlockIdGenerator = vi.fn((kind) => `test-${kind}`); const mockPositionMap: PositionMap = new Map(); @@ -96,7 +102,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -129,7 +134,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -162,7 +166,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -216,7 +219,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -251,7 +253,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -292,7 +293,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -330,7 +330,6 @@ describe('table converter', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 16, - styleContext: mockStyleContext, trackedChangesConfig: undefined, bookmarks: undefined, hyperlinkConfig: DEFAULT_HYPERLINK_CONFIG, @@ -383,7 +382,6 @@ describe('table converter', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 16, - styleContext: mockStyleContext, trackedChangesConfig: undefined, bookmarks: undefined, hyperlinkConfig: DEFAULT_HYPERLINK_CONFIG, @@ -456,7 +454,6 @@ describe('table converter', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 16, - styleContext: mockStyleContext, trackedChangesConfig: undefined, bookmarks: undefined, hyperlinkConfig: DEFAULT_HYPERLINK_CONFIG, @@ -503,7 +500,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -542,7 +538,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -585,7 +580,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -625,7 +619,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -664,7 +657,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -705,7 +697,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -741,7 +732,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -779,7 +769,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -818,7 +807,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -862,7 +850,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -899,7 +886,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -935,7 +921,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -973,7 +958,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1011,7 +995,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1047,7 +1030,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1084,7 +1066,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1128,7 +1109,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1168,7 +1148,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1210,7 +1189,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, trackedChangesConfig, undefined, undefined, @@ -1243,7 +1221,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 16, - mockStyleContext, undefined, undefined, undefined, @@ -1289,7 +1266,6 @@ describe('table converter', () => { positions: new Map(), defaultFont: 'Arial', defaultSize: 16, - styleContext: mockStyleContext, trackedChangesConfig: undefined, bookmarks: undefined, hyperlinkConfig: undefined, @@ -1321,7 +1297,6 @@ describe('table converter', () => { positions: new Map(), defaultFont: 'Arial', defaultSize: 16, - styleContext: mockStyleContext, trackedChangesConfig: undefined, bookmarks: undefined, hyperlinkConfig: undefined, @@ -1382,7 +1357,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1432,7 +1406,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1477,7 +1450,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1524,7 +1496,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1570,7 +1541,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1611,7 +1581,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, @@ -1654,7 +1623,6 @@ describe('table converter', () => { mockPositionMap, 'Arial', 12, - mockStyleContext, undefined, undefined, undefined, diff --git a/packages/layout-engine/pm-adapter/src/converters/table.ts b/packages/layout-engine/pm-adapter/src/converters/table.ts index 0c4034f5c..2aa3f7ba9 100644 --- a/packages/layout-engine/pm-adapter/src/converters/table.ts +++ b/packages/layout-engine/pm-adapter/src/converters/table.ts @@ -24,7 +24,6 @@ import type { NodeHandlerContext, BlockIdGenerator, PositionMap, - StyleContext, TrackedChangesConfig, HyperlinkConfig, ThemeColorPalette, @@ -47,9 +46,6 @@ import { TableProperties } from '@superdoc/style-engine/ooxml'; type TableParserDependencies = { nextBlockId: BlockIdGenerator; positions: PositionMap; - defaultFont: string; - defaultSize: number; - styleContext: StyleContext; trackedChangesConfig?: TrackedChangesConfig; bookmarks?: Map; hyperlinkConfig: HyperlinkConfig; @@ -251,9 +247,6 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => { para: childNode, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -275,9 +268,6 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => { para: nestedNode, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -294,9 +284,6 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => { node: nestedNode, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -320,9 +307,6 @@ const parseTableCell = (args: ParseTableCellArgs): TableCell | null => { node: childNode, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -661,8 +645,6 @@ function extractFloatingTableAnchorWrap(node: PMNode): { anchor?: TableAnchor; w * @param node - Table node to convert * @param nextBlockId - Block ID generator * @param positions - Position map for PM node tracking - * @param defaultFont - Default font family - * @param defaultSize - Default font size * @param _styleContext - Style context (unused in current implementation) * @param trackedChanges - Optional tracked changes configuration * @param bookmarks - Optional bookmark position map @@ -674,9 +656,6 @@ export function tableNodeToBlock({ node, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -692,9 +671,6 @@ export function tableNodeToBlock({ const parserDeps: TableParserDependencies = { nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -882,9 +858,6 @@ export function handleTableNode(node: PMNode, context: NodeHandlerContext): void recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -897,9 +870,6 @@ export function handleTableNode(node: PMNode, context: NodeHandlerContext): void node, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/index.test.ts b/packages/layout-engine/pm-adapter/src/index.test.ts index 1b3b6ccf1..26e1e8a4d 100644 --- a/packages/layout-engine/pm-adapter/src/index.test.ts +++ b/packages/layout-engine/pm-adapter/src/index.test.ts @@ -9,7 +9,7 @@ import imageFixture from './fixtures/image-inline-and-block.json'; import hummingbirdFixture from './fixtures/hummingbird.json'; import boldDemoFixture from './fixtures/bold-demo.json'; -const DEFAULT_CONVERTER_CONTEXT = { +const createDefaultConverterContext = () => ({ docx: {}, translatedLinkedStyles: { docDefaults: {}, @@ -20,10 +20,10 @@ const DEFAULT_CONVERTER_CONTEXT = { abstracts: {}, definitions: {}, }, -}; +}); const toFlowBlocks = (pmDoc: PMNode | object, options: AdapterOptions = {}) => - baseToFlowBlocks(pmDoc, { converterContext: DEFAULT_CONVERTER_CONTEXT, ...options }); + baseToFlowBlocks(pmDoc, { converterContext: createDefaultConverterContext(), ...options }); const createTestBodySectPr = () => ({ type: 'element', @@ -70,11 +70,11 @@ describe('toFlowBlocks', () => { runs: [ { text: 'Hello world', - fontFamily: 'Arial', - fontSize: 16, + fontFamily: 'Times New Roman, sans-serif', }, ], }); + expect(blocks[0].runs[0]?.fontSize).toBeCloseTo((10 * 96) / 72, 5); }); it('generates unique BlockIds based on position', () => { @@ -114,9 +114,9 @@ describe('toFlowBlocks', () => { }); expect(blocks[0].runs[0]).toMatchObject({ - fontFamily: 'Times New Roman', - fontSize: 14, + fontFamily: 'Times New Roman, sans-serif', }); + expect(blocks[0].runs[0]?.fontSize).toBeCloseTo(14, 5); }); }); diff --git a/packages/layout-engine/pm-adapter/src/integration.test.ts b/packages/layout-engine/pm-adapter/src/integration.test.ts index 112219c6f..4fd038ca3 100644 --- a/packages/layout-engine/pm-adapter/src/integration.test.ts +++ b/packages/layout-engine/pm-adapter/src/integration.test.ts @@ -258,13 +258,13 @@ describe('PM → FlowBlock → Measure integration', () => { // Typography metrics should be reasonable for 20px font size // Note: Exact values depend on font rendering (canvas vs fallback), so we check ranges - expect(measure.lines[0].ascent).toBeGreaterThan(14); // ~70-90% of font size + expect(measure.lines[0].ascent).toBeGreaterThan(10); // ~50-80% of font size expect(measure.lines[0].ascent).toBeLessThan(20); expect(measure.lines[0].descent).toBeGreaterThan(2); // ~10-25% of font size expect(measure.lines[0].descent).toBeLessThan(6); - // Line height should be at least the font size - expect(measure.lines[0].lineHeight).toBeGreaterThanOrEqual(20); - expect(measure.totalHeight).toBeGreaterThanOrEqual(20); + // Line height should be within a reasonable range for the resolved font size + expect(measure.lines[0].lineHeight).toBeGreaterThanOrEqual(14); + expect(measure.totalHeight).toBeGreaterThanOrEqual(14); }); it('propagates tab stops and decimal separators through measurement', async () => { diff --git a/packages/layout-engine/pm-adapter/src/internal.test.ts b/packages/layout-engine/pm-adapter/src/internal.test.ts index 144c14c43..27e9ed570 100644 --- a/packages/layout-engine/pm-adapter/src/internal.test.ts +++ b/packages/layout-engine/pm-adapter/src/internal.test.ts @@ -284,8 +284,8 @@ describe('internal', () => { expect(handleParagraphNode).toHaveBeenCalledWith( expect.any(Object), expect.objectContaining({ - defaultFont: 'Arial', - defaultSize: 16, + defaultFont: 'Times New Roman', + defaultSize: 10 / 0.75, }), ); }); @@ -637,26 +637,6 @@ describe('internal', () => { }); describe('options handling', () => { - it('should pass custom decimal separator to style context', () => { - const doc: PMNode = { - type: 'doc', - content: [{ type: 'paragraph', content: [] }], - }; - - toFlowBlocks(doc, { locale: { decimalSeparator: ',' } }); - - expect(handleParagraphNode).toHaveBeenCalledWith( - expect.any(Object), - expect.objectContaining({ - styleContext: expect.objectContaining({ - defaults: expect.objectContaining({ - decimalSeparator: ',', - }), - }), - }), - ); - }); - it('should extract lang from document attrs', () => { const doc: PMNode = { type: 'doc', @@ -902,7 +882,6 @@ describe('internal', () => { positions: context.positions, defaultFont: context.defaultFont, defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/internal.ts b/packages/layout-engine/pm-adapter/src/internal.ts index eeb62a656..09a86e199 100644 --- a/packages/layout-engine/pm-adapter/src/internal.ts +++ b/packages/layout-engine/pm-adapter/src/internal.ts @@ -11,18 +11,9 @@ */ import type { FlowBlock, ParagraphBlock } from '@superdoc/contracts'; -import type { StyleContext } from '@superdoc/style-engine'; import { isValidTrackedMode } from './tracked-changes.js'; import { analyzeSectionRanges, createSectionBreakBlock, publishSectionMetadata } from './sections/index.js'; -import { - pxToPt, - pickNumber, - pickDecimalSeparator, - pickLang, - normalizePrefix, - buildPositionMap, - createBlockIdGenerator, -} from './utilities.js'; +import { normalizePrefix, buildPositionMap, createBlockIdGenerator } from './utilities.js'; import { paragraphToFlowBlocks, contentBlockNodeToDrawingBlock, @@ -59,11 +50,9 @@ import type { NestedConverters, ConverterContext, } from './types.js'; -import { defaultDecimalSeparatorFor } from '@superdoc/locale-utils'; -const DEFAULT_FONT = 'Arial'; -const DEFAULT_SIZE = 16; -const DEFAULT_DECIMAL_SEPARATOR = '.'; +const DEFAULT_FONT = 'Times New Roman'; +const DEFAULT_SIZE = 10 / 0.75; // 10pt in pixels /** * Dispatch map for node type handlers. @@ -131,27 +120,10 @@ export function toFlowBlocks(pmDoc: PMNode | object, options?: AdapterOptions): const doc = pmDoc as PMNode; - const docAttrs = (typeof doc.attrs === 'object' && doc.attrs !== null ? doc.attrs : {}) as Record; - const docDecimalSeparator = pickDecimalSeparator(doc.attrs?.decimalSeparator); - const docLang = pickLang(docAttrs.lang ?? docAttrs.language ?? docAttrs.locale); - const derivedSeparator = docLang ? defaultDecimalSeparatorFor(docLang) : undefined; - const docTabIntervalTwips = - pickNumber(docAttrs.defaultTabIntervalTwips ?? docAttrs.tabIntervalTwips ?? undefined) ?? - ((): number | undefined => { - const px = pickNumber(docAttrs.defaultTabIntervalPx ?? docAttrs.tabIntervalPx); - return px != null ? Math.round(px * 15) : undefined; - })(); - const optionDecimalSeparator = pickDecimalSeparator(options?.locale?.decimalSeparator); - const decimalSeparator = - optionDecimalSeparator ?? docDecimalSeparator ?? derivedSeparator ?? DEFAULT_DECIMAL_SEPARATOR; - const styleContext: StyleContext = { - defaults: { - paragraphFont: defaultFont, - fontSize: pxToPt(defaultSize) ?? 12, - decimalSeparator, - defaultTabIntervalTwips: docTabIntervalTwips, - }, - }; + if (!doc.content) { + return { blocks: [], bookmarks: new Map() }; + } + const trackedChangesMode = isValidTrackedMode(options?.trackedChangesMode) ? options.trackedChangesMode : 'review'; const enableTrackedChanges = options?.enableTrackedChanges ?? true; const trackedChangesConfig: TrackedChangesConfig = { @@ -162,25 +134,11 @@ export function toFlowBlocks(pmDoc: PMNode | object, options?: AdapterOptions): enableRichHyperlinks: options?.enableRichHyperlinks ?? false, }; const enableComments = options?.enableComments ?? true; - const converterContext: ConverterContext = options?.converterContext ?? { - translatedNumbering: {}, - translatedLinkedStyles: { - docDefaults: { - runProperties: { - fontFamily: { - ascii: defaultFont, - }, - fontSize: pxToPt(defaultSize) ?? 12, - }, - }, - latentStyles: {}, - styles: {}, - }, - }; - - if (!doc.content) { - return { blocks: [], bookmarks: new Map() }; - } + const converterContext: ConverterContext = normalizeConverterContext( + options?.converterContext, + defaultFont, + defaultSize, + ); const blocks: FlowBlock[] = []; const bookmarks = new Map(); @@ -217,7 +175,6 @@ export function toFlowBlocks(pmDoc: PMNode | object, options?: AdapterOptions): positions, defaultFont, defaultSize, - styleContext, converterContext, trackedChangesConfig, hyperlinkConfig, @@ -323,3 +280,49 @@ function mergeDropCapParagraphs(blocks: FlowBlock[]): FlowBlock[] { return result; } + +/** + * Normalize and populate the converter context with defaults. + * + * Ensures that essential properties like default font and size + * are set in the converter context for consistent styling. + * + * @param context - Existing converter context (may be undefined) + * @param defaultFont - Default font family to use + * @param defaultSize - Default font size in pixels + * @returns Normalized converter context + */ +function normalizeConverterContext( + context: ConverterContext | undefined, + defaultFont: string, + defaultSize: number, +): ConverterContext { + if (!context) { + context = { + translatedNumbering: {}, + translatedLinkedStyles: { + docDefaults: {}, + latentStyles: {}, + styles: {}, + }, + }; + } + + if (!context.translatedLinkedStyles.docDefaults) { + context.translatedLinkedStyles.docDefaults = {}; + } + if (!context.translatedLinkedStyles.docDefaults.runProperties) { + context.translatedLinkedStyles.docDefaults.runProperties = {}; + } + if (!context.translatedLinkedStyles.docDefaults.runProperties.fontFamily) { + context.translatedLinkedStyles.docDefaults.runProperties.fontFamily = {}; + } + if (!context.translatedLinkedStyles.docDefaults.runProperties.fontFamily.ascii) { + context.translatedLinkedStyles.docDefaults.runProperties.fontFamily.ascii = defaultFont; + } + if (!context.translatedLinkedStyles.docDefaults.runProperties.fontSize) { + context.translatedLinkedStyles.docDefaults.runProperties.fontSize = defaultSize * 0.75 * 2; // size in half-points + } + + return context; +} diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-index.test.ts b/packages/layout-engine/pm-adapter/src/sdt/document-index.test.ts index 7334fbb5f..3aab93174 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-index.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-index.test.ts @@ -28,7 +28,6 @@ describe('document-index', () => { positions: [], defaultFont: 'Arial', defaultSize: 12, - styleContext: {}, listCounterContext: { getListCounter: vi.fn().mockReturnValue(1), incrementListCounter: vi.fn(), diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-index.ts b/packages/layout-engine/pm-adapter/src/sdt/document-index.ts index 71da588d7..4f2f6856d 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-index.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-index.ts @@ -45,9 +45,6 @@ export function handleIndexNode(node: PMNode, context: NodeHandlerContext): void recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -82,9 +79,6 @@ export function handleIndexNode(node: PMNode, context: NodeHandlerContext): void para: child, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-part-object.test.ts b/packages/layout-engine/pm-adapter/src/sdt/document-part-object.test.ts index cc0140057..767398b0f 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-part-object.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-part-object.test.ts @@ -30,10 +30,6 @@ describe('document-part-object', () => { describe('handleDocumentPartObjectNode', () => { const mockBlockIdGenerator = vi.fn((kind: string) => `${kind}-test-id`); const mockPositionMap = new Map(); - const mockStyleContext = { - styles: new Map(), - numbering: new Map(), - }; const mockHyperlinkConfig = { enableRichHyperlinks: false, }; @@ -60,7 +56,6 @@ describe('document-part-object', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, bookmarks: new Map(), hyperlinkConfig: mockHyperlinkConfig, enableComments: mockEnableComments, @@ -339,7 +334,7 @@ describe('document-part-object', () => { // ==================== Context Passing Tests ==================== describe('Context passing to processTocChildren', () => { - it('should pass correct style context', () => { + it('should pass correct context parameters', () => { const node: PMNode = { type: 'documentPartObject', content: [{ type: 'paragraph' }], @@ -357,14 +352,12 @@ describe('document-part-object', () => { expect.objectContaining({ nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, - styleContext: mockStyleContext, bookmarks: mockContext.bookmarks, hyperlinkConfig: mockHyperlinkConfig, converters: mockContext.converters, converterContext: mockConverterContext, enableComments: mockEnableComments, + trackedChangesConfig: undefined, }), ); }); diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-part-object.ts b/packages/layout-engine/pm-adapter/src/sdt/document-part-object.ts index c3ea6583e..72b495dc1 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-part-object.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-part-object.ts @@ -25,9 +25,6 @@ export function handleDocumentPartObjectNode(node: PMNode, context: NodeHandlerC recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, bookmarks, hyperlinkConfig, converters, @@ -49,9 +46,6 @@ export function handleDocumentPartObjectNode(node: PMNode, context: NodeHandlerC { nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, bookmarks, hyperlinkConfig, enableComments, @@ -69,9 +63,6 @@ export function handleDocumentPartObjectNode(node: PMNode, context: NodeHandlerC para: child, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-section.test.ts b/packages/layout-engine/pm-adapter/src/sdt/document-section.test.ts index bed28bda8..688374c6f 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-section.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-section.test.ts @@ -32,10 +32,6 @@ describe('document-section', () => { describe('processDocumentSectionChildren', () => { const mockBlockIdGenerator = vi.fn((kind: string) => `${kind}-test-id`); const mockPositionMap = new Map(); - const mockStyleContext = { - styles: new Map(), - numbering: new Map(), - }; const mockHyperlinkConfig = { enableRichHyperlinks: false, }; @@ -65,7 +61,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -107,7 +102,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -153,7 +147,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -191,7 +184,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -224,7 +216,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -258,7 +249,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -274,9 +264,6 @@ describe('document-section', () => { para: children[0], nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }), ); @@ -302,7 +289,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -336,7 +322,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -369,7 +354,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -404,7 +388,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -453,7 +436,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -469,9 +451,6 @@ describe('document-section', () => { node: children[0], nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }), ); @@ -496,7 +475,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -528,7 +506,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -563,7 +540,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -596,7 +572,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -627,7 +602,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -675,7 +649,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -722,7 +695,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -768,7 +740,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -810,7 +781,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -844,7 +814,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -890,7 +859,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -937,7 +905,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -989,7 +956,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1046,7 +1012,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1102,7 +1067,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1139,7 +1103,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1179,7 +1142,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1220,7 +1182,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1242,9 +1203,8 @@ describe('document-section', () => { expect.objectContaining({ nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, converters: expect.any(Object), + hyperlinkConfig: mockHyperlinkConfig, }), { blocks, recordBlockKind }, ); @@ -1275,7 +1235,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1324,7 +1283,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1373,7 +1331,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1416,7 +1373,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1458,7 +1414,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, }, @@ -1475,9 +1430,6 @@ describe('document-section', () => { para: children[0], nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, - styleContext: mockStyleContext, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, }), @@ -1503,7 +1455,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1537,7 +1488,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1577,7 +1527,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, @@ -1612,7 +1561,6 @@ describe('document-section', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, }, { blocks, recordBlockKind }, diff --git a/packages/layout-engine/pm-adapter/src/sdt/document-section.ts b/packages/layout-engine/pm-adapter/src/sdt/document-section.ts index a1ee3960c..96da5fcd1 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/document-section.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/document-section.ts @@ -5,12 +5,11 @@ * Document sections can contain paragraphs, lists, tables, images, and nested SDTs. */ -import type { FlowBlock, ParagraphBlock, SdtMetadata, TrackedChangeMeta } from '@superdoc/contracts'; +import type { FlowBlock, ParagraphBlock, SdtMetadata } from '@superdoc/contracts'; import type { PMNode, BlockIdGenerator, PositionMap, - StyleContext, HyperlinkConfig, NodeHandlerContext, TrackedChangesConfig, @@ -34,9 +33,6 @@ import { processTocChildren } from './toc.js'; interface ProcessingContext { nextBlockId: BlockIdGenerator; positions: PositionMap; - defaultFont: string; - defaultSize: number; - styleContext: StyleContext; trackedChangesConfig?: TrackedChangesConfig; bookmarks?: Map; hyperlinkConfig: HyperlinkConfig; @@ -74,9 +70,6 @@ function processParagraphChild( para: child, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: undefined, // trackedChanges bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -115,9 +108,6 @@ function processTableChild( node: child, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -187,9 +177,6 @@ function processNestedStructuredContent( para: grandchild, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -211,9 +198,6 @@ function processNestedStructuredContent( node: grandchild, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -266,9 +250,6 @@ function processDocumentPartObject( { nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, enableComments: context.enableComments, @@ -353,9 +334,6 @@ export function handleDocumentSectionNode(node: PMNode, context: NodeHandlerCont recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, bookmarks, hyperlinkConfig, converters, @@ -372,9 +350,6 @@ export function handleDocumentSectionNode(node: PMNode, context: NodeHandlerCont { nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, bookmarks, trackedChangesConfig, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/sdt/index.test.ts b/packages/layout-engine/pm-adapter/src/sdt/index.test.ts index a7a1d489d..43edf6163 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/index.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/index.test.ts @@ -176,7 +176,6 @@ describe('sdt module exports', () => { positions: new Map(), defaultFont: 'Arial', defaultSize: 12, - styleContext: { styles: new Map(), numbering: new Map() }, listCounterContext: { getListCounter: () => ({ value: 0, styleId: null }), incrementListCounter: () => undefined, diff --git a/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.test.ts b/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.test.ts index 4b70c4331..eba3677ab 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.test.ts @@ -22,10 +22,6 @@ describe('structured-content-block', () => { describe('handleStructuredContentBlockNode', () => { const mockBlockIdGenerator = vi.fn((kind: string) => `${kind}-test-id`); const mockPositionMap = new Map(); - const mockStyleContext = { - styles: new Map(), - numbering: new Map(), - }; const mockHyperlinkConfig = { enableRichHyperlinks: false, }; @@ -62,7 +58,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -96,7 +91,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -127,7 +121,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -171,7 +164,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -221,7 +213,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -260,7 +251,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -300,7 +290,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -318,14 +307,13 @@ describe('structured-content-block', () => { para: node.content[0], nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Arial', - defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, enableComments: mockEnableComments, converterContext: mockConverterContext, + converters: { paragraphToFlowBlocks: mockParagraphConverter }, + themeColors: undefined, }), ); }); @@ -355,7 +343,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -396,7 +383,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -437,7 +423,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -484,7 +469,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -521,7 +505,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -566,7 +549,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -603,7 +585,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -649,7 +630,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -694,7 +674,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -736,7 +715,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -774,7 +752,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -811,7 +788,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -856,7 +832,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, @@ -901,7 +876,6 @@ describe('structured-content-block', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChangesConfig, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.ts b/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.ts index 9c1f4b952..21f365ee1 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/structured-content-block.ts @@ -24,9 +24,6 @@ export function handleStructuredContentBlockNode(node: PMNode, context: NodeHand recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -44,9 +41,6 @@ export function handleStructuredContentBlockNode(node: PMNode, context: NodeHand para: child, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -70,9 +64,6 @@ export function handleStructuredContentBlockNode(node: PMNode, context: NodeHand node: child, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, diff --git a/packages/layout-engine/pm-adapter/src/sdt/toc.test.ts b/packages/layout-engine/pm-adapter/src/sdt/toc.test.ts index 9214a45ba..86a6e2a70 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/toc.test.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/toc.test.ts @@ -145,10 +145,6 @@ describe('toc', () => { describe('processTocChildren', () => { const mockBlockIdGenerator = () => 'test-id'; const mockPositionMap = new Map(); - const mockStyleContext = { - styles: new Map(), - numbering: new Map(), - }; const mockHyperlinkConfig = { mode: 'preserve' as const, }; @@ -192,7 +188,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, enableComments: true, converters: { paragraphToFlowBlocks: mockParagraphConverter } as never, @@ -249,7 +244,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, enableComments: true, converters: { paragraphToFlowBlocks: mockParagraphConverter } as never, @@ -304,7 +298,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, enableComments: true, converters: { paragraphToFlowBlocks: mockParagraphConverter } as never, @@ -357,7 +350,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, enableComments: true, converters: { paragraphToFlowBlocks: mockParagraphConverter } as never, @@ -415,7 +407,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Arial', defaultSize: 12, - styleContext: mockStyleContext, hyperlinkConfig: mockHyperlinkConfig, enableComments: true, converters: { paragraphToFlowBlocks: mockParagraphConverter } as never, @@ -464,7 +455,6 @@ describe('toc', () => { positions: mockPositionMap, defaultFont: 'Calibri', defaultSize: 14, - styleContext: mockStyleContext, bookmarks: mockBookmarks, trackedChangesConfig: mockTrackedChanges, hyperlinkConfig: mockHyperlinkConfig, @@ -480,14 +470,12 @@ describe('toc', () => { para: children[0], nextBlockId: mockBlockIdGenerator, positions: mockPositionMap, - defaultFont: 'Calibri', - defaultSize: 14, - styleContext: mockStyleContext, trackedChangesConfig: mockTrackedChanges, bookmarks: mockBookmarks, hyperlinkConfig: mockHyperlinkConfig, enableComments: false, converterContext: mockConverterContext, + converters: { paragraphToFlowBlocks: mockParagraphConverter }, }), ); }); diff --git a/packages/layout-engine/pm-adapter/src/sdt/toc.ts b/packages/layout-engine/pm-adapter/src/sdt/toc.ts index ecff0c6fb..7b0d3e447 100644 --- a/packages/layout-engine/pm-adapter/src/sdt/toc.ts +++ b/packages/layout-engine/pm-adapter/src/sdt/toc.ts @@ -10,7 +10,6 @@ import type { PMNode, BlockIdGenerator, PositionMap, - StyleContext, HyperlinkConfig, TrackedChangesConfig, NodeHandlerContext, @@ -95,9 +94,6 @@ export function processTocChildren( context: { nextBlockId: BlockIdGenerator; positions: PositionMap; - defaultFont: string; - defaultSize: number; - styleContext: StyleContext; bookmarks?: Map; trackedChangesConfig?: TrackedChangesConfig; hyperlinkConfig: HyperlinkConfig; @@ -122,9 +118,6 @@ export function processTocChildren( para: child, nextBlockId: context.nextBlockId, positions: context.positions, - defaultFont: context.defaultFont, - defaultSize: context.defaultSize, - styleContext: context.styleContext, trackedChangesConfig: context.trackedChangesConfig, bookmarks: context.bookmarks, hyperlinkConfig: context.hyperlinkConfig, @@ -177,9 +170,6 @@ export function handleTableOfContentsNode(node: PMNode, context: NodeHandlerCont recordBlockKind, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, hyperlinkConfig, @@ -197,9 +187,6 @@ export function handleTableOfContentsNode(node: PMNode, context: NodeHandlerCont para: child, nextBlockId, positions, - defaultFont, - defaultSize, - styleContext, trackedChangesConfig, bookmarks, themeColors, diff --git a/packages/layout-engine/pm-adapter/src/types.ts b/packages/layout-engine/pm-adapter/src/types.ts index 2f3cb3d09..aabc6c3f5 100644 --- a/packages/layout-engine/pm-adapter/src/types.ts +++ b/packages/layout-engine/pm-adapter/src/types.ts @@ -3,7 +3,6 @@ */ import type { TrackedChangesMode, SectionMetadata, FlowBlock } from '@superdoc/contracts'; -import type { StyleContext as StyleEngineContext, ComputedParagraphStyle } from '@superdoc/style-engine'; import type { SectionRange } from './sections/index.js'; import type { ConverterContext } from './converter-context.js'; import type { paragraphToFlowBlocks } from './converters/paragraph.js'; @@ -18,9 +17,6 @@ import type { } from './converters/shapes.js'; export type { ConverterContext } from './converter-context.js'; -export type StyleContext = StyleEngineContext; -export type { ComputedParagraphStyle }; - export type ThemeColorPalette = Record; /** @@ -282,7 +278,6 @@ export interface NodeHandlerContext { // Style & defaults defaultFont: string; defaultSize: number; - styleContext: StyleContext; converterContext: ConverterContext; // Tracked changes & hyperlinks @@ -317,9 +312,6 @@ export type ParagraphToFlowBlocksParams = { para: PMNode; nextBlockId: BlockIdGenerator; positions: PositionMap; - defaultFont: string; - defaultSize: number; - styleContext: StyleContext; trackedChangesConfig?: TrackedChangesConfig; hyperlinkConfig: HyperlinkConfig; themeColors?: ThemeColorPalette; @@ -333,9 +325,6 @@ export type TableNodeToBlockParams = { node: PMNode; nextBlockId: BlockIdGenerator; positions: PositionMap; - defaultFont: string; - defaultSize: number; - styleContext: StyleContext; trackedChangesConfig?: TrackedChangesConfig; bookmarks?: Map; hyperlinkConfig: HyperlinkConfig; diff --git a/packages/layout-engine/style-engine/src/cascade.test.ts b/packages/layout-engine/style-engine/src/cascade.test.ts index 870f93327..b9940714e 100644 --- a/packages/layout-engine/style-engine/src/cascade.test.ts +++ b/packages/layout-engine/style-engine/src/cascade.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { combineProperties, combineRunProperties, orderDefaultsAndNormal, combineIndentProperties } from './cascade.js'; +import { combineProperties, combineRunProperties, combineIndentProperties } from './cascade.js'; describe('cascade - combineProperties', () => { it('returns empty object when propertiesArray is empty', () => { @@ -154,47 +154,6 @@ describe('cascade - combineRunProperties', () => { }); }); -describe('cascade - orderDefaultsAndNormal', () => { - const defaultProps = { fontSize: 22, bold: true }; - const normalProps = { fontSize: 20, italic: true }; - - it('returns [defaults, Normal] when isNormalDefault is true', () => { - const [first, second] = orderDefaultsAndNormal(defaultProps, normalProps, true); - expect(first).toBe(defaultProps); - expect(second).toBe(normalProps); - }); - - it('returns [Normal, defaults] when isNormalDefault is false', () => { - const [first, second] = orderDefaultsAndNormal(defaultProps, normalProps, false); - expect(first).toBe(normalProps); - expect(second).toBe(defaultProps); - }); - - it('preserves object references without cloning', () => { - const [first, second] = orderDefaultsAndNormal(defaultProps, normalProps, true); - expect(first).toBe(defaultProps); // Same reference - expect(second).toBe(normalProps); // Same reference - }); - - it('handles empty objects', () => { - const [first, second] = orderDefaultsAndNormal({}, {}, true); - expect(first).toEqual({}); - expect(second).toEqual({}); - }); - - it('affects cascade order when used with combineProperties', () => { - // When Normal is default (true), Normal should override defaults - const [first, second] = orderDefaultsAndNormal(defaultProps, normalProps, true); - const result = combineProperties([first, second]); - expect(result.fontSize).toBe(20); // normalProps wins - - // When Normal is NOT default (false), defaults should override Normal - const [first2, second2] = orderDefaultsAndNormal(defaultProps, normalProps, false); - const result2 = combineProperties([first2, second2]); - expect(result2.fontSize).toBe(22); // defaultProps wins - }); -}); - describe('cascade - combineIndentProperties', () => { it('extracts and combines indent properties from objects', () => { const result = combineIndentProperties([{ indent: { left: 720 } }, { indent: { left: 1440, hanging: 360 } }]); diff --git a/packages/layout-engine/style-engine/src/cascade.ts b/packages/layout-engine/style-engine/src/cascade.ts index 054eb63db..bc4c1fe77 100644 --- a/packages/layout-engine/style-engine/src/cascade.ts +++ b/packages/layout-engine/style-engine/src/cascade.ts @@ -117,31 +117,6 @@ function isObject(item: unknown): item is PropertyObject { // Style Chain Ordering // --------------------------------------------------------------------------- -/** - * Determines the correct ordering for defaults and Normal style in the cascade. - * - * Per OOXML spec, when Normal style is marked as w:default="1", it should - * come AFTER document defaults in the cascade (so Normal values override defaults). - * When Normal is NOT the default style, defaults should come after Normal. - * - * @param defaultProps - Document default properties. - * @param normalProps - Normal style properties. - * @param isNormalDefault - Whether Normal style has w:default="1". - * @returns Ordered array [first, second] for the cascade. - */ -export function orderDefaultsAndNormal( - defaultProps: T, - normalProps: T, - isNormalDefault: boolean, -): [T, T] { - if (isNormalDefault) { - // Normal is default: [defaults, Normal] - Normal wins when both exist - return [defaultProps, normalProps]; - } else { - // Normal is NOT default: [Normal, defaults] - defaults win when both exist - return [normalProps, defaultProps]; - } -} /** * Combines run property objects while fully overriding certain keys. * This is a convenience wrapper for run properties (w:rPr). diff --git a/packages/layout-engine/style-engine/src/index.ts b/packages/layout-engine/style-engine/src/index.ts index 2cd2c462b..7a801ff31 100644 --- a/packages/layout-engine/style-engine/src/index.ts +++ b/packages/layout-engine/style-engine/src/index.ts @@ -11,13 +11,7 @@ */ // Re-export cascade utilities - these are the SINGLE SOURCE OF TRUTH for property merging -export { - combineProperties, - combineRunProperties, - orderDefaultsAndNormal, - combineIndentProperties, - type PropertyObject, -} from './cascade.js'; +export { combineProperties, combineRunProperties, combineIndentProperties, type PropertyObject } from './cascade.js'; import type { TabStop, FieldAnnotationMetadata, diff --git a/packages/layout-engine/style-engine/src/ooxml/index.ts b/packages/layout-engine/style-engine/src/ooxml/index.ts index ea1f78853..16c6c9724 100644 --- a/packages/layout-engine/style-engine/src/ooxml/index.ts +++ b/packages/layout-engine/style-engine/src/ooxml/index.ts @@ -5,18 +5,13 @@ * This module is format-aware (docx), but translator-agnostic. */ -import { - combineIndentProperties, - combineProperties, - combineRunProperties, - orderDefaultsAndNormal, -} from '../cascade.js'; +import { combineIndentProperties, combineProperties, combineRunProperties } from '../cascade.js'; import type { PropertyObject } from '../cascade.js'; import type { ParagraphProperties, RunProperties } from './types.ts'; import type { NumberingProperties } from './numbering-types.ts'; import type { StylesDocumentProperties, TableStyleType, TableProperties, TableLookProperties } from './styles-types.ts'; -export { combineIndentProperties, combineProperties, combineRunProperties, orderDefaultsAndNormal }; +export { combineIndentProperties, combineProperties, combineRunProperties }; export type { PropertyObject }; export type * from './types.ts'; export type * from './numbering-types.ts'; @@ -57,7 +52,6 @@ export function resolveRunProperties( const defaultProps = params.translatedLinkedStyles.docDefaults?.runProperties ?? {}; const normalStyleDef = params.translatedLinkedStyles.styles['Normal']; const normalProps = (normalStyleDef?.runProperties ?? {}) as RunProperties; - const isNormalDefault = normalStyleDef?.default ?? false; // Getting table style run properties const tableStyleProps = ( @@ -81,7 +75,12 @@ export function resolveRunProperties( ) as RunProperties; } - const defaultsChain = orderDefaultsAndNormal(defaultProps, normalProps, isNormalDefault); + let defaultsChain; + if (!paragraphStyleId) { + defaultsChain = [defaultProps, normalProps]; + } else { + defaultsChain = [defaultProps]; + } let styleChain: RunProperties[]; if (isListNumber) { @@ -136,7 +135,6 @@ export function resolveParagraphProperties( const defaultProps = params.translatedLinkedStyles.docDefaults?.paragraphProperties ?? {}; const normalStyleDef = params.translatedLinkedStyles.styles['Normal']; const normalProps = (normalStyleDef?.paragraphProperties ?? {}) as ParagraphProperties; - const isNormalDefault = normalStyleDef?.default ?? false; // Properties from styles let styleId = inlineProps.styleId as string | undefined; @@ -187,7 +185,12 @@ export function resolveParagraphProperties( // Resolve property chain - regular properties are treated differently from indentation // Chain for regular properties - const defaultsChain = orderDefaultsAndNormal(defaultProps, normalProps, isNormalDefault); + let defaultsChain; + if (!styleId) { + defaultsChain = [defaultProps, normalProps]; + } else { + defaultsChain = [defaultProps]; + } const propsChain = [...defaultsChain, tableProps, ...cellStyleProps, numberingProps, styleProps, inlineProps]; // Chain for indentation properties diff --git a/packages/layout-engine/style-engine/src/ooxml/styles-types.ts b/packages/layout-engine/style-engine/src/ooxml/styles-types.ts index 9224e99d7..d77844e7b 100644 --- a/packages/layout-engine/style-engine/src/ooxml/styles-types.ts +++ b/packages/layout-engine/style-engine/src/ooxml/styles-types.ts @@ -11,7 +11,7 @@ import type { */ export interface StylesDocumentProperties { /** Default run and paragraph properties for the document. */ - docDefaults: DocDefaults | undefined; + docDefaults: DocDefaults; /** Latent style definitions and defaults. */ latentStyles: LatentStyles; /** Styles keyed by styleId. */