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
« 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
6from PIL import ImageFont
7from PIL import __version__ as PILLOW_VERSION
9import rtflite.fonts
11from .fonts_mapping import FontMapping, FontName, FontNumber
13Unit = Literal["in", "mm", "px"]
15_FONT_PATHS = FontMapping.get_font_paths()
17RTF_FONT_NUMBERS = FontMapping.get_font_name_to_number_mapping()
18RTF_FONT_NAMES: Mapping[int, FontName] = FontMapping.get_font_number_to_name_mapping()
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)
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.
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.
43 Returns:
44 Width of the string in the specified unit.
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
57 if font_name not in _FONT_PATHS:
58 raise ValueError(f"Unsupported font name: {font_name}")
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)
66 conversions = {
67 "px": lambda x: x,
68 "in": lambda x: x / dpi,
69 "mm": lambda x: (x / dpi) * 25.4,
70 }
72 if unit not in conversions:
73 raise ValueError(f"Unsupported unit: {unit}")
75 return conversions[unit](width_px)