File: MS\Internal\TextFormatting\FullTextBreakpoint.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Windows.Media.TextFormatting;
 
namespace MS.Internal.TextFormatting
{
    /// <summary>
    /// Full text implementation of the specialized text line representing state 
    /// of line up to the point where line break may occur. Unlike a more tangible
    /// type like text line , breakpoint could not draw or performs hit-testing 
    /// operation. It could only reflect formatting result back to the client.
    /// </summary>
    internal sealed class FullTextBreakpoint : TextBreakpoint
    {
        private TextMetrics                         _metrics;           // full text metrics
        private IntPtr                              _ploline;           // native object representing this break
        private IntPtr                              _penaltyResource;   // unsafe handle to the internal factors used to determines penalty of the break. By default, the lifetime of this resource is managed by _ploline.
        private bool                                _isDisposed;        // flag indicates whether this object is disposed
        private bool                                _isLineTruncated;   // flag indicates whether the line produced at this breakpoint is truncated.
 
 
        /// <summary>
        /// Construct a list of potential breakpoints starting from the specified firstCharIndex
        /// </summary>
        /// <param name="paragraphCache">cache of paragraph content of all possible breaks within</param>
        /// <param name="firstCharIndex">index to first character where formatting starts</param>
        /// <param name="maxLineWidth">max format ideal width of the line being built</param>
        /// <param name="previousLineBreak">LineBreak property of the previous text line, or null if this is the first line in the paragraph</param>
        /// <param name="penaltyRestriction">constraint on what breakpoint is returned based on its implied calculated penalty</param>
        /// <param name="bestFitIndex">index of the best fit breakpoint in the returned collection</param>        
        /// <returns>a list of potential breakpoints starting from firstCharIndex</returns>
        internal static IList<TextBreakpoint> CreateMultiple(
            TextParagraphCache          paragraphCache,
            int                         firstCharIndex,
            int                         maxLineWidth,
            TextLineBreak               previousLineBreak,
            IntPtr                      penaltyRestriction,
            out int                     bestFitIndex            
            )
        {
            Invariant.Assert(paragraphCache != null);
 
            // grab full text state from paragraph cache
            FullTextState fullText = paragraphCache.FullText;
            Invariant.Assert(fullText != null);
 
            FormatSettings settings = fullText.TextStore.Settings;
            Invariant.Assert(settings != null);
 
            // update formatting parameters at line start
            settings.UpdateSettingsForCurrentLine(
                maxLineWidth,
                previousLineBreak, 
                (firstCharIndex == fullText.TextStore.CpFirst)
                );
 
            Invariant.Assert(settings.Formatter != null);
 
            // acquiring LS context
            TextFormatterContext context = settings.Formatter.AcquireContext(fullText, IntPtr.Zero);
 
            IntPtr previousBreakRecord = IntPtr.Zero;
            if (settings.PreviousLineBreak != null)
                previousBreakRecord = settings.PreviousLineBreak.BreakRecord;
 
            // need not consider marker as tab since marker does not affect line metrics and it wasnt drawn.
            fullText.SetTabs(context);
 
            LsBreaks lsbreaks = new LsBreaks();
 
            LsErr lserr = context.CreateBreaks(
                fullText.GetBreakpointInternalCp(firstCharIndex),
                previousBreakRecord,
                paragraphCache.Ploparabreak,  // para breaking session
                penaltyRestriction,
                ref lsbreaks, 
                out bestFitIndex
                );
 
            // get the exception in context before it is released
            Exception callbackException = context.CallbackException;
            
            // release the context
            context.Release();
 
            if(lserr != LsErr.None)
            {
                if(callbackException != null)
                {                        
                    // rethrow exception thrown in callbacks
                    throw callbackException;
                }
                else
                {
                    // throw with LS error codes
                    TextFormatterContext.ThrowExceptionFromLsError(SR.Format(SR.CreateBreaksFailure, lserr), lserr);
                }
            }
 
            // keep context alive at least till here
            GC.KeepAlive(context);
 
            TextBreakpoint[] breakpoints = new TextBreakpoint[lsbreaks.cBreaks];
 
            for (int i = 0; i < lsbreaks.cBreaks; i++)
            {
                breakpoints[i] = new FullTextBreakpoint(
                    fullText,
                    firstCharIndex,
                    maxLineWidth,
                    ref lsbreaks,
                    i   // the current break
                    );
            }
 
            return breakpoints;
        }
 
 
        /// <summary>
        /// Construct breakpoint from full text info
        /// </summary>
        private FullTextBreakpoint(
            FullTextState           fullText,
            int                     firstCharIndex,
            int                     maxLineWidth,
            ref LsBreaks            lsbreaks,
            int                     breakIndex
            ) : this()
        {
            // According to antons: PTS only uses the width of a feasible break to avoid
            // clipping in subpage. At the moment, there is no good solution as of how
            // PTS client would be able to compute this width efficiently using LS. 
            // The work around - although could be conceived would simply be too slow.
            // The width should therefore be set to the paragraph width for the time being.
            //
            // Client of text formatter would simply pass the value of TextBreakpoint.Width
            // back to PTS pfnFormatLineVariants call.
            LsLineWidths lineWidths = new LsLineWidths();
            lineWidths.upLimLine = maxLineWidth;
            lineWidths.upStartMainText = fullText.TextStore.Settings.TextIndent;
            lineWidths.upStartMarker = lineWidths.upStartMainText;
            lineWidths.upStartTrailing = lineWidths.upLimLine;
            lineWidths.upMinStartTrailing = lineWidths.upStartTrailing;
 
            // construct the correspondent text metrics
            unsafe
            {
                _metrics.Compute(
                    fullText,
                    firstCharIndex,
                    maxLineWidth,
                    null,   // collapsingSymbol
                    ref lineWidths,
                    &lsbreaks.plslinfoArray[breakIndex]
                    );
 
                _ploline = lsbreaks.pplolineArray[breakIndex];
 
                // keep the line penalty handle
                _penaltyResource = lsbreaks.plinepenaltyArray[breakIndex];
 
                if (lsbreaks.plslinfoArray[breakIndex].fForcedBreak != 0)
                    _isLineTruncated = true;
            }
        }
 
 
        /// <summary>
        /// Empty private constructor
        /// </summary>
        private FullTextBreakpoint()
        {
            _metrics = new TextMetrics();
        }
 
 
        /// <summary>
        /// Finalizing the break
        /// </summary>
        ~FullTextBreakpoint()
        {
            Dispose(false);
        }
 
 
        /// <summary>
        /// Disposing LS unmanaged memory for text line
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if(_ploline != IntPtr.Zero)
            {
                UnsafeNativeMethods.LoDisposeLine(_ploline, !disposing);
                _ploline = IntPtr.Zero;
                _penaltyResource = IntPtr.Zero;
                _isDisposed = true;
                GC.KeepAlive(this);
            }
        }
 
        #region TextBreakpoint
 
        /// <summary>
        /// Client to acquire a state at the point where breakpoint is determined 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.FormatParagraphBreakpoints when 
        /// formatting the next set of breakpoints within the same paragraph.
        /// </summary>
        public override TextLineBreak GetTextLineBreak()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(SR.TextBreakpointHasBeenDisposed);
            }
            return _metrics.GetTextLineBreak(_ploline);
        }
 
 
        /// <summary>
        /// Client to get the handle of the internal factors that are used to determine penalty of this breakpoint.
        /// </summary>
        /// <remarks>
        /// Calling this method means that the client will now manage the lifetime of this unmanaged resource themselves using unsafe penalty handler.
        /// We would make a correspondent call to notify our unmanaged wrapper to release them from duty of managing this 
        /// resource. 
        /// </remarks>
        internal override IntPtr GetTextPenaltyResource()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(SR.TextBreakpointHasBeenDisposed);
            }
 
            LsErr lserr = UnsafeNativeMethods.LoRelievePenaltyResource(_ploline);
            if (lserr != LsErr.None)
            {
                TextFormatterContext.ThrowExceptionFromLsError(SR.Format(SR.RelievePenaltyResourceFailure, lserr), lserr);
            }
 
            return _penaltyResource;
        }
 
 
        /// <summary>
        /// Client to get a Boolean flag indicating whether the line is truncated in the 
        /// middle of a word. This flag is set only when TextParagraphProperties.TextWrapping 
        /// is set to TextWrapping.Wrap and a single word is longer than the formatting 
        /// paragraph width. In such situation, TextFormatter truncates the line in the middle 
        /// of the word to honor the desired behavior specified by TextWrapping.Wrap setting.
        /// </summary>
        public override bool IsTruncated
        {
            get { return _isLineTruncated; }
        }
 
 
        #endregion
 
        #region TextMetrics
 
        /// <summary>
        /// Client to get the number of text source positions of this line
        /// </summary>
        public override int Length
        {
            get { return _metrics.Length; }
        }
 
 
        /// <summary>
        /// Client to get the number of characters following the last character 
        /// of the line that may trigger reformatting of the current line.
        /// </summary>
        public override int DependentLength
        {
            get { return _metrics.DependentLength; }
        }
 
 
        /// <summary>
        /// Client to get the number of newline characters at line end
        /// </summary>
        public override int NewlineLength 
        { 
            get { return _metrics.NewlineLength; }
        }
 
 
        /// <summary>
        /// Client to get distance from paragraph start to line start
        /// </summary>
        public override double Start
        {
            get { return _metrics.Start; }
        }
 
 
        /// <summary>
        /// Client to get the total width of this line
        /// </summary>
        public override double Width
        {
            get { return _metrics.Width; }
        }
 
 
        /// <summary>
        /// Client to get the total width of this line including width of whitespace characters at the end of the line.
        /// </summary>
        public override double WidthIncludingTrailingWhitespace
        {
            get { return _metrics.WidthIncludingTrailingWhitespace; }
        }
 
 
        /// <summary>
        /// Client to get the height of the line
        /// </summary>
        public override double Height 
        { 
            get { return _metrics.Height; } 
        }
 
 
        /// <summary>
        /// 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
        /// </summary>
        public override double TextHeight
        {
            get { return _metrics.TextHeight; }
        }
 
 
        /// <summary>
        /// Client to get the distance from top to baseline of this text line
        /// </summary>
        public override double Baseline
        { 
            get { return _metrics.Baseline; } 
        }
 
 
        /// <summary>
        /// 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
        /// </summary>
        public override double TextBaseline
        {
            get { return _metrics.TextBaseline; }
        }
 
 
        /// <summary>
        /// Client to get the distance from the before edge of line height 
        /// to the baseline of marker of the line if any.
        /// </summary>
        public override double MarkerBaseline 
        { 
            get { return _metrics.MarkerBaseline; } 
        }
 
 
        /// <summary>
        /// Client to get the overall height of the list items marker of the line if any.
        /// </summary>
        public override double MarkerHeight 
        { 
            get { return _metrics.MarkerHeight; } 
        }
 
        #endregion
    }
}