Edited some module descriptions
[deark.git] / src / deark-char.c
blob587bbd8ad5f46c7d55f0e16a4edfd89d628a2c3c
1 // This file is part of Deark.
2 // Copyright (C) 2016 Jason Summers
3 // See the file COPYING for terms of use.
5 // deark-char.c
6 //
7 // Functions related to character graphics.
9 #define DE_NOT_IN_MODULE
10 #include "deark-config.h"
11 #include "deark-private.h"
13 struct screen_stats {
14 u32 fgcol_count[16];
15 u32 bgcol_count[16];
16 u32 most_used_fgcol;
17 u32 most_used_bgcol;
20 struct charextractx {
21 u8 vga_9col_mode; // Flag: Render an extra column, like VGA does
22 u8 uses_custom_font;
23 u8 used_underline;
24 u8 used_strikethru;
25 u8 used_blink;
26 u8 used_24bitcolor;
27 u8 used_fgcol[16];
28 u8 used_bgcol[16];
29 struct de_bitmap_font *standard_font;
30 struct de_bitmap_font *font_to_use;
32 i64 char_width_in_pixels;
33 i64 char_height_in_pixels;
35 struct screen_stats *scrstats; // pointer to array of struct screen_stats
38 struct de_char_context *de_create_charctx(deark *c, unsigned int flags)
40 struct de_char_context *charctx;
42 charctx = de_malloc(c, sizeof(struct de_char_context));
43 return charctx;
46 // Free the ->screens data, assuming it has been allocated in a particular way.
47 void de_free_charctx_screens(deark *c, struct de_char_context *charctx)
49 i64 pgnum;
50 i64 j;
52 if(!charctx || !charctx->screens) return;
54 for(pgnum=0; pgnum<charctx->nscreens; pgnum++) {
55 if(charctx->screens[pgnum]) {
56 if(charctx->screens[pgnum]->cell_rows) {
57 for(j=0; j<charctx->screens[pgnum]->height; j++) {
58 de_free(c, charctx->screens[pgnum]->cell_rows[j]);
60 de_free(c, charctx->screens[pgnum]->cell_rows);
62 de_free(c, charctx->screens[pgnum]);
65 de_free(c, charctx->screens);
66 charctx->screens = NULL;
69 // Frees a charctx struct.
70 // Does not free charctx->font.
71 // Does not free the ucstring fields.
72 void de_destroy_charctx(deark *c, struct de_char_context *charctx)
74 if(!charctx) return;
76 if(charctx->screens) {
77 de_internal_err_fatal(c, "charctx");
79 de_free(c, charctx);
82 // Deprecated in favor of de_free_charctx_screens() (if applicable),
83 // followed by de_destroy_charctx().
84 void de_free_charctx(deark *c, struct de_char_context *charctx)
86 if(charctx) {
87 de_free_charctx_screens(c, charctx);
88 de_free(c, charctx);
92 static void do_prescan_screen(deark *c, struct de_char_context *charctx,
93 struct charextractx *ectx, i64 screen_idx)
95 const struct de_char_cell *cell;
96 int i, j;
97 struct de_char_screen *screen;
98 u32 highest_fgcol_count;
99 u32 highest_bgcol_count;
101 screen = charctx->screens[screen_idx];
103 for(j=0; j<screen->height; j++) {
104 for(i=0; i<screen->width; i++) {
105 if(!screen->cell_rows || !screen->cell_rows[j]) continue;
106 cell = &screen->cell_rows[j][i];
107 if(!cell) continue;
109 if(DE_IS_PAL_COLOR(cell->fgcol)) {
110 ectx->used_fgcol[cell->fgcol] = 1;
111 ectx->scrstats[screen_idx].fgcol_count[cell->fgcol]++;
113 else {
114 ectx->used_24bitcolor = 1;
116 if(DE_IS_PAL_COLOR(cell->bgcol)) {
117 ectx->used_bgcol[cell->bgcol] = 1;
118 ectx->scrstats[screen_idx].bgcol_count[cell->bgcol]++;
120 else {
121 ectx->used_24bitcolor = 1;
123 if(cell->underline) ectx->used_underline = 1;
124 if(cell->strikethru) ectx->used_strikethru = 1;
125 if(cell->blink) ectx->used_blink = 1;
129 // Find the most-used foreground and background (palette) colors
130 highest_fgcol_count = ectx->scrstats[screen_idx].fgcol_count[0];
131 highest_bgcol_count = ectx->scrstats[screen_idx].bgcol_count[0];
132 ectx->scrstats->most_used_fgcol = 0;
133 ectx->scrstats->most_used_bgcol = 0;
135 for(i=1; i<16; i++) {
136 if(ectx->scrstats[screen_idx].fgcol_count[i] > highest_fgcol_count) {
137 highest_fgcol_count = ectx->scrstats[screen_idx].fgcol_count[i];
138 ectx->scrstats->most_used_fgcol = (u32)i;
140 if(ectx->scrstats[screen_idx].bgcol_count[i] > highest_bgcol_count) {
141 highest_bgcol_count = ectx->scrstats[screen_idx].bgcol_count[i];
142 ectx->scrstats->most_used_bgcol = (u32)i;
147 struct span_info {
148 u32 fgcol, bgcol;
149 u8 underline;
150 u8 strikethru;
151 u8 blink;
152 u8 is_suppressed;
155 // This may modify sp->is_suppressed.
156 static void span_open(deark *c, dbuf *ofile, struct span_info *sp,
157 const struct screen_stats *scrstats)
159 int need_fgcol_attr, need_bgcol_attr;
160 int need_underline, need_strikethru, need_blink;
161 int attrindex = 0;
162 int attrcount;
163 int fgcol_is_24bit, bgcol_is_24bit;
164 int need_style = 0;
166 fgcol_is_24bit = !DE_IS_PAL_COLOR(sp->fgcol);
167 bgcol_is_24bit = !DE_IS_PAL_COLOR(sp->bgcol);
169 need_fgcol_attr = !scrstats || sp->fgcol!=scrstats->most_used_fgcol;
170 need_bgcol_attr = !scrstats || sp->bgcol!=scrstats->most_used_bgcol;
171 if(fgcol_is_24bit) { need_fgcol_attr=0; need_style=1; }
172 if(bgcol_is_24bit) { need_bgcol_attr=0; need_style=1; }
173 need_underline = (sp->underline!=0);
174 need_strikethru = (sp->strikethru!=0);
175 need_blink = (sp->blink!=0);
177 attrcount = need_fgcol_attr + need_bgcol_attr + need_underline +
178 need_strikethru + need_blink;
179 if(attrcount==0 && !need_style) {
180 sp->is_suppressed = 1;
181 return;
184 sp->is_suppressed = 0;
186 dbuf_puts(ofile, "<span");
188 if(attrcount==0)
189 goto no_class;
191 dbuf_puts(ofile, " class=");
192 if(attrcount>1) // Don't need quotes if there's only one attribute
193 dbuf_puts(ofile, "\"");
195 // Classes for foreground and background colors
197 if(need_fgcol_attr) {
198 dbuf_printf(ofile, "f%c", de_get_hexchar(sp->fgcol));
199 attrindex++;
202 if(need_bgcol_attr) {
203 if(attrindex) dbuf_puts(ofile, " ");
204 dbuf_printf(ofile, "b%c", de_get_hexchar(sp->bgcol));
205 attrindex++;
208 // Other attributes
210 if(sp->underline) {
211 if(attrindex) dbuf_puts(ofile, " ");
212 dbuf_puts(ofile, "u");
213 attrindex++;
215 if(sp->strikethru) {
216 if(attrindex) dbuf_puts(ofile, " ");
217 dbuf_puts(ofile, "s");
218 attrindex++;
220 if(sp->blink) {
221 if(attrindex) dbuf_puts(ofile, " ");
222 dbuf_puts(ofile, "blink");
223 attrindex++;
226 if(attrcount>1)
227 dbuf_puts(ofile, "\"");
229 no_class:
230 if(fgcol_is_24bit || bgcol_is_24bit) {
231 char tmpbuf[16];
233 dbuf_puts(ofile, " style=\"");
234 if(fgcol_is_24bit) {
235 de_color_to_css(sp->fgcol, tmpbuf, sizeof(tmpbuf));
236 dbuf_printf(ofile, "color:%s", tmpbuf);
239 if(bgcol_is_24bit) {
240 if(fgcol_is_24bit)
241 dbuf_puts(ofile, ";");
242 de_color_to_css(sp->bgcol, tmpbuf, sizeof(tmpbuf));
243 dbuf_printf(ofile, "background-color:%s", tmpbuf);
245 dbuf_puts(ofile, "\"");
248 dbuf_puts(ofile, ">");
249 return;
252 static void span_close(deark *c, dbuf *ofile, struct span_info *sp)
254 if(sp->is_suppressed) return;
255 dbuf_puts(ofile, "</span>");
258 static void do_output_html_screen(deark *c, struct de_char_context *charctx,
259 struct charextractx *ectx, i64 screen_idx, dbuf *ofile)
261 const struct de_char_cell *cell;
262 struct de_char_cell blank_cell;
263 struct de_char_screen *screen;
264 int i, j;
265 i32 n;
266 int in_span = 0;
267 int need_newline = 0;
268 u32 active_fgcol = 0;
269 u32 active_bgcol = 0;
270 u8 active_underline = 0;
271 u8 active_strikethru = 0;
272 u8 active_blink = 0;
273 int is_blank_char;
274 struct span_info default_span;
275 struct span_info cur_span;
277 de_zeromem(&default_span, sizeof(struct span_info));
278 de_zeromem(&cur_span, sizeof(struct span_info));
280 screen = charctx->screens[screen_idx];
282 // In case a cell is missing, we'll use this one:
283 de_zeromem(&blank_cell, sizeof(struct de_char_cell));
284 blank_cell.codepoint = 32;
285 blank_cell.codepoint_unicode = 32;
287 dbuf_puts(ofile, "<table class=mt><tr>\n<td>");
288 dbuf_puts(ofile, "<pre>");
290 // Containing <span> with default colors.
291 default_span.fgcol = ectx->scrstats[screen_idx].most_used_fgcol;
292 default_span.bgcol = ectx->scrstats[screen_idx].most_used_bgcol;
293 span_open(c, ofile, &default_span, NULL);
295 for(j=0; j<screen->height; j++) {
296 for(i=0; i<screen->width; i++) {
297 if(!screen->cell_rows || !screen->cell_rows[j]) {
298 cell = &blank_cell;
300 else {
301 cell = &screen->cell_rows[j][i];
302 if(!cell) cell = &blank_cell;
305 n = cell->codepoint_unicode;
307 if((cell->size_flags&DE_PAINTFLAG_RIGHTHALF) ||
308 (cell->size_flags&DE_PAINTFLAG_BOTTOMHALF))
310 // We don't support double-size characters with HTML output.
311 // Make the left / bottom parts of the cell blank so we don't
312 // duplicate the foreground character.
313 n = 0x20;
316 if(n==0x00) n=0x20;
317 if(n<0x20) n='?';
318 is_blank_char = (n==0x20 || n==0xa0) &&
319 !cell->underline && !cell->strikethru;
321 if(in_span) {
322 // If we're in an incompatible 'span' context, close it.
324 // Optimization: If this is a blank character, ignore a foreground color
325 // mismatch, because it won't be visible anyway. (Many other similar
326 // optimizations are also possible, but that could get very complex.)
327 if((cell->fgcol!=active_fgcol && !is_blank_char) ||
328 cell->bgcol!=active_bgcol ||
329 cell->underline!=active_underline ||
330 cell->strikethru!=active_strikethru ||
331 cell->blink!=active_blink)
333 span_close(c, ofile, &cur_span);
334 in_span=0;
338 if(in_span==0) {
339 if(need_newline) {
340 dbuf_puts(ofile, "\n");
341 need_newline = 0;
344 cur_span.fgcol = cell->fgcol;
345 cur_span.bgcol = cell->bgcol;
346 cur_span.underline = cell->underline;
347 cur_span.strikethru = cell->strikethru;
348 cur_span.blink = cell->blink;
349 span_open(c, ofile, &cur_span, &ectx->scrstats[screen_idx]);
351 in_span=1;
352 active_fgcol = cell->fgcol;
353 active_bgcol = cell->bgcol;
354 active_underline = cell->underline;
355 active_strikethru = cell->strikethru;
356 active_blink = cell->blink;
359 if(need_newline) {
360 dbuf_puts(ofile, "\n");
361 need_newline = 0;
364 de_write_codepoint_to_html(c, ofile, n);
367 // Defer emitting a newline, so that we have more control over where
368 // to put it. We prefer to put it after "</span>".
369 need_newline = 1;
372 if(in_span) {
373 span_close(c, ofile, &cur_span);
376 // Close containing <span>
377 span_close(c, ofile, &default_span);
379 dbuf_puts(ofile, "</pre>");
380 dbuf_puts(ofile, "</td>\n</tr></table>\n");
383 static void output_css_color_block(deark *c, dbuf *ofile, u32 *pal,
384 const char *selectorprefix, const char *prop, const u8 *used_flags)
386 char tmpbuf[16];
387 int i;
389 for(i=0; i<16; i++) {
390 if(!used_flags[i]) continue;
391 de_color_to_css(pal[i], tmpbuf, sizeof(tmpbuf));
392 dbuf_printf(ofile, " %s%c { %s: %s }\n", selectorprefix, de_get_hexchar(i),
393 prop, tmpbuf);
397 static void write_ucstring_to_html(deark *c, const de_ucstring *s, dbuf *f)
399 i64 i;
400 i64 len_at_last_linebreak;
401 int prev_space = 0;
402 i32 ch;
404 if(!s) return;
406 len_at_last_linebreak = f->len;
408 for(i=0; i<s->len; i++) {
409 ch = s->str[i];
411 // Don't let HTML collapse consecutive spaces
412 // (this is not ideal, but it's better than nothing).
413 if(ch==0x20) {
414 if(prev_space) {
415 ch = 0xa0; // nbsp
417 prev_space = 1;
419 else {
420 prev_space = 0;
423 if(ch==0x0a) {
424 dbuf_puts(f, "<br>\n");
425 len_at_last_linebreak = f->len;
427 else if(ch==0x20 && (f->len - len_at_last_linebreak > 70)) {
428 // Low-quality attempt at word wrapping
429 dbuf_puts(f, "\n");
430 len_at_last_linebreak = f->len;
432 else {
433 de_write_codepoint_to_html(c, f, ch);
438 static void print_header_item(deark *c, dbuf *ofile, const char *name_rawhtml, const de_ucstring *value)
440 int k;
442 dbuf_puts(ofile, "<td class=htc>");
443 if(ucstring_isnonempty(value)) {
444 dbuf_printf(ofile, "<span class=hn>%s:&nbsp; </span><span class=hv>", name_rawhtml);
445 write_ucstring_to_html(c, value, ofile);
446 dbuf_puts(ofile, "</span>");
448 else {
449 // Placeholder
450 for(k=0; k<20; k++) {
451 de_write_codepoint_to_html(c, ofile, 0x00a0); // nbsp
454 dbuf_puts(ofile, "</td>\n");
457 static void print_comments(deark *c, struct de_char_context *charctx, dbuf *ofile)
459 if(ucstring_isempty(charctx->comment)) return;
460 dbuf_puts(ofile, "<table class=htt><tr>\n");
461 dbuf_puts(ofile, "<td class=hcth>");
462 dbuf_printf(ofile, "<span class=hn>Comments:</span>");
463 dbuf_puts(ofile, "</td>\n");
465 dbuf_puts(ofile, "<td class=hctc>");
467 dbuf_puts(ofile, "<span class=hv>");
468 // TODO: Some line breaks would be good.
469 write_ucstring_to_html(c, charctx->comment, ofile);
470 dbuf_puts(ofile, "</span>");
472 dbuf_puts(ofile, "</td>\n");
474 dbuf_puts(ofile, "</tr></table>\n");
477 static void timestamp_to_ucstring(const struct de_timestamp *ts, de_ucstring *s)
479 char timestamp_buf[64];
480 de_timestamp_to_string(ts, timestamp_buf, sizeof(timestamp_buf), 0);
481 ucstring_append_sz(s, timestamp_buf, DE_ENCODING_UTF8);
484 static void do_output_html_header(deark *c, struct de_char_context *charctx,
485 struct charextractx *ectx, dbuf *ofile)
487 int has_metadata; // metadata other than comments
489 has_metadata = charctx->title || charctx->artist || charctx->organization ||
490 (charctx->creation_date.is_valid);
491 if(c->write_bom && !c->ascii_html) dbuf_write_uchar_as_utf8(ofile, 0xfeff);
492 dbuf_puts(ofile, "<!DOCTYPE html>\n");
493 dbuf_puts(ofile, "<html>\n");
494 dbuf_puts(ofile, "<head>\n");
495 dbuf_printf(ofile, "<meta charset=\"%s\">\n", c->ascii_html?"US-ASCII":"UTF-8");
496 dbuf_puts(ofile, "<title>");
497 write_ucstring_to_html(c, charctx->title, ofile);
498 dbuf_puts(ofile, "</title>\n");
500 dbuf_puts(ofile, "<style type=\"text/css\">\n");
502 dbuf_puts(ofile, " body { background-color: #222; background-image: url(\"data:image/png;base64,"
503 "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEUgICAoKCidji3LAAAAMUlE"
504 "QVQI12NgaGBgPMDA/ICB/QMD/w8G+T8M9v8Y6v8z/P8PIoFsoAhQHCgLVMN4AACOoBFvDLHV4QAA"
505 "AABJRU5ErkJggg==\") }\n");
507 // The table for the main graphics
508 dbuf_puts(ofile, " .mt { margin-left: auto; margin-right: auto }\n");
510 if(has_metadata || charctx->comment) {
511 // Styles for header name and value
512 // Header table <table> styles
513 dbuf_puts(ofile, " .htt { width: 100%; border-collapse: collapse; background-color: #034 }\n");
514 // Header table cell <td> styles
515 dbuf_puts(ofile, " .htc { border: 2px solid #056; text-align: center }\n");
516 if(charctx->comment) {
517 // Comment table cell <td> styles
518 dbuf_puts(ofile, " .hcth { border: 2px solid #056; padding-left: 0.5em; "
519 "padding-right: 0.5em; width: 1px }\n");
520 dbuf_puts(ofile, " .hctc { border: 2px solid #056; padding-left: 0.5em }\n");
522 // Header name styles
523 dbuf_puts(ofile, " .hn { color: #aaa; font-style: italic }\n");
524 // Header value styles
525 dbuf_puts(ofile, " .hv { color: #fff }\n");
528 output_css_color_block(c, ofile, charctx->pal, ".f", "color", &ectx->used_fgcol[0]);
529 output_css_color_block(c, ofile, charctx->pal, ".b", "background-color", &ectx->used_bgcol[0]);
531 if(ectx->used_underline) {
532 dbuf_puts(ofile, " .u { text-decoration: underline }\n");
534 if(ectx->used_strikethru) {
535 dbuf_puts(ofile, " .s { text-decoration: line-through }\n");
538 if(ectx->used_blink) {
539 dbuf_puts(ofile, " .blink {\n"
540 " animation: blink 1s steps(1) infinite;\n"
541 " -webkit-animation: blink 1s steps(1) infinite }\n"
542 " @keyframes blink { 50% { color: transparent } }\n"
543 " @-webkit-keyframes blink { 50% { color: transparent } }\n");
545 dbuf_puts(ofile, "</style>\n");
547 dbuf_puts(ofile, "</head>\n");
548 dbuf_puts(ofile, "<body>\n");
550 if(has_metadata) {
551 de_ucstring *tmps = NULL;
552 dbuf_puts(ofile, "<table class=htt><tr>\n");
553 print_header_item(c, ofile, "Title", charctx->title);
554 print_header_item(c, ofile, "Organization", charctx->organization);
555 print_header_item(c, ofile, "Artist", charctx->artist);
556 tmps = ucstring_create(c);
557 if(charctx->creation_date.is_valid) {
558 timestamp_to_ucstring(&charctx->creation_date, tmps);
560 print_header_item(c, ofile, "Date", tmps);
561 ucstring_destroy(tmps);
562 dbuf_puts(ofile, "</tr></table>\n");
565 print_comments(c, charctx, ofile);
568 static void do_output_html_footer(deark *c, struct de_char_context *charctx,
569 struct charextractx *ectx, dbuf *ofile)
571 dbuf_puts(ofile, "</body>\n</html>\n");
574 static void de_char_output_to_html_file(deark *c, struct de_char_context *charctx,
575 struct charextractx *ectx)
577 i64 i;
578 dbuf *ofile = NULL;
580 if(charctx->font && !charctx->suppress_custom_font_warning) {
581 de_warn(c, "This file uses a custom font, which is not supported with "
582 "HTML output.");
585 if(ectx->used_24bitcolor) {
586 de_info(c, "Note: This file uses 24-bit colors, which are supported but "
587 "not optimized. The HTML file may be very large.");
590 ofile = dbuf_create_output_file(c, "html", NULL, 0);
592 do_output_html_header(c, charctx, ectx, ofile);
593 for(i=0; i<charctx->nscreens; i++) {
594 do_output_html_screen(c, charctx, ectx, i, ofile);
596 do_output_html_footer(c, charctx, ectx, ofile);
598 dbuf_close(ofile);
601 static void do_render_character(deark *c, struct de_char_context *charctx,
602 struct charextractx *ectx, de_bitmap *img,
603 i64 xpos, i64 ypos,
604 i32 codepoint, int codepoint_is_unicode,
605 u32 fgcol, u32 bgcol,
606 unsigned int extra_flags)
608 i64 xpos_in_pix, ypos_in_pix;
609 u32 fgcol_rgb, bgcol_rgb;
610 unsigned int flags;
612 xpos_in_pix = xpos * ectx->char_width_in_pixels;
613 ypos_in_pix = ypos * ectx->char_height_in_pixels;
615 if(DE_IS_PAL_COLOR(fgcol))
616 fgcol_rgb = charctx->pal[fgcol];
617 else
618 fgcol_rgb = fgcol;
619 if(DE_IS_PAL_COLOR(bgcol))
620 bgcol_rgb = charctx->pal[bgcol];
621 else
622 bgcol_rgb = bgcol;
624 flags = extra_flags;
625 if(ectx->vga_9col_mode) flags |= DE_PAINTFLAG_VGA9COL;
627 if(codepoint_is_unicode) {
628 de_font_paint_character_cp(c, img, ectx->font_to_use, codepoint,
629 xpos_in_pix, ypos_in_pix, fgcol_rgb, bgcol_rgb, flags);
631 else {
632 de_font_paint_character_idx(c, img, ectx->font_to_use, (i64)codepoint,
633 xpos_in_pix, ypos_in_pix, fgcol_rgb, bgcol_rgb, flags);
637 static void set_density(deark *c, struct de_char_context *charctx,
638 struct charextractx *ectx, de_finfo *fi)
640 // FIXME: This is quick and dirty. Need to put more thought into how to
641 // figure out the pixel density.
643 if(charctx->no_density) return;
645 if(ectx->char_height_in_pixels==16 && ectx->char_width_in_pixels==8) {
646 // Assume the intended display is 640x400.
647 fi->density.code = DE_DENSITY_UNK_UNITS;
648 fi->density.xdens = 480.0;
649 fi->density.ydens = 400.0;
651 else if(ectx->char_height_in_pixels==16 && ectx->char_width_in_pixels==9) {
652 // Assume the intended display is 720x400.
653 fi->density.code = DE_DENSITY_UNK_UNITS;
654 fi->density.xdens = 540.0;
655 fi->density.ydens = 400.0;
659 static void de_char_output_screen_to_image_file(deark *c, struct de_char_context *charctx,
660 struct charextractx *ectx, struct de_char_screen *screen)
662 i64 screen_width_in_pixels, screen_height_in_pixels;
663 de_bitmap *img = NULL;
664 de_finfo *fi = NULL;
665 int i, j;
666 const struct de_char_cell *cell;
667 unsigned int flags;
669 screen_width_in_pixels = screen->width * ectx->char_width_in_pixels;
670 screen_height_in_pixels = screen->height * ectx->char_height_in_pixels;
672 if(!de_good_image_dimensions(c, screen_width_in_pixels, screen_height_in_pixels)) goto done;
674 img = de_bitmap_create(c, screen_width_in_pixels, screen_height_in_pixels, 3);
676 fi = de_finfo_create(c);
677 set_density(c, charctx, ectx, fi);
679 if(charctx->creation_date.is_valid) {
680 // The ->creation_date field is most likely from a SAUCE record, for which the
681 // only date field is documented as "The date the file was created".
682 // We intentionally treat it as a last-modified timestamp.
683 fi->internal_mod_time = charctx->creation_date;
686 for(j=0; j<screen->height; j++) {
687 for(i=0; i<screen->width; i++) {
688 if(!screen->cell_rows[j]) continue;
689 cell = &screen->cell_rows[j][i];
690 if(!cell) continue;
692 flags = cell->size_flags;
694 do_render_character(c, charctx, ectx, img, i, j,
695 ectx->uses_custom_font ? cell->codepoint : cell->codepoint_unicode,
696 ectx->uses_custom_font ? 0 : 1,
697 cell->fgcol, cell->bgcol, flags);
699 // TODO: It might be better to draw our own underline and/or
700 // strikethru marks, rather than relying on font glyphs that
701 // might be customized or otherwise sub-optimal.
702 if(cell->underline) {
703 do_render_character(c, charctx, ectx, img, i, j,
704 0x5f, 1, cell->fgcol, cell->bgcol, flags|DE_PAINTFLAG_TRNSBKGD);
706 if(cell->strikethru) {
707 do_render_character(c, charctx, ectx, img, i, j,
708 0x2d, 1, cell->fgcol, cell->bgcol, flags|DE_PAINTFLAG_TRNSBKGD);
713 de_bitmap_write_to_file_finfo(img, fi, 0);
714 done:
715 de_bitmap_destroy(img);
716 de_finfo_destroy(c, fi);
719 #define NUM_EXTRA_FONT_CHARS 13
720 static const u8 extra_font_data[NUM_EXTRA_FONT_CHARS*16] = {
721 0,0,0,126,66,66,66,66,66,66,66,126,0,0,0,0, // replacement char
722 0,0,0,0,16,56,124,254,124,56,16,0,0,0,0,0, // 25c6 diamond
723 0,0,216,216,248,216,216,0,30,12,12,12,12,0,0,0, // 2409 "HT"
724 0,0,248,192,240,192,192,0,62,48,60,48,48,0,0,0, // 240c "FF"
725 0,0,120,192,192,192,120,0,60,54,60,54,54,0,0,0, // 240d "CR"
726 0,0,192,192,192,192,240,0,62,48,60,48,48,0,0,0, // 240a "LF"
727 0,0,204,236,220,204,204,0,48,48,48,48,62,0,0,0, // 2424 "NL"
728 0,0,216,216,112,112,32,0,30,12,12,12,12,0,0,0, // 240b "VT"
729 0,255,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 23ba scan 1
730 0,0,0,0,255,0,0,0,0,0,0,0,0,0,0,0, // 23bb scan 3
731 0,0,0,0,0,0,0,0,0,0,255,0,0,0,0,0, // 23bc scan 7
732 0,0,0,0,0,0,0,0,0,0,0,0,0,255,0,0, // 23bd scan 9
733 0,0,0,2,4,126,8,16,126,32,64,0,0,0,0,0 // 2260 not equal
735 static const i32 extra_font_codepoints[NUM_EXTRA_FONT_CHARS] = {
736 0xfffd,0x25c6,0x2409,0x240c,0x240d,0x240a,0x2424,0x240b,
737 0x23ba,0x23bb,0x23bc,0x23bd,0x2260
740 static void do_create_standard_font(deark *c, struct charextractx *ectx)
742 i64 i;
743 struct de_bitmap_font *font;
744 const u8 *vga_cp437_font_data;
745 struct de_bitmap_font_char *ch;
746 struct de_encconv_state es;
748 font = de_create_bitmap_font(c);
749 ectx->standard_font = font;
751 vga_cp437_font_data = de_get_vga_cp437_font_ptr();
753 font->num_chars = 256 + NUM_EXTRA_FONT_CHARS;
754 font->nominal_width = 8;
755 font->nominal_height = 16;
756 font->has_nonunicode_codepoints = 1;
757 font->has_unicode_codepoints = 1;
759 font->char_array = de_mallocarray(c, font->num_chars, sizeof(struct de_bitmap_font_char));
761 // Set defaults for each character
762 for(i=0; i<font->num_chars; i++) {
763 ch = &font->char_array[i];
764 ch->width = font->nominal_width;
765 ch->height = font->nominal_height;
766 ch->rowspan = 1;
769 de_encconv_init(&es, DE_ENCODING_CP437_G);
770 for(i=0; i<font->num_chars; i++) {
771 ch = &font->char_array[i];
772 ch->codepoint_nonunicode = (i32)i;
773 ch->codepoint_unicode = de_char_to_unicode_ex((i32)i, &es);
774 ch->bitmap = (u8*)&vga_cp437_font_data[i*16];
777 // Add vt100 characters that aren't in CP437
778 for(i=0; i<NUM_EXTRA_FONT_CHARS; i++) {
779 ch = &font->char_array[256+i];
780 ch->codepoint_nonunicode = DE_CODEPOINT_INVALID;
781 ch->codepoint_unicode = extra_font_codepoints[i];
782 ch->bitmap = (u8*)&extra_font_data[i*16];
784 font->index_of_replacement_char = 256;
787 static void de_char_output_to_image_files(deark *c, struct de_char_context *charctx,
788 struct charextractx *ectx)
790 i64 i;
792 if(ectx->used_blink) {
793 de_warn(c, "This file uses blinking characters, which are not supported with "
794 "image output.");
797 if(charctx->font) {
798 ectx->uses_custom_font = 1;
799 ectx->font_to_use = charctx->font;
801 else {
802 ectx->uses_custom_font = 0;
803 do_create_standard_font(c, ectx);
804 ectx->font_to_use = ectx->standard_font;
807 if(ectx->vga_9col_mode)
808 ectx->char_width_in_pixels = 9;
809 else
810 ectx->char_width_in_pixels = ectx->font_to_use->nominal_width;
812 ectx->char_height_in_pixels = ectx->font_to_use->nominal_height;
814 for(i=0; i<charctx->nscreens; i++) {
815 de_char_output_screen_to_image_file(c, charctx, ectx, charctx->screens[i]);
818 if(ectx->standard_font) {
819 de_free(c, ectx->standard_font->char_array);
820 de_destroy_bitmap_font(c, ectx->standard_font);
824 // Sets charctx->outfmt
825 void de_char_decide_output_format(deark *c, struct de_char_context *charctx)
827 const char *s;
829 if(charctx->outfmt_known) return;
830 charctx->outfmt_known = 1;
832 if(charctx->prefer_image_output)
833 charctx->outfmt = 1;
835 s = de_get_ext_option(c, "char:output");
836 if(s) {
837 if(!de_strcmp(s, "html")) {
838 charctx->outfmt = 0;
840 else if(!de_strcmp(s, "image")) {
841 charctx->outfmt = 1;
846 void de_char_output_to_file(deark *c, struct de_char_context *charctx)
848 i64 i;
849 const char *s;
850 int n;
851 struct charextractx *ectx = NULL;
853 ectx = de_malloc(c, sizeof(struct charextractx));
855 de_char_decide_output_format(c, charctx);
857 if(charctx->prefer_9col_mode) {
858 ectx->vga_9col_mode = 1;
861 s = de_get_ext_option(c, "char:charwidth");
862 if(s) {
863 n = de_atoi(s);
864 if(n>=9) {
865 ectx->vga_9col_mode = 1;
867 else if(n>=1) {
868 ectx->vga_9col_mode = 0;
872 ectx->scrstats = de_mallocarray(c, charctx->nscreens, sizeof(struct screen_stats));
874 for(i=0; i<charctx->nscreens; i++) {
875 do_prescan_screen(c, charctx, ectx, i);
878 switch(charctx->outfmt) {
879 case 1:
880 de_char_output_to_image_files(c, charctx, ectx);
881 break;
882 default:
883 de_char_output_to_html_file(c, charctx, ectx);
886 if(ectx) {
887 de_free(c, ectx->scrstats);
889 de_free(c, ectx);