optimized renderers docx pptx xlsx for performance

This commit is contained in:
ValueOn AG 2026-01-05 01:41:28 +01:00
parent 25cbaa45b8
commit ecababe600
3 changed files with 253 additions and 137 deletions

View file

@ -439,35 +439,61 @@ class RendererDocx(BaseRenderer):
# else: no borders # else: no borders
self.logger.debug(f"_renderJsonTable: Borders applied in {time.time() - border_start:.2f}s") self.logger.debug(f"_renderJsonTable: Borders applied in {time.time() - border_start:.2f}s")
# Add headers with AI-generated styling # Add headers with AI-generated styling - OPTIMIZED for performance
header_start = time.time() header_start = time.time()
header_row = table.rows[0] header_row = table.rows[0]
header_style = styles["table_header"] header_style = styles["table_header"]
# Pre-calculate and cache style objects to avoid repeated parsing
bg_color_hex = header_style["background"].lstrip('#')
header_bg_rgb = RGBColor(int(bg_color_hex[0:2], 16), int(bg_color_hex[2:4], 16), int(bg_color_hex[4:6], 16))
text_color_hex = header_style["text_color"].lstrip('#')
header_text_rgb = RGBColor(int(text_color_hex[0:2], 16), int(text_color_hex[2:4], 16), int(text_color_hex[4:6], 16))
header_font_size = Pt(11)
header_align = WD_ALIGN_PARAGRAPH.CENTER if header_style["align"] == "center" else WD_ALIGN_PARAGRAPH.LEFT
header_bold = header_style["bold"]
for i, header in enumerate(headers): for i, header in enumerate(headers):
if i < len(header_row.cells): if i < len(header_row.cells):
cell = header_row.cells[i] cell = header_row.cells[i]
cell.text = str(header) cell.text = str(header)
# Apply background color # Apply background color
bg_color = header_style["background"].lstrip('#') self._setCellBackground(cell, header_bg_rgb)
self._setCellBackground(cell, RGBColor(int(bg_color[0:2], 16), int(bg_color[2:4], 16), int(bg_color[4:6], 16)))
# Apply text styling # Apply text styling - use direct access instead of iterating
for paragraph in cell.paragraphs: if len(cell.paragraphs) > 0:
paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER if header_style["align"] == "center" else WD_ALIGN_PARAGRAPH.LEFT para = cell.paragraphs[0]
for run in paragraph.runs: para.alignment = header_align
run.bold = header_style["bold"] # Use direct run access instead of iterating
run.font.size = Pt(11) if len(para.runs) > 0:
text_color = header_style["text_color"].lstrip('#') run = para.runs[0]
run.font.color.rgb = RGBColor(int(text_color[0:2], 16), int(text_color[2:4], 16), int(text_color[4:6], 16)) run.bold = header_bold
run.font.size = header_font_size
run.font.color.rgb = header_text_rgb
else:
# Create run if none exists
run = para.add_run()
run.bold = header_bold
run.font.size = header_font_size
run.font.color.rgb = header_text_rgb
self.logger.debug(f"_renderJsonTable: Headers rendered in {time.time() - header_start:.2f}s") self.logger.debug(f"_renderJsonTable: Headers rendered in {time.time() - header_start:.2f}s")
# Add data rows with AI-generated styling # Add data rows with AI-generated styling - OPTIMIZED for performance
rows_start = time.time() rows_start = time.time()
cell_style = styles["table_cell"] cell_style = styles["table_cell"]
total_cells = len(rows) * len(headers) total_cells = len(rows) * len(headers)
log_interval = max(1, total_cells // 20) # Log every 5% progress log_interval = max(1, total_cells // 20) # Log every 5% progress
# Pre-calculate and cache style objects to avoid repeated parsing
needsStyling = cell_style.get("text_color") != "#2F2F2F" or cell_style.get("font_size") != 10
cell_text_color_rgb = None
cell_font_size = None
if needsStyling:
text_color_hex = cell_style["text_color"].lstrip('#')
cell_text_color_rgb = RGBColor(int(text_color_hex[0:2], 16), int(text_color_hex[2:4], 16), int(text_color_hex[4:6], 16))
cell_font_size = Pt(cell_style.get("font_size", 10))
for row_idx, row_data in enumerate(rows): for row_idx, row_data in enumerate(rows):
if row_idx + 1 < len(table.rows): if row_idx + 1 < len(table.rows):
table_row = table.rows[row_idx + 1] table_row = table.rows[row_idx + 1]
@ -476,16 +502,22 @@ class RendererDocx(BaseRenderer):
cell = table_row.cells[col_idx] cell = table_row.cells[col_idx]
cell.text = str(cell_data) cell.text = str(cell_data)
# Apply text styling - OPTIMIZED: Only style if needed # Apply text styling - OPTIMIZED: Only style if needed, use direct access
# For large tables, styling every cell can be very slow if needsStyling:
# Check if we need to apply styling (only if style differs from default) # Use direct paragraph access instead of iterating
if cell_style.get("text_color") != "#2F2F2F" or cell_style.get("font_size") != 10: if len(cell.paragraphs) > 0:
for paragraph in cell.paragraphs: para = cell.paragraphs[0]
paragraph.alignment = WD_ALIGN_PARAGRAPH.LEFT para.alignment = WD_ALIGN_PARAGRAPH.LEFT
for run in paragraph.runs: # Use direct run access instead of iterating
run.font.size = Pt(10) if len(para.runs) > 0:
text_color = cell_style["text_color"].lstrip('#') run = para.runs[0]
run.font.color.rgb = RGBColor(int(text_color[0:2], 16), int(text_color[2:4], 16), int(text_color[4:6], 16)) run.font.size = cell_font_size
run.font.color.rgb = cell_text_color_rgb
else:
# Create run if none exists
run = para.add_run()
run.font.size = cell_font_size
run.font.color.rgb = cell_text_color_rgb
# Log progress for large tables # Log progress for large tables
if (row_idx + 1) % log_interval == 0 or row_idx == len(rows) - 1: if (row_idx + 1) % log_interval == 0 or row_idx == len(rows) - 1:
@ -595,7 +627,7 @@ class RendererDocx(BaseRenderer):
def _renderJsonBulletList(self, doc: Document, list_data: Dict[str, Any], styles: Dict[str, Any]) -> None: def _renderJsonBulletList(self, doc: Document, list_data: Dict[str, Any], styles: Dict[str, Any]) -> None:
"""Render a JSON bullet list to DOCX using AI-generated styles.""" """Render a JSON bullet list to DOCX using AI-generated styles - OPTIMIZED for performance."""
try: try:
# Extract from nested content structure # Extract from nested content structure
content = list_data.get("content", {}) content = list_data.get("content", {})
@ -604,20 +636,38 @@ class RendererDocx(BaseRenderer):
items = content.get("items", []) items = content.get("items", [])
bullet_style = styles.get("bullet_list", {}) bullet_style = styles.get("bullet_list", {})
# Pre-calculate and cache style objects to avoid repeated parsing
font_size_pt = None
text_color_rgb = None
if bullet_style:
if "font_size" in bullet_style:
font_size_pt = Pt(bullet_style["font_size"])
if "color" in bullet_style:
color_hex = bullet_style["color"].lstrip('#')
text_color_rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16))
for item in items: for item in items:
if isinstance(item, str): if isinstance(item, str):
para = doc.add_paragraph(item, style='List Bullet') para = doc.add_paragraph(item, style='List Bullet')
elif isinstance(item, dict) and "text" in item: elif isinstance(item, dict) and "text" in item:
para = doc.add_paragraph(item["text"], style='List Bullet') para = doc.add_paragraph(item["text"], style='List Bullet')
# Apply bullet list styling from style set # Apply bullet list styling from style set - use cached objects
if bullet_style and para.runs: if bullet_style and para.runs:
for run in para.runs: # Use direct access instead of iterating
if "font_size" in bullet_style: if len(para.runs) > 0:
run.font.size = Pt(bullet_style["font_size"]) run = para.runs[0]
if "color" in bullet_style: if font_size_pt:
color_hex = bullet_style["color"].lstrip('#') run.font.size = font_size_pt
run.font.color.rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16)) if text_color_rgb:
run.font.color.rgb = text_color_rgb
else:
# Create run if none exists
run = para.add_run()
if font_size_pt:
run.font.size = font_size_pt
if text_color_rgb:
run.font.color.rgb = text_color_rgb
except Exception as e: except Exception as e:
self.logger.warning(f"Error rendering bullet list: {str(e)}") self.logger.warning(f"Error rendering bullet list: {str(e)}")
@ -670,17 +720,36 @@ class RendererDocx(BaseRenderer):
if text: if text:
para = doc.add_paragraph(text) para = doc.add_paragraph(text)
# Apply paragraph styling from style set # Apply paragraph styling from style set - OPTIMIZED: pre-calculate style objects
paragraph_style = styles.get("paragraph", {}) paragraph_style = styles.get("paragraph", {})
if paragraph_style: if paragraph_style:
for run in para.runs: # Pre-calculate and cache style objects
if "font_size" in paragraph_style: font_size_pt = None
run.font.size = Pt(paragraph_style["font_size"]) text_color_rgb = None
if "bold" in paragraph_style: if "font_size" in paragraph_style:
run.font.bold = paragraph_style["bold"] font_size_pt = Pt(paragraph_style["font_size"])
if "color" in paragraph_style: if "color" in paragraph_style:
color_hex = paragraph_style["color"].lstrip('#') color_hex = paragraph_style["color"].lstrip('#')
run.font.color.rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16)) text_color_rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16))
bold = paragraph_style.get("bold", False)
# Use direct access instead of iterating
if len(para.runs) > 0:
run = para.runs[0]
if font_size_pt:
run.font.size = font_size_pt
run.font.bold = bold
if text_color_rgb:
run.font.color.rgb = text_color_rgb
else:
# Create run if none exists
run = para.add_run()
if font_size_pt:
run.font.size = font_size_pt
run.font.bold = bold
if text_color_rgb:
run.font.color.rgb = text_color_rgb
if "align" in paragraph_style: if "align" in paragraph_style:
align = paragraph_style["align"] align = paragraph_style["align"]
if align == "center": if align == "center":
@ -707,16 +776,32 @@ class RendererDocx(BaseRenderer):
if code: if code:
if language: if language:
lang_para = doc.add_paragraph(f"Code ({language}):") lang_para = doc.add_paragraph(f"Code ({language}):")
if lang_para.runs: if len(lang_para.runs) > 0:
lang_para.runs[0].bold = True lang_para.runs[0].bold = True
# Pre-calculate and cache style objects
code_font_name = code_style.get("font", "Courier New")
code_font_size_pt = Pt(code_style.get("font_size", 9))
code_text_color_rgb = None
if "color" in code_style:
color_hex = code_style["color"].lstrip('#')
code_text_color_rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16))
code_para = doc.add_paragraph(code) code_para = doc.add_paragraph(code)
for run in code_para.runs: # Use direct access instead of iterating
run.font.name = code_style.get("font", "Courier New") if len(code_para.runs) > 0:
run.font.size = Pt(code_style.get("font_size", 9)) run = code_para.runs[0]
if "color" in code_style: run.font.name = code_font_name
color_hex = code_style["color"].lstrip('#') run.font.size = code_font_size_pt
run.font.color.rgb = RGBColor(int(color_hex[0:2], 16), int(color_hex[2:4], 16), int(color_hex[4:6], 16)) if code_text_color_rgb:
run.font.color.rgb = code_text_color_rgb
else:
# Create run if none exists
run = code_para.add_run()
run.font.name = code_font_name
run.font.size = code_font_size_pt
if code_text_color_rgb:
run.font.color.rgb = code_text_color_rgb
except Exception as e: except Exception as e:
self.logger.warning(f"Error rendering code block: {str(e)}") self.logger.warning(f"Error rendering code block: {str(e)}")

View file

@ -1266,78 +1266,96 @@ JSON ONLY. NO OTHER TEXT."""
for col_idx in range(num_cols): for col_idx in range(num_cols):
table.columns[col_idx].width = col_width_emu table.columns[col_idx].width = col_width_emu
# Add headers with styling # Add headers with styling - OPTIMIZED: pre-calculate color/style objects
header_style = styles.get("table_header", {}) header_style = styles.get("table_header", {})
header_bg_color = self._getSafeColor(header_style.get("background", (31, 78, 121))) header_bg_color = self._getSafeColor(header_style.get("background", (31, 78, 121)))
header_text_color = self._getSafeColor(header_style.get("text_color", (255, 255, 255))) header_text_color = self._getSafeColor(header_style.get("text_color", (255, 255, 255)))
header_font_size = header_style.get("font_size", 18) header_font_size = header_style.get("font_size", 18)
# Pre-calculate and cache RGB color objects
header_bg_rgb = RGBColor(*header_bg_color)
header_text_rgb = RGBColor(*header_text_color)
header_font_size_pt = Pt(header_font_size)
header_bold = header_style.get("bold", True)
# Determine alignment once
align = header_style.get("align", "center")
if align == "left":
header_alignment = PP_ALIGN.LEFT
elif align == "right":
header_alignment = PP_ALIGN.RIGHT
else:
header_alignment = PP_ALIGN.CENTER
for col_idx, header in enumerate(headers): for col_idx, header in enumerate(headers):
cell = table.cell(0, col_idx) cell = table.cell(0, col_idx)
# Clear existing text and set new text # Clear existing text and set new text
cell.text_frame.clear() cell.text_frame.clear()
cell.text = str(header) if header else "" header_text = str(header) if header else ""
cell.text = header_text
# Ensure paragraph exists # Ensure paragraph exists
if len(cell.text_frame.paragraphs) == 0: if len(cell.text_frame.paragraphs) == 0:
cell.text_frame.add_paragraph() cell.text_frame.add_paragraph()
# Apply styling # Apply styling - use cached objects
cell.fill.solid() cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(*header_bg_color) cell.fill.fore_color.rgb = header_bg_rgb
para = cell.text_frame.paragraphs[0] para = cell.text_frame.paragraphs[0]
para.font.bold = header_style.get("bold", True) para.font.bold = header_bold
para.font.size = Pt(header_font_size) para.font.size = header_font_size_pt
para.font.color.rgb = RGBColor(*header_text_color) para.font.color.rgb = header_text_rgb
para.alignment = header_alignment
align = header_style.get("align", "center")
if align == "left":
para.alignment = PP_ALIGN.LEFT
elif align == "right":
para.alignment = PP_ALIGN.RIGHT
else:
para.alignment = PP_ALIGN.CENTER
# Ensure text is set on paragraph # Ensure text is set on paragraph
if not para.text: if not para.text:
para.text = str(header) if header else "" para.text = header_text
# Add data rows with styling # Add data rows with styling - OPTIMIZED: pre-calculate color/style objects
cell_style = styles.get("table_cell", {}) cell_style = styles.get("table_cell", {})
cell_bg_color = self._getSafeColor(cell_style.get("background", (255, 255, 255))) cell_bg_color = self._getSafeColor(cell_style.get("background", (255, 255, 255)))
cell_text_color = self._getSafeColor(cell_style.get("text_color", (47, 47, 47))) cell_text_color = self._getSafeColor(cell_style.get("text_color", (47, 47, 47)))
cell_font_size = cell_style.get("font_size", 16) cell_font_size = cell_style.get("font_size", 16)
# Pre-calculate and cache RGB color objects
cell_bg_rgb = RGBColor(*cell_bg_color)
cell_text_rgb = RGBColor(*cell_text_color)
cell_font_size_pt = Pt(cell_font_size)
cell_bold = cell_style.get("bold", False)
# Determine alignment once
align = cell_style.get("align", "left")
if align == "center":
cell_alignment = PP_ALIGN.CENTER
elif align == "right":
cell_alignment = PP_ALIGN.RIGHT
else:
cell_alignment = PP_ALIGN.LEFT
for row_idx, row_data in enumerate(rows, 1): for row_idx, row_data in enumerate(rows, 1):
for col_idx, cell_data in enumerate(row_data[:num_cols]): for col_idx, cell_data in enumerate(row_data[:num_cols]):
cell = table.cell(row_idx, col_idx) cell = table.cell(row_idx, col_idx)
# Clear existing text and set new text # Clear existing text and set new text
cell.text_frame.clear() cell.text_frame.clear()
cell.text = str(cell_data) if cell_data is not None else "" cell_text = str(cell_data) if cell_data is not None else ""
cell.text = cell_text
# Ensure paragraph exists # Ensure paragraph exists
if len(cell.text_frame.paragraphs) == 0: if len(cell.text_frame.paragraphs) == 0:
cell.text_frame.add_paragraph() cell.text_frame.add_paragraph()
# Apply styling # Apply styling - use cached objects
cell.fill.solid() cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(*cell_bg_color) cell.fill.fore_color.rgb = cell_bg_rgb
para = cell.text_frame.paragraphs[0] para = cell.text_frame.paragraphs[0]
para.font.size = Pt(cell_font_size) para.font.size = cell_font_size_pt
para.font.bold = cell_style.get("bold", False) para.font.bold = cell_bold
para.font.color.rgb = RGBColor(*cell_text_color) para.font.color.rgb = cell_text_rgb
para.alignment = cell_alignment
align = cell_style.get("align", "left")
if align == "center":
para.alignment = PP_ALIGN.CENTER
elif align == "right":
para.alignment = PP_ALIGN.RIGHT
else:
para.alignment = PP_ALIGN.LEFT
# Ensure text is set on paragraph # Ensure text is set on paragraph
if not para.text: if not para.text:
para.text = str(cell_data) if cell_data is not None else "" para.text = cell_text
except Exception as e: except Exception as e:
logger.warning(f"Error adding table to slide: {str(e)}") logger.warning(f"Error adding table to slide: {str(e)}")
@ -1362,6 +1380,13 @@ JSON ONLY. NO OTHER TEXT."""
base_font_size = list_style.get("font_size", 14) base_font_size = list_style.get("font_size", 14)
calculated_size = max(10, int(base_font_size * font_size_multiplier)) # Minimum 10pt for readability calculated_size = max(10, int(base_font_size * font_size_multiplier)) # Minimum 10pt for readability
# Pre-calculate and cache style objects to avoid repeated parsing
font_size_pt = Pt(calculated_size)
text_color = self._getSafeColor(list_style.get("color", (47, 47, 47)))
text_color_rgb = RGBColor(*text_color)
space_before_pt = Pt(2)
space_after_pt = Pt(2)
logger.debug(f"Rendering bullet list with {len(items)} items") logger.debug(f"Rendering bullet list with {len(items)} items")
for idx, item in enumerate(items): for idx, item in enumerate(items):
@ -1387,12 +1412,12 @@ JSON ONLY. NO OTHER TEXT."""
# Set text content # Set text content
p.text = item_text p.text = item_text
# Apply formatting first # Apply formatting - use cached objects
p.font.size = Pt(calculated_size) p.font.size = font_size_pt
p.font.color.rgb = RGBColor(*self._getSafeColor(list_style.get("color", (47, 47, 47)))) p.font.color.rgb = text_color_rgb
p.alignment = PP_ALIGN.LEFT # Left align bullet lists p.alignment = PP_ALIGN.LEFT # Left align bullet lists
p.space_before = Pt(2) # Small spacing before p.space_before = space_before_pt # Small spacing before
p.space_after = Pt(2) # Small spacing after p.space_after = space_after_pt # Small spacing after
# In python-pptx, setting level > 0 should enable bullets automatically # In python-pptx, setting level > 0 should enable bullets automatically
# However, some versions may not support paragraph_format, so we'll use manual bullets as fallback # However, some versions may not support paragraph_format, so we'll use manual bullets as fallback

View file

@ -1148,60 +1148,69 @@ class RendererXlsx(BaseRenderer):
headerRow = startRow headerRow = startRow
header_style = styles.get("table_header", {}) header_style = styles.get("table_header", {})
# Add headers with formatting # Pre-calculate and cache style objects to avoid repeated parsing
header_font_color = self._getSafeColor(header_style.get("text_color", "FF000000"))
header_font = Font(bold=header_style.get("bold", True), color=header_font_color)
header_bg_color = None
header_fill = None
if header_style.get("background"):
header_bg_color = self._getSafeColor(header_style["background"])
header_fill = PatternFill(start_color=header_bg_color, end_color=header_bg_color, fill_type="solid")
header_alignment = Alignment(
horizontal=self._getSafeAlignment(header_style.get("align", "left")),
vertical="center"
)
# Add headers with formatting - OPTIMIZED: use cached style objects
for col, header in enumerate(headers, 1): for col, header in enumerate(headers, 1):
sanitized_header = self._sanitizeCellValue(header) sanitized_header = self._sanitizeCellValue(header)
cell = sheet.cell(row=headerRow, column=col, value=sanitized_header) cell = sheet.cell(row=headerRow, column=col, value=sanitized_header)
# Apply styling with fallbacks - don't let styling errors prevent data rendering # Apply styling with fallbacks - use pre-calculated objects
try: try:
# Font styling cell.font = header_font
cell.font = Font(
bold=header_style.get("bold", True),
color=self._getSafeColor(header_style.get("text_color", "FF000000"))
)
except Exception: except Exception:
# Fallback to default font if styling fails
try: try:
cell.font = Font(bold=True, color=self._getSafeColor("FF000000")) cell.font = Font(bold=True, color=self._getSafeColor("FF000000"))
except Exception: except Exception:
pass # Continue even if font fails pass
try: try:
# Background color if header_fill:
if header_style.get("background"): cell.fill = header_fill
cell.fill = PatternFill(
start_color=self._getSafeColor(header_style["background"]),
end_color=self._getSafeColor(header_style["background"]),
fill_type="solid"
)
except Exception: except Exception:
pass # Continue without background color if it fails pass
try: try:
# Alignment cell.alignment = header_alignment
cell.alignment = Alignment(
horizontal=self._getSafeAlignment(header_style.get("align", "left")),
vertical="center"
)
except Exception: except Exception:
# Fallback to default alignment if it fails
try: try:
cell.alignment = Alignment(horizontal="left", vertical="center") cell.alignment = Alignment(horizontal="left", vertical="center")
except Exception: except Exception:
pass # Continue even if alignment fails pass
try: try:
# Border
cell.border = thin_border cell.border = thin_border
except Exception: except Exception:
pass # Continue without border if it fails pass
startRow += 1 startRow += 1
# Add rows with formatting # Add rows with formatting - OPTIMIZED: pre-calculate style objects
cell_style = styles.get("table_cell", {}) cell_style = styles.get("table_cell", {})
header_count = len(headers) header_count = len(headers)
# Pre-calculate and cache style objects to avoid repeated parsing
cell_text_color = None
cell_font = None
if cell_style.get("text_color"):
cell_text_color = self._getSafeColor(cell_style["text_color"])
cell_font = Font(color=cell_text_color)
cell_alignment = Alignment(
horizontal=self._getSafeAlignment(cell_style.get("align", "left")),
vertical="center"
)
for row_data in rows: for row_data in rows:
# Handle different row formats # Handle different row formats
if isinstance(row_data, list): if isinstance(row_data, list):
@ -1223,32 +1232,25 @@ class RendererXlsx(BaseRenderer):
sanitized_value = self._sanitizeCellValue(cell_value) sanitized_value = self._sanitizeCellValue(cell_value)
cell = sheet.cell(row=startRow, column=col, value=sanitized_value) cell = sheet.cell(row=startRow, column=col, value=sanitized_value)
# Apply styling with fallbacks - don't let styling errors prevent data rendering # Apply styling with fallbacks - use pre-calculated objects
try: try:
# Font styling if cell_font:
if cell_style.get("text_color"): cell.font = cell_font
cell.font = Font(color=self._getSafeColor(cell_style["text_color"]))
except Exception: except Exception:
pass # Continue without font color if it fails pass
try: try:
# Alignment cell.alignment = cell_alignment
cell.alignment = Alignment(
horizontal=self._getSafeAlignment(cell_style.get("align", "left")),
vertical="center"
)
except Exception: except Exception:
# Fallback to default alignment if it fails
try: try:
cell.alignment = Alignment(horizontal="left", vertical="center") cell.alignment = Alignment(horizontal="left", vertical="center")
except Exception: except Exception:
pass # Continue even if alignment fails pass
try: try:
# Border
cell.border = thin_border cell.border = thin_border
except Exception: except Exception:
pass # Continue without border if it fails pass
startRow += 1 startRow += 1
@ -1448,28 +1450,32 @@ class RendererXlsx(BaseRenderer):
if code: if code:
code_style = styles.get("code_block", {}) code_style = styles.get("code_block", {})
# Pre-calculate and cache style objects to avoid repeated parsing
code_font_name = code_style.get("font", "Courier New")
code_font_size = code_style.get("font_size", 10)
code_text_color = self._getSafeColor(code_style.get("color", "FF2F2F2F"))
code_font = Font(name=code_font_name, size=code_font_size, color=code_text_color)
code_bg_color = None
code_fill = None
if code_style.get("background"):
code_bg_color = self._getSafeColor(code_style["background"])
code_fill = PatternFill(start_color=code_bg_color, end_color=code_bg_color, fill_type="solid")
# Add language label if present # Add language label if present
if language: if language:
langCell = sheet.cell(row=startRow, column=1, value=f"Code ({language}):") langCell = sheet.cell(row=startRow, column=1, value=f"Code ({language}):")
langCell.font = Font(bold=True, color=self._getSafeColor(code_style.get("color", "FF000000"))) langCell.font = Font(bold=True, color=code_text_color)
startRow += 1 startRow += 1
# Split code into lines and add each line # Split code into lines and add each line - use cached style objects
code_lines = code.split('\n') code_lines = code.split('\n')
for line in code_lines: for line in code_lines:
codeCell = sheet.cell(row=startRow, column=1, value=line) codeCell = sheet.cell(row=startRow, column=1, value=line)
codeCell.font = Font( codeCell.font = code_font
name=code_style.get("font", "Courier New"),
size=code_style.get("font_size", 10),
color=self._getSafeColor(code_style.get("color", "FF2F2F2F"))
)
# Set background color if specified # Set background color if specified
if code_style.get("background"): if code_fill:
codeCell.fill = PatternFill( codeCell.fill = code_fill
start_color=self._getSafeColor(code_style["background"]),
end_color=self._getSafeColor(code_style["background"]),
fill_type="solid"
)
startRow += 1 startRow += 1
# Add spacing after code block # Add spacing after code block