|
1 | 1 | from __future__ import annotations |
2 | 2 |
|
3 | 3 | import collections |
| 4 | +import functools |
4 | 5 | import os |
5 | 6 | import sys |
6 | 7 | import warnings |
@@ -121,6 +122,68 @@ def get_supported_codecs() -> list[str]: |
121 | 122 | return [f for f in codecs if check_codec(f)] |
122 | 123 |
|
123 | 124 |
|
| 125 | +@functools.cache |
| 126 | +def _enabled_freetype_features() -> frozenset[str]: |
| 127 | + """Get the compiled-in FreeType features that affect Pillow.""" |
| 128 | + if not check_module("freetype2"): |
| 129 | + return frozenset() |
| 130 | + |
| 131 | + from PIL import _imagingft |
| 132 | + |
| 133 | + return frozenset(getattr(_imagingft, "freetype2_features", "").split()) |
| 134 | + |
| 135 | + |
| 136 | +@functools.cache |
| 137 | +def _freetype_reads_gpos_kerning() -> bool | None: |
| 138 | + """ |
| 139 | + Ask the loaded FreeType whether it can read kerning out of ``GPOS``. |
| 140 | +
|
| 141 | + Pillow's built-in font has pair kerning in ``GPOS`` and no legacy ``kern`` |
| 142 | + table, so FreeType only reports kerning data for it when it was built with |
| 143 | + ``TT_CONFIG_OPTION_GPOS_KERNING``. |
| 144 | +
|
| 145 | + :returns: ``True`` or ``False``, or ``None`` if the probe could not be run. |
| 146 | + """ |
| 147 | + if not check_module("freetype2"): |
| 148 | + return None |
| 149 | + |
| 150 | + from . import ImageFont |
| 151 | + |
| 152 | + try: |
| 153 | + font = ImageFont.load_default() |
| 154 | + return bool(font.font.has_kerning) |
| 155 | + except (AttributeError, OSError): |
| 156 | + return None |
| 157 | + |
| 158 | + |
| 159 | +def check_freetype_feature(feature: str) -> bool: |
| 160 | + """ |
| 161 | + Checks whether a FreeType feature that affects Pillow is enabled. |
| 162 | +
|
| 163 | + :param feature: The feature to check for. |
| 164 | + :returns: ``True`` if enabled, ``False`` otherwise. |
| 165 | + """ |
| 166 | + if feature == "gpos_kerning": |
| 167 | + # This requires loading the embedded font. |
| 168 | + probed = _freetype_reads_gpos_kerning() |
| 169 | + if probed is not None: |
| 170 | + return probed |
| 171 | + |
| 172 | + return feature in _enabled_freetype_features() |
| 173 | + |
| 174 | + |
| 175 | +def get_supported_freetype_features() -> list[str]: |
| 176 | + """ |
| 177 | + :returns: A sorted list of all enabled FreeType build features. |
| 178 | + """ |
| 179 | + supported = set(_enabled_freetype_features()) |
| 180 | + if check_freetype_feature("gpos_kerning"): |
| 181 | + supported.add("gpos_kerning") |
| 182 | + else: |
| 183 | + supported.discard("gpos_kerning") |
| 184 | + return sorted(supported) |
| 185 | + |
| 186 | + |
124 | 187 | features: dict[str, tuple[str, str, str | None]] = { |
125 | 188 | "raqm": ("PIL._imagingft", "HAVE_RAQM", "raqm_version"), |
126 | 189 | "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), |
@@ -226,6 +289,19 @@ def get_supported() -> list[str]: |
226 | 289 | return ret |
227 | 290 |
|
228 | 291 |
|
| 292 | +def _print_freetype_build(out: IO[str]) -> None: |
| 293 | + """Report how the loaded FreeType was built, for bug reports.""" |
| 294 | + from PIL import _imagingft |
| 295 | + |
| 296 | + interpreter_version = getattr(_imagingft, "freetype2_interpreter_version", None) |
| 297 | + if interpreter_version is not None: |
| 298 | + print(f" TrueType interpreter version {interpreter_version}", file=out) |
| 299 | + |
| 300 | + enabled = get_supported_freetype_features() |
| 301 | + if enabled: |
| 302 | + print(" FreeType built with", ", ".join(enabled), file=out) |
| 303 | + |
| 304 | + |
229 | 305 | def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None: |
230 | 306 | """ |
231 | 307 | Prints information about this installation of Pillow. |
@@ -309,6 +385,8 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None: |
309 | 385 | print("---", feature, "support ok,", t, v, file=out) |
310 | 386 | else: |
311 | 387 | print("---", feature, "support ok", file=out) |
| 388 | + if name == "freetype2": |
| 389 | + _print_freetype_build(out) |
312 | 390 | else: |
313 | 391 | print("***", feature, "support not installed", file=out) |
314 | 392 | print("-" * 68, file=out) |
|
0 commit comments