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