Jo Shields a575963da9 Imported Upstream version 3.6.0
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
2014-08-13 10:39:27 +01:00

522 lines
19 KiB
C#

//
// TextRenderer.cs
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
// Copyright (c) 2006 Novell, Inc.
//
// Authors:
// Jonathan Pobst (monkey@jpobst.com)
//
// This has become a monster class for all things text measuring and drawing.
//
// The public API is MeasureText/DrawText, which uses GDI on Win32, and
// GDI+ on other platforms.
//
// There is an internal API MeasureTextInternal/DrawTextInternal, which allows
// you to pass a flag of whether to use GDI or GDI+. This is used mainly for
// controls that have the UseCompatibleTextRendering flag.
//
// There are also thread-safe versions of MeasureString/MeasureCharacterRanges
// for things that want to measure strings without having a Graphics object.
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
using System.Drawing.Text;
namespace System.Windows.Forms
{
public sealed class TextRenderer
{
private TextRenderer ()
{
}
#region Public Methods
public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor)
{
DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor)
{
DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor)
{
DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags)
{
DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor)
{
DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags)
{
DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags)
{
DrawTextInternal (dc, text, font, pt, foreColor, backColor, flags, false);
}
public static void DrawText (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags)
{
DrawTextInternal (dc, text, font, bounds, foreColor, backColor, flags, false);
}
public static Size MeasureText (string text, Font font)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, Size.Empty, TextFormatFlags.Default, false);
}
public static Size MeasureText (IDeviceContext dc, string text, Font font)
{
return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, false);
}
public static Size MeasureText (string text, Font font, Size proposedSize)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, TextFormatFlags.Default, false);
}
public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize)
{
return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, false);
}
public static Size MeasureText (string text, Font font, Size proposedSize, TextFormatFlags flags)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, flags, false);
}
public static Size MeasureText (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags)
{
return MeasureTextInternal (dc, text, font, proposedSize, flags, false);
}
#endregion
#region Internal Methods That Do Stuff
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
{
if (dc == null)
throw new ArgumentNullException ("dc");
if (text == null || text.Length == 0)
return;
// We use MS GDI API's unless told not to, or we aren't on Windows
if (!useDrawString && !XplatUI.RunningOnUnix) {
if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter || (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
flags |= TextFormatFlags.SingleLine;
// Calculate the text bounds (there is often padding added)
Rectangle new_bounds = PadRectangle (bounds, flags);
new_bounds.Offset ((int)(dc as Graphics).Transform.OffsetX, (int)(dc as Graphics).Transform.OffsetY);
IntPtr hdc = IntPtr.Zero;
bool clear_clip_region = false;
// If we need to use the graphics clipping region, add it to our hdc
if ((flags & TextFormatFlags.PreserveGraphicsClipping) == TextFormatFlags.PreserveGraphicsClipping) {
Graphics graphics = (Graphics)dc;
Region clip_region = graphics.Clip;
if (!clip_region.IsInfinite (graphics)) {
IntPtr hrgn = clip_region.GetHrgn (graphics);
hdc = dc.GetHdc ();
SelectClipRgn (hdc, hrgn);
DeleteObject (hrgn);
clear_clip_region = true;
}
}
if (hdc == IntPtr.Zero)
hdc = dc.GetHdc ();
// Set the fore color
if (foreColor != Color.Empty)
SetTextColor (hdc, ColorTranslator.ToWin32 (foreColor));
// Set the back color
if (backColor != Color.Transparent && backColor != Color.Empty) {
SetBkMode (hdc, 2); //1-Transparent, 2-Opaque
SetBkColor (hdc, ColorTranslator.ToWin32 (backColor));
}
else {
SetBkMode (hdc, 1); //1-Transparent, 2-Opaque
}
XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new_bounds);
IntPtr prevobj;
if (font != null) {
prevobj = SelectObject (hdc, font.ToHfont ());
Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
prevobj = SelectObject (hdc, prevobj);
DeleteObject (prevobj);
}
else {
Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
}
if (clear_clip_region)
SelectClipRgn (hdc, IntPtr.Zero);
dc.ReleaseHdc ();
}
// Use Graphics.DrawString as a fallback method
else {
Graphics g;
IntPtr hdc = IntPtr.Zero;
if (dc is Graphics)
g = (Graphics)dc;
else {
hdc = dc.GetHdc ();
g = Graphics.FromHdc (hdc);
}
StringFormat sf = FlagsToStringFormat (flags);
Rectangle new_bounds = PadDrawStringRectangle (bounds, flags);
g.DrawString (text, font, ThemeEngine.Current.ResPool.GetSolidBrush (foreColor), new_bounds, sf);
if (!(dc is Graphics)) {
g.Dispose ();
dc.ReleaseHdc ();
}
}
}
internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
{
if (!useMeasureString && !XplatUI.RunningOnUnix) {
// Tell DrawText to calculate size instead of draw
flags |= (TextFormatFlags)1024; // DT_CALCRECT
IntPtr hdc = dc.GetHdc ();
XplatUIWin32.RECT r = XplatUIWin32.RECT.FromRectangle (new Rectangle (Point.Empty, proposedSize));
IntPtr prevobj;
if (font != null) {
prevobj = SelectObject (hdc, font.ToHfont ());
Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
prevobj = SelectObject (hdc, prevobj);
DeleteObject (prevobj);
}
else {
Win32DrawText (hdc, text, text.Length, ref r, (int)flags);
}
dc.ReleaseHdc ();
// Really, I am just making something up here, which as far as I can tell, MS
// just makes something up as well. This will require lots of tweaking to match MS. :(
Size retval = r.ToRectangle ().Size;
if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0) {
retval.Width += 6;
retval.Width += (int)retval.Height / 8;
}
return retval;
}
else {
StringFormat sf = FlagsToStringFormat (flags);
Size retval;
if (dc is Graphics)
retval = (dc as Graphics).MeasureString (text, font, proposedSize.Width == 0 ? Int32.MaxValue : proposedSize.Width, sf).ToSize ();
else
retval = TextRenderer.MeasureString (text, font, proposedSize.Width == 0 ? Int32.MaxValue : proposedSize.Width, sf).ToSize ();
if (retval.Width > 0 && (flags & TextFormatFlags.NoPadding) == 0)
retval.Width += 9;
return retval;
}
}
#endregion
#region Internal Methods That Are Just Overloads
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, bool useDrawString)
{
DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, TextFormatFlags.Default, useDrawString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, bool useDrawString)
{
DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, bool useDrawString)
{
DrawTextInternal (dc, text, font, pt, foreColor, backColor, TextFormatFlags.Default, useDrawString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, TextFormatFlags flags, bool useDrawString)
{
DrawTextInternal (dc, text, font, pt, foreColor, Color.Transparent, flags, useDrawString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, Color backColor, bool useDrawString)
{
DrawTextInternal (dc, text, font, bounds, foreColor, backColor, TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter, useDrawString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags, bool useDrawString)
{
DrawTextInternal (dc, text, font, bounds, foreColor, Color.Transparent, flags, useDrawString);
}
internal static Size MeasureTextInternal (string text, Font font, bool useMeasureString)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
}
internal static void DrawTextInternal (IDeviceContext dc, string text, Font font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags, bool useDrawString)
{
Size sz = MeasureTextInternal (dc, text, font, useDrawString);
DrawTextInternal (dc, text, font, new Rectangle (pt, sz), foreColor, backColor, flags, useDrawString);
}
internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, bool useMeasureString)
{
return MeasureTextInternal (dc, text, font, Size.Empty, TextFormatFlags.Default, useMeasureString);
}
internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, bool useMeasureString)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
}
internal static Size MeasureTextInternal (IDeviceContext dc, string text, Font font, Size proposedSize, bool useMeasureString)
{
return MeasureTextInternal (dc, text, font, proposedSize, TextFormatFlags.Default, useMeasureString);
}
internal static Size MeasureTextInternal (string text, Font font, Size proposedSize, TextFormatFlags flags, bool useMeasureString)
{
return MeasureTextInternal (Hwnd.GraphicsContext, text, font, proposedSize, flags, useMeasureString);
}
#endregion
#region Thread-Safe Static Graphics Methods
internal static SizeF MeasureString (string text, Font font)
{
return Hwnd.GraphicsContext.MeasureString (text, font);
}
internal static SizeF MeasureString (string text, Font font, int width)
{
return Hwnd.GraphicsContext.MeasureString (text, font, width);
}
internal static SizeF MeasureString (string text, Font font, SizeF layoutArea)
{
return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea);
}
internal static SizeF MeasureString (string text, Font font, int width, StringFormat format)
{
return Hwnd.GraphicsContext.MeasureString (text, font, width, format);
}
internal static SizeF MeasureString (string text, Font font, PointF origin, StringFormat stringFormat)
{
return Hwnd.GraphicsContext.MeasureString (text, font, origin, stringFormat);
}
internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat)
{
return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat);
}
internal static SizeF MeasureString (string text, Font font, SizeF layoutArea, StringFormat stringFormat, out int charactersFitted, out int linesFilled)
{
return Hwnd.GraphicsContext.MeasureString (text, font, layoutArea, stringFormat, out charactersFitted, out linesFilled);
}
internal static Region[] MeasureCharacterRanges (string text, Font font, RectangleF layoutRect, StringFormat stringFormat)
{
return Hwnd.GraphicsContext.MeasureCharacterRanges (text, font, layoutRect, stringFormat);
}
internal static SizeF GetDpi ()
{
return new SizeF (Hwnd.GraphicsContext.DpiX, Hwnd.GraphicsContext.DpiY);
}
#endregion
#region Private Methods
private static StringFormat FlagsToStringFormat (TextFormatFlags flags)
{
StringFormat sf = new StringFormat ();
// Translation table: http://msdn.microsoft.com/msdnmag/issues/06/03/TextRendering/default.aspx?fig=true#fig4
// Horizontal Alignment
if ((flags & TextFormatFlags.HorizontalCenter) == TextFormatFlags.HorizontalCenter)
sf.Alignment = StringAlignment.Center;
else if ((flags & TextFormatFlags.Right) == TextFormatFlags.Right)
sf.Alignment = StringAlignment.Far;
else
sf.Alignment = StringAlignment.Near;
// Vertical Alignment
if ((flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom)
sf.LineAlignment = StringAlignment.Far;
else if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter)
sf.LineAlignment = StringAlignment.Center;
else
sf.LineAlignment = StringAlignment.Near;
// Ellipsis
if ((flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis)
sf.Trimming = StringTrimming.EllipsisCharacter;
else if ((flags & TextFormatFlags.PathEllipsis) == TextFormatFlags.PathEllipsis)
sf.Trimming = StringTrimming.EllipsisPath;
else if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis)
sf.Trimming = StringTrimming.EllipsisWord;
else
sf.Trimming = StringTrimming.Character;
// Hotkey Prefix
if ((flags & TextFormatFlags.NoPrefix) == TextFormatFlags.NoPrefix)
sf.HotkeyPrefix = HotkeyPrefix.None;
else if ((flags & TextFormatFlags.HidePrefix) == TextFormatFlags.HidePrefix)
sf.HotkeyPrefix = HotkeyPrefix.Hide;
else
sf.HotkeyPrefix = HotkeyPrefix.Show;
// Text Padding
if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding)
sf.FormatFlags |= StringFormatFlags.FitBlackBox;
// Text Wrapping
if ((flags & TextFormatFlags.SingleLine) == TextFormatFlags.SingleLine)
sf.FormatFlags |= StringFormatFlags.NoWrap;
else if ((flags & TextFormatFlags.TextBoxControl) == TextFormatFlags.TextBoxControl)
sf.FormatFlags |= StringFormatFlags.LineLimit;
// Other Flags
//if ((flags & TextFormatFlags.RightToLeft) == TextFormatFlags.RightToLeft)
// sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft;
if ((flags & TextFormatFlags.NoClipping) == TextFormatFlags.NoClipping)
sf.FormatFlags |= StringFormatFlags.NoClip;
return sf;
}
private static Rectangle PadRectangle (Rectangle r, TextFormatFlags flags)
{
if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
r.X += 3;
r.Width -= 3;
}
if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
r.Width -= 4;
}
if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
r.X += 2;
r.Width -= 2;
}
if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
r.Width -= 4;
}
if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter) {
r.Y += 1;
}
return r;
}
private static Rectangle PadDrawStringRectangle (Rectangle r, TextFormatFlags flags)
{
if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == 0 && (flags & TextFormatFlags.HorizontalCenter) == 0) {
r.X += 1;
r.Width -= 1;
}
if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Right) == TextFormatFlags.Right) {
r.Width -= 4;
}
if ((flags & TextFormatFlags.NoPadding) == TextFormatFlags.NoPadding) {
r.X -= 2;
}
if ((flags & TextFormatFlags.NoPadding) == 0 && (flags & TextFormatFlags.Bottom) == TextFormatFlags.Bottom) {
r.Y += 1;
}
if ((flags & TextFormatFlags.LeftAndRightPadding) == TextFormatFlags.LeftAndRightPadding) {
r.X += 2;
r.Width -= 2;
}
if ((flags & TextFormatFlags.WordEllipsis) == TextFormatFlags.WordEllipsis || (flags & TextFormatFlags.EndEllipsis) == TextFormatFlags.EndEllipsis || (flags & TextFormatFlags.WordBreak) == TextFormatFlags.WordBreak) {
r.Width -= 4;
}
if ((flags & TextFormatFlags.VerticalCenter) == TextFormatFlags.VerticalCenter && XplatUI.RunningOnUnix) {
r.Y -= 1;
}
return r;
}
#endregion
#region DllImports (Windows)
[DllImport ("user32", CharSet = CharSet.Unicode, EntryPoint = "DrawText")]
static extern int Win32DrawText (IntPtr hdc, string lpStr, int nCount, ref XplatUIWin32.RECT lpRect, int wFormat);
[DllImport ("gdi32")]
static extern int SetTextColor (IntPtr hdc, int crColor);
[DllImport ("gdi32")]
static extern IntPtr SelectObject (IntPtr hDC, IntPtr hObject);
[DllImport ("gdi32")]
static extern int SetBkColor (IntPtr hdc, int crColor);
[DllImport ("gdi32")]
static extern int SetBkMode (IntPtr hdc, int iBkMode);
[DllImport ("gdi32")]
static extern bool DeleteObject (IntPtr objectHandle);
[DllImport("gdi32")]
static extern bool SelectClipRgn(IntPtr hdc, IntPtr hrgn);
#endregion
}
}