From 3280b20650e8e122bd8c985bbc22c6e18851c240 Mon Sep 17 00:00:00 2001 From: Stephen McConnel Date: Tue, 27 Jan 2015 15:40:11 -0600 Subject: [PATCH] Fix TextBox display to handle kerning fonts Many fonts, including the default DejaVu Sans, use kerning between certain character pairs. This resulted in garbled or even invisible display during typing of strings with a lot of kerning going on. An attempt is made to retain the current speed-optimized implementation while using accurate, slower string width measurements when needed. See https://bugzilla.xamarin.com/show_bug.cgi?id=26478 for details. --- .../System.Windows.Forms/Line.cs | 61 +++++++++++++++++++--- 1 file changed, 54 insertions(+), 7 deletions(-) diff --git a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/Line.cs b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/Line.cs index 93dc2c05df5..8d9b2a27fc5 100644 --- a/mcs/class/Managed.Windows.Forms/System.Windows.Forms/Line.cs +++ b/mcs/class/Managed.Windows.Forms/System.Windows.Forms/Line.cs @@ -60,6 +60,8 @@ namespace System.Windows.Forms internal LineColor color; // We're doing a black/red tree. this is the node color static int DEFAULT_TEXT_LEN = 0; // internal bool recalc; // Line changed + + private static Hashtable kerning_fonts = new Hashtable (); // record which fonts use kerning #endregion // Local Variables #region Constructors @@ -453,6 +455,11 @@ namespace System.Windows.Forms /// internal bool RecalculateLine (Graphics g, Document doc) { + return RecalculateLine (g, doc, kerning_fonts.ContainsKey (tags.Font.GetHashCode ())); + } + + private bool RecalculateLine (Graphics g, Document doc, bool handleKerning) + { LineTag tag; int pos; int len; @@ -495,16 +502,38 @@ namespace System.Windows.Forms tag = tag.Next; } - size = tag.SizeOfPosition (g, pos); - w = size.Width; + // kerning is a problem. The original code in this method assumed that the + // width of a string equals the sum of the widths of its characters. This is + // not true when kerning takes place during the display process. Since it's + // impossible to find out easily whether a font does kerning, and with which + // characters, we just detect that kerning must have happened and use a slower + // (but accurate) measurement for those fonts henceforth. Without handling + // kerning, many fonts for English become unreadable during typing for many + // input strings, and text in many other languages is even worse trying to + // type in TextBoxes. + // See https://bugzilla.xamarin.com/show_bug.cgi?id=26478 for details. + float newWidth; + if (handleKerning && !Char.IsWhiteSpace(text[pos])) + { + // MeasureText doesn't measure trailing spaces, so we do the best we can for those + // in the else branch. + size = TextBoxTextRenderer.MeasureText (g, text.ToString (0, pos + 1), tag.Font); + newWidth = widths[0] + size.Width; + } + else + { + size = tag.SizeOfPosition (g, pos); + w = size.Width; + newWidth = widths[pos] + w; + } if (Char.IsWhiteSpace (text[pos])) wrap_pos = pos + 1; if (doc.wrap) { - if ((wrap_pos > 0) && (wrap_pos != len) && (widths[pos] + w) + 5 > (doc.viewport_width - this.right_indent)) { + if ((wrap_pos > 0) && (wrap_pos != len) && (newWidth + 5) > (doc.viewport_width - this.right_indent)) { // Make sure to set the last width of the line before wrapping - widths[pos + 1] = widths[pos] + w; + widths[pos + 1] = newWidth; pos = wrap_pos; len = text.Length; @@ -514,11 +543,11 @@ namespace System.Windows.Forms retval = true; wrapped = true; - } else if (pos > 1 && (widths[pos] + w) > (doc.viewport_width - this.right_indent)) { + } else if (pos > 1 && newWidth > (doc.viewport_width - this.right_indent)) { // No suitable wrap position was found so break right in the middle of a word // Make sure to set the last width of the line before wrapping - widths[pos + 1] = widths[pos] + w; + widths[pos + 1] = newWidth; doc.Split (this, tag, pos); ending = LineEnding.Wrap; @@ -532,7 +561,7 @@ namespace System.Windows.Forms if (!wrapped) { pos++; - widths[pos] = widths[pos - 1] + w; + widths[pos] = newWidth; if (pos == len) { line = doc.GetLine (this.line_no + 1); @@ -577,6 +606,24 @@ namespace System.Windows.Forms } } + var fullText = text.ToString(); + if (!handleKerning && fullText.Length > 1 && !wrapped) + { + // Check whether kerning takes place for this string and font. + var realSize = TextBoxTextRenderer.MeasureText(g, fullText, tags.Font); + float realWidth = realSize.Width + widths[0]; + // MeasureText ignores trailing whitespace, so we will too at this point. + int length = fullText.TrimEnd().Length; + float sumWidth = widths[length]; + if (realWidth != sumWidth) + { + kerning_fonts.Add(tags.Font.GetHashCode (), true); + // Using a slightly incorrect width this time around isn't that bad. All that happens + // is that the cursor is a pixel or two off until the next character is typed. It's + // the accumulation of pixel after pixel that causes display problems. + } + } + while (tag != null) { tag.Shift = (tag.Line.ascent - tag.Ascent) / 72; tag = tag.Next; -- 2.11.4.GIT