uharfbuzz: HarfBuzz Python Bindings
uharfbuzz provides streamlined Cython bindings for the HarfBuzz shaping engine. It includes a minimal HarfBuzz source distribution by default, but can optionally link against a system-provided HarfBuzz. The library maintains an active development pace with frequent updates, often aligning with new HarfBuzz C library releases.
Warnings
- gotcha uharfbuzz is a thin wrapper over the HarfBuzz C API. This means it exposes a low-level interface and requires manual handling of concepts like blobs, faces, fonts, and buffers, which can involve significant boilerplate compared to higher-level text rendering libraries.
- breaking When linking against a system-provided HarfBuzz library using `USE_SYSTEM_LIBS=1`, the system HarfBuzz must be built with experimental API support enabled. If not, compilation or runtime errors related to missing APIs may occur.
- gotcha Although the underlying HarfBuzz C library aims for API/ABI stability, frequent updates to HarfBuzz itself (which `uharfbuzz` tracks) can sometimes introduce new features or subtle behavioral changes in the low-level API that `uharfbuzz` exposes. While not always 'breaking' in a strict sense, new HarfBuzz versions might require adjustments to client code to leverage new capabilities or handle altered defaults.
Install
-
pip install uharfbuzz -
USE_SYSTEM_LIBS=1 pip install uharfbuzz --no-binary :uharfbuzz:
Imports
- harfbuzz
import uharfbuzz as hb
- Blob
hb.Blob
- Face
hb.Face
- Font
hb.Font
- Buffer
hb.Buffer
- shape
hb.shape(font, buf, features)
Quickstart
import uharfbuzz as hb
import os
# For demonstration, we'll assume a font file exists at this path.
# In a real application, you'd load a font from a known path.
# We'll use a placeholder and note to provide a real font path.
FONT_PATH = os.environ.get('UHARFBUZZ_FONT_PATH', 'path/to/your/font.ttf')
TEXT_TO_SHAPE = "Hello, World!"
if not os.path.exists(FONT_PATH):
print(f"Warning: Font file not found at {FONT_PATH}. Quickstart will use a dummy font file placeholder.\n" \
"Please replace 'path/to/your/font.ttf' with an actual font path or set UHARFBUZZ_FONT_PATH env var.")
# Create a dummy file for the example to run without error, though it won't shape properly.
with open(FONT_PATH, 'wb') as f:
f.write(b'dummy font data')
# 1. Load the font blob
blob = hb.Blob.from_file_path(FONT_PATH)
# 2. Create a font face and font instance
face = hb.Face(blob)
font = hb.Font(face)
# 3. Create a buffer and add text
buf = hb.Buffer()
buf.add_str(TEXT_TO_SHAPE)
buf.guess_segment_properties()
# 4. Define shaping features (optional)
features = {"kern": True, "liga": True}
# 5. Shape the text
hb.shape(font, buf, features)
# 6. Access shaped glyph information
infos = buf.glyph_infos
positions = buf.glyph_positions
print(f"Shaped '{TEXT_TO_SHAPE}' with {len(infos)} glyphs:")
for info, pos in zip(infos, positions):
# gid = info.codepoint (this is actually glyph ID after shaping)
gid = info.codepoint
cluster = info.cluster
x_advance = pos.x_advance
x_offset = pos.x_offset
y_offset = pos.y_offset
glyph_name = font.glyph_to_string(gid)
print(f" {glyph_name} (gid={gid}, cluster={cluster}) @ advance={x_advance}, offset=({x_offset},{y_offset})")
# Clean up the dummy font file if it was created
if os.environ.get('UHARFBUZZ_FONT_PATH') is None and os.path.exists(FONT_PATH) and b'dummy font data' in open(FONT_PATH, 'rb').read():
os.remove(FONT_PATH)