void CreateTextRuns(char[] textBuffer, List <CssRun> runlist, int startIndex, int appendLength)
 {
     //this may produce more than 1 css text run
     if (TextBreaker != null)
     {
         //use text break to parse more
         breakAtList.Clear();
         TextBreaker.DoBreak(textBuffer, startIndex, appendLength, breakAtList);
         int j   = breakAtList.Count;
         int pos = startIndex;
         for (int i = 0; i < j; ++i)
         {
             int sepAt = breakAtList[i];
             runlist.Add(
                 CssTextRun.CreateTextRun(pos, sepAt - pos));
             pos = sepAt;
         }
         breakAtList.Clear();
         //TextBreaker.DoBreak(textBuffer, startIndex, appendLength, bounds =>
         //{
         //    //iterate new split
         //    runlist.Add(
         //        CssTextRun.CreateTextRun(startIndex + bounds.startIndex, bounds.length));
         //});
     }
     else
     {
         runlist.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
     }
 }
Example #2
0
        void AddToRunList(char[] textBuffer, List <CssRun> runlist, int startIndex, int appendLength, ref bool needICUSplitter)
        {
            if (needICUSplitter)
            {
                //use icu splitter
                //copy text buffer to icu ***
                var parts = Icu.BreakIterator.GetSplitBoundIter(Icu.BreakIterator.UBreakIteratorType.WORD,
                                                                icuLocal, textBuffer, startIndex, appendLength);

                //iterate new split
                foreach (var bound in parts)
                {
                    runlist.Add(
                        CssTextRun.CreateTextRun(startIndex + bound.startIndex, bound.length));
                }

                needICUSplitter = false;//reset
            }
            else
            {
                runlist.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
            }
        }
        internal void ParseWordContent(
            char[] textBuffer,
            BoxSpec spec,
            bool isBlock,
            List <CssRun> runlistOutput,
            out bool hasSomeCharacter)
        {
            bool preserverLine      = false;
            bool preserveWhiteSpace = false;
            var  isblock            = spec.CssDisplay == CssDisplay.Block;

            switch (spec.WhiteSpace)
            {
            case CssWhiteSpace.Pre:
            case CssWhiteSpace.PreWrap:
                //run and preserve whitespace
                preserveWhiteSpace = true;
                preserverLine      = true;
                break;

            case CssWhiteSpace.PreLine:
                preserverLine = true;
                break;
            }

            //---------------------------------------
            //1. check if has some text
            //--------------------------------------

            hasSomeCharacter = false;
            //--------------------------------------
            //just parse and preserve all whitespace
            //--------------------------------------
            int startIndex = 0;
            int buffLength = textBuffer.Length;
            //whitespace and respect newline
            WordParsingState parsingState = WordParsingState.Init;
            int appendLength = 0;
            //--------------------------------------

            //some text
            bool needFinerTextParser = false;

            for (int i = 0; i < buffLength; ++i)
            {
                char c0 = textBuffer[i];
                if (c0 == '\r')
                {
                    continue;
                }
                //----------------------------------------
                //switch by state
                switch (parsingState)
                {
                case WordParsingState.Init:
                {
                    if (char.IsWhiteSpace(c0))
                    {
                        if (c0 == '\n')
                        {
                            if (preserverLine)
                            {
                                runlistOutput.Add(CssTextRun.CreateLineBreak());
                                appendLength = 0;         //clear append length
                                continue;
                            }
                        }
                        parsingState = WordParsingState.Whitespace;
                        //start collect whitespace
                        startIndex   = i;
                        appendLength = 1;        //start collect whitespace
                    }
                    else if (char.IsPunctuation(c0))
                    {
                        parsingState = WordParsingState.Init;
                        startIndex   = i;
                        appendLength = 1;
                        //add single token
                        runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
                    }
                    else
                    {
                        //character
                        if (c0 > '~')
                        {
                            needFinerTextParser = true;
                        }

                        parsingState = WordParsingState.CharacterCollecting;
                        startIndex   = i;
                        appendLength = 1;        //start collect whitespace
                    }
                }
                break;

                case WordParsingState.Whitespace:
                {
                    if (char.IsWhiteSpace(c0))
                    {
                        if (c0 == '\n')
                        {
                            if (preserverLine)
                            {
                                runlistOutput.Add(CssTextRun.CreateLineBreak());
                                appendLength = 0;         //clear append length
                                continue;
                            }
                        }
                        if (appendLength == 0)
                        {
                            startIndex = i;
                        }

                        appendLength++;
                    }
                    else
                    {
                        runlistOutput.Add(CssTextRun.CreateWhitespace(preserveWhiteSpace ? appendLength : 1));
                        if (char.IsPunctuation(c0))
                        {
                            parsingState = WordParsingState.Init;
                            startIndex   = i;
                            appendLength = 1;
                            //add single token
                            runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
                        }
                        else
                        {
                            if (c0 > '~')
                            {
                                needFinerTextParser = true;
                            }

                            parsingState = WordParsingState.CharacterCollecting;
                            startIndex   = i;      //start collect
                            appendLength = 1;      //start append length
                        }
                    }
                }
                break;

                case WordParsingState.CharacterCollecting:
                {
                    bool isWhiteSpace;
                    if ((isWhiteSpace = char.IsWhiteSpace(c0)) || char.IsPunctuation(c0))
                    {
                        //flush collecting token
                        if (needFinerTextParser && TextBreaker != null)
                        {
                            CreateTextRuns(textBuffer, runlistOutput, startIndex, appendLength);
                            needFinerTextParser = false;        //reset
                        }
                        else
                        {
                            runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
                        }

                        hasSomeCharacter = true;
                        startIndex       = i;  //start collect
                        appendLength     = 1;  //collect whitespace
                        if (isWhiteSpace)
                        {
                            if (c0 == '\n')
                            {
                                if (preserverLine)
                                {
                                    runlistOutput.Add(CssTextRun.CreateLineBreak());
                                    appendLength = 0;         //clear append length
                                    continue;
                                }
                            }
                            parsingState = WordParsingState.Whitespace;
                        }
                        else
                        {
                            parsingState = WordParsingState.Init;
                            runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
                        }
                        //----------------------------------------
                    }
                    else
                    {
                        if (c0 > '~')
                        {
                            needFinerTextParser = true;
                        }

                        if (appendLength == 0)
                        {
                            startIndex = i;
                        }
                        appendLength++;
                    }
                }
                break;
                }
            }
            //--------------------

            if (appendLength > 0)
            {
                switch (parsingState)
                {
                case WordParsingState.Whitespace:
                {
                    //last with whitespace
                    if (preserveWhiteSpace)
                    {
                        runlistOutput.Add(CssTextRun.CreateWhitespace(appendLength));
                    }
                    else
                    {
                        if (!isblock || (runlistOutput.Count > 0))
                        {
                            runlistOutput.Add(CssTextRun.CreateWhitespace(1));
                        }
                    }
                }
                break;

                case WordParsingState.CharacterCollecting:
                {
                    if (needFinerTextParser && TextBreaker != null)
                    {
                        CreateTextRuns(textBuffer, runlistOutput, startIndex, appendLength);
                        needFinerTextParser = false;        //reset
                    }
                    else
                    {
                        runlistOutput.Add(CssTextRun.CreateTextRun(startIndex, appendLength));
                    }

                    hasSomeCharacter = true;
                }
                break;
                }
            }
        }
Example #4
0
        /*
         * Docs: https://www.w3.org/TR/css-display-3/#intro
         * Docs: https://www.w3.org/TR/CSS22/visuren.html#box-gen
         */

        /// <summary>
        /// Generates an appropriate CSS principal box object for the given element, populated with any appropriate child boxes
        /// </summary>
        /// <param name="E"></param>
        /// <returns></returns>
        public static void Generate_Tree(Node StartNode, Node EndNode = null)
        {
            if (StartNode is null)
            {
                throw new ArgumentNullException(nameof(StartNode));
            }

            if (!StartNode.GetFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate))
            {
                throw new ArgumentException($"Neither {nameof(StartNode)} nor its children are flagged for box updates.");
            }

            Contract.EndContractBlock();

            /* This is the method we use to populate the tree from a given point onwards:
             * 1) While queue is not empty, Pop next node.
             * 2) If next node == End then break;
             * 3) Delete all box-nodes in chain to the nodes real principal-box parent.
             * 4) Generate a new principal-box or text-run.
             * 5) Insert the new box-node into tree.
             * 6) Queue all children of element.
             */

            var Queue = new Queue <Node>();

            Queue.Enqueue(StartNode);
            while (Queue.Count > 0)
            {
                Node node = Queue.Dequeue();
                if (ReferenceEquals(node, EndNode))
                {
                    break;
                }

                if (node.GetFlag(ENodeFlags.NeedsBoxUpdate))
                {
                    CssBoxTreeNode Box             = node.Box;
                    Element        nearestAncestor = Get_Closest_Box_Generating_Ancestor(node);

                    // 3) Delete the nodes current box
                    // 3) Unlink all box-nodes in the chain leading to the nodes real principal-box parent
                    // The current box might be wrapped in an anonymous box. in which case the index we WANT is actually THAT boxs'
                    int index = -1;
                    if (Box is object)
                    {
                        var ChainRoot = Box.Unlink(nearestAncestor.Box);
                        index = ChainRoot.index;
                    }

                    CssBoxTreeNode nextBox = null;
                    // 4) Generate a new principal-box or text-run
                    switch (node.nodeType)
                    {
                    case DOM.Enums.ENodeType.TEXT_NODE:
                    {
                        // Texts runs encompass all of the contiguous sibling text-nodes, skip those contiguous nodes in the queue
                        var TextNodes = new List <Text>(node.parentNode.childNodes.Count);
                        while (Queue.Peek().nodeType == DOM.Enums.ENodeType.TEXT_NODE && ReferenceEquals(Queue.Peek().parentNode, node.parentNode))
                        {
                            TextNodes.Add((Text)Queue.Dequeue());
                        }

                        nextBox = new CssTextRun(TextNodes.ToArray());
                    }
                    break;

                    case DOM.Enums.ENodeType.ELEMENT_NODE:
                    {
                        nextBox = Generate_Box((Element)node);
                    }
                    break;
                    }

                    // Transfer child nodes from old box to the new box (they will remove themselves if needed)
                    ITreeNode current = Box.firstChild;
                    while (current is object)
                    {
                        current.parentNode = null;
                        nextBox.childNodes.Add(current);
                        current = current.nextSibling;
                    }

                    // 5) Insert the new box-node into tree
                    if (index > -1)
                    {
                        nearestAncestor.Box.Insert(index, nextBox);
                    }
                    else
                    {
                        nearestAncestor.Box.Add(nextBox);
                    }

                    // Notify the tree that we need to be reflowed
                    node.Propagate_Flag(ENodeFlags.ChildNeedsReflow, exclude_self: true);
                }

                node.ClearFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate);
                // 6) Queue all children of node.
                foreach (var n in node.childNodes)
                {
                    // Only add items which we KNOW will need an update
                    if (n.GetFlag(ENodeFlags.NeedsBoxUpdate | ENodeFlags.ChildNeedsBoxUpdate))
                    {
                        Queue.Enqueue(n);
                    }
                }
            }
        }