public virtual SizeF_ SizeOfPosition(Graphics dc, int pos) { if (pos >= line.TextLengthWithoutEnding() && line.document.multiline) { return(SizeF_.Empty); } string text = line.text.ToString(pos, 1); switch ((int)text [0]) { case '\t': if (!line.document.multiline) { goto case 10; } SizeF_ res = TextBoxTextRenderer.MeasureText(dc, " ", font); res.Width *= 8.0F; return(res); case 10: case 13: return(TextBoxTextRenderer.MeasureText(dc, "\u000D", font)); } return(TextBoxTextRenderer.MeasureText(dc, text, font)); }
// This doesn't do exactly what you would think, it just pulls off the \n part of the ending internal void DrawEnding(Graphics dc, float y) { if (document.multiline) { return; } LineTag last = tags; while (last.Next != null) { last = last.Next; } string end_str = null; switch (document.LineEndingLength(ending)) { case 0: return; case 1: end_str = "\u0013"; break; case 2: end_str = "\u0013\u0013"; break; case 3: end_str = "\u0013\u0013\u0013"; break; } TextBoxTextRenderer.DrawText(dc, end_str, last.Font, last.Color, X + widths [TextLengthWithoutEnding()] - document.viewport_x + document.OffsetX, y, true); }
public virtual void Draw(Graphics dc, Color color, float x, float y, int start, int end) { if (text_position == TextPositioning.Subscript) { y += OffsetY; } TextBoxTextRenderer.DrawText(dc, line.text.ToString(start, end).Replace("\r", string.Empty), FontToDisplay, color, x, y, false); }
public virtual SizeF SizeOfPosition(Graphics dc, int pos) { if ((pos >= line.TextLengthWithoutEnding() && line.document.multiline) || !visible) { return(SizeF.Empty); } string text = line.text.ToString(pos, 1); switch ((int)text [0]) { case '\t': if (!line.document.multiline) { goto case 10; } SizeF res = TextBoxTextRenderer.MeasureText(dc, " ", FontToDisplay); // This way we get the height, not that it is ever used... float left = line.widths [pos]; float right = -1; TabStopCollection stops = line.tab_stops; float tabPos; for (int i = 0; i < stops.Count; i++) { tabPos = stops [i].Position; if (tabPos >= left) { if (tabPos <= line.document.viewport_width - line.RightIndent) { break; // Can't use tabs that are past the end of the line. } right = stops [i].CalculateRight(line, pos); break; } } if (right < 0) { float maxWidth = dc.DpiX / 2; // tab stops are 1/2" right = (float)(Math.Floor(left / maxWidth) + 1) * maxWidth; } res.Width = right - left; return(res); case 10: case 13: return(TextBoxTextRenderer.MeasureText(dc, "\u000D", FontToDisplay)); } return(TextBoxTextRenderer.MeasureText(dc, text, FontToDisplay)); }
/// <summary> /// /// </summary> /// <param name="drawStart">0 based start index</param> public virtual void Draw(Graphics dc, Color color, float xoff, float y, int drawStart, int drawEnd, string text, out Rectangle measuredText, bool measureText) { if (!visible) { measuredText = new Rectangle(); return; } if (text_position == TextPositioning.Subscript) { y += OffsetY; } if (measureText) { int xstart = (int)line.widths [drawStart] + (int)xoff; int xend = (int)line.widths [drawEnd] - (int)line.widths [drawStart]; int ystart = (int)y; int yend = (int)TextBoxTextRenderer.MeasureText(dc, Text(), FontToDisplay).Height; measuredText = new Rectangle(xstart, ystart, xend, yend); } else { measuredText = new Rectangle(); } while (drawStart < drawEnd) { int tab_index = text.IndexOf("\t", drawStart); if (tab_index == -1 || tab_index > drawEnd) { tab_index = drawEnd; } TextBoxTextRenderer.DrawText(dc, text.Substring(drawStart, tab_index - drawStart).Replace("\r", string.Empty), FontToDisplay, color, xoff + line.widths [drawStart], y, false); // non multilines get the unknown char if (!line.document.multiline && tab_index != drawEnd) { TextBoxTextRenderer.DrawText(dc, "\u0013", FontToDisplay, color, xoff + line.widths [tab_index], y, true); } drawStart = tab_index + 1; } }
/// <summary> /// Recalculate a single line using the same char for every character in the line /// </summary> internal bool RecalculatePasswordLine(Graphics g, Document doc) { LineTag tag; int pos; int len; float w; bool ret; pos = 0; len = this.text.Length; tag = this.tags; ascent = 0; tag.Shift = 0; this.recalc = false; widths[0] = document.left_margin + indent; w = TextBoxTextRenderer.MeasureText(g, doc.password_char, tags.Font).Width; if (this.textHeight != (int)tag.Font.Height) { ret = true; } else { ret = false; } this.textHeight = (int)tag.Font.Height; tag.Height = this.textHeight; this.height = (int)(this.LineSpacing + this.TotalParagraphSpacing); this.ascent = tag.Ascent; while (pos < len) { pos++; widths[pos] = widths[pos - 1] + w; } return(ret); }
/// <summary> /// Recalculate a single line using the same char for every character in the line /// </summary> internal bool RecalculatePasswordLine(Graphics g, Document doc) { LineTag tag; int pos; int len; bool ret; pos = 0; len = this.text.Length; tag = this.tags; ascent = 0; tag.Shift = 0; this.recalc = false; widths[0] = document.left_margin + indent; if (this.height != (int)tag.Font.Height) { ret = true; } else { ret = false; } this.height = (int)tag.Font.Height; tag.Height = this.height; this.ascent = tag.Ascent; while (pos < len) { pos++; string s = new string(doc.password_char[0], len); widths[pos] = widths[0] + TextBoxTextRenderer.MeasureText(g, s.Substring(0, pos), tags.Font).Width; } return(ret); }
private void CheckKerning(Graphics g, Font font, int start, int length) { if (length > 1) { if (!kerning_fonts.ContainsKey(font.GetHashCode())) { // Check whether kerning takes place for this string and font. var partText = text.ToString(start, length); var realSize = TextBoxTextRenderer.MeasureText(g, partText, font); float realWidth = realSize.Width + widths[start + 1]; // MeasureText ignores trailing whitespace, so we will too at this point. int textLength = partText.TrimEnd().Length; float sumWidth = widths[textLength + start + 1]; if (realWidth != sumWidth) { kerning_fonts.Add(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. } } } }
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); }
public virtual void Draw(Graphics dc, Color_ color, float x, float y, int start, int end) { TextBoxTextRenderer.DrawText(dc, line.text.ToString(start, end).Replace("\r", string.Empty), FontToDisplay, color, x, y, false); }
/// <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; Font currentFont; int currentFontStart; SizeF size; float w; int prev_offset; bool retval; bool wrapped; bool first_in_para; Line line; int wrap_pos; int prev_wrap_pos; int prev_height; int prev_ascent; float prev_spacing_before; int max_above_baseline; int max_below_baseline; int total_ascent; int total_descent; TabStop lastTab; int lastTabPos; char c; bool handleKerning; float right_indent; pos = 0; len = this.text.Length; currentFont = tags.FontToDisplay; currentFontStart = 0; tag = this.tags; prev_offset = this.offset; // For drawing optimization calculations prev_height = this.height; prev_ascent = this.ascent; prev_spacing_before = this.SpacingBefore; max_above_baseline = 0; max_below_baseline = 0; total_ascent = 0; total_descent = 0; lastTab = null; lastTabPos = 0; 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) right_indent = Math.Max(this.right_indent, 0); // Ignore any negative right indent. if (line_no > 0) { line = doc.GetLine(LineNo - 1); first_in_para = line != null && line.ending != LineEnding.Wrap; } else { first_in_para = true; } if (first_in_para) { widths [0] = indent; } else { widths [0] = indent + hanging_indent; } if (widths [0] < 0) { widths [0] = 0; // Don't allow a negative indent to take the line to a negative position. } widths [0] += document.left_margin; this.recalc = false; retval = false; wrapped = false; wrap_pos = 0; prev_wrap_pos = 0; handleKerning = kerning_fonts.ContainsKey(currentFont.GetHashCode()); 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; if (tag.Length != 0 && tag.FontToDisplay != currentFont) { CheckKerning(g, currentFont, currentFontStart, pos - currentFontStart); currentFont = tag.FontToDisplay; currentFontStart = pos; handleKerning = kerning_fonts.ContainsKey(currentFont.GetHashCode()); } } c = text [pos]; // 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. // It doesn't measure /t characters either, we need to add it manually with add_width. size = TextBoxTextRenderer.MeasureText(g, text.ToString(currentFontStart, pos + 1 - currentFontStart), currentFont); newWidth = widths [currentFontStart] + size.Width; } else if (c != '\t') { size = tag.SizeOfPosition(g, pos); w = size.Width; newWidth = widths[pos] + w; } else { CheckKerning(g, currentFont, currentFontStart, pos - currentFontStart); currentFontStart = pos + 1; // Don't try handling the tab along with kerned text. if (lastTab != null) { ProcessLastTab(lastTab, lastTabPos, pos); lastTab = null; } float l = widths [pos]; w = -1; for (int i = 0; i < tab_stops.Count; i++) { if (tab_stops [i].Position > l) { lastTab = tab_stops [i]; lastTabPos = pos; w = lastTab.GetInitialWidth(this, pos); break; } } if (w < 0) { w = tag.SizeOfPosition(g, pos).Width; } newWidth = widths [pos] + w; } if (doc.Wrap) { // FIXME: Technically there are multiple no-break spaces, not just the main one. if ((Char.IsWhiteSpace(c) && c != '\u00A0') || c == '-' || c == '\u2013' || c == '\u2014') { // Primarily break on dashes or whitespace other than a no-break space. prev_wrap_pos = wrap_pos; if (c == '\t') { wrap_pos = pos; // Wrap before tabs for some reason. } else { wrap_pos = pos + 1; } } if (newWidth > (doc.viewport_width - this.right_indent)) { LineTag split_tag = null; if (wrap_pos > 0) { // Make sure to set the last width of the line before wrapping widths [pos + 1] = newWidth; if (Char.IsWhiteSpace(c)) { if (wrap_pos > pos) { while (wrap_pos < text.Length && Char.IsWhiteSpace(text [wrap_pos]) && text [wrap_pos] != '\t') { wrap_pos++; } pos++; wrapped = true; // don't try pulling more into this line, but keep looping to deal with the rest of the widths and tags } } else { if (wrap_pos > pos && pos > 0) { // We're at a dash (otherwise we'd be above), but don't have room to fit it in. // Wrap at the previous wrap point if possible. wrap_pos = prev_wrap_pos > 0 ? prev_wrap_pos : pos; } split_tag = tag; pos = wrap_pos; } } else if (pos > 0) { // 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; split_tag = tag; } // Else don't wrap -- pos == 0, so we'd infinite loop adding blank lines before this. if (split_tag != null) { if (lastTab != null) { ProcessLastTab(lastTab, lastTabPos, pos); lastTab = null; } while (pos < split_tag.Start) { split_tag = split_tag.Previous; } // We have to pass Split the correct tag, and that can change if pos // is set somewhere before the tag change (e.g. by wrap_pos). doc.Split(this, split_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); do { if ((line != null) && (ending == LineEnding.Wrap || ending == LineEnding.None) && (widths[pos] < (doc.viewport_width - this.right_indent) || line.text.Length == 0)) { // Pull the two lines together // Only do this if the line isn't already full, or the next line is empty. var h = this.height; // Back up h, because Combine sets it to zero. doc.Combine(this, line); this.height = h; // And restore it. There's no point starting at the start again. // Document.Combine() called Line.Streamline(), so it is possible tag points a tag that got removed. tag = FindTag(pos - 1); // So make sure we've got the correct tag. len = this.text.Length; line = doc.GetLine(this.line_no + 1); retval = true; } } while ((ending == LineEnding.Wrap || ending == LineEnding.None) && line != null && line.text.Length == 0); // If the next line is empty, do it again (if possible). // The amount of room on this line doesn't matter when there's no text being added... } } if (pos == (tag.Start - 1 + tag.Length)) { // We just found the end of our current tag tag.Height = tag.MaxHeight(); /* line.ascent is the highest point above the baseline. * total_ascent will equal the maximum distance of the tag above the baseline. * total_descent is needed to calculate the line height. * tag.Shift does not include tag.CharOffset, because Shift puts the tag * on the baseline, while CharOffset moves the baseline. * However, we move the normal baseline when CharOffset is trying to push * stuff off the top. */ total_ascent = tag.Ascent + (int)tag.CharOffset; total_descent = tag.Descent - (int)tag.CharOffset; // gets bigger as CharOffset gets smaller if (total_ascent > max_above_baseline) { int moveBy = total_ascent - max_above_baseline; max_above_baseline = total_ascent; LineTag t = tags; while (t != null && t != tag) { t.Shift += moveBy; t = t.Next; } tag.Shift = (int)tag.CharOffset; this.ascent = max_above_baseline; } else { tag.Shift = (this.ascent - tag.Ascent); } if (total_descent > max_below_baseline) { max_below_baseline = total_descent; } if (this.height < max_above_baseline + max_below_baseline + tag.Height - tag.Ascent - tag.Descent) { this.height = max_above_baseline + max_below_baseline + tag.Height - tag.Ascent - tag.Descent; } tag = tag.Next; if (tag != null) { if (tag.Length != 0 && tag.FontToDisplay != currentFont) { CheckKerning(g, currentFont, currentFontStart, pos - currentFontStart); currentFont = tag.FontToDisplay; currentFontStart = pos; handleKerning = kerning_fonts.ContainsKey(currentFont.GetHashCode()); } tag.Shift = 0; // We can't just wrap on tag boundaries -- e.g. if the first letter of the word has a different colour / font. } } } if (pos != currentFontStart) { CheckKerning(g, currentFont, currentFontStart, pos - currentFontStart); } if (lastTab != null) { ProcessLastTab(lastTab, lastTabPos, pos); lastTab = null; } 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; } this.textHeight = this.height; this.height = (int)(this.LineSpacing + this.TotalParagraphSpacing); if (prev_offset != offset || prev_height != this.height || prev_ascent != this.ascent || Math.Abs(prev_spacing_before - this.SpacingBefore) > document.Dpi / 1440f) { retval = true; } return(retval); }