#region Using ステートメント
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
#endregion
namespace WpfFontPipeline
{
///
/// レイアウト対象になる矩形情報
///
class BoxLayoutItem
{
///
/// 矩形情報
///
public Rectangle Bounds { get; set; }
///
/// タグ
///
public object Tag { get; set; }
///
/// 配置済みか?
///
public bool Placed { get; set; }
}
///
/// 複数矩形を1つのテクスチャにまとめるヘルパークラス
///
///
/// 高速化の為に処理する矩形の多くが同じ高さであるという前提のアルゴリズムを使用
///
class BoxLayouter
{
#region 定数
///
/// 矩形間の空きスペース
///
readonly int PadSize = 1;
#endregion
#region プロパティ
///
/// 登録された矩形要素の取得と設定
///
public List Items {get; set; }
#endregion
#region パブリックメソッド
///
/// コンストラクタ
///
public BoxLayouter()
{
Items = new List();
}
///
/// 矩形の追加
///
/// 追加する矩形情報
public void Add(BoxLayoutItem item)
{
// 矩形の高さ毎に分類、格納する
List itemBacket;
if (!itemBackets.TryGetValue(item.Bounds.Height, out itemBacket))
{
itemBacket = new List();
itemBackets.Add(item.Bounds.Height, itemBacket);
}
itemBacket.Add(item);
// 必要な矩形面積を追加
int w = item.Bounds.Width + PadSize;
int h = item.Bounds.Height + PadSize;
totalAreaSize += (w * h);
Items.Add(item);
}
///
/// レイアウトの実行
///
/// レイアウトに必要な横幅
/// レイアウトに必要な高さ
public void Layout(out int outWidth, out int outHeight)
{
// 総面積から必要となるサイズを概算
int size = (int)Math.Sqrt(totalAreaSize);
int h = (int)(Math.Pow(2, (int)(Math.Log(size, 2) - 0.5)));
int w = (int)(Math.Pow(2, (int)(Math.Log(size, 2) + 0.5)));
while ((long)w * (long)h < totalAreaSize)
{
if (w <= h)
w *= 2;
else
h *= 2;
}
// 高さ、幅の順に並び替える
var keys = from key in itemBackets.Keys orderby key descending select key;
sortedKeys = keys.ToList();
foreach (int key in sortedKeys)
{
var items = from item in itemBackets[key]
orderby item.Bounds.Width descending
select item;
itemBackets[key] = items.ToList();
}
// 現在のサイズで配置してみる
while (TryLayout(w, h) == false)
{
// 全部配置しきれなかったので、サイズを大きくして再試行
ClearPlacedInfo();
if (w <= h)
w *= 2;
else
h *= 2;
}
outWidth = w;
outHeight = h;
}
#endregion
#region プライベートメソッド
///
/// 配置処理
///
bool TryLayout(int width, int height)
{
// 配置位置
int x = PadSize;
int y = PadSize;
// 列の高さ
int lineHeight = sortedKeys[0];
// 高さの順に配置
foreach (int key in sortedKeys)
{
var itemBacket = itemBackets[key];
for (int i = 0; i < itemBacket.Count; ++i)
{
var item = itemBacket[i];
// 既に配置済みか?
if (item.Placed)
continue;
// 現在の列に配置できるか?
if (x + item.Bounds.Width + PadSize < width)
{
// 現在の列の右端に追加
var bounds = item.Bounds;
bounds.X = x;
bounds.Y = y;
item.Bounds = bounds;
item.Placed = true;
x += item.Bounds.Width + PadSize;
}
else
{
// 右端の空きスペースに配置できるか試す
// 幅の狭いものから試す
for (int j = itemBacket.Count - 1; i < j; --j)
{
var narrowItem = itemBacket[j];
// 配置済みのものは無視
if (narrowItem.Placed)
continue;
// この列に、これ以上配置できない
if (x + narrowItem.Bounds.Width + PadSize >= width)
break;
var bounds = narrowItem.Bounds;
bounds.X = x;
bounds.Y = y;
narrowItem.Bounds = bounds;
narrowItem.Placed = true;
x += narrowItem.Bounds.Width + PadSize;
}
// 次の行へ移動
y += lineHeight + PadSize;
// おっと、サイズ足らずで配置しきれなかった
if (y + lineHeight > height)
return false;
lineHeight = key;
x = PadSize;
--i;
}
}
}
return true;
}
///
/// 配置情報の初期化
///
void ClearPlacedInfo()
{
foreach (var itemBacket in itemBackets.Values)
{
foreach (var item in itemBacket)
item.Placed = false;
}
}
#endregion
#region プライベートフィールド
// 現在の行情報をDictionaryとして格納
Dictionary> itemBackets =
new Dictionary>();
// 追加された矩形の総面積
long totalAreaSize;
// 高さ毎に並び替える為のソートキー
List sortedKeys;
#endregion
}
}