Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * Portions of this file are copyright Rebirth contributors and licensed as
3
 * described in COPYING.txt.
4
 * Portions of this file are copyright Parallax Software and licensed
5
 * according to the Parallax license below.
6
 * See COPYING.txt for license details.
7
 
8
THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9
SOFTWARE CORPORATION ("PARALLAX").  PARALLAX, IN DISTRIBUTING THE CODE TO
10
END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11
ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12
IN USING, DISPLAYING,  AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13
SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14
FREE PURPOSES.  IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15
CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES.  THE END-USER UNDERSTANDS
16
AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17
COPYRIGHT 1993-1999 PARALLAX SOFTWARE CORPORATION.  ALL RIGHTS RESERVED.
18
*/
19
 
20
/*
21
 *
22
 * Graphical routines for drawing fonts.
23
 *
24
 */
25
 
26
#include <algorithm>
27
#include <memory>
28
#include <stdexcept>
29
#include <stdarg.h>
30
#include <stdio.h>
31
#include <stdlib.h>
32
#include <string.h>
33
#ifndef macintosh
34
#include <fcntl.h>
35
#endif
36
 
37
#include "u_mem.h"
38
#include "gr.h"
39
#include "grdef.h"
40
#include "dxxerror.h"
41
#include "common/2d/bitmap.h"
42
#include "makesig.h"
43
#include "physfsx.h"
44
#include "gamefont.h"
45
#include "byteutil.h"
46
#include "console.h"
47
#include "config.h"
48
#if DXX_USE_OGL
49
#include "ogl_init.h"
50
#endif
51
 
52
#include "compiler-range_for.h"
53
#include "partial_range.h"
54
#include <array>
55
#include <memory>
56
 
57
static font_x_scale_float FONTSCALE_X()
58
{
59
        return FNTScaleX.operator float();
60
}
61
 
62
static auto FONTSCALE_Y(const int &y)
63
{
64
        return font_scaled_float<'y'>(FNTScaleY * y);
65
}
66
 
67
#define MAX_OPEN_FONTS  50
68
 
69
namespace {
70
 
71
constexpr std::integral_constant<uint8_t, 255> kerndata_terminator{};
72
 
73
}
74
 
75
namespace dcx {
76
 
77
//list of open fonts, for use (for now) for palette remapping
78
static std::array<grs_font *, MAX_OPEN_FONTS> open_font;
79
 
80
#define BITS_TO_BYTES(x)    (((x)+7)>>3)
81
 
82
static int gr_internal_string_clipped(grs_canvas &, const grs_font &cv_font, int x, int y, const char *s);
83
static int gr_internal_string_clipped_m(grs_canvas &, const grs_font &cv_font, int x, int y, const char *s);
84
 
85
static const uint8_t *find_kern_entry(const grs_font &font, const uint8_t first, const uint8_t second)
86
{
87
        auto p = font.ft_kerndata;
88
 
89
        while (*p != kerndata_terminator)
90
                if (p[0]==first && p[1]==second)
91
                        return p;
92
                else p+=3;
93
        return NULL;
94
}
95
 
96
//takes the character AFTER being offset into font
97
 
98
namespace {
99
 
100
class font_character_extent
101
{
102
        const unsigned r;
103
public:
104
        font_character_extent(const grs_font &cv_font) :
105
                r(cv_font.ft_maxchar - cv_font.ft_minchar)
106
        {
107
        }
108
        bool operator()(const unsigned c) const
109
        {
110
                return c <= r;
111
        }
112
};
113
 
114
template <typename T>
115
struct get_char_width_result
116
{
117
        T width, spacing;
118
};
119
 
120
/* Floating form never uses width.  This specialization allows the
121
 * compiler to recognize width as dead, shortening
122
 * get_char_width<float>.
123
 */
124
template <>
125
struct get_char_width_result<float>
126
{
127
        float spacing;
128
        get_char_width_result(float, float s) :
129
                spacing(s)
130
        {
131
        }
132
};
133
 
134
}
135
 
136
//takes the character BEFORE being offset into current font
137
template <typename T>
138
static get_char_width_result<T> get_char_width(const grs_font &cv_font, const uint8_t c, const uint8_t c2)
139
{
140
        const unsigned letter = c - cv_font.ft_minchar;
141
        const auto ft_flags = cv_font.ft_flags;
142
        const auto proportional = ft_flags & FT_PROPORTIONAL;
143
 
144
        const auto &&fontscale_x = FONTSCALE_X();
145
        const auto &&INFONT = font_character_extent(cv_font);
146
        if (!INFONT(letter)) {                          //not in font, draw as space
147
                return {0, static_cast<T>(proportional ? fontscale_x(cv_font.ft_w) / 2 : cv_font.ft_w)};
148
        }
149
        const T width = proportional ? fontscale_x(cv_font.ft_widths[letter]).operator float() : cv_font.ft_w;
150
        if (ft_flags & FT_KERNED)
151
        {
152
                if (!(c2==0 || c2=='\n')) {
153
                        const unsigned letter2 = c2 - cv_font.ft_minchar;
154
 
155
                        if (INFONT(letter2)) {
156
                                const auto p = find_kern_entry(cv_font, letter, letter2);
157
                                if (p)
158
                                        return {width, static_cast<T>(fontscale_x(p[2]))};
159
                        }
160
                }
161
        }
162
        return {width, width};
163
}
164
 
165
static int get_centered_x(const grs_canvas &canvas, const grs_font &cv_font, const char *s)
166
{
167
        float w;
168
        for (w = 0.; const char c = *s; ++s)
169
        {
170
                if (c == '\n')
171
                        break;
172
                if (c <= 0x06)
173
                {
174
                        if (c <= 0x03)
175
                                s++;
176
                        continue;//skip color codes.
177
                }
178
                w += get_char_width<float>(cv_font, c, s[1]).spacing;
179
        }
180
 
181
        return (canvas.cv_bitmap.bm_w - w) / 2;
182
}
183
 
184
//hack to allow color codes to be embedded in strings -MPM
185
//note we subtract one from color, since 255 is "transparent" so it'll never be used, and 0 would otherwise end the string.
186
//function must already have orig_color var set (or they could be passed as args...)
187
//perhaps some sort of recursive orig_color type thing would be better, but that would be way too much trouble for little gain
188
constexpr std::integral_constant<int, 1> gr_message_color_level{};
189
#define CHECK_EMBEDDED_COLORS() if ((*text_ptr >= 0x01) && (*text_ptr <= 0x02)) { \
190
                text_ptr++; \
191
                if (*text_ptr){ \
192
                        if (gr_message_color_level >= *(text_ptr-1)) \
193
                                canvas.cv_font_fg_color = static_cast<uint8_t>(*text_ptr); \
194
                        text_ptr++; \
195
                } \
196
        } \
197
        else if (*text_ptr == 0x03) \
198
        { \
199
                underline = 1; \
200
                text_ptr++; \
201
        } \
202
        else if ((*text_ptr >= 0x04) && (*text_ptr <= 0x06)){ \
203
                if (gr_message_color_level >= *text_ptr - 3) \
204
                        canvas.cv_font_fg_color= static_cast<uint8_t>(orig_color); \
205
                text_ptr++; \
206
        }
207
 
208
template <bool masked_draws_background>
209
static int gr_internal_string0_template(grs_canvas &canvas, const grs_font &cv_font, const int x, int y, const char *const s)
210
{
211
        const auto &&INFONT = font_character_extent(cv_font);
212
        const auto ft_flags = cv_font.ft_flags;
213
        const auto proportional = ft_flags & FT_PROPORTIONAL;
214
        const auto cv_font_bg_color = canvas.cv_font_bg_color;
215
        int     skip_lines = 0;
216
 
217
        unsigned int VideoOffset1;
218
 
219
        //to allow easy reseting to default string color with colored strings -MPM
220
        const auto orig_color = canvas.cv_font_fg_color;
221
        VideoOffset1 = y * canvas.cv_bitmap.bm_rowsize + x;
222
        auto next_row = s;
223
        while (next_row != NULL )
224
        {
225
                const auto text_ptr1 = next_row;
226
                next_row = NULL;
227
 
228
                if (x==0x8000) {                        //centered
229
                        int xx = get_centered_x(canvas, cv_font, text_ptr1);
230
                        VideoOffset1 = y * canvas.cv_bitmap.bm_rowsize + xx;
231
                }
232
 
233
                for (int r=0; r < cv_font.ft_h; ++r)
234
                {
235
                        auto text_ptr = text_ptr1;
236
 
237
                        unsigned VideoOffset = VideoOffset1;
238
 
239
                        for (; const uint8_t c0 = *text_ptr; ++text_ptr)
240
                        {
241
                                if (c0 == '\n')
242
                                {
243
                                        next_row = &text_ptr[1];
244
                                        break;
245
                                }
246
 
247
                                if (c0 == CC_COLOR)
248
                                {
249
                                        canvas.cv_font_fg_color = static_cast<uint8_t>(*++text_ptr);
250
                                        continue;
251
                                }
252
 
253
                                if (c0 == CC_LSPACING)
254
                                {
255
                                        skip_lines = *++text_ptr - '0';
256
                                        continue;
257
                                }
258
 
259
                                auto underline = unlikely(c0 == CC_UNDERLINE)
260
                                        ? ++text_ptr, r == cv_font.ft_baseline + 2 || r == cv_font.ft_baseline + 3
261
                                        : 0;
262
 
263
                                const uint8_t c = *text_ptr;
264
                                const auto &result = get_char_width<int>(cv_font, c, text_ptr[1]);
265
                                const auto &width = result.width;
266
                                const auto &spacing = result.spacing;
267
 
268
                                const unsigned letter = c - cv_font.ft_minchar;
269
 
270
                                if constexpr (masked_draws_background)
271
                                {
272
                                        (void)orig_color;
273
                                        if (!INFONT(letter)) {  //not in font, draw as space
274
                                                VideoOffset += spacing;
275
                                                text_ptr++;
276
                                                continue;
277
                                        }
278
                                }
279
                                else
280
                                {
281
                                        if (!INFONT(letter) || c <= 0x06)       //not in font, draw as space
282
                                        {
283
                                                CHECK_EMBEDDED_COLORS() else{
284
                                                        VideoOffset += spacing;
285
                                                        text_ptr++;
286
                                                }
287
                                                continue;
288
                                        }
289
                                }
290
 
291
                                if (width)
292
                                {
293
                                        auto data = &canvas.cv_bitmap.get_bitmap_data()[VideoOffset];
294
                                        const auto cv_font_fg_color = canvas.cv_font_fg_color;
295
                                if (underline)
296
                                {
297
                                        std::fill_n(data, width, cv_font_fg_color);
298
                                }
299
                                else
300
                                {
301
                                        auto fp = proportional ? cv_font.ft_chars[letter] : &cv_font.ft_data[letter * BITS_TO_BYTES(width) * cv_font.ft_h];
302
                                        fp += BITS_TO_BYTES(width)*r;
303
 
304
                                        /* Setting bits=0 is a dead store, but is necessary to
305
                                         * prevent -Og -Wuninitialized from issuing a bogus
306
                                         * warning.  -Og does not see that bits_remaining=0
307
                                         * guarantees that bits will be initialized from *fp before
308
                                         * it is read.
309
                                         */
310
                                        uint8_t bits_remaining = 0, bits = 0;
311
                                        for (uint_fast32_t i = width; i--; ++data, --bits_remaining)
312
                                        {
313
                                                if (!bits_remaining)
314
                                                {
315
                                                        bits = *fp++;
316
                                                        bits_remaining = 8;
317
                                                }
318
 
319
                                                const auto bit_enabled = (bits & 0x80);
320
                                                bits <<= 1;
321
                                                if constexpr (!masked_draws_background)
322
                                                {
323
                                                        if (!bit_enabled)
324
                                                                continue;
325
                                                }
326
                                                *data = bit_enabled ? cv_font_fg_color : cv_font_bg_color;
327
                                        }
328
                                }
329
                                }
330
                                VideoOffset += spacing;
331
                        }
332
                        VideoOffset1 += canvas.cv_bitmap.bm_rowsize;
333
                        y++;
334
                }
335
                y += skip_lines;
336
                VideoOffset1 += canvas.cv_bitmap.bm_rowsize * skip_lines;
337
                skip_lines = 0;
338
        }
339
        return 0;
340
}
341
 
342
static int gr_internal_string0(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
343
{
344
        return gr_internal_string0_template<true>(canvas, cv_font, x, y, s);
345
}
346
 
347
static int gr_internal_string0m(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
348
{
349
        return gr_internal_string0_template<false>(canvas, cv_font, x, y, s);
350
}
351
 
352
#if !DXX_USE_OGL
353
static int gr_internal_color_string(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
354
{
355
//a bitmap for the character
356
        grs_bitmap char_bm = {};
357
        char_bm.set_type(bm_mode::linear);
358
        char_bm.set_flags(BM_FLAG_TRANSPARENT);
359
        const char *text_ptr, *next_row, *text_ptr1;
360
        int letter;
361
        int xx,yy;
362
 
363
        const auto &&INFONT = font_character_extent(cv_font);
364
        char_bm.bm_h = cv_font.ft_h;            //set height for chars of this font
365
 
366
        next_row = s;
367
 
368
        yy = y;
369
 
370
        const auto &&fspacy1 = FSPACY(1);
371
        while (next_row != NULL)
372
        {
373
                text_ptr1 = next_row;
374
                next_row = NULL;
375
 
376
                text_ptr = text_ptr1;
377
 
378
                xx = x;
379
 
380
                if (xx==0x8000)                 //centered
381
                        xx = get_centered_x(canvas, cv_font, text_ptr);
382
 
383
                while (*text_ptr)
384
                {
385
                        if (*text_ptr == '\n' )
386
                        {
387
                                next_row = &text_ptr[1];
388
                                yy += cv_font.ft_h + fspacy1;
389
                                break;
390
                        }
391
 
392
                        letter = static_cast<uint8_t>(*text_ptr) - cv_font.ft_minchar;
393
 
394
                        const auto &result = get_char_width<int>(cv_font, text_ptr[0], text_ptr[1]);
395
                        const auto &width = result.width;
396
                        const auto &spacing = result.spacing;
397
 
398
                        if (!INFONT(letter)) {  //not in font, draw as space
399
                                xx += spacing;
400
                                text_ptr++;
401
                                continue;
402
                        }
403
 
404
                        const auto fp = (cv_font.ft_flags & FT_PROPORTIONAL)
405
                                ? cv_font.ft_chars[letter]
406
                                : &cv_font.ft_data[letter * BITS_TO_BYTES(width) * cv_font.ft_h];
407
 
408
                        gr_init_bitmap(char_bm, bm_mode::linear, 0, 0, width, cv_font.ft_h, width, fp);
409
                        gr_bitmapm(canvas, xx, yy, char_bm);
410
 
411
                        xx += spacing;
412
 
413
                        text_ptr++;
414
                }
415
 
416
        }
417
        return 0;
418
}
419
 
420
#else //OGL
421
 
422
static int get_font_total_width(const grs_font &font)
423
{
424
        if (font.ft_flags & FT_PROPORTIONAL)
425
        {
426
                int w=0;
427
                range_for (const auto v, unchecked_partial_range(font.ft_widths, static_cast<unsigned>(font.ft_maxchar - font.ft_minchar) + 1))
428
                {
429
                        if (v < 0)
430
                                throw std::underflow_error("negative width");
431
                        w += v;
432
                }
433
                return w;
434
        }else{
435
                return font.ft_w*(font.ft_maxchar-font.ft_minchar+1);
436
        }
437
}
438
 
439
static void ogl_font_choose_size(grs_font * font,int gap,int *rw,int *rh){
440
        int     nchars = font->ft_maxchar-font->ft_minchar+1;
441
        int r,x,y,nc=0,smallest=999999,smallr=-1,tries;
442
        int smallprop=10000;
443
        int w;
444
        for (int h=32;h<=256;h*=2){
445
//              h=pow2ize(font->ft_h*rows+gap*(rows-1));
446
                if (font->ft_h>h)continue;
447
                r=(h/(font->ft_h+gap));
448
                w=pow2ize((get_font_total_width(*font)+(nchars-r)*gap)/r);
449
                tries=0;
450
                do {
451
                        if (tries)
452
                                w=pow2ize(w+1);
453
                        if(tries>3){
454
                                break;
455
                        }
456
                        nc=0;
457
                        y=0;
458
                        while(y+font->ft_h<=h){
459
                                x=0;
460
                                while (x<w){
461
                                        if (nc==nchars)
462
                                                break;
463
                                        if (font->ft_flags & FT_PROPORTIONAL){
464
                                                if (x+font->ft_widths[nc]+gap>w)break;
465
                                                x+=font->ft_widths[nc++]+gap;
466
                                        }else{
467
                                                if (x+font->ft_w+gap>w)break;
468
                                                x+=font->ft_w+gap;
469
                                                nc++;
470
                                        }
471
                                }
472
                                if (nc==nchars)
473
                                        break;
474
                                y+=font->ft_h+gap;
475
                        }
476
 
477
                        tries++;
478
                }while(nc!=nchars);
479
                if (nc!=nchars)
480
                        continue;
481
 
482
                if (w*h==smallest){//this gives squarer sizes priority (ie, 128x128 would be better than 512*32)
483
                        if (w>=h){
484
                                if (w/h<smallprop){
485
                                        smallprop=w/h;
486
                                        smallest++;//hack
487
                                }
488
                        }else{
489
                                if (h/w<smallprop){
490
                                        smallprop=h/w;
491
                                        smallest++;//hack
492
                                }
493
                        }
494
                }
495
                if (w*h<smallest){
496
                        smallr=1;
497
                        smallest=w*h;
498
                        *rw=w;
499
                        *rh=h;
500
                }
501
        }
502
        if (smallr<=0)
503
                Error("Could not fit font?\n");
504
}
505
 
506
static void ogl_init_font(grs_font * font)
507
{
508
        int oglflags = OGL_FLAG_ALPHA;
509
        int     nchars = font->ft_maxchar-font->ft_minchar+1;
510
        int w,h,tw,th,curx=0,cury=0;
511
        int gap=1; // x/y offset between the chars so we can filter
512
 
513
        th = tw = 0xffff;
514
 
515
        ogl_font_choose_size(font,gap,&tw,&th);
516
        {
517
                RAIIdmem<uint8_t[]> data;
518
                const unsigned length = tw * th;
519
                MALLOC(data, uint8_t, length);
520
                std::fill_n(data.get(), length, TRANSPARENCY_COLOR); // map the whole data with transparency so we won't have borders if using gap
521
                gr_init_main_bitmap(font->ft_parent_bitmap, bm_mode::linear, 0, 0, tw, th, tw, std::move(data));
522
        }
523
        gr_set_transparent(font->ft_parent_bitmap, 1);
524
 
525
        if (!(font->ft_flags & FT_COLOR))
526
                oglflags |= OGL_FLAG_NOCOLOR;
527
        ogl_init_texture(*(font->ft_parent_bitmap.gltexture = ogl_get_free_texture()), tw, th, oglflags); // have to init the gltexture here so the subbitmaps will find it.
528
 
529
        font->ft_bitmaps = std::make_unique<grs_bitmap[]>(nchars);
530
        h=font->ft_h;
531
 
532
        for(int i=0;i<nchars;i++)
533
        {
534
                if (font->ft_flags & FT_PROPORTIONAL)
535
                        w=font->ft_widths[i];
536
                else
537
                        w=font->ft_w;
538
 
539
                if (w<1 || w>256)
540
                        continue;
541
 
542
                if (curx+w+gap>tw)
543
                {
544
                        cury+=h+gap;
545
                        curx=0;
546
                }
547
 
548
                if (cury+h>th)
549
                        Error("font doesn't really fit (%i/%i)?\n",i,nchars);
550
 
551
                if (font->ft_flags & FT_COLOR)
552
                {
553
                        const auto fp = (font->ft_flags & FT_PROPORTIONAL)
554
                                ? font->ft_chars[i]
555
                                : font->ft_data + i * w*h;
556
                        for (int y=0;y<h;y++)
557
                        {
558
                                for (int x=0;x<w;x++)
559
                                {
560
                                        font->ft_parent_bitmap.get_bitmap_data()[curx+x+(cury+y)*tw] = fp[x+y*w];
561
                                        // Let's call this a HACK:
562
                                        // If we filter the fonts, the sliders will be messed up as the border pixels will have an
563
                                        // alpha value while filtering. So the slider bitmaps will not look "connected".
564
                                        // To prevent this, duplicate the first/last pixel-row with a 1-pixel offset.
565
                                        if (gap && i >= 99 && i <= 102)
566
                                        {
567
                                                // See which bitmaps need left/right shifts:
568
                                                // 99  = SLIDER_LEFT - shift RIGHT
569
                                                // 100 = SLIDER_RIGHT - shift LEFT
570
                                                // 101 = SLIDER_MIDDLE - shift LEFT+RIGHT
571
                                                // 102 = SLIDER_MARKER - shift RIGHT
572
 
573
                                                // shift left border
574
                                                if (x==0 && i != 99 && i != 102)
575
                                                        font->ft_parent_bitmap.get_bitmap_data()[(curx+x+(cury+y)*tw)-1] = fp[x+y*w];
576
 
577
                                                // shift right border
578
                                                if (x==w-1 && i != 100)
579
                                                        font->ft_parent_bitmap.get_bitmap_data()[(curx+x+(cury+y)*tw)+1] = fp[x+y*w];
580
                                        }
581
                                }
582
                        }
583
                }
584
                else
585
                {
586
                        int BitMask, bits = 0;
587
                        auto white = gr_find_closest_color(63, 63, 63);
588
                        auto fp = (font->ft_flags & FT_PROPORTIONAL)
589
                                ? font->ft_chars[i]
590
                                : font->ft_data + i * BITS_TO_BYTES(w)*h;
591
                        for (int y=0;y<h;y++){
592
                                BitMask=0;
593
                                for (int x=0; x< w; x++ )
594
                                {
595
                                        if (BitMask==0) {
596
                                                bits = *fp++;
597
                                                BitMask = 0x80;
598
                                        }
599
 
600
                                        if (bits & BitMask)
601
                                                font->ft_parent_bitmap.get_bitmap_data()[curx+x+(cury+y)*tw] = white;
602
                                        else
603
                                                font->ft_parent_bitmap.get_bitmap_data()[curx+x+(cury+y)*tw] = 255;
604
                                        BitMask >>= 1;
605
                                }
606
                        }
607
                }
608
                gr_init_sub_bitmap(font->ft_bitmaps[i],font->ft_parent_bitmap,curx,cury,w,h);
609
                curx+=w+gap;
610
        }
611
        ogl_loadbmtexture_f(font->ft_parent_bitmap, CGameCfg.TexFilt, 0, 0);
612
}
613
 
614
static int ogl_internal_string(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
615
{
616
        const char * text_ptr, * next_row, * text_ptr1;
617
        int letter;
618
        int xx,yy;
619
        int orig_color = canvas.cv_font_fg_color;//to allow easy reseting to default string color with colored strings -MPM
620
        int underline;
621
 
622
        next_row = s;
623
 
624
        yy = y;
625
 
626
        if (grd_curscreen->sc_canvas.cv_bitmap.get_type() != bm_mode::ogl)
627
                Error("carp.\n");
628
        const auto &&fspacy1 = FSPACY(1);
629
        const auto &&INFONT = font_character_extent(cv_font);
630
        const auto &&fontscale_x = FONTSCALE_X();
631
        const auto &&FONTSCALE_Y_ft_h = FONTSCALE_Y(cv_font.ft_h);
632
        ogl_colors colors;
633
        while (next_row != NULL)
634
        {
635
                text_ptr1 = next_row;
636
                next_row = NULL;
637
 
638
                text_ptr = text_ptr1;
639
 
640
                xx = x;
641
 
642
                if (xx==0x8000)                 //centered
643
                        xx = get_centered_x(canvas, cv_font, text_ptr);
644
 
645
                while (*text_ptr)
646
                {
647
 
648
                        if (*text_ptr == '\n' )
649
                        {
650
                                next_row = &text_ptr[1];
651
                                yy += FONTSCALE_Y_ft_h + fspacy1;
652
                                break;
653
                        }
654
 
655
                        letter = static_cast<uint8_t>(*text_ptr) - cv_font.ft_minchar;
656
 
657
                        const auto &result = get_char_width<int>(cv_font, text_ptr[0], text_ptr[1]);
658
                        const auto &spacing = result.spacing;
659
 
660
                        underline = 0;
661
                        if (!INFONT(letter) || static_cast<uint8_t>(*text_ptr) <= 0x06) //not in font, draw as space
662
                        {
663
                                CHECK_EMBEDDED_COLORS() else{
664
                                        xx += spacing;
665
                                        text_ptr++;
666
                                }
667
 
668
                                if (underline)
669
                                {
670
                                        const auto color = canvas.cv_font_fg_color;
671
                                        gr_rect(canvas, xx, yy + cv_font.ft_baseline + 2, xx + cv_font.ft_w, yy + cv_font.ft_baseline + 3, color);
672
                                }
673
 
674
                                continue;
675
                        }
676
 
677
                        const auto ft_w = (cv_font.ft_flags & FT_PROPORTIONAL)
678
                                ? cv_font.ft_widths[letter]
679
                                : cv_font.ft_w;
680
 
681
                        ogl_ubitmapm_cs(canvas, xx, yy, fontscale_x(ft_w), FONTSCALE_Y_ft_h, cv_font.ft_bitmaps[letter], (cv_font.ft_flags & FT_COLOR) ? colors.white : (canvas.cv_bitmap.get_type() == bm_mode::ogl) ? colors.init(canvas.cv_font_fg_color) : throw std::runtime_error("non-color string to non-ogl dest"), F1_0);
682
 
683
                        xx += spacing;
684
 
685
                        text_ptr++;
686
                }
687
        }
688
        return 0;
689
}
690
 
691
#define gr_internal_color_string ogl_internal_string
692
#endif //OGL
693
 
694
void gr_string(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
695
{
696
        int w, h;
697
        gr_get_string_size(cv_font, s, &w, &h, nullptr);
698
        gr_string(canvas, cv_font, x, y, s, w, h);
699
}
700
 
701
static void gr_ustring_mono(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
702
{
703
        switch (canvas.cv_bitmap.get_type())
704
        {
705
                case bm_mode::linear:
706
                        if (canvas.cv_font_bg_color == -1)
707
                                gr_internal_string0m(canvas, cv_font, x, y, s);
708
                        else
709
                                gr_internal_string0(canvas, cv_font, x, y, s);
710
        }
711
}
712
 
713
void gr_string(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s, const int w, const int h)
714
{
715
        if (y + h < 0)
716
                return;
717
        const auto bm_h = canvas.cv_bitmap.bm_h;
718
        if (y > bm_h)
719
                return;
720
        const auto bm_w = canvas.cv_bitmap.bm_w;
721
        int xw = w;
722
        if ( x == 0x8000 )      {
723
                // for x, since this will be centered, only look at
724
                // width.
725
        } else {
726
                if (x > bm_w)
727
                        return;
728
                xw += x;
729
                if (xw < 0)
730
                        return;
731
        }
732
        if (
733
#if DXX_USE_OGL
734
                canvas.cv_bitmap.get_type() == bm_mode::ogl ||
735
#endif
736
                cv_font.ft_flags & FT_COLOR)
737
        {
738
                gr_internal_color_string(canvas, cv_font, x, y, s);
739
                return;
740
        }
741
        // Partially clipped...
742
        if (!(y < 0 ||
743
                x < 0 ||
744
                xw > bm_w ||
745
                y + h > bm_h))
746
        {
747
                gr_ustring_mono(canvas, cv_font, x, y, s);
748
                return;
749
        }
750
 
751
        if (canvas.cv_font_bg_color == -1)
752
        {
753
                gr_internal_string_clipped_m(canvas, cv_font, x, y, s);
754
                return;
755
        }
756
        gr_internal_string_clipped(canvas, cv_font, x, y, s);
757
}
758
 
759
void gr_ustring(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
760
{
761
#if DXX_USE_OGL
762
        if (canvas.cv_bitmap.get_type() == bm_mode::ogl)
763
        {
764
                ogl_internal_string(canvas, cv_font, x, y, s);
765
                return;
766
        }
767
#endif
768
 
769
        if (canvas.cv_font->ft_flags & FT_COLOR) {
770
                gr_internal_color_string(canvas, cv_font, x, y, s);
771
        }
772
        else
773
                gr_ustring_mono(canvas, cv_font, x, y, s);
774
}
775
 
776
int gr_get_string_height(const grs_font &cv_font, const unsigned lines)
777
{
778
        const auto fontscale_y = FONTSCALE_Y(cv_font.ft_h);
779
        return static_cast<int>(fontscale_y + (lines * (fontscale_y + FSPACY(1))));
780
}
781
 
782
void gr_get_string_size(const grs_font &cv_font, const char *s, int *const string_width, int *const string_height, int *const average_width)
783
{
784
        gr_get_string_size(cv_font, s, string_width, string_height, average_width, UINT_MAX);
785
}
786
 
787
void gr_get_string_size(const grs_font &cv_font, const char *s, int *const string_width, int *const string_height, int *const average_width, const unsigned max_chars_per_line)
788
{
789
        float longest_width=0.0,string_width_f=0.0;
790
        unsigned lines = 0;
791
        if (average_width)
792
                *average_width = cv_font.ft_w;
793
        if (!string_width && !string_height)
794
                return;
795
        if (s)
796
        {
797
                unsigned remaining_chars_this_line = max_chars_per_line;
798
                while (*s)
799
                {
800
                        if (*s == '\n')
801
                        {
802
                                if (longest_width < string_width_f)
803
                                        longest_width = string_width_f;
804
                                string_width_f = 0;
805
                                const auto os = s;
806
                                while (*++s == '\n')
807
                                {
808
                                }
809
                                lines += s - os;
810
                                if (!*s)
811
                                        break;
812
                                remaining_chars_this_line = max_chars_per_line;
813
                        }
814
 
815
                        const auto &result = get_char_width<float>(cv_font, s[0], s[1]);
816
                        const auto &spacing = result.spacing;
817
                        string_width_f += spacing;
818
                        s++;
819
                        if (!--remaining_chars_this_line)
820
                                break;
821
                }
822
        }
823
        if (string_width)
824
                *string_width = std::max(longest_width, string_width_f);
825
        if (string_height)
826
                *string_height = gr_get_string_height(cv_font, lines);
827
}
828
 
829
std::pair<const char *, unsigned> gr_get_string_wrap(const grs_font &cv_font, const char *s, unsigned limit)
830
{
831
        assert(s);
832
        float string_width_f=0.0;
833
        const float limit_f = limit;
834
        for (uint8_t c; (c = *s); ++s)
835
        {
836
                const auto &&spacing = get_char_width<float>(cv_font, c, s[1]).spacing;
837
                const float next_string_width = string_width_f + spacing;
838
                if (next_string_width >= limit_f)
839
                        break;
840
                string_width_f = next_string_width;
841
        }
842
        return {s, static_cast<unsigned>(string_width_f)};
843
}
844
 
845
template <void (&F)(grs_canvas &, const grs_font &, int, int, const char *)>
846
void (gr_printt)(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const format, ...)
847
{
848
        char buffer[1000];
849
        va_list args;
850
 
851
        va_start(args, format );
852
        vsnprintf(buffer, sizeof(buffer), format, args);
853
        va_end(args);
854
        F(canvas, cv_font, x, y, buffer);
855
}
856
 
857
template void gr_printt<gr_string>(grs_canvas &, const grs_font &, int, int, const char *, ...);
858
template void gr_printt<gr_ustring>(grs_canvas &, const grs_font &, int, int, const char *, ...);
859
 
860
void gr_close_font(std::unique_ptr<grs_font> font)
861
{
862
        if (font)
863
        {
864
                //find font in list
865
                if (!(font->ft_flags & FT_COLOR))
866
                        return;
867
                auto e = end(open_font);
868
                auto i = std::find(begin(open_font), e, font.get());
869
                if (i == e)
870
                        throw std::logic_error("closing non-open font");
871
                *i = nullptr;
872
        }
873
}
874
 
875
//remap a font, re-reading its data & palette
876
static void gr_remap_font(grs_font *font, const char *fontname);
877
 
878
//remap (by re-reading) all the color fonts
879
void gr_remap_color_fonts()
880
{
881
        range_for (auto font, open_font)
882
        {
883
                if (font)
884
                        gr_remap_font(font, &font->ft_filename[0]);
885
        }
886
}
887
 
888
/*
889
 * reads a grs_font structure from a PHYSFS_File
890
 */
891
static void grs_font_read(grs_font *gf, PHYSFS_File *fp)
892
{
893
        gf->ft_w = PHYSFSX_readShort(fp);
894
        gf->ft_h = PHYSFSX_readShort(fp);
895
        gf->ft_flags = PHYSFSX_readShort(fp);
896
        gf->ft_baseline = PHYSFSX_readShort(fp);
897
        gf->ft_minchar = PHYSFSX_readByte(fp);
898
        gf->ft_maxchar = PHYSFSX_readByte(fp);
899
        PHYSFSX_readShort(fp);
900
        gf->ft_data = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(PHYSFSX_readInt(fp)) - GRS_FONT_SIZE);
901
        PHYSFSX_readInt(fp);
902
        gf->ft_widths = reinterpret_cast<int16_t *>(static_cast<uintptr_t>(PHYSFSX_readInt(fp)) - GRS_FONT_SIZE);
903
        gf->ft_kerndata = reinterpret_cast<uint8_t *>(static_cast<uintptr_t>(PHYSFSX_readInt(fp)) - GRS_FONT_SIZE);
904
}
905
 
906
static std::unique_ptr<grs_font> gr_internal_init_font(const char *fontname)
907
{
908
        const uint8_t *ptr;
909
        color_palette_index *ft_data;
910
        struct {
911
                std::array<char, 4> magic;
912
                unsigned datasize;      //size up to (but not including) palette
913
        } file_header;
914
 
915
        auto fontfile = PHYSFSX_openReadBuffered(fontname);
916
 
917
        if (!fontfile) {
918
                con_printf(CON_VERBOSE, "Can't open font file %s", fontname);
919
                return {};
920
        }
921
 
922
        static_assert(sizeof(file_header) == 8, "file header size error");
923
        if (PHYSFS_read(fontfile, &file_header, sizeof(file_header), 1) != 1 ||
924
                memcmp(file_header.magic.data(), "PSFN", 4) ||
925
                (file_header.datasize = INTEL_INT(file_header.datasize)) < GRS_FONT_SIZE)
926
        {
927
                con_printf(CON_NORMAL, "Invalid header in font file %s", fontname);
928
                return {};
929
        }
930
 
931
        file_header.datasize -= GRS_FONT_SIZE; // subtract the size of the header.
932
        const auto &datasize = file_header.datasize;
933
 
934
        auto font = std::make_unique<grs_font>();
935
        grs_font_read(font.get(), fontfile);
936
 
937
        const unsigned nchars = font->ft_maxchar - font->ft_minchar + 1;
938
        std::size_t ft_chars_storage = (font->ft_flags & FT_PROPORTIONAL)
939
                ? sizeof(uint8_t *) * nchars
940
                : 0;
941
 
942
        auto ft_allocdata = std::make_unique<color_palette_index[]>(datasize + ft_chars_storage);
943
        const auto font_data = &ft_allocdata[ft_chars_storage];
944
        if (PHYSFS_read(fontfile, font_data, 1, datasize) != datasize)
945
        {
946
                con_printf(CON_URGENT, "Insufficient data in font file %s", fontname);
947
                return {};
948
        }
949
 
950
        if (font->ft_flags & FT_PROPORTIONAL) {
951
                const auto offset_widths = reinterpret_cast<uintptr_t>(font->ft_widths);
952
                auto w = reinterpret_cast<short *>(&font_data[offset_widths]);
953
                if (offset_widths >= datasize || offset_widths + (nchars * sizeof(*w)) >= datasize)
954
                {
955
                        con_printf(CON_URGENT, "Missing widths in font file %s", fontname);
956
                        return {};
957
                }
958
                font->ft_widths = w;
959
                const auto offset_data = reinterpret_cast<uintptr_t>(font->ft_data);
960
                if (offset_data >= datasize)
961
                {
962
                        con_printf(CON_URGENT, "Missing data in font file %s", fontname);
963
                        return {};
964
                }
965
                font->ft_data = ptr = ft_data = &font_data[offset_data];
966
                const auto ft_chars = reinterpret_cast<const uint8_t **>(ft_allocdata.get());
967
                font->ft_chars = reinterpret_cast<const uint8_t *const *>(ft_chars);
968
 
969
                const unsigned is_color = font->ft_flags & FT_COLOR;
970
                const unsigned ft_h = font->ft_h;
971
                std::generate_n(ft_chars, nchars, [is_color, ft_h, &w, &ptr]{
972
                        const unsigned s = INTEL_SHORT(*w);
973
                        if constexpr (words_bigendian)
974
                                *w = static_cast<uint16_t>(s);
975
                        ++w;
976
                        const auto r = ptr;
977
                        ptr += ft_h * (is_color ? s : BITS_TO_BYTES(s));
978
                        return r;
979
                });
980
        } else  {
981
 
982
                font->ft_data   = ft_data = font_data;
983
                font->ft_widths = NULL;
984
 
985
                ptr = font->ft_data + (nchars * font->ft_w * font->ft_h);
986
        }
987
 
988
        if (font->ft_flags & FT_KERNED)
989
        {
990
                const auto offset_kerndata = reinterpret_cast<uintptr_t>(font->ft_kerndata);
991
                if (datasize <= offset_kerndata)
992
                {
993
                        con_printf(CON_URGENT, "Missing kerndata in font file %s", fontname);
994
                        return {};
995
                }
996
                const auto begin_kerndata = reinterpret_cast<const unsigned char *>(&font_data[offset_kerndata]);
997
                const auto end_font_data = &font_data[datasize - ((datasize - offset_kerndata + 2) % 3)];
998
                for (auto cur_kerndata = begin_kerndata;; cur_kerndata += 3)
999
                {
1000
                        if (cur_kerndata == end_font_data)
1001
                        {
1002
                                con_printf(CON_URGENT, "Unterminated kerndata in font file %s", fontname);
1003
                                return {};
1004
                        }
1005
                        if (*cur_kerndata == kerndata_terminator)
1006
                                break;
1007
                }
1008
                font->ft_kerndata = begin_kerndata;
1009
        }
1010
        else
1011
                font->ft_kerndata = nullptr;
1012
 
1013
        if (font->ft_flags & FT_COLOR) {                //remap palette
1014
                palette_array_t palette;
1015
                std::array<color_palette_index, 256> colormap;
1016
                /* `freq` exists so that decode_data can write to it, but it is
1017
                 * otherwise unused.  `decode_data` is not guaranteed to write
1018
                 * to every element, but the bitset constructor will initialize
1019
                 * the storage, so reading unwritten fields will always return
1020
                 * false.
1021
                 */
1022
                std::bitset<256> freq;
1023
 
1024
                PHYSFS_read(fontfile,&palette[0],sizeof(palette[0]),palette.size());            //read the palette
1025
 
1026
                build_colormap_good(palette, colormap);
1027
 
1028
                colormap[TRANSPARENCY_COLOR] = TRANSPARENCY_COLOR;              // changed from colormap[255] = 255 to this for macintosh
1029
 
1030
                decode_data(ft_data, ptr - font->ft_data, colormap, freq );
1031
        }
1032
        fontfile.reset();
1033
        //set curcanv vars
1034
 
1035
        auto &ft_filename = font->ft_filename;
1036
        font->ft_allocdata = move(ft_allocdata);
1037
        strncpy(&ft_filename[0], fontname, ft_filename.size());
1038
        return font;
1039
}
1040
 
1041
grs_font_ptr gr_init_font(grs_canvas &canvas, const char *fontname)
1042
{
1043
        auto font = gr_internal_init_font(fontname);
1044
        if (!font)
1045
                return {};
1046
 
1047
        canvas.cv_font        = font.get();
1048
        canvas.cv_font_fg_color    = 0;
1049
        canvas.cv_font_bg_color    = 0;
1050
        if (font->ft_flags & FT_COLOR)
1051
        {
1052
                auto e = end(open_font);
1053
                auto i = std::find(begin(open_font), e, static_cast<grs_font *>(nullptr));
1054
                if (i == e)
1055
                        throw std::logic_error("too many open fonts");
1056
                *i = font.get();
1057
        }
1058
#if DXX_USE_OGL
1059
        ogl_init_font(font.get());
1060
#endif
1061
        return grs_font_ptr(font.release());
1062
}
1063
 
1064
//remap a font by re-reading its data & palette
1065
void gr_remap_font(grs_font *font, const char *fontname)
1066
{
1067
        auto n = gr_internal_init_font(fontname);
1068
        if (!n)
1069
                return;
1070
        if (!(n->ft_flags & FT_COLOR))
1071
                return;
1072
        *font = std::move(*n.get());
1073
#if DXX_USE_OGL
1074
        ogl_init_font(font);
1075
#endif
1076
}
1077
 
1078
void gr_set_curfont(grs_canvas &canvas, const grs_font *n)
1079
{
1080
        canvas.cv_font = n;
1081
}
1082
 
1083
template <bool masked_draws_background>
1084
static int gr_internal_string_clipped_template(grs_canvas &canvas, const grs_font &cv_font, int x, int y, const char *const s)
1085
{
1086
        int letter;
1087
        int x1 = x, last_x;
1088
 
1089
        const auto &&INFONT = font_character_extent(cv_font);
1090
        const auto ft_flags = cv_font.ft_flags;
1091
        const auto proportional = ft_flags & FT_PROPORTIONAL;
1092
        const auto cv_font_bg_color = canvas.cv_font_bg_color;
1093
 
1094
        for (auto next_row = s; next_row;)
1095
        {
1096
                const auto text_ptr1 = next_row;
1097
                next_row = NULL;
1098
 
1099
                x = x1;
1100
                if (x==0x8000)                  //centered
1101
                        x = get_centered_x(canvas, cv_font, text_ptr1);
1102
 
1103
                last_x = x;
1104
 
1105
                for (int r=0; r < cv_font.ft_h; r++) {
1106
                        auto text_ptr = text_ptr1;
1107
                        x = last_x;
1108
 
1109
                        for (; const uint8_t c0 = *text_ptr; ++text_ptr)
1110
                        {
1111
                                if (c0 == '\n')
1112
                                {
1113
                                        next_row = &text_ptr[1];
1114
                                        break;
1115
                                }
1116
                                if (c0 == CC_COLOR)
1117
                                {
1118
                                        canvas.cv_font_fg_color = static_cast<uint8_t>(*++text_ptr);
1119
                                        continue;
1120
                                }
1121
 
1122
                                if (c0 == CC_LSPACING)
1123
                                {
1124
                                        Int3(); //      Warning: skip lines not supported for clipped strings.
1125
                                        text_ptr += 1;
1126
                                        continue;
1127
                                }
1128
 
1129
                                const auto underline = unlikely(c0 == CC_UNDERLINE)
1130
                                        ? ++text_ptr, r == cv_font.ft_baseline + 2 || r == cv_font.ft_baseline + 3
1131
                                        : 0;
1132
                                const uint8_t c = *text_ptr;
1133
                                const auto &result = get_char_width<int>(cv_font, c, text_ptr[1]);
1134
                                const auto &width = result.width;
1135
                                const auto &spacing = result.spacing;
1136
 
1137
                                letter = c - cv_font.ft_minchar;
1138
 
1139
                                if (!INFONT(letter)) {  //not in font, draw as space
1140
                                        x += spacing;
1141
                                        continue;
1142
                                }
1143
                                const auto cv_font_fg_color = canvas.cv_font_fg_color;
1144
                                auto color = cv_font_fg_color;
1145
                                if (width)
1146
                                {
1147
                                if (underline)  {
1148
                                        for (uint_fast32_t i = width; i--;)
1149
                                        {
1150
                                                gr_pixel(canvas.cv_bitmap, x++, y, color);
1151
                                        }
1152
                                } else {
1153
                                        auto fp = proportional ? cv_font.ft_chars[letter] : cv_font.ft_data + letter * BITS_TO_BYTES(width) * cv_font.ft_h;
1154
                                        fp += BITS_TO_BYTES(width)*r;
1155
 
1156
                                        /* Setting bits=0 is a dead store, but is necessary to
1157
                                         * prevent -Og -Wuninitialized from issuing a bogus
1158
                                         * warning.  -Og does not see that bits_remaining=0
1159
                                         * guarantees that bits will be initialized from *fp before
1160
                                         * it is read.
1161
                                         */
1162
                                        uint8_t bits_remaining = 0, bits = 0;
1163
 
1164
                                        for (uint_fast32_t i = width; i--; ++x, --bits_remaining)
1165
                                        {
1166
                                                if (!bits_remaining)
1167
                                                {
1168
                                                        bits = *fp++;
1169
                                                        bits_remaining = 8;
1170
                                                }
1171
                                                const auto bit_enabled = (bits & 0x80);
1172
                                                bits <<= 1;
1173
                                                if constexpr (masked_draws_background)
1174
                                                        color = bit_enabled ? cv_font_fg_color : cv_font_bg_color;
1175
                                                else
1176
                                                {
1177
                                                        if (!bit_enabled)
1178
                                                                continue;
1179
                                                }
1180
                                                gr_pixel(canvas.cv_bitmap, x, y, color);
1181
                                        }
1182
                                }
1183
                                }
1184
                                x += spacing-width;             //for kerning
1185
                        }
1186
                        y++;
1187
                }
1188
        }
1189
        return 0;
1190
}
1191
 
1192
static int gr_internal_string_clipped_m(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
1193
{
1194
        return gr_internal_string_clipped_template<true>(canvas, cv_font, x, y, s);
1195
}
1196
 
1197
static int gr_internal_string_clipped(grs_canvas &canvas, const grs_font &cv_font, const int x, const int y, const char *const s)
1198
{
1199
        return gr_internal_string_clipped_template<false>(canvas, cv_font, x, y, s);
1200
}
1201
 
1202
}