Example #1
0
        internal TextParagraphCache( 
            FormatSettings      settings,
            int                 firstCharIndex, 
            int                 paragraphWidth
            )
        {
            Invariant.Assert(settings != null); 

            // create full text 
            _finiteFormatWidth = settings.GetFiniteFormatWidth(paragraphWidth); 
            _fullText = FullTextState.Create(settings, firstCharIndex, _finiteFormatWidth);
 
            // acquiring LS context
            TextFormatterContext context = settings.Formatter.AcquireContext(_fullText, IntPtr.Zero);

            _fullText.SetTabs(context); 

            IntPtr ploparabreakValue = IntPtr.Zero; 
 
            LsErr lserr = context.CreateParaBreakingSession(
                firstCharIndex, 
                _finiteFormatWidth,
                // breakrec is not needed before the first cp of para cache
                // since we handle Bidi break ourselves.
                IntPtr.Zero, 
                ref ploparabreakValue,
                ref _penalizedAsJustified 
                ); 

            // get the exception in context before it is released 
            Exception callbackException = context.CallbackException;

            // release the context
            context.Release(); 

            if(lserr != LsErr.None) 
            { 
                GC.SuppressFinalize(this);
                if(callbackException != null) 
                {
                    // rethrow exception thrown in callbacks
                    throw new InvalidOperationException(SR.Get(SRID.CreateParaBreakingSessionFailure, lserr), callbackException);
                } 
                else
                { 
                    // throw with LS error codes 
                    TextFormatterContext.ThrowExceptionFromLsError(SR.Get(SRID.CreateParaBreakingSessionFailure, lserr), lserr);
                } 
            }

            _ploparabreak.Value = ploparabreakValue;
 
            // keep context alive till here
            GC.KeepAlive(context); 
        } 
Example #2
0
        /// <summary>
        /// Set tab stops
        /// </summary>
        internal void SetTabs(TextFormatterContext context)
        {
            unsafe
            {
                ParaProp       pap      = _store.Pap;
                FormatSettings settings = _store.Settings;

                // set up appropriate tab stops
                int     incrementalTab = TextFormatterImp.RealToIdeal(pap.DefaultIncrementalTab);
                int     lsTbdCount     = pap.Tabs != null ? pap.Tabs.Count : 0;
                LsTbd[] lsTbds;

                if (_markerStore != null)
                {
                    if (pap.Tabs != null && pap.Tabs.Count > 0)
                    {
                        lsTbdCount   = pap.Tabs.Count + 1;
                        lsTbds       = new LsTbd[lsTbdCount];
                        lsTbds[0].ur = settings.TextIndent; // marker requires a tab stop at text start position
                        fixed(LsTbd *plsTbds = &lsTbds[1])
                        {
                            CreateLsTbds(pap, plsTbds, lsTbdCount - 1);
                            context.SetTabs(incrementalTab, plsTbds - 1, lsTbdCount);
                        }
                    }
                    else
                    {
                        LsTbd markerRequiredLsTbd = new LsTbd();
                        markerRequiredLsTbd.ur = settings.TextIndent; // marker requires a tab stop at text start position
                        context.SetTabs(incrementalTab, &markerRequiredLsTbd, 1);
                    }
                }
                else
                {
                    if (pap.Tabs != null && pap.Tabs.Count > 0)
                    {
                        lsTbds = new LsTbd[lsTbdCount];
                        fixed(LsTbd *plsTbds = &lsTbds[0])
                        {
                            CreateLsTbds(pap, plsTbds, lsTbdCount);
                            context.SetTabs(incrementalTab, plsTbds, lsTbdCount);
                        }
                    }
                    else
                    {
                        // work with only incremental tab
                        context.SetTabs(incrementalTab, null, 0);
                    }
                }
            }
        }
Example #3
0
        /// <summary>
        /// Create a fulltext state object from formatting info for subsequent fulltext formatting
        /// e.g. fulltext line, mix/max computation, potential breakpoints for optimal paragraph formatting.
        /// </summary>
        internal static FullTextState Create(
            FormatSettings settings,
            int cpFirst,
            int finiteFormatWidth
            )
        {
            // prepare text stores
            TextStore store = new TextStore(
                settings,
                cpFirst,
                0,
                settings.GetFormatWidth(finiteFormatWidth)
                );

            ParaProp  pap         = settings.Pap;
            TextStore markerStore = null;

            if (pap.FirstLineInParagraph &&
                pap.TextMarkerProperties != null &&
                pap.TextMarkerProperties.TextSource != null)
            {
                // create text store specifically for marker
                markerStore = new TextStore(
                    // create specialized settings for marker store e.g. with marker text source
                    new FormatSettings(
                        settings.Formatter,
                        pap.TextMarkerProperties.TextSource,
                        new TextRunCacheImp(),   // no cross-call run cache available for marker store
                        pap,                     // marker by default use the same paragraph properties
                        null,                    // not consider previousLineBreak
                        true,                    // isSingleLineFormatting
                        settings.TextFormattingMode,
                        settings.IsSideways
                        ),
                    0,                           // marker store always started with cp == 0
                    TextStore.LscpFirstMarker,   // first lscp value for marker text
                    Constants.IdealInfiniteWidth // formatWidth
                    );
            }

            // construct a new fulltext state object
            return(new FullTextState(store, markerStore, settings.IsSideways));
        }
        /// <summary>
        /// Create a fulltext state object from formatting info for subsequent fulltext formatting
        /// e.g. fulltext line, mix/max computation, potential breakpoints for optimal paragraph formatting.
        /// </summary>
        internal static FullTextState Create(
            FormatSettings      settings,
            int                 cpFirst,
            int                 finiteFormatWidth
            )
        {
            // prepare text stores
            TextStore store = new TextStore(
                settings, 
                cpFirst, 
                0, 
                settings.GetFormatWidth(finiteFormatWidth)
                );

            ParaProp pap = settings.Pap;
            TextStore markerStore = null;

            if(     pap.FirstLineInParagraph
                &&  pap.TextMarkerProperties != null
                &&  pap.TextMarkerProperties.TextSource != null)
            {
                // create text store specifically for marker
                markerStore = new TextStore(
                    // create specialized settings for marker store e.g. with marker text source
                    new FormatSettings(
                        settings.Formatter,
                        pap.TextMarkerProperties.TextSource,
                        new TextRunCacheImp(),   // no cross-call run cache available for marker store
                        pap,                     // marker by default use the same paragraph properties
                        null,                    // not consider previousLineBreak
                        true,                    // isSingleLineFormatting
                        settings.TextFormattingMode,
                        settings.IsSideways
                        ),
                    0,                           // marker store always started with cp == 0
                    TextStore.LscpFirstMarker,   // first lscp value for marker text
                    Constants.IdealInfiniteWidth // formatWidth 
                    );
            }

            // construct a new fulltext state object
            return new FullTextState(store, markerStore, settings.IsSideways);
        }
        internal override TextParagraphCache CreateParagraphCache(
#endif
            TextSource textSource,
            int firstCharIndex,
            double paragraphWidth,
            TextParagraphProperties paragraphProperties,
            TextLineBreak previousLineBreak,
            TextRunCache textRunCache
            )
        {
            // prepare formatting settings
            FormatSettings settings = PrepareFormatSettings(
                textSource,
                firstCharIndex,
                paragraphWidth,
                paragraphProperties,
                previousLineBreak,
                textRunCache,
                true,   // optimalBreak
                false,  // !isSingleLineFormatting
                _textFormattingMode
                );

            //
            // Optimal paragraph formatting session specific check
            //
            if (!settings.Pap.Wrap && settings.Pap.OptimalBreak)
            {
                // Optimal paragraph must wrap.
                throw new ArgumentException(SR.Get(SRID.OptimalParagraphMustWrap));
            }

            // create paragraph content cache object
            return(new TextParagraphCache(
                       settings,
                       firstCharIndex,
                       RealToIdeal(paragraphWidth)
                       ));
        }
        /// <summary>
        /// Client to ask for the possible smallest and largest paragraph width that can fully contain the passing text content
        /// </summary>
        /// <param name="textSource">an object representing text layout clients text source for TextFormatter.</param>
        /// <param name="firstCharIndex">character index to specify where in the source text the line starts</param>
        /// <param name="paragraphProperties">properties that can change from one paragraph to the next, such as text flow direction, text alignment, or indentation.</param>
        /// <param name="textRunCache">an object representing content cache of the client.</param>
        /// <returns>min max paragraph width</returns>
        public override MinMaxParagraphWidth FormatMinMaxParagraphWidth(
            TextSource textSource,
            int firstCharIndex,
            TextParagraphProperties paragraphProperties,
            TextRunCache textRunCache
            )
        {
            // prepare formatting settings
            FormatSettings settings = PrepareFormatSettings(
                textSource,
                firstCharIndex,
                0,      // infinite paragraphWidth
                paragraphProperties,
                null,   // always format the whole paragraph - no previousLineBreak
                textRunCache,
                false,  // optimalBreak
                true,   // isSingleLineFormatting
                _textFormattingMode
                );

            // create specialized line specifically for min/max calculation
            TextMetrics.FullTextLine line = new TextMetrics.FullTextLine(
                settings,
                firstCharIndex,
                0,  // lineLength
                0,  // paragraph width has no significant meaning in min/max calculation
                (LineFlags.KeepState | LineFlags.MinMax)
                );

            // line width in this case is the width of a line when the entire paragraph is laid out
            // as a single long line.
            MinMaxParagraphWidth minMax = new MinMaxParagraphWidth(line.MinWidth, line.Width);

            line.Dispose();
            return(minMax);
        }
Example #7
0
        /// <summary>
        /// Fetch cached textrun 
        /// </summary>
        internal TextRun FetchTextRun( 
            FormatSettings          settings, 
            int                     cpFetch,
            int                     cpFirst, 
            out int                 offsetToFirstCp,
            out int                 runLength
            )
        { 
            SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch);
            _latestPosition = textRunSpanRider.SpanPosition; 
            TextRun textRun = (TextRun)textRunSpanRider.CurrentElement; 

            if(textRun == null) 
            {
                // run not already cached, fetch new run and cache it

                textRun = settings.TextSource.GetTextRun(cpFetch); 

                if (textRun.Length < 1) 
                { 
                    throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero));
                } 

                Plsrun plsrun = TextRunInfo.GetRunType(textRun);

                if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject) 
                {
                    TextRunProperties properties = textRun.Properties; 
 
                    if (properties == null)
                        throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull)); 

                    if (properties.FontRenderingEmSize <= 0)
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties"));
 
                    double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;
 
                    if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize) 
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize));
 
                    CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo);

                    if (culture == null)
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties")); 

                    if (properties.Typeface == null) 
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties")); 
                }
 

                //
                // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be
                // consistent with the positions encoded by SpanVector. In run cache, the begining of a span 
                // should always correspond to the begining of a cached text run. If the end of the currently fetched
                // run overlaps with the begining of an already cached run, the begining of the cached run needs to be 
                // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range 
                // so we'll simply remove the overlapped runs here.
                // 

                // Move the rider to the end of the current run
                textRunSpanRider.At(cpFetch + textRun.Length - 1);
                _latestPosition = textRunSpanRider.SpanPosition; 

                if (textRunSpanRider.CurrentElement != _textRunVector.Default) 
                { 
                    // The end overlaps with one or more cached runs, clear the range from the
                    // begining of the current fetched run to the end of the last overlapped cached run. 
                    _latestPosition = _textRunVector.SetReference(
                        cpFetch,
                        textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch,
                        _textRunVector.Default, 
                        _latestPosition
                        ); 
                } 

                _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition); 

                // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls
                textRunSpanRider.At(_latestPosition, cpFetch);
            } 

            offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart; 
            runLength = textRunSpanRider.Length; 
            Debug.Assert(textRun != null && runLength > 0, "Invalid run!");
 
            bool isText = textRun is ITextSymbols;

            if (isText)
            { 
                // Chop text run to optimal length so we dont spend forever analysing
                // them all at once. 
 
                int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst;
 
                if(looseCharLength <= 0)
                {
                    // this line already exceeds typical line length, incremental fetch goes
                    // about a quarter of the typical length. 

                    looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25); 
                } 

                if(runLength > looseCharLength) 
                {
                    if (TextRunInfo.GetRunType(textRun) == Plsrun.Text)
                    {
                        // 
                        // When chopping the run at the typical line length,
                        // - don't chop in between of higher & lower surrogate 
                        // - don't chop combining mark away from its base character 
                        // - don't chop joiner from surrounding characters
                        // 
                        // Starting from the initial chopping point, we look ahead to find a safe position. We stop at
                        // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make
                        // much sense in shaping already.
                        // 

                        CharacterBufferReference charBufferRef = textRun.CharacterBufferReference; 
 
                        // We look ahead by one more line at most. It is not normal to have
                        // so many combining mark or joiner characters in a row. It doesn't make sense to 
                        // look further if so.
                        int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine);

                        int sizeOfChar = 0; 
                        int endOffset  = 0;
                        bool canBreakAfterPrecedingChar = false; 
 
                        for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar)
                        { 
                            CharacterBufferRange charString = new CharacterBufferRange(
                                charBufferRef.CharacterBuffer,
                                charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset,
                                runLength - endOffset 
                                );
 
                            int ch = Classification.UnicodeScalar(charString, out sizeOfChar); 

                            // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after), 
                            // and the current char is not combining or joiner (i.e. can-break-before).
                            if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch) )
                            {
                                break; 
                            }
 
                            canBreakAfterPrecedingChar = !Classification.IsJoiner(ch); 
                        }
 
                        looseCharLength = Math.Min(runLength, endOffset);
                    }

                    runLength = looseCharLength; 
                }
            } 
 

            Debug.Assert( 

                // valid run found
                runLength > 0
 
                // non-text run always fetched at run start
                &&  (   isText 
                    || textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0) 

                // span rider of both text and format point to valid position 
                &&  (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null),

                "Text run fetching error!"
                ); 

            return textRun; 
        } 
            /// <summary>
            /// Constructing a FullTextLine
            /// </summary>
            /// <param name="settings">text formatting settings</param>
            /// <param name="cpFirst">Line's first cp</param>
            /// <param name="lineLength">character length of the line</param>
            /// <param name="paragraphWidth">paragraph width</param>
            /// <param name="lineFlags">line formatting control flags</param>
            internal FullTextLine(
                FormatSettings          settings,
                int                     cpFirst,
                int                     lineLength,
                int                     paragraphWidth,
                LineFlags               lineFlags
                )
                : this(settings.TextFormattingMode, settings.Pap.Justify)
            {
                if (    (lineFlags & LineFlags.KeepState) != 0
                    ||  settings.Pap.AlwaysCollapsible)
                {
                    _statusFlags |= StatusFlags.KeepState;
                }

                int finiteFormatWidth = settings.GetFiniteFormatWidth(paragraphWidth);

                FullTextState fullText = FullTextState.Create(settings, cpFirst, finiteFormatWidth);

                // formatting the line
                FormatLine(
                    fullText,
                    cpFirst,
                    lineLength,
                    fullText.FormatWidth,
                    finiteFormatWidth,
                    paragraphWidth,
                    lineFlags,
                    null    // collapsingSymbol
                    );
            }
Example #9
0
        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.Value;
            }

            // 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.Value,  // 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.Get(SRID.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);
        }
Example #10
0
        /// <summary>
        /// Fetch cached textrun
        /// </summary>
        internal TextRun FetchTextRun(
            FormatSettings settings,
            int cpFetch,
            int cpFirst,
            out int offsetToFirstCp,
            out int runLength
            )
        {
            SpanRider textRunSpanRider = new SpanRider(_textRunVector, _latestPosition, cpFetch);

            _latestPosition = textRunSpanRider.SpanPosition;
            TextRun textRun = (TextRun)textRunSpanRider.CurrentElement;

            if (textRun == null)
            {
                // run not already cached, fetch new run and cache it

                textRun = settings.TextSource.GetTextRun(cpFetch);

                if (textRun.Length < 1)
                {
                    throw new ArgumentOutOfRangeException("textRun.Length", SR.Get(SRID.ParameterMustBeGreaterThanZero));
                }

                Plsrun plsrun = TextRunInfo.GetRunType(textRun);

                if (plsrun == Plsrun.Text || plsrun == Plsrun.InlineObject)
                {
                    TextRunProperties properties = textRun.Properties;

                    if (properties == null)
                    {
                        throw new ArgumentException(SR.Get(SRID.TextRunPropertiesCannotBeNull));
                    }

                    if (properties.FontRenderingEmSize <= 0)
                    {
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassMustBeGreaterThanZero, "FontRenderingEmSize", "TextRunProperties"));
                    }

                    double realMaxFontRenderingEmSize = Constants.RealInfiniteWidth / Constants.GreatestMutiplierOfEm;

                    if (properties.FontRenderingEmSize > realMaxFontRenderingEmSize)
                    {
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeGreaterThan, "FontRenderingEmSize", "TextRunProperties", realMaxFontRenderingEmSize));
                    }

                    CultureInfo culture = CultureMapper.GetSpecificCulture(properties.CultureInfo);

                    if (culture == null)
                    {
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "CultureInfo", "TextRunProperties"));
                    }

                    if (properties.Typeface == null)
                    {
                        throw new ArgumentException(SR.Get(SRID.PropertyOfClassCannotBeNull, "Typeface", "TextRunProperties"));
                    }
                }


                //
                // TextRun is specifial to SpanVector because TextRun also encodes position which needs to be
                // consistent with the positions encoded by SpanVector. In run cache, the begining of a span
                // should always correspond to the begining of a cached text run. If the end of the currently fetched
                // run overlaps with the begining of an already cached run, the begining of the cached run needs to be
                // adjusted as well as its span. Because we can't gurantee the correctness of the overlapped range
                // so we'll simply remove the overlapped runs here.
                //

                // Move the rider to the end of the current run
                textRunSpanRider.At(cpFetch + textRun.Length - 1);
                _latestPosition = textRunSpanRider.SpanPosition;

                if (textRunSpanRider.CurrentElement != _textRunVector.Default)
                {
                    // The end overlaps with one or more cached runs, clear the range from the
                    // begining of the current fetched run to the end of the last overlapped cached run.
                    _latestPosition = _textRunVector.SetReference(
                        cpFetch,
                        textRunSpanRider.CurrentPosition + textRunSpanRider.Length - cpFetch,
                        _textRunVector.Default,
                        _latestPosition
                        );
                }

                _latestPosition = _textRunVector.SetReference(cpFetch, textRun.Length, textRun, _latestPosition);

                // Refresh the rider's SpanPosition following previous SpanVector.SetReference calls
                textRunSpanRider.At(_latestPosition, cpFetch);
            }

            // If the TextRun was obtained from the cache, make sure it has the right PixelsPerDip set on its properties.

            if (textRun.Properties != null)
            {
                textRun.Properties.PixelsPerDip = settings.TextSource.PixelsPerDip;
            }

            offsetToFirstCp = textRunSpanRider.CurrentPosition - textRunSpanRider.CurrentSpanStart;
            runLength       = textRunSpanRider.Length;
            Debug.Assert(textRun != null && runLength > 0, "Invalid run!");

            bool isText = textRun is ITextSymbols;

            if (isText)
            {
                // Chop text run to optimal length so we dont spend forever analysing
                // them all at once.

                int looseCharLength = TextStore.TypicalCharactersPerLine - cpFetch + cpFirst;

                if (looseCharLength <= 0)
                {
                    // this line already exceeds typical line length, incremental fetch goes
                    // about a quarter of the typical length.

                    looseCharLength = (int)Math.Round(TextStore.TypicalCharactersPerLine * 0.25);
                }

                if (runLength > looseCharLength)
                {
                    if (TextRunInfo.GetRunType(textRun) == Plsrun.Text)
                    {
                        //
                        // When chopping the run at the typical line length,
                        // - don't chop in between of higher & lower surrogate
                        // - don't chop combining mark away from its base character
                        // - don't chop joiner from surrounding characters
                        //
                        // Starting from the initial chopping point, we look ahead to find a safe position. We stop at
                        // a limit in case the run consists of many combining mark & joiner. That is rare and doesn't make
                        // much sense in shaping already.
                        //

                        CharacterBufferReference charBufferRef = textRun.CharacterBufferReference;

                        // We look ahead by one more line at most. It is not normal to have
                        // so many combining mark or joiner characters in a row. It doesn't make sense to
                        // look further if so.
                        int lookAheadLimit = Math.Min(runLength, looseCharLength + TextStore.TypicalCharactersPerLine);

                        int  sizeOfChar = 0;
                        int  endOffset  = 0;
                        bool canBreakAfterPrecedingChar = false;

                        for (endOffset = looseCharLength - 1; endOffset < lookAheadLimit; endOffset += sizeOfChar)
                        {
                            CharacterBufferRange charString = new CharacterBufferRange(
                                charBufferRef.CharacterBuffer,
                                charBufferRef.OffsetToFirstChar + offsetToFirstCp + endOffset,
                                runLength - endOffset
                                );

                            int ch = Classification.UnicodeScalar(charString, out sizeOfChar);

                            // We can only safely break if the preceding char is not a joiner character (i.e. can-break-after),
                            // and the current char is not combining or joiner (i.e. can-break-before).
                            if (canBreakAfterPrecedingChar && !Classification.IsCombining(ch) && !Classification.IsJoiner(ch))
                            {
                                break;
                            }

                            canBreakAfterPrecedingChar = !Classification.IsJoiner(ch);
                        }

                        looseCharLength = Math.Min(runLength, endOffset);
                    }

                    runLength = looseCharLength;
                }
            }


            Debug.Assert(

                // valid run found
                runLength > 0

                // non-text run always fetched at run start
                && (isText ||
                    textRunSpanRider.CurrentSpanStart - textRunSpanRider.CurrentPosition == 0)

                // span rider of both text and format point to valid position
                && (textRunSpanRider.Length > 0 && textRunSpanRider.CurrentElement != null),

                "Text run fetching error!"
                );

            return(textRun);
        }
        /// <summary>
        /// Creating a lightweight text line
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="cpFirst">First cp of the line</param>
        /// <param name="paragraphWidth">paragraph width</param>
        /// <returns>TextLine instance</returns>
        /// <remarks>
        /// This method breaks line using Ideal width such that it will be
        /// consistent with FullTextLine
        /// </remarks>
        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;
            int nonHiddenLength = 0;    // length of non-hidden runs seen so far

            // paragraphWidth == 0 means the format width is unlimited.
            int widthLeft = (pap.Wrap && paragraphWidth > 0) ? paragraphWidth : int.MaxValue;
            int idealRunOffsetUnRounded = 0;

            SimpleRun prev = null;

            SimpleRun run = SimpleRun.Create(
                settings,
                cp,
                cpFirst,
                widthLeft,
                paragraphWidth,
                idealRunOffsetUnRounded
                );


            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;
                idealRunOffsetUnRounded += run.IdealWidth;
                prev = run;

                run = SimpleRun.Create(
                    settings,
                    cp,
                    cpFirst,
                    widthLeft,
                    paragraphWidth,
                    idealRunOffsetUnRounded
                    );

                if(run == null)
                {
                    return null;
                }
            }


            int trailing = 0;
            ArrayList runs = new ArrayList(2);

            if(prev != null)
            {
                AddRun(runs, prev, ref nonHiddenLength);
            }

            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, ref nonHiddenLength);

                // As a security mitigation, we impose a limit on the length of a single line
                // (see comments for TextStore.MaxCharactersPerLine) - only non-hidden
                // runs count against this limit.   If the line exceeds the limit,
                // use FullTextLine instead of SimpleTextLine - this assures consistency
                // in cases such as collapsing a line.
                if (nonHiddenLength >= TextStore.MaxCharactersPerLine)
                {
                    return null;
                }

                prev = run;
                cp += run.Length;
                widthLeft               -= run.IdealWidth;
                idealRunOffsetUnRounded += run.IdealWidth;

                if(run.EOT)
                {
                    // we're done
                    break;
                }

                run = SimpleRun.Create(
                    settings,
                    cp,
                    cpFirst,
                    widthLeft,
                    paragraphWidth,
                    idealRunOffsetUnRounded
                    );

                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;
        }
        /// <summary>
        /// Constructing a lightweight text line
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="cpFirst">line first cp</param>
        /// <param name="paragraphWidth">paragraph width</param>
        /// <param name="runs">collection of simple runs</param>
        /// <param name="trailing">line trailing spaces</param>
        /// <param name="trailingSpaceWidth">line trailing spaces width</param>
        /// <Remarks>
        /// SimpleTextLine is constructed with Ideal width such that the line breaking
        /// behavior is consistent with the FullTextLine
        /// </Remarks>
        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:
                        _idealOffsetUnRounded = paragraphWidth - idealWidthAtTrailing;
                        _offset = formatter.IdealToReal(_idealOffsetUnRounded);
                        break;
                    case TextAlignment.Center:
                        // exactly consistent with FullTextLine
                        _idealOffsetUnRounded = (int)Math.Round((paragraphWidth - idealWidthAtTrailing) * 0.5);
                        _offset = formatter.IdealToReal(_idealOffsetUnRounded);
                        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;
            }
        }
        /// <summary>
        /// Returns a simple text run that represents a Tab.
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="textRun">text run</param>
        /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param>
        static private SimpleRun CreateSimpleRunForTab(
            FormatSettings settings,
            TextRun textRun,
            int idealRunOffsetUnRounded
            )
        {
            if (settings == null || textRun == null || textRun.Properties == null || textRun.Properties.Typeface == null)
            {
                return null;
            }

            GlyphTypeface glyphTypeface = textRun.Properties.Typeface.TryGetGlyphTypeface();

            // Check whether the font has the space character. If not then we have to go through
            // font fallback.
            // We are not calling CreateSimpleTextRun() because CheckFastPathNominalGlyphs()
            // can fail if a font has TypographicAvailabilities. We are simply rendering a space
            // so we don't realy care about TypographicFeatures. This is a perf optimization.
            if (glyphTypeface == null || !glyphTypeface.HasCharacter(' '))
            {
                return null;
            }

            // The full shaping path converts tabs to spaces.
            // Note: In order to get exactly the same metrics as we did in FullTextLine (specifically ink bounding box)
            // we need to "Draw" a space in place of a Tab (previously we were just ignoring the Tab and rendering nothing)
            // which turned out to give different overhang and extent values than those returned using the full shaping path.
            // So in order to avoid vertical jiggling when a line is changed from SimpleTextLine to FullTextLine by adding/removing
            // a complex character, we need to do the same thing as the full shaping path and draw a space for each tab.
            TextRun modifedTextRun = new TextCharacters(" ", textRun.Properties);
            CharacterBufferRange characterBufferRange = new CharacterBufferRange(modifedTextRun);
            SimpleRun run = new SimpleRun(1, modifedTextRun, Flags.Tab, settings.Formatter);
            run.CharBufferReference = characterBufferRange.CharacterBufferReference;
            run.TextRun.Properties.Typeface.GetCharacterNominalWidthsAndIdealWidth(
                    characterBufferRange,
                    run.EmSize,
                    TextFormatterImp.ToIdeal,
                    settings.Formatter.TextFormattingMode,
                    false,
                    out run.NominalAdvances
                    );

            int idealIncrementalTab = TextFormatterImp.RealToIdeal(settings.Pap.DefaultIncrementalTab);

            // Here we get the next tab stop without snapping the metrics to pixels.
            // We do the pixel snapping on the final position of the tab stop (and not on the IncrementalTab)
            // to achieve the same results as those in full shaping.
            int idealNextTabStopUnRounded = ((idealRunOffsetUnRounded / idealIncrementalTab) + 1) * idealIncrementalTab;

            run.IdealWidth = run.NominalAdvances[0] = idealNextTabStopUnRounded - idealRunOffsetUnRounded;
            return run;
        }
        /// <summary>
        /// Creating a simple text run
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="charString">character string associated to textrun</param>
        /// <param name="textRun">text run</param>
        /// <param name="cp">first cp of the run</param>
        /// <param name="cpFirst">first cp of the line</param>
        /// <param name="runLength">run length</param>
        /// <param name="widthLeft">maximum run width</param>
        /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param>
        /// <returns>a SimpleRun object</returns>
        static public SimpleRun Create(
            FormatSettings          settings,
            CharacterBufferRange    charString,
            TextRun                 textRun,
            int                     cp,
            int                     cpFirst,
            int                     runLength,
            int                     widthLeft,
            int                     idealRunOffsetUnRounded
            )
        {
            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;
                }

                bool canProcessTabsInSimpleShapingPath = CanProcessTabsInSimpleShapingPath(
                                                                settings.Pap,
                                                                settings.Formatter.TextFormattingMode
                                                                );

                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);
                }
                else if (canProcessTabsInSimpleShapingPath && charString[0] == TextStore.CharTab)
                {
                    return CreateSimpleRunForTab(settings,
                                                 textRun,
                                                 idealRunOffsetUnRounded);
                }

                // attempt to create a simple run for text
                run = CreateSimpleTextRun(
                    charString,
                    textRun,
                    settings.Formatter,
                    widthLeft,
                    settings.Pap.EmergencyWrap,
                    canProcessTabsInSimpleShapingPath
                    );

                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;
        }
        /// <summary>
        /// Creating a simple text run
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="cp">first cp of the run</param>
        /// <param name="cpFirst">first cp of the line</param>
        /// <param name="widthLeft">maxium run width</param>
        /// <param name="widthMax">maximum column width</param>
        /// <param name="idealRunOffsetUnRounded">run's offset from the beginning of the line</param>
        /// <returns>a SimpleRun object</returns>
        static public SimpleRun Create(
            FormatSettings          settings,
            int                     cp,
            int                     cpFirst,
            int                     widthLeft,
            int                     widthMax,
            int                     idealRunOffsetUnRounded
            )
        {
            TextRun textRun;
            int runLength;

            CharacterBufferRange charBufferRange = settings.FetchTextRun(
                cp,
                cpFirst,
                out textRun,
                out runLength
                );

            return Create(
                settings,
                charBufferRange,
                textRun,
                cp,
                cpFirst,
                runLength,
                widthLeft,
                idealRunOffsetUnRounded
                );
        }
Example #16
0
        /// <summary>
        /// Creating a lightweight text line 
        /// </summary>
        /// <param name="settings">text formatting settings</param>
        /// <param name="cpFirst">First cp of the line</param>
        /// <param name="paragraphWidth">paragraph width</param> 
        /// <returns>TextLine instance</returns>
        /// <remarks> 
        /// This method breaks line using Ideal width such that it will be 
        /// consistent with FullTextLine
        /// </remarks> 
        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; 
        }