|
|
using Microsoft.Xna.Framework;
|
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
|
using System;
|
|
|
|
|
|
namespace FontStashSharp
|
|
|
{
|
|
|
internal class FontAtlas
|
|
|
{
|
|
|
public int Width
|
|
|
{
|
|
|
get; private set;
|
|
|
}
|
|
|
|
|
|
public int Height
|
|
|
{
|
|
|
get; private set;
|
|
|
}
|
|
|
|
|
|
public int NodesNumber
|
|
|
{
|
|
|
get; private set;
|
|
|
}
|
|
|
|
|
|
public FontAtlasNode[] Nodes
|
|
|
{
|
|
|
get; private set;
|
|
|
}
|
|
|
|
|
|
public Texture2D Texture
|
|
|
{
|
|
|
get; set;
|
|
|
}
|
|
|
|
|
|
public FontAtlas(int w, int h, int count)
|
|
|
{
|
|
|
Width = w;
|
|
|
Height = h;
|
|
|
Nodes = new FontAtlasNode[count];
|
|
|
count = 0;
|
|
|
Nodes[0].X = 0;
|
|
|
Nodes[0].Y = 0;
|
|
|
Nodes[0].Width = w;
|
|
|
NodesNumber++;
|
|
|
}
|
|
|
|
|
|
public void InsertNode(int idx, int x, int y, int w)
|
|
|
{
|
|
|
if (NodesNumber + 1 > Nodes.Length)
|
|
|
{
|
|
|
var oldNodes = Nodes;
|
|
|
var newLength = Nodes.Length == 0 ? 8 : Nodes.Length * 2;
|
|
|
Nodes = new FontAtlasNode[newLength];
|
|
|
for (var i = 0; i < oldNodes.Length; ++i)
|
|
|
{
|
|
|
Nodes[i] = oldNodes[i];
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (var i = NodesNumber; i > idx; i--)
|
|
|
Nodes[i] = Nodes[i - 1];
|
|
|
Nodes[idx].X = x;
|
|
|
Nodes[idx].Y = y;
|
|
|
Nodes[idx].Width = w;
|
|
|
NodesNumber++;
|
|
|
}
|
|
|
|
|
|
public void RemoveNode(int idx)
|
|
|
{
|
|
|
if (NodesNumber == 0)
|
|
|
return;
|
|
|
for (var i = idx; i < NodesNumber - 1; i++)
|
|
|
Nodes[i] = Nodes[i + 1];
|
|
|
NodesNumber--;
|
|
|
}
|
|
|
|
|
|
public void Expand(int w, int h)
|
|
|
{
|
|
|
if (w > Width)
|
|
|
InsertNode(NodesNumber, Width, 0, w - Width);
|
|
|
Width = w;
|
|
|
Height = h;
|
|
|
}
|
|
|
|
|
|
public void Reset(int w, int h)
|
|
|
{
|
|
|
Width = w;
|
|
|
Height = h;
|
|
|
NodesNumber = 0;
|
|
|
Nodes[0].X = 0;
|
|
|
Nodes[0].Y = 0;
|
|
|
Nodes[0].Width = w;
|
|
|
NodesNumber++;
|
|
|
}
|
|
|
|
|
|
public bool AddSkylineLevel(int idx, int x, int y, int w, int h)
|
|
|
{
|
|
|
InsertNode(idx, x, y + h, w);
|
|
|
for (var i = idx + 1; i < NodesNumber; i++)
|
|
|
if (Nodes[i].X < Nodes[i - 1].X + Nodes[i - 1].Width)
|
|
|
{
|
|
|
var shrink = Nodes[i - 1].X + Nodes[i - 1].Width - Nodes[i].X;
|
|
|
Nodes[i].X += shrink;
|
|
|
Nodes[i].Width -= shrink;
|
|
|
if (Nodes[i].Width <= 0)
|
|
|
{
|
|
|
RemoveNode(i);
|
|
|
i--;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
for (var i = 0; i < NodesNumber - 1; i++)
|
|
|
if (Nodes[i].Y == Nodes[i + 1].Y)
|
|
|
{
|
|
|
Nodes[i].Width += Nodes[i + 1].Width;
|
|
|
RemoveNode(i + 1);
|
|
|
i--;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public int RectFits(int i, int w, int h)
|
|
|
{
|
|
|
var x = Nodes[i].X;
|
|
|
var y = Nodes[i].Y;
|
|
|
if (x + w > Width)
|
|
|
return -1;
|
|
|
var spaceLeft = w;
|
|
|
while (spaceLeft > 0)
|
|
|
{
|
|
|
if (i == NodesNumber)
|
|
|
return -1;
|
|
|
y = Math.Max(y, Nodes[i].Y);
|
|
|
if (y + h > Height)
|
|
|
return -1;
|
|
|
spaceLeft -= Nodes[i].Width;
|
|
|
++i;
|
|
|
}
|
|
|
|
|
|
return y;
|
|
|
}
|
|
|
|
|
|
public bool AddRect(int rw, int rh, ref int rx, ref int ry)
|
|
|
{
|
|
|
var besth = Height;
|
|
|
var bestw = Width;
|
|
|
var besti = -1;
|
|
|
var bestx = -1;
|
|
|
var besty = -1;
|
|
|
for (var i = 0; i < NodesNumber; i++)
|
|
|
{
|
|
|
var y = RectFits(i, rw, rh);
|
|
|
if (y != -1)
|
|
|
if (y + rh < besth || y + rh == besth && Nodes[i].Width < bestw)
|
|
|
{
|
|
|
besti = i;
|
|
|
bestw = Nodes[i].Width;
|
|
|
besth = y + rh;
|
|
|
bestx = Nodes[i].X;
|
|
|
besty = y;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (besti == -1)
|
|
|
return false;
|
|
|
if (!AddSkylineLevel(besti, bestx, besty, rw, rh))
|
|
|
return false;
|
|
|
|
|
|
rx = bestx;
|
|
|
ry = besty;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public void RenderGlyph(GraphicsDevice device, FontGlyph glyph)
|
|
|
{
|
|
|
if (glyph.Bounds.Width == 0 || glyph.Bounds.Height == 0)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Render glyph to byte buffer
|
|
|
var buffer = new byte[glyph.Bounds.Width * glyph.Bounds.Height];
|
|
|
Array.Clear(buffer, 0, buffer.Length);
|
|
|
|
|
|
var g = glyph.Index;
|
|
|
glyph.Font.RenderGlyphBitmap(buffer,
|
|
|
glyph.Bounds.Width,
|
|
|
glyph.Bounds.Height,
|
|
|
glyph.Bounds.Width,
|
|
|
g);
|
|
|
|
|
|
// Byte buffer to RGBA
|
|
|
var colorBuffer = new Color[glyph.Bounds.Width * glyph.Bounds.Height];
|
|
|
for (var i = 0; i < colorBuffer.Length; ++i)
|
|
|
{
|
|
|
var c = buffer[i];
|
|
|
colorBuffer[i].R = colorBuffer[i].G = colorBuffer[i].B = colorBuffer[i].A = c;
|
|
|
}
|
|
|
|
|
|
// Write to texture
|
|
|
if (Texture == null)
|
|
|
{
|
|
|
Texture = new Texture2D(device, Width, Height);
|
|
|
}
|
|
|
|
|
|
Texture.SetData(0, glyph.Bounds, colorBuffer, 0, colorBuffer.Length);
|
|
|
}
|
|
|
}
|
|
|
}
|