Exemple #1
0
        /// <summary>
        /// Add a letter to the layouter.
        /// </summary>
        /// <param name="c">The letter to add.</param>
        /// <param name="g">The atlas glyph corresponding to the letter.</param>
        /// <returns>The draw position of the letter.</returns>
        public virtual Vector2 AddLetter(char c, out DrawableGlyph g)
        {
            Vector2 position = GetNextGlyphPosition(_pen, c, out Vector2 drawPosition, out g);

            _pen = position;
            if (g == null)
            {
                return(position);
            }
            _pen.X += g.XAdvance;
            return(drawPosition);
        }
Exemple #2
0
        /// <summary>
        /// Add a letter to the layouter.
        /// </summary>
        /// <param name="c">The letter to add.</param>
        /// <param name="g">The atlas glyph corresponding to the letter.</param>
        /// <returns>The draw position of the letter.</returns>
        public override Vector2 AddLetter(char c, out DrawableGlyph g)
        {
            if (_newLineIndices.IndexOf(_counter) != -1)
            {
                NewLine();
            }
            _counter++;

            Vector2 position = base.AddLetter(c, out g);

            if (_singleLineNegativeY != 0)
            {
                position.Y -= _singleLineNegativeY;
            }
            return(position);
        }
Exemple #3
0
        public static unsafe DrawableFontAtlas RenderFontStbPacked(byte[] ttf, float fontSize, Vector2 atlasSize, int numChars, Font f, out StbTrueType.stbtt_fontinfo fontInfo)
        {
            var atlasObj = new DrawableFontAtlas(f, fontSize);

            fontSize = atlasObj.FontSize;

            fontInfo = new StbTrueType.stbtt_fontinfo();
            fixed(byte *ttPtr = &ttf[0])
            {
                StbTrueType.stbtt_InitFont(fontInfo, ttPtr, 0);

                float scaleFactor = StbTrueType.stbtt_ScaleForMappingEmToPixels(fontInfo, fontSize);
                int   ascent, descent, lineGap;

                StbTrueType.stbtt_GetFontVMetrics(fontInfo, &ascent, &descent, &lineGap);

                atlasSize *= 3; // Needs to be big as the packing sucks, and glyphs getting cut out messes with the tests.
                var pixels = new byte[(int)atlasSize.X * (int)atlasSize.Y];

                var pc = new StbTrueType.stbtt_pack_context();

                fixed(byte *pixelsPtr = pixels)
                {
                    StbTrueType.stbtt_PackBegin(pc, pixelsPtr, (int)atlasSize.X, (int)atlasSize.Y, (int)atlasSize.X, 1, null);
                }

                var cd = new StbTrueType.stbtt_packedchar[numChars];

                fixed(StbTrueType.stbtt_packedchar *charPtr = cd)
                {
                    StbTrueType.stbtt_PackFontRange(pc, ttPtr, 0, -fontSize, 0, numChars, charPtr);
                }

                StbTrueType.stbtt_PackEnd(pc);
                for (var i = 0; i < cd.Length; ++i)
                {
                    var atlasGlyph = DrawableGlyph.CreateForTest(cd[i].xadvance, cd[i].xoff, cd[i].x1 - cd[i].x0, cd[i].y1 - cd[i].y0);
                    atlasGlyph.GlyphUV       = new Rectangle(cd[i].x0, cd[i].y0, atlasGlyph.Width, atlasGlyph.Height);
                    atlasObj.Glyphs[(char)i] = atlasGlyph;
                }
            }

            return(atlasObj);
        }
Exemple #4
0
        /// <summary>
        /// Returns the position of the next glyph.
        /// </summary>
        /// <param name="pen">The position of the pen.</param>
        /// <param name="c">The character which is the next glyph.</param>
        /// <param name="drawPosition">The position to draw the character at.</param>
        /// <param name="g">The atlas glyph corresponding to the provided character, or null if none.</param>
        /// <returns>The position of the next glyph along the pen.</returns>
        public Vector2 GetNextGlyphPosition(Vector2 pen, char c, out Vector2 drawPosition, out DrawableGlyph g)
        {
            var result = new Vector2(pen.X, pen.Y);

            drawPosition = Vector2.Zero;
            g            = null;

            if (c == '\n')
            {
                result.X  = 0;
                result.Y += MathF.Round(_atlas.FontHeight);
            }
            else
            {
                if (!_atlas.Glyphs.TryGetValue(c, out g))
                {
                    if (!_hasZeroGlyph)
                    {
                        return(result);
                    }
                    g = _atlas.Glyphs[(char)0];
                }

                drawPosition    = result;
                drawPosition.X += g.XBearing;
            }

            return(result);
        }
Exemple #5
0
        public void VerifyFontRendering()
        {
            var fonts = new[]
            {
                "Junction-Bold.otf",   // Cff
                "CaslonOS.otf",        // Cff 2 (covers other cases)
                "1980XX.ttf",          // Ttf
                "LatoWeb-Regular.ttf", // Composite
                "Junction-Bold.otf",   // 14 font size
                "Junction-Bold.otf"    // 11 font size
            };

            var names = new[]
            {
                "Junction-Bold",
                "CaslonOS-Regular",
                "1980XX",
                "Lato Regular",
                "Junction-Bold",
                "Junction-Bold"
            };

            var unitsPerEm = new[]
            {
                1000,
                1000,
                1024,
                2000,
                1000,
                1000
            };

            int[] descender =
            {
                -250,
                -360,
                -128,
                -426,
                -250,
                -250
            };

            var ascender = new[]
            {
                750,
                840,
                682,
                1974,
                750,
                750
            };

            var glyphs = new[]
            {
                270,
                279,
                141,
                2164,
                270,
                270
            };

            string[] cachedRender =
            {
                ResultDb.EmotionCffAtlas,
                "",
                ResultDb.EmotionTtAtlas,
                ResultDb.EmotionCompositeAtlas,
                "",
                ""
            };

            int[] fontSizes =
            {
                17,
                17,
                17,
                17,
                14,
                11
            };

            FrameBuffer b = null;

            for (var i = 0; i < fonts.Length; i++)
            {
                Engine.Log.Info($"Running font {fonts[i]} ({i})...", TestRunnerLogger.TestRunnerSrc);
                ReadOnlyMemory <byte> data = Engine.AssetLoader.Get <OtherAsset>($"Fonts/{fonts[i]}")?.Content ?? ReadOnlyMemory <byte> .Empty;
                var f = new Font(data);

                // Verify basic font data.
                Assert.True(f.Valid);
                Assert.True(f.FullName == names[i]);
                Assert.True(f.UnitsPerEm == unitsPerEm[i]);
                Assert.True(f.Descender == descender[i]);
                Assert.True(f.Ascender == ascender[i]);
                Assert.True(f.CharToGlyph.Count == glyphs[i]);

                // Get atlases.
                int fontSize     = fontSizes[i];
                var emotionAtlas = new StbDrawableFontAtlas(f, fontSize, false);
                Runner.ExecuteAsLoop(_ =>
                {
                    var str = "";
                    for (uint j = emotionAtlas.Font.FirstCharIndex; j < emotionAtlas.Font.LastCharIndex; j++)
                    {
                        str += (char)j;
                    }

                    emotionAtlas.CacheGlyphs(str);
                }).WaitOne();
                DrawableFontAtlas packedStbAtlas = RenderFontStbPacked(data.ToArray(), fontSize, emotionAtlas.Texture.Size * 3, (int)f.LastCharIndex + 1, f, out StbTrueType.stbtt_fontinfo stbFont);

                // Compare glyph parsing.
                CompareMetricsWithStb(f, emotionAtlas, stbFont);

                // Compare render metrics.
                foreach (KeyValuePair <char, DrawableGlyph> g in emotionAtlas.Glyphs)
                {
                    DrawableGlyph glyph = packedStbAtlas.Glyphs[g.Key];
                    Assert.Equal(glyph.XAdvance, g.Value.XAdvance);

                    var fontGlyph = g.Value.FontGlyph;
                    int width     = (int)(MathF.Ceiling(fontGlyph.Max.X * emotionAtlas.RenderScale) - MathF.Floor(fontGlyph.Min.X * emotionAtlas.RenderScale));
                    int height    = (int)(MathF.Ceiling(-fontGlyph.Min.Y * emotionAtlas.RenderScale) - MathF.Floor(-fontGlyph.Max.Y * emotionAtlas.RenderScale));

                    Assert.Equal(glyph.Width, width);
                    Assert.Equal(glyph.Height, height);
                }

                // Check if there's a verified render.
                if (string.IsNullOrEmpty(cachedRender[i]))
                {
                    continue;
                }

                // Compare with cached render.
                // ReSharper disable AccessToModifiedClosure

                Runner.ExecuteAsLoop(_ =>
                {
                    if (b == null)
                    {
                        b = new FrameBuffer(emotionAtlas.Texture.Size).WithColor();
                    }
                    else
                    {
                        b.Resize(emotionAtlas.Texture.Size, true);
                    }

                    RenderComposer composer = Engine.Renderer.StartFrame();
                    composer.RenderToAndClear(b);
                    composer.RenderSprite(Vector3.Zero, emotionAtlas.Texture.Size, Color.White, emotionAtlas.Texture);
                    composer.RenderTo(null);
                    Engine.Renderer.EndFrame();
                    Runner.VerifyScreenshot(cachedRender[i], b);
                }).WaitOne();
            }
        }
Exemple #6
0
        public static void RenderGlyph(Font font, StbGlyphCanvas canvas, DrawableGlyph glyph, float scale, bool invertY = false)
        {
            if (scale == 0f)
            {
                return;
            }
            if (canvas.Width == 0 || canvas.Height == 0)
            {
                return;
            }

            const float flatnessInPixels = 0.35f;

            Vector2[]? windings = FlattenCurves(glyph.FontGlyph.Commands, flatnessInPixels / scale, out int[]? contourLengths);

            if (windings == null || contourLengths == null)
            {
                return;
            }

            float scaleX = scale;
            float scaleY = scale;

            if (invertY)
            {
                scaleY = -scaleY;
            }
            int n = contourLengths.Sum();

            var e = new StbRendererGlyphEdge[n + 1];

            n = 0;
            var wOffset   = 0;
            var subSample = 1;

            for (var i = 0; i < contourLengths.Length; i++)
            {
                int j = contourLengths[i] - 1;
                for (var k = 0; k < contourLengths[i]; j = k++)
                {
                    int a = k;
                    int b = j;
                    if (windings[wOffset + j].Y == windings[wOffset + k].Y)
                    {
                        continue;
                    }
                    e[n].Invert = false;
                    if (invertY && windings[wOffset + j].Y > windings[wOffset + k].Y || !invertY && windings[wOffset + j].Y < windings[wOffset + k].Y)
                    {
                        e[n].Invert = true;
                        a           = j;
                        b           = k;
                    }

                    e[n].X  = windings[wOffset + a].X * scaleX;
                    e[n].Y  = windings[wOffset + a].Y * scaleY * subSample;
                    e[n].X1 = windings[wOffset + b].X * scaleX;
                    e[n].Y1 = windings[wOffset + b].Y * scaleY * subSample;
                    n++;
                }

                wOffset += contourLengths[i];
            }

            Array.Resize(ref e, n + 1); // todo

            QuickSortEdges(new Span <StbRendererGlyphEdge>(e), n);

            // Insert Sort
            for (var i = 1; i < n; i++)
            {
                StbRendererGlyphEdge t = e[i];
                int j = i;
                while (j > 0)
                {
                    ref StbRendererGlyphEdge b = ref e[j - 1];
                    if (!(t.Y < b.Y))
                    {
                        break;
                    }
                    e[j] = e[j - 1];
                    j--;
                }

                if (i != j)
                {
                    e[j] = t;
                }
            }
Exemple #7
0
        public static void RenderGlyphs(Font font, RenderComposer composer, List <DrawableGlyph> glyphs, Vector3 offset, uint clr, float scale, Rectangle[]?dstRects = null)
        {
            for (var c = 0; c < glyphs.Count; c++)
            {
                DrawableGlyph atlasGlyph = glyphs[c];
                FontGlyph     fontGlyph  = atlasGlyph.FontGlyph;

                Rectangle dst = dstRects != null ? dstRects[c] : atlasGlyph.GlyphUV;
                if (CLIP_GLYPH_TEXTURES)
                {
                    composer.SetClipRect(dst);
                }

                float   baseline       = font.Descender * scale;
                Vector3 atlasRenderPos = (dst.Position - new Vector2(atlasGlyph.XBearing, baseline)).ToVec3();
                atlasRenderPos += offset;
                composer.PushModelMatrix(Matrix4x4.CreateScale(scale, scale, 1) * Matrix4x4.CreateTranslation(atlasRenderPos));

                // Count vertices.
                GlyphDrawCommand[]? commands = fontGlyph.Commands;
                if (commands == null)
                {
                    continue;
                }

                var verticesCount = 0;
                for (var v = 0; v < commands.Length; v++)
                {
                    GlyphDrawCommand currentCommand = commands[v];
                    if (currentCommand.Type != GlyphDrawCommandType.Move)
                    {
                        verticesCount++;
                    }
                }

                // Draw lines between all vertex points.
                Span <VertexData> lines = composer.RenderStream.GetStreamMemory((uint)(verticesCount * 3), BatchMode.SequentialTriangles);
                for (var j = 0; j < lines.Length; j++)
                {
                    lines[j].UV    = Vector2.Zero;
                    lines[j].Color = clr;
                }

                Vector3 prevPos             = Vector3.Zero;
                int     currentContourStart = -1;
                var     currentVertIdx      = 0;
                for (var i = 0; i < commands.Length; i++)
                {
                    GlyphDrawCommand currentCommand = commands[i];
                    if (currentContourStart == -1)
                    {
                        currentContourStart = i;
                    }
                    if (currentCommand.Type != GlyphDrawCommandType.Move)
                    {
                        Vector3 currentVertPos = currentCommand.P0.ToVec3();

                        if (currentCommand.Type == GlyphDrawCommandType.Close)
                        {
                            GlyphDrawCommand startingVert = commands[currentContourStart];
                            currentVertPos      = startingVert.P0.ToVec3();
                            currentContourStart = -1;
                        }

                        lines[currentVertIdx].Vertex = Vector3.Zero;
                        currentVertIdx++;
                        lines[currentVertIdx].Vertex = currentVertPos;
                        currentVertIdx++;
                        lines[currentVertIdx].Vertex = prevPos;
                        currentVertIdx++;
                    }

                    prevPos = currentCommand.P0.ToVec3();
                }

                // Draw curves. These will flip pixels in the curve approximations from above.
                for (var i = 0; i < commands.Length; i++)
                {
                    GlyphDrawCommand currentCommand = commands[i];
                    if (currentCommand.Type == GlyphDrawCommandType.Curve)
                    {
                        Span <VertexData> memory = composer.GetStreamedQuadraticCurveMesh(prevPos, currentCommand.P0.ToVec3(), currentCommand.P1.ToVec3());
                        for (var j = 0; j < memory.Length; j++)
                        {
                            memory[j].UV    = Vector2.Zero;
                            memory[j].Color = clr;
                        }
                    }

                    prevPos = currentCommand.P0.ToVec3();
                }

                composer.PopModelMatrix();
                if (CLIP_GLYPH_TEXTURES)
                {
                    composer.SetClipRect(null);
                }
            }
        }