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 if (proposedSize.Width == 0)
264 proposedWidth = Int32.MaxValue;
266 proposedWidth = proposedSize.Width;
267 if ((flags & TextFormatFlags.NoPadding) == 0)
271 retval = (dc as Graphics).MeasureString (text, font, proposedWidth, sf).ToSize ();
273 retval = TextRenderer.MeasureString (text, font, proposedWidth, sf).ToSize ();
275 if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0)
283 #region Internal Methods That Are Just Overloads
284 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, bool useDrawString)
286 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, useDrawString);
289 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, bool useDrawString)
291 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
294 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, bool useDrawString)
296 DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, useDrawString);
299 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags, bool useDrawString)
301 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, useDrawString);
304 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, bool useDrawString)
306 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
309 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags, bool useDrawString)
311 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, useDrawString);
314 internal static Size MeasureTextInternal (string text, Font font, bool useMeasureString)
316 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
319 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
321 Size sz = MeasureTextInternal (dc, text, font, useDrawString);
322 DrawTextInternal (dc, text, font, new Rectangle (pt, sz), foreColor, backColor, flags, useDrawString);
325 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, bool useMeasureString)
327 return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
330 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, bool useMeasureString)
332 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
335 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, bool useMeasureString)
337 return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
340 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
342 return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, flags, useMeasureString);
346 #region Thread-Safe Static Graphics Methods
347 internal static SizeF MeasureString (string text, Font font)
349 return Hwnd.GraphicsContext.MeasureString (text, font);
352 internal static SizeF MeasureString (string text, Font font, int width)
354 return Hwnd.GraphicsContext.MeasureString (text, font, width);
357 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea)
359 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea);
362 internal static SizeF MeasureString (string text, Font font, int width, StringFormat format)
364 return Hwnd.GraphicsContext.MeasureString (text, font, width, format);
367 internal static SizeF MeasureString (string text, Font font, PointF origin, StringFormat stringFormat)
369 return Hwnd.GraphicsContext.MeasureString (text, font, origin, stringFormat);
372 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat)
374 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat);
377 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat, out int charactersFitted, out int linesFilled)
379 return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat, out charactersFitted, out linesFilled);
382 internal static Region[] MeasureCharacterRanges (string text, Font font, RectangleF layoutRect, StringFormat stringFormat)
384 return Hwnd.GraphicsContext.MeasureCharacterRanges (text, font, layoutRect, stringFormat);
387 internal static SizeF GetDpi ()
389 return new SizeF (Hwnd.GraphicsContext.DpiX, Hwnd.GraphicsContext.DpiY);
393 #region Private Methods
394 private static StringFormat FlagsToStringFormat (TextFormatFlags flags)
396 StringFormat sf = new StringFormat ();
398 // Translation table: http://msdn.microsoft.com/msdnmag/issues/06/03/TextRendering/default.aspx?fig=true#fig4
400 // Horizontal Alignment
401 if ((flags & TextFormatFlags.HorizontalCenter) == TextFormatFlags.HorizontalCenter)
402 sf.Alignment = StringAlignment.Center;
403 else if ((flags & TextFormatFlags.Right) == TextFormatFlags.Right)
404 sf.Alignment = StringAlignment.Far;
406 sf.Alignment = StringAlignment.Near;
408 // Vertical Alignment
409 if ((flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
410 sf.LineAlignment = StringAlignment.Far;
411 else if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter)
412 sf.LineAlignment = StringAlignment.Center;
414 sf.LineAlignment = StringAlignment.Near;
417 if ((flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis)
418 sf.Trimming = StringTrimming.EllipsisCharacter;
419 else if ((flags & TextFormatFlags.PathEllipsis) == TextFormatFlags.PathEllipsis)
420 sf.Trimming = StringTrimming.EllipsisPath;
421 else if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis)
422 sf.Trimming = StringTrimming.EllipsisWord;
424 sf.Trimming = StringTrimming.Character;
427 if ((flags & TextFormatFlags.NoPrefix) == TextFormatFlags.NoPrefix)
428 sf.HotkeyPrefix = HotkeyPrefix.None;
429 else if ((flags & TextFormatFlags.HidePrefix) == TextFormatFlags.HidePrefix)
430 sf.HotkeyPrefix = HotkeyPrefix.Hide;
432 sf.HotkeyPrefix = HotkeyPrefix.Show;
435 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding)
436 sf.FormatFlags |= StringFormatFlags.FitBlackBox;
439 if ((flags & TextFormatFlags.SingleLine) == TextFormatFlags.SingleLine)
440 sf.FormatFlags |= StringFormatFlags.NoWrap;
441 else if ((flags & TextFormatFlags.TextBoxControl) == TextFormatFlags.TextBoxControl)
442 sf.FormatFlags |= StringFormatFlags.LineLimit;
445 //if ((flags & TextFormatFlags.RightToLeft) == TextFormatFlags.RightToLeft)
446 // sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
447 if ((flags & TextFormatFlags.NoClipping) == TextFormatFlags.NoClipping)
448 sf.FormatFlags |= StringFormatFlags.NoClip;
453 private static Rectangle PadRectangle (Rectangle r, TextFormatFlags flags)
455 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
459 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
462 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
466 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
469 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
476 private static Rectangle PadDrawStringRectangle (Rectangle r, TextFormatFlags flags)
478 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
482 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
485 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding) {
488 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom) {
491 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
495 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
498 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter && XplatUI.RunningOnUnix) {
506 #region DllImports (Windows)
507 [DllImport ("user32", CharSet = CharSet.Unicode, EntryPoint = "DrawText")]
508 static extern int Win32DrawText (IntPtr hdc, string lpStr, int nCount, ref XplatUIWin32.RECT lpRect, int wFormat);
510 [DllImport ("gdi32")]
511 static extern int SetTextColor (IntPtr hdc, int crColor);
513 [DllImport ("gdi32")]
514 static extern IntPtr SelectObject (IntPtr hDC, IntPtr hObject);
516 [DllImport ("gdi32")]
517 static extern int SetBkColor (IntPtr hdc, int crColor);
519 [DllImport ("gdi32")]
520 static extern int SetBkMode (IntPtr hdc, int iBkMode);
522 [DllImport ("gdi32")]
523 static extern bool DeleteObject (IntPtr objectHandle);
526 static extern bool SelectClipRgn(IntPtr hdc, IntPtr hrgn);