@@ -222,13 +222,19 @@ def _convert_to_font(cls, style_dict: dict) -> Font:
222222 is_bold = True
223223
224224 # Map style keys to Font constructor arguments
225+ # (accept both shorthand and CSS-like keys)
225226 key_map = {
226227 "b" : "bold" ,
228+ "bold" : "bold" ,
227229 "i" : "italic" ,
230+ "italic" : "italic" ,
228231 "u" : "underline" ,
232+ "underline" : "underline" ,
229233 "strike" : "strikethrough" ,
230234 "vertAlign" : "vertAlign" ,
235+ "vertalign" : "vertAlign" ,
231236 "sz" : "size" ,
237+ "size" : "size" ,
232238 "color" : "color" ,
233239 "name" : "name" ,
234240 "family" : "family" ,
@@ -239,10 +245,7 @@ def _convert_to_font(cls, style_dict: dict) -> Font:
239245
240246 # Process other font properties
241247 for style_key , font_key in key_map .items ():
242- if style_key in style_dict and style_key not in (
243- "b" ,
244- "bold" ,
245- ): # Skip b/bold as we've already handled it
248+ if style_key in style_dict and style_key not in ("b" , "bold" ):
246249 value = style_dict [style_key ]
247250 if font_key == "color" and value is not None :
248251 value = cls ._convert_to_color (value )
@@ -515,7 +518,60 @@ def _write_cells(
515518 for cell in cells :
516519 xrow = startrow + cell .row
517520 xcol = startcol + cell .col
518- xcell = wks .cell (row = xrow + 1 , column = xcol + 1 ) # +1 for 1-based indexing
521+
522+ # Handle merged ranges if specified on this cell
523+ if cell .mergestart is not None and cell .mergeend is not None :
524+ start_r = xrow + 1
525+ start_c = xcol + 1
526+ end_r = startrow + cell .mergestart + 1
527+ end_c = startcol + cell .mergeend + 1
528+
529+ # Create the merged range
530+ wks .merge_cells (
531+ start_row = start_r ,
532+ start_column = start_c ,
533+ end_row = end_r ,
534+ end_column = end_c ,
535+ )
536+
537+ # Top-left cell of the merged range
538+ tl = wks .cell (row = start_r , column = start_c )
539+ tl .value , fmt = self ._value_with_fmt (cell .val )
540+ if fmt :
541+ tl .number_format = fmt
542+
543+ style_kwargs = None
544+ if cell .style :
545+ key = str (cell .style )
546+ if key not in _style_cache :
547+ style_kwargs = self ._convert_to_style_kwargs (cell .style )
548+ _style_cache [key ] = style_kwargs
549+ else :
550+ style_kwargs = _style_cache [key ]
551+
552+ for k , v in style_kwargs .items ():
553+ setattr (tl , k , v )
554+
555+ # Apply style across merged cells to satisfy tests
556+ # that inspect non-top-left cells
557+ if style_kwargs :
558+ for r in range (start_r , end_r + 1 ):
559+ for c in range (start_c , end_c + 1 ):
560+ if r == start_r and c == start_c :
561+ continue
562+ mcell = wks .cell (row = r , column = c )
563+ for k , v in style_kwargs .items ():
564+ setattr (mcell , k , v )
565+
566+ # Update bounds with the entire merged rectangle
567+ min_row = xrow if min_row is None else min (min_row , xrow )
568+ min_col = xcol if min_col is None else min (min_col , xcol )
569+ max_row = (end_r - 1 ) if max_row is None else max (max_row , end_r - 1 )
570+ max_col = (end_c - 1 ) if max_col is None else max (max_col , end_c - 1 )
571+ continue
572+
573+ # Non-merged cell path
574+ xcell = wks .cell (row = xrow + 1 , column = xcol + 1 )
519575
520576 # Apply cell value and format
521577 xcell .value , fmt = self ._value_with_fmt (cell .val )
@@ -531,7 +587,6 @@ def _write_cells(
531587 else :
532588 style_kwargs = _style_cache [key ]
533589
534- # Apply the style
535590 for k , v in style_kwargs .items ():
536591 setattr (xcell , k , v )
537592
0 commit comments