| File: System\Windows\Forms\Layout\LayoutUtils.MeasureTextCache.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Drawing; namespace System.Windows.Forms.Layout; internal static partial class LayoutUtils { /// MeasureTextCache /// 3000 character strings take 9 seconds to load the form public sealed class MeasureTextCache { // the number of preferred sizes to store private const int MaxCacheSize = 6; // the next place in the ring buffer to store a preferred size private int _nextCacheEntry = -1; // MRU of size MaxCacheSize private PreferredSizeCache[]? _sizeCacheList; private Size _unconstrainedPreferredSize = s_invalidSize; /// GetTextSize /// Given constraints, format flags a font and text, determine the size of the string /// employs an MRU of the last several constraints passed in via a ring-buffer of size MaxCacheSize. /// Assumes Text and TextFormatFlags are the same, if either were to change, a call to /// InvalidateCache should be made public Size GetTextSize(string? text, Font? font, Size proposedConstraints, TextFormatFlags flags) { if (!TextRequiresWordBreak(text, font, proposedConstraints, flags)) { // Text fits within proposed width // IF we're here, this means we've got text that can fit into the proposedConstraints // without wrapping. We've determined this because our // as a side effect of calling TextRequiresWordBreak, // unconstrainedPreferredSize is set. return _unconstrainedPreferredSize; } else { // Text does NOT fit within proposed width - requires WordBreak // IF we're here, this means that the wrapping width is smaller // than our max width. For example: we measure the text with infinite // bounding box and we determine the width to fit all the characters // to be 200 px wide. We would come here only for proposed widths less // than 200 px. // Create our ring buffer if we don't have one _sizeCacheList ??= new PreferredSizeCache[MaxCacheSize]; // check the existing constraints from previous calls foreach (PreferredSizeCache sizeCache in _sizeCacheList) { if (sizeCache.ConstrainingSize == proposedConstraints) { return sizeCache.PreferredSize; } else if ((sizeCache.ConstrainingSize.Width == proposedConstraints.Width) && (sizeCache.PreferredSize.Height <= proposedConstraints.Height)) { // Caching a common case where the width matches perfectly, and the stored preferred height // is smaller or equal to the constraining size. // prefSize = GetPreferredSize(w,Int32.MaxValue); // prefSize = GetPreferredSize(w,prefSize.Height); return sizeCache.PreferredSize; } // } // if we've gotten here, it means we don't have a cache entry, therefore // we should add a new one in the next available slot. Size prefSize = TextRenderer.MeasureText(text, font, proposedConstraints, flags); _nextCacheEntry = (_nextCacheEntry + 1) % MaxCacheSize; _sizeCacheList[_nextCacheEntry] = new PreferredSizeCache(proposedConstraints, prefSize); return prefSize; } } /// InvalidateCache /// Clears out the cached values, should be called whenever Text, Font or a TextFormatFlag has changed public void InvalidateCache() { _unconstrainedPreferredSize = s_invalidSize; _sizeCacheList = null; } /// TextRequiresWordBreak /// If you give the text all the space in the world it wants, then there should be no reason /// for it to break on a word. So we find out what the unconstrained size is (Int32.MaxValue, Int32.MaxValue) /// for a string - eg. 35, 13. If the size passed in has a larger width than 35, then we know that /// the WordBreak flag is not necessary. public bool TextRequiresWordBreak(string? text, Font? font, Size size, TextFormatFlags flags) { // if the unconstrained size of the string is larger than the proposed width // we need the word break flag, otherwise we don't, its a perf hit to use it. return GetUnconstrainedSize(text, font, flags).Width > size.Width; } /// GetUnconstrainedSize /// Gets the unconstrained (Int32.MaxValue, Int32.MaxValue) size for a piece of text private Size GetUnconstrainedSize(string? text, Font? font, TextFormatFlags flags) { if (_unconstrainedPreferredSize == s_invalidSize) { // we also investigated setting the SingleLine flag, however this did not yield as much benefit as the word break // and had possibility of causing internationalization issues. flags &= ~TextFormatFlags.WordBreak; // rip out the wordbreak flag _unconstrainedPreferredSize = TextRenderer.MeasureText(text, font, s_maxSize, flags); } return _unconstrainedPreferredSize; } } }