diff --git a/client/BattleInterface/CBattleInterface.cpp b/client/BattleInterface/CBattleInterface.cpp index 73abfcc8c..825a58f60 100644 --- a/client/BattleInterface/CBattleInterface.cpp +++ b/client/BattleInterface/CBattleInterface.cpp @@ -2271,14 +2271,9 @@ void CBattleInterface::showAliveStack(const CStack *stack, SDL_Surface * to) SDL_Rect temp_rect = genRect(amountNormal->h, amountNormal->w, creAnims[ID]->pos.x + xAdd, creAnims[ID]->pos.y + yAdd); SDL_BlitSurface(amountBG, NULL, to, &temp_rect); //blitting amount - CSDL_Ext::printAtMiddle( - makeNumberShort(stack->count), - creAnims[ID]->pos.x + xAdd + 15, - creAnims[ID]->pos.y + yAdd + 5, - FONT_TINY, - Colors::WHITE, - to - ); + + Point textPos(creAnims[ID]->pos.x + xAdd + 15, creAnims[ID]->pos.y + yAdd + 5); + graphics->fonts[FONT_TINY]->renderTextCenter(to, makeNumberShort(stack->count), Colors::WHITE, textPos); } } diff --git a/client/BattleInterface/CBattleInterfaceClasses.cpp b/client/BattleInterface/CBattleInterfaceClasses.cpp index 7b7281045..e031197c9 100644 --- a/client/BattleInterface/CBattleInterfaceClasses.cpp +++ b/client/BattleInterface/CBattleInterfaceClasses.cpp @@ -23,6 +23,7 @@ #include "../../lib/CTownHandler.h" #include "../CBitmapHandler.h" #include "../CCreatureWindow.h" +#include "../CMessage.h" CBattleConsole::~CBattleConsole() { @@ -31,24 +32,27 @@ CBattleConsole::~CBattleConsole() void CBattleConsole::showAll(SDL_Surface * to) { + Point textPos(pos.x + pos.w/2, pos.y + 11); + if(ingcAlter.size()) { - CSDL_Ext::printAtMiddleWB(ingcAlter, pos.x + pos.w/2, pos.y + 11, FONT_SMALL, 80, Colors::WHITE, to); + graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(ingcAlter, pos.w, FONT_SMALL), Colors::WHITE, textPos); } else if(alterTxt.size()) { - CSDL_Ext::printAtMiddleWB(alterTxt, pos.x + pos.w/2, pos.y + 11, FONT_SMALL, 80, Colors::WHITE, to); + graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(alterTxt, pos.w, FONT_SMALL), Colors::WHITE, textPos); } else if(texts.size()) { if(texts.size()==1) { - CSDL_Ext::printAtMiddleWB(texts[0], pos.x + pos.w/2, pos.y + 11, FONT_SMALL, 80, Colors::WHITE, to); + graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[0], pos.w, FONT_SMALL), Colors::WHITE, textPos); } else { - CSDL_Ext::printAtMiddleWB(texts[lastShown-1], pos.x + pos.w/2, pos.y + 11, FONT_SMALL, 80, Colors::WHITE, to); - CSDL_Ext::printAtMiddleWB(texts[lastShown], pos.x + pos.w/2, pos.y + 27, FONT_SMALL, 80, Colors::WHITE, to); + graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown - 1], pos.w, FONT_SMALL), Colors::WHITE, textPos); + textPos.y += 16; + graphics->fonts[FONT_SMALL]->renderTextLinesCenter(to, CMessage::breakText(texts[lastShown], pos.w, FONT_SMALL), Colors::WHITE, textPos); } } } diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index 25bfe9004..b84c03937 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -335,20 +335,19 @@ CResDataBar::~CResDataBar() void CResDataBar::draw(SDL_Surface * to) { blitAt(bg,pos.x,pos.y,to); - char * buf = new char[15]; for (int i=0;i<7;i++) { - SDL_itoa(LOCPLINT->cb->getResourceAmount(i),buf,10); - printAt(buf,txtpos[i].first,txtpos[i].second,FONT_SMALL,Colors::WHITE,to); + std::string text = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(i)); + + graphics->fonts[FONT_SMALL]->renderTextLeft(to, text, Colors::WHITE, Point(txtpos[i].first,txtpos[i].second)); } std::vector temp; - SDL_itoa(LOCPLINT->cb->getDate(3),buf,10); temp+=std::string(buf); - SDL_itoa(LOCPLINT->cb->getDate(2),buf,10); temp+=std::string(buf); - SDL_itoa(LOCPLINT->cb->getDate(1),buf,10); temp+=std::string(buf); - printAt(processStr(datetext,temp),txtpos[7].first,txtpos[7].second,FONT_SMALL,Colors::WHITE,to); - temp.clear(); - //updateRect(&pos,screen); - delete[] buf; + + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(3))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(2))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(1))); + + graphics->fonts[FONT_SMALL]->renderTextLeft(to, processStr(datetext,temp), Colors::WHITE, Point(txtpos[7].first,txtpos[7].second)); } void CResDataBar::show(SDL_Surface * to) diff --git a/client/CAnimation.cpp b/client/CAnimation.cpp index 3331eedce..dc142d736 100644 --- a/client/CAnimation.cpp +++ b/client/CAnimation.cpp @@ -1390,7 +1390,7 @@ void CShowableAnim::rotate(bool on, bool vertical) } CCreatureAnim::CCreatureAnim(int x, int y, std::string name, Rect picPos, ui8 flags, EAnimType type): - CShowableAnim(x,y,name,flags,3,type) + CShowableAnim(x,y,name,flags,4,type) { xOffset = picPos.x; yOffset = picPos.y; diff --git a/client/CCreatureWindow.cpp b/client/CCreatureWindow.cpp index 97a9e06d3..cefcce890 100644 --- a/client/CCreatureWindow.cpp +++ b/client/CCreatureWindow.cpp @@ -562,8 +562,9 @@ void CCreatureWindow::showAll(SDL_Surface * to) void CCreatureWindow::show(SDL_Surface * to) { - if (count.size()) //army stack - printTo(count, pos.x + 114, pos.y + 174,FONT_TIMES, Colors::WHITE, to); + CIntObject::show(to); + if (!count.empty()) //army stack + graphics->fonts[FONT_TIMES]->renderTextRight(to, count, Colors::WHITE, Point(pos.x + 114, pos.y + 174)); } @@ -710,8 +711,8 @@ void CBonusItem::showAll (SDL_Surface * to) { if (visible) { - printAt(name, pos.x + 72, pos.y + 6, FONT_SMALL, Colors::YELLOW, to); - printAt(description, pos.x + 72, pos.y + 30, FONT_SMALL, Colors::WHITE, to); + graphics->fonts[FONT_SMALL]->renderTextLeft(to, name, Colors::YELLOW, Point(pos.x + 72, pos.y + 6)); + graphics->fonts[FONT_SMALL]->renderTextLeft(to, name, Colors::WHITE, Point(pos.x + 72, pos.y + 30)); if (bonusGraphics && bonusGraphics->bg) blitAtLoc(bonusGraphics->bg, 12, 2, to); } diff --git a/client/CHeroWindow.cpp b/client/CHeroWindow.cpp index 8a327cf87..ec6ed4df3 100644 --- a/client/CHeroWindow.cpp +++ b/client/CHeroWindow.cpp @@ -339,28 +339,9 @@ void CHeroWindow::showAll(SDL_Surface * to) printAtMiddleLoc(CGI->generaltexth->jktexts[4], 262, 99, FONT_SMALL, Colors::YELLOW, to); //dismiss / quest log - std::vector toPrin = CMessage::breakText(CGI->generaltexth->jktexts[8]); - if(toPrin.size()==1) - { - printAtLoc(toPrin[0], 372, 439, FONT_SMALL, Colors::WHITE, to); - } - else - { - printAtLoc(toPrin[0], 372, 430, FONT_SMALL, Colors::WHITE, to); - printAtLoc(toPrin[1], 372, 446, FONT_SMALL, Colors::WHITE, to); - } - - toPrin = CMessage::breakText(CGI->generaltexth->jktexts[9]); - if(toPrin.size()==1) - { - printAtLoc(toPrin[0], 512, 439, FONT_SMALL, Colors::WHITE, to); - } - else - { - printAtLoc(toPrin[0], 512, 430, FONT_SMALL, Colors::WHITE, to); - printAtLoc(toPrin[1], 512, 446, FONT_SMALL, Colors::WHITE, to); - } - + printAtMiddleWBLoc(CGI->generaltexth->jktexts[8], 388, 455, FONT_SMALL, 50, Colors::WHITE, to); + printAtMiddleWBLoc(CGI->generaltexth->jktexts[9], 534, 455, FONT_SMALL, 50, Colors::WHITE, to); + //printing primary skills' amounts for(int m=0; m<4; ++m) { diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 68ffb11da..e2d5a2421 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -14,6 +14,7 @@ set(client_SRCS UIFramework/CGuiHandler.cpp UIFramework/CIntObject.cpp UIFramework/CIntObjectClasses.cpp + UIFramework/Fonts.cpp UIFramework/Geometries.cpp UIFramework/CCursorHandler.cpp UIFramework/SDL_Extensions.cpp diff --git a/client/CMessage.cpp b/client/CMessage.cpp index 9c852d000..e74b3cec1 100644 --- a/client/CMessage.cpp +++ b/client/CMessage.cpp @@ -58,8 +58,8 @@ struct ComponentsToBlit std::vector< std::vector > comps; int w, h; - void blitCompsOnSur(SDL_Surface * _or, int inter, int &curh, SDL_Surface *ret); - ComponentsToBlit(std::vector & SComps, int maxw, SDL_Surface* _or); //c-tor + void blitCompsOnSur(bool blitOr, int inter, int &curh, SDL_Surface *ret); + ComponentsToBlit(std::vector & SComps, int maxw, bool blitOr); //c-tor ~ComponentsToBlit(); //d-tor }; @@ -131,9 +131,7 @@ SDL_Surface * CMessage::drawDialogBox(int w, int h, int playerColor) return ret; } -/* The map file contains long texts, with or without line breaks. This - * method takes such a text and breaks it into into several lines. */ -std::vector CMessage::breakText( std::string text, size_t maxLineSize/*=30*/, const boost::function &charMetric /*= 0*/, bool allowLeadingWhitespace /*= false*/ ) +std::vector CMessage::breakText( std::string text, size_t maxLineSize, EFonts font ) { std::vector ret; @@ -153,11 +151,8 @@ std::vector CMessage::breakText( std::string text, size_t maxLineSi opened=true; else if (text[z]=='}') opened=false; - else if(charMetric) - lineLength += charMetric(text[z]); else - lineLength++; - + lineLength += graphics->fonts[font]->getSymbolWidth(text[z]); z++; } @@ -176,8 +171,8 @@ std::vector CMessage::breakText( std::string text, size_t maxLineSi if (pos > 0) z = pos+1; } - - if(z) //non-blank line + + if(z) //non-blank line { ret.push_back(text.substr(0, z)); @@ -187,7 +182,7 @@ std::vector CMessage::breakText( std::string text, size_t maxLineSi text.erase(0, z); } - else if(text[z] == 0x0a) //blank line + else if(text[z] == 0x0a) //blank line { ret.push_back(""); //add empty string, no extra actions needed } @@ -204,8 +199,9 @@ std::vector CMessage::breakText( std::string text, size_t maxLineSi lineManuallyBroken = true; } - if(!allowLeadingWhitespace || !lineManuallyBroken) - boost::algorithm::trim_left_if(text,boost::algorithm::is_any_of(std::string(" "))); + //if(!allowLeadingWhitespace || !lineManuallyBroken) + if(!lineManuallyBroken) + boost::algorithm::trim_left_if(text,boost::algorithm::is_any_of(std::string(" "))); if (opened) { @@ -216,164 +212,18 @@ std::vector CMessage::breakText( std::string text, size_t maxLineSi } /* Trim whitespaces of every line. */ - if(!allowLeadingWhitespace) + //if(!allowLeadingWhitespace) for (size_t i=0; i CMessage::breakText( std::string text, size_t maxLineWidth, EFonts font ) -{ - return breakText(text, maxLineWidth, boost::bind(&Font::getCharWidth, graphics->fonts[font], _1), true); -} - -std::pair CMessage::getMaxSizes(std::vector > * txtg, int fontHeight) -{ - std::pair ret; - ret.first = -1; - ret.second=0; - for (size_t i=0; isize();i++) //we are searching widest line and total height - { - int lw=0; - for (size_t j=0;j<(*txtg)[i].size();j++) - { - lw+=(*txtg)[i][j]->w; - ret.second+=(*txtg)[i][j]->h; - } - if(!(*txtg)[i].size()) - ret.second+=fontHeight; - if (ret.first > * txtg, int fontHeight, int & curh, SDL_Surface * ret, int xCenterPos) -{ - for (size_t i=0; isize(); i++, curh += fontHeight) - { - int lw=0; //line width - for (size_t j=0;j<(*txtg)[i].size();j++) - lw+=(*txtg)[i][j]->w; - - int pw = (xCenterPos < 0) ? ret->w/2 : xCenterPos; - pw -= lw/2; //x coord for the start of the text - - int tw = pw; - for (size_t j=0;j<(*txtg)[i].size();j++) //blit text - { - SDL_Surface *surf = (*txtg)[i][j]; - blitAt(surf, tw, curh, ret); - tw+=surf->w; - SDL_FreeSurface(surf); - (*txtg)[i][j] = NULL; - } - } - - return ret; -} - -SDL_Surface * FNT_RenderText (EFonts font, std::string text, SDL_Color kolor= Colors::WHITE) -{ - if (graphics->fontsTrueType[font]) - return TTF_RenderText_Blended(graphics->fontsTrueType[font], text.c_str(), kolor); - const Font *f = graphics->fonts[font]; - int w = f->getWidth(text.c_str()), - h = f->height; - SDL_Surface * ret = CSDL_Ext::newSurface(w, h, screen); - CSDL_Ext::fillRect (ret, NULL, SDL_MapRGB(ret->format,128,128,128));//if use default black - no shadowing - SDL_SetColorKey(ret,SDL_SRCCOLORKEY,SDL_MapRGB(ret->format,128,128,128)); - CSDL_Ext::printAt(text.c_str(), 0, 0, font, kolor, ret); - return ret; -} - -std::vector > * CMessage::drawText(std::vector * brtext, int &fontHeigh, EFonts font) -{ - std::vector > * txtg = new std::vector >(); - txtg->resize(brtext->size()); - if (graphics->fontsTrueType[font]) - fontHeigh = TTF_FontHeight(graphics->fontsTrueType[font]); - else - fontHeigh = graphics->fonts[font]->height; - - for (size_t i=0; isize();i++) //foreach line - { - while((*brtext)[i].length()) //if something left - { - size_t z; - - /* Handle normal text. */ - z = 0; - while(z < (*brtext)[i].length() && (*brtext)[i][z] != ('{')) - z++; - - if (z) - (*txtg)[i].push_back(FNT_RenderText(font, (*brtext)[i].substr(0,z), Colors::WHITE)); - (*brtext)[i].erase(0,z); - - if ((*brtext)[i].length() && (*brtext)[i][0] == '{') - /* Remove '{' */ - (*brtext)[i].erase(0,1); - - if ((*brtext)[i].length()==0) - /* End of line */ - continue; - - /* This text will be highlighted. */ - z = 0; - while(z < (*brtext)[i].length() && (*brtext)[i][z] != ('}')) - z++; - - if (z) - (*txtg)[i].push_back(FNT_RenderText(font, (*brtext)[i].substr(0,z), Colors::YELLOW)); - (*brtext)[i].erase(0,z); - - if ((*brtext)[i].length() && (*brtext)[i][0] == '}') - /* Remove '}' */ - (*brtext)[i].erase(0,1); - - } //ends while((*brtext)[i].length()) - } //ends for(int i=0; isize();i++) - return txtg; -} - -SDL_Surface * CMessage::drawBoxTextBitmapSub( int player, std::string text, SDL_Surface* bitmap, std::string sub, int charperline/*=30*/, int imgToBmp/*=55*/ ) -{ - int curh; - int fontHeight; - std::vector tekst = breakText(text,charperline); - std::vector > * txtg = drawText(&tekst, fontHeight); - std::pair txts = getMaxSizes(txtg, fontHeight), boxs; - boxs.first = std::max(txts.first,bitmap->w) // text/bitmap max width - + 50; //side margins - boxs.second = - (curh=45) //top margin - + txts.second //text total height - + imgToBmp //text <=> img - + bitmap->h - + 5 // to sibtitle - + (*txtg)[0][0]->h - + 30; - SDL_Surface *ret = drawDialogBox(boxs.first,boxs.second,player); - blitTextOnSur(txtg,fontHeight,curh,ret); - curh += imgToBmp; - blitAt(bitmap,(ret->w/2)-(bitmap->w/2),curh,ret); - curh += bitmap->h + 5; - CSDL_Ext::printAtMiddle(sub,ret->w/2,curh+10,FONT_SMALL,Colors::WHITE,ret); - delete txtg; - return ret; -} - void CMessage::drawIWindow(CInfoWindow * ret, std::string text, int player) { - SDL_Surface * _or = NULL; - + bool blitOr = false; if(dynamic_cast(ret)) //it's selection window, so we'll blit "or" between components - _or = FNT_RenderText(FONT_MEDIUM,CGI->generaltexth->allTexts[4],Colors::WHITE); + blitOr = true; const int sizes[][2] = {{400, 125}, {500, 150}, {600, 200}, {480, 400}}; @@ -392,7 +242,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, int player) std::pair winSize(ret->text->pos.w, ret->text->pos.h); //start with text size - ComponentsToBlit comps(ret->components,500,_or); + ComponentsToBlit comps(ret->components,500, blitOr); if (ret->components.size()) winSize.second += 10 + comps.h; //space to first component @@ -436,7 +286,7 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, int player) if (ret->components.size()) { curh += BEFORE_COMPONENTS; - comps.blitCompsOnSur (_or, BETWEEN_COMPS, curh, ret->bitmap); + comps.blitCompsOnSur (blitOr, BETWEEN_COMPS, curh, ret->bitmap); } if(ret->buttons.size()) { @@ -452,9 +302,6 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, int player) } for(size_t i=0; icomponents.size(); i++) ret->components[i]->moveBy(Point(ret->pos.x, ret->pos.y)); - - if(_or) - SDL_FreeSurface(_or); } void CMessage::drawBorder(int playerColor, SDL_Surface * ret, int w, int h, int x, int y) @@ -562,8 +409,10 @@ ComponentsToBlit::~ComponentsToBlit() } -ComponentsToBlit::ComponentsToBlit(std::vector & SComps, int maxw, SDL_Surface* _or) +ComponentsToBlit::ComponentsToBlit(std::vector & SComps, int maxw, bool blitOr) { + int orWidth = graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4]); + w = h = 0; if(SComps.empty()) return; @@ -576,7 +425,7 @@ ComponentsToBlit::ComponentsToBlit(std::vector & SComps, int maxw, { ComponentResolved *cur = new ComponentResolved(SComps[i]); - int toadd = (cur->pos.w + BETWEEN_COMPS + (_or ? _or->w : 0)); + int toadd = (cur->pos.w + BETWEEN_COMPS + (blitOr ? orWidth : 0)); if (curw + toadd > maxw) { curr++; @@ -603,8 +452,10 @@ ComponentsToBlit::ComponentsToBlit(std::vector & SComps, int maxw, } } -void ComponentsToBlit::blitCompsOnSur( SDL_Surface * _or, int inter, int &curh, SDL_Surface *ret ) +void ComponentsToBlit::blitCompsOnSur( bool blitOr, int inter, int &curh, SDL_Surface *ret ) { + int orWidth = graphics->fonts[FONT_MEDIUM]->getStringWidth(CGI->generaltexth->allTexts[4]); + for (size_t i=0;iw) * ((comps)[i].size() - 1); + if(blitOr) + totalw += (inter*2+orWidth) * ((comps)[i].size() - 1); else totalw += (inter) * ((comps)[i].size() - 1); @@ -636,11 +487,14 @@ void ComponentsToBlit::blitCompsOnSur( SDL_Surface * _or, int inter, int &curh, //if there is subsequent component blit "or" if(j<((comps)[i].size()-1)) { - if(_or) + if(blitOr) { curw+=inter; - blitAt(_or,curw,middleh-(_or->h/2),ret); - curw+=_or->w; + + graphics->fonts[FONT_MEDIUM]->renderTextLeft(ret, CGI->generaltexth->allTexts[4], Colors::WHITE, + Point(curw,middleh-(graphics->fonts[FONT_MEDIUM]->getLineHeight()/2))); + + curw+=orWidth; } curw+=inter; } diff --git a/client/CMessage.h b/client/CMessage.h index 680b6b69b..a9d528d67 100644 --- a/client/CMessage.h +++ b/client/CMessage.h @@ -1,6 +1,6 @@ #pragma once -#include "FontBase.h" +#include "Graphics.h" #include "UIFramework/Geometries.h" @@ -28,7 +28,6 @@ class CMessage public: //Function usd only in CMessage.cpp static std::pair getMaxSizes(std::vector > * txtg, int fontHeight); - static std::vector > * drawText(std::vector * brtext, int &fontHeigh, EFonts font = FONT_MEDIUM); static SDL_Surface * blitTextOnSur(std::vector > * txtg, int fontHeight, int & curh, SDL_Surface * ret, int xCenterPos=-1); //xPos==-1 works as if ret->w/2 /// Draw border on exiting surface @@ -37,14 +36,9 @@ public: /// Draw simple dialog box (borders and background only) static SDL_Surface * drawDialogBox(int w, int h, int playerColor=1); - /// Draw simple dialog box and blit bitmap with text on it - static SDL_Surface * drawBoxTextBitmapSub(int player, std::string text, SDL_Surface* bitmap, std::string sub, int charPerline=30, int imgToBmp=55); - - static void drawIWindow(CInfoWindow * ret, std::string text, int player); /// split text in lines - static std::vector breakText(std::string text, size_t maxLineSize=30, const boost::function &charMetric = boost::function(), bool allowLeadingWhitespace = false); static std::vector breakText(std::string text, size_t maxLineWidth, EFonts font); /// constructor diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index a791fcfa1..929c8c3a3 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -1369,7 +1369,6 @@ void SelectionTab::printMaps(SDL_Surface *to) SDL_Color itemColor; -#define POS(xx, yy) pos.x + xx, pos.y + yy + line*25 for (int line = 0; line < positions && elemIdx < curItems.size(); elemIdx++, line++) { CMapInfo *currentItem = curItems[elemIdx]; @@ -1384,7 +1383,7 @@ void SelectionTab::printMaps(SDL_Surface *to) //amount of players std::ostringstream ostr(std::ostringstream::out); ostr << currentItem->playerAmnt << "/" << currentItem->humanPlayers; - CSDL_Ext::printAt(ostr.str(), POS(29, 120), FONT_SMALL, itemColor, to); + printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to); //map size std::string temp2 = "C"; @@ -1403,7 +1402,7 @@ void SelectionTab::printMaps(SDL_Surface *to) temp2="XL"; break; } - CSDL_Ext::printAtMiddle(temp2, POS(70, 128), FONT_SMALL, itemColor, to); + printAtMiddleLoc(temp2, 70, 128 + line * 25, FONT_SMALL, itemColor, to); int temp=-1; switch (currentItem->mapHeader->version) @@ -1425,28 +1424,28 @@ void SelectionTab::printMaps(SDL_Surface *to) tlog2 << "Warning: " << currentItem->fileURI << " has wrong version!\n"; continue; } - blitAt(format->ourImages[temp].bitmap, POS(88, 117), to); + blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to); //victory conditions if (currentItem->mapHeader->victoryCondition.condition == EVictoryConditionType::WINSTANDARD) temp = 11; else temp = currentItem->mapHeader->victoryCondition.condition; - blitAt(CGP->victory->ourImages[temp].bitmap, POS(306, 117), to); + blitAtLoc(CGP->victory->ourImages[temp].bitmap, 306, 117 + line * 25, to); //loss conditions if (currentItem->mapHeader->lossCondition.typeOfLossCon == ELossConditionType::LOSSSTANDARD) temp=3; else temp=currentItem->mapHeader->lossCondition.typeOfLossCon; - blitAt(CGP->loss->ourImages[temp].bitmap, POS(339, 117), to); + blitAtLoc(CGP->loss->ourImages[temp].bitmap, 339, 117 + line * 25, to); } else //if campaign { //number of maps in campaign std::ostringstream ostr(std::ostringstream::out); ostr << CGI->generaltexth->campaignRegionNames[ currentItem->campaignHeader->mapVersion ].size(); - CSDL_Ext::printAt(ostr.str(), POS(29, 120), FONT_SMALL, itemColor, to); + printAtLoc(ostr.str(), 29, 120 + line * 25, FONT_SMALL, itemColor, to); } std::string name; @@ -1467,10 +1466,8 @@ void SelectionTab::printMaps(SDL_Surface *to) } //print name - CSDL_Ext::printAtMiddle(CSDL_Ext::trimToFit(name, 185, FONT_SMALL), POS(213, 128), FONT_SMALL, itemColor, to); - + printAtMiddleLoc(name, 213, 128 + line * 25, FONT_SMALL, itemColor, to); } -#undef POS } void SelectionTab::showAll(SDL_Surface * to) @@ -1490,14 +1487,14 @@ void SelectionTab::showAll(SDL_Surface * to) title = CGI->generaltexth->arraytxt[231]; break; case CMenuScreen::campaignList: - title = "Select a Campaign"; //TODO: find where is the title + title = CGI->generaltexth->arraytxt[726]; break; } - CSDL_Ext::printAtMiddle(title, pos.x+205, pos.y+28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play + printAtMiddleLoc(title, 205, 28, FONT_MEDIUM, Colors::YELLOW, to); //Select a Scenario to Play if(tabType != CMenuScreen::campaignList) { - CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[510], pos.x+87, pos.y+62, FONT_SMALL, Colors::YELLOW, to); //Map sizes + printAtMiddleLoc(CGI->generaltexth->allTexts[510], 87, 62, FONT_SMALL, Colors::YELLOW, to); //Map sizes } } @@ -1871,13 +1868,12 @@ CChatBox::CChatBox(const Rect &rect) addUsedEvents(KEYBOARD); captureAllKeys = true; - const int height = graphics->fonts[FONT_SMALL]->height; + const int height = graphics->fonts[FONT_SMALL]->getLineHeight(); inputBox = new CTextInput(Rect(0, rect.h - height, rect.w, height)); inputBox->removeUsedEvents(KEYBOARD); chatHistory = new CTextBox("", Rect(0, 0, rect.w, rect.h - height), 1); - SDL_Color green = {0,252,0, SDL_ALPHA_OPAQUE}; - chatHistory->color = green; + chatHistory->color = Colors::GREEN; } void CChatBox::keyPressed(const SDL_KeyboardEvent & key) @@ -1989,7 +1985,7 @@ void InfoCard::showAll(SDL_Surface * to) { if(i->second.playerID != PlayerSettings::PLAYER_AI) { - printAtLoc(i->second.name, 24, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->height, FONT_SMALL, Colors::WHITE, to); + printAtLoc(i->second.name, 24, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to); playerNames.erase(i->second.playerID); } } @@ -1997,7 +1993,7 @@ void InfoCard::showAll(SDL_Surface * to) playerSoFar = 0; for (auto i = playerNames.cbegin(); i != playerNames.cend(); i++) { - printAtLoc(i->second, 193, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->height, FONT_SMALL, Colors::WHITE, to); + printAtLoc(i->second, 193, 285 + playerSoFar++ * graphics->fonts[FONT_SMALL]->getLineHeight(), FONT_SMALL, Colors::WHITE, to); } } @@ -2064,8 +2060,8 @@ void InfoCard::showAll(SDL_Surface * to) printToLoc((static_cast(SEL->current))->date,308,34, FONT_SMALL, Colors::WHITE, to); //print flags - int fx = 34 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[390].c_str()); - int ex = 200 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[391].c_str()); + int fx = 34 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]); + int ex = 200 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]); int myT; @@ -2117,7 +2113,7 @@ void InfoCard::showAll(SDL_Surface * to) //name if (name.length()) - printAtLoc(CSDL_Ext::trimToFit(name, 300, FONT_BIG), 26, 39, FONT_BIG, Colors::YELLOW, to); + printAtLoc(name, 26, 39, FONT_BIG, Colors::YELLOW, to); else printAtLoc("Unnamed", 26, 39, FONT_BIG, Colors::YELLOW, to); } @@ -2150,14 +2146,16 @@ void InfoCard::clickRight( tribool down, bool previousState ) void InfoCard::showTeamsPopup() { SDL_Surface *bmp = CMessage::drawDialogBox(256, 90 + 50 * SEL->current->mapHeader->howManyTeams); - CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[657], 128, 30, FONT_MEDIUM, Colors::YELLOW, bmp); //{Team Alignments} + + graphics->fonts[FONT_MEDIUM]->renderTextCenter(bmp, CGI->generaltexth->allTexts[657], Colors::YELLOW, Point(128, 30)); for(int i = 0; i < SEL->current->mapHeader->howManyTeams; i++) { std::vector flags; std::string hlp = CGI->generaltexth->allTexts[656]; //Team %d hlp.replace(hlp.find("%d"), 2, boost::lexical_cast(i+1)); - CSDL_Ext::printAtMiddle(hlp, 128, 65 + 50*i, FONT_SMALL, Colors::WHITE, bmp); + + graphics->fonts[FONT_SMALL]->renderTextCenter(bmp, hlp, Colors::WHITE, Point(128, 65 + 50 * i)); for(int j = 0; j < GameConstants::PLAYER_LIMIT; j++) if((SEL->current->mapHeader->players[j].canHumanPlay || SEL->current->mapHeader->players[j].canComputerPlay) @@ -2224,11 +2222,11 @@ void OptionsTab::showAll(SDL_Surface * to) { CIntObject::showAll(to); printAtMiddleLoc(CGI->generaltexth->allTexts[515], 222, 30, FONT_BIG, Colors::YELLOW, to); - printAtMiddleWBLoc(CGI->generaltexth->allTexts[516], 222, 58, FONT_SMALL, 55, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game. - printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 102, FONT_SMALL, 14, Colors::YELLOW, to); //Player Name Handicap Type - printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 102, FONT_SMALL, 10, Colors::YELLOW, to); //Starting Town - printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 102, FONT_SMALL, 10, Colors::YELLOW, to); //Starting Hero - printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 102, FONT_SMALL, 10, Colors::YELLOW, to); //Starting Bonus + printAtMiddleWBLoc(CGI->generaltexth->allTexts[516], 222, 68, FONT_SMALL, 300, Colors::WHITE, to); //Select starting options, handicap, and name for each player in the game. + printAtMiddleWBLoc(CGI->generaltexth->allTexts[517], 107, 110, FONT_SMALL, 100, Colors::YELLOW, to); //Player Name Handicap Type + printAtMiddleWBLoc(CGI->generaltexth->allTexts[518], 197, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Town + printAtMiddleWBLoc(CGI->generaltexth->allTexts[519], 273, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Hero + printAtMiddleWBLoc(CGI->generaltexth->allTexts[520], 349, 110, FONT_SMALL, 70, Colors::YELLOW, to); //Starting Bonus printAtMiddleLoc(CGI->generaltexth->allTexts[521], 222, 538, FONT_SMALL, Colors::YELLOW, to); // Player Turn Duration if (turnDuration) printAtMiddleLoc(CGI->generaltexth->turnDurations[turnDuration->value], 319,559, FONT_SMALL, Colors::WHITE, to);//Turn duration value @@ -2558,7 +2556,7 @@ void OptionsTab::PlayerOptionsEntry::showAll(SDL_Surface * to) { CIntObject::showAll(to); printAtMiddleLoc(s.name, 55, 10, FONT_SMALL, Colors::WHITE, to); - printAtMiddleWBLoc(CGI->generaltexth->arraytxt[206+whoCanPlay], 28, 34, FONT_TINY, 8, Colors::WHITE, to); + printAtMiddleWBLoc(CGI->generaltexth->arraytxt[206+whoCanPlay], 28, 39, FONT_TINY, 50, Colors::WHITE, to); } void OptionsTab::PlayerOptionsEntry::update() @@ -3130,16 +3128,15 @@ void CBonusSelection::init() //campaign name if (ourCampaign->camp->header.name.length()) - CSDL_Ext::printAt(ourCampaign->camp->header.name, 481, 28, FONT_BIG, Colors::YELLOW, background); + graphics->fonts[FONT_BIG]->renderTextLeft(background, ourCampaign->camp->header.name, Colors::YELLOW, Point(481, 28)); else - CSDL_Ext::printAt("Unnamed", 481, 28, FONT_BIG, Colors::YELLOW, background); + graphics->fonts[FONT_BIG]->renderTextLeft(background, CGI->generaltexth->allTexts[508], Colors::YELLOW, Point(481, 28)); //map size icon sizes = CDefHandler::giveDef("SCNRMPSZ.DEF"); - //campaign description - CSDL_Ext::printAt(CGI->generaltexth->allTexts[38], 481, 63, FONT_SMALL, Colors::YELLOW, background); + graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[38], Colors::YELLOW, Point(481, 63)); cmpgDesc = new CTextBox(ourCampaign->camp->header.description, Rect(480, 86, 286, 117), 1); //cmpgDesc->showAll(background); @@ -3148,7 +3145,7 @@ void CBonusSelection::init() mapDesc = new CTextBox("", Rect(480, 280, 286, 117), 1); //bonus choosing - CSDL_Ext::printAt(CGI->generaltexth->allTexts[71], 511, 432, FONT_MEDIUM, Colors::WHITE, background); //Choose a bonus: + graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, CGI->generaltexth->allTexts[71], Colors::WHITE, Point(511, 432)); bonuses = new CHighlightableButtonsGroup(bind(&CBonusSelection::selectBonus, this, _1)); //set left part of window @@ -3181,15 +3178,15 @@ void CBonusSelection::init() // } //allies / enemies - CSDL_Ext::printAt(CGI->generaltexth->allTexts[390] + ":", 486, 407, FONT_SMALL, Colors::WHITE, background); //Allies - CSDL_Ext::printAt(CGI->generaltexth->allTexts[391] + ":", 619, 407, FONT_SMALL, Colors::WHITE, background); //Enemies + graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[390] + ":", Colors::WHITE, Point(486, 407)); + graphics->fonts[FONT_SMALL]->renderTextLeft(background, CGI->generaltexth->allTexts[391] + ":", Colors::WHITE, Point(619, 407)); SDL_FreeSurface(panel); //difficulty std::vector difficulty; boost::split(difficulty, CGI->generaltexth->allTexts[492], boost::is_any_of(" ")); - CSDL_Ext::printAt(difficulty.back(), 689, 432, FONT_MEDIUM, Colors::WHITE, background); //Difficulty + graphics->fonts[FONT_MEDIUM]->renderTextLeft(background, difficulty.back(), Colors::WHITE, Point(689, 432)); //difficulty pics for (int b=0; bourImages[temp].bitmap, 735, 26, to); //flags - int fx = 496 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[390].c_str()); - int ex = 629 + graphics->fonts[FONT_SMALL]->getWidth(CGI->generaltexth->allTexts[391].c_str()); + int fx = 496 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[390]); + int ex = 629 + graphics->fonts[FONT_SMALL]->getStringWidth(CGI->generaltexth->allTexts[391]); int myT; myT = ourHeader->players[playerColor].team; for (auto i = sInfo.playerInfos.cbegin(); i != sInfo.playerInfos.cend(); i++) @@ -4045,7 +4042,9 @@ std::string CLoadingScreen::getBackground() CLoadingScreen::CLoadingScreen(boost::function loader): CWindowObject(BORDERED, getBackground()), loadingThread(loader) -{} +{ + CCS->musich->stopMusic(5000); +} CLoadingScreen::~CLoadingScreen() { diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 12e5dba61..e37a6ad31 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -321,7 +321,7 @@ void CSpellWindow::showAll(SDL_Surface * to) std::ostringstream mana; mana<mana; - CSDL_Ext::printAtMiddle(mana.str(), pos.x+435, pos.y +426, FONT_SMALL, Colors::YELLOW, to); + printAtMiddleLoc(mana.str(), 435, 426, FONT_SMALL, Colors::YELLOW, to); statusBar->showAll(to); @@ -800,12 +800,8 @@ void CSpellWindow::SpellArea::clickRight(tribool down, bool previousState) boost::algorithm::replace_first(dmgInfo, "%d", boost::lexical_cast(causedDmg)); } - SDL_Surface *spellBox = CMessage::drawBoxTextBitmapSub( - owner->myInt->playerID, - CGI->spellh->spells[mySpell]->descriptions[schoolLevel] + dmgInfo, this->owner->spells->ourImages[mySpell].bitmap, - CGI->spellh->spells[mySpell]->name,30,30); - CInfoPopup *vinya = new CInfoPopup(spellBox, true); - GH.pushInt(vinya); + CRClickPopup::createAndPush(CGI->spellh->spells[mySpell]->descriptions[schoolLevel] + dmgInfo, + new CComponent(CComponent::spell, mySpell)); } } @@ -850,13 +846,13 @@ void CSpellWindow::SpellArea::showAll(SDL_Surface * to) secondLineColor = Colors::WHITE; } //printing spell's name - CSDL_Ext::printAtMiddle(spell->name, pos.x + 39, pos.y + 70, FONT_TINY, firstLineColor, to); + printAtMiddleLoc(spell->name, 39, 70, FONT_TINY, firstLineColor, to); //printing lvl - CSDL_Ext::printAtMiddle(CGI->generaltexth->allTexts[171 + spell->level], pos.x + 39, pos.y + 82, FONT_TINY, secondLineColor, to); + printAtMiddleLoc(CGI->generaltexth->allTexts[171 + spell->level], 39, 82, FONT_TINY, secondLineColor, to); //printing cost std::ostringstream ss; ss << CGI->generaltexth->allTexts[387] << ": " << spellCost; - CSDL_Ext::printAtMiddle(ss.str(), pos.x + 39, pos.y + 94, FONT_TINY, secondLineColor, to); + printAtMiddleLoc(ss.str(), 39, 94, FONT_TINY, secondLineColor, to); } void CSpellWindow::SpellArea::setSpell(int spellID) diff --git a/client/FontBase.h b/client/FontBase.h deleted file mode 100644 index e730dbb29..000000000 --- a/client/FontBase.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/* - * FontBase.h, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -enum EFonts -{ - FONT_BIG, FONT_CALLI, FONT_CREDITS, FONT_HIGH_SCORE, FONT_MEDIUM, FONT_SMALL, FONT_TIMES, FONT_TINY, FONT_VERD -}; - -struct Font -{ - struct Char - { - si32 leftOffset, width, rightOffset; - ui8 *pixels; - }; - - Char chars[256]; - ui8 height; - - ui8 *data; - - Font(ui8 *Data); - ~Font(); - int getWidth(const char *text) const; - int getCharWidth(char c) const; -}; diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index 5d6d433d3..fcd5adfa1 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -880,7 +880,7 @@ void CComponent::init(Etype Type, int Subtype, int Val, ESize imageSize) std::vector textLines = CMessage::breakText(getSubtitle(), std::max(80, pos.w), font); BOOST_FOREACH(auto & line, textLines) { - int height = graphics->fonts[font]->height; + int height = graphics->fonts[font]->getLineHeight(); CLabel * label = new CLabel(pos.w/2, pos.h + height/2, font, CENTER, Colors::WHITE, line); pos.h += height; @@ -1748,24 +1748,22 @@ void CMinorResDataBar::show(SDL_Surface * to) void CMinorResDataBar::showAll(SDL_Surface * to) { blitAt(bg,pos.x,pos.y,to); - char buf[30]; for (int i=0;i<7;i++) { - SDL_itoa(LOCPLINT->cb->getResourceAmount(i),buf,10); - CSDL_Ext::printAtMiddle(buf,pos.x + 50 + 76*i,pos.y+pos.h/2,FONT_SMALL,Colors::WHITE,to); + std::string text = boost::lexical_cast(LOCPLINT->cb->getResourceAmount(i)); + + graphics->fonts[FONT_SMALL]->renderTextCenter(to, text, Colors::WHITE, Point(pos.x + 50 + 76 * i, pos.y + pos.h/2)); } std::vector temp; - SDL_itoa(LOCPLINT->cb->getDate(3),buf,10); temp.push_back(std::string(buf)); - SDL_itoa(LOCPLINT->cb->getDate(2),buf,10); temp.push_back(buf); - SDL_itoa(LOCPLINT->cb->getDate(1),buf,10); temp.push_back(buf); - CSDL_Ext::printAtMiddle(CSDL_Ext::processStr( - CGI->generaltexth->allTexts[62] - +": %s, " - + CGI->generaltexth->allTexts[63] - + ": %s, " - + CGI->generaltexth->allTexts[64] - + ": %s",temp) - ,pos.x+545+(pos.w-545)/2,pos.y+pos.h/2,FONT_SMALL,Colors::WHITE,to); + + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(3))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(2))); + temp.push_back(boost::lexical_cast(LOCPLINT->cb->getDate(1))); + + std::string datetext = CGI->generaltexth->allTexts[62]+": %s, " + CGI->generaltexth->allTexts[63] + + ": %s, " + CGI->generaltexth->allTexts[64] + ": %s"; + + graphics->fonts[FONT_SMALL]->renderTextCenter(to, processStr(datetext,temp), Colors::WHITE, Point(pos.x+545+(pos.w-545)/2,pos.y+pos.h/2)); } CMinorResDataBar::CMinorResDataBar() @@ -3663,7 +3661,7 @@ void CTavernWindow::show(SDL_Surface * to) boost::algorithm::replace_first(recruit->hoverTexts[0],"%s",sel->h->type->heroClass->name); } - printAtMiddleWB(sel->descr,pos.x+146,pos.y+389,FONT_SMALL,40,Colors::WHITE,to); + printAtMiddleWBLoc(sel->descr, 146, 389, FONT_SMALL, 200, Colors::WHITE, to); CSDL_Ext::drawBorder(to,sel->pos.x-2,sel->pos.y-2,sel->pos.w+4,sel->pos.h+4,int3(247,223,123)); } } @@ -3728,13 +3726,14 @@ void CInGameConsole::show(SDL_Surface * to) boost::unique_lock lock(texts_mx); for(std::list< std::pair< std::string, int > >::iterator it = texts.begin(); it != texts.end(); ++it, ++number) { - SDL_Color green = {0,0xff,0,0}; Point leftBottomCorner(0, screen->h); if(LOCPLINT->battleInt) { leftBottomCorner = LOCPLINT->battleInt->pos.bottomLeft(); } - CSDL_Ext::printAt(it->first, leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number*20, FONT_MEDIUM, green); + graphics->fonts[FONT_MEDIUM]->renderTextLeft(to, it->first, Colors::GREEN, + Point(leftBottomCorner.x + 50, leftBottomCorner.y - texts.size() * 20 - 80 + number*20)); + if(SDL_GetTicks() - it->second > defaultTimeout) { toDel.push_back(it); diff --git a/client/Graphics.cpp b/client/Graphics.cpp index 48cfd4933..28477e340 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -124,7 +124,6 @@ Graphics::Graphics() std::vector tasks; //preparing list of graphics to load tasks += boost::bind(&Graphics::loadFonts,this); - tasks += boost::bind(&Graphics::loadTrueType,this); tasks += boost::bind(&Graphics::loadPaletteAndColors,this); tasks += boost::bind(&Graphics::loadHeroFlags,this); tasks += boost::bind(&Graphics::initializeBattleGraphics,this); @@ -386,38 +385,24 @@ void Graphics::blueToPlayersAdv(SDL_Surface * sur, int player) } } -void Graphics::loadTrueType() -{ - for(int i = 0; i < FONTS_NUMBER; i++) - fontsTrueType[i] = NULL; -} - -Font * Graphics::loadFont( const char * name ) -{ - ui8 * hlp = CResourceHandler::get()->loadData( - ResourceID(std::string("DATA/") + name, EResType::FONT)).first.release(); - if(!hlp) - { - tlog1 << "Error: cannot load font: " << name << std::endl; - return NULL; - } - - int magic = SDL_SwapLE32(*(const Uint32*)hlp); - if(magic != 589598 && magic != 589599) - tlog1 << "Suspicious font file, fname " << name << "n"; - - Font *ret = new Font(hlp); - return ret; -} - void Graphics::loadFonts() { - static const char *fontnames [] = {"BIGFONT.FNT", "CALLI10R.FNT", "CREDITS.FNT", "HISCORE.FNT", "MEDFONT.FNT", - "SMALFONT.FNT", "TIMES08R.FNT", "TINY.FNT", "VERD10B.FNT"} ; + const JsonNode config(ResourceID("config/fonts.json")); - assert(ARRAY_COUNT(fontnames) == FONTS_NUMBER); - for(int i = 0; i < FONTS_NUMBER; i++) - fonts[i] = loadFont(fontnames[i]); + const JsonVector & bmpConf = config["bitmap"].Vector(); + const JsonNode & ttfConf = config["trueType"]; + + assert(bmpConf.size() == FONTS_NUMBER); + + for (size_t i=0; i=30) - // tlog0 << ci << ". (" << (char)ci << "). Width: " << chars[ci].width << " U1/U2:" << chars[ci].unknown1 << "/" << chars[ci].unknown2 << std::endl; - } - for(int ci = 0; ci < 256; ci++) - { - int offset = read_le_u32(data + i); i+=4; - chars[ci].pixels = data + 4128 + offset; - } -} - -Font::~Font() -{ - delete [] data; -} - -int Font::getWidth(const char *text ) const -{ - int length = std::strlen(text); - int ret = 0; - - for(int i = 0; i < length; i++) - { - ui8 c = text[i]; - ret += chars[c].width + chars[c].leftOffset + chars[c].rightOffset; - } - - return ret; -} - -int Font::getCharWidth( char c ) const -{ - const Char &C = chars[(ui8)c]; - return C.width + C.leftOffset + C.rightOffset;; -} - -/* -void Font::WriteAt(const char *text, SDL_Surface *sur, int x, int y ) -{ - SDL_Surface *SDL_CreateRGBSurfaceFrom(pixels, w, h, 8, int pitch, - 224, 28, 3, 0); -} -*/ diff --git a/client/Graphics.h b/client/Graphics.h index c06cf0b9a..d66bf63a2 100644 --- a/client/Graphics.h +++ b/client/Graphics.h @@ -1,7 +1,7 @@ #pragma once -#include "FontBase.h" +#include "UIFramework/Fonts.h" #include "../lib/GameConstants.h" #include "UIFramework/Geometries.h" @@ -27,7 +27,10 @@ struct InfoAboutTown; class CGObjectInstance; class CGDefInfo; -typedef struct _TTF_Font TTF_Font; //from SDL_ttf.h +enum EFonts +{ + FONT_BIG, FONT_CALLI, FONT_CREDITS, FONT_HIGH_SCORE, FONT_MEDIUM, FONT_SMALL, FONT_TIMES, FONT_TINY, FONT_VERD +}; /// Handles fonts, hero images, town images, various graphics class Graphics @@ -35,9 +38,8 @@ class Graphics public: //Fonts static const int FONTS_NUMBER = 9; - Font *fonts[FONTS_NUMBER]; - TTF_Font * fontsTrueType[FONTS_NUMBER];//true type fonts, if some of the fonts not loaded - NULL - + IFont * fonts[FONTS_NUMBER]; + \ //various graphics SDL_Color * playerColors; //array [8] SDL_Color * neutralColor; @@ -77,9 +79,7 @@ public: CDefEssential * loadHeroAnim(const std::string &name, const std::vector > &rotations); void loadErmuToPicture(); void blueToPlayersAdv(SDL_Surface * sur, int player); //replaces blue interface colour with a color of player - void loadTrueType(); void loadFonts(); - Font *loadFont(const char * name); }; extern Graphics * graphics; diff --git a/client/UIFramework/CGuiHandler.cpp b/client/UIFramework/CGuiHandler.cpp index 400cd7153..f24f611c1 100644 --- a/client/UIFramework/CGuiHandler.cpp +++ b/client/UIFramework/CGuiHandler.cpp @@ -424,7 +424,7 @@ void CGuiHandler::drawFPSCounter() Uint32 black = SDL_MapRGB(screen->format, 10, 10, 10); SDL_FillRect(screen, &overlay, black); std::string fps = boost::lexical_cast(mainFPSmng->fps); - CSDL_Ext::printAt(fps, 10, 10, FONT_BIG, yellow, screen); + graphics->fonts[FONT_BIG]->renderTextLeft(screen, fps, yellow, Point(10, 10)); } SDLKey CGuiHandler::arrowToNum( SDLKey key ) diff --git a/client/UIFramework/CIntObject.cpp b/client/UIFramework/CIntObject.cpp index d9f05ed3c..fa363c9f6 100644 --- a/client/UIFramework/CIntObject.cpp +++ b/client/UIFramework/CIntObject.cpp @@ -2,6 +2,7 @@ #include "CIntObject.h" #include "CGuiHandler.h" #include "SDL_Extensions.h" +#include "CMessage.h" CIntObject::CIntObject(int used_, Point pos_): parent_m(nullptr), @@ -129,17 +130,17 @@ CIntObject::~CIntObject() void CIntObject::printAtLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ ) { - CSDL_Ext::printAt(text, pos.x + x, pos.y + y, font, kolor, dst); + graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y)); } void CIntObject::printAtMiddleLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ ) { - CSDL_Ext::printAtMiddle(text, pos.x + x, pos.y + y, font, kolor, dst); + printAtMiddleLoc(text, Point(x,y), font, kolor, dst); } void CIntObject::printAtMiddleLoc(const std::string & text, const Point &p, EFonts font, SDL_Color kolor, SDL_Surface * dst) { - printAtMiddleLoc(text, p.x, p.y, font, kolor, dst); + graphics->fonts[font]->renderTextCenter(dst, text, kolor, pos.topLeft() + p); } void CIntObject::blitAtLoc( SDL_Surface * src, int x, int y, SDL_Surface * dst ) @@ -154,12 +155,12 @@ void CIntObject::blitAtLoc(SDL_Surface * src, const Point &p, SDL_Surface * dst) void CIntObject::printAtMiddleWBLoc( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst) { - CSDL_Ext::printAtMiddleWB(text, pos.x + x, pos.y + y, font, charpr, kolor, dst); + graphics->fonts[font]->renderTextLinesCenter(dst, CMessage::breakText(text, charpr, font), kolor, Point(pos.x + x, pos.y + y)); } void CIntObject::printToLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor, SDL_Surface * dst ) { - CSDL_Ext::printTo(text, pos.x + x, pos.y + y, font, kolor, dst); + graphics->fonts[font]->renderTextRight(dst, text, kolor, Point(pos.x + x, pos.y + y)); } void CIntObject::addUsedEvents(ui16 newActions) diff --git a/client/UIFramework/CIntObject.h b/client/UIFramework/CIntObject.h index 1eadfc4ee..24e733ed1 100644 --- a/client/UIFramework/CIntObject.h +++ b/client/UIFramework/CIntObject.h @@ -2,7 +2,7 @@ #include #include "Geometries.h" -#include "../FontBase.h" +#include "../Graphics.h" struct SDL_Surface; class CPicture; diff --git a/client/UIFramework/CIntObjectClasses.cpp b/client/UIFramework/CIntObjectClasses.cpp index 5e95e1601..d45fead4a 100644 --- a/client/UIFramework/CIntObjectClasses.cpp +++ b/client/UIFramework/CIntObjectClasses.cpp @@ -1147,7 +1147,8 @@ void CStatusBar::show(SDL_Surface * to) SDL_Rect srcRect = genRect(pos.h,pos.w,0,0); SDL_Rect dstRect = genRect(pos.h,pos.w,pos.x,pos.y); CSDL_Ext::blitSurface(bg,&srcRect,to,&dstRect); - CSDL_Ext::printAtMiddle(current,middlex,middley,FONT_SMALL,Colors::WHITE,to); + + graphics->fonts[FONT_SMALL]->renderTextCenter(to, current, Colors::WHITE, Point(middlex, middley)); } std::string CStatusBar::getCurrent() @@ -1214,8 +1215,18 @@ void CLabel::showAll(SDL_Surface * to) if(!toPrint.length()) return; - static void (*printer[3])(const std::string &, int, int, EFonts, SDL_Color, SDL_Surface *) = {&CSDL_Ext::printAt, &CSDL_Ext::printAtMiddle, &CSDL_Ext::printTo}; //array of printing functions - printer[alignment](toPrint, pos.x + textOffset.x, pos.y + textOffset.y, font, color, to); + switch (alignment) + { + break; case TOPLEFT : + CIntObject::printAtLoc(toPrint, textOffset.x, textOffset.y, font, color, to); + break; case CENTER : + CIntObject::printAtMiddleLoc(toPrint, textOffset.x, textOffset.y, font, color, to); + break; case BOTTOMRIGHT : + CIntObject::printToLoc(toPrint, textOffset.x, textOffset.y, font, color, to); + break; default : + assert(0); + } + } CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, const SDL_Color &Color /*= Colors::WHITE*/, const std::string &Text /*= ""*/) @@ -1228,8 +1239,8 @@ CLabel::CLabel(int x, int y, EFonts Font /*= FONT_SMALL*/, EAlignment Align, con bg = NULL; ignoreLeadingWhitespace = false; - pos.w = graphics->fonts[font]->getWidth(text.c_str()); - pos.h = graphics->fonts[font]->height; + pos.w = graphics->fonts[font]->getStringWidth(text.c_str()); + pos.h = graphics->fonts[font]->getLineHeight(); } std::string CLabel::visibleText() @@ -1267,7 +1278,7 @@ void CBoundedLabel::setTxt(const std::string &Txt) void CBoundedLabel::blitLine(SDL_Surface *to, Point where, std::string what) { - const Font &f = *graphics->fonts[font]; + const IFont * f = graphics->fonts[font]; size_t begin = 0; size_t end; @@ -1277,14 +1288,16 @@ void CBoundedLabel::blitLine(SDL_Surface *to, Point where, std::string what) do { end = what.find_first_of(delimeters[currDelimeter % 2], begin); - std::string toPrint = what.substr(begin, end); - if (currDelimeter % 2) - CSDL_Ext::printAt(toPrint, where.x, where.y, font, Colors::YELLOW, to); - else - CSDL_Ext::printAt(toPrint, where.x, where.y, font, color, to); - begin = end; - where.x += f.getWidth(toPrint.c_str()); - + if (begin != end) + { + std::string toPrint = what.substr(begin, end-1); + if (currDelimeter % 2) // Enclosed in {} text - set to yellow + graphics->fonts[font]->renderTextLeft(to, toPrint, Colors::YELLOW, where); + else // Non-enclosed text + graphics->fonts[font]->renderTextLeft(to, toPrint, color, where); + begin = end; + where.x += f->getStringWidth(toPrint.c_str()); + } currDelimeter++; } while (begin++ != std::string::npos); @@ -1294,11 +1307,11 @@ void CBoundedLabel::showAll(SDL_Surface * to) { CIntObject::showAll(to); - const Font &f = *graphics->fonts[font]; - int lineHeight = f.height; + const IFont * f = graphics->fonts[font]; + int lineHeight = f->getLineHeight(); int lineCapacity = pos.h / lineHeight; - int dy = f.height; //line height + int dy = f->getLineHeight(); //line height int base_y = pos.y; if(alignment == CENTER) base_y += std::max((pos.h - maxH)/2,0); @@ -1312,7 +1325,7 @@ void CBoundedLabel::showAll(SDL_Surface * to) int x = pos.x; if(alignment == CENTER) { - x += (pos.w - f.getWidth(line.c_str())) / 2; + x += pos.w - f->getStringWidth(line.c_str()) / 2; } blitLine(to, Point(x, base_y + i * dy), line); @@ -1323,15 +1336,15 @@ void CBoundedLabel::recalculateLines(const std::string &Txt) { lines.clear(); - const Font &f = *graphics->fonts[font]; - int lineHeight = f.height; + const IFont * f = graphics->fonts[font]; + int lineHeight = f->getLineHeight(); lines = CMessage::breakText(Txt, pos.w, font); maxH = lineHeight * lines.size(); maxW = 0; BOOST_FOREACH(const std::string &line, lines) - vstd::amax(maxW, f.getWidth(line.c_str())); + vstd::amax(maxW, f->getStringWidth(line.c_str())); } CLabelGroup::CLabelGroup(EFonts Font, EAlignment Align, const SDL_Color &Color): @@ -1362,8 +1375,8 @@ void CTextBox::recalculateLines(const std::string &Txt) vstd::clear_pointer(slider); lines.clear(); - const Font &f = *graphics->fonts[font]; - int lineHeight = f.height; + const IFont * f = graphics->fonts[font]; + int lineHeight = f->getLineHeight(); int lineCapacity = pos.h / lineHeight; lines = CMessage::breakText(Txt, pos.w, font); @@ -1379,15 +1392,15 @@ void CTextBox::recalculateLines(const std::string &Txt) maxH = lineHeight * lines.size(); maxW = 0; BOOST_FOREACH(const std::string &line, lines) - vstd::amax(maxW, f.getWidth(line.c_str())); + vstd::amax(maxW, f->getStringWidth(line)); } void CTextBox::showAll(SDL_Surface * to) { CIntObject::showAll(to); - const Font &f = *graphics->fonts[font]; - int dy = f.height; //line height + const IFont * f = graphics->fonts[font]; + int dy = f->getLineHeight(); //line height int base_y = pos.y; if(alignment == CENTER) base_y += std::max((pos.h - maxH)/2,0); @@ -1403,7 +1416,7 @@ void CTextBox::showAll(SDL_Surface * to) int x = pos.x; if(alignment == CENTER) { - x += (pos.w - f.getWidth(line.c_str())) / 2; + x += (pos.w - f->getStringWidth(line.c_str())) / 2; if(slider) x -= slider->pos.w / 2 + 5; } diff --git a/client/UIFramework/Fonts.cpp b/client/UIFramework/Fonts.cpp new file mode 100644 index 000000000..6b9b6e691 --- /dev/null +++ b/client/UIFramework/Fonts.cpp @@ -0,0 +1,274 @@ +#include "StdInc.h" +#include "Fonts.h" + +#include + +#include "SDL_Pixels.h" +#include "../../lib/JsonNode.h" +#include "../../lib/vcmi_endian.h" +#include "../../lib/Filesystem/CResourceLoader.h" + +/* + * Fonts.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + + +void IFont::renderTextLeft(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const +{ + renderText(surface, data, color, pos); +} + +void IFont::renderTextRight(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const +{ + Point size(getStringWidth(data), getLineHeight()); + renderText(surface, data, color, pos - size); +} + +void IFont::renderTextCenter(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const +{ + Point size(getStringWidth(data), getLineHeight()); + renderText(surface, data, color, pos - size / 2); +} + +void IFont::renderTextLinesLeft(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const +{ + Point currPos = pos; + + BOOST_FOREACH(const std::string & line, data) + { + renderTextLeft(surface, line, color, currPos); + currPos.y += getLineHeight(); + } +} + +void IFont::renderTextLinesRight(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const +{ + Point currPos = pos; + currPos.y -= data.size() * getLineHeight(); + + BOOST_FOREACH(const std::string & line, data) + { + renderTextRight(surface, line, color, currPos); + currPos.y += getLineHeight(); + } +} + +void IFont::renderTextLinesCenter(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const +{ + Point currPos = pos; + currPos.y -= data.size() * getLineHeight()/2; + + BOOST_FOREACH(const std::string & line, data) + { + renderTextCenter(surface, line, color, currPos); + currPos.y += getLineHeight(); + } +} + +std::array CBitmapFont::loadChars() const +{ + std::array ret; + + size_t offset = 32; + + for (size_t i=0; i< ret.size(); i++) + { + ret[i].leftOffset = read_le_u32(data.first.get() + offset); offset+=4; + ret[i].width = read_le_u32(data.first.get() + offset); offset+=4; + ret[i].rightOffset = read_le_u32(data.first.get() + offset); offset+=4; + } + + for (size_t i=0; i< ret.size(); i++) + { + int pixelOffset = read_le_u32(data.first.get() + offset); offset+=4; + ret[i].pixels = data.first.get() + 4128 + pixelOffset; + + assert(pixelOffset + 4128 < data.second); + } + return ret; +} + +CBitmapFont::CBitmapFont(const std::string & filename): + data(CResourceHandler::get()->loadData(ResourceID("data/" + filename, EResType::BMP_FONT))), + chars(loadChars()), + height(data.first.get()[5]) +{} + +size_t CBitmapFont::getLineHeight() const +{ + return height; +} + +size_t CBitmapFont::getSymbolWidth(char data) const +{ + const Char & ch = chars[ui8(data)]; + return ch.leftOffset + ch.width + ch.rightOffset; +} + +size_t CBitmapFont::getStringWidth(const std::string & data) const +{ + size_t width = 0; + + BOOST_FOREACH(auto & ch, data) + { + width += getSymbolWidth(ch); + } + return width; +} + +void CBitmapFont::renderCharacter(SDL_Surface * surface, const Char & character, const SDL_Color & color, int &posX, int &posY) const +{ + Rect clipRect; + SDL_GetClipRect(surface, &clipRect); + + posX += character.leftOffset; + + TColorPutter colorPutter = CSDL_Ext::getPutterFor(surface, 0); + + Uint8 bpp = surface->format->BytesPerPixel; + + // start of line, may differ from 0 due to end of surface or clipped surface + int lineBegin = std::max(0, clipRect.y - posY); + int lineEnd = std::min(height, clipRect.y + clipRect.h - posY - 1); + + // start end end of each row, may differ from 0 + int rowBegin = std::max(0, clipRect.x - posX); + int rowEnd = std::min(character.width, clipRect.x + clipRect.w - posX - 1); + + //for each line in symbol + for(int dy = lineBegin; dy pixels; + Uint8 *srcLine = character.pixels; + + // shift source\destination pixels to current position + dstLine += (posY+dy) * surface->pitch + posX * bpp; + srcLine += dy * character.width; + + //for each column in line + for(int dx = rowBegin; dx < rowEnd; dx++) + { + Uint8* dstPixel = dstLine + dx*bpp; + switch(srcLine[dx]) + { + case 1: //black "shadow" + std::fill(dstPixel, dstPixel + bpp, 0); + break; + case 255: //text colour + colorPutter(dstPixel, color.r, color.g, color.b); + break; + default : + break; //transparency + } + } + } + posX += character.width; + posX += character.rightOffset; +} + +void CBitmapFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const +{ + if (data.empty()) + return; + + assert(surface); + + int posX = pos.x; + int posY = pos.y; + + // Safe to remove but I would like to trace all cases of this. Please report on Mantis or send note to me + // Ivan + assert(data[0] != '{'); + assert(data[data.size()-1] != '}'); + + SDL_LockSurface(surface); + // for each symbol + for(size_t index = 0; index < data.size(); index++) + { + renderCharacter(surface, chars[ui8(data[index])], color, posX, posY); + } + SDL_UnlockSurface(surface); +} + +std::pair, ui64> CTrueTypeFont::loadData(const JsonNode & config) +{ + std::string filename = "Data/" + config["file"].String(); + return CResourceHandler::get()->loadData(ResourceID(filename, EResType::TTF_FONT)); +} + +TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config) +{ + int pointSize = config["size"].Float(); + + if(!TTF_WasInit() && TTF_Init()==-1) + throw std::runtime_error(std::string("Failed to initialize true type support: ") + TTF_GetError() + "\n"); + + return TTF_OpenFontRW(SDL_RWFromConstMem(data.first.get(), data.second), 1, pointSize); +} + +int CTrueTypeFont::getFontStyle(const JsonNode &config) +{ + const JsonVector & names = config["style"].Vector(); + int ret = 0; + BOOST_FOREACH(const JsonNode & node, names) + { + if (node.String() == "bold") + ret |= TTF_STYLE_BOLD; + else if (node.String() == "italic") + ret |= TTF_STYLE_ITALIC; + } + return ret; +} + +CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig): + data(loadData(fontConfig)), + font(loadFont(fontConfig), TTF_CloseFont), + blended(fontConfig["blend"].Bool()) +{ + assert(font); + + TTF_SetFontStyle(font.get(), getFontStyle(fontConfig)); +} + +size_t CTrueTypeFont::getLineHeight() const +{ + return TTF_FontHeight(font.get()); +} + +size_t CTrueTypeFont::getSymbolWidth(char data) const +{ + int advance; + TTF_GlyphMetrics(font.get(), data, NULL, NULL, NULL, NULL, &advance); + return advance; +} + +size_t CTrueTypeFont::getStringWidth(const std::string & data) const +{ + int width; + TTF_SizeText(font.get(), data.c_str(), &width, NULL); + return width; +} + +void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const +{ + if (!data.empty()) + { + SDL_Surface * rendered; + if (blended) + rendered = TTF_RenderText_Solid(font.get(), data.c_str(), color); + else + rendered = TTF_RenderText_Blended(font.get(), data.c_str(), color); + + assert(rendered); + + Rect rect(pos.x, pos.y, rendered->w, rendered->h); + SDL_BlitSurface(rendered, NULL, surface, &rect); + SDL_FreeSurface(rendered); + } +} diff --git a/client/UIFramework/Fonts.h b/client/UIFramework/Fonts.h new file mode 100644 index 000000000..cdf3dbe2c --- /dev/null +++ b/client/UIFramework/Fonts.h @@ -0,0 +1,110 @@ +#pragma once + +/* + * Fonts.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +class JsonNode; + +struct Point; +struct SDL_Surface; +struct SDL_Color; + +typedef struct _TTF_Font TTF_Font; + +class IFont +{ +protected: + /// Internal function to render font, see renderTextLeft + virtual void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const = 0; + +public: + virtual ~IFont() + {} + + /// Returns height of font + virtual size_t getLineHeight() const = 0; + /// Returns width of a single symbol + virtual size_t getSymbolWidth(char data) const = 0; + /// Return width of the string + virtual size_t getStringWidth(const std::string & data) const = 0; + + /** + * @param surface - destination to print text on + * @param data - string to print + * @param color - font color + * @param pos - position of rendered font + */ + /// pos = topleft corner of the text + void renderTextLeft(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const; + /// pos = center of the text + void renderTextRight(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const; + /// pos = bottomright corner of the text + void renderTextCenter(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const; + + /** + * @param maxWidth - max width in pixels of one line + */ + /// pos = topleft corner of the text + void renderTextLinesLeft(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const; + /// pos = center of the text + void renderTextLinesRight(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const; + /// pos = bottomright corner of the text + void renderTextLinesCenter(SDL_Surface * surface, const std::vector & data, const SDL_Color & color, const Point & pos) const; +}; + +class CBitmapFont : public IFont +{ + static const size_t totalChars = 256; + + struct Char + { + si32 leftOffset; + ui32 width; + si32 rightOffset; + ui8 *pixels; // pixels of this character, part of BitmapFont::data + }; + + const std::pair, ui64> data; + + const std::array chars; + const ui8 height; + + std::array loadChars() const; + + void renderCharacter(SDL_Surface * surface, const Char & character, const SDL_Color & color, int &posX, int &posY) const; + + void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const; +public: + CBitmapFont(const std::string & filename); + + size_t getLineHeight() const; + size_t getSymbolWidth(char data) const; + size_t getStringWidth(const std::string & data) const; +}; + +class CTrueTypeFont : public IFont +{ + const std::pair, ui64> data; + + const std::unique_ptr font; + const bool blended; + + std::pair, ui64> loadData(const JsonNode & config); + TTF_Font * loadFont(const JsonNode & config); + int getFontStyle(const JsonNode & config); + + void renderText(SDL_Surface * surface, const std::string & data, const SDL_Color & color, const Point & pos) const; +public: + CTrueTypeFont(const JsonNode & fontConfig); + + size_t getLineHeight() const; + size_t getSymbolWidth(char data) const; + size_t getStringWidth(const std::string & data) const; +}; diff --git a/client/UIFramework/Geometries.h b/client/UIFramework/Geometries.h index 5a7aa2ceb..8e56c1e8a 100644 --- a/client/UIFramework/Geometries.h +++ b/client/UIFramework/Geometries.h @@ -48,6 +48,12 @@ struct Point return Point(x+b.x,y+b.y); } + template + Point operator/(const T &div) const + { + return Point(x/div, y/div); + } + template Point operator*(const T &mul) const { diff --git a/client/UIFramework/SDL_Extensions.cpp b/client/UIFramework/SDL_Extensions.cpp index 81d6a7e74..ed52dd9cb 100644 --- a/client/UIFramework/SDL_Extensions.cpp +++ b/client/UIFramework/SDL_Extensions.cpp @@ -11,6 +11,7 @@ const SDL_Color Colors::YELLOW = { 229, 215, 123, 0 }; const SDL_Color Colors::WHITE = { 255, 243, 222, 0 }; const SDL_Color Colors::METALLIC_GOLD = { 173, 142, 66, 0 }; +const SDL_Color Colors::GREEN = { 0, 255, 0, 0 }; SDL_Surface * CSDL_Ext::newSurface(int w, int h, SDL_Surface * mod) //creates new surface, with flags/format same as in surface given { @@ -76,311 +77,6 @@ void updateRect (SDL_Rect * rect, SDL_Surface * scr) SDL_UpdateRect(scr,rect->x,rect->y,rect->w,rect->h); } -void printAtMiddleWB(const std::string & text, int x, int y, TTF_Font * font, int charpr, SDL_Color kolor, SDL_Surface * dst) -{ - std::vector ws = CMessage::breakText(text,charpr); - std::vector wesu; - wesu.resize(ws.size()); - for (size_t i=0; i < wesu.size(); ++i) - { - wesu[i]=TTF_RenderText_Blended(font,ws[i].c_str(),kolor); - } - - int tox=0, toy=0; - for (size_t i=0; i < wesu.size(); ++i) - { - toy+=wesu[i]->h; - if (tox < wesu[i]->w) - tox=wesu[i]->w; - } - int evx, evy = y - (toy/2); - for (size_t i=0; i < wesu.size(); ++i) - { - evx = (x - (tox/2)) + ((tox-wesu[i]->w)/2); - blitAt(wesu[i],evx,evy,dst); - evy+=wesu[i]->h; - } - - - for (size_t i=0; i < wesu.size(); ++i) - SDL_FreeSurface(wesu[i]); -} - -void printAtWB(const std::string & text, int x, int y, TTF_Font * font, int charpr, SDL_Color kolor, SDL_Surface * dst) -{ - std::vector ws = CMessage::breakText(text,charpr); - std::vector wesu; - wesu.resize(ws.size()); - for (size_t i=0; i < wesu.size(); ++i) - wesu[i]=TTF_RenderText_Blended(font,ws[i].c_str(),kolor); - - int evy = y; - for (size_t i=0; i < wesu.size(); ++i) - { - blitAt(wesu[i],x,evy,dst); - evy+=wesu[i]->h; - } - - for (size_t i=0; i < wesu.size(); ++i) - SDL_FreeSurface(wesu[i]); -} - -void CSDL_Ext::printAtWB(const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor, SDL_Surface * dst) -{ - if (graphics->fontsTrueType[font]) - { - printAtWB(text,x, y, graphics->fontsTrueType[font], charpr, kolor, dst); - return; - } - const Font *f = graphics->fonts[font]; - std::vector ws = CMessage::breakText(text,charpr); - - int cury = y; - for (size_t i=0; i < ws.size(); ++i) - { - printAt(ws[i], x, cury, font, kolor, dst); - cury += f->height; - } -} - - -void CSDL_Ext::printAtMiddleWB( const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor/*=Colors::YELLOW*/, SDL_Surface * dst/*=screen*/ ) -{ - if (graphics->fontsTrueType[font]) - { - printAtMiddleWB(text,x, y, graphics->fontsTrueType[font], charpr, kolor, dst); - return; - } - - const Font *f = graphics->fonts[font]; - std::vector ws = CMessage::breakText(text,charpr); - int totalHeight = ws.size() * f->height; - - int cury = y - totalHeight/2; - for (size_t i=0; i < ws.size(); ++i) - { - printAt(ws[i], x - f->getWidth(ws[i].c_str())/2, cury, font, kolor, dst); - cury += f->height; - } -} - -void printAtMiddle(const std::string & text, int x, int y, TTF_Font * font, SDL_Color kolor, SDL_Surface * dst, ui8 quality=2) -{ - if(text.length()==0) return; - SDL_Surface * temp; - switch (quality) - { - case 0: - temp = TTF_RenderText_Solid(font,text.c_str(),kolor); - break; - case 1: - SDL_Color tem; - tem.b = 0xff-kolor.b; - tem.g = 0xff-kolor.g; - tem.r = 0xff-kolor.r; - tem.unused = 0xff-kolor.unused; - temp = TTF_RenderText_Shaded(font,text.c_str(),kolor,tem); - break; - case 2: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - default: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - } - SDL_Rect dstRect = genRect(temp->h, temp->w, x-(temp->w/2), y-(temp->h/2)); - CSDL_Ext::blitSurface(temp, NULL, dst, &dstRect); - SDL_FreeSurface(temp); -} - -void CSDL_Ext::printAtMiddle( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ ) -{ - if (graphics->fontsTrueType[font]) - { - printAtMiddle(text,x, y, graphics->fontsTrueType[font], kolor, dst); - return; - } - const Font *f = graphics->fonts[font]; - int nx = x - f->getWidth(text.c_str())/2, - ny = y - f->height/2; - - printAt(text, nx, ny, font, kolor, dst); -} - -void printAt(const std::string & text, int x, int y, TTF_Font * font, SDL_Color kolor, SDL_Surface * dst, ui8 quality=2, bool refresh=false) -{ - if (text.length()==0) - return; - SDL_Surface * temp; - switch (quality) - { - case 0: - temp = TTF_RenderText_Solid(font,text.c_str(),kolor); - break; - case 1: - SDL_Color tem; - tem.b = 0xff-kolor.b; - tem.g = 0xff-kolor.g; - tem.r = 0xff-kolor.r; - tem.unused = 0xff-kolor.unused; - temp = TTF_RenderText_Shaded(font,text.c_str(),kolor,tem); - break; - case 2: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - default: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - } - SDL_Rect dstRect = genRect(temp->h,temp->w,x,y); - CSDL_Ext::blitSurface(temp,NULL,dst,&dstRect); - if(refresh) - SDL_UpdateRect(dst,x,y,temp->w,temp->h); - SDL_FreeSurface(temp); -} - -void CSDL_Ext::printAt( const std::string & text, int dstX, int dstY, EFonts font, SDL_Color color, SDL_Surface * dst) -{ - if(!text.size()) - return; - - if (graphics->fontsTrueType[font]) - { - printAt(text,dstX, dstY, graphics->fontsTrueType[font], color, dst); - return; - } - - assert(dst); - assert(font < Graphics::FONTS_NUMBER); - - Rect clipRect; - SDL_GetClipRect(dst, &clipRect); - - const Font *f = graphics->fonts[font]; - const Uint8 bpp = dst->format->BytesPerPixel; - - TColorPutter colorPutter = getPutterFor(dst, 0); - - //if text is in {} braces, we'll ommit them - const int textBegin = (text[0] == '{' ? 1 : 0); - const int textEnd = (text[text.size()-1] == '}' ? text.size()-1 : text.size()); - - SDL_LockSurface(dst); - // for each symbol - for(int index = textBegin; index < textEnd; index++) - { - const ui8 symbol = text[index]; - dstX += f->chars[symbol].leftOffset; - - int lineBegin = std::max(0, clipRect.y - dstY); - int lineEnd = std::min(f->height, clipRect.y + clipRect.h - dstY - 1); - - int rowBegin = std::max(0, clipRect.x - dstX); - int rowEnd = std::min(f->chars[symbol].width, clipRect.x + clipRect.w - dstX - 1); - - //for each line in symbol - for(int dy = lineBegin; dy pixels; - Uint8 *srcLine = f->chars[symbol].pixels; - - dstLine += (dstY+dy) * dst->pitch + dstX * bpp; - srcLine += dy * f->chars[symbol].width; - - //for each column in line - for(int dx = rowBegin; dx < rowEnd; dx++) - { - Uint8* dstPixel = dstLine + dx*bpp; - switch(*(srcLine + dx)) - { - case 1: //black "shadow" - memset(dstPixel, 0, bpp); - break; - case 255: //text colour - colorPutter(dstPixel, color.r, color.g, color.b); - break; - } - } - } - - dstX += f->chars[symbol].width; - dstX += f->chars[symbol].rightOffset; - } - SDL_UnlockSurface(dst); -} - -void printTo(const std::string & text, int x, int y, TTF_Font * font, SDL_Color kolor, SDL_Surface * dst, ui8 quality=2) -{ - if (text.length()==0) - return; - SDL_Surface * temp; - switch (quality) - { - case 0: - temp = TTF_RenderText_Solid(font,text.c_str(),kolor); - break; - case 1: - SDL_Color tem; - tem.b = 0xff-kolor.b; - tem.g = 0xff-kolor.g; - tem.r = 0xff-kolor.r; - tem.unused = 0xff-kolor.unused; - temp = TTF_RenderText_Shaded(font,text.c_str(),kolor,tem); - break; - case 2: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - default: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - } - SDL_Rect dstRect = genRect(temp->h,temp->w,x-temp->w,y-temp->h); - CSDL_Ext::blitSurface(temp,NULL,dst,&dstRect); - SDL_UpdateRect(dst,x-temp->w,y-temp->h,temp->w,temp->h); - SDL_FreeSurface(temp); -} - -void CSDL_Ext::printTo( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ ) -{ - if (graphics->fontsTrueType[font]) - { - printTo(text,x, y, graphics->fontsTrueType[font], kolor, dst); - return; - } - const Font *f = graphics->fonts[font]; - printAt(text, x - f->getWidth(text.c_str()), y - f->height, font, kolor, dst); -} - -void printToWR(const std::string & text, int x, int y, TTF_Font * font, SDL_Color kolor, SDL_Surface * dst, ui8 quality=2) -{ - if (text.length()==0) - return; - SDL_Surface * temp; - switch (quality) - { - case 0: - temp = TTF_RenderText_Solid(font,text.c_str(),kolor); - break; - case 1: - SDL_Color tem; - tem.b = 0xff-kolor.b; - tem.g = 0xff-kolor.g; - tem.r = 0xff-kolor.r; - tem.unused = 0xff-kolor.unused; - temp = TTF_RenderText_Shaded(font,text.c_str(),kolor,tem); - break; - case 2: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - default: - temp = TTF_RenderText_Blended(font,text.c_str(),kolor); - break; - } - SDL_Rect dstRect = genRect(temp->h,temp->w,x-temp->w,y-temp->h); - CSDL_Ext::blitSurface(temp,NULL,dst,&dstRect); - SDL_FreeSurface(temp); -} - // Vertical flip SDL_Surface * CSDL_Ext::rotate01(SDL_Surface * toRot) { @@ -1282,23 +978,6 @@ void CSDL_Ext::fillTexture(SDL_Surface *dst, SDL_Surface * src) } } -std::string CSDL_Ext::trimToFit(std::string text, int widthLimit, EFonts font) -{ - int widthSoFar = 0; - for(auto i = text.begin(); i != text.end(); i++) - { - widthSoFar += graphics->fonts[font]->getCharWidth(*i); - if(widthSoFar > widthLimit) - { - //remove all characteres past limit - text.erase(i, text.end()); - break; - } - } - - return text; -} - template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<2>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<3>(int, int); template SDL_Surface * CSDL_Ext::createSurfaceWithBpp<4>(int, int); diff --git a/client/UIFramework/SDL_Extensions.h b/client/UIFramework/SDL_Extensions.h index f069c5b0c..67161ab04 100644 --- a/client/UIFramework/SDL_Extensions.h +++ b/client/UIFramework/SDL_Extensions.h @@ -3,7 +3,7 @@ #include #include #include "../../lib/int3.h" -#include "../FontBase.h" +#include "../Graphics.h" #include "Geometries.h" /* @@ -51,6 +51,9 @@ public: /** the metallic gold color used mostly as a border around buttons */ static const SDL_Color METALLIC_GOLD; + + /** green color used for in-game console */ + static const SDL_Color GREEN; }; //MSVC gives an error when calling abs with ui64 -> we add template that will match calls with unsigned arg and return it @@ -169,12 +172,6 @@ namespace CSDL_Ext int blit8bppAlphaTo24bpp(const SDL_Surface * src, const SDL_Rect * srcRect, SDL_Surface * dst, SDL_Rect * dstRect); //blits 8 bpp surface with alpha channel to 24 bpp surface Uint32 colorToUint32(const SDL_Color * color); //little endian only - void printAtWB(const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor=Colors::WHITE, SDL_Surface * dst=screen); - void printAt(const std::string & text, int x, int y, EFonts font, SDL_Color kolor=Colors::WHITE, SDL_Surface * dst=screen); - void printTo(const std::string & text, int x, int y, EFonts font, SDL_Color kolor=Colors::WHITE, SDL_Surface * dst=screen); - void printAtMiddle(const std::string & text, int x, int y, EFonts font, SDL_Color kolor=Colors::WHITE, SDL_Surface * dst=screen); - void printAtMiddleWB(const std::string & text, int x, int y, EFonts font, int charpr, SDL_Color kolor=Colors::YELLOW, SDL_Surface * dst=screen); - void update(SDL_Surface * what = screen); //updates whole surface (default - main screen) void drawBorder(SDL_Surface * sur, int x, int y, int w, int h, const int3 &color); void drawBorder(SDL_Surface * sur, const SDL_Rect &r, const int3 &color); @@ -196,6 +193,4 @@ namespace CSDL_Ext template void applyEffectBpp( SDL_Surface * surf, const SDL_Rect * rect, int mode ); void applyEffect(SDL_Surface * surf, const SDL_Rect * rect, int mode); //mode: 0 - sepia, 1 - grayscale - - std::string trimToFit(std::string text, int widthLimit, EFonts font); } diff --git a/config/fonts.json b/config/fonts.json new file mode 100644 index 000000000..af6be5050 --- /dev/null +++ b/config/fonts.json @@ -0,0 +1,37 @@ +{ + // Original HoMM 3 bitmap fonts + // Stored in H3Bitmap.lod with fnt extension + // Warning: Do not change number of entries in this list + "bitmap" : + [ + "BIGFONT", // Mostly used for window titles + "CALLI10R", // Unused in VCMI + "CREDITS", // Used for credits menu + "HISCORE", // Unused in VCMI + "MEDFONT", // Some titles + "SMALFONT", // Most of the messages + "TIMES08R", // Used to display amounts on creature card + "TINY", // Some text + "VERD10B" // Unused in VCMI + ], + + // True type replacements + // Should be in format: + // : + // "file" - file to load font from, must be in data/ directory + // "size" - point size of font + // "style" - italic and\or bold, indicates font style + // "blend" - if set to true, font will be antialiased + "trueType": + { + //"BIGFONT" : { "file" : "TimesNewRoman.ttf", "size" : 20, "style" : ["bold"], "blend" : true}, + //"CALLI10R" : { "file" : "Georgia.ttf", "size" : 10}, + //"CREDITS" : { "file" : "TimesNewRoman.ttf", "size" : 13, "blend" : true}, + //"HISCORE" : { "file" : "Georgia.ttf", "size" : 13}, + //"MEDFONT" : { "file" : "TimesNewRoman.ttf", "size" : 16, "blend" : true}, + //"SMALFONT" : { "file" : "Georgia.ttf", "size" : 13}, + //"TIMES08R" : { "file" : "TimesNewRoman.ttf", "size" : 8, "blend" : true}, + //"TINY" : { "file" : "Georgia.ttf", "size" : 10}, + //"VERD10B" : { "file" : "Georgia.ttf", "size" : 13} + } +} diff --git a/config/fonts.txt b/config/fonts.txt deleted file mode 100644 index 7ede8dd2f..000000000 --- a/config/fonts.txt +++ /dev/null @@ -1,29 +0,0 @@ -0 0 tnrb.ttf 20 -0 1 georgia.ttf 10 -0 2 tnrb.ttf 13 -0 3 georgia.ttf 13 -0 4 tnrb.ttf 16 -0 5 georgia.ttf 13 -0 6 tnrb.ttf 08 -0 7 georgia.ttf 10 -0 8 georgia.ttf 13 --1 - -Format: -1 = enabled, 0=disabled -font numeric ID -TTF font name -font size - - -ID of fonts: - -0 BIGFONT.FNT //windows titles -1 CALLI10R.FNT ? -2 CREDITS.FNT -3 HISCORE.FNT -4 MEDFONT.FNT //most used font -5 SMALFONT.FNT //most used font -6 TIMES08R.FNT ? -7 TINY.FNT //used for small texts -8 VERD10B.FNT ? diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index bd932c1c3..629a51e60 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -363,6 +363,8 @@ void CCreatureHandler::loadCreatures() } } + c->nameRef = creature["name"].Vector().at(0).String(); + /* A creature can have several names. */ BOOST_FOREACH(const JsonNode &name, creature["name"].Vector()) { diff --git a/lib/Filesystem/CResourceLoader.cpp b/lib/Filesystem/CResourceLoader.cpp index 12f058bd9..9727595f8 100644 --- a/lib/Filesystem/CResourceLoader.cpp +++ b/lib/Filesystem/CResourceLoader.cpp @@ -230,7 +230,8 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) (".MSG", EResType::MASK) (".H3C", EResType::CAMPAIGN) (".H3M", EResType::MAP) - (".FNT", EResType::FONT) + (".FNT", EResType::BMP_FONT) + (".TTF", EResType::TTF_FONT) (".BMP", EResType::IMAGE) (".JPG", EResType::IMAGE) (".PCX", EResType::IMAGE) @@ -241,7 +242,8 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) (".SMK", EResType::VIDEO) (".BIK", EResType::VIDEO) (".MJPG", EResType::VIDEO) - (".MPG", EResType::VIDEO) + (".MPG", EResType::VIDEO) + (".AVI", EResType::VIDEO) (".MP3", EResType::MUSIC) (".OGG", EResType::MUSIC) (".LOD", EResType::ARCHIVE_LOD) @@ -269,7 +271,8 @@ std::string EResTypeHelper::getEResTypeAsString(EResType::Type type) MAP_ENUM(MASK) MAP_ENUM(CAMPAIGN) MAP_ENUM(MAP) - MAP_ENUM(FONT) + MAP_ENUM(BMP_FONT) + MAP_ENUM(TTF_FONT) MAP_ENUM(IMAGE) MAP_ENUM(VIDEO) MAP_ENUM(SOUND) diff --git a/lib/Filesystem/CResourceLoader.h b/lib/Filesystem/CResourceLoader.h index bbd658147..cd1a46724 100644 --- a/lib/Filesystem/CResourceLoader.h +++ b/lib/Filesystem/CResourceLoader.h @@ -46,7 +46,8 @@ namespace EResType MASK, CAMPAIGN, MAP, - FONT, + BMP_FONT, + TTF_FONT, IMAGE, VIDEO, SOUND, diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index bf39c08bb..e0e068aba 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -1,1089 +1,1090 @@ -/* - * JsonNode.cpp, part of VCMI engine - * - * Authors: listed in file AUTHORS in main folder - * - * License: GNU General Public License v2.0 or later - * Full text of license available in license.txt file, in main folder - * - */ - -#include "StdInc.h" -#include "JsonNode.h" - -#include "HeroBonus.h" -#include "Filesystem/CResourceLoader.h" - -using namespace JsonDetail; - -static const JsonNode nullNode; - -JsonNode::JsonNode(JsonType Type): - type(DATA_NULL) -{ - setType(Type); -} - -JsonNode::JsonNode(const char *data, size_t datasize): - type(DATA_NULL) -{ - JsonParser parser(data, datasize, *this); - JsonValidator validator(*this); -} - -JsonNode::JsonNode(ResourceID && fileURI): - type(DATA_NULL) -{ - std::string filename = CResourceHandler::get()->getResourceName(fileURI); - FILE * file = fopen(filename.c_str(), "rb"); - if (!file) - { - tlog1 << "Failed to open file " << filename << "\n"; - perror("Last system error was "); - return; - } - - fseek(file, 0, SEEK_END); - size_t datasize = ftell(file); - fseek(file, 0, SEEK_SET); - - char *input = new char[datasize]; - datasize = fread((void*)input, 1, datasize, file); - fclose(file); - - JsonParser parser(input, datasize, *this); - JsonValidator validator(*this); - delete [] input; -} - -JsonNode::JsonNode(const JsonNode ©): - type(DATA_NULL) -{ - setType(copy.getType()); - switch(type) - { - break; case DATA_NULL: - break; case DATA_BOOL: Bool() = copy.Bool(); - break; case DATA_FLOAT: Float() = copy.Float(); - break; case DATA_STRING: String() = copy.String(); - break; case DATA_VECTOR: Vector() = copy.Vector(); - break; case DATA_STRUCT: Struct() = copy.Struct(); - } -} - -JsonNode::~JsonNode() -{ - setType(DATA_NULL); -} - -void JsonNode::swap(JsonNode &b) -{ - using std::swap; - swap(data, b.data); - swap(type, b.type); -} - -JsonNode & JsonNode::operator =(JsonNode node) -{ - swap(node); - return *this; -} - -bool JsonNode::operator == (const JsonNode &other) const -{ - if (getType() == other.getType()) - { - switch(type) - { - break; case DATA_NULL: return true; - break; case DATA_BOOL: return Bool() == other.Bool(); - break; case DATA_FLOAT: return Float() == other.Float(); - break; case DATA_STRING: return String() == other.String(); - break; case DATA_VECTOR: return Vector() == other.Vector(); - break; case DATA_STRUCT: return Struct() == other.Struct(); - } - } - return false; -} - -bool JsonNode::operator != (const JsonNode &other) const -{ - return !(*this == other); -} - -JsonNode::JsonType JsonNode::getType() const -{ - return type; -} - -void JsonNode::setType(JsonType Type) -{ - if (type == Type) - return; - - //Reset node to NULL - if (Type != DATA_NULL) - setType(DATA_NULL); - - switch (type) - { - break; case DATA_STRING: delete data.String; - break; case DATA_VECTOR: delete data.Vector; - break; case DATA_STRUCT: delete data.Struct; - break; default: - break; - } - //Set new node type - type = Type; - switch(type) - { - break; case DATA_NULL: - break; case DATA_BOOL: data.Bool = false; - break; case DATA_FLOAT: data.Float = 0; - break; case DATA_STRING: data.String = new std::string; - break; case DATA_VECTOR: data.Vector = new JsonVector; - break; case DATA_STRUCT: data.Struct = new JsonMap; - } -} - -bool JsonNode::isNull() const -{ - return type == DATA_NULL; -} - -bool & JsonNode::Bool() -{ - setType(DATA_BOOL); - return data.Bool; -} - -double & JsonNode::Float() -{ - setType(DATA_FLOAT); - return data.Float; -} - -std::string & JsonNode::String() -{ - setType(DATA_STRING); - return *data.String; -} - -JsonVector & JsonNode::Vector() -{ - setType(DATA_VECTOR); - return *data.Vector; -} - -JsonMap & JsonNode::Struct() -{ - setType(DATA_STRUCT); - return *data.Struct; -} - -const bool boolDefault = false; -const bool & JsonNode::Bool() const -{ - if (type == DATA_NULL) - return boolDefault; - assert(type == DATA_BOOL); - return data.Bool; -} - -const double floatDefault = 0; -const double & JsonNode::Float() const -{ - if (type == DATA_NULL) - return floatDefault; - assert(type == DATA_FLOAT); - return data.Float; -} - -const std::string stringDefault = std::string(); -const std::string & JsonNode::String() const -{ - if (type == DATA_NULL) - return stringDefault; - assert(type == DATA_STRING); - return *data.String; -} - -const JsonVector vectorDefault = JsonVector(); -const JsonVector & JsonNode::Vector() const -{ - if (type == DATA_NULL) - return vectorDefault; - assert(type == DATA_VECTOR); - return *data.Vector; -} - -const JsonMap mapDefault = JsonMap(); -const JsonMap & JsonNode::Struct() const -{ - if (type == DATA_NULL) - return mapDefault; - assert(type == DATA_STRUCT); - return *data.Struct; -} - -JsonNode & JsonNode::operator[](std::string child) -{ - return Struct()[child]; -} - -const JsonNode & JsonNode::operator[](std::string child) const -{ - JsonMap::const_iterator it = Struct().find(child); - if (it != Struct().end()) - return it->second; - return nullNode; -} -//////////////////////////////////////////////////////////////////////////////// - -template -void JsonWriter::writeContainer(Iterator begin, Iterator end) -{ - if (begin == end) - return; - - prefix += '\t'; - end--; - while (begin != end) - { - writeEntry(begin++); - out<<",\n"; - } - - writeEntry(begin); - out<<"\n"; - prefix.resize(prefix.size()-1); -} - -void JsonWriter::writeEntry(JsonMap::const_iterator entry) -{ - out << prefix; - writeString(entry->first); - out << " : "; - writeNode(entry->second); -} - -void JsonWriter::writeEntry(JsonVector::const_iterator entry) -{ - out << prefix; - writeNode(*entry); -} - -void JsonWriter::writeString(const std::string &string) -{ - static const std::string escaped = "\"\\/\b\f\n\r\t"; - - out <<'\"'; - size_t pos=0, start=0; - for (; pos= '0' && input[pos] <= '9') - return extractFloat(node); - return error("Value expected!"); - } - } -} - -bool JsonParser::extractWhitespace(bool verbose) -{ - while (true) - { - while (pos < input.size() && (ui8)input[pos] <= ' ') - { - if (input[pos] == '\n') - { - lineCount++; - lineStart = pos+1; - } - pos++; - } - if (pos >= input.size() || input[pos] != '/') - break; - - pos++; - if (pos == input.size()) - break; - if (input[pos] == '/') - pos++; - else - error("Comments must consist from two slashes!", true); - - while (pos < input.size() && input[pos] != '\n') - pos++; - } - - if (pos >= input.size() && verbose) - return error("Unexpected end of file!"); - return true; -} - -bool JsonParser::extractEscaping(std::string &str) -{ - switch(input[pos]) - { - break; case '\"': str += '\"'; - break; case '\\': str += '\\'; - break; case '/': str += '/'; - break; case 'b': str += '\b'; - break; case 'f': str += '\f'; - break; case 'n': str += '\n'; - break; case 'r': str += '\r'; - break; case 't': str += '\t'; - break; default: return error("Unknown escape sequence!", true); - }; - return true; -} - -bool JsonParser::extractString(std::string &str) -{ - if (input[pos] != '\"') - return error("String expected!"); - pos++; - - size_t first = pos; - - while (pos != input.size()) - { - if (input[pos] == '\"') // Correct end of string - { - str.append( &input[first], pos-first); - pos++; - return true; - } - if (input[pos] == '\\') // Escaping - { - str.append( &input[first], pos-first); - first = pos++; - if (pos == input.size()) - break; - extractEscaping(str); - } - if (input[pos] == '\n') // end-of-line - { - str.append( &input[first], pos-first); - return error("Closing quote not found!", true); - } - if ((unsigned char)(input[pos]) < ' ') // control character - { - str.append( &input[first], pos-first); - first = pos+1; - error("Illegal character in the string!", true); - } - pos++; - } - return error("Unterminated string!"); -} - -bool JsonParser::extractString(JsonNode &node) -{ - std::string str; - if (!extractString(str)) - return false; - - node.setType(JsonNode::DATA_STRING); - node.String() = str; - return true; -} - -bool JsonParser::extractLiteral(const std::string &literal) -{ - if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) - { - while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') - || (input[pos]>'A' && input[pos]<'Z'))) - pos++; - return error("Unknown literal found", true); - } - - pos += literal.size(); - return true; -} - -bool JsonParser::extractNull(JsonNode &node) -{ - if (!extractLiteral("null")) - return false; - - node.setType(JsonNode::DATA_NULL); - return true; -} - -bool JsonParser::extractTrue(JsonNode &node) -{ - if (!extractLiteral("true")) - return false; - - node.Bool() = true; - return true; -} - -bool JsonParser::extractFalse(JsonNode &node) -{ - if (!extractLiteral("false")) - return false; - - node.Bool() = false; - return true; -} - -bool JsonParser::extractStruct(JsonNode &node) -{ - node.setType(JsonNode::DATA_STRUCT); - pos++; - - if (!extractWhitespace()) - return false; - - //Empty struct found - if (input[pos] == '}') - { - pos++; - return true; - } - - while (true) - { - if (!extractWhitespace()) - return false; - - std::string key; - if (!extractString(key)) - return false; - - if (node.Struct().find(key) != node.Struct().end()) - error("Dublicated element encountered!", true); - - if (!extractSeparator()) - return false; - - if (!extractElement(node.Struct()[key], '}')) - return false; - - if (input[pos] == '}') - { - pos++; - return true; - } - } -} - -bool JsonParser::extractArray(JsonNode &node) -{ - pos++; - node.setType(JsonNode::DATA_VECTOR); - - if (!extractWhitespace()) - return false; - - //Empty array found - if (input[pos] == ']') - { - pos++; - return true; - } - - while (true) - { - //NOTE: currently 50% of time is this vector resizing. - //May be useful to use list during parsing and then swap() all items to vector - node.Vector().resize(node.Vector().size()+1); - - if (!extractElement(node.Vector().back(), ']')) - return false; - - if (input[pos] == ']') - { - pos++; - return true; - } - } -} - -bool JsonParser::extractElement(JsonNode &node, char terminator) -{ - if (!extractValue(node)) - return false; - - if (!extractWhitespace()) - return false; - - bool comma = (input[pos] == ','); - if (comma ) - { - pos++; - if (!extractWhitespace()) - return false; - } - - if (input[pos] == terminator) - return true; - - if (!comma) - error("Comma expected!", true); - - return true; -} - -bool JsonParser::extractFloat(JsonNode &node) -{ - assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); - bool negative=false; - double result=0; - - if (input[pos] == '-') - { - pos++; - negative = true; - } - - if (input[pos] < '0' || input[pos] > '9') - return error("Number expected!"); - - //Extract integer part - while (input[pos] >= '0' && input[pos] <= '9') - { - result = result*10+(input[pos]-'0'); - pos++; - } - - if (input[pos] == '.') - { - //extract fractional part - pos++; - double fractMult = 0.1; - if (input[pos] < '0' || input[pos] > '9') - return error("Decimal part expected!"); - - while (input[pos] >= '0' && input[pos] <= '9') - { - result = result + fractMult*(input[pos]-'0'); - fractMult /= 10; - pos++; - } - } - //TODO: exponential part - if (negative) - result = -result; - - node.setType(JsonNode::DATA_FLOAT); - node.Float() = result; - return true; -} - -bool JsonParser::error(const std::string &message, bool warning) -{ - std::ostringstream stream; - std::string type(warning?" warning: ":" error: "); - - stream << "At line " << lineCount << ", position "< stringToType = - boost::assign::map_list_of - ("null", JsonNode::DATA_NULL) ("bool", JsonNode::DATA_BOOL) - ("number", JsonNode::DATA_FLOAT) ("string", JsonNode::DATA_STRING) - ("array", JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT); - -//Check current schema entry for validness and converts "type" string to JsonType -bool JsonValidator::validateSchema(JsonNode::JsonType &type, const JsonNode &schema) -{ - if (schema.isNull()) - return addMessage("Missing schema for current entry!"); - - const JsonNode &nodeType = schema["type"]; - if (nodeType.isNull()) - return addMessage("Entry type is not defined in schema!"); - - if (nodeType.getType() != JsonNode::DATA_STRING) - return addMessage("Entry type must be string!"); - - std::map::const_iterator iter = stringToType.find(nodeType.String()); - - if (iter == stringToType.end()) - return addMessage("Unknown entry type found!"); - - type = iter->second; - return true; -} - -//Replaces node with default value if needed and calls type-specific validators -bool JsonValidator::validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type) -{ - if (node.isNull()) - { - const JsonNode & defaultValue = schema["default"]; - if (defaultValue.isNull()) - return addMessage("Null entry without default entry!"); - else - node = defaultValue; - } - if (minimize && node == schema["default"]) - { - node.setType(JsonNode::DATA_NULL); - return false; - } - - if (type != node.getType()) - { - node.setType(JsonNode::DATA_NULL); - return addMessage("Type mismatch!"); - } - - if (type == JsonNode::DATA_VECTOR) - return validateItems(node, schema["items"]); - - if (type == JsonNode::DATA_STRUCT) - return validateProperties(node, schema["properties"]); - - return true; -} - -// Basic checks common for any nodes -bool JsonValidator::validateNode(JsonNode &node, const JsonNode &schema, const std::string &name) -{ - currentPath.push_back(name); - - JsonNode::JsonType type = JsonNode::DATA_NULL; - if (!validateSchema(type, schema) - || !validateType(node, schema, type)) - { - node.setType(JsonNode::DATA_NULL); - currentPath.pop_back(); - return false; - } - currentPath.pop_back(); - return true; -} - -//Checks "items" entry from schema (type-specific check for Vector) -bool JsonValidator::validateItems(JsonNode &node, const JsonNode &schema) -{ - JsonNode::JsonType type = JsonNode::DATA_NULL; - if (!validateSchema(type, schema)) - return false; - - bool result = true; - BOOST_FOREACH(JsonNode &entry, node.Vector()) - { - if (!validateType(entry, schema, type)) - { - result = false; - entry.setType(JsonNode::DATA_NULL); - } - } - return result; -} - -//Checks "propertries" entry from schema (type-specific check for Struct) -//Function is similar to merging of two sorted lists - check every entry that present in one of the input nodes -bool JsonValidator::validateProperties(JsonNode &node, const JsonNode &schema) -{ - if (schema.isNull()) - return addMessage("Properties entry is missing for struct in schema"); - - JsonMap::iterator nodeIter = node.Struct().begin(); - JsonMap::const_iterator schemaIter = schema.Struct().begin(); - - while (nodeIter != node.Struct().end() && schemaIter != schema.Struct().end()) - { - if (nodeIter->first < schemaIter->first) //No schema for entry - { - validateNode(nodeIter->second, nullNode, nodeIter->first); - - JsonMap::iterator toRemove = nodeIter++; - node.Struct().erase(toRemove); - } - else - if (schemaIter->first < nodeIter->first) //No entry - { - if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) - node.Struct().erase(schemaIter->first); - schemaIter++; - } - else //both entry and schema are present - { - JsonMap::iterator current = nodeIter++; - if (!validateNode(current->second, schemaIter->second, current->first)) - node.Struct().erase(current); - - schemaIter++; - } - } - while (nodeIter != node.Struct().end()) - { - validateNode(nodeIter->second, nullNode, nodeIter->first); - JsonMap::iterator toRemove = nodeIter++; - node.Struct().erase(toRemove); - } - - while (schemaIter != schema.Struct().end()) - { - if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) - node.Struct().erase(schemaIter->first); - schemaIter++; - } - return true; -} - -bool JsonValidator::addMessage(const std::string &message) -{ - std::ostringstream stream; - - stream << "At "; - BOOST_FOREACH(const std::string &path, currentPath) - stream << path<<"/"; - stream << "\t Error: " << message <<"\n"; - errors += stream.str(); - return false; -} - -JsonValidator::JsonValidator(JsonNode &root, bool Minimize): - minimize(Minimize) -{ - if (root.getType() != JsonNode::DATA_STRUCT) - return; - - JsonNode schema; - schema.swap(root["schema"]); - root.Struct().erase("schema"); - - if (!schema.isNull()) - { - validateProperties(root, schema); - } - //This message is quite annoying now - most files do not have schemas. May be re-enabled later - //else - // addMessage("Schema not found!", true); - - //TODO: better way to show errors (like printing file name as well) - tlog3<type = it->second; - b->val = ability_vec[1].Float(); - b->subtype = ability_vec[2].Float(); - b->additionalInfo = ability_vec[3].Float(); - b->duration = Bonus::PERMANENT; //TODO: handle flags (as integer) - b->turnsRemain = 0; - return b; -} -template -const T & parseByMap(const std::map & map, const JsonNode * val, std::string err) -{ - static T defaultValue; - if (!val->isNull()) - { - std::string type = val->String(); - auto it = map.find(type); - if (it == map.end()) - { - tlog1 << "Error: invalid " << err << type << std::endl; - return defaultValue; - } - else - { - return it->second; - } - } - else - return defaultValue; -}; - -Bonus * JsonUtils::parseBonus (const JsonNode &ability) -{ - - Bonus * b = new Bonus(); - const JsonNode *value; - - std::string type = ability["type"].String(); - auto it = bonusNameMap.find(type); - if (it == bonusNameMap.end()) - { - tlog1 << "Error: invalid ability type " << type << std::endl; - return b; - } - b->type = it->second; - - value = &ability["subtype"]; - if (!value->isNull()) - b->subtype = value->Float(); - - value = &ability["val"]; - if (!value->isNull()) - b->val = value->Float(); - - value = &ability["valueType"]; - if (!value->isNull()) - b->valType = parseByMap(bonusValueMap, value, "value type "); - - value = &ability["additionalInfo"]; - if (!value->isNull()) - b->additionalInfo = value->Float(); - - value = &ability["turns"]; - if (!value->isNull()) - b->turnsRemain = value->Float(); - - value = &ability["sourceID"]; - if (!value->isNull()) - b->sid = value->Float(); - - value = &ability["description"]; - if (!value->isNull()) - b->description = value->String(); - - value = &ability["effectRange"]; - if (!value->isNull()) - b->valType = parseByMap(bonusLimitEffect, value, "effect range "); - value = &ability["duration"]; - if (!value->isNull()) - b->valType = parseByMap(bonusDurationMap, value, "duration type "); - value = &ability["source"]; - if (!value->isNull()) - b->valType = parseByMap(bonusSourceMap, value, "source type "); - - value = &ability["limiter"]; - if (!value->isNull()) - b->limiter = parseByMap(bonusLimiterMap, value, "limiter type "); - - value = &ability["propagator"]; - if (!value->isNull()) - b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); - - return b; -} - -//returns first Key with value equal to given one -template -Key reverseMapFirst(const Val & val, const std::map map) -{ - BOOST_FOREACH(auto it, map) - { - if(it.second == val) - { - return it.first; - } - } - assert(0); - return ""; -} - -void JsonUtils::unparseBonus( JsonNode &node, const Bonus * bonus ) -{ - node["type"].String() = reverseMapFirst(bonus->type, bonusNameMap); - node["subtype"].Float() = bonus->subtype; - node["val"].Float() = bonus->val; - node["valueType"].String() = reverseMapFirst(bonus->valType, bonusValueMap); - node["additionalInfo"].Float() = bonus->additionalInfo; - node["turns"].Float() = bonus->turnsRemain; - node["sourceID"].Float() = bonus->source; - node["description"].String() = bonus->description; - node["effectRange"].String() = reverseMapFirst(bonus->effectRange, bonusLimitEffect); - node["duration"].String() = reverseMapFirst(bonus->duration, bonusDurationMap); - node["source"].String() = reverseMapFirst(bonus->source, bonusSourceMap); - if(bonus->limiter) - { - node["limiter"].String() = reverseMapFirst(bonus->limiter, bonusLimiterMap); - } - if(bonus->propagator) - { - node["propagator"].String() = reverseMapFirst(bonus->propagator, bonusPropagatorMap); - } -} - -void JsonUtils::minimize(JsonNode & node, const JsonNode& schema) -{ - JsonValidator validator(node, schema, true); -} - -void JsonUtils::validate(JsonNode & node, const JsonNode& schema) -{ - JsonValidator validator(node, schema, false); -} - -void JsonUtils::merge(JsonNode & dest, JsonNode & source) -{ - if (dest.getType() == JsonNode::DATA_NULL) - { - std::swap(dest, source); - return; - } - - switch (source.getType()) - { - break; case JsonNode::DATA_NULL: dest.setType(JsonNode::DATA_NULL); - break; case JsonNode::DATA_BOOL: std::swap(dest.Bool(), source.Bool()); - break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float()); - break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); - break; case JsonNode::DATA_VECTOR: - { - size_t total = std::min(source.Vector().size(), dest.Vector().size()); - - for (size_t i=0; i< total; i++) - merge(dest.Vector()[i], source.Vector()[i]); - - if (source.Vector().size() < dest.Vector().size()) - { - //reserve place and *move* data from source to dest - source.Vector().reserve(source.Vector().size() + dest.Vector().size()); - - std::move(source.Vector().begin(), source.Vector().end(), - std::back_inserter(dest.Vector())); - } - } - break; case JsonNode::DATA_STRUCT: - { - //recursively merge all entries from struct - BOOST_FOREACH(auto & node, source.Struct()) - merge(dest[node.first], node.second); - } - } -} - -void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) -{ - // uses copy created in stack to safely merge two nodes - merge(dest, source); -} - -JsonNode JsonUtils::assembleFromFiles(std::vector files) -{ - JsonNode result; - - BOOST_FOREACH(std::string file, files) - { - JsonNode section(ResourceID(file, EResType::TEXT)); - merge(result, section); - } - return result; -} +/* + * JsonNode.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "JsonNode.h" + +#include "HeroBonus.h" +#include "Filesystem/CResourceLoader.h" + +using namespace JsonDetail; + +static const JsonNode nullNode; + +JsonNode::JsonNode(JsonType Type): + type(DATA_NULL) +{ + setType(Type); +} + +JsonNode::JsonNode(const char *data, size_t datasize): + type(DATA_NULL) +{ + JsonParser parser(data, datasize, *this); + JsonValidator validator(*this); +} + +JsonNode::JsonNode(ResourceID && fileURI): + type(DATA_NULL) +{ + std::string filename = CResourceHandler::get()->getResourceName(fileURI); + FILE * file = fopen(filename.c_str(), "rb"); + if (!file) + { + tlog1 << "Failed to open file " << filename << "\n"; + perror("Last system error was "); + return; + } + + fseek(file, 0, SEEK_END); + size_t datasize = ftell(file); + fseek(file, 0, SEEK_SET); + + char *input = new char[datasize]; + datasize = fread((void*)input, 1, datasize, file); + fclose(file); + + JsonParser parser(input, datasize, *this); + JsonValidator validator(*this); + delete [] input; +} + +JsonNode::JsonNode(const JsonNode ©): + type(DATA_NULL) +{ + setType(copy.getType()); + switch(type) + { + break; case DATA_NULL: + break; case DATA_BOOL: Bool() = copy.Bool(); + break; case DATA_FLOAT: Float() = copy.Float(); + break; case DATA_STRING: String() = copy.String(); + break; case DATA_VECTOR: Vector() = copy.Vector(); + break; case DATA_STRUCT: Struct() = copy.Struct(); + } +} + +JsonNode::~JsonNode() +{ + setType(DATA_NULL); +} + +void JsonNode::swap(JsonNode &b) +{ + using std::swap; + swap(data, b.data); + swap(type, b.type); +} + +JsonNode & JsonNode::operator =(JsonNode node) +{ + swap(node); + return *this; +} + +bool JsonNode::operator == (const JsonNode &other) const +{ + if (getType() == other.getType()) + { + switch(type) + { + break; case DATA_NULL: return true; + break; case DATA_BOOL: return Bool() == other.Bool(); + break; case DATA_FLOAT: return Float() == other.Float(); + break; case DATA_STRING: return String() == other.String(); + break; case DATA_VECTOR: return Vector() == other.Vector(); + break; case DATA_STRUCT: return Struct() == other.Struct(); + } + } + return false; +} + +bool JsonNode::operator != (const JsonNode &other) const +{ + return !(*this == other); +} + +JsonNode::JsonType JsonNode::getType() const +{ + return type; +} + +void JsonNode::setType(JsonType Type) +{ + if (type == Type) + return; + + //Reset node to NULL + if (Type != DATA_NULL) + setType(DATA_NULL); + + switch (type) + { + break; case DATA_STRING: delete data.String; + break; case DATA_VECTOR: delete data.Vector; + break; case DATA_STRUCT: delete data.Struct; + break; default: + break; + } + //Set new node type + type = Type; + switch(type) + { + break; case DATA_NULL: + break; case DATA_BOOL: data.Bool = false; + break; case DATA_FLOAT: data.Float = 0; + break; case DATA_STRING: data.String = new std::string; + break; case DATA_VECTOR: data.Vector = new JsonVector; + break; case DATA_STRUCT: data.Struct = new JsonMap; + } +} + +bool JsonNode::isNull() const +{ + return type == DATA_NULL; +} + +bool & JsonNode::Bool() +{ + setType(DATA_BOOL); + return data.Bool; +} + +double & JsonNode::Float() +{ + setType(DATA_FLOAT); + return data.Float; +} + +std::string & JsonNode::String() +{ + setType(DATA_STRING); + return *data.String; +} + +JsonVector & JsonNode::Vector() +{ + setType(DATA_VECTOR); + return *data.Vector; +} + +JsonMap & JsonNode::Struct() +{ + setType(DATA_STRUCT); + return *data.Struct; +} + +const bool boolDefault = false; +const bool & JsonNode::Bool() const +{ + if (type == DATA_NULL) + return boolDefault; + assert(type == DATA_BOOL); + return data.Bool; +} + +const double floatDefault = 0; +const double & JsonNode::Float() const +{ + if (type == DATA_NULL) + return floatDefault; + assert(type == DATA_FLOAT); + return data.Float; +} + +const std::string stringDefault = std::string(); +const std::string & JsonNode::String() const +{ + if (type == DATA_NULL) + return stringDefault; + assert(type == DATA_STRING); + return *data.String; +} + +const JsonVector vectorDefault = JsonVector(); +const JsonVector & JsonNode::Vector() const +{ + if (type == DATA_NULL) + return vectorDefault; + assert(type == DATA_VECTOR); + return *data.Vector; +} + +const JsonMap mapDefault = JsonMap(); +const JsonMap & JsonNode::Struct() const +{ + if (type == DATA_NULL) + return mapDefault; + assert(type == DATA_STRUCT); + return *data.Struct; +} + +JsonNode & JsonNode::operator[](std::string child) +{ + return Struct()[child]; +} + +const JsonNode & JsonNode::operator[](std::string child) const +{ + JsonMap::const_iterator it = Struct().find(child); + if (it != Struct().end()) + return it->second; + return nullNode; +} +//////////////////////////////////////////////////////////////////////////////// + +template +void JsonWriter::writeContainer(Iterator begin, Iterator end) +{ + if (begin == end) + return; + + prefix += '\t'; + end--; + while (begin != end) + { + writeEntry(begin++); + out<<",\n"; + } + + writeEntry(begin); + out<<"\n"; + prefix.resize(prefix.size()-1); +} + +void JsonWriter::writeEntry(JsonMap::const_iterator entry) +{ + out << prefix; + writeString(entry->first); + out << " : "; + writeNode(entry->second); +} + +void JsonWriter::writeEntry(JsonVector::const_iterator entry) +{ + out << prefix; + writeNode(*entry); +} + +void JsonWriter::writeString(const std::string &string) +{ + static const std::string escaped = "\"\\/\b\f\n\r\t"; + + out <<'\"'; + size_t pos=0, start=0; + for (; pos= '0' && input[pos] <= '9') + return extractFloat(node); + return error("Value expected!"); + } + } +} + +bool JsonParser::extractWhitespace(bool verbose) +{ + while (true) + { + while (pos < input.size() && (ui8)input[pos] <= ' ') + { + if (input[pos] == '\n') + { + lineCount++; + lineStart = pos+1; + } + pos++; + } + if (pos >= input.size() || input[pos] != '/') + break; + + pos++; + if (pos == input.size()) + break; + if (input[pos] == '/') + pos++; + else + error("Comments must consist from two slashes!", true); + + while (pos < input.size() && input[pos] != '\n') + pos++; + } + + if (pos >= input.size() && verbose) + return error("Unexpected end of file!"); + return true; +} + +bool JsonParser::extractEscaping(std::string &str) +{ + switch(input[pos]) + { + break; case '\"': str += '\"'; + break; case '\\': str += '\\'; + break; case '/': str += '/'; + break; case 'b': str += '\b'; + break; case 'f': str += '\f'; + break; case 'n': str += '\n'; + break; case 'r': str += '\r'; + break; case 't': str += '\t'; + break; default: return error("Unknown escape sequence!", true); + }; + return true; +} + +bool JsonParser::extractString(std::string &str) +{ + if (input[pos] != '\"') + return error("String expected!"); + pos++; + + size_t first = pos; + + while (pos != input.size()) + { + if (input[pos] == '\"') // Correct end of string + { + str.append( &input[first], pos-first); + pos++; + return true; + } + if (input[pos] == '\\') // Escaping + { + str.append( &input[first], pos-first); + pos++; + if (pos == input.size()) + break; + extractEscaping(str); + first = pos + 1; + } + if (input[pos] == '\n') // end-of-line + { + str.append( &input[first], pos-first); + return error("Closing quote not found!", true); + } + if ((unsigned char)(input[pos]) < ' ') // control character + { + str.append( &input[first], pos-first); + first = pos+1; + error("Illegal character in the string!", true); + } + pos++; + } + return error("Unterminated string!"); +} + +bool JsonParser::extractString(JsonNode &node) +{ + std::string str; + if (!extractString(str)) + return false; + + node.setType(JsonNode::DATA_STRING); + node.String() = str; + return true; +} + +bool JsonParser::extractLiteral(const std::string &literal) +{ + if (literal.compare(0, literal.size(), &input[pos], literal.size()) != 0) + { + while (pos < input.size() && ((input[pos]>'a' && input[pos]<'z') + || (input[pos]>'A' && input[pos]<'Z'))) + pos++; + return error("Unknown literal found", true); + } + + pos += literal.size(); + return true; +} + +bool JsonParser::extractNull(JsonNode &node) +{ + if (!extractLiteral("null")) + return false; + + node.setType(JsonNode::DATA_NULL); + return true; +} + +bool JsonParser::extractTrue(JsonNode &node) +{ + if (!extractLiteral("true")) + return false; + + node.Bool() = true; + return true; +} + +bool JsonParser::extractFalse(JsonNode &node) +{ + if (!extractLiteral("false")) + return false; + + node.Bool() = false; + return true; +} + +bool JsonParser::extractStruct(JsonNode &node) +{ + node.setType(JsonNode::DATA_STRUCT); + pos++; + + if (!extractWhitespace()) + return false; + + //Empty struct found + if (input[pos] == '}') + { + pos++; + return true; + } + + while (true) + { + if (!extractWhitespace()) + return false; + + std::string key; + if (!extractString(key)) + return false; + + if (node.Struct().find(key) != node.Struct().end()) + error("Dublicated element encountered!", true); + + if (!extractSeparator()) + return false; + + if (!extractElement(node.Struct()[key], '}')) + return false; + + if (input[pos] == '}') + { + pos++; + return true; + } + } +} + +bool JsonParser::extractArray(JsonNode &node) +{ + pos++; + node.setType(JsonNode::DATA_VECTOR); + + if (!extractWhitespace()) + return false; + + //Empty array found + if (input[pos] == ']') + { + pos++; + return true; + } + + while (true) + { + //NOTE: currently 50% of time is this vector resizing. + //May be useful to use list during parsing and then swap() all items to vector + node.Vector().resize(node.Vector().size()+1); + + if (!extractElement(node.Vector().back(), ']')) + return false; + + if (input[pos] == ']') + { + pos++; + return true; + } + } +} + +bool JsonParser::extractElement(JsonNode &node, char terminator) +{ + if (!extractValue(node)) + return false; + + if (!extractWhitespace()) + return false; + + bool comma = (input[pos] == ','); + if (comma ) + { + pos++; + if (!extractWhitespace()) + return false; + } + + if (input[pos] == terminator) + return true; + + if (!comma) + error("Comma expected!", true); + + return true; +} + +bool JsonParser::extractFloat(JsonNode &node) +{ + assert(input[pos] == '-' || (input[pos] >= '0' && input[pos] <= '9')); + bool negative=false; + double result=0; + + if (input[pos] == '-') + { + pos++; + negative = true; + } + + if (input[pos] < '0' || input[pos] > '9') + return error("Number expected!"); + + //Extract integer part + while (input[pos] >= '0' && input[pos] <= '9') + { + result = result*10+(input[pos]-'0'); + pos++; + } + + if (input[pos] == '.') + { + //extract fractional part + pos++; + double fractMult = 0.1; + if (input[pos] < '0' || input[pos] > '9') + return error("Decimal part expected!"); + + while (input[pos] >= '0' && input[pos] <= '9') + { + result = result + fractMult*(input[pos]-'0'); + fractMult /= 10; + pos++; + } + } + //TODO: exponential part + if (negative) + result = -result; + + node.setType(JsonNode::DATA_FLOAT); + node.Float() = result; + return true; +} + +bool JsonParser::error(const std::string &message, bool warning) +{ + std::ostringstream stream; + std::string type(warning?" warning: ":" error: "); + + stream << "At line " << lineCount << ", position "< stringToType = + boost::assign::map_list_of + ("null", JsonNode::DATA_NULL) ("bool", JsonNode::DATA_BOOL) + ("number", JsonNode::DATA_FLOAT) ("string", JsonNode::DATA_STRING) + ("array", JsonNode::DATA_VECTOR) ("object", JsonNode::DATA_STRUCT); + +//Check current schema entry for validness and converts "type" string to JsonType +bool JsonValidator::validateSchema(JsonNode::JsonType &type, const JsonNode &schema) +{ + if (schema.isNull()) + return addMessage("Missing schema for current entry!"); + + const JsonNode &nodeType = schema["type"]; + if (nodeType.isNull()) + return addMessage("Entry type is not defined in schema!"); + + if (nodeType.getType() != JsonNode::DATA_STRING) + return addMessage("Entry type must be string!"); + + std::map::const_iterator iter = stringToType.find(nodeType.String()); + + if (iter == stringToType.end()) + return addMessage("Unknown entry type found!"); + + type = iter->second; + return true; +} + +//Replaces node with default value if needed and calls type-specific validators +bool JsonValidator::validateType(JsonNode &node, const JsonNode &schema, JsonNode::JsonType type) +{ + if (node.isNull()) + { + const JsonNode & defaultValue = schema["default"]; + if (defaultValue.isNull()) + return addMessage("Null entry without default entry!"); + else + node = defaultValue; + } + if (minimize && node == schema["default"]) + { + node.setType(JsonNode::DATA_NULL); + return false; + } + + if (type != node.getType()) + { + node.setType(JsonNode::DATA_NULL); + return addMessage("Type mismatch!"); + } + + if (type == JsonNode::DATA_VECTOR) + return validateItems(node, schema["items"]); + + if (type == JsonNode::DATA_STRUCT) + return validateProperties(node, schema["properties"]); + + return true; +} + +// Basic checks common for any nodes +bool JsonValidator::validateNode(JsonNode &node, const JsonNode &schema, const std::string &name) +{ + currentPath.push_back(name); + + JsonNode::JsonType type = JsonNode::DATA_NULL; + if (!validateSchema(type, schema) + || !validateType(node, schema, type)) + { + node.setType(JsonNode::DATA_NULL); + currentPath.pop_back(); + return false; + } + currentPath.pop_back(); + return true; +} + +//Checks "items" entry from schema (type-specific check for Vector) +bool JsonValidator::validateItems(JsonNode &node, const JsonNode &schema) +{ + JsonNode::JsonType type = JsonNode::DATA_NULL; + if (!validateSchema(type, schema)) + return false; + + bool result = true; + BOOST_FOREACH(JsonNode &entry, node.Vector()) + { + if (!validateType(entry, schema, type)) + { + result = false; + entry.setType(JsonNode::DATA_NULL); + } + } + return result; +} + +//Checks "propertries" entry from schema (type-specific check for Struct) +//Function is similar to merging of two sorted lists - check every entry that present in one of the input nodes +bool JsonValidator::validateProperties(JsonNode &node, const JsonNode &schema) +{ + if (schema.isNull()) + return addMessage("Properties entry is missing for struct in schema"); + + JsonMap::iterator nodeIter = node.Struct().begin(); + JsonMap::const_iterator schemaIter = schema.Struct().begin(); + + while (nodeIter != node.Struct().end() && schemaIter != schema.Struct().end()) + { + if (nodeIter->first < schemaIter->first) //No schema for entry + { + validateNode(nodeIter->second, nullNode, nodeIter->first); + + JsonMap::iterator toRemove = nodeIter++; + node.Struct().erase(toRemove); + } + else + if (schemaIter->first < nodeIter->first) //No entry + { + if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) + node.Struct().erase(schemaIter->first); + schemaIter++; + } + else //both entry and schema are present + { + JsonMap::iterator current = nodeIter++; + if (!validateNode(current->second, schemaIter->second, current->first)) + node.Struct().erase(current); + + schemaIter++; + } + } + while (nodeIter != node.Struct().end()) + { + validateNode(nodeIter->second, nullNode, nodeIter->first); + JsonMap::iterator toRemove = nodeIter++; + node.Struct().erase(toRemove); + } + + while (schemaIter != schema.Struct().end()) + { + if (!validateNode(node[schemaIter->first], schemaIter->second, schemaIter->first)) + node.Struct().erase(schemaIter->first); + schemaIter++; + } + return true; +} + +bool JsonValidator::addMessage(const std::string &message) +{ + std::ostringstream stream; + + stream << "At "; + BOOST_FOREACH(const std::string &path, currentPath) + stream << path<<"/"; + stream << "\t Error: " << message <<"\n"; + errors += stream.str(); + return false; +} + +JsonValidator::JsonValidator(JsonNode &root, bool Minimize): + minimize(Minimize) +{ + if (root.getType() != JsonNode::DATA_STRUCT) + return; + + JsonNode schema; + schema.swap(root["schema"]); + root.Struct().erase("schema"); + + if (!schema.isNull()) + { + validateProperties(root, schema); + } + //This message is quite annoying now - most files do not have schemas. May be re-enabled later + //else + // addMessage("Schema not found!", true); + + //TODO: better way to show errors (like printing file name as well) + tlog3<type = it->second; + b->val = ability_vec[1].Float(); + b->subtype = ability_vec[2].Float(); + b->additionalInfo = ability_vec[3].Float(); + b->duration = Bonus::PERMANENT; //TODO: handle flags (as integer) + b->turnsRemain = 0; + return b; +} +template +const T & parseByMap(const std::map & map, const JsonNode * val, std::string err) +{ + static T defaultValue; + if (!val->isNull()) + { + std::string type = val->String(); + auto it = map.find(type); + if (it == map.end()) + { + tlog1 << "Error: invalid " << err << type << std::endl; + return defaultValue; + } + else + { + return it->second; + } + } + else + return defaultValue; +}; + +Bonus * JsonUtils::parseBonus (const JsonNode &ability) +{ + + Bonus * b = new Bonus(); + const JsonNode *value; + + std::string type = ability["type"].String(); + auto it = bonusNameMap.find(type); + if (it == bonusNameMap.end()) + { + tlog1 << "Error: invalid ability type " << type << std::endl; + return b; + } + b->type = it->second; + + value = &ability["subtype"]; + if (!value->isNull()) + b->subtype = value->Float(); + + value = &ability["val"]; + if (!value->isNull()) + b->val = value->Float(); + + value = &ability["valueType"]; + if (!value->isNull()) + b->valType = parseByMap(bonusValueMap, value, "value type "); + + value = &ability["additionalInfo"]; + if (!value->isNull()) + b->additionalInfo = value->Float(); + + value = &ability["turns"]; + if (!value->isNull()) + b->turnsRemain = value->Float(); + + value = &ability["sourceID"]; + if (!value->isNull()) + b->sid = value->Float(); + + value = &ability["description"]; + if (!value->isNull()) + b->description = value->String(); + + value = &ability["effectRange"]; + if (!value->isNull()) + b->valType = parseByMap(bonusLimitEffect, value, "effect range "); + value = &ability["duration"]; + if (!value->isNull()) + b->valType = parseByMap(bonusDurationMap, value, "duration type "); + value = &ability["source"]; + if (!value->isNull()) + b->valType = parseByMap(bonusSourceMap, value, "source type "); + + value = &ability["limiter"]; + if (!value->isNull()) + b->limiter = parseByMap(bonusLimiterMap, value, "limiter type "); + + value = &ability["propagator"]; + if (!value->isNull()) + b->propagator = parseByMap(bonusPropagatorMap, value, "propagator type "); + + return b; +} + +//returns first Key with value equal to given one +template +Key reverseMapFirst(const Val & val, const std::map map) +{ + BOOST_FOREACH(auto it, map) + { + if(it.second == val) + { + return it.first; + } + } + assert(0); + return ""; +} + +void JsonUtils::unparseBonus( JsonNode &node, const Bonus * bonus ) +{ + node["type"].String() = reverseMapFirst(bonus->type, bonusNameMap); + node["subtype"].Float() = bonus->subtype; + node["val"].Float() = bonus->val; + node["valueType"].String() = reverseMapFirst(bonus->valType, bonusValueMap); + node["additionalInfo"].Float() = bonus->additionalInfo; + node["turns"].Float() = bonus->turnsRemain; + node["sourceID"].Float() = bonus->source; + node["description"].String() = bonus->description; + node["effectRange"].String() = reverseMapFirst(bonus->effectRange, bonusLimitEffect); + node["duration"].String() = reverseMapFirst(bonus->duration, bonusDurationMap); + node["source"].String() = reverseMapFirst(bonus->source, bonusSourceMap); + if(bonus->limiter) + { + node["limiter"].String() = reverseMapFirst(bonus->limiter, bonusLimiterMap); + } + if(bonus->propagator) + { + node["propagator"].String() = reverseMapFirst(bonus->propagator, bonusPropagatorMap); + } +} + +void JsonUtils::minimize(JsonNode & node, const JsonNode& schema) +{ + JsonValidator validator(node, schema, true); +} + +void JsonUtils::validate(JsonNode & node, const JsonNode& schema) +{ + JsonValidator validator(node, schema, false); +} + +void JsonUtils::merge(JsonNode & dest, JsonNode & source) +{ + if (dest.getType() == JsonNode::DATA_NULL) + { + std::swap(dest, source); + return; + } + + switch (source.getType()) + { + break; case JsonNode::DATA_NULL: dest.setType(JsonNode::DATA_NULL); + break; case JsonNode::DATA_BOOL: std::swap(dest.Bool(), source.Bool()); + break; case JsonNode::DATA_FLOAT: std::swap(dest.Float(), source.Float()); + break; case JsonNode::DATA_STRING: std::swap(dest.String(), source.String()); + break; case JsonNode::DATA_VECTOR: + { + size_t total = std::min(source.Vector().size(), dest.Vector().size()); + + for (size_t i=0; i< total; i++) + merge(dest.Vector()[i], source.Vector()[i]); + + if (source.Vector().size() < dest.Vector().size()) + { + //reserve place and *move* data from source to dest + source.Vector().reserve(source.Vector().size() + dest.Vector().size()); + + std::move(source.Vector().begin(), source.Vector().end(), + std::back_inserter(dest.Vector())); + } + } + break; case JsonNode::DATA_STRUCT: + { + //recursively merge all entries from struct + BOOST_FOREACH(auto & node, source.Struct()) + merge(dest[node.first], node.second); + } + } +} + +void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) +{ + // uses copy created in stack to safely merge two nodes + merge(dest, source); +} + +JsonNode JsonUtils::assembleFromFiles(std::vector files) +{ + JsonNode result; + + BOOST_FOREACH(std::string file, files) + { + JsonNode section(ResourceID(file, EResType::TEXT)); + merge(result, section); + } + return result; +}