|
|
@@ -4,6 +4,7 @@
|
|
|
#include "CmTexture.h"
|
|
|
#include "CmResources.h"
|
|
|
#include "CmDebug.h"
|
|
|
+#include "CmTexAtlasGenerator.h"
|
|
|
|
|
|
#include <ft2build.h>
|
|
|
#include <freetype/freetype.h>
|
|
|
@@ -67,107 +68,154 @@ namespace CamelotEngine
|
|
|
vector<std::pair<UINT32, UINT32>>::type charIndexRanges = gpuProgImportOptions->getCharIndexRanges();
|
|
|
vector<UINT32>::type fontSizes = gpuProgImportOptions->getFontSizes();
|
|
|
UINT32 dpi = gpuProgImportOptions->getDPI();
|
|
|
+
|
|
|
+ vector<FontData>::type dataPerSize;
|
|
|
for(size_t i = 0; i < fontSizes.size(); i++)
|
|
|
{
|
|
|
FT_F26Dot6 ftSize = (FT_F26Dot6)(fontSizes[i] * (1 << 6));
|
|
|
if(FT_Set_Char_Size( face, ftSize, 0, dpi, dpi))
|
|
|
CM_EXCEPT(InternalErrorException, "Could not set character size." );
|
|
|
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- /************************************************************************/
|
|
|
- /* DEBUG */
|
|
|
- /************************************************************************/
|
|
|
-
|
|
|
- if(FT_Set_Char_Size(face, 12 * (1 << 6), 0, 96, 96))
|
|
|
- CM_EXCEPT(InternalErrorException, "Could not set character size." );
|
|
|
+ FontData fontData;
|
|
|
|
|
|
- INT32 maxWidth = 0;
|
|
|
- INT32 maxHeight = 0;
|
|
|
- for(UINT32 i = 33; i < 166; i++)
|
|
|
- {
|
|
|
- error = FT_Load_Char(face, i, FT_LOAD_RENDER);
|
|
|
-
|
|
|
- if(error)
|
|
|
+ // Get all char sizes so we can generate texture layout
|
|
|
+ vector<TexAtlasElementDesc>::type atlasElements;
|
|
|
+ map<UINT32, UINT32>::type seqIdxToCharIdx;
|
|
|
+ for(auto iter = charIndexRanges.begin(); iter != charIndexRanges.end(); ++iter)
|
|
|
{
|
|
|
- CM_EXCEPT(InternalErrorException, "Failed to load a character");
|
|
|
- }
|
|
|
-
|
|
|
- FT_GlyphSlot slot = face->glyph;
|
|
|
-
|
|
|
- if(slot->bitmap.width > maxWidth)
|
|
|
- maxWidth = slot->bitmap.width;
|
|
|
+ for(UINT32 charIdx = iter->first; charIdx <= iter->second; charIdx++)
|
|
|
+ {
|
|
|
+ error = FT_Load_Char(face, (FT_ULong)charIdx, FT_LOAD_RENDER);
|
|
|
|
|
|
- if(slot->bitmap.rows > maxHeight)
|
|
|
- maxHeight = slot->bitmap.rows;
|
|
|
- }
|
|
|
+ if(error)
|
|
|
+ CM_EXCEPT(InternalErrorException, "Failed to load a character");
|
|
|
|
|
|
- UINT32 texWidth = 1024;
|
|
|
- UINT32 texHeight = 1024;
|
|
|
+ FT_GlyphSlot slot = face->glyph;
|
|
|
|
|
|
- UINT8* pixelBuffer = new UINT8[texWidth * texHeight * 2];
|
|
|
- memset(pixelBuffer, 0, texWidth * texHeight * 2);
|
|
|
- PixelData pixelData(texWidth, texHeight, 1, PF_R8G8, pixelBuffer, true);
|
|
|
+ TexAtlasElementDesc atlasElement;
|
|
|
+ atlasElement.input.width = slot->bitmap.width;
|
|
|
+ atlasElement.input.height = slot->bitmap.rows;
|
|
|
|
|
|
- UINT32 curX = 0;
|
|
|
- UINT32 curY = 0;
|
|
|
- for(UINT32 i = 33; i < 166; i++)
|
|
|
- {
|
|
|
- if((curX + maxWidth) >= texWidth)
|
|
|
- {
|
|
|
- curX = 0;
|
|
|
- curY += maxHeight;
|
|
|
-
|
|
|
- if((curY + maxHeight) >= texHeight)
|
|
|
- CM_EXCEPT(InternalErrorException, "Texture to small to fit all glyphs.");
|
|
|
+ atlasElements.push_back(atlasElement);
|
|
|
+ seqIdxToCharIdx[(UINT32)atlasElements.size() - 1] = charIdx;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- error = FT_Load_Char(face, i, FT_LOAD_RENDER);
|
|
|
- if(error)
|
|
|
+ // Create an optimal layout for character bitmaps
|
|
|
+ TexAtlasGenerator texAtlasGen(false, MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE);
|
|
|
+ vector<TexAtlasPageDesc>::type pages = texAtlasGen.createAtlasLayout(atlasElements);
|
|
|
+
|
|
|
+ // Create char bitmap atlas textures and load character information
|
|
|
+ UINT32 pageIdx = 0;
|
|
|
+ for(auto pageIter = pages.begin(); pageIter != pages.end(); ++pageIter)
|
|
|
{
|
|
|
- CM_EXCEPT(InternalErrorException, "Failed to load a character");
|
|
|
- }
|
|
|
+ UINT32 bufferSize = pageIter->width * pageIter->height * 2;
|
|
|
|
|
|
- FT_GlyphSlot slot = face->glyph;
|
|
|
+ UINT8* pixelBuffer = new UINT8[bufferSize];
|
|
|
+ memset(pixelBuffer, 0, bufferSize);
|
|
|
|
|
|
- if(slot->bitmap.buffer == nullptr && slot->bitmap.rows > 0 && slot->bitmap.width > 0)
|
|
|
- {
|
|
|
- CM_EXCEPT(InternalErrorException, "Failed to render glyph bitmap");
|
|
|
- }
|
|
|
+ PixelData pixelData(pageIter->width, pageIter->height, 1, PF_R8G8, pixelBuffer, true);
|
|
|
|
|
|
- UINT8* sourceBuffer = slot->bitmap.buffer;
|
|
|
- UINT8* dstBuffer = pixelBuffer + (curY * texWidth * 2) + curX * 2;
|
|
|
- for(INT32 bitmapRow = 0; bitmapRow < slot->bitmap.rows; bitmapRow++)
|
|
|
- {
|
|
|
- for(INT32 bitmapColumn = 0; bitmapColumn < slot->bitmap.width; bitmapColumn++)
|
|
|
+ for(size_t elementIdx = 0; elementIdx < atlasElements.size(); elementIdx++)
|
|
|
{
|
|
|
- dstBuffer[bitmapColumn * 2 + 0] = sourceBuffer[bitmapColumn];
|
|
|
- dstBuffer[bitmapColumn * 2 + 1] = sourceBuffer[bitmapColumn];
|
|
|
+ // Copy character bitmap
|
|
|
+ if(atlasElements[elementIdx].output.page != pageIdx)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ TexAtlasElementDesc curElement = atlasElements[elementIdx];
|
|
|
+
|
|
|
+ UINT32 charIdx = seqIdxToCharIdx[(UINT32)elementIdx];
|
|
|
+
|
|
|
+ error = FT_Load_Char(face, charIdx, FT_LOAD_RENDER);
|
|
|
+ if(error)
|
|
|
+ CM_EXCEPT(InternalErrorException, "Failed to load a character");
|
|
|
+
|
|
|
+ FT_GlyphSlot slot = face->glyph;
|
|
|
+
|
|
|
+ if(slot->bitmap.buffer == nullptr && slot->bitmap.rows > 0 && slot->bitmap.width > 0)
|
|
|
+ CM_EXCEPT(InternalErrorException, "Failed to render glyph bitmap");
|
|
|
+
|
|
|
+ UINT8* sourceBuffer = slot->bitmap.buffer;
|
|
|
+ UINT8* dstBuffer = pixelBuffer + (curElement.output.y * pageIter->width * 2) + curElement.output.x * 2;
|
|
|
+ for(INT32 bitmapRow = 0; bitmapRow < slot->bitmap.rows; bitmapRow++)
|
|
|
+ {
|
|
|
+ for(INT32 bitmapColumn = 0; bitmapColumn < slot->bitmap.width; bitmapColumn++)
|
|
|
+ {
|
|
|
+ dstBuffer[bitmapColumn * 2 + 0] = sourceBuffer[bitmapColumn];
|
|
|
+ dstBuffer[bitmapColumn * 2 + 1] = sourceBuffer[bitmapColumn];
|
|
|
+ }
|
|
|
+
|
|
|
+ dstBuffer += pageIter->width * 2;
|
|
|
+ sourceBuffer += slot->bitmap.pitch;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Store character information
|
|
|
+ CHAR_DESC charDesc;
|
|
|
+
|
|
|
+ float invTexWidth = 1.0f / pageIter->width;
|
|
|
+ float invTexHeight = 1.0f / pageIter->height;
|
|
|
+
|
|
|
+ charDesc.charId = charIdx;
|
|
|
+ charDesc.width = curElement.input.width;
|
|
|
+ charDesc.height = curElement.input.height;
|
|
|
+ charDesc.page = curElement.output.page;
|
|
|
+ charDesc.uvWidth = invTexWidth * curElement.input.width;
|
|
|
+ charDesc.uvHeight = invTexHeight * curElement.input.height;
|
|
|
+ charDesc.uvX = invTexWidth * curElement.output.x;
|
|
|
+ charDesc.uvY = invTexHeight * curElement.output.y;
|
|
|
+ charDesc.xOffset = slot->bitmap_left;
|
|
|
+ charDesc.yOffset = slot->bitmap_top;
|
|
|
+ charDesc.xAdvance = slot->advance.x >> 6;
|
|
|
+ charDesc.yAdvance = slot->advance.y >> 6;
|
|
|
+
|
|
|
+ // Load kerning
|
|
|
+ FT_Vector resultKerning;
|
|
|
+ for(auto kerningIter = charIndexRanges.begin(); kerningIter != charIndexRanges.end(); ++kerningIter)
|
|
|
+ {
|
|
|
+ for(UINT32 kerningCharIdx = kerningIter->first; kerningCharIdx <= kerningIter->second; kerningCharIdx++)
|
|
|
+ {
|
|
|
+ if(kerningCharIdx == charIdx)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ error = FT_Get_Kerning(face, charIdx, kerningCharIdx, FT_KERNING_DEFAULT, &resultKerning);
|
|
|
+
|
|
|
+ if(error)
|
|
|
+ CM_EXCEPT(InternalErrorException, "Failed to get kerning information for character: " + toString(charIdx));
|
|
|
+
|
|
|
+ INT32 kerningX = (INT32)(resultKerning.x >> 6); // Y kerning is ignored because it is so rare
|
|
|
+ if(kerningX == 0) // We don't store 0 kerning, this is assumed default
|
|
|
+ continue;
|
|
|
+
|
|
|
+ KerningPair pair;
|
|
|
+ pair.amount = kerningX;
|
|
|
+ pair.otherCharId = kerningCharIdx;
|
|
|
+
|
|
|
+ charDesc.kerningPairs.push_back(pair);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fontData.fontDesc.characters[charIdx] = charDesc;
|
|
|
}
|
|
|
|
|
|
- dstBuffer += texWidth * 2;
|
|
|
- sourceBuffer += slot->bitmap.pitch;
|
|
|
- }
|
|
|
+ TextureHandle newTex = Texture::create(TEX_TYPE_2D, pageIter->width, pageIter->height, 0, PF_R8G8);
|
|
|
+ newTex->waitUntilInitialized();
|
|
|
+ newTex->setRawPixels(pixelData);
|
|
|
|
|
|
- curX += maxWidth;
|
|
|
- }
|
|
|
+ gDebug().writeAsBMP(pixelData, "C:\\FontTex.bmp");
|
|
|
|
|
|
- gDebug().writeAsBMP(pixelData, "C:\\FontTex.bmp");
|
|
|
+ fontData.texturePages.push_back(newTex.getInternalPtr());
|
|
|
|
|
|
- //TextureHandle newTex = Texture::create(TEX_TYPE_2D, texWidth, texHeight, 0, PF_R8G8);
|
|
|
- //newTex->waitUntilInitialized();
|
|
|
- //newTex->setRawPixels(pixelData);
|
|
|
+ pageIdx++;
|
|
|
+ }
|
|
|
|
|
|
- //Resources::instance().create(newTex, "C:\\FontTex.tex", true);
|
|
|
+ fontData.size = fontSizes[i];
|
|
|
+ dataPerSize.push_back(fontData);
|
|
|
+ }
|
|
|
|
|
|
- /************************************************************************/
|
|
|
- /* END DEBUG */
|
|
|
- /************************************************************************/
|
|
|
+ FontHandle newFont = Font::create(dataPerSize);
|
|
|
|
|
|
-
|
|
|
FT_Done_FreeType(library);
|
|
|
|
|
|
- return BaseResourceHandle();
|
|
|
+ return newFont;
|
|
|
}
|
|
|
}
|