GCC Code Coverage Report
Directory: src/ Exec Total Coverage
File: src/gui/fonts/font.cpp Lines: 97 166 58.4 %
Date: 2018-12-09 Branches: 55 180 30.6 %

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-2018  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
764
Font::Font(std::string filename,
106
           int size,
107
764
           const int style) :
108
    mFont(nullptr),
109
    mCreateCounter(0),
110
    mDeleteCounter(0),
111

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

127
        if (TTF_Init() == -1)
117
        {
118
            logger->error("Unable to initialize SDL_ttf: " +
119
                std::string(SDL_GetError()));
120
        }
121
    }
122
123
764
    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
764
    if (fontCounter == 0)
132
    {
133
127
        strBuf = new char[65535];
134
127
        memset(strBuf, 0, 65535);
135
    }
136
137
764
    ++fontCounter;
138
139
764
    fixDirSeparators(filename);
140
764
    logger->log("Attempt to load font: %s",
141
764
        filename.c_str());
142
764
    mFont = openFont(filename.c_str(), size);
143
144
764
    if (mFont == nullptr)
145
    {
146
        logger->log("Error normal loading font " + filename);
147
148
        filename = "fonts/dejavusans.ttf";
149
        mFont = openFont(fixDirSeparators(filename).c_str(), size);
150
        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
            logger->log("Loaded fallback font %s, %d",
162
                filename.c_str(),
163
                size);
164
        }
165
    }
166
    else
167
    {
168
764
        logger->log("Loaded font %s, %d",
169
            filename.c_str(),
170
764
            size);
171
    }
172
173
764
    TTF_SetFontStyle(mFont, style);
174
764
}
175
176

1528
Font::~Font()
177
{
178
764
    TTF_CloseFont(mFont);
179
764
    mFont = nullptr;
180
764
    --fontCounter;
181
764
    clear();
182
183
764
    if (fontCounter == 0)
184
    {
185
127
        TTF_Quit();
186
127
        delete []strBuf;
187
    }
188
764
}
189
190
764
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
3820
    const std::string path = VirtFs::getPath(name);
203

764
    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
764
    logger->log("Loading physical font file: %s",
213
764
        path.c_str());
214
764
    return TTF_OpenFontIndex(path.c_str(),
215
764
        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

196348
    for (size_t f = 0; f < CACHES_NUMBER; f ++)
250
195584
        mCache[f].clear();
251
}
252
253
50
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
50
    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
49
    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
49
    col.a = 255;
274
275
49
    const unsigned char chr = text[0];
276
49
    TextChunkList *const cache = &mCache[chr];
277
278
49
    std::map<TextChunkSmall, TextChunk*> &search = cache->search;
279
    std::map<TextChunkSmall, TextChunk*>::iterator i
280
147
        = search.find(TextChunkSmall(text, col, col2));
281
49
    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
49
        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
49
        TextChunk *chunk2 = new TextChunk(text, col, col2, this);
307
308
49
        chunk2->generate(mFont, alpha);
309
49
        cache->insertFirst(chunk2);
310
311
49
        const Image *const image = chunk2->img;
312
49
        if (image != nullptr)
313
49
            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
9627
int Font::getWidth(const std::string &text) const
334
{
335
9627
    if (text.empty())
336
        return 0;
337
338
9415
    const unsigned char chr = text[0];
339
9415
    TextChunkList *const cache = &mCache[chr];
340
341
9415
    std::map<std::string, TextChunk*> &search = cache->searchWidth;
342
9415
    std::map<std::string, TextChunk*>::iterator i = search.find(text);
343
9415
    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;
355
    int h;
356
28245
    getSafeUtf8String(text, strBuf);
357
9415
    TTF_SizeUTF8(mFont, strBuf, &w, &h);
358
9415
    return w;
359
}
360
361
2313
int Font::getHeight() const
362
{
363
2313
    return TTF_FontHeight(mFont);
364
}
365
366
void Font::doClean()
367
{
368
    for (unsigned int f = 0; f < CACHES_NUMBER; f ++)
369
    {
370
        TextChunkList *const cache = &mCache[f];
371
        const size_t size = CAST_SIZE(cache->size);
372
#ifdef DEBUG_FONT_COUNTERS
373
        logger->log("ptr: %u, size: %ld", f, size);
374
#endif  // DEBUG_FONT_COUNTERS
375
376
        if (size > CACHE_SIZE_SMALL3)
377
        {
378
#ifdef DEBUG_FONT_COUNTERS
379
            mDeleteCounter += 100;
380
#endif  // DEBUG_FONT_COUNTERS
381
382
            cache->removeBack(100);
383
#ifdef DEBUG_FONT_COUNTERS
384
            logger->log("delete3");
385
#endif  // DEBUG_FONT_COUNTERS
386
        }
387
        else if (size > CACHE_SIZE_SMALL2)
388
        {
389
#ifdef DEBUG_FONT_COUNTERS
390
            mDeleteCounter += 20;
391
#endif  // DEBUG_FONT_COUNTERS
392
393
            cache->removeBack(20);
394
#ifdef DEBUG_FONT_COUNTERS
395
            logger->log("delete2");
396
#endif  // DEBUG_FONT_COUNTERS
397
        }
398
        else if (size > CACHE_SIZE_SMALL1)
399
        {
400
#ifdef DEBUG_FONT_COUNTERS
401
            mDeleteCounter ++;
402
#endif  // DEBUG_FONT_COUNTERS
403
404
            cache->removeBack();
405
#ifdef DEBUG_FONT_COUNTERS
406
            logger->log("delete1");
407
#endif  // DEBUG_FONT_COUNTERS
408
        }
409
    }
410
}
411
412
int Font::getStringIndexAt(const std::string& text, const int x) const
413
{
414
    const size_t sz = text.size();
415
    for (size_t i = 0; i < sz; ++i)
416
    {
417
        if (getWidth(text.substr(0, i)) > x)
418
            return CAST_S32(i);
419
    }
420
421
    return CAST_S32(sz);
422
}
423
424
const TextChunkList *Font::getCache() const noexcept2
425
{
426
    return mCache;
427
}
428
429
236
void Font::generate(TextChunk &chunk)
430
{
431
236
    const std::string &text = chunk.text;
432
236
    if (text.empty())
433
41
        return;
434
435
195
    const unsigned char chr = text[0];
436
195
    TextChunkList *const cache = &mCache[chr];
437
195
    Color &col = chunk.color;
438
195
    Color &col2 = chunk.color2;
439
195
    const int oldAlpha = col.a;
440
195
    col.a = 255;
441
442
390
    TextChunkSmall key(text, col, col2);
443
195
    std::map<TextChunkSmall, TextChunk*> &search = cache->search;
444
195
    std::map<TextChunkSmall, TextChunk*>::iterator i = search.find(key);
445
195
    if (i != search.end())
446
    {
447
        TextChunk *const chunk2 = (*i).second;
448
        cache->moveToFirst(chunk2);
449
//        search.erase(key);
450
        cache->remove(chunk2);
451
        chunk.img = chunk2->img;
452
        chunk2->img = nullptr;
453
        delete chunk2;
454
//        logger->log("cached image: " + chunk.text);
455
    }
456
    else
457
    {
458
195
        if (cache->size >= CACHE_SIZE)
459
        {
460
#ifdef DEBUG_FONT_COUNTERS
461
            mDeleteCounter ++;
462
#endif  // DEBUG_FONT_COUNTERS
463
464
            cache->removeBack();
465
        }
466
#ifdef DEBUG_FONT_COUNTERS
467
        mCreateCounter ++;
468
#endif  // DEBUG_FONT_COUNTERS
469
470
195
        const float alpha = static_cast<float>(chunk.color.a) / 255.0F;
471
195
        chunk.generate(mFont, alpha);
472
//        logger->log("generate image: " + chunk.text);
473
    }
474
195
    col.a = oldAlpha;
475
}
476
477
431
void Font::insertChunk(TextChunk *const chunk)
478
{
479


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