Example #1
0
    private int MeasureVanillaParagraph(TigFont font, TigTextStyle style, ReadOnlySpan <char> text)
    {
        Span <char> tempText = stackalloc char[text.Length + 1];

        text.CopyTo(tempText);
        tempText[text.Length] = '\n';

        var maxLineLen = 0;

        Span <char> textRest    = tempText;
        var         nextNewline = textRest.IndexOf('\n');

        while (nextNewline != -1)
        {
            var currentLine = textRest.Slice(0, nextNewline);
            textRest = textRest.Slice(nextNewline + 1);
            var lineLen = MeasureVanillaLine(font, style, currentLine);
            if (lineLen > maxLineLen)
            {
                maxLineLen = lineLen;
            }

            nextNewline = textRest.IndexOf('\n');
        }

        return(maxLineLen);
    }
    public CharUiMainWidget(CharUiParams uiParams) : base(uiParams.CharUiMainWindow)
    {
        _normalBackground  = new WidgetImage("art/interface/char_ui/main_window.img");
        _ironmanBackground = new WidgetImage("art/interface/char_ui/ironman_main_window.img");
        SetSize(_normalBackground.GetPreferredSize());

        _translation = Tig.FS.ReadMesFile("mes/0_char_ui_text.mes");
        var pcCreationMes = Tig.FS.ReadMesFile("mes/pc_creation.mes");

        var rerollsLabel = pcCreationMes[10001];

        _pointBuyLabel = pcCreationMes[10015];

        var attributeModeStyle = new TigTextStyle(new ColorRect(new PackedLinearColorA(0xFF5A7390)));

        attributeModeStyle.kerning  = 1;
        attributeModeStyle.tracking = 3;

        var attributeCountStyle = attributeModeStyle.Copy();

        attributeCountStyle.textColor = new ColorRect(PackedLinearColorA.White);

        _attributeRollModeLabel  = new WidgetText(rerollsLabel, "char-ui-attribute-mode");
        _attributeRollCountLabel = new WidgetText("", "char-ui-attribute-rerolls");
    }
Example #3
0
    // TODO I believe this function measures how many characters will fit into the current line given the bounds.
    public int MeasureLineWrap(TigFont font, TigTextStyle style, ReadOnlySpan <char> text, Rectangle bounds)
    {
        if (bounds.Width == 0)
        {
            return(text.Length);
        }

        Span <char> textCopy = stackalloc char[text.Length];

        text.CopyTo(textCopy);

        var iterator  = new LayoutRunIterator(textCopy, font, bounds, style);
        int endOfLine = 0;

        while (iterator.MoveToNextRun(out var run))
        {
            if (run.Y <= bounds.Y)
            {
                endOfLine = run.End;
            }
            else
            {
                break;
            }
        }

        return(endOfLine);
    }
Example #4
0
    private void LayoutAndDrawVanilla(
        Span <char> text,
        TigFont font,
        ref Rectangle extents,
        TigTextStyle style)
    {
        var extentsWidth  = extents.Width;
        var extentsHeight = extents.Height;

        if (extentsWidth == 0)
        {
            var metrics = new TigFontMetrics();
            metrics.width  = extents.Width;
            metrics.height = extents.Height;
            Tig.Fonts.Measure(style, text, ref metrics);

            extents.Width  = metrics.width;
            extents.Height = metrics.height;
            extentsWidth   = metrics.width;
            extentsHeight  = metrics.height;
        }

        if ((style.flags & (TigTextStyleFlag.TTSF_BACKGROUND | TigTextStyleFlag.TTSF_BORDER)) != 0)
        {
            var rect = new Rectangle(
                extents.X,
                extents.Y,
                Math.Max(extentsWidth, extents.Width),
                Math.Max(extentsHeight, extents.Height)
                );
            DrawBackgroundOrOutline(rect, style);
        }

        var iterator = new LayoutRunIterator(text, font, extents, style);

        while (iterator.MoveToNextRun(out var run))
        {
            if (run.Truncated)
            {
                _renderer.RenderRun(
                    "...",
                    run.X,
                    run.Y,
                    run.Bounds,
                    style,
                    font);
            }
            else
            {
                _renderer.RenderRun(
                    text.Slice(run.Start, run.End - run.Start),
                    run.X,
                    run.Y,
                    run.Bounds,
                    style,
                    font);
            }
        }
    }
Example #5
0
    private void DrawBackgroundOrOutline(Rectangle rect, TigTextStyle style)
    {
        float left   = rect.X;
        float top    = rect.Y;
        var   right  = left + rect.Width;
        var   bottom = top + rect.Height;

        left   -= 3;
        top    -= 3;
        right  += 3;
        bottom += 3;

        if (style.flags.HasFlag(TigTextStyleFlag.TTSF_BACKGROUND))
        {
            Span <Vertex2d> corners = stackalloc Vertex2d[4];
            corners[0].pos = new Vector4(left, top, 0.5f, 1);
            corners[1].pos = new Vector4(right, top, 0.5f, 1);
            corners[2].pos = new Vector4(right, bottom, 0.5f, 1);
            corners[3].pos = new Vector4(left, bottom, 0.5f, 1);

            if (style.bgColor.HasValue)
            {
                var bgColor = style.bgColor.Value;
                corners[0].diffuse = bgColor.topLeft;
                corners[1].diffuse = bgColor.topRight;
                corners[2].diffuse = bgColor.bottomRight;
                corners[3].diffuse = bgColor.bottomLeft;
            }
            else
            {
                foreach (ref var corner in corners)
                {
                    corner.diffuse = PackedLinearColorA.White;
                }
            }

            corners[0].uv = Vector2.Zero;
            corners[1].uv = Vector2.Zero;
            corners[2].uv = Vector2.Zero;
            corners[3].uv = Vector2.Zero;

            // Draw an untexture rectangle
            _shapeRenderer.DrawRectangle(corners, null);
        }

        if (style.flags.HasFlag(TigTextStyleFlag.TTSF_BORDER))
        {
            var topLeft     = new Vector2(left - 1, top - 1);
            var bottomRight = new Vector2(right + 1, bottom + 1);

            _shapeRenderer.DrawRectangleOutline(
                topLeft,
                bottomRight,
                PackedLinearColorA.Black
                );
        }
    }
Example #6
0
 internal LayoutRunIterator(Span <char> text,
                            TigFont font,
                            Rectangle extents,
                            TigTextStyle style) : this()
 {
     this.text    = text;
     this.font    = font;
     this.extents = extents;
     this.style   = style;
     glyphs       = font.FontFace.Glyphs;
     state        = 0;
 }
Example #7
0
    private void MeasureVanilla(TigFont font,
                                TigTextStyle style,
                                ReadOnlySpan <char> text,
                                ref TigFontMetrics metrics)
    {
        if (metrics.width == 0 && text.Contains('\n'))
        {
            metrics.width = MeasureVanillaParagraph(font, style, text);
        }

        var largestHeight = font.FontFace.LargestHeight;

        if (metrics.width == 0)
        {
            metrics.width      = MeasureVanillaLine(font, style, text);
            metrics.height     = largestHeight;
            metrics.lines      = 1;
            metrics.lineheight = largestHeight;
            return;
        }

        metrics.lines = 1; // Default
        if (metrics.height != 0)
        {
            var maxLines = metrics.height / largestHeight;
            if (!(style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE)))
            {
                maxLines++;
            }

            if (maxLines != 1)
            {
                metrics.lines = CountLinesVanilla(metrics.width, maxLines, text, font, style);
            }
        }
        else
        {
            if (!(style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE)))
            {
                metrics.lines = CountLinesVanilla(metrics.width, 0, text, font, style);
            }
        }

        if (metrics.height == 0)
        {
            metrics.height  = metrics.lines * largestHeight;
            metrics.height -= -(font.FontFace.BaseLine - largestHeight);
        }

        metrics.lineheight = largestHeight;
    }
Example #8
0
    private int MeasureVanillaLine(TigFont font, TigTextStyle style, ReadOnlySpan <char> text)
    {
        if (text.IsEmpty)
        {
            return(0);
        }

        var result = 0;
        var length = text.Length;
        var glyphs = font.FontFace.Glyphs;

        for (var i = 0; i < length; i++)
        {
            var ch = text[i];

            // Skip @ characters if they are followed by a number between 0 and 9
            if (ch == '@' & i + 1 < length && text[i + 1] >= '0' && text[i + 1] <= '9')
            {
                i++;
                continue;
            }

            if (ch >= 0 && ch < 128 && char.IsWhiteSpace(ch))
            {
                if (ch != '\n')
                {
                    result += style.tracking;
                }
            }
            else
            {
                if (font.GetGlyphIdx(ch, out var glyphIdx))
                {
                    result += glyphs[glyphIdx].WidthLine + style.kerning;
                }
            }
        }

        return(result);
    }
    public void RenderRun(ReadOnlySpan <char> text,
                          int x,
                          int y,
                          Rectangle bounds,
                          TigTextStyle style,
                          TigFont font)
    {
        foreach (var state in _fileState)
        {
            state.GlyphCount = 0;
        }

        var fontFace = font.FontFace;

        for (var it = 0; it < text.Length; ++it)
        {
            var ch     = text[it];
            var nextCh = '\0';
            if (it + 1 < text.Length)
            {
                nextCh = text[it + 1];
            }

            // @0 to @9 select one of the text colors
            if (ch == '@' && char.IsDigit(nextCh))
            {
                ++it; // Skip the digit
                style.colorSlot = nextCh - '0';
                continue;
            }

            // Handle @t tabstop movement
            if (ch == '@' && nextCh == 't')
            {
                var tabWidth = style.tabStop - bounds.X;

                if (tabWidth > 0)
                {
                    ++it; // Skip the t

                    var tabCount = 1 + (x - bounds.X) / tabWidth;
                    x = bounds.X + tabCount * tabWidth;
                }

                continue;
            }

            if (!font.GetGlyphIdx(text[it], out var glyphIdx))
            {
                continue;
            }

            if (text[it] == ' ')
            {
                glyphIdx = '-' - '!';
            }

            if (glyphIdx >= fontFace.Glyphs.Length)
            {
                return; // Trying to render invalid character
            }

            if (ch >= 0 && ch < 128 && char.IsWhiteSpace(ch))
            {
                x += style.tracking;
                continue;
            }

            var glyph = fontFace.Glyphs[glyphIdx];

            // For some mysterious reason ToEE actually uses one pixel more to the left of the
            // Glyph than is specified in the font file. That area should be transparent, but
            // it means all rendered text is shifted one pixel more to the right than it should be.
            // NOTE: When using linear filtering, the adjacent char sometimes bleeds into the current one
            //       To avoid this, instead of actually rendering this 1px offset, we just offset the
            //       destination rectangle by (1,1) below.
            float u1 = glyph.Rectangle.X;
            float v1 = glyph.Rectangle.Y;
            var   u2 = u1 + glyph.Rectangle.Width;
            var   v2 = v1 + glyph.Rectangle.Height;

            var state = _fileState[glyph.FontArtIndex];

            // See big comment above for reasoning for the 1,1 offset
            var destRect = new Rectangle(
                x + 1,
                y + fontFace.BaseLine - glyph.BaseLineYOffset + 1,
                glyph.Rectangle.Width,
                glyph.Rectangle.Height
                );

            x += style.kerning + glyph.WidthLine;

            Trace.Assert(!style.flags.HasFlag((TigTextStyleFlag)0x1000));
            Trace.Assert(!style.flags.HasFlag((TigTextStyleFlag)0x2000));

            // Drop Shadow
            if (style.flags.HasFlag(TigTextStyleFlag.TTSF_DROP_SHADOW))
            {
                Trace.Assert(style.shadowColor.HasValue);
                var shadowVertexIdx = state.GlyphCount * 4;
                var shadowColor     = style.shadowColor.Value.topLeft;
                shadowColor.A = 255;

                // Top Left
                ref var sVertexTL = ref state.Vertices[shadowVertexIdx];
                sVertexTL.X       = destRect.X + 1.0f;
                sVertexTL.Y       = destRect.Y + 1.0f;
                sVertexTL.U       = u1;
                sVertexTL.V       = v1;
                sVertexTL.diffuse = shadowColor;

                // Top Right
                ref var sVertexTR = ref state.Vertices[shadowVertexIdx + 1];
                sVertexTR.X       = destRect.X + destRect.Width + 1.0f;
                sVertexTR.Y       = destRect.Y + 1.0f;
                sVertexTR.U       = u2;
                sVertexTR.V       = v1;
                sVertexTR.diffuse = shadowColor;

                // Bottom Right
                ref var sVertexBR = ref state.Vertices[shadowVertexIdx + 2];
Example #10
0
    private int CountLinesVanilla(int maxWidth, int maxLines, ReadOnlySpan <char> text, TigFont font,
                                  TigTextStyle style)
    {
        var length = text.Length;

        if (length <= 0)
        {
            return(1);
        }

        var lineWidth = 0;
        var lines     = 1;

        var glyphs = font.FontFace.Glyphs;

        var ch = '\0';

        for (var i = 0; i < length; i++)
        {
            var wordWidth = 0;

            // Measure the length of the current word
            for (; i < length; i++)
            {
                ch = text[i];
                if (ch == '’') // fix for this character that sometimes appears in vanilla
                {
                    ch = '\'';
                }
                // Skip @[0-9]
                if (ch == '@' & i + 1 < length && text[i + 1] >= '0' && text[i + 1] <= '9')
                {
                    i++;
                    continue;
                }


                if (ch < 255 && ch >= 0)
                {
                    if (char.IsWhiteSpace(ch))
                    {
                        break;
                    }
                }

                if (font.GetGlyphIdx(ch, out var glyphIdx))
                {
                    wordWidth += glyphs[glyphIdx].WidthLine + style.kerning;
                }
            }

            lineWidth += wordWidth;

            // If there's enough space in the maxWidth left and we're not at a newline
            // increase the linewidth and continue on.
            if (lineWidth <= maxWidth && ch != '\n')
            {
                if (ch < 255 && ch >= 0 && char.IsWhiteSpace(ch))
                {
                    lineWidth += style.tracking;
                }

                continue;
            }

            // We're either at a newline, or break the line here due to reaching the maxwidth
            lines++;

            // Reached the max number of lines . quit
            if (maxLines != 0 && lines >= maxLines)
            {
                break;
            }

            if (lineWidth <= maxWidth)
            {
                // We reached a normal line break
                lineWidth = 0;
            }
            else
            {
                // We're breaking the line, so we'll keep the current word
                // width as the initial length of the new line
                lineWidth = wordWidth;
            }

            // Continuation indent
            if (style.flags.HasFlag(TigTextStyleFlag.TTSF_CONTINUATION_INDENT))
            {
                lineWidth += 8 * style.tracking;
            }

            if (ch < 255 && ch >= 0 && char.IsWhiteSpace(ch))
            {
                if (ch != '\n')
                {
                    lineWidth += style.tracking;
                }
            }
        }

        return(lines);
    }
Example #11
0
 public void Measure(TigFont font, TigTextStyle style, ReadOnlySpan <char> text, ref TigFontMetrics metrics)
 {
     // use the old font drawing algorithm
     MeasureVanilla(font, style, text, ref metrics);
 }
Example #12
0
    public void LayoutAndDraw(ReadOnlySpan <char> text, TigFont font, ref Rectangle extents, TigTextStyle style)
    {
        if (text.Length == 0)
        {
            return;
        }

        // Get the base text format and check if we should render using the new or old algorithms
        // Make the text mutable since vanilla drawing might change escape characters
        // within the text span.
        Span <char> mutableText = stackalloc char[text.Length];

        text.CopyTo(mutableText);

        // use the old font drawing algorithm
        LayoutAndDrawVanilla(mutableText, font, ref extents, style);
    }
Example #13
0
    internal static Tuple <int, int> MeasureCharRun(ReadOnlySpan <char> text,
                                                    TigTextStyle style,
                                                    Rectangle extents,
                                                    int extentsWidth,
                                                    TigFont font,
                                                    int linePadding,
                                                    bool lastLine)
    {
        var lineWidth            = 0;
        var wordCountWithPadding = 0;
        var wordWidth            = 0;
        var wordCount            = 0;

        var glyphs = font.FontFace.Glyphs;

        // This seems to be special handling for the sequence "@t" and @0 - @9
        var index = 0;

        for (; index < text.Length; ++index)
        {
            var ch     = text[index];
            var nextCh = '\0';
            if (index + 1 < text.Length)
            {
                nextCh = text[index + 1];
            }

            // Handles @0 to @9
            if (ch == '@' & char.IsDigit(nextCh))
            {
                ++index; // Skip the number
            }
            else if (ch == '@' && nextCh == 't')
            {
                ++index; // Skip the t

                // Same handling as whitespaces, but variable sized increment rather than just adding tracking!
                if (lineWidth + wordWidth <= extentsWidth)
                {
                    wordCount++;
                    if (lineWidth + wordWidth <= extentsWidth + linePadding)
                    {
                        wordCountWithPadding++;
                    }

                    lineWidth += wordWidth;
                    wordWidth  = 0;

                    // Increase the line width such that it continues at the tab stop location,
                    // but do not move backwards (unsupported)
                    lineWidth += Math.Max(0, style.tabStop - lineWidth);
                }
                else
                {
                    // Stop if we have run out of space on this line
                    break;
                }
            }
            else if (ch == '\n')
            {
                if (lineWidth + wordWidth <= extentsWidth)
                {
                    wordCount++;
                    if (lineWidth + wordWidth <= extentsWidth + linePadding)
                    {
                        wordCountWithPadding++;
                    }

                    lineWidth += wordWidth;
                    wordWidth  = 0;
                }

                break;
            }
            else if (ch < 255 && ch > -1 && char.IsWhiteSpace(ch))
            {
                if (lineWidth + wordWidth <= extentsWidth)
                {
                    wordCount++;
                    if (lineWidth + wordWidth <= extentsWidth + linePadding)
                    {
                        wordCountWithPadding++;
                    }

                    lineWidth += wordWidth + style.tracking;
                    wordWidth  = 0;
                }
                else
                {
                    // Stop if we have run out of space on this line
                    break;
                }
            }
            else if (ch == '’') // special casing this m**********r
            {
                ch = '\'';
                if (font.GetGlyphIdx(ch, out var glyphIdx))
                {
                    wordWidth += style.kerning + glyphs[glyphIdx].WidthLine;
                }
            }
            else
            {
                if (font.GetGlyphIdx(ch, out var glyphIdx))
                {
                    wordWidth += style.kerning + glyphs[glyphIdx].WidthLine;
                }
            }
        }

        // Handle the last word, if we're at the end of the string
        if (index >= text.Length && wordWidth > 0)
        {
            if (lineWidth + wordWidth <= extentsWidth)
            {
                wordCount++;
                lineWidth += wordWidth;
                if (lineWidth + wordWidth <= extentsWidth + linePadding)
                {
                    wordCountWithPadding++;
                }
            }
            else if (style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE))
            {
                // The word would actually not fit, but we're the last
                // thing in the string and we truncate with ...
                lineWidth += wordWidth;
                wordCount++;
                wordCountWithPadding++;
            }
        }

        // Ignore the padding if we'd not print ellipsis anyway
        if (!lastLine || index >= text.Length || !style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE))
        {
            wordCountWithPadding = wordCount;
        }

        return(Tuple.Create(wordCountWithPadding, lineWidth));
    }
Example #14
0
    internal static ScanWordResult ScanWord(Span <char> text,
                                            int firstIdx,
                                            int textLength,
                                            bool lastLine,
                                            TigFont font,
                                            TigTextStyle style,
                                            int remainingSpace)
    {
        var result = new ScanWordResult();

        result.firstIdx = firstIdx;

        var glyphs = font.FontFace.Glyphs;

        var i = firstIdx;

        for (; i < textLength; i++)
        {
            var curCh  = text[i];
            var nextCh = '\0';
            if (i + 1 < textLength)
            {
                nextCh = text[i + 1];
            }

            if (curCh == '’')
            {
                curCh = text[i] = '\'';
            }

            // Simply skip @t without increasing the width
            if (curCh == '@' && char.IsDigit(nextCh))
            {
                i++; // Skip the number
                continue;
            }

            // @t will advance the width up to the next tabstop, but only if a tabstop has been specified
            if (curCh == '@' && nextCh == 't' && style.tabStop > 0)
            {
                break; // Treat it like whitespace
            }

            if (!font.GetGlyphIdx(curCh, out var glyphIdx))
            {
                Logger.Warn("Tried to display character {0} in text '{1}'", glyphIdx, new string(text));
                continue;
            }

            if (curCh == '\n')
            {
                if (lastLine && style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE))
                {
                    result.drawEllipsis = true;
                }

                break;
            }

            if (curCh < 128 && curCh > 0 && char.IsWhiteSpace(curCh))
            {
                break;
            }

            if (style.flags.HasFlag(TigTextStyleFlag.TTSF_TRUNCATE))
            {
                result.fullWidth += glyphs[glyphIdx].WidthLine + style.kerning;
                if (result.fullWidth > remainingSpace)
                {
                    result.drawEllipsis = true;
                    continue;
                }

                result.idxBeforePadding = i;
            }

            result.Width += glyphs[glyphIdx].WidthLine + style.kerning;
        }

        result.lastIdx = i;
        return(result);
    }