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 | } |