HarfBuzz Ruby Gem Version CI

Ruby bindings for HarfBuzz text shaping engine.

Features

  • Complete HarfBuzz API bindings using Ruby-FFI
  • Two-layer architecture: low-level HarfBuzz::C (1:1 C binding) + high-level Ruby-idiomatic layer
  • Safe memory management with borrow/own distinction
  • Thread-safe immutable font objects
  • Cross-platform support (macOS, Linux)
  • RBS type signatures included

Requirements

  • Ruby 3.1+
  • HarfBuzz C library installed on your system
  • Supported platforms:
    • macOS (Homebrew)
    • Ubuntu / Debian
    • Fedora / RHEL
    • Alpine

Installation

Install the HarfBuzz C library:

# macOS
brew install harfbuzz

# Ubuntu / Debian
sudo apt-get install libharfbuzz-dev libharfbuzz-subset0

# Fedora / RHEL
sudo dnf install harfbuzz-devel

# Alpine
apk add harfbuzz-dev

Add to your Gemfile:

gem 'harfbuzz-ruby'

Then run:

bundle install

Or install directly:

gem install harfbuzz-ruby

Custom Library Path

To use a custom build of HarfBuzz:

export HARFBUZZ_LIB_PATH=/path/to/libharfbuzz.dylib

Usage

Basic Setup

require "harfbuzz"

result = HarfBuzz.shape_text("Hello, World!", font_path: "/path/to/font.ttf")
result.each do |info, pos|
  puts "glyph=#{info.glyph_id} x_advance=#{pos.x_advance}"
end
puts "Total advance: #{result.total_advance.first}"

Text Shaping Example

require "harfbuzz"

# Load font
blob = HarfBuzz::Blob.from_file!("/path/to/font.ttf")
face = HarfBuzz::Face.new(blob, 0)
font = HarfBuzz::Font.new(face)

# Create buffer and add text
text = "Hello, World!"
buffer = HarfBuzz::Buffer.new
buffer.add_utf8(text)
buffer.guess_segment_properties

# Shape
HarfBuzz.shape(font, buffer)

# Read results without allocating GlyphInfo/GlyphPosition wrappers
last_cluster = nil
buffer.each_glyph_with_clusters do |glyph_id, cluster, next_cluster, x_advance, _y_advance, x_offset, _y_offset|
  source = cluster == last_cluster ? "" : text.byteslice(cluster...(next_cluster || text.bytesize))
  puts "glyph_id=#{glyph_id} source=#{source.inspect} cluster=#{cluster} next_cluster=#{next_cluster} " \
       "x_advance=#{x_advance} x_offset=#{x_offset}"
  last_cluster = cluster
end

Buffer#each_glyph yields the original six values. Buffer#each_glyph_with_clusters adds next_cluster as the third value; it is the next distinct cluster in glyph order, or nil for the last cluster.

Shaping with Features

features = [
  HarfBuzz::Feature.from_string("liga"),    # enable ligatures
  HarfBuzz::Feature.from_string("-kern"),   # disable kerning
]
HarfBuzz.shape(font, buffer, features)

# Or use a hash
features = HarfBuzz::Feature.from_hash(liga: true, kern: false, smcp: 2)
HarfBuzz.shape(font, buffer, features)

Variable Fonts

font = HarfBuzz::Font.new(face)

# Set weight=700 and width=75
variations = [
  HarfBuzz::Variation.from_string("wght=700"),
  HarfBuzz::Variation.from_string("wdth=75"),
]
font.variations = variations

Glyph Outline Extraction

draw = HarfBuzz::DrawFuncs.new

path_commands = []
draw.on_move_to      { |x, y, _|        path_commands << "M #{x},#{y}" }
draw.on_line_to      { |x, y, _|        path_commands << "L #{x},#{y}" }
draw.on_quadratic_to { |cx, cy, x, y, _| path_commands << "Q #{cx},#{cy} #{x},#{y}" }
draw.on_cubic_to     { |c1x, c1y, c2x, c2y, x, y, _|
                        path_commands << "C #{c1x},#{c1y} #{c2x},#{c2y} #{x},#{y}" }
draw.on_close_path   { |_|              path_commands << "Z" }
draw.make_immutable!

font.draw_glyph(36, draw)
puts path_commands.join(" ")

Font Subsetting

input = HarfBuzz::Subset::Input.new

unicode_set = input.unicode_set
"Hello".each_codepoint { |cp| unicode_set.add(cp) }

subsetted_face = HarfBuzz::Subset.subset(face, input)
puts "Subsetted glyph count: #{subsetted_face.glyph_count}"

Examples

See the examples/ directory for more complete examples:

  • basic_shaping.rb - Basic text shaping
  • glyph_outlines.rb - Glyph outline extraction
  • opentype_features.rb - OpenType feature queries
  • variable_fonts.rb - Variable font axis manipulation
  • render_svg.rb - Render shaped text to SVG and open in browser
  • render_waterfall.rb - Variable font weight waterfall (HTML + SVG)

Run an example:

bundle exec ruby examples/basic_shaping.rb

Rendering examples generate SVG/HTML and open them in the default browser:

bundle exec ruby examples/render_svg.rb "Hello, HarfBuzz!"
bundle exec ruby examples/render_waterfall.rb /path/to/variable_font.ttf

API Overview

Core Objects

Class Description
HarfBuzz::Blob Binary data container for font files
HarfBuzz::Face Font face (typeface + index)
HarfBuzz::Font Font instance with metrics and scale
HarfBuzz::Buffer Text buffer for shaping input/output

Shaping

Class Description
HarfBuzz::Feature OpenType feature toggle
HarfBuzz::Variation Variable font axis setting
HarfBuzz::ShapePlan Reusable shaping plan
HarfBuzz::ShapingResult Shaped text result with glyph info
HarfBuzz::GlyphInfo Glyph ID and cluster info
HarfBuzz::GlyphPosition Glyph advance and offset

Drawing & Color

Class Description
HarfBuzz::DrawFuncs Glyph outline extraction callbacks
HarfBuzz::PaintFuncs Color font paint callbacks

OpenType

Class Description
HarfBuzz::OT::Layout GSUB/GPOS script, language, and feature queries
HarfBuzz::OT::Var Variable font axis and instance queries
HarfBuzz::OT::Metrics Font metrics (x-height, cap-height, etc.)
HarfBuzz::OT::Name Name table access
HarfBuzz::OT::Color Color palette and COLR queries
HarfBuzz::OT::Math Math table queries
HarfBuzz::OT::Meta Metadata queries

Subsetting

Class Description
HarfBuzz::Subset Font subsetting API
HarfBuzz::Set Integer set for codepoints/glyphs
HarfBuzz::Map Integer-to-integer mapping

Development

git clone https://github.com/ydah/harfbuzz.git
cd harfbuzz
bundle install
bundle exec rake spec

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/ydah/harfbuzz.

License

The gem is available as open source under the terms of the MIT License.

References