From b429b8e8749e63c0d65c017704f1d30b0cd07bce Mon Sep 17 00:00:00 2001 From: fuzhongyun <15339891972@163.com> Date: Tue, 30 Dec 2025 16:35:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=94=B9=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E9=A2=9C=E8=89=B2=E6=9C=AA=E4=BD=BF=E7=94=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/renderer.py | 46 ++++++++++++++++++++++++++------ tests/kshj_gt1767064690.xlsx | Bin 11024 -> 0 bytes tests/kshj_gt1767081783800.xlsx | Bin 0 -> 11320 bytes tests/test_font_color.py | 36 +++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 8 deletions(-) delete mode 100755 tests/kshj_gt1767064690.xlsx create mode 100755 tests/kshj_gt1767081783800.xlsx create mode 100644 tests/test_font_color.py diff --git a/core/renderer.py b/core/renderer.py index 1638d1a..6538e49 100644 --- a/core/renderer.py +++ b/core/renderer.py @@ -179,8 +179,9 @@ class ExcelRenderer: current_font = self._get_font(is_bold, font_size) # Font color - font_color_hex = cell.font.color.rgb if (cell.font and cell.font.color) else None - text_color = self._parse_color(font_color_hex, default=(0, 0, 0)) + # Excel's Color object can be complex. We pass the whole object to _parse_color. + font_color_obj = cell.font.color if (cell.font and cell.font.color) else None + text_color = self._parse_color(font_color_obj, default=(0, 0, 0)) # Alignment h_align = cell.alignment.horizontal if (cell.alignment and cell.alignment.horizontal) else 'left' @@ -189,15 +190,44 @@ class ExcelRenderer: # Text rendering with simple truncation self._draw_text(draw, text, x1, y1, x2, y2, current_font, text_color, h_align, v_align, font_size) - def _parse_color(self, color_code, default=(0, 0, 0)) -> Tuple[int, int, int]: - if not color_code or color_code == '00000000' or not isinstance(color_code, str): + def _parse_color(self, color_obj, default=(0, 0, 0)) -> Tuple[int, int, int]: + """ + Parse Excel color object to RGB tuple. + """ + if not color_obj: return default + + color_code = None - # Handle ARGB (Excel often uses this) + # If it's a string, treat as hex + if isinstance(color_obj, str): + color_code = color_obj + # If it's a Color object + elif hasattr(color_obj, 'type'): + if color_obj.type == 'rgb': + color_code = color_obj.rgb + elif color_obj.type == 'theme': + # Use hardcoded common theme colors as a fallback for MVP + # Theme 0: Light 1 (White) - FFFFFF + # Theme 1: Dark 1 (Black) - 000000 + # Theme 2: Light 2 (EEECE1) + # Theme 3: Dark 2 (1F497D) + if color_obj.theme == 0: + color_code = "FFFFFFFF" # White + elif color_obj.theme == 1: + color_code = "FF000000" # Black + else: + # Attempt to check if rgb is populated even for theme + if hasattr(color_obj, 'rgb') and color_obj.rgb: + color_code = color_obj.rgb + elif hasattr(color_obj, 'rgb'): + color_code = color_obj.rgb + + if not color_code or color_code == '00000000': + return default + + # Handle ARGB if len(color_code) > 6: - # Strip alpha if present (usually first 2 chars for ARGB) - # Example: FF000000 -> 000000 (Black), FFFFFFFF -> FFFFFF (White) - # Note: This is a simplification. if color_code.startswith('FF') or len(color_code) == 8: color_code = color_code[2:] diff --git a/tests/kshj_gt1767064690.xlsx b/tests/kshj_gt1767064690.xlsx deleted file mode 100755 index 9343eeb2072322e50d031acc3d1710b8f9320714..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11024 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 diff --git a/tests/kshj_gt1767081783800.xlsx b/tests/kshj_gt1767081783800.xlsx new file mode 100755 index 0000000000000000000000000000000000000000..960a8c98487edccc36baa5f192b3765e08b63d48 GIT binary patch literal 11320 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 b18x2b3(HAD{^c7L1Qi4tNECR~&$s!1G@D=~ literal 0 HcmV?d00001 diff --git a/tests/test_font_color.py b/tests/test_font_color.py new file mode 100644 index 0000000..15eb6e5 --- /dev/null +++ b/tests/test_font_color.py @@ -0,0 +1,36 @@ +import pytest +import io +import os +from core.renderer import ExcelRenderer +from PIL import Image + +# Use the file provided by the user for reproduction +TEST_FILE_PATH = "tests/kshj_gt1767081783800.xlsx" + +@pytest.mark.skipif(not os.path.exists(TEST_FILE_PATH), reason="Test file not found") +def test_font_color_rendering_real_file(): + """ + Test rendering with a real file that has font color issues. + This test will generate an output image for visual inspection. + """ + with open(TEST_FILE_PATH, "rb") as f: + content = f.read() + + renderer = ExcelRenderer(content) + + try: + # Render the first sheet (or specific sheet if known, default to active) + img_bytes = renderer.render_to_bytes() + + # Save for visual inspection + output_path = "tests/test_output_font_color.png" + with open(output_path, "wb") as f_out: + f_out.write(img_bytes) + + print(f"Generated test image at: {os.path.abspath(output_path)}") + + assert isinstance(img_bytes, bytes) + assert len(img_bytes) > 0 + + except Exception as e: + pytest.fail(f"Rendering failed: {e}")