GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/fonts/font.cpp Lines: 99 162 61.1 %
Date: 2017-11-29 Branches: 61 178 34.3 %

Line Branch Exec Source
1
/*
2
 *  The ManaPlus Client
3
 *  Copyright (C) 2004-2009  The Mana World Development Team
4
 *  Copyright (C) 2009-2010  The Mana Developers
5
 *  Copyright (C) 2009  Aethyra Development Team
6
 *  Copyright (C) 2011-2017  The ManaPlus Developers
7
 *
8
 *  This file is part of The ManaPlus Client.
9
 *
10
 *  This program is free software; you can redistribute it and/or modify
11
 *  it under the terms of the GNU General Public License as published by
12
 *  the Free Software Foundation; either version 2 of the License, or
13
 *  any later version.
14
 *
15
 *  This program is distributed in the hope that it will be useful,
16
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 *  GNU General Public License for more details.
19
 *
20
 *  You should have received a copy of the GNU General Public License
21
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
22
 */
23
24
/*      _______   __   __   __   ______   __   __   _______   __   __
25
 *     / _____/\ / /\ / /\ / /\ / ____/\ / /\ / /\ / ___  /\ /  |\/ /\
26
 *    / /\____\// / // / // / // /\___\// /_// / // /\_/ / // , |/ / /
27
 *   / / /__   / / // / // / // / /    / ___  / // ___  / // /| ' / /
28
 *  / /_// /\ / /_// / // / // /_/_   / / // / // /\_/ / // / |  / /
29
 * /______/ //______/ //_/ //_____/\ /_/ //_/ //_/ //_/ //_/ /|_/ /
30
 * \______\/ \______\/ \_\/ \_____\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/
31
 *
32
 * Copyright (c) 2004 - 2008 Olof Naessén and Per Larsson
33
 *
34
 *
35
 * Per Larsson a.k.a finalman
36
 * Olof Naessén a.k.a jansem/yakslem
37
 *
38
 * Visit: http://guichan.sourceforge.net
39
 *
40
 * License: (BSD)
41
 * Redistribution and use in source and binary forms, with or without
42
 * modification, are permitted provided that the following conditions
43
 * are met:
44
 * 1. Redistributions of source code must retain the above copyright
45
 *    notice, this list of conditions and the following disclaimer.
46
 * 2. Redistributions in binary form must reproduce the above copyright
47
 *    notice, this list of conditions and the following disclaimer in
48
 *    the documentation and/or other materials provided with the
49
 *    distribution.
50
 * 3. Neither the name of Guichan nor the names of its contributors may
51
 *    be used to endorse or promote products derived from this software
52
 *    without specific prior written permission.
53
 *
54
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
60
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
61
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
62
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
63
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
64
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65
 */
66
67
#include "gui/fonts/font.h"
68
69
#include "fs/files.h"
70
#include "fs/paths.h"
71
72
#include "fs/virtfs/tools.h"
73
#ifdef USE_SDL2
74
#include "fs/virtfs/rwops.h"
75
#endif  // USE_SDL2
76
77
#include "gui/fonts/textchunk.h"
78
79
#include "render/graphics.h"
80
81
#include "resources/imagehelper.h"
82
83
#include "resources/image/image.h"
84
85
#include "utils/checkutils.h"
86
#include "utils/delete2.h"
87
#include "utils/sdlcheckutils.h"
88
#include "utils/stringutils.h"
89
#include "utils/timer.h"
90
91
#include "debug.h"
92
93
const unsigned int CACHE_SIZE = 256;
94
const unsigned int CACHE_SIZE_SMALL1 = 2;
95
const unsigned int CACHE_SIZE_SMALL2 = 50;
96
const unsigned int CACHE_SIZE_SMALL3 = 170;
97
const unsigned int CLEAN_TIME = 7;
98
99
bool Font::mSoftMode(false);
100
101
extern char *strBuf;
102
103
static int fontCounter;
104
105
1490
Font::Font(std::string filename,
106
           int size,
107
1490
           const int style) :
108
    mFont(nullptr),
109
    mCreateCounter(0),
110
    mDeleteCounter(0),
111

1490
    mCleanTime(cur_time + CLEAN_TIME)
112
{
113
1490
    if (fontCounter == 0)
114
    {
115
248
        mSoftMode = imageHelper->useOpenGL() == RENDER_SOFTWARE;
116

248
        if (TTF_Init() == -1)
117
        {
118
            logger->error("Unable to initialize SDL_ttf: " +
119
                std::string(SDL_GetError()));
120
        }
121
    }
122
123
1490
    if (size < 4)
124
    {
125
        reportAlways("Error: requested load font %s with size %d",
126
            filename.c_str(),
127
            size);
128
        size = 4;
129
    }
130
131
1490
    if (fontCounter == 0)
132
    {
133
248
        strBuf = new char[65535];
134
248
        memset(strBuf, 0, 65535);
135
    }
136
137
1490
    ++fontCounter;
138
139
1490
    fixDirSeparators(filename);
140
2980
    logger->log("Attempt to load font: %s",
141
        filename.c_str());
142
1490
    mFont = openFont(filename.c_str(), size);
143
144
1490
    if (mFont == nullptr)
145
    {
146

4
        logger->log("Error normal loading font " + filename);
147
148
2
        filename = "fonts/dejavusans.ttf";
149

4
        mFont = openFont(fixDirSeparators(filename).c_str(), size);
150
2
        if (mFont == nullptr)
151
        {
152
#ifdef UNITTESTS
153
            reportAlways("Font load failed %s",
154
                filename.c_str());
155
#endif  // UNITTESTS
156
            logger->error("Font::Font: " +
157
                std::string(SDL_GetError()));
158
        }
159
        else
160
        {
161
4
            logger->log("Loaded fallback font %s, %d",
162
                filename.c_str(),
163
                size);
164
        }
165
    }
166
    else
167
    {
168
2976
        logger->log("Loaded font %s, %d",
169
            filename.c_str(),
170
            size);
171
    }
172
173
1490
    TTF_SetFontStyle(mFont, style);
174
1490
}
175
176
2980
Font::~Font()
177
{
178
1490
    TTF_CloseFont(mFont);
179
1490
    mFont = nullptr;
180
1490
    --fontCounter;
181
1490
    clear();
182
183
1490
    if (fontCounter == 0)
184
    {
185
248
        TTF_Quit();
186
248
        delete []strBuf;
187
    }
188
1490
}
189
190
1492
TTF_Font *Font::openFont(const char *const name,
191
                         const int size)
192
{
193
#ifdef USE_SDL2
194
    SDL_RWops *const rw = VirtFs::rwopsOpenRead(name);
195
    if (rw)
196
    {
197
        logger->log("Loading virtfs font file: %s",
198
            name);
199
        return TTF_OpenFontIndexRW(rw, 1, size, 0);
200
    }
201
#endif
202

7460
    const std::string path = VirtFs::getPath(name);
203

1492
    if (Files::existsLocal(path) == false)
204
    {
205
#ifndef UNITTESTS
206
        // +++ in future need trigger assert in unit tests here too
207
        reportAlways("Font::openFont font not exists: %s",
208
            path.c_str());
209
#endif  // UNITTESTS
210
        return nullptr;
211
    }
212
2980
    logger->log("Loading physical font file: %s",
213
        path.c_str());
214
1490
    return TTF_OpenFontIndex(path.c_str(),
215
1490
        size, 0);
216
}
217
218
void Font::loadFont(std::string filename,
219
                    const int size,
220
                    const int style)
221
{
222
    if (fontCounter == 0 && TTF_Init() == -1)
223
    {
224
        logger->log("Unable to initialize SDL_ttf: " +
225
                    std::string(SDL_GetError()));
226
        return;
227
    }
228
229
    fixDirSeparators(filename);
230
    TTF_Font *const font = openFont(filename.c_str(), size);
231
232
    if (font == nullptr)
233
    {
234
        logger->log("Font::Font: " +
235
                    std::string(SDL_GetError()));
236
        return;
237
    }
238
239
    if (mFont != nullptr)
240
        TTF_CloseFont(mFont);
241
242
    mFont = font;
243
    TTF_SetFontStyle(mFont, style);
244
    clear();
245
}
246
247
void Font::clear()
248
{
249

382930
    for (size_t f = 0; f < CACHES_NUMBER; f ++)
250
381440
        mCache[f].clear();
251
}
252
253
98
void Font::drawString(Graphics *const graphics,
254
                      Color col,
255
                      const Color &col2,
256
                      const std::string &text,
257
                      const int x, const int y)
258
{
259
    BLOCK_START("Font::drawString")
260
98
    if (text.empty())
261
    {
262
        BLOCK_END("Font::drawString")
263
        return;
264
    }
265
266
//    Color col = graphics->getColor();
267
//    const Color &col2 = graphics->getColor2();
268
96
    const float alpha = static_cast<float>(col.a) / 255.0F;
269
270
    /* The alpha value is ignored at string generation so avoid caching the
271
     * same text with different alpha values.
272
     */
273
96
    col.a = 255;
274
275
96
    const unsigned char chr = text[0];
276
96
    TextChunkList *const cache = &mCache[chr];
277
278
96
    std::map<TextChunkSmall, TextChunk*> &search = cache->search;
279
    std::map<TextChunkSmall, TextChunk*>::iterator i
280
288
        = search.find(TextChunkSmall(text, col, col2));
281
96
    if (i != search.end())
282
    {
283
        TextChunk *const chunk2 = (*i).second;
284
        cache->moveToFirst(chunk2);
285
        Image *const image = chunk2->img;
286
        if (image != nullptr)
287
        {
288
            image->setAlpha(alpha);
289
            graphics->drawImage(image, x, y);
290
        }
291
    }
292
    else
293
    {
294
96
        if (cache->size >= CACHE_SIZE)
295
        {
296
#ifdef DEBUG_FONT_COUNTERS
297
            mDeleteCounter ++;
298
#endif  // DEBUG_FONT_COUNTERS
299
300
            cache->removeBack();
301
        }
302
#ifdef DEBUG_FONT_COUNTERS
303
        mCreateCounter ++;
304
#endif  // DEBUG_FONT_COUNTERS
305
306
96
        TextChunk *chunk2 = new TextChunk(text, col, col2, this);
307
308
96
        chunk2->generate(mFont, alpha);
309
96
        cache->insertFirst(chunk2);
310
311
96
        const Image *const image = chunk2->img;
312
96
        if (image != nullptr)
313
96
            graphics->drawImage(image, x, y);
314
    }
315
    BLOCK_END("Font::drawString")
316
}
317
318
void Font::slowLogic(const int rnd)
319
{
320
    BLOCK_START("Font::slowLogic")
321
    if (mCleanTime == 0)
322
    {
323
        mCleanTime = cur_time + CLEAN_TIME + rnd;
324
    }
325
    else if (mCleanTime < cur_time)
326
    {
327
        doClean();
328
        mCleanTime = cur_time + CLEAN_TIME + rnd;
329
    }
330
    BLOCK_END("Font::slowLogic")
331
}
332
333
18670
int Font::getWidth(const std::string &text) const
334
{
335
18670
    if (text.empty())
336
        return 0;
337
338
18292
    const unsigned char chr = text[0];
339
18292
    TextChunkList *const cache = &mCache[chr];
340
341
18292
    std::map<std::string, TextChunk*> &search = cache->searchWidth;
342
18292
    std::map<std::string, TextChunk*>::iterator i = search.find(text);
343
18292
    if (i != search.end())
344
    {
345
        TextChunk *const chunk = (*i).second;
346
        cache->moveToFirst(chunk);
347
        const Image *const image = chunk->img;
348
        if (image != nullptr)
349
            return image->getWidth();
350
        return 0;
351
    }
352
353
    // if string was not drawed
354
    int w, h;
355
54876
    getSafeUtf8String(text, strBuf);
356
18292
    TTF_SizeUTF8(mFont, strBuf, &w, &h);
357
18292
    return w;
358
}
359
360
4287
int Font::getHeight() const
361
{
362
4287
    return TTF_FontHeight(mFont);
363
}
364
365
void Font::doClean()
366
{
367
    for (unsigned int f = 0; f < CACHES_NUMBER; f ++)
368
    {
369
        TextChunkList *const cache = &mCache[f];
370
        const size_t size = CAST_SIZE(cache->size);
371
#ifdef DEBUG_FONT_COUNTERS
372
        logger->log("ptr: %u, size: %ld", f, size);
373
#endif  // DEBUG_FONT_COUNTERS
374
375
        if (size > CACHE_SIZE_SMALL3)
376
        {
377
#ifdef DEBUG_FONT_COUNTERS
378
            mDeleteCounter += 100;
379
#endif  // DEBUG_FONT_COUNTERS
380
381
            cache->removeBack(100);
382
#ifdef DEBUG_FONT_COUNTERS
383
            logger->log("delete3");
384
#endif  // DEBUG_FONT_COUNTERS
385
        }
386
        else if (size > CACHE_SIZE_SMALL2)
387
        {
388
#ifdef DEBUG_FONT_COUNTERS
389
            mDeleteCounter += 20;
390
#endif  // DEBUG_FONT_COUNTERS
391
392
            cache->removeBack(20);
393
#ifdef DEBUG_FONT_COUNTERS
394
            logger->log("delete2");
395
#endif  // DEBUG_FONT_COUNTERS
396
        }
397
        else if (size > CACHE_SIZE_SMALL1)
398
        {
399
#ifdef DEBUG_FONT_COUNTERS
400
            mDeleteCounter ++;
401
#endif  // DEBUG_FONT_COUNTERS
402
403
            cache->removeBack();
404
#ifdef DEBUG_FONT_COUNTERS
405
            logger->log("delete1");
406
#endif  // DEBUG_FONT_COUNTERS
407
        }
408
    }
409
}
410
411
int Font::getStringIndexAt(const std::string& text, const int x) const
412
{
413
    const size_t sz = text.size();
414
    for (size_t i = 0; i < sz; ++i)
415
    {
416
        if (getWidth(text.substr(0, i)) > x)
417
            return CAST_S32(i);
418
    }
419
420
    return CAST_S32(sz);
421
}
422
423
const TextChunkList *Font::getCache() const noexcept2
424
{
425
    return mCache;
426
}
427
428
472
void Font::generate(TextChunk &chunk)
429
{
430
472
    const std::string &text = chunk.text;
431
472
    if (text.empty())
432
82
        return;
433
434
390
    const unsigned char chr = text[0];
435
390
    TextChunkList *const cache = &mCache[chr];
436
390
    Color &col = chunk.color;
437
390
    Color &col2 = chunk.color2;
438
390
    const int oldAlpha = col.a;
439
390
    col.a = 255;
440
441
780
    TextChunkSmall key(text, col, col2);
442
390
    std::map<TextChunkSmall, TextChunk*> &search = cache->search;
443
390
    std::map<TextChunkSmall, TextChunk*>::iterator i = search.find(key);
444
390
    if (i != search.end())
445
    {
446
        TextChunk *const chunk2 = (*i).second;
447
        cache->moveToFirst(chunk2);
448
//        search.erase(key);
449
        cache->remove(chunk2);
450
        chunk.img = chunk2->img;
451
        chunk2->img = nullptr;
452
        delete chunk2;
453
//        logger->log("cached image: " + chunk.text);
454
    }
455
    else
456
    {
457
390
        if (cache->size >= CACHE_SIZE)
458
        {
459
#ifdef DEBUG_FONT_COUNTERS
460
            mDeleteCounter ++;
461
#endif  // DEBUG_FONT_COUNTERS
462
463
            cache->removeBack();
464
        }
465
#ifdef DEBUG_FONT_COUNTERS
466
        mCreateCounter ++;
467
#endif  // DEBUG_FONT_COUNTERS
468
469
390
        const float alpha = static_cast<float>(chunk.color.a) / 255.0F;
470
390
        chunk.generate(mFont, alpha);
471
//        logger->log("generate image: " + chunk.text);
472
    }
473
390
    col.a = oldAlpha;
474
}
475
476
862
void Font::insertChunk(TextChunk *const chunk)
477
{
478

1724
    if ((chunk == nullptr) || chunk->text.empty() || (chunk->img == nullptr))
479
        return;
480
//    logger->log("insert chunk: text=%s, color: %d,%d,%d",
481
//        chunk->text.c_str(), chunk->color.r, chunk->color.g, chunk->color.b);
482
636
    const unsigned char chr = chunk->text[0];
483
318
    TextChunkList *const cache = &mCache[chr];
484
485
318
    std::map<TextChunkSmall, TextChunk*> &search = cache->search;
486
    std::map<TextChunkSmall, TextChunk*>::iterator i
487
636
        = search.find(TextChunkSmall(chunk->text,
488
318
        chunk->color, chunk->color2));
489
318
    if (i != search.end())
490
    {
491
6
        delete2(chunk->img);
492
6
        return;
493
    }
494
495
    TextChunk *const chunk2 = new TextChunk(chunk->text,
496
312
        chunk->color, chunk->color2, chunk->textFont);
497
312
    chunk2->img = chunk->img;
498
312
    cache->insertFirst(chunk2);
499
}