fix:图片字体应该黑体

This commit is contained in:
fuzhongyun 2025-12-31 15:02:20 +08:00
parent b6a866289a
commit 0ba4800ff7
13 changed files with 73 additions and 310 deletions

View File

@ -28,15 +28,34 @@ class ExcelRenderer:
# Cache for loaded fonts to avoid reloading for same size
self.font_cache = {}
def _get_font(self, is_bold: bool, size: int) -> ImageFont.FreeTypeFont:
def _get_font(self, font_name: Optional[str], is_bold: bool, size: int) -> ImageFont.FreeTypeFont:
"""
Get font with specific properties, using cache.
"""
key = (is_bold, size)
key = (font_name, is_bold, size)
if key in self.font_cache:
return self.font_cache[key]
font_path = self.font_path_bold if is_bold else self.font_path_regular
# Determine font file path based on font name
# Default to regular (simsun) if not specified or not found
font_path = self.font_path_regular
if font_name:
font_name_lower = font_name.lower()
if "黑体" in font_name_lower or "simhei" in font_name_lower or "heiti" in font_name_lower:
font_path = self.font_path_bold # Use SimHei for Heiti
elif "宋体" in font_name_lower or "simsun" in font_name_lower or "songti" in font_name_lower:
font_path = self.font_path_regular # Use SimSun for Songti
# Add more mappings here if needed (e.g., Arial -> arial.ttf)
# If is_bold is True but we selected a regular font (like SimSun), PIL can fake bold but it's better to use a bold font file if available.
# However, for Chinese fonts like SimHei, it's already "bold-like" (sans-serif bold).
# If we are using SimSun (Regular) and want bold, we might want to check if we have a Bold version of SimSun (usually we don't in this simple setup).
# Current logic: self.font_path_bold is SimHei.
# So if is_bold is True, we often prefer SimHei over SimSun if no specific font is requested.
if not font_name and is_bold:
font_path = self.font_path_bold
try:
font = ImageFont.truetype(font_path, size)
@ -215,11 +234,12 @@ class ExcelRenderer:
# Font handling
is_bold = cell.font and cell.font.bold
font_name = cell.font.name # Get font family name
# Excel font size is in points. 12 is default.
font_size = int(cell.font.sz) if (cell.font and cell.font.sz) else 12
# Scale the font size
scaled_font_size = int(font_size * scale)
current_font = self._get_font(is_bold, scaled_font_size)
current_font = self._get_font(font_name, is_bold, scaled_font_size)
# Font color
# Excel's Color object can be complex. We pass the whole object to _parse_color.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/kshj_total.xlsx Executable file

Binary file not shown.

View File

@ -1,60 +0,0 @@
import pytest
from fastapi.testclient import TestClient
from app import app
from openpyxl import Workbook
import io
client = TestClient(app)
@pytest.fixture
def sample_excel_file():
wb = Workbook()
ws = wb.active
ws.title = "APITest"
ws['A1'] = "API"
out = io.BytesIO()
wb.save(out)
out.seek(0)
return out
def test_health_check():
response = client.get("/health")
assert response.status_code == 200
assert response.json() == {"status": "ok"}
def test_convert_endpoint(sample_excel_file):
files = {'file': ('test.xlsx', sample_excel_file, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
response = client.post("/api/v1/convert", files=files)
assert response.status_code == 200
assert response.headers["content-type"] == "image/png"
assert len(response.content) > 0
def test_convert_invalid_file_type():
files = {'file': ('test.txt', io.BytesIO(b"dummy"), 'text/plain')}
response = client.post("/api/v1/convert", files=files)
assert response.status_code == 400
assert "Invalid file format" in response.json()["detail"]
def test_convert_specific_sheet(sample_excel_file):
# Re-create file because previous read might have consumed it if not handled carefully (TestClient usually handles this)
# But let's be safe and use the fixture which returns a new BytesIO if we construct it that way.
# Actually the fixture returns the same object, let's seek 0 just in case.
sample_excel_file.seek(0)
files = {'file': ('test.xlsx', sample_excel_file, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
data = {'sheet_name': 'APITest'}
response = client.post("/api/v1/convert", files=files, data=data)
assert response.status_code == 200
def test_convert_missing_sheet(sample_excel_file):
sample_excel_file.seek(0)
files = {'file': ('test.xlsx', sample_excel_file, 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')}
data = {'sheet_name': 'MissingSheet'}
response = client.post("/api/v1/convert", files=files, data=data)
assert response.status_code == 400
assert "Sheet 'MissingSheet' not found" in response.json()["detail"]

View File

@ -1,36 +0,0 @@
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_border_rendering_real_file():
"""
Test rendering with a real file that has border 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 with high DPI scale
img_bytes = renderer.render_to_bytes(scale=3)
# Save for visual inspection
output_path = "tests/test_output_border.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}")

View File

@ -1,36 +0,0 @@
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}")

49
tests/test_font_family.py Normal file
View File

@ -0,0 +1,49 @@
import pytest
import io
import os
from openpyxl import Workbook
from openpyxl.styles import Font
from core.renderer import ExcelRenderer
from PIL import Image, ImageFont
@pytest.fixture
def font_family_excel_bytes():
wb = Workbook()
ws = wb.active
ws.title = "FontFamilyTest"
# SimSun (Songti)
ws['A1'] = "宋体文本"
ws['A1'].font = Font(name="宋体", size=12)
# SimHei (Heiti)
ws['A2'] = "黑体文本"
ws['A2'].font = Font(name="黑体", size=12)
# English Font
ws['A3'] = "Arial Text"
ws['A3'].font = Font(name="Arial", size=12)
out = io.BytesIO()
wb.save(out)
out.seek(0)
return out.getvalue()
def test_font_family_selection(font_family_excel_bytes):
"""
Test that different font families are mapped correctly.
Note: We can't easily check visual output in unit test,
but we can check if the renderer attempts to load different fonts.
"""
renderer = ExcelRenderer(font_family_excel_bytes)
try:
renderer.render_to_bytes(sheet_name="FontFamilyTest")
except Exception as e:
pytest.fail(f"Rendering failed: {e}")
# Check internal cache to see if different fonts were loaded
# This assumes we implement logic to cache based on font name too
# Currently it might fail or only show 1 font if logic is missing
# print(renderer.font_cache.keys())
pass

View File

@ -1,50 +0,0 @@
import pytest
from openpyxl import Workbook
from openpyxl.styles import Font
import io
from core.renderer import ExcelRenderer
from PIL import Image
@pytest.fixture
def font_size_excel_bytes():
wb = Workbook()
ws = wb.active
ws.title = "FontTest"
# Standard size
ws['A1'] = "Standard 12"
ws['A1'].font = Font(size=12)
# Large size
ws['A2'] = "Large 20"
ws['A2'].font = Font(size=20)
# Small size
ws['A3'] = "Small 8"
ws['A3'].font = Font(size=8)
out = io.BytesIO()
wb.save(out)
out.seek(0)
return out.getvalue()
def test_font_size_rendering(font_size_excel_bytes):
"""
Test that rendering handles different font sizes without crashing.
Note: Visual verification is hard in unit tests, but we can ensure
the code paths for dynamic font loading are executed.
"""
renderer = ExcelRenderer(font_size_excel_bytes)
try:
img_bytes = renderer.render_to_bytes(sheet_name="FontTest")
except Exception as e:
pytest.fail(f"Rendering failed with error: {e}")
assert isinstance(img_bytes, bytes)
img = Image.open(io.BytesIO(img_bytes))
assert img.format == "PNG"
# We can also inspect the internal cache to see if different fonts were loaded
# (Accessing private attribute for testing purpose)
assert len(renderer.font_cache) >= 3, "Should have cached at least 3 different font configurations"

View File

@ -1,31 +0,0 @@
import pytest
import io
import os
from core.renderer import ExcelRenderer
from PIL import Image
TEST_FILE_PATH = "tests/kshj_gt1767081783800.xlsx"
@pytest.mark.skipif(not os.path.exists(TEST_FILE_PATH), reason="Test file not found")
def test_high_dpi_rendering():
"""
Test rendering with higher scale factor.
"""
with open(TEST_FILE_PATH, "rb") as f:
content = f.read()
renderer = ExcelRenderer(content)
# Render with scale=3 (High Quality)
img_bytes = renderer.render_to_bytes(scale=3, dpi=300)
assert isinstance(img_bytes, bytes)
assert len(img_bytes) > 0
img = Image.open(io.BytesIO(img_bytes))
# Save for visual inspection
output_path = "tests/test_output_high_dpi.png"
img.save(output_path)
print(f"Generated high DPI test image at: {os.path.abspath(output_path)}")
print(f"Image Size: {img.size}")

View File

@ -1,46 +0,0 @@
import pytest
from openpyxl import Workbook
from openpyxl.styles import Alignment
import io
from core.renderer import ExcelRenderer
from PIL import Image
@pytest.fixture
def merged_cell_excel_bytes():
wb = Workbook()
ws = wb.active
ws.title = "MergedTest"
# Merge A1:B2
ws.merge_cells('A1:B2')
cell = ws['A1']
cell.value = "Merged Content"
cell.alignment = Alignment(horizontal='center', vertical='center')
# Add some other content
ws['C1'] = "C1"
ws['C2'] = "C2"
ws['A3'] = "A3"
out = io.BytesIO()
wb.save(out)
out.seek(0)
return out.getvalue()
def test_merged_cell_rendering(merged_cell_excel_bytes):
"""
Test that rendering an Excel file with merged cells does not raise an AttributeError.
Specifically checking for 'MergedCell' object has no attribute 'column_letter'.
"""
renderer = ExcelRenderer(merged_cell_excel_bytes)
try:
img_bytes = renderer.render_to_bytes(sheet_name="MergedTest")
except AttributeError as e:
pytest.fail(f"Rendering failed with AttributeError: {e}")
except Exception as e:
pytest.fail(f"Rendering failed with unexpected error: {e}")
assert isinstance(img_bytes, bytes)
img = Image.open(io.BytesIO(img_bytes))
assert img.format == "PNG"

View File

@ -1,47 +0,0 @@
import pytest
from openpyxl import Workbook
import io
from core.renderer import ExcelRenderer
from PIL import Image
@pytest.fixture
def sample_excel_bytes():
wb = Workbook()
ws = wb.active
ws.title = "TestSheet"
ws['A1'] = "Hello"
ws['B1'] = "World"
ws['A2'] = 123
ws['B2'] = 456.78
# Add some color
from openpyxl.styles import PatternFill
fill = PatternFill(start_color="FFFF0000", end_color="FFFF0000", fill_type="solid")
ws['A1'].fill = fill
out = io.BytesIO()
wb.save(out)
out.seek(0)
return out.getvalue()
def test_renderer_initialization(sample_excel_bytes):
renderer = ExcelRenderer(sample_excel_bytes)
assert renderer is not None
def test_render_to_bytes(sample_excel_bytes):
renderer = ExcelRenderer(sample_excel_bytes)
img_bytes = renderer.render_to_bytes(sheet_name="TestSheet")
assert isinstance(img_bytes, bytes)
assert len(img_bytes) > 0
# Verify it's a valid image
img = Image.open(io.BytesIO(img_bytes))
assert img.format == "PNG"
assert img.width > 0
assert img.height > 0
def test_render_invalid_sheet(sample_excel_bytes):
renderer = ExcelRenderer(sample_excel_bytes)
with pytest.raises(ValueError, match="Sheet 'NonExistent' not found"):
renderer.render_to_bytes(sheet_name="NonExistent")