Пример #1
0
 public Data(Font font, Gdi32.QUALITY quality)
 {
     Font      = new WeakReference <Font>(font);
     Quality   = quality;
     HFONT     = FromFont(font, quality);
     _tmHeight = null;
 }
Пример #2
0
 internal static void DrawTextInternal(
     Gdi32.HDC hdc,
     string?text,
     Font?font,
     Rectangle bounds,
     Color foreColor,
     Gdi32.QUALITY fontQuality,
     TextFormatFlags flags)
 => DrawTextInternal(hdc, text, font, bounds, foreColor, fontQuality, Color.Empty, flags);
Пример #3
0
            /// <summary>
            ///  Constructs a WindowsFont object from an existing System.Drawing.Font object (GDI+), based on the screen dc
            ///  MapMode and resolution (normally: MM_TEXT and 96 dpi).
            /// </summary>
            private static Gdi32.HFONT FromFont(Font font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT)
            {
                string familyName = font.FontFamily.Name;

                // Strip vertical-font mark from the name if needed.
                if (familyName != null && familyName.Length > 1 && familyName[0] == '@')
                {
                    familyName = familyName.Substring(1);
                }

                // Now, creating it using the Font.SizeInPoints makes it GraphicsUnit-independent.

                Debug.Assert(font.SizeInPoints > 0.0f, "size has a negative value.");

                // Get the font height from the specified size. The size is in point units and height in logical units
                // (pixels when using MM_TEXT) so we need to make the conversion using the number of pixels per logical
                // inch along the screen height. (1 point = 1/72 inch.)
                int pixelsY = (int)Math.Ceiling(DpiHelper.DeviceDpi * font.SizeInPoints / 72);

                // The lfHeight represents the font cell height (line spacing) which includes the internal leading; we
                // specify a negative size value (in pixels) for the height so the font mapper provides the closest match
                // for the character height rather than the cell height.

                User32.LOGFONTW logFont = new User32.LOGFONTW
                {
                    lfHeight       = -pixelsY,
                    lfCharSet      = font.GdiCharSet,
                    lfOutPrecision = Gdi32.OUT_PRECIS.TT,
                    lfQuality      = quality,
                    lfWeight       = (font.Style & FontStyle.Bold) == FontStyle.Bold ? Gdi32.FW.BOLD : Gdi32.FW.NORMAL,
                    lfItalic       = (font.Style & FontStyle.Italic) == FontStyle.Italic ? True : False,
                    lfUnderline    = (font.Style & FontStyle.Underline) == FontStyle.Underline ? True : False,
                    lfStrikeOut    = (font.Style & FontStyle.Strikeout) == FontStyle.Strikeout ? True : False,
                    FaceName       = familyName
                };

                if (logFont.FaceName.IsEmpty)
                {
                    logFont.FaceName = DefaultFaceName;
                }

                Gdi32.HFONT hfont = Gdi32.CreateFontIndirectW(ref logFont);

                if (hfont.IsNull)
                {
                    // Get the default font if we couldn't get what we requested.
                    logFont.FaceName       = DefaultFaceName;
                    logFont.lfOutPrecision = Gdi32.OUT_PRECIS.TT_ONLY;
                    hfont = Gdi32.CreateFontIndirectW(ref logFont);

                    Debug.Assert(!hfont.IsNull);
                }

                return(hfont);
            }
Пример #4
0
        public static FontCache.Scope GetHFONT(Font?font, Gdi32.QUALITY quality, Gdi32.HDC hdc)
        {
            if (font != null)
            {
                return(GetHFONT(font, quality));
            }

            // Font is null, build off of the specified HDC's current font.
            Gdi32.HFONT hfont = (Gdi32.HFONT)Gdi32.GetCurrentObject(hdc, Gdi32.OBJ.FONT);
            return(new FontCache.Scope(hfont));
        }
Пример #5
0
        public static void DrawText(IDeviceContext dc, string?text, Font?font, Point pt, Color foreColor, Color backColor, TextFormatFlags flags)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }

            Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics);

            using var wgr         = new WindowsGraphicsWrapper(dc, flags);
            using WindowsFont? wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality);
            wgr.WindowsGraphics.DrawText(text, wf, pt, foreColor, backColor, GetTextFormatFlags(flags));
        }
Пример #6
0
 private static void DrawTextInternal(
     Gdi32.HDC hdc,
     ReadOnlySpan <char> text,
     Font?font,
     Rectangle bounds,
     Color foreColor,
     Gdi32.QUALITY fontQuality,
     Color backColor,
     TextFormatFlags flags)
 {
     using var hfont = GdiCache.GetHFONT(font, fontQuality, hdc);
     hdc.DrawText(text, hfont, bounds, foreColor, flags, backColor);
 }
Пример #7
0
 internal static void DrawTextInternal(
     Gdi32.HDC hdc,
     string?text,
     Font?font,
     Rectangle bounds,
     Color foreColor,
     Gdi32.QUALITY fontQuality,
     Color backColor,
     User32.DT flags)
 {
     using var hfont = GdiCache.GetHFONT(font, fontQuality);
     hdc.DrawText(text, hfont, bounds, foreColor, flags, backColor);
 }
Пример #8
0
        public static void DrawText(IDeviceContext dc, string text, Font font, Rectangle bounds, Color foreColor, TextFormatFlags flags)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }

            Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics);

            using (WindowsGraphicsWrapper wgr = new WindowsGraphicsWrapper(dc, flags))
            {
                using (WindowsFont wf = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality))
                {
                    wgr.WindowsGraphics.DrawText(text, wf, bounds, foreColor, GetTextFormatFlags(flags));
                }
            }
        }
Пример #9
0
        public static Size MeasureText(IDeviceContext dc, string?text, Font?font, Size proposedSize, TextFormatFlags flags)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }
            if (string.IsNullOrEmpty(text))
            {
                return(Size.Empty);
            }

            Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics);

            using var wgr = new WindowsGraphicsWrapper(dc, flags);
            using var wf  = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality);
            return(wgr.WindowsGraphics.MeasureText(text, wf, proposedSize, GetTextFormatFlags(flags)));
        }
Пример #10
0
        /// <summary>
        ///  Contructs a WindowsFont object from an existing System.Drawing.Font object (GDI+), based on the screen dc MapMode
        ///  and resolution (normally: MM_TEXT and 96 dpi).
        /// </summary>
        public static WindowsFont FromFont(Font font, Gdi32.QUALITY fontQuality = Gdi32.QUALITY.DEFAULT)
        {
            string familyName = font.FontFamily.Name;

            // Strip vertical-font mark from the name if needed.
            if (familyName != null && familyName.Length > 1 && familyName[0] == '@')
            {
                familyName = familyName.Substring(1);
            }

            // Note: Creating the WindowsFont from Font using a LOGFONT structure from GDI+ (Font.ToLogFont(logFont)) may sound like
            // a better choice (more accurate) for doing this but tests show that is not the case (see WindowsFontTests test suite),
            // the results are the same.  Also, that approach has some issues when the Font is created in a different application
            // domain since the LOGFONT cannot be marshalled properly.

            // Now, creating it using the Font.SizeInPoints makes it GraphicsUnit-independent.

            Debug.Assert(font.SizeInPoints > 0.0f, "size has a negative value.");

            // Get the font height from the specified size.  size is in point units and height in logical
            // units (pixels when using MM_TEXT) so we need to make the conversion using the number of
            // pixels per logical inch along the screen height. (1 point = 1/72 inch.)
            int pixelsY = (int)Math.Ceiling(WindowsGraphicsCacheManager.MeasurementGraphics.DeviceContext.DpiY * font.SizeInPoints / 72);

            // The lfHeight represents the font cell height (line spacing) which includes the internal
            // leading; we specify a negative size value (in pixels) for the height so the font mapper
            // provides the closest match for the character height rather than the cell height (MSDN).

            User32.LOGFONTW logFont = new User32.LOGFONTW()
            {
                lfHeight       = -pixelsY,
                lfCharSet      = font.GdiCharSet,
                lfOutPrecision = Gdi32.OUT_PRECIS.TT,
                lfQuality      = fontQuality,
                lfWeight       = (font.Style & FontStyle.Bold) == FontStyle.Bold ? Gdi32.FW.BOLD : Gdi32.FW.NORMAL,
                lfItalic       = (font.Style & FontStyle.Italic) == FontStyle.Italic ? True : False,
                lfUnderline    = (font.Style & FontStyle.Underline) == FontStyle.Underline ? True : False,
                lfStrikeOut    = (font.Style & FontStyle.Strikeout) == FontStyle.Strikeout ? True : False,
                FaceName       = familyName
            };

            return(new WindowsFont(logFont, font.Style, createHandle: true));
        }
Пример #11
0
        private static Size MeasureTextInternal(
            IDeviceContext dc,
            ReadOnlySpan<char> text,
            Font? font,
            Size proposedSize,
            TextFormatFlags flags = TextFormatFlags.Bottom)
        {
            if (dc is null)
                throw new ArgumentNullException(nameof(dc));

            if (text.IsEmpty)
                return Size.Empty;

            // This MUST come before retreiving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            using var hdc = new DeviceContextHdcScope(dc);
            using var hfont = GdiCache.GetHFONT(font, quality, hdc);
            return hdc.MeasureText(text, hfont, proposedSize, GetTextFormatFlags(flags));
        }
Пример #12
0
        public static void DrawText(IDeviceContext dc, string?text, Font?font, Rectangle bounds, Color foreColor)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }

            Gdi32.QUALITY fontQuality = WindowsFont.WindowsFontQualityFromTextRenderingHint(dc as Graphics);

            IntPtr hdc = dc.GetHdc();

            try
            {
                using WindowsGraphics wg = WindowsGraphics.FromHdc(hdc);
                using WindowsFont? wf    = WindowsGraphicsCacheManager.GetWindowsFont(font, fontQuality);
                wg.DrawText(text, wf, bounds, foreColor);
            }
            finally
            {
                dc.ReleaseHdc();
            }
        }
Пример #13
0
        private static Size MeasureTextInternal(
            IDeviceContext dc,
            ReadOnlySpan <char> text,
            Font?font,
            Size proposedSize,
            TextFormatFlags flags = TextFormatFlags.Bottom)
        {
            ArgumentNullException.ThrowIfNull(dc);

            if (text.IsEmpty)
            {
                return(Size.Empty);
            }

            // This MUST come before retrieving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            // Applying state may not impact text size measurements. Rather than risk missing some
            // case we'll apply as we have historically to avoid surprise regressions.
            using var hdc   = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags));
            using var hfont = GdiCache.GetHFONT(font, quality, hdc);
            return(hdc.HDC.MeasureText(text, hfont, proposedSize, flags));
        }
Пример #14
0
        internal static void DrawTextInternal(
            IDeviceContext dc,
            ReadOnlySpan<char> text,
            Font? font,
            Rectangle bounds,
            Color foreColor,
            Color backColor,
            User32.DT flags = User32.DT.CENTER | User32.DT.VCENTER)
        {
            if (dc is null)
                throw new ArgumentNullException(nameof(dc));

            // Avoid creating the HDC, etc if we're not going to do any drawing
            if (text.IsEmpty || foreColor == Color.Transparent)
                return;

            // This MUST come before retreiving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            using var hdc = new DeviceContextHdcScope(dc);

            DrawTextInternal(hdc, text, font, bounds, foreColor, quality, backColor, flags);
        }
Пример #15
0
        internal static void DrawTextInternal(
            PaintEventArgs e,
            string?text,
            Font?font,
            Rectangle bounds,
            Color foreColor,
            Color backColor,
            TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter)
        {
            Gdi32.HDC hdc = e.HDC;
            if (hdc.IsNull)
            {
                // This MUST come before retreiving the HDC, which locks the Graphics object
                Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(e.GraphicsInternal);

                using var graphicsHdc = new DeviceContextHdcScope(e.GraphicsInternal, applyGraphicsState: false);
                DrawTextInternal(graphicsHdc, text, font, bounds, foreColor, quality, backColor, flags);
            }
            else
            {
                DrawTextInternal(hdc, text, font, bounds, foreColor, DefaultQuality, backColor, flags);
            }
        }
Пример #16
0
        internal static void DrawTextInternal(
            IDeviceContext dc,
            ReadOnlySpan <char> text,
            Font?font,
            Rectangle bounds,
            Color foreColor,
            Color backColor,
            TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter)
        {
            ArgumentNullException.ThrowIfNull(dc);

            // Avoid creating the HDC, etc if we're not going to do any drawing
            if (text.IsEmpty || foreColor == Color.Transparent)
            {
                return;
            }

            // This MUST come before retrieving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            using var hdc = new DeviceContextHdcScope(dc, GetApplyStateFlags(dc, flags));

            DrawTextInternal(hdc, text, font, bounds, foreColor, quality, backColor, flags);
        }
Пример #17
0
        private static Size MeasureTextInternal(
            IDeviceContext dc,
            string?text,
            Font?font,
            Size proposedSize,
            TextFormatFlags flags = TextFormatFlags.Bottom)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }

            if (string.IsNullOrEmpty(text))
            {
                return(Size.Empty);
            }

            // This MUST come before retreiving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            using var hdc   = new DeviceContextHdcScope(dc);
            using var hfont = GdiCache.GetHFONT(font, quality);
            return(hdc.MeasureText(text, hfont, proposedSize, GetTextFormatFlags(flags)));
        }
Пример #18
0
        private static Size MeasureTextInternal(
            IDeviceContext dc,
            string?text,
            Font?font,
            Size proposedSize,
            TextFormatFlags flags = TextFormatFlags.Bottom)
        {
            if (dc == null)
            {
                throw new ArgumentNullException(nameof(dc));
            }

            if (string.IsNullOrEmpty(text))
            {
                return(Size.Empty);
            }

            // This MUST come before retreiving the HDC, which locks the Graphics object
            Gdi32.QUALITY quality = FontQualityFromTextRenderingHint(dc);

            using var wgr = new WindowsGraphicsWrapper(dc, flags);
            using var wf  = WindowsGraphicsCacheManager.GetWindowsFont(font, quality);
            return(wgr.WindowsGraphics.MeasureText(text, wf, proposedSize, GetTextFormatFlags(flags)));
        }
Пример #19
0
        /// <summary>
        ///  Gets a ref-counting scope containing the <see cref="Gdi32.HFONT"/> that matches the specified
        ///  <paramref name="font"/> and <paramref name="quality"/>. The scope MUST be disposed to release the ref
        ///  count accurately. Use the result in a using statement to avoid leaking fonts.
        /// </summary>
        public FontScope GetHFONT(Font font, Gdi32.QUALITY quality)
        {
            if (font is null)
            {
                throw new ArgumentNullException(nameof(font));
            }

            if (_clean)
            {
                _lock.EnterWriteLock();
                try
                {
                    Clean();
                }
                finally
                {
                    _lock.ExitWriteLock();
                }
            }

            _lock.EnterReadLock();
            try
            {
                if (Find(font, quality, out FontScope scope))
                {
                    return(scope);
                }
            }
            finally
            {
                _lock.ExitReadLock();
            }

            _lock.EnterWriteLock();
            try
            {
                return(Add(font, quality));
            }
            finally
            {
                _lock.ExitWriteLock();
            }

            bool Find(Font font, Gdi32.QUALITY quality, out FontScope scope)
            {
                scope = default;
                LinkedListNode <Data>?node = _list.First;

                while (node != null)
                {
                    Data data = node.Value;

                    if (!data.Font.TryGetTarget(out Font? currentFont))
                    {
                        // Found a dead node, mark ourselves for cleaning
                        _clean = true;
                    }
                    else if (font == currentFont && quality == data.Quality)
                    {
                        // Current node is still alive
                        scope = new FontScope(data);
                        return(true);
                    }

                    node = node.Next;
                }

                return(false);
            }

            FontScope Add(Font font, Gdi32.QUALITY quality)
            {
                // Need to try and find the node again as it may have been created on another thread.
                if (Find(font, quality, out FontScope scope))
                {
                    return(scope);
                }

                Data node = new Data(font, quality);

                _list.AddFirst(node);
                _count++;
                if (_count > _softLimit)
                {
                    _clean = true;
                }
                return(new FontScope(node));
            }

            void Clean()
            {
                if (!_clean)
                {
                    return;
                }

                _clean = false;

                // Get rid of all collected Font nodes where the HFONT is unused

                LinkedListNode <Data>?node = _list.First;

                while (node != null)
                {
                    Data data = node.Value;
                    LinkedListNode <Data>?nextNode = node.Next;

                    if (!data.Font.TryGetTarget(out Font? _))
                    {
                        if (data.RefCount == 0)
                        {
                            // Font was garbage collected, and the HFONT isn't currently used
                            Gdi32.DeleteObject(data.HFONT);
                            _list.Remove(node);
                            _count--;
                        }
                        else
                        {
                            // Mark ourselves for clean again so we can get this later
                            _clean = true;
                        }
                    }

                    node = nextNode;
                }

                // If the count is over hard limit, expunge excess entries from the end of the list
                // where the ref count is 0.

                int overage = _count - _hardLimit;

                if (overage <= 0)
                {
                    return;
                }

                node = _list.Last;

                while (node != null && overage > 0)
                {
                    Data data = node.Value;
                    LinkedListNode <Data>?priorNode = node.Previous;

                    if (data.RefCount == 0)
                    {
                        // HFONT isn't currently in use, remove the node.
                        Gdi32.DeleteObject(data.HFONT);
                        _list.Remove(node);
                        _count--;
                        overage--;
                    }

                    node = priorNode;
                }

                _clean |= _count > _softLimit;
            }
        }
        /// <summary>
        ///  Get the cached WindowsFont associated with the specified font if one exists, otherwise create one and
        ///  add it to the cache.
        /// </summary>
        public static WindowsFont?GetWindowsFont(Font?font, Gdi32.QUALITY fontQuality = Gdi32.QUALITY.DEFAULT)
        {
            if (font == null)
            {
                return(null);
            }

            // First check if font is in the cache.

            int count = 0;
            int index = t_currentIndex;

            // Search by index of most recently added object.
            while (count < WindowsFontCache.Count)
            {
                if (WindowsFontCache[index].Key.Equals(font))  // don't do shallow comparison, we could miss cloned fonts.
                {
                    // We got a Font in the cache, let's see if we have a WindowsFont with the same quality as required by the caller.

                    // WARNING: It is not expected that the WindowsFont is disposed externally since it is created by this class.
                    Debug.Assert(WindowsFontCache[index].Value.Hfont != IntPtr.Zero, "Cached WindowsFont was disposed, enable GDI_FINALIZATION_WATCH to track who did it!");

                    WindowsFont wf = WindowsFontCache[index].Value;
                    if (wf.Quality == fontQuality)
                    {
                        return(wf);
                    }
                }

                index--;
                count++;

                if (index < 0)
                {
                    index = CacheSize - 1;
                }
            }

            // Font is not in the cache, let's add it.

            WindowsFont winFont = WindowsFont.FromFont(font, fontQuality);
            KeyValuePair <Font, WindowsFont> newEntry = new KeyValuePair <Font, WindowsFont>(font, winFont);

            t_currentIndex++;

            if (t_currentIndex == CacheSize)
            {
                t_currentIndex = 0;
            }

            if (WindowsFontCache.Count == CacheSize)  // No more room, update current index.
            {
                WindowsFont?wfont = null;

                // Go through the existing fonts in the cache, and see if any
                // are not in use by a DC.  If one isn't, replace that.  If
                // all are in use, new up a new font and do not cache it.

                bool finished   = false;
                int  startIndex = t_currentIndex;
                int  loopIndex  = startIndex + 1;
                while (!finished)
                {
                    if (loopIndex >= CacheSize)
                    {
                        loopIndex = 0;
                    }

                    if (loopIndex == startIndex)
                    {
                        finished = true;
                    }

                    wfont = WindowsFontCache[loopIndex].Value;
                    if (!DeviceContexts.IsFontInUse(wfont))
                    {
                        t_currentIndex = loopIndex;
                        finished       = true;
                        break;
                    }
                    else
                    {
                        loopIndex++;
                        wfont = null;
                    }
                }

                if (wfont != null)
                {
                    WindowsFontCache[t_currentIndex] = newEntry;
                    winFont.OwnedByCacheManager      = true;

                    wfont.OwnedByCacheManager = false;
                    wfont.Dispose();
                }
                else
                {
                    // do not cache font - caller is ALWAYS responsible for
                    // disposing now.  If it is owned  by the CM, it will not
                    // disposed.
                    winFont.OwnedByCacheManager = false;
                }
            }
            else
            {
                winFont.OwnedByCacheManager = true;
                WindowsFontCache.Add(newEntry);
            }
            return(winFont);
        }
Пример #21
0
 /// <summary>
 ///  Gets a cached <see cref="Gdi32.HFONT"/> based off of the given <paramref name="font"/> and
 ///  <paramref name="quality"/>.
 /// </summary>
 /// <remarks>
 ///  Use in a using statement for proper cleanup.
 ///
 ///  When disposed the <see cref="Gdi32.HFONT"/> will be returned to the cache.  If you must pass the scope to
 ///  another method it must be passed by reference or you risk double disposal and accidentally returning extra
 ///  copies to the cache.
 /// </remarks>
 public static FontCache.FontScope GetHFONT(Font?font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT)
 => font is null ? default : s_fontCache.GetHFONT(font, quality);
Пример #22
0
 /// <summary>
 ///  Gets a cached <see cref="Gdi32.HFONT"/> based off of the given <paramref name="font"/> and
 ///  <paramref name="quality"/>.
 /// </summary>
 /// <remarks>
 ///  Use in a using statement for proper cleanup.
 ///
 ///  When disposed the <see cref="Gdi32.HFONT"/> will be returned to the cache.  If you must pass the scope to
 ///  another method it must be passed by reference or you risk double disposal and accidentally returning extra
 ///  copies to the cache.
 /// </remarks>
 public static FontCache.Scope GetHFONT(Font?font, Gdi32.QUALITY quality = Gdi32.QUALITY.DEFAULT)
 {
     Debug.Assert(font != null);
     return(font is null ? new FontCache.Scope() : s_fontCache.GetEntry(font, quality));
 }