Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Imaging;
using System.IO;
namespace QuickFont
{
/// <summary>
/// Class for building a Quick Font, given a Font
/// and a configuration object.
/// </summary>
class Builder
{
private string charSet;
private QFontBuilderConfiguration config;
private Font font;
public Builder(Font font, QFontBuilderConfiguration config)
{
this.charSet = config.charSet;
this.config = config;
this.font = font;
}
private static Dictionary<char, QFontGlyph> CreateCharGlyphMapping(QFontGlyph[] glyphs)
{
var dict = new Dictionary<char, QFontGlyph>();
for (int i = 0; i < glyphs.Length; i++)
dict.Add(glyphs[i].character, glyphs[i]);
return dict;
}
//these do not affect the actual width of glyphs (we measure widths pixel-perfectly ourselves), but is used to detect whether a font is monospaced
private List<SizeF> GetGlyphSizes(Font font)
{
Bitmap bmp = new Bitmap(512, 512, PixelFormat.Format24bppRgb);
Graphics graph = Graphics.FromImage(bmp);
List<SizeF> sizes = new List<SizeF>();
for (int i = 0; i < charSet.Length; i++)
{
var charSize = graph.MeasureString("" + charSet[i], font);
sizes.Add(new SizeF(charSize.Width, charSize.Height));
}
graph.Dispose();
bmp.Dispose();
return sizes;
}
private SizeF GetMaxGlyphSize(List<SizeF> sizes)
{
SizeF maxSize = new SizeF(0f, 0f);
for (int i = 0; i < charSet.Length; i++)
{
if (sizes[i].Width > maxSize.Width)
maxSize.Width = sizes[i].Width;
if (sizes[i].Height > maxSize.Height)
maxSize.Height = sizes[i].Height;
}
return maxSize;
}
private SizeF GetMinGlyphSize(List<SizeF> sizes)
{
SizeF minSize = new SizeF(float.MaxValue, float.MaxValue);
for (int i = 0; i < charSet.Length; i++)
{
if (sizes[i].Width < minSize.Width)
minSize.Width = sizes[i].Width;
if (sizes[i].Height < minSize.Height)
minSize.Height = sizes[i].Height;
}
return minSize;
}
/// <summary>
/// Returns true if all glyph widths are within 5% of each other
/// </summary>
/// <param name="sizes"></param>
/// <returns></returns>
private bool IsMonospaced(List<SizeF> sizes)
{
var min = GetMinGlyphSize(sizes);
var max = GetMaxGlyphSize(sizes);
if (max.Width - min.Width < max.Width * 0.05f)
return true;
return false;
}
/*
private SizeF GetMaxGlyphSize(Font font)
{
Bitmap bmp = new Bitmap(256, 256, PixelFormat.Format24bppRgb);
Graphics graph = Graphics.FromImage(bmp);
SizeF maxSize = new SizeF(0f, 0f);
for (int i = 0; i < charSet.Length; i++)
{
var charSize = graph.MeasureString("" + charSet[i], font);
if (charSize.Width > maxSize.Width)
maxSize.Width = charSize.Width;
if (charSize.Height > maxSize.Height)
maxSize.Height = charSize.Height;
}
graph.Dispose();
bmp.Dispose();
return maxSize;
}*/
//The initial bitmap is simply a long thin strip of all glyphs in a row
private Bitmap CreateInitialBitmap(Font font, SizeF maxSize, int initialMargin, out QFontGlyph[] glyphs, TextGenerationRenderHint renderHint)
{
glyphs = new QFontGlyph[charSet.Length];
int spacing = (int)Math.Ceiling(maxSize.Width) + 2 * initialMargin;
Bitmap bmp = new Bitmap(spacing * charSet.Length, (int)Math.Ceiling(maxSize.Height) + 2 * initialMargin, PixelFormat.Format24bppRgb);
Graphics graph = Graphics.FromImage(bmp);
switch(renderHint){
case TextGenerationRenderHint.SizeDependent:
graph.TextRenderingHint = font.Size <= 12.0f ? TextRenderingHint.ClearTypeGridFit : TextRenderingHint.AntiAlias;
break;
case TextGenerationRenderHint.AntiAlias:
graph.TextRenderingHint = TextRenderingHint.AntiAlias;
break;
case TextGenerationRenderHint.AntiAliasGridFit:
graph.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
break;
case TextGenerationRenderHint.ClearTypeGridFit:
graph.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
break;
case TextGenerationRenderHint.SystemDefault:
graph.TextRenderingHint = TextRenderingHint.SystemDefault;
break;
}
int xOffset = initialMargin;
for (int i = 0; i < charSet.Length; i++)
{
graph.DrawString("" + charSet[i], font, Brushes.White, xOffset, initialMargin);
var charSize = graph.MeasureString("" + charSet[i], font);
glyphs[i] = new QFontGlyph(0, new Rectangle(xOffset - initialMargin, 0, (int)charSize.Width + initialMargin * 2, (int)charSize.Height + initialMargin * 2), 0, charSet[i]);
xOffset += (int)charSize.Width + initialMargin * 2;
}
graph.Flush();
graph.Dispose();
return bmp;
}
private delegate bool EmptyDel(BitmapData data, int x, int y);
private static void RetargetGlyphRectangleInwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance)
{
int startX, endX;
int startY, endY;
var rect = glyph.rect;
EmptyDel emptyPix;
if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb)
emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); };
else
emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyPixel(data, x, y); };
unsafe
{
for (startX = rect.X; startX < bitmapData.Width; startX++)
for (int j = rect.Y; j < rect.Y + rect.Height; j++)
if (!emptyPix(bitmapData, startX, j))
goto Done1;
Done1:
for (endX = rect.X + rect.Width; endX >= 0; endX--)
for (int j = rect.Y; j < rect.Y + rect.Height; j++)
if (!emptyPix(bitmapData, endX, j))
goto Done2;
Done2:
for (startY = rect.Y; startY < bitmapData.Height; startY++)
for (int i = startX; i < endX; i++)
if (!emptyPix(bitmapData, i, startY))
goto Done3;
Done3:
for (endY = rect.Y + rect.Height; endY >= 0; endY--)
for (int i = startX; i < endX; i++)
if (!emptyPix(bitmapData, i, endY))
goto Done4;
Done4:;
}
if (endY < startY)
startY = endY = rect.Y;
if (endX < startX)
startX = endX = rect.X;
glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1);
if (setYOffset)
glyph.yOffset = glyph.rect.Y;
}
private static void RetargetGlyphRectangleOutwards(BitmapData bitmapData, QFontGlyph glyph, bool setYOffset, byte alphaTolerance)
{
int startX,endX;
int startY,endY;
var rect = glyph.rect;
EmptyDel emptyPix;
if (bitmapData.PixelFormat == PixelFormat.Format32bppArgb)
emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyAlphaPixel(data, x, y, alphaTolerance); };
else
emptyPix = delegate(BitmapData data, int x, int y) { return QBitmap.EmptyPixel(data, x, y); };
unsafe
{
for (startX = rect.X; startX >= 0; startX--)
{
bool foundPix = false;
for (int j = rect.Y; j <= rect.Y + rect.Height; j++)
{
if (!emptyPix(bitmapData, startX, j))
{
foundPix = true;
break;
}
}
if (!foundPix)
{
startX++;
break;
}
}
for (endX = rect.X + rect.Width; endX < bitmapData.Width; endX++)
{
bool foundPix = false;
for (int j = rect.Y; j <= rect.Y + rect.Height; j++)
{
if (!emptyPix(bitmapData, endX, j))
{
foundPix = true;
break;
}
}
if (!foundPix)
{
endX--;
break;
}
}
for (startY = rect.Y; startY >= 0; startY--)
{
bool foundPix = false;
for (int i = startX; i <= endX; i++)
{
if (!emptyPix(bitmapData, i, startY))
{
foundPix = true;
break;
}
}
if (!foundPix)
{
startY++;
break;
}
}
for (endY = rect.Y + rect.Height; endY < bitmapData.Height; endY++)
{
bool foundPix = false;
for (int i = startX; i <= endX; i++)
{
if (!emptyPix(bitmapData, i, endY))
{
foundPix = true;
break;
}
}
if (!foundPix)
{
endY--;
break;
}
}
}
glyph.rect = new Rectangle(startX, startY, endX - startX + 1, endY - startY + 1);
if (setYOffset)
glyph.yOffset = glyph.rect.Y;
}
private static List<QBitmap> GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo)
{
var pages = new List<QBitmap>();
destGlyphs = new QFontGlyph[sourceGlyphs.Length];
QBitmap currentPage = null;
int maxY = 0;
foreach (var glph in sourceGlyphs)
maxY = Math.Max(glph.rect.Height, maxY);
int finalPageIndex = 0;
int finalPageRequiredWidth = 0;
int finalPageRequiredHeight = 0;
for (int k = 0; k < 2; k++)
{
bool pre = k == 0; //first iteration is simply to determine the required size of the final page, so that we can crop it in advance
int xPos = 0;
int yPos = 0;
int maxYInRow = 0;
int totalTries = 0;
for (int i = 0; i < sourceGlyphs.Length; i++)
{
if(!pre && currentPage == null){
if (finalPageIndex == pages.Count)
{
int width = Math.Min(destSheetWidth, usePowerOfTwo ? PowerOfTwo(finalPageRequiredWidth) : finalPageRequiredWidth);
int height = Math.Min(destSheetHeight, usePowerOfTwo ? PowerOfTwo(finalPageRequiredHeight) : finalPageRequiredHeight);
currentPage = new QBitmap(new Bitmap(width, height, PixelFormat.Format32bppArgb));
currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent
}
else
{
currentPage = new QBitmap(new Bitmap(destSheetWidth, destSheetHeight, PixelFormat.Format32bppArgb));
currentPage.Clear32(255, 255, 255, 0); //clear to white, but totally transparent
}
pages.Add(currentPage);
}
totalTries++;
if (totalTries > 10 * sourceGlyphs.Length)
throw new Exception("Failed to fit font into texture pages");
var rect = sourceGlyphs[i].rect;
if (xPos + rect.Width + 2 * destMargin <= destSheetWidth && yPos + rect.Height + 2 * destMargin <= destSheetHeight)
{
if (!pre)
{
//add to page
if(sourceBitmaps[sourceGlyphs[i].page].PixelFormat == PixelFormat.Format32bppArgb)
QBitmap.Blit(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin);
else
QBitmap.BlitMask(sourceBitmaps[sourceGlyphs[i].page], currentPage.bitmapData, rect.X, rect.Y, rect.Width, rect.Height, xPos + destMargin, yPos + destMargin);
destGlyphs[i] = new QFontGlyph(pages.Count - 1, new Rectangle(xPos + destMargin, yPos + destMargin, rect.Width, rect.Height), sourceGlyphs[i].yOffset, sourceGlyphs[i].character);
}
else
{
finalPageRequiredWidth = Math.Max(finalPageRequiredWidth, xPos + rect.Width + 2 * destMargin);
finalPageRequiredHeight = Math.Max(finalPageRequiredHeight, yPos + rect.Height + 2 * destMargin);
}
xPos += rect.Width + 2 * destMargin;
maxYInRow = Math.Max(maxYInRow, rect.Height);
continue;
}
if (xPos + rect.Width + 2 * destMargin > destSheetWidth)
{
i--;
yPos += maxYInRow + 2 * destMargin;
xPos = 0;
if (yPos + maxY + 2 * destMargin > destSheetHeight)
{
yPos = 0;
if (!pre)
{
currentPage = null;
}
else
{
finalPageRequiredWidth = 0;
finalPageRequiredHeight = 0;
finalPageIndex++;
}
}
continue;
}
}
}
return pages;
}
public QFontData BuildFontData()
{
return BuildFontData(null);
}
public QFontData BuildFontData(string saveName)
{
if (config.ForcePowerOfTwo && config.SuperSampleLevels != PowerOfTwo(config.SuperSampleLevels))
{
throw new ArgumentOutOfRangeException("SuperSampleLevels must be a power of two when using ForcePowerOfTwo.");
}
if (config.SuperSampleLevels <= 0 || config.SuperSampleLevels > 8)
{
throw new ArgumentOutOfRangeException("SuperSampleLevels = [" + config.SuperSampleLevels + "] is an unsupported value. Please use values in the range [1,8]");
}
int margin = 2; //margin in initial bitmap (don't bother to make configurable - likely to cause confusion
int pageWidth = config.PageWidth * config.SuperSampleLevels; //texture page width
int pageHeight = config.PageHeight * config.SuperSampleLevels; //texture page height
bool usePowerOfTwo = config.ForcePowerOfTwo;
int glyphMargin = config.GlyphMargin * config.SuperSampleLevels;
QFontGlyph[] initialGlyphs;
var sizes = GetGlyphSizes(font);
var maxSize = GetMaxGlyphSize(sizes);
var initialBmp = CreateInitialBitmap(font, maxSize, margin, out initialGlyphs,config.TextGenerationRenderHint);
var initialBitmapData = initialBmp.LockBits(new Rectangle(0, 0, initialBmp.Width, initialBmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
int minYOffset = int.MaxValue;
foreach (var glyph in initialGlyphs){
RetargetGlyphRectangleInwards(initialBitmapData, glyph, true, config.KerningConfig.alphaEmptyPixelTolerance);
minYOffset = Math.Min(minYOffset,glyph.yOffset);
}
minYOffset--; //give one pixel of breathing room?
foreach (var glyph in initialGlyphs)
glyph.yOffset -= minYOffset;
QFontGlyph[] glyphs;
var bitmapPages = GenerateBitmapSheetsAndRepack( initialGlyphs,new BitmapData[1] { initialBitmapData},pageWidth, pageHeight, out glyphs, glyphMargin, usePowerOfTwo);
initialBmp.UnlockBits(initialBitmapData);
initialBmp.Dispose();
if (config.SuperSampleLevels != 1)
{
ScaleSheetsAndGlyphs(bitmapPages, glyphs, 1.0f / config.SuperSampleLevels);
RetargetAllGlyphs(bitmapPages, glyphs,config.KerningConfig.alphaEmptyPixelTolerance);
}
//create list of texture pages
var pages = new List<TexturePage>();
foreach (var page in bitmapPages)
pages.Add(new TexturePage(page.bitmapData));
var fontData = new QFontData();
fontData.CharSetMapping = CreateCharGlyphMapping(glyphs);
fontData.Pages = pages.ToArray();
fontData.CalculateMeanWidth();
fontData.CalculateMaxHeight();
fontData.KerningPairs = KerningCalculator.CalculateKerning(charSet.ToCharArray(), glyphs, bitmapPages,config.KerningConfig);
fontData.naturallyMonospaced = IsMonospaced(sizes);
if (saveName != null)
{
if (bitmapPages.Count == 1)
bitmapPages[0].bitmap.Save(saveName + ".png", System.Drawing.Imaging.ImageFormat.Png);
else
{
for (int i = 0; i < bitmapPages.Count; i++)
bitmapPages[i].bitmap.Save(saveName + "_sheet_" + i + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
}
if (config.ShadowConfig != null)
fontData.dropShadow = BuildDropShadow(bitmapPages, glyphs, config.ShadowConfig, charSet.ToCharArray(),config.KerningConfig.alphaEmptyPixelTolerance);
foreach (var page in bitmapPages)
page.Free();
//validate glyphs
var intercept = FirstIntercept(fontData.CharSetMapping);
if (intercept != null)
throw new Exception("Failed to create glyph set. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This is could be due to an error in the font, or a bug in Graphics.MeasureString().");
return fontData;
}
private static QFont BuildDropShadow(List<QBitmap> sourceFontSheets, QFontGlyph[] sourceFontGlyphs, QFontShadowConfiguration shadowConfig, char[] charSet, byte alphaTolerance)
{
QFontGlyph[] newGlyphs;
var sourceBitmapData = new List<BitmapData>();
foreach(var sourceSheet in sourceFontSheets)
sourceBitmapData.Add(sourceSheet.bitmapData);
//GenerateBitmapSheetsAndRepack(QFontGlyph[] sourceGlyphs, BitmapData[] sourceBitmaps, int destSheetWidth, int destSheetHeight, out QFontGlyph[] destGlyphs, int destMargin, bool usePowerOfTwo)
var bitmapSheets = GenerateBitmapSheetsAndRepack(sourceFontGlyphs, sourceBitmapData.ToArray(), shadowConfig.PageWidth, shadowConfig.PageHeight, out newGlyphs, shadowConfig.GlyphMargin + shadowConfig.blurRadius*3, shadowConfig.ForcePowerOfTwo);
//scale up in case we wanted bigger/smaller shadows
if (shadowConfig.Scale != 1.0f)
ScaleSheetsAndGlyphs(bitmapSheets, newGlyphs, shadowConfig.Scale); //no point in retargeting yet, since we will do it after blur
//blacken and blur
foreach (var bitmapSheet in bitmapSheets)
{
bitmapSheet.Colour32(0, 0, 0);
bitmapSheet.BlurAlpha(shadowConfig.blurRadius, shadowConfig.blurPasses);
}
//retarget after blur and scale
RetargetAllGlyphs(bitmapSheets, newGlyphs, alphaTolerance);
//create list of texture pages
var newTextureSheets = new List<TexturePage>();
foreach (var page in bitmapSheets)
newTextureSheets.Add(new TexturePage(page.bitmapData));
var fontData = new QFontData();
fontData.CharSetMapping = new Dictionary<char, QFontGlyph>();
for(int i = 0; i < charSet.Length; i++)
fontData.CharSetMapping.Add(charSet[i],newGlyphs[i]);
fontData.Pages = newTextureSheets.ToArray();
fontData.CalculateMeanWidth();
fontData.CalculateMaxHeight();
foreach (var sheet in bitmapSheets)
sheet.Free();
return new QFont(fontData);
}
private static void ScaleSheetsAndGlyphs(List<QBitmap> pages, QFontGlyph[] glyphs, float scale)
{
foreach (var page in pages)
page.DownScale32((int)(page.bitmap.Width * scale), (int)(page.bitmap.Height * scale));
foreach (var glyph in glyphs)
{
glyph.rect = new Rectangle((int)(glyph.rect.X * scale), (int)(glyph.rect.Y * scale), (int)(glyph.rect.Width * scale), (int)(glyph.rect.Height * scale));
glyph.yOffset = (int)(glyph.yOffset * scale);
}
}
private static void RetargetAllGlyphs(List<QBitmap> pages, QFontGlyph[] glyphs, byte alphaTolerance)
{
foreach (var glyph in glyphs)
RetargetGlyphRectangleOutwards(pages[glyph.page].bitmapData, glyph, false, alphaTolerance);
}
public static void SaveQFontDataToFile(QFontData data, string filePath)
{
var lines = data.Serialize();
StreamWriter writer = new StreamWriter(filePath + ".qfont");
foreach (var line in lines)
writer.WriteLine(line);
writer.Close();
}
public static void CreateBitmapPerGlyph(QFontGlyph[] sourceGlyphs, QBitmap[] sourceBitmaps, out QFontGlyph[] destGlyphs, out QBitmap[] destBitmaps){
destBitmaps = new QBitmap[sourceGlyphs.Length];
destGlyphs = new QFontGlyph[sourceGlyphs.Length];
for(int i = 0; i < sourceGlyphs.Length; i++){
var sg = sourceGlyphs[i];
destGlyphs[i] = new QFontGlyph(i,new Rectangle(0,0,sg.rect.Width,sg.rect.Height),sg.yOffset,sg.character);
destBitmaps[i] = new QBitmap(new Bitmap(sg.rect.Width,sg.rect.Height,PixelFormat.Format32bppArgb));
QBitmap.Blit(sourceBitmaps[sg.page].bitmapData,destBitmaps[i].bitmapData,sg.rect,0,0);
}
}
public static QFontData LoadQFontDataFromFile(string filePath, float downSampleFactor, QFontLoaderConfiguration loaderConfig)
{
var lines = new List<String>();
StreamReader reader = new StreamReader(filePath);
string line;
while((line = reader.ReadLine()) != null)
lines.Add(line);
reader.Close();
var data = new QFontData();
int pageCount = 0;
char[] charSet;
data.Deserialize(lines, out pageCount, out charSet);
string namePrefix = filePath.Replace(".qfont","").Replace(" ", "");
var bitmapPages = new List<QBitmap>();
if (pageCount == 1)
{
bitmapPages.Add(new QBitmap(namePrefix + ".png"));
}
else
{
for (int i = 0; i < pageCount; i++)
bitmapPages.Add(new QBitmap(namePrefix + "_sheet_" + i));
}
foreach (var glyph in data.CharSetMapping.Values)
RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, loaderConfig.KerningConfig.alphaEmptyPixelTolerance);
var intercept = FirstIntercept(data.CharSetMapping);
if (intercept != null)
{
throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. If you are texturing your font without locking pixel opacity, then consider using a larger glyph margin. This can be done by setting QFontBuilderConfiguration myQfontBuilderConfig.GlyphMargin, and passing it into CreateTextureFontFiles.");
}
if (downSampleFactor > 1.0f)
{
foreach (var page in bitmapPages)
page.DownScale32((int)(page.bitmap.Width * downSampleFactor), (int)(page.bitmap.Height * downSampleFactor));
foreach (var glyph in data.CharSetMapping.Values)
{
glyph.rect = new Rectangle((int)(glyph.rect.X * downSampleFactor),
(int)(glyph.rect.Y * downSampleFactor),
(int)(glyph.rect.Width * downSampleFactor),
(int)(glyph.rect.Height * downSampleFactor));
glyph.yOffset = (int)(glyph.yOffset * downSampleFactor);
}
}
else if (downSampleFactor < 1.0f )
{
// If we were simply to shrink the entire texture, then at some point we will make glyphs overlap, breaking the font.
// For this reason it is necessary to copy every glyph to a separate bitmap, and then shrink each bitmap individually.
QFontGlyph[] shrunkGlyphs;
QBitmap[] shrunkBitmapsPerGlyph;
CreateBitmapPerGlyph(Helper.ToArray(data.CharSetMapping.Values), bitmapPages.ToArray(), out shrunkGlyphs, out shrunkBitmapsPerGlyph);
//shrink each bitmap
for (int i = 0; i < shrunkGlyphs.Length; i++)
{
var bmp = shrunkBitmapsPerGlyph[i];
bmp.DownScale32(Math.Max((int)(bmp.bitmap.Width * downSampleFactor),1), Math.Max((int)(bmp.bitmap.Height * downSampleFactor),1));
shrunkGlyphs[i].rect = new Rectangle(0, 0, bmp.bitmap.Width, bmp.bitmap.Height);
shrunkGlyphs[i].yOffset = (int)(shrunkGlyphs[i].yOffset * downSampleFactor);
}
var shrunkBitmapData = new BitmapData[shrunkBitmapsPerGlyph.Length];
for(int i = 0; i < shrunkBitmapsPerGlyph.Length; i ++ ){
shrunkBitmapData[i] = shrunkBitmapsPerGlyph[i].bitmapData;
}
//use roughly the same number of pages as before..
int newWidth = (int)(bitmapPages[0].bitmap.Width * (0.1f + downSampleFactor));
int newHeight = (int)(bitmapPages[0].bitmap.Height * (0.1f + downSampleFactor));
//free old bitmap pages since we are about to chuck them away
for (int i = 0; i < pageCount; i++)
bitmapPages[i].Free();
QFontGlyph[] shrunkRepackedGlyphs;
bitmapPages = GenerateBitmapSheetsAndRepack(shrunkGlyphs, shrunkBitmapData, newWidth, newHeight, out shrunkRepackedGlyphs, 4, false);
data.CharSetMapping = CreateCharGlyphMapping(shrunkRepackedGlyphs);
foreach (var bmp in shrunkBitmapsPerGlyph)
bmp.Free();
pageCount = bitmapPages.Count;
}
data.Pages = new TexturePage[pageCount];
for(int i = 0; i < pageCount; i ++ )
data.Pages[i] = new TexturePage(bitmapPages[i].bitmapData);
if (downSampleFactor != 1.0f)
{
foreach (var glyph in data.CharSetMapping.Values)
RetargetGlyphRectangleOutwards(bitmapPages[glyph.page].bitmapData, glyph, false, loaderConfig.KerningConfig.alphaEmptyPixelTolerance);
intercept = FirstIntercept(data.CharSetMapping);
if (intercept != null)
{
throw new Exception("Failed to load font from file. Glyphs '" + intercept[0] + "' and '" + intercept[1] + "' were overlapping. This occurred only after resizing your texture font, implying that there is a bug in QFont. ");
}
}
var glyphList = new List<QFontGlyph>();
foreach (var c in charSet)
glyphList.Add(data.CharSetMapping[c]);
if (loaderConfig.ShadowConfig != null)
data.dropShadow = BuildDropShadow(bitmapPages, glyphList.ToArray(), loaderConfig.ShadowConfig, Helper.ToArray(charSet),loaderConfig.KerningConfig.alphaEmptyPixelTolerance);
data.KerningPairs = KerningCalculator.CalculateKerning(Helper.ToArray(charSet), glyphList.ToArray(), bitmapPages, loaderConfig.KerningConfig);
data.CalculateMeanWidth();
data.CalculateMaxHeight();
for (int i = 0; i < pageCount; i++)
bitmapPages[i].Free();
return data;
}
private static char[] FirstIntercept(Dictionary<char,QFontGlyph> charSet)
{
char[] keys = Helper.ToArray(charSet.Keys);
for (int i = 0; i < keys.Length; i++)
{
for (int j = i + 1; j < keys.Length; j++)
{
if (charSet[keys[i]].page == charSet[keys[j]].page && RectangleIntersect(charSet[keys[i]].rect, charSet[keys[j]].rect))
{
return new char[2] { keys[i], keys[j] };
}
}
}
return null;
}
private static bool RectangleIntersect(Rectangle r1, Rectangle r2)
{
return (r1.X < r2.X + r2.Width && r1.X + r1.Width > r2.X &&
r1.Y < r2.Y + r2.Height && r1.Y + r1.Height > r2.Y);
}
/// <summary>
/// Returns the power of 2 that is closest to x, but not smaller than x.
/// </summary>
private static int PowerOfTwo(int x)
{
int shifts = 0;
uint val = (uint)x;
if (x < 0)
return 0;
while (val > 0)
{
val = val >> 1;
shifts++;
}
val = (uint)1 << (shifts - 1);
if (val < x)
{
val = val << 1;
}
return (int)val;
}
}
}
5203ce801This file is pinned to the selected release. Download the original to open it in your design tool.