HarfBuzz GPU text rendering for raylib.
rl_harfbuzz_textlib shapes and renders UTF-8 text with HarfBuzz. It is aimed at projects that want support for ligatures, Arabic and RTL shaping, as well as GPU font rasterization to make arbitrary-size font drawing crispy and beautiful.
- C API in
include/rl_harfbuzz_textlib/rl_harfbuzz_textlib.h - Thin C++ wrapper in
include/rl_harfbuzz_textlib/rl_harfbuzz_textlib.hpp - Core implementation in
src/rl_harfbuzz_textlib.cpp - Bundled default font
DejaVuSans.ttf - Three examples: a C API example, C++ API example, and an interactive demo
- raylib with the OpenGL 3.3 backend
- local or subproject CMake usage
- HarfBuzz build from
harfbuzz-world.cc
Requirements:
- CMake 4.0 or newer
- A C++17-capable compiler
- raylib available in your environment, or
RLHB_FETCH_RAYLIB=ON
Typical build for this repository:
cmake -S . -B build -DRLHB_BUILD_EXAMPLES=ON -DRLHB_FETCH_RAYLIB=ON
cmake --build buildIf your environment already provides raylib or raylib::raylib, leave RLHB_FETCH_RAYLIB off.
CMake options:
RLHB_BUILD_EXAMPLES=ON|OFFbuilds the example programs. Default:OFFRLHB_FETCH_RAYLIB=ON|OFFfetches raylib if it is not already available. Default:OFFRLHB_BUNDLE_DEFAULT_FONT=ON|OFFembeds the bundled fallback font. Default:ONRLHB_HARFBUZZ_SOURCE_DIR=/path/to/harfbuzzuses your own HarfBuzz source tree instead of the pinned download
This project is set up for subproject use:
add_subdirectory(rl_harfbuzz_textlib)
target_link_libraries(my_app PRIVATE rl_harfbuzz_textlib::rl_harfbuzz_textlib)The library vendors HarfBuzz internally and links against raylib.
Core objects:
rlhbRenderer: owns GPU state and the glyph atlasrlhbFont: a font associated with a rendererrlhbTextRun: a reusable shaped run
Main C functions:
rlhbCreateRenderer()/rlhbDestroyRenderer()rlhbLoadFontFromFile()/rlhbLoadDefaultFont()/rlhbUnloadFont()rlhbGetDefaultShapeOptions()rlhbDrawText()/rlhbDrawTextN()rlhbShapeText()/rlhbShapeTextN()/rlhbDrawTextRun()rlhbMeasureText()/rlhbMeasureTextN()rlhbBeginDraw()/rlhbEndDraw()
Main C++ wrapper types:
rlhb::Rendererrlhb::Fontrlhb::TextRun
One-shot draw example in C:
InitWindow(800, 450, "rlhb example");
rlhbRenderer *renderer = rlhbCreateRenderer();
rlhbFont *font = rlhbLoadDefaultFont(renderer);
rlhbShapeOptions options = rlhbGetDefaultShapeOptions();
options.fontSize = 32.0f;
options.align = rlhbTextAlignCenter;
BeginDrawing();
ClearBackground(RAYWHITE);
rlhbDrawText(renderer,
font,
"Hello world",
(Vector2){400.0f, 220.0f},
DARKGRAY,
&options);
EndDrawing();If the text stays the same across frames, shape it once with rlhbShapeText*() and reuse the result with rlhbDrawTextRun().
The one-shot draw helpers work on their own, but if you are drawing several text items in one frame, you should prefer to begin and end an explicit draw scope:
BeginDrawing();
ClearBackground(RAYWHITE);
rlhbBeginDraw(renderer);
rlhbDrawText(renderer, font, "First", (Vector2){40.0f, 120.0f}, DARKGRAY, &options);
rlhbDrawText(renderer, font, "Second", (Vector2){40.0f, 170.0f}, DARKGRAY, &options);
rlhbEndDraw(renderer);
EndDrawing();The C++ wrapper exposes this as renderer.beginDraw() and renderer.endDraw().
If the call is not wrapped in rlhbBeginDraw and rlhbEndDraw, it will set up and tear down the state for every single call.
Ending the scope returns the renderer to a default raylib state when it ends. It does not restore any previously active custom shader or other user-managed OpenGL state.
- Text input is UTF-8
fontSizeis in pixelsdirectiondefaults torlhbDirectionAutoaligncontrols howbaseline.xanchors the run- Draw positions use a typographic baseline, not a top-left origin
rlhbRunMetricsexposes width, ascent, descent, bounds, and glyph count
If you want top-left style layout, measure first and place the baseline at top + ascent.
When RLHB_BUNDLE_DEFAULT_FONT=ON, the library embeds DejaVuSans.ttf and exposes it through rlhbLoadDefaultFont.
Relevant files:
assets/fonts/DejaVuSans.ttfassets/fonts/LICENSE.DejaVu.txtsrc/rlhb_default_font_data.h
By default the library logs through raylib's TraceLog.
You can override that with:
- C API:
rlhbSetLogCallback() - C++ API:
rlhb::setLogCallback()
Passing nullptr restores the default logger.
The repository currently ships three examples:
rlhb_c_api_example: a tiny C example in the spirit of raylib'score_basic_window.crlhb_cpp_api_example: the same kind of minimal example using the C++ wrapperrlhb_type_lab_example: an interactive demo for typing, shaping, zooming, panning, and trying different fonts
Type lab controls:
- Type to replace the sample text
- Click preset, direction, and alignment chips
Backspacedeletes one UTF-8 codepointCtrl+Vpastes text from the clipboard- Mouse wheel zooms
- Left mouse drag pans
- Drop a font file onto the window to switch fonts
UpandDownchange font sizeF4cycles direction,F5resets the view, andF6cycles alignment
Current limitations include:
- no full bidi run segmentation or reordering for mixed-direction text
- no paragraph layout or line wrapping
- no automatic font fallback chain
- fixed-size glyph atlas that cannot expand at runtime
- OpenGL 3.3 backend only (untested on web)
These will be addressed in future.
This code was written using GPT-5.4 and then reviewed and tested by @Peter0x44. If that offends your moral framework, this library is not for you.
| Project | Link | Notes |
|---|---|---|
| @raysan5 | raysan5/raylib | Raylib library |
| @JeffM2501 | raylib-extras/rTextLib | rTextLib API design inspiration |
| @behdad | harfbuzz/harfbuzz | HarfBuzz maintainer |