Skip to content
Switch branches/tags
Go to file

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


HarfPy is a Python 3 binding for
[HarfBuzz]( It is meant to be used in
conjunction with my Python wrappers for other major parts of the Linux typography stack:

* Qahirah ([GitLab](,
  [GitHub]( -- my binding for the
  [Cairo]( graphics library
* python_freetype ([GitLab](,
  [GitHub]( -- my binding for
* PyBidi ([GitLab](,
  [GitHub]( -- my binding for

The basic steps in doing a piece of high-quality text rendering on Linux are:

* Make sure all text is encoded in Unicode. (This should go without saying...)
* Use FriBidi to reorder a string of unicode-encoded text from logical (reading) order to
  visual (rendering) order. (This step can be skipped in cases where you do not mix text
  of different directions.)
* Use FreeType to load a suitable font.
* Use HarfBuzz to choose and lay out suitable glyphs for the specified text, controlled by
  appropriate features selected from the specified font.
* Use Cairo to draw the actual glyphs, as laid out by HarfBuzz, with that font.


To install HarfPy on your system, type

    python3 install

This will install the Python module named “`harfbuzz`”.

Basic Usage

All functionality comes from the one module:

    import harfbuzz

You will typically also want more direct access to constants and other definitions within
the `HARFBUZZ` class:

    from harfbuzz import \

Or you may want to abbreviate the names, for convenience:

    import harfbuzz as hb
    from harfbuzz import \
        HARFBUZZ as HB

Names in the module omit the “`hb_`” and "`HB_`” prefixes. For example, whereas in C you
might write

    hb_shape(font, buffer);

in Python this becomes (with the above import abbreviations):

    hb.shape(font, buffer)

And a constant like `HB_TAG_NONE` can be accessed (again with the above import
abbreviations) as `HB.TAG_NONE`.

Operations on specific HarfBuzz objects further lose the prefix parts of their names
associated with those objects, because they become methods defined within those objects.
Thus, instead of, in C,

    buf = hb_buffer_create();

in Python this becomes

    buf = hb.Buffer.create()





Hello-HarfBuzz Example

The HarfBuzz manual includes [this example](
on how to get started. Let’s recreate that using HarfPy.

    import sys
    import qahirah as qah
    import harfbuzz as hb

We will need to load a font with FreeType. To use FreeType, you have to create a `Library`
instance. But Qahirah already has one, so it’s simpler to just use that:

    ft = qah.get_ft_lib()

(This also lets you use the same font instances for drawing the text.) Let’s make up a
simple line of right-to-left text:

    text_line = "\u0627\u0644\u0642\u0627\u0647\u0631\u0629" # “al-qahirah”

Create a `Buffer`:

    buf = hb.Buffer.create()

Put the text into the buffer:


Let HarfBuzz figure out the run properties:


Load a suitable font:

    ft_face = ft.find_face("Scheherazade")

We need to set a font size, otherwise we won’t get any sensible metrics. A size of 1 will
do for measurement purposes:

    ft_face.set_char_size(size = 1, resolution = qah.base_dpi)

HarfBuzz wants us to wrap this in one of its own `Font` objects:

    hb_font = hb.Font.ft_create(ft_face)

Do the glyph selection and layout, based on the default font features:

    hb.shape(hb_font, buf)

And let’s see what we got:

    sys.stdout.write \
            "buf.glyph_infos = %s\n glyph_positions = %s\n"
            (buf.glyph_infos, buf.glyph_positions)

Using OpenType Font Features

An important part of OpenType is the [font features
registry]( This defines
standard “features” that a specific font may implement: for example, auto-kerning,
alternate glyphs for different number styles, small caps, old-style ligatures and so on.

To specify the feature settings, you just need to pass an extra argument to the `hb.shape`
method call:

    hb.shape(hb_font, buf, «features»)

where «features» is a sequence of `Feature` settings to be applied. For example, to turn
on old-style ligatures over the entire line of text in the buffer:

    hb.shape(hb_font, buf, [hb.Feature(HB.TAG(b'hlig'), value = 1)])

Rendering Bidirectional Text

How would you render a line containing a mixture of left-to-right and right-to-left text?
First of all, note that HarfBuzz can only do shaping on one run at a time. So you have to
break up your line into segments, put them through HarfBuzz shaping one at a time, and
collect all the glyphs together. Here is an example to walk you through the necessary
steps, using all the abovementioned Python modules. First, the necessary imports:

    import sys
    import os
    import math
    # import freetype2 as freetype # use Qahirah instance
    import qahirah as qah
    from qahirah import \
        CAIRO, \
        Colour, \
        Glyph, \
    ft = qah.get_ft_lib()
    import fribidi as fb
    from fribidi import \
        FRIBIDI as FB
    import harfbuzz as hb

Next, the text we are going to render:

    book_title = \
            "\u0627\u0644\u0643\u062a\u0627\u0628" # “al-kitab”
            " \u0627\u0644\u0645\u062e\u062a\u0635\u0631" # “al-mukhtasar”
            " \u0641\u064a" # “fi”
            " \u062d\u0633\u0627\u0628" # “hisab”
            " \u0627\u0644\u062c\u0628\u0631" # “al-jabr”
            " \u0648\u0627\u0644\u0645\u0642\u0627\u0628\u0644\u0629" # “wa’l-muqabala”
    author = \
            "\u0645\u062d\u0645\u062f" # “Muhammad’
            " \u0628\u0646" # “ibn”
            " \u0645\u0648\u0633\u0649" # “Musa”
            " \u0627\u0644\u062e\u0648\u0627\u0631\u0632\u0645\u06cc" # “al-Khwarizmi”
    text_line = \
            "The book “%(title)s” gives us the word “algebra”,"
            " its author’s name %(author)s gives us “algorithm”."
            {"author" : author, "title" : book_title}
    base_rtl = False # overall direction of line

Initial font and buffer setup:

    text_size = 36
    buf = hb.Buffer.create()
    ft_face = ft.find_face("DejaVu Sans")
    ft_face.set_char_size(size = text_size, resolution = qah.base_dpi)
    hb_font = hb.Font.ft_create(ft_face)

Use FriBidi to reorder the line and define the embedding levels, using the
`ReorderLine` convenience wrapper class provided by PyBidi:

    reordered = fribidi.ReorderLine \
        text_line = text_line,
        base_dir = (FB.PAR_LTR, FB.PAR_RTL)[base_rtl],

Next, collect the glyphs for each segment/run into a list of Qahirah `Glyphs` objects.
Note the `Buffer.get_glyphs` convenience method provided by HarfPy:

    glyphs = []
    glyph_pos = Vector(0, 0)
    for substr, pos1, pos2, level in reordered.each_embedding_run(vis_order = False) :
        hb.shape(hb_font, buf)
        new_glyphs, end_glyph_pos = buf.get_glyphs(glyph_pos)
        glyph_pos = end_glyph_pos
    #end for

Do the Cairo font setup, and figure out how big an `ImageSurface` we need:

    qah_face = qah.FontFace.create_for_ft_face(ft_face)
    glyph_extents = \
    figure_bounds = math.ceil(glyph_extents.bounds)
    pix = qah.ImageSurface.create \
        format = CAIRO.FORMAT_RGB24,
        dimensions = figure_bounds.dimensions

Actually render the glyphs into a Cairo context:

        .translate(- figure_bounds.topleft)

And finally, save the `ImageSurface` contents to a PNG file for viewing:

    pix.flush().write_to_png("%s.png" % os.path.basename(sys.argv[0]))

For more complete example scripts, see my harfpy_examples
[GitHub]( repo.

More Info About HarfBuzz

The abovementioned [manual]( has a lot of useful
reference info. Some other introductory info may be found at the
[HarfBuzz Wiki](

Lawrence D'Oliveiro <>
2020 August 22


Python wrapper for HarfBuzz





No packages published