/// <summary> /// Cache a <paramref name="measuredSize"/> for an <paramref name="availableSize"/>, given /// the <paramref name="source"/> characteristics. /// </summary> /// <param name="source"></param> /// <param name="availableSize"></param> /// <param name="measuredSize"></param> public void CacheMeasure(TextBlock source, Size availableSize, Size measuredSize) { var key = new MeasureKey(source); if (_entries.TryGetValue(key, out var entry)) { if (_queue.Count > 1 && !_queue.Last.Value.Equals(key)) { // Move this key as last in the queue for perf _queue.Remove(entry.ListNode); _queue.AddLast(entry.ListNode); } } else { Scavenge(); var node = _queue.AddLast(key); _entries[key] = entry = new MeasureEntry(node); } if (this.Log().IsEnabled(LogLevel.Debug)) { // {0} is used to avoid parsing errors caused by formatting a "{}" in the text this.Log().LogDebug("{0}", $"TextMeasure-new [{source.Text} / {source.TextWrapping} / {source.MaxLines}]: {availableSize} -> {measuredSize}"); } entry.CacheMeasure(availableSize, measuredSize); }
/// <summary> /// Finds a cached measure for the provided <see cref="TextBlock"/> characteristics /// given an <paramref name="availableSize"/>. /// </summary> /// <param name="source">The source</param> /// <param name="availableSize">The available size to query</param> /// <returns>An optional <see cref="Size"/> if found.</returns> public Size?FindMeasuredSize(TextBlock source, Size availableSize) { if (!FeatureConfiguration.TextBlock.IsMeasureCacheEnabled) { return(null); // Measure cache feature disabled } var key = new MeasureKey(source); // Extract a key from TextBlock properties if (!_entries.TryGetValue(key, out var entry)) { if (this.Log().IsEnabled(LogLevel.Debug)) { // {0} is used to avoid parsing errors caused by formatting a "{}" in the text this.Log().LogDebug("{0}", $"TextMeasure-cached [{source.Text} / {source.TextWrapping} / {source.MaxLines}]: {availableSize} -> NOT FOUND."); } return(null); // This key not present in cache } var measuredSize = entry.FindMeasuredSize(key, availableSize); // Get cache for specified availableSize, if exists if (this.Log().IsEnabled(LogLevel.Debug)) { // {0} is used to avoid parsing errors caused by formatting a "{}" in the text this.Log().LogDebug("{0}", $"TextMeasure-cached [{source.Text} / {source.TextWrapping} / {source.MaxLines}]: {availableSize} -> {measuredSize}"); } return(measuredSize); }
public MeasureData(MeasureKey key, double val, int threadID) { Parent = key.Parent; Key = key.Key; RecordedValue = val; ThreadID = threadID; }
public override bool Equals(object obj) { if (obj is MeasureKey) { MeasureKey other = (MeasureKey)obj; if (other.Key == Key && other.Parent == Parent) { return(true); } } return(false); }
/// <summary> /// Finds a cached measure for the provided <see cref="TextBlock"/> characteristics /// given an <paramref name="availableSize"/>. /// </summary> /// <param name="source">The source</param> /// <param name="availableSize">The available size to query</param> /// <returns>An optional <see cref="Size"/> if found.</returns> public Size?FindMeasuredSize(TextBlock source, Size availableSize) { var key = new MeasureKey(source); if (FeatureConfiguration.TextBlock.IsMeasureCacheEnabled && _entries.TryGetValue(key, out var entry)) { var measuredSize = entry.FindMeasuredSize(key, availableSize); if (this.Log().IsEnabled(LogLevel.Debug)) { // {0} is used to avoid parsing errors caused by formatting a "{}" in the text this.Log().LogDebug("{0}", $"TextMeasure-cached [{source.Text} / {source.TextWrapping} / {source.MaxLines}]: {availableSize} -> {measuredSize}"); } return(measuredSize); } return(null); }
public Size?FindMeasuredSize(MeasureKey key, Size availableSize) { if (_sizes.TryGetValue(CachedTuple.Create(availableSize.Width, availableSize.Height), out var sizeEntry)) { return(sizeEntry.MeasuredSize); } else { if (key.TextWrapping == TextWrapping.NoWrap) { // No wrap, assume any measured width below the asked available size // is valid, if the available size is greater. foreach (var keySize in _sizes) { if (keySize.Key.Item1 >= availableSize.Width && keySize.Value.MeasuredSize.Width <= availableSize.Width) { MoveToLast(keySize.Key, keySize.Value); return(keySize.Value.MeasuredSize); } } } else { foreach (var keySize in _sizes) { // If text wraps and the available width is the same, any height below the // available size is valid. if ( keySize.Key.Item1 == availableSize.Width && keySize.Value.MeasuredSize.Height <= availableSize.Height ) { MoveToLast(keySize.Key, keySize.Value); return(keySize.Value.MeasuredSize); } } } } return(null); }
public Size?FindMeasuredSize(MeasureKey key, Size availableSize) { var currentAvailableWidth = availableSize.Width; var currentAvailableHeight = availableSize.Height; if (_sizes.TryGetValue(CachedTuple.Create(currentAvailableWidth, currentAvailableHeight), out var sizeEntry)) { return(sizeEntry.MeasuredSize); } var isWrapping = key.IsWrapping; var isClipping = key.IsClipping; foreach (var kvp in _sizes) { var size = kvp.Key; var measureSizeEntry = kvp.Value; var measurementCachedWidth = measureSizeEntry.MeasuredSize.Width; var measurementCachedHeight = measureSizeEntry.MeasuredSize.Height; var measurementAvailableWidth = size.Item1; var measurementAvailableHeight = size.Item2; if (isWrapping || isClipping) { if (measurementCachedWidth <= currentAvailableWidth && currentAvailableWidth <= measurementAvailableWidth) { // Ok we can reuse it } else { continue; // Check for another cached measurement } } else { // Non-wrapping text if (double.IsInfinity(measurementAvailableWidth)) { // Previous measurement was unconstrained // horizontally: we can definitely reuse it. } else { if (Math.Abs(measurementCachedWidth - measurementAvailableWidth) < 0.5d) { // This measure was constrained, we can reuse only if the width is the same. if (Math.Abs(measurementCachedWidth - currentAvailableWidth) < 0.5d) { // Yep, that's good } else { continue; // Check for another cached measurement } } } } // We need to make sure the height is ok if (double.IsInfinity(measurementAvailableHeight)) { // Previous measurement was unconstrained // vertically: we can definitely reuse it. } else { // A max-height was specified in the cached measurement: // We must check if we can reuse it. if (Math.Abs(measurementCachedHeight - measurementAvailableHeight) < 0.5d) { // This measure was constrained, we can reuse only if the available height // is same or higher than current available height if (measurementCachedHeight >= currentAvailableHeight) { // Yep, that's good } else { continue; // Check for another cached measurement } } } // Got it, this cached measurement fits MoveToLast(size, measureSizeEntry); return(measureSizeEntry.MeasuredSize); } return(null); // No valid cache entry found }