#include "BsFontImporter.h" #include "BsFontImportOptions.h" #include "BsPixelData.h" #include "BsTexture.h" #include "BsResources.h" #include "BsDebug.h" #include "BsTexAtlasGenerator.h" #include "BsCoreApplication.h" #include "BsCoreThread.h" #include "BsCoreThreadAccessor.h" #include #include #include FT_FREETYPE_H using namespace std::placeholders; namespace BansheeEngine { FontImporter::FontImporter() :SpecificImporter() { mExtensions.push_back(L"ttf"); mExtensions.push_back(L"otf"); } FontImporter::~FontImporter() { } bool FontImporter::isExtensionSupported(const WString& ext) const { WString lowerCaseExt = ext; StringUtil::toLowerCase(lowerCaseExt); return find(mExtensions.begin(), mExtensions.end(), lowerCaseExt) != mExtensions.end(); } bool FontImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const { // TODO return false; } ImportOptionsPtr FontImporter::createImportOptions() const { return bs_shared_ptr_new(); } ResourcePtr FontImporter::import(const Path& filePath, ConstImportOptionsPtr importOptions) { const FontImportOptions* fontImportOptions = static_cast(importOptions.get()); FT_Library library; FT_Error error = FT_Init_FreeType(&library); if (error) BS_EXCEPT(InternalErrorException, "Error occurred during FreeType library initialization."); FT_Face face; error = FT_New_Face(library, filePath.toString().c_str(), 0, &face); if (error == FT_Err_Unknown_File_Format) { BS_EXCEPT(InternalErrorException, "Failed to load font file: " + filePath.toString() + ". Unsupported file format."); } else if (error) { BS_EXCEPT(InternalErrorException, "Failed to load font file: " + filePath.toString() + ". Unknown error."); } Vector> charIndexRanges = fontImportOptions->getCharIndexRanges(); Vector fontSizes = fontImportOptions->getFontSizes(); UINT32 dpi = fontImportOptions->getDPI(); FT_Int32 loadFlags; switch (fontImportOptions->getRenderMode()) { case FontRenderMode::Smooth: loadFlags = FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING; break; case FontRenderMode::Raster: loadFlags = FT_LOAD_TARGET_MONO | FT_LOAD_NO_HINTING; break; case FontRenderMode::HintedSmooth: loadFlags = FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_AUTOHINT; break; case FontRenderMode::HintedRaster: loadFlags = FT_LOAD_TARGET_MONO | FT_LOAD_NO_AUTOHINT; break; default: loadFlags = FT_LOAD_TARGET_NORMAL; break; } FT_Render_Mode renderMode = FT_LOAD_TARGET_MODE(loadFlags); Vector> dataPerSize; for(size_t i = 0; i < fontSizes.size(); i++) { // Note: Disabled as its not working and I have bigger issues to handle than to figure this out atm //FT_Matrix m; //if (fontImportOptions->getBold()) // m.xx = (long)(1.25f * (1 << 16)); //else // m.xx = (long)(1 * (1 << 16)); //if (fontImportOptions->getItalic()) // m.xy = (long)(0.25f * (1 << 16)); //else // m.xy = (long)(0 * (1 << 16)); //m.yx = (long)(0 * (1 << 16)); //m.yy = (long)(1 * (1 << 16)); //FT_Set_Transform(face, &m, nullptr); FT_F26Dot6 ftSize = (FT_F26Dot6)(fontSizes[i] * (1 << 6)); if (FT_Set_Char_Size(face, ftSize, 0, dpi, dpi)) BS_EXCEPT(InternalErrorException, "Could not set character size."); SPtr fontData = bs_shared_ptr_new(); // Get all char sizes so we can generate texture layout Vector atlasElements; Map seqIdxToCharIdx; for(auto iter = charIndexRanges.begin(); iter != charIndexRanges.end(); ++iter) { for(UINT32 charIdx = iter->first; charIdx <= iter->second; charIdx++) { error = FT_Load_Char(face, (FT_ULong)charIdx, loadFlags); if(error) BS_EXCEPT(InternalErrorException, "Failed to load a character"); FT_Render_Glyph(face->glyph, renderMode); if (error) BS_EXCEPT(InternalErrorException, "Failed to render a character"); FT_GlyphSlot slot = face->glyph; TexAtlasElementDesc atlasElement; atlasElement.input.width = slot->bitmap.width; atlasElement.input.height = slot->bitmap.rows; atlasElements.push_back(atlasElement); seqIdxToCharIdx[(UINT32)atlasElements.size() - 1] = charIdx; } } // Add missing glyph { error = FT_Load_Glyph(face, (FT_ULong)0, loadFlags); if(error) BS_EXCEPT(InternalErrorException, "Failed to load a character"); FT_Render_Glyph(face->glyph, renderMode); if (error) BS_EXCEPT(InternalErrorException, "Failed to render a character"); FT_GlyphSlot slot = face->glyph; TexAtlasElementDesc atlasElement; atlasElement.input.width = slot->bitmap.width; atlasElement.input.height = slot->bitmap.rows; atlasElements.push_back(atlasElement); } // Create an optimal layout for character bitmaps TexAtlasGenerator texAtlasGen(false, MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE); Vector pages = texAtlasGen.createAtlasLayout(atlasElements); INT32 baselineOffset = 0; UINT32 lineHeight = 0; // Create char bitmap atlas textures and load character information UINT32 pageIdx = 0; for(auto pageIter = pages.begin(); pageIter != pages.end(); ++pageIter) { UINT32 bufferSize = pageIter->width * pageIter->height * 2; // TODO - I don't actually need a 2 channel texture PixelDataPtr pixelData = bs_shared_ptr_new(pageIter->width, pageIter->height, 1, PF_R8G8); pixelData->allocateInternalBuffer(); UINT8* pixelBuffer = pixelData->getData(); memset(pixelBuffer, 0, bufferSize); for(size_t elementIdx = 0; elementIdx < atlasElements.size(); elementIdx++) { // Copy character bitmap if(atlasElements[elementIdx].output.page != pageIdx) continue; TexAtlasElementDesc curElement = atlasElements[elementIdx]; bool isMissingGlypth = elementIdx == (atlasElements.size() - 1); // It's always the last element UINT32 charIdx = 0; if(!isMissingGlypth) { charIdx = seqIdxToCharIdx[(UINT32)elementIdx]; error = FT_Load_Char(face, charIdx, loadFlags); } else { error = FT_Load_Glyph(face, 0, loadFlags); } if(error) BS_EXCEPT(InternalErrorException, "Failed to load a character"); FT_Render_Glyph(face->glyph, renderMode); if (error) BS_EXCEPT(InternalErrorException, "Failed to render a character"); FT_GlyphSlot slot = face->glyph; if(slot->bitmap.buffer == nullptr && slot->bitmap.rows > 0 && slot->bitmap.width > 0) BS_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; if(slot->bitmap.pixel_mode == ft_pixel_mode_grays) { 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; } } else if(slot->bitmap.pixel_mode == ft_pixel_mode_mono) { // 8 pixels are packed into a byte, so do some unpacking for(INT32 bitmapRow = 0; bitmapRow < slot->bitmap.rows; bitmapRow++) { for(INT32 bitmapColumn = 0; bitmapColumn < slot->bitmap.width; bitmapColumn++) { UINT8 srcValue = sourceBuffer[bitmapColumn >> 3]; UINT8 dstValue = (srcValue & (128 >> (bitmapColumn & 7))) != 0 ? 255 : 0; dstBuffer[bitmapColumn * 2 + 0] = dstValue; dstBuffer[bitmapColumn * 2 + 1] = dstValue; } dstBuffer += pageIter->width * 2; sourceBuffer += slot->bitmap.pitch; } } else BS_EXCEPT(InternalErrorException, "Unsupported pixel mode for a FreeType bitmap."); // 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; baselineOffset = std::max(baselineOffset, (INT32)(slot->metrics.horiBearingY >> 6)); lineHeight = std::max(lineHeight, charDesc.height); // Load kerning and store char if(!isMissingGlypth) { 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) BS_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; } else { fontData->fontDesc.missingGlyph = charDesc; } } HTexture newTex = Texture::create(TEX_TYPE_2D, pageIter->width, pageIter->height, 0, PF_R8G8); UINT32 subresourceIdx = newTex->getProperties().mapToSubresourceIdx(0, 0); // It's possible the formats no longer match if (newTex->getProperties().getFormat() != pixelData->getFormat()) { PixelDataPtr temp = newTex->getProperties().allocateSubresourceBuffer(subresourceIdx); PixelUtil::bulkPixelConversion(*pixelData, *temp); newTex->writeSubresource(gCoreAccessor(), subresourceIdx, temp, false); } else { newTex->writeSubresource(gCoreAccessor(), subresourceIdx, pixelData, false); } newTex->setName(L"FontPage" + toWString((UINT32)fontData->texturePages.size())); fontData->texturePages.push_back(newTex); pageIdx++; } fontData->size = fontSizes[i]; fontData->fontDesc.baselineOffset = baselineOffset; fontData->fontDesc.lineHeight = lineHeight; // Get space size error = FT_Load_Char(face, 32, loadFlags); if(error) BS_EXCEPT(InternalErrorException, "Failed to load a character"); fontData->fontDesc.spaceWidth = face->glyph->advance.x >> 6; dataPerSize.push_back(fontData); } FontPtr newFont = Font::_createPtr(dataPerSize); FT_Done_FreeType(library); WString fileName = filePath.getWFilename(false); newFont->setName(fileName); return newFont; } }