123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- #include <math.h>
- #include <cairo/cairo.h>
- #include <pango/pangocairo.h>
- #define NICKNAME_TEXT_SIZE 23
- #define MESSAGE_TEXT_SIZE 24
- #define HPADDING 18
- #define VPADDING 8
- #define AVATAR_OFFSET 3
- #define AVATAR_PADDING 12
- #define AVATAR_DIM 75
- #define AVATAR_WIDTH (AVATAR_DIM + AVATAR_PADDING)
- #define TAIL_WIDTH 18
- #define TAIL_HEIGHT 30
- #define MESSAGE_BOX_RADIUS 8
- #define MESSAGE_BOX_MIN 160
- #define WRAP_WIDTH 400
- #define TAIL_PATH "./resources/tail.png"
- #define MAX(x, y) ((x) >= (y)? (x): (y))
- #define MIN(x, y) ((x) <= (y)? (x): (y))
- typedef unsigned int uint_t;
- typedef struct {
- int r, g, b;
- } color_t;
- color_t COLORS_TABLE[] = {
- /* Nickname colors */
- {0xee, 0x49, 0x28}, /* 0 */
- {0x41, 0xa9, 0x03}, /* 1 */
- {0xe0, 0x96, 0x02}, /* 2 */
- {0x0f, 0x94, 0xed}, /* 3 */
- {0x8f, 0x3b, 0xf7}, /* 4 */
- {0xfc, 0x43, 0x80}, /* 5 */
- {0x00, 0xa1, 0xc4}, /* 6 */
- {0xeb, 0x70, 0x02}, /* 7 */
- /* Message text color */
- {0xff, 0xff, 0xff}, /* 8 */
- /* Message box color */
- {0x2b, 0x2b, 0x2b} /* 9 */
- };
- #define COLOR(i) (&COLORS_TABLE[i])
- #define C_MESSAGE_TEXT COLOR(8)
- #define C_MESSAGE_BOX COLOR(9)
- typedef struct {
- PangoLayout *layout;
- uint_t w, h;
- } text_t;
- typedef struct {
- unsigned char *data;
- size_t size, allocated;
- } buffer_t;
- void *malloc_protected(size_t z) {
- void *p;
- if (!(p = malloc(z))) {
- fputs("out of memory.\n", stderr);
- exit(1);
- }
- return p;
- }
- void *realloc_protected(void *old_p, size_t z) {
- void *p;
- if (!(p = realloc(old_p, z))) {
- fputs("out of memory.\n", stderr);
- exit(1);
- }
- return p;
- }
- buffer_t *new_buffer() {
- buffer_t *buffer;
- buffer = malloc_protected(sizeof(buffer_t));
-
- buffer->data = NULL;
- buffer->size = 0;
- buffer->allocated = 0;
- return buffer;
- }
- void free_buffer(buffer_t *buffer) {
- free(buffer->data);
- free(buffer);
- }
- void buffer_expand(buffer_t *buffer, size_t size) {
- buffer->allocated += size;
- buffer->data = realloc_protected(buffer->data, buffer->allocated);
- }
- void buffer_append_data(buffer_t *buffer, unsigned char *data, size_t data_length) {
- size_t tail = buffer->size;
-
- buffer->size += data_length;
- if (buffer->size > buffer->allocated)
- buffer_expand(buffer, data_length);
- for (size_t i = 0; i < data_length; i++)
- buffer->data[tail + i] = data[i];
- }
- #define BUFFER_APPEND(b, s) buffer_append_data(b, s, strlen(s))
- #define BUFFER_CONCAT(b1, b2) buffer_append_data(b1, b2->data, b2->size)
- #define BUFFER_PREPROCESS(b1, b2) do {\
- buffer_append_data(b2, "\0", 1);\
- preprocess_text(b1, b2->data);\
- } while(0)
- void buffer_append_escaped(buffer_t *buffer, char c) {
- switch (c) {
- case '<':
- BUFFER_APPEND(buffer, "<");
- return;
- case '>':
- BUFFER_APPEND(buffer, ">");
- return;
- case '&':
- BUFFER_APPEND(buffer, "&");
- return;
- case '"':
- BUFFER_APPEND(buffer, """);
- return;
- case '\'':
- BUFFER_APPEND(buffer, "'");
- return;
- }
- buffer_append_data(buffer, &c, 1);
- }
- #define SET(s,tag,pr) do {\
- buffer_t *temp_buffer;\
- uint_t k;\
- k = strlen(s);\
- i += k;\
- if (strncmp(&text[i], s, k) == 0) {\
- BUFFER_APPEND(buffer, s);\
- BUFFER_APPEND(buffer, s);\
- i += k-1;\
- break;\
- }\
- temp_buffer = new_buffer();\
- while (text[i]) {\
- if (strncmp(&text[i], s, k) == 0) {\
- i += k-1;\
- buffer_append_data(buffer, "<", 1);\
- if (*tag == ' ')\
- BUFFER_APPEND(buffer, "span");\
- BUFFER_APPEND(buffer, tag);\
- buffer_append_data(buffer, ">", 1);\
- if (pr)\
- BUFFER_PREPROCESS(buffer, temp_buffer);\
- else\
- BUFFER_CONCAT(buffer, temp_buffer);\
- buffer_append_data(buffer, "</", 2);\
- if (*tag == ' ')\
- BUFFER_APPEND(buffer, "span");\
- else\
- BUFFER_APPEND(buffer, tag);\
- buffer_append_data(buffer, ">", 1);\
- free_buffer(temp_buffer);\
- goto escape;\
- }\
- buffer_append_escaped(temp_buffer, text[i]);\
- i++;\
- }\
- i--;\
- BUFFER_APPEND(buffer, s);\
- BUFFER_PREPROCESS(buffer, temp_buffer);\
- free_buffer(temp_buffer);\
- } while(0)
- void preprocess_text(buffer_t *buffer, char *text) {
- for (size_t i = 0; text[i]; i++) {
- unsigned char c, nc, fc;
- c = text[i];
- nc = text[i+1];
- fc = text[i+2];
- if (c == '`')
- SET("`", "tt", 0);
- else if (c == '*' && nc == '*')
- SET("**", "b", 1);
- else if (c == '_' && nc == '_')
- SET("__", "i", 1);
- else if (c == '~' && nc == '~')
- SET("~~", "s", 1);
- else if (c == 0xee && nc == 0x80 && fc == 0x80)
- SET("\xee\x80\x80", "u", 1);
- else if (c == 0xee && nc == 0x80 && fc == 0x81)
- SET("\xee\x80\x81", " bgcolor=\"#ffffff\"", 0);
- else if (c == 0xee && nc == 0x80 && fc == 0x82)
- SET("\xee\x80\x82", " fgcolor=\"#70baf5\" underline=\"single\" underline_color=\"#70baf5\"", 0);
- else if (c == 0xee && nc == 0x80 && fc == 0x83)
- SET("\xee\x80\x83", " fgcolor=\"#70baf5\"", 0);
- else
- buffer_append_escaped(buffer, c);
- escape:;
- }
- }
- text_t *new_text(cairo_t *cr, char *s, int size, int weight, int preprocess) {
- text_t *text;
- PangoFontDescription *font_description;
- buffer_t *buffer;
- text = malloc_protected(sizeof(text_t));
- font_description = pango_font_description_from_string("Apple Color Emoji 16,Open Sans 16");
- pango_font_description_set_absolute_size(font_description, size * PANGO_SCALE);
- pango_font_description_set_weight(font_description, weight);
- text->layout = pango_cairo_create_layout(cr);
- pango_layout_set_font_description(text->layout, font_description);
- pango_layout_set_wrap(text->layout, PANGO_WRAP_WORD_CHAR);
- pango_layout_set_width(text->layout, WRAP_WIDTH * PANGO_SCALE);
- pango_font_description_free(font_description);
- if (preprocess) {
- buffer = new_buffer();
- preprocess_text(buffer, s);
- pango_layout_set_markup(text->layout, buffer->data, buffer->size);
- free_buffer(buffer);
- } else
- pango_layout_set_text(text->layout, s, -1);
- pango_layout_get_pixel_size(text->layout, &text->w, &text->h);
- return text;
- }
- void free_text(text_t *text) {
- g_object_unref(text->layout);
- free(text);
- }
- /* TO-DO: do this better */
- void get_text_size(char *s, uint_t size, uint_t weight, uint_t *w, uint_t *h, int preprocess) {
- cairo_surface_t *surface;
- cairo_t *cr;
- surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1);
- cr = cairo_create(surface);
- text_t *text = new_text(cr, s, size, weight, preprocess);
- *w = text->w;
- *h = text->h;
- free_text(text);
- cairo_destroy(cr);
- cairo_surface_destroy(surface);
- }
- void select_color(cairo_t *cr, color_t *color) {
- cairo_set_source_rgb(cr, color->r / 255.0F, color->g / 255.0F, color->b / 255.0F);
- }
- void paste_clipped_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
- cairo_surface_t *surface, *surface2;
- cairo_t *ic;
- surface = cairo_image_surface_create_from_png(path);
- ic = cairo_create(surface);
- cairo_scale(ic, ((double)AVATAR_DIM) / cairo_image_surface_get_width(surface), ((double)AVATAR_DIM) / cairo_image_surface_get_height(surface));
- cairo_set_source_surface(ic, surface, 0, 0);
- cairo_paint(ic);
- cairo_destroy(ic);
- surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, AVATAR_DIM, AVATAR_DIM);
- ic = cairo_create(surface2);
- cairo_arc(ic, AVATAR_DIM/2, AVATAR_DIM/2, AVATAR_DIM/2, 0, 2*M_PI);
- cairo_clip(ic);
- cairo_new_path(ic);
- cairo_set_source_surface(ic, surface, 0, 0);
- cairo_paint(ic);
- cairo_set_source_surface(cr, surface2, x, y);
- cairo_paint(cr);
- cairo_destroy(ic);
- cairo_surface_destroy(surface);
- cairo_surface_destroy(surface2);
- }
- void rounded_rectangle(cairo_t *cr, uint_t x, uint_t y, uint_t w, uint_t h, uint_t r) {
- cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
- cairo_new_sub_path(cr);
- cairo_arc(cr, x + r, y + r, r, M_PI, 3 * M_PI / 2);
- cairo_arc(cr, x + w - r, y + r, r, 3 *M_PI / 2, 2 * M_PI);
- cairo_arc(cr, x + w - r, y + h - r, r, 0, M_PI / 2);
- cairo_arc(cr, x + r, y + h - r, r, M_PI / 2, M_PI);
- cairo_close_path(cr);
- cairo_fill(cr);
- }
- void text(cairo_t *cr, uint_t x, uint_t y, text_t *text) {
- cairo_move_to(cr, x, y);
- pango_cairo_update_layout(cr, text->layout);
- pango_cairo_show_layout(cr, text->layout);
- }
- void paste_image(cairo_t *cr, uint_t x, uint_t y, char *path) {
- cairo_surface_t *surface;
- surface = cairo_image_surface_create_from_png(path);
- cairo_set_source_surface(cr, surface, x, y);
- cairo_paint(cr);
- cairo_surface_destroy(surface);
- }
- void contain(cairo_surface_t **surface, uint_t width, uint_t height) {
- uint_t image_width, image_height;
- uint_t new_width, new_height;
- double image_ratio, new_ratio;
- cairo_surface_t *surface2;
- cairo_t *cr;
- image_width = cairo_image_surface_get_width(*surface);
- image_height = cairo_image_surface_get_height(*surface);
- new_width = width;
- new_height = height;
- image_ratio = ((double)image_width) / ((double)image_height);
- new_ratio = ((double)width) / ((double)height);
- if (image_ratio > new_ratio)
- new_height = ((double)image_height) / ((double)image_width) * ((double)width);
- else
- new_width = ((double)image_width) / ((double)image_height) * ((double)height);
- surface2 = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, new_width, new_height);
- cr = cairo_create(surface2);
- cairo_scale(cr, ((double)new_width) / ((double)image_width), ((double)new_height) / ((double)image_height));
- cairo_set_source_surface(cr, *surface, 0, 0);
- cairo_paint(cr);
- cairo_destroy(cr);
- cairo_surface_destroy(*surface);
- *surface = surface2;
- }
- void read_uint(uint_t *r) {
- *r = 0;
- for (uint_t i = 0; i < sizeof(uint_t); i++)
- *r |= ((uint_t)fgetc(stdin)) << (i*8);
- }
- void read_string(buffer_t *buffer) {
- uint_t length;
- read_uint(&length);
- buffer_expand(buffer, length);
- buffer->size += fread(buffer->data + buffer->size, 1, length, stdin);
- buffer_append_data(buffer, &"\0", 1);
- }
- main() {
- uint_t avatar_width;
- uint_t image_width;
- uint_t image_height;
- uint_t x, y;
- uint_t w, h;
- cairo_surface_t *surface;
- cairo_t *cr;
- text_t *nickname;
- text_t *message;
-
- buffer_t *output_path;
- buffer_t *avatar_path;
- buffer_t *username_text;
- uint_t username_color;
- buffer_t *message_text;
-
- uint_t nickname_w, nickname_h;
- uint_t message_w, message_h;
- output_path = new_buffer();
- avatar_path = new_buffer();
- username_text = new_buffer();
- message_text = new_buffer();
- read_string(output_path);
- read_string(avatar_path);
- read_string(username_text);
- read_uint(&username_color);
- read_string(message_text);
- username_color = MIN(username_color, 6);
- get_text_size(username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, &nickname_w, &nickname_h, 0);
- get_text_size(message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, &message_w, &message_h, 1);
- image_width = AVATAR_WIDTH + MAX(message_w, nickname_w) + HPADDING*2;
- image_height = message_h + nickname_h + VPADDING*2;
- if (image_width - AVATAR_WIDTH < MESSAGE_BOX_MIN)
- image_width = AVATAR_WIDTH + MESSAGE_BOX_MIN;
- surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, image_width, image_height+AVATAR_OFFSET);
- cr = cairo_create(surface);
- nickname = new_text(cr, username_text->data, NICKNAME_TEXT_SIZE, PANGO_WEIGHT_BOLD, 0);
- message = new_text(cr, message_text->data, MESSAGE_TEXT_SIZE, PANGO_WEIGHT_NORMAL, 1);
- free_buffer(username_text);
- free_buffer(message_text);
- x = 0;
- y = image_height - AVATAR_DIM + AVATAR_OFFSET;
- paste_clipped_image(cr, x, y, avatar_path->data);
-
- free_buffer(avatar_path);
- x = AVATAR_WIDTH;
- y = 0;
- w = MAX(message->w, nickname->w) + HPADDING*2;
- h = message->h + nickname->h + VPADDING*2;
- if (w < MESSAGE_BOX_MIN)
- w = MESSAGE_BOX_MIN;
- select_color(cr, C_MESSAGE_BOX);
- rounded_rectangle(cr, x, y, w, h, MESSAGE_BOX_RADIUS);
- x = AVATAR_WIDTH - TAIL_WIDTH + 9;
- y = image_height - TAIL_HEIGHT;
- paste_image(cr, x, y, TAIL_PATH);
- x = AVATAR_WIDTH + HPADDING;
- y = VPADDING / 2;
- select_color(cr, COLOR(username_color));
- text(cr, x, y, nickname);
- x = AVATAR_WIDTH + HPADDING;
- y = nickname->h + VPADDING - 3;
- select_color(cr, C_MESSAGE_TEXT);
- text(cr, x, y, message);
- cairo_destroy(cr);
- contain(&surface, 512, 512);
- cairo_surface_write_to_png(surface, output_path->data);
- free_buffer(output_path);
- cairo_surface_destroy(surface);
- free_text(nickname);
- free_text(message);
- }
|