diff --git a/Ink Canvas/Helpers/AutoFontSizeHelper.cs b/Ink Canvas/Helpers/AutoFontSizeHelper.cs index c14b74a8..4e6b432e 100644 --- a/Ink Canvas/Helpers/AutoFontSizeHelper.cs +++ b/Ink Canvas/Helpers/AutoFontSizeHelper.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Globalization; using System.Windows; using System.Windows.Controls; +using System.Windows.Controls.Primitives; using System.Windows.Media; using System.Windows.Threading; @@ -65,6 +66,16 @@ namespace Ink_Canvas.Helpers private static void SetIsAdjusting(DependencyObject element, bool value) => element.SetValue(IsAdjustingProperty, value); private static bool GetIsAdjusting(DependencyObject element) => (bool)element.GetValue(IsAdjustingProperty); + private static readonly DependencyProperty OriginalFontSizeProperty = + DependencyProperty.RegisterAttached( + "OriginalFontSize", + typeof(double), + typeof(AutoFontSizeHelper), + new PropertyMetadata(double.NaN)); + + private static void SetOriginalFontSize(DependencyObject element, double value) => element.SetValue(OriginalFontSizeProperty, value); + private static double GetOriginalFontSize(DependencyObject element) => (double)element.GetValue(OriginalFontSizeProperty); + private static void OnIsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is FrameworkElement fe)) return; @@ -72,6 +83,12 @@ namespace Ink_Canvas.Helpers if ((bool)e.NewValue) { + var originalFontSize = GetElementFontSize(fe); + if (!double.IsNaN(originalFontSize) && originalFontSize > 0) + { + SetOriginalFontSize(fe, originalFontSize); + } + fe.SizeChanged += Element_OnSizeChanged; fe.Loaded += Element_OnLoaded; fe.Unloaded += Element_OnUnloaded; @@ -155,6 +172,12 @@ namespace Ink_Canvas.Helpers var text = GetElementText(fe); if (string.IsNullOrEmpty(text)) return; + if (!ShouldAutoScaleForCurrentCulture(text)) + { + RestoreOriginalFontSize(fe); + return; + } + var availableWidth = GetAvailableWidth(fe); if (double.IsNaN(availableWidth) || availableWidth <= 1) return; @@ -169,6 +192,8 @@ namespace Ink_Canvas.Helpers var max = GetMaxFontSize(fe); if (double.IsNaN(max) || max <= 0) max = current; + // Never enlarge: auto-fit should only reduce font size when needed. + if (max > current) max = current; var startFont = Math.Min(current, max); if (startFont < min) startFont = min; @@ -176,13 +201,6 @@ namespace Ink_Canvas.Helpers SetIsAdjusting(fe, true); try { - var desiredAtMax = MeasureTextWidth(fe, text, max); - if (desiredAtMax > 0 && desiredAtMax <= availableWidth + 0.5) - { - if (Math.Abs(current - max) > 0.01) SetElementFontSize(fe, max); - return; - } - var font = startFont; var desired = MeasureTextWidth(fe, text, font); if (desired <= 0) return; @@ -194,6 +212,22 @@ namespace Ink_Canvas.Helpers if (desired <= 0) break; } + // Hard-fit fallback: when very narrow slots (e.g., 28px) still overflow at MinFontSize, + // keep shrinking proportionally so text always fits in the available width. + if (desired > availableWidth + 0.5) + { + var hardFont = font; + for (var i = 0; i < 6 && desired > availableWidth + 0.5; i++) + { + var ratio = availableWidth / Math.Max(1.0, desired); + hardFont = Math.Max(1.0, hardFont * ratio); + desired = MeasureTextWidth(fe, text, hardFont); + if (desired <= 0) break; + } + + font = hardFont; + } + if (!double.IsNaN(font) && font > 0 && Math.Abs(current - font) > 0.01) { SetElementFontSize(fe, font); @@ -212,6 +246,44 @@ namespace Ink_Canvas.Helpers return null; } + private static bool ShouldAutoScaleForCurrentCulture(string text) + { + // Requirement: auto-scale for English UI only, keep Chinese font size unchanged. + var culture = CultureInfo.CurrentUICulture; + var name = culture?.Name ?? string.Empty; + if (name.StartsWith("en", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + // Fallback: if actual rendered text is Latin-heavy, still auto-scale. + // This avoids clipping when culture detection is out of sync. + if (string.IsNullOrWhiteSpace(text)) return false; + foreach (var ch in text) + { + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) + { + return true; + } + } + + return false; + } + + private static void RestoreOriginalFontSize(FrameworkElement fe) + { + var original = GetOriginalFontSize(fe); + if (double.IsNaN(original) || original <= 0) return; + + var current = GetElementFontSize(fe); + if (double.IsNaN(current) || current <= 0) return; + + if (Math.Abs(current - original) > 0.01) + { + SetElementFontSize(fe, original); + } + } + private static double GetElementFontSize(FrameworkElement fe) { if (fe is TextBlock tb) return tb.FontSize; @@ -229,12 +301,65 @@ namespace Ink_Canvas.Helpers { double width = double.PositiveInfinity; + // Explicit width on the element itself should be a hard cap. + if (!double.IsNaN(fe.Width) && !double.IsInfinity(fe.Width) && fe.Width > 1) + { + width = Math.Min(width, fe.Width - fe.Margin.Left - fe.Margin.Right); + } + + if (!double.IsNaN(fe.MaxWidth) && !double.IsInfinity(fe.MaxWidth) && fe.MaxWidth > 1) + { + width = Math.Min(width, fe.MaxWidth - fe.Margin.Left - fe.Margin.Right); + } + + // Prefer the real layout slot first. This is usually the most accurate + // "space actually assigned by layout" for the element. + var slot = LayoutInformation.GetLayoutSlot(fe); + if (!double.IsNaN(slot.Width) && !double.IsInfinity(slot.Width)) + { + var slotWidth = slot.Width - fe.Margin.Left - fe.Margin.Right; + if (slotWidth > 1) width = Math.Min(width, slotWidth); + } + if (fe.ActualWidth > 1) width = Math.Min(width, fe.ActualWidth); - if (fe.Parent is FrameworkElement parent && parent.ActualWidth > 1) + // Immediate parent may be a StackPanel that does not constrain width. + // Walk a few ancestors and take the tightest finite width as fallback. + DependencyObject ancestor = fe.Parent ?? VisualTreeHelper.GetParent(fe); + var depth = 0; + while (ancestor != null && depth < 8) { - var parentWidth = parent.ActualWidth - fe.Margin.Left - fe.Margin.Right; - if (parentWidth > 1) width = Math.Min(width, parentWidth); + if (ancestor is FrameworkElement af && af.ActualWidth > 1) + { + var candidate = af.ActualWidth; + + // If ancestor sets explicit width, treat it as a stronger cap. + if (!double.IsNaN(af.Width) && !double.IsInfinity(af.Width) && af.Width > 1) + { + candidate = Math.Min(candidate, af.Width); + } + + if (!double.IsNaN(af.MaxWidth) && !double.IsInfinity(af.MaxWidth) && af.MaxWidth > 1) + { + candidate = Math.Min(candidate, af.MaxWidth); + } + + if (ancestor is Control ac) + { + candidate -= ac.Padding.Left + ac.Padding.Right; + candidate -= ac.BorderThickness.Left + ac.BorderThickness.Right; + } + else if (ancestor is Border ab) + { + candidate -= ab.Padding.Left + ab.Padding.Right; + candidate -= ab.BorderThickness.Left + ab.BorderThickness.Right; + } + + if (candidate > 1) width = Math.Min(width, candidate); + } + + ancestor = (ancestor as FrameworkElement)?.Parent ?? VisualTreeHelper.GetParent(ancestor); + depth++; } if (double.IsInfinity(width) || double.IsNaN(width) || width <= 1) return -1; diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 46ac643f..921353e1 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -71,7 +71,7 @@ - + @@ -80,6 +80,17 @@ + + + + +