///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary> public LineTag Break(int pos) { LineTag new_tag; #if DEBUG // Sanity if (pos < this.Start) { throw new Exception("Breaking at a negative point"); } #endif #if DEBUG if (pos > End) { throw new Exception("Breaking past the end of a line"); } #endif new_tag = new LineTag(line, pos); new_tag.CopyFormattingFrom(this); new_tag.next = this.next; this.next = new_tag; new_tag.previous = this; if (new_tag.next != null) { new_tag.next.previous = new_tag; } return(new_tag); }
public void Delete() { // If we are the only tag, we can't be deleted if (previous == null && next == null) { return; } // If we are the last tag, deletion is easy if (next == null) { previous.next = null; return; } // Easy cases gone, little tougher, delete ourself // Update links, and start next.previous = null; LineTag loop = next; while (loop != null) { loop.Start -= Length; loop = loop.next; } return; }
// 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); }
// Inserts a string at the given position public void InsertString(int pos, string s, LineTag tag) { int len = s.Length; // Insert the text into the StringBuilder text.Insert(pos, s); // Check that tag is still in use in the line. If not, then we choose the last tag at that position. LineTag t = tags; while (t != null) { if (((t.Start - 1) <= pos) && (pos < (t.End - 1) || (pos == t.End - 1 && t.Length == 0))) { // found the location bool foundTag = false; while (pos < (t.Start + t.Length - 1)) { if (t == tag) { foundTag = true; break; } if (t.Next == null) { break; } t = t.Next; } if (!foundTag) { if (pos < (t.Start + t.Length - 1)) { tag = t.Previous; } else { tag = t; } } break; } t = t.Next; } // Update the start position of every tag after this one tag = tag.Next; while (tag != null) { tag.Start += len; tag = tag.Next; } // Make sure we have room in the widths array Grow(len); // This line needs to be recalculated recalc = true; }
/// <summary> Find the tag on a line based on the character position, pos is 0-based</summary> internal LineTag FindTag(int pos) { LineTag tag; if (pos == 0) { return(tags); } tag = this.tags; if (pos >= text.Length) { pos = text.Length - 1; } while (tag != null) { if (((tag.Start - 1) <= pos) && (pos <= (tag.Start + tag.Length - 1))) { return(LineTag.GetFinalTag(tag)); } tag = tag.Next; } return(null); }
internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, Color back_color, TextPositioning text_position, float char_offset, float left_indent, float hanging_indent, float right_indent, float spacing_before, float spacing_after, float line_spacing, bool line_spacing_multiple, TabStopCollection tab_stops, bool visible, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length + 1 : DEFAULT_TEXT_LEN; text = new StringBuilder(Text, space); line_no = LineNo; this.ending = ending; alignment = align; indent = left_indent; HangingIndent = hanging_indent; this.right_indent = right_indent; this.spacing_before = spacing_before; this.spacing_after = spacing_after; this.tab_stops = tab_stops; this.line_spacing = line_spacing; this.line_spacing_multiple = line_spacing_multiple; widths = new float[space + 1]; tags = new LineTag(this, 1); tags.Font = font; tags.Color = color; tags.BackColor = back_color; tags.TextPosition = text_position; tags.CharOffset = char_offset; tags.Visible = visible; }
// Get the tag that contains this x coordinate public LineTag GetTag(int x) { LineTag tag = tags; // Coord is to the left of the first character if (x < tag.X) { return(LineTag.GetFinalTag(tag)); } // All we have is a linked-list of tags, so we have // to do a linear search. But there shouldn't be // too many tags per line in general. while (true) { if (x >= tag.X && x < (tag.X + tag.Width)) { return(tag); } if (tag.Next != null) { tag = tag.Next; } else { return(LineTag.GetFinalTag(tag)); } } }
private static void SetFormat(LineTag tag, Font font, Color color, Color back_color, TextPositioning text_position, float char_offset, bool visible, FormatSpecified specified) { if ((FormatSpecified.Font & specified) == FormatSpecified.Font) { tag.Font = font; } if ((FormatSpecified.Color & specified) == FormatSpecified.Color) { tag.color = color; } if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) { tag.back_color = back_color; } if ((FormatSpecified.TextPosition & specified) == FormatSpecified.TextPosition) { tag.TextPosition = text_position; } if ((FormatSpecified.CharOffset & specified) == FormatSpecified.CharOffset) { tag.CharOffset = char_offset; } if ((FormatSpecified.Visibility & specified) == FormatSpecified.Visibility) { tag.Visible = visible; } // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color); }
/// <summary>Finds the tag that describes the character at position 'pos' (0 based) on 'line'</summary> public static LineTag FindTag(Line line, int pos) { LineTag tag = line.tags; // Beginning of line is a bit special if (pos == 0) { return(tag); // Not sure if we should get the final tag here } while (tag != null) { // [H e][l][l o _ W][o r] Text // [1 2][3][4 5 6 7][8 9] Start // 3 4 8 10 End // 0 1 2 3 4 5 6 7 8 9 Pos if ((tag.start <= pos) && (pos < tag.End)) { return(GetFinalTag(tag)); } tag = tag.next; } return(null); }
public void CopyFormattingFrom(LineTag other) { Font = other.font; color = other.color; back_color = other.back_color; TextPosition = other.text_position; CharOffset = other.CharOffset; Visible = other.Visible; }
/// <summary> /// Clears all link properties from tags /// </summary> internal void ClearLinks() { LineTag tag = tags; while (tag != null) { tag.IsLink = false; tag = tag.Next; } }
internal void Streamline(int lines) { LineTag current; LineTag next; current = this.tags; next = current.Next; // // Catch what the loop below wont; eliminate 0 length // tags, but only if there are other tags after us // We only eliminate text tags if there is another text tag // after it. Otherwise we wind up trying to type on picture tags // while ((current.Length == 0) && (next != null) && (next.IsTextTag)) { tags = next; tags.Previous = null; current = next; next = current.Next; } if (next == null) { return; } while (next != null) { // Take out 0 length tags unless it's the last tag in the document if (current.IsTextTag && next.Length == 0 && next.IsTextTag) { if ((next.Next != null) || (line_no != lines)) { current.Next = next.Next; if (current.Next != null) { current.Next.Previous = current; } next = current.Next; continue; } } if (current.Combine(next)) { next = current.Next; continue; } current = current.Next; next = current.Next; } }
internal Line(Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length + 1 : DEFAULT_TEXT_LEN; text = new StringBuilder(Text, space); this.ending = ending; line_no = LineNo; widths = new float[space + 1]; tags = tag; }
// There can be multiple tags at the same position, we want to make // sure we are using the very last tag at the given position // Empty tags are necessary if style is set at a position with // no length. public static LineTag GetFinalTag(LineTag tag) { LineTag res = tag; while (res.Length == 0 && res.next != null && res.next.Length == 0) { res = res.next; } return(res); }
internal Line(Document document, int LineNo, string Text, Font font, Color color, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length + 1 : DEFAULT_TEXT_LEN; text = new StringBuilder(Text, space); line_no = LineNo; this.ending = ending; widths = new float[space + 1]; tags = new LineTag(this, 1); tags.Font = font; tags.Color = color; }
private static void SetFormat(LineTag tag, Font font, Color_ color, Color_ back_color, FormatSpecified specified) { if ((FormatSpecified.Font & specified) == FormatSpecified.Font) { tag.Font = font; } if ((FormatSpecified.Color & specified) == FormatSpecified.Color) { tag.color = color; } if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) { tag.back_color = back_color; } // Console.WriteLine ("setting format: {0} {1} new Color_ {2}", color.Color, specified, tag.color.Color); }
/// <summary>Combines 'this' tag with 'other' tag</summary> public bool Combine(LineTag other) { if (!this.Equals(other)) { return(false); } this.next = other.next; if (this.next != null) { this.next.previous = this; } return(true); }
internal Line(Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length + 1 : DEFAULT_TEXT_LEN; text = new StringBuilder(Text, space); line_no = LineNo; this.ending = ending; alignment = align; tab_stops = new TabStopCollection(); widths = new float[space + 1]; tags = new LineTag(this, 1); tags.Font = font; tags.Color = color; }
/// <summary> /// Builds a simple code to record which tags are links and how many tags /// used to compare lines before and after to see if the scan for links /// process has changed anything. /// </summary> internal void LinkRecord(StringBuilder linkRecord) { LineTag tag = tags; while (tag != null) { if (tag.IsLink) { linkRecord.Append("L"); } else { linkRecord.Append("N"); } tag = tag.Next; } }
// Inserts a string at the given position public void InsertString(int pos, string s, LineTag tag) { int len = s.Length; // Insert the text into the StringBuilder text.Insert(pos, s); // Update the start position of every tag after this one tag = tag.Next; while (tag != null) { tag.Start += len; tag = tag.Next; } // Make sure we have room in the widths array Grow(len); // This line needs to be recalculated recalc = true; }
/// <summary>Combines 'this' tag with 'other' tag</summary> public bool Combine (LineTag other) { if (!this.Equals (other)) return false; this.next = other.next; if (this.next != null) this.next.previous = this; return true; }
private void PointToTagPos(Point pt, out LineTag tag, out int pos) { Point p; p = pt; if (p.X >= document.ViewPortWidth) { p.X = document.ViewPortWidth - 1; } else if (p.X < 0) { p.X = 0; } if (p.Y >= document.ViewPortHeight) { p.Y = document.ViewPortHeight - 1; } else if (p.Y < 0) { p.Y = 0; } tag = document.FindCursor(p.X + document.ViewPortX, p.Y + document.ViewPortY, out pos); }
// start_pos = 1-based // end_pos = 1-based public Line Duplicate(Line start_line, int start_pos, Line end_line, int end_pos) { Line ret; Line line; Line current; LineTag tag; LineTag current_tag; int start; int end; int tag_start; line = new Line (start_line.document, start_line.ending); ret = line; for (int i = start_line.line_no; i <= end_line.line_no; i++) { current = document.GetLine(i); if (start_line.line_no == i) { start = start_pos; } else { start = 0; } if (end_line.line_no == i) { end = end_pos; } else { end = current.text.Length; } if (end_pos == 0) continue; // Text for the tag line.text = new StringBuilder (current.text.ToString (start, end - start)); // Copy tags from start to start+length onto new line current_tag = current.FindTag (start + 1); while ((current_tag != null) && (current_tag.Start <= end)) { if ((current_tag.Start <= start) && (start < (current_tag.Start + current_tag.Length))) { // start tag is within this tag tag_start = start; } else { tag_start = current_tag.Start; } tag = new LineTag(line, tag_start - start + 1); tag.CopyFormattingFrom (current_tag); current_tag = current_tag.Next; // Add the new tag to the line if (line.tags == null) { line.tags = tag; } else { LineTag tail; tail = line.tags; while (tail.Next != null) { tail = tail.Next; } tail.Next = tag; tag.Previous = tail; } } if ((i + 1) <= end_line.line_no) { line.ending = current.ending; // Chain them (we use right/left as next/previous) line.right = new Line (start_line.document, start_line.ending); line.right.left = line; line = line.right; } } return ret; }
/// <summary>Retrieve the next tag; walks line boundaries</summary> internal LineTag NextTag(LineTag tag) { Line l; if (tag.Next != null) { return tag.Next; } // Next line l = GetLine(tag.Line.line_no + 1); if (l != null) { return l.tags; } return null; }
internal Line (Document document, int LineNo, string Text, LineTag tag, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN; text = new StringBuilder (Text, space); this.ending = ending; line_no = LineNo; widths = new float[space + 1]; tags = tag; }
/// <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); }
public void DeleteCharacters(int pos, int count) { LineTag tag; bool streamline = false; // Can't delete more than the line has if (pos >= text.Length) { return; } // Find the first tag that we are deleting from tag = FindTag(pos + 1); // Remove the characters from the line text.Remove(pos, count); if (tag == null) { return; } // Check if we're crossing tag boundaries if ((pos + count) > (tag.Start + tag.Length - 1)) { int left; // We have to delete cross tag boundaries streamline = true; left = count; left -= tag.Start + tag.Length - pos - 1; tag = tag.Next; // Update the start of each tag while ((tag != null) && (left > 0)) { // Cache tag.Length as is will be indireclty modified // by changes to tag.Start int tag_length = tag.Length; tag.Start -= count - left; if (tag_length > left) { left = 0; } else { left -= tag_length; tag = tag.Next; } } } else { // We got off easy, same tag if (tag.Length == 0) { streamline = true; } } // Delete empty orphaned tags at the end LineTag walk = tag; while (walk != null && walk.Next != null && walk.Next.Length == 0) { LineTag t = walk; walk.Next = walk.Next.Next; if (walk.Next != null) { walk.Next.Previous = t; } walk = walk.Next; } // Adjust the start point of any tags following if (tag != null) { tag = tag.Next; while (tag != null) { tag.Start -= count; tag = tag.Next; } } recalc = true; if (streamline) { Streamline(document.Lines); } }
// This is for future use, right now Document.Split does it by hand, with some added shortcut logic public void Split(Line move_to_line, int split_at) { line = move_to_line; pos -= split_at; tag = LineTag.FindTag(line, pos); }
public void Combine(Line move_to_line, int move_to_line_length) { line = move_to_line; pos += move_to_line_length; tag = LineTag.FindTag(line, pos); }
internal Line (Document document, int LineNo, string Text, HorizontalAlignment align, Font font, Color color, LineEnding ending) : this(document, ending) { space = Text.Length > DEFAULT_TEXT_LEN ? Text.Length+1 : DEFAULT_TEXT_LEN; text = new StringBuilder (Text, space); line_no = LineNo; this.ending = ending; alignment = align; widths = new float[space + 1]; tags = new LineTag(this, 1); tags.Font = font; tags.Color = color; }
private static void SetFormat(LineTag tag, Font font, Color color, Color back_color, FormatSpecified specified) { SetFormat(tag, font, color, back_color, TextPositioning.Normal, 0, true, specified); }
// There can be multiple tags at the same position, we want to make // sure we are using the very last tag at the given position // Empty tags are necessary if style is set at a position with // no length. public static LineTag GetFinalTag (LineTag tag) { LineTag res = tag; while (res.Length == 0 && res.next != null && res.next.Length == 0) res = res.next; return res; }
public void CopyFormattingFrom (LineTag other) { Font = other.font; color = other.color; back_color = other.back_color; }
public void CopyFormattingFrom(LineTag other) { Font = other.font; color = other.color; back_color = other.back_color; }
internal void CharIndexToLineTag(int index, out Line line_out, out LineTag tag_out, out int pos) { Line line; LineTag tag; int i; int chars; int start; chars = 0; for (i = 1; i <= lines; i++) { line = GetLine(i); start = chars; chars += line.text.Length; if (index <= chars) { // we found the line tag = line.tags; while (tag != null) { if (index < (start + tag.Start + tag.Length - 1)) { line_out = line; tag_out = LineTag.GetFinalTag (tag); pos = index - start; return; } if (tag.Next == null) { Line next_line; next_line = GetLine(line.line_no + 1); if (next_line != null) { line_out = next_line; tag_out = LineTag.GetFinalTag (next_line.tags); pos = 0; return; } else { line_out = line; tag_out = LineTag.GetFinalTag (tag); pos = line_out.text.Length; return; } } tag = tag.Next; } } } line_out = GetLine(lines); tag = line_out.tags; while (tag.Next != null) { tag = tag.Next; } tag_out = tag; pos = line_out.text.Length; }
///<summary>Break a tag into two with identical attributes; pos is 1-based; returns tag starting at >pos< or null if end-of-line</summary> public LineTag Break (int pos) { LineTag new_tag; #if DEBUG // Sanity if (pos < this.Start) throw new Exception ("Breaking at a negative point"); #endif #if DEBUG if (pos > End) throw new Exception ("Breaking past the end of a line"); #endif new_tag = new LineTag(line, pos); new_tag.CopyFormattingFrom (this); new_tag.next = this.next; this.next = new_tag; new_tag.previous = this; if (new_tag.next != null) new_tag.next.previous = new_tag; return new_tag; }
/// <summary>Retrieve the previous tag; walks line boundaries</summary> internal LineTag PreviousTag(LineTag tag) { Line l; if (tag.Previous != null) { return tag.Previous; } // Next line if (tag.Line.line_no == 1) { return null; } l = GetLine(tag.Line.line_no - 1); if (l != null) { LineTag t; t = l.tags; while (t.Next != null) { t = t.Next; } return t; } return null; }
// Insert text at the given position; use formatting at insertion point for inserted text internal void Insert (Line line, int pos, bool update_caret, string s, LineTag tag) { int break_index; int base_line; int old_line_count; int count = 1; LineEnding ending; Line split_line; // Don't recalculate while we mess around SuspendRecalc (); base_line = line.line_no; old_line_count = lines; // Discard chars after any possible -unlikely- end of file int eof_index = s.IndexOf ('\0'); if (eof_index != -1) s = s.Substring (0, eof_index); break_index = GetLineEnding (s, 0, out ending, LineEnding.Hard | LineEnding.Rich); // There are no line feeds in our text to be pasted if (break_index == s.Length) { line.InsertString (pos, s, tag); } else { // Add up to the first line feed to our current position line.InsertString (pos, s.Substring (0, break_index + LineEndingLength (ending)), tag); // Split the rest of the original line to a new line Split (line, pos + (break_index + LineEndingLength (ending))); line.ending = ending; break_index += LineEndingLength (ending); split_line = GetLine (line.line_no + 1); // Insert brand new lines for any more line feeds in the inserted string while (true) { int next_break = GetLineEnding (s, break_index, out ending, LineEnding.Hard | LineEnding.Rich); if (next_break == s.Length) break; string line_text = s.Substring (break_index, next_break - break_index + LineEndingLength (ending)); Add (base_line + count, line_text, line.alignment, tag.Font, tag.Color, ending); Line last = GetLine (base_line + count); last.ending = ending; count++; break_index = next_break + LineEndingLength (ending); } // Add the remainder of the insert text to the split // part of the original line split_line.InsertString (0, s.Substring (break_index)); } // Allow the document to recalculate things ResumeRecalc (false); // Update our character count CharCount += s.Length; UpdateView (line, lines - old_line_count + 1, pos); // Move the caret to the end of the inserted text if requested if (update_caret) { Line l = GetLine (line.line_no + lines - old_line_count); PositionCaret (l, l.text.Length); DisplayCaret (); } }
private static void SetFormat (LineTag tag, Font font, Color color, Color back_color, FormatSpecified specified) { if ((FormatSpecified.Font & specified) == FormatSpecified.Font) { tag.Font = font; } if ((FormatSpecified.Color & specified) == FormatSpecified.Color) tag.color = color; if ((FormatSpecified.BackColor & specified) == FormatSpecified.BackColor) { tag.back_color = back_color; } // Console.WriteLine ("setting format: {0} {1} new color {2}", color.Color, specified, tag.color.Color); }
// Inserts a string at the given position public void InsertString (int pos, string s, LineTag tag) { int len = s.Length; // Insert the text into the StringBuilder text.Insert (pos, s); // Update the start position of every tag after this one tag = tag.Next; while (tag != null) { tag.Start += len; tag = tag.Next; } // Make sure we have room in the widths array Grow (len); // This line needs to be recalculated recalc = true; }
///<summary>Split line at given tag and position into two lines</summary> ///if more space becomes available on previous line internal void Split(Line line, LineTag tag, int pos) { LineTag new_tag; Line new_line; bool move_caret; bool move_sel_start; bool move_sel_end; move_caret = false; move_sel_start = false; move_sel_end = false; #if DEBUG SanityCheck(); if (tag.End < pos) throw new Exception ("Split called with the wrong tag"); #endif // Adjust selection and cursors if (caret.line == line && caret.pos >= pos) { move_caret = true; } if (selection_start.line == line && selection_start.pos > pos) { move_sel_start = true; } if (selection_end.line == line && selection_end.pos > pos) { move_sel_end = true; } // cover the easy case first if (pos == line.text.Length) { Add (line.line_no + 1, String.Empty, line.alignment, tag.Font, tag.Color, line.ending); new_line = GetLine (line.line_no + 1); if (move_caret) { caret.line = new_line; caret.tag = new_line.tags; caret.pos = 0; if (selection_visible == false) { SetSelectionToCaret (true); } } if (move_sel_start) { selection_start.line = new_line; selection_start.pos = 0; selection_start.tag = new_line.tags; } if (move_sel_end) { selection_end.line = new_line; selection_end.pos = 0; selection_end.tag = new_line.tags; } #if DEBUG SanityCheck (); #endif return; } // We need to move the rest of the text into the new line Add (line.line_no + 1, line.text.ToString (pos, line.text.Length - pos), line.alignment, tag.Font, tag.Color, line.ending); // Now transfer our tags from this line to the next new_line = GetLine(line.line_no + 1); line.recalc = true; new_line.recalc = true; //make sure that if we are at the end of a tag, we start on the begining //of a new one, if one exists... Stops us creating an empty tag and //make the operation easier. if (tag.Next != null && (tag.Next.Start - 1) == pos) tag = tag.Next; if ((tag.Start - 1) == pos) { int shift; // We can simply break the chain and move the tag into the next line // if the tag we are moving is the first, create an empty tag // for the line we are leaving behind if (tag == line.tags) { new_tag = new LineTag(line, 1); new_tag.CopyFormattingFrom (tag); line.tags = new_tag; } if (tag.Previous != null) { tag.Previous.Next = null; } new_line.tags = tag; tag.Previous = null; tag.Line = new_line; // Walk the list and correct the start location of the tags we just bumped into the next line shift = tag.Start - 1; new_tag = tag; while (new_tag != null) { new_tag.Start -= shift; new_tag.Line = new_line; new_tag = new_tag.Next; } } else { int shift; new_tag = new LineTag (new_line, 1); new_tag.Next = tag.Next; new_tag.CopyFormattingFrom (tag); new_line.tags = new_tag; if (new_tag.Next != null) { new_tag.Next.Previous = new_tag; } tag.Next = null; shift = pos; new_tag = new_tag.Next; while (new_tag != null) { new_tag.Start -= shift; new_tag.Line = new_line; new_tag = new_tag.Next; } } if (move_caret) { caret.line = new_line; caret.pos = caret.pos - pos; caret.tag = caret.line.FindTag(caret.pos); if (selection_visible == false) { SetSelectionToCaret (true); move_sel_start = false; move_sel_end = false; } } if (move_sel_start) { selection_start.line = new_line; selection_start.pos = selection_start.pos - pos; if (selection_start.Equals(selection_end)) selection_start.tag = new_line.FindTag(selection_start.pos); else selection_start.tag = new_line.FindTag (selection_start.pos + 1); } if (move_sel_end) { selection_end.line = new_line; selection_end.pos = selection_end.pos - pos; selection_end.tag = new_line.FindTag(selection_end.pos); } CharCount -= line.text.Length - pos; line.text.Remove(pos, line.text.Length - pos); #if DEBUG SanityCheck (); #endif }
public LinkRectangle (Rectangle rect) { link_tag = null; link_area_rectangle = rect; }
internal void Streamline (int lines) { LineTag current; LineTag next; current = this.tags; next = current.Next; // // Catch what the loop below wont; eliminate 0 length // tags, but only if there are other tags after us // We only eliminate text tags if there is another text tag // after it. Otherwise we wind up trying to type on picture tags // while ((current.Length == 0) && (next != null) && (next.IsTextTag)) { tags = next; tags.Previous = null; current = next; next = current.Next; } if (next == null) return; while (next != null) { // Take out 0 length tags unless it's the last tag in the document if (current.IsTextTag && next.Length == 0 && next.IsTextTag) { if ((next.Next != null) || (line_no != lines)) { current.Next = next.Next; if (current.Next != null) { current.Next.Previous = current; } next = current.Next; continue; } } if (current.Combine (next)) { next = current.Next; continue; } current = current.Next; next = current.Next; } }