Exemple #1
0
        /// <summary>Gets the next/previous suitable newline index.</summary>
        public int FindNewline(int direction)
        {
            // Get the text content:
            RenderableTextNode htn = TextHolder;

            if (htn == null || htn.RenderData.Text == null || htn.RenderData.Text.Characters == null)
            {
                return(CaretIndex);
            }

            // Get the renderer so we can access the chars:
            TextRenderingProperty trp = htn.RenderData.Text;

            // First index to check is..
            int index = CaretIndex + direction;

            // Safety check:
            int max = trp.Characters.Length;

            if (index >= max)
            {
                index = max - 1;
            }
            else if (index < 0)
            {
                index = 0;
            }

            while (index > 0 && index < max)
            {
                // Check if char[index] is a newline:
                InfiniText.Glyph glyph = trp.Characters[index];

                if (glyph != null && glyph.Charcode == (int)'\n')
                {
                    // Got it!
                    if (direction == 1)
                    {
                        index++;
                    }
                    break;
                }

                // Next one:
                index += direction;
            }

            return(index);
        }
        /// <summary>Finds the index of the nearest character to x pixels.</summary>
        /// <param name="x">The number of pixels from the left edge of the screen.</param>
        public int LetterIndex(float x, LayoutBox box)
        {
            if (box == null)
            {
                // Nope!
                return(0);
            }

            // Get the text renderer:
            TextRenderingProperty trp = RenderData_.Text;

            if (trp == null)
            {
                // It's not been rendered at all yet.
                return(0);
            }

            // Walk the characters in the box until we've walked at least x units.

            float left     = box.X;
            float fontSize = trp.FontSize;

            for (int i = box.TextStart; i < box.TextEnd; i++)
            {
                // Get the char:
                InfiniText.Glyph glyph = trp.Characters[i];

                if (glyph == null)
                {
                    continue;
                }

                // Move width along:
                left += glyph.AdvanceWidth * fontSize;

                if (left >= x)
                {
                    // Got it!
                    return(i);
                }

                // Advance over spacing:
                left += trp.LetterSpacing;
            }

            // End of the box.
            return(box.TextEnd);
        }
        internal override void Layout(LayoutBox box, Renderman renderer)
        {
            // Does the boxes text region overlap our selected range?
            if (box.TextEnd <= StartIndex || EndIndex <= box.TextStart)
            {
                return;
            }

            // It overlaps! We need to figure out which section we're selecting (and how large it is).

            // Get the top left inner corner (inside margin and border):
            // Assume we've selected the whole thing; we remove from the start and remove from the end if needed.
            float width  = box.PaddedWidth;
            float height = box.PaddedHeight;
            float top    = box.Y + box.Border.Top;
            float left   = box.X + box.Border.Left;
            float fs     = Text.FontSize;

            // If the selected indices totally contain the box..
            if (StartIndex > box.TextStart)
            {
                // Trim the start. We need to chop off the width of these characters.

                float characterWidth = 0f;

                int max = StartIndex;

                if (max > Text.Characters.Length)
                {
                    max = Text.Characters.Length;
                }

                for (int i = box.TextStart; i < max; i++)
                {
                    InfiniText.Glyph character = Text.Characters[i];

                    if (character == null)
                    {
                        continue;
                    }

                    // Advance over the glyph:
                    if (Text.Kerning != null)
                    {
                        characterWidth += Text.Kerning[i] * fs;
                    }

                    characterWidth += (character.AdvanceWidth * fs) + Text.LetterSpacing;

                    if (character.Charcode == (int)' ')
                    {
                        characterWidth += Text.WordSpacing;
                    }
                }

                // Chop it off:
                left  += characterWidth;
                width -= characterWidth;
            }

            if (EndIndex < box.TextEnd)
            {
                // Trim the end. We need to chop off the width of these characters.

                float characterWidth = 0f;

                int max = box.TextEnd;

                if (max > Text.Characters.Length)
                {
                    max = Text.Characters.Length;
                }

                for (int i = EndIndex; i < max; i++)
                {
                    InfiniText.Glyph character = Text.Characters[i];

                    if (character == null)
                    {
                        continue;
                    }

                    // Advance over the glyph:
                    if (Text.Kerning != null)
                    {
                        characterWidth += Text.Kerning[i] * fs;
                    }

                    characterWidth += (character.AdvanceWidth * fs) + Text.LetterSpacing;

                    if (character.Charcode == (int)' ')
                    {
                        characterWidth += Text.WordSpacing;
                    }
                }

                // Chop it off:
                width -= characterWidth;
            }

            // Is it clipped?
            if (renderer.IsInvisible(left, top, width, height))
            {
                // Totally not visible.
                return;
            }

            // Ensure we have a batch (doesn't change graphics or font thus both nulls):
            renderer.SetupBatch(this, null, null);

            // Allocate the block:
            MeshBlock block = Add(renderer);

            // Using firstblock as our block here.
            // Set the UV to that of the solid block colour pixel:
            block.SetSolidColourUV();
            // Set the colour:
            block.SetColour(BaseColour);

            // And finally sort out the verts:
            block.SetClipped(renderer.ClippingBoundary, new BoxRegion(left, top, width, height), renderer, RenderData.computedStyle.ZIndex - 0.004f);

            // Flush it:
            block.Done(renderer.Transform);
        }
        /// <summary>Part of shrink-to-fit. Computes the maximum and minimum possible width for an element.</summary>
        public void GetWidthBounds(out float min, out float max)
        {
            // Get the text renderer (or create it):
            Css.TextRenderingProperty text = RenderData_.RequireTextProperty();

            if (text.AllEmpty)
            {
                min = 0f;
                max = 0f;
                return;
            }

            // Need to compute TAW?
            if (TotalAdvanceWidth == float.MinValue)
            {
                float width      = 0f;
                int   spaceCount = 0;
                float wordWidth  = 0f;
                LongestWord = 0f;

                for (int i = 0; i < text.Characters.Length; i++)
                {
                    // Get the glyph:
                    InfiniText.Glyph glyph = text.Characters[i];

                    if (glyph == null)
                    {
                        // Skip!
                        continue;
                    }

                    // The glyph's width is..
                    float gWidth = glyph.AdvanceWidth + text.LetterSpacing;
                    wordWidth += gWidth;
                    width     += gWidth;

                    // Got a space?
                    if (glyph.Charcode == (int)' ')
                    {
                        if (wordWidth > LongestWord)
                        {
                            LongestWord = wordWidth;
                            wordWidth   = 0f;
                        }

                        // Advance width:
                        spaceCount += 1;
                    }
                }

                if (wordWidth > LongestWord)
                {
                    LongestWord = wordWidth;
                }

                TotalAdvanceWidth = width;
                SpaceCount        = spaceCount;
            }

            min = LongestWord * text.FontSize;
            max = (TotalAdvanceWidth * text.FontSize) + (SpaceCount * text.WordSpacing);
        }
        /// <summary>Gets the relative position (relative to parent) in pixels of the letter at the given index.</summary>
        /// <param name="index">The index of the letter in this text element.</param>
        /// <returns>The number of pixels from the left and top edges of this text element the letter is as a vector.</returns>
        public Vector2 GetPosition(int index)
        {
            // Get the text renderer:
            TextRenderingProperty trp = RenderData.Text;

            // Get the box that contains the given text index.
            LayoutBox box = RenderData.FirstBox;

            if (trp == null || box == null || trp.Characters == null)
            {
                // It's not been rendered at all yet.
                return(Vector2.zero);
            }

            LayoutBox previous = null;

            while (box != null)
            {
                // Note that start is inclusive, end is not.
                if (index < box.TextStart)
                {
                    // Use the previous box (this allows us to catch newlines):
                    if (previous != null)
                    {
                        box = previous;
                    }

                    break;
                }

                // Next one:
                previous = box;
                box      = box.NextInElement;
            }

            if (box == null)
            {
                // Use the last box:
                box = RenderData.LastBox;

                // Check if index goes beyond the end:
                if (index > box.TextEnd)
                {
                    index = box.TextEnd;
                }
            }

            // Relative to the given box:
            float top  = box.ParentOffsetTop + box.Border.Top;
            float left = box.ParentOffsetLeft + box.Border.Left;
            float fs   = trp.FontSize;

            for (int i = box.TextStart; i < index; i++)
            {
                InfiniText.Glyph character = trp.Characters[i];

                if (character == null)
                {
                    continue;
                }

                // Advance over the glyph:
                if (trp.Kerning != null)
                {
                    left += trp.Kerning[i] * fs;
                }

                left += (character.AdvanceWidth * fs) + trp.LetterSpacing;

                if (character.Charcode == (int)' ')
                {
                    left += trp.WordSpacing;
                }
            }

            // Done!
            return(new Vector2(left, top));
        }
        public override void Reflow(Renderman renderer)
        {
            // Get the text renderer (or create it):
            Css.TextRenderingProperty text = RequireTextProperty();

            if (text.Characters == null || text.AllEmpty)
            {
                return;
            }

            LayoutBox box = null;

            // Get the baseline offset:
            float baseline = text.FontSize * text.FontToDraw.Descender;

            // Compute our line boxes based on text.Characters and the available space.
            // Safely ignore direction here because either way the selected characters are the same.
            // Note that things like first-letter are also considered.

            // Get the top of the stack:
            LineBoxMeta lbm           = renderer.TopOfStack;
            float       cssLineHeight = lbm.CssLineHeight;

            // Is it justify (if so, every word is its own box):
            bool breakAllWords = (lbm.HorizontalAlign == HorizontalAlignMode.Justify);

            // Offset the baseline:
            float lineHeightOffset = (cssLineHeight - text.FontSize) / 2f;

            text.LineHeightOffset = lineHeightOffset;
            baseline += lineHeightOffset;

            float wordWidth        = 0f;
            float boxWidth         = 0f;
            bool  wrappable        = ((lbm.WhiteSpace & WhiteSpaceMode.Wrappable) != 0);
            int   i                = 0;
            int   latestBreakpoint = -1;
            // bidi text direction (0 = Not set yet, UnicodeBidiMode.LeftwardsNormal, UnicodeBidiMode.RightwardsNormal)
            int direction = 0;
            // Direction before the current block of weak chars.
            int beforeWeak = 0;


            while (i < text.Characters.Length)
            {
                // Get the glyph:
                InfiniText.Glyph glyph = text.Characters[i];

                if (glyph == null)
                {
                    // Skip!
                    i++;
                    continue;
                }

                // The glyph's width is..
                float width = (glyph.AdvanceWidth * text.FontSize) + text.LetterSpacing;

                if (box == null)
                {
                    // The box always has an inner height of 'font size':
                    if (renderer.FirstLetter != null)
                    {
                        // Clear FL immediately (so it can't go recursive):
                        SparkInformerNode firstLetter = renderer.FirstLetter;
                        renderer.FirstLetter = null;

                        // Update its internal text node:
                        RenderableTextNode textNode = firstLetter.firstChild as RenderableTextNode;

                        // Note that we have to do it this way as the node might
                        // change the *font*.
                        textNode.characterData_ = ((char)glyph.Charcode) + "";
                        Css.TextRenderingProperty textData = textNode.RenderData.RequireTextProperty();
                        textData.Dirty = true;

                        // Ask it to reflow right now (must ask the node so it correctly takes the style into account):
                        firstLetter.RenderData.UpdateCss(renderer);
                        firstLetter.RenderData.Reflow(renderer);

                        i++;
                        continue;
                    }

                    // Create the box now:
                    box = new LayoutBox();
                    box.PositionMode = PositionMode.Static;
                    box.DisplayMode  = DisplayMode.Inline;
                    box.Baseline     = baseline;
                    box.TextStart    = i;

                    // Adopt current direction:
                    box.UnicodeBidi = direction;

                    if (FirstBox == null)
                    {
                        FirstBox = box;
                        LastBox  = box;
                    }
                    else
                    {
                        // add to this element:
                        LastBox.NextInElement = box;
                        LastBox = box;
                    }

                    // line-height is the inner height here:
                    box.InnerHeight = cssLineHeight;
                    boxWidth        = 0f;
                    wordWidth       = 0f;
                }

                // Are we breaking this word?
                bool implicitNewline = (glyph.Charcode == (int)'\n');
                int  breakMode       = implicitNewline ? 1 : 0;

                // direction:
                int dir = glyph.Directionality;

                if (dir == InfiniText.BidiBlock.Rightwards)
                {
                    // Rightwards (includes weak ones):

                    // Was the previous one 'weak'?
                    if (dir == InfiniText.BidiBlock.WeakLeftToRight)
                    {
                        // Update before weak:
                        beforeWeak = direction;
                    }
                    else
                    {
                        // Not weak:
                        beforeWeak = 0;
                    }

                    if (direction != UnicodeBidiMode.RightwardsNormal)
                    {
                        if (direction == 0)
                        {
                            // Not set yet - Set the mode into the box:
                            box.UnicodeBidi = UnicodeBidiMode.RightwardsNormal;
                        }
                        else
                        {
                            // Changed from leftwards to rightwards. Break the boxes here.
                            breakMode = 3;
                        }

                        // Update dir:
                        direction = UnicodeBidiMode.RightwardsNormal;
                    }
                }
                else if (dir == InfiniText.BidiBlock.RightToLeft)
                {
                    // Leftwards

                    if (direction != UnicodeBidiMode.LeftwardsNormal)
                    {
                        if (direction == 0)
                        {
                            // Not set yet - Set the mode into the box:
                            box.UnicodeBidi = UnicodeBidiMode.LeftwardsNormal;
                        }
                        else
                        {
                            // Changed from rightwards to leftwards. Break the boxes here.
                            breakMode = 3;
                        }

                        // Update dir:
                        direction = UnicodeBidiMode.LeftwardsNormal;
                    }

                    // Never weak:
                    beforeWeak = 0;
                }
                else if (beforeWeak != 0)
                {
                    // Neutral otherwise
                    // (adopts whatever the current is, unless the previous one was *weak*,
                    // in which case, it adopts whatever the direction was before that):
                    if (direction != beforeWeak)
                    {
                        // Change direction!
                        direction = beforeWeak;
                        breakMode = 3;
                    }
                }

                // Got a space?
                bool space = (glyph.Charcode == (int)' ');

                if (space)
                {
                    // Advance width:
                    width += text.WordSpacing;

                    if (breakAllWords)
                    {
                        breakMode = 3;
                    }
                    else
                    {
                        latestBreakpoint = i;
                    }

                    // Lock in the previous text:
                    boxWidth += wordWidth + width;
                    wordWidth = 0f;
                }
                else
                {
                    // Advance word width now:
                    wordWidth += width;
                }

                // Word wrapping next:
                if (breakMode == 0 && wrappable && i != box.TextStart)
                {
                    // Test if we can break here:
                    breakMode = lbm.GetLineSpace(wordWidth, boxWidth);

                    // Return to the previous space if we should.
                    if (breakMode == 2 && text.OverflowWrapActive)
                    {
                        // The word doesn't fit at all (2) and we're supposed to break it.
                        boxWidth += wordWidth - width;
                        wordWidth = 0f;
                    }
                    else if (breakMode != 0)
                    {
                        if (latestBreakpoint == -1)
                        {
                            // Isn't a previous space!

                            if (breakMode == 2)
                            {
                                // Instead, we'll try and break a parent.
                                // This typically happens with inline elements
                                // which are right on the end of the host line.
                                lbm.TryBreakParent();

                                // Don't break the node:
                                breakMode = 0;
                            }
                            else if (breakMode == 3)
                            {
                                // Break but no newline - just advance to the following char:
                                i++;
                            }
                            else if (lbm.PenX != lbm.LineStart)
                            {
                                // Newline:
                                lbm.CompleteLine(LineBreakMode.Normal);

                                // Don't break the node:
                                breakMode = 0;
                            }
                            else
                            {
                                // Don't break the node:
                                breakMode = 0;
                            }
                        }
                        else
                        {
                            i = latestBreakpoint + 1;
                        }
                    }
                }

                if (breakMode != 0)
                {
                    // We're breaking!
                    box.InnerWidth   = boxWidth;
                    box.TextEnd      = i;
                    latestBreakpoint = i;

                    // If the previous glyph is a space, update EndSpaceSize:
                    if (space)
                    {
                        // Update ending spaces:
                        box.EndSpaceSize = width;
                    }

                    // Ensure dimensions are set:
                    box.SetDimensions(false, false);

                    // Add the box to the line:
                    lbm.AddToLine(box);

                    // Update dim's:
                    lbm.AdvancePen(box);

                    // Clear:
                    box      = null;
                    boxWidth = 0f;

                    if (breakMode != 3)
                    {
                        // Newline:
                        if (implicitNewline)
                        {
                            // Also the last line:
                            lbm.CompleteLine(LineBreakMode.Normal | LineBreakMode.Last);
                        }
                        else
                        {
                            // Normal:
                            lbm.CompleteLine(LineBreakMode.Normal);

                            // Process it again.
                            continue;
                        }
                    }
                }

                // Next character:
                i++;
            }

            if (box != null)
            {
                // Always apply inner width:
                box.InnerWidth = boxWidth + wordWidth;
                box.TextEnd    = text.Characters.Length;

                // Ensure dimensions are set:
                box.SetDimensions(false, false);

                // Add the box to the line:
                lbm.AddToLine(box);

                // Update dim's:
                lbm.AdvancePen(box);
            }
        }