1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsFontImporter.h"
4#include "Text/BsFontImportOptions.h"
5#include "Image/BsPixelData.h"
6#include "Image/BsTexture.h"
7#include "Image/BsTextureAtlasLayout.h"
8#include "BsCoreApplication.h"
9#include "CoreThread/BsCoreThread.h"
10
11#include <ft2build.h>
12#include <freetype/freetype.h>
13#include FT_FREETYPE_H
14#include "FileSystem/BsFileSystem.h"
15
16using namespace std::placeholders;
17
18namespace bs
19{
20 FontImporter::FontImporter()
21 :SpecificImporter()
22 {
23 mExtensions.push_back(u8"ttf");
24 mExtensions.push_back(u8"otf");
25 }
26
27 bool FontImporter::isExtensionSupported(const String& ext) const
28 {
29 String lowerCaseExt = ext;
30 StringUtil::toLowerCase(lowerCaseExt);
31
32 return find(mExtensions.begin(), mExtensions.end(), lowerCaseExt) != mExtensions.end();
33 }
34
35 bool FontImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const
36 {
37 // Not used
38 return false;
39 }
40
41 SPtr<ImportOptions> FontImporter::createImportOptions() const
42 {
43 return bs_shared_ptr_new<FontImportOptions>();
44 }
45
46 SPtr<Resource> FontImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions)
47 {
48 const FontImportOptions* fontImportOptions = static_cast<const FontImportOptions*>(importOptions.get());
49
50 FT_Library library;
51
52 FT_Error error = FT_Init_FreeType(&library);
53 if (error)
54 BS_EXCEPT(InternalErrorException, "Error occurred during FreeType library initialization.");
55
56 FT_Face face;
57
58 {
59 Lock fileLock = FileScheduler::getLock(filePath);
60 error = FT_New_Face(library, filePath.toString().c_str(), 0, &face);
61 }
62
63 if (error == FT_Err_Unknown_File_Format)
64 {
65 BS_EXCEPT(InternalErrorException, "Failed to load font file: " + filePath.toString() + ". Unsupported file format.");
66 }
67 else if (error)
68 {
69 BS_EXCEPT(InternalErrorException, "Failed to load font file: " + filePath.toString() + ". Unknown error.");
70 }
71
72 Vector<CharRange> charIndexRanges = fontImportOptions->charIndexRanges;
73 Vector<UINT32> fontSizes = fontImportOptions->fontSizes;
74 UINT32 dpi = fontImportOptions->dpi;
75
76 FT_Int32 loadFlags;
77 switch (fontImportOptions->renderMode)
78 {
79 case FontRenderMode::Smooth:
80 loadFlags = FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_HINTING;
81 break;
82 case FontRenderMode::Raster:
83 loadFlags = FT_LOAD_TARGET_MONO | FT_LOAD_NO_HINTING;
84 break;
85 case FontRenderMode::HintedSmooth:
86 loadFlags = FT_LOAD_TARGET_NORMAL | FT_LOAD_NO_AUTOHINT;
87 break;
88 case FontRenderMode::HintedRaster:
89 loadFlags = FT_LOAD_TARGET_MONO | FT_LOAD_NO_AUTOHINT;
90 break;
91 default:
92 loadFlags = FT_LOAD_TARGET_NORMAL;
93 break;
94 }
95
96 FT_Render_Mode renderMode = FT_LOAD_TARGET_MODE(loadFlags);
97
98 Vector<SPtr<FontBitmap>> dataPerSize;
99 for(size_t i = 0; i < fontSizes.size(); i++)
100 {
101 // Note: Disabled as its not working and I have bigger issues to handle than to figure this out atm
102 //FT_Matrix m;
103 //if (fontImportOptions->getBold())
104 // m.xx = (long)(1.25f * (1 << 16));
105 //else
106 // m.xx = (long)(1 * (1 << 16));
107
108 //if (fontImportOptions->getItalic())
109 // m.xy = (long)(0.25f * (1 << 16));
110 //else
111 // m.xy = (long)(0 * (1 << 16));
112
113 //m.yx = (long)(0 * (1 << 16));
114 //m.yy = (long)(1 * (1 << 16));
115
116 //FT_Set_Transform(face, &m, nullptr);
117
118 FT_F26Dot6 ftSize = (FT_F26Dot6)(fontSizes[i] * (1 << 6));
119 if (FT_Set_Char_Size(face, ftSize, 0, dpi, dpi))
120 BS_EXCEPT(InternalErrorException, "Could not set character size.");
121
122 SPtr<FontBitmap> fontData = bs_shared_ptr_new<FontBitmap>();
123
124 // Get all char sizes so we can generate texture layout
125 Vector<TextureAtlasUtility::Element> atlasElements;
126 Map<UINT32, UINT32> seqIdxToCharIdx;
127 for(auto iter = charIndexRanges.begin(); iter != charIndexRanges.end(); ++iter)
128 {
129 for(UINT32 charIdx = iter->start; charIdx <= iter->end; charIdx++)
130 {
131 error = FT_Load_Char(face, (FT_ULong)charIdx, loadFlags);
132
133 if(error)
134 BS_EXCEPT(InternalErrorException, "Failed to load a character");
135
136 FT_Render_Glyph(face->glyph, renderMode);
137
138 if (error)
139 BS_EXCEPT(InternalErrorException, "Failed to render a character");
140
141 FT_GlyphSlot slot = face->glyph;
142
143 TextureAtlasUtility::Element atlasElement;
144 atlasElement.input.width = slot->bitmap.width;
145 atlasElement.input.height = slot->bitmap.rows;
146
147 atlasElements.push_back(atlasElement);
148 seqIdxToCharIdx[(UINT32)atlasElements.size() - 1] = charIdx;
149 }
150 }
151
152 // Add missing glyph
153 {
154 error = FT_Load_Glyph(face, (FT_ULong)0, loadFlags);
155
156 if(error)
157 BS_EXCEPT(InternalErrorException, "Failed to load a character");
158
159 FT_Render_Glyph(face->glyph, renderMode);
160
161 if (error)
162 BS_EXCEPT(InternalErrorException, "Failed to render a character");
163
164 FT_GlyphSlot slot = face->glyph;
165
166 TextureAtlasUtility::Element atlasElement;
167 atlasElement.input.width = slot->bitmap.width;
168 atlasElement.input.height = slot->bitmap.rows;
169
170 atlasElements.push_back(atlasElement);
171 }
172
173 // Create an optimal layout for character bitmaps
174 Vector<TextureAtlasUtility::Page> pages = TextureAtlasUtility::createAtlasLayout(atlasElements, 64, 64,
175 MAXIMUM_TEXTURE_SIZE, MAXIMUM_TEXTURE_SIZE, true);
176
177 INT32 baselineOffset = 0;
178 UINT32 lineHeight = 0;
179
180 // Create char bitmap atlas textures and load character information
181 UINT32 pageIdx = 0;
182 for(auto pageIter = pages.begin(); pageIter != pages.end(); ++pageIter)
183 {
184 UINT32 bufferSize = pageIter->width * pageIter->height * 2;
185
186 // TODO - I don't actually need a 2 channel texture
187 SPtr<PixelData> pixelData = bs_shared_ptr_new<PixelData>(pageIter->width, pageIter->height, 1, PF_RG8);
188
189 pixelData->allocateInternalBuffer();
190 UINT8* pixelBuffer = pixelData->getData();
191 memset(pixelBuffer, 0, bufferSize);
192
193 for(size_t i = 0; i < atlasElements.size(); i++)
194 {
195 // Copy character bitmap
196 if(atlasElements[i].output.page != (INT32)pageIdx)
197 continue;
198
199 TextureAtlasUtility::Element curElement = atlasElements[i];
200 UINT32 elementIdx = curElement.output.idx;
201
202 bool isMissingGlypth = elementIdx == (atlasElements.size() - 1); // It's always the last element
203
204 UINT32 charIdx = 0;
205 if(!isMissingGlypth)
206 {
207 charIdx = seqIdxToCharIdx[(UINT32)elementIdx];
208
209 error = FT_Load_Char(face, charIdx, loadFlags);
210 }
211 else
212 {
213 error = FT_Load_Glyph(face, 0, loadFlags);
214 }
215
216 if(error)
217 BS_EXCEPT(InternalErrorException, "Failed to load a character");
218
219 FT_Render_Glyph(face->glyph, renderMode);
220
221 if (error)
222 BS_EXCEPT(InternalErrorException, "Failed to render a character");
223
224 FT_GlyphSlot slot = face->glyph;
225
226 if(slot->bitmap.buffer == nullptr && slot->bitmap.rows > 0 && slot->bitmap.width > 0)
227 BS_EXCEPT(InternalErrorException, "Failed to render glyph bitmap");
228
229 UINT8* sourceBuffer = slot->bitmap.buffer;
230 UINT8* dstBuffer = pixelBuffer + (curElement.output.y * pageIter->width * 2) + curElement.output.x * 2;
231
232 if(slot->bitmap.pixel_mode == ft_pixel_mode_grays)
233 {
234 for(INT32 bitmapRow = 0; bitmapRow < slot->bitmap.rows; bitmapRow++)
235 {
236 for(INT32 bitmapColumn = 0; bitmapColumn < slot->bitmap.width; bitmapColumn++)
237 {
238 dstBuffer[bitmapColumn * 2 + 0] = sourceBuffer[bitmapColumn];
239 dstBuffer[bitmapColumn * 2 + 1] = sourceBuffer[bitmapColumn];
240 }
241
242 dstBuffer += pageIter->width * 2;
243 sourceBuffer += slot->bitmap.pitch;
244 }
245 }
246 else if(slot->bitmap.pixel_mode == ft_pixel_mode_mono)
247 {
248 // 8 pixels are packed into a byte, so do some unpacking
249 for(INT32 bitmapRow = 0; bitmapRow < slot->bitmap.rows; bitmapRow++)
250 {
251 for(INT32 bitmapColumn = 0; bitmapColumn < slot->bitmap.width; bitmapColumn++)
252 {
253 UINT8 srcValue = sourceBuffer[bitmapColumn >> 3];
254 UINT8 dstValue = (srcValue & (128 >> (bitmapColumn & 7))) != 0 ? 255 : 0;
255
256 dstBuffer[bitmapColumn * 2 + 0] = dstValue;
257 dstBuffer[bitmapColumn * 2 + 1] = dstValue;
258 }
259
260 dstBuffer += pageIter->width * 2;
261 sourceBuffer += slot->bitmap.pitch;
262 }
263 }
264 else
265 BS_EXCEPT(InternalErrorException, "Unsupported pixel mode for a FreeType bitmap.");
266
267 // Store character information
268 CharDesc charDesc;
269
270 float invTexWidth = 1.0f / pageIter->width;
271 float invTexHeight = 1.0f / pageIter->height;
272
273 charDesc.charId = charIdx;
274 charDesc.width = curElement.input.width;
275 charDesc.height = curElement.input.height;
276 charDesc.page = curElement.output.page;
277 charDesc.uvWidth = invTexWidth * curElement.input.width;
278 charDesc.uvHeight = invTexHeight * curElement.input.height;
279 charDesc.uvX = invTexWidth * curElement.output.x;
280 charDesc.uvY = invTexHeight * curElement.output.y;
281 charDesc.xOffset = slot->bitmap_left;
282 charDesc.yOffset = slot->bitmap_top;
283 charDesc.xAdvance = slot->advance.x >> 6;
284 charDesc.yAdvance = slot->advance.y >> 6;
285
286 baselineOffset = std::max(baselineOffset, (INT32)(slot->metrics.horiBearingY >> 6));
287 lineHeight = std::max(lineHeight, charDesc.height);
288
289 // Load kerning and store char
290 if(!isMissingGlypth)
291 {
292 FT_Vector resultKerning;
293 for(auto kerningIter = charIndexRanges.begin(); kerningIter != charIndexRanges.end(); ++kerningIter)
294 {
295 for(UINT32 kerningCharIdx = kerningIter->start; kerningCharIdx <= kerningIter->end; kerningCharIdx++)
296 {
297 if(kerningCharIdx == charIdx)
298 continue;
299
300 error = FT_Get_Kerning(face, charIdx, kerningCharIdx, FT_KERNING_DEFAULT, &resultKerning);
301
302 if(error)
303 BS_EXCEPT(InternalErrorException, "Failed to get kerning information for character: " + toString(charIdx));
304
305 INT32 kerningX = (INT32)(resultKerning.x >> 6); // Y kerning is ignored because it is so rare
306 if(kerningX == 0) // We don't store 0 kerning, this is assumed default
307 continue;
308
309 KerningPair pair;
310 pair.amount = kerningX;
311 pair.otherCharId = kerningCharIdx;
312
313 charDesc.kerningPairs.push_back(pair);
314 }
315 }
316
317 fontData->characters[charIdx] = charDesc;
318 }
319 else
320 {
321 fontData->missingGlyph = charDesc;
322 }
323 }
324
325 TEXTURE_DESC texDesc;
326 texDesc.width = pageIter->width;
327 texDesc.height = pageIter->height;
328 texDesc.format = PF_RG8;
329
330 HTexture newTex = Texture::create(texDesc);
331
332 // It's possible the formats no longer match
333 if (newTex->getProperties().getFormat() != pixelData->getFormat())
334 {
335 SPtr<PixelData> temp = newTex->getProperties().allocBuffer(0, 0);
336 PixelUtil::bulkPixelConversion(*pixelData, *temp);
337
338 newTex->writeData(temp);
339 }
340 else
341 {
342 newTex->writeData(pixelData);
343 }
344
345 newTex->setName(u8"FontPage" + toString((UINT32)fontData->texturePages.size()));
346
347 fontData->texturePages.push_back(newTex);
348 pageIdx++;
349 }
350
351 fontData->size = fontSizes[i];
352 fontData->baselineOffset = baselineOffset;
353 fontData->lineHeight = lineHeight;
354
355 // Get space size
356 error = FT_Load_Char(face, 32, loadFlags);
357
358 if(error)
359 BS_EXCEPT(InternalErrorException, "Failed to load a character");
360
361 fontData->spaceWidth = face->glyph->advance.x >> 6;
362
363 dataPerSize.push_back(fontData);
364 }
365
366 SPtr<Font> newFont = Font::_createPtr(dataPerSize);
367
368 FT_Done_FreeType(library);
369
370 const String fileName = filePath.getFilename(false);
371 newFont->setName(fileName);
372
373 return newFont;
374 }
375}