---
title: "Shape text in C# (ligatures, kerning, Arabic, Hindi)"
url: https://recursiveai.org/onefont/cookbook/shape-text/
description: "Shape text in C# with OpenType GSUB and GPOS: ligatures, kerning, Arabic joining and Hindi conjuncts, as glyph ids, advances and clusters from TextShaping.ShapeRun. Pure .NET, no native HarfBuzz."
version: "1.3.0"
---

# Shape text in C# (ligatures, kerning, Arabic, Hindi)

Load the font and call `TextShaping.ShapeRun(text, face, rightToLeft)`. It returns the shaped clusters: the font’s glyphs after its ligatures, joining forms and conjuncts, with kerned advances and mark offsets, each tied to the characters it came from. onefont is written in C#, with no native library to ship.

```shell
dotnet new console -o Shaping && cd Shaping
dotnet add package RecursiveAI.OneFont --version 1.3.0
NOTO=https://github.com/notofonts/notofonts.github.io/raw/main/fonts
curl -LO $NOTO/NotoSerif/hinted/ttf/NotoSerif-Regular.ttf
curl -LO $NOTO/NotoSansArabic/hinted/ttf/NotoSansArabic-Regular.ttf
curl -LO $NOTO/NotoSansDevanagari/hinted/ttf/NotoSansDevanagari-Regular.ttf
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using OneFont;

Shape("office AV", "NotoSerif-Regular.ttf", rightToLeft: false);
// "hello world" in Arabic, then "namaste" in Hindi
Shape("مرحبا بالعالم", "NotoSansArabic-Regular.ttf", rightToLeft: true);
Shape("नमस्ते", "NotoSansDevanagari-Regular.ttf", rightToLeft: false);

static void Shape(string text, string fontPath, bool rightToLeft)
{
    FontFace face = FontFace.Load(File.ReadAllBytes(fontPath));
    var clusters = TextShaping.ShapeRun(text, face, rightToLeft);

    // Clusters come in logical (typing) order; Start indexes the string.
    // Advances and offsets are in font units: multiply by size / UnitsPerEm.
    var glyphs = clusters.SelectMany(c =>
        c.Glyphs.ToArray().Select(g => $"{g.GlyphId}@{c.Start}+{g.XAdvance}"));
    float px = clusters.Sum(c => c.Advance) * 16f / face.UnitsPerEm;
    Console.WriteLine($"{text} ({px:F1} px at 16 px):");
    Console.WriteLine($"  {string.Join(" ", glyphs)}");
}
```

```text
office AV (66.7 px at 16 px):
  82@0+577 2146@1+1002 70@4+492 72@5+535 3@6+260 36@7+625 57@8+675
مرحبا بالعالم (77.1 px at 16 px):
  79@0+525 31@1+356 27@2+589 16@3+343 316@3+0 9@4+291 3@5+260 19@6+269 316@6+0 9@7+291 72@8+260 48@9+521 9@10+291 72@11+260 77@12+562
नमस्ते (33.8 px at 16 px):
  75@0+555 80@1+598 256@2+389 71@2+570 40@2+0
```

Each item is `glyph@cluster+advance`, in font units.

## What happened

- **Ligature and kerning.** In “office AV”, glyph 2146 is the “ffi” ligature: one glyph and one cluster for characters 1 to 3. The A’s advance is 625 rather than its normal 705 because the font kerns A and V.
- **Arabic.** The letters take their joined initial, medial and final forms, and this font adds a mark glyph (316) with no advance to each ب, in the same cluster as its letter. The clusters come back in logical order, the order of the string: to display a right-to-left run, draw its clusters from last to first.
- **Hindi.** In “नमस्ते” the last four characters are one cluster: स becomes a half form (glyph 256) joined to ते. A cluster is the smallest unit you can place, select or break.
- **The script shaper** is picked from the text: Arabic (with Syriac and N’Ko), the Indic scripts, the Universal Shaping Engine scripts such as Khmer, Myanmar and Tibetan, and Thai and Lao. You pass only the direction.

## What a shaper leaves to you

A shaper handles one run: one font, one direction, one script. Splitting a paragraph by direction, choosing a font per character, breaking lines and drawing happen around it. onefont has the pieces next to the shaper: [right-to-left and mixed text](https://recursiveai.org/onefont/cookbook/right-to-left-text/), [font fallback](https://recursiveai.org/onefont/cookbook/font-supports-character/), [measuring](https://recursiveai.org/onefont/cookbook/measure-text-width/) and [wrapping](https://recursiveai.org/onefont/cookbook/measure-and-wrap-text/). Features such as small caps and fractions are in [OpenType features](https://recursiveai.org/onefont/cookbook/opentype-features/).

## Limits

- **It shapes; it doesn’t draw.** Turn glyphs into outlines with [text to SVG path](https://recursiveai.org/onefont/cookbook/text-to-svg-path/), or draw on images with [OneImage.Text](https://recursiveai.org/oneimage/docs/text/).
- **Not read in 1.3.0:** WOFF and WOFF2 files, color glyphs, hinting, vertical text, and Apple `morx` and `kerx` tables. A variable font shapes as its default instance.

The same samples with HarfBuzzSharp, side by side, and how to choose between the two, are in [Text shaping in C#: HarfBuzzSharp or a pure .NET shaper](https://recursiveai.org/onefont/articles/text-shaping-csharp/).
