/// <summary>
        /// Inserts a text between two nodes
        /// </summary>
        internal static void InsertTextBetweenTwoNodes(XmlCursorPos cursorPos, System.Xml.XmlNode nodeBefore, System.Xml.XmlNode nodeAfter, string text, XmlRules xmlRules)
        {
            if (ToolboxXml.IsTextOrCommentNode(nodeBefore))  // if the node is already text before, then simply append to it
            {
                nodeBefore.InnerText += text;
                cursorPos.SetPos(nodeBefore, XmlCursorPositions.CursorInsideTextNode, nodeBefore.InnerText.Length);
            }
            else  // the node before is no text
            {
                if (ToolboxXml.IsTextOrCommentNode(nodeAfter))  // if the node behind it is already text then just paste it into
                {
                    nodeAfter.InnerText = $"{text}{nodeAfter.InnerText}";
                    cursorPos.SetPos(nodeAfter, XmlCursorPositions.CursorInsideTextNode, text.Length);
                }
                else // the node behind is also no text
                {
                    // Insert between two non-text nodes
                    if (xmlRules.IsThisTagAllowedAtThisPos("#PCDATA", cursorPos))
                    {
                        System.Xml.XmlText newTextNode = cursorPos.ActualNode.OwnerDocument.CreateTextNode(text);
                        InsertXmlNode(cursorPos, newTextNode, xmlRules, false);
                    }
                    else
                    {
#warning Insert another correct message or sound
                        Debug.Assert(false, "Error - Beep!");
                    }
                }
            }
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Optimizes the selected area
        /// </summary>
        public async Task OptimizeSelection()
        {
            // Define exchange buffer variables
            XmlCursorPositions dummyPos;
            int dummyTextPos;

            if (StartPos.ActualNode == null)
            {
                return;
            }

            // 1. if the start pos is behind the end pos, then swap both
            if (StartPos.ActualNode == EndPos.ActualNode)  // Both nodes are equal
            {
                if (StartPos.PosOnNode > EndPos.PosOnNode) //  If StartPos is within a node behind EndPos
                {
                    // exchange both positions at the same node
                    dummyPos     = StartPos.PosOnNode;
                    dummyTextPos = StartPos.PosInTextNode;
                    StartPos.SetPos(EndPos.ActualNode, EndPos.PosOnNode, EndPos.PosInTextNode);
                    EndPos.SetPos(EndPos.ActualNode, dummyPos, dummyTextPos);
                }
                else // StartPos was not behind Endpos
                {
                    // Is a text part within a text node selected ?
                    if ((StartPos.PosOnNode == XmlCursorPositions.CursorInsideTextNode) && (EndPos.PosOnNode == XmlCursorPositions.CursorInsideTextNode))
                    {                                                      // A part of a text node is selected
                        if (StartPos.PosInTextNode > EndPos.PosInTextNode) // If the TextStartpos is behind the TextEndpos, then change
                        {                                                  // Exchange text selection
                            dummyTextPos = StartPos.PosInTextNode;
                            StartPos.SetPos(StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, EndPos.PosInTextNode);
                            EndPos.SetPos(StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, dummyTextPos);
                        }
                    }
                }
            }
            else // Both nodes are not equal
            {
                // If the nodes are wrong in the order, then swap both
                if (ToolboxXml.Node1LaisBeforeNode2(EndPos.ActualNode, StartPos.ActualNode))
                {
                    var tempPos = this.StartPos.Clone();
                    this.StartPos.SetPos(this.EndPos.ActualNode, this.EndPos.PosOnNode, this.EndPos.PosInTextNode);
                    this.EndPos.SetPos(tempPos.ActualNode, tempPos.PosOnNode, tempPos.PosInTextNode);
                }

                // If the EndNode is in the StartNode, select the entire surrounding StartNode
                if (ToolboxXml.IsChild(EndPos.ActualNode, StartPos.ActualNode))
                {
                    await SetPositions(StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag, 0, throwChangedEventWhenValuesChanged : false);
                }

                // Find the first common parent of start and end and select the nodes in this height.
                // This leads to the fact that e.g. with LI elements and UL only the whole LI
                // is selected when dragging the selection over several LI and not only parts of it
                if (StartPos.ActualNode.ParentNode != EndPos.ActualNode.ParentNode) // if start and end are not directly in the same parent
                {
                    // - first find out which is the deepest common parent of start and end node
                    XmlNode commonParent = XmlCursorSelectionHelper.DeepestCommonParent(StartPos.ActualNode, EndPos.ActualNode);
                    // - then upscale start- and end-node to before the parent
                    XmlNode nodeStart = StartPos.ActualNode;
                    while (nodeStart.ParentNode != commonParent)
                    {
                        nodeStart = nodeStart.ParentNode;
                    }
                    XmlNode nodeEnde = EndPos.ActualNode;
                    while (nodeEnde.ParentNode != commonParent)
                    {
                        nodeEnde = nodeEnde.ParentNode;
                    }
                    // - finally show the new start and end nodes
                    StartPos.SetPos(nodeStart, XmlCursorPositions.CursorOnNodeStartTag);
                    EndPos.SetPos(nodeEnde, XmlCursorPositions.CursorOnNodeStartTag);
                }
            }
        }
Ejemplo n.º 3
0
        internal static async Task <bool> MoveLeft(XmlCursorPos cursorPos, System.Xml.XmlNode rootnode, XmlRules xmlRules)
        {
            var actualNode = cursorPos.ActualNode;

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:
            case XmlCursorPositions.CursorOnNodeEndTag:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:
                if (actualNode != rootnode)
                {
                    if (actualNode.PreviousSibling != null)
                    {
                        cursorPos.SetPos(actualNode.PreviousSibling, XmlCursorPositions.CursorBehindTheNode);
                        await MoveLeft(cursorPos, rootnode, xmlRules);
                    }
                    else     // no previous sibling node available
                    {
                        cursorPos.SetPos(actualNode.ParentNode, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                else
                {
                    return(false);
                }
                break;

            case XmlCursorPositions.CursorBehindTheNode:
                if (ToolboxXml.IsTextOrCommentNode(actualNode))     // With a text node the cursor is placed behind the last character
                {
                    cursorPos.SetPos(actualNode, XmlCursorPositions.CursorInsideTextNode, Math.Max(0, ToolboxXml.TextFromNodeCleaned(actualNode).Length - 1));
                }
                else
                {
                    if (actualNode.ChildNodes.Count == 0)
                    {
                        if (xmlRules.HasEndTag(actualNode))
                        {
                            //  If the cursor shows a close tag, place it in the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                        }
                        else
                        {
                            // If the cursor does *not* show a close tag, place it before the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                        }
                    }
                    else     // there are children in the node
                    {
                        cursorPos.SetPos(actualNode.LastChild, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                break;

            case XmlCursorPositions.CursorInsideTextNode:
                if (ToolboxXml.IsTextOrCommentNode(actualNode))     // Node is text node
                {
                    if (cursorPos.PosInTextNode > 1)
                    {      // Cursor one character to the left
                        cursorPos.SetPos(cursorPos.ActualNode, cursorPos.PosOnNode, cursorPos.PosInTextNode - 1);
                    }
                    else
                    {
                        // Put in front of the node
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                else     // not a text node
                {
                    throw new ApplicationException(string.Format("XMLCursorPos.MoveLeft: CursorPos is XMLCursorPositions.CursorInsideTextNodes, but no text node is selected, but the node {0}", actualNode.OuterXml));
                }
                break;

            default:
                throw new ApplicationException(String.Format("XMLCursorPos.MoveLeft: unknown CursorPos {0}", cursorPos.PosOnNode));
            }
            return(true);
        }
Ejemplo n.º 4
0
        internal static async Task <bool> MoveRight(XmlCursorPos cursorPos, System.Xml.XmlNode rootnode, XmlRules xmlRules)
        {
            System.Xml.XmlNode node = cursorPos.ActualNode;

            switch (cursorPos.PosOnNode)
            {
            case XmlCursorPositions.CursorOnNodeStartTag:
            case XmlCursorPositions.CursorOnNodeEndTag:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                break;

            case XmlCursorPositions.CursorBehindTheNode:
                if (node.NextSibling != null)
                {
                    // Place in front of the next sibling
                    cursorPos.SetPos(node.NextSibling, XmlCursorPositions.CursorInFrontOfNode);
                    // Since "behind the first" looks the same as "before the second", move one more step to the right
                    await MoveRight(cursorPos, rootnode, xmlRules);
                }
                else     // No following siblings available, then set behind the parent node
                {
                    if (node.ParentNode != rootnode)
                    {
                        cursorPos.SetPos(node.ParentNode, XmlCursorPositions.CursorBehindTheNode);
                        if (!xmlRules.HasEndTag(node.ParentNode))
                        {     // If no closed tag is displayed for the parent, then one more to the right
                            await MoveRight(cursorPos, rootnode, xmlRules);
                        }
                    }
                    else
                    {
                        return(false);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTheEmptyNode:
                cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                break;

            case XmlCursorPositions.CursorInFrontOfNode:
                if (ToolboxXml.IsTextOrCommentNode(node))                                                   // The node itself is text node
                {
                    if (ToolboxXml.TextFromNodeCleaned(node).Length > 1)                                    // Text node is not empty
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, 1); // one character forward, i.e. after the first character
                    }
                    else                                                                                    // Text node is empty
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                else      // Node is not a text node
                {
                    if (node.ChildNodes.Count == 0)
                    {
                        if (!xmlRules.HasEndTag(node))     // If no closed tag is displayed for this node, then directly behind the node
                        {
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                        }
                        else      // Node has closing tag, so put it in between
                        {
                            // Set to the empty node
                            cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                        }
                    }
                    else     // Children available
                    {
                        cursorPos.SetPos(node.FirstChild, XmlCursorPositions.CursorInFrontOfNode);
                    }
                }
                break;

            case XmlCursorPositions.CursorInsideTextNode:
                if (ToolboxXml.IsTextOrCommentNode(node))                                          // Node is text node
                {
                    if (ToolboxXml.TextFromNodeCleaned(node).Length > cursorPos.PosInTextNode + 1) // there is text in the text node on the right
                    {
                        // one character forward, i.e. after the first character
                        cursorPos.SetPos(cursorPos.ActualNode, cursorPos.PosOnNode, cursorPos.PosInTextNode + 1);

                        /*if ((XMLEditor.TextAusTextNodeBereinigt(node).Length == cursor.PosInNode) && (node.NextSibling != null))
                         * {
                         *  // If after the last drawing of the text node and following sibling exists, then before the following sibling node
                         * }*/
                    }
                    else      // no text follows in the text node
                    {
                        cursorPos.SetPos(cursorPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                    }
                }
                else     // Node is not a text node
                {
                    throw new ApplicationException(String.Format("XMLCurorPos.MoveRight: CursorPos is XMLCursorPositions.CursorInsideTextNodes, but no text node is selected, but the node {0}", node.OuterXml));
                }
                break;

            default:
                throw new ApplicationException(String.Format("XMLCurorPos.MoveRight: unknown CursorPos {0}", cursorPos.PosOnNode));
            }
            return(true);
        }
Ejemplo n.º 5
0
 /// <summary>
 ///  Checks whether the specified node lies before this cursor position
 /// </summary>
 public bool LiesBeforeThisPos(System.Xml.XmlNode node)
 {
     return(ToolboxXml.Node1LaisBeforeNode2(node, ActualNode));
 }
Ejemplo n.º 6
0
        /// <summary>
        /// Deletes the characters and nodes between StartPos and EndPos of the cursor
        /// </summary>
        internal static async Task <DeleteSelectionResult> DeleteSelection(XmlCursor cursor)
        {
            // If the cursor contains no selection at all
            if (!cursor.IsSomethingSelected)
            {
                return(new DeleteSelectionResult
                {
                    NewCursorPosAfterDelete = cursor.StartPos.Clone(), // Cursor is not changed
                    Success = false                                    // nothing deleted
                });
            }
            else
            {
                if (cursor.StartPos.ActualNode == cursor.EndPos.ActualNode) // If both nodes are identical
                {
                    switch (cursor.StartPos.PosOnNode)
                    {
                    case XmlCursorPositions.CursorOnNodeStartTag:
                    case XmlCursorPositions.CursorOnNodeEndTag:
                        // a single node is selected and should be deleted
                        System.Xml.XmlNode nodeDelete = cursor.StartPos.ActualNode; // This node should be deleted
                        System.Xml.XmlNode nodeBefore = nodeDelete.PreviousSibling; // This node is before the node to be deleted
                        System.Xml.XmlNode nodeAfter  = nodeDelete.NextSibling;     // This node lies behind the node to be deleted

                        var newPosAfterDelete = new XmlCursorPos();                 // This neighboring node will get the cursor after deletion

                        // If the node to be deleted is located between two text nodes, then these two text nodes are combined into one
                        if (nodeBefore != null && nodeAfter != null)
                        {
                            if (nodeBefore is System.Xml.XmlText && nodeAfter is System.Xml.XmlText)
                            {
                                // the node to be deleted lies between two text nodes, therefore these two text nodes are combined into one

                                // Afterwards, the cursor is positioned at the insertion point between the two text modules
                                newPosAfterDelete.SetPos(nodeBefore, XmlCursorPositions.CursorInsideTextNode, nodeBefore.InnerText.Length);

                                nodeBefore.InnerText += nodeAfter.InnerText;     // Append the text from after node to the before node

                                // Delete node to be deleted
                                nodeDelete.ParentNode.RemoveChild(nodeDelete);

                                // delete after node
                                nodeAfter.ParentNode.RemoveChild(nodeAfter);

                                return(new DeleteSelectionResult
                                {
                                    NewCursorPosAfterDelete = newPosAfterDelete,
                                    Success = true
                                });
                            }
                        }

                        // The node to be deleted is *not* between two text nodes

                        // Determine what should be selected after deletion

                        if (nodeBefore != null)
                        {
                            // After deletion, the cursor is positioned behind the previous node
                            newPosAfterDelete.SetPos(nodeBefore, XmlCursorPositions.CursorBehindTheNode);
                        }
                        else
                        {
                            if (nodeAfter != null)
                            {
                                // After deletion, the cursor is positioned before the following node
                                newPosAfterDelete.SetPos(nodeAfter, XmlCursorPositions.CursorInFrontOfNode);
                            }
                            else
                            {
                                // After deletion, the cursor is in the parent node
                                newPosAfterDelete.SetPos(nodeDelete.ParentNode, XmlCursorPositions.CursorInsideTheEmptyNode);
                            }
                        }

                        // delete the node
                        nodeDelete.ParentNode.RemoveChild(nodeDelete);
                        return(new DeleteSelectionResult
                        {
                            NewCursorPosAfterDelete = newPosAfterDelete,
                            Success = true
                        });

                    case XmlCursorPositions.CursorInFrontOfNode:
                        // Start and end of the deletion area point to the same node and the start is before the node: This only makes sense with a text node!
                        if (ToolboxXml.IsTextOrCommentNode(cursor.StartPos.ActualNode))
                        {
                            // Place the cursor in the text node before the first character and then resend
                            cursor.StartPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, 0);
                            return(await DeleteSelection(cursor));    // resend to delete
                        }
                        else
                        {
                            // if it is not a text node, then select the whole node and send it again
                            await cursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag);

                            return(await DeleteSelection(cursor));    // resend to delete
                        }

                    case XmlCursorPositions.CursorBehindTheNode:
                        // Start and end of the deletion area point to the same node and the start is behind the node
                        if (ToolboxXml.IsTextOrCommentNode(cursor.StartPos.ActualNode))
                        {
                            // Place the cursor in the text node before the first character and then resend
                            cursor.StartPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, cursor.StartPos.ActualNode.InnerText.Length);
                            return(await DeleteSelection(cursor));    // resend to delete
                        }
                        else
                        {
                            // if it is not a text node, then select the whole node and send it again
                            await cursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag);

                            return(await DeleteSelection(cursor));    // resend to delete
                        }

                    case XmlCursorPositions.CursorInsideTextNode:
                        // a part of a text node is to be deleted
                        // Determine the part of the text to be deleted
                        int startpos = cursor.StartPos.PosInTextNode;
                        int endpos   = cursor.EndPos.PosInTextNode;

                        if (cursor.EndPos.PosOnNode == XmlCursorPositions.CursorBehindTheNode)
                        {
                            // If the end of the selection is behind the text node, then all remaining text is selected
                            endpos = cursor.StartPos.ActualNode.InnerText.Length;
                        }

                        // If all text is selected, then delete the entire text node
                        if (startpos == 0 && endpos >= cursor.StartPos.ActualNode.InnerText.Length)
                        {
                            // The whole text node is to be deleted, this is passed on to the method for deleting individually selected nodes
                            XmlCursor nodeSelectedCursor = new XmlCursor();
                            await nodeSelectedCursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag);

                            return(await DeleteSelection(nodeSelectedCursor));
                        }
                        else
                        {
                            // Only a part of the text is to be deleted
                            string restText = cursor.StartPos.ActualNode.InnerText;
                            restText = restText.Remove(startpos, endpos - startpos);
                            cursor.StartPos.ActualNode.InnerText = restText;

                            // determine where the cursor is after deletion
                            newPosAfterDelete = new XmlCursorPos();
                            if (startpos == 0)     // The cursor is positioned before the first character
                            {
                                // then it can better be placed before the text node itself
                                newPosAfterDelete.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                            }
                            else
                            {
                                newPosAfterDelete.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorInsideTextNode, startpos);
                            }

                            return(new DeleteSelectionResult
                            {
                                NewCursorPosAfterDelete = newPosAfterDelete,
                                Success = true
                            });
                        }

                    case XmlCursorPositions.CursorInsideTheEmptyNode:
                        if (cursor.EndPos.PosOnNode == XmlCursorPositions.CursorBehindTheNode ||
                            cursor.EndPos.PosOnNode == XmlCursorPositions.CursorInFrontOfNode)
                        {
                            XmlCursor newCursor = new XmlCursor();
                            await newCursor.SetBothPositionsAndFireChangedEventIfChanged(cursor.StartPos.ActualNode, XmlCursorPositions.CursorOnNodeStartTag, 0);

                            return(await DeleteSelection(newCursor));
                        }
                        else
                        {
                            throw new ApplicationException($"DeleteSelection:#6363S undefined Endpos '{cursor.EndPos.PosOnNode}'!");
                        }

                    default:
                        //  what else should be selected besides text and the node itself, if start node and end node are identical?
                        throw new ApplicationException($"DeleteSelection:#63346 StartPos.PosAmNode '{cursor.StartPos.PosOnNode}' not allowed!");
                    }
                }
                else // Both nodes are not identical
                {
                    // If both nodes are not identical, then remove all nodes in between until the two nodes are behind each other
                    while (cursor.StartPos.ActualNode.NextSibling != cursor.EndPos.ActualNode)
                    {
                        cursor.StartPos.ActualNode.ParentNode.RemoveChild(cursor.StartPos.ActualNode.NextSibling);
                    }

                    // delete the endnode or a part of it
                    XmlCursor temp = cursor.Clone();
                    temp.StartPos.SetPos(cursor.EndPos.ActualNode, XmlCursorPositions.CursorInFrontOfNode);
                    await DeleteSelection(temp);

                    // delete the start node, or a part of it
                    // -> Done by recursion in the selection delete method
                    cursor.EndPos.SetPos(cursor.StartPos.ActualNode, XmlCursorPositions.CursorBehindTheNode);
                    return(await DeleteSelection(cursor));
                }
            }
        }
Ejemplo n.º 7
0
        protected override async Task <PaintContext> PaintInternal(PaintContext paintContext, bool cursorBlinkOn, XmlCursor cursor, IGraphics gfx, PaintModes paintMode, int depth)
        {
            paintContext.PaintPosX += 3;

            var actualText      = ToolboxXml.TextFromNodeCleaned(XmlNode);
            var selection       = this.CalculateStartAndEndOfSelection(actualText, cursor);
            var actualPaintData = LastPaintingDataText.CalculateActualPaintData(paintContext, cursorBlinkOn, this.XmlNode, actualText, this.Config.FontTextNode.Height, cursor, selection.Start, selection.Length);

            var alreadyUnpainted = false;

            switch (paintMode)
            {
            case PaintModes.ForcePaintNoUnPaintNeeded:
                alreadyUnpainted   = true;
                this.lastPaintData = null;
                break;

            case PaintModes.ForcePaintAndUnpaintBefore:
                alreadyUnpainted   = true;
                this.lastPaintData = null;
                this.UnPaint(gfx);
                break;

            case PaintModes.OnlyPaintWhenChanged:
                if (!actualPaintData.Equals(lastPaintData))
                {
                    this.lastPaintData = null;
                }
                break;
            }

            if (this.lastPaintData != null && this.lastPaintContextResult != null)
            {
                return(lastPaintContextResult.Clone());
            }

            this.lastPaintData  = actualPaintData;
            this.cursorPaintPos = null;

            if (lastFontHeight != this.Config.FontTextNode.Height)
            {
                lastFontHeight          = this.Config.FontTextNode.Height;
                lastCalculatedFontWidth = await this.xmlEditor.NativePlatform.Gfx.MeasureDisplayStringWidthAsync("W", this.Config.FontTextNode);
            }

            paintContext.HeightActualRow = Math.Max(paintContext.HeightActualRow, this.Config.MinLineHeight);

            int marginY = (paintContext.HeightActualRow - this.Config.FontTextNode.Height) / 2;

            const int charMarginRight = 2;

            var textPartsRaw = TextSplitHelper.SplitText(
                text: actualText,
                invertStart: selection.Start,
                invertLength: selection.Length,
                maxLength: (int)((paintContext.LimitRight - paintContext.LimitLeft) / lastCalculatedFontWidth) - charMarginRight,
                maxLengthFirstLine: (int)((paintContext.LimitRight - paintContext.PaintPosX) / lastCalculatedFontWidth) - charMarginRight)
                               .ToArray();

            var newTextParts = this.GetTextLinesFromTextParts(textPartsRaw, paintContext, cursorBlinkOn, cursor, lastCalculatedFontWidth).ToArray();

            // Now draw the content, if necessary wrap to several text parts and lines
            for (int i = 0; i < newTextParts.Length; i++)
            {
                var newPart = newTextParts[i];
                var oldPart = (this.textParts != null && i < this.textParts.Length) ? this.textParts[i] : null;
                if (alreadyUnpainted == false && newPart.Equals(oldPart))
                {
                    // no need to paint the text part again
                }
                else
                {
                    // draw the inverted background
                    if (!alreadyUnpainted && oldPart != null)
                    {
                        gfx.UnPaintRectangle(oldPart.Rectangle);
                    }

                    if (newPart.Inverted || this.colorBackground != this.Config.ColorBackground)
                    {
                        gfx.AddJob(new JobDrawRectangle
                        {
                            Batchable = true,
                            Layer     = GfxJob.Layers.TagBackground,
                            Rectangle = newPart.Rectangle,
                            FillColor = newPart.Inverted ? this.colorBackground.InvertedColor : this.colorBackground,
                        });
                    }

                    // draw the text
                    gfx.AddJob(new JobDrawString
                    {
                        Batchable = false,
                        Layer     = GfxJob.Layers.Text,
                        Text      = newPart.Text,
                        Color     = newPart.Inverted ? this.colorText.InvertedColor : this.colorText,
                        X         = newPart.Rectangle.X,
                        Y         = newPart.Rectangle.Y + marginY,
                        Font      = Config.FontTextNode
                    });;
                }
                paintContext.PaintPosY = newPart.Rectangle.Y;
                paintContext.PaintPosX = newPart.Rectangle.X + newPart.Rectangle.Width;
                paintContext.FoundMaxX = Math.Max(paintContext.FoundMaxX, paintContext.PaintPosX);
            }

            if (this.textParts != null) // unpaint old text parts out of new parts range
            {
                for (int i = newTextParts.Length; i < this.textParts.Length; i++)
                {
                    gfx.UnPaintRectangle(this.textParts[i].Rectangle);
                }
            }

            this.textParts = newTextParts;

            paintContext.PaintPosX += 2;

            this.lastPaintContextResult = paintContext.Clone();
            return(paintContext.Clone());
        }