---
title: "Shape complex scripts"
url: https://recursiveai.org/onefont/cookbook/complex-scripts/
description: "Shape Hindi, other Indic scripts and Thai in C# with onefont: syllables, conjuncts, reordered vowel signs and stacked marks, and which character each…"
version: "1.3.0"
---

# Shape complex scripts

Shape Hindi, other Indic scripts and Thai in C# with onefont: syllables, conjuncts, reordered vowel signs and stacked marks, and which character each glyph came from.

In complex scripts the glyphs on screen aren’t one per character in typing order. A vowel sign can be drawn before its consonant, two consonants can merge into a conjunct, and marks stack above and below. onefont chooses a script shaper from the text and runs the font’s features in the order each script needs.

```csharp
using OneFont;

FontFace hindi = FontFace.Load(File.ReadAllBytes("NotoSansDevanagari-Regular.ttf"));
FontFace thai = FontFace.Load(File.ReadAllBytes("NotoSansThai-Regular.ttf"));
var run = new ShapedRun();

Show("हिंदी", hindi);    // "Hindi": the vowel sign ि is drawn before its consonant
Show("क्षत्र", hindi);   // conjuncts: क्ष and त्र
Show("ि", hindi);        // a vowel sign with no consonant gets a dotted circle
Show("น้ำ", thai);       // "water": SARA AM splits, NIKHAHIT moves before the tone mark

void Show(string text, FontFace face)
{
    TextShaping.ShapeRun(text, face, run);
    Console.WriteLine($"{text}: {run.ClusterCount} cluster(s), {run.GlyphCount} glyphs");
    // Origins: for each glyph, the first code point it stands for. Glyphs are in drawing order.
    for (int i = 0; i < run.GlyphCount; i++)
    {
        ShapedGlyph g = run.Glyphs[i];
        int cp = run.Origins[i].CodePoint;
        string from = $"U+{cp:X4} {char.ConvertFromUtf32(cp)}";
        Console.WriteLine($"  glyph {g.GlyphId,4}  from {from}  advance {g.XAdvance,3}  offset {g.XOffset},{g.YOffset}");
    }
}
```

```text
हिंदी: 2 cluster(s), 5 glyphs
  glyph  619  from U+093F ि  advance 259  offset 0,0
  glyph   61  from U+0939 ह  advance 531  offset 0,0
  glyph  652  from U+0902 ं  advance   0  offset 0,0
  glyph   42  from U+0926 द  advance 520  offset 0,0
  glyph  653  from U+0940 ी  advance 259  offset 0,0
क्षत्र: 2 cluster(s), 2 glyphs
  glyph  179  from U+0915 क  advance 712  offset 0,0
  glyph  269  from U+0924 त  advance 552  offset 0,0
ि: 1 cluster(s), 2 glyphs
  glyph   67  from U+093F ि  advance 259  offset 0,0
  glyph  134  from U+25CC ◌  advance 510  offset 0,0
น้ำ: 1 cluster(s), 4 glyphs
  glyph   71  from U+0E19 น  advance 613  offset 0,0
  glyph   59  from U+0E4D ํ  advance   0  offset 0,0
  glyph   49  from U+0E49 ้  advance   0  offset -29,0
  glyph   86  from U+0E32 า  advance 406  offset 0,0
```

- **Reordering.** In “हिंदी” the vowel sign ि (U+093F) is typed after ह but drawn before it. `ShapedRun.Origins` gives the first code point each glyph stands for, so you can see the move.
- **Conjuncts.** “क्षत्र” is six characters and two glyphs: the font’s क्ष and त्र conjuncts. Each glyph’s origin is the first consonant of its conjunct.
- **Broken clusters.** A vowel sign with no consonant gets a dotted circle (U+25CC) as its base, and the pre-base sign moves in front of the circle as it would in front of a consonant.
- **Thai.** In “น้ำ” the SARA AM (U+0E33) is split into NIKHAHIT and SARA AA, and the nikhahit moves in front of the tone mark. The font’s positioning gives the tone mark an advance of 0 and an x offset of −29 units.
- **Clusters.** A syllable’s characters share one cluster. Place each cluster as a whole, and don’t break a line or move a caret inside one.

The shapers:

| Shaper | Scripts |
|---|---|
| Arabic | Arabic, Syriac, N’Ko |
| Indic | Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, Kannada, Malayalam |
| Universal Shaping Engine | Khmer, Myanmar, Sinhala, Tibetan, Balinese, Javanese and the other scripts of the USE list |
| Thai and Lao | Thai, Lao |

The Indic shaper uses a font’s v2 script tags (`dev2`, `bng2`…) first and the v1 tags otherwise. A syllable is cut after 48 glyphs, and runs over 4,096 glyphs are shaped in pieces cut at syllable boundaries, so cost stays linear. Some outputs differ from SixLabors.Fonts on purpose, case by case: the list is in [Shaping, bidi and segmentation](https://recursiveai.org/onefont/docs/shaping/).

Not done in 1.3.0: Syriac Alaph forms (`fin2`, `fin3`, `med2` selection), canonical reordering of combining marks, Apple `morx` and `kerx` tables, and vertical layout.

A longer guide, compared with HarfBuzzSharp: [Text shaping in C#](https://recursiveai.org/onefont/articles/text-shaping-csharp/).
