Example #1
0
        internal static bool TryGetCharacterBounds(IReadOnlyList <GlyphLayout> glyphLayouts, Vector2 dpi, out GlyphMetric[] characterBounds)
        {
            bool hasSize = false;

            if (glyphLayouts.Count == 0)
            {
                characterBounds = EmptyGlyphMetricArray;
                return(hasSize);
            }

            var characterBoundsList = new GlyphMetric[glyphLayouts.Count];

            for (int i = 0; i < glyphLayouts.Count; i++)
            {
                GlyphLayout c = glyphLayouts[i];

                // TODO: This sets the hasSize value to the last layout... is this correct?
                if (!c.IsControlCharacter)
                {
                    hasSize = true;
                }

                characterBoundsList[i] = new GlyphMetric(c.CodePoint, c.BoundingBox(dpi), c.IsControlCharacter);
            }

            characterBounds = characterBoundsList;
            return(hasSize);
        }
Example #2
0
        internal static FontRectangle GetBounds(IReadOnlyList <GlyphLayout> glyphLayouts, Vector2 dpi)
        {
            if (glyphLayouts.Count == 0)
            {
                return(FontRectangle.Empty);
            }

            bool hasSize = false;

            float left   = int.MaxValue;
            float top    = int.MaxValue;
            float bottom = int.MinValue;
            float right  = int.MinValue;

            for (int i = 0; i < glyphLayouts.Count; i++)
            {
                GlyphLayout c = glyphLayouts[i];
                if (!c.IsControlCharacter)
                {
                    hasSize = true;
                    FontRectangle box = c.BoundingBox(dpi);
                    if (left > box.Left)
                    {
                        left = box.Left;
                    }

                    if (top > box.Top)
                    {
                        top = box.Top;
                    }

                    if (bottom < box.Bottom)
                    {
                        bottom = box.Bottom;
                    }

                    if (right < box.Right)
                    {
                        right = box.Right;
                    }
                }
            }

            if (!hasSize)
            {
                return(FontRectangle.Empty);
            }

            float width  = right - left;
            float height = bottom - top;

            return(new FontRectangle(left, top, width, height));
        }
        internal static bool TryGetCharacterBounds(IReadOnlyList <GlyphLayout> glyphLayouts, float dpi, out GlyphBounds[] characterBounds)
        {
            bool hasSize = false;

            if (glyphLayouts.Count == 0)
            {
                characterBounds = Array.Empty <GlyphBounds>();
                return(hasSize);
            }

            var characterBoundsList = new GlyphBounds[glyphLayouts.Count];

            for (int i = 0; i < glyphLayouts.Count; i++)
            {
                GlyphLayout g = glyphLayouts[i];
                hasSize |= !g.IsStartOfLine;
                characterBoundsList[i] = new GlyphBounds(g.Glyph.GlyphMetrics.CodePoint, g.BoundingBox(dpi));
            }

            characterBounds = characterBoundsList;
            return(hasSize);
        }
Example #4
0
        /// <summary>
        /// Generates the layout.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="options">The style.</param>
        /// <returns>A collection of layout that describe all thats needed to measure or render a series of glyphs.</returns>
        public IReadOnlyList <GlyphLayout> GenerateLayout(ReadOnlySpan <char> text, RendererOptions options)
        {
            if (text.IsEmpty)
            {
                return(Array.Empty <GlyphLayout>());
            }

            var     dpi    = new Vector2(options.DpiX, options.DpiY);
            Vector2 origin = options.Origin / dpi;

            float maxWidth = float.MaxValue;
            float originX  = 0;

            if (options.WrappingWidth > 0)
            {
                // trim trailing white spaces from the text
                text     = text.TrimEnd(null);
                maxWidth = options.WrappingWidth / options.DpiX;
            }

            // lets convert the text into codepoints
            Memory <int> codePointsMemory = LineBreaker.ToUtf32(text);

            if (codePointsMemory.IsEmpty)
            {
                return(Array.Empty <GlyphLayout>());
            }

            Span <int> codepoints  = codePointsMemory.Span;
            var        lineBreaker = new LineBreaker();

            lineBreaker.Reset(codePointsMemory);

            AppliedFontStyle spanStyle = options.GetStyle(0, codepoints.Length);
            var layout = new List <GlyphLayout>(codepoints.Length);

            float   unscaledLineHeight       = 0f;
            float   lineHeight               = 0f;
            float   unscaledLineMaxAscender  = 0f;
            float   unscaledLineMaxDescender = 0f;
            float   lineMaxAscender          = 0f;
            float   lineMaxDescender         = 0f;
            Vector2 location = Vector2.Zero;
            float   lineHeightOfFirstLine = 0;

            // Remember where the top of the layouted text is for accurate vertical alignment.
            // This is important because there is considerable space between the lineHeight at the glyph's ascender.
            float top = 0;

            bool          firstLine             = true;
            GlyphInstance?previousGlyph         = null;
            float         scale                 = 0;
            int           lastWrappableLocation = -1;
            int           nextWrappableLocation = codepoints.Length;
            bool          nextWrappableRequired = false;
            bool          startOfLine           = true;
            float         totalHeight           = 0;
            int           graphemeIndex         = 0;

            if (lineBreaker.TryGetNextBreak(out LineBreak b))
            {
                nextWrappableLocation = b.PositionWrap - 1;
                nextWrappableRequired = b.Required;
            }

            for (int i = 0; i < codepoints.Length; i++)
            {
                if (spanStyle.End < i)
                {
                    spanStyle     = options.GetStyle(i, codepoints.Length);
                    previousGlyph = null;
                }

                int codePoint = codepoints[i];

                GlyphInstance[] glyphs = spanStyle.GetGlyphLayers(codePoint, options.ColorFontSupport);
                if (glyphs.Length == 0)
                {
                    return(FontsThrowHelper.ThrowGlyphMissingException <IReadOnlyList <GlyphLayout> >(codePoint));
                }

                GlyphInstance?glyph      = glyphs[0];
                float         fontHeight = glyph.Font.LineHeight * options.LineSpacing;
                if (fontHeight > unscaledLineHeight)
                {
                    // get the larget lineheight thus far
                    unscaledLineHeight = fontHeight;
                    scale      = glyph.Font.EmSize * 72;
                    lineHeight = unscaledLineHeight * spanStyle.PointSize / scale;
                }

                if (glyph.Font.Ascender > unscaledLineMaxAscender)
                {
                    unscaledLineMaxAscender = glyph.Font.Ascender;
                    scale           = glyph.Font.EmSize * 72;
                    lineMaxAscender = unscaledLineMaxAscender * spanStyle.PointSize / scale;
                }

                if (Math.Abs(glyph.Font.Descender) > unscaledLineMaxDescender)
                {
                    unscaledLineMaxDescender = Math.Abs(glyph.Font.Descender);
                    scale            = glyph.Font.EmSize * 72;
                    lineMaxDescender = unscaledLineMaxDescender * spanStyle.PointSize / scale;
                }

                if (firstLine)
                {
                    // Reset the line height for the first line to prevent initial lead.
                    float unspacedLineHeight = lineHeight / options.LineSpacing;
                    if (unspacedLineHeight > lineHeightOfFirstLine)
                    {
                        lineHeightOfFirstLine = unspacedLineHeight;
                    }

                    switch (options.VerticalAlignment)
                    {
                    case VerticalAlignment.Top:
                        top = lineMaxAscender;
                        break;

                    case VerticalAlignment.Center:
                        top = (lineMaxAscender / 2F) - (lineMaxDescender / 2F);
                        break;

                    case VerticalAlignment.Bottom:
                        top = -lineMaxDescender;
                        break;
                    }
                }

                if ((options.WrappingWidth > 0 && nextWrappableLocation == i) || nextWrappableRequired)
                {
                    // keep a record of where to wrap text and ensure that no line starts with white space
                    for (int j = layout.Count - 1; j >= 0; j--)
                    {
                        if (!layout[j].IsWhiteSpace)
                        {
                            lastWrappableLocation = j + 1;
                            break;
                        }
                    }
                }

                if (nextWrappableLocation == i)
                {
                    if (lineBreaker.TryGetNextBreak(out b))
                    {
                        nextWrappableLocation = b.PositionWrap - 1;
                        nextWrappableRequired = b.Required;
                    }
                }

                float glyphWidth  = glyph.AdvanceWidth * spanStyle.PointSize / scale;
                float glyphHeight = glyph.Height * spanStyle.PointSize / scale;

                if (codepoints[i] != '\r' && codepoints[i] != '\n' && codepoints[i] != '\t' && codepoints[i] != ' ')
                {
                    Vector2 glyphLocation = location;
                    if (spanStyle.ApplyKerning && previousGlyph != null)
                    {
                        // if there is special instructions for this glyph pair use that width
                        Vector2 scaledOffset = spanStyle.GetOffset(glyph, previousGlyph) * spanStyle.PointSize / scale;

                        glyphLocation += scaledOffset;

                        // only fix the 'X' of the current tracked location but use the actual 'X'/'Y' of the offset
                        location.X = glyphLocation.X;
                    }

                    foreach (GlyphInstance?g in glyphs)
                    {
                        float w = g.AdvanceWidth * spanStyle.PointSize / scale;
                        float h = g.Height * spanStyle.PointSize / scale;
                        layout.Add(new GlyphLayout(graphemeIndex, codePoint, new Glyph(g, spanStyle.PointSize), glyphLocation, w, h, lineHeight, startOfLine, false, false));

                        if (w > glyphWidth)
                        {
                            glyphWidth = w;
                        }
                    }

                    // Increment the index to signify we have moved on the a new cluster.
                    graphemeIndex++;
                    startOfLine = false;

                    // move forward the actual width of the glyph, we are retaining the baseline
                    location.X += glyphWidth;

                    // if the word extended pass the end of the box, wrap it
                    if (location.X >= maxWidth && lastWrappableLocation > 0)
                    {
                        if (lastWrappableLocation < layout.Count)
                        {
                            float wrappingOffset = layout[lastWrappableLocation].Location.X;
                            startOfLine = true;

                            // move the characters to the next line
                            for (int j = lastWrappableLocation; j < layout.Count; j++)
                            {
                                if (layout[j].IsWhiteSpace)
                                {
                                    wrappingOffset += layout[j].Width;
                                    layout.RemoveAt(j);
                                    j--;
                                    continue;
                                }

                                Vector2 current = layout[j].Location;
                                layout[j]   = new GlyphLayout(layout[j].GraphemeIndex, layout[j].CodePoint, layout[j].Glyph, new Vector2(current.X - wrappingOffset, current.Y + lineHeight), layout[j].Width, layout[j].Height, layout[j].LineHeight, startOfLine, layout[j].IsWhiteSpace, layout[j].IsControlCharacter);
                                startOfLine = false;

                                location.X = layout[j].Location.X + layout[j].Width;
                            }

                            location.Y           += lineHeight;
                            totalHeight          += lineHeight;
                            firstLine             = false;
                            lastWrappableLocation = -1;
                        }
                    }

                    previousGlyph = glyph;
                }
                else if (codepoints[i] == '\r')
                {
                    // carriage return resets the XX coordinate to 0
                    location.X    = 0;
                    previousGlyph = null;
                    startOfLine   = true;

                    layout.Add(new GlyphLayout(-1, codePoint, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    startOfLine = false;
                }
                else if (codepoints[i] == '\n')
                {
                    // carriage return resets the XX coordinate to 0
                    layout.Add(new GlyphLayout(-1, codePoint, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    location.X              = 0;
                    location.Y             += lineHeight;
                    totalHeight            += lineHeight;
                    unscaledLineHeight      = 0;
                    unscaledLineMaxAscender = 0;
                    previousGlyph           = null;
                    firstLine             = false;
                    lastWrappableLocation = -1;
                    startOfLine           = true;
                }
                else if (codepoints[i] == '\t')
                {
                    float tabStop    = glyphWidth * spanStyle.TabWidth;
                    float finalWidth = 0;

                    if (tabStop > 0)
                    {
                        finalWidth = tabStop - (location.X % tabStop);
                    }

                    if (finalWidth < glyphWidth)
                    {
                        // if we are not going to tab atleast a glyph width add another tabstop to it ???
                        // should I be doing this?
                        finalWidth += tabStop;
                    }

                    layout.Add(new GlyphLayout(-1, codePoint, new Glyph(glyph, spanStyle.PointSize), location, finalWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine = false;

                    // advance to a position > width away that
                    location.X   += finalWidth;
                    previousGlyph = null;
                }
                else if (codepoints[i] == ' ')
                {
                    layout.Add(new GlyphLayout(-1, codePoint, new Glyph(glyph, spanStyle.PointSize), location, glyphWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine   = false;
                    location.X   += glyphWidth;
                    previousGlyph = null;
                }
            }

            var offsetY = new Vector2(0, top);

            switch (options.VerticalAlignment)
            {
            case VerticalAlignment.Center:
                offsetY += new Vector2(0, -(totalHeight / 2F));
                break;

            case VerticalAlignment.Bottom:
                offsetY += new Vector2(0, -totalHeight);
                break;
            }

            Vector2 offsetX = Vector2.Zero;

            for (int i = 0; i < layout.Count; i++)
            {
                GlyphLayout glyphLayout = layout[i];
                graphemeIndex = glyphLayout.GraphemeIndex;

                // Scan ahead getting the width.
                if (glyphLayout.StartOfLine)
                {
                    float width = 0;
                    for (int j = i; j < layout.Count; j++)
                    {
                        GlyphLayout current = layout[j];
                        int         currentGraphemeIndex = current.GraphemeIndex;
                        if (current.StartOfLine && (currentGraphemeIndex != graphemeIndex))
                        {
                            // Leading graphemes are made up of multiple glyphs all marked as 'StartOfLine so we only
                            // break when we are sure we have entered a new cluster or previously defined break.
                            break;
                        }

                        width = current.Location.X + current.Width;
                    }

                    switch (options.HorizontalAlignment)
                    {
                    case HorizontalAlignment.Left:
                        offsetX = new Vector2(originX, 0) + offsetY;
                        break;

                    case HorizontalAlignment.Right:
                        offsetX = new Vector2(originX - width, 0) + offsetY;
                        break;

                    case HorizontalAlignment.Center:
                        offsetX = new Vector2(originX - (width / 2F), 0) + offsetY;
                        break;
                    }
                }

                // TODO calculate an offset from the 'origin' based on TextAlignment for each line
                layout[i] = new GlyphLayout(
                    glyphLayout.GraphemeIndex,
                    glyphLayout.CodePoint,
                    glyphLayout.Glyph,
                    glyphLayout.Location + offsetX + origin,
                    glyphLayout.Width,
                    glyphLayout.Height,
                    glyphLayout.LineHeight,
                    glyphLayout.StartOfLine,
                    glyphLayout.IsWhiteSpace,
                    glyphLayout.IsControlCharacter);
            }

            return(layout);
        }
Example #5
0
        /// <summary>
        /// Generates the layout.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="options">The style.</param>
        /// <returns>A collection of layout that describe all thats needed to measure or render a series of glyphs.</returns>
        public IReadOnlyList <GlyphLayout> GenerateLayout(ReadOnlySpan <char> text, RendererOptions options)
        {
            var     dpi    = new Vector2(options.DpiX, options.DpiY);
            Vector2 origin = (Vector2)options.Origin / dpi;

            float maxWidth = float.MaxValue;
            float originX  = 0;

            if (options.WrappingWidth > 0)
            {
                // trim trailing white spaces from the text
                text = text.TrimEnd(null);

                maxWidth = options.WrappingWidth / options.DpiX;

                switch (options.HorizontalAlignment)
                {
                case HorizontalAlignment.Right:
                    originX = maxWidth;
                    break;

                case HorizontalAlignment.Center:
                    originX = 0.5f * maxWidth;
                    break;

                case HorizontalAlignment.Left:
                default:
                    originX = 0;
                    break;
                }
            }

            AppliedFontStyle spanStyle = options.GetStyle(0, text.Length);
            var layout = new List <GlyphLayout>(text.Length);

            float   unscaledLineHeight      = 0f;
            float   lineHeight              = 0f;
            float   unscaledLineMaxAscender = 0f;
            float   lineMaxAscender         = 0f;
            Vector2 location = Vector2.Zero;
            float   lineHeightOfFirstLine = 0;

            // Remember where the top of the layouted text is for accurate vertical alignment.
            // This is important because there is considerable space between the lineHeight at the glyph's ascender.
            float top = 0;

            bool          firstLine             = true;
            GlyphInstance previousGlyph         = null;
            float         scale                 = 0;
            int           lastWrappableLocation = -1;
            bool          startOfLine           = true;
            float         totalHeight           = 0;

            for (int i = 0; i < text.Length; i++)
            {
                // four-byte characters are processed on the first char
                if (char.IsLowSurrogate(text[i]))
                {
                    continue;
                }

                if (spanStyle.End < i)
                {
                    spanStyle     = options.GetStyle(i, text.Length);
                    previousGlyph = null;
                }

                if (spanStyle.Font.LineHeight > unscaledLineHeight)
                {
                    // get the larget lineheight thus far
                    unscaledLineHeight = spanStyle.Font.LineHeight;
                    scale      = spanStyle.Font.EmSize * 72;
                    lineHeight = (unscaledLineHeight * spanStyle.PointSize) / scale;
                }

                if (spanStyle.Font.Ascender > unscaledLineMaxAscender)
                {
                    unscaledLineMaxAscender = spanStyle.Font.Ascender;
                    scale           = spanStyle.Font.EmSize * 72;
                    lineMaxAscender = (unscaledLineMaxAscender * spanStyle.PointSize) / scale;
                }

                if (firstLine)
                {
                    if (lineHeight > lineHeightOfFirstLine)
                    {
                        lineHeightOfFirstLine = lineHeight;
                    }

                    top = lineHeightOfFirstLine - lineMaxAscender;
                }

                if (options.WrappingWidth > 0 && char.IsWhiteSpace(text[i]))
                {
                    // keep a record of where to wrap text and ensure that no line starts with white space
                    for (int j = layout.Count - 1; j >= 0; j--)
                    {
                        if (!layout[j].IsWhiteSpace)
                        {
                            lastWrappableLocation = j + 1;
                            break;
                        }
                    }
                }

                bool hasFourBytes = char.IsHighSurrogate(text[i]);
                int  codePoint    = hasFourBytes ? char.ConvertToUtf32(text[i], text[i + 1]) : text[i];

                GlyphInstance glyph       = spanStyle.Font.GetGlyph(codePoint);
                float         glyphWidth  = (glyph.AdvanceWidth * spanStyle.PointSize) / scale;
                float         glyphHeight = (glyph.Height * spanStyle.PointSize) / scale;

                if (hasFourBytes || (text[i] != '\r' && text[i] != '\n' && text[i] != '\t' && text[i] != ' '))
                {
                    Vector2 glyphLocation = location;
                    if (spanStyle.ApplyKerning && previousGlyph != null)
                    {
                        // if there is special instructions for this glyph pair use that width
                        Vector2 scaledOffset = (spanStyle.Font.GetOffset(glyph, previousGlyph) * spanStyle.PointSize) / scale;

                        glyphLocation += scaledOffset;

                        // only fix the 'X' of the current tracked location but use the actual 'X'/'Y' of the offset
                        location.X = glyphLocation.X;
                    }

                    layout.Add(new GlyphLayout(codePoint, new Glyph(glyph, spanStyle.PointSize), glyphLocation, glyphWidth, glyphHeight, lineHeight, startOfLine, false, false));
                    startOfLine = false;

                    // move forward the actual width of the glyph, we are retaining the baseline
                    location.X += glyphWidth;

                    // if the word extended pass the end of the box, wrap it
                    if (location.X >= maxWidth && lastWrappableLocation > 0)
                    {
                        if (lastWrappableLocation < layout.Count)
                        {
                            float wrappingOffset = layout[lastWrappableLocation].Location.X;
                            startOfLine = true;

                            // move the characters to the next line
                            for (int j = lastWrappableLocation; j < layout.Count; j++)
                            {
                                if (layout[j].IsWhiteSpace)
                                {
                                    wrappingOffset += layout[j].Width;
                                    layout.RemoveAt(j);
                                    j--;
                                    continue;
                                }

                                Vector2 current = layout[j].Location;
                                layout[j]   = new GlyphLayout(layout[j].CodePoint, layout[j].Glyph, new Vector2(current.X - wrappingOffset, current.Y + lineHeight), layout[j].Width, layout[j].Height, layout[j].LineHeight, startOfLine, layout[j].IsWhiteSpace, layout[j].IsControlCharacter);
                                startOfLine = false;

                                location.X = layout[j].Location.X + layout[j].Width;
                            }

                            location.Y           += lineHeight;
                            firstLine             = false;
                            lastWrappableLocation = -1;
                        }
                    }

                    float bottom = location.Y + lineHeight;
                    if (bottom > totalHeight)
                    {
                        totalHeight = bottom;
                    }

                    previousGlyph = glyph;
                }
                else if (text[i] == '\r')
                {
                    // carriage return resets the XX coordinate to 0
                    location.X    = 0;
                    previousGlyph = null;
                    startOfLine   = true;

                    layout.Add(new GlyphLayout(codePoint, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    startOfLine = false;
                }
                else if (text[i] == '\n')
                {
                    // carriage return resets the XX coordinate to 0
                    layout.Add(new GlyphLayout(codePoint, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    location.X              = 0;
                    location.Y             += lineHeight;
                    unscaledLineHeight      = 0;
                    unscaledLineMaxAscender = 0;
                    previousGlyph           = null;
                    firstLine             = false;
                    lastWrappableLocation = -1;
                    startOfLine           = true;
                }
                else if (text[i] == '\t')
                {
                    float tabStop    = glyphWidth * spanStyle.TabWidth;
                    float finalWidth = 0;

                    if (tabStop > 0)
                    {
                        finalWidth = tabStop - (location.X % tabStop);
                    }

                    if (finalWidth < glyphWidth)
                    {
                        // if we are not going to tab atleast a glyph width add another tabstop to it ???
                        // should I be doing this?
                        finalWidth += tabStop;
                    }

                    layout.Add(new GlyphLayout(codePoint, new Glyph(glyph, spanStyle.PointSize), location, finalWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine = false;

                    // advance to a position > width away that
                    location.X   += finalWidth;
                    previousGlyph = null;
                }
                else if (text[i] == ' ')
                {
                    layout.Add(new GlyphLayout(codePoint, new Glyph(glyph, spanStyle.PointSize), location, glyphWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine   = false;
                    location.X   += glyphWidth;
                    previousGlyph = null;
                }
            }

            totalHeight -= top;
            var offset = new Vector2(0, lineHeightOfFirstLine - top);

            switch (options.VerticalAlignment)
            {
            case VerticalAlignment.Center:
                offset += new Vector2(0, -0.5f * totalHeight);
                break;

            case VerticalAlignment.Bottom:
                offset += new Vector2(0, -totalHeight);
                break;

            case VerticalAlignment.Top:
            default:
                // no change
                break;
            }

            Vector2 lineOffset = offset;

            for (int i = 0; i < layout.Count; i++)
            {
                GlyphLayout glyphLayout = layout[i];
                if (glyphLayout.StartOfLine)
                {
                    lineOffset = offset;

                    // scan ahead measuring width
                    float width = glyphLayout.Width;
                    for (int j = i + 1; j < layout.Count; j++)
                    {
                        if (layout[j].StartOfLine)
                        {
                            break;
                        }

                        width = layout[j].Location.X + layout[j].Width; // rhs
                    }

                    switch (options.HorizontalAlignment)
                    {
                    case HorizontalAlignment.Right:
                        lineOffset = new Vector2(originX - width, 0) + offset;
                        break;

                    case HorizontalAlignment.Center:
                        lineOffset = new Vector2(originX - (width / 2f), 0) + offset;
                        break;

                    case HorizontalAlignment.Left:
                    default:
                        lineOffset = new Vector2(originX, 0) + offset;
                        break;
                    }
                }

                // TODO calculate an offset from the 'origin' based on TextAlignment for each line
                layout[i] = new GlyphLayout(glyphLayout.CodePoint, glyphLayout.Glyph, glyphLayout.Location + lineOffset + origin, glyphLayout.Width, glyphLayout.Height, glyphLayout.LineHeight, glyphLayout.StartOfLine, glyphLayout.IsWhiteSpace, glyphLayout.IsControlCharacter);
            }

            return(layout);
        }
Example #6
0
        /// <summary>
        /// Generates the layout.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="options">The style.</param>
        /// <returns>A collection of layout that describe all thats needed to measure or render a series of glyphs.</returns>
        public ImmutableArray <GlyphLayout> GenerateLayout(string text, RendererOptions options)
        {
            var     dpi    = new Vector2(options.DpiX, options.DpiY);
            Vector2 origin = (Vector2)options.Origin / dpi;

            float maxWidth = float.MaxValue;
            float originX  = 0;

            if (options.WrappingWidth > 0)
            {
                // trim trailing white spaces from the text
                text = text.TrimEnd(null);

                maxWidth = options.WrappingWidth / options.DpiX;

                switch (options.HorizontalAlignment)
                {
                case HorizontalAlignment.Right:
                    originX = maxWidth;
                    break;

                case HorizontalAlignment.Center:
                    originX = maxWidth / 2f;
                    break;

                case HorizontalAlignment.Left:
                default:
                    originX = 0;
                    break;
                }
            }

            AppliedFontStyle   spanStyle = options.GetStyle(0, text.Length);
            List <GlyphLayout> layout    = new List <GlyphLayout>(text.Length);

            float         lineHeight            = 0f;
            Vector2       location              = Vector2.Zero;
            float         lineHeightOfFirstLine = 0;
            bool          firstLine             = true;
            GlyphInstance previousGlyph         = null;
            float         scale = 0;
            int           lastWrappableLocation = -1;
            bool          startOfLine           = true;
            float         totalHeight           = 0;

            for (int i = 0; i < text.Length; i++)
            {
                if (spanStyle.End < i)
                {
                    spanStyle     = options.GetStyle(i, text.Length);
                    previousGlyph = null;
                }

                if (spanStyle.Font.LineHeight > lineHeight)
                {
                    // get the larget lineheight thus far
                    scale      = spanStyle.Font.EmSize * 72;
                    lineHeight = (spanStyle.Font.LineHeight * spanStyle.PointSize) / scale;
                }

                if (firstLine && lineHeight > lineHeightOfFirstLine)
                {
                    lineHeightOfFirstLine = lineHeight;
                }

                char c = text[i];

                if (options.WrappingWidth > 0 && char.IsWhiteSpace(c))
                {
                    // keep a record of where to wrap text and ensure that no line starts with white space
                    for (int j = layout.Count - 1; j >= 0; j--)
                    {
                        if (!layout[j].IsWhiteSpace)
                        {
                            lastWrappableLocation = j + 1;
                            break;
                        }
                    }
                }
                GlyphInstance glyph       = spanStyle.Font.GetGlyph(c);
                float         glyphWidth  = (glyph.AdvanceWidth * spanStyle.PointSize) / scale;
                float         glyphHeight = (glyph.Height * spanStyle.PointSize) / scale;

                switch (c)
                {
                case '\r':
                    // carrage return resets the XX coordinate to 0
                    location.X    = 0;
                    previousGlyph = null;
                    startOfLine   = true;

                    layout.Add(new GlyphLayout(c, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    startOfLine = false;
                    break;

                case '\n':
                {
                    // carrage return resets the XX coordinate to 0
                    layout.Add(new GlyphLayout(c, new Glyph(glyph, spanStyle.PointSize), location, 0, glyphHeight, lineHeight, startOfLine, true, true));
                    location.X            = 0;
                    location.Y           += lineHeight;
                    lineHeight            = 0; // reset line height tracking for next line
                    previousGlyph         = null;
                    firstLine             = false;
                    lastWrappableLocation = -1;
                    startOfLine           = true;
                }
                break;

                case '\t':
                {
                    float tabStop    = glyphWidth * spanStyle.TabWidth;
                    float finalWidth = 0;

                    if (tabStop > 0)
                    {
                        finalWidth = tabStop - (location.X % tabStop);
                    }

                    if (finalWidth < glyphWidth)
                    {
                        // if we are not going to tab atleast a glyph width add another tabstop to it ???
                        // should I be doing this?
                        finalWidth += tabStop;
                    }

                    layout.Add(new GlyphLayout(c, new Glyph(glyph, spanStyle.PointSize), location, finalWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine = false;

                    // advance to a position > width away that
                    location.X   += finalWidth;
                    previousGlyph = null;
                }

                break;

                case ' ':
                {
                    layout.Add(new GlyphLayout(c, new Glyph(glyph, spanStyle.PointSize), location, glyphWidth, glyphHeight, lineHeight, startOfLine, true, false));
                    startOfLine   = false;
                    location.X   += glyphWidth;
                    previousGlyph = null;
                }

                break;

                default:
                {
                    Vector2 glyphLocation = location;
                    if (spanStyle.ApplyKerning && previousGlyph != null)
                    {
                        // if there is special instructions for this glyph pair use that width
                        Vector2 scaledOffset = (spanStyle.Font.GetOffset(glyph, previousGlyph) * spanStyle.PointSize) / scale;

                        glyphLocation += scaledOffset;

                        // only fix the 'X' of the current tracked location but use the actual 'X'/'Y' of the offset
                        location.X = glyphLocation.X;
                    }

                    layout.Add(new GlyphLayout(c, new Glyph(glyph, spanStyle.PointSize), glyphLocation, glyphWidth, glyphHeight, lineHeight, startOfLine, false, false));
                    startOfLine = false;

                    // move forward the actual width of the glyph, we are retaining the baseline
                    location.X += glyphWidth;

                    // if the word extended pass the end of the box, wrap it
                    if (location.X >= maxWidth && lastWrappableLocation > 0)
                    {
                        if (lastWrappableLocation < layout.Count)
                        {
                            float wrappingOffset = layout[lastWrappableLocation].Location.X;
                            startOfLine = true;

                            // move the characters to the next line
                            for (int j = lastWrappableLocation; j < layout.Count; j++)
                            {
                                if (layout[j].IsWhiteSpace)
                                {
                                    wrappingOffset += layout[j].Width;
                                    layout.RemoveAt(j);
                                    j--;
                                    continue;
                                }

                                Vector2 current = layout[j].Location;
                                layout[j]   = new GlyphLayout(layout[j].Character, layout[j].Glyph, new Vector2(current.X - wrappingOffset, current.Y + lineHeight), layout[j].Width, layout[j].Height, layout[j].LineHeight, startOfLine, layout[j].IsWhiteSpace, layout[j].IsControlCharacter);
                                startOfLine = false;

                                location.X = layout[j].Location.X + layout[j].Width;
                            }

                            location.Y           += lineHeight;
                            firstLine             = false;
                            lastWrappableLocation = -1;
                        }
                    }
                    float bottom = location.Y + lineHeight;
                    if (bottom > totalHeight)
                    {
                        totalHeight = bottom;
                    }
                    previousGlyph = glyph;
                }

                break;
                }
            }

            Vector2 offset = new Vector2(0, lineHeightOfFirstLine);

            switch (options.VerticalAlignment)
            {
            case VerticalAlignment.Center:
                offset += new Vector2(0, -(totalHeight / 2));
                break;

            case VerticalAlignment.Bottom:
                offset += new Vector2(0, -totalHeight);
                break;

            case VerticalAlignment.Top:
            default:
                // no change
                break;
            }
            Vector2 lineOffset = offset;

            for (int i = 0; i < layout.Count; i++)
            {
                GlyphLayout glyphLayout = layout[i];
                if (glyphLayout.StartOfLine)
                {
                    lineOffset = offset;
                    // scan ahead measuring width
                    float width = glyphLayout.Width;
                    for (int j = i + 1; j < layout.Count; j++)
                    {
                        if (layout[j].StartOfLine)
                        {
                            break;
                        }
                        width = layout[j].Location.X + layout[j].Width; // rhs
                    }
                    switch (options.HorizontalAlignment)
                    {
                    case HorizontalAlignment.Right:
                        lineOffset = new Vector2(originX - width, 0) + offset;
                        break;

                    case HorizontalAlignment.Center:
                        lineOffset = new Vector2(originX - (width / 2f), 0) + offset;
                        break;

                    case HorizontalAlignment.Left:
                    default:
                        lineOffset = new Vector2(originX, 0) + offset;
                        break;
                    }
                }

                // TODO calculate an offset from the 'origin' based on TextAlignment for each line
                layout[i] = new GlyphLayout(glyphLayout.Character, glyphLayout.Glyph, glyphLayout.Location + lineOffset + origin, glyphLayout.Width, glyphLayout.Height, glyphLayout.LineHeight, glyphLayout.StartOfLine, glyphLayout.IsWhiteSpace, glyphLayout.IsControlCharacter);
            }

            return(layout.ToImmutableArray());
        }
Example #7
0
        /// <summary>
        /// Generates the layout.
        /// </summary>
        /// <param name="text">The text.</param>
        /// <param name="style">The style.</param>
        /// <returns>A collection of layout that describe all thats needed to measure or render a series of glyphs.</returns>
        public ImmutableArray <GlyphLayout> GenerateLayout(string text, FontSpan style)
        {
            AppliedFontStyle   spanStyle = style.GetStyle(0, text.Length);
            List <GlyphLayout> layout    = new List <GlyphLayout>(text.Length);

            float         lineHeight            = 0f;
            Vector2       location              = Vector2.Zero;
            float         lineHeightOfFirstLine = 0;
            bool          firstLine             = true;
            GlyphInstance previousGlyph         = null;
            float         scale = 0;

            for (var i = 0; i < text.Length; i++)
            {
                if (spanStyle.End < i)
                {
                    spanStyle     = style.GetStyle(i, text.Length);
                    previousGlyph = null;
                }

                if (spanStyle.Font.LineHeight > lineHeight)
                {
                    // get the larget lineheight thus far
                    scale      = spanStyle.Font.EmSize * 72;
                    lineHeight = (spanStyle.Font.LineHeight * spanStyle.PointSize) / scale;
                }

                if (firstLine && lineHeight > lineHeightOfFirstLine)
                {
                    lineHeightOfFirstLine = lineHeight;
                }

                var c = text[i];
                switch (c)
                {
                case '\r':
                    // carrage return resets the XX coordinate to 0
                    location.X    = 0;
                    previousGlyph = null;

                    break;

                case '\n':
                    // carrage return resets the XX coordinate to 0
                    location.X    = 0;
                    location.Y   += lineHeight;
                    lineHeight    = 0;  // reset line height tracking for next line
                    previousGlyph = null;
                    firstLine     = false;
                    break;

                case '\t':
                {
                    var glyph   = spanStyle.Font.GetGlyph(c);
                    var width   = (glyph.AdvanceWidth * spanStyle.PointSize) / scale;
                    var tabStop = width * spanStyle.TabWidth;

                    // advance to a position > width away that
                    var dist = tabStop - ((location.X + width) % tabStop);
                    location.X   += dist;
                    previousGlyph = null;
                }

                break;

                case ' ':
                {
                    var glyph = spanStyle.Font.GetGlyph(c);
                    var width = (glyph.AdvanceWidth * spanStyle.PointSize) / scale;
                    location.X   += width;
                    previousGlyph = null;
                }

                break;

                default:
                {
                    var glyph  = spanStyle.Font.GetGlyph(c);
                    var width  = (glyph.AdvanceWidth * spanStyle.PointSize) / scale;
                    var height = (glyph.Height * spanStyle.PointSize) / scale;

                    var glyphLocation = location;
                    if (spanStyle.ApplyKerning && previousGlyph != null)
                    {
                        // if there is special instructions for this glyph pair use that width
                        var scaledOffset = (spanStyle.Font.GetOffset(glyph, previousGlyph) * spanStyle.PointSize) / scale;

                        glyphLocation += scaledOffset;

                        // only fix the 'X' of the current tracked location but use the actual 'X'/'Y' of the offset
                        location.X = glyphLocation.X;
                    }

                    layout.Add(new GlyphLayout(new Glyph(glyph, spanStyle.PointSize), glyphLocation, width, height));

                    // move foraward the actual with of the glyph, we are retaining the baseline
                    location.X   += width;
                    previousGlyph = glyph;
                }

                break;
                }
            }

            var offset = new Vector2(0, lineHeightOfFirstLine);

            for (var i = 0; i < layout.Count; i++)
            {
                var glyphLayout = layout[i];
                layout[i] = new GlyphLayout(glyphLayout.Glyph, glyphLayout.Location + offset, glyphLayout.Width, glyphLayout.Height);
            }
            return(layout.ToImmutableArray());
        }