@@ -67,38 +67,68 @@ function extractTextPreview(node: ProseMirrorNode): string | null {
6767
6868const HEADING_PATTERN = / ^ H e a d i n g ( \d ) $ / ;
6969
70- /** OOXML implicit default font size when neither Normal style nor docDefaults specifies one. */
70+ /** OOXML implicit default font size when neither styles nor docDefaults specifies one. */
7171const OOXML_DEFAULT_FONT_SIZE_PT = 10 ;
7272
73+ /** Pre-built style context for fontSize resolution across all blocks in a list call. */
74+ interface StyleContext {
75+ styles : Record < string , { runProperties ?: { fontSize ?: unknown } ; basedOn ?: string } > ;
76+ docDefaultsFontSizeHp : number | undefined ;
77+ }
78+
79+ function buildStyleContext ( editor : Editor ) : StyleContext | null {
80+ const styleProps = readTranslatedLinkedStyles ( editor ) ;
81+ if ( ! styleProps ) return null ;
82+ return {
83+ styles : styleProps . styles ?? { } ,
84+ docDefaultsFontSizeHp :
85+ typeof styleProps . docDefaults ?. runProperties ?. fontSize === 'number'
86+ ? styleProps . docDefaults . runProperties . fontSize
87+ : undefined ,
88+ } ;
89+ }
90+
7391/**
74- * Resolve the document's default font size (in points) from the style catalog .
75- * Falls back through: Normal style rPr → docDefaults rPr → OOXML implicit default ( 10pt) .
92+ * Resolve the effective font size (in points) for a block by walking its style chain .
93+ * Cascade: inline mark → block's paragraph style → basedOn chain → Normal → docDefaults → 10pt.
7694 * OOXML stores fontSize as half-points (w:sz val), so we divide by 2.
7795 */
78- function resolveDefaultFontSizePt ( editor : Editor ) : number {
79- const styleProps = readTranslatedLinkedStyles ( editor ) ;
80- if ( ! styleProps ) return OOXML_DEFAULT_FONT_SIZE_PT ;
96+ function resolveBlockFontSizePt ( styleCtx : StyleContext | null , styleId : string | null | undefined ) : number {
97+ if ( ! styleCtx ) return OOXML_DEFAULT_FONT_SIZE_PT ;
98+
99+ // Walk the style's basedOn chain (limit depth to avoid infinite loops)
100+ let currentId = styleId ?? 'Normal' ;
101+ const visited = new Set < string > ( ) ;
102+ while ( currentId && ! visited . has ( currentId ) ) {
103+ visited . add ( currentId ) ;
104+ const style = styleCtx . styles [ currentId ] ;
105+ if ( ! style ) break ;
106+ const fs = style . runProperties ?. fontSize ;
107+ if ( typeof fs === 'number' ) return fs / 2 ;
108+ currentId = style . basedOn ?? '' ;
109+ }
81110
82- // Try Normal style first
83- const normalStyle = styleProps . styles ?. [ 'Normal' ] ;
84- const normalFontSize = normalStyle ?. runProperties ?. fontSize ;
85- if ( typeof normalFontSize === 'number' ) return normalFontSize / 2 ;
111+ // Try Normal if we haven't already
112+ if ( ! visited . has ( 'Normal' ) ) {
113+ const normal = styleCtx . styles [ 'Normal' ] ;
114+ const fs = normal ?. runProperties ?. fontSize ;
115+ if ( typeof fs === 'number' ) return fs / 2 ;
116+ }
86117
87- // Fall back to docDefaults
88- const defaultFontSize = styleProps . docDefaults ?. runProperties ?. fontSize ;
89- if ( typeof defaultFontSize === 'number' ) return defaultFontSize / 2 ;
118+ // docDefaults
119+ if ( typeof styleCtx . docDefaultsFontSizeHp === 'number' ) return styleCtx . docDefaultsFontSizeHp / 2 ;
90120
91121 return OOXML_DEFAULT_FONT_SIZE_PT ;
92122}
93123
94124/**
95125 * Extract key formatting from a block node's first text run marks.
96- * When defaultFontSizePt is provided, it's used as fallback when the
97- * inline marks don't specify a fontSize (common for inherited styles).
126+ * When styleCtx is provided, resolves fontSize from the block's paragraph style
127+ * chain when inline marks don't specify one (common for inherited styles).
98128 */
99129function extractBlockFormatting (
100130 node : ProseMirrorNode ,
101- defaultFontSizePt ?: number ,
131+ styleCtx ?: StyleContext | null ,
102132) : {
103133 styleId ?: string | null ;
104134 fontFamily ?: string ;
@@ -156,7 +186,11 @@ function extractBlockFormatting(
156186 return {
157187 ...( styleId ? { styleId } : { } ) ,
158188 ...( fontFamily ? { fontFamily } : { } ) ,
159- ...( ( fontSize ?? defaultFontSizePt ) !== undefined ? { fontSize : fontSize ?? defaultFontSizePt } : { } ) ,
189+ ...( fontSize !== undefined
190+ ? { fontSize }
191+ : styleCtx
192+ ? { fontSize : resolveBlockFontSizePt ( styleCtx , styleId ) }
193+ : { } ) ,
160194 ...( bold ? { bold } : { } ) ,
161195 ...( underline ? { underline } : { } ) ,
162196 ...( color ? { color } : { } ) ,
@@ -270,8 +304,8 @@ export function blocksListWrapper(editor: Editor, input?: BlocksListInput): Bloc
270304
271305 const rev = getRevision ( editor ) ;
272306
273- // Resolve document's default fontSize once for all blocks
274- const defaultFontSizePt = resolveDefaultFontSizePt ( editor ) ;
307+ // Build style context once — used to resolve fontSize per-block via style chain
308+ const styleCtx = buildStyleContext ( editor ) ;
275309
276310 const blocks : BlockListEntry [ ] = paged . map ( ( candidate , i ) => {
277311 const textLength = computeTextContentLength ( candidate . node ) ;
@@ -294,7 +328,7 @@ export function blocksListWrapper(editor: Editor, input?: BlocksListInput): Bloc
294328 nodeType : candidate . nodeType ,
295329 textPreview : extractTextPreview ( candidate . node ) ,
296330 isEmpty : textLength === 0 ,
297- ...extractBlockFormatting ( candidate . node , defaultFontSizePt ) ,
331+ ...extractBlockFormatting ( candidate . node , styleCtx ) ,
298332 ...( ref ? { ref } : { } ) ,
299333 } ;
300334 } ) ;
0 commit comments