Using native GDI for text rendering in C#
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#的更多相关文章
- OpenCascade Chinese Text Rendering
OpenCascade Chinese Text Rendering eryar@163.com Abstract. OpenCascade uses advanced text rendering ...
- React Native组件之Text
React Native组件之Text相当于iOS中的UILabel. 其基本属性如下: /** * Sample React Native App * https://github.com/face ...
- CSS Animation triggers text rendering change in Safari
薄荷新首页上周五内测,花哥反馈在 MacBook Safari 浏览器下 鼠标移动到第一个商品的时候后面几个商品的文字会加粗.这是什么鬼??? 待我回到家打开笔记本,鼠标蹭蹭蹭的发现问题远不止如此: ...
- React Native - 3 View, Text简介以及onPress & onLongPress事件
我们要生成如下的构图 直接上图,不解释. 如下图所示,定义函数,函数之间不需要逗号,在元素上添加事件,使用关键字this.{function name}
- React (Native) Rendering Lifecycle
How Does React Native Work? The idea of writing mobile applications in JavaScript feels a little odd ...
- React Native 在 Airbnb 的起起落落
写在前面 Airbnb 早在 2016 年就上了 React Native 大船,是很具代表性的先驱布道者: In 2016, we took a big bet on React Native. T ...
- React Native知识5-Touchable类组件
React Native 没有像web那样可以给元素绑定click事件,前面我们已经知道Text组件有onPress事件,为了给其他组件 也绑定点击事件,React Native提供了3个组件来做这件 ...
- iOS原生项目中集成React Native
1.本文的前提条件是,电脑上已经安装了CocoaPods,React Native相关环境. 2.使用Xcode新建一个工程.EmbedRNMeituan [图1] 3.使用CocoaPods安装Re ...
- Quartz2D Text
[Quartz2D Text] Quartz 2D provides a limited, low-level interface for drawing text encoded in the Ma ...
随机推荐
- zedGraph
“zedGraph”控件画图的时候遇到了一点小问题,就是在同一个控件实例上第一次绘制饼图的时候一切正常,但是再次绘制的时候不是重新绘制新的饼图,而是在原有基础之上又添加进新的对象(PieItem),又 ...
- 经典算法面试题目-翻转一个C风格的字符串(1.2)
题目: Write code to reverse a C-Style String. (C-String means that "abcd" is represented as ...
- wxWidgets学习笔记——在屏幕上画简单的图形和文字
在屏幕上画简单图形和显示图片.处理简单鼠标键盘事件 /*************************************************************** * Name: M ...
- head frist 设计模式学习之 JVM中的博物馆奇妙夜(观察者模式)
博物馆奇妙夜! 博物馆奇妙夜!博物馆奇妙夜!重说三!!!JVM看了<博物馆奇妙夜>电影之后,决定在自己家里开一个博物馆!毕竟需要什么new一下就好,博物馆很快就开起来了,并且任命你为馆长( ...
- 用APK Downloader直接从Google Play上下载apk
APK Downloader可以直接从Google Play上下载apk,相比较其他软件,这个不需要提供Google ID,对于没有刷机的同学还是有些帮助的.
- CentOS源码安装搭建LNMP全过程(包括nginx,mysql,php,svn)【转】
转自:http://blog.csdn.net/yanzi1225627/article/details/49123659 服务器环境为:CentOS6.5 64位 目标:搭建LNMP(Linux + ...
- Android时间互换代码
收藏备用. http://www.oschina.net/code/snippet_575610_22694 /** * 获取现在时间 * * @return 返回时间类型 yyyy-MM-dd HH ...
- Android Drawable 和String 相互转化
在我们经常应用开发中,经常用到将drawable和string相互转化.注意这情况最好用于小图片入icon等. public synchronized Drawable byteToDrawable( ...
- 随便说说removeFromSuperview方法
之前写过一篇关于removeFromSuperview方法处理的文章,写完后一直就没怎么更新这篇文章.这两天回过头来看看,感觉这篇文章有些地方写的不够严谨,而且还有一些自己理解错的地方,所以打算重写这 ...
- Android(java)学习笔记186:对ListView等列表组件中数据进行增、删、改操作
1.ListView介绍 解决大量的相似的数据显示问题 采用了MVC模式: M: model (数据模型) V: view (显示的视图) C: controller 控制器 入门案例: acit ...