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.
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}"); } }
हिंदी: 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.Originsgives 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.
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#.