1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
7 // Functions related to fonts.
9 #define DE_NOT_IN_MODULE
10 #include "deark-config.h"
11 #include "deark-private.h"
13 static int is_valid_char(struct de_bitmap_font_char
*ch
)
16 if(!ch
->bitmap
) return 0;
17 if(ch
->width
<1 || ch
->height
<1) return 0;
21 struct de_bitmap_font
*de_create_bitmap_font(deark
*c
)
23 struct de_bitmap_font
*font
;
24 font
= de_malloc(c
, sizeof(struct de_bitmap_font
));
25 font
->index_of_replacement_char
= -1;
29 void de_destroy_bitmap_font(deark
*c
, struct de_bitmap_font
*font
)
34 static void paint_character_internal(deark
*c
, de_bitmap
*img
,
35 struct de_bitmap_font_char
*ch
,
36 i64 xpos
, i64 ypos
, u32 fgcol
, unsigned int flags
)
39 i64 num_x_pixels_to_paint
;
42 num_x_pixels_to_paint
= (i64
)ch
->width
;
43 if((flags
&DE_PAINTFLAG_VGA9COL
) && ch
->width
==8) {
45 num_x_pixels_to_paint
= 9;
48 for(j
=0; j
<ch
->height
; j
++) {
52 if(flags
&DE_PAINTFLAG_TOPHALF
) {
55 else if(flags
&DE_PAINTFLAG_BOTTOMHALF
) {
56 j_src
= (ch
->height
+j
)/2;
59 for(i
=0; i
<num_x_pixels_to_paint
; i
++) {
60 i64 i_src
; // -1 = No source position
65 if(flags
&DE_PAINTFLAG_LEFTHALF
) {
68 else if(flags
&DE_PAINTFLAG_RIGHTHALF
) {
69 i_src
= (num_x_pixels_to_paint
+i
)/2;
72 if(i_src
==8 && vga9col_flag
) {
73 // Manufacture a column 8.
74 if(ch
->codepoint_nonunicode
>=0xb0 && ch
->codepoint_nonunicode
<=0xdf) {
75 i_src
= 7; // Make this pixel a duplicate of the one in col #7.
78 i_src
= -1; // Make this pixel a background pixel.
82 if(i_src
>=0 && i_src
<ch
->width
) {
83 x
= ch
->bitmap
[j_src
*ch
->rowspan
+ i_src
/8];
84 if(x
& (1<<(7-i_src
%8))) {
92 de_bitmap_setpixel_rgba(img
, xpos
+i
, ypos
+j
, clr
);
98 // Paint a character at the given index in the given font, to the given bitmap.
99 void de_font_paint_character_idx(deark
*c
, de_bitmap
*img
,
100 struct de_bitmap_font
*font
, i64 char_idx
,
101 i64 xpos
, i64 ypos
, de_color fgcol
, de_color bgcol
,
104 struct de_bitmap_font_char
*ch
;
106 if(char_idx
<0 || char_idx
>=font
->num_chars
) return;
107 ch
= &font
->char_array
[char_idx
];
108 if(!is_valid_char(ch
)) return;
109 if(ch
->width
> font
->nominal_width
) return;
110 if(ch
->height
> font
->nominal_height
) return;
112 // Paint a "canvas" for the char, if needed.
114 // If the "extraspace" feature is used, paint an additional canvas of
115 // a different color.
116 if(!(flags
&DE_PAINTFLAG_TRNSBKGD
) && (ch
->extraspace_l
|| ch
->extraspace_r
)) {
117 i64 canvas_x
, canvas_y
;
118 i64 canvas_w
, canvas_h
;
121 canvas_y
= ypos
+ch
->v_offset
;
122 canvas_w
= (i64
)ch
->extraspace_l
+ (i64
)ch
->width
+ (i64
)ch
->extraspace_r
;
123 canvas_h
= ch
->height
;
124 // (We don't need to support both the "extraspace" and the VGA9COL
125 // feature at the same time.)
127 de_bitmap_rect(img
, canvas_x
, canvas_y
,
128 canvas_w
, canvas_h
, DE_MAKE_RGB(192,255,192), 0);
131 // Paint the canvas for the main part of the character.
132 if(!(flags
&DE_PAINTFLAG_TRNSBKGD
)) {
133 i64 canvas_x
, canvas_y
;
134 i64 canvas_w
, canvas_h
;
136 canvas_x
= xpos
+ ch
->extraspace_l
;
137 canvas_y
= ypos
+ch
->v_offset
;
138 canvas_w
= ch
->width
;
139 if((flags
&DE_PAINTFLAG_VGA9COL
) && ch
->width
==8) {
142 canvas_h
= ch
->height
;
144 de_bitmap_rect(img
, canvas_x
, canvas_y
,
145 canvas_w
, canvas_h
, bgcol
, 0);
148 paint_character_internal(c
, img
, ch
,
149 ch
->extraspace_l
+ xpos
, ch
->v_offset
+ ypos
, fgcol
, flags
);
152 // Given a codepoint, returns the character index in the font.
153 // 'codepoint' is expected to be a Unicode codepoint. If the font does not
154 // have Unicode codepoints, the non-Unicode codepoint will be used instead.
155 // Returns -1 if not found.
156 static i64
get_char_idx_by_cp(deark
*c
, struct de_bitmap_font
*font
, i32 codepoint
)
160 // TODO: Sometimes, a font has multiple characters that map to the same
161 // codepoint. We should have a way to find the *best* such character,
162 // which might not be the first one.
164 for(i
=0; i
<font
->num_chars
; i
++) {
165 if(font
->has_unicode_codepoints
) {
166 if(font
->char_array
[i
].codepoint_unicode
== codepoint
)
170 if(font
->char_array
[i
].codepoint_nonunicode
== codepoint
)
177 // 'codepoint' is expected to be a Unicode codepoint. If the font does not
178 // have Unicode codepoints, the non-Unicode codepoint will be used instead.
179 void de_font_paint_character_cp(deark
*c
, de_bitmap
*img
,
180 struct de_bitmap_font
*font
, i32 codepoint
,
181 i64 xpos
, i64 ypos
, de_color fgcol
, de_color bgcol
, unsigned int flags
)
185 char_idx
= get_char_idx_by_cp(c
, font
, codepoint
);
187 if(font
->index_of_replacement_char
>=0) {
188 char_idx
= font
->index_of_replacement_char
;
192 char_idx
= get_char_idx_by_cp(c
, font
, '?');
194 if(char_idx
<0 || char_idx
>=font
->num_chars
) {
197 de_font_paint_character_idx(c
, img
, font
, char_idx
, xpos
, ypos
, fgcol
, bgcol
, flags
);
200 struct dfont_char_data
{
201 i32 codepoint_unicode
;
205 static const struct dfont_char_data dfont_data
[16] = {
206 {48, {0x30,0x48,0x48,0x48,0x48,0x48,0x30}}, // 0
207 {49, {0x10,0x30,0x10,0x10,0x10,0x10,0x38}}, // 1
208 {50, {0x70,0x08,0x08,0x30,0x40,0x40,0x78}}, // 2
209 {51, {0x70,0x08,0x08,0x30,0x08,0x08,0x70}}, // 3
210 {52, {0x48,0x48,0x48,0x78,0x08,0x08,0x08}}, // 4
211 {53, {0x78,0x40,0x40,0x70,0x08,0x08,0x70}}, // 5
212 {54, {0x38,0x40,0x40,0x70,0x48,0x48,0x30}}, // 6
213 {55, {0x78,0x08,0x08,0x10,0x10,0x10,0x10}}, // 7
214 {56, {0x30,0x48,0x48,0x30,0x48,0x48,0x30}}, // 8
215 {57, {0x30,0x48,0x48,0x38,0x08,0x08,0x70}}, // 9
216 {65, {0x30,0x48,0x48,0x78,0x48,0x48,0x48}}, // A
217 {66, {0x70,0x48,0x48,0x70,0x48,0x48,0x70}}, // B
218 {67, {0x30,0x48,0x40,0x40,0x40,0x48,0x30}}, // C
219 {68, {0x70,0x58,0x48,0x48,0x48,0x58,0x70}}, // D
220 {69, {0x78,0x40,0x40,0x70,0x40,0x40,0x78}}, // E
221 {70, {0x78,0x40,0x40,0x70,0x40,0x40,0x40}} // F
224 static struct de_bitmap_font
*make_digit_font(deark
*c
)
226 struct de_bitmap_font
*dfont
= NULL
;
229 dfont
= de_create_bitmap_font(c
);
230 dfont
->num_chars
= 16;
231 dfont
->nominal_width
= 6;
232 dfont
->nominal_height
= 7;
233 dfont
->has_unicode_codepoints
= 1;
234 dfont
->char_array
= de_mallocarray(c
, dfont
->num_chars
, sizeof(struct de_bitmap_font_char
));
236 for(i
=0; i
<dfont
->num_chars
; i
++) {
237 dfont
->char_array
[i
].codepoint_unicode
= dfont_data
[i
].codepoint_unicode
;
238 dfont
->char_array
[i
].width
= dfont
->nominal_width
;
239 dfont
->char_array
[i
].height
= dfont
->nominal_height
;
240 dfont
->char_array
[i
].rowspan
= 1;
241 dfont
->char_array
[i
].bitmap
= (u8
*)dfont_data
[i
].bitmap
;
247 struct font_render_ctx
{
248 struct de_bitmap_font
*font
;
249 i32 min_codepoint
; // currently unused
252 int render_as_unicode
;
254 // Array of the actual codepoints we will use when dumping the font
255 // to an image. Size is font->num_chars.
259 #define DNFLAG_HEX 0x1
260 #define DNFLAG_LEADING_ZEROES 0x2
261 #define DNFLAG_HCENTER 0x4
263 // (xpos,ypos) is the lower-right corner
264 // (or the bottom-center, if hcenter==1).
265 static void draw_number(deark
*c
, de_bitmap
*img
,
266 struct de_bitmap_font
*dfont
, i64 n
, i64 xpos1
, i64 ypos1
,
275 if(flags
& DNFLAG_HEX
) {
276 if(flags
& DNFLAG_LEADING_ZEROES
)
277 de_snprintf(buf
, sizeof(buf
), "%04X", (unsigned int)n
);
279 de_snprintf(buf
, sizeof(buf
), "%X", (unsigned int)n
);
282 de_snprintf(buf
, sizeof(buf
), "%u", (unsigned int)n
);
284 len
= (i64
)de_strlen(buf
);
286 if(flags
& DNFLAG_HCENTER
)
287 xpos_start
= xpos1
-(dfont
->nominal_width
*len
)/2;
289 xpos_start
= xpos1
-dfont
->nominal_width
*len
;
291 // Make sure number doesn't go beyond the image
292 if(xpos_start
+ dfont
->nominal_width
*len
> img
->width
) {
293 xpos_start
= img
->width
- dfont
->nominal_width
*len
;
296 for(i
=len
-1; i
>=0; i
--) {
297 xpos
= xpos_start
+ dfont
->nominal_width
*i
;
298 ypos
= ypos1
-dfont
->nominal_height
;
299 de_font_paint_character_cp(c
, img
, dfont
, buf
[i
], xpos
, ypos
,
300 DE_MAKE_GRAY(255), 0, DE_PAINTFLAG_TRNSBKGD
);
304 static void get_min_max_codepoint(struct font_render_ctx
*fctx
)
308 fctx
->min_codepoint
= 0x10ffff;
309 fctx
->max_codepoint
= 0;
310 fctx
->num_valid_chars
= 0;
312 for(i
=0; i
<fctx
->font
->num_chars
; i
++) {
313 if(!is_valid_char(&fctx
->font
->char_array
[i
])) continue;
314 if(fctx
->codepoint_tmp
[i
] == DE_CODEPOINT_INVALID
) continue;
315 fctx
->num_valid_chars
++;
316 if(fctx
->codepoint_tmp
[i
] < fctx
->min_codepoint
)
317 fctx
->min_codepoint
= fctx
->codepoint_tmp
[i
];
318 if(fctx
->codepoint_tmp
[i
] > fctx
->max_codepoint
)
319 fctx
->max_codepoint
= fctx
->codepoint_tmp
[i
];
323 // Put the actual codepont to use in the font->char_array[].codepoint_tmp field.
324 static void fixup_codepoints(deark
*c
, struct font_render_ctx
*fctx
)
328 i64 num_uncoded_chars
= 0;
329 u8
*used_codepoint_map
= NULL
;
330 u8 codepoint_already_used
;
332 if(!fctx
->render_as_unicode
) {
333 for(i
=0; i
<fctx
->font
->num_chars
; i
++) {
334 fctx
->codepoint_tmp
[i
] = fctx
->font
->char_array
[i
].codepoint_nonunicode
;
339 // An array of bits to remember if we've seen a codepoint before (BMP only).
340 // A character with a duplicate codepoint will be moved to another
341 // location, so that it doesn't get painted over the previous one.
342 used_codepoint_map
= de_malloc(c
, 65536/8);
344 for(i
=0; i
<fctx
->font
->num_chars
; i
++) {
345 if(!is_valid_char(&fctx
->font
->char_array
[i
])) continue;
346 c1
= fctx
->font
->char_array
[i
].codepoint_unicode
;
348 codepoint_already_used
= 0;
349 if(c1
>=0 && c1
<65536) {
350 // Check if we've seen this codepoint before.
351 codepoint_already_used
= used_codepoint_map
[c1
/8] & (1<<(c1
%8));
353 // Remember that we've seen this codepoint.
354 used_codepoint_map
[c1
/8] |= 1<<(c1
%8);
357 if(codepoint_already_used
|| c1
==DE_CODEPOINT_INVALID
) {
358 if(codepoint_already_used
) {
359 de_dbg2(c
, "moving duplicate codepoint U+%04x at index %d to private use area",
360 (unsigned int)c1
, (int)i
);
362 // Move uncoded characters to a Private Use area.
363 // (Supplementary Private Use Area-A = U+F0000 - U+FFFFD)
364 if(DE_CODEPOINT_MOVED
+ num_uncoded_chars
<= DE_CODEPOINT_MOVED_MAX
) {
365 fctx
->codepoint_tmp
[i
] = (i32
)(DE_CODEPOINT_MOVED
+ num_uncoded_chars
);
370 fctx
->codepoint_tmp
[i
] = c1
;
375 de_free(c
, used_codepoint_map
);
378 struct row_info_struct
{
383 struct col_info_struct
{
388 static void checkerboard_bkgd(de_bitmap
*img
, i64 xpos
, i64 ypos
, i64 w
, i64 h
)
392 for(jj
=0; jj
<h
; jj
++) {
393 for(ii
=0; ii
<w
; ii
++) {
394 de_bitmap_setpixel_gray(img
, xpos
+ii
, ypos
+jj
, (ii
/2+jj
/2)%2 ? 176 : 192);
399 void de_font_bitmap_font_to_image(deark
*c
, struct de_bitmap_font
*font1
, de_finfo
*fi
,
400 unsigned int createflags
)
402 struct font_render_ctx
*fctx
= NULL
;
404 de_bitmap
*img
= NULL
;
406 i64 img_leftmargin
, img_topmargin
;
407 i64 img_rightmargin
, img_bottommargin
;
408 i64 img_vpixelsperchar
;
409 i64 img_width
, img_height
;
410 i64 num_table_rows_to_display
;
411 i64 num_table_rows_total
;
413 struct de_bitmap_font
*dfont
= NULL
;
414 i64 chars_per_row
= 32;
416 struct row_info_struct
*row_info
= NULL
;
417 struct col_info_struct
*col_info
= NULL
;
422 unsigned int dnflags
;
424 fctx
= de_malloc(c
, sizeof(struct font_render_ctx
));
427 if(fctx
->font
->num_chars
<1) goto done
;
428 if(fctx
->font
->num_chars
>17*65536) goto done
;
429 if(fctx
->font
->nominal_width
>512 || fctx
->font
->nominal_height
>512) {
430 de_err(c
, "Font size too big (%d"DE_CHAR_TIMES
"%d). Not supported.",
431 (int)fctx
->font
->nominal_width
, (int)fctx
->font
->nominal_height
);
435 // -1 = "no preference"
436 unicode_req
= de_get_ext_option_bool(c
, "font:tounicode", -1);
439 (fctx
->font
->has_nonunicode_codepoints
|| !fctx
->font
->has_unicode_codepoints
))
441 ; // Render as nonunicode.
443 else if(fctx
->font
->has_unicode_codepoints
&&
444 (unicode_req
>0 || fctx
->font
->prefer_unicode
|| !fctx
->font
->has_nonunicode_codepoints
))
446 fctx
->render_as_unicode
= 1;
449 s
= de_get_ext_option(c
, "font:charsperrow");
451 chars_per_row
= de_atoi64(s
);
452 if(chars_per_row
<1) chars_per_row
=1;
455 dfont
= make_digit_font(c
);
457 fctx
->codepoint_tmp
= de_mallocarray(c
, fctx
->font
->num_chars
, sizeof(i32
));
458 fixup_codepoints(c
, fctx
);
460 get_min_max_codepoint(fctx
);
461 if(fctx
->num_valid_chars
<1) goto done
;
462 num_table_rows_total
= fctx
->max_codepoint
/chars_per_row
+1;
464 // TODO: Clean up these margin calculations, and make it more general.
465 if(fctx
->render_as_unicode
) {
466 img_leftmargin
= (i64
)dfont
->nominal_width
* 5 + 6;
469 if(fctx
->max_codepoint
>= 1000)
470 img_leftmargin
= (i64
)dfont
->nominal_width
* 5 + 6;
472 img_leftmargin
= (i64
)dfont
->nominal_width
* 3 + 6;
474 img_topmargin
= (i64
)dfont
->nominal_height
+ 6;
476 img_bottommargin
= 1;
478 // Scan the characters, and record relevant information.
479 row_info
= de_mallocarray(c
, num_table_rows_total
, sizeof(struct row_info_struct
));
480 col_info
= de_mallocarray(c
, chars_per_row
, sizeof(struct col_info_struct
));
481 for(i
=0; i
<chars_per_row
; i
++) {
482 #define MIN_CHAR_CELL_WIDTH 5
483 col_info
[i
].display_width
= MIN_CHAR_CELL_WIDTH
;
486 for(k
=0; k
<fctx
->font
->num_chars
; k
++) {
487 i64 char_display_width
;
489 if(fctx
->codepoint_tmp
[k
] == DE_CODEPOINT_INVALID
) continue;
490 if(!is_valid_char(&fctx
->font
->char_array
[k
])) continue;
491 rownum
= fctx
->codepoint_tmp
[k
] / chars_per_row
;
492 colnum
= fctx
->codepoint_tmp
[k
] % chars_per_row
;
493 if(rownum
<0 || rownum
>=num_table_rows_total
) {
494 de_internal_err_fatal(c
, "bad rownum");
497 // Remember that there is at least one valid character in this character's row.
498 row_info
[rownum
].is_visible
= 1;
500 // Track the maximum width of any character in this character's column.
501 char_display_width
= (i64
)fctx
->font
->char_array
[k
].width
+
502 (i64
)fctx
->font
->char_array
[k
].extraspace_l
+
503 (i64
)fctx
->font
->char_array
[k
].extraspace_r
;
504 if(char_display_width
> col_info
[colnum
].display_width
) {
505 col_info
[colnum
].display_width
= char_display_width
;
509 img_vpixelsperchar
= (i64
)fctx
->font
->nominal_height
+ 1;
511 // Figure out how many rows are used, and where to draw them.
512 num_table_rows_to_display
= 0;
514 curpos
= img_topmargin
;
515 for(j
=0; j
<num_table_rows_total
; j
++) {
516 if(!row_info
[j
].is_visible
) continue;
518 // If we skipped one or more rows, leave some extra vertical space.
519 if(num_table_rows_to_display
>0 && !row_info
[j
-1].is_visible
) curpos
+=3;
522 row_info
[j
].display_pos
= curpos
;
523 curpos
+= img_vpixelsperchar
;
524 num_table_rows_to_display
++;
526 if(num_table_rows_to_display
<1) goto done
;
528 // Figure out the positions of the columns.
529 curpos
= img_leftmargin
;
530 for(i
=0; i
<chars_per_row
; i
++) {
531 col_info
[i
].display_pos
= curpos
;
532 curpos
+= col_info
[i
].display_width
+ 1;
535 img_width
= col_info
[chars_per_row
-1].display_pos
+
536 col_info
[chars_per_row
-1].display_width
+ img_rightmargin
;
537 img_height
= row_info
[last_valid_row
].display_pos
+
538 img_vpixelsperchar
-1 + img_bottommargin
;
540 img
= de_bitmap_create(c
, img_width
, img_height
, 3);
543 de_bitmap_rect(img
, 0, 0, img
->width
, img
->height
, DE_MAKE_RGB(32,32,144), 0);
545 // Draw/clear the cell backgrounds
546 for(j
=0; j
<num_table_rows_total
; j
++) {
547 if(!row_info
[j
].is_visible
) continue;
548 ypos
= row_info
[j
].display_pos
;
550 for(i
=0; i
<chars_per_row
; i
++) {
551 xpos
= col_info
[i
].display_pos
;
552 de_bitmap_rect(img
, xpos
, ypos
,
553 col_info
[i
].display_width
, img_vpixelsperchar
-1,
554 DE_MAKE_RGB(112,112,160), 0);
558 // Draw the labels in the top margin.
560 // TODO: Better label spacing logic.
561 if(fctx
->font
->nominal_width
<= 12)
566 for(i
=0; i
<chars_per_row
; i
++) {
567 if(i
%label_stride
!= 0) continue;
568 xpos
= col_info
[i
].display_pos
+ col_info
[i
].display_width
/2;
569 ypos
= img_topmargin
- 3;
571 dnflags
= DNFLAG_HCENTER
;
572 if(fctx
->render_as_unicode
) dnflags
|= DNFLAG_HEX
;
574 draw_number(c
, img
, dfont
, i
, xpos
, ypos
, dnflags
);
577 // Draw the labels in the left margin.
578 for(j
=0; j
<num_table_rows_total
; j
++) {
579 if(!row_info
[j
].is_visible
) continue;
580 xpos
= img_leftmargin
- 3;
581 ypos
= row_info
[j
].display_pos
+ (img_vpixelsperchar
+ dfont
->nominal_height
+ 1)/2;
584 if(fctx
->render_as_unicode
) dnflags
|= DNFLAG_HEX
| DNFLAG_LEADING_ZEROES
;
586 draw_number(c
, img
, dfont
, j
*chars_per_row
, xpos
, ypos
, dnflags
);
589 // Render the glyphs.
590 for(k
=0; k
<fctx
->font
->num_chars
; k
++) {
591 if(fctx
->codepoint_tmp
[k
] == DE_CODEPOINT_INVALID
) continue;
592 if(!is_valid_char(&fctx
->font
->char_array
[k
])) continue;
594 rownum
= fctx
->codepoint_tmp
[k
] / chars_per_row
;
595 colnum
= fctx
->codepoint_tmp
[k
] % chars_per_row
;
596 if(rownum
<0 || rownum
>=num_table_rows_total
) {
597 de_internal_err_fatal(c
, "bad rownum");
600 xpos
= col_info
[colnum
].display_pos
;
601 ypos
= row_info
[rownum
].display_pos
;
603 checkerboard_bkgd(img
, xpos
, ypos
,
604 col_info
[colnum
].display_width
, img_vpixelsperchar
-1);
606 de_font_paint_character_idx(c
, img
, fctx
->font
, k
, xpos
, ypos
,
607 DE_STOCKCOLOR_BLACK
, DE_STOCKCOLOR_WHITE
, 0);
610 de_bitmap_write_to_file_finfo(img
, fi
, createflags
);
614 de_free(c
, dfont
->char_array
);
615 de_destroy_bitmap_font(c
, dfont
);
617 de_bitmap_destroy(img
);
618 de_free(c
, row_info
);
619 de_free(c
, col_info
);
621 de_free(c
, fctx
->codepoint_tmp
);
626 // Do we recognize the font as a standard VGA CP437 font?
627 // This function is quick and dirty. Ideally we would:
628 // * look at each character, instead or requiring the whole font to be identical
629 // * recognize fonts with other character sets
630 int de_font_is_standard_vga_font(deark
*c
, u32 crc
)
633 case 0x2c3cf7d2U
: // e.g.: ndh - Ada.xb
634 case 0x3c0aa3eeU
: // https://commons.wikimedia.org/w/index.php?title=File:Codepage-437.png&oldid=153353189
635 case 0x71e15998U
: // Used in many XBIN files.
636 case 0xb6133c6eU
: // blocktronics_baud_dudes/k1-strax.xb (8x14)
637 case 0xb7cb6e5cU
: // e.g.: T1-XBIN.XB