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

30 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-25 22:35 +0000

1import importlib.resources as pkg_resources 

2import math 

3from collections.abc import Mapping 

4from typing import Literal 

5 

6from PIL import ImageFont 

7from PIL import __version__ as PILLOW_VERSION 

8 

9import rtflite.fonts 

10 

11from .fonts_mapping import FontMapping, FontName, FontNumber 

12 

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

14 

15_FONT_PATHS = FontMapping.get_font_paths() 

16 

17RTF_FONT_NUMBERS = FontMapping.get_font_name_to_number_mapping() 

18RTF_FONT_NAMES: Mapping[int, FontName] = FontMapping.get_font_number_to_name_mapping() 

19 

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

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

22_PILLOW_REQUIRES_INT_SIZE = _PILLOW_VERSION < (10, 0) 

23 

24 

25def get_string_width( 

26 text: str, 

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

28 font_size: float = 12, 

29 unit: Unit = "in", 

30 dpi: float = 72.0, 

31) -> float: 

32 """ 

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

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

35 

36 Args: 

37 text: The string to measure. 

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

39 font_size: Font size in points. 

40 unit: Unit to return the width in. 

41 dpi: Dots per inch for unit conversion. 

42 

43 Returns: 

44 Width of the string in the specified unit. 

45 

46 Raises: 

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

48 """ 

49 # Convert font type number to name if needed 

50 if isinstance(font, int): 

51 if font not in RTF_FONT_NAMES: 

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

53 font_name = RTF_FONT_NAMES[font] 

54 else: 

55 font_name = font 

56 

57 if font_name not in _FONT_PATHS: 

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

59 

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

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

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

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

64 width_px = font_obj.getlength(text) 

65 

66 conversions = { 

67 "px": lambda x: x, 

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

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

70 } 

71 

72 if unit not in conversions: 

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

74 

75 return conversions[unit](width_px)