Rust FFI “Adventure” with the HarfBuzz Text Shaping Engine
Rust FFI, or Foreign Function Interface, is a mechanism that allows Rust code to interact with programs written in other languages, such as C and C-compatible languages. The HarfBuzz text shaping engine is written in C++.
In this article, we describe how to build the HarfBuzz text shaping engine on both Windows and Ubuntu. We then demonstrate how to write simple Rust code that calls the hb_version_string() function from HarfBuzz using FFI.
🦀 Index of the Complete Series.
![]() |
|---|
| Rust FFI “Adventure” with the HarfBuzz Text Shaping Engine |
I should mention that this article wasn’t the one I originally set out to write. I had been working on font subsetting, and midway through, I decided to change the location of the HarfBuzz build on Windows. That build was successfully completed around 02 October 2025, and my Rust font subsetting code was working on both Windows and Ubuntu. However, after making the change on 20 October 2025, the Rust subsetting code stopped working on Windows, and I haven’t been able to get it running again yet.
Since I’m not yet familiar with HarfBuzz and FFI, I decided to scale back the scope of my documentation and focus instead on the build process and a simple example of FFI. This article isn’t a tutorial—it’s a personal record of my exploration into the subject.
💡 Most of the steps were guided by ChatGPT and Copilot. It took several iterations to get everything installed correctly and to get the Rust code working. AI-generated instructions sometimes assume the presence of certain tools or prior knowledge, which I didn’t always have. I also had to do my own reading to fully understand and apply the instructions.
❶ Windows: Install Build Tools and the HarfBuzz Text Shaping Engine
There are several viable methods for building HarfBuzz on Windows, all of which require external build tools. Depending on the features we want our built version of HarfBuzz to support, we may also need to build or install additional libraries. I encountered several failed attempts during this process—it can be quite intimidating. That’s the main reason I decided to document it in this article.
⓵ Install the Meson Build Tool
Follow the instructions in the Installing Meson and Ninja with the MSI installer section. Download meson-1.9.1-64.msi from the Meson releases page on GitHub.
I installed it to C:\Program Files\Meson\. During installation, you may see warnings about updating files used by other applications, and a system restart may be required. The installation path is added to the system environment automatically, and the meson and ninja command-line tools become globally available.
⓶ Install LLVM
LLVM is a collection of compiler and toolchain technologies. For more information, see
The LLVM Compiler Infrastructure and the
Wikipedia article on LLVM.
We’ll be using the bindgen crate to generate Rust bindings for the HarfBuzz
library. This crate requires the LLVM toolchain to function.
Download the latest installer from the
LLVM GitHub releases page, and run it. I downloaded
LLVM-21.1.2-win64.exe. During installation, select the option
Add LLVM to the system PATH for all users, and accept the default
installation directory C:\Program Files\LLVM.
💡 Important 💥 The following steps rely on an up-to-date installation of
Visual Studio 2022 on Windows. I haven’t tested this with later versions of
Visual Studio, so I can’t comment on compatibility beyond 2022.
To access the 64-bit Developer Command Prompt for Visual Studio 2022 (or for your installed version),
open a Windows Terminal session, launch a new Command Prompt, and run the following batch file:
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
You should see output similar to the following—note the x64 architecture:
**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.14.18
** Copyright (c) 2025 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
Running where cl.exe, where cmake.exe, and
where dumpbin.exe should report the following paths:
-
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\cl.exe -
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe -
C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\bin\Hostx64\x64\dumpbin.exe
While the paths for cl.exe and dumpbin.exe clearly indicate x64,
the directory for cmake.exe does not. To confirm that cmake.exe is also a 64-bit CLI,
run the following command:
dumpbin /headers "C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe" | find "machine"
The output should include 8664 machine (x64), confirming that it is a 64-bit executable.
vcpkg is a C/C++ dependency manager from Microsoft that supports all platforms, build systems, and workflows. References:
I followed this Microsoft article:
Tutorial: Install and use packages with CMake
to install vcpkg to C:\PF\. After changing to the C:\PF\ directory, run the following command:
git clone https://github.com/microsoft/vcpkg.git
This repository is approximately 118MB in size. Then:
cd vcpkg
The current working directory should now be C:\PF\vcpkg\. Then run:
.\bootstrap-vcpkg.bat
⓸ Install the FreeType Text Rendering Engine
References:
This is a system-wide installation. Change to the C:\PF\vcpkg\ directory, then run the following command:
vcpkg install freetype:x64-windows
The installation may take several minutes and produce a long output. I was able to install it successfully on the first attempt. After installation, you should see the following:
-
C:\PF\vcpkg\installed\x64-windows\include\freetype -
C:\PF\vcpkg\installed\x64-windows\lib\freetype.lib -
C:\PF\vcpkg\buildtrees\freetype\x64-windows-rel\freetype.dll
To confirm that freetype.dll is a 64-bit binary, run:
dumpbin /headers "C:\PF\vcpkg\buildtrees\freetype\x64-windows-rel\freetype.dll" | find "machine"
The output should include 8664 machine (x64).
⓹ Install pkgconf into the vcpkg Tree
Reference: https://vcpkgx.com/details.html?package=pkgconf. The Meson build tool requires this utility to locate the FreeType library.
Change to the C:\PF\vcpkg\ directory, then run the following command:
vcpkg install pkgconf:x64-windows
The installation should complete quickly. Afterward, you should see the following:
-
C:\PF\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe: This is a 64-bit CLI. You can verify its architecture usingdumpbin. -
C:\PF\vcpkg\installed\x64-windows\lib\pkgconfig\freetype2.pc
To confirm that pkgconf can detect the FreeType version, run the following command:
set PKG_CONFIG_PATH=C:\PF\vcpkg\installed\x64-windows\lib\pkgconfig
C:\PF\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe --modversion freetype2
This should report 26.2.20.
References:
Change to the C:\PF\vcpkg\ directory and run the following command:
vcpkg install glib:x64-windows
On my Intel(R) Core(TM) i7-7700 CPU @ 3.6GHz with 16GB RAM, 4 cores, and 8 logical processors,
the installation took 44 minutes to complete.
While still in the C:\PF\vcpkg\ directory, run the following command:
dir /s *glib*.dll
I counted 8 copies across 8 different subdirectories. I’m not entirely sure which one is used by the HarfBuzz build (install) process.
⓻ Build (Install) the HarfBuzz Text Shaping Engine
References:
Change to the C:\PF\ directory and clone the HarfBuzz repository:
git clone https://github.com/harfbuzz/harfbuzz.git
The repository is approximately 206MB. I encountered several failed builds—meaning the Rust code didn’t compile correctly. The command sequence below is what worked for me:
cd harfbuzz
Now in the C:\PF\harfbuzz\ directory, run the following commands:
set PKG_CONFIG=C:\PF\vcpkg\installed\x64-windows\tools\pkgconf\pkgconf.exe
set PKG_CONFIG_PATH=C:\PF\vcpkg\installed\x64-windows\lib\pkgconfig
meson setup build --wipe --buildtype=release -Ddefault_library=shared -Dfreetype=enabled -Dglib=enabled -Dutilities=enabled
meson compile -C build
The last part of the meson setup build... command:
harfbuzz 12.1.0
Directories
prefix : c:/
bindir : bin
libdir : lib
includedir : include
datadir : share
cmakepackagedir : lib/cmake
Unicode callbacks (you want at least one)
Builtin : YES
Glib : YES
ICU : NO
Font callbacks (the more the merrier)
Builtin : YES
FreeType : YES
Fontations : NO
Dependencies used for command-line utilities
Cairo : NO
Chafa : NO
Additional shapers
Graphite2 : NO
WebAssembly (experimental): NO
Platform / other shapers (not normally needed)
CoreText : NO
DirectWrite : NO
GDI/Uniscribe : NO
HarfRust : NO
kbts : NO
Other features
Utilities : YES
Documentation : NO
GObject bindings : YES
Cairo integration : NO
Introspection : NO
Experimental APIs : NO
Testing
Tests : YES
Benchmark : NO
User defined options
buildtype : release
default_library : shared
freetype : enabled
glib : enabled
utilities : enabled
Found ninja-1.12.1 at "C:\Program Files\Meson\ninja.EXE"
The output of the meson compile -C build command:
INFO: autodetecting backend as ninja
INFO: calculating backend command to run: "C:\Program Files\Meson\ninja.EXE" -C C:/PF/harfbuzz/build
ninja: Entering directory `C:/PF/harfbuzz/build'
[62/258] Linking target src/harfbuzz.dll
Creating library src\harfbuzz.lib and object src\harfbuzz.exp
[83/258] Linking target src/harfbuzz-gobject.dll
Creating library src\harfbuzz-gobject.lib and object src\harfbuzz-gobject.exp
[142/258] Compiling C object test/api/test-style.exe.p/test-style.c.obj
../test/api/test-style.c(175): warning C4305: 'function': truncation from 'double' to 'float'
[163/258] Compiling C object test/api/test-unicode.exe.p/test-unicode.c.obj
../test/api/test-unicode.c(614): warning C4068: unknown pragma 'GCC'
../test/api/test-unicode.c(615): warning C4068: unknown pragma 'GCC'
../test/api/test-unicode.c(623): warning C4068: unknown pragma 'GCC'
[166/258] Compiling C++ object test/fuzzing/hb-set-fuzzer.exe.p/hb-set-fuzzer.cc.obj
../test/fuzzing/hb-set-fuzzer.cc(49): warning C4068: unknown pragma 'GCC'
../test/fuzzing/hb-set-fuzzer.cc(50): warning C4068: unknown pragma 'GCC'
../test/fuzzing/hb-set-fuzzer.cc(52): warning C4068: unknown pragma 'GCC'
[207/258] Linking target src/harfbuzz-subset.dll
Creating library src\harfbuzz-subset.lib and object src\harfbuzz-subset.exp
[258/258] Linking target test/threads/hb-subset-threads.exe
After a successful build, you should see the following files:
-
C:\PF\harfbuzz\build\src\harfbuzz.dll -
C:\PF\harfbuzz\build\src\harfbuzz-subset.dll -
C:\PF\harfbuzz\build\util\: This directory should contain:-
hb-info.exe -
hb-info.exe.p -
hb-shape.exe -
hb-shape.exe.p -
hb-subset.exe -
hb-subset.exe.p
-
To verify that these binaries are 64-bit, run the following commands. Each should report 8664 machine (x64):
dumpbin /headers C:\PF\harfbuzz\build\src\harfbuzz.dll | find "machine"
dumpbin /headers C:\PF\harfbuzz\build\src\harfbuzz-subset.dll | find "machine"
dumpbin /headers C:\PF\harfbuzz\build\util\hb-info.exe | find "machine"
dumpbin /headers C:\PF\harfbuzz\build\util\hb-shape.exe | find "machine"
dumpbin /headers C:\PF\harfbuzz\build\util\hb-subset.exe | find "machine"
❷ Ubuntu: Install Build Tools and the HarfBuzz Text Shaping Engine
On Ubuntu, the process is surprisingly straightforward. The instructions provided by AI didn’t work out of the box. Based on those suggestions, I did some research and was able to get everything built and installed successfully on the fourth or fifth attempt.
I followed the instructions in this build guide. Make sure to update your Ubuntu system if it’s outdated. Then install the required build tools with:
$ sudo apt-get install meson pkg-config ragel gtk-doc-tools gcc g++ libfreetype6-dev libglib2.0-dev libcairo2-dev
From the user home directory /home/behai, clone the HarfBuzz repository:
$ git clone https://github.com/harfbuzz/harfbuzz
Change into the newly created harfbuzz/ subdirectory:
$ cd harfbuzz/
Now in /home/behai/harfbuzz, build HarfBuzz with:
$ meson build && ninja -C build && meson test -C build
At completion, meson reports the locations of the build log and the test log.
To install the build artifacts:
$ sudo meson install -C build
The output is lengthy, and at completion, it also reports the location of the installation log.
And that’s about it! You should now have the following:
-
/usr/local/lib/x86_64-linux-gnu/, which contains:-
libharfbuzz.so: Equivalent to Windowsharfbuzz.dll. -
libharfbuzz-subset.so: Equivalent to Windowsharfbuzz-subset.dll. - Other related shared libraries.
-
-
/usr/local/bin/, which includes:-
hb-info -
hb-shape -
hb-subset -
hb-view
-
❸ A Rust FFI That Calls HarfBuzz’s
hb_version_string() Function
💡 Please note: on both Windows and Ubuntu, I’m running Rust version
rustc 1.90.0 (1159e78c4 2025-09-14).
Since this is just an exploratory one-off project, I don’t plan to extend it further in future development—although future code will likely follow the same approach. I’ve placed the project under the harfbuzz_ffi directory. The structure is simple:
.
├── Cargo.toml
├── build.rs
└── src
└── main.rs
Let’s list the contents of each file and walk through them.
Content of Cargo.toml:
[package]
name = "harfbuzz_ffi"
version = "0.1.0"
edition = "2024"
[dependencies]
libc = "0.2.177"
[build-dependencies]
bindgen = "0.72.1"
Both libc and bindgen make Rust FFI possible. References:
- libc crate
- The bindgen User Guide
- Bindgen Tutorial, which links to this full tutorial. I followed it in full.
- bindgen crate
Content of build.rs:
use std::env;
use std::path::PathBuf;
use std::process::Command;
fn main() {
// println!("cargo:rustc-link-lib=dylib=harfbuzz");
// println!("cargo:rustc-link-lib=static=harfbuzz");
println!("cargo:rustc-link-lib=harfbuzz");
// C:\PF\harfbuzz\src\[*.h, hb.h]
// /usr/local/include/harfbuzz/[*.h, hb.h]
// C:\PF\harfbuzz\build\src\harfbuzz.lib
// /usr/local/lib/x86_64-linux-gnu/libharfbuzz.so.0
let (hb_include, lib_search) = if cfg!(target_os = "windows") {
(
"C:/PF/harfbuzz/src/",
"C:/PF/harfbuzz/build/src/",
)
} else {
(
"/usr/local/include/harfbuzz/",
"/usr/local/lib/x86_64-linux-gnu/",
)
};
println!("cargo:rustc-link-search=native={}", lib_search);
// Windows vs Linux include paths
let mut clang_args = Vec::new();
// Try to auto-detect GCC’s include path on Unix-like systems
// Handle the problem:
// /usr/local/include/harfbuzz/hb-common.h:68:10: fatal error: 'stddef.h' file not found
if cfg!(not(target_os = "windows")) {
if let Ok(output) = Command::new("gcc").arg("-print-file-name=include").output() {
if output.status.success() {
if let Ok(path) = String::from_utf8(output.stdout) {
let trimmed = path.trim();
clang_args.push(format!("-I{}", trimmed));
}
}
}
}
// Add the HarfBuzz include and common system paths
clang_args.push(format!("-I{}", hb_include));
clang_args.push("-I/usr/include".to_string());
let bindings = bindgen::Builder::default()
.clang_args(clang_args)
.header(format!("{}/hb.h", hb_include))
.generate()
.expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
Most of the build.rs logic is discussed in the tutorial above.
Note that the .clang_args() call in
bindgen::Builder::default().clang_args([...]) requires
LLVM.
Content of src/main.rs:
use std::ffi::CStr;
#[allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
mod hb {
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[link(name = "harfbuzz")]
unsafe extern "C" {
pub fn hb_version_string() -> *const std::os::raw::c_char;
}
fn main() {
unsafe {
let version_ptr = hb::hb_version_string();
let version = CStr::from_ptr(version_ptr);
println!("HarfBuzz version: {}", version.to_str().unwrap());
}
}
src/main.rs is also covered in the tutorial mentioned above.
Please note that cargo build produces over 1,000 warnings, mostly related to mixed-case constant names. These can be suppressed, but it’s not necessary for this project.
On Ubuntu, all required libraries are globally recognized. On Windows, I haven’t added the paths for harfbuzz.dll and its dependencies to the PATH environment variable. So in each new Windows terminal session, I run the following once:
set PATH=C:\PF\harfbuzz\build\src\;%PATH%
set PATH=C:\PF\vcpkg\installed\x64-windows\bin\;%PATH%
After that, cargo run works as expected. The screenshots below show the result of running the Rust FFI code on both Windows and Ubuntu:
![]() |
|---|
| Windows |
![]() |
|---|
| Ubuntu |
For me, it’s back to troubleshooting font subsetting on Windows. If I can’t resolve the issue, I’ll settle for calling the hb-subset CLI on both Windows and Ubuntu. Even though everything works fine on Ubuntu, I’d prefer a single, unified code path. I’ll document that process as well.
Thanks for reading! I hope this post helps others on a similar journey.
As always—stay curious, stay safe 🦊
✿✿✿
Feature image sources:
- https://www.omgubuntu.co.uk/2024/03/ubuntu-24-04-wallpaper
- https://in.pinterest.com/pin/337277459600111737/
- https://www.rust-lang.org/
- https://www.pngitem.com/download/ibmJoR_rust-language-hd-png-download/
- https://ur.wikipedia.org/wiki/%D9%81%D8%A7%D8%A6%D9%84:HarfBuzz.svg


