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
« 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
5from PIL import ImageFont
6from PIL import __version__ as PILLOW_VERSION
8import rtflite.fonts
10from .fonts_mapping import FontMapping, FontName, FontNumber
12Unit = Literal["in", "mm", "px"]
14_FONT_PATHS = FontMapping.get_font_paths()
16RTF_FONT_NUMBERS = FontMapping.get_font_name_to_number_mapping()
17RTF_FONT_NAMES: dict[int, FontName] = FontMapping.get_font_number_to_name_mapping()
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)
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.
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.
42 Returns:
43 Width of the string in the specified unit.
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
56 if font_name not in _FONT_PATHS:
57 raise ValueError(f"Unsupported font name: {font_name}")
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)
65 conversions = {
66 "px": lambda x: x,
67 "in": lambda x: x / dpi,
68 "mm": lambda x: (x / dpi) * 25.4,
69 }
71 if unit not in conversions:
72 raise ValueError(f"Unsupported unit: {unit}")
74 return conversions[unit](width_px)