4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 // Copyright (c) 2006 Novell, Inc.
26 // Jonathan Pobst (monkey@jpobst.com)
29 // This has become a monster class for all things text measuring and drawing.
31 // The public API is MeasureText/DrawText, which uses GDI on Win32, and
32 // GDI+ on other platforms.
34 // There is an internal API MeasureTextInternal/DrawTextInternal, which allows
35 // you to pass a flag of whether to use GDI or GDI+. This is used mainly for
36 // controls that have the UseCompatibleTextRendering flag.
38 // There are also thread-safe versions of MeasureString/MeasureCharacterRanges
39 // for things that want to measure strings without having a Graphics object.
42 using System.Runtime.InteropServices;
44 using System.Drawing.Text;
46 namespace System.Windows.Forms
48 public sealed class TextRenderer
50 private TextRenderer ()
54 #region Public Methods
55 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor)
57 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, false);
60 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor)
62 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
65 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor)
67 DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, false);
70 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags)
72 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, false);
75 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor)
77 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
80 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags)
82 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, false);
85 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags)
87 DrawTextInternal (dc, text, font, pt, foreColor, backColor, flags, false);
90 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags)
92 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, flags, false);
95 public static Size MeasureText (string text, Font font)
97 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, Size.Empty, TextFormatFlags.Default, false);
100 public static Size MeasureText (IDeviceContext dc, string text, Font font)
102 return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, false);
105 public static Size MeasureText (string text, Font font, Size proposedSize)
107 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, TextFormatFlags.Default, false);
110 public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize)
112 return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, false);
115 public static Size MeasureText (string text, Font font, Size proposedSize, TextFormatFlags flags)
117 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, flags, false);
120 public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags)
122 return MeasureTextInternal (dc, text, font, proposedSize, flags, false);
126 #region Internal Methods That Do Stuff
127 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
130 throw new ArgumentNullException ("dc");
132 if (text == null || text.Length == 0)
135 // We use MS GDI API's unless told not to, or we aren't on Windows
136 if (!useDrawString && !XplatUI.RunningOnUnix) {
137 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter || (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
138 flags |= TextFormatFlags.SingleLine;
140 // Calculate the text bounds (there is often padding added)
141 Rectangle new_bounds = PadRectangle (bounds, flags);
142 new_bounds.Offset ((int)(dc as Graphics).Transform.OffsetX, (int)(dc as Graphics).Transform.OffsetY);
144 IntPtr hdc = IntPtr.Zero;
145 bool clear_clip_region = false;
147 // If we need to use the graphics clipping region, add it to our hdc
148 if ((flags & TextFormatFlags.PreserveGraphicsClipping) == TextFormatFlags.PreserveGraphicsClipping) {
149 Graphics graphics = (Graphics)dc;
150 Region clip_region = graphics.Clip;
152 if (!clip_region.IsInfinite (graphics)) {
153 IntPtr hrgn = clip_region.GetHrgn (graphics);
155 SelectClipRgn (hdc, hrgn);
158 clear_clip_region = true;
162 if (hdc == IntPtr.Zero)
165 // Set the fore color
166 if (foreColor != Color.Empty)
167 SetTextColor (hdc, ColorTranslator.ToWin32 (foreColor));
169 // Set the back color
170 if (backColor != Color.Transparent && backColor != Color.Empty) {
171 SetBkMode (hdc, 2); //1-Transparent, 2-Opaque
172 SetBkColor (hdc, ColorTranslator.ToWin32 (backColor));
175 SetBkMode (hdc, 1); //1-Transparent, 2-Opaque
178 XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new_bounds);
183 prevobj = SelectObject (hdc, font.ToHfont ());
184 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
185 prevobj = SelectObject (hdc, prevobj);
186 DeleteObject (prevobj);
189 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
192 if (clear_clip_region)
193 SelectClipRgn (hdc, IntPtr.Zero);
197 // Use Graphics.DrawString as a fallback method
200 IntPtr hdc = IntPtr.Zero;
206 g = Graphics.FromHdc (hdc);
209 StringFormat sf = FlagsToStringFormat (flags);
211 Rectangle new_bounds = PadDrawStringRectangle (bounds, flags);
213 g.DrawString (text, font, ThemeEngine.Current.ResPool.GetSolidBrush (foreColor), new_bounds, sf);
215 if (!(dc is Graphics)) {
222 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
224 if (!useMeasureString && !XplatUI.RunningOnUnix) {
225 // Tell DrawText to calculate size instead of draw
226 flags |= (TextFormatFlags)1024; // DT_CALCRECT
228 IntPtr hdc = dc.GetHdc ();
230 XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new Rectangle (Point.Empty, proposedSize));
235 prevobj = SelectObject (hdc, font.ToHfont ());
236 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
237 prevobj = SelectObject (hdc, prevobj);
238 DeleteObject (prevobj);
241 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
246 // Really, I am just making something up here, which as far as I can tell, MS
247 // just makes something up as well. This will require lots of tweaking to match MS. :(
248 Size retval = r.ToRectangle ().Size;
250 if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0) {
252 retval.Width += (int)retval.Height / 8;
258 StringFormat sf = FlagsToStringFormat (flags);
263 retval = (dc as Graphics).MeasureString (text, font, proposedSize.Width == 0 ? Int32.MaxValue : proposedSize.Width, sf).ToSize ();
265 retval = TextRenderer.MeasureString (text, font, proposedSize.Width == 0 ? Int32.MaxValue : proposedSize.Width, sf).ToSize ();
267 if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0)
275 #region Internal Methods That Are Just Overloads
276 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, bool useDrawString)
278 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, useDrawString);
281 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, bool useDrawString)
283 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
286 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, bool useDrawString)
288 DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, useDrawString);
291 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags, bool useDrawString)
293 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, useDrawString);
296 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, bool useDrawString)
298 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
301 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags, bool useDrawString)
303 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, useDrawString);
306 internal static Size MeasureTextInternal (string text, Font font, bool useMeasureString)
308 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
311 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
313 Size sz = MeasureTextInternal (dc, text, font, useDrawString);
314 DrawTextInternal (dc, text, font, new Rectangle (pt, sz), foreColor, backColor, flags, useDrawString);
317 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, bool useMeasureString)
319 return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
322 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, bool useMeasureString)
324 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
327 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, bool useMeasureString)
329 return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
332 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
334 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, flags, useMeasureString);
338 #region Thread-Safe Static Graphics Methods
339 internal static SizeF MeasureString (string text, Font font)
341 return Hwnd.GraphicsContext.MeasureString (text, font);
344 internal static SizeF MeasureString (string text, Font font, int width)
346 return Hwnd.GraphicsContext.MeasureString (text, font, width);
349 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea)
351 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea);
354 internal static SizeF MeasureString (string text, Font font, int width, StringFormat format)
356 return Hwnd.GraphicsContext.MeasureString (text, font, width, format);
359 internal static SizeF MeasureString (string text, Font font, PointF origin, StringFormat stringFormat)
361 return Hwnd.GraphicsContext.MeasureString (text, font, origin, stringFormat);
364 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat)
366 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat);
369 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat, out int charactersFitted, out int linesFilled)
371 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat, out charactersFitted, out linesFilled);
374 internal static Region[] MeasureCharacterRanges (string text, Font font, RectangleF layoutRect, StringFormat stringFormat)
376 return Hwnd.GraphicsContext.MeasureCharacterRanges (text, font, layoutRect, stringFormat);
379 internal static SizeF GetDpi ()
381 return new SizeF (Hwnd.GraphicsContext.DpiX, Hwnd.GraphicsContext.DpiY);
385 #region Private Methods
386 private static StringFormat FlagsToStringFormat (TextFormatFlags flags)
388 StringFormat sf = new StringFormat ();
390 // Translation table: http://msdn.microsoft.com/msdnmag/issues/06/03/TextRendering/default.aspx?fig=true#fig4
392 // Horizontal Alignment
393 if ((flags & TextFormatFlags.HorizontalCenter) == TextFormatFlags.HorizontalCenter)
394 sf.Alignment = StringAlignment.Center;
395 else if ((flags & TextFormatFlags.Right) == TextFormatFlags.Right)
396 sf.Alignment = StringAlignment.Far;
398 sf.Alignment = StringAlignment.Near;
400 // Vertical Alignment
401 if ((flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
402 sf.LineAlignment = StringAlignment.Far;
403 else if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter)
404 sf.LineAlignment = StringAlignment.Center;
406 sf.LineAlignment = StringAlignment.Near;
409 if ((flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis)
410 sf.Trimming = StringTrimming.EllipsisCharacter;
411 else if ((flags & TextFormatFlags.PathEllipsis) == TextFormatFlags.PathEllipsis)
412 sf.Trimming = StringTrimming.EllipsisPath;
413 else if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis)
414 sf.Trimming = StringTrimming.EllipsisWord;
416 sf.Trimming = StringTrimming.Character;
419 if ((flags & TextFormatFlags.NoPrefix) == TextFormatFlags.NoPrefix)
420 sf.HotkeyPrefix = HotkeyPrefix.None;
421 else if ((flags & TextFormatFlags.HidePrefix) == TextFormatFlags.HidePrefix)
422 sf.HotkeyPrefix = HotkeyPrefix.Hide;
424 sf.HotkeyPrefix = HotkeyPrefix.Show;
427 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding)
428 sf.FormatFlags |= StringFormatFlags.FitBlackBox;
431 if ((flags & TextFormatFlags.SingleLine) == TextFormatFlags.SingleLine)
432 sf.FormatFlags |= StringFormatFlags.NoWrap;
433 else if ((flags & TextFormatFlags.TextBoxControl) == TextFormatFlags.TextBoxControl)
434 sf.FormatFlags |= StringFormatFlags.LineLimit;
437 //if ((flags & TextFormatFlags.RightToLeft) == TextFormatFlags.RightToLeft)
438 // sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
439 if ((flags & TextFormatFlags.NoClipping) == TextFormatFlags.NoClipping)
440 sf.FormatFlags |= StringFormatFlags.NoClip;
445 private static Rectangle PadRectangle (Rectangle r, TextFormatFlags flags)
447 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
451 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
454 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
458 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
461 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
468 private static Rectangle PadDrawStringRectangle (Rectangle r, TextFormatFlags flags)
470 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
474 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
477 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding) {
480 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom) {
483 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
487 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
490 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
498 #region DllImports (Windows)
499 [DllImport ("user32", CharSet = CharSet.Unicode, EntryPoint = "DrawText")]
500 static extern int Win32DrawText (IntPtr hdc, string lpStr, int nCount, ref XplatUIWin32.RECT lpRect, int wFormat);
502 [DllImport ("gdi32")]
503 static extern int SetTextColor (IntPtr hdc, int crColor);
505 [DllImport ("gdi32")]
506 static extern IntPtr SelectObject (IntPtr hDC, IntPtr hObject);
508 [DllImport ("gdi32")]
509 static extern int SetBkColor (IntPtr hdc, int crColor);
511 [DllImport ("gdi32")]
512 static extern int SetBkMode (IntPtr hdc, int iBkMode);
514 [DllImport ("gdi32")]
515 static extern bool DeleteObject (IntPtr objectHandle);
518 static extern bool SelectClipRgn(IntPtr hdc, IntPtr hrgn);