Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Core / CSharp / System / Windows / Media / FormattedText.cs / 1 / FormattedText.cs
//---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // Description: Implementation of FormattedText class. The FormattedText class is targeted at programmers // needing to add some simple text to a MIL visual. // // History: // 04/10/2003 : mleonov - Created // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Windows; using System.Windows.Media; using System.Windows.Media.TextFormatting; using System.Runtime.InteropServices; using MS.Internal; using MS.Internal.TextFormatting; using MS.Internal.FontFace; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; #pragma warning disable 1634, 1691 //Allow suppression of Presharp warnings namespace System.Windows.Media { ////// The FormattedText class is targeted at programmers needing to add some simple text to a MIL visual. /// public class FormattedText { #region Construction ////// Construct a FormattedText object. /// /// String of text to be displayed. /// Culture of text. /// Flow direction of text. /// Type face used to display text. /// Font em size in visual units (1/96 of an inch). /// Foreground brush used to render text. public FormattedText( string textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, double emSize, Brush foreground) : this( textToFormat, culture, flowDirection, typeface, emSize, foreground, null // numberSubstitution ) { } ////// Construct a FormattedText object. /// /// String of text to be displayed. /// Culture of text. /// Flow direction of text. /// Type face used to display text. /// Font em size in visual units (1/96 of an inch). /// Foreground brush used to render text. /// Number substitution behavior to apply to the text; can be null, /// in which case the default number number method for the text culture is used. public FormattedText( string textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, double emSize, Brush foreground, NumberSubstitution numberSubstitution) { if (textToFormat == null) throw new ArgumentNullException("textToFormat"); if (typeface == null) throw new ArgumentNullException("typeface"); ValidateCulture(culture); ValidateFlowDirection(flowDirection, "flowDirection"); ValidateFontSize(emSize); _text = textToFormat; GenericTextRunProperties runProps = new GenericTextRunProperties( typeface, emSize, 12.0f, // default hinting size null, // decorations foreground, null, // highlight background BaselineAlignment.Baseline, culture, numberSubstitution ); _latestPosition = _formatRuns.SetValue(0, _text.Length, runProps, _latestPosition); _defaultParaProps = new GenericTextParagraphProperties( flowDirection, TextAlignment.Left, false, false, runProps, TextWrapping.WrapWithOverflow, 0, // line height not specified 0 // indentation not specified ); InvalidateMetrics(); } ////// Returns the string of text to be displayed /// public string Text { get { return _text; } } #endregion #region Formatting properties private static void ValidateCulture(CultureInfo culture) { if (culture == null) throw new ArgumentNullException("culture"); } private static void ValidateFontSize(double emSize) { if (emSize <= 0) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterMustBeGreaterThanZero)); if (emSize > MaxFontEmSize) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterCannotBeGreaterThan, MaxFontEmSize)); if (DoubleUtil.IsNaN(emSize)) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterValueCannotBeNaN)); } private static void ValidateFlowDirection(FlowDirection flowDirection, string parameterName) { if ((int)flowDirection < 0 || (int)flowDirection > (int)FlowDirection.RightToLeft) throw new InvalidEnumArgumentException(parameterName, (int)flowDirection, typeof(FlowDirection)); } private int ValidateRange(int startIndex, int count) { if (startIndex < 0 || startIndex > _text.Length) throw new ArgumentOutOfRangeException("startIndex"); int limit = startIndex + count; if (count < 0 || limit < startIndex || limit > _text.Length) throw new ArgumentOutOfRangeException("count"); return limit; } private void InvalidateMetrics() { _metrics = null; _minWidth = double.MinValue; } ////// Sets foreground brush used for drawing text /// /// Foreground brush public void SetForegroundBrush(Brush foregroundBrush) { SetForegroundBrush(foregroundBrush, 0, _text.Length); } ////// Sets foreground brush used for drawing text /// /// Foreground brush /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetForegroundBrush(Brush foregroundBrush, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.ForegroundBrush == foregroundBrush) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, foregroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); } } ////// Sets or changes the font family for the text object /// /// Font family name public void SetFontFamily(string fontFamily) { SetFontFamily(fontFamily, 0, _text.Length); } ////// Sets or changes the font family for the text object /// /// Font family name /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontFamily(string fontFamily, int startIndex, int count) { if (fontFamily == null) throw new ArgumentNullException("fontFamily"); SetFontFamily(new FontFamily(fontFamily), startIndex, count); } ////// Sets or changes the font family for the text object /// /// Font family public void SetFontFamily(FontFamily fontFamily) { SetFontFamily(fontFamily, 0, _text.Length); } ////// Sets or changes the font family for the text object /// /// Font family /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontFamily(FontFamily fontFamily, int startIndex, int count) { if (fontFamily == null) throw new ArgumentNullException("fontFamily"); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (fontFamily.Equals(oldTypeface.FontFamily)) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font em size measured in MIL units /// /// Font em size public void SetFontSize(double emSize) { SetFontSize(emSize, 0, _text.Length); } ////// Sets or changes the font em size measured in MIL units /// /// Font em size /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontSize(double emSize, int startIndex, int count) { ValidateFontSize(emSize); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.FontRenderingEmSize == emSize) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, emSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); #pragma warning restore 6506 InvalidateMetrics(); } } ////// Sets or changes the culture for the text object. /// /// The new culture for the text object. public void SetCulture(CultureInfo culture) { SetCulture(culture, 0, _text.Length); } ////// Sets or changes the culture for the text object. /// /// The new culture for the text object. /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetCulture(CultureInfo culture, int startIndex, int count) { ValidateCulture(culture); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit; ) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.CultureInfo.Equals(culture)) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, culture, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the number substitution behavior for the text. /// /// Number substitution behavior to apply to the text; can be null, /// in which case the default number substitution method for the text culture is used. public void SetNumberSubstitution( NumberSubstitution numberSubstitution ) { SetNumberSubstitution(numberSubstitution, 0, _text.Length); } ////// Sets or changes the number substitution behavior for a range of text. /// /// Number substitution behavior to apply to the text; can be null, /// in which case the default number substitution method for the text culture is used. /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetNumberSubstitution( NumberSubstitution numberSubstitution, int startIndex, int count ) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit; ) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (numberSubstitution != null) { if (numberSubstitution.Equals(runProps.NumberSubstitution)) continue; } else { if (runProps.NumberSubstitution == null) continue; } GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, numberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font weight /// /// Font weight public void SetFontWeight(FontWeight weight) { SetFontWeight(weight, 0, _text.Length); } ////// Sets or changes the font weight /// /// Font weight /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontWeight(FontWeight weight, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Weight == weight) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font style /// /// Font style public void SetFontStyle(FontStyle style) { SetFontStyle(style, 0, _text.Length); } ////// Sets or changes the font style /// /// Font style /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontStyle(FontStyle style, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Style == style) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); // invalidate cached metrics } } ////// Sets or changes the font stretch /// /// Font stretch public void SetFontStretch(FontStretch stretch) { SetFontStretch(stretch, 0, _text.Length); } ////// Sets or changes the font stretch /// /// Font stretch /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontStretch(FontStretch stretch, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Stretch == stretch) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, oldTypeface.Style, oldTypeface.Weight, stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); #pragma warning restore 6506 InvalidateMetrics(); } } ////// Sets or changes the type face /// /// Typeface public void SetFontTypeface(Typeface typeface) { SetFontTypeface(typeface, 0, _text.Length); } ////// Sets or changes the type face /// /// Typeface /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontTypeface(Typeface typeface, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.Typeface == typeface) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the text decorations /// /// Text decorations public void SetTextDecorations(TextDecorationCollection textDecorations) { SetTextDecorations(textDecorations, 0, _text.Length); } ////// Sets or changes the text decorations /// /// Text decorations /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetTextDecorations(TextDecorationCollection textDecorations, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.TextDecorations == textDecorations) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, textDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); } } #endregion #region Line enumerator /// Note: enumeration is temporarily made private /// because of PS #828532 /// ////// Strongly typed enumerator used for enumerating text lines /// private struct LineEnumerator : IEnumerator, IDisposable { int _textStorePosition; int _lineCount; double _totalHeight; TextLine _currentLine; TextLine _nextLine; TextFormatter _formatter; FormattedText _that; // these are needed because _currentLine can be disposed before the next MoveNext() call double _previousHeight; int _previousLength; // line break before _currentLine, needed in case we have to reformat it with collapsing symbol TextLineBreak _previousLineBreak; internal LineEnumerator(FormattedText text) { _previousHeight = 0; _previousLength = 0; _previousLineBreak = null; _textStorePosition = 0; _lineCount = 0; _totalHeight = 0; _currentLine = null; _nextLine = null; _formatter = TextFormatter.FromCurrentDispatcher(); _that = text; if (_that._textSourceImpl == null) _that._textSourceImpl = new TextSourceImplementation(_that); } public void Dispose() { if (_currentLine != null) { _currentLine.Dispose(); _currentLine = null; } if (_nextLine != null) { _nextLine.Dispose(); _nextLine = null; } } internal int Position { get { return _textStorePosition; } } internal int Length { get { return _previousLength; } } ////// Gets the current text line in the collection /// public TextLine Current { get { return _currentLine; } } ////// Gets the current text line in the collection /// object IEnumerator.Current { get { return (Current); } } ////// Gets the paragraph width used to format the current text line /// internal double CurrentParagraphWidth { get { return MaxLineLength(_lineCount); } } private double MaxLineLength(int line) { if (_that._maxTextWidths == null) return _that._maxTextWidth; return _that._maxTextWidths[Math.Min(line, _that._maxTextWidths.Length - 1)]; } ////// Advances the enumerator to the next text line of the collection /// ///true if the enumerator was successfully advanced to the next element; /// false if the enumerator has passed the end of the collection public bool MoveNext() { if (_currentLine == null) { // this is the first line if (_that._text.Length == 0) return false; _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, null // no previous line break ); // check if this line fits the text height if (_totalHeight + _currentLine.Height > _that._maxTextHeight) { _currentLine.Dispose(); _currentLine = null; return false; } Debug.Assert(_nextLine == null); } else { // there is no next line or it didn't fit // either way we're finished if (_nextLine == null) return false; _totalHeight += _previousHeight; _textStorePosition += _previousLength; ++_lineCount; _currentLine = _nextLine; _nextLine = null; } TextLineBreak currentLineBreak = _currentLine.GetTextLineBreak(); // this line is guaranteed to fit the text height Debug.Assert(_totalHeight + _currentLine.Height <= _that._maxTextHeight); // now, check if the next line fits, we need to do this on this iteration // because we might need to add ellipsis to the current line // as a result of the next line measurement // maybe there is no next line at all if (_textStorePosition + _currentLine.Length < _that._text.Length) { bool nextLineFits; if (_lineCount + 1 >= _that._maxLineCount) nextLineFits = false; else { _nextLine = FormatLine( _that._textSourceImpl, _textStorePosition + _currentLine.Length, MaxLineLength(_lineCount + 1), _that._defaultParaProps, currentLineBreak ); nextLineFits = (_totalHeight + _currentLine.Height + _nextLine.Height <= _that._maxTextHeight); } if (!nextLineFits) { // next line doesn't fit if (_nextLine != null) { _nextLine.Dispose(); _nextLine = null; } if (_that._trimming != TextTrimming.None && !_currentLine.HasCollapsed) { // recreate the current line with ellipsis added // Note: Paragraph ellipsis is not supported today. We'll workaround // it here by faking a non-wrap text on finite column width. TextWrapping currentWrap = _that._defaultParaProps.TextWrapping; _that._defaultParaProps.SetTextWrapping(TextWrapping.NoWrap); if (currentLineBreak != null) currentLineBreak.Dispose(); _currentLine.Dispose(); _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, _previousLineBreak ); currentLineBreak = _currentLine.GetTextLineBreak(); _that._defaultParaProps.SetTextWrapping(currentWrap); } } } _previousHeight = _currentLine.Height; _previousLength = _currentLine.Length; if (_previousLineBreak != null) _previousLineBreak.Dispose(); _previousLineBreak = currentLineBreak; return true; } ////// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed. /// private TextLine FormatLine(TextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak lineBreak) { TextLine line = _formatter.FormatLine( textSource, textSourcePosition, maxLineLength, paraProps, lineBreak ); if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0) { // what I really need here is the last displayed text run of the line // textSourcePosition + line.Length - 1 works except the end of paragraph case, // where line length includes the fake paragraph break run Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1); SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1) ); GenericTextRunProperties lastRunProps = thatFormatRider.CurrentElement as GenericTextRunProperties; TextCollapsingProperties trailingEllipsis; if (_that._trimming == TextTrimming.CharacterEllipsis) trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps); else { Debug.Assert(_that._trimming == TextTrimming.WordEllipsis); trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); } TextLine collapsedLine = line.Collapse(trailingEllipsis); if (collapsedLine != line) { line.Dispose(); line = collapsedLine; } } return line; } ////// Sets the enumerator to its initial position, /// which is before the first element in the collection /// public void Reset() { _textStorePosition = 0; _lineCount = 0; _totalHeight = 0; _currentLine = null; _nextLine = null; } } ////// Returns an enumerator that can iterate through the text line collection /// private LineEnumerator GetEnumerator() { return new LineEnumerator(this); } #if NEVER ////// Returns an enumerator that can iterate through the text line collection /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endif private void AdvanceLineOrigin(ref Point lineOrigin, TextLine currentLine) { double height = currentLine.Height; // advance line origin according to the flow direction switch (_defaultParaProps.FlowDirection) { case FlowDirection.LeftToRight: case FlowDirection.RightToLeft: lineOrigin.Y += height; break; } } #endregion #region Measurement and layout properties private class CachedMetrics { // vertical public double Height; public double Baseline; // horizontal public double Width; public double WidthIncludingTrailingWhitespace; // vertical bounding box metrics public double Extent; public double OverhangAfter; // horizontal bounding box metrics public double OverhangLeading; public double OverhangTrailing; } ////// Defines the flow direction /// public FlowDirection FlowDirection { set { ValidateFlowDirection(value, "value"); _defaultParaProps.SetFlowDirection(value); InvalidateMetrics(); } get { return _defaultParaProps.FlowDirection; } } ////// Defines the alignment of text within the column /// public TextAlignment TextAlignment { set { _defaultParaProps.SetTextAlignment(value); InvalidateMetrics(); } get { return _defaultParaProps.TextAlignment; } } ////// Gets or sets the height of, or the spacing between, each line where /// zero represents the default line height. /// public double LineHeight { set { if (value < 0) throw new ArgumentOutOfRangeException("LineHeight", SR.Get(SRID.ParameterCannotBeNegative)); _defaultParaProps.SetLineHeight(value); InvalidateMetrics(); } get { return _defaultParaProps.LineHeight; } } ////// The MaxTextWidth property defines the alignment edges for the FormattedText. /// For example, left aligned text is wrapped such that the leftmost glyph alignment point /// on each line falls exactly on the left edge of the rectangle. /// Note that for many fonts, especially in italic style, some glyph strokes may extend beyond the edges of the alignment rectangle. /// For this reason, it is recommended that clients draw text with at least 1/6 em (i.e of the font size) unused margin space either side. /// Zero value of MaxTextWidth is equivalent to the maximum possible paragraph width. /// public double MaxTextWidth { set { if (value < 0) throw new ArgumentOutOfRangeException("MaxTextWidth", SR.Get(SRID.ParameterCannotBeNegative)); _maxTextWidth = value; InvalidateMetrics(); } get { return _maxTextWidth; } } ////// Sets the array of lengths, /// which will be applied to each line of text in turn. /// If the text covers more lines than there are entries in the length array, /// the last entry is reused as many times as required. /// The maxTextWidths array overrides the MaxTextWidth property. /// /// The max text width array public void SetMaxTextWidths(double [] maxTextWidths) { if (maxTextWidths == null || maxTextWidths.Length <= 0) throw new ArgumentNullException("maxTextWidths"); _maxTextWidths = maxTextWidths; InvalidateMetrics(); } ////// Obtains a copy of the array of lengths, /// which will be applied to each line of text in turn. /// If the text covers more lines than there are entries in the length array, /// the last entry is reused as many times as required. /// The maxTextWidths array overrides the MaxTextWidth property. /// ///The copy of max text width array public double [] GetMaxTextWidths() { return (_maxTextWidths == null) ? null : (double [])_maxTextWidths.Clone(); } ////// Sets the maximum length of a column of text. /// The last line of text displayed is the last whole line that will fit within this limit, /// or the nth line as specified by MaxLineCount, whichever occurs first. /// Use the Trimming property to control how the omission of text is indicated. /// public double MaxTextHeight { set { if (value <= 0) throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyMustBeGreaterThanZero, "MaxTextHeight")); if (DoubleUtil.IsNaN(value)) throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyValueCannotBeNaN, "MaxTextHeight")); _maxTextHeight = value; InvalidateMetrics(); } get { return _maxTextHeight; } } ////// Defines the maximum number of lines to display. /// The last line of text displayed is the lineCount-1'th line, /// or the last whole line that will fit within the count set by MaxTextHeight, /// whichever occurs first. /// Use the Trimming property to control how the omission of text is indicated /// public int MaxLineCount { set { if (value <= 0) throw new ArgumentOutOfRangeException("MaxLineCount", SR.Get(SRID.ParameterMustBeGreaterThanZero)); _maxLineCount = value; InvalidateMetrics(); } get { return _maxLineCount; } } ////// Defines how omission of text is indicated. /// CharacterEllipsis trimming allows partial words to be displayed, /// while WordEllipsis removes whole words to fit. /// Both guarantee to include an ellipsis ('...') at the end of the lines /// where text has been trimmed as a result of line and column limits. /// public TextTrimming Trimming { set { if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis) throw new InvalidEnumArgumentException("value", (int)value, typeof(TextTrimming)); _trimming = value; if (_trimming == TextTrimming.None) { // if trimming is disabled, enforce emergency wrap _defaultParaProps.SetTextWrapping(TextWrapping.Wrap); } else { _defaultParaProps.SetTextWrapping(TextWrapping.WrapWithOverflow); } InvalidateMetrics(); } get { return _trimming; } } ////// Lazily initializes the cached metrics EXCEPT for black box metrics and /// returns the CachedMetrics structure. /// private CachedMetrics Metrics { get { if (_metrics == null) { // We need to obtain the metrics. DON'T compute black box metrics here because // they probably won't be needed and computing them requires GlyphRun creation. // In the common case where a client measures and then draws, we'll format twice // but create GlyphRuns only during drawing. _metrics = DrawAndCalculateMetrics( null, // drawing context new Point(), // drawing offset false); // don't calculate black box metrics } return _metrics; } } ////// Lazily initializes the cached metrics INCLUDING black box metrics and /// returns the CachedMetrics structure. /// private CachedMetrics BlackBoxMetrics { get { if (_metrics == null || double.IsNaN(_metrics.Extent)) { // We need to obtain the metrics, including black box metrics. _metrics = DrawAndCalculateMetrics( null, // drawing context new Point(), // drawing offset true); // calculate black box metrics } return _metrics; } } ////// The distance from the top of the first line to the bottom of the last line. /// public double Height { get { return Metrics.Height; } } ////// The distance from the topmost black pixel of the first line /// to the bottommost black pixel of the last line. /// public double Extent { get { return BlackBoxMetrics.Extent; } } ////// The distance from the top of the first line to the baseline of the first line. /// public double Baseline { get { return Metrics.Baseline; } } ////// The distance from the bottom of the last line to the extent bottom. /// public double OverhangAfter { get { return BlackBoxMetrics.OverhangAfter; } } ////// The maximum distance from the leading black pixel to the leading alignment point of a line. /// public double OverhangLeading { get { return BlackBoxMetrics.OverhangLeading; } } ////// The maximum distance from the trailing black pixel to the trailing alignment point of a line. /// public double OverhangTrailing { get { return BlackBoxMetrics.OverhangTrailing; } } ////// The maximum advance width between the leading and trailing alignment points of a line, /// excluding the width of whitespace characters at the end of the line. /// public double Width { get { return Metrics.Width; } } ////// The maximum advance width between the leading and trailing alignment points of a line, /// including the width of whitespace characters at the end of the line. /// public double WidthIncludingTrailingWhitespace { get { return Metrics.WidthIncludingTrailingWhitespace; } } ////// The minimum line width that can be specified without causing any word to break. /// public double MinWidth { get { if (_minWidth != double.MinValue) return _minWidth; _minWidth = TextFormatter.FromCurrentDispatcher().FormatMinMaxParagraphWidth( _textSourceImpl, 0, // textSourceCharacterIndex _defaultParaProps ).MinWidth; return _minWidth; } } ////// Builds a highlight geometry object. /// /// The origin of the highlight region ///Geometry that surrounds the text. public Geometry BuildHighlightGeometry(Point origin) { return BuildHighlightGeometry(origin, 0, _text.Length); } ////// Obtains geometry for the text, including underlines and strikethroughs. /// /// The left top origin of the resulting geometry. ///The geometry returned contains the combined geometry /// of all of the glyphs, underlines and strikeThroughs that represent the formatted text. /// Overlapping contours are merged by performing a Boolean union operation. public Geometry BuildGeometry(Point origin) { GeometryGroup accumulatedGeometry = null; Point lineOrigin = origin; DrawingGroup drawing = new DrawingGroup(); DrawingContext ctx = drawing.Open(); // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { currentLine.Draw(ctx, lineOrigin, InvertAxes.None); AdvanceLineOrigin(ref lineOrigin, currentLine); } } } ctx.Close(); // recursively go down the DrawingGroup to build up the geometry CombineGeometryRecursive(drawing, ref accumulatedGeometry); // Make sure to always return Geometry.Empty from public methods for empty geometries. if (accumulatedGeometry == null || accumulatedGeometry.IsEmpty()) return Geometry.Empty; return accumulatedGeometry; } ////// Builds a highlight geometry object for a given character range. /// /// The origin of the highlight region. /// The start index of initial character the bounds should be obtained for. /// The number of characters the bounds should be obtained for. ///Geometry that surrounds the specified character range. public Geometry BuildHighlightGeometry(Point origin, int startIndex, int count) { ValidateRange(startIndex, count); PathGeometry accumulatedBounds = null; using (LineEnumerator enumerator = GetEnumerator()) { Point lineOrigin = origin; while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { int x0 = Math.Max(enumerator.Position, startIndex); int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count); // check if this line is intersects with the specified character range if (x0 < x1) { IListhighlightBounds = currentLine.GetTextBounds( x0, x1 - x0 ); if (highlightBounds != null) { foreach (TextBounds bound in highlightBounds) { Rect rect = bound.Rectangle; if (FlowDirection == FlowDirection.RightToLeft) { // Convert logical units (which extend leftward from the right edge // of the paragraph) to physical units. // // Note that since rect is in logical units, rect.Right corresponds to // the visual *left* edge of the rectangle in the RTL case. Specifically, // is the distance leftward from the right edge of the formatting rectangle // whose width is the paragraph width passed to FormatLine. // rect.X = enumerator.CurrentParagraphWidth - rect.Right; } rect.X += lineOrigin.X; rect.Y += lineOrigin.Y; RectangleGeometry rectangleGeometry = new RectangleGeometry(rect); if (accumulatedBounds == null) accumulatedBounds = rectangleGeometry.GetAsPathGeometry(); else accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union, null); } } } AdvanceLineOrigin(ref lineOrigin, currentLine); } } } if (accumulatedBounds == null || accumulatedBounds.IsEmpty()) return null; return accumulatedBounds; } #endregion #region Drawing /// /// Draws the text object /// internal void Draw( DrawingContext dc, Point origin ) { Point lineOrigin = origin; if (_metrics != null && !double.IsNaN(_metrics.Extent)) { // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { currentLine.Draw(dc, lineOrigin, InvertAxes.None); AdvanceLineOrigin(ref lineOrigin, currentLine); } } } } else { // Calculate metrics as we draw to avoid formatting again if we need metrics later; we compute // black box metrics too because these are already known as a side-effect of drawing _metrics = DrawAndCalculateMetrics(dc, origin, true); } } private CachedMetrics DrawAndCalculateMetrics(DrawingContext dc, Point drawingOffset, bool getBlackBoxMetrics) { CachedMetrics metrics = new CachedMetrics(); if (_text.Length == 0) { return metrics; } // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { bool first = true; double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; accBlackBoxLeft = accBlackBoxTop = double.MaxValue; accBlackBoxRight = accBlackBoxBottom = double.MinValue; Point origin = new Point(0, 0); while (enumerator.MoveNext()) { // enumerator will dispose the currentLine using (TextLine currentLine = enumerator.Current) { // if we're drawing, do it first as this will compute black box metrics as a side-effect if (dc != null) { currentLine.Draw( dc, new Point(origin.X + drawingOffset.X, origin.Y + drawingOffset.Y), InvertAxes.None ); } if (getBlackBoxMetrics) { double blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading; double blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing; double blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter; double blackBoxTop = blackBoxBottom - currentLine.Extent; accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight); accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); metrics.OverhangAfter = currentLine.OverhangAfter; } metrics.Height += currentLine.Height; metrics.Width = Math.Max(metrics.Width, currentLine.Width + currentLine.Start); metrics.WidthIncludingTrailingWhitespace = Math.Max(metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace + currentLine.Start); if (first) { metrics.Baseline = currentLine.Baseline; first = false; } AdvanceLineOrigin(ref origin, currentLine); } } if (getBlackBoxMetrics) { metrics.Extent = accBlackBoxBottom - accBlackBoxTop; metrics.OverhangLeading = accBlackBoxLeft; metrics.OverhangTrailing = metrics.Width - accBlackBoxRight; } else { // indicate that black box metrics are not known metrics.Extent = double.NaN; } } return metrics; } #endregion #region TextSource implementation private class TextSourceImplementation : TextSource { private FormattedText _that; public TextSourceImplementation(FormattedText text) { _that = text; } ////// TextFormatter to get a text run started at specified text source position /// /// character index to specify where in the source text the fetch is to start. public override TextRun GetTextRun( int textSourceCharacterIndex ) { if (textSourceCharacterIndex >= _that._text.Length) { return new TextEndOfParagraph(1); } SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, textSourceCharacterIndex ); return new TextCharacters(_that._text, textSourceCharacterIndex, thatFormatRider.Length, thatFormatRider.CurrentElement as GenericTextRunProperties ); } ////// TextFormatter to get text immediately before specified text source position. /// /// character index to specify where in the source text the text retrieval stops. ///character string immediately before the specify text source character index. public override TextSpanGetPrecedingText( int textSourceCharacterIndexLimit ) { CharacterBufferRange charString = CharacterBufferRange.Empty; CultureInfo culture = null; if (textSourceCharacterIndexLimit > 0) { SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, textSourceCharacterIndexLimit - 1 ); charString = new CharacterBufferRange( new CharacterBufferReference(_that._text, thatFormatRider.CurrentSpanStart), textSourceCharacterIndexLimit - thatFormatRider.CurrentSpanStart ); culture = ((TextRunProperties)thatFormatRider.CurrentElement).CultureInfo; } return new TextSpan ( charString.Length, new CultureSpecificCharacterBufferRange(culture, charString) ); } /// /// TextFormatter to map a text source character index to a text effect character index /// /// text source character index ///the text effect index corresponding to the text effect character index public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex( int textSourceCharacterIndex ) { throw new NotSupportedException(); } }; #endregion #region private methods private void CombineGeometryRecursive(Drawing drawing, ref GeometryGroup accumulatedGeometry) { DrawingGroup group = drawing as DrawingGroup; if (group != null) { // recursively go down for DrawingGroup foreach (Drawing child in group.Children) { CombineGeometryRecursive(child, ref accumulatedGeometry); } } else { GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing; if (glyphRunDrawing != null) { // process glyph run GlyphRun glyphRun = glyphRunDrawing.GlyphRun; if (glyphRun != null) { Geometry glyphRunGeometry = glyphRun.BuildGeometry(); if (!glyphRunGeometry.IsEmpty()) { if (accumulatedGeometry == null) { accumulatedGeometry = new GeometryGroup(); accumulatedGeometry.FillRule = FillRule.Nonzero; } accumulatedGeometry.Children.Add(glyphRunGeometry); } } } else { GeometryDrawing geometryDrawing = drawing as GeometryDrawing; if (geometryDrawing != null) { // process geometry (i.e. TextDecoration on the line) Geometry geometry = geometryDrawing.Geometry; if (geometry != null) { LineGeometry lineGeometry = geometry as LineGeometry; if (lineGeometry != null) { // For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no // bounding area. So this line won't show up. Work aroud it by increase the Bounding rect // to be Pen's thickness Rect bound = lineGeometry.Bounds; if (bound.Height == 0) { bound.Height = geometryDrawing.Pen.Thickness; } else if (bound.Width == 0) { bound.Width = geometryDrawing.Pen.Thickness; } // convert the line geometry into a rectangle geometry // we lost line cap info here geometry = new RectangleGeometry(bound); } if (accumulatedGeometry == null) { accumulatedGeometry = new GeometryGroup(); accumulatedGeometry.FillRule = FillRule.Nonzero; } accumulatedGeometry.Children.Add(geometry); } } } } } #endregion #region Private fields // properties and format runs private string _text; private SpanVector _formatRuns = new SpanVector(null); private SpanPosition _latestPosition = new SpanPosition(); private GenericTextParagraphProperties _defaultParaProps; private double _maxTextWidth; private double [] _maxTextWidths; private double _maxTextHeight = double.MaxValue; private int _maxLineCount = int.MaxValue; private TextTrimming _trimming = TextTrimming.WordEllipsis; // text source callbacks private TextSourceImplementation _textSourceImpl; // cached metrics private CachedMetrics _metrics; private double _minWidth; #endregion #region Constants // we always use default text formatter so we can use Constants.DefaultIdealToReal const double RealInfiniteWidth = Constants.InfiniteWidth * Constants.DefaultIdealToReal; const double MaxFontEmSize = RealInfiniteWidth / Constants.GreatestMutiplierOfEm; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. //---------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // Description: Implementation of FormattedText class. The FormattedText class is targeted at programmers // needing to add some simple text to a MIL visual. // // History: // 04/10/2003 : mleonov - Created // //--------------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.Windows; using System.Windows.Media; using System.Windows.Media.TextFormatting; using System.Runtime.InteropServices; using MS.Internal; using MS.Internal.TextFormatting; using MS.Internal.FontFace; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; #pragma warning disable 1634, 1691 //Allow suppression of Presharp warnings namespace System.Windows.Media { ////// The FormattedText class is targeted at programmers needing to add some simple text to a MIL visual. /// public class FormattedText { #region Construction ////// Construct a FormattedText object. /// /// String of text to be displayed. /// Culture of text. /// Flow direction of text. /// Type face used to display text. /// Font em size in visual units (1/96 of an inch). /// Foreground brush used to render text. public FormattedText( string textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, double emSize, Brush foreground) : this( textToFormat, culture, flowDirection, typeface, emSize, foreground, null // numberSubstitution ) { } ////// Construct a FormattedText object. /// /// String of text to be displayed. /// Culture of text. /// Flow direction of text. /// Type face used to display text. /// Font em size in visual units (1/96 of an inch). /// Foreground brush used to render text. /// Number substitution behavior to apply to the text; can be null, /// in which case the default number number method for the text culture is used. public FormattedText( string textToFormat, CultureInfo culture, FlowDirection flowDirection, Typeface typeface, double emSize, Brush foreground, NumberSubstitution numberSubstitution) { if (textToFormat == null) throw new ArgumentNullException("textToFormat"); if (typeface == null) throw new ArgumentNullException("typeface"); ValidateCulture(culture); ValidateFlowDirection(flowDirection, "flowDirection"); ValidateFontSize(emSize); _text = textToFormat; GenericTextRunProperties runProps = new GenericTextRunProperties( typeface, emSize, 12.0f, // default hinting size null, // decorations foreground, null, // highlight background BaselineAlignment.Baseline, culture, numberSubstitution ); _latestPosition = _formatRuns.SetValue(0, _text.Length, runProps, _latestPosition); _defaultParaProps = new GenericTextParagraphProperties( flowDirection, TextAlignment.Left, false, false, runProps, TextWrapping.WrapWithOverflow, 0, // line height not specified 0 // indentation not specified ); InvalidateMetrics(); } ////// Returns the string of text to be displayed /// public string Text { get { return _text; } } #endregion #region Formatting properties private static void ValidateCulture(CultureInfo culture) { if (culture == null) throw new ArgumentNullException("culture"); } private static void ValidateFontSize(double emSize) { if (emSize <= 0) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterMustBeGreaterThanZero)); if (emSize > MaxFontEmSize) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterCannotBeGreaterThan, MaxFontEmSize)); if (DoubleUtil.IsNaN(emSize)) throw new ArgumentOutOfRangeException("emSize", SR.Get(SRID.ParameterValueCannotBeNaN)); } private static void ValidateFlowDirection(FlowDirection flowDirection, string parameterName) { if ((int)flowDirection < 0 || (int)flowDirection > (int)FlowDirection.RightToLeft) throw new InvalidEnumArgumentException(parameterName, (int)flowDirection, typeof(FlowDirection)); } private int ValidateRange(int startIndex, int count) { if (startIndex < 0 || startIndex > _text.Length) throw new ArgumentOutOfRangeException("startIndex"); int limit = startIndex + count; if (count < 0 || limit < startIndex || limit > _text.Length) throw new ArgumentOutOfRangeException("count"); return limit; } private void InvalidateMetrics() { _metrics = null; _minWidth = double.MinValue; } ////// Sets foreground brush used for drawing text /// /// Foreground brush public void SetForegroundBrush(Brush foregroundBrush) { SetForegroundBrush(foregroundBrush, 0, _text.Length); } ////// Sets foreground brush used for drawing text /// /// Foreground brush /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetForegroundBrush(Brush foregroundBrush, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.ForegroundBrush == foregroundBrush) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, foregroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); } } ////// Sets or changes the font family for the text object /// /// Font family name public void SetFontFamily(string fontFamily) { SetFontFamily(fontFamily, 0, _text.Length); } ////// Sets or changes the font family for the text object /// /// Font family name /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontFamily(string fontFamily, int startIndex, int count) { if (fontFamily == null) throw new ArgumentNullException("fontFamily"); SetFontFamily(new FontFamily(fontFamily), startIndex, count); } ////// Sets or changes the font family for the text object /// /// Font family public void SetFontFamily(FontFamily fontFamily) { SetFontFamily(fontFamily, 0, _text.Length); } ////// Sets or changes the font family for the text object /// /// Font family /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontFamily(FontFamily fontFamily, int startIndex, int count) { if (fontFamily == null) throw new ArgumentNullException("fontFamily"); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (fontFamily.Equals(oldTypeface.FontFamily)) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(fontFamily, oldTypeface.Style, oldTypeface.Weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font em size measured in MIL units /// /// Font em size public void SetFontSize(double emSize) { SetFontSize(emSize, 0, _text.Length); } ////// Sets or changes the font em size measured in MIL units /// /// Font em size /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontSize(double emSize, int startIndex, int count) { ValidateFontSize(emSize); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.FontRenderingEmSize == emSize) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, emSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); #pragma warning restore 6506 InvalidateMetrics(); } } ////// Sets or changes the culture for the text object. /// /// The new culture for the text object. public void SetCulture(CultureInfo culture) { SetCulture(culture, 0, _text.Length); } ////// Sets or changes the culture for the text object. /// /// The new culture for the text object. /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetCulture(CultureInfo culture, int startIndex, int count) { ValidateCulture(culture); int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit; ) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.CultureInfo.Equals(culture)) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, culture, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the number substitution behavior for the text. /// /// Number substitution behavior to apply to the text; can be null, /// in which case the default number substitution method for the text culture is used. public void SetNumberSubstitution( NumberSubstitution numberSubstitution ) { SetNumberSubstitution(numberSubstitution, 0, _text.Length); } ////// Sets or changes the number substitution behavior for a range of text. /// /// Number substitution behavior to apply to the text; can be null, /// in which case the default number substitution method for the text culture is used. /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetNumberSubstitution( NumberSubstitution numberSubstitution, int startIndex, int count ) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit; ) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (numberSubstitution != null) { if (numberSubstitution.Equals(runProps.NumberSubstitution)) continue; } else { if (runProps.NumberSubstitution == null) continue; } GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, numberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font weight /// /// Font weight public void SetFontWeight(FontWeight weight) { SetFontWeight(weight, 0, _text.Length); } ////// Sets or changes the font weight /// /// Font weight /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontWeight(FontWeight weight, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Weight == weight) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, oldTypeface.Style, weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the font style /// /// Font style public void SetFontStyle(FontStyle style) { SetFontStyle(style, 0, _text.Length); } ////// Sets or changes the font style /// /// Font style /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontStyle(FontStyle style, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Style == style) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, style, oldTypeface.Weight, oldTypeface.Stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); // invalidate cached metrics } } ////// Sets or changes the font stretch /// /// Font stretch public void SetFontStretch(FontStretch stretch) { SetFontStretch(stretch, 0, _text.Length); } ////// Sets or changes the font stretch /// /// Font stretch /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontStretch(FontStretch stretch, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); Typeface oldTypeface = runProps.Typeface; if (oldTypeface.Stretch == stretch) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( new Typeface(oldTypeface.FontFamily, oldTypeface.Style, oldTypeface.Weight, stretch), runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); #pragma warning restore 6506 InvalidateMetrics(); } } ////// Sets or changes the type face /// /// Typeface public void SetFontTypeface(Typeface typeface) { SetFontTypeface(typeface, 0, _text.Length); } ////// Sets or changes the type face /// /// Typeface /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetFontTypeface(Typeface typeface, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.Typeface == typeface) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, runProps.TextDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); InvalidateMetrics(); } } ////// Sets or changes the text decorations /// /// Text decorations public void SetTextDecorations(TextDecorationCollection textDecorations) { SetTextDecorations(textDecorations, 0, _text.Length); } ////// Sets or changes the text decorations /// /// Text decorations /// The start index of initial character to apply the change to. /// The number of characters the change should be applied to. public void SetTextDecorations(TextDecorationCollection textDecorations, int startIndex, int count) { int limit = ValidateRange(startIndex, count); for (int i = startIndex; i < limit;) { SpanRider formatRider = new SpanRider(_formatRuns, _latestPosition, i); i = Math.Min(limit, i + formatRider.Length); #pragma warning disable 6506 // Presharp warns that runProps is not validated, but it can never be null // because the rider is already checked to be in range GenericTextRunProperties runProps = formatRider.CurrentElement as GenericTextRunProperties; Invariant.Assert(runProps != null); if (runProps.TextDecorations == textDecorations) continue; GenericTextRunProperties newProps = new GenericTextRunProperties( runProps.Typeface, runProps.FontRenderingEmSize, runProps.FontHintingEmSize, textDecorations, runProps.ForegroundBrush, runProps.BackgroundBrush, runProps.BaselineAlignment, runProps.CultureInfo, runProps.NumberSubstitution ); #pragma warning restore 6506 _latestPosition = _formatRuns.SetValue(formatRider.CurrentPosition, i - formatRider.CurrentPosition, newProps, formatRider.SpanPosition); } } #endregion #region Line enumerator /// Note: enumeration is temporarily made private /// because of PS #828532 /// ////// Strongly typed enumerator used for enumerating text lines /// private struct LineEnumerator : IEnumerator, IDisposable { int _textStorePosition; int _lineCount; double _totalHeight; TextLine _currentLine; TextLine _nextLine; TextFormatter _formatter; FormattedText _that; // these are needed because _currentLine can be disposed before the next MoveNext() call double _previousHeight; int _previousLength; // line break before _currentLine, needed in case we have to reformat it with collapsing symbol TextLineBreak _previousLineBreak; internal LineEnumerator(FormattedText text) { _previousHeight = 0; _previousLength = 0; _previousLineBreak = null; _textStorePosition = 0; _lineCount = 0; _totalHeight = 0; _currentLine = null; _nextLine = null; _formatter = TextFormatter.FromCurrentDispatcher(); _that = text; if (_that._textSourceImpl == null) _that._textSourceImpl = new TextSourceImplementation(_that); } public void Dispose() { if (_currentLine != null) { _currentLine.Dispose(); _currentLine = null; } if (_nextLine != null) { _nextLine.Dispose(); _nextLine = null; } } internal int Position { get { return _textStorePosition; } } internal int Length { get { return _previousLength; } } ////// Gets the current text line in the collection /// public TextLine Current { get { return _currentLine; } } ////// Gets the current text line in the collection /// object IEnumerator.Current { get { return (Current); } } ////// Gets the paragraph width used to format the current text line /// internal double CurrentParagraphWidth { get { return MaxLineLength(_lineCount); } } private double MaxLineLength(int line) { if (_that._maxTextWidths == null) return _that._maxTextWidth; return _that._maxTextWidths[Math.Min(line, _that._maxTextWidths.Length - 1)]; } ////// Advances the enumerator to the next text line of the collection /// ///true if the enumerator was successfully advanced to the next element; /// false if the enumerator has passed the end of the collection public bool MoveNext() { if (_currentLine == null) { // this is the first line if (_that._text.Length == 0) return false; _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, null // no previous line break ); // check if this line fits the text height if (_totalHeight + _currentLine.Height > _that._maxTextHeight) { _currentLine.Dispose(); _currentLine = null; return false; } Debug.Assert(_nextLine == null); } else { // there is no next line or it didn't fit // either way we're finished if (_nextLine == null) return false; _totalHeight += _previousHeight; _textStorePosition += _previousLength; ++_lineCount; _currentLine = _nextLine; _nextLine = null; } TextLineBreak currentLineBreak = _currentLine.GetTextLineBreak(); // this line is guaranteed to fit the text height Debug.Assert(_totalHeight + _currentLine.Height <= _that._maxTextHeight); // now, check if the next line fits, we need to do this on this iteration // because we might need to add ellipsis to the current line // as a result of the next line measurement // maybe there is no next line at all if (_textStorePosition + _currentLine.Length < _that._text.Length) { bool nextLineFits; if (_lineCount + 1 >= _that._maxLineCount) nextLineFits = false; else { _nextLine = FormatLine( _that._textSourceImpl, _textStorePosition + _currentLine.Length, MaxLineLength(_lineCount + 1), _that._defaultParaProps, currentLineBreak ); nextLineFits = (_totalHeight + _currentLine.Height + _nextLine.Height <= _that._maxTextHeight); } if (!nextLineFits) { // next line doesn't fit if (_nextLine != null) { _nextLine.Dispose(); _nextLine = null; } if (_that._trimming != TextTrimming.None && !_currentLine.HasCollapsed) { // recreate the current line with ellipsis added // Note: Paragraph ellipsis is not supported today. We'll workaround // it here by faking a non-wrap text on finite column width. TextWrapping currentWrap = _that._defaultParaProps.TextWrapping; _that._defaultParaProps.SetTextWrapping(TextWrapping.NoWrap); if (currentLineBreak != null) currentLineBreak.Dispose(); _currentLine.Dispose(); _currentLine = FormatLine( _that._textSourceImpl, _textStorePosition, MaxLineLength(_lineCount), _that._defaultParaProps, _previousLineBreak ); currentLineBreak = _currentLine.GetTextLineBreak(); _that._defaultParaProps.SetTextWrapping(currentWrap); } } } _previousHeight = _currentLine.Height; _previousLength = _currentLine.Length; if (_previousLineBreak != null) _previousLineBreak.Dispose(); _previousLineBreak = currentLineBreak; return true; } ////// Wrapper of TextFormatter.FormatLine that auto-collapses the line if needed. /// private TextLine FormatLine(TextSource textSource, int textSourcePosition, double maxLineLength, TextParagraphProperties paraProps, TextLineBreak lineBreak) { TextLine line = _formatter.FormatLine( textSource, textSourcePosition, maxLineLength, paraProps, lineBreak ); if (_that._trimming != TextTrimming.None && line.HasOverflowed && line.Length > 0) { // what I really need here is the last displayed text run of the line // textSourcePosition + line.Length - 1 works except the end of paragraph case, // where line length includes the fake paragraph break run Debug.Assert(_that._text.Length > 0 && textSourcePosition + line.Length <= _that._text.Length + 1); SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, Math.Min(textSourcePosition + line.Length - 1, _that._text.Length - 1) ); GenericTextRunProperties lastRunProps = thatFormatRider.CurrentElement as GenericTextRunProperties; TextCollapsingProperties trailingEllipsis; if (_that._trimming == TextTrimming.CharacterEllipsis) trailingEllipsis = new TextTrailingCharacterEllipsis(maxLineLength, lastRunProps); else { Debug.Assert(_that._trimming == TextTrimming.WordEllipsis); trailingEllipsis = new TextTrailingWordEllipsis(maxLineLength, lastRunProps); } TextLine collapsedLine = line.Collapse(trailingEllipsis); if (collapsedLine != line) { line.Dispose(); line = collapsedLine; } } return line; } ////// Sets the enumerator to its initial position, /// which is before the first element in the collection /// public void Reset() { _textStorePosition = 0; _lineCount = 0; _totalHeight = 0; _currentLine = null; _nextLine = null; } } ////// Returns an enumerator that can iterate through the text line collection /// private LineEnumerator GetEnumerator() { return new LineEnumerator(this); } #if NEVER ////// Returns an enumerator that can iterate through the text line collection /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } #endif private void AdvanceLineOrigin(ref Point lineOrigin, TextLine currentLine) { double height = currentLine.Height; // advance line origin according to the flow direction switch (_defaultParaProps.FlowDirection) { case FlowDirection.LeftToRight: case FlowDirection.RightToLeft: lineOrigin.Y += height; break; } } #endregion #region Measurement and layout properties private class CachedMetrics { // vertical public double Height; public double Baseline; // horizontal public double Width; public double WidthIncludingTrailingWhitespace; // vertical bounding box metrics public double Extent; public double OverhangAfter; // horizontal bounding box metrics public double OverhangLeading; public double OverhangTrailing; } ////// Defines the flow direction /// public FlowDirection FlowDirection { set { ValidateFlowDirection(value, "value"); _defaultParaProps.SetFlowDirection(value); InvalidateMetrics(); } get { return _defaultParaProps.FlowDirection; } } ////// Defines the alignment of text within the column /// public TextAlignment TextAlignment { set { _defaultParaProps.SetTextAlignment(value); InvalidateMetrics(); } get { return _defaultParaProps.TextAlignment; } } ////// Gets or sets the height of, or the spacing between, each line where /// zero represents the default line height. /// public double LineHeight { set { if (value < 0) throw new ArgumentOutOfRangeException("LineHeight", SR.Get(SRID.ParameterCannotBeNegative)); _defaultParaProps.SetLineHeight(value); InvalidateMetrics(); } get { return _defaultParaProps.LineHeight; } } ////// The MaxTextWidth property defines the alignment edges for the FormattedText. /// For example, left aligned text is wrapped such that the leftmost glyph alignment point /// on each line falls exactly on the left edge of the rectangle. /// Note that for many fonts, especially in italic style, some glyph strokes may extend beyond the edges of the alignment rectangle. /// For this reason, it is recommended that clients draw text with at least 1/6 em (i.e of the font size) unused margin space either side. /// Zero value of MaxTextWidth is equivalent to the maximum possible paragraph width. /// public double MaxTextWidth { set { if (value < 0) throw new ArgumentOutOfRangeException("MaxTextWidth", SR.Get(SRID.ParameterCannotBeNegative)); _maxTextWidth = value; InvalidateMetrics(); } get { return _maxTextWidth; } } ////// Sets the array of lengths, /// which will be applied to each line of text in turn. /// If the text covers more lines than there are entries in the length array, /// the last entry is reused as many times as required. /// The maxTextWidths array overrides the MaxTextWidth property. /// /// The max text width array public void SetMaxTextWidths(double [] maxTextWidths) { if (maxTextWidths == null || maxTextWidths.Length <= 0) throw new ArgumentNullException("maxTextWidths"); _maxTextWidths = maxTextWidths; InvalidateMetrics(); } ////// Obtains a copy of the array of lengths, /// which will be applied to each line of text in turn. /// If the text covers more lines than there are entries in the length array, /// the last entry is reused as many times as required. /// The maxTextWidths array overrides the MaxTextWidth property. /// ///The copy of max text width array public double [] GetMaxTextWidths() { return (_maxTextWidths == null) ? null : (double [])_maxTextWidths.Clone(); } ////// Sets the maximum length of a column of text. /// The last line of text displayed is the last whole line that will fit within this limit, /// or the nth line as specified by MaxLineCount, whichever occurs first. /// Use the Trimming property to control how the omission of text is indicated. /// public double MaxTextHeight { set { if (value <= 0) throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyMustBeGreaterThanZero, "MaxTextHeight")); if (DoubleUtil.IsNaN(value)) throw new ArgumentOutOfRangeException("value", SR.Get(SRID.PropertyValueCannotBeNaN, "MaxTextHeight")); _maxTextHeight = value; InvalidateMetrics(); } get { return _maxTextHeight; } } ////// Defines the maximum number of lines to display. /// The last line of text displayed is the lineCount-1'th line, /// or the last whole line that will fit within the count set by MaxTextHeight, /// whichever occurs first. /// Use the Trimming property to control how the omission of text is indicated /// public int MaxLineCount { set { if (value <= 0) throw new ArgumentOutOfRangeException("MaxLineCount", SR.Get(SRID.ParameterMustBeGreaterThanZero)); _maxLineCount = value; InvalidateMetrics(); } get { return _maxLineCount; } } ////// Defines how omission of text is indicated. /// CharacterEllipsis trimming allows partial words to be displayed, /// while WordEllipsis removes whole words to fit. /// Both guarantee to include an ellipsis ('...') at the end of the lines /// where text has been trimmed as a result of line and column limits. /// public TextTrimming Trimming { set { if ((int)value < 0 || (int)value > (int)TextTrimming.WordEllipsis) throw new InvalidEnumArgumentException("value", (int)value, typeof(TextTrimming)); _trimming = value; if (_trimming == TextTrimming.None) { // if trimming is disabled, enforce emergency wrap _defaultParaProps.SetTextWrapping(TextWrapping.Wrap); } else { _defaultParaProps.SetTextWrapping(TextWrapping.WrapWithOverflow); } InvalidateMetrics(); } get { return _trimming; } } ////// Lazily initializes the cached metrics EXCEPT for black box metrics and /// returns the CachedMetrics structure. /// private CachedMetrics Metrics { get { if (_metrics == null) { // We need to obtain the metrics. DON'T compute black box metrics here because // they probably won't be needed and computing them requires GlyphRun creation. // In the common case where a client measures and then draws, we'll format twice // but create GlyphRuns only during drawing. _metrics = DrawAndCalculateMetrics( null, // drawing context new Point(), // drawing offset false); // don't calculate black box metrics } return _metrics; } } ////// Lazily initializes the cached metrics INCLUDING black box metrics and /// returns the CachedMetrics structure. /// private CachedMetrics BlackBoxMetrics { get { if (_metrics == null || double.IsNaN(_metrics.Extent)) { // We need to obtain the metrics, including black box metrics. _metrics = DrawAndCalculateMetrics( null, // drawing context new Point(), // drawing offset true); // calculate black box metrics } return _metrics; } } ////// The distance from the top of the first line to the bottom of the last line. /// public double Height { get { return Metrics.Height; } } ////// The distance from the topmost black pixel of the first line /// to the bottommost black pixel of the last line. /// public double Extent { get { return BlackBoxMetrics.Extent; } } ////// The distance from the top of the first line to the baseline of the first line. /// public double Baseline { get { return Metrics.Baseline; } } ////// The distance from the bottom of the last line to the extent bottom. /// public double OverhangAfter { get { return BlackBoxMetrics.OverhangAfter; } } ////// The maximum distance from the leading black pixel to the leading alignment point of a line. /// public double OverhangLeading { get { return BlackBoxMetrics.OverhangLeading; } } ////// The maximum distance from the trailing black pixel to the trailing alignment point of a line. /// public double OverhangTrailing { get { return BlackBoxMetrics.OverhangTrailing; } } ////// The maximum advance width between the leading and trailing alignment points of a line, /// excluding the width of whitespace characters at the end of the line. /// public double Width { get { return Metrics.Width; } } ////// The maximum advance width between the leading and trailing alignment points of a line, /// including the width of whitespace characters at the end of the line. /// public double WidthIncludingTrailingWhitespace { get { return Metrics.WidthIncludingTrailingWhitespace; } } ////// The minimum line width that can be specified without causing any word to break. /// public double MinWidth { get { if (_minWidth != double.MinValue) return _minWidth; _minWidth = TextFormatter.FromCurrentDispatcher().FormatMinMaxParagraphWidth( _textSourceImpl, 0, // textSourceCharacterIndex _defaultParaProps ).MinWidth; return _minWidth; } } ////// Builds a highlight geometry object. /// /// The origin of the highlight region ///Geometry that surrounds the text. public Geometry BuildHighlightGeometry(Point origin) { return BuildHighlightGeometry(origin, 0, _text.Length); } ////// Obtains geometry for the text, including underlines and strikethroughs. /// /// The left top origin of the resulting geometry. ///The geometry returned contains the combined geometry /// of all of the glyphs, underlines and strikeThroughs that represent the formatted text. /// Overlapping contours are merged by performing a Boolean union operation. public Geometry BuildGeometry(Point origin) { GeometryGroup accumulatedGeometry = null; Point lineOrigin = origin; DrawingGroup drawing = new DrawingGroup(); DrawingContext ctx = drawing.Open(); // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { currentLine.Draw(ctx, lineOrigin, InvertAxes.None); AdvanceLineOrigin(ref lineOrigin, currentLine); } } } ctx.Close(); // recursively go down the DrawingGroup to build up the geometry CombineGeometryRecursive(drawing, ref accumulatedGeometry); // Make sure to always return Geometry.Empty from public methods for empty geometries. if (accumulatedGeometry == null || accumulatedGeometry.IsEmpty()) return Geometry.Empty; return accumulatedGeometry; } ////// Builds a highlight geometry object for a given character range. /// /// The origin of the highlight region. /// The start index of initial character the bounds should be obtained for. /// The number of characters the bounds should be obtained for. ///Geometry that surrounds the specified character range. public Geometry BuildHighlightGeometry(Point origin, int startIndex, int count) { ValidateRange(startIndex, count); PathGeometry accumulatedBounds = null; using (LineEnumerator enumerator = GetEnumerator()) { Point lineOrigin = origin; while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { int x0 = Math.Max(enumerator.Position, startIndex); int x1 = Math.Min(enumerator.Position + enumerator.Length, startIndex + count); // check if this line is intersects with the specified character range if (x0 < x1) { IListhighlightBounds = currentLine.GetTextBounds( x0, x1 - x0 ); if (highlightBounds != null) { foreach (TextBounds bound in highlightBounds) { Rect rect = bound.Rectangle; if (FlowDirection == FlowDirection.RightToLeft) { // Convert logical units (which extend leftward from the right edge // of the paragraph) to physical units. // // Note that since rect is in logical units, rect.Right corresponds to // the visual *left* edge of the rectangle in the RTL case. Specifically, // is the distance leftward from the right edge of the formatting rectangle // whose width is the paragraph width passed to FormatLine. // rect.X = enumerator.CurrentParagraphWidth - rect.Right; } rect.X += lineOrigin.X; rect.Y += lineOrigin.Y; RectangleGeometry rectangleGeometry = new RectangleGeometry(rect); if (accumulatedBounds == null) accumulatedBounds = rectangleGeometry.GetAsPathGeometry(); else accumulatedBounds = Geometry.Combine(accumulatedBounds, rectangleGeometry, GeometryCombineMode.Union, null); } } } AdvanceLineOrigin(ref lineOrigin, currentLine); } } } if (accumulatedBounds == null || accumulatedBounds.IsEmpty()) return null; return accumulatedBounds; } #endregion #region Drawing /// /// Draws the text object /// internal void Draw( DrawingContext dc, Point origin ) { Point lineOrigin = origin; if (_metrics != null && !double.IsNaN(_metrics.Extent)) { // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { using (TextLine currentLine = enumerator.Current) { currentLine.Draw(dc, lineOrigin, InvertAxes.None); AdvanceLineOrigin(ref lineOrigin, currentLine); } } } } else { // Calculate metrics as we draw to avoid formatting again if we need metrics later; we compute // black box metrics too because these are already known as a side-effect of drawing _metrics = DrawAndCalculateMetrics(dc, origin, true); } } private CachedMetrics DrawAndCalculateMetrics(DrawingContext dc, Point drawingOffset, bool getBlackBoxMetrics) { CachedMetrics metrics = new CachedMetrics(); if (_text.Length == 0) { return metrics; } // we can't use foreach because it requires GetEnumerator and associated classes to be public // foreach (TextLine currentLine in this) using (LineEnumerator enumerator = GetEnumerator()) { bool first = true; double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; accBlackBoxLeft = accBlackBoxTop = double.MaxValue; accBlackBoxRight = accBlackBoxBottom = double.MinValue; Point origin = new Point(0, 0); while (enumerator.MoveNext()) { // enumerator will dispose the currentLine using (TextLine currentLine = enumerator.Current) { // if we're drawing, do it first as this will compute black box metrics as a side-effect if (dc != null) { currentLine.Draw( dc, new Point(origin.X + drawingOffset.X, origin.Y + drawingOffset.Y), InvertAxes.None ); } if (getBlackBoxMetrics) { double blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading; double blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing; double blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter; double blackBoxTop = blackBoxBottom - currentLine.Extent; accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight); accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); metrics.OverhangAfter = currentLine.OverhangAfter; } metrics.Height += currentLine.Height; metrics.Width = Math.Max(metrics.Width, currentLine.Width + currentLine.Start); metrics.WidthIncludingTrailingWhitespace = Math.Max(metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace + currentLine.Start); if (first) { metrics.Baseline = currentLine.Baseline; first = false; } AdvanceLineOrigin(ref origin, currentLine); } } if (getBlackBoxMetrics) { metrics.Extent = accBlackBoxBottom - accBlackBoxTop; metrics.OverhangLeading = accBlackBoxLeft; metrics.OverhangTrailing = metrics.Width - accBlackBoxRight; } else { // indicate that black box metrics are not known metrics.Extent = double.NaN; } } return metrics; } #endregion #region TextSource implementation private class TextSourceImplementation : TextSource { private FormattedText _that; public TextSourceImplementation(FormattedText text) { _that = text; } ////// TextFormatter to get a text run started at specified text source position /// /// character index to specify where in the source text the fetch is to start. public override TextRun GetTextRun( int textSourceCharacterIndex ) { if (textSourceCharacterIndex >= _that._text.Length) { return new TextEndOfParagraph(1); } SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, textSourceCharacterIndex ); return new TextCharacters(_that._text, textSourceCharacterIndex, thatFormatRider.Length, thatFormatRider.CurrentElement as GenericTextRunProperties ); } ////// TextFormatter to get text immediately before specified text source position. /// /// character index to specify where in the source text the text retrieval stops. ///character string immediately before the specify text source character index. public override TextSpanGetPrecedingText( int textSourceCharacterIndexLimit ) { CharacterBufferRange charString = CharacterBufferRange.Empty; CultureInfo culture = null; if (textSourceCharacterIndexLimit > 0) { SpanRider thatFormatRider = new SpanRider( _that._formatRuns, _that._latestPosition, textSourceCharacterIndexLimit - 1 ); charString = new CharacterBufferRange( new CharacterBufferReference(_that._text, thatFormatRider.CurrentSpanStart), textSourceCharacterIndexLimit - thatFormatRider.CurrentSpanStart ); culture = ((TextRunProperties)thatFormatRider.CurrentElement).CultureInfo; } return new TextSpan ( charString.Length, new CultureSpecificCharacterBufferRange(culture, charString) ); } /// /// TextFormatter to map a text source character index to a text effect character index /// /// text source character index ///the text effect index corresponding to the text effect character index public override int GetTextEffectCharacterIndexFromTextSourceCharacterIndex( int textSourceCharacterIndex ) { throw new NotSupportedException(); } }; #endregion #region private methods private void CombineGeometryRecursive(Drawing drawing, ref GeometryGroup accumulatedGeometry) { DrawingGroup group = drawing as DrawingGroup; if (group != null) { // recursively go down for DrawingGroup foreach (Drawing child in group.Children) { CombineGeometryRecursive(child, ref accumulatedGeometry); } } else { GlyphRunDrawing glyphRunDrawing = drawing as GlyphRunDrawing; if (glyphRunDrawing != null) { // process glyph run GlyphRun glyphRun = glyphRunDrawing.GlyphRun; if (glyphRun != null) { Geometry glyphRunGeometry = glyphRun.BuildGeometry(); if (!glyphRunGeometry.IsEmpty()) { if (accumulatedGeometry == null) { accumulatedGeometry = new GeometryGroup(); accumulatedGeometry.FillRule = FillRule.Nonzero; } accumulatedGeometry.Children.Add(glyphRunGeometry); } } } else { GeometryDrawing geometryDrawing = drawing as GeometryDrawing; if (geometryDrawing != null) { // process geometry (i.e. TextDecoration on the line) Geometry geometry = geometryDrawing.Geometry; if (geometry != null) { LineGeometry lineGeometry = geometry as LineGeometry; if (lineGeometry != null) { // For TextDecoration drawn by DrawLine(), the geometry is a LineGeometry which has no // bounding area. So this line won't show up. Work aroud it by increase the Bounding rect // to be Pen's thickness Rect bound = lineGeometry.Bounds; if (bound.Height == 0) { bound.Height = geometryDrawing.Pen.Thickness; } else if (bound.Width == 0) { bound.Width = geometryDrawing.Pen.Thickness; } // convert the line geometry into a rectangle geometry // we lost line cap info here geometry = new RectangleGeometry(bound); } if (accumulatedGeometry == null) { accumulatedGeometry = new GeometryGroup(); accumulatedGeometry.FillRule = FillRule.Nonzero; } accumulatedGeometry.Children.Add(geometry); } } } } } #endregion #region Private fields // properties and format runs private string _text; private SpanVector _formatRuns = new SpanVector(null); private SpanPosition _latestPosition = new SpanPosition(); private GenericTextParagraphProperties _defaultParaProps; private double _maxTextWidth; private double [] _maxTextWidths; private double _maxTextHeight = double.MaxValue; private int _maxLineCount = int.MaxValue; private TextTrimming _trimming = TextTrimming.WordEllipsis; // text source callbacks private TextSourceImplementation _textSourceImpl; // cached metrics private CachedMetrics _metrics; private double _minWidth; #endregion #region Constants // we always use default text formatter so we can use Constants.DefaultIdealToReal const double RealInfiniteWidth = Constants.InfiniteWidth * Constants.DefaultIdealToReal; const double MaxFontEmSize = RealInfiniteWidth / Constants.GreatestMutiplierOfEm; #endregion } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TextDecorationUnitValidation.cs
- Stream.cs
- ObjectDataSourceView.cs
- DataControlFieldTypeEditor.cs
- OpCopier.cs
- ProxyWebPartConnectionCollection.cs
- EnvelopedSignatureTransform.cs
- AutomationPatternInfo.cs
- ToolStripSeparatorRenderEventArgs.cs
- PaintValueEventArgs.cs
- SafeCryptHandles.cs
- CrossContextChannel.cs
- ListViewCommandEventArgs.cs
- TypeUtils.cs
- DbConnectionClosed.cs
- Transactions.cs
- GetPageCompletedEventArgs.cs
- SByteConverter.cs
- MessageQueueConverter.cs
- ConfigurationManagerHelper.cs
- DbProviderFactories.cs
- PenLineJoinValidation.cs
- FlowLayoutPanelDesigner.cs
- ErrorEventArgs.cs
- CustomExpressionEventArgs.cs
- EmbossBitmapEffect.cs
- DebugHandleTracker.cs
- DataTableMappingCollection.cs
- CleanUpVirtualizedItemEventArgs.cs
- ProcessModelInfo.cs
- LoginUtil.cs
- AdornerHitTestResult.cs
- MemberExpression.cs
- ProvidersHelper.cs
- tibetanshape.cs
- MessageDirection.cs
- SqlEnums.cs
- HtmlEncodedRawTextWriter.cs
- DropShadowBitmapEffect.cs
- SchemaNamespaceManager.cs
- XmlDictionaryReaderQuotas.cs
- ClientSponsor.cs
- ToolStripPanelRow.cs
- DataSourceView.cs
- ReadOnlyObservableCollection.cs
- Material.cs
- EncoderParameters.cs
- MouseEvent.cs
- _AutoWebProxyScriptWrapper.cs
- ListView.cs
- X509Certificate2.cs
- SingleKeyFrameCollection.cs
- StylusShape.cs
- HttpHandler.cs
- BinHexDecoder.cs
- ParameterRetriever.cs
- MulticastNotSupportedException.cs
- DesignerActionListCollection.cs
- StructuredType.cs
- AdornerHitTestResult.cs
- TempEnvironment.cs
- DefinitionBase.cs
- Iis7Helper.cs
- TreeNodeCollectionEditor.cs
- InputScopeNameConverter.cs
- FrameworkTextComposition.cs
- QilFunction.cs
- ObjectViewListener.cs
- RegexRunnerFactory.cs
- GenericTypeParameterBuilder.cs
- SwitchLevelAttribute.cs
- AliasGenerator.cs
- XpsFontSubsetter.cs
- PriorityRange.cs
- WeakEventTable.cs
- FlowStep.cs
- Pair.cs
- ConsumerConnectionPointCollection.cs
- DesignerActionPropertyItem.cs
- DbSourceCommand.cs
- CompensationParticipant.cs
- GeneralTransformGroup.cs
- TableParagraph.cs
- MyContact.cs
- LogStore.cs
- EntryWrittenEventArgs.cs
- _DomainName.cs
- ReferenceCountedObject.cs
- CloudCollection.cs
- ProxyManager.cs
- _BasicClient.cs
- DropShadowBitmapEffect.cs
- InternalTypeHelper.cs
- ServicePointManagerElement.cs
- Int64Animation.cs
- HandledMouseEvent.cs
- TemplatePartAttribute.cs
- CheckedPointers.cs
- ToolStripDropTargetManager.cs
- ProviderIncompatibleException.cs