Using native GDI for text rendering in C#

To complete my previous post on text rendering in .NET I will present here the pitfalls I encountered migrating HTML Renderer to native GDI text rendering. The final result is ready to use NativeTextRenderer class with simple managed API that can be used for native GDI text rendering.

HDC

The first thing required for GDI native rendering is a handle to device context of the graphics object, HDC is used to set the font, color, clip range and text draw/measure GDI functions. HDC is received via Graphics.GetHdc and must be released via Graphics.ReleaseHdc, during the scope of a the method pair you can make calls only to GDI functions, calls to GDI+ methods of the Graphicsobject will fail with an ObjectBudy error.
To handle this the NativeTextRenderer calls GetHdc in the constructor and implements IDisposable interface to call ReleaseHdc in the dispose method those allowing the use of the using pattern.

Background mode

By default created HDC will use opaque background mode when rendering text, filling the rectangle with the current background color.
Because this is not the default behavior of managed draw methods the NativeTextRenderer, using SetBkMode function, sets the background mode to transparent on creation of the HDC.

Clip range

When HDC is created it doesn't inherit the clipping region set on the Graphics object therefor the text can be drawn outside of the clipping bounds.
To set the same clipping range NativeTextRenderer first gets the Graphics clip region using Graphics.Clip property before GetHdc is called and then sets the region on HDC using SelectClipRgnfunction.

Text color

Unlike managed text draw methods that receive the text color as parameter, GDI text draw functions require the text color to be set before calling text draw using SetTextColor. For simplicity NativeTextRenderer methods receive the color of the text for each text draw calling SetTextColor.

Fonts

As text color, GDI text rendering methods do not receive the font as one of the parameters but need it set before rendering functions call using SelectObject. The SelectObject function receives font handle that can be created from Font.ToHfont method and must be released using DeleteObject. Because fonts creation and deletion can be relatively expensive NativeTextRenderer caches the fonts in a static collection. For simplicity NativeTextRenderer methods receive the managed font object of the text for each text draw/measure getting native font handle and calling SelectObject.

Using NativeTextRenderer

To get the most performance out of NativeTextRenderer you should create/dispose it as little as possible so it is best to group all text rendering. Also reusing the same managed font object is advisable as its creation is also relatively expensive.

NativeTextRenderer class

For more convenient source viewer see: NativeTextRenderer source.

/// <summary>
/// Wrapper for GDI text rendering functions<br/>
/// This class is not thread-safe as GDI function should be called from the UI thread.
/// </summary>
public sealed class NativeTextRenderer : IDisposable
{
#region Fields and Consts /// <summary>
/// used for <see cref="MeasureString(string,System.Drawing.Font,float,out int,out int)"/> calculation.
/// </summary>
private static readonly int[] _charFit = new int[]; /// <summary>
/// used for <see cref="MeasureString(string,System.Drawing.Font,float,out int,out int)"/> calculation.
/// </summary>
private static readonly int[] _charFitWidth = new int[]; /// <summary>
/// cache of all the font used not to create same font again and again
/// </summary>
private static readonly Dictionary<string, Dictionary<float, Dictionary<FontStyle, IntPtr>>> _fontsCache = new Dictionary<string, Dictionary<float, Dictionary<FontStyle, IntPtr>>>(StringComparer.InvariantCultureIgnoreCase); /// <summary>
/// The wrapped WinForms graphics object
/// </summary>
private readonly Graphics _g; /// <summary>
/// the initialized HDC used
/// </summary>
private IntPtr _hdc; #endregion /// <summary>
/// Init.
/// </summary>
public NativeTextRenderer(Graphics g)
{
_g = g; var clip = _g.Clip.GetHrgn(_g); _hdc = _g.GetHdc();
SetBkMode(_hdc, ); SelectClipRgn(_hdc, clip); DeleteObject(clip);
} /// <summary>
/// Measure the width and height of string <paramref name="str"/> when drawn on device context HDC
/// using the given font <paramref name="font"/>.
/// </summary>
/// <param name="str">the string to measure</param>
/// <param name="font">the font to measure string with</param>
/// <returns>the size of the string</returns>
public Size MeasureString(string str, Font font)
{
SetFont(font); var size = new Size();
GetTextExtentPoint32(_hdc, str, str.Length, ref size);
return size;
} /// <summary>
/// Measure the width and height of string <paramref name="str"/> when drawn on device context HDC
/// using the given font <paramref name="font"/>.<br/>
/// Restrict the width of the string and get the number of characters able to fit in the restriction and
/// the width those characters take.
/// </summary>
/// <param name="str">the string to measure</param>
/// <param name="font">the font to measure string with</param>
/// <param name="maxWidth">the max width to render the string in</param>
/// <param name="charFit">the number of characters that will fit under <see cref="maxWidth"/> restriction</param>
/// <param name="charFitWidth"></param>
/// <returns>the size of the string</returns>
public Size MeasureString(string str, Font font, float maxWidth, out int charFit, out int charFitWidth)
{
SetFont(font); var size = new Size();
GetTextExtentExPoint(_hdc, str, str.Length, (int)Math.Round(maxWidth), _charFit, _charFitWidth, ref size);
charFit = _charFit[];
charFitWidth = charFit > ? _charFitWidth[charFit - ] : ;
return size;
} /// <summary>
/// Draw the given string using the given font and foreground color at given location.
/// </summary>
/// <param name="str">the string to draw</param>
/// <param name="font">the font to use to draw the string</param>
/// <param name="color">the text color to set</param>
/// <param name="point">the location to start string draw (top-left)</param>
public void DrawString(String str, Font font, Color color, Point point)
{
SetFont(font);
SetTextColor(color); TextOut(_hdc, point.X, point.Y, str, str.Length);
} /// <summary>
/// Draw the given string using the given font and foreground color at given location.<br/>
/// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15].
/// </summary>
/// <param name="str">the string to draw</param>
/// <param name="font">the font to use to draw the string</param>
/// <param name="color">the text color to set</param>
/// <param name="rect">the rectangle in which the text is to be formatted</param>
/// <param name="flags">The method of formatting the text</param>
public void DrawString(String str, Font font, Color color, Rectangle rect, TextFormatFlags flags)
{
SetFont(font);
SetTextColor(color); var rect2 = new Rect(rect);
DrawText(_hdc, str, str.Length, ref rect2, (uint)flags);
} /// <summary>
/// Release current HDC to be able to use <see cref="Graphics"/> methods.
/// </summary>
public void Dispose()
{
if (_hdc != IntPtr.Zero)
{
SelectClipRgn(_hdc, IntPtr.Zero);
_g.ReleaseHdc(_hdc);
_hdc = IntPtr.Zero;
}
} #region Private methods /// <summary>
/// Set a resource (e.g. a font) for the specified device context.
/// </summary>
private void SetFont(Font font)
{
SelectObject(_hdc, GetCachedHFont(font));
} /// <summary>
/// Get cached unmanaged font handle for given font.<br/>
/// </summary>
/// <param name="font">the font to get unmanaged font handle for</param>
/// <returns>handle to unmanaged font</returns>
private static IntPtr GetCachedHFont(Font font)
{
IntPtr hfont = IntPtr.Zero;
Dictionary<float, Dictionary<FontStyle, IntPtr>> dic1;
if (_fontsCache.TryGetValue(font.Name, out dic1))
{
Dictionary<FontStyle, IntPtr> dic2;
if (dic1.TryGetValue(font.Size, out dic2))
{
dic2.TryGetValue(font.Style, out hfont);
}
else
{
dic1[font.Size] = new Dictionary<FontStyle, IntPtr>();
}
}
else
{
_fontsCache[font.Name] = new Dictionary<float, Dictionary<FontStyle, IntPtr>>();
_fontsCache[font.Name][font.Size] = new Dictionary<FontStyle, IntPtr>();
} if (hfont == IntPtr.Zero)
{
_fontsCache[font.Name][font.Size][font.Style] = hfont = font.ToHfont();
} return hfont;
} /// <summary>
/// Set the text color of the device context.
/// </summary>
private void SetTextColor(Color color)
{
int rgb = ( color.B & 0xFF ) << | ( color.G & 0xFF ) << | color.R;
SetTextColor(_hdc, rgb);
} [DllImport("gdi32.dll")]
private static extern int SetBkMode(IntPtr hdc, int mode); [DllImport("gdi32.dll")]
private static extern int SelectObject(IntPtr hdc, IntPtr hgdiObj); [DllImport("gdi32.dll")]
private static extern int SetTextColor(IntPtr hdc, int color); [DllImport("gdi32.dll", EntryPoint = "GetTextExtentPoint32W")]
private static extern int GetTextExtentPoint32(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string str, int len, ref Size size); [DllImport("gdi32.dll", EntryPoint = "GetTextExtentExPointW")]
private static extern bool GetTextExtentExPoint(IntPtr hDc, [MarshalAs(UnmanagedType.LPWStr)]string str, int nLength, int nMaxExtent, int[] lpnFit, int[] alpDx, ref Size size); [DllImport("gdi32.dll", EntryPoint = "TextOutW")]
private static extern bool TextOut(IntPtr hdc, int x, int y, [MarshalAs(UnmanagedType.LPWStr)] string str, int len); [DllImport("user32.dll", EntryPoint = "DrawTextW")]
private static extern int DrawText(IntPtr hdc, [MarshalAs(UnmanagedType.LPWStr)] string str, int len, ref Rect rect, uint uFormat); [DllImport("gdi32.dll")]
private static extern int SelectClipRgn(IntPtr hdc, IntPtr hrgn); [DllImport("gdi32.dll")]
private static extern bool DeleteObject(IntPtr hObject); // ReSharper disable NotAccessedField.Local
private struct Rect
{
private int _left;
private int _top;
private int _right;
private int _bottom; public Rect(Rectangle r)
{
_left = r.Left;
_top = r.Top;
_bottom = r.Bottom;
_right = r.Right;
}
}
// ReSharper restore NotAccessedField.Local #endregion
} /// <summary>
/// See [http://msdn.microsoft.com/en-us/library/windows/desktop/dd162498(v=vs.85).aspx][15]
/// </summary>
[Flags]
public enum TextFormatFlags : uint
{
Default = 0x00000000,
Center = 0x00000001,
Right = 0x00000002,
VCenter = 0x00000004,
Bottom = 0x00000008,
WordBreak = 0x00000010,
SingleLine = 0x00000020,
ExpandTabs = 0x00000040,
TabStop = 0x00000080,
NoClip = 0x00000100,
ExternalLeading = 0x00000200,
CalcRect = 0x00000400,
NoPrefix = 0x00000800,
Internal = 0x00001000,
EditControl = 0x00002000,
PathEllipsis = 0x00004000,
EndEllipsis = 0x00008000,
ModifyString = 0x00010000,
RtlReading = 0x00020000,
WordEllipsis = 0x00040000,
NoFullWidthCharBreak = 0x00080000,
HidePrefix = 0x00100000,
ProfixOnly = 0x00200000,
}

摘自:

https://theartofdev.com/2013/08/12/using-native-gdi-for-text-rendering-in-c/

Using native GDI for text rendering in C#的更多相关文章

  1. OpenCascade Chinese Text Rendering

    OpenCascade Chinese Text Rendering eryar@163.com Abstract. OpenCascade uses advanced text rendering ...

  2. React Native组件之Text

    React Native组件之Text相当于iOS中的UILabel. 其基本属性如下: /** * Sample React Native App * https://github.com/face ...

  3. CSS Animation triggers text rendering change in Safari

    薄荷新首页上周五内测,花哥反馈在 MacBook Safari 浏览器下 鼠标移动到第一个商品的时候后面几个商品的文字会加粗.这是什么鬼??? 待我回到家打开笔记本,鼠标蹭蹭蹭的发现问题远不止如此: ...

  4. React Native - 3 View, Text简介以及onPress & onLongPress事件

    我们要生成如下的构图   直接上图,不解释.       如下图所示,定义函数,函数之间不需要逗号,在元素上添加事件,使用关键字this.{function name}    

  5. React (Native) Rendering Lifecycle

    How Does React Native Work? The idea of writing mobile applications in JavaScript feels a little odd ...

  6. React Native 在 Airbnb 的起起落落

    写在前面 Airbnb 早在 2016 年就上了 React Native 大船,是很具代表性的先驱布道者: In 2016, we took a big bet on React Native. T ...

  7. React Native知识5-Touchable类组件

    React Native 没有像web那样可以给元素绑定click事件,前面我们已经知道Text组件有onPress事件,为了给其他组件 也绑定点击事件,React Native提供了3个组件来做这件 ...

  8. iOS原生项目中集成React Native

    1.本文的前提条件是,电脑上已经安装了CocoaPods,React Native相关环境. 2.使用Xcode新建一个工程.EmbedRNMeituan [图1] 3.使用CocoaPods安装Re ...

  9. Quartz2D Text

    [Quartz2D Text] Quartz 2D provides a limited, low-level interface for drawing text encoded in the Ma ...

随机推荐

  1. 【转】virtualbox 4.08安装虚机Ubuntu11.04增强功能失败解决方法

    原文网址:http://fuliang.iteye.com/blog/1102998 在笔记本安装Ubuntu11.04增强功能失败 引用 fuliang@fuliang-VirtualBox:~$ ...

  2. HDU-1241 Oil Deposits (DFS)

    Oil Deposits Time Limit : 2000/1000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total ...

  3. yml文件数据的简洁表达方法(Hashes to OpenStruct)

    通过ruby编写测试脚本的时候,我还是喜欢采用yml来管理测试数据,就像以前的文章(Selenium WebDriver + Grid2 + RSpec之旅(五))提到的一样,但是在引用yml中的数据 ...

  4. Win7,8的上帝模式文件夹 GodMode.{ED7BA470-8E54-465E-825C-99712043E01C}

    新建一文件夹,命名为:“GodMode.{ED7BA470-8E54-465E-825C-99712043E01C}”, 创建后双击进入,可以看到计算机所有的配置选项信息.

  5. 进程与线程(四) linux进程间通信的方式总结

    1概述: 上文说到,每个进程都有自己的地址空间,包括什么呢?向下生长得栈,向上生长的堆,代码段,数据段等,这些都是进程私有的,如何实现通信的呢?通信需要媒介,这个媒介很重要. 对于研发人员来说,进程不 ...

  6. C中位域的使用

    一.位域 有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可.为了节省存储空间,并使处理简便,C语言又提供了一 ...

  7. opencv 重用代码块记录

    Created on 2013-8-7URL : http://blog.sina.com.cn/s/blog_a502f1a30101mfj4.html@author: zhxfl 转载请说明出处 ...

  8. 记录一次MVC3升级MVC4遇到的问题

    前些天,项目组将项目从mvc3升级至mvc4,完了之后,发现突然涌现出一大堆问题.主要是在前端,Razor引擎升级导致的. Razor是从mvc3开始引入的,用过mvc2的就会知道,Razor用起来确 ...

  9. Yii AR Model CRUD数据库操作

    Yii AR很好很强大,但刚开始不知道怎么使用,可以先看下官方文档 官方文档:http://www.yiichina.com/guide/database.ar 下面是我对AR的一些理解 对于一个Mo ...

  10. phpnow下mysqli加载不了的问题

    用了phpnow也有蛮长一段时间了.一直感觉还不错,不过就是差点把手动配置apache/php/mysql环境的方法都给忘了. 这几天一直都在找crm系统,可惜没有几个满意的.今天逛上了weberp这 ...