Beispiel #1
0
        /// <summary>
        ///     Word-wraps a given piece of CuteML, assuming that it is linearly flowing text. Newline (<c>\n</c>) characters can
        ///     be used to split the text into multiple paragraphs. The special <c>[+...]</c> tag marks text that may not be
        ///     broken by wrapping (effectively turning all spaces into non-breaking spaces).</summary>
        /// <typeparam name="TState">
        ///     The type of the text state that a tag can change, e.g. font or color.</typeparam>
        /// <param name="node">
        ///     The root node of the CuteML tree to word-wrap.</param>
        /// <param name="initialState">
        ///     The initial text state.</param>
        /// <param name="wrapWidth">
        ///     The maximum width at which to word-wrap. This width can be measured in any unit, as long as <paramref
        ///     name="measure"/> uses the same unit.</param>
        /// <param name="measure">
        ///     A delegate that measures the width of any piece of text.</param>
        /// <param name="render">
        ///     A delegate that is called whenever a piece of text is ready to be rendered.</param>
        /// <param name="advanceToNextLine">
        ///     A delegate that is called to advance to the next line.</param>
        /// <param name="nextState">
        ///     A delegate that determines how each CuteML tag character modifies the state (font, color etc.).</param>
        /// <returns>
        ///     The maximum width of the text.</returns>
        public static int WordWrap <TState>(CuteNode node, TState initialState, int wrapWidth,
                                            CuteMeasure <TState> measure, CuteRender <TState> render, CuteNextLine <TState> advanceToNextLine, CuteNextState <TState> nextState)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }
            if (wrapWidth <= 0)
            {
                throw new ArgumentException("Wrap width must be greater than zero.", nameof(wrapWidth));
            }
            var data = new CuteWalkData <TState>
            {
                AtStartOfLine       = true,
                WordPieces          = new List <string>(),
                WordPiecesState     = new List <TState>(),
                WordPiecesWidths    = new List <int>(),
                WordPiecesWidthsSum = 0,
                Measure             = measure,
                Render            = render,
                AdvanceToNextLine = advanceToNextLine,
                NextState         = nextState,
                X           = 0,
                WrapWidth   = wrapWidth,
                ActualWidth = 0
            };

            data.CuteWalkWordWrap(node, initialState);
            return(data.ActualWidth);
        }
Beispiel #2
0
            public void CuteWalkWordWrap(CuteNode node, TState initialState)
            {
                if (node == null)
                {
                    throw new ArgumentNullException(nameof(node));
                }

                cuteWalkWordWrapRecursive(node, initialState, false);

                if (WordPieces.Count > 0)
                {
                    renderPieces(initialState);
                }
            }
Beispiel #3
0
            private void cuteWalkWordWrapRecursive(CuteNode node, TState state, bool curNowrap)
            {
                if (node is CuteTag tag)
                {
                    var newState = state;
                    if (tag.Tag == '+')
                    {
                        curNowrap = true;
                    }
                    else if (tag.Tag != null)
                    {
                        var tup = NextState(state, tag.Tag.Value, tag.Attribute);
                        newState = tup.newState;
                        X       += tup.advance;
                    }
                    foreach (var child in tag.Children)
                    {
                        cuteWalkWordWrapRecursive(child, newState, curNowrap);
                    }
                }
                else if (node is CuteText text)
                {
                    var txt = text.Text;
                    for (int i = 0; i < txt.Length; i++)
                    {
                        // Check whether we are looking at a whitespace character or not, and if not, find the end of the word.
                        int lengthOfWord = 0;
                        while (lengthOfWord + i < txt.Length && (curNowrap || !char.IsWhiteSpace(txt, lengthOfWord + i)) && txt[lengthOfWord + i] != '\n')
                        {
                            lengthOfWord++;
                        }

                        if (lengthOfWord > 0)
                        {
                            // We are looking at a word. (It doesn’t matter whether we’re at the beginning of the word or in the middle of one.)
retry1:
                            string fragment = txt.Substring(i, lengthOfWord);
                            var fragmentWidth = Measure(state, fragment);
retry2:

                            // If we are at the start of a line, and the word itself doesn’t fit on a line by itself, we need to break the word up.
                            if (AtStartOfLine && X + WordPiecesWidthsSum + fragmentWidth > WrapWidth)
                            {
                                // We don’t know exactly where to break the word, so use binary search to discover where that is.
                                if (lengthOfWord > 1)
                                {
                                    lengthOfWord /= 2;
                                    goto retry1;
                                }

                                // If we get to here, ‘WordPieces’ contains as much of the word as fits into one line, and the next letter makes it too long.
                                // If ‘WordPieces’ is empty, we are at the beginning of a paragraph and the first letter already doesn’t fit.
                                if (WordPieces.Count > 0)
                                {
                                    // Render the part of the word that fits on the line and then move to the next line.
                                    renderPieces(state);
                                    advanceToNextLine(state, false);
                                }
                            }
                            else if (!AtStartOfLine && X + Measure(state, " ") + WordPiecesWidthsSum + fragmentWidth > WrapWidth)
                            {
                                // We have already rendered some text on this line, but the word we’re looking at right now doesn’t
                                // fit into the rest of the line, so leave the rest of this line blank and advance to the next line.
                                advanceToNextLine(state, false);

                                // In case the word also doesn’t fit on a line all by itself, go back to top (now that ‘AtStartOfLine’ is true)
                                // where it will check whether we need to break the word apart.
                                goto retry2;
                            }

                            // If we get to here, the current fragment fits on the current line (or it is a single character that overflows
                            // the line all by itself).
                            WordPieces.Add(fragment);
                            WordPiecesState.Add(state);
                            WordPiecesWidths.Add(fragmentWidth);
                            WordPiecesWidthsSum += fragmentWidth;
                            i += lengthOfWord - 1;
                            continue;
                        }

                        // We encounter a whitespace character. All the word pieces fit on the current line, so render them.
                        if (WordPieces.Count > 0)
                        {
                            renderPieces(state);
                            AtStartOfLine = false;
                        }

                        SpaceState = state;

                        if (txt[i] == '\n')
                        {
                            // If the whitespace character is actually a newline, start a new paragraph.
                            advanceToNextLine(state, true);
                        }
                        else if (AtStartOfLine)
                        {
                            // Otherwise, if we are at the beginning of the line, treat this space as the paragraph’s indentation.
                            CurParagraphIndent += renderSpace(state);
                        }
                    }
                }
                else
                {
                    throw new InvalidOperationException("A CuteNode is expected to be either CuteTag or CuteText, not {0}.".Fmt(node.GetType().FullName));
                }
            }