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
54 private static Bitmap static_bitmap;
56 private static Graphics static_graphics;
58 private TextRenderer ()
62 #region Public Methods
64 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor)
66 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, false);
69 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor)
71 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
74 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor)
76 DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, false);
79 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags)
81 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, false);
84 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor)
86 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
89 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags)
91 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, false);
94 public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags)
96 DrawTextInternal (dc, text, font, pt, foreColor, backColor, flags, false);
99 public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags)
101 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, flags, false);
104 public static Size MeasureText (string text, Font font)
106 return MeasureTextInternal (StaticGraphics, text, font, Size.Empty, TextFormatFlags.Default, false);
109 public static Size MeasureText (IDeviceContext dc, string text, Font font)
111 return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, false);
114 public static Size MeasureText (string text, Font font, Size proposedSize)
116 return MeasureTextInternal (StaticGraphics, text, font, proposedSize, TextFormatFlags.Default, false);
119 public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize)
121 return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, false);
124 public static Size MeasureText (string text, Font font, Size proposedSize, TextFormatFlags flags)
126 return MeasureTextInternal (StaticGraphics, text, font, proposedSize, flags, false);
129 public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags)
131 return MeasureTextInternal (dc, text, font, proposedSize, flags, false);
136 #region Internal Methods That Do Stuff
138 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
140 internal static void DrawTextInternal (Graphics dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
145 throw new ArgumentNullException ("dc");
147 if (string.IsNullOrEmpty (text))
150 // We use MS GDI API's unless told not to, or we aren't on Windows
151 if (!useDrawString && !XplatUI.RunningOnUnix) {
152 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter || (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
153 flags |= TextFormatFlags.SingleLine;
155 // Calculate the text bounds (there is often padding added)
156 Rectangle new_bounds = PadRectangle (bounds, flags);
157 new_bounds.Offset ((int)(dc as Graphics).Transform.OffsetX, (int)(dc as Graphics).Transform.OffsetY);
159 IntPtr hdc = IntPtr.Zero;
160 bool clear_clip_region = false;
162 // If we need to use the graphics clipping region, add it to our hdc
163 if ((flags & TextFormatFlags.PreserveGraphicsClipping) == TextFormatFlags.PreserveGraphicsClipping) {
164 Graphics graphics = (Graphics)dc;
165 Region clip_region = graphics.Clip;
167 if (!clip_region.IsInfinite (graphics)) {
168 IntPtr hrgn = clip_region.GetHrgn (graphics);
170 SelectClipRgn (hdc, hrgn);
171 clip_region.ReleaseHrgn (hrgn);
172 clear_clip_region = true;
176 if (hdc == IntPtr.Zero)
179 // Set the fore color
180 if (foreColor != Color.Empty)
181 SetTextColor (hdc, ColorTranslator.ToWin32 (foreColor));
183 // Set the back color
184 if (backColor != Color.Transparent && backColor != Color.Empty) {
185 SetBkMode (hdc, 2); //1-Transparent, 2-Opaque
186 SetBkColor (hdc, ColorTranslator.ToWin32 (backColor));
189 SetBkMode (hdc, 1); //1-Transparent, 2-Opaque
192 XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new_bounds);
197 prevobj = SelectObject (hdc, font.ToHfont ());
198 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
199 prevobj = SelectObject (hdc, prevobj);
200 DeleteObject (prevobj);
203 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
206 if (clear_clip_region)
207 SelectClipRgn (hdc, IntPtr.Zero);
211 // Use Graphics.DrawString as a fallback method
220 g = Graphics.FromHdc (dc.GetHdc ());
226 StringFormat sf = FlagsToStringFormat (flags);
228 Rectangle new_bounds = PadDrawStringRectangle (bounds, flags);
230 g.DrawString (text, font, ThemeEngine.Current.ResPool.GetSolidBrush (foreColor), new_bounds, sf);
233 if (!(dc is Graphics)) {
242 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
244 internal static Size MeasureTextInternal (Graphics dc, string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
248 if (!useMeasureString && !XplatUI.RunningOnUnix) {
249 // Tell DrawText to calculate size instead of draw
250 flags |= (TextFormatFlags)1024; // DT_CALCRECT
252 IntPtr hdc = dc.GetHdc ();
254 XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new Rectangle (Point.Empty, proposedSize));
259 prevobj = SelectObject (hdc, font.ToHfont ());
260 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
261 prevobj = SelectObject (hdc, prevobj);
262 DeleteObject (prevobj);
265 Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
270 // Really, I am just making something up here, which as far as I can tell, MS
271 // just makes something up as well. This will require lots of tweaking to match MS. :(
272 Size retval = r.ToRectangle ().Size;
274 if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0) {
276 retval.Width += (int)retval.Height / 8;
283 StringFormat sf = FlagsToStringFormat (flags);
289 retval = (dc as Graphics).MeasureString (text, font, Int32.MaxValue, sf).ToSize ();
291 retval = TextRenderer.MeasureString (text, font, Int32.MaxValue, sf).ToSize ();
293 retval = dc.MeasureString (text, font, Int32.MaxValue, sf).ToSize ();
296 if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0)
306 #region Internal Methods That Are Just Overloads
308 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, bool useDrawString)
310 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, useDrawString);
313 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, bool useDrawString)
315 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
318 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, bool useDrawString)
320 DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, useDrawString);
323 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags, bool useDrawString)
325 DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, useDrawString);
328 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, bool useDrawString)
330 DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
333 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags, bool useDrawString)
335 internal static void DrawTextInternal (Graphics dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags, bool useDrawString)
338 DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, useDrawString);
341 internal static Size MeasureTextInternal (string text, Font font, bool useMeasureString)
343 return MeasureTextInternal (StaticGraphics, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
347 internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
349 Size sz = MeasureTextInternal (dc, text, font, useDrawString);
350 DrawTextInternal (dc, text, font, new Rectangle (pt, sz), foreColor, backColor, flags, useDrawString);
353 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, bool useMeasureString)
355 return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
358 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, bool useMeasureString)
360 return MeasureTextInternal (StaticGraphics, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
363 internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, bool useMeasureString)
365 return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
369 internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
371 return MeasureTextInternal (StaticGraphics, text, font, proposedSize, flags, useMeasureString);
375 #region Thread-Safe Static Graphics Methods
376 internal static SizeF MeasureString (string text, Font font)
378 return StaticGraphics.MeasureString (text, font);
381 internal static SizeF MeasureString (string text, Font font, int width)
383 return StaticGraphics.MeasureString (text, font, width);
386 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea)
388 return StaticGraphics.MeasureString (text, font, layoutArea);
391 internal static SizeF MeasureString (string text, Font font, int width, StringFormat format)
393 return StaticGraphics.MeasureString (text, font, width, format);
396 internal static SizeF MeasureString (string text, Font font, PointF origin, StringFormat stringFormat)
398 return StaticGraphics.MeasureString (text, font, origin, stringFormat);
401 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat)
403 return StaticGraphics.MeasureString (text, font, layoutArea, stringFormat);
406 internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat, out int charactersFitted, out int linesFilled)
408 return StaticGraphics.MeasureString (text, font, layoutArea, stringFormat, out charactersFitted, out linesFilled);
411 internal static Region[] MeasureCharacterRanges (string text, Font font, RectangleF layoutRect, StringFormat stringFormat)
413 return StaticGraphics.MeasureCharacterRanges (text, font, layoutRect, stringFormat);
416 internal static SizeF GetDpi ()
418 return new SizeF (StaticGraphics.DpiX, StaticGraphics.DpiY);
421 internal static Bitmap StaticBitmap {
423 if (static_bitmap == null)
424 static_bitmap = new Bitmap (1, 1, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
426 return static_bitmap;
430 internal static Graphics StaticGraphics {
432 if (static_graphics == null)
433 static_graphics = Graphics.FromImage (StaticBitmap);
435 return static_graphics;
440 #region Private Methods
441 private static StringFormat FlagsToStringFormat (TextFormatFlags flags)
443 StringFormat sf = new StringFormat ();
445 // Translation table: http://msdn.microsoft.com/msdnmag/issues/06/03/TextRendering/default.aspx?fig=true#fig4
447 // Horizontal Alignment
448 if ((flags & TextFormatFlags.HorizontalCenter) == TextFormatFlags.HorizontalCenter)
449 sf.Alignment = StringAlignment.Center;
450 else if ((flags & TextFormatFlags.Right) == TextFormatFlags.Right)
451 sf.Alignment = StringAlignment.Far;
453 sf.Alignment = StringAlignment.Near;
455 // Vertical Alignment
456 if ((flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
457 sf.LineAlignment = StringAlignment.Far;
458 else if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter)
459 sf.LineAlignment = StringAlignment.Center;
461 sf.LineAlignment = StringAlignment.Near;
464 if ((flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis)
465 sf.Trimming = StringTrimming.EllipsisCharacter;
466 else if ((flags & TextFormatFlags.PathEllipsis) == TextFormatFlags.PathEllipsis)
467 sf.Trimming = StringTrimming.EllipsisPath;
468 else if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis)
469 sf.Trimming = StringTrimming.EllipsisWord;
471 sf.Trimming = StringTrimming.Character;
474 if ((flags & TextFormatFlags.NoPrefix) == TextFormatFlags.NoPrefix)
475 sf.HotkeyPrefix = HotkeyPrefix.None;
476 else if ((flags & TextFormatFlags.HidePrefix) == TextFormatFlags.HidePrefix)
477 sf.HotkeyPrefix = HotkeyPrefix.Hide;
479 sf.HotkeyPrefix = HotkeyPrefix.Show;
482 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding)
483 sf.FormatFlags |= StringFormatFlags.FitBlackBox;
486 if ((flags & TextFormatFlags.SingleLine) == TextFormatFlags.SingleLine)
487 sf.FormatFlags |= StringFormatFlags.NoWrap;
488 else if ((flags & TextFormatFlags.TextBoxControl) == TextFormatFlags.TextBoxControl)
489 sf.FormatFlags |= StringFormatFlags.LineLimit;
492 //if ((flags & TextFormatFlags.RightToLeft) == TextFormatFlags.RightToLeft)
493 // sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
494 if ((flags & TextFormatFlags.NoClipping) == TextFormatFlags.NoClipping)
495 sf.FormatFlags |= StringFormatFlags.NoClip;
500 private static Rectangle PadRectangle (Rectangle r, TextFormatFlags flags)
502 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
506 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
509 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
513 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
516 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
523 private static Rectangle PadDrawStringRectangle (Rectangle r, TextFormatFlags flags)
525 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
529 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
532 if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding) {
535 if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom) {
538 if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
542 if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
545 if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
553 #region DllImports (Windows)
555 [DllImport ("user32", CharSet = CharSet.Unicode, EntryPoint = "DrawText")]
556 static extern int Win32DrawText (IntPtr hdc, string lpStr, int nCount, ref XplatUIWin32.RECT lpRect, int wFormat);
558 [DllImport ("gdi32")]
559 static extern int SetTextColor (IntPtr hdc, int crColor);
561 [DllImport ("gdi32")]
562 static extern IntPtr SelectObject (IntPtr hDC, IntPtr hObject);
564 [DllImport ("gdi32")]
565 static extern int SetBkColor (IntPtr hdc, int crColor);
567 [DllImport ("gdi32")]
568 static extern int SetBkMode (IntPtr hdc, int iBkMode);
570 [DllImport ("gdi32")]
571 static extern bool DeleteObject (IntPtr objectHandle);
574 static extern bool SelectClipRgn(IntPtr hdc, IntPtr hrgn);