fixed bug in font rendering when zooming
All checks were successful
Python CI / test (push) Successful in 1m32s
Lint / lint (push) Successful in 1m18s
Tests / test (3.11) (push) Successful in 1m47s
Tests / test (3.12) (push) Successful in 1m48s
Tests / test (3.13) (push) Successful in 1m42s
Tests / test (3.14) (push) Successful in 1m18s
All checks were successful
Python CI / test (push) Successful in 1m32s
Lint / lint (push) Successful in 1m18s
Tests / test (3.11) (push) Successful in 1m47s
Tests / test (3.12) (push) Successful in 1m48s
Tests / test (3.13) (push) Successful in 1m42s
Tests / test (3.14) (push) Successful in 1m18s
This commit is contained in:
parent
6a791b1397
commit
cf27d9ebee
@ -254,7 +254,8 @@ class RenderingMixin:
|
||||
screen_h = h * renderer.zoom
|
||||
|
||||
font_family = element.font_settings.get("family", "Arial")
|
||||
font_size = int(element.font_settings.get("size", 12) * renderer.zoom)
|
||||
# Use base font size without zoom - zoom is applied via painter transform
|
||||
font_size = int(element.font_settings.get("size", 12))
|
||||
font = QFont(font_family, font_size)
|
||||
painter.setFont(font)
|
||||
|
||||
@ -265,16 +266,17 @@ class RenderingMixin:
|
||||
color = QColor(int(font_color[0] * 255), int(font_color[1] * 255), int(font_color[2] * 255))
|
||||
painter.setPen(QPen(color))
|
||||
|
||||
# Apply zoom via painter transform so font scales consistently with page
|
||||
painter.save()
|
||||
painter.translate(screen_x, screen_y)
|
||||
painter.scale(renderer.zoom, renderer.zoom)
|
||||
|
||||
if element.rotation != 0:
|
||||
painter.save()
|
||||
center_x = screen_x + screen_w / 2
|
||||
center_y = screen_y + screen_h / 2
|
||||
painter.translate(center_x, center_y)
|
||||
painter.translate(w / 2, h / 2)
|
||||
painter.rotate(element.rotation)
|
||||
painter.translate(-screen_w / 2, -screen_h / 2)
|
||||
rect = QRectF(0, 0, screen_w, screen_h)
|
||||
else:
|
||||
rect = QRectF(screen_x, screen_y, screen_w, screen_h)
|
||||
painter.translate(-w / 2, -h / 2)
|
||||
|
||||
rect = QRectF(0, 0, w, h)
|
||||
|
||||
alignment = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
||||
if element.alignment == "center":
|
||||
@ -288,8 +290,7 @@ class RenderingMixin:
|
||||
|
||||
painter.drawText(rect, int(alignment | text_flags), element.text_content)
|
||||
|
||||
if element.rotation != 0:
|
||||
painter.restore()
|
||||
painter.restore()
|
||||
|
||||
finally:
|
||||
painter.end()
|
||||
@ -308,15 +309,20 @@ class RenderingMixin:
|
||||
px, py, pw, ph = ghost_data.get_page_rect()
|
||||
|
||||
screen_x, screen_y = renderer.page_to_screen(px, py)
|
||||
screen_w = pw * renderer.zoom
|
||||
screen_h = ph * renderer.zoom
|
||||
|
||||
font = QFont("Arial", int(16 * renderer.zoom), QFont.Weight.Bold)
|
||||
# Use base font size without zoom - zoom is applied via painter transform
|
||||
font = QFont("Arial", 16, QFont.Weight.Bold)
|
||||
painter.setFont(font)
|
||||
painter.setPen(QColor(120, 120, 120))
|
||||
|
||||
rect = QRectF(screen_x, screen_y, screen_w, screen_h)
|
||||
painter.save()
|
||||
painter.translate(screen_x, screen_y)
|
||||
painter.scale(renderer.zoom, renderer.zoom)
|
||||
|
||||
rect = QRectF(0, 0, pw, ph)
|
||||
painter.drawText(rect, Qt.AlignmentFlag.AlignCenter, "Click to Add Page")
|
||||
|
||||
painter.restore()
|
||||
|
||||
finally:
|
||||
painter.end()
|
||||
|
||||
@ -670,3 +670,232 @@ class TestRenderGhostPage:
|
||||
|
||||
# Painter should be ended (cleanup)
|
||||
mock_painter.end.assert_called_once()
|
||||
|
||||
|
||||
class TestFontZoomIndependence:
|
||||
"""Test that font size is independent of zoom level (bug fix verification)"""
|
||||
|
||||
def test_font_size_not_scaled_by_zoom(self, qtbot):
|
||||
"""Test that font size remains constant regardless of zoom level.
|
||||
|
||||
This test verifies the fix for the bug where font size was multiplied
|
||||
by zoom level, causing text to scale differently than in PDF export.
|
||||
"""
|
||||
widget = TestRenderingWidget()
|
||||
qtbot.addWidget(widget)
|
||||
widget.resize(1000, 800)
|
||||
|
||||
base_font_size = 24
|
||||
|
||||
# Create text element with specific font size
|
||||
text_element = TextBoxData(
|
||||
x=50, y=50, width=200, height=100,
|
||||
text_content="Test Text",
|
||||
font_settings={
|
||||
"family": "Arial",
|
||||
"size": base_font_size,
|
||||
"color": (0, 0, 0)
|
||||
},
|
||||
alignment="left"
|
||||
)
|
||||
|
||||
# Mock page with text element
|
||||
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
||||
page.layout.elements = [text_element]
|
||||
|
||||
# Test with different zoom levels
|
||||
for zoom_level in [0.5, 1.0, 2.0, 3.0]:
|
||||
mock_renderer = Mock()
|
||||
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
||||
mock_renderer.zoom = zoom_level
|
||||
|
||||
widget._page_renderers = [(mock_renderer, page)]
|
||||
|
||||
# Mock QPainter and QFont
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
|
||||
mock_painter = Mock()
|
||||
mock_painter_class.return_value = mock_painter
|
||||
|
||||
widget._render_text_overlays()
|
||||
|
||||
# QFont should be created with base font size, NOT scaled by zoom
|
||||
mock_qfont_class.assert_called()
|
||||
call_args = mock_qfont_class.call_args
|
||||
actual_font_size = call_args[0][1] # Second positional arg is size
|
||||
|
||||
assert actual_font_size == base_font_size, (
|
||||
f"Font size should be {base_font_size} at zoom {zoom_level}, "
|
||||
f"got {actual_font_size}"
|
||||
)
|
||||
|
||||
def test_zoom_applied_via_painter_scale(self, qtbot):
|
||||
"""Test that zoom is applied via painter.scale() transformation.
|
||||
|
||||
This ensures text scales consistently with the page content using
|
||||
the painter's transformation matrix rather than font size modification.
|
||||
"""
|
||||
widget = TestRenderingWidget()
|
||||
qtbot.addWidget(widget)
|
||||
widget.resize(1000, 800)
|
||||
|
||||
# Create text element
|
||||
text_element = TextBoxData(
|
||||
x=50, y=50, width=200, height=100,
|
||||
text_content="Test Text",
|
||||
font_settings={
|
||||
"family": "Arial",
|
||||
"size": 12,
|
||||
"color": (0, 0, 0)
|
||||
},
|
||||
alignment="left"
|
||||
)
|
||||
|
||||
# Mock page with text element
|
||||
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
||||
page.layout.elements = [text_element]
|
||||
|
||||
zoom_level = 2.5
|
||||
mock_renderer = Mock()
|
||||
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
||||
mock_renderer.zoom = zoom_level
|
||||
|
||||
widget._page_renderers = [(mock_renderer, page)]
|
||||
|
||||
# Mock QPainter
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
||||
mock_painter = Mock()
|
||||
mock_painter_class.return_value = mock_painter
|
||||
|
||||
widget._render_text_overlays()
|
||||
|
||||
# Painter should call scale() with zoom level
|
||||
mock_painter.scale.assert_called_with(zoom_level, zoom_level)
|
||||
|
||||
# Painter should use save/restore for transformation
|
||||
mock_painter.save.assert_called()
|
||||
mock_painter.restore.assert_called()
|
||||
|
||||
def test_text_rect_uses_page_coordinates(self, qtbot):
|
||||
"""Test that text rect uses page coordinates (not screen coordinates).
|
||||
|
||||
The text rect should be in page coordinates (w, h) since the painter
|
||||
applies the zoom transformation via scale().
|
||||
"""
|
||||
widget = TestRenderingWidget()
|
||||
qtbot.addWidget(widget)
|
||||
widget.resize(1000, 800)
|
||||
|
||||
element_width = 200
|
||||
element_height = 100
|
||||
|
||||
# Create text element
|
||||
text_element = TextBoxData(
|
||||
x=50, y=50, width=element_width, height=element_height,
|
||||
text_content="Test Text",
|
||||
font_settings={
|
||||
"family": "Arial",
|
||||
"size": 12,
|
||||
"color": (0, 0, 0)
|
||||
},
|
||||
alignment="left"
|
||||
)
|
||||
|
||||
# Mock page with text element
|
||||
page = Page(layout=PageLayout(width=210, height=297), page_number=1)
|
||||
page.layout.elements = [text_element]
|
||||
|
||||
mock_renderer = Mock()
|
||||
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
||||
mock_renderer.zoom = 2.0 # Zoom that would double screen size
|
||||
|
||||
widget._page_renderers = [(mock_renderer, page)]
|
||||
|
||||
# Mock QPainter and QRectF
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QRectF") as mock_qrectf_class:
|
||||
mock_painter = Mock()
|
||||
mock_painter_class.return_value = mock_painter
|
||||
|
||||
widget._render_text_overlays()
|
||||
|
||||
# QRectF should be called with page coordinates (0, 0, w, h)
|
||||
# not screen coordinates (0, 0, w*zoom, h*zoom)
|
||||
mock_qrectf_class.assert_called()
|
||||
call_args = mock_qrectf_class.call_args
|
||||
rect_width = call_args[0][2]
|
||||
rect_height = call_args[0][3]
|
||||
|
||||
assert rect_width == element_width, (
|
||||
f"Rect width should be {element_width} (page coords), got {rect_width}"
|
||||
)
|
||||
assert rect_height == element_height, (
|
||||
f"Rect height should be {element_height} (page coords), got {rect_height}"
|
||||
)
|
||||
|
||||
def test_ghost_page_font_not_scaled_by_zoom(self, qtbot):
|
||||
"""Test that ghost page font size is not scaled by zoom level."""
|
||||
widget = TestRenderingWidget()
|
||||
qtbot.addWidget(widget)
|
||||
widget.resize(1000, 800)
|
||||
|
||||
# Create mock ghost data
|
||||
ghost_data = Mock()
|
||||
ghost_data.page_size = (210, 297)
|
||||
ghost_data.render = Mock()
|
||||
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
||||
|
||||
for zoom_level in [0.5, 1.0, 2.0]:
|
||||
# Create mock renderer with varying zoom
|
||||
mock_renderer = Mock()
|
||||
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
||||
mock_renderer.zoom = zoom_level
|
||||
mock_renderer.begin_render = Mock()
|
||||
mock_renderer.end_render = Mock()
|
||||
|
||||
# Mock QPainter and QFont
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QFont") as mock_qfont_class:
|
||||
mock_painter = Mock()
|
||||
mock_painter_class.return_value = mock_painter
|
||||
|
||||
widget._render_ghost_page(ghost_data, mock_renderer)
|
||||
|
||||
# QFont should be created with base font size 16, NOT scaled
|
||||
mock_qfont_class.assert_called()
|
||||
call_args = mock_qfont_class.call_args
|
||||
actual_font_size = call_args[0][1] # Second positional arg is size
|
||||
|
||||
assert actual_font_size == 16, (
|
||||
f"Ghost page font size should be 16 at zoom {zoom_level}, "
|
||||
f"got {actual_font_size}"
|
||||
)
|
||||
|
||||
def test_ghost_page_zoom_applied_via_scale(self, qtbot):
|
||||
"""Test that ghost page zoom is applied via painter.scale()."""
|
||||
widget = TestRenderingWidget()
|
||||
qtbot.addWidget(widget)
|
||||
widget.resize(1000, 800)
|
||||
|
||||
# Create mock ghost data
|
||||
ghost_data = Mock()
|
||||
ghost_data.page_size = (210, 297)
|
||||
ghost_data.render = Mock()
|
||||
ghost_data.get_page_rect = Mock(return_value=(0, 0, 794, 1123))
|
||||
|
||||
zoom_level = 1.5
|
||||
mock_renderer = Mock()
|
||||
mock_renderer.page_to_screen = Mock(return_value=(100, 100))
|
||||
mock_renderer.zoom = zoom_level
|
||||
mock_renderer.begin_render = Mock()
|
||||
mock_renderer.end_render = Mock()
|
||||
|
||||
# Mock QPainter
|
||||
with patch("pyPhotoAlbum.mixins.rendering.QPainter") as mock_painter_class:
|
||||
mock_painter = Mock()
|
||||
mock_painter_class.return_value = mock_painter
|
||||
|
||||
widget._render_ghost_page(ghost_data, mock_renderer)
|
||||
|
||||
# Painter should call scale() with zoom level
|
||||
mock_painter.scale.assert_called_with(zoom_level, zoom_level)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user