//------------------------------------------------------------------------------
// <copyright file=”*.cs” company=”EhLib Team”>
//     Copyright (c) 2017 Dmitry V. Bolshakov   
// </copyright>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources", Scope = "member", Target = "EhLib.DisplayGraphics.#screenDC")]

namespace EhLib
{

  public enum DropDownAlign
  {
    Left, Right, Center
  }

  public enum DropLayout
  {
    AboveControl, UnderControl
  }

  public enum UsedGraphicEngine
  {
    // ReSharper disable once InconsistentNaming
    WinFormsGDI,
    // ReSharper disable once InconsistentNaming
    WinFormsGDIPlus,
    NativeGDI,
    AsGlobalDefined
  }

  [Flags]
  public enum TextFormatFlagsEh
  {
    None,
    WordBreak,
    SingleLine,
    EndEllipsis,
    RightToLeft
  }

  public struct DrawStringLineInfo
  {
    private int first;
    private int length;
    private int lengthPixels;
    private int heightPixels;

    public DrawStringLineInfo(int first, int length, int lengthPixels, int heightPixels)
    {
      this.first = first;
      this.length = length;
      this.lengthPixels = lengthPixels;
      this.heightPixels = heightPixels;
    }

    public int First
    {
      get { return first; }
      set { first = value; }
    }

    public int Length
    {
      get { return length; }
      set { length = value; }
    }

    public int LengthPixels
    {
      get { return lengthPixels; }
      set { lengthPixels = value; }
    }

    public int HeightPixels
    {
      get { return heightPixels; }
      set { heightPixels = value; }
    }

  }

  public static class EhLibUtils
  {

    private static RichTextBox toolRichTextBox;

    static EhLibUtils()
    {
    }

    public const string LineCaption = "-";
    //public const string EhLibDesignDesignerVersionInfo = ", EhLib.WinForms.Design, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d379614f4f89b099";
    public const string EhLibDesignDesignerVersionInfo = "";

    public static bool DebugPaint { get; set; }
    public static bool DebugIgnoreMouseCancelMode { get; set; }

    public static Graphics DisplayGraphicsCash
    {
      get
      {
        return EhLibUtilsInternal.DisplayGraphicsCash;
      }
    }

    public static bool CanDrawWordBreak(Graphics gr, Rectangle paintRect, Font font, Padding padding)
    {
      return EhLibUtilsInternal.CanDrawWordBreak(gr, paintRect, font, padding);
    }

    internal static int[] StringAllIndexsOf(string srcString, string value, StringComparison stringComparison, bool wholeWord)
    {
      return EhLibUtilsInternal.StringAllIndexsOf(srcString, value, stringComparison, wholeWord);
    }

    public static void FillRectangleGradient(Graphics g, Rectangle rect, Color fromColor, Color toColor)
    {
      EhLibUtilsInternal.FillRectangleGradient(g, rect, fromColor, toColor);
    }

    public static void FillRectangle(Graphics g, Rectangle rect, Color color)
    {
      EhLibUtilsInternal.FillRectangle(g, rect, color);
    }

    //private static int TextHeight(Graphics g, string text, Font font)
    //{
    //  return EhLibUtilsInternal.TextHeight(g, text, font);
    //}

    public static void DrawText(Graphics g, string text, Font font, Rectangle bounds,
      Color foreColor,
      HorizontalAlignment horzAlign,
      VerticalAlignment vertAlign,
      TextFormatFlagsEh flags,
      UsedGraphicEngine graphicEngine = UsedGraphicEngine.AsGlobalDefined
      )
    {
      EhLibUtilsInternal.DrawText(g, text, font, bounds, foreColor, horzAlign, vertAlign, flags, graphicEngine);
    }

    public static Size MeasureText(Graphics g, string text, Font font)
    {
      return EhLibUtilsInternal.MeasureText(g, text, font);
    }

    public static Size MeasureText(Graphics g, string text, Font font, Size proposedSize, DataGridTextWrapMode wrapMode)
    {
      return EhLibUtilsInternal.MeasureText(g, text, font, proposedSize, wrapMode);
    }

    internal static string PlainTextToRtfText(string centerText)
    {
      return PlainTextToRtfText(centerText, HorizontalAlignment.Left);
    }

    internal static string PlainTextToRtfText(string plainText, HorizontalAlignment align)
    {
      if (toolRichTextBox == null)
        toolRichTextBox = new RichTextBox();
      toolRichTextBox.Text = plainText;

      toolRichTextBox.SelectAll();
      toolRichTextBox.SelectionAlignment = align;
      toolRichTextBox.DeselectAll();

      return toolRichTextBox.Rtf;
    }

    public static Size MeasureText(Graphics g, string text, Font font, Size proposedSize, DataGridTextWrapMode wrapMode, ref DrawStringLineInfo[] wrappedLines)
    {
      return EhLibUtilsInternal.MeasureText(g, text, font, proposedSize, wrapMode, ref wrappedLines);
    }

    public static Rectangle[] MeasureCharacterRanges(Graphics g, string text, Font font,
      Rectangle layoutRect, HorizontalAlignment horzAlign, VerticalAlignment vertAlign,
      /*DataGridTextWrapMode wrapMode,*/ bool wordWrap,
      CharacterRange[] charRanges)
    {
      return EhLibUtilsInternal.MeasureCharacterRanges(g, text, font, layoutRect,
        horzAlign, vertAlign, wordWrap, charRanges);
    }

    // ReSharper disable once InconsistentNaming
    public static bool DBValueEqual(object x, object y)
    {
      return EhLibUtilsInternal.DBValueEqual(x, y);
    }

    // ReSharper disable once InconsistentNaming
    public static int DBCompareValues(object x, object y)
    {
      return EhLibUtilsInternal.DBCompareValues(x, y);
    }

    public static Rectangle[] MeasureCharacterRangesStringLine(Graphics g, string text, Font font,
      Rectangle layoutRect, HorizontalAlignment horzAlign, VerticalAlignment vertAlign,
      CharacterRange[] charRanges)
    {
      return EhLibUtilsInternal.MeasureCharacterRangesStringLine(g, text, font, layoutRect, horzAlign, vertAlign, charRanges);
    }

    public static int GetFontHeight(Font font)
    {
      return EhLibUtilsInternal.GetFontHeight(font);
    }

    public static Rectangle TrimPadding(Rectangle rect, Padding padding)
    {
      rect.Y += padding.Top;
      rect.X += padding.Left;
      rect.Width -= padding.Horizontal;
      rect.Height -= padding.Vertical;

      return rect;
    }

    public static TextFormatFlags TranslateContentAlignmentToTextFormatFlags(System.Drawing.ContentAlignment align)
    {
      return EhLibUtilsInternal.TranslateContentAlignmentToTextFormatFlags(align);
    }

    public static TextFormatFlags TranslateAlignmentToTextFormatFlags(HorizontalAlignment horzAlign, VerticalAlignment vertAlign)
    {
      return EhLibUtilsInternal.TranslateAlignmentToTextFormatFlags(horzAlign, vertAlign);
    }

    public static StringAlignment HorizontalAlignmentToStringAlignment(HorizontalAlignment horzAlign)
    {
      return EhLibUtilsInternal.HorizontalAlignmentToStringAlignment(horzAlign);
    }

    public static StringAlignment VerticalAlignmentToStringAlignment(VerticalAlignment vertAlign)
    {
      return EhLibUtilsInternal.VerticalAlignmentToStringAlignment(vertAlign);
    }

    public static Rectangle RectCenter(Rectangle r, Rectangle bounds)
    {
      Rectangle centerRect = r;
      centerRect.X = bounds.Left + (bounds.Width - centerRect.Width) / 2;
      centerRect.Y = bounds.Top + (bounds.Height - centerRect.Height) / 2;
      return centerRect;
    }

    public static IntPtr SendMessage(Control control, int msg, IntPtr wParam, IntPtr lParam)
    {
      return EhLibUtilsInternal.SendMessage(control, msg, wParam, lParam);
    }

    public static IntPtr SendMouseDownMessage(Control control, Point mousePos, MouseButtons mouseButton)
    {
      return EhLibUtilsInternal.SendMouseDownMessage(control, mousePos, mouseButton);
    }

    internal static bool PeekMessage(Control control, out Message msg, uint msgMin, uint msgMax, bool isRemoveMsg)
    {
      return EhLibUtilsInternal.PeekMessage(control, out msg, msgMin, msgMax, isRemoveMsg);
    }

    public static Rectangle GetControlWindowRect(Control control)
    {
      return EhLibUtilsInternal.GetControlWindowRect(control);
    }

    public static void ScrollControlWindow(Control control, int xAmount, int yAmount, Rectangle rectScrollRegion)
    {
      EhLibUtilsInternal.ScrollControlWindow(control, xAmount, yAmount, rectScrollRegion);
    }

    public static bool IsCriticalException(Exception ex)
    {
      return ex is NullReferenceException
              || ex is StackOverflowException
              || ex is OutOfMemoryException
              || ex is System.Threading.ThreadAbortException
              //              || ex is ExecutionEngineException
              || ex is IndexOutOfRangeException
              || ex is AccessViolationException;
    }

    public static PushButtonState GetPushButtonState(bool isHot, bool isPressed, bool isDisabled)
    {
      return EhLibUtilsInternal.GetPushButtonState(isHot, isPressed, isDisabled);
    }

    public static Color ApproximateColor(Color fromColor, Color toColor, double quota)
    {
      return EhLibUtilsInternal.ApproximateColor(fromColor, toColor, quota);
    }

    //
    // https://blogs.msdn.microsoft.com/cjacks/2006/04/12/converting-from-hsb-to-rgb-in-net/
    //
    public static Color ColorFromAHSB(int a, float h, float s, float b)
    {
      return EhLibUtilsInternal.ColorFromAHSB(a, h, s, b);
    }

    public static void FillGradient(Graphics g, Point topLeft, Point[] points, Color fromColor, Color toColor)
    {
      EhLibUtilsInternal.FillGradient(g, topLeft, points, fromColor, toColor);
    }

    public static void DrawImage(Graphics g, Image image, Rectangle destRect, Rectangle srcRect, int transparencyLevel, bool greyScale)
    {
      EhLibUtilsInternal.DrawImage(g, image, destRect, srcRect, transparencyLevel, greyScale);
    }

    public static void DrawFitImage(Graphics g, Image image, Rectangle drawRect, HorizontalAlignment horzAlign, VerticalAlignment vertAlign)
    {
      Size imageSize = image.Size;
      Rectangle imageRect = Rectangle.Empty;

      imageRect.Size = GetFittedImageSize(imageSize, drawRect.Size);

      switch (horzAlign)
      {
        case HorizontalAlignment.Left:
          imageRect.X = drawRect.X;
          break;
        case HorizontalAlignment.Center:
          imageRect.X = drawRect.X + (drawRect.Width - imageRect.Width) / 2;
          break;
        case HorizontalAlignment.Right:
          imageRect.X = drawRect.Right - imageRect.Width;
          break;
      }

      switch (vertAlign)
      {
        case VerticalAlignment.Top:
          imageRect.Y = drawRect.Y;
          break;
        case VerticalAlignment.Center:
          imageRect.Y = drawRect.Y + (drawRect.Height - imageRect.Height) / 2;
          break;
        case VerticalAlignment.Bottom:
          imageRect.Y = drawRect.Bottom - imageRect.Height;
          break;
      }

      g.DrawImage(image, imageRect);
    }

    public static Size GetFittedImageSize(Size imageSize, Size sizeToFit)
    {
      //Size imageSize = image.Size;
      Size fittedSize = imageSize;

      if (imageSize.Width > sizeToFit.Width && (double)sizeToFit.Width / imageSize.Width < (double)sizeToFit.Height / imageSize.Height)
      {
        fittedSize.Width = sizeToFit.Width;
        fittedSize.Height = (int)Math.Round((double)imageSize.Height * sizeToFit.Width / imageSize.Width);
      }
      else if (imageSize.Height > sizeToFit.Height)
      {
        fittedSize.Height = sizeToFit.Height;
        fittedSize.Width = (int)Math.Round((double)imageSize.Width * sizeToFit.Height / imageSize.Height);
      }
      return fittedSize;
    }

    public static void SplitIntPtr(IntPtr value, out int x, out int y)
    {
      EhLibUtilsInternal.SplitIntPtr(value, out x, out y);
    }

    public static bool IsAnyKeyPressed(Control control)
    {
      return EhLibUtilsInternal.IsAnyKeyPressed(control);
    }

    public static bool CheckKeyPressed(Control control, Keys key, bool isRemoveMsg)
    {
      return EhLibUtilsInternal.CheckKeyPressed(control, key, isRemoveMsg);
    }

    public static Control GetFocusControl()
    {
      return EhLibUtilsInternal.GetFocusControl();
    }

    public static Control ControlFromScreenPoint(Point screenPos)
    {
      return EhLibUtilsInternal.ControlFromScreenPoint(screenPos);
    }

    public static VerticalDropLayout AlignDropDownWindowRect(Rectangle hostScreenRect, Control dropDownWin, DropDownAlign align)
    {
      return EhLibUtilsInternal.AlignDropDownWindowRect(hostScreenRect, dropDownWin, align);
    }

    public static bool StringEquals(string a, string b, bool ignoreCase, bool partialKey)
    {
      return EhLibUtilsInternal.StringEquals(a, b, ignoreCase, partialKey);
    }

    public static string SafeSubstring(this string text, int start, int length)
    {
      return EhLibUtilsInternal.SafeSubstring(text, start, length);
    }

    public static Rectangle RightToLeftFlipRectangle(Rectangle baseRect, Rectangle rect)
    {
      return EhLibUtilsInternal.RightToLeftFlipRectangle(baseRect, rect);
    }

    public static Point RightToLeftFlipPoint(Rectangle baseRect, Point point)
    {
      return EhLibUtilsInternal.RightToLeftFlipPoint(baseRect, point);
    }

    public static void DoNothing()
    {
    }

    public static DialogResult InputBox(string title, string promptText, ref string value)
    {
      return EhLibUtilsInternal.InputBox(title, promptText, ref value);
    }

    //public static Size FixSizeByCellCustomAreaMeasures(Size srcSize, CellCustomAreaMeasures areaMeasures)
    //{
    //  Size result = srcSize;
    //  result.Height = result.Height + areaMeasures.TopSide.Height + areaMeasures.BottomSide.Height;
    //  result.Width = result.Width + areaMeasures.LeftSide.Width + areaMeasures.RightSide.Width;
    //  result.Height = Math.Max(Math.Max(areaMeasures.LeftSide.Height, areaMeasures.RightSide.Height), result.Height);
    //  result.Width = Math.Max(Math.Max(areaMeasures.TopSide.Width, areaMeasures.BottomSide.Width), result.Width);
    //  return result;
    //}

    //public static Rectangle ReduceRectCellCustomAreaMeasures(Rectangle srcRect, CellCustomAreaMeasures areaMeasures)
    //{
    //  Rectangle result = srcRect;

    //  result.Width = result.Width - areaMeasures.LeftSide.Width - areaMeasures.RightSide.Width;
    //  result.X = result.X + areaMeasures.LeftSide.Width;

    //  result.Height = result.Height - areaMeasures.TopSide.Height - areaMeasures.BottomSide.Height;
    //  result.Y = result.Y + areaMeasures.TopSide.Height;

    //  return result;
    //}

    public static string TruncString(string str, int threshold)
    {
      if (str.Length > threshold)
        return str.Substring(0, threshold) + "...";
      return str;
    }

    public static MouseButtons MouseButtonsAsyn
    {
      get
      {
        return EhLibUtilsInternal.MouseButtonsAsyn;
      }
    }

    internal static Point ApproximatePoint(Point sourcePoint, Point distantPoint, double factor)
    {
      Point result = Point.Empty;
      result.X = sourcePoint.X + (int)Math.Round((distantPoint.X - sourcePoint.X) * factor);
      result.Y = sourcePoint.Y + (int)Math.Round((distantPoint.Y - sourcePoint.Y) * factor);
      return result;
    }

    internal static Rectangle ApproximateRectangle(Rectangle sourceRect, Rectangle distantRect, double factor)
    {
      Rectangle result = Rectangle.Empty;
      result.X = sourceRect.X + (int)Math.Round((distantRect.X - sourceRect.X) * factor);
      result.Y = sourceRect.Y + (int)Math.Round((distantRect.Y - sourceRect.Y) * factor);
      result.Width = sourceRect.Width + (int)Math.Round((distantRect.Width - sourceRect.Width) * factor);
      result.Height = sourceRect.Height + (int)Math.Round((distantRect.Height - sourceRect.Height) * factor);
      return result;
    }

    public static Rectangle ChangeRectangle(Rectangle rect, int changeLeft, int changeTop, int changeWidth, int changeHeight)
    {
      rect.X = rect.X + changeLeft;
      rect.Y = rect.Y + changeTop;
      rect.Width = rect.Width + changeWidth;
      rect.Height = rect.Height + changeHeight;

      return rect;
    }

  }

  internal static class EhLibUtilsInternal
  {
    private static readonly float[][] TransparencyMatrix;
    private static readonly float[][] GreyscaleMatrix;

    private static string _lastFontName = "";
    private static float _lastFontSize;
    private static FontStyle _lastFontStyle = 0;
    private static int _lastFontHeight;
    private static readonly Dictionary<string, Dictionary<float, Dictionary<FontStyle, int>>> FontHeightsCache = new Dictionary<string, Dictionary<float, Dictionary<FontStyle, int>>>(StringComparer.InvariantCultureIgnoreCase);

    //public static UsedGraphicEngine GraphicEngine = UsedGraphicEngine.GDIPlus;
    internal static UsedGraphicEngine GraphicEngine { get; set; }

    static EhLibUtilsInternal()
    {
      //GraphicEngine = UsedGraphicEngine.WinFormsGDI;
      GraphicEngine = UsedGraphicEngine.NativeGDI;

      TransparencyMatrix = new float[5][];
      TransparencyMatrix[0] = new float[] { 1, 0, 0, 0, 0 };
      TransparencyMatrix[1] = new float[] { 0, 1, 0, 0, 0 };
      TransparencyMatrix[2] = new float[] { 0, 0, 1, 0, 0 };
      TransparencyMatrix[3] = new float[] { 0, 0, 0, .5f, 0 };
      TransparencyMatrix[4] = new float[] { 0, 0, 0, 0, 0 };

      GreyscaleMatrix = new float[5][];
      GreyscaleMatrix[0] = new float[] { .3f, .3f, .3f, 0, 0 };
      GreyscaleMatrix[1] = new float[] { .59f, .59f, .59f, 0, 0 };
      GreyscaleMatrix[2] = new float[] { .11f, .11f, .11f, 0, 0 };
      GreyscaleMatrix[3] = new float[] { 0, 0, 0, 1, 0 };
      GreyscaleMatrix[4] = new float[] { 0, 0, 0, 0, 1 };
    }

    private const int AnyRight =
      (int)System.Drawing.ContentAlignment.TopRight | (int)System.Drawing.ContentAlignment.MiddleRight | (int)System.Drawing.ContentAlignment.BottomRight;
    private const int AnyBottom =
      (int)System.Drawing.ContentAlignment.BottomLeft | (int)System.Drawing.ContentAlignment.BottomCenter | (int)System.Drawing.ContentAlignment.BottomRight;
    private const int AnyCenter =
      (int)System.Drawing.ContentAlignment.TopCenter | (int)System.Drawing.ContentAlignment.MiddleCenter | (int)System.Drawing.ContentAlignment.BottomCenter;
    private const int AnyMiddle =
      (int)System.Drawing.ContentAlignment.MiddleLeft | (int)System.Drawing.ContentAlignment.MiddleCenter | (int)System.Drawing.ContentAlignment.MiddleRight;

    private static readonly DisplayGraphics InternalDisplayGraphicsCash = new DisplayGraphics();

    internal static Graphics DisplayGraphicsCash
    {
      get
      {
        return InternalDisplayGraphicsCash.Graphics;
      }
    }

    internal static bool CanDrawWordBreak(Graphics gr,
      Rectangle paintRect, Font font, Padding padding)
    {
      Size sz = TextRenderer.MeasureText(gr, "0", font);
      int th = paintRect.Height - padding.Top - padding.Bottom;

      if (th >= sz.Height * 2)
        return true;
      else
        return false;
    }

    internal static int[] StringAllIndexsOf(string srcString, string value, StringComparison stringComparison, bool wholeWord)
    {
      int pos = -1;
      List<int> posList = new List<int>();

      while (pos < srcString.Length)
      {
        pos = srcString.IndexOf(value, pos + 1, stringComparison);
        if (pos >= 0)
          posList.Add(pos);
        else
          break;
      }

      return posList.ToArray();
    }

    internal static void FillRectangleGradient(Graphics g, Rectangle rect, Color fromColor, Color toColor)
    {
      Rectangle rec1 = rect;
      rec1.Y = rec1.Y - 1;
      rec1.Height = rec1.Height + 1;
      using (
        LinearGradientBrush lgb = new LinearGradientBrush(rec1, fromColor, toColor, 90))
      {
        g.FillRectangle(lgb, rect);
      }
    }

    internal static void FillRectangle(Graphics g, Rectangle rect, Color color)
    {
      using (
        SolidBrush fillBrush = new SolidBrush(color))
      {
        g.FillRectangle(fillBrush, rect);
      }

    }

    internal static int TextHeight(Graphics g, string text, Font font)
    {
      if (GraphicEngine == UsedGraphicEngine.WinFormsGDI ||
          GraphicEngine == UsedGraphicEngine.NativeGDI)
      {
        Size sz = TextRenderer.MeasureText(g, text, font);
        return sz.Height;
      }
      else
      {
        SizeF sz = g.MeasureString(text, font);
        return (int)sz.Height;
      }
    }

    internal static void DrawText(Graphics g, string text, Font font, Rectangle bounds,
      Color foreColor,
      HorizontalAlignment horzAlign,
      VerticalAlignment vertAlign,
      TextFormatFlagsEh flags,
      UsedGraphicEngine graphicEngine = UsedGraphicEngine.AsGlobalDefined
      )
    {
      //-      return;
      if ((graphicEngine == UsedGraphicEngine.WinFormsGDI) ||
          (graphicEngine == UsedGraphicEngine.AsGlobalDefined && GraphicEngine == UsedGraphicEngine.WinFormsGDI))
      {
        TextFormatFlags fmtFlags =
          TranslateAlignmentToTextFormatFlags(horzAlign, vertAlign) |
          TextFormatFlags.NoPadding |
          TextFormatFlags.PreserveGraphicsClipping;
        if ((flags & TextFormatFlagsEh.WordBreak) != 0)
          fmtFlags = fmtFlags | TextFormatFlags.WordBreak;

        TextRenderer.DrawText(g, text, font, bounds, foreColor, fmtFlags);

      }
      else if ((graphicEngine == UsedGraphicEngine.NativeGDI) ||
          (graphicEngine == UsedGraphicEngine.AsGlobalDefined && GraphicEngine == UsedGraphicEngine.NativeGDI))
      {
        TextFormatFlags fmtFlags =
          TranslateAlignmentToTextFormatFlags(horzAlign, vertAlign) |
          TextFormatFlags.NoPadding |
          TextFormatFlags.PreserveGraphicsClipping;
        if ((flags & TextFormatFlagsEh.WordBreak) != 0)
          fmtFlags = fmtFlags | TextFormatFlags.WordBreak;

        using (GDITextRenderer gdiTextRenderer = new GDITextRenderer(g))
        {
          gdiTextRenderer.DrawString(text, font, foreColor, bounds, fmtFlags);
        }
      }
      else
      {
        using (SolidBrush drawBrush = new SolidBrush(foreColor))
        {
          using (StringFormat drawFormat = StringFormat.GenericTypographic)
          {
            RectangleF rectf = bounds;

            if ((flags & TextFormatFlagsEh.WordBreak) == 0)
              drawFormat.FormatFlags = StringFormatFlags.NoWrap;
            else
              drawFormat.FormatFlags = 0;
            drawFormat.Alignment = HorizontalAlignmentToStringAlignment(horzAlign);
            drawFormat.LineAlignment = VerticalAlignmentToStringAlignment(vertAlign);
            drawFormat.Trimming = StringTrimming.None;
            g.DrawString(text, font, drawBrush, rectf, drawFormat);
          }
        }
      }
    }

    internal static Size MeasureText(Graphics g, string text, Font font)
    {
      if (GraphicEngine == UsedGraphicEngine.WinFormsGDI ||
          GraphicEngine == UsedGraphicEngine.NativeGDI)
      {
        return MeasureText(g, text, font, new Size(0, 0), DataGridTextWrapMode.NoWrap);
      }
      else
      {
        throw new NotSupportedException(@"MeasureText for GDIPlus is not Implemented");
      }
    }

    internal static Size MeasureText(Graphics g, string text, Font font, Size proposedSize, DataGridTextWrapMode wrapMode)
    {
      if (GraphicEngine == UsedGraphicEngine.WinFormsGDI ||
          GraphicEngine == UsedGraphicEngine.NativeGDI)
      {
        DrawStringLineInfo[] wrapedLines = null;
        Size result = MeasureText(g, text, font, proposedSize, wrapMode, ref wrapedLines);
        return result;
      }
      else
      {
        throw new NotSupportedException(@"MeasureText for GDIPlus is not Implemented");
      }
    }

    internal static Size MeasureText(Graphics g, string text, Font font, Size proposedSize, DataGridTextWrapMode wrapMode, ref DrawStringLineInfo[] wrappedLines)
    {
      if (GraphicEngine == UsedGraphicEngine.WinFormsGDI ||
          GraphicEngine == UsedGraphicEngine.NativeGDI)
      {
        using (GDITextRenderer ntr = new GDITextRenderer(g))
        {
          bool ml = wrapMode == DataGridTextWrapMode.WordWrap || wrapMode == DataGridTextWrapMode.Auto;
          Size result = ntr.MeasureStringWrap(text, font, proposedSize.Width, ml, ref wrappedLines);
          return result;
        }
      }
      else
      {
        throw new NotSupportedException(@"MeasureText for GDIPlus is not Implemented");
      }
    }

    internal static Rectangle[] MeasureCharacterRanges(Graphics g, string text, Font font,
      Rectangle layoutRect, HorizontalAlignment horzAlign, VerticalAlignment vertAlign,
      /*DataGridTextWrapMode wrapMode,*/ bool wordWrap,
      CharacterRange[] charRanges)
    {
      if (wordWrap)
      {
        List<Rectangle> result = new List<Rectangle>();
        DrawStringLineInfo[] wrappedLines = new DrawStringLineInfo[] { };
        List<CharacterRange> oneLineCharRanges = new List<CharacterRange>();
        int oneLineHeight = GetFontHeight(font);
        Rectangle oneLineRect = new Rectangle(layoutRect.X, 0, layoutRect.Width, oneLineHeight);
        int lineIndex = 0;

        MeasureText(g, text, font, layoutRect.Size, DataGridTextWrapMode.WordWrap, ref wrappedLines);

        foreach (DrawStringLineInfo line in wrappedLines)
        {
          oneLineCharRanges.Clear();

          foreach (CharacterRange charRange in charRanges)
          {
            if ((charRange.First >= line.First) &&
                (charRange.First < line.First + line.Length))
            {
              CharacterRange charRangeForAdd = charRange;
              charRangeForAdd.First = charRange.First - line.First;
              oneLineCharRanges.Add(charRangeForAdd);
            }


          }

          string oneLineStr = text.Substring(line.First, line.Length);
          oneLineRect.Y = lineIndex * oneLineHeight + layoutRect.Top;

          Rectangle[] oneLineRecs = MeasureCharacterRangesStringLine(g, oneLineStr, font, oneLineRect,
                                      horzAlign, VerticalAlignment.Top, oneLineCharRanges.ToArray());
          result.AddRange(oneLineRecs);

          lineIndex = lineIndex + 1;
        }

        return result.ToArray();
      }
      else
        return MeasureCharacterRangesStringLine(g, text, font, layoutRect,
          horzAlign, vertAlign, charRanges);
    }

    // ReSharper disable once InconsistentNaming
    internal static bool DBValueEqual(object x, object y)
    {
      if ((x == null || Convert.IsDBNull(x)) && ((y == null) || Convert.IsDBNull(y)))
        return true;
      else if (x == null || Convert.IsDBNull(x))
        return false;
      else if (y == null || Convert.IsDBNull(y))
        return false;
      else
      {
        IComparable cmp = x as IComparable;
        if (cmp != null)
          return cmp.CompareTo(y) == 0;
        else
          return x.Equals(y);
      }
    }

    // ReSharper disable once InconsistentNaming
    internal static int DBCompareValues(object x, object y)
    {
      if ((x == null || Convert.IsDBNull(x)) && ((y == null) || Convert.IsDBNull(y)))
        return 0;
      else if (x == null || Convert.IsDBNull(x))
        return -1;
      else if (y == null || Convert.IsDBNull(y))
        return 1;
      else
        return ((IComparable)x).CompareTo(y);
    }

    internal static Rectangle[] MeasureCharacterRangesStringLine(Graphics g, string text, Font font,
      Rectangle layoutRect, HorizontalAlignment horzAlign, VerticalAlignment vertAlign,
      CharacterRange[] charRanges)
    {
      if (GraphicEngine == UsedGraphicEngine.WinFormsGDI ||
          GraphicEngine == UsedGraphicEngine.NativeGDI)
      {
        Rectangle[] result;
        int textHeight;
        Size textSize;
        int shift = 0;

        using (GDITextRenderer ntr = new GDITextRenderer(g))
        {
          result = ntr.MeasureCharacterRanges(text, font, charRanges, out textSize);
          textHeight = ntr.MeasureString(text, font).Height;
        }

        switch (horzAlign)
        {
          case HorizontalAlignment.Left:
            shift = layoutRect.Left;
            break;
          case HorizontalAlignment.Center:
            shift = (layoutRect.Left + layoutRect.Right - textSize.Width) / 2;
            break;
          case HorizontalAlignment.Right:
            shift = layoutRect.Right - textSize.Width;
            break;
            //default: shift = layoutRect.Left;
        }

        for (int i = 0; i < result.Length; i++)
        {
          result[i].Y = layoutRect.Y;
          result[i].Height = textHeight;
          result[i].X = result[i].X + shift;
        }

        return result;
      }
      else
      {
        Rectangle[] result = null;
        Region[] regions;

        using (StringFormat stringFormat = StringFormat.GenericTypographic)
        {
          stringFormat.SetMeasurableCharacterRanges(charRanges);
          stringFormat.FormatFlags = StringFormatFlags.NoWrap |
                                     StringFormatFlags.NoClip;

          regions = g.MeasureCharacterRanges(text, font, layoutRect, stringFormat);
          Array.Resize(ref result, regions.Length);

          for (int i = 0; i < regions.Length; i++)
            result[i] = Rectangle.Round(regions[i].GetBounds(g));
        }

        return result;
      }
    }

    internal static int GetFontHeight(Font font)
    {
      if ((_lastFontName == font.Name) &&
          _lastFontSize == font.Size &&
          (_lastFontStyle == font.Style))
      {
        return _lastFontHeight;
      }

      int fontHeight = 0;
      Dictionary<float, Dictionary<FontStyle, int>> dic1;
      if (FontHeightsCache.TryGetValue(font.Name, out dic1))
      {
        Dictionary<FontStyle, int> dic2;
        if (dic1.TryGetValue(font.Size, out dic2))
        {
          dic2.TryGetValue(font.Style, out fontHeight);
        }
        else
        {
          dic1[font.Size] = new Dictionary<FontStyle, int>();
        }
      }
      else
      {
        FontHeightsCache[font.Name] = new Dictionary<float, Dictionary<FontStyle, int>>();
        FontHeightsCache[font.Name][font.Size] = new Dictionary<FontStyle, int>();
      }

      if (fontHeight == 0)
      {
        fontHeight = TextHeight(DisplayGraphicsCash, "Wg", font);
        FontHeightsCache[font.Name][font.Size][font.Style] = fontHeight;
      }

      _lastFontName = font.Name;
      _lastFontSize = font.Size;
      _lastFontStyle = font.Style;
      _lastFontHeight = fontHeight;

      return fontHeight;
    }

    internal static TextFormatFlags TranslateContentAlignmentToTextFormatFlags(System.Drawing.ContentAlignment align)
    {
      TextFormatFlags result;

      if (((int)align & AnyBottom) != 0)
        result = TextFormatFlags.Bottom;
      else if (((int)align & AnyMiddle) != 0)
        result = TextFormatFlags.VerticalCenter;
      else
        result = TextFormatFlags.Top;

      if (((int)align & AnyRight) != 0)
        result = result | TextFormatFlags.Right;
      else if (((int)align & AnyCenter) != 0)
        result = result | TextFormatFlags.HorizontalCenter;
      else
        result = result | TextFormatFlags.Left;

      return result;
    }

    internal static TextFormatFlags TranslateAlignmentToTextFormatFlags(HorizontalAlignment horzAlign, VerticalAlignment vertAlign)
    {
      TextFormatFlags result;

      if (vertAlign == VerticalAlignment.Bottom)
        result = TextFormatFlags.Bottom;
      else if (vertAlign == VerticalAlignment.Center)
        result = TextFormatFlags.VerticalCenter;
      else
        result = TextFormatFlags.Top;

      if (horzAlign == HorizontalAlignment.Right)
        result = result | TextFormatFlags.Right;
      else if (horzAlign == HorizontalAlignment.Center)
        result = result | TextFormatFlags.HorizontalCenter;
      else
        result = result | TextFormatFlags.Left;

      return result;
    }

    internal static StringAlignment HorizontalAlignmentToStringAlignment(HorizontalAlignment horzAlign)
    {
      if (horzAlign == HorizontalAlignment.Right)
        return StringAlignment.Far;
      else if (horzAlign == HorizontalAlignment.Center)
        return StringAlignment.Center;
      else
        return StringAlignment.Near;
    }

    internal static StringAlignment VerticalAlignmentToStringAlignment(VerticalAlignment vertAlign)
    {
      if (vertAlign == VerticalAlignment.Bottom)
        return StringAlignment.Far;
      else if (vertAlign == VerticalAlignment.Center)
        return StringAlignment.Center;
      else
        return StringAlignment.Near;
    }

    internal static IntPtr SendMessage(Control control, int msg, IntPtr wParam, IntPtr lParam)
    {
      return NativeMethods.SendMessage(control.Handle, msg, wParam, lParam);
    }

    internal static IntPtr SendMouseDownMessage(Control control, Point mousePos, MouseButtons mouseButton)
    {
      int lParam = 0;

      if ((mouseButton & MouseButtons.Left) == MouseButtons.Left)
        lParam |= NativeMethods.MK_LBUTTON;

      if ((mouseButton & MouseButtons.Right) == MouseButtons.Right)
        lParam |= NativeMethods.MK_RBUTTON;

      if ((mouseButton & MouseButtons.Middle) == MouseButtons.Middle)
        lParam |= NativeMethods.MK_MBUTTON;

      return NativeMethods.SendMessage(control.Handle, NativeMethods.WM_LBUTTONDOWN, new IntPtr(NativeMethods.MK_LBUTTON), new IntPtr(lParam));
    }

    internal static bool PeekMessage(Control control, out Message msg, uint msgMin, uint msgMax, bool isRemoveMsg)
    {
      IntPtr hwnd = IntPtr.Zero;
      NativeMethods.NativeMessage natMsg;
      uint wRemoveMsg;

      if (control != null)
        hwnd = control.Handle;

      if (isRemoveMsg)
        wRemoveMsg = NativeMethods.PM_REMOVE;
      else
        wRemoveMsg = NativeMethods.PM_NOREMOVE;

      bool result = NativeMethods.PeekMessage(out natMsg, hwnd, msgMin, msgMax, wRemoveMsg);
      msg = Message.Create(natMsg.hwnd, natMsg.message, natMsg.wParam, natMsg.lParam);
      return result;
    }

    internal static Rectangle GetControlWindowRect(Control control)
    {
      NativeMethods.RECT r;
      NativeMethods.GetWindowRect(control.Handle, out r);
      return r.ToRectangle();
    }

    internal static void ScrollControlWindow(Control control, int xAmount, int yAmount, Rectangle rectScrollRegion)
    {
      NativeMethods.RECT rcClip = NativeMethods.RECT.FromXYWH(rectScrollRegion.X, rectScrollRegion.Y, rectScrollRegion.Width, rectScrollRegion.Height);
      NativeMethods.RECT rcUpdate = NativeMethods.RECT.FromXYWH(rectScrollRegion.X, rectScrollRegion.Y, rectScrollRegion.Width, rectScrollRegion.Height);
      int hres = NativeMethods.ScrollWindowEx(control.Handle, xAmount, yAmount,
                                                 ref rcClip,
                                                 ref rcClip,
                                                 NativeMethods.NullHandleRef,
                                                 ref rcUpdate,
                                                 NativeMethods.SW_INVALIDATE);
      Debug.Assert(hres != 0, "hres != 0");
    }

    internal static CheckBoxState FixCheckBoxStateByState(CheckBoxState cbState, bool isHot, bool isPressed, bool isEnabled)
    {
      if (!isEnabled)
      {
        switch (cbState)
        {
          case CheckBoxState.UncheckedNormal: return CheckBoxState.UncheckedDisabled;
          case CheckBoxState.CheckedNormal: return CheckBoxState.CheckedDisabled;
          case CheckBoxState.MixedNormal: return CheckBoxState.MixedDisabled;
          default: return cbState;
        }
      }
      else if (isPressed)
      {
        switch (cbState)
        {
          case CheckBoxState.UncheckedNormal: return CheckBoxState.UncheckedPressed;
          case CheckBoxState.CheckedNormal: return CheckBoxState.CheckedPressed;
          case CheckBoxState.MixedNormal: return CheckBoxState.MixedPressed;
          default: return cbState;
        }
      }
      else if (isHot)
      {
        switch (cbState)
        {
          case CheckBoxState.UncheckedNormal: return CheckBoxState.UncheckedHot;
          case CheckBoxState.CheckedNormal: return CheckBoxState.CheckedHot;
          case CheckBoxState.MixedNormal: return CheckBoxState.MixedHot;
          default: return cbState;
        }
      }
      else
      {
        return cbState;
      }
    }

    internal static RadioButtonState FixRadioButtonStateByState(RadioButtonState cbState, bool isHot, bool isPressed, bool isEnabled)
    {
      if (!isEnabled)
      {
        switch (cbState)
        {
          case RadioButtonState.UncheckedNormal: return RadioButtonState.UncheckedDisabled;
          case RadioButtonState.CheckedNormal: return RadioButtonState.CheckedDisabled;
          default: return cbState;
        }
      }
      else if (isPressed)
      {
        switch (cbState)
        {
          case RadioButtonState.UncheckedNormal: return RadioButtonState.UncheckedPressed;
          case RadioButtonState.CheckedNormal: return RadioButtonState.CheckedPressed;
          default: return cbState;
        }
      }
      else if (isHot)
      {
        switch (cbState)
        {
          case RadioButtonState.UncheckedNormal: return RadioButtonState.UncheckedHot;
          case RadioButtonState.CheckedNormal: return RadioButtonState.CheckedHot;
          default: return cbState;
        }
      }
      else
      {
        return cbState;
      }
    }

    internal static PushButtonState GetPushButtonState(bool isHot, bool isPressed, bool isDisabled)
    {
      if (isDisabled)
        return PushButtonState.Disabled;
      else if (isPressed)
        return PushButtonState.Pressed;
      else if (isHot)
        return PushButtonState.Hot;
      else
        return PushButtonState.Normal;
    }

    internal static Color ApproximateColor(Color fromColor, Color toColor, double quota)
    {
      int r1 = fromColor.R;
      int g1 = fromColor.G;
      int b1 = fromColor.B;

      int r2 = toColor.R;
      int g2 = toColor.G;
      int b2 = toColor.B;

      int r = Math.Max(0, Math.Min(255, r1 + (int)((float)(r2 - r1) / 255 * quota)));
      int g = Math.Max(0, Math.Min(255, g1 + (int)((float)(g2 - g1) / 255 * quota)));
      int b = Math.Max(0, Math.Min(255, b1 + (int)((float)(b2 - b1) / 255 * quota)));

      Color result = Color.FromArgb(r, g, b);

      return result;
    }

    //
    // https://blogs.msdn.microsoft.com/cjacks/2006/04/12/converting-from-hsb-to-rgb-in-net/
    //
    internal static Color ColorFromAHSB(int a, float h, float s, float b)
    {

      if (0 > a || 255 < a)
      {
        throw new ArgumentOutOfRangeException("a", a, @"InvalidAlpha");
      }
      if (0f > h || 360f < h)
      {
        throw new ArgumentOutOfRangeException("h", h, @"InvalidHue");
      }
      if (0f > s || 1f < s)
      {
        throw new ArgumentOutOfRangeException("s", s, @"InvalidSaturation");
      }
      if (0f > b || 1f < b)
      {
        throw new ArgumentOutOfRangeException("b", b, @"InvalidBrightness");
      }

      if (s == 0f)
      {
        return Color.FromArgb(a, Convert.ToInt32(b * 255),
          Convert.ToInt32(b * 255), Convert.ToInt32(b * 255));
      }

      float fMax, fMid, fMin;

      if (0.5 < b)
      {
        fMax = b - (b * s) + s;
        fMin = b + (b * s) - s;
      }
      else
      {
        fMax = b + (b * s);
        fMin = b - (b * s);
      }

      int iSextant = (int)Math.Floor(h / 60f);
      if (300f <= h)
      {
        h -= 360f;
      }
      h /= 60f;
      h -= 2f * (float)Math.Floor(((iSextant + 1f) % 6f) / 2f);
      if (0 == iSextant % 2)
      {
        fMid = h * (fMax - fMin) + fMin;
      }
      else
      {
        fMid = fMin - h * (fMax - fMin);
      }

      int iMax = Convert.ToInt32(fMax * 255);
      int iMid = Convert.ToInt32(fMid * 255);
      int iMin = Convert.ToInt32(fMin * 255);

      switch (iSextant)
      {
        case 1:
          return Color.FromArgb(a, iMid, iMax, iMin);
        case 2:
          return Color.FromArgb(a, iMin, iMax, iMid);
        case 3:
          return Color.FromArgb(a, iMin, iMid, iMax);
        case 4:
          return Color.FromArgb(a, iMid, iMin, iMax);
        case 5:
          return Color.FromArgb(a, iMax, iMin, iMid);
        default:
          return Color.FromArgb(a, iMax, iMid, iMin);
      }
    }

    internal static void FillGradient(Graphics g, Point topLeft, Point[] points, Color fromColor, Color toColor)
    {
      Rectangle par = new Rectangle();

      int h = points.Length / 2 - 1;

      SolidBrush sBrush = new SolidBrush(fromColor);

      for (int i = 0; i <= h; i++)
      {
        par.X = topLeft.X + points[i * 2].X;
        par.Y = topLeft.Y + points[i * 2].Y;
        par.Width = points[i * 2 + 1].X - points[i * 2].X;
        par.Height = 1;
        g.FillRectangle(sBrush, par);
      }
    }

    internal static void DrawImage(Graphics g, Image image, Rectangle destRect, Rectangle srcRect, int transparencyLevel, bool greyScale)
    {

      float trFctr = 1 - (float)(255 - transparencyLevel) / 255;

      TransparencyMatrix[3][3] = trFctr;

      using (ImageAttributes imageAttributes = new ImageAttributes())
      {

        ColorMatrix colorMatrix;

        if (greyScale)
          colorMatrix = MultiplyColorMatrix(TransparencyMatrix, GreyscaleMatrix);
        else
          colorMatrix = new ColorMatrix(TransparencyMatrix);

        imageAttributes.SetColorMatrix(
           colorMatrix,
           ColorMatrixFlag.Default,
           ColorAdjustType.Bitmap);

        g.DrawImage(
           image,
           destRect,  // destination rectangle 
           srcRect.X, srcRect.Y,        // upper-left corner of source rectangle 
           srcRect.Width,       // width of source rectangle
           srcRect.Height,      // height of source rectangle
           GraphicsUnit.Pixel,
           imageAttributes);
      }
    }

    internal static ColorMatrix MultiplyColorMatrix(float[][] matrix1, float[][] matrix2)
    {
      int size = 5; // multiplies 2 5x5 matrices.

      // build up an empty 5x5 array for results
      float[][] result = new float[size][];
      for (int row = 0; row < size; row++)
      {
        result[row] = new float[size];
      }

      float[] column = new float[size];
      for (int j = 0; j < size; j++)
      {
        for (int k = 0; k < size; k++)
        {
          column[k] = matrix1[k][j];
        }
        for (int i = 0; i < size; i++)
        {
          float[] row = matrix2[i];
          float s = 0;
          for (int k = 0; k < size; k++)
          {
            s += row[k] * column[k];
          }
          result[i][j] = s;
        }
      }

      return new ColorMatrix(result);

    }

    internal static void SplitIntPtr(IntPtr intPtrVal, out int x, out int y)
    {
      x = unchecked((short)(long)intPtrVal);
      y = unchecked((int)(long)intPtrVal) >> 16;
    }

    internal static bool IsAnyKeyPressed(Control control)
    {
      Message charMsg;
      bool wmChatInQueue = PeekMessage(control, out charMsg, NativeMethods.WM_KEYDOWN, NativeMethods.WM_KEYDOWN, false);
      if (wmChatInQueue)
        return true;
      else
        return false;
    }

    internal static bool CheckKeyPressed(Control control, Keys key, bool isRemoveMsg)
    {
      Message charMsg;
      bool result = false;
      bool wmChatInQueue = PeekMessage(control, out charMsg, NativeMethods.WM_KEYDOWN, NativeMethods.WM_KEYDOWN, false);
      if (wmChatInQueue)
      {
        if ((isRemoveMsg == true) && ((int)charMsg.WParam == (int)key))
        {
          PeekMessage(control, out charMsg, NativeMethods.WM_KEYDOWN, NativeMethods.WM_KEYDOWN, true);
          result = true;
        }
      }
      return result;
    }

    internal static Control GetFocusControl()
    {
      IntPtr handle = NativeMethods.GetFocus();
      if (handle != IntPtr.Zero)
        return Control.FromHandle(handle);
      else
        return null;

    }

    internal static Control ControlFromScreenPoint(Point screenPos)
    {
      NativeMethods.POINTSTRUCT ps = new NativeMethods.POINTSTRUCT(screenPos.X, screenPos.Y);
      IntPtr handle = NativeMethods.WindowFromPoint(ps);

      return Control.FromHandle(handle);
    }

    internal static VerticalDropLayout AlignDropDownWindowRect(Rectangle hostScreenRect, Control dropDownWin, DropDownAlign align)
    {
      VerticalDropLayout result = VerticalDropLayout.UnderControl;
      Rectangle newBounds = dropDownWin.Bounds;

      Screen myScreen = Screen.FromRectangle(hostScreenRect);
      Rectangle workArea = myScreen.WorkingArea;

      Point hostP = hostScreenRect.Location;

      newBounds.Location = new Point(hostP.X, hostP.Y + (hostScreenRect.Bottom - hostScreenRect.Top) + 1);

      switch (align)
      {
        case DropDownAlign.Right:
          newBounds.X = newBounds.X - (dropDownWin.Width - (hostScreenRect.Right - hostScreenRect.Left));
          break;
        case DropDownAlign.Center:
          newBounds.X = newBounds.X - ((dropDownWin.Width - (hostScreenRect.Right - hostScreenRect.Left)) / 2);
          break;
      }

      if (newBounds.Width > workArea.Right - workArea.Left)
        newBounds.Width = workArea.Right - workArea.Left;

      if (newBounds.Left + newBounds.Width > workArea.Right)
        newBounds.X = workArea.Right - newBounds.Width;
      if (newBounds.Left < workArea.Left)
        newBounds.X = workArea.Left;

      if (newBounds.Top + newBounds.Height > workArea.Bottom)
      {
        if (hostP.Y - workArea.Top > workArea.Bottom - hostP.Y - (hostScreenRect.Bottom - hostScreenRect.Top))
        {
          result = VerticalDropLayout.AboveControl;
          newBounds.Y = hostP.Y - newBounds.Height;
        }
      }

      if (newBounds.Top < workArea.Top)
      {
        newBounds.Height = newBounds.Height - (workArea.Top - newBounds.Top);
        newBounds.Y = workArea.Top;
      }
      if (newBounds.Top + newBounds.Height > workArea.Bottom)
      {
        newBounds.Height = workArea.Bottom - newBounds.Top;
      }

      dropDownWin.Bounds = newBounds;
      return result;

    }

    internal static bool StringEquals(string a, string b, bool ignoreCase, bool partialKey)
    {
      string b1;

      if (partialKey)
        b1 = b.SafeSubstring(0, a.Length);
      else
        b1 = b;

      if (ignoreCase)
        return string.Equals(a, b1, StringComparison.OrdinalIgnoreCase);
      else
        return string.Equals(a, b1, StringComparison.Ordinal);
    }

    internal static string SafeSubstring(string text, int start, int length)
    {
      if (start >= text.Length)
        return "";
      else if (text.Length - start <= length)
        return text.Substring(start);
      else
        return text.Substring(start, length);
    }

    internal static Rectangle RightToLeftFlipRectangle(Rectangle baseRect, Rectangle rect)
    {
      Rectangle result = rect;
      result.X = baseRect.Width - rect.Width - rect.X + baseRect.X + baseRect.X;
      return result;
    }

    internal static Point RightToLeftFlipPoint(Rectangle baseRect, Point point)
    {
      Point result = point;
      result.X = baseRect.Width - point.X - 1 + baseRect.X + baseRect.X;
      return result;
    }

    internal static DialogResult InputBox(string title, string promptText, ref string value)
    {
      Form form = new Form();
      Label label = new Label();
      TextBox textBox = new TextBox();
      Button buttonOk = new Button();
      Button buttonCancel = new Button();

      form.Text = title;
      label.Text = promptText;
      textBox.Text = value;

      buttonOk.Text = @"OK";
      buttonCancel.Text = @"Cancel";
      buttonOk.DialogResult = DialogResult.OK;
      buttonCancel.DialogResult = DialogResult.Cancel;

      label.SetBounds(9, 20, 372, 13);
      textBox.SetBounds(12, 36, 372, 20);
      buttonOk.SetBounds(228, 72, 75, 23);
      buttonCancel.SetBounds(309, 72, 75, 23);

      label.AutoSize = true;
      textBox.Anchor = textBox.Anchor | AnchorStyles.Right;
      buttonOk.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
      buttonCancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;

      form.ClientSize = new Size(396, 107);
      form.Controls.AddRange(new Control[] { label, textBox, buttonOk, buttonCancel });
      form.ClientSize = new Size(Math.Max(300, label.Right + 10), form.ClientSize.Height);
      form.FormBorderStyle = FormBorderStyle.FixedDialog;
      form.StartPosition = FormStartPosition.CenterScreen;
      form.MinimizeBox = false;
      form.MaximizeBox = false;
      form.AcceptButton = buttonOk;
      form.CancelButton = buttonCancel;

      DialogResult dialogResult = form.ShowDialog();
      value = textBox.Text;
      return dialogResult;
    }

    internal static MouseButtons MouseButtonsAsyn
    {
      get
      {
        MouseButtons buttons = (MouseButtons)0;
        if (NativeMethods.GetAsyncKeyState((int)Keys.LButton) < 0) buttons |= MouseButtons.Left;
        if (NativeMethods.GetAsyncKeyState((int)Keys.RButton) < 0) buttons |= MouseButtons.Right;
        if (NativeMethods.GetAsyncKeyState((int)Keys.MButton) < 0) buttons |= MouseButtons.Middle;
        if (NativeMethods.GetAsyncKeyState((int)Keys.XButton1) < 0) buttons |= MouseButtons.XButton1;
        if (NativeMethods.GetAsyncKeyState((int)Keys.XButton2) < 0) buttons |= MouseButtons.XButton2;
        return buttons;
      }
    }
  }

  internal class DisplayGraphics : object, IDisposable
  {

    private Graphics graphics;
    // ReSharper disable once InconsistentNaming
    private IntPtr screenDC;
    private bool disposed;

    public Graphics Graphics
    {
      get
      {
        if (graphics == null)
          CreateGraphics();
        return graphics;
      }
    }

    private void CreateGraphics()
    {
      screenDC = NativeMethods.GetDC(NativeMethods.NullHandleRef);
      graphics = Graphics.FromHdcInternal(screenDC);
    }

    ~DisplayGraphics()
    {
      Dispose(false);
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposed)
        return;
      if (disposing)
      {
        graphics.Dispose();
      }
      //NativeMethods.ReleaseDC is called in graphics.Dispose;
      //int hres = NativeMethods.ReleaseDC(NativeMethods.NullHandleRef, screenDC);
      //Debug.Assert(hres != 0, "hres != 0");
      disposed = true;
    }
  }

  internal class ControlGraphicsWrapper : object, IDisposable
  {
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2006:UseSafeHandleToEncapsulateNativeResources")]
    private readonly IntPtr hdc;
    private readonly Graphics graphics;
    private readonly Control control;
    private bool disposed;

    public ControlGraphicsWrapper(Control control)
    {
      this.control = control;
      hdc = NativeMethods.GetWindowDC(control.Handle);
      if (hdc == IntPtr.Zero)
        throw new Win32Exception();
      graphics = Graphics.FromHdcInternal(hdc);
    }

    public Graphics Graphics
    {
      get { return graphics; }
    }

    ~ControlGraphicsWrapper()
    {
      Dispose(false);
    }

    public void Dispose()
    {
      Dispose(true);
      GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing)
    {
      if (disposed)
        return;
      if (disposing)
      {
        graphics.Dispose();
      }
      int hres = NativeMethods.ReleaseDC(control.Handle, hdc);
      Debug.Assert(hres != 0, "hres != 0");
      disposed = true;
    }

  }

  // ReSharper disable once InconsistentNaming
  internal sealed class GDITextRenderer : IDisposable
  {
    #region Fields and Consts
    private static readonly int[] CharFit1 = new int[1];
    private static int[] _charFitWidths = new int[1000];
    private static readonly Dictionary<string, Dictionary<float, Dictionary<FontStyle, IntPtr>>> FontsCache = new Dictionary<string, Dictionary<float, Dictionary<FontStyle, IntPtr>>>(StringComparer.InvariantCultureIgnoreCase);
    private readonly Graphics _g;
    private IntPtr _hdc;

    private static string _lastFontName = "";
    private static float _lastFontSize;
    private static FontStyle _lastFontStyle = 0;
    private static IntPtr _lastFontHandle = new IntPtr(0);
    #endregion

    public GDITextRenderer(Graphics g)
    {
      _g = g;

      IntPtr clip = _g.Clip.GetHrgn(_g);

      _hdc = _g.GetHdc();
      int hres = NativeMethods.SetBkMode(_hdc, NativeMethods.TRANSPARENT);
      Debug.Assert(hres != 0, "hres != 0 Failed");

      NativeMethods.SelectClipRgn(_hdc, clip);
      NativeMethods.DeleteObject(clip);
    }

    public void Dispose()
    {
      if (_hdc != IntPtr.Zero)
      {
        NativeMethods.SelectClipRgn(_hdc, IntPtr.Zero);
        _g.ReleaseHdc(_hdc);
        _hdc = IntPtr.Zero;
      }
    }

    public Size MeasureString(string str, Font font)
    {
      SetFont(font);

      var size = new Size();
      int hres = NativeMethods.GetTextExtentPoint32(_hdc, str, str.Length, ref size);
      Debug.Assert(hres != 0, "hres != 0");
      return size;
    }
    public Size MeasureString(string str, Font font, float maxWidth, out int charFit, out int charFitWidth)
    {
      SetFont(font);

      var size = new Size();
      NativeMethods.GetTextExtentExPoint(_hdc, str, str.Length, (int)Math.Round(maxWidth), CharFit1, _charFitWidths, ref size);
      charFit = CharFit1[0];
      charFitWidth = charFit > 0 ? _charFitWidths[charFit - 1] : 0;
      return size;
    }

    public Rectangle[] MeasureCharacterRanges(string text, Font font, CharacterRange[] charRanges, out Size textSize)
    {
      Rectangle[] result = new Rectangle[charRanges.Length];
      Rectangle rec = new Rectangle();
      int last;

      SetFont(font);

      var size = new Size();
      Array.Resize(ref _charFitWidths, text.Length);

      NativeMethods.GetTextExtentExPoint(_hdc, text, text.Length, 10000, CharFit1, _charFitWidths, ref size);
      for (int i = 0; i < charRanges.Length; i++)
      {
        if (charRanges[i].First == 0)
          rec.X = 0;
        else
          rec.X = _charFitWidths[charRanges[i].First - 1];

        last = charRanges[i].First - 1 + charRanges[i].Length;
        if (last >= _charFitWidths.Length)
          last = _charFitWidths.Length - 1;
        rec.Width = _charFitWidths[last] - rec.X;
        result[i] = rec;
      }

      textSize = new Size(_charFitWidths[text.Length - 1], 0);
      return result;
    }

    private int Geli(string s, int p0, int w, out int widthPxl, int[] cex, bool ml)
    {
      int cw = 0;
      int ls0 = -1;
      int wpxl = 0;
      int result;
      char si;

      for (int i = p0; i < s.Length; i++)
      {
        si = s[i];
        if (si == ' ')
        {
          ls0 = i + 1;
          wpxl = cw + cex[i];
        }

        cw = cw + cex[i];

        if (ml && (si == '\n'))
        {
          //if (cw > w)
          //  result = i;
          //else 
          if ((i + 1 < s.Length) && (s[i + 1] == '\r'))
            result = i + 2;
          else
            result = i + 1;
          widthPxl = cw;
          return result;
        }
        else if (ml && (si == '\r'))
        {
          //if (cw > w)
          //  Result = i;
          if ((i + 1 < s.Length) && (s[i + 1] == '\n'))
            result = i + 2;
          else
            result = i + 1;
          widthPxl = cw;
          return result;
        }
        else if ((cw > w) && (ls0 > -1) && ml)
        {
          // ReSharper disable once ConditionIsAlwaysTrueOrFalse
          if (ml)
          {
            result = ls0;
            widthPxl = wpxl;
          }
          else
          // ReSharper disable once HeuristicUnreachableCode
          {
            // ReSharper disable once HeuristicUnreachableCode
            result = i;
            widthPxl = cw;
          }
          return result;
        }
      }
      result = s.Length;
      widthPxl = cw;
      return result;
    }

    public Size MeasureStringWrap(string text, Font font, int boxWidth, bool ml, ref DrawStringLineInfo[] wrappedLines)
    {
      Size resultSize = Size.Empty;
      Size stringSize = Size.Empty;
      int olp = 0;
      int line = 0;
      int nlp;
      int wpxl;

      if (String.IsNullOrEmpty(text)) return Size.Empty;

      SetFont(font);

      Array.Resize(ref _charFitWidths, text.Length);
      NativeMethods.GetTextExtentExPoint(_hdc, text, text.Length, 10000, CharFit1, _charFitWidths, ref stringSize);

      for (int i = text.Length - 1; i > 0; i--)
        _charFitWidths[i] = _charFitWidths[i] - _charFitWidths[i - 1];

      resultSize.Width = 0;
      while (true)
      {
        nlp = Geli(text, olp, boxWidth, out wpxl, _charFitWidths, ml);
        if (wrappedLines != null)
        {
          Array.Resize(ref wrappedLines, line + 1);
          wrappedLines[line].First = olp;
          wrappedLines[line].Length = nlp - olp;
          if ((nlp < text.Length) && (wrappedLines[line].Length > 1))
            wrappedLines[line].Length = wrappedLines[line].Length - 1;
          wrappedLines[line].LengthPixels = wpxl;
          wrappedLines[line].HeightPixels = stringSize.Height;
        }
        if (wpxl > resultSize.Width)
          resultSize.Width = wpxl;
        if (nlp >= text.Length)
          break;
        olp = nlp;
        if (!ml)
          break;
        line = line + 1;
      }

      resultSize.Height = (line + 1) * stringSize.Height;
      return resultSize;
    }

    public Size MeasureStringWrap(string text, Font font, int boxWidth)
    {
      NativeTextFormatFlags flags = NativeTextFormatFlags.WordBreak | NativeTextFormatFlags.CalcRect;

      SetFont(font);

      var rect = new Rectangle(0, 0, boxWidth, 0);
      var rect2 = new NativeMethods.RECT(rect);
      int hres = NativeMethods.DrawText(_hdc, text, text.Length, ref rect2, (int)flags);
      Debug.Assert(hres != 0, "hres != 0");
      return rect2.Size;
    }

    public void DrawString(string str, Font font, Color color, Point point)
    {
      SetFont(font);
      SetTextColor(color);

      NativeMethods.TextOut(_hdc, point.X, point.Y, str, str.Length);
    }

    public Size DrawString(string str, Font font, Color color, Rectangle rect, TextFormatFlags flags)
    {
      SetFont(font);

      SetTextColor(color);

      Rectangle bounds = AdjustForVerticalAlignment(_hdc, str, rect, flags);
      var rect2 = new NativeMethods.RECT(bounds);

      //var rect2 = new NativeMethods.RECT(rect);

      int hres = NativeMethods.DrawText(_hdc, str, str.Length, ref rect2, (int)flags);

      return rect2.Size;
    }

    #region Private methods

    private void SetFont(Font font)
    {
      IntPtr hFont = GetCachedHFont(font);
      NativeMethods.SelectObject(_hdc, hFont);
    }

    private static IntPtr GetCachedHFont(Font font)
    {
      if ((_lastFontName == font.Name) &&
          (_lastFontSize == font.Size) &&
          (_lastFontStyle == font.Style))
      {
        return _lastFontHandle;
      }

      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();
      }

      _lastFontName = font.Name;
      _lastFontSize = font.Size;
      _lastFontStyle = font.Style;
      _lastFontHandle = hfont;

      return hfont;
    }

    private void SetTextColor(Color color)
    {
      int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R;
      int hres = NativeMethods.SetTextColor(_hdc, rgb);
      Debug.Assert(hres != NativeMethods.CLR_INVALID, "hres != NativeMethods.CLR_INVALID Failed");
    }

    public static Rectangle AdjustForVerticalAlignment(IntPtr hdc, string text, Rectangle bounds, TextFormatFlags flags)
    {
      // Ok if any Top single line text or measuring text.
      bool isTop = (flags & TextFormatFlags.Bottom) == 0 && (flags & TextFormatFlags.VerticalCenter) == 0;
      if (isTop || (flags & TextFormatFlags.SingleLine) != 0)
      {
        return bounds;
      }

      NativeMethods.RECT rect = new NativeMethods.RECT(bounds);

      // Get the text bounds.
      int nativeFlags = (int)flags | (int)NativeTextFormatFlags.CalcRect;
      int textHeight = NativeMethods.DrawText(hdc, text, text.Length, ref rect, (int)nativeFlags);

      // if the text does not fit inside the bounds then return the bounds that were passed in
      if (textHeight > bounds.Height)
      {
        return bounds;
      }

      Rectangle adjustedBounds = bounds;

      if ((flags & TextFormatFlags.VerticalCenter) != 0)  // Middle
      {
        adjustedBounds.Y = adjustedBounds.Top + adjustedBounds.Height / 2 - textHeight / 2;
      }
      else // Bottom.
      {
        adjustedBounds.Y = adjustedBounds.Bottom - textHeight;
      }

      return adjustedBounds;
    }

    [Flags]
    internal enum NativeTextFormatFlags : 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,
    }

    #endregion
  }


  internal class TransparentRichTextBox : RichTextBox
  {
    public TransparentRichTextBox()
    {

    }

    protected override CreateParams CreateParams
    {
      get
      {
        CreateParams CP = base.CreateParams;
        CP.ExStyle = CP.ExStyle | NativeMethods.WS_EX_TRANSPARENT;
        return CP;
      }
    }

  }

  public class RichTextBoxPrintSerivce : IDisposable
  {
    //Convert the unit used by the .NET framework (1/100 inch) 
    //and the unit used by Win32 API calls (twips 1/1440 inch)
    private const double anInch = 14.4;

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
      public int Left;
      public int Top;
      public int Right;
      public int Bottom;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct CHARRANGE
    {
      public int cpMin;
      public int cpMax;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct FORMATRANGE
    {
      public IntPtr hdc;
      public IntPtr hdcTarget;
      public RECT rc;         
      public RECT rcPage;     
      public CHARRANGE chrg;  
    }

    private const int WM_USER = 0x0400;
    private const int EM_FORMATRANGE = WM_USER + 57;

    private RichTextBox richTextBox;
    private bool disposedValue = false;

    [DllImport("USER32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);

    public int Measure(string rtf, int charFrom, int charTo, PrintPageEventArgs e, Rectangle bounds, out int measuredHeight)
    {
      return Print(rtf, charFrom, charTo, e, bounds, true, out measuredHeight);
    }

    public int Print(string rtf, int charFrom, int charTo, PrintPageEventArgs e, Rectangle bounds)
    {
      int measuredHeight;
      return Print(rtf, charFrom, charTo, e, bounds, false, out measuredHeight);
    }

    public int Print(string rtf, int charFrom, int charTo, PrintPageEventArgs e, Rectangle bounds, bool measureHeight, out int measuredHeight)
    {
      if (richTextBox == null)
        richTextBox = new TransparentRichTextBox();
      richTextBox.Rtf = rtf;

      RECT rectToPrint;
      RECT rectPage;

      if (bounds == Rectangle.Empty)
      {
        //area to render and print
        rectToPrint.Top = (int)(e.MarginBounds.Top * anInch);
        rectToPrint.Bottom = (int)(e.MarginBounds.Bottom * anInch);
        rectToPrint.Left = (int)(e.MarginBounds.Left * anInch);
        rectToPrint.Right = (int)(e.MarginBounds.Right * anInch);

        //size of the page
        rectPage.Top = (int)(e.PageBounds.Top * anInch);
        rectPage.Bottom = (int)(e.PageBounds.Bottom * anInch);
        rectPage.Left = (int)(e.PageBounds.Left * anInch);
        rectPage.Right = (int)(e.PageBounds.Right * anInch);
      }
      else
      {
        //area to render and print
        rectToPrint.Top = (int)(bounds.Top * anInch);
        rectToPrint.Bottom = (int)(bounds.Bottom * anInch);
        rectToPrint.Left = (int)(bounds.Left * anInch);
        rectToPrint.Right = (int)(bounds.Right * anInch);

        //size of the page
        rectPage.Top = (int)(bounds.Top * anInch);
        rectPage.Bottom = (int)(bounds.Bottom * anInch);
        rectPage.Left = (int)(bounds.Left * anInch);
        rectPage.Right = (int)(bounds.Right * anInch);
      }

      IntPtr hdc = e.Graphics.GetHdc();

      FORMATRANGE fmtRange;
      fmtRange.chrg.cpMax = charTo;
      fmtRange.chrg.cpMin = charFrom;
      fmtRange.hdc = hdc;          
      fmtRange.hdcTarget = hdc;    //Point at printer hDC
      fmtRange.rc = rectToPrint;   //area on page to print
      fmtRange.rcPage = rectPage;  //size of page

      IntPtr res;
      IntPtr wparam;
      IntPtr lparam;

      if (measureHeight)
        wparam = new IntPtr(0);
      else
        wparam = new IntPtr(1);

      lparam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
      Marshal.StructureToPtr(fmtRange, lparam, false);

      res = SendMessage(richTextBox.Handle, EM_FORMATRANGE, wparam, lparam);

      fmtRange = (FORMATRANGE)Marshal.PtrToStructure(lparam, typeof(FORMATRANGE));

      Marshal.FreeCoTaskMem(lparam);

      e.Graphics.ReleaseHdc(hdc);

      if (measureHeight)
        measuredHeight = (int)Math.Ceiling(((fmtRange.rc.Bottom - fmtRange.rc.Top) / anInch));
      else
        measuredHeight = 0;

      return res.ToInt32(); //last + 1 or 0
    }

    protected virtual void Dispose(bool disposing)
    {
      if (!disposedValue)
      {
        if (disposing)
        {
          richTextBox.Dispose();
          disposedValue = true;
        }
      }
    }

    public void Dispose()
    {
      Dispose(true);
    }
  }

}
