Coverage for pyWebLayout/core/query.py: 94%

33 statements  

« prev     ^ index     » next       coverage.py v7.11.2, created at 2025-11-12 12:02 +0000

1""" 

2Query system for pixel-to-content mapping. 

3 

4This module provides data structures for querying rendered content, 

5enabling interactive features like link clicking, word definition lookup, 

6and text selection. 

7""" 

8 

9from __future__ import annotations 

10from dataclasses import dataclass 

11from typing import Optional, Tuple, List, Any, TYPE_CHECKING 

12 

13if TYPE_CHECKING: 13 ↛ 14line 13 didn't jump to line 14 because the condition on line 13 was never true

14 from pyWebLayout.core.base import Queriable 

15 

16 

17@dataclass 

18class QueryResult: 

19 """ 

20 Result of querying a point on a rendered page. 

21 

22 This encapsulates all information about what was found at a pixel location, 

23 including geometry, content, and interaction capabilities. 

24 """ 

25 # What was found 

26 object: 'Queriable' # The object at this point 

27 object_type: str # "link", "text", "image", "button", "word", "empty" 

28 

29 # Geometry 

30 bounds: Tuple[int, int, int, int] # (x, y, width, height) in page coordinates 

31 

32 # Content (for text/words) 

33 text: Optional[str] = None 

34 word_index: Optional[int] = None # Index in abstract document structure 

35 block_index: Optional[int] = None # Block index in document 

36 

37 # Interaction (for links/buttons) 

38 is_interactive: bool = False 

39 link_target: Optional[str] = None # URL or internal reference 

40 callback: Optional[Any] = None # Interaction callback 

41 

42 # Hierarchy (for debugging/traversal) 

43 parent_line: Optional[Any] = None 

44 parent_page: Optional[Any] = None 

45 

46 def to_dict(self) -> dict: 

47 """Convert to dictionary for serialization""" 

48 return { 

49 'object_type': self.object_type, 

50 'bounds': self.bounds, 

51 'text': self.text, 

52 'is_interactive': self.is_interactive, 

53 'link_target': self.link_target, 

54 'word_index': self.word_index, 

55 'block_index': self.block_index 

56 } 

57 

58 

59@dataclass 

60class SelectionRange: 

61 """ 

62 Represents a range of selected text between two points. 

63 """ 

64 start_point: Tuple[int, int] 

65 end_point: Tuple[int, int] 

66 results: List[QueryResult] # All query results in the range 

67 

68 @property 

69 def text(self) -> str: 

70 """Get concatenated text from all results""" 

71 return " ".join(r.text for r in self.results if r.text) 

72 

73 @property 

74 def bounds_list(self) -> List[Tuple[int, int, int, int]]: 

75 """Get list of all bounding boxes for highlighting""" 

76 return [r.bounds for r in self.results] 

77 

78 def to_dict(self) -> dict: 

79 """Convert to dictionary for serialization""" 

80 return { 

81 'start': self.start_point, 

82 'end': self.end_point, 

83 'text': self.text, 

84 'word_count': len(self.results), 

85 'bounds': self.bounds_list 

86 }