/// <summary> /// Go through all tags on a line and recalculate all size-related values; /// returns true if lineheight changed /// </summary> internal bool RecalculateLine (Graphics g, Document doc) { LineTag tag; int pos; int len; SizeF size; float w; int prev_offset; bool retval; bool wrapped; Line line; int wrap_pos; int prev_height; int prev_ascent; pos = 0; len = this.text.Length; tag = this.tags; prev_offset = this.offset; // For drawing optimization calculations prev_height = this.height; prev_ascent = this.ascent; this.height = 0; // Reset line height this.ascent = 0; // Reset the ascent for the line tag.Shift = 0; // Reset shift (which should be stored as pixels, not as points) if (ending == LineEnding.Wrap) widths[0] = document.left_margin + hanging_indent; else widths[0] = document.left_margin + indent; this.recalc = false; retval = false; wrapped = false; wrap_pos = 0; while (pos < len) { while (tag.Length == 0) { // We should always have tags after a tag.length==0 unless len==0 //tag.Ascent = 0; tag.Shift = (tag.Line.ascent - tag.Ascent) / 72; tag = tag.Next; } size = tag.SizeOfPosition (g, pos); w = size.Width; 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)) { // Make sure to set the last width of the line before wrapping widths[pos + 1] = widths[pos] + w; pos = wrap_pos; len = text.Length; doc.Split (this, tag, pos); ending = LineEnding.Wrap; len = this.text.Length; retval = true; wrapped = true; } else if (pos > 1 && (widths[pos] + w) > (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; doc.Split (this, tag, pos); ending = LineEnding.Wrap; len = this.text.Length; retval = true; wrapped = true; } } // Contract all wrapped lines that follow back into our line if (!wrapped) { pos++; widths[pos] = widths[pos - 1] + w; if (pos == len) { line = doc.GetLine (this.line_no + 1); if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) { // Pull the two lines together doc.Combine (this.line_no, this.line_no + 1); len = this.text.Length; retval = true; } } } if (pos == (tag.Start - 1 + tag.Length)) { // We just found the end of our current tag tag.Height = tag.MaxHeight (); // Check if we're the tallest on the line (so far) if (tag.Height > this.height) this.height = tag.Height; // Yep; make sure the line knows if (tag.Ascent > this.ascent) { LineTag t; // We have a tag that has a taller ascent than the line; t = tags; while (t != null && t != tag) { t.Shift = (tag.Ascent - t.Ascent) / 72; t = t.Next; } // Save on our line this.ascent = tag.Ascent; } else { tag.Shift = (this.ascent - tag.Ascent) / 72; } tag = tag.Next; if (tag != null) { tag.Shift = 0; wrap_pos = pos; } } } while (tag != null) { tag.Shift = (tag.Line.ascent - tag.Ascent) / 72; tag = tag.Next; } if (this.height == 0) { this.height = tags.Font.Height; tags.Height = this.height; tags.Shift = 0; } if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent) retval = true; return retval; }
private bool RecalculateLine (Graphics g, Document doc, bool handleKerning) { LineTag tag; int pos; int len; SizeF size; float w; int prev_offset; bool retval; bool wrapped; Line line; int wrap_pos; int prev_height; int prev_ascent; pos = 0; len = this.text.Length; tag = this.tags; prev_offset = this.offset; // For drawing optimization calculations prev_height = this.height; prev_ascent = this.ascent; this.height = 0; // Reset line height this.ascent = 0; // Reset the ascent for the line tag.Shift = 0; // Reset shift (which should be stored as pixels, not as points) if (ending == LineEnding.Wrap) widths[0] = document.left_margin + hanging_indent; else widths[0] = document.left_margin + indent; this.recalc = false; retval = false; wrapped = false; wrap_pos = 0; while (pos < len) { while (tag.Length == 0) { // We should always have tags after a tag.length==0 unless len==0 //tag.Ascent = 0; tag.Shift = (tag.Line.ascent - tag.Ascent) / 72; tag = tag.Next; } // 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) && (newWidth + 5) > (doc.viewport_width - this.right_indent)) { // Make sure to set the last width of the line before wrapping widths[pos + 1] = newWidth; pos = wrap_pos; len = text.Length; doc.Split (this, tag, pos); ending = LineEnding.Wrap; len = this.text.Length; retval = true; wrapped = true; } 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] = newWidth; doc.Split (this, tag, pos); ending = LineEnding.Wrap; len = this.text.Length; retval = true; wrapped = true; } } // Contract all wrapped lines that follow back into our line if (!wrapped) { pos++; widths[pos] = newWidth; if (pos == len) { line = doc.GetLine (this.line_no + 1); if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None)) { // Pull the two lines together doc.Combine (this.line_no, this.line_no + 1); len = this.text.Length; retval = true; } } } if (pos == (tag.Start - 1 + tag.Length)) { // We just found the end of our current tag tag.Height = tag.MaxHeight (); // Check if we're the tallest on the line (so far) if (tag.Height > this.height) this.height = tag.Height; // Yep; make sure the line knows if (tag.Ascent > this.ascent) { LineTag t; // We have a tag that has a taller ascent than the line; t = tags; while (t != null && t != tag) { t.Shift = (tag.Ascent - t.Ascent) / 72; t = t.Next; } // Save on our line this.ascent = tag.Ascent; } else { tag.Shift = (this.ascent - tag.Ascent) / 72; } tag = tag.Next; if (tag != null) { tag.Shift = 0; wrap_pos = pos; } } } 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; } if (this.height == 0) { this.height = tags.Font.Height; tags.Height = this.height; tags.Shift = 0; } if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent) retval = true; return retval; }