fixed issue with bounding box height being wrong
Some checks failed
Python CI / test (3.10) (push) Has been cancelled
Python CI / test (3.12) (push) Has been cancelled
Python CI / test (3.13) (push) Has been cancelled

This commit is contained in:
Duncan Tourolle 2025-11-09 17:10:50 +01:00
parent 56c2c21021
commit 50b9aa5431
4 changed files with 56 additions and 13 deletions

View File

@ -227,8 +227,11 @@ class Text(Renderable, Queriable):
@property @property
def size(self) -> int: def size(self) -> int:
"""Get the width of the text""" """Get the width and height of the text"""
return np.array((self._width, self._style.font_size)) # Return actual rendered height (ascent + descent) not just font_size
ascent, descent = self._style.font.getmetrics()
actual_height = ascent + descent
return np.array((self._width, actual_height))
def set_origin(self, origin: np.generic): def set_origin(self, origin: np.generic):
"""Set the origin (left baseline ("ls")) of this text element""" """Set the origin (left baseline ("ls")) of this text element"""
@ -238,6 +241,31 @@ class Text(Renderable, Queriable):
"""Add this text to a line""" """Add this text to a line"""
self._line = line self._line = line
def in_object(self, point: np.generic):
"""
Check if a point is in the text object.
Override Queriable.in_object() because Text uses baseline-anchored positioning.
The origin is at the baseline (anchor="ls"), not the top-left corner.
Args:
point: The coordinates to check
Returns:
True if the point is within the text bounds
"""
point_array = np.array(point)
# Text origin is at baseline, so visual top is origin[1] - ascent
visual_top = self._origin[1] - self._ascent
visual_bottom = self._origin[1] + (self.size[1] - self._ascent)
# Check if point is within bounds
# X: origin[0] to origin[0] + width
# Y: visual_top to visual_bottom
return (self._origin[0] <= point_array[0] < self._origin[0] + self.size[0] and
visual_top <= point_array[1] < visual_bottom)
def _apply_decoration(self, next_text: Optional['Text'] = None, spacing: int = 0): def _apply_decoration(self, next_text: Optional['Text'] = None, spacing: int = 0):
""" """
Apply text decoration (underline or strikethrough). Apply text decoration (underline or strikethrough).

View File

@ -125,8 +125,8 @@ class TestLinkText(unittest.TestCase):
# Mock width property # Mock width property
renderable._width = 80 renderable._width = 80
# Point inside link # Point inside link - origin is at baseline (10, 20), so test at baseline Y
self.assertTrue(renderable.in_object((15, 25))) self.assertTrue(renderable.in_object((15, 20)))
# Point outside link # Point outside link
self.assertFalse(renderable.in_object((200, 200))) self.assertFalse(renderable.in_object((200, 200)))

View File

@ -76,8 +76,14 @@ class TestText(unittest.TestCase):
def test_in_object_true(self): def test_in_object_true(self):
text_instance = Text(text="Test", style=self.style, draw=self.draw) text_instance = Text(text="Test", style=self.style, draw=self.draw)
# Set origin at baseline position (50, 50)
text_instance.set_origin(np.array([50, 50]))
# Test with a point that should be inside the text bounds # Test with a point that should be inside the text bounds
point = (5, 5) # The text origin is at the baseline (50, 50)
# Visual bounds are: top = 50 - ascent, bottom = 50 + descent
# So a point at (55, 50) should be inside (at baseline)
point = (55, 50)
self.assertTrue(text_instance.in_object(point)) self.assertTrue(text_instance.in_object(point))
def test_in_object_false(self): def test_in_object_false(self):

View File

@ -145,7 +145,9 @@ class TestTextQueryPoint(unittest.TestCase):
text.set_origin(np.array([100, 100])) text.set_origin(np.array([100, 100]))
# Point inside text bounds # Point inside text bounds
self.assertTrue(text.in_object(np.array([110, 105]))) # Origin is at baseline (100, 100), so test a point slightly above (at ascent/2)
# and to the right
self.assertTrue(text.in_object(np.array([110, 100])))
def test_in_object_miss(self): def test_in_object_miss(self):
"""Test in_object returns False for point outside text""" """Test in_object returns False for point outside text"""
@ -188,7 +190,9 @@ class TestLineQueryPoint(unittest.TestCase):
# (after rendering, text objects have positions set) # (after rendering, text objects have positions set)
if len(line._text_objects) > 0: if len(line._text_objects) > 0:
text_obj = line._text_objects[0] text_obj = line._text_objects[0]
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1] + 5)) # Origin is at baseline, so query at baseline position (Y = origin[1])
# with X offset into the text
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1]))
result = line.query_point(point) result = line.query_point(point)
@ -234,7 +238,8 @@ class TestLineQueryPoint(unittest.TestCase):
# Query the link # Query the link
if len(line._text_objects) > 0: if len(line._text_objects) > 0:
text_obj = line._text_objects[0] text_obj = line._text_objects[0]
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1] + 5)) # Origin is at baseline, query at baseline Y position
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1]))
result = line.query_point(point) result = line.query_point(point)
@ -279,7 +284,8 @@ class TestPageQueryPoint(unittest.TestCase):
# Query a point inside the line # Query a point inside the line
if len(line._text_objects) > 0: if len(line._text_objects) > 0:
text_obj = line._text_objects[0] text_obj = line._text_objects[0]
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1] + 5)) # Origin is at baseline, query at baseline Y position
point = (int(text_obj._origin[0] + 5), int(text_obj._origin[1]))
result = self.page.query_point(point) result = self.page.query_point(point)
@ -321,7 +327,8 @@ class TestPageQueryPoint(unittest.TestCase):
# Query first line # Query first line
if len(line1._text_objects) > 0: if len(line1._text_objects) > 0:
text_obj1 = line1._text_objects[0] text_obj1 = line1._text_objects[0]
point1 = (int(text_obj1._origin[0] + 5), int(text_obj1._origin[1] + 5)) # Origin is at baseline, query at baseline Y position
point1 = (int(text_obj1._origin[0] + 5), int(text_obj1._origin[1]))
result1 = self.page.query_point(point1) result1 = self.page.query_point(point1)
self.assertIsNotNone(result1) self.assertIsNotNone(result1)
@ -330,7 +337,8 @@ class TestPageQueryPoint(unittest.TestCase):
# Query second line # Query second line
if len(line2._text_objects) > 0: if len(line2._text_objects) > 0:
text_obj2 = line2._text_objects[0] text_obj2 = line2._text_objects[0]
point2 = (int(text_obj2._origin[0] + 5), int(text_obj2._origin[1] + 5)) # Origin is at baseline, query at baseline Y position
point2 = (int(text_obj2._origin[0] + 5), int(text_obj2._origin[1]))
result2 = self.page.query_point(point2) result2 = self.page.query_point(point2)
self.assertIsNotNone(result2) self.assertIsNotNone(result2)
@ -368,9 +376,10 @@ class TestPageQueryRange(unittest.TestCase):
start_text = line._text_objects[0] start_text = line._text_objects[0]
end_text = line._text_objects[1] end_text = line._text_objects[1]
# Origin is at baseline, query at baseline Y position
start_point = ( start_point = (
int(start_text._origin[0] + 5), int(start_text._origin[1] + 5)) int(start_text._origin[0] + 5), int(start_text._origin[1]))
end_point = (int(end_text._origin[0] + 5), int(end_text._origin[1] + 5)) end_point = (int(end_text._origin[0] + 5), int(end_text._origin[1]))
sel_range = self.page.query_range(start_point, end_point) sel_range = self.page.query_range(start_point, end_point)