PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
sdl/PlatformFont.cpp
Go to the documentation of this file.
1//* -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*- */
3// Name: src/platforms/sdl/PlatformFont.cpp
4// Purpose: Contains SDL-specific Font Classes
5// Author: Shane T. Mueller, Ph.D.
6// Copyright: (c) 2003-2026 Shane T. Mueller <smueller@obereed.net>
7// License: GPL 2
8//
9//
10//
11// This file is part of the PEBL project.
12//
13// PEBL is free software; you can redistribute it and/or modify
14// it under the terms of the GNU General Public License as published by
15// the Free Software Foundation; either version 2 of the License, or
16// (at your option) any later version.
17//
18// PEBL is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with PEBL; if not, write to the Free Software
25// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27#include "PlatformFont.h"
28#include "../../objects/PFont.h"
29#include "../../objects/PColor.h"
30
31#include "../../utility/PEBLUtility.h"
32#include "../../utility/PEBLPath.h"
33#include "../../utility/PError.h"
34
35#ifdef PEBL_EMSCRIPTEN
36#include "../../base/Evaluator-es.h"
37#else
38#include "../../base/Evaluator.h"
39#endif
40
41//this is for right-to-left rendering supported by harfbuzz+freetype.
42#ifdef PEBL_RTL
43#include <harfbuzz/hb.h>
44#include <harfbuzz/hb-ft.h>
45#endif
46
47
48#if defined(PEBL_OSX) | defined(PEBL_EMSCRIPTEN)
49#include "SDL.h"
50#include "SDL_ttf.h"
51#include "SDL_rwops.h"
52#else
53#include "SDL.h"
54#include "SDL_ttf.h"
55#include "SDL_rwops.h"
56#endif
57
58#include <stdio.h>
59#include <iostream>
60
61using std::string;
62using std::cerr;
63using std::endl;
64
65// Helper function to convert std::string script to const char* for TTF functions
66// Returns NULL for empty string (Latin/unknown scripts)
67static const char* script_to_cstr(const std::string & script) {
68 if (script.empty()) return NULL;
69 return script.c_str();
70}
71
72
73//Stolen from
74// http://stackoverflow.com/questions/5134404/c-read-binary-file-to-memory-alter-buffer-write-buffer-to-file
75
76
77long unsigned int getFileSize(FILE **file){
78 long unsigned int size;
79 if(fseek(*file, 0, SEEK_END) == -1){ return -1; }
80 size = ftell(*file);
81 fseek(*file, 0, SEEK_SET);
82 return size;
83}
84
85
86
87char * getFileBuffer(FILE **file, unsigned int fileSize){
88 char *buffer = (char*)malloc(fileSize + 1);
89 fread(buffer, sizeof(char),fileSize,*file);
90 return buffer;
91}
92
93unsigned long int readFileToMemory(const char path[], char ** buffr){
94
95 unsigned long int fileSize;
96 FILE *file = fopen(path, "rb");
97 if(file != NULL){
98 fileSize = getFileSize(&file);
99 //cerr << "FIlesize: " << fileSize << endl;
100 char* buff = getFileBuffer(&file,(unsigned int)fileSize);
101
102
103 (*buffr) = buff;
104
105
106 fclose(file);
107 return fileSize;
108 }else{
109 *buffr = NULL;
110 return 0;
111 }
112}
113
114
115
117PlatformFont::PlatformFont(const std::string & filename, int style, int size, PColor fgcolor, PColor bgcolor, bool aa):
118 PFont(filename, style, size, fgcolor, bgcolor, aa),
119 mBuffer(nullptr),
120 mChanged(false)
121
122{
123 string fontname = Evaluator::gPath.FindFile(mFontFileName);
124 if(fontname == "")
125 PError::SignalFatalError(string("Unable to find font file [") + mFontFileName + string("]."));
126
127 // Create cache key from base class properties (after PFont constructor)
128 mCacheKey.filename = mFontFileName;
129 mCacheKey.style = mFontStyle;
130 mCacheKey.size = mFontSize;
131
132 // Get font from cache (will load from disk if not cached)
133 mTTF_Font = FontCache::FontCacheManager::GetInstance().GetFont(mCacheKey, fontname);
134
135 if(!mTTF_Font)
136 {
137 printf("Oh My Goodness, an error : [%s]\n", TTF_GetError());
138 PError::SignalFatalError("Failed to create font\n");
139 }
140
141 //Translate PColor to SDLcolor for direct use in rendering.
142 PColor* fgColor = GetFontColorPtr();
143 PColor* bgColor = GetBackgroundColorPtr();
144
145 if(fgColor) mSDL_FGColor = SDLUtility::PColorToSDLColor(*fgColor);
146 if(bgColor) mSDL_BGColor = SDLUtility::PColorToSDLColor(*bgColor);
147}
148
149
150
153 PFont(font), // Chain to base class copy constructor which handles colors
154 mBuffer(nullptr),
155 mChanged(false)
156
157{
159 mFontStyle = font.GetFontStyle();
160 mFontSize = font.GetFontSize();
162
163 // Create cache key from font properties
164 mCacheKey.filename = mFontFileName;
165 mCacheKey.style = mFontStyle;
166 mCacheKey.size = mFontSize;
167
168 // Get font from cache (may share TTF_Font with source font)
169 string fontname = Evaluator::gPath.FindFile(mFontFileName);
170 mTTF_Font = FontCache::FontCacheManager::GetInstance().GetFont(mCacheKey, fontname);
171
172 //Translate PColor to SDLcolor for direct use in rendering.
173 PColor* fgColor = GetFontColorPtr();
174 PColor* bgColor = GetBackgroundColorPtr();
175 if(fgColor) mSDL_FGColor = SDLUtility::PColorToSDLColor(*fgColor);
176 if(bgColor) mSDL_BGColor = SDLUtility::PColorToSDLColor(*bgColor);
177
178}
179
180
184{
185 // Release font from cache (decrements ref count, closes if ref count reaches 0)
187 mTTF_Font = NULL;
188
189 // mBuffer is always nullptr with FontCache, but we keep it for binary compatibility
190 // free(nullptr) is safe and does nothing
191 free(mBuffer);
192 mBuffer = NULL;
193}
194
195
196
199{
200 //Chain up to parent method
201 PFont::SetFontColor(color);
202
203 //Set child member data - retrieve from property map
204 PColor* fgColor = GetFontColorPtr();
205 if(fgColor) mSDL_FGColor = SDLUtility::PColorToSDLColor(*fgColor);
206 mChanged = true; // Mark font as changed to trigger re-rendering
207}
208
209
210
213{
214 //Chain up to parent method
216
217 //Set child member data - retrieve from property map
218 PColor* bgColor = GetBackgroundColorPtr();
219 if(bgColor) mSDL_BGColor = SDLUtility::PColorToSDLColor(*bgColor);
220 mChanged = true; // Mark font as changed to trigger re-rendering
221}
222
223
224
226void PlatformFont::SetFontSize(const int size)
227{
228 // If size unchanged, nothing to do
229 if (size == mFontSize) return;
230
231 //Chain up to parent method
232 PFont::SetFontSize(size);
233
234 // Release old cached font
236
237 // Update cache key with new size
238 mCacheKey.size = size;
239
240 // Get new cached font with new size
241 string fontname = Evaluator::gPath.FindFile(mFontFileName);
242 mTTF_Font = FontCache::FontCacheManager::GetInstance().GetFont(mCacheKey, fontname);
243
244 mChanged = true; // Mark font as changed to trigger re-rendering
245}
246
248void PlatformFont::SetFontStyle(const int style)
249{
250 // If style unchanged, nothing to do
251 if (style == mFontStyle) return;
252
253 //Chain up to parent method
254 PFont::SetFontStyle(style);
255
256 // Release old cached font
258
259 // Update cache key with new style
260 mCacheKey.style = style;
261
262 // Get new cached font with new style
263 string fontname = Evaluator::gPath.FindFile(mFontFileName);
264 mTTF_Font = FontCache::FontCacheManager::GetInstance().GetFont(mCacheKey, fontname);
265
266 mChanged = true; // Mark font as changed to trigger re-rendering
267}
268
271{
272 // Check if colors changed and update SDL cache if needed
273 PColor* fgColor = GetFontColorPtr();
274 PColor* bgColor = GetBackgroundColorPtr();
275
276 if(fgColor && fgColor->HasChanged())
277 {
279 return true;
280 }
281 if(bgColor && bgColor->HasChanged())
282 {
284 return true;
285 }
286 return mChanged;
287}
288
291{
292 mChanged = false;
293 PColor* fgColor = GetFontColorPtr();
294 PColor* bgColor = GetBackgroundColorPtr();
295 if(fgColor) fgColor->ClearChanged();
296 if(bgColor) bgColor->ClearChanged();
297}
298
301{
302 PColor* fgColor = GetFontColorPtr();
303 PColor* bgColor = GetBackgroundColorPtr();
304
305 if(fgColor)
306 {
307 mSDL_FGColor = SDLUtility::PColorToSDLColor(*fgColor);
308 }
309 if(bgColor)
310 {
311 mSDL_BGColor = SDLUtility::PColorToSDLColor(*bgColor);
312 }
313 mChanged = true; // Mark font as changed so label re-renders
314}
315
316
317
318SDL_Surface * PlatformFont::RenderText(const std::string & text)
319{
320
321 int maxchars = 1000;
322#if 0
323 cerr << "About to render text [" << text << "] with font " << *this << endl;
324
325 int i = 0;
326 while(i < text.length())
327 {
328
329 cerr << "[" << text[i] << "|" << (unsigned int)(text[i]) << "]";
330 i++;
331 }
332
333 cerr << endl;
334#endif
335
336 //If there is no text, return a null surface.
337 // if(text=="") return NULL; don't do this;render anyway because this causes updates
338 // to blank labels to fail in 2.0
339
340
341
342
343 //Get a temporary pointer that we return
344 std::string toBeRendered = StripText(text);
345 SDL_Surface * tmpSurface = NULL;
346
347 if(toBeRendered.length()>maxchars)
348 {
349 toBeRendered=toBeRendered.substr(0,maxchars);
350 }
351 //The text renderer doesn't like to render empty text.
352 if(toBeRendered.length() == 0) toBeRendered = " ";
353
354 // Auto-detect script and set both script and direction for proper HarfBuzz shaping
355 std::string script = PEBLUtility::DetectScript(toBeRendered);
356 const char* script_cstr = script_to_cstr(script);
357
358 // Set the script for proper shaping (ligatures, contextual forms, etc.)
359 TTF_SetFontScriptName(mTTF_Font, script_cstr);
360
361 // Set direction based on script
362 if (PEBLUtility::IsRTLScript(script)) {
363 TTF_SetFontDirection(mTTF_Font, TTF_DIRECTION_RTL);
364 } else {
365 TTF_SetFontDirection(mTTF_Font, TTF_DIRECTION_LTR);
366 }
367
368 //Using the RenderUTF8 stuff below has a hard time with 'foreign' characters; possibly because
369 //the toberendered needs to be converted to UTF-8????
370
371 //Note: Modern Emscripten (2.0+) DOES support TTF_RenderUTF8 functions
372 if(mAntiAliased)
373 {
374 // If background alpha is < 255, we need transparency support
375 // SDL's Shaded mode doesn't support alpha properly, so use Blended + manual background
376 if(mSDL_BGColor.a < 255)
377 {
378 // Use Blended mode for text (transparent background)
379 SDL_Surface* textSurface = NULL;
380 if(PEBLUtility::is_utf8(toBeRendered))
381 {
382 textSurface = TTF_RenderUTF8_Blended(mTTF_Font, toBeRendered.c_str(), mSDL_FGColor);
383 } else {
384 textSurface = TTF_RenderText_Blended(mTTF_Font, toBeRendered.c_str(), mSDL_FGColor);
385 }
386
387 if(textSurface)
388 {
389 // Create a new RGBA surface for compositing
390 tmpSurface = SDL_CreateRGBSurface(0, textSurface->w, textSurface->h, 32,
391 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
392 if(tmpSurface)
393 {
394 // Fill with background color INCLUDING its alpha value
395 // This encodes transparency directly in the pixel data
396 SDL_FillRect(tmpSurface, NULL, SDL_MapRGBA(tmpSurface->format,
397 mSDL_BGColor.r, mSDL_BGColor.g,
398 mSDL_BGColor.b, mSDL_BGColor.a));
399 // Blit the opaque text on top (will replace background pixels)
400 SDL_BlitSurface(textSurface, NULL, tmpSurface, NULL);
401 }
402 SDL_FreeSurface(textSurface);
403 }
404 }
405 else
406 {
407 // Use Shaded mode for opaque backgrounds (more efficient)
408 if(PEBLUtility::is_utf8(toBeRendered))
409 {
410 tmpSurface = TTF_RenderUTF8_Shaded(mTTF_Font, toBeRendered.c_str(), mSDL_FGColor, mSDL_BGColor);
411 } else {
412 tmpSurface = TTF_RenderText_Shaded(mTTF_Font, toBeRendered.c_str(), mSDL_FGColor, mSDL_BGColor);
413 }
414 }
415
416 }
417 else
418 {
419 // tmpSurface = TTF_RenderText_Blended(mTTF_Font,toBeRendered.c_str(), mSDL_FGColor);
420 if(PEBLUtility::is_utf8(toBeRendered) )
421 {
422 tmpSurface = TTF_RenderUTF8_Blended(mTTF_Font,toBeRendered.c_str(), mSDL_FGColor);
423 }
424 else
425 {
426 tmpSurface = TTF_RenderText_Blended(mTTF_Font, toBeRendered.c_str(), mSDL_FGColor);
427 }
428 }
429
430 //
431 //TTF_RenderText_Blended(
432 //TTF_Font *font, // This is the TTF_Font to use.
433 //char *cstr, // This is the text to render.
434 // SDL_Color &clr, // This is the color to use.
435 // );
436
437
438
439
440 if(tmpSurface)
441 {
442 return tmpSurface;
443 //we need to copy the surface to the texture, render it, and return I think.
444
445 }
446 else
447 {
448 string message = "Unable to render text [" + toBeRendered + "] in PlatformFont::RenderText. Attempting to render '' instead\n";
449 PError::SignalWarning(message);
450
451 //We have a problem, probably because we are trying to render garbage. Let's try to render "" instead, and
452 //signal a waring. If there is still an error, we will signal a fatal error.
453 std::string tmp = "";
454 tmpSurface = TTF_RenderText_Blended(mTTF_Font, tmp.c_str(), mSDL_FGColor);
455
456 if(tmpSurface)
457 {
458 return tmpSurface;
459 }
460 else
461 {
462 string message = "Unable to render text in PlatformFont::RenderText";
464 }
465 }
466 return NULL;
467}
468
469
470//This transforms an escape-filled text string into its displayable version.
471std::string PlatformFont::StripText(const std::string & text)
472{
473 //First, transform text into the to-be-rendered text. I.E., replace
474 //escape characters etc.
475 //This might destroy UTF-formatting and stuff, so we have to be careful.
476
477 std::string toBeRendered;
478
479 for(unsigned int i = 0; i < text.size(); i++)
480 {
481
482
483 if(text[i] == 10 ||
484 text[i] == 13 ||
485 text[i] == 18)
486 {
487 //Do nothing.;
488
489 }
490 else if(text[i] == 9)
491 {
492 //This is a tab character. First, figure out
493 //what absolute position it should be in: round
494 //the length eof toBeRendered up to the next value
495 //i mod 4.
496
497 int x = 8*(((int)(toBeRendered.length())+1) /8 + 1 );
498 int diff = (int)x-(int)(toBeRendered.length());
499 string tmp = " ";
500 for(int j = 0; j < diff; j++)
501 {
502 toBeRendered.push_back(tmp[0]);
503 }
504
505 }
506 else
507 {
508 toBeRendered.push_back(text[i]);
509 }
510
511 }
512
513 return toBeRendered;
514}
515
516
517unsigned int PlatformFont::GetTextWidth(const std::string & text)
518{
519 int height, width;
520 std::string toBeRendered = StripText(text.c_str());
521
522 // Auto-detect script and set direction for accurate width measurement
523 std::string script = PEBLUtility::DetectScript(toBeRendered);
524 const char* script_cstr = script_to_cstr(script);
525
526 TTF_SetFontScriptName(mTTF_Font, script_cstr);
527 if (PEBLUtility::IsRTLScript(script)) {
528 TTF_SetFontDirection(mTTF_Font, TTF_DIRECTION_RTL);
529 } else {
530 TTF_SetFontDirection(mTTF_Font, TTF_DIRECTION_LTR);
531 }
532
533 if(PEBLUtility::is_utf8(toBeRendered))
534 {
535
536 TTF_SizeUTF8(mTTF_Font,toBeRendered.c_str(),&width,&height); // should work on all utf8
537 }
538 else
539 TTF_SizeText(mTTF_Font,toBeRendered.c_str(),&width,&height); //breaks on non-ascii text
540
541 // TTF_SizeUNICODE(mTTF_Font,toBeRendered.c_str(),&width,&height); //requires conversion to Uint16 array.
542
543 unsigned int uwidth = (unsigned int)width;
544 return uwidth;
545}
546
547unsigned int PlatformFont::GetTextHeight(const std::string & toBeRendered)
548{
549 int height, width;
550
551 if(PEBLUtility::is_utf8(toBeRendered))
552 TTF_SizeUTF8(mTTF_Font,toBeRendered.c_str(),&width,&height); // should work on all utf8
553 else
554 TTF_SizeText(mTTF_Font,toBeRendered.c_str(),&width,&height); //breaks on non-ascii text
555
556 unsigned int uheight = (unsigned int)height;
557 return uheight;
558}
559
560
561//This returns the nearest character to the pixel column specified by x
562unsigned int PlatformFont::GetPosition(const std::string & text, unsigned int x)
563{
564
565 //Start at 0 and check until the width of the rendered text is bigger than x
566
567 unsigned int lastcutoff = 0;
568 unsigned int lastwidth = 0;
569
570 unsigned int cutoff = 1;
571
572 std::string::const_iterator start;
573 std::string::const_iterator end;
574
575 start = text.begin();
576 end = start+1;
577
578 while(cutoff < text.size())
579 {
580
581 //the bytes don't align with the glyphs. Be sure we only
582 //permit the cutoff to occur between valid utf8 glyphs.
583 while(!PEBLUtility::is_utf8(text.substr(0,cutoff)))
584 {
585 //end++;
586 cutoff++;
587 }
588 //If the width of the rendered text is larger than the x argument,
589 unsigned int width = GetTextWidth(text.substr(0,cutoff));
590 if(width > x)
591 {
592 //width is greater that the x position. now, round to the
593 //best cutoff.
594 double prop = (x-lastwidth)/(width-lastwidth);
595 if(prop>.5)
596 {
597 return cutoff;
598 }
599 else
600 {
601 return lastcutoff;
602 }
603
604
605 }
606 lastcutoff = cutoff;
607 lastwidth = width;
608
609 cutoff++;
610 //end++;
611 }
612
613 //If we make it this far, we have run out of letters, so return the last character.
614
615 return (unsigned int)(text.size());
616}
617
618
619 std::ostream & PlatformFont::SendToStream(std::ostream& out) const
620{
621 out << "<SDL-Specific Font>" << std::flush;
622 return out;
623}
624
625
626
#define NULL
Definition BinReloc.cpp:317
static PEBLPath gPath
static FontCacheManager & GetInstance()
Get singleton instance.
Definition FontCache.cpp:33
void ReleaseFont(const FontCacheKey &key)
Definition FontCache.cpp:79
TTF_Font * GetFont(const FontCacheKey &key, const std::string &full_path)
Definition FontCache.cpp:42
void ClearChanged()
Definition PColor.h:79
bool HasChanged() const
Change detection for nested property modifications.
Definition PColor.h:78
std::string FindFile(const string &filename)
Definition PEBLPath.cpp:368
Definition PFont.h:53
virtual void SetFontColor(PColor color)
Definition PFont.cpp:303
PColor * GetBackgroundColorPtr() const
Definition PFont.cpp:280
PColor * GetFontColorPtr() const
Definition PFont.cpp:272
int mFontStyle
Definition PFont.h:105
virtual void SetFontStyle(const int style)
Definition PFont.cpp:257
virtual int GetFontStyle() const
Definition PFont.h:84
virtual void SetBackgroundColor(PColor color)
Definition PFont.cpp:315
int mFontSize
Definition PFont.h:106
virtual void SetFontSize(const int size)
Definition PFont.cpp:265
virtual std::string GetFontFileName() const
Definition PFont.h:83
std::string mFontFileName
Definition PFont.h:104
virtual bool GetAntiAliased() const
Definition PFont.h:88
bool mAntiAliased
Definition PFont.h:110
virtual int GetFontSize() const
Definition PFont.h:85
unsigned int GetTextHeight(const std::string &text)
SDL_Surface * RenderText(const std::string &text)
This takes care of all the busy work of rendering the text.
virtual std::ostream & SendToStream(std::ostream &out) const
This sends the font descriptions to the specified stream.
PlatformFont(const std::string &filename)
unsigned int GetTextWidth(const std::string &text)
unsigned int GetPosition(const std::string &text, unsigned int x)
virtual void SetBackgroundColor(PColor color)
Set*Color needs to be overridden because it doesn't change the SDL_Color data.
virtual void SetFontColor(PColor color)
Set*Color needs to be overridden because it doesn't change the SDL_Color data.
void UpdateSDLColors()
Update SDL color cache from PColor objects.
void ClearChanged()
Clear all changed flags.
virtual void SetFontStyle(const int style)
Override SetFontStyle to update TTF font and mark as changed.
virtual ~PlatformFont()
Copy constructor.
bool HasChanged()
Check if font or its colors have changed.
virtual void SetFontSize(const int size)
Override SetFontSize to update TTF font and mark as changed.
bool is_utf8(const std::string str)
std::string DetectScript(const std::string &text)
bool IsRTLScript(const std::string &script)
void SignalWarning(const std::string &message)
Definition PError.cpp:119
void SignalFatalError(const std::string &message)
SDL_Color PColorToSDLColor(PColor pcolor)
This converts between a PColor and an SDL color.
char * getFileBuffer(FILE **file, unsigned int fileSize)
long unsigned int getFileSize(FILE **file)
unsigned long int readFileToMemory(const char path[], char **buffr)