/** \brief Print a string. * * Print an UTF-8 string at the given coordinates, using the default * foreground and background values. The coordinates may be outside the * canvas boundaries (eg. a negative Y coordinate) and the string will * be cropped accordingly if it is too long. * * See caca_put_char() for more information on how fullwidth characters * are handled when overwriting each other or at the canvas' boundaries. * * This function returns the number of cells printed by the string. It is * not the number of characters printed, because fullwidth characters * account for two cells. * * This function never fails. * * \param cv A handle to the libcaca canvas. * \param x X coordinate. * \param y Y coordinate. * \param s The string to print. * \return The number of cells printed. */ int caca_put_str(caca_canvas_t *cv, int x, int y, char const *s) { size_t rd; int len = 0; if (y < 0 || y >= (int)cv->height || x >= (int)cv->width) { while (*s) { len += caca_utf32_is_fullwidth(caca_utf8_to_utf32(s, &rd)) ? 2 : 1; s += rd ? rd : 1; } return len; } while (*s) { uint32_t ch = caca_utf8_to_utf32(s, &rd); if (x + len >= -1 && x + len < (int)cv->width) caca_put_char(cv, x + len, y, ch); len += caca_utf32_is_fullwidth(ch) ? 2 : 1; s += rd ? rd : 1; } return len; }
static int ncurses_get_event(caca_display_t *dp, caca_privevent_t *ev) { int intkey; intkey = getch(); if(intkey == ERR) { ev->type = CACA_EVENT_NONE; return 0; } if(intkey < 0x7f) { ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.ch = intkey; ev->data.key.utf32 = intkey; ev->data.key.utf8[0] = intkey; ev->data.key.utf8[1] = '\0'; return 1; } /* If the key was UTF-8, parse the whole sequence */ if(intkey >= 0x80 && intkey < 0x100) { int keys[7]; /* Necessary for ungetch(); */ char utf8[7]; uint32_t utf32; size_t i, bytes = 0; keys[0] = intkey; utf8[0] = intkey; for(i = 1; i < 6; i++) { keys[i] = getch(); utf8[i] = (unsigned char)keys[i]; } utf8[i] = '\0'; utf32 = caca_utf8_to_utf32(utf8, &bytes); while(i > bytes) ungetch(keys[--i]); if(bytes) { ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.ch = 0; ev->data.key.utf32 = utf32; strcpy(ev->data.key.utf8, utf8); return 1; } } if(intkey == KEY_MOUSE) { MEVENT mevent; getmouse(&mevent); switch(mevent.bstate) { #define PRESS(x) ev->data.mouse.button = x; \ ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev) #define RELEASE(x) ev->data.mouse.button = x; \ ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev) #define CLICK(x) PRESS(x); RELEASE(x) case BUTTON1_PRESSED: PRESS(1); break; case BUTTON1_RELEASED: RELEASE(1); break; case BUTTON1_CLICKED: CLICK(1); break; case BUTTON1_DOUBLE_CLICKED: CLICK(1); CLICK(1); break; case BUTTON1_TRIPLE_CLICKED: CLICK(1); CLICK(1); CLICK(1); break; case BUTTON1_RESERVED_EVENT: break; case BUTTON2_PRESSED: PRESS(2); break; case BUTTON2_RELEASED: RELEASE(2); break; case BUTTON2_CLICKED: CLICK(2); break; case BUTTON2_DOUBLE_CLICKED: CLICK(2); CLICK(2); break; case BUTTON2_TRIPLE_CLICKED: CLICK(2); CLICK(2); CLICK(2); break; case BUTTON2_RESERVED_EVENT: break; case BUTTON3_PRESSED: PRESS(3); break; case BUTTON3_RELEASED: RELEASE(3); break; case BUTTON3_CLICKED: CLICK(3); break; case BUTTON3_DOUBLE_CLICKED: CLICK(3); CLICK(3); break; case BUTTON3_TRIPLE_CLICKED: CLICK(3); CLICK(3); CLICK(3); break; case BUTTON3_RESERVED_EVENT: break; case BUTTON4_PRESSED: PRESS(4); break; case BUTTON4_RELEASED: RELEASE(4); break; case BUTTON4_CLICKED: CLICK(4); break; case BUTTON4_DOUBLE_CLICKED: CLICK(4); CLICK(4); break; case BUTTON4_TRIPLE_CLICKED: CLICK(4); CLICK(4); CLICK(4); break; case BUTTON4_RESERVED_EVENT: break; default: break; #undef PRESS #undef RELEASE #undef CLICK } if(dp->mouse.x == mevent.x && dp->mouse.y == mevent.y) return _pop_event(dp, ev); dp->mouse.x = mevent.x; dp->mouse.y = mevent.y; ev->type = CACA_EVENT_MOUSE_MOTION; ev->data.mouse.x = dp->mouse.x; ev->data.mouse.y = dp->mouse.y; return 1; } switch(intkey) { case KEY_UP: ev->data.key.ch = CACA_KEY_UP; break; case KEY_DOWN: ev->data.key.ch = CACA_KEY_DOWN; break; case KEY_LEFT: ev->data.key.ch = CACA_KEY_LEFT; break; case KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break; case KEY_IC: ev->data.key.ch = CACA_KEY_INSERT; break; case KEY_DC: ev->data.key.ch = CACA_KEY_DELETE; break; case 0x7f: case KEY_BACKSPACE: ev->data.key.ch = CACA_KEY_BACKSPACE; break; case KEY_HOME: ev->data.key.ch = CACA_KEY_HOME; break; case KEY_END: ev->data.key.ch = CACA_KEY_END; break; case KEY_PPAGE: ev->data.key.ch = CACA_KEY_PAGEUP; break; case KEY_NPAGE: ev->data.key.ch = CACA_KEY_PAGEDOWN; break; case KEY_F(1): ev->data.key.ch = CACA_KEY_F1; break; case KEY_F(2): ev->data.key.ch = CACA_KEY_F2; break; case KEY_F(3): ev->data.key.ch = CACA_KEY_F3; break; case KEY_F(4): ev->data.key.ch = CACA_KEY_F4; break; case KEY_F(5): ev->data.key.ch = CACA_KEY_F5; break; case KEY_F(6): ev->data.key.ch = CACA_KEY_F6; break; case KEY_F(7): ev->data.key.ch = CACA_KEY_F7; break; case KEY_F(8): ev->data.key.ch = CACA_KEY_F8; break; case KEY_F(9): ev->data.key.ch = CACA_KEY_F9; break; case KEY_F(10): ev->data.key.ch = CACA_KEY_F10; break; case KEY_F(11): ev->data.key.ch = CACA_KEY_F11; break; case KEY_F(12): ev->data.key.ch = CACA_KEY_F12; break; default: /* Unknown key */ ev->type = CACA_EVENT_NONE; return 0; } ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.utf32 = 0; ev->data.key.utf8[0] = '\0'; return 1; }
static caca_charfont_t * open_charfont(char const *path) { char buf[2048]; char hardblank[10]; caca_charfont_t *ff; char *data = NULL; caca_file_t *f; #if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S) int const pathlen = 2048; char *altpath = NULL; #endif int i, j, size, comment_lines; ff = malloc(sizeof(caca_charfont_t)); if(!ff) { seterrno(ENOMEM); return NULL; } /* Open font: if not found, try .tlf, then .flf */ f = caca_file_open(path, "r"); #if !defined __KERNEL__ && (defined HAVE_SNPRINTF || defined HAVE_SPRINTF_S) if(!f) altpath = malloc(pathlen); if(!f) { #if defined HAVE_SPRINTF_S sprintf_s(altpath, pathlen - 1, "%s.tlf", path); #else snprintf(altpath, pathlen - 1, "%s.tlf", path); #endif altpath[pathlen - 1] = '\0'; f = caca_file_open(altpath, "r"); } if(!f) { #if defined HAVE_SPRINTF_S sprintf_s(altpath, pathlen - 1, "%s.flf", path); #else snprintf(altpath, pathlen - 1, "%s.flf", path); #endif altpath[pathlen - 1] = '\0'; f = caca_file_open(altpath, "r"); } if (altpath) free(altpath); #endif if(!f) { free(ff); seterrno(ENOENT); return NULL; } /* Read header */ ff->print_direction = 0; ff->full_layout = 0; ff->codetag_count = 0; caca_file_gets(f, buf, 2048); if(sscanf(buf, "%*[ft]lf2a%6s %u %u %u %i %u %u %u %u\n", hardblank, &ff->height, &ff->baseline, &ff->max_length, &ff->old_layout, &comment_lines, &ff->print_direction, &ff->full_layout, &ff->codetag_count) < 6) { debug("figfont error: `%s' has invalid header: %s", path, buf); caca_file_close(f); free(ff); seterrno(EINVAL); return NULL; } if(ff->old_layout < -1 || ff->old_layout > 63 || ff->full_layout > 32767 || ((ff->full_layout & 0x80) && (ff->full_layout & 0x3f) == 0 && ff->old_layout)) { debug("figfont error: `%s' has invalid layout %i/%u", path, ff->old_layout, ff->full_layout); caca_file_close(f); free(ff); seterrno(EINVAL); return NULL; } ff->hardblank = caca_utf8_to_utf32(hardblank, NULL); /* Skip comment lines */ for(i = 0; i < comment_lines; i++) caca_file_gets(f, buf, 2048); /* Read mandatory characters (32-127, 196, 214, 220, 228, 246, 252, 223) * then read additional characters. */ ff->glyphs = 0; ff->lookup = NULL; for(i = 0, size = 0; !caca_file_eof(f); ff->glyphs++) { if((ff->glyphs % 2048) == 0) ff->lookup = realloc(ff->lookup, (ff->glyphs + 2048) * 2 * sizeof(int)); if(ff->glyphs < STD_GLYPHS) { ff->lookup[ff->glyphs * 2] = 32 + ff->glyphs; } else if(ff->glyphs < EXT_GLYPHS) { static int const tab[7] = { 196, 214, 220, 228, 246, 252, 223 }; ff->lookup[ff->glyphs * 2] = tab[ff->glyphs - STD_GLYPHS]; } else { unsigned int tmp; if(caca_file_gets(f, buf, 2048) == NULL) break; /* Ignore blank lines, as in jacky.flf */ if(buf[0] == '\n' || buf[0] == '\r') continue; /* Ignore negative indices for now, as in ivrit.flf */ if(buf[0] == '-') { for(j = 0; j < ff->height; j++) caca_file_gets(f, buf, 2048); continue; } if(!buf[0] || buf[0] < '0' || buf[0] > '9') { debug("figfont error: glyph #%u in `%s'", ff->glyphs, path); free(data); free(ff->lookup); free(ff); seterrno(EINVAL); return NULL; } sscanf(buf, buf[1] == 'x' ? "%x" : "%u", &tmp); ff->lookup[ff->glyphs * 2] = tmp; } ff->lookup[ff->glyphs * 2 + 1] = 0; for(j = 0; j < ff->height; j++) { if(i + 2048 >= size) data = realloc(data, size += 2048); caca_file_gets(f, data + i, 2048); i = (uintptr_t)strchr(data + i, 0) - (uintptr_t)data; } } caca_file_close(f); if(ff->glyphs < EXT_GLYPHS) { debug("figfont error: only %u glyphs in `%s', expected at least %u", ff->glyphs, path, EXT_GLYPHS); free(data); free(ff->lookup); free(ff); seterrno(EINVAL); return NULL; } /* Remaining initialisation */ ff->charcv = NULL; ff->left = NULL; ff->right = NULL; /* Import buffer into canvas */ ff->fontcv = caca_create_canvas(0, 0); caca_import_canvas_from_memory(ff->fontcv, data, i, "utf8"); free(data); /* Remove EOL characters. For now we ignore hardblanks, don’t do any * smushing, nor any kind of error checking. */ for(j = 0; j < ff->height * ff->glyphs; j++) { uint32_t ch, oldch = 0; for(i = ff->max_length; i--;) { ch = caca_get_char(ff->fontcv, i, j); /* Replace hardblanks with U+00A0 NO-BREAK SPACE */ if(ch == ff->hardblank) caca_put_char(ff->fontcv, i, j, ch = 0xa0); if(oldch && ch != oldch) { if(!ff->lookup[j / ff->height * 2 + 1]) ff->lookup[j / ff->height * 2 + 1] = i + 1; } else if(oldch && ch == oldch) caca_put_char(ff->fontcv, i, j, ' '); else if(ch != ' ') { oldch = ch; caca_put_char(ff->fontcv, i, j, ' '); } } } return ff; }
static int slang_get_event(caca_display_t *dp, caca_privevent_t *ev) { int intkey; /* If SIGINT was caught, we pass it to the application as Ctrl-C. */ if(dp->drv.p->sigint_event > 0) { ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.ch = CACA_KEY_CTRL_C; ev->data.key.utf32 = 0x03; ev->data.key.utf8[0] = 0x03; ev->data.key.utf8[1] = 0; dp->drv.p->sigint_event--; return 1; } if(!SLang_input_pending(0)) { ev->type = CACA_EVENT_NONE; return 0; } /* We first use SLang_getkey() to see whether Esc was pressed * alone, then (if it wasn't) we unget the key and use SLkp_getkey() * instead, so that escape sequences are interpreted. */ intkey = SLang_getkey(); if(intkey != 0x1b /* Esc */ || SLang_input_pending(0)) { SLang_ungetkey(intkey); intkey = SLkp_getkey(); } /* If the key was ASCII, return it immediately */ if(intkey < 0x7f) { ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.ch = intkey; ev->data.key.utf32 = intkey; ev->data.key.utf8[0] = intkey; ev->data.key.utf8[1] = '\0'; return 1; } /* If the key was UTF-8, parse the whole sequence */ if(intkey >= 0x80 && intkey < 0x100) { int keys[7]; /* Necessary for ungetkey(); */ char utf8[7]; uint32_t utf32; size_t i, bytes = 0; keys[0] = intkey; utf8[0] = intkey; for(i = 1; i < 6; i++) { if(!SLang_input_pending(0)) break; keys[i] = SLang_getkey(); utf8[i] = (unsigned char)keys[i]; } utf8[i] = '\0'; utf32 = caca_utf8_to_utf32(utf8, &bytes); while(i > bytes) SLang_ungetkey(keys[--i]); if(bytes) { ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.ch = 0; ev->data.key.utf32 = utf32; strcpy(ev->data.key.utf8, utf8); return 1; } } if(intkey == 0x3e9) { int button = (SLang_getkey() - ' ' + 1) & 0xf; int x = SLang_getkey() - '!'; int y = SLang_getkey() - '!'; ev->data.mouse.button = button; ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev); ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev); if(dp->mouse.x == x && dp->mouse.y == y) return _pop_event(dp, ev); dp->mouse.x = x; dp->mouse.y = y; ev->type = CACA_EVENT_MOUSE_MOTION; ev->data.mouse.x = dp->mouse.x; ev->data.mouse.y = dp->mouse.y; return 1; } switch(intkey) { case SL_KEY_UP: ev->data.key.ch = CACA_KEY_UP; break; case SL_KEY_DOWN: ev->data.key.ch = CACA_KEY_DOWN; break; case SL_KEY_LEFT: ev->data.key.ch = CACA_KEY_LEFT; break; case SL_KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break; case SL_KEY_IC: ev->data.key.ch = CACA_KEY_INSERT; break; case SL_KEY_DELETE: ev->data.key.ch = CACA_KEY_DELETE; break; case 0x7f: case SL_KEY_BACKSPACE: ev->data.key.ch = CACA_KEY_BACKSPACE; break; case SL_KEY_HOME: ev->data.key.ch = CACA_KEY_HOME; break; case SL_KEY_END: ev->data.key.ch = CACA_KEY_END; break; case SL_KEY_PPAGE: ev->data.key.ch = CACA_KEY_PAGEUP; break; case SL_KEY_NPAGE: ev->data.key.ch = CACA_KEY_PAGEDOWN; break; case SL_KEY_F(1): ev->data.key.ch = CACA_KEY_F1; break; case SL_KEY_F(2): ev->data.key.ch = CACA_KEY_F2; break; case SL_KEY_F(3): ev->data.key.ch = CACA_KEY_F3; break; case SL_KEY_F(4): ev->data.key.ch = CACA_KEY_F4; break; case SL_KEY_F(5): ev->data.key.ch = CACA_KEY_F5; break; case SL_KEY_F(6): ev->data.key.ch = CACA_KEY_F6; break; case SL_KEY_F(7): ev->data.key.ch = CACA_KEY_F7; break; case SL_KEY_F(8): ev->data.key.ch = CACA_KEY_F8; break; case SL_KEY_F(9): ev->data.key.ch = CACA_KEY_F9; break; case SL_KEY_F(10): ev->data.key.ch = CACA_KEY_F10; break; case SL_KEY_F(11): ev->data.key.ch = CACA_KEY_F11; break; case SL_KEY_F(12): ev->data.key.ch = CACA_KEY_F12; break; default: /* Unknown key */ ev->type = CACA_EVENT_NONE; return 0; } ev->type = CACA_EVENT_KEY_PRESS; ev->data.key.utf32 = 0; ev->data.key.utf8[0] = '\0'; return 1; }
int main(int argc, char *argv[]) { textentry entries[TEXT_ENTRIES]; caca_canvas_t *cv; caca_display_t *dp; unsigned int i, e = 0, running = 1; cv = caca_create_canvas(0, 0); if(cv == NULL) { printf("Can't create canvas\n"); return -1; } dp = caca_create_display(cv); if(dp == NULL) { printf("Can't create display\n"); return -1; } caca_set_cursor(dp, 1); caca_set_color_ansi(cv, CACA_WHITE, CACA_BLUE); caca_put_str(cv, 1, 1, "Text entries - press tab to cycle"); for(i = 0; i < TEXT_ENTRIES; i++) { entries[i].buffer[0] = 0; entries[i].size = 0; entries[i].cursor = 0; entries[i].changed = 1; caca_printf(cv, 3, 3 * i + 4, "[entry %i]", i + 1); } /* Put Unicode crap in the last text entry */ entries[TEXT_ENTRIES - 1].buffer[0] = 'A'; entries[TEXT_ENTRIES - 1].buffer[1] = 'b'; entries[TEXT_ENTRIES - 1].buffer[2] = caca_utf8_to_utf32("Ç", NULL); entries[TEXT_ENTRIES - 1].buffer[3] = caca_utf8_to_utf32("đ", NULL); entries[TEXT_ENTRIES - 1].buffer[4] = caca_utf8_to_utf32("ボ", NULL); entries[TEXT_ENTRIES - 1].buffer[5] = CACA_MAGIC_FULLWIDTH; entries[TEXT_ENTRIES - 1].buffer[6] = caca_utf8_to_utf32("♥", NULL); entries[TEXT_ENTRIES - 1].size = 7; while(running) { caca_event_t ev; for(i = 0; i < TEXT_ENTRIES; i++) { unsigned int j, start, size; if(!entries[i].changed) continue; caca_set_color_ansi(cv, CACA_BLACK, CACA_LIGHTGRAY); caca_fill_box(cv, 2, 3 * i + 5, BUFFER_SIZE + 1, 1, ' '); start = 0; size = entries[i].size; for(j = 0; j < size; j++) { caca_put_char(cv, 2 + j, 3 * i + 5, entries[i].buffer[start + j]); } entries[i].changed = 0; } /* Put the cursor on the active textentry */ caca_gotoxy(cv, 2 + entries[e].cursor, 3 * e + 5); caca_refresh_display(dp); if(caca_get_event(dp, CACA_EVENT_KEY_PRESS, &ev, -1) == 0) continue; switch(caca_get_event_key_ch(&ev)) { case CACA_KEY_ESCAPE: running = 0; break; case CACA_KEY_TAB: case CACA_KEY_RETURN: e = (e + 1) % TEXT_ENTRIES; break; case CACA_KEY_HOME: entries[e].cursor = 0; break; case CACA_KEY_END: entries[e].cursor = entries[e].size; break; case CACA_KEY_LEFT: if(entries[e].cursor) entries[e].cursor--; break; case CACA_KEY_RIGHT: if(entries[e].cursor < entries[e].size) entries[e].cursor++; break; case CACA_KEY_DELETE: if(entries[e].cursor < entries[e].size) { memmove(entries[e].buffer + entries[e].cursor, entries[e].buffer + entries[e].cursor + 1, (entries[e].size - entries[e].cursor + 1) * 4); entries[e].size--; entries[e].changed = 1; } break; case CACA_KEY_BACKSPACE: if(entries[e].cursor) { memmove(entries[e].buffer + entries[e].cursor - 1, entries[e].buffer + entries[e].cursor, (entries[e].size - entries[e].cursor) * 4); entries[e].size--; entries[e].cursor--; entries[e].changed = 1; } break; default: if(entries[e].size < BUFFER_SIZE) { memmove(entries[e].buffer + entries[e].cursor + 1, entries[e].buffer + entries[e].cursor, (entries[e].size - entries[e].cursor) * 4); entries[e].buffer[entries[e].cursor] = caca_get_event_key_utf32(&ev); entries[e].size++; entries[e].cursor++; entries[e].changed = 1; } break; } } caca_free_display(dp); caca_free_canvas(cv); return 0; }