Code:
/ 4.0 / 4.0 / DEVDIV_TFS / Dev10 / Releases / RTMRel / wpf / src / Core / CSharp / MS / Internal / TextFormatting / SimpleTextLine.cs / 1477649 / SimpleTextLine.cs
//------------------------------------------------------------------------ // // Microsoft Windows Client Platform // Copyright (C) Microsoft Corporation, 2001 // // File: SimpleTextLine.cs // // Contents: Light-weight implementation of TextLine // // Created: 11-7-2001 Worachai Chaoweeraprasit (wchao) // //----------------------------------------------------------------------- using System; using System.Security; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.TextFormatting; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using MS.Internal.Shaping; using MS.Internal.FontCache; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace MS.Internal.TextFormatting { ////// Light-weight implementation of TextLine /// /// Support following functionalities /// o Non-complex script text metrics through font CMAP/HMTX /// o Multiple character formats, each limited to single font face /// o Simple text underlining for individual run (no averaging) /// /// In the event that either the incoming text or formatting is more /// complicated than what this implementation can handle. The .ctor /// simply stops and leaves this.Valid flag unset. The caller examines /// this flag and lets the full path takes over if needed. /// internal class SimpleTextLine : TextLine { private SimpleRun[] _runs; // contained runs private int _cpFirst; // line first cp private int _cpLength; // all characters private int _cpLengthEOT; // newline characters private double _widthAtTrailing; // width excluding trailing space private double _width; // whole width private double _paragraphWidth; // paragraph width private double _height; // line height private double _offset; // offset to the first character private double _baselineOffset; // offset to baseline private int _trailing; // trailing spaces private Rect _boundingBox; // line bounding rectangle private StatusFlags _statusFlags; // status flags private FormatSettings _settings; // formatting settings (only kept in an overflowed line for collapsing purpose only) [Flags] private enum StatusFlags { None = 0, BoundingBoxComputed = 0x00000001, // bounding box has been computed HasOverflowed = 0x00000002, // line width overflows paragraph width } ////// Creating a lightweight text line /// /// text formatting settings /// First cp of the line /// paragraph width ///TextLine instance ////// This method breaks line using Ideal width such that it will be /// consistent with FullTextLine /// static public TextLine Create( FormatSettings settings, int cpFirst, int paragraphWidth ) { ParaProp pap = settings.Pap; if( pap.RightToLeft || pap.Justify || ( pap.FirstLineInParagraph && pap.TextMarkerProperties != null) || settings.TextIndent != 0 || pap.ParagraphIndent != 0 || pap.LineHeight > 0 || pap.AlwaysCollapsible || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) ) { // unsupported paragraph properties return null; } int cp = cpFirst; // paragraphWidth == 0 means the format width is unlimited. int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue; SimpleRun prev = null; SimpleRun run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { // fail to create run e.g. complex content encountered return null; } else if(!run.EOT && run.IdealWidth <= widthLeft) { // create next run cp += run.Length; widthLeft -= run.IdealWidth; prev = run; run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { return null; } } int trailing = 0; ArrayList runs = new ArrayList(2); if(prev != null) { AddRun(runs, prev, null); } do { if(!run.EOT && run.IdealWidth > widthLeft) { // linebreaking required, even simple text requires classification-based linebreaking, // we'll now let LS handle this line. return null; } AddRun(runs, run, null); prev = run; cp += run.Length; widthLeft -= run.IdealWidth; if(run.EOT) { // we're done break; } run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if( run == null || ( run.Underline != null && prev != null && prev.Underline != null && !prev.IsUnderlineCompatible(run)) ) { // fail to create run or // runs cannot support averaging underline return null; } } while(true); int trailingSpaceWidth = 0; CollectTrailingSpaces( runs, settings.Formatter, ref trailing, ref trailingSpaceWidth ); // create a simple line return new SimpleTextLine( settings, cpFirst, paragraphWidth, runs, ref trailing, ref trailingSpaceWidth ) as TextLine; } ////// Constructing a lightweight text line /// /// text formatting settings /// line first cp /// paragraph width /// collection of simple runs /// line trailing spaces /// line trailing spaces width ////// SimpleTextLine is constructed with Ideal width such that the line breaking /// behavior is consistent with the FullTextLine /// public SimpleTextLine( FormatSettings settings, int cpFirst, int paragraphWidth, ArrayList runs, ref int trailing, ref int trailingSpaceWidth ) { // Compute line metrics int count = 0; _settings = settings; double realAscent = 0; double realDescent = 0; double realHeight = 0; ParaProp pap = settings.Pap; TextFormatterImp formatter = settings.Formatter; int idealWidth = 0; while(count < runs.Count) { SimpleRun run = (SimpleRun)runs[count]; if(run.Length > 0) { if(run.EOT) { // EOT run has no effect on height, it is part of trailing spaces trailing += run.Length; _cpLengthEOT += run.Length; } else { realHeight = Math.Max(realHeight, run.Height); realAscent = Math.Max(realAscent, run.Baseline); realDescent = Math.Max(realDescent, run.Height - run.Baseline); } _cpLength += run.Length; idealWidth += run.IdealWidth; } count++; } // Roundtrip run baseline and height to take its precision back to the specified formatting resolution. // // We have to do this to guarantee sameness of line alignment metrics produced by fast and full path. // This is critical for TextBlock/TextFlow. They rely on the fact that line created during Measure must // yield the same metrics as one created during Render, while there is no guarantee that the paragraph // properties of that same line remains the same in both timings e.g. Measure may not specify // justification (which results in us formatting the line in fast path), while Render might // (which results in us formatting that same line in full path). _baselineOffset = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent)); if (realAscent + realDescent == realHeight) { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realHeight)); } else { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent) + TextFormatterImp.RealToIdeal(realDescent)); } if(_height <= 0) { // line is empty (containing only EOP) // we need to work out the line height // It needs to be exactly the same as in full path. _height = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.LineSpacing(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); _baselineOffset = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.Baseline(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); } // Initialize the array of runs and set the TrimTrailingUnderline flag // for runs that contain trailing spaces at the end of the line. _runs = new SimpleRun[count]; for(int i = count - 1, t = trailing; i >= 0; --i) { SimpleRun run = (SimpleRun)runs[i]; if (t > 0) { run.TrimTrailingUnderline = true; t -= run.Length; } _runs[i] = run; } _cpFirst = cpFirst; _trailing = trailing; int idealWidthAtTrailing = idealWidth - trailingSpaceWidth; if(pap.Align != TextAlignment.Left) { switch(pap.Align) { case TextAlignment.Right: _offset = formatter.IdealToReal(paragraphWidth - idealWidthAtTrailing); break; case TextAlignment.Center: // exactly consistent with FullTextLine _offset = formatter.IdealToReal((int) Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5)); break; } } // converting all the ideal values to real values _width = formatter.IdealToReal(idealWidth); _widthAtTrailing = formatter.IdealToReal(idealWidthAtTrailing); _paragraphWidth = formatter.IdealToReal(paragraphWidth); // paragraphWidth == 0 means format width is unlimited and hence not overflowable. // we keep paragraphWidth for alignment calculation if (paragraphWidth > 0 && _widthAtTrailing > _paragraphWidth) { _statusFlags |= StatusFlags.HasOverflowed; } } ////// Nothing to release /// public override void Dispose() {} ////// Scanning the run list backward to collect run's trailing spaces. /// /// current runs in the line /// formatter /// trailing spaces /// trailing spaces width in ideal values static private void CollectTrailingSpaces( ArrayList runs, TextFormatterImp formatter, ref int trailing, ref int trailingSpaceWidth ) { int left = runs != null ? runs.Count : 0; SimpleRun run = null; bool continueCollecting = true; while(left > 0 && continueCollecting) { run = (SimpleRun)runs[--left]; continueCollecting = run.CollectTrailingSpaces( formatter, ref trailing, ref trailingSpaceWidth ); } } ////// Collecting glyph runs /// static private void AddRun( ArrayList runs, SimpleRun run, SimpleRun prev ) { Invariant.Assert( prev == null || (runs.Count > 0 && prev == runs[runs.Count - 1]), "Trailing space run is not after the last existing run!" ); if(run.Length > 0) { // dont add 0-length run runs.Add(run); } } ////// Get distance from line start to the specified cp /// private double DistanceFromCp(int currentIndex) { Invariant.Assert(currentIndex >= _cpFirst); double advance = 0; int dcp = currentIndex - _cpFirst; foreach(SimpleRun run in _runs) { advance += run.DistanceFromDcp(dcp); if(dcp <= run.Length) { break; } dcp -= run.Length; } return advance + _offset; } ////// Draw line /// /// drawing context /// drawing origin /// indicate the inversion of the drawing surface public override void Draw( DrawingContext drawingContext, Point origin, InvertAxes inversion ) { if (drawingContext == null) { throw new ArgumentNullException("drawingContext"); } MatrixTransform antiInversion = TextFormatterImp.CreateAntiInversionTransform( inversion, _paragraphWidth, _height ); if (antiInversion == null) { DrawTextLine(drawingContext, origin); } else { // Apply anti-inversion transform to correct the visual drawingContext.PushTransform(antiInversion); try { DrawTextLine(drawingContext, origin); } finally { drawingContext.Pop(); } } } ////// Client to collapse the line to fit for display /// /// a list of collapsing properties public override TextLine Collapse( params TextCollapsingProperties[] collapsingPropertiesList ) { if (!HasOverflowed) return this; Invariant.Assert(_settings != null); // instantiate a collapsible full text line, collapse it and return the collapsed line TextMetrics.FullTextLine textLine = new TextMetrics.FullTextLine( _settings, _cpFirst, 0, // lineLength TextFormatterImp.RealToIdeal(_paragraphWidth), LineFlags.None ); Invariant.Assert(textLine.HasOverflowed); TextLine collapsedTextLine = textLine.Collapse(collapsingPropertiesList); if (collapsedTextLine != textLine) { // if collapsed line is genuinely new, // Dispose its maker as we no longer need it around, dispose it explicitly // to reduce unnecessary finalization of this intermediate line. textLine.Dispose(); } return collapsedTextLine; } ////// Make sure the bounding box is calculated /// private void CheckBoundingBox() { if ((_statusFlags & StatusFlags.BoundingBoxComputed) == 0) { DrawTextLine(null, new Point(0, 0)); } Debug.Assert((_statusFlags & StatusFlags.BoundingBoxComputed) != 0); } ////// Draw a simple text line /// ///a drawing bounding box private void DrawTextLine( DrawingContext drawingContext, Point origin ) { if (_runs.Length <= 0) { _boundingBox = Rect.Empty; _statusFlags |= StatusFlags.BoundingBoxComputed; return; } double x = origin.X + _offset; double y = origin.Y + Baseline; if (drawingContext != null) { drawingContext.PushGuidelineY1(y); } Rect boundingBox = Rect.Empty; try { foreach (SimpleRun run in _runs) { boundingBox.Union( run.Draw( drawingContext, x, y, false ) ); x += _settings.Formatter.IdealToReal(run.IdealWidth); } } finally { if (drawingContext != null) { drawingContext.Pop(); } } if(boundingBox.IsEmpty) { boundingBox = new Rect(Start, 0, 0, 0); } else { boundingBox.X -= origin.X; boundingBox.Y -= origin.Y; } _boundingBox = boundingBox; _statusFlags |= StatusFlags.BoundingBoxComputed; } ////// Client to get the character hit corresponding to the specified /// distance from the beginning of the line. /// /// distance in text flow direction from the beginning of the line ///character hit public override CharacterHit GetCharacterHitFromDistance( double distance ) { double advance = distance - _offset; int first = _cpFirst; if (advance < 0) { // hit happens before the line, return the first position return new CharacterHit(_cpFirst, 0); } // process hit that happens within the line SimpleRun run = null; CharacterHit runIndex = new CharacterHit(); for(int i = 0; i < _runs.Length; i++) { run = (SimpleRun)_runs[i]; if (!run.EOT) { // move forward to start of next non-EOT run first += runIndex.TrailingLength; runIndex = run.DcpFromDistance(advance); first += runIndex.FirstCharacterIndex; } if(advance <= _settings.Formatter.IdealToReal(run.IdealWidth)) { break; } advance -= _settings.Formatter.IdealToReal(run.IdealWidth); } return new CharacterHit(first, runIndex.TrailingLength); } ////// Client to get the distance from the beginning of the line from the specified /// character hit. /// /// character hit of the character to query the distance. ///distance in text flow direction from the beginning of the line. public override double GetDistanceFromCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0)); } ////// Client to get the next character hit for caret navigation /// /// the current character hit ///the next character hit public override CharacterHit GetNextCaretCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); int nextVisisbleCp; bool navigableCpFound; if (characterHit.TrailingLength == 0) { navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex, out nextVisisbleCp); if (navigableCpFound) { // Move from leading to trailing edge return new CharacterHit(nextVisisbleCp, 1); } } navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex + 1, out nextVisisbleCp); if (navigableCpFound) { // Move from trailing edge of current character to trailing edge of next return new CharacterHit(nextVisisbleCp, 1); } // Can't move, we're after the last character return characterHit; } ////// Client to get the previous character hit for caret navigation /// /// the current character hit ///the previous character hit public override CharacterHit GetPreviousCaretCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); int previousVisisbleCp; bool navigableCpFound; int cpHit = characterHit.FirstCharacterIndex; bool trailingHit = (characterHit.TrailingLength != 0); // Input can be right after the end of the current line. Snap it to be at the end of the line. if (cpHit >= _cpFirst + _cpLength) { cpHit = _cpFirst + _cpLength - 1; trailingHit = true; } if (trailingHit) { navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisisbleCp); if (navigableCpFound) { // Move from trailing to leading edge return new CharacterHit(previousVisisbleCp, 0); } } navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisisbleCp); if (navigableCpFound) { // Move from leading edge of current character to leading edge of previous return new CharacterHit(previousVisisbleCp, 0); } // Can't move, we're before the first character return characterHit; } ////// Client to get the previous character hit after backspacing /// /// the current character hit ///the character hit after backspacing public override CharacterHit GetBackspaceCaretCharacterHit( CharacterHit characterHit ) { // same operation as move-to-previous return GetPreviousCaretCharacterHit(characterHit); } ////// Client to get an array of bounding rectangles of a range of characters within a text line. /// /// index of first character of specified range /// number of characters of the specified range ///an array of bounding rectangles. public override IListGetTextBounds( int firstTextSourceCharacterIndex, int textLength ) { if (textLength == 0) { throw new ArgumentOutOfRangeException("textLength", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } if (textLength < 0) { firstTextSourceCharacterIndex += textLength; textLength = -textLength; } if (firstTextSourceCharacterIndex < _cpFirst) { textLength += (firstTextSourceCharacterIndex - _cpFirst); firstTextSourceCharacterIndex = _cpFirst; } if (firstTextSourceCharacterIndex + textLength > _cpFirst + _cpLength) { textLength = _cpFirst + _cpLength - firstTextSourceCharacterIndex; } double x1 = GetDistanceFromCharacterHit( new CharacterHit(firstTextSourceCharacterIndex, 0) ); double x2 = GetDistanceFromCharacterHit( new CharacterHit(firstTextSourceCharacterIndex + textLength, 0) ); IList boundsList = null; int dcp = firstTextSourceCharacterIndex - _cpFirst; int ich = 0; boundsList = new List (2); foreach(SimpleRun run in _runs) { if( !run.EOT && !run.Ghost && ich + run.Length > dcp) { if(ich >= dcp + textLength) break; int first = Math.Max(ich, dcp) + _cpFirst; int afterLast = Math.Min(ich + run.Length, dcp + textLength) + _cpFirst; boundsList.Add( new TextRunBounds( new Rect( new Point( DistanceFromCp(first), _baselineOffset - run.Baseline ), new Point( DistanceFromCp(afterLast), _baselineOffset - run.Baseline + run.Height ) ), first, afterLast, run.TextRun ) ); } ich += run.Length; } return new TextBounds[] { new TextBounds( new Rect( x1, 0, x2 - x1, _height ), FlowDirection.LeftToRight, (boundsList == null || boundsList.Count == 0 ? null : boundsList) ) }; } /// /// Client to get a collection of TextRun objects within a line /// public override IList> GetTextRunSpans() { TextSpan [] textRunSpans = new TextSpan [_runs.Length]; for (int i = 0; i < _runs.Length; i++) { textRunSpans[i] = new TextSpan (_runs[i].Length, _runs[i].TextRun); } return textRunSpans; } /// /// Client to get a IEnumerable<IndexedGlyphRun> to enumerate GlyphRuns /// within in a line /// ////// Critical - calls critical code, accepts pointer parameters, unsafe code /// [SecurityCritical] public override IEnumerableGetIndexedGlyphRuns() { List indexedGlyphRuns = new List (_runs.Length); // create each GlyphRun at Point(0, 0) Point start = new Point(0, 0); int currentCp = _cpFirst; foreach(SimpleRun run in _runs) { if (run.Length > 0 && !run.Ghost) { IList displayGlyphAdvances; if (_settings.TextFormattingMode == TextFormattingMode.Ideal) { displayGlyphAdvances = new ThousandthOfEmRealDoubles(run.EmSize, run.NominalAdvances.Length); for (int i = 0; i < displayGlyphAdvances.Count; i++) { // convert ideal glyph advance width to real width for displaying displayGlyphAdvances[i] = _settings.Formatter.IdealToReal(run.NominalAdvances[i]); } } else { displayGlyphAdvances = new List (run.NominalAdvances.Length); for (int i = 0; i < run.NominalAdvances.Length; i++) { // convert ideal glyph advance width to real width for displaying displayGlyphAdvances.Add(_settings.Formatter.IdealToReal(run.NominalAdvances[i])); } } GlyphTypeface glyphTypeface = run.Typeface.TryGetGlyphTypeface(); Invariant.Assert(glyphTypeface != null); // this simple run has GlyphRun GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( start, new CharacterBufferRange(run.CharBufferReference, run.Length), displayGlyphAdvances, run.EmSize, run.TextRun.Properties.FontHintingEmSize, run.Typeface.NullFont, CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo), null, // device font name _settings.TextFormattingMode ); if (glyphRun != null) { indexedGlyphRuns.Add( new IndexedGlyphRun( currentCp, run.Length, glyphRun ) ); } } currentCp += run.Length; } return indexedGlyphRuns; } /// /// Client to acquire a settings at the point where line is broken by line breaking process; /// can be null when the line ends by the ending of the paragraph. Client may pass this /// value back to TextFormatter as an input argument to TextFormatter.FormatLine when /// formatting the next line within the same paragraph. /// public override TextLineBreak GetTextLineBreak() { // No line break implemented in simple text return null; } ////// Client to get a collection of collapsed cha----r ranges after a line has been collapsed /// public override IListGetTextCollapsedRanges() { // A collapsed line is never implemented as simple text line Invariant.Assert(!HasCollapsed); return null; } /// /// Client to get the number of text source positions of this line /// public override int Length { get { return _cpLength; } } ////// Client to get the number of whitespace characters at the end of the line. /// public override int TrailingWhitespaceLength { get { return _trailing; } } ////// Client to get the number of characters following the last character /// of the line that may trigger reformatting of the current line. /// public override int DependentLength { get { return 0; } } ////// Client to get the number of newline characters at line end /// public override int NewlineLength { get { return _cpLengthEOT; } } ////// Client to get distance from paragraph start to line start /// public override double Start { get { return _offset; } } ////// Client to get the total width of this line /// public override double Width { get { return _widthAtTrailing; } } ////// Client to get the total width of this line including width of whitespace characters at the end of the line. /// public override double WidthIncludingTrailingWhitespace { get { return _width; } } ////// Client to get the height of the line /// public override double Height { get { return _height; } } ////// Client to get the height of the text (or other content) in the line; this property may differ from the Height /// property if the client specified the line height /// public override double TextHeight { // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 get { return _height; } } ////// Client to get the height of the actual black of the line /// public override double Extent { get { CheckBoundingBox(); return _boundingBox.Bottom - _boundingBox.Top; } } ////// Client to get the distance from top to baseline of this text line /// public override double Baseline { get { return _baselineOffset; } } ////// Client to get the distance from the top of the text (or other content) to the baseline of this text line; /// this property may differ from the Baseline property if the client specified the line height /// public override double TextBaseline { // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 get { return _baselineOffset; } } ////// Client to get the distance from the before edge of line height /// to the baseline of marker of the line if any. /// public override double MarkerBaseline { get { return Baseline; } } ////// Client to get the overall height of the list items marker of the line if any. /// public override double MarkerHeight { get { return Height; } } ////// Client to get the distance covering all black preceding the leading edge of the line. /// public override double OverhangLeading { get { CheckBoundingBox(); return _boundingBox.Left - Start; } } ////// Client to get the distance covering all black following the trailing edge of the line. /// public override double OverhangTrailing { get { CheckBoundingBox(); return Start + Width - _boundingBox.Right; } } ////// Client to get the distance from the after edge of line height to the after edge of the extent of the line. /// public override double OverhangAfter { get { CheckBoundingBox(); return _boundingBox.Bottom - Height; } } ////// Client to get a boolean value indicates whether content of the line overflows /// the specified paragraph width. /// public override bool HasOverflowed { get { return (_statusFlags & StatusFlags.HasOverflowed) != 0; } } ////// Client to get a boolean value indicates whether a line has been collapsed /// public override bool HasCollapsed { // A collapsed line is never implemented as simple text line get { return false; } } ////// Search forward from the given cp index (inclusive) to find the next navigable cp index. /// Return true if one such cp is found, false otherwise. /// private bool FindNextVisibleCp(int cp, out int cpVisible) { cpVisible = cp; if (cp >= _cpFirst + _cpLength) { return false; // Cannot go forward anymore } int cpRunStart, runIndex; GetRunIndexAtCp(cp, out runIndex, out cpRunStart); while (runIndex < _runs.Length) { // When navigating forward, only the trailing edge of visible content is // navigable. if (_runs[runIndex].IsVisible && !_runs[runIndex].EOT) { cpVisible = Math.Max(cpRunStart, cp); return true; } cpRunStart += _runs[runIndex++].Length; } return false; } ////// Search backward from the given cp index (inclusive) to find the previous navigable cp index. /// Return true if one such cp is found, false otherwise. /// private bool FindPreviousVisibleCp(int cp, out int cpVisible) { cpVisible = cp; if (cp < _cpFirst) { return false; // Cannot go backward anymore. } int cpRunEnd, runIndex; // Position the cpRunEnd at the end of the span that contains the given cp GetRunIndexAtCp(cp, out runIndex, out cpRunEnd); cpRunEnd += _runs[runIndex].Length - 1; while (runIndex >= 0) { // Visible content has caret stops at its leading edge. if (_runs[runIndex].IsVisible && !_runs[runIndex].EOT) { cpVisible = Math.Min(cpRunEnd, cp); return true; } // Newline sequence has caret stops at its leading edge. if (_runs[runIndex].EOT) { // Get the cp index at the beginning of the newline sequence. cpVisible = cpRunEnd - _runs[runIndex].Length + 1; return true; } cpRunEnd -= _runs[runIndex--].Length; } return false; } private void GetRunIndexAtCp( int cp, out int runIndex, out int cpRunStart ) { Invariant.Assert(cp >= _cpFirst && cp < _cpFirst + _cpLength); cpRunStart= _cpFirst; runIndex = 0; // Find the span that contains the given cp while (runIndex < _runs.Length && cpRunStart + _runs[runIndex].Length <= cp) { cpRunStart += _runs[runIndex++].Length; } } } ////// Simple text run /// internal sealed class SimpleRun { public CharacterBufferReference CharBufferReference; // character buffer reference public int Length; // CP length public int[] NominalAdvances; // nominal glyph advance widths in ideal units public int IdealWidth; // Ideal width of the line. Use ideal width to be consistent with FullTextLine in linebreaking public TextRun TextRun; // text run public TextDecoration Underline; // only support single underline public Flags RunFlags; // run flags private TextFormatterImp _textFormatterImp; [Flags] internal enum Flags : ushort { None = 0, EOT = 0x0001, // end-of-text mark Ghost = 0x0002, // non-existence run - only consume cp TrimTrailingUnderline = 0x0004, // trailing whitespace should not be underlined } internal bool EOT { get { return (RunFlags & Flags.EOT) != 0; } } internal bool Ghost { get { return (RunFlags & Flags.Ghost) != 0; } } internal bool TrimTrailingUnderline { get { return (RunFlags & Flags.TrimTrailingUnderline) != 0; } set { if (value) { RunFlags |= Flags.TrimTrailingUnderline; } else { RunFlags &= ~Flags.TrimTrailingUnderline; } } } internal double Baseline { get { if (Ghost || EOT) return 0; return TextRun.Properties.Typeface.Baseline(TextRun.Properties.FontRenderingEmSize, 1, Util.PixelsPerDip, _textFormatterImp.TextFormattingMode); } } internal double Height { get { if (Ghost || EOT) return 0; return TextRun.Properties.Typeface.LineSpacing(TextRun.Properties.FontRenderingEmSize, 1, Util.PixelsPerDip, _textFormatterImp.TextFormattingMode); } } internal Typeface Typeface { get { return TextRun.Properties.Typeface; } } internal double EmSize { get { return TextRun.Properties.FontRenderingEmSize; } } internal bool IsVisible { get { return this.TextRun is TextCharacters; } } internal SimpleRun(TextFormatterImp textFormatterImp) { _textFormatterImp = textFormatterImp; } ////// Creating a simple text run /// /// text formatting settings /// first cp of the run /// first cp of the line /// maxium run width /// maximum column width ///a SimpleRun object static public SimpleRun Create( FormatSettings settings, int cp, int cpFirst, int widthLeft, int widthMax ) { TextRun textRun; int runLength; CharacterBufferRange charBufferRange = settings.FetchTextRun( cp, cpFirst, out textRun, out runLength ); return Create( settings, charBufferRange, textRun, cp, cpFirst, runLength, widthLeft ); } ////// Creating a simple text run /// /// text formatting settings /// character string associated to textrun /// text run /// first cp of the run /// first cp of the line /// run length /// maximum run width ///a SimpleRun object static public SimpleRun Create( FormatSettings settings, CharacterBufferRange charString, TextRun textRun, int cp, int cpFirst, int runLength, int widthLeft ) { SimpleRun run = null; if (textRun is TextCharacters) { if ( textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline || (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) ) { // fast path does not handle the following conditions // o non-default baseline alignment // o text drawing effect ( return null; } TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; if ( textDecorations != null && textDecorations.Count != 0 && !textDecorations.ValueEquals(TextDecorations.Underline)) { // we only support a single underline return null; } settings.DigitState.SetTextRunProperties(textRun.Properties); if (settings.DigitState.RequiresNumberSubstitution) { // don't support number substitution in fast path return null; } if (charString[0] == TextStore.CharCarriageReturn) { // CR in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; if (charString.Length > 1 && charString[1] == TextStore.CharLineFeed) { runLength = 2; } // This path handles the case where the backing store breaks the text run in between // a Carriage Return and a Line Feed. So we fetch the next run to check whether the next // character is a line feed. else if (charString.Length == 1) { // Prefetch to check for line feed. TextRun newRun; int newRunLength; CharacterBufferRange newBufferRange = settings.FetchTextRun( cp + 1, cpFirst, out newRun, out newRunLength ); if (newBufferRange.Length > 0 && newBufferRange[0] == TextStore.CharLineFeed) { // Merge the 2 runs. int lengthOfRun = 2; char[] characterArray = new char[lengthOfRun]; characterArray[0] = TextStore.CharCarriageReturn; characterArray[1] = TextStore.CharLineFeed; TextRun mergedTextRun = new TextCharacters(characterArray, 0, lengthOfRun, textRun.Properties); return new SimpleRun(lengthOfRun, mergedTextRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } } return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (charString[0] == TextStore.CharLineFeed) { // LF in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } // attempt to create a simple run for text run = CreateSimpleTextRun( charString, textRun, settings.Formatter, widthLeft, settings.Pap.EmergencyWrap ); if (run == null) { // fail to create simple text run, the run content is too complex return null; } // Check for underline condition if (textDecorations != null && textDecorations.Count == 1 ) { run.Underline = textDecorations[0]; } } else if (textRun is TextEndOfLine) { run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (textRun is TextHidden) { // hidden run run = new SimpleRun(runLength, textRun, Flags.Ghost, settings.Formatter); } return run; } ////// Create simple run of text, /// returning null if the specified text run cannot be correctly formatted as simple run /// static internal SimpleRun CreateSimpleTextRun( CharacterBufferRange charBufferRange, TextRun textRun, TextFormatterImp formatter, int widthLeft, bool emergencyWrap ) { Invariant.Assert(textRun is TextCharacters); SimpleRun run = new SimpleRun(formatter); run.CharBufferReference = charBufferRange.CharacterBufferReference; run.TextRun = textRun; if (!run.TextRun.Properties.Typeface.CheckFastPathNominalGlyphs( charBufferRange, run.EmSize, 1.0, formatter.IdealToReal(widthLeft), !emergencyWrap, false, CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo), formatter.TextFormattingMode, false, //No support for isSideways out run.Length )) { // Getting nominal glyphs is not supported by the font, // or it is but it results in low typographic quality text // e.g. OpenType support is not utilized. return null; } run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( new CharacterBufferRange(run.CharBufferReference, run.Length), run.EmSize, TextFormatterImp.ToIdeal, formatter.TextFormattingMode, false, out run.NominalAdvances, out run.IdealWidth ); return run; } ////// Construct simple text run /// /// run length /// text run /// run flags private SimpleRun( int length, TextRun textRun, Flags flags, TextFormatterImp textFormatterImp ) { Length = length; TextRun = textRun; RunFlags = flags; _textFormatterImp = textFormatterImp; } ////// Draw a simple run /// ///drawing bounding box ////// Critical - as this calls critical function ComputeUnshapedGlyphRun. /// Safe - as this just draws text and returns a rect. /// [SecurityCritical, SecurityTreatAsSafe] internal Rect Draw( DrawingContext drawingContext, double x, double y, bool visiCodePath ) { if (Length <= 0 || this.Ghost) { return Rect.Empty; // nothing to draw } Brush foregroundBrush = TextRun.Properties.ForegroundBrush; if(visiCodePath && foregroundBrush is SolidColorBrush) { Color color = ((SolidColorBrush)foregroundBrush).Color; foregroundBrush = new SolidColorBrush(Color.FromArgb( (byte)(color.A>>2), // * 0.25 color.R, color.G, color.B )); } Rect inkBoundingBox; IListdisplayGlyphAdvances; if (_textFormatterImp.TextFormattingMode == TextFormattingMode.Ideal) { displayGlyphAdvances = new ThousandthOfEmRealDoubles(EmSize, NominalAdvances.Length); for (int i = 0; i < displayGlyphAdvances.Count; i++) { // convert ideal glyph advance width to real width for displaying. displayGlyphAdvances[i] = _textFormatterImp.IdealToReal(NominalAdvances[i]); } } else { displayGlyphAdvances = new List (NominalAdvances.Length); for (int i = 0; i < NominalAdvances.Length; i++) { // convert ideal glyph advance width to real width for displaying. displayGlyphAdvances.Add(_textFormatterImp.IdealToReal(NominalAdvances[i])); } } CharacterBufferRange charBufferRange = new CharacterBufferRange(CharBufferReference, Length); GlyphTypeface glyphTypeface = Typeface.TryGetGlyphTypeface(); Invariant.Assert(glyphTypeface != null); GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( new Point(x, y), charBufferRange, displayGlyphAdvances, EmSize, TextRun.Properties.FontHintingEmSize, Typeface.NullFont, CultureMapper.GetSpecificCulture(TextRun.Properties.CultureInfo), null, // device font name _textFormatterImp.TextFormattingMode ); if (glyphRun != null) { inkBoundingBox = glyphRun.ComputeInkBoundingBox(); } else { inkBoundingBox = Rect.Empty; } if (!inkBoundingBox.IsEmpty) { // glyph run's ink bounding box is relative to its origin inkBoundingBox.X += glyphRun.BaselineOrigin.X; inkBoundingBox.Y += glyphRun.BaselineOrigin.Y; } if (drawingContext != null) { if (glyphRun != null) { glyphRun.EmitBackground(drawingContext, TextRun.Properties.BackgroundBrush); drawingContext.DrawGlyphRun(foregroundBrush, glyphRun); } // draw underline here if (Underline != null) { // Determine number of characters to underline. We don't underline trailing spaces // if the TrimTrailingUnderline flag is set. int underlineLength = Length; if (TrimTrailingUnderline) { while (underlineLength > 0 && TextStore.IsSpace(charBufferRange[underlineLength - 1])) { --underlineLength; } } // Determine the width of the underline. double dxUnderline = 0; for (int i = 0; i < underlineLength; ++i) { dxUnderline += _textFormatterImp.IdealToReal(NominalAdvances[i]); } // We know only TextDecoration.Underline will be handled in Simple Path. double offset = -Typeface.UnderlinePosition * EmSize; double penThickness = Typeface.UnderlineThickness * EmSize; Point lineOrigin = new Point(x, y + offset); Rect underlineRect = new Rect( lineOrigin.X, lineOrigin.Y - penThickness * 0.5, dxUnderline, penThickness ); // Apply the pair of guidelines: one for baseline and another // for top edge of undelining line. Both will be snapped to pixel grid. // Guideline pairing algorithm detects the case when these two // guidelines happen to be close to one another and provides // synchronous snapping, so that the gap between baseline and // undelining line does not depend on the position of text line. drawingContext.PushGuidelineY2(y, lineOrigin.Y - penThickness * 0.5 - y); try { drawingContext.DrawRectangle( foregroundBrush, null, // pen underlineRect ); } finally { drawingContext.Pop(); } // underline pen thickness is always positive in fast path inkBoundingBox.Union( underlineRect ); } } return inkBoundingBox; } /// /// Scan backward to collect trailing spaces of the run /// /// formatter /// trailing spaces /// trailing spaces width ///continue collecting the previous run? internal bool CollectTrailingSpaces( TextFormatterImp formatter, ref int trailing, ref int trailingSpaceWidth ) { // As we are collecting trailing space cp, we also collect the trailing space width. // In Full text line, TrailingSpaceWidth = ToReal(Sumof(ToIdeal(glyphsWidths)); // we do the same thing here so that trailing space width is exactly the same // as Full Text Line. if(Ghost) { if(!EOT) { trailing += Length; trailingSpaceWidth += IdealWidth; } return true; } int offsetToFirstChar = CharBufferReference.OffsetToFirstChar; CharacterBuffer charBuffer = CharBufferReference.CharacterBuffer; int dcp = Length; if (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) { // scan backward to find the first blank following a non-blank while (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) { // summing the ideal value of each glyph trailingSpaceWidth += NominalAdvances[dcp - 1]; dcp--; trailing++; } return dcp == 0; } return false; } internal bool IsUnderlineCompatible(SimpleRun nextRun) { return Typeface.Equals(nextRun.Typeface) && EmSize == nextRun.EmSize && Baseline == nextRun.Baseline; } internal double DistanceFromDcp(int dcp) { if (Ghost) { return dcp <= 0 ? 0 : _textFormatterImp.IdealToReal(IdealWidth); } if (dcp > Length) { dcp = Length; } double distance = 0; for(int i = 0; i < dcp; i++) { distance += _textFormatterImp.IdealToReal(NominalAdvances[i]); } return distance; } internal CharacterHit DcpFromDistance(double distance) { if (Ghost) { return (EOT || distance <= 0) ? new CharacterHit() : new CharacterHit(Length, 0); } if (Length <= 0) { return new CharacterHit(); } int dcp = 0; double currentRealAdvance = 0; while (dcp < Length && distance > (currentRealAdvance = _textFormatterImp.IdealToReal(NominalAdvances[dcp]))) { distance -= currentRealAdvance; dcp++; } if (dcp < Length) { // hit occurs in this run return new CharacterHit(dcp, (distance > currentRealAdvance / 2 ? 1 : 0)); } // hit doesn't occur in this run return new CharacterHit(Length - 1, 1); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------ // // Microsoft Windows Client Platform // Copyright (C) Microsoft Corporation, 2001 // // File: SimpleTextLine.cs // // Contents: Light-weight implementation of TextLine // // Created: 11-7-2001 Worachai Chaoweeraprasit (wchao) // //----------------------------------------------------------------------- using System; using System.Security; using System.Windows; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.TextFormatting; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using MS.Internal.Shaping; using MS.Internal.FontCache; using SR=MS.Internal.PresentationCore.SR; using SRID=MS.Internal.PresentationCore.SRID; namespace MS.Internal.TextFormatting { ////// Light-weight implementation of TextLine /// /// Support following functionalities /// o Non-complex script text metrics through font CMAP/HMTX /// o Multiple character formats, each limited to single font face /// o Simple text underlining for individual run (no averaging) /// /// In the event that either the incoming text or formatting is more /// complicated than what this implementation can handle. The .ctor /// simply stops and leaves this.Valid flag unset. The caller examines /// this flag and lets the full path takes over if needed. /// internal class SimpleTextLine : TextLine { private SimpleRun[] _runs; // contained runs private int _cpFirst; // line first cp private int _cpLength; // all characters private int _cpLengthEOT; // newline characters private double _widthAtTrailing; // width excluding trailing space private double _width; // whole width private double _paragraphWidth; // paragraph width private double _height; // line height private double _offset; // offset to the first character private double _baselineOffset; // offset to baseline private int _trailing; // trailing spaces private Rect _boundingBox; // line bounding rectangle private StatusFlags _statusFlags; // status flags private FormatSettings _settings; // formatting settings (only kept in an overflowed line for collapsing purpose only) [Flags] private enum StatusFlags { None = 0, BoundingBoxComputed = 0x00000001, // bounding box has been computed HasOverflowed = 0x00000002, // line width overflows paragraph width } ////// Creating a lightweight text line /// /// text formatting settings /// First cp of the line /// paragraph width ///TextLine instance ////// This method breaks line using Ideal width such that it will be /// consistent with FullTextLine /// static public TextLine Create( FormatSettings settings, int cpFirst, int paragraphWidth ) { ParaProp pap = settings.Pap; if( pap.RightToLeft || pap.Justify || ( pap.FirstLineInParagraph && pap.TextMarkerProperties != null) || settings.TextIndent != 0 || pap.ParagraphIndent != 0 || pap.LineHeight > 0 || pap.AlwaysCollapsible || (pap.TextDecorations != null && pap.TextDecorations.Count != 0) ) { // unsupported paragraph properties return null; } int cp = cpFirst; // paragraphWidth == 0 means the format width is unlimited. int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue; SimpleRun prev = null; SimpleRun run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { // fail to create run e.g. complex content encountered return null; } else if(!run.EOT && run.IdealWidth <= widthLeft) { // create next run cp += run.Length; widthLeft -= run.IdealWidth; prev = run; run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if(run == null) { return null; } } int trailing = 0; ArrayList runs = new ArrayList(2); if(prev != null) { AddRun(runs, prev, null); } do { if(!run.EOT && run.IdealWidth > widthLeft) { // linebreaking required, even simple text requires classification-based linebreaking, // we'll now let LS handle this line. return null; } AddRun(runs, run, null); prev = run; cp += run.Length; widthLeft -= run.IdealWidth; if(run.EOT) { // we're done break; } run = SimpleRun.Create( settings, cp, cpFirst, widthLeft, paragraphWidth ); if( run == null || ( run.Underline != null && prev != null && prev.Underline != null && !prev.IsUnderlineCompatible(run)) ) { // fail to create run or // runs cannot support averaging underline return null; } } while(true); int trailingSpaceWidth = 0; CollectTrailingSpaces( runs, settings.Formatter, ref trailing, ref trailingSpaceWidth ); // create a simple line return new SimpleTextLine( settings, cpFirst, paragraphWidth, runs, ref trailing, ref trailingSpaceWidth ) as TextLine; } ////// Constructing a lightweight text line /// /// text formatting settings /// line first cp /// paragraph width /// collection of simple runs /// line trailing spaces /// line trailing spaces width ////// SimpleTextLine is constructed with Ideal width such that the line breaking /// behavior is consistent with the FullTextLine /// public SimpleTextLine( FormatSettings settings, int cpFirst, int paragraphWidth, ArrayList runs, ref int trailing, ref int trailingSpaceWidth ) { // Compute line metrics int count = 0; _settings = settings; double realAscent = 0; double realDescent = 0; double realHeight = 0; ParaProp pap = settings.Pap; TextFormatterImp formatter = settings.Formatter; int idealWidth = 0; while(count < runs.Count) { SimpleRun run = (SimpleRun)runs[count]; if(run.Length > 0) { if(run.EOT) { // EOT run has no effect on height, it is part of trailing spaces trailing += run.Length; _cpLengthEOT += run.Length; } else { realHeight = Math.Max(realHeight, run.Height); realAscent = Math.Max(realAscent, run.Baseline); realDescent = Math.Max(realDescent, run.Height - run.Baseline); } _cpLength += run.Length; idealWidth += run.IdealWidth; } count++; } // Roundtrip run baseline and height to take its precision back to the specified formatting resolution. // // We have to do this to guarantee sameness of line alignment metrics produced by fast and full path. // This is critical for TextBlock/TextFlow. They rely on the fact that line created during Measure must // yield the same metrics as one created during Render, while there is no guarantee that the paragraph // properties of that same line remains the same in both timings e.g. Measure may not specify // justification (which results in us formatting the line in fast path), while Render might // (which results in us formatting that same line in full path). _baselineOffset = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent)); if (realAscent + realDescent == realHeight) { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realHeight)); } else { _height = formatter.IdealToReal(TextFormatterImp.RealToIdeal(realAscent) + TextFormatterImp.RealToIdeal(realDescent)); } if(_height <= 0) { // line is empty (containing only EOP) // we need to work out the line height // It needs to be exactly the same as in full path. _height = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.LineSpacing(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); _baselineOffset = formatter.IdealToReal((int)Math.Round(pap.DefaultTypeface.Baseline(pap.EmSize, Constants.DefaultIdealToReal, Util.PixelsPerDip, _settings.TextFormattingMode))); } // Initialize the array of runs and set the TrimTrailingUnderline flag // for runs that contain trailing spaces at the end of the line. _runs = new SimpleRun[count]; for(int i = count - 1, t = trailing; i >= 0; --i) { SimpleRun run = (SimpleRun)runs[i]; if (t > 0) { run.TrimTrailingUnderline = true; t -= run.Length; } _runs[i] = run; } _cpFirst = cpFirst; _trailing = trailing; int idealWidthAtTrailing = idealWidth - trailingSpaceWidth; if(pap.Align != TextAlignment.Left) { switch(pap.Align) { case TextAlignment.Right: _offset = formatter.IdealToReal(paragraphWidth - idealWidthAtTrailing); break; case TextAlignment.Center: // exactly consistent with FullTextLine _offset = formatter.IdealToReal((int) Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5)); break; } } // converting all the ideal values to real values _width = formatter.IdealToReal(idealWidth); _widthAtTrailing = formatter.IdealToReal(idealWidthAtTrailing); _paragraphWidth = formatter.IdealToReal(paragraphWidth); // paragraphWidth == 0 means format width is unlimited and hence not overflowable. // we keep paragraphWidth for alignment calculation if (paragraphWidth > 0 && _widthAtTrailing > _paragraphWidth) { _statusFlags |= StatusFlags.HasOverflowed; } } ////// Nothing to release /// public override void Dispose() {} ////// Scanning the run list backward to collect run's trailing spaces. /// /// current runs in the line /// formatter /// trailing spaces /// trailing spaces width in ideal values static private void CollectTrailingSpaces( ArrayList runs, TextFormatterImp formatter, ref int trailing, ref int trailingSpaceWidth ) { int left = runs != null ? runs.Count : 0; SimpleRun run = null; bool continueCollecting = true; while(left > 0 && continueCollecting) { run = (SimpleRun)runs[--left]; continueCollecting = run.CollectTrailingSpaces( formatter, ref trailing, ref trailingSpaceWidth ); } } ////// Collecting glyph runs /// static private void AddRun( ArrayList runs, SimpleRun run, SimpleRun prev ) { Invariant.Assert( prev == null || (runs.Count > 0 && prev == runs[runs.Count - 1]), "Trailing space run is not after the last existing run!" ); if(run.Length > 0) { // dont add 0-length run runs.Add(run); } } ////// Get distance from line start to the specified cp /// private double DistanceFromCp(int currentIndex) { Invariant.Assert(currentIndex >= _cpFirst); double advance = 0; int dcp = currentIndex - _cpFirst; foreach(SimpleRun run in _runs) { advance += run.DistanceFromDcp(dcp); if(dcp <= run.Length) { break; } dcp -= run.Length; } return advance + _offset; } ////// Draw line /// /// drawing context /// drawing origin /// indicate the inversion of the drawing surface public override void Draw( DrawingContext drawingContext, Point origin, InvertAxes inversion ) { if (drawingContext == null) { throw new ArgumentNullException("drawingContext"); } MatrixTransform antiInversion = TextFormatterImp.CreateAntiInversionTransform( inversion, _paragraphWidth, _height ); if (antiInversion == null) { DrawTextLine(drawingContext, origin); } else { // Apply anti-inversion transform to correct the visual drawingContext.PushTransform(antiInversion); try { DrawTextLine(drawingContext, origin); } finally { drawingContext.Pop(); } } } ////// Client to collapse the line to fit for display /// /// a list of collapsing properties public override TextLine Collapse( params TextCollapsingProperties[] collapsingPropertiesList ) { if (!HasOverflowed) return this; Invariant.Assert(_settings != null); // instantiate a collapsible full text line, collapse it and return the collapsed line TextMetrics.FullTextLine textLine = new TextMetrics.FullTextLine( _settings, _cpFirst, 0, // lineLength TextFormatterImp.RealToIdeal(_paragraphWidth), LineFlags.None ); Invariant.Assert(textLine.HasOverflowed); TextLine collapsedTextLine = textLine.Collapse(collapsingPropertiesList); if (collapsedTextLine != textLine) { // if collapsed line is genuinely new, // Dispose its maker as we no longer need it around, dispose it explicitly // to reduce unnecessary finalization of this intermediate line. textLine.Dispose(); } return collapsedTextLine; } ////// Make sure the bounding box is calculated /// private void CheckBoundingBox() { if ((_statusFlags & StatusFlags.BoundingBoxComputed) == 0) { DrawTextLine(null, new Point(0, 0)); } Debug.Assert((_statusFlags & StatusFlags.BoundingBoxComputed) != 0); } ////// Draw a simple text line /// ///a drawing bounding box private void DrawTextLine( DrawingContext drawingContext, Point origin ) { if (_runs.Length <= 0) { _boundingBox = Rect.Empty; _statusFlags |= StatusFlags.BoundingBoxComputed; return; } double x = origin.X + _offset; double y = origin.Y + Baseline; if (drawingContext != null) { drawingContext.PushGuidelineY1(y); } Rect boundingBox = Rect.Empty; try { foreach (SimpleRun run in _runs) { boundingBox.Union( run.Draw( drawingContext, x, y, false ) ); x += _settings.Formatter.IdealToReal(run.IdealWidth); } } finally { if (drawingContext != null) { drawingContext.Pop(); } } if(boundingBox.IsEmpty) { boundingBox = new Rect(Start, 0, 0, 0); } else { boundingBox.X -= origin.X; boundingBox.Y -= origin.Y; } _boundingBox = boundingBox; _statusFlags |= StatusFlags.BoundingBoxComputed; } ////// Client to get the character hit corresponding to the specified /// distance from the beginning of the line. /// /// distance in text flow direction from the beginning of the line ///character hit public override CharacterHit GetCharacterHitFromDistance( double distance ) { double advance = distance - _offset; int first = _cpFirst; if (advance < 0) { // hit happens before the line, return the first position return new CharacterHit(_cpFirst, 0); } // process hit that happens within the line SimpleRun run = null; CharacterHit runIndex = new CharacterHit(); for(int i = 0; i < _runs.Length; i++) { run = (SimpleRun)_runs[i]; if (!run.EOT) { // move forward to start of next non-EOT run first += runIndex.TrailingLength; runIndex = run.DcpFromDistance(advance); first += runIndex.FirstCharacterIndex; } if(advance <= _settings.Formatter.IdealToReal(run.IdealWidth)) { break; } advance -= _settings.Formatter.IdealToReal(run.IdealWidth); } return new CharacterHit(first, runIndex.TrailingLength); } ////// Client to get the distance from the beginning of the line from the specified /// character hit. /// /// character hit of the character to query the distance. ///distance in text flow direction from the beginning of the line. public override double GetDistanceFromCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); return DistanceFromCp(characterHit.FirstCharacterIndex + (characterHit.TrailingLength != 0 ? 1 : 0)); } ////// Client to get the next character hit for caret navigation /// /// the current character hit ///the next character hit public override CharacterHit GetNextCaretCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); int nextVisisbleCp; bool navigableCpFound; if (characterHit.TrailingLength == 0) { navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex, out nextVisisbleCp); if (navigableCpFound) { // Move from leading to trailing edge return new CharacterHit(nextVisisbleCp, 1); } } navigableCpFound = FindNextVisibleCp(characterHit.FirstCharacterIndex + 1, out nextVisisbleCp); if (navigableCpFound) { // Move from trailing edge of current character to trailing edge of next return new CharacterHit(nextVisisbleCp, 1); } // Can't move, we're after the last character return characterHit; } ////// Client to get the previous character hit for caret navigation /// /// the current character hit ///the previous character hit public override CharacterHit GetPreviousCaretCharacterHit( CharacterHit characterHit ) { TextFormatterImp.VerifyCaretCharacterHit(characterHit, _cpFirst, _cpLength); int previousVisisbleCp; bool navigableCpFound; int cpHit = characterHit.FirstCharacterIndex; bool trailingHit = (characterHit.TrailingLength != 0); // Input can be right after the end of the current line. Snap it to be at the end of the line. if (cpHit >= _cpFirst + _cpLength) { cpHit = _cpFirst + _cpLength - 1; trailingHit = true; } if (trailingHit) { navigableCpFound = FindPreviousVisibleCp(cpHit, out previousVisisbleCp); if (navigableCpFound) { // Move from trailing to leading edge return new CharacterHit(previousVisisbleCp, 0); } } navigableCpFound = FindPreviousVisibleCp(cpHit - 1, out previousVisisbleCp); if (navigableCpFound) { // Move from leading edge of current character to leading edge of previous return new CharacterHit(previousVisisbleCp, 0); } // Can't move, we're before the first character return characterHit; } ////// Client to get the previous character hit after backspacing /// /// the current character hit ///the character hit after backspacing public override CharacterHit GetBackspaceCaretCharacterHit( CharacterHit characterHit ) { // same operation as move-to-previous return GetPreviousCaretCharacterHit(characterHit); } ////// Client to get an array of bounding rectangles of a range of characters within a text line. /// /// index of first character of specified range /// number of characters of the specified range ///an array of bounding rectangles. public override IListGetTextBounds( int firstTextSourceCharacterIndex, int textLength ) { if (textLength == 0) { throw new ArgumentOutOfRangeException("textLength", SR.Get(SRID.ParameterMustBeGreaterThanZero)); } if (textLength < 0) { firstTextSourceCharacterIndex += textLength; textLength = -textLength; } if (firstTextSourceCharacterIndex < _cpFirst) { textLength += (firstTextSourceCharacterIndex - _cpFirst); firstTextSourceCharacterIndex = _cpFirst; } if (firstTextSourceCharacterIndex + textLength > _cpFirst + _cpLength) { textLength = _cpFirst + _cpLength - firstTextSourceCharacterIndex; } double x1 = GetDistanceFromCharacterHit( new CharacterHit(firstTextSourceCharacterIndex, 0) ); double x2 = GetDistanceFromCharacterHit( new CharacterHit(firstTextSourceCharacterIndex + textLength, 0) ); IList boundsList = null; int dcp = firstTextSourceCharacterIndex - _cpFirst; int ich = 0; boundsList = new List (2); foreach(SimpleRun run in _runs) { if( !run.EOT && !run.Ghost && ich + run.Length > dcp) { if(ich >= dcp + textLength) break; int first = Math.Max(ich, dcp) + _cpFirst; int afterLast = Math.Min(ich + run.Length, dcp + textLength) + _cpFirst; boundsList.Add( new TextRunBounds( new Rect( new Point( DistanceFromCp(first), _baselineOffset - run.Baseline ), new Point( DistanceFromCp(afterLast), _baselineOffset - run.Baseline + run.Height ) ), first, afterLast, run.TextRun ) ); } ich += run.Length; } return new TextBounds[] { new TextBounds( new Rect( x1, 0, x2 - x1, _height ), FlowDirection.LeftToRight, (boundsList == null || boundsList.Count == 0 ? null : boundsList) ) }; } /// /// Client to get a collection of TextRun objects within a line /// public override IList> GetTextRunSpans() { TextSpan [] textRunSpans = new TextSpan [_runs.Length]; for (int i = 0; i < _runs.Length; i++) { textRunSpans[i] = new TextSpan (_runs[i].Length, _runs[i].TextRun); } return textRunSpans; } /// /// Client to get a IEnumerable<IndexedGlyphRun> to enumerate GlyphRuns /// within in a line /// ////// Critical - calls critical code, accepts pointer parameters, unsafe code /// [SecurityCritical] public override IEnumerableGetIndexedGlyphRuns() { List indexedGlyphRuns = new List (_runs.Length); // create each GlyphRun at Point(0, 0) Point start = new Point(0, 0); int currentCp = _cpFirst; foreach(SimpleRun run in _runs) { if (run.Length > 0 && !run.Ghost) { IList displayGlyphAdvances; if (_settings.TextFormattingMode == TextFormattingMode.Ideal) { displayGlyphAdvances = new ThousandthOfEmRealDoubles(run.EmSize, run.NominalAdvances.Length); for (int i = 0; i < displayGlyphAdvances.Count; i++) { // convert ideal glyph advance width to real width for displaying displayGlyphAdvances[i] = _settings.Formatter.IdealToReal(run.NominalAdvances[i]); } } else { displayGlyphAdvances = new List (run.NominalAdvances.Length); for (int i = 0; i < run.NominalAdvances.Length; i++) { // convert ideal glyph advance width to real width for displaying displayGlyphAdvances.Add(_settings.Formatter.IdealToReal(run.NominalAdvances[i])); } } GlyphTypeface glyphTypeface = run.Typeface.TryGetGlyphTypeface(); Invariant.Assert(glyphTypeface != null); // this simple run has GlyphRun GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( start, new CharacterBufferRange(run.CharBufferReference, run.Length), displayGlyphAdvances, run.EmSize, run.TextRun.Properties.FontHintingEmSize, run.Typeface.NullFont, CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo), null, // device font name _settings.TextFormattingMode ); if (glyphRun != null) { indexedGlyphRuns.Add( new IndexedGlyphRun( currentCp, run.Length, glyphRun ) ); } } currentCp += run.Length; } return indexedGlyphRuns; } /// /// Client to acquire a settings at the point where line is broken by line breaking process; /// can be null when the line ends by the ending of the paragraph. Client may pass this /// value back to TextFormatter as an input argument to TextFormatter.FormatLine when /// formatting the next line within the same paragraph. /// public override TextLineBreak GetTextLineBreak() { // No line break implemented in simple text return null; } ////// Client to get a collection of collapsed cha----r ranges after a line has been collapsed /// public override IListGetTextCollapsedRanges() { // A collapsed line is never implemented as simple text line Invariant.Assert(!HasCollapsed); return null; } /// /// Client to get the number of text source positions of this line /// public override int Length { get { return _cpLength; } } ////// Client to get the number of whitespace characters at the end of the line. /// public override int TrailingWhitespaceLength { get { return _trailing; } } ////// Client to get the number of characters following the last character /// of the line that may trigger reformatting of the current line. /// public override int DependentLength { get { return 0; } } ////// Client to get the number of newline characters at line end /// public override int NewlineLength { get { return _cpLengthEOT; } } ////// Client to get distance from paragraph start to line start /// public override double Start { get { return _offset; } } ////// Client to get the total width of this line /// public override double Width { get { return _widthAtTrailing; } } ////// Client to get the total width of this line including width of whitespace characters at the end of the line. /// public override double WidthIncludingTrailingWhitespace { get { return _width; } } ////// Client to get the height of the line /// public override double Height { get { return _height; } } ////// Client to get the height of the text (or other content) in the line; this property may differ from the Height /// property if the client specified the line height /// public override double TextHeight { // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 get { return _height; } } ////// Client to get the height of the actual black of the line /// public override double Extent { get { CheckBoundingBox(); return _boundingBox.Bottom - _boundingBox.Top; } } ////// Client to get the distance from top to baseline of this text line /// public override double Baseline { get { return _baselineOffset; } } ////// Client to get the distance from the top of the text (or other content) to the baseline of this text line; /// this property may differ from the Baseline property if the client specified the line height /// public override double TextBaseline { // simple path assumes no client-specified line height, i.e., TextParagraphProperties.LineHeight <= 0 get { return _baselineOffset; } } ////// Client to get the distance from the before edge of line height /// to the baseline of marker of the line if any. /// public override double MarkerBaseline { get { return Baseline; } } ////// Client to get the overall height of the list items marker of the line if any. /// public override double MarkerHeight { get { return Height; } } ////// Client to get the distance covering all black preceding the leading edge of the line. /// public override double OverhangLeading { get { CheckBoundingBox(); return _boundingBox.Left - Start; } } ////// Client to get the distance covering all black following the trailing edge of the line. /// public override double OverhangTrailing { get { CheckBoundingBox(); return Start + Width - _boundingBox.Right; } } ////// Client to get the distance from the after edge of line height to the after edge of the extent of the line. /// public override double OverhangAfter { get { CheckBoundingBox(); return _boundingBox.Bottom - Height; } } ////// Client to get a boolean value indicates whether content of the line overflows /// the specified paragraph width. /// public override bool HasOverflowed { get { return (_statusFlags & StatusFlags.HasOverflowed) != 0; } } ////// Client to get a boolean value indicates whether a line has been collapsed /// public override bool HasCollapsed { // A collapsed line is never implemented as simple text line get { return false; } } ////// Search forward from the given cp index (inclusive) to find the next navigable cp index. /// Return true if one such cp is found, false otherwise. /// private bool FindNextVisibleCp(int cp, out int cpVisible) { cpVisible = cp; if (cp >= _cpFirst + _cpLength) { return false; // Cannot go forward anymore } int cpRunStart, runIndex; GetRunIndexAtCp(cp, out runIndex, out cpRunStart); while (runIndex < _runs.Length) { // When navigating forward, only the trailing edge of visible content is // navigable. if (_runs[runIndex].IsVisible && !_runs[runIndex].EOT) { cpVisible = Math.Max(cpRunStart, cp); return true; } cpRunStart += _runs[runIndex++].Length; } return false; } ////// Search backward from the given cp index (inclusive) to find the previous navigable cp index. /// Return true if one such cp is found, false otherwise. /// private bool FindPreviousVisibleCp(int cp, out int cpVisible) { cpVisible = cp; if (cp < _cpFirst) { return false; // Cannot go backward anymore. } int cpRunEnd, runIndex; // Position the cpRunEnd at the end of the span that contains the given cp GetRunIndexAtCp(cp, out runIndex, out cpRunEnd); cpRunEnd += _runs[runIndex].Length - 1; while (runIndex >= 0) { // Visible content has caret stops at its leading edge. if (_runs[runIndex].IsVisible && !_runs[runIndex].EOT) { cpVisible = Math.Min(cpRunEnd, cp); return true; } // Newline sequence has caret stops at its leading edge. if (_runs[runIndex].EOT) { // Get the cp index at the beginning of the newline sequence. cpVisible = cpRunEnd - _runs[runIndex].Length + 1; return true; } cpRunEnd -= _runs[runIndex--].Length; } return false; } private void GetRunIndexAtCp( int cp, out int runIndex, out int cpRunStart ) { Invariant.Assert(cp >= _cpFirst && cp < _cpFirst + _cpLength); cpRunStart= _cpFirst; runIndex = 0; // Find the span that contains the given cp while (runIndex < _runs.Length && cpRunStart + _runs[runIndex].Length <= cp) { cpRunStart += _runs[runIndex++].Length; } } } ////// Simple text run /// internal sealed class SimpleRun { public CharacterBufferReference CharBufferReference; // character buffer reference public int Length; // CP length public int[] NominalAdvances; // nominal glyph advance widths in ideal units public int IdealWidth; // Ideal width of the line. Use ideal width to be consistent with FullTextLine in linebreaking public TextRun TextRun; // text run public TextDecoration Underline; // only support single underline public Flags RunFlags; // run flags private TextFormatterImp _textFormatterImp; [Flags] internal enum Flags : ushort { None = 0, EOT = 0x0001, // end-of-text mark Ghost = 0x0002, // non-existence run - only consume cp TrimTrailingUnderline = 0x0004, // trailing whitespace should not be underlined } internal bool EOT { get { return (RunFlags & Flags.EOT) != 0; } } internal bool Ghost { get { return (RunFlags & Flags.Ghost) != 0; } } internal bool TrimTrailingUnderline { get { return (RunFlags & Flags.TrimTrailingUnderline) != 0; } set { if (value) { RunFlags |= Flags.TrimTrailingUnderline; } else { RunFlags &= ~Flags.TrimTrailingUnderline; } } } internal double Baseline { get { if (Ghost || EOT) return 0; return TextRun.Properties.Typeface.Baseline(TextRun.Properties.FontRenderingEmSize, 1, Util.PixelsPerDip, _textFormatterImp.TextFormattingMode); } } internal double Height { get { if (Ghost || EOT) return 0; return TextRun.Properties.Typeface.LineSpacing(TextRun.Properties.FontRenderingEmSize, 1, Util.PixelsPerDip, _textFormatterImp.TextFormattingMode); } } internal Typeface Typeface { get { return TextRun.Properties.Typeface; } } internal double EmSize { get { return TextRun.Properties.FontRenderingEmSize; } } internal bool IsVisible { get { return this.TextRun is TextCharacters; } } internal SimpleRun(TextFormatterImp textFormatterImp) { _textFormatterImp = textFormatterImp; } ////// Creating a simple text run /// /// text formatting settings /// first cp of the run /// first cp of the line /// maxium run width /// maximum column width ///a SimpleRun object static public SimpleRun Create( FormatSettings settings, int cp, int cpFirst, int widthLeft, int widthMax ) { TextRun textRun; int runLength; CharacterBufferRange charBufferRange = settings.FetchTextRun( cp, cpFirst, out textRun, out runLength ); return Create( settings, charBufferRange, textRun, cp, cpFirst, runLength, widthLeft ); } ////// Creating a simple text run /// /// text formatting settings /// character string associated to textrun /// text run /// first cp of the run /// first cp of the line /// run length /// maximum run width ///a SimpleRun object static public SimpleRun Create( FormatSettings settings, CharacterBufferRange charString, TextRun textRun, int cp, int cpFirst, int runLength, int widthLeft ) { SimpleRun run = null; if (textRun is TextCharacters) { if ( textRun.Properties.BaselineAlignment != BaselineAlignment.Baseline || (textRun.Properties.TextEffects != null && textRun.Properties.TextEffects.Count != 0) ) { // fast path does not handle the following conditions // o non-default baseline alignment // o text drawing effect ( return null; } TextDecorationCollection textDecorations = textRun.Properties.TextDecorations; if ( textDecorations != null && textDecorations.Count != 0 && !textDecorations.ValueEquals(TextDecorations.Underline)) { // we only support a single underline return null; } settings.DigitState.SetTextRunProperties(textRun.Properties); if (settings.DigitState.RequiresNumberSubstitution) { // don't support number substitution in fast path return null; } if (charString[0] == TextStore.CharCarriageReturn) { // CR in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; if (charString.Length > 1 && charString[1] == TextStore.CharLineFeed) { runLength = 2; } // This path handles the case where the backing store breaks the text run in between // a Carriage Return and a Line Feed. So we fetch the next run to check whether the next // character is a line feed. else if (charString.Length == 1) { // Prefetch to check for line feed. TextRun newRun; int newRunLength; CharacterBufferRange newBufferRange = settings.FetchTextRun( cp + 1, cpFirst, out newRun, out newRunLength ); if (newBufferRange.Length > 0 && newBufferRange[0] == TextStore.CharLineFeed) { // Merge the 2 runs. int lengthOfRun = 2; char[] characterArray = new char[lengthOfRun]; characterArray[0] = TextStore.CharCarriageReturn; characterArray[1] = TextStore.CharLineFeed; TextRun mergedTextRun = new TextCharacters(characterArray, 0, lengthOfRun, textRun.Properties); return new SimpleRun(lengthOfRun, mergedTextRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } } return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (charString[0] == TextStore.CharLineFeed) { // LF in the middle of text stream treated as explicit paragraph break // simple hard line break runLength = 1; return new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } // attempt to create a simple run for text run = CreateSimpleTextRun( charString, textRun, settings.Formatter, widthLeft, settings.Pap.EmergencyWrap ); if (run == null) { // fail to create simple text run, the run content is too complex return null; } // Check for underline condition if (textDecorations != null && textDecorations.Count == 1 ) { run.Underline = textDecorations[0]; } } else if (textRun is TextEndOfLine) { run = new SimpleRun(runLength, textRun, (Flags.EOT | Flags.Ghost), settings.Formatter); } else if (textRun is TextHidden) { // hidden run run = new SimpleRun(runLength, textRun, Flags.Ghost, settings.Formatter); } return run; } ////// Create simple run of text, /// returning null if the specified text run cannot be correctly formatted as simple run /// static internal SimpleRun CreateSimpleTextRun( CharacterBufferRange charBufferRange, TextRun textRun, TextFormatterImp formatter, int widthLeft, bool emergencyWrap ) { Invariant.Assert(textRun is TextCharacters); SimpleRun run = new SimpleRun(formatter); run.CharBufferReference = charBufferRange.CharacterBufferReference; run.TextRun = textRun; if (!run.TextRun.Properties.Typeface.CheckFastPathNominalGlyphs( charBufferRange, run.EmSize, 1.0, formatter.IdealToReal(widthLeft), !emergencyWrap, false, CultureMapper.GetSpecificCulture(run.TextRun.Properties.CultureInfo), formatter.TextFormattingMode, false, //No support for isSideways out run.Length )) { // Getting nominal glyphs is not supported by the font, // or it is but it results in low typographic quality text // e.g. OpenType support is not utilized. return null; } run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth( new CharacterBufferRange(run.CharBufferReference, run.Length), run.EmSize, TextFormatterImp.ToIdeal, formatter.TextFormattingMode, false, out run.NominalAdvances, out run.IdealWidth ); return run; } ////// Construct simple text run /// /// run length /// text run /// run flags private SimpleRun( int length, TextRun textRun, Flags flags, TextFormatterImp textFormatterImp ) { Length = length; TextRun = textRun; RunFlags = flags; _textFormatterImp = textFormatterImp; } ////// Draw a simple run /// ///drawing bounding box ////// Critical - as this calls critical function ComputeUnshapedGlyphRun. /// Safe - as this just draws text and returns a rect. /// [SecurityCritical, SecurityTreatAsSafe] internal Rect Draw( DrawingContext drawingContext, double x, double y, bool visiCodePath ) { if (Length <= 0 || this.Ghost) { return Rect.Empty; // nothing to draw } Brush foregroundBrush = TextRun.Properties.ForegroundBrush; if(visiCodePath && foregroundBrush is SolidColorBrush) { Color color = ((SolidColorBrush)foregroundBrush).Color; foregroundBrush = new SolidColorBrush(Color.FromArgb( (byte)(color.A>>2), // * 0.25 color.R, color.G, color.B )); } Rect inkBoundingBox; IListdisplayGlyphAdvances; if (_textFormatterImp.TextFormattingMode == TextFormattingMode.Ideal) { displayGlyphAdvances = new ThousandthOfEmRealDoubles(EmSize, NominalAdvances.Length); for (int i = 0; i < displayGlyphAdvances.Count; i++) { // convert ideal glyph advance width to real width for displaying. displayGlyphAdvances[i] = _textFormatterImp.IdealToReal(NominalAdvances[i]); } } else { displayGlyphAdvances = new List (NominalAdvances.Length); for (int i = 0; i < NominalAdvances.Length; i++) { // convert ideal glyph advance width to real width for displaying. displayGlyphAdvances.Add(_textFormatterImp.IdealToReal(NominalAdvances[i])); } } CharacterBufferRange charBufferRange = new CharacterBufferRange(CharBufferReference, Length); GlyphTypeface glyphTypeface = Typeface.TryGetGlyphTypeface(); Invariant.Assert(glyphTypeface != null); GlyphRun glyphRun = glyphTypeface.ComputeUnshapedGlyphRun( new Point(x, y), charBufferRange, displayGlyphAdvances, EmSize, TextRun.Properties.FontHintingEmSize, Typeface.NullFont, CultureMapper.GetSpecificCulture(TextRun.Properties.CultureInfo), null, // device font name _textFormatterImp.TextFormattingMode ); if (glyphRun != null) { inkBoundingBox = glyphRun.ComputeInkBoundingBox(); } else { inkBoundingBox = Rect.Empty; } if (!inkBoundingBox.IsEmpty) { // glyph run's ink bounding box is relative to its origin inkBoundingBox.X += glyphRun.BaselineOrigin.X; inkBoundingBox.Y += glyphRun.BaselineOrigin.Y; } if (drawingContext != null) { if (glyphRun != null) { glyphRun.EmitBackground(drawingContext, TextRun.Properties.BackgroundBrush); drawingContext.DrawGlyphRun(foregroundBrush, glyphRun); } // draw underline here if (Underline != null) { // Determine number of characters to underline. We don't underline trailing spaces // if the TrimTrailingUnderline flag is set. int underlineLength = Length; if (TrimTrailingUnderline) { while (underlineLength > 0 && TextStore.IsSpace(charBufferRange[underlineLength - 1])) { --underlineLength; } } // Determine the width of the underline. double dxUnderline = 0; for (int i = 0; i < underlineLength; ++i) { dxUnderline += _textFormatterImp.IdealToReal(NominalAdvances[i]); } // We know only TextDecoration.Underline will be handled in Simple Path. double offset = -Typeface.UnderlinePosition * EmSize; double penThickness = Typeface.UnderlineThickness * EmSize; Point lineOrigin = new Point(x, y + offset); Rect underlineRect = new Rect( lineOrigin.X, lineOrigin.Y - penThickness * 0.5, dxUnderline, penThickness ); // Apply the pair of guidelines: one for baseline and another // for top edge of undelining line. Both will be snapped to pixel grid. // Guideline pairing algorithm detects the case when these two // guidelines happen to be close to one another and provides // synchronous snapping, so that the gap between baseline and // undelining line does not depend on the position of text line. drawingContext.PushGuidelineY2(y, lineOrigin.Y - penThickness * 0.5 - y); try { drawingContext.DrawRectangle( foregroundBrush, null, // pen underlineRect ); } finally { drawingContext.Pop(); } // underline pen thickness is always positive in fast path inkBoundingBox.Union( underlineRect ); } } return inkBoundingBox; } /// /// Scan backward to collect trailing spaces of the run /// /// formatter /// trailing spaces /// trailing spaces width ///continue collecting the previous run? internal bool CollectTrailingSpaces( TextFormatterImp formatter, ref int trailing, ref int trailingSpaceWidth ) { // As we are collecting trailing space cp, we also collect the trailing space width. // In Full text line, TrailingSpaceWidth = ToReal(Sumof(ToIdeal(glyphsWidths)); // we do the same thing here so that trailing space width is exactly the same // as Full Text Line. if(Ghost) { if(!EOT) { trailing += Length; trailingSpaceWidth += IdealWidth; } return true; } int offsetToFirstChar = CharBufferReference.OffsetToFirstChar; CharacterBuffer charBuffer = CharBufferReference.CharacterBuffer; int dcp = Length; if (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) { // scan backward to find the first blank following a non-blank while (dcp > 0 && TextStore.IsSpace(charBuffer[offsetToFirstChar + dcp - 1])) { // summing the ideal value of each glyph trailingSpaceWidth += NominalAdvances[dcp - 1]; dcp--; trailing++; } return dcp == 0; } return false; } internal bool IsUnderlineCompatible(SimpleRun nextRun) { return Typeface.Equals(nextRun.Typeface) && EmSize == nextRun.EmSize && Baseline == nextRun.Baseline; } internal double DistanceFromDcp(int dcp) { if (Ghost) { return dcp <= 0 ? 0 : _textFormatterImp.IdealToReal(IdealWidth); } if (dcp > Length) { dcp = Length; } double distance = 0; for(int i = 0; i < dcp; i++) { distance += _textFormatterImp.IdealToReal(NominalAdvances[i]); } return distance; } internal CharacterHit DcpFromDistance(double distance) { if (Ghost) { return (EOT || distance <= 0) ? new CharacterHit() : new CharacterHit(Length, 0); } if (Length <= 0) { return new CharacterHit(); } int dcp = 0; double currentRealAdvance = 0; while (dcp < Length && distance > (currentRealAdvance = _textFormatterImp.IdealToReal(NominalAdvances[dcp]))) { distance -= currentRealAdvance; dcp++; } if (dcp < Length) { // hit occurs in this run return new CharacterHit(dcp, (distance > currentRealAdvance / 2 ? 1 : 0)); } // hit doesn't occur in this run return new CharacterHit(Length - 1, 1); } } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TabPage.cs
- WebSysDefaultValueAttribute.cs
- SubpageParagraph.cs
- MsmqReceiveParameters.cs
- Transform3DGroup.cs
- XmlIgnoreAttribute.cs
- DesignTimeParseData.cs
- SmtpSpecifiedPickupDirectoryElement.cs
- HttpModuleAction.cs
- FacetChecker.cs
- OleDbReferenceCollection.cs
- DocumentReference.cs
- Assembly.cs
- SmiRecordBuffer.cs
- UIElement.cs
- XmlParserContext.cs
- Slider.cs
- ValidatorCollection.cs
- HostingMessageProperty.cs
- WebPermission.cs
- InfoCardProofToken.cs
- WebPartDeleteVerb.cs
- ScriptingSectionGroup.cs
- XmlException.cs
- SimplePropertyEntry.cs
- AutomationPeer.cs
- ZoneLinkButton.cs
- ToolStripButton.cs
- Sequence.cs
- SqlConnectionHelper.cs
- SrgsOneOf.cs
- RegistryKey.cs
- EndpointDispatcherTable.cs
- DoubleKeyFrameCollection.cs
- mil_sdk_version.cs
- LiteralTextParser.cs
- MethodBuilderInstantiation.cs
- TransformedBitmap.cs
- ThreadExceptionEvent.cs
- ObjectTypeMapping.cs
- IndependentlyAnimatedPropertyMetadata.cs
- BitmapDecoder.cs
- ISCIIEncoding.cs
- ObjectDataSourceFilteringEventArgs.cs
- FileDialogCustomPlaces.cs
- DataGridPagerStyle.cs
- TargetException.cs
- DefinitionBase.cs
- XXXInfos.cs
- DefaultExpression.cs
- SqlConnectionPoolGroupProviderInfo.cs
- XmlReaderSettings.cs
- XamlSerializerUtil.cs
- StylusEventArgs.cs
- EnumBuilder.cs
- ApplicationFileCodeDomTreeGenerator.cs
- ICspAsymmetricAlgorithm.cs
- ToolStripItemRenderEventArgs.cs
- DynamicMethod.cs
- ValueQuery.cs
- DataGridItemEventArgs.cs
- DllHostInitializer.cs
- ScopeCollection.cs
- Switch.cs
- Point3DCollectionValueSerializer.cs
- ModelItemDictionaryImpl.cs
- ShaperBuffers.cs
- XmlAtomicValue.cs
- ToolStripGripRenderEventArgs.cs
- ResourceContainer.cs
- ListCommandEventArgs.cs
- DataBoundControlHelper.cs
- ManagedWndProcTracker.cs
- TypeDescriptionProviderAttribute.cs
- ConfigXmlElement.cs
- ExpressionConverter.cs
- NameValueSectionHandler.cs
- Encoder.cs
- XmlSecureResolver.cs
- WebPartConnectionsEventArgs.cs
- ComponentCodeDomSerializer.cs
- ToolStripPanelDesigner.cs
- ConnectorEditor.cs
- XmlValidatingReader.cs
- ConnectionStringsExpressionEditor.cs
- DbDataReader.cs
- NavigationHelper.cs
- FlowDocumentScrollViewerAutomationPeer.cs
- NameValueSectionHandler.cs
- SHA512.cs
- Cursor.cs
- SmtpException.cs
- MonthCalendar.cs
- EntityReference.cs
- AppearanceEditorPart.cs
- WebPartAddingEventArgs.cs
- ApplicationTrust.cs
- CodeAccessPermission.cs
- ReferencedType.cs
- _AutoWebProxyScriptEngine.cs