1 /*
2  * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3  *
4  * This file is part of Arx Libertatis.
5  *
6  * Arx Libertatis is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Arx Libertatis is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Arx Libertatis.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 /* Based on:
20 ===========================================================================
21 ARX FATALIS GPL Source Code
22 Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
23 
24 This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
25 
26 Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
27 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
28 
29 Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
30 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
31 
32 You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code.  If not, see
33 <http://www.gnu.org/licenses/>.
34 
35 In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
36 additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
37 Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
38 
39 If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
40 ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
41 ===========================================================================
42 */
43 // Copyright (c) 1999-2000 ARKANE Studios SA. All rights reserved
44 
45 #include "gui/Text.h"
46 
47 #include <sstream>
48 
49 #include "core/Localisation.h"
50 #include "core/Config.h"
51 #include "core/Core.h"
52 
53 #include "gui/Interface.h"
54 #include "gui/TextManager.h"
55 
56 #include "graphics/Renderer.h"
57 #include "graphics/Math.h"
58 #include "graphics/font/FontCache.h"
59 
60 #include "io/resource/PakReader.h"
61 #include "io/resource/ResourcePath.h"
62 #include "io/log/Logger.h"
63 
64 using std::string;
65 
66 TextManager * pTextManage = NULL;
67 TextManager * pTextManageFlyingOver = NULL;
68 
69 Font * hFontInBook = NULL;
70 Font * hFontMainMenu = NULL;
71 Font * hFontMenu = NULL;
72 Font * hFontControls = NULL;
73 Font * hFontCredits = NULL;
74 Font * hFontInGame = NULL;
75 Font * hFontInGameNote = NULL;
76 
ARX_UNICODE_FormattingInRect(Font * font,const std::string & text,const Rect & rect,Color col,long * textHeight=0,long * numChars=0,bool computeOnly=false)77 void ARX_UNICODE_FormattingInRect(Font * font, const std::string & text,
78                                   const Rect & rect, Color col, long * textHeight = 0,
79                                   long * numChars = 0, bool computeOnly = false) {
80 
81 	std::string::const_iterator itLastLineBreak = text.begin();
82 	std::string::const_iterator itLastWordBreak = text.begin();
83 	std::string::const_iterator it = text.begin();
84 
85 	int maxLineWidth;
86 	if(rect.right == Rect::Limits::max()) {
87 		maxLineWidth = std::numeric_limits<int>::max();
88 	} else {
89 		maxLineWidth = rect.width();
90 	}
91 	arx_assert(maxLineWidth > 0);
92 	int penY = rect.top;
93 
94 	if(textHeight) {
95 		*textHeight = 0;
96 	}
97 
98 	if(numChars) {
99 		*numChars = 0;
100 	}
101 
102 	// Ensure we can at least draw one line...
103 	if(penY + font->getLineHeight() > rect.bottom) {
104 		return;
105 	}
106 
107 	for(it = text.begin(); it != text.end(); ++it) {
108 
109 		// Line break ?
110 		bool isLineBreak = false;
111 		if(*it == '\n' || *it == '*') {
112 			isLineBreak = true;
113 		} else {
114 
115 			// Word break ?
116 			if(*it == ' ' || *it == '\t') {
117 				itLastWordBreak = it;
118 			}
119 
120 			// Check length of string up to this point
121 			Vec2i size = font->getTextSize(itLastLineBreak, it + 1);
122 			if(size.x > maxLineWidth) { // Too long ?
123 				isLineBreak = true;
124 				if(itLastWordBreak > itLastLineBreak) {
125 					// Draw a line from the last line break up to the last word break
126 					it = itLastWordBreak;
127 				} else if(it == itLastLineBreak) {
128 					// Not enough space to render even one character!
129 					break;
130 				} else {
131 					// The current word is too long to fit on a line, force a line break
132 				}
133 			}
134 		}
135 
136 		// If we have to draw a line
137 		//  OR
138 		// This is the last character of the string
139 		if(isLineBreak || it + 1 == text.end()) {
140 
141 			std::string::const_iterator itTextStart = itLastLineBreak;
142 			std::string::const_iterator itTextEnd;
143 
144 			itTextEnd = (isLineBreak) ? it : it + 1;
145 
146 			// Draw the line
147 			if(!computeOnly) {
148 				font->draw(rect.left, penY, itTextStart, itTextEnd, col);
149 			}
150 
151 			if(it != text.end()) {
152 				itLastLineBreak = it + 1;
153 			}
154 
155 			penY += font->getLineHeight();
156 
157 			// Validate that the new line will fit inside the rect...
158 			if(penY + font->getLineHeight() > rect.bottom) {
159 				break;
160 			}
161 		}
162 	}
163 
164 	// Return text height
165 	if(textHeight) {
166 		*textHeight = penY - rect.top;
167 	}
168 
169 	// Return num characters displayed
170 	if(numChars) {
171 		*numChars = it - text.begin();
172 	}
173 }
174 
ARX_UNICODE_ForceFormattingInRect(Font * font,const string & text,const Rect & rect)175 long ARX_UNICODE_ForceFormattingInRect(Font * font, const string & text,
176                                        const Rect & rect) {
177 	long numChars;
178 	ARX_UNICODE_FormattingInRect(font, text, rect, Color::none, 0, &numChars, true);
179 	return numChars;
180 }
181 
182 //-----------------------------------------------------------------------------
ARX_UNICODE_DrawTextInRect(Font * font,float x,float y,float maxx,const std::string & _text,Color col,const Rect * pClipRect)183 long ARX_UNICODE_DrawTextInRect(Font* font,
184                                 float x, float y,
185                                 float maxx,
186                                 const std::string& _text,
187                                 Color col,
188                                 const Rect * pClipRect
189                                ) {
190 
191 	Rect previousViewport;
192 	if(pClipRect) {
193 		previousViewport = GRenderer->GetViewport();
194 		GRenderer->SetViewport(*pClipRect);
195 	}
196 
197 	Rect rect((Rect::Num)x, (Rect::Num)y, (Rect::Num)maxx, Rect::Limits::max());
198 	if(maxx == std::numeric_limits<float>::infinity()) {
199 		rect.right = Rect::Limits::max();
200 	}
201 
202 	long height;
203 	ARX_UNICODE_FormattingInRect(font, _text, rect, col, &height);
204 
205 	if(pClipRect) {
206 		GRenderer->SetViewport(previousViewport);
207 	}
208 
209 	return height;
210 }
211 
ARX_TEXT_Draw(Font * ef,float x,float y,const std::string & car,Color col)212 void ARX_TEXT_Draw(Font* ef,
213                    float x, float y,
214                    const std::string& car,
215                    Color col) {
216 
217 	if (car.empty() || car[0] == 0)
218 		return;
219 
220 	ARX_UNICODE_DrawTextInRect(ef, x, y, std::numeric_limits<float>::infinity(), car, col);
221 }
222 
ARX_TEXT_DrawRect(Font * ef,float x,float y,float maxx,const string & car,Color col,const Rect * pClipRect)223 long ARX_TEXT_DrawRect(Font* ef,
224                        float x, float y,
225                        float maxx,
226                        const string & car,
227                        Color col,
228                        const Rect * pClipRect) {
229 	return ARX_UNICODE_DrawTextInRect(ef, x, y, maxx, car, col, pClipRect);
230 }
231 
DrawBookTextInRect(Font * font,float x,float y,float maxx,const std::string & text,Color col)232 float DrawBookTextInRect(Font* font, float x, float y, float maxx, const std::string& text, Color col) {
233 	return (float)ARX_TEXT_DrawRect(font, (BOOKDECX + x) * Xratio, (BOOKDECY + y) * Yratio, (BOOKDECX + maxx) * Xratio, text, col);
234 }
235 
236 //-----------------------------------------------------------------------------
DrawBookTextCenter(Font * font,float x,float y,const std::string & text,Color col)237 void DrawBookTextCenter( Font* font, float x, float y, const std::string& text, Color col )
238 {
239 	UNICODE_ARXDrawTextCenter(font, (BOOKDECX + x)*Xratio, (BOOKDECY + y)*Yratio, text, col);
240 }
241 
242 //-----------------------------------------------------------------------------
243 
UNICODE_ARXDrawTextCenter(Font * font,float x,float y,const std::string & str,Color col)244 long UNICODE_ARXDrawTextCenter( Font* font, float x, float y, const std::string& str, Color col )
245 {
246 	Vec2i size = font->getTextSize(str);
247 	int drawX = ((int)x) - (size.x / 2);
248 	int drawY = (int)y;
249 
250 	font->draw(drawX, drawY, str, col);
251 
252 	return size.x;
253 }
254 
255 
256 
UNICODE_ARXDrawTextCenteredScroll(Font * font,float x,float y,float x2,const std::string & str,Color col,int iTimeScroll,float fSpeed,int iNbLigne,int iTimeOut)257 long UNICODE_ARXDrawTextCenteredScroll( Font* font, float x, float y, float x2, const std::string& str, Color col, int iTimeScroll, float fSpeed, int iNbLigne, int iTimeOut) {
258 
259 	Rect::Num _x = checked_range_cast<Rect::Num>(x - x2);
260 	Rect::Num _y = checked_range_cast<Rect::Num>(y);
261 	Rect::Num w = checked_range_cast<Rect::Num>(x + x2);
262 
263 	Rect rRect(_x, _y, w, Rect::Limits::max());
264 
265 	if (pTextManage)
266 	{
267 		pTextManage->AddText(font,
268 							 str,
269 							 rRect,
270 							 col,
271 							 iTimeOut,
272 							 iTimeScroll,
273 							 fSpeed,
274 							 iNbLigne
275 							);
276 
277 		return static_cast<long>(x2);
278 	}
279 
280 	return 0;
281 }
282 
createFont(const res::path & fontFace,const string & fontProfileName,unsigned int fontSize,float scaleFactor)283 static Font * createFont(const res::path & fontFace,
284                          const string & fontProfileName, unsigned int fontSize,
285                          float scaleFactor) {
286 
287 	std::stringstream ss;
288 
289 	std::string szFontSize;
290 	ss << fontSize;
291 	ss >> szFontSize;
292 	ss.clear();
293 
294 	std::string szUT;
295 	szUT = getLocalised(fontProfileName, szFontSize);
296 	ss << szUT;
297 	ss >> fontSize;
298 	ss.clear();
299 
300 	fontSize *= scaleFactor;
301 
302 	Font * newFont = FontCache::getFont(fontFace, fontSize);
303 	if(!newFont) {
304 		LogError << "Error loading font: " << fontFace << " of size " << fontSize;
305 	}
306 
307 	return newFont;
308 }
309 
310 static float created_font_scale = 0.f;
311 
getFontFile(res::path & result)312 static bool getFontFile(res::path & result) {
313 
314 	if(!config.language.empty()) {
315 		result = "misc/arx_" + config.language + ".ttf";
316 		if(resources->hasFile(result)) {
317 			return true;
318 		}
319 	}
320 
321 	result = "misc/arx_default.ttf";
322 	if(resources->hasFile(result)) {
323 		return true;
324 	}
325 
326 	result = "misc/arx.ttf";
327 	if(resources->hasFile(result)) {
328 		return true;
329 	}
330 
331 	if(!config.language.empty()) {
332 		LogCritical << "Missing font file: need either misc/arx_" << config.language
333 		            << ".ttf, misc/arx_default.ttf or misc/arx.ttf.";
334 	} else {
335 		LogCritical << "Missing font file: need either misc/arx_default.ttf or misc/arx.ttf.";
336 	}
337 	return false;
338 }
339 
ARX_Text_Init()340 bool ARX_Text_Init() {
341 
342 	res::path file;
343 	if(!getFontFile(file)) {
344 		return false;
345 	}
346 
347 	float scale = std::max(std::min(Yratio, Xratio), .001f);
348 	if(scale == created_font_scale) {
349 		return true;
350 	}
351 	created_font_scale = scale;
352 
353 	// Keep small font small when increasing resolution
354 	// TODO font size jumps around scale = 1
355 	float small_scale = scale > 1.0f ? scale * 0.8f : scale;
356 
357 	delete pTextManage;
358 	pTextManage = new TextManager();
359 	delete pTextManageFlyingOver;
360 	pTextManageFlyingOver = new TextManager();
361 
362 	FontCache::initialize();
363 
364 	Font * nFontMainMenu = createFont(file, "system_font_mainmenu_size", 58, scale);
365 	Font * nFontMenu = createFont(file, "system_font_menu_size", 32, scale);
366 	Font * nFontControls = createFont(file, "system_font_menucontrols_size", 22, scale);
367 	Font * nFontCredits = createFont(file, "system_font_menucredits_size", 36, scale);
368 	Font * nFontInGame = createFont(file, "system_font_book_size", 18, small_scale);
369 	Font * nFontInGameNote = createFont(file, "system_font_note_size", 18, small_scale);
370 	Font * nFontInBook = createFont(file, "system_font_book_size", 18, small_scale);
371 
372 	// Only release old fonts after creating new ones to allow same fonts to be cached.
373 	FontCache::releaseFont(hFontMainMenu);
374 	FontCache::releaseFont(hFontMenu);
375 	FontCache::releaseFont(hFontControls);
376 	FontCache::releaseFont(hFontCredits);
377 	FontCache::releaseFont(hFontInGame);
378 	FontCache::releaseFont(hFontInGameNote);
379 	FontCache::releaseFont(hFontInBook);
380 
381 	hFontMainMenu = nFontMainMenu;
382 	hFontMenu = nFontMenu;
383 	hFontControls = nFontControls;
384 	hFontCredits = nFontCredits;
385 	hFontInGame = nFontInGame;
386 	hFontInGameNote = nFontInGameNote;
387 	hFontInBook = nFontInBook;
388 
389 	if(!hFontMainMenu || !hFontMenu || !hFontControls || !hFontCredits || !hFontInGame
390 	   || !hFontInGameNote || !hFontInBook) {
391 		LogCritical << "Could not load font " << file << " for scale " << scale
392 		            << " / small scale " << small_scale;
393 		return false;
394 	}
395 
396 	LogInfo << "Loaded font " << file << " with sizes " << hFontMainMenu->getSize() << ", "
397 	        << hFontMenu->getSize() << ", " << hFontControls->getSize()
398 	        << ", " << hFontCredits->getSize() << ", " << hFontInGame->getSize() << ", "
399 	        << hFontInGameNote->getSize() << ", " << hFontInBook->getSize();
400 
401 	return true;
402 }
403 
404 //-----------------------------------------------------------------------------
ARX_Text_Close()405 void ARX_Text_Close() {
406 
407 	created_font_scale = 0.f;
408 
409 	delete pTextManage;
410 	pTextManage = NULL;
411 
412 	delete pTextManageFlyingOver;
413 	pTextManageFlyingOver = NULL;
414 
415 	FontCache::releaseFont(hFontInBook);
416 	hFontInBook = NULL;
417 
418 	FontCache::releaseFont(hFontMainMenu);
419 	hFontMainMenu = NULL;
420 
421 	FontCache::releaseFont(hFontMenu);
422 	hFontMenu = NULL;
423 
424 	FontCache::releaseFont(hFontControls);
425 	hFontControls = NULL;
426 
427 	FontCache::releaseFont(hFontCredits);
428 	hFontCredits = NULL;
429 
430 	FontCache::releaseFont(hFontInGame);
431 	hFontInGame = NULL;
432 
433 	FontCache::releaseFont(hFontInGameNote);
434 	hFontInGameNote = NULL;
435 
436 	FontCache::shutdown();
437 }
438