#include #include #include #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, "", 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); }