Coverage for src/rtflite/strwidth.py: 100%

29 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-03 15:40 +0000

1import importlib.resources as pkg_resources 

2from typing import Literal, overload 

3 

4from PIL import ImageFont 

5 

6import rtflite.fonts 

7 

8Unit = Literal["in", "mm", "px"] 

9 

10FontName = Literal[ 

11 "Times New Roman", 

12 "Times New Roman Greek", 

13 "Arial Greek", 

14 "Arial", 

15 "Helvetica", 

16 "Calibri", 

17 "Georgia", 

18 "Cambria", 

19 "Courier New", 

20 "Symbol", 

21] 

22 

23_FONT_PATHS = { 

24 "Times New Roman": "liberation/LiberationSerif-Regular.ttf", 

25 "Times New Roman Greek": "liberation/LiberationSerif-Regular.ttf", 

26 "Arial Greek": "liberation/LiberationSans-Regular.ttf", 

27 "Arial": "liberation/LiberationSans-Regular.ttf", 

28 "Helvetica": "liberation/LiberationSans-Regular.ttf", 

29 "Calibri": "cros/Carlito-Regular.ttf", 

30 "Georgia": "cros/Gelasio-Regular.ttf", 

31 "Cambria": "cros/Caladea-Regular.ttf", 

32 "Courier New": "liberation/LiberationMono-Regular.ttf", 

33 "Symbol": "liberation/LiberationSerif-Regular.ttf", 

34} 

35 

36# Add type number type 

37FontNumber = Literal[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 

38 

39# Define bidirectional mappings 

40RTF_FONT_NUMBERS = { 

41 "Times New Roman": 1, 

42 "Times New Roman Greek": 2, 

43 "Arial Greek": 3, 

44 "Arial": 4, 

45 "Helvetica": 5, 

46 "Calibri": 6, 

47 "Georgia": 7, 

48 "Cambria": 8, 

49 "Courier New": 9, 

50 "Symbol": 10, 

51} 

52 

53RTF_FONT_NAMES: dict[int, FontName] = {v: k for k, v in RTF_FONT_NUMBERS.items()} 

54 

55 

56@overload 

57def get_string_width( 

58 text: str, 

59 font_name: FontName = "Times New Roman", 

60 font_size: int = 12, 

61 unit: Unit = "in", 

62 dpi: float = 72.0, 

63) -> float: ... 

64 

65 

66@overload 

67def get_string_width( 

68 text: str, 

69 font_type: FontNumber, 

70 font_size: int = 12, 

71 unit: Unit = "in", 

72 dpi: float = 72.0, 

73) -> float: ... 

74 

75 

76def get_string_width( 

77 text: str, 

78 font: FontName | FontNumber = "Times New Roman", 

79 font_size: int = 12, 

80 unit: Unit = "in", 

81 dpi: float = 72.0, 

82) -> float: 

83 """ 

84 Calculate the width of a string for a given font and size. 

85 Uses metric-compatible fonts that match the metrics of common proprietary fonts. 

86 

87 Args: 

88 text: The string to measure. 

89 font: RTF font name or RTF font number (1-10). 

90 font_size: Font size in points. 

91 unit: Unit to return the width in. 

92 dpi: Dots per inch for unit conversion. 

93 

94 Returns: 

95 Width of the string in the specified unit. 

96 

97 Raises: 

98 ValueError: If an unsupported font name/number or unit is provided. 

99 """ 

100 # Convert font type number to name if needed 

101 if isinstance(font, int): 

102 if font not in RTF_FONT_NAMES: 

103 raise ValueError(f"Unsupported font number: {font}") 

104 font_name = RTF_FONT_NAMES[font] 

105 else: 

106 font_name = font 

107 

108 if font_name not in _FONT_PATHS: 

109 raise ValueError(f"Unsupported font name: {font_name}") 

110 

111 font_path = pkg_resources.files(rtflite.fonts) / _FONT_PATHS[font_name] 

112 font = ImageFont.truetype(str(font_path), size=font_size) 

113 width_px = font.getlength(text) 

114 

115 conversions = { 

116 "px": lambda x: x, 

117 "in": lambda x: x / dpi, 

118 "mm": lambda x: (x / dpi) * 25.4, 

119 } 

120 

121 if unit not in conversions: 

122 raise ValueError(f"Unsupported unit: {unit}") 

123 

124 return conversions[unit](width_px)