private GlyphPage FindPage(int width, int height, out int packX, out int packY) { foreach (var page in pages) { if (page.Pack(width, height, out var rect)) { packX = rect.X; packY = rect.Y; return(page); } } // none found, make a new page { var result = new GlyphPage(graphicsDevice, TextureSize); pages.Add(result); if (!result.Pack(width, height, out var rect)) { throw new Exception("Failed to pack item even in new page"); } packX = rect.X; packY = rect.Y; return(result); } }
// load one single glyph into the cache private GlyphData Load(char glyph, FormatOptions formatOptions) { int fontId = GetFontId(IsAnsiChar(glyph) ? formatOptions.AnsiFont : formatOptions.Font); var font = m_registeredFonts[fontId]; uint glyphId = ((uint)fontId << 16) + glyph; GlyphData glyphData; if (m_loadedGlyphs.TryGetValue(glyphId, out glyphData)) { glyphData.m_timeStamp = m_timeStamp; return glyphData; } var str = glyph.ToString(); var chRect = MeasureCharacter(glyph, m_registeredFonts[fontId].m_fontObject); chRect.Inflate(font.m_outlineThickness * 0.5f, font.m_outlineThickness * 0.5f); int width = Math.Max((int)Math.Ceiling(chRect.Width), 1); int height = Math.Max((int)Math.Ceiling(chRect.Height), 1); int pagesInX = (width - 1) / PageSize + 1; int pagesInY = (height - 1) / PageSize + 1; GlyphPage[] pages = new GlyphPage[pagesInX * pagesInY]; for (int i = 0; i < pages.Length; ++i) { pages[i].m_x = i % pagesInX; pages[i].m_y = i / pagesInX; pages[i].m_pageIndex = RequestPage(); } using (var bmp = new SystemDrawing.Bitmap(pagesInX * PageSize, pagesInY * PageSize)) using (var g = SystemDrawing.Graphics.FromImage(bmp)) using (var memStream = new MemoryStream()) { // draw text using GDI+ g.TextRenderingHint = SystemDrawing.Text.TextRenderingHint.AntiAliasGridFit; g.SmoothingMode = SystemDrawing.Drawing2D.SmoothingMode.AntiAlias; g.InterpolationMode = SystemDrawing.Drawing2D.InterpolationMode.HighQualityBicubic; if (font.m_outlineThickness > 0) { SystemDrawing.Pen outlinePen; if (!m_outlinePensWithWidths.TryGetValue(font.m_outlineThickness, out outlinePen)) { outlinePen = new SystemDrawing.Pen(SystemDrawing.Color.Gray, font.m_outlineThickness); outlinePen.MiterLimit = font.m_outlineThickness; m_outlinePensWithWidths.Add(font.m_outlineThickness, outlinePen); } // draw outline using (var outlinePath = new SystemDrawing.Drawing2D.GraphicsPath()) { outlinePath.AddString(str, font.m_fontObject.FontFamily, (int)font.m_fontObject.Style, g.DpiX * font.m_fontObject.SizeInPoints / 72, new SystemDrawing.PointF(-chRect.Left, -chRect.Top), SystemDrawing.StringFormat.GenericDefault); g.DrawPath(outlinePen, outlinePath); g.FillPath(m_whiteBrush, outlinePath); } } else { g.DrawString(str, font.m_fontObject, m_whiteBrush, new SystemDrawing.PointF(-chRect.Left, -chRect.Top)); } bmp.Save(memStream, System.Drawing.Imaging.ImageFormat.Png); using (var tmpTexture = Texture2D.FromStream(GameApp.Instance.GraphicsDevice, memStream)) { var device = GameApp.Instance.GraphicsDevice; device.DepthStencilState = DepthStencilState.None; device.RasterizerState = RasterizerState.CullCounterClockwise; device.Indices = null; m_effect.CurrentTechnique = m_techBlit; m_paramTexture.SetValue(tmpTexture); foreach (var batch in pages.GroupBy(page => page.m_pageIndex / PagesInOneCacheTexture)) { var textureId = batch.Key; device.SetRenderTarget(m_cacheTextures[textureId].m_physicalRTTexture); device.BlendState = m_channelMasks[textureId % 4]; var pagesInBatch = batch.ToArray(); var vertices = new VertexDataBlit[pagesInBatch.Length * 6]; for (int i = 0; i < pagesInBatch.Length; ++i) { var page = pagesInBatch[i]; var dstRectLeft = (page.m_pageIndex % PagesInOneCacheTexture) % PagesInOneRow * PageSize; var dstRectTop = (page.m_pageIndex % PagesInOneCacheTexture) / PagesInOneRow * PageSize; float posLeft = (dstRectLeft - 0.5f) / CacheTextureSize * 2 - 1; float posTop = 1 - (dstRectTop - 0.5f) / CacheTextureSize * 2; float posWidth = PageSize / (float)CacheTextureSize * 2; float posHeight = -PageSize / (float)CacheTextureSize * 2; float uvLeft = page.m_x / (float)pagesInX; float uvTop = page.m_y / (float)pagesInY; float uvWidth = 1.0f / pagesInX; float uvHeight = 1.0f / pagesInY; // left-top vertices[i * 6 + 0].pos = new Vector2(posLeft, posTop); vertices[i * 6 + 0].uv = new Vector2(uvLeft, uvTop); // right-top vertices[i * 6 + 1].pos = vertices[i * 6 + 4].pos = new Vector2(posLeft + posWidth, posTop); vertices[i * 6 + 1].uv = vertices[i * 6 + 4].uv = new Vector2(uvLeft + uvWidth, uvTop); // left-bottom vertices[i * 6 + 2].pos = vertices[i * 6 + 3].pos = new Vector2(posLeft, posTop + posHeight); vertices[i * 6 + 2].uv = vertices[i * 6 + 3].uv = new Vector2(uvLeft, uvTop + uvHeight); // right-bottom vertices[i * 6 + 5].pos = new Vector2(posLeft + posWidth, posTop + posHeight); vertices[i * 6 + 5].uv = new Vector2(uvLeft + uvWidth, uvTop + uvHeight); } foreach (var pass in m_techBlit.Passes) { pass.Apply(); device.DrawUserPrimitives(PrimitiveType.TriangleList, vertices, 0, pagesInBatch.Length * 2); } } device.SetRenderTarget(null); } } glyphData = new GlyphData(); glyphData.m_pageIndices = new int[pagesInX, pagesInY]; pages.ForEach(page => glyphData.m_pageIndices[page.m_x, page.m_y] = page.m_pageIndex); glyphData.m_glyphSize = chRect.Size; glyphData.m_timeStamp = m_timeStamp; m_loadedGlyphs.Add(glyphId, glyphData); return glyphData; }