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

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

127
        if (TTF_Init() == -1)
118
        {
119
            logger->error("Unable to initialize SDL_ttf: " +
120
                std::string(SDL_GetError()));
121
        }
122
    }
123
124
764
    if (size < 4)
125
    {
126
        reportAlways("Error: requested load font %s with size %d",
127
            filename.c_str(),
128
            size)
129
        size = 4;
130
    }
131
132
764
    if (fontCounter == 0)
133
    {
134
127
        strBuf = new char[65535];
135
127
        memset(strBuf, 0, 65535);
136
    }
137
138
764
    ++fontCounter;
139
140
764
    fixDirSeparators(filename);
141
764
    logger->log("Attempt to load font: %s",
142
764
        filename.c_str());
143
764
    mFont = openFont(filename.c_str(), size);
144
145
764
    if (mFont == nullptr)
146
    {
147
        logger->log("Error normal loading font " + filename);
148
149
        filename = "fonts/dejavusans.ttf";
150
        mFont = openFont(fixDirSeparators(filename).c_str(), size);
151
        if (mFont == nullptr)
152
        {
153
#ifdef UNITTESTS
154
            reportAlways("Font load failed %s",
155
                filename.c_str())
156
#endif  // UNITTESTS
157
            logger->error("Font::Font: " +
158
                std::string(SDL_GetError()));
159
        }
160
        else
161
        {
162
            logger->log("Loaded fallback font %s, %d",
163
                filename.c_str(),
164
                size);
165
        }
166
    }
167
    else
168
    {
169
764
        logger->log("Loaded font %s, %d",
170
            filename.c_str(),
171
764
            size);
172
    }
173
174
764
    TTF_SetFontStyle(mFont, style);
175
764
}
176
177

1528
Font::~Font()
178
{
179
764
    TTF_CloseFont(mFont);
180
764
    mFont = nullptr;
181
764
    --fontCounter;
182
764
    clear();
183
184
764
    if (fontCounter == 0)
185
    {
186
127
        TTF_Quit();
187
127
        delete []strBuf;
188
    }
189
764
}
190
191
764
TTF_Font *Font::openFont(const char *const name,
192
                         const int size)
193
{
194
#ifdef USE_SDL2
195
3056
    SDL_RWops *const rw = VirtFs::rwopsOpenRead(name);
196
764
    if (rw)
197
    {
198
764
        logger->log("Loading virtfs font file: %s",
199
764
            name);
200
764
        return TTF_OpenFontIndexRW(rw, 1, size, 0);
201
    }
202
#endif
203
    const std::string path = VirtFs::getPath(name);
204
    if (Files::existsLocal(path) == false)
205
    {
206
#ifndef UNITTESTS
207
        // +++ in future need trigger assert in unit tests here too
208
        reportAlways("Font::openFont font not exists: %s",
209
            path.c_str())
210
#endif  // UNITTESTS
211
        return nullptr;
212
    }
213
    logger->log("Loading physical font file: %s",
214
        path.c_str());
215
    return TTF_OpenFontIndex(path.c_str(),
216
        size, 0);
217
}
218
219
void Font::loadFont(std::string filename,
220
                    const int size,
221
                    const int style)
222
{
223
    if (fontCounter == 0 && TTF_Init() == -1)
224
    {
225
        logger->log("Unable to initialize SDL_ttf: " +
226
                    std::string(SDL_GetError()));
227
        return;
228
    }
229
230
    fixDirSeparators(filename);
231
    TTF_Font *const font = openFont(filename.c_str(), size);
232
233
    if (font == nullptr)
234
    {
235
        logger->log("Font::Font: " +
236
                    std::string(SDL_GetError()));
237
        return;
238
    }
239
240
    if (mFont != nullptr)
241
        TTF_CloseFont(mFont);
242
243
    mFont = font;
244
    TTF_SetFontStyle(mFont, style);
245
    clear();
246
}
247
248
void Font::clear()
249
{
250

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


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