diff --git a/Makefile b/Makefile
index 4679d8b..060818f 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@ CLIBS := -lm
INCLUDES := -Isrc
OBJECTS := obj/stb_image.o obj/stb_image_resize.o \
obj/colors.o obj/args.o obj/image.o obj/commons.o \
- obj/mod_blocks.o obj/mod_braille.o
+ obj/mod_blocks.o obj/mod_braille.o obj/mod_charmap.o
VERSION := "v-git-$(shell git rev-parse --short HEAD)"
all: lib yaitaa
diff --git a/src/args.c b/src/args.c
index e2785e9..9e7a88f 100644
--- a/src/args.c
+++ b/src/args.c
@@ -9,6 +9,7 @@
#include "commons.h"
#include "mod_blocks.h"
#include "mod_braille.h"
+#include "mod_charmap.h"
typedef struct {
int value;
@@ -32,7 +33,7 @@ const asc_handler_t asc_handlers[ASC_MOD_ENDL + 1] = {
{ ASC_MOD_GRADIENT,
{ "g", "grd", "gradient", NULL },
"Gradient of characters. No matching at all",
- NULL, NULL },
+ mod_cmap_prepare, mod_cmap_main },
{ ASC_MOD_BRUTEFORCE,
{ "f", "guess", "bruteforce", NULL },
"Looking for best possible character",
@@ -100,6 +101,7 @@ void usage(int argc, char **argv)
fprintf(stderr, "-O FILE\t\tOutput file. Default: - (stdout)\n");
fprintf(stderr, "-W WIDTH\tOutput width (in characters)\n");
fprintf(stderr, "-H HEIGHT\tOutput height (in characters)\n");
+ fprintf(stderr, "-C CHARSET\tList of characters. Used in some modes\n");
fprintf(stderr, "\n");
fprintf(stderr, "-M MODE\t\tOutput mode\n");
fprintf(stderr, "-S STYLE\tStyle (palette)\n");
@@ -147,9 +149,9 @@ int parse_args(int argc, char **argv, asc_args_t *args)
args->charset = " .'-*+$@";
int c;
#ifdef DISABLE_LOGGING
- while ((c = getopt(argc, argv, "hdVW:H:M:S:F:P:O:")) != -1)
+ while ((c = getopt(argc, argv, "hdVC:W:H:M:S:F:P:O:")) != -1)
#else
- while ((c = getopt(argc, argv, "vhdVW:H:M:S:F:P:O:")) != -1)
+ while ((c = getopt(argc, argv, "vhdVC:W:H:M:S:F:P:O:")) != -1)
#endif
{
switch (c)
@@ -168,6 +170,9 @@ int parse_args(int argc, char **argv, asc_args_t *args)
case 'd':
args->dither = true;
break;
+ case 'C':
+ args->charset = optarg;
+ break;
case 'W':
if ((args->width = atoi(optarg)) < 0)
{
@@ -282,6 +287,10 @@ int prepare_state(int argc, char **argv, asc_args_t args, asc_state_t *state)
LOG("Image size: %dx%d",
state->source_image->width, state->source_image->height);
+ state->image = state->source_image;
+
+ state->userdata = NULL;
+
// Palette configuration
switch (args.out_style)
{
diff --git a/src/args.h b/src/args.h
index b21f939..9d3a98d 100644
--- a/src/args.h
+++ b/src/args.h
@@ -68,11 +68,12 @@ typedef struct {
image_t *source_image;
image_t *image;
palette_t *palette;
+ void *userdata;
FILE *out_file;
} asc_state_t;
-typedef void (*asc_module_initializer_fn)(asc_state_t *state);
-typedef void (*asc_module_handler_fn)(asc_state_t state);
+typedef bool (*asc_module_initializer_fn)(asc_state_t *state);
+typedef bool (*asc_module_handler_fn)(asc_state_t state);
typedef struct {
int id;
diff --git a/src/colors.c b/src/colors.c
index 48a5877..dfc1b99 100644
--- a/src/colors.c
+++ b/src/colors.c
@@ -141,7 +141,7 @@ void make_pal256(palette_t *dst, palette_t ansi)
float calc_brightness(rgba8 c)
{
- return 0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b;
+ return (0.2126 * c.r + 0.7152 * c.g + 0.0722 * c.b) / 255.0;
}
bool load_palette_gpl(palette_t *pal, FILE *fp)
diff --git a/src/commons.c b/src/commons.c
index 3de7ef6..0d3c11f 100644
--- a/src/commons.c
+++ b/src/commons.c
@@ -44,9 +44,14 @@ void _log(const char *fmt, ...)
}
#endif
-void c_fatal(int code, const char *reason)
+void c_fatal(int code, const char *reason, ...)
{
- fprintf(stderr, "Error: %s\n", reason);
+ fprintf(stderr, "Error: ");
+ va_list args;
+ va_start(args, reason);
+ vfprintf(stderr, reason, args);
+ va_end(args);
+ fprintf(stderr, "\n");
exit(code);
}
@@ -64,3 +69,129 @@ palette_t *get_palette_by_id(asc_style_t stl)
}
return pal;
}
+
+// From: https://github.com/raysan5/raylib/blob/e5ee69a/src/rtext.c#L1729
+__attribute__((annotate("oclint:suppress[bitwise operator in conditional]")))
+__attribute__((annotate("oclint:suppress[high cyclomatic complexity]")))
+__attribute__((annotate("oclint:suppress[high npath complexity]")))
+__attribute__((annotate("oclint:suppress[high ncss method]")))
+__attribute__((annotate("oclint:suppress[long method]")))
+int get_codepoint(char *text, int *processed)
+{
+ int code = 0;
+ int octet = (unsigned char)(text[0]);
+ *processed = 1;
+
+ if (octet <= 0x7f)
+ {
+ code = text[0];
+ }
+ else if ((octet & 0xe0) == 0xc0)
+ {
+ unsigned char octet1 = text[1];
+ if ((octet1 == '\0') || ((octet1 >> 6) != 2))
+ { *processed = 2; return 0; }
+ if ((octet >= 0xc2) && (octet <= 0xdf))
+ {
+ code = ((octet & 0x1f) << 6) | (octet1 & 0x3f);
+ *processed = 2;
+ }
+ }
+ else if ((octet & 0xf0) == 0xe0)
+ {
+ unsigned char octet1 = text[1];
+ unsigned char octet2 = '\0';
+ if ((octet1 == '\0') || ((octet1 >> 6) != 2))
+ { *processed = 2; return 0; }
+ octet2 = text[2];
+ if ((octet2 == '\0') || ((octet2 >> 6) != 2))
+ { *processed = 3; return 0; }
+ if (((octet == 0xe0) && !((octet1 >= 0xa0) && (octet1 < 0xbf))) ||
+ ((octet == 0xed) && !((octet1 >= 0x80) && (octet1 <= 0x9f))))
+ { *processed = 2; return 0; }
+ if ((octet >= 0xe0) && (octet <= 0xef))
+ { code = ((octet & 0xf) << 12) | ((octet1 & 0x3f) << 6) | (octet2 & 0x3f);
+ *processed = 3; }
+ }
+ else if ((octet & 0xf8) == 0xf0)
+ {
+ if (octet > 0xf4) return 0;
+ unsigned char octet1 = text[1];
+ unsigned char octet2 = '\0';
+ unsigned char octet3 = '\0';
+ if ((octet1 == '\0') || ((octet1 >> 6) != 2))
+ { *processed = 2; return 0; }
+ octet2 = text[2];
+ if ((octet2 == '\0') || ((octet2 >> 6) != 2))
+ { *processed = 3; return 0; }
+ if ((octet3 == '\0') || ((octet3 >> 6) != 2))
+ { *processed = 4; return 0; }
+ if (((octet == 0xf0) && !((octet1 >= 0x90) && (octet1 <= 0xbf))) ||
+ ((octet == 0xf4) && !((octet1 >= 0x80) && (octet1 <= 0x8f))))
+ { *processed = 2; return 0; }
+ if (octet >= 0xf0)
+ {
+ code = (((octet & 0x7) << 18) |
+ ((octet1 & 0x3f) << 12) |
+ ((octet2 & 0x3f) << 6) |
+ (octet3 & 0x3f));
+ *processed = 4;
+ }
+ }
+ return code;
+}
+
+// From: https://gist.github.com/MightyPork/52eda3e5677b4b03524e40c9f0ab1da5
+__attribute__((annotate("oclint:suppress[high ncss method]")))
+int set_codepoint(char *buf, int codepoint)
+{
+ if (codepoint <= 0x7F)
+ {
+ *buf++ = (char)codepoint;
+ *buf++ = '\0';
+ return 1;
+ }
+ else if (codepoint <= 0x07FF)
+ {
+ *buf++ = (char)(((codepoint >> 6) & 0x1F) | 0xC0);
+ *buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
+ *buf++ = '\0';
+ return 2;
+ }
+ else if (codepoint <= 0xFFFF)
+ {
+ *buf++ = (char)(((codepoint >> 12) & 0x0F) | 0xE0);
+ *buf++ = (char)(((codepoint >> 6) & 0x3F) | 0x80);
+ *buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
+ *buf++ = '\0';
+ return 3;
+ }
+ else if (codepoint <= 0x10FFFF)
+ {
+ *buf++ = (char)(((codepoint >> 18) & 0x07) | 0xF0);
+ *buf++ = (char)(((codepoint >> 12) & 0x3F) | 0x80);
+ *buf++ = (char)(((codepoint >> 6) & 0x3F) | 0x80);
+ *buf++ = (char)(((codepoint >> 0) & 0x3F) | 0x80);
+ *buf++ = '\0';
+ return 4;
+ }
+ *buf++ = '\xEF';
+ *buf++ = '\xBF';
+ *buf++ = '\xBD';
+ *buf++ = '\0';
+ return 0;
+}
+
+int n_codepoints(char *text)
+{
+ int proc = 0, length = 0;
+ while (*text != '\0')
+ {
+ if (get_codepoint(text, &proc) == 0)
+ break;
+ length++;
+ text += proc;
+ }
+ return length;
+}
+
diff --git a/src/commons.h b/src/commons.h
index 3e85ccc..8148fb3 100644
--- a/src/commons.h
+++ b/src/commons.h
@@ -32,8 +32,12 @@ extern bool b_logging;
void _log(const char *fmt, ...);
#endif
-void c_fatal(int code, const char *reason);
+void c_fatal(int code, const char *reason, ...);
void m_prepare_dither(asc_state_t *state);
palette_t *get_palette_by_id(asc_style_t stl);
+int get_codepoint(char *text, int *processed);
+int set_codepoint(char *buf, int codepoint);
+int n_codepoints(char *text);
+
#endif
diff --git a/src/fmt_strings.h b/src/fmt_strings.h
index 4719ecb..4a66ea9 100644
--- a/src/fmt_strings.h
+++ b/src/fmt_strings.h
@@ -22,6 +22,7 @@
#define S_JSON_LSTA " [\n"
#define S_JSON_PBBW " { \"char\": \"%s\", \"fg\": 16777215, \"bg\": 0 }"
#define S_JSON_PRGB " { \"char\": \"\\u%04x\", \"fg\": %d, \"bg\": %d }"
+#define S_JSON_PUCC " { \"char\": \"\\u%04x\", \"fg\": %d, \"bg\": 0 }"
#define S_JSON_PBLK " { \"char\": \"%s\", \"fg\": %d, \"bg\": %d }"
#define S_JSON_LEND " ],\n"
#define S_JSON_LEND_FINAL " ]\n"
@@ -33,13 +34,18 @@
"background: rgb(%d, %d, %d);\">%d;"
#define S_HTML_PBLK "
%s | "
+#define S_HTML_PUCC "%d; | "
+#define S_HTML_PUBW "%d; | "
#define S_HTML_PBBW "%s | "
#define S_HTML_LEND "\n"
#define S_HTML_TAIL ""
-#define S_ANSI "\033[%d;%dm"
-#define S_ANSI_RGB "\033[38;2;%d;%d;%d;48;2;%d;%d;%dm"
-#define S_ANSI_256 "\033[38;5;%d;48;5;%dm"
-#define S_ANSI_RST "\033[0m"
+#define S_ANSI "\033[%d;%dm"
+#define S_ANSI_FG "\033[%dm"
+#define S_ANSI_RGB "\033[38;2;%d;%d;%d;48;2;%d;%d;%dm"
+#define S_ANSI_256 "\033[38;5;%d;48;5;%dm"
+#define S_ANSI_FG_256 "\033[38;5;%dm"
+#define S_ANSI_FG_RGB "\033[38;2;%d;%d;%dm"
+#define S_ANSI_RST "\033[0m"
#endif
diff --git a/src/main.c b/src/main.c
index d4e27a6..7d7e437 100644
--- a/src/main.c
+++ b/src/main.c
@@ -20,10 +20,9 @@
#include
#include
#include "args.h"
-#include "mod_blocks.h"
-#include "mod_braille.h"
#include "commons.h"
+__attribute__((annotate("oclint:suppress[high npath complexity]")))
int main(int argc, char **argv)
{
asc_args_t args;
@@ -40,10 +39,14 @@ int main(int argc, char **argv)
if (handler.prepare == NULL)
c_fatal(12, "this mode is not implemented yet");
- handler.prepare(&state);
- handler.main(state);
+ if (!handler.prepare(&state))
+ c_fatal(15, "handler.prepare() said a big no-no");
+
+ if (!handler.main(state))
+ c_fatal(16, "handler.main() said a big no-no");
- image_unload(state.image);
+ if (state.image != state.source_image)
+ image_unload(state.image);
image_unload(state.source_image);
if (args.out_style == ASC_STL_PALETTE)
diff --git a/src/mod_blocks.c b/src/mod_blocks.c
index 2c7e3ce..5404d5b 100644
--- a/src/mod_blocks.c
+++ b/src/mod_blocks.c
@@ -19,7 +19,7 @@ void __blk_end_line(asc_state_t s, bool final);
void __blk_end_output(asc_state_t s);
-void mod_blocks_prepare(asc_state_t *state)
+bool mod_blocks_prepare(asc_state_t *state)
{
int width, height;
get_size_keep_aspect(
@@ -33,9 +33,10 @@ void mod_blocks_prepare(asc_state_t *state)
state->image = image_resize(state->source_image, width, height);
if (state->args.dither)
m_prepare_dither(state);
+ return true;
}
-void mod_blocks_main(asc_state_t state)
+bool mod_blocks_main(asc_state_t state)
{
image_t *img = state.image;
__blk_start_output(state);
@@ -52,6 +53,7 @@ void mod_blocks_main(asc_state_t state)
__blk_end_line(state, final);
}
__blk_end_output(state);
+ return true;
}
void __blk_start_output(asc_state_t state)
diff --git a/src/mod_blocks.h b/src/mod_blocks.h
index 0e16dea..797afea 100644
--- a/src/mod_blocks.h
+++ b/src/mod_blocks.h
@@ -21,7 +21,7 @@
#include "colors.h"
#include "args.h"
-void mod_blocks_prepare(asc_state_t *state);
-void mod_blocks_main(asc_state_t state);
+bool mod_blocks_prepare(asc_state_t *state);
+bool mod_blocks_main(asc_state_t state);
#endif
diff --git a/src/mod_braille.c b/src/mod_braille.c
index 183e34f..f5dc600 100644
--- a/src/mod_braille.c
+++ b/src/mod_braille.c
@@ -30,7 +30,7 @@ void __bra_update2x4(image_t *img, rgba8 block[8], int x, int y)
}
-void mod_braille_prepare(asc_state_t *state)
+bool mod_braille_prepare(asc_state_t *state)
{
int width, height;
get_size_keep_aspect(
@@ -44,9 +44,10 @@ void mod_braille_prepare(asc_state_t *state)
state->image = image_resize(state->source_image, width, height);
if (state->args.dither)
m_prepare_dither(state);
+ return true;
}
-void mod_braille_main(asc_state_t state)
+bool mod_braille_main(asc_state_t state)
{
image_t *img = state.image;
@@ -92,6 +93,7 @@ void mod_braille_main(asc_state_t state)
__bra_end_line(state, final);
}
__bra_end_output(state);
+ return true;
}
int __bra_best_match_i(rgba8 a, rgba8 b, rgba8 t)
diff --git a/src/mod_braille.h b/src/mod_braille.h
index 7f9b0ad..ac6b398 100644
--- a/src/mod_braille.h
+++ b/src/mod_braille.h
@@ -21,7 +21,7 @@
#include "colors.h"
#include "args.h"
-void mod_braille_prepare(asc_state_t *state);
-void mod_braille_main(asc_state_t state);
+bool mod_braille_prepare(asc_state_t *state);
+bool mod_braille_main(asc_state_t state);
#endif
diff --git a/src/mod_charmap.c b/src/mod_charmap.c
new file mode 100644
index 0000000..923288d
--- /dev/null
+++ b/src/mod_charmap.c
@@ -0,0 +1,209 @@
+#include
+#include
+#include "mod_charmap.h"
+#include "image.h"
+#include "colors.h"
+#include "commons.h"
+#include "fmt_strings.h"
+
+void __chr_start_output(asc_state_t s);
+void __chr_start_line(asc_state_t s, bool final);
+void __chr_put_pixel(asc_state_t s, rgba8 fg, bool final);
+void __chr_putc_bw(asc_state_t s, int cc, bool final);
+void __chr_putc_ansi(asc_state_t s, int fg, int cc, bool final);
+void __chr_putc_256(asc_state_t s, int fg, int cc, bool final);
+void __chr_putc_true(asc_state_t s, rgba8 fg, int cc, bool final);
+void __chr_end_line(asc_state_t s, bool final);
+void __chr_end_output(asc_state_t s);
+
+typedef struct {
+ int n_codepoints;
+ int *codepoints;
+} __m_cmap_userdata_t;
+
+bool mod_cmap_prepare(asc_state_t *state)
+{
+ static __m_cmap_userdata_t userdata;
+ static int codepoints[M_CMAP_MAX_CODEPOINTS];
+ userdata.codepoints = codepoints;
+ int n_cp = 0, cp, proc;
+ char *charset = state->args.charset;
+ while ((cp = get_codepoint(charset, &proc)) != 0)
+ { codepoints[n_cp++] = cp; charset += proc; }
+ if (n_cp > M_CMAP_MAX_CODEPOINTS)
+ {
+ fprintf(stderr, "Error: maximum of %d codepoints is allowed, got %d\n",
+ M_CMAP_MAX_CODEPOINTS, n_cp);
+ return false;
+ }
+ userdata.n_codepoints = n_cp;
+ state->userdata = (void *)&userdata;
+
+ int width, height;
+ get_size_keep_aspect(
+ state->source_image->width * 2, state->source_image->height,
+ state->args.width, state->args.height, &width, &height);
+ LOG("Source size: %dx%d", state->source_image->width,
+ state->source_image->height);
+ LOG("Requested size: %dx%d", state->args.width, state->args.height);
+ LOG("Resizing image to %dx%d", width, height);
+ state->image = image_resize(state->source_image, width, height);
+ if (state->args.dither)
+ m_prepare_dither(state);
+ return true;
+}
+
+bool mod_cmap_main(asc_state_t state)
+{
+ image_t *img = state.image;
+ __chr_start_output(state);
+ for (int y = 0; y < img->height; y++)
+ {
+ bool final = y >= img->height;
+ __chr_start_line(state, final);
+ for (int x = 0; x < img->width; x++)
+ {
+ __chr_put_pixel(state, img->pixels[x + y * img->width], x >= img->width);
+ }
+ __chr_end_line(state, final);
+ }
+ __chr_end_output(state);
+ return true;
+}
+
+void __chr_start_output(asc_state_t state)
+{
+ if (state.args.out_format == ASC_FMT_JSON)
+ fprintf(state.out_file, S_JSON_HEAD,
+ state.image->width, state.image->height / 2);
+ else if (state.args.out_format == ASC_FMT_HTML)
+ fprintf(state.out_file, S_HTML_HEAD);
+}
+
+void __chr_start_line(asc_state_t state, bool final)
+{
+ (void)final;
+ if (state.args.out_format == ASC_FMT_JSON)
+ fprintf(state.out_file, S_JSON_LSTA);
+ else if (state.args.out_format == ASC_FMT_HTML)
+ fprintf(state.out_file, S_HTML_LSTA);
+}
+
+void __chr_put_pixel(asc_state_t s, rgba8 fg, bool final)
+{
+ float brightness = calc_brightness(fg);
+ __m_cmap_userdata_t data = *(__m_cmap_userdata_t *)s.userdata;
+ int ndx = floorf(brightness * data.n_codepoints);
+ if (ndx >= data.n_codepoints) ndx = data.n_codepoints - 1;
+ int codepoint = data.codepoints[ndx];
+ switch (s.args.out_style)
+ {
+ case ASC_STL_BLACKWHITE:
+ __chr_putc_bw(s, codepoint, final);
+ break;
+ case ASC_STL_ANSI_VGA:
+ case ASC_STL_ANSI_XTERM:
+ case ASC_STL_ANSI_DISCORD:
+ __chr_putc_ansi(s, closest_color(*s.palette, fg), codepoint, final);
+ break;
+ case ASC_STL_256COLOR:
+ __chr_putc_256(s, closest_color(c_palette_256, fg), codepoint, final);
+ break;
+ case ASC_STL_PALETTE:
+ __chr_putc_true(s, clamp_to_pal(*s.palette, fg), codepoint, final);
+ break;
+ case ASC_STL_TRUECOLOR:
+ __chr_putc_true(s, fg, codepoint, final);
+ break;
+ case ASC_STL_ENDL:
+ break;
+ }
+}
+
+void __chr_putc_bw(asc_state_t s, int cc, bool final)
+{
+ static char buf[8];
+ set_codepoint(buf, cc);
+ if (s.args.out_format == ASC_FMT_JSON)
+ {
+ fprintf(s.out_file, S_JSON_PUCC, cc, 0xFFFFFF);
+ if (!final) fprintf(s.out_file, ",");
+ fprintf(s.out_file, "\n");
+ }
+ else if (s.args.out_format == ASC_FMT_HTML)
+ fprintf(s.out_file, S_HTML_PUBW, cc);
+ else
+ fprintf(s.out_file, "%s", buf);
+}
+
+void __chr_putc_ansi(asc_state_t s, int fg, int cc, bool final)
+{
+ static char buf[8];
+ rgba8 fgc = s.palette->palette[fg];
+ set_codepoint(buf, cc);
+ if (s.args.out_format == ASC_FMT_JSON)
+ {
+ fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fgc));
+ if (!final) fprintf(s.out_file, ",");
+ fprintf(s.out_file, "\n");
+ }
+ else if (s.args.out_format == ASC_FMT_HTML)
+ fprintf(s.out_file, S_HTML_PUCC, fgc.r, fgc.g, fgc.b, cc);
+ else
+ fprintf(s.out_file, S_ANSI_FG"%s", fg + (fg >= 8 ? 82 : 30), buf);
+}
+
+void __chr_putc_256(asc_state_t s, int fg, int cc, bool final)
+{
+ static char buf[8];
+ rgba8 fgc = pal256_to_rgb(*s.palette, fg);
+ set_codepoint(buf, cc);
+ if (s.args.out_format == ASC_FMT_JSON)
+ {
+ fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fgc));
+ if (!final) fprintf(s.out_file, ",");
+ fprintf(s.out_file, "\n");
+ }
+ else if (s.args.out_format == ASC_FMT_HTML)
+ fprintf(s.out_file, S_HTML_PUCC, fgc.r, fgc.g, fgc.b, cc);
+ else
+ fprintf(s.out_file, S_ANSI_FG_256"%s", fg, buf);
+}
+
+void __chr_putc_true(asc_state_t s, rgba8 fg, int cc, bool final)
+{
+ static char buf[8];
+ set_codepoint(buf, cc);
+ if (s.args.out_format == ASC_FMT_JSON)
+ {
+ fprintf(s.out_file, S_JSON_PUCC, cc, RGBN(fg));
+ if (!final) fprintf(s.out_file, ",");
+ fprintf(s.out_file, "\n");
+ }
+ else if (s.args.out_format == ASC_FMT_HTML)
+ fprintf(s.out_file, S_HTML_PUCC, fg.r, fg.g, fg.b, cc);
+ else
+ fprintf(s.out_file, S_ANSI_FG_RGB"%s", fg.r, fg.g, fg.b, buf);
+}
+
+void __chr_end_line(asc_state_t state, bool final)
+{
+ if (state.args.out_format == ASC_FMT_JSON)
+ fprintf(state.out_file, final ? S_JSON_LEND_FINAL : S_JSON_LEND);
+ else if (state.args.out_format == ASC_FMT_HTML)
+ fprintf(state.out_file, S_HTML_LEND);
+ else
+ {
+ if (state.args.out_style != ASC_STL_BLACKWHITE)
+ fprintf(state.out_file, S_ANSI_RST);
+ fprintf(state.out_file, "\n");
+ }
+}
+
+void __chr_end_output(asc_state_t state)
+{
+ if (state.args.out_format == ASC_FMT_JSON)
+ fprintf(state.out_file, S_JSON_TAIL);
+ else if (state.args.out_format == ASC_FMT_HTML)
+ fprintf(state.out_file, S_HTML_TAIL);
+}
diff --git a/src/mod_charmap.h b/src/mod_charmap.h
new file mode 100644
index 0000000..787a170
--- /dev/null
+++ b/src/mod_charmap.h
@@ -0,0 +1,29 @@
+/*
+ * yaitaa - yet another image to ascii art converter
+ * Copyright (C) 2022 hatkidchan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#ifndef _MOD_CHARMAP_
+#define _MOD_CHARMAP_
+#include
+#include "colors.h"
+#include "args.h"
+
+#define M_CMAP_MAX_CODEPOINTS 256
+
+bool mod_cmap_prepare(asc_state_t *state);
+bool mod_cmap_main(asc_state_t state);
+
+#endif