public static unsafe void CompareMetricsWithStb(Font f, DrawableFontAtlas atlas, StbTrueType.stbtt_fontinfo stbFont) { var pc = new StbTrueType.stbtt_pack_context(); StbTrueType.stbtt_PackBegin(pc, (byte *)0, 512, 512, 512, 1, null); var cd = new StbTrueType.stbtt_packedchar[f.LastCharIndex + 1]; StbTrueType.stbrp_rect[] rects; fixed(StbTrueType.stbtt_packedchar *charDataPtr = &cd[0]) { var range = new StbTrueType.stbtt_pack_range { first_unicode_codepoint_in_range = 0, array_of_unicode_codepoints = null, num_chars = (int)f.LastCharIndex + 1, chardata_for_range = charDataPtr, font_size = -atlas.FontSize }; rects = new StbTrueType.stbrp_rect[f.LastCharIndex + 1]; fixed(StbTrueType.stbrp_rect *rectPtr = &rects[0]) { int n = StbTrueType.stbtt_PackFontRangesGatherRects(pc, stbFont, &range, 1, rectPtr); StbTrueType.stbtt_PackFontRangesPackRects(pc, rectPtr, n); } } foreach ((char charIndex, DrawableGlyph atlasGlyph) in atlas.Glyphs) { FontGlyph glyph = atlasGlyph.FontGlyph; var advance = 0; var bearing = 0; StbTrueType.stbtt_GetCodepointHMetrics(stbFont, charIndex, &advance, &bearing); Assert.True(advance == glyph.AdvanceWidth); Assert.True(bearing == glyph.LeftSideBearing || glyph.LeftSideBearing == 0); // stb has junk data beyond valid var minX = 0; var maxX = 0; var minY = 0; var maxY = 0; StbTrueType.stbtt_GetCodepointBitmapBoxSubpixel(stbFont, charIndex, atlas.RenderScale, atlas.RenderScale, 0, 0, &minX, &minY, &maxX, &maxY); Rectangle bbox = glyph.GetBBox(atlas.RenderScale); Assert.Equal(minX, bbox.X); Assert.Equal(minY, bbox.Y); Assert.Equal(maxX, bbox.Width); Assert.Equal(maxY, bbox.Height); Rectangle drawBox = new Rectangle(0, 0, bbox.Width - bbox.X, bbox.Height - bbox.Y); drawBox.Size += Vector2.One; // Add padding from stb StbTrueType.stbrp_rect rect = rects[charIndex]; Assert.Equal(rect.w, drawBox.Width); Assert.Equal(rect.h, drawBox.Height); } }
public void Begin(int width, int height) { bitmapWidth = width; bitmapHeight = height; _bitmap = new byte[width * height]; _context = new StbTrueType.stbtt_pack_context(); fixed(byte *pixelsPtr = _bitmap) { StbTrueType.stbtt_PackBegin(_context, pixelsPtr, width, height, width, 1, null); } _glyphs = new Dictionary <int, GlyphInfo>(); }
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); }
public static TtfFontBakerResult Bake(byte[] ttf, float fontPixelHeight, int bitmapWidth, int bitmapHeight, IEnumerable <CharacterRange> characterRanges) { if (ttf == null || ttf.Length == 0) { throw new ArgumentNullException(nameof(ttf)); } if (fontPixelHeight <= 0) { throw new ArgumentOutOfRangeException(nameof(fontPixelHeight)); } if (bitmapWidth <= 0) { throw new ArgumentOutOfRangeException(nameof(bitmapWidth)); } if (bitmapHeight <= 0) { throw new ArgumentOutOfRangeException(nameof(bitmapHeight)); } if (characterRanges == null) { throw new ArgumentNullException(nameof(characterRanges)); } if (!characterRanges.Any()) { throw new ArgumentException("characterRanges must have a least one value."); } byte[] pixels; var glyphs = new Dictionary <int, GlyphInfo>(); fixed(byte *ttfPtr = ttf) { StbTrueType.stbtt_fontinfo fontInfo = new StbTrueType.stbtt_fontinfo(); if (StbTrueType.stbtt_InitFont(fontInfo, ttfPtr, 0) == 0) { throw new Exception("Failed to init font."); } float scaleFactor = StbTrueType.stbtt_ScaleForPixelHeight(fontInfo, fontPixelHeight); int ascent, descent, lineGap; StbTrueType.stbtt_GetFontVMetrics(fontInfo, &ascent, &descent, &lineGap); pixels = new byte[bitmapWidth * bitmapHeight]; StbTrueType.stbtt_pack_context pc = new StbTrueType.stbtt_pack_context(); fixed(byte *pixelsPtr = pixels) { StbTrueType.stbtt_PackBegin(pc, pixelsPtr, bitmapWidth, bitmapHeight, bitmapWidth, 1, null); } foreach (var range in characterRanges) { if (range.Start > range.End) { continue; } var cd = new StbTrueType.stbtt_packedchar[range.End - range.Start + 1]; fixed(StbTrueType.stbtt_packedchar *chardataPtr = cd) { StbTrueType.stbtt_PackFontRange(pc, ttfPtr, 0, fontPixelHeight, range.Start, range.End - range.Start + 1, chardataPtr); } for (var i = 0; i < cd.Length; ++i) { var yOff = cd[i].yoff; yOff += ascent * scaleFactor; var glyphInfo = new GlyphInfo { X = cd[i].x0, Y = cd[i].y0, Width = cd[i].x1 - cd[i].x0, Height = cd[i].y1 - cd[i].y0, XOffset = (int)cd[i].xoff, YOffset = (int)Math.Round(yOff), XAdvance = (int)Math.Round(cd[i].xadvance) }; glyphs[(char)(i + range.Start)] = glyphInfo; } } } return(new TtfFontBakerResult(glyphs, fontPixelHeight, pixels, bitmapWidth, bitmapHeight)); }