29#include "../../objects/PTextBox.h"
34#include "../../base/PComplexData.h"
35#include "../../devices/PKeyboard.h"
37#include "../../utility/rc_ptrs.h"
38#include "../../utility/PError.h"
39#include "../../utility/PEBLUtility.h"
40#include "../../utility/FormatParser.h"
41#include "../../utility/FontCache.h"
51#include "SDL_scancode.h"
59#include "../../../libs/utfcpp/source/utf8.h"
71static bool has_rtl_text(
const std::string & text) {
72 if (!utf8::is_valid(text.begin(), text.end())) {
76 const unsigned char *bytes = (
const unsigned char *)text.c_str();
79 if ((bytes[0] == 0xD6 && bytes[1] >= 0x90) ||
80 (bytes[0] == 0xD7 && bytes[1] <= 0xBF)) {
85 if (bytes[0] >= 0xD8 && bytes[0] <= 0xDB) {
90 if (bytes[0] < 0x80) bytes += 1;
91 else if ((bytes[0] & 0xE0) == 0xC0) bytes += 2;
92 else if ((bytes[0] & 0xF0) == 0xE0) bytes += 3;
93 else if ((bytes[0] & 0xF8) == 0xF0) bytes += 4;
102static bool is_line_right_justified(
const std::string & line_text,
const Variant & justify_property) {
104 if (has_rtl_text(line_text)) {
108 if (justify_property ==
"RIGHT") {
124 mIsUTF8= utf8::is_valid(text.begin(),text.end());
129 mTextureWidth=mWidth;
130 mTextureHeight=mHeight;
150 PTextBox(text.GetText(), (int)(text.GetWidth()), (int)(text.GetHeight()))
157 mIsUTF8= utf8::is_valid(
mText.begin(),
mText.end());
181 out <<
"<SDL PlatformTextBox: [" <<
mText <<
"] in " << *GetPlatformFont() <<
">" <<flush;
200#if SDL_BYTEORDER == SDL_BIG_ENDIAN
202 Uint32 rmask = 0xff000000;
203 Uint32 gmask = 0x00ff0000;
204 Uint32 bmask = 0x0000ff00;
205 Uint32 amask = 0x00000000;
209 Uint32 rmask = 0x000000ff;
210 Uint32 gmask = 0x0000ff00;
211 Uint32 bmask = 0x00ff0000;
212 Uint32 amask = 0x00000000;
231 mSurface = SDL_CreateRGBSurface(SDL_SWSURFACE,
234 rmask, gmask, bmask, amask);
251 unsigned int linestart = 0;
252 unsigned int linelength = 0;
253 unsigned int totalheight = 0;
255 SDL_Surface * tmpSurface=
NULL;
256 std::vector<int>::iterator i =
mBreaks.begin();
263 bool isFormatted = (formattedVar.
GetInteger() != 0);
266 std::vector<FormatParser::FormatSegment> segments;
278 linelength = *i - linestart;
286 std::string line_text;
288 line_text = mStrippedText.substr(linestart, linelength);
290 line_text =
mText.substr(linestart, linelength);
292 bool isLineRTL = has_rtl_text(line_text);
297 effectiveJustify =
"RIGHT";
305 std::map<std::tuple<int, int, unsigned int>,
PlatformFont*> fontCache;
309 int maxLineHeight = 0;
310 unsigned int segmentTextPos = 0;
311 bool hasHorizontalRule =
false;
313 int totalLineWidth = 0;
325 unsigned int segStart = segmentTextPos;
326 unsigned int segEnd = segmentTextPos + seg.text.length();
328 if (segEnd > linestart && segStart < linestart + linelength) {
330 if (seg.isHorizontalRule) {
331 hasHorizontalRule =
true;
335 if (seg.indentPixels > 0) {
336 lineIndent = seg.indentPixels;
341 lineJustification = seg.justification;
344 unsigned int overlapStart = std::max(segStart, (
unsigned int)linestart);
345 unsigned int overlapEnd = std::min(segEnd, (
unsigned int)(linestart + linelength));
346 std::string segmentText = seg.text.substr(overlapStart - segStart, overlapEnd - overlapStart);
348 if (!segmentText.empty()) {
349 int segStyle = seg.style;
351 int segSize = baseFontSize;
352 if (seg.hasSizeOverride) {
353 segSize = (baseFontSize * seg.sizeOverride) / 100;
355 if (segSize < 1) segSize = 1;
356 if (segSize > 200) segSize = 200;
358 PColor segFgColor = seg.hasColorOverride ? seg.colorOverride : baseFgColor;
361 unsigned int colorKey = segFgColor.
GetRed() * 65536 +
364 auto cacheKey = std::make_tuple(segStyle, segSize, colorKey);
368 auto it = fontCache.find(cacheKey);
369 if (it != fontCache.end()) {
370 renderFont = it->second;
373 fontFileName, segStyle, segSize,
374 segFgColor, baseBgColor, antiAliased);
375 fontCache[cacheKey] = renderFont;
379 int ascent = TTF_FontAscent(renderFont->
GetTTFFont());
380 if (ascent > maxAscent) maxAscent = ascent;
383 int lineHeight = TTF_FontHeight(renderFont->
GetTTFFont());
384 if (lineHeight > maxLineHeight) maxLineHeight = lineHeight;
387 totalLineWidth += renderFont->
GetTextWidth(segmentText);
390 segmentTextPos += seg.text.length();
396 int justifyOffset = 0;
398 justifyOffset = (
mWidth - totalLineWidth) / 2;
400 justifyOffset =
mWidth - totalLineWidth;
403 if (effectiveJustify ==
"CENTER") {
404 justifyOffset = (
mWidth - totalLineWidth) / 2;
405 }
else if (is_line_right_justified(line_text, effectiveJustify)) {
406 justifyOffset =
mWidth - totalLineWidth;
411 int xOffset = justifyOffset;
415 if (hasHorizontalRule) {
418 int lineY =
static_cast<int>(totalheight) + maxAscent / 2;
419 int lineWidth =
mWidth - (lineIndent * 2);
422 SDL_Rect hrRect = {lineIndent, lineY, lineWidth, 2};
432 unsigned int segStart = segmentTextPos;
433 unsigned int segEnd = segmentTextPos + seg.text.length();
435 if (segEnd > linestart && segStart < linestart + linelength) {
436 unsigned int overlapStart = std::max(segStart, (
unsigned int)linestart);
437 unsigned int overlapEnd = std::min(segEnd, (
unsigned int)(linestart + linelength));
439 std::string segmentText = seg.text.substr(
440 overlapStart - segStart,
441 overlapEnd - overlapStart);
443 if (!segmentText.empty()) {
445 if (seg.indentPixels > xOffset) {
446 xOffset = seg.indentPixels;
449 int segStyle = seg.style;
451 int segSize = baseFontSize;
452 if (seg.hasSizeOverride) {
453 segSize = (baseFontSize * seg.sizeOverride) / 100;
455 if (segSize < 1) segSize = 1;
456 if (segSize > 200) segSize = 200;
458 PColor segFgColor = seg.hasColorOverride ? seg.colorOverride : baseFgColor;
461 unsigned int colorKey = segFgColor.
GetRed() * 65536 +
464 auto cacheKey = std::make_tuple(segStyle, segSize, colorKey);
469 tmpSurface = renderFont->
RenderText(segmentText.c_str());
472 int thisAscent = TTF_FontAscent(renderFont->
GetTTFFont());
475 int yOffset = maxAscent - thisAscent;
478 SDL_Rect segRect = {xOffset,
static_cast<int>(totalheight) + yOffset,
479 tmpSurface->w, tmpSurface->h};
482 SDL_FreeSurface(tmpSurface);
484 xOffset += segRect.w;
488 segmentTextPos += seg.text.length();
492 for (
auto& pair : fontCache) {
498 totalheight += maxLineHeight;
501 if(effectiveJustify ==
"RIGHT")
504 tmpSurface = GetPlatformFont()->
RenderText(line_text.c_str());
505 SDL_Rect tmprect = {
mSurface->w - tmpSurface->w,
506 static_cast<int>(totalheight),
507 tmpSurface->w,tmpSurface->h};
510 else if (effectiveJustify ==
"CENTER"){
512 tmpSurface = GetPlatformFont()->
RenderText(line_text.c_str());
513 int xval = (
mSurface->w - tmpSurface->w)/2;
514 SDL_Rect tmprect = {xval,
static_cast<int>(totalheight),
515 tmpSurface->w,tmpSurface->h};
520 tmpSurface = GetPlatformFont()->
RenderText(line_text.c_str());
521 SDL_Rect tmprect = {0,
static_cast<int>(totalheight),tmpSurface->w,tmpSurface->h};
530 std::string tmptext =
mText.substr(linestart,linelength);
538 tmpSurface = GetPlatformFont()->
RenderText(rtext.c_str());
541 SDL_Rect tmprect = {(
mSurface->w - tmpSurface->w),
static_cast<int>(totalheight),
542 tmpSurface->w, tmpSurface->h};
549 SDL_FreeSurface(tmpSurface);
552 totalheight += height;
558 totalheight += height;
571 mTexture = SDL_CreateTexture(
mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
575 SDL_SetTextureScaleMode(
mTexture, SDL_ScaleModeBest);
576 SDL_SetTextureBlendMode(
mTexture, SDL_BLENDMODE_BLEND);
581 mTexture = SDL_CreateTexture(
mRenderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
584 SDL_SetTextureScaleMode(
mTexture, SDL_ScaleModeBest);
585 SDL_SetTextureBlendMode(
mTexture, SDL_BLENDMODE_BLEND);
602 SDL_DestroyTexture(tmp);
638 else if (name ==
"FONT")
642 else if(name==
"WIDTH")
646 else if(name==
"HEIGHT")
657 if(name ==
"NUMTEXTLINES")
676 mStrippedText.clear();
687 mStrippedText.clear();
724 mIsUTF8= utf8::is_valid(text.begin(),text.end());
743 bool isFormatted = (formattedVar.
GetInteger() != 0);
745 if (val && isFormatted) {
748 std::cerr <<
"INFO: Disabling formatted mode for editing." << std::endl;
749 std::cerr <<
" Formatting tags will be visible as text and can be edited directly." << std::endl;
768void PlatformTextBox::FindBreaks()
780 unsigned int linestart = 0;
781 unsigned int newlinestart = 0;
782 unsigned int linelength = 0;
783 unsigned int totalheight = 0;
790 bool isFormatted = (formattedVar.
GetInteger() != 0);
800 FindBreaksFormatted(segments);
805 std::string textForBreaking =
mText;
820 bool isAdaptive = isAdaptiveVar.
GetInteger() != 0;
822 while(newlinestart <
mText.size())
826 linelength = FindNextLineBreak(linestart);
831 newlinestart = linestart+ linelength;
832 totalheight += height;
834 mBreaks.push_back(newlinestart);
835 linestart=newlinestart;
839 if (!isAdaptive && totalheight > (
unsigned int)
mHeight && newlinestart <
mText.size()) {
857 bool allTextProcessed = (newlinestart >=
mText.size());
858 bool lastLineFits =
true;
863 unsigned int lastLineStart = (totalheight > (
unsigned int)height) ? (totalheight - height) : 0;
864 unsigned int lastLineEnd = totalheight;
867 lastLineFits = (lastLineStart < (
unsigned int)
mHeight) && (lastLineEnd <= (
unsigned int)
mHeight);
869 bool textComplete = allTextProcessed && lastLineFits;
876void PlatformTextBox::FindBreaksFormatted(
const std::vector<FormatParser::FormatSegment>& segments)
879 mStrippedText.clear();
881 int baseFontSize = GetPlatformFont()->
GetFontSize();
888 int currentLineWidth = 0;
889 int currentLineMaxHeight = 0;
890 std::string currentLineText;
891 int strippedTextPos = 0;
895 std::map<std::tuple<int, int>,
PlatformFont*> fontCache;
899 bool isAdaptive = isAdaptiveVar.
GetInteger() != 0;
903 int segSize = baseFontSize;
904 if (seg.hasSizeOverride) {
905 segSize = (baseFontSize * seg.sizeOverride) / 100;
906 segSize = std::max(1, std::min(200, segSize));
910 auto cacheKey = std::make_tuple(seg.style, segSize);
912 auto it = fontCache.find(cacheKey);
913 if (it != fontCache.end()) {
914 segFont = it->second;
916 segFont =
new PlatformFont(fontFileName, seg.style, segSize,
917 fgColor, bgColor, antiAliased);
918 fontCache[cacheKey] = segFont;
922 FindBreaksInSegment(seg, segFont, currentLineWidth,
923 currentLineMaxHeight, currentLineText,
924 strippedTextPos, totalHeight);
927 if (!isAdaptive && totalHeight > (
unsigned int)
mHeight && strippedTextPos < (
int)mStrippedText.length()) {
933 if (!currentLineText.empty()) {
934 mBreaks.push_back(strippedTextPos);
935 totalHeight += currentLineMaxHeight;
939 for (
auto& pair : fontCache) {
948 bool allTextProcessed = (strippedTextPos >= (int)mStrippedText.length());
949 bool lastLineFits =
true;
954 int lastLineHeight = currentLineMaxHeight;
955 unsigned int lastLineStart = (totalHeight > lastLineHeight) ? (totalHeight - lastLineHeight) : 0;
956 unsigned int lastLineEnd = totalHeight;
958 lastLineFits = (lastLineStart < (
unsigned int)
mHeight) && (lastLineEnd <= (
unsigned int)
mHeight);
960 bool textComplete = allTextProcessed && lastLineFits;
967void PlatformTextBox::FindBreaksInSegment(
970 int& currentLineWidth,
971 int& currentLineMaxHeight,
972 std::string& currentLineText,
973 int& strippedTextPos,
976 std::string segText = seg.
text;
977 size_t wordStart = 0;
980 int segHeight = TTF_FontHeight(segFont->
GetTTFFont());
981 if (segHeight > currentLineMaxHeight) {
982 currentLineMaxHeight = segHeight;
986 while (wordStart < segText.length()) {
988 size_t wordEnd = segText.find_first_of(
" \n", wordStart);
989 if (wordEnd == std::string::npos) {
990 wordEnd = segText.length();
995 if (wordEnd < segText.length() && segText[wordEnd] ==
'\n' && wordEnd == wordStart) {
998 mStrippedText +=
'\n';
1002 mBreaks.push_back(strippedTextPos);
1005 totalHeight += currentLineMaxHeight;
1008 currentLineWidth = 0;
1009 currentLineMaxHeight = 0;
1010 currentLineText.clear();
1013 wordStart = wordEnd + 1;
1019 bool hasSpace = (wordEnd < segText.length() && segText[wordEnd] ==
' ');
1020 std::string word = segText.substr(wordStart, wordEnd - wordStart + (hasSpace ? 1 : 0));
1022 if (!word.empty()) {
1029 if (currentLineWidth + wordWidth + 10 >=
mWidth && !currentLineText.empty()) {
1032 mBreaks.push_back(strippedTextPos);
1035 totalHeight += currentLineMaxHeight;
1038 currentLineWidth = wordWidth;
1039 currentLineMaxHeight = segHeight;
1040 currentLineText = word;
1043 currentLineWidth += wordWidth;
1044 currentLineText += word;
1047 mStrippedText += word;
1048 strippedTextPos += word.length();
1051 wordStart = wordEnd + (hasSpace ? 1 : 0);
1219int PlatformTextBox::FindNextLineBreak(
unsigned int curposition)
1221 unsigned int sublength = 0;
1222 unsigned int lastsep = 0;
1223 unsigned int sep = 0;
1224 std::string tmpstring;
1228 std::string::iterator start;
1229 std::string::iterator end;
1232 while (curposition + sublength <
mText.size()+1)
1238 tmpstring =
mText.substr(curposition,sublength);
1239 int tmpWidth = GetPlatformFont()->
GetTextWidth(tmpstring);
1246 if(
mText[curposition + sublength] == 10
1247 ||
mText[curposition+sublength]==0
1254 if(tmpWidth >= (
unsigned int)(
mWidth))
1283 while(GetPlatformFont()->GetTextWidth(tmpstring) >(
unsigned int)
mWidth)
1290 start =
mText.begin()+curposition;
1291 end = start+sublength;
1297 end = start+sublength;
1301 cont = !utf8::is_valid(start,end);
1304 tmpstring =
mText.substr(curposition,sublength);
1318 while (curposition + sublength <
mText.size()+1)
1321 if(
mText[curposition + sublength] == 10
1322 ||
mText[curposition+sublength]==0)
1333 start =
mText.begin()+curposition;
1334 end = start+sublength;
1343 end = start+sublength;
1344 cont = !utf8::is_valid(start,end);
1350 end=start+sublength;
1377 if(
mText[curposition + sublength] ==
' '
1378 ||
mText[curposition + sublength] ==
'-'
1379 || curposition + sublength ==
mText.size())
1386 tmpstring =
mText.substr(curposition, sublength);
1388 if(GetPlatformFont()->GetTextWidth(tmpstring) > (
unsigned int)
mWidth)
1421 start =
mText.begin()+curposition;
1422 end = start+sublength;
1429 end = start+sublength;
1431 cont = !utf8::is_valid(start,end);
1458 if(
mText.length()==0)
1470 unsigned long int linenum = y / height;
1486 startchar =
mBreaks[linenum-1];
1496 int length =
mBreaks[linenum] - startchar;
1499 std::string line_text =
mText.substr(startchar, length);
1503 unsigned int x_adjusted = x;
1504 if (is_line_right_justified(line_text,
mJustify)) {
1506 int line_width = GetPlatformFont()->
GetTextWidth(line_text);
1507 int text_start_x =
mWidth - line_width;
1509 if ((
int)x >= text_start_x) {
1515 if (has_rtl_text(line_text)) {
1516 x_adjusted = line_width - ((int)x - text_start_x);
1519 x_adjusted = (int)x - text_start_x;
1525 x_adjusted = has_rtl_text(line_text) ? line_width : 0;
1529 int charnum = GetPlatformFont()->
GetPosition(line_text, x_adjusted);
1537 if(charnum + startchar > 0)
1542 return charnum + startchar;
1547void PlatformTextBox::DrawCursor()
1554 unsigned int width = (
unsigned int)
GetWidth();
1563 if (has_rtl_text(
mText)) {
1586 std::string full_line =
mText.substr(linestart, line_end - linestart);
1588 if (is_line_right_justified(full_line,
mJustify)) {
1592 int line_width = GetPlatformFont()->
GetTextWidth(full_line);
1593 int text_before_cursor_width = GetPlatformFont()->
GetTextWidth(subst);
1594 int text_start_x = width - line_width;
1596 if (has_rtl_text(full_line)) {
1598 x = text_start_x + (line_width - text_before_cursor_width);
1601 x = text_start_x + text_before_cursor_width;
1603 x = ( x >= width ) ? width-1: x;
1607 x = ( x >= width ) ? width-1: x;
1642 (GetPlatformFont()->GetFontColor()));
1654 if(GetPlatformFont()->HasChanged())
1674 bool hadAdaptiveFont = (mAdaptiveFontObject.
get() !=
NULL);
1678 int requestedSize = requestedSizeVar.
GetInteger();
1681 if (requestedSize == 0) {
1688 int maxIterations = 20;
1690 bool needsAdaptation =
true;
1692 while (iteration < maxIterations && needsAdaptation) {
1698 bool textComplete = textCompleteVar.
GetInteger() != 0;
1702 needsAdaptation =
false;
1707 int minFontSize = 8;
1708 int currentFontSize = GetPlatformFont()->
GetFontSize();
1709 int targetSize = currentFontSize;
1714 int reduction = std::max(currentFontSize / 10, 1);
1715 targetSize = std::max(currentFontSize - reduction, minFontSize);
1719 if (targetSize >= currentFontSize) {
1720 targetSize = std::max(currentFontSize - 1, minFontSize);
1724 if (targetSize != currentFontSize && targetSize >= minFontSize) {
1732 if (mAdaptiveFontObject.
get()) {
1733 mIntermediateFonts.push_back(mAdaptiveFontObject);
1738 fontColor, bgColor, antiAliased);
1742 mAdaptiveFontObject = newFontPtr;
1747 needsAdaptation =
false;
1754 if (mAdaptiveFontObject.
get()) {
1794 cerr <<
"handling keypress in PlatformTextBox: " << keycode << endl;
1825 std::string text_before_cursor =
mText.substr(linestart,
mCursorPos - linestart);
1829 std::string full_line =
mText.substr(linestart, line_end - linestart);
1831 if (is_line_right_justified(full_line,
mJustify)) {
1837 int text_before_cursor_width = GetPlatformFont()->
GetTextWidth(text_before_cursor);
1838 x =
mWidth - text_before_cursor_width;
1841 x = GetPlatformFont()->
GetTextWidth(text_before_cursor);
1859 std::string lb = std::string() + (char(10));
1882 bool isRTLLine =
false;
1892 std::string current_line =
mText.substr(linestart, line_end - linestart);
1893 isRTLLine = has_rtl_text(current_line);
1957 std::vector<int>::iterator i =
mBreaks.begin();
counted_ptr< PEBLObjectBase > GetObject() const
virtual bool SetProperty(std::string name, Variant v)
Variant GetProperty(std::string) const
virtual PColor GetBackgroundColor() const
virtual int GetFontStyle() const
virtual PColor GetFontColor() const
virtual std::string GetFontFileName() const
virtual bool GetAntiAliased() const
virtual int GetFontSize() const
void PushBack(const Variant &v)
This class is the basic generic text box.
virtual void HandleTextInput(std::string text)
virtual long unsigned int IncrementCursor()
virtual void InsertText(const std::string character)
virtual void SetWidth(int w)
virtual long unsigned int DecrementCursor()
virtual bool AtPrintableCharacter(unsigned long int x)
virtual bool SetProperty(std::string, Variant v)
unsigned long int mCursorPos
virtual void SetEditable(bool val)
virtual void SetHeight(int h)
virtual Variant GetProperty(std::string) const
virtual void DeleteText(int length)
This class simply represent an abstract text-based object.
virtual void SetText(const std::string &text)
Validator platform textbox - no rendering, used only for compilation.
virtual void SetHeight(int h)
virtual counted_ptr< PEBLObjectBase > GetFont() const
virtual int FindCursorPosition(long int x, long int y)
virtual void SetFont(counted_ptr< PEBLObjectBase > font)
virtual void HandleKeyPress(int keycode, int modkeys, Uint16 unicode)
virtual void SetText(std::string text)
virtual void HandleTextInput(std::string input)
PlatformTextBox(std::string text, counted_ptr< PEBLObjectBase > font, int width, int height)
virtual void SetEditable(bool val)
virtual bool Draw()
This method initiates everything needed to display the main window
virtual Variant GetProperty(std::string name) const
virtual ~PlatformTextBox()
Standard Destructor.
virtual bool RenderText()
std::vector< int > mBreaks
virtual bool SetProperty(std::string, Variant v)
virtual std::ostream & SendToStream(std::ostream &out) const
An inheritable printing class used by PEBLObjectBase::operator<<.
virtual void SetWidth(int w)
PComplexData * GetComplexData() const
Variant SetFont(Variant v)
void strrev_utf8(char *p)
void SignalFatalError(const std::string &message)
void DrawLine(SDL_Renderer *renderer, PlatformWidget *pwidget, int x1, int y1, int x2, int y2, PColor color)
This sets a pixel to be a certain color.