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

29 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 16:35 +0000

1import importlib.resources as pkg_resources 

2import math 

3from typing import Literal 

4 

5from PIL import ImageFont 

6from PIL import __version__ as PILLOW_VERSION 

7 

8import rtflite.fonts 

9 

10from .fonts_mapping import FontMapping, FontName, FontNumber 

11 

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

13 

14_FONT_PATHS = FontMapping.get_font_paths() 

15 

16RTF_FONT_NUMBERS = FontMapping.get_font_name_to_number_mapping() 

17RTF_FONT_NAMES: dict[int, FontName] = FontMapping.get_font_number_to_name_mapping() 

18 

19# Check Pillow version to determine if size parameter should be int or float 

20_PILLOW_VERSION = tuple(map(int, PILLOW_VERSION.split(".")[:2])) 

21_PILLOW_REQUIRES_INT_SIZE = _PILLOW_VERSION < (10, 0) 

22 

23 

24def get_string_width( 

25 text: str, 

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

27 font_size: float = 12, 

28 unit: Unit = "in", 

29 dpi: float = 72.0, 

30) -> float: 

31 """ 

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

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

34 

35 Args: 

36 text: The string to measure. 

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

38 font_size: Font size in points. 

39 unit: Unit to return the width in. 

40 dpi: Dots per inch for unit conversion. 

41 

42 Returns: 

43 Width of the string in the specified unit. 

44 

45 Raises: 

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

47 """ 

48 # Convert font type number to name if needed 

49 if isinstance(font, int): 

50 if font not in RTF_FONT_NAMES: 

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

52 font_name = RTF_FONT_NAMES[font] 

53 else: 

54 font_name = font 

55 

56 if font_name not in _FONT_PATHS: 

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

58 

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

60 # Convert size to int for Pillow < 10.0.0 compatibility (use ceiling for conservative pagination) 

61 size_param = int(math.ceil(font_size)) if _PILLOW_REQUIRES_INT_SIZE else font_size 

62 font_obj = ImageFont.truetype(str(font_path), size=size_param) 

63 width_px = font_obj.getlength(text) 

64 

65 conversions = { 

66 "px": lambda x: x, 

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

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

69 } 

70 

71 if unit not in conversions: 

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

73 

74 return conversions[unit](width_px)