ravi.joshi53's picture

Rendering Hindi and Urdu Language Text in OpenTK

Hi,
I am looking for a library which can render the non English text based on the provided font file. Following are the two approaches, which I tried -

(1) QuickFont library. It doesn't work well for non English alphabets, even after providing the font file. Following is the snippet of code-

QFont testFont;
 
string text = "दिल्ली का मुख्यमंत्री पद";
string hindiCharset = "अआइईउऊऋएऐओऔकखगघङचछजझञटठडढणतथदधनपफबभमयरलवशषसह१२३४५६७८९०";
 
public Program() : base(800, 600, GraphicsMode.Default, "Hindi Text")
{
    this.WindowBorder = WindowBorder.Fixed;
}
 
protected override void OnLoad(EventArgs e)
{
    base.OnLoad(e);
    QFontBuilderConfiguration fontBuilderConfiguration = new QFontBuilderConfiguration(false);
    fontBuilderConfiguration.charSet = fontBuilderConfiguration.charSet + hindiCharset;			
    testFont = new QFont("Fonts/Hindi/shusha.ttf", 20, fontBuilderConfiguration);
}
 
protected override void OnRenderFrame(FrameEventArgs e)
{
    base.OnRenderFrame(e);
    GL.Clear(ClearBufferMask.ColorBufferBit);
    GL.MatrixMode(MatrixMode.Modelview);
    GL.LoadIdentity();
    testFont.Print(text, new Vector2(50, 20));
    SwapBuffers();
}

I have used popular Shusha font in this code.
Please see the attached screenshot of the output, in which all the Hindi vowels are missing.

(2) TextRenderer which uses System.Drawing API. The results are better but no control over font style (i.e. I can't draw cursive Hindi font).

Please see the attached screenshot.

Inline Images
Output using QuickFont library
Output using TextRenderer

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Frassle's picture

If using System.Drawing you should be able to change the font to support things like italics, bold etc. See the MSDN documentation for the Font class.

ravi.joshi53's picture
Frassle wrote:

If using System.Drawing you should be able to change the font to support things like italics, bold etc

I am curious to know, can I get this kind of Hindi font using System.Drawing ? The font file can be downloaded from here.

Frassle's picture

If it's a GDI supported font you should be able to add it to a PrivateFontCollection. I don't know anymore about fonts, so you'll have to read the documentation for how to get it working.

ravi.joshi53's picture

I want to Keep TextRenderer in a separate class to make it more portable and usable. Below is the code-

TextRenderer.cs

using System;
using System.Drawing;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
 
namespace Text
{
    /// <summary>
    /// Uses System.Drawing for 2d text rendering.
    /// </summary>
    public class TextRenderer : IDisposable
    {
        Bitmap bmp;
        Graphics gfx;
        int texture;
        Rectangle dirty_region;
        bool disposed;
 
        #region Constructors
 
        /// <summary>
        /// Constructs a new instance.
        /// </summary>
        /// <param name="width">The width of the backing store in pixels.</param>
        /// <param name="height">The height of the backing store in pixels.</param>
        public TextRenderer(OpenTK.GameWindow Window)
        {
            if (Window.Width <= 0)
                throw new ArgumentOutOfRangeException("Width");
            if (Window.Height <= 0)
                throw new ArgumentOutOfRangeException("Height");
            if (GraphicsContext.CurrentContext == null)
                throw new InvalidOperationException("No GraphicsContext is current on the calling thread.");
 
            bmp = new Bitmap(Window.Width, Window.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            gfx = Graphics.FromImage(bmp);
            gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
 
            texture = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, texture);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Window.Width, Window.Height, 0,
                PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
        }
 
        #endregion
 
        #region Public Members
 
        /// <summary>
        /// Clears the backing store to the specified color.
        /// </summary>
        /// <param name="color">A <see cref="System.Drawing.Color"/>.</param>
        public void Clear(Color color)
        {
            gfx.Clear(color);
            dirty_region = new Rectangle(0, 0, bmp.Width, bmp.Height);
        }
 
        /// <summary>
        /// Draws the specified string to the backing store.
        /// </summary>
        /// <param name="text">The <see cref="System.String"/> to draw.</param>
        /// <param name="font">The <see cref="System.Drawing.Font"/> that will be used.</param>
        /// <param name="brush">The <see cref="System.Drawing.Brush"/> that will be used.</param>
        /// <param name="point">The location of the text on the backing store, in 2d pixel coordinates.
        /// The origin (0, 0) lies at the top-left corner of the backing store.</param>
        void DrawString(string text, Font font, Brush brush, PointF point)
        {
            gfx.DrawString(text, font, brush, point);
 
            SizeF size = gfx.MeasureString(text, font);
            dirty_region = Rectangle.Round(RectangleF.Union(dirty_region, new RectangleF(point, size)));
            dirty_region = Rectangle.Intersect(dirty_region, new Rectangle(0, 0, bmp.Width, bmp.Height));
        }
 
        /// <summary>
        /// Gets a <see cref="System.Int32"/> that represents an OpenGL 2d texture handle.
        /// The texture contains a copy of the backing store. Bind this texture to TextureTarget.Texture2d
        /// in order to render the drawn text on screen.
        /// </summary>
        public int Texture
        {
            get
            {
                UploadBitmap();
                return texture;
            }
        }
 
        #endregion
 
        #region Private Members
 
        // Uploads the dirty regions of the backing store to the OpenGL texture.
        void UploadBitmap()
        {
            if (dirty_region != RectangleF.Empty)
            {
                System.Drawing.Imaging.BitmapData data = bmp.LockBits(dirty_region,
                    System.Drawing.Imaging.ImageLockMode.ReadOnly,
                    System.Drawing.Imaging.PixelFormat.Format32bppArgb);
 
                GL.BindTexture(TextureTarget.Texture2D, texture);
                GL.TexSubImage2D(TextureTarget.Texture2D, 0,
                    dirty_region.X, dirty_region.Y, dirty_region.Width, dirty_region.Height,
                    PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0);
 
                bmp.UnlockBits(data);
 
                dirty_region = Rectangle.Empty;
            }
        }
 
        #endregion
 
        #region IDisposable Members
 
        void Dispose(bool manual)
        {
            if (!disposed)
            {
                if (manual)
                {
                    bmp.Dispose();
                    gfx.Dispose();
                    if (GraphicsContext.CurrentContext != null)
                        GL.DeleteTexture(texture);
                }
 
                disposed = true;
            }
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        ~TextRenderer()
        {
            Console.WriteLine("[Warning] Resource leaked: {0}.", typeof(TextRenderer));
        }
 
        #endregion
 
        void Begin()
        {
            GL.Enable(EnableCap.Texture2D);
            GL.BindTexture(TextureTarget.Texture2D, Texture);
            GL.Begin(PrimitiveType.Quads);
        }
 
        void End()
        {
            GL.TexCoord2(0.0f, 1.0f); GL.Vertex2(-1f, -1f);
            GL.TexCoord2(1.0f, 1.0f); GL.Vertex2(1f, -1f);
            GL.TexCoord2(1.0f, 0.0f); GL.Vertex2(1f, 1f);
            GL.TexCoord2(0.0f, 0.0f); GL.Vertex2(-1f, 1f);
            GL.End();
            GL.Disable(EnableCap.Texture2D);
        }
 
        public void Print(string text, Font font, Brush brush, PointF point)
        {
            DrawString(text, font, brush, point);
            Begin();
            End();
        }
    }
}

This is the main class-

class TextRendering : GameWindow
{
    TextRenderer renderer;
    Font serif = new Font(FontFamily.GenericSerif, 12);
    Font sans = new Font(FontFamily.GenericSansSerif, 12);
    Font mono = new Font(FontFamily.GenericMonospace, 12);
 
    public TextRendering() : base(800,600) {}
 
    protected override void OnLoad(EventArgs e)
    {
        renderer = new TextRenderer(this);
    }
 
    protected override void OnUnload(EventArgs e)
    {
        renderer.Dispose();
    }
 
    protected override void OnResize(EventArgs e)
    {
        GL.Viewport(ClientRectangle);
        GL.MatrixMode(MatrixMode.Projection);
        GL.LoadIdentity();
        GL.Ortho(-1.0, 1.0, -1.0, 1.0, 0.0, 4.0);
    }
 
    protected override void OnRenderFrame(FrameEventArgs e)
    {
        GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
        GL.MatrixMode(MatrixMode.Modelview);
        GL.LoadIdentity();
 
        PointF position = PointF.Empty;
 
        renderer.Clear(Color.MidnightBlue);
        renderer.Print(DateTime.Now + serif, serif, Brushes.White, position);
        SwapBuffers();
    }
 
    public static void Main()
    {
        using (TextRendering example = new TextRendering())
        {
            example.Run();
        }
    }
}

I want to include following two functionality in the TextRenderer class-

  1. Method to change the Orthographic Projection (If I change Orthographic projection in main class, the render doesn't work :( )
  2. Eliminate method public void Clear(Color color), Since I want TextRenderer to work based on any background color, which depends on main class

Please guide.