/// <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); }
/// <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); }
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); }
/// <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); }
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(); } }
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; } }
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); } } }