OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4FontLoader.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2003-2009, RedWolf Design GmbH, http://www.clonk.de/
5  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
6  *
7  * Distributed under the terms of the ISC license; see accompanying file
8  * "COPYING" for details.
9  *
10  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
11  * See accompanying file "TRADEMARK" for details.
12  *
13  * To redistribute this file separately, substitute the full license texts
14  * for the above references.
15  */
16 
17 // text drawing facility for C4Draw
18 
19 #include "C4Include.h"
21 #include "graphics/C4FontLoader.h"
22 
23 #ifndef USE_CONSOLE
24 #include "c4group/C4Components.h"
25 #include "lib/C4Markup.h"
26 #include "lib/StdColors.h"
27 #include "graphics/C4Draw.h"
28 #include "graphics/C4Surface.h"
29 
30 #ifdef _WIN32
32 #endif
33 
34 #include <ft2build.h>
35 #include FT_FREETYPE_H
36 #endif
37 
38 /* Initialization */
39 
40 bool C4FontLoader::InitFont(CStdFont * rFont, const char *szFontName, FontType eType, int32_t iSize, C4GroupSet *pGfxGroups, bool fDoShadow)
41 {
42 #ifdef USE_CONSOLE
43  return true;
44 #else
45  // safety
46  assert(szFontName);
47  if (!szFontName || !*szFontName)
48  {
49  LogFatal(FormatString(R"(%s ("%s"))", LoadResStr("IDS_ERR_INITFONTS"), szFontName ? szFontName : "(null)").getData());
50  return false;
51  }
52  // if def has not been found, use the def as font name
53  // determine font def string
54  const char *szFontString = szFontName;
55  // font not assigned?
56  assert(*szFontString);
57  if (!*szFontString)
58  {
59  // invalid call or spec
60  LogFatal(LoadResStr("IDS_ERR_INITFONTS")); return false;
61  }
62  // get font name
63  char FontFaceName[C4MaxName+1], FontParam[C4MaxName+1];
64  SCopyUntil(szFontString, FontFaceName, ',', C4MaxName);
65  // is it an image file?
66  int32_t iDefFontSize; DWORD dwDefWeight=FW_NORMAL;
67  switch (eType)
68  {
69  case C4FT_Log: iDefFontSize = iSize*12/14; break;
70  case C4FT_MainSmall:iDefFontSize = iSize*13/14; break;
71  case C4FT_Main: iDefFontSize = iSize; break;
72  case C4FT_Caption: iDefFontSize = iSize*16/14; break;
73  case C4FT_Title: iDefFontSize = iSize*22/14; break;
74  default: assert(false); LogFatal(LoadResStr("IDS_ERR_INITFONTS")); return false; // invalid call
75  }
76  // regular font name: let WinGDI or Freetype draw a font with the given parameters
77  // font size given?
78  if (SCopySegment(szFontString, 1, FontParam, ',', C4MaxName))
79  sscanf(FontParam, "%i", &iDefFontSize);
80  // font weight given?
81  if (SCopySegment(szFontString, 2, FontParam, ',', C4MaxName))
82  {
83  int iDefWeight;
84  sscanf(FontParam, "%i", &iDefWeight);
85  dwDefWeight = iDefWeight;
86  }
87  // check if it's already loaded from that group with that parameters
88  if (rFont->IsSameAs(FontFaceName, iDefFontSize, dwDefWeight))
89  return true;
90  // it's not; so (re-)load it now!
91  if (rFont->IsInitialized())
92  {
93  // reloading
94  rFont->Clear();
95  LogF(LoadResStr("IDS_PRC_UPDATEFONT"), FontFaceName, iDefFontSize, dwDefWeight);
96  }
97  // check if one of the internally listed fonts should be used
98  const char * const extensions[] = { "ttf", "otf", "ttc", "fon", "fnt", "fot", nullptr };
99  char FileName[_MAX_PATH+1];
100  int32_t ID;
101  C4Group * pGrp = pGfxGroups->FindSuitableFile(FontFaceName, extensions, FileName, &ID);
102  if (pGrp)
103  {
104  if (LastUsedGrpID != ID || LastUsedName != FontFaceName)
105  {
107  pLastUsedFont = nullptr;
108  }
109  if (!pLastUsedFont)
110  {
111  StdBuf Data;
112  if (pGrp->LoadEntry(FileName, &Data))
113  {
114  try
115  {
116  pLastUsedFont = CreateFont(Data);
117  LastUsedGrpID = ID;
118  LastUsedName = FontFaceName;
119  }
120  catch (std::runtime_error & e)
121  {
122  LogFatal(e.what());
123  pGrp = nullptr;
124  }
125  }
126  }
127  }
128  // no internal font match? Then create one using the given face/filename (using a system font)
129  if (!pGrp)
130  {
131  if (LastUsedGrpID != -1 || LastUsedName != FontFaceName)
132  {
134  pLastUsedFont = nullptr;
135  }
136  if (!pLastUsedFont)
137  {
138  try
139  {
140  pLastUsedFont = CreateFont(FontFaceName);
141  if (!pLastUsedFont)
142  // no match for font face found
143  throw std::runtime_error(FormatString("Font face %s undefined", FontFaceName).getData());
144  LastUsedGrpID = -1;
145  LastUsedName = FontFaceName;
146  }
147  catch (std::runtime_error & e)
148  {
149  LogFatal(e.what());
150  }
151  }
152  }
153  if (!pLastUsedFont)
154  {
155  LogFatal(LoadResStr("IDS_ERR_INITFONTS"));
156  return false;
157  }
158  try
159  {
160  rFont->Init(*pLastUsedFont, FontFaceName, iDefFontSize, dwDefWeight, fDoShadow); // throws exception on error
161  return true;
162  }
163  catch (std::runtime_error & e)
164  {
165  LogFatal(e.what());
166  LogFatal(LoadResStr("IDS_ERR_INITFONTS"));
167  return false;
168  }
169 #endif
170 }
171 
173 {
174 #ifndef USE_CONSOLE
175  // delete vector font cache
177  pLastUsedFont = nullptr;
178 #endif
179 }
180 
181 #ifndef USE_CONSOLE
183 {
184  FT_Library library;
185  FT_Face face;
186  StdBuf Data;
187 public:
188  CStdVectorFont(const char * FontFaceName): RefCnt(1)
189  {
190 #if defined(_WIN32)
191  // Win32 using freetype: Load TrueType-data from WinGDI into Data-buffer to be used by FreeType
192  bool fSuccess = false;
193  HDC hDC = ::CreateCompatibleDC(nullptr);
194  if (hDC)
195  {
196  HFONT hFont = ::CreateFontA(0, 0, 0, 0, FW_DONTCARE, false,
197  false, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
198  CLIP_DEFAULT_PRECIS, 5,
199  VARIABLE_PITCH, FontFaceName);
200  if (hFont)
201  {
202  SelectObject( hDC, hFont );
203  uint32_t dwTTFSize = ::GetFontData(hDC, 0, 0, nullptr, 0);
204  if (dwTTFSize && dwTTFSize != GDI_ERROR)
205  {
206  Data.SetSize(dwTTFSize);
207  uint32_t dwRealTTFSize = ::GetFontData(hDC, 0, 0, Data.getMData(), dwTTFSize);
208  if (dwRealTTFSize == dwTTFSize)
209  {
210  fSuccess = true;
211  }
212  else
213  Data.Clear();
214  }
215  DeleteObject(hFont);
216  }
217  DeleteDC(hDC);
218  }
219  if (!fSuccess)
220  throw std::runtime_error("Some Win32 error");
221  // Initialize Freetype
222  if (FT_Init_FreeType(&library))
223  throw std::runtime_error("Cannot init Freetype");
224  // Load the font
225  FT_Error e;
226  if ((e=FT_New_Memory_Face(library, static_cast<const FT_Byte *>(Data.getData()), Data.getSize(), 0, &face)))
227  throw std::runtime_error(std::string("Cannot load font: ") + FormatString("%d",e).getData());
228 #else
229  //FIXME: get path name from OS
230  // Initialize Freetype
231  if (FT_Init_FreeType(&library))
232  throw std::runtime_error("Cannot init Freetype");
233  // Load the font
234  FT_Error e;
235  if ((e=FT_New_Face(library, FontFaceName, 0, &face)))
236  throw std::runtime_error(std::string("Cannot load ") + FontFaceName + ": " + FormatString("%d",e).getData());
237 #endif
238  }
239  CStdVectorFont(StdBuf & Data) : Data(Data), RefCnt(1)
240  {
241  // Initialize Freetype
242  if (FT_Init_FreeType(&library))
243  throw std::runtime_error("Cannot init Freetype");
244  // Load the font
245  FT_Error e;
246  if ((e=FT_New_Memory_Face(library, static_cast<const FT_Byte *>(Data.getData()), Data.getSize(), 0, &face)))
247  throw std::runtime_error(std::string("Cannot load font: ") + FormatString("%d",e).getData());
248  }
250  {
251  FT_Done_Face(face);
252  FT_Done_FreeType(library);
253  }
254  operator FT_Face () { return face; }
255  FT_Face operator -> () { return face; }
256  int RefCnt;
257 };
258 
259 CStdVectorFont * C4FontLoader::CreateFont(const char *szFaceName)
260 {
261  return new CStdVectorFont(szFaceName);
262 }
264 {
265  return new CStdVectorFont(Data);
266 }
268 {
269  if (!pFont) return;
270  --(pFont->RefCnt);
271  if (!pFont->RefCnt)
272  delete pFont;
273 }
274 #endif
275 
277 
279 {
280 #ifndef USE_CONSOLE
281  // set default values
282  sfcCurrent = nullptr;
283  iSfcSizes = 64;
285  iFontZoom=1; // default: no internal font zooming - likely no antialiasing either...
286  iHSpace=-1;
289  fDoShadow=false;
290  // font not yet initialized
291  *szFontName=0;
292  id=0;
293  pCustomImages=nullptr;
294  pVectorFont = nullptr;
295 #endif
296 }
297 
298 #ifndef USE_CONSOLE
300 {
301  // add new surface as render target
302  auto sfcNew = std::make_unique<C4Surface>(iSfcSizes, iSfcSizes, 0);
303  // If old surface was locked, unlock it and lock the new one in its stead
304  if (sfcCurrent && sfcCurrent->IsLocked())
305  {
306  sfcCurrent->Unlock();
307  sfcNew->Lock();
308  }
309  sfcCurrent = sfcNew.get();
310  psfcFontData.push_back(std::move(sfcNew));
312  return true;
313 }
314 
315 bool CStdFont::CheckRenderedCharSpace(uint32_t iCharWdt, uint32_t iCharHgt)
316 {
317  // need to do a line break?
318  if (iCurrentSfcX + iCharWdt >= (uint32_t)iSfcSizes) if (iCurrentSfcX)
319  {
320  iCurrentSfcX = 0;
321  iCurrentSfcY += iCharHgt;
322  if (iCurrentSfcY + iCharHgt >= (uint32_t)iSfcSizes)
323  {
324  // surface is full: Next one
325  if (!AddSurface()) return false;
326  }
327  }
328  // OK draw it there
329  return true;
330 }
331 
332 bool CStdFont::AddRenderedChar(uint32_t dwChar, C4Facet *pfctTarget)
333 {
334  if (!pVectorFont) return false;
335  // Freetype character rendering
336  FT_Set_Pixel_Sizes(*pVectorFont, dwDefFontHeight, dwDefFontHeight);
337  int32_t iBoldness = dwWeight-400; // zero is normal; 300 is bold
338  if (iBoldness)
339  {
340  iBoldness = (1<<16) + (iBoldness<<16)/400;
341  FT_Matrix mat;
342  mat.xx = iBoldness; mat.xy = mat.yx = 0; mat.yy = 1<<16;
343  FT_Set_Transform(*pVectorFont, &mat, nullptr);
344  }
345  else
346  {
347  FT_Set_Transform(*pVectorFont, nullptr, nullptr);
348  }
349  // Render
350  if (FT_Load_Char(*pVectorFont, dwChar, FT_LOAD_RENDER | FT_LOAD_NO_HINTING))
351  {
352  // although the character was not drawn, assume it's not in the font and won't be needed
353  // so return success here
354  return true;
355  }
356  // Make a shortcut to the glyph
357  FT_GlyphSlot slot = (*pVectorFont)->glyph;
358  if (slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
359  {
360  // although the character was drawn in a strange way, assume it's not in the font and won't be needed
361  // so return success here
362  return true;
363  }
364  // linebreak/ new surface check
365  int width = std::max<int>(slot->advance.x / 64, std::max(slot->bitmap_left,0) + slot->bitmap.width) + fDoShadow;
366  if (!CheckRenderedCharSpace(width, iGfxLineHgt)) return false;
367  // offset from the top
368  int at_y = iCurrentSfcY + dwDefFontHeight * (*pVectorFont)->ascender / (*pVectorFont)->units_per_EM - slot->bitmap_top;
369  int at_x = iCurrentSfcX + std::max(slot->bitmap_left,0);
370  // Copy to the surface
371  if (!sfcCurrent->Lock()) return false;
372  for (unsigned int y = 0; y < slot->bitmap.rows + fDoShadow; ++y)
373  {
374  for (unsigned int x = 0; x < slot->bitmap.width + fDoShadow; ++x)
375  {
376  unsigned char bAlpha, bAlphaShadow;
377  if (x < slot->bitmap.width && y < slot->bitmap.rows)
378  bAlpha = (unsigned char)(slot->bitmap.buffer[slot->bitmap.width * y + x]);
379  else
380  bAlpha = 0;
381  // Make a shadow from the upper-left pixel, and blur with the eight neighbors
382  DWORD dwPixVal = 0u;
383  bAlphaShadow = 0;
384  if ((x || y) && fDoShadow)
385  {
386  int iShadow = 0;
387  if (x < slot->bitmap.width && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 0)];
388  if (x > 1 && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 0)];
389  if (x > 0 && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 0)];
390  if (x < slot->bitmap.width && y > 1 ) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 2)];
391  if (x > 1 && y > 1 ) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 2)];
392  if (x > 0 && y > 1 ) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 2)];
393  if (x < slot->bitmap.width && y > 0 ) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 1)];
394  if (x > 1 && y > 0 ) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 1)];
395  if (x > 0 && y > 0 ) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 1)]*8;
396  bAlphaShadow += iShadow / 16;
397  // because blitting on a black pixel reduces luminosity as compared to shadowless font,
398  // assume luminosity as if blitting shadowless font on a 50% gray background
399  unsigned char cBack = bAlpha;
400  dwPixVal = RGBA(cBack/2, cBack/2, cBack/2, bAlphaShadow);
401  }
402  BltAlpha(dwPixVal, bAlpha << 24 | 0xffffff);
403  sfcCurrent->SetPixDw(at_x + x, at_y + y, dwPixVal);
404  }
405  }
406  sfcCurrent->Unlock();
407  // Save the position of the glyph for the rendering code
408  pfctTarget->Set(sfcCurrent, iCurrentSfcX, iCurrentSfcY, width, iGfxLineHgt);
409 
410  // advance texture position
411  iCurrentSfcX += pfctTarget->Wdt;
412  return true;
413 }
414 
416 {
417  // find/add facet in map
418  C4Facet &rFacet = fctUnicodeMap[c];
419  // create character on the fly if necessary and possible
420  if (!rFacet.Surface) AddRenderedChar(c, &rFacet);
421  // rendering might have failed, in which case rFacet remains empty. Should be OK; char won't be printed then
422  return rFacet;
423 }
424 #endif
425 
426 void CStdFont::Init(CStdVectorFont & VectorFont, const char *font_face_name, DWORD dwHeight, DWORD dwFontWeight, bool fDoShadow)
427 {
428 #ifndef USE_CONSOLE
429  // clear previous
430  Clear();
431  // set values
432  iHSpace=fDoShadow ? -1 : 0; // horizontal shadow
433  dwWeight=dwFontWeight;
434  this->fDoShadow = fDoShadow;
435  // determine needed texture size
436  if (dwHeight * iFontZoom > 40)
437  iSfcSizes = 512;
438  else if (dwDefFontHeight * iFontZoom > 20)
439  iSfcSizes = 256;
440  else
441  iSfcSizes = 128;
442  SCopy(font_face_name, szFontName, 80);
443  dwDefFontHeight = dwHeight;
444  // create surface
445  if (!AddSurface())
446  {
447  Clear();
448  throw std::runtime_error(std::string("Cannot create surface (") + szFontName + ")");
449  }
450 
451  // Store vector font
452  pVectorFont = &VectorFont;
453  ++(pVectorFont->RefCnt);
454  // Get size
455  // FIXME: use bbox or dynamically determined line heights here
456  iLineHgt = (VectorFont->ascender - VectorFont->descender) * dwHeight / VectorFont->units_per_EM;
457  iGfxLineHgt = iLineHgt + fDoShadow; // vertical shadow
458 
459  // loop through all ASCII printable characters and prepare them
460  // Non-ASCII Unicode characters will be created on the fly
461  // now render all characters!
462 
463  int cMax = 127;
464  sfcCurrent->Lock();
465  for (int c=' '; c <= cMax; ++c)
466  {
467  if (!AddRenderedChar(c, &(fctAsciiTexCoords[c-' '])))
468  {
469  sfcCurrent->Unlock();
470  Clear();
471  throw std::runtime_error(std::string("Cannot render characters for Font (") + szFontName + ")");
472  }
473  }
474  sfcCurrent->Unlock();
475  // adjust line height
476  iLineHgt /= iFontZoom;
477 #endif
478 }
479 
481 {
482 #ifndef USE_CONSOLE
483  FontLoader.DestroyFont(pVectorFont);
484  pVectorFont = nullptr;
485 
486  // clear font sfcs
487  sfcCurrent = nullptr;
488  psfcFontData.clear();
489  for (int c=' '; c<256; ++c) fctAsciiTexCoords[c-' '].Default();
490  fctUnicodeMap.clear();
491  // set default values
493  iFontZoom=1; // default: no internal font zooming - likely no antialiasing either...
494  iHSpace=-1;
497  fDoShadow=false;
498  // font not yet initialized
499  *szFontName=0;
500  id=0;
501 #endif
502 }
503 
504 /* Text size measurement */
505 bool CStdFont::GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup)
506 {
507  // safety
508  if (!szText) return false;
509  assert(IsValidUtf8(szText));
510 #ifdef USE_CONSOLE
511  rsx = rsy = 0;
512 #else
513  // keep track of each row's size
514  int iRowWdt=0,iWdt=0,iHgt=iLineHgt;
515  // ignore any markup
516  C4Markup MarkupChecker(false);
517  // go through all text
518  while (*szText)
519  {
520  // ignore markup
521  if (fCheckMarkup) MarkupChecker.SkipTags(&szText);
522  // get current char
523  uint32_t c = GetNextCharacter(&szText);
524  // done? (must check here, because markup-skip may have led to text end)
525  if (!c) break;
526  // line break?
527  if (c == '\n' || (fCheckMarkup && c == '|')) { iRowWdt=0; iHgt+=iLineHgt; continue; }
528  // ignore system characters
529  if (c < ' ') continue;
530  // image?
531  int iImgLgt;
532  if (fCheckMarkup && c=='{' && szText[0]=='{' && szText[1]!='{' && (iImgLgt=SCharPos('}', szText+1))>0 && szText[iImgLgt+2]=='}')
533  {
534  char imgbuf[101];
535  SCopy(szText+1, imgbuf, std::min(iImgLgt, 100));
536 
537  int w2, h2;
538  if(!GetFontImageSize(imgbuf, w2, h2))
539  { w2 = 0; h2 = 0; }
540 
541  iRowWdt += w2;
542  // skip image tag
543  szText+=iImgLgt+3;
544  }
545  else
546  {
547  // regular char
548  // look up character width in texture coordinates table
549  iRowWdt += GetCharacterFacet(c).Wdt / iFontZoom;
550  }
551  // apply horizontal indent for all but last char
552  if (*szText) iRowWdt += iHSpace;
553  // adjust max row size
554  if (iRowWdt>iWdt) iWdt=iRowWdt;
555  }
556  // store output
557  rsx=iWdt; rsy=iHgt;
558  // done, success
559 #endif
560  return true;
561 }
562 
563 int CStdFont::BreakMessage(const char *szMsg, int iWdt, char *szOut, int iMaxOutLen, bool fCheckMarkup, float fZoom)
564 {
565 #ifdef USE_CONSOLE
566  return 0;
567 #else
568  // note iMaxOutLen does not include terminating null character
569  // safety
570  if (!iMaxOutLen) return 0;
571  if (!szMsg)
572  {
573  if (szOut) *szOut=0;
574  return 0;
575  }
576  auto t = BreakMessage(szMsg, iWdt, fCheckMarkup, fZoom);
577  auto str = std::get<0>(t);
578  auto len = str.copy(szOut, iMaxOutLen);
579  szOut[len] = '\0';
580  return std::get<1>(t);
581 #endif
582 }
583 
584 int CStdFont::BreakMessage(const char *szMsg, int iWdt, StdStrBuf *pOut, bool fCheckMarkup, float fZoom)
585 {
586 #ifdef USE_CONSOLE
587  return 0;
588 #else
589  if (!szMsg || !pOut) return 0;
590  auto t = BreakMessage(szMsg, iWdt, fCheckMarkup, fZoom);
591  auto str = std::get<0>(t);
592  pOut->Clear();
593  pOut->Append(str.c_str(), str.size());
594  return std::get<1>(t);
595 #endif
596 }
597 
598 // Returns broken message and resulting height.
599 std::tuple<std::string, int> CStdFont::BreakMessage(const char *szMsg, int iWdt, bool fCheckMarkup, float fZoom)
600 {
601 #ifdef USE_CONSOLE
602  return std::make_tuple("", 0);
603 #else
604  if (!szMsg) return std::make_tuple("", 0);
605  std::string out;
606  // TODO: might szLastEmergenyBreakPos, iLastBreakOutLen or iXEmergencyBreak not be properly initialised before use?
607  uint32_t c;
608  const char *szPos=szMsg, // current parse position in the text
609  *szLastBreakPos = szMsg, // points to the char after at (whitespace) or after ('-') which text can be broken
610  *szLastEmergenyBreakPos = nullptr, // same, but at last char in case no suitable linebreak could be found
611  *szLastPos; // last position until which buffer has been transferred to output
612  int iLastBreakOutLen = 0, iLastEmergencyBreakOutLen = 0; // size of output string at break positions
613  int iX=0, // current text width at parse pos
614  iXBreak=0, // text width as it was at last break pos
615  iXEmergencyBreak = 0, // same, but at last char in case no suitable linebreak could be found
616  iHgt=iLineHgt; // total height of output text
617  int iCharHOverlap = std::max<int>(-iHSpace, 0); // character width exceeding placement of next character
618  bool fIsFirstLineChar = true;
619  // ignore any markup
620  C4Markup MarkupChecker(false);
621  // go through all text
622  while (*(szLastPos = szPos))
623  {
624  // ignore markup
625  if (fCheckMarkup) MarkupChecker.SkipTags(&szPos);
626  // get current char
627  c = GetNextCharacter(&szPos);
628  // done? (must check here, because markup-skip may have led to text end)
629  if (!c) break;
630  // manual break?
631  int iCharWdt = 0;
632  if (c != '\n' && (!fCheckMarkup || c != '|'))
633  {
634  // image?
635  int iImgLgt;
636  if (fCheckMarkup && c=='{' && szPos[0]=='{' && szPos[1]!='{' && (iImgLgt=SCharPos('}', szPos+1))>0 && szPos[iImgLgt+2]=='}')
637  {
638  char imgbuf[101];
639  SCopy(szPos+1, imgbuf, std::min(iImgLgt, 100));
640 
641  int iCharHgt;
642  if(!GetFontImageSize(imgbuf, iCharWdt, iCharHgt))
643  iCharWdt = 0;
644 
645  // skip image tag
646  szPos+=iImgLgt+3;
647  }
648  else
649  {
650  // regular char
651  // look up character width in texture coordinates table
652  if (c >= ' ')
653  iCharWdt = int(fZoom * GetCharacterFacet(c).Wdt / iFontZoom) + iHSpace;
654  else
655  iCharWdt = 0; // OMFG ctrl char
656  }
657  // add chars to output
658  out.append(szLastPos, szPos - szLastPos);
659  // add to line; always add one char at minimum
660  if ((iX+=iCharWdt)+iCharHOverlap <= iWdt || fIsFirstLineChar)
661  {
662  // check whether linebreak possibility shall be marked here
663  // 2do: What about unicode-spaces?
664  if (c<256) if (isspace((unsigned char)c) || c == '-')
665  {
666  szLastBreakPos = szPos;
667  iLastBreakOutLen = out.size();
668  // space: Break directly at space if it isn't the first char here
669  // first char spaces must remain, in case the output area is just one char width
670  if (c != '-' && !fIsFirstLineChar) --szLastBreakPos; // because c<256, the character length can be safely assumed to be 1 here
671  iXBreak = iX;
672  }
673  // always mark emergency break after char that fitted the line
674  szLastEmergenyBreakPos = szPos;
675  iXEmergencyBreak = iX;
676  iLastEmergencyBreakOutLen = out.size();
677  // line OK; continue filling it
678  fIsFirstLineChar = false;
679  continue;
680  }
681  // line must be broken now
682  // check if a linebreak is possible directly here, because it's a space
683  // only check for space and not for other breakable characters (such as '-'), because the break would happen after those characters instead of at them
684  if (c<128 && isspace((unsigned char)c))
685  {
686  szLastBreakPos = szPos-1;
687  iLastBreakOutLen = out.size();
688  iXBreak = iX;
689  }
690  // if there was no linebreak, do it at emergency pos
691  else if (szLastBreakPos == szMsg)
692  {
693  szLastBreakPos = szLastEmergenyBreakPos;
694  iLastBreakOutLen = iLastEmergencyBreakOutLen;
695  iXBreak = iXEmergencyBreak;
696  }
697  // insert linebreak at linebreak pos
698  // was it a space? Then just overwrite space with a linebreak
699  if (uint8_t(*szLastBreakPos)<128 && isspace((unsigned char)*szLastBreakPos))
700  out.at(iLastBreakOutLen-1) = '\n';
701  else
702  {
703  // otherwise, insert line break
704  out.insert(iLastBreakOutLen, 1, '\n');
705  }
706  // calc next line usage
707  iX -= iXBreak;
708  }
709  else
710  {
711  // a static linebreak: Everything's well; this just resets the line width
712  iX = 0;
713  // add to output
714  out.append(szLastPos, szPos - szLastPos);
715  }
716  // forced or manual line break: set new line beginning to char after line break
717  szLastBreakPos = szMsg = szPos;
718  // manual line break or line width overflow: add char to next line
719  iHgt += iLineHgt;
720  fIsFirstLineChar = true;
721  }
722  // transfer final data to buffer (any missing markup)
723  out.append(szLastPos, szPos - szLastPos);
724  // return text height
725  return std::make_tuple(out, iHgt);
726 #endif
727 }
728 
729 // get message break and pos after message break
730 // 2do: Function not ready for UTF-8, markup or inline images. Remove its usage using standardized BreakMessage
731 int CStdFont::GetMessageBreak(const char *szMsg, const char **ppNewPos, int iBreakWidth, float fZoom)
732 {
733 #ifdef USE_CONSOLE
734  *ppNewPos = szMsg;
735  while(**ppNewPos) ++*ppNewPos;
736  return *ppNewPos - szMsg;
737 #else
738  // safety
739  if (!szMsg || !*szMsg) { *ppNewPos = szMsg; return 0; }
740  const char *szPos = szMsg; unsigned char c;
741  int iWdt = 0; int iPos = 0;
742  // check all message until it's too wide
743  while ((c = *szPos++))
744  {
745  ++iPos;
746  // get char width
747  int iCharWdt = int(fZoom * fctAsciiTexCoords[c-' '].Wdt / iFontZoom) + iHSpace;
748  // add to overall line width
749  iWdt += iCharWdt;
750  // next char only if the line didn't overflow
751  if (iWdt > iBreakWidth) break;
752  }
753  // did it all fit?
754  if (!c)
755  {
756  // all OK then; use all the buffer
757  *ppNewPos = szPos-1;
758  return iPos;
759  }
760  // line must be broken - trace back until first break char
761  // szPos2 will be first char of next line
762  const char *szPos2 = szPos-1; int i=0;
763  while ((!i++ || *szPos2 != '-') && *szPos2 != ' ')
764  if (szPos2 == szMsg)
765  {
766  // do not go past beginning of line
767  // then better print out an unfitting break
768  szPos2 = szPos-1;
769  break;
770  }
771  else
772  --szPos2;
773  // but do print at least one char
774  if (szPos2 <= szMsg) szPos2 = szMsg+1;
775  // assign next line start pos - skip spaces
776  *ppNewPos = szPos2;
777  if (*szPos2 == ' ') ++*ppNewPos;
778  // return output string length
779  return szPos2 - szMsg;
780 #endif
781 }
782 
783 
784 
785 
786 /* Text drawing */
787 
788 
789 void CStdFont::DrawText(C4Surface * sfcDest, float iX, float iY, DWORD dwColor, const char *szText, DWORD dwFlags, C4Markup &Markup, float fZoom)
790 {
791 #ifndef USE_CONSOLE
792  assert(IsValidUtf8(szText));
793  C4DrawTransform bt, *pbt=nullptr;
794  // set blit color
795  DWORD dwOldModClr;
796  bool fWasModulated = pDraw->GetBlitModulation(dwOldModClr);
797  if (fWasModulated) ModulateClr(dwColor, dwOldModClr);
798  // get alpha fade percentage
799  DWORD dwAlphaMod = std::min<uint32_t>(((dwColor>>0x18)*0xff)/0xaf, 255)<<0x18 | 0xffffff;
800 
801  /* char TEXT[8192];
802  sprintf(TEXT, "%s(%x-%x-%x)", szText, dwAlphaMod>>0x18, dwColor>>0x15, (((int)(dwColor>>0x15)-0x50)*0xff)/0xaf); szText=TEXT;*/
803  // adjust text starting position (horizontal only)
804  if (dwFlags & STDFONT_CENTERED)
805  {
806  // centered
807  int32_t sx,sy;
808  GetTextExtent(szText, sx,sy, !(dwFlags & STDFONT_NOMARKUP));
809  sx = int(fZoom*sx); sy = int(fZoom*sy);
810  iX-=sx/2;
811  }
812  else if (dwFlags & STDFONT_RIGHTALGN)
813  {
814  // right-aligned
815  int32_t sx,sy;
816  GetTextExtent(szText, sx,sy, !(dwFlags & STDFONT_NOMARKUP));
817  sx = int(fZoom*sx); sy = int(fZoom*sy);
818  iX-=sx;
819  }
820  // apply texture zoom
821  fZoom /= iFontZoom;
822  // set start markup transformation
823  if (!Markup.Clean()) pbt=&bt;
824  // output text
825  uint32_t c;
826  C4Facet fctFromBlt; // source facet
827  while ((c = GetNextCharacter(&szText)))
828  {
829  // ignore system characters
830  if (c < ' ') continue;
831  // apply markup
832  if (c=='<' && (~dwFlags & STDFONT_NOMARKUP))
833  {
834  // get tag
835  if (Markup.Read(&--szText))
836  {
837  // mark transform to be done
838  // (done only if tag was found, so most normal blits don't init a trasnformation matrix)
839  pbt=&bt;
840  // skip the tag
841  continue;
842  }
843  // invalid tag: render it as text
844  ++szText;
845  }
846  int w2, h2; // dst width/height
847  // custom image?
848  int iImgLgt;
849  char imgbuf[101] = "";
850  if (c=='{' && szText[0]=='{' && szText[1]!='{' && (iImgLgt=SCharPos('}', szText+1))>0 && szText[iImgLgt+2]=='}' && !(dwFlags & STDFONT_NOMARKUP))
851  {
852  SCopy(szText+1, imgbuf, std::min(iImgLgt, 100));
853  szText+=iImgLgt+3;
854  if(!GetFontImageSize(imgbuf, w2, h2))
855  continue;
856  //normal: not modulated, unless done by transform or alpha fadeout
857  if ((dwColor>>0x18) >= 0xaf)
859  else
860  pDraw->ActivateBlitModulation((dwColor&0xff000000) | 0xffffff);
861  }
862  else
863  {
864  // regular char
865  // get texture coordinates
866  fctFromBlt = GetCharacterFacet(c);
867  if(!fctFromBlt.Surface) continue;
868  w2=int(fctFromBlt.Wdt*fZoom); h2=int(fctFromBlt.Hgt*fZoom);
869  pDraw->ActivateBlitModulation(dwColor);
870  }
871  // do color/markup
872  if (pbt)
873  {
874  // reset data to be transformed by markup
875  DWORD dwBlitClr = dwColor;
876  bt.Set(1,0,0,0,1,0,0,0,1);
877  // apply markup
878  Markup.Apply(bt, dwBlitClr);
879  if (dwBlitClr != dwColor) ModulateClrA(dwBlitClr, dwAlphaMod);
880  pDraw->ActivateBlitModulation(dwBlitClr);
881  // move transformation center to center of letter
882  float fOffX=(float) w2/2 + iX;
883  float fOffY=(float) h2/2 + iY;
884  bt.mat[2] += fOffX - fOffX*bt.mat[0] - fOffY*bt.mat[1];
885  bt.mat[5] += fOffY - fOffX*bt.mat[3] - fOffY*bt.mat[4];
886  }
887  if(imgbuf[0])
888  {
889  C4Facet fct;
890  fct.Set(sfcDest, iX, iY + (iGfxLineHgt - h2)/2.0f, w2, h2);
891  pCustomImages->DrawFontImage(imgbuf, fct, pbt);
892  }
893  else
894  {
895  // blit character
896  pDraw->Blit(fctFromBlt.Surface, float(fctFromBlt.X), float(fctFromBlt.Y), float(fctFromBlt.Wdt),float(fctFromBlt.Hgt),
897  sfcDest, iX, iY, float(w2), float(h2),
898  true, pbt);
899  }
900  // advance pos and skip character indent
901  iX+=w2+iHSpace;
902  }
903  // reset blit modulation
904  if (fWasModulated)
905  pDraw->ActivateBlitModulation(dwOldModClr);
906  else
908 #endif
909 }
910 
911 bool CStdFont::GetFontImageSize(const char* szTag, int& width, int& height) const
912 {
913 #ifdef USE_CONSOLE
914  width = height = 0;
915 #else
916  const float aspect = pCustomImages ? pCustomImages->GetFontImageAspect(szTag) : -1.0f;
917 
918  // aspect < 0 means there is no such image
919  if (aspect < 0.0f) return false;
920 
921  // image found: adjust aspect by font height and calc appropriate width
922  height = iGfxLineHgt;
923  width = static_cast<int>(height * aspect + 0.5f);
924 
925  // make images not ridiciously wide
926  if(width > height)
927  {
928  float scale = static_cast<float>(height)/static_cast<float>(width);
929 
930  width = height;//static_cast<int32_t>(width*scale + 0.5f);
931  height = static_cast<int32_t>(height*scale + 0.5f);
932  }
933 #endif
934 
935  return true;
936 }
const char * getData() const
Definition: StdBuf.h:442
const void * getData() const
Definition: StdBuf.h:99
Definition: StdBuf.h:29
float Y
Definition: C4Facet.h:118
#define STDFONT_NOMARKUP
Definition: C4FontLoader.h:33
std::map< uint32_t, C4Facet > fctUnicodeMap
Definition: C4FontLoader.h:99
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool IsSameAs(const char *szCFontName, DWORD iCHeight, DWORD dwCWeight) const
Definition: C4FontLoader.h:176
CStdVectorFont * pLastUsedFont
Definition: C4FontLoader.h:62
bool CheckRenderedCharSpace(uint32_t iCharWdt, uint32_t iCharHgt)
uint32_t GetNextCharacter(const char **pszString)
Definition: Standard.h:84
void Clear()
Definition: StdBuf.h:466
int GetMessageBreak(const char *szMsg, const char **ppNewPos, int iBreakWidth, float fZoom=1.0f)
bool AddSurface()
bool SCopySegment(const char *szString, int iSegment, char *sTarget, char cSeparator, int iMaxL, bool fSkipWhitespace)
Definition: Standard.cpp:273
bool Lock()
Definition: C4Surface.cpp:453
C4Group * FindSuitableFile(const char *szName, const char *const extensions[], char *szFileName, int32_t *pID=nullptr)
Definition: C4GroupSet.cpp:191
DWORD dwWeight
Definition: C4FontLoader.h:95
void Clear()
Definition: StdBuf.h:190
CStdVectorFont(StdBuf &Data)
bool Unlock()
Definition: C4Surface.cpp:464
#define STDFONT_CENTERED
Definition: C4FontLoader.h:28
bool LoadEntry(const char *szEntryName, char **lpbpBuf, size_t *ipSize=nullptr, int iAppendZeros=0)
Definition: C4Group.cpp:1893
bool GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup=true)
void ModulateClr(DWORD &dwDst, DWORD dwMod)
Definition: StdColors.h:53
StdCopyStrBuf LastUsedName
Definition: C4FontLoader.h:63
#define _MAX_PATH
void Set(C4Surface &rSfc)
Definition: C4Facet.cpp:459
int iFontZoom
Definition: C4FontLoader.h:88
void Set(float fA, float fB, float fC, float fD, float fE, float fF, float fG, float fH, float fI)
Definition: C4Facet.h:76
int iLineHgt
Definition: C4FontLoader.h:115
size_t getSize() const
Definition: StdBuf.h:101
void SetSize(size_t inSize)
Definition: StdBuf.h:204
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
int32_t iCurrentSfcX
Definition: C4FontLoader.h:91
int32_t iCurrentSfcY
Definition: C4FontLoader.h:91
std::vector< std::unique_ptr< C4Surface > > psfcFontData
Definition: C4FontLoader.h:86
bool SkipTags(const char **ppText)
Definition: C4Markup.cpp:105
C4Facet & GetUnicodeCharacterFacet(uint32_t c)
int IsLocked() const
Definition: C4Surface.h:93
bool fDoShadow
Definition: C4FontLoader.h:96
bool SetPixDw(int iX, int iY, DWORD dwCol)
Definition: C4Surface.cpp:576
FT_Face operator->()
int iGfxLineHgt
Definition: C4FontLoader.h:94
virtual float GetFontImageAspect(const char *szImageTag)=0
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
C4Facet fctAsciiTexCoords[256-' ']
Definition: C4FontLoader.h:98
bool GetFontImageSize(const char *szTag, int &width, int &height) const
const unsigned int C4MaxName
C4Surface * sfcCurrent
Definition: C4FontLoader.h:90
C4Draw * pDraw
Definition: C4Draw.cpp:42
bool LogFatal(const char *szMessage)
Definition: C4Log.cpp:237
bool IsValidUtf8(const char *text, int length)
Definition: Standard.cpp:696
bool Clean()
Definition: C4Markup.h:76
bool AddRenderedChar(uint32_t dwChar, C4Facet *pfctTarget)
void Clear()
CStdVectorFont * CreateFont(StdBuf &Data)
void BltAlpha(DWORD &dwDst, DWORD dwSrc)
Definition: StdColors.h:31
std::tuple< std::string, int > BreakMessage(const char *szMsg, int iWdt, bool fCheckMarkup, float fZoom=1.0f)
bool Read(const char **ppText, bool fSkip=false)
Definition: C4Markup.cpp:34
bool Blit(C4Surface *sfcSource, float fx, float fy, float fwdt, float fhgt, C4Surface *sfcTarget, float tx, float ty, float twdt, float thgt, bool fSrcColKey=false, const C4BltTransform *pTransform=nullptr)
Definition: C4Draw.cpp:301
void DrawText(C4Surface *sfcDest, float iX, float iY, DWORD dwColor, const char *szText, DWORD dwFlags, C4Markup &Markup, float fZoom)
DWORD dwDefFontHeight
Definition: C4FontLoader.h:83
C4FontLoader FontLoader
float Hgt
Definition: C4Facet.h:118
bool GetBlitModulation(DWORD &rdwColor)
Definition: C4Draw.h:190
int32_t LastUsedGrpID
Definition: C4FontLoader.h:64
void DeactivateBlitModulation()
Definition: C4Draw.h:189
void Apply(C4BltTransform &rBltTrf, DWORD &dwClr)
Definition: C4Markup.h:74
void DestroyFont(CStdVectorFont *pFont)
void Init(CStdVectorFont &VectorFont, const char *font_face_name, DWORD dwHeight, DWORD dwFontWeight=FW_NORMAL, bool fDoShadow=true)
char szFontName[80+1]
Definition: C4FontLoader.h:84
int SCharPos(char cTarget, const char *szInStr, int iIndex)
Definition: Standard.cpp:233
void ActivateBlitModulation(DWORD dwWithClr)
Definition: C4Draw.h:188
CStdVectorFont * pVectorFont
Definition: C4FontLoader.h:103
uint32_t RGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a)
Definition: StdColors.h:22
#define FW_NORMAL
Definition: C4FontLoader.h:36
C4Surface * Surface
Definition: C4Facet.h:117
bool IsInitialized() const
Definition: C4FontLoader.h:159
CStdVectorFont(const char *FontFaceName)
virtual bool DrawFontImage(const char *szImageTag, C4Facet &cgo, C4DrawTransform *transform)=0
uint32_t DWORD
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:260
C4Facet & GetCharacterFacet(uint32_t c)
Definition: C4FontLoader.h:109
float Wdt
Definition: C4Facet.h:118
#define STDFONT_RIGHTALGN
Definition: C4FontLoader.h:31
int iHSpace
Definition: C4FontLoader.h:93
float X
Definition: C4Facet.h:118
CustomImages * pCustomImages
Definition: C4FontLoader.h:101
int iSfcSizes
Definition: C4FontLoader.h:87
void SCopyUntil(const char *szSource, char *sTarget, char cUntil, int iMaxL, int iIndex)
Definition: Standard.cpp:168
void * getMData()
Definition: StdBuf.h:100
bool InitFont(CStdFont *Font, const char *szFontName, FontType eType, int32_t iSize, C4GroupSet *pGfxGroups, bool fDoShadow=true)
int iSize
Definition: TstC4NetIO.cpp:32
void ModulateClrA(DWORD &dwDst, DWORD dwMod)
Definition: StdColors.h:65
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270