Edited some module descriptions
[deark.git] / src / deark-font.c
blobf98d813cf18e1d7fa18981d998004f9a83d7e0ef
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // deark-font.c
6 //
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)
15 if(!ch) return 0;
16 if(!ch->bitmap) return 0;
17 if(ch->width<1 || ch->height<1) return 0;
18 return 1;
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;
26 return font;
29 void de_destroy_bitmap_font(deark *c, struct de_bitmap_font *font)
31 de_free(c, 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)
38 i64 i, j;
39 i64 num_x_pixels_to_paint;
40 int vga9col_flag = 0;
42 num_x_pixels_to_paint = (i64)ch->width;
43 if((flags&DE_PAINTFLAG_VGA9COL) && ch->width==8) {
44 vga9col_flag = 1;
45 num_x_pixels_to_paint = 9;
48 for(j=0; j<ch->height; j++) {
49 i64 j_src;
51 j_src = j;
52 if(flags&DE_PAINTFLAG_TOPHALF) {
53 j_src = j/2;
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
61 int is_fg = 0;
62 u8 x;
64 i_src = i;
65 if(flags&DE_PAINTFLAG_LEFTHALF) {
66 i_src = i/2;
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.
77 else {
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))) {
85 is_fg = 1;
89 if(is_fg) {
90 u32 clr = fgcol;
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,
102 unsigned int flags)
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;
120 canvas_x = xpos;
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) {
140 canvas_w++;
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)
158 i64 i;
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)
167 return i;
169 else {
170 if(font->char_array[i].codepoint_nonunicode == codepoint)
171 return i;
174 return -1;
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)
183 i64 char_idx;
185 char_idx = get_char_idx_by_cp(c, font, codepoint);
186 if(char_idx<0) {
187 if(font->index_of_replacement_char>=0) {
188 char_idx = font->index_of_replacement_char;
191 if(char_idx<0) {
192 char_idx = get_char_idx_by_cp(c, font, '?');
194 if(char_idx<0 || char_idx>=font->num_chars) {
195 return;
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;
202 u8 bitmap[7];
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;
227 i64 i;
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;
244 return dfont;
247 struct font_render_ctx {
248 struct de_bitmap_font *font;
249 i32 min_codepoint; // currently unused
250 i32 max_codepoint;
251 i64 num_valid_chars;
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.
256 i32 *codepoint_tmp;
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,
267 unsigned int flags)
269 char buf[32];
270 i64 len;
271 i64 i;
272 i64 xpos_start;
273 i64 xpos, ypos;
275 if(flags & DNFLAG_HEX) {
276 if(flags & DNFLAG_LEADING_ZEROES)
277 de_snprintf(buf, sizeof(buf), "%04X", (unsigned int)n);
278 else
279 de_snprintf(buf, sizeof(buf), "%X", (unsigned int)n);
281 else {
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;
288 else
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)
306 i64 i;
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)
326 i64 i;
327 i32 c1;
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;
336 goto done;
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);
366 num_uncoded_chars++;
369 else {
370 fctx->codepoint_tmp[i] = c1;
374 done:
375 de_free(c, used_codepoint_map);
378 struct row_info_struct {
379 u8 is_visible;
380 i64 display_pos;
383 struct col_info_struct {
384 i64 display_width;
385 i64 display_pos;
388 static void checkerboard_bkgd(de_bitmap *img, i64 xpos, i64 ypos, i64 w, i64 h)
390 i64 ii, jj;
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;
403 i64 i, j, k;
404 de_bitmap *img = NULL;
405 i64 xpos, ypos;
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;
412 i64 last_valid_row;
413 struct de_bitmap_font *dfont = NULL;
414 i64 chars_per_row = 32;
415 const char *s;
416 struct row_info_struct *row_info = NULL;
417 struct col_info_struct *col_info = NULL;
418 int unicode_req = 0;
419 i64 label_stride;
420 i64 rownum, colnum;
421 i64 curpos;
422 unsigned int dnflags;
424 fctx = de_malloc(c, sizeof(struct font_render_ctx));
425 fctx->font = font1;
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);
432 goto done;
435 // -1 = "no preference"
436 unicode_req = de_get_ext_option_bool(c, "font:tounicode", -1);
438 if(unicode_req==0 &&
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");
450 if(s) {
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;
468 else {
469 if(fctx->max_codepoint >= 1000)
470 img_leftmargin = (i64)dfont->nominal_width * 5 + 6;
471 else
472 img_leftmargin = (i64)dfont->nominal_width * 3 + 6;
474 img_topmargin = (i64)dfont->nominal_height + 6;
475 img_rightmargin = 1;
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;
513 last_valid_row = -1;
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;
521 last_valid_row = j;
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);
542 // Clear the image
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)
562 label_stride = 2;
563 else
564 label_stride = 1;
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;
583 dnflags = 0;
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);
612 done:
613 if(dfont) {
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);
620 if(fctx) {
621 de_free(c, fctx->codepoint_tmp);
622 de_free(c, fctx);
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)
632 switch(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
638 return 1;
640 return 0;