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技巧与高级特性

    原文网址:http://www.searchvirtual.com.cn/showcontent_76463.htm VirtualBox集成的许多功能你可能从来没有使用过,即使你经常用它来运行虚拟机 ...

  2. Kia's Calculation(HDU 4267)

    Problem Description Doctor Ghee is teaching Kia how to calculate the sum of two integers. But Kia is ...

  3. Windows 8.1中怎么启用Framework3.5或2.0 ( 一安装就跳到下载 Win8.1自带了Framework)

    Windows 8.1中怎么启用Framework3.5或2.0      ( 一安装就跳到下载 Win8.1自带了Framework): Win+X键 打开   开始菜单 -> 命令提示符(管 ...

  4. PTA 07-图4 哈利·波特的考试 (25分)

    哈利·波特要考试了,他需要你的帮助.这门课学的是用魔咒将一种动物变成另一种动物的本事.例如将猫变成老鼠的魔咒是haha,将老鼠变成鱼的魔咒是hehe等等.反方向变化的魔咒就是简单地将原来的魔咒倒过来念 ...

  5. div+css的兼容性问题和解决方法

    1. 默认的内外边距不同 问题: 各个浏览器默认的内外边距不同 解决: *{margin:0;padding:0;}   2. 水平居中的问题 问题: 设置 text-align: center    ...

  6. Apache Shiro 集成-Cas

    http://blog.csdn.net/peterwanghao/article/details/8825008 Shiro集成CAS是在1.2版本里新增的功能. Shiro-cas模块将应用作为C ...

  7. sublimeText3安装package control和禁止弹出更新下载弹窗

    1.sublimeText3安装package control import urllib.request,os; pf = 'Package Control.sublime-package'; ip ...

  8. PowerShell自定义对象

    前面的性能高 使用[pscustomobject][ordered]强制类型转换 [pscustomobject][ordered]@{Name= 'Boe'Number = 1ID = 007} 使 ...

  9. C语言 小游戏之贪吃蛇

    还记得非常久曾经听群里人说做贪吃蛇什么的,那时候大一刚学了C语言,认为非常难,根本没什么思路. 前不久群里有些人又在谈论C语言贪吃蛇的事了,看着他们在做,我也打算做一个出来. 如今大三,经过了这一年半 ...

  10. Android开发优化之——对Bitmap的内存优化

    http://blog.csdn.net/arui319/article/details/7953690 在Android应用里,最耗费内存的就是图片资源.而且在Android系统中,读取位图Bitm ...