// "Therefore those skilled at the unorthodox // are infinite as heaven and earth, // inexhaustible as the great rivers. // When they come to an end, // they begin again, // like the days and months; // they die and are reborn, // like the four seasons." // // - Sun Tsu, // "The Art of War" using System; using System.Drawing; using System.Drawing.Drawing2D; using TheArtOfDev.HtmlRenderer.Adapters.Entities; using TheArtOfDev.HtmlRenderer.Core.Utils; using TheArtOfDev.HtmlRenderer.Adapters; using TheArtOfDev.HtmlRenderer.WinForms.Utilities; namespace TheArtOfDev.HtmlRenderer.WinForms.Adapters { /// /// Adapter for WinForms Graphics for core. /// internal sealed class GraphicsAdapter : RGraphics { #region Fields and Consts /// /// used for calculation. /// private static readonly int[] _charFit = new int[1]; /// /// used for calculation. /// private static readonly int[] _charFitWidth = new int[1000]; /// /// Used for GDI+ measure string. /// private static readonly CharacterRange[] _characterRanges = new CharacterRange[1]; /// /// The string format to use for measuring strings for GDI+ text rendering /// private static readonly StringFormat _stringFormat; /// /// The string format to use for rendering strings for GDI+ text rendering /// private static readonly StringFormat _stringFormat2; /// /// The wrapped WinForms graphics object /// private readonly Graphics _g; /// /// Use GDI+ text rendering to measure/draw text. /// private readonly bool _useGdiPlusTextRendering; #if !MONO /// /// the initialized HDC used /// private IntPtr _hdc; #endif /// /// if to release the graphics object on dispose /// private readonly bool _releaseGraphics; /// /// If text alignment was set to RTL /// private bool _setRtl; #endregion /// /// Init static resources. /// static GraphicsAdapter() { _stringFormat = new StringFormat(StringFormat.GenericTypographic); _stringFormat.FormatFlags = StringFormatFlags.NoClip | StringFormatFlags.MeasureTrailingSpaces; _stringFormat2 = new StringFormat(StringFormat.GenericTypographic); } /// /// Init. /// /// the win forms graphics object to use /// Use GDI+ text rendering to measure/draw text /// optional: if to release the graphics object on dispose (default - false) public GraphicsAdapter(Graphics g, bool useGdiPlusTextRendering, bool releaseGraphics = false) : base(WinFormsAdapter.Instance, Utils.Convert(g.ClipBounds)) { ArgChecker.AssertArgNotNull(g, "g"); _g = g; _releaseGraphics = releaseGraphics; #if MONO _useGdiPlusTextRendering = true; #else _useGdiPlusTextRendering = useGdiPlusTextRendering; #endif } public override void PopClip() { ReleaseHdc(); _clipStack.Pop(); _g.SetClip(Utils.Convert(_clipStack.Peek()), CombineMode.Replace); } public override void PushClip(RRect rect) { ReleaseHdc(); _clipStack.Push(rect); _g.SetClip(Utils.Convert(rect), CombineMode.Replace); } public override void PushClipExclude(RRect rect) { ReleaseHdc(); _clipStack.Push(_clipStack.Peek()); _g.SetClip(Utils.Convert(rect), CombineMode.Exclude); } public override Object SetAntiAliasSmoothingMode() { ReleaseHdc(); var prevMode = _g.SmoothingMode; _g.SmoothingMode = SmoothingMode.AntiAlias; return prevMode; } public override void ReturnPreviousSmoothingMode(Object prevMode) { if (prevMode != null) { ReleaseHdc(); _g.SmoothingMode = (SmoothingMode)prevMode; } } public override RSize MeasureString(string str, RFont font) { if (_useGdiPlusTextRendering) { ReleaseHdc(); var fontAdapter = (FontAdapter)font; var realFont = fontAdapter.Font; _characterRanges[0] = new CharacterRange(0, str.Length); _stringFormat.SetMeasurableCharacterRanges(_characterRanges); var size = _g.MeasureCharacterRanges(str, realFont, RectangleF.Empty, _stringFormat)[0].GetBounds(_g).Size; if (font.Height < 0) { var height = realFont.Height; var descent = realFont.Size * realFont.FontFamily.GetCellDescent(realFont.Style) / realFont.FontFamily.GetEmHeight(realFont.Style); fontAdapter.SetMetrics(height, (int)Math.Round((height - descent + .5f))); } return Utils.Convert(size); } else { #if !MONO SetFont(font); var size = new Size(); Win32Utils.GetTextExtentPoint32(_hdc, str, str.Length, ref size); if (font.Height < 0) { TextMetric lptm; Win32Utils.GetTextMetrics(_hdc, out lptm); ((FontAdapter)font).SetMetrics(size.Height, lptm.tmHeight - lptm.tmDescent + lptm.tmUnderlined + 1); } return Utils.Convert(size); #else throw new InvalidProgramException("Invalid Mono code"); #endif } } public override void MeasureString(string str, RFont font, double maxWidth, out int charFit, out double charFitWidth) { charFit = 0; charFitWidth = 0; if (_useGdiPlusTextRendering) { ReleaseHdc(); var size = MeasureString(str, font); for (int i = 1; i <= str.Length; i++) { charFit = i - 1; RSize pSize = MeasureString(str.Substring(0, i), font); if (pSize.Height <= size.Height && pSize.Width < maxWidth) charFitWidth = pSize.Width; else break; } } else { #if !MONO SetFont(font); var size = new Size(); Win32Utils.GetTextExtentExPoint(_hdc, str, str.Length, (int)Math.Round(maxWidth), _charFit, _charFitWidth, ref size); charFit = _charFit[0]; charFitWidth = charFit > 0 ? _charFitWidth[charFit - 1] : 0; #endif } } public override void DrawString(string str, RFont font, RColor color, RPoint point, RSize size, bool rtl) { if (_useGdiPlusTextRendering) { ReleaseHdc(); SetRtlAlignGdiPlus(rtl); var brush = ((BrushAdapter)_adapter.GetSolidBrush(color)).Brush; _g.DrawString(str, ((FontAdapter)font).Font, brush, (int)(Math.Round(point.X) + (rtl ? size.Width : 0)), (int)Math.Round(point.Y), _stringFormat2); } else { #if !MONO var pointConv = Utils.ConvertRound(point); var colorConv = Utils.Convert(color); if (color.A == 255) { SetFont(font); SetTextColor(colorConv); SetRtlAlignGdi(rtl); Win32Utils.TextOut(_hdc, pointConv.X, pointConv.Y, str, str.Length); } else { InitHdc(); SetRtlAlignGdi(rtl); DrawTransparentText(_hdc, str, font, pointConv, Utils.ConvertRound(size), colorConv); } #endif } } public override RBrush GetTextureBrush(RImage image, RRect dstRect, RPoint translateTransformLocation) { var brush = new TextureBrush(((ImageAdapter)image).Image, Utils.Convert(dstRect)); brush.TranslateTransform((float)translateTransformLocation.X, (float)translateTransformLocation.Y); return new BrushAdapter(brush, true); } public override RGraphicsPath GetGraphicsPath() { return new GraphicsPathAdapter(); } public override void Dispose() { ReleaseHdc(); if (_releaseGraphics) _g.Dispose(); if (_useGdiPlusTextRendering && _setRtl) _stringFormat2.FormatFlags ^= StringFormatFlags.DirectionRightToLeft; } #region Delegate graphics methods public override void DrawLine(RPen pen, double x1, double y1, double x2, double y2) { ReleaseHdc(); _g.DrawLine(((PenAdapter)pen).Pen, (float)x1, (float)y1, (float)x2, (float)y2); } public override void DrawRectangle(RPen pen, double x, double y, double width, double height) { ReleaseHdc(); _g.DrawRectangle(((PenAdapter)pen).Pen, (float)x, (float)y, (float)width, (float)height); } public override void DrawRectangle(RBrush brush, double x, double y, double width, double height) { ReleaseHdc(); _g.FillRectangle(((BrushAdapter)brush).Brush, (float)x, (float)y, (float)width, (float)height); } public override void DrawImage(RImage image, RRect destRect, RRect srcRect) { ReleaseHdc(); _g.DrawImage(((ImageAdapter)image).Image, Utils.Convert(destRect), Utils.Convert(srcRect), GraphicsUnit.Pixel); } public override void DrawImage(RImage image, RRect destRect) { ReleaseHdc(); _g.DrawImage(((ImageAdapter)image).Image, Utils.Convert(destRect)); } public override void DrawPath(RPen pen, RGraphicsPath path) { _g.DrawPath(((PenAdapter)pen).Pen, ((GraphicsPathAdapter)path).GraphicsPath); } public override void DrawPath(RBrush brush, RGraphicsPath path) { ReleaseHdc(); _g.FillPath(((BrushAdapter)brush).Brush, ((GraphicsPathAdapter)path).GraphicsPath); } public override void DrawPolygon(RBrush brush, RPoint[] points) { if (points != null && points.Length > 0) { ReleaseHdc(); _g.FillPolygon(((BrushAdapter)brush).Brush, Utils.Convert(points)); } } #endregion #region Private methods /// /// Release current HDC to be able to use methods. /// private void ReleaseHdc() { #if !MONO if (_hdc != IntPtr.Zero) { Win32Utils.SelectClipRgn(_hdc, IntPtr.Zero); _g.ReleaseHdc(_hdc); _hdc = IntPtr.Zero; } #endif } #if !MONO /// /// Init HDC for the current graphics object to be used to call GDI directly. /// private void InitHdc() { if (_hdc == IntPtr.Zero) { var clip = _g.Clip.GetHrgn(_g); _hdc = _g.GetHdc(); _setRtl = false; Win32Utils.SetBkMode(_hdc, 1); Win32Utils.SelectClipRgn(_hdc, clip); Win32Utils.DeleteObject(clip); } } /// /// Set a resource (e.g. a font) for the specified device context. /// WARNING: Calling Font.ToHfont() many times without releasing the font handle crashes the app. /// private void SetFont(RFont font) { InitHdc(); Win32Utils.SelectObject(_hdc, ((FontAdapter)font).HFont); } /// /// Set the text color of the device context. /// private void SetTextColor(Color color) { InitHdc(); int rgb = (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R; Win32Utils.SetTextColor(_hdc, rgb); } /// /// Change text align to Left-to-Right or Right-to-Left if required. /// private void SetRtlAlignGdi(bool rtl) { if (_setRtl) { if (!rtl) Win32Utils.SetTextAlign(_hdc, Win32Utils.TextAlignDefault); } else if (rtl) { Win32Utils.SetTextAlign(_hdc, Win32Utils.TextAlignRtl); } _setRtl = rtl; } /// /// Special draw logic to draw transparent text using GDI.
/// 1. Create in-memory DC
/// 2. Copy background to in-memory DC
/// 3. Draw the text to in-memory DC
/// 4. Copy the in-memory DC to the proper location with alpha blend
///
private static void DrawTransparentText(IntPtr hdc, string str, RFont font, Point point, Size size, Color color) { IntPtr dib; var memoryHdc = Win32Utils.CreateMemoryHdc(hdc, size.Width, size.Height, out dib); try { // copy target background to memory HDC so when copied back it will have the proper background Win32Utils.BitBlt(memoryHdc, 0, 0, size.Width, size.Height, hdc, point.X, point.Y, Win32Utils.BitBltCopy); // Create and select font Win32Utils.SelectObject(memoryHdc, ((FontAdapter)font).HFont); Win32Utils.SetTextColor(memoryHdc, (color.B & 0xFF) << 16 | (color.G & 0xFF) << 8 | color.R); // Draw text to memory HDC Win32Utils.TextOut(memoryHdc, 0, 0, str, str.Length); // copy from memory HDC to normal HDC with alpha blend so achieve the transparent text Win32Utils.AlphaBlend(hdc, point.X, point.Y, size.Width, size.Height, memoryHdc, 0, 0, size.Width, size.Height, new BlendFunction(color.A)); } finally { Win32Utils.ReleaseMemoryHdc(memoryHdc, dib); } } #endif /// /// Change text align to Left-to-Right or Right-to-Left if required. /// private void SetRtlAlignGdiPlus(bool rtl) { if (_setRtl) { if (!rtl) _stringFormat2.FormatFlags ^= StringFormatFlags.DirectionRightToLeft; } else if (rtl) { _stringFormat2.FormatFlags |= StringFormatFlags.DirectionRightToLeft; } _setRtl = rtl; } #endregion } }