xnote.c (12774B)
1#include <GL/gl.h> 2#include <GLFW/glfw3.h> 3#include <fontconfig/fontconfig.h> 4#include <ft2build.h> 5#include FT_FREETYPE_H 6 7#include <assert.h> 8#include <string.h> 9#include <stdlib.h> 10#include <stdio.h> 11#include <stdarg.h> 12#include <stdbool.h> 13 14enum align { 15 CENTER, 16 LEFT, 17 RIGHT 18}; 19 20struct point { 21 double x, y; 22}; 23 24struct line { 25 struct point *points; 26 size_t count, cap; 27}; 28 29struct note { 30 double x, y; 31 char *text; 32 enum align align; 33 size_t len, cap; 34}; 35 36enum event { 37 EVENT_START, 38 EVENT_START_LINE, 39 EVENT_START_RECT, 40 EVENT_END_SHAPE, 41 EVENT_START_NOTE, 42 EVENT_MOUSE_MOVE, 43 EVENT_MOUSE_DRAG, 44}; 45 46static struct line *lines = NULL; 47static size_t line_count = 0; 48static size_t line_cap = 0; 49 50static struct note *notes = NULL; 51static size_t note_count = 0; 52static size_t note_cap = 0; 53 54static enum event *events = NULL; 55static size_t event_count = 0; 56static size_t event_cap = 0; 57 58static double canvas_x = 0; 59static double canvas_y = 0; 60 61static double cursor_x = 0; 62static double cursor_y = 0; 63 64static double drag_canvas_x = 0; 65static double drag_canvas_y = 0; 66static double drag_cursor_x = 0; 67static double drag_cursor_y = 0; 68 69static int window_width = 0; 70static int window_height = 0; 71 72static GLuint glyphs[128]; 73static FT_Vector glyph_advance[128]; 74static FT_Vector glyph_offset[128]; 75static FT_Vector glyph_size[128]; 76 77static void 78die(const char *fmt, ...) 79{ 80 va_list ap; 81 82 va_start(ap, fmt); 83 fputs("xnote: ", stderr); 84 vfprintf(stderr, fmt, ap); 85 if (*fmt && fmt[strlen(fmt)] == ':') 86 perror(NULL); 87 else 88 putc('\n', stderr); 89 va_end(ap); 90 91 exit(1); 92} 93 94static void * 95append(void *_array, size_t *count, size_t *cap, size_t size) 96{ 97 uint8_t **array = _array; 98 if (*count == *cap) { 99 if (*cap == 0) *cap = 1; 100 *cap *= 2; 101 *array = realloc(*array, *cap * size); 102 if (!*array) die("realloc:"); 103 } 104 return (void *) (*array + (*count)++ * size); 105} 106 107static void 108shrink(void *_array, size_t count, size_t *cap, size_t size) 109{ 110 uint8_t **array = _array; 111 if (count == 0) { 112 *array = NULL; 113 *cap = 0; 114 } else { 115 *cap = count; 116 *array = realloc(*array, *cap * size); 117 if (!*array) die("realloc:"); 118 } 119} 120 121static void 122glfw_error(int rc, const char *error) 123{ 124 die("glfw error (%i): %s", rc, error); 125} 126 127static void 128push_event(enum event event) 129{ 130 enum event *slot = append(&events, 131 &event_count, &event_cap, sizeof(enum event)); 132 *slot = event; 133} 134 135static enum event 136pop_event(void) 137{ 138 event_count -= 1; 139 enum event event = events[event_count]; 140 switch (event) { 141 case EVENT_START_LINE: 142 case EVENT_START_RECT: 143 line_count -= 1; 144 free(lines[line_count].points); 145 break; 146 case EVENT_START_NOTE: 147 note_count -= 1; 148 free(notes[note_count].text); 149 break; 150 case EVENT_START: 151 event_count += 1; 152 break; 153 default: 154 break; 155 } 156 return event; 157} 158 159static void 160undo(void) 161{ 162 while (1) { 163 switch (pop_event()) { 164 case EVENT_START_LINE: 165 case EVENT_START_NOTE: 166 case EVENT_START_RECT: 167 case EVENT_START: 168 break; 169 default: 170 continue; 171 } 172 break; 173 } 174 shrink(&events, event_count, &event_cap, sizeof(enum event)); 175} 176 177static enum event 178last_event(void) 179{ 180 return events[event_count - 1]; 181} 182 183static void 184window_resize_cb(GLFWwindow *window, int width, int height) 185{ 186 window_width = width; 187 window_height = height; 188 glViewport(0, 0, width, height); 189 glClear(GL_COLOR_BUFFER_BIT); 190 glLoadIdentity(); 191 glOrtho(0, width, height, 0, -1, 1); 192} 193 194static void 195mouse_move_cb(GLFWwindow *window, double x, double y) 196{ 197 cursor_x = x; 198 cursor_y = y; 199 if (last_event() == EVENT_START_LINE) { 200 struct line *line = &lines[line_count-1]; 201 if (line->count > 0) { 202 struct point *last_point = &line->points[line->count-1]; 203 if (cursor_x == last_point->x && cursor_y == last_point->y) 204 return; 205 } 206 struct point *point = append(&line->points, 207 &line->count, &line->cap, sizeof(struct point)); 208 point->x = canvas_x + cursor_x; 209 point->y = canvas_y + cursor_y; 210 } else if (last_event() == EVENT_START_RECT) { 211 struct line *line = &lines[line_count-1]; 212 line->points[1].x = canvas_x + cursor_x; 213 line->points[2].x = canvas_x + cursor_x; 214 line->points[2].y = canvas_y + cursor_y; 215 line->points[3].y = canvas_y + cursor_y; 216 } else if (last_event() == EVENT_MOUSE_DRAG) { 217 canvas_x = drag_canvas_x - (cursor_x - drag_cursor_x); 218 canvas_y = drag_canvas_y - (cursor_y - drag_cursor_y); 219 } else if (last_event() != EVENT_MOUSE_MOVE) { 220 push_event(EVENT_MOUSE_MOVE); 221 } 222} 223 224static void 225mouse_press_cb(GLFWwindow *window, int button, int action, int mods) 226{ 227 if (button == GLFW_MOUSE_BUTTON_LEFT) { 228 if (action == GLFW_PRESS) { 229 struct line *line = append(&lines, 230 &line_count, &line_cap, sizeof(struct line)); 231 line->count = line->cap = 0; 232 line->points = NULL; 233 struct point *point = append(&line->points, 234 &line->count, &line->cap, sizeof(struct point)); 235 point->x = canvas_x + cursor_x; 236 point->y = canvas_y + cursor_y; 237 push_event(EVENT_START_LINE); 238 } else if (action == GLFW_RELEASE) { 239 if (last_event() == EVENT_START_LINE 240 || last_event() == EVENT_START_RECT) { 241 struct line *line = &lines[line_count-1]; 242 shrink(&line->points, line->count, 243 &line->cap, sizeof(struct point)); 244 push_event(EVENT_END_SHAPE); 245 } 246 } 247 } else if (button == GLFW_MOUSE_BUTTON_RIGHT 248 || button == GLFW_MOUSE_BUTTON_MIDDLE) { 249 if (action == GLFW_PRESS) { 250 drag_canvas_x = canvas_x; 251 drag_canvas_y = canvas_y; 252 drag_cursor_x = cursor_x; 253 drag_cursor_y = cursor_y; 254 push_event(EVENT_MOUSE_DRAG); 255 } else if (action == GLFW_RELEASE && last_event() == EVENT_MOUSE_DRAG) { 256 pop_event(); 257 } 258 } 259} 260 261static void 262key_char_cb(GLFWwindow *window, unsigned int codepoint) 263{ 264 if (codepoint < 10 || codepoint >= 128) 265 return; 266 267 if (last_event() != EVENT_START_NOTE) { 268 struct note *note = append(¬es, ¬e_count, ¬e_cap, sizeof(struct note)); 269 note->text = NULL; 270 note->align = CENTER; 271 note->len = note->cap = 0; 272 note->x = canvas_x + cursor_x; 273 note->y = canvas_y + cursor_y; 274 push_event(EVENT_START_NOTE); 275 } 276 277 struct note *note = ¬es[note_count - 1]; 278 *(char *)append(¬e->text, ¬e->len, ¬e->cap, 1) = codepoint; 279} 280 281static void 282key_press_cb(GLFWwindow *window, int key, int scancode, int action, int mods) 283{ 284 if (action == GLFW_PRESS || action == GLFW_REPEAT) { 285 const char *keysym = glfwGetKeyName(key, scancode); 286 if (key == GLFW_KEY_ENTER) { 287 key_char_cb(window, '\n'); 288 return; 289 } 290 if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "z")) { 291 if (event_count) undo(); 292 } else if ((mods & GLFW_MOD_CONTROL) 293 && keysym && !strcmp(keysym, "l") 294 && last_event() == EVENT_START_NOTE) { 295 notes[note_count-1].align = LEFT; 296 } else if ((mods & GLFW_MOD_CONTROL) 297 && keysym && !strcmp(keysym, "r") 298 && last_event() == EVENT_START_NOTE) { 299 notes[note_count-1].align = RIGHT; 300 } else if ((mods & GLFW_MOD_CONTROL) 301 && keysym && !strcmp(keysym, "c") 302 && last_event() == EVENT_START_NOTE) { 303 notes[note_count-1].align = CENTER; 304 } else if (action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL) 305 && keysym && !strcmp(keysym, "r")) { 306 struct line *line = append(&lines, 307 &line_count, &line_cap, sizeof(struct line)); 308 line->count = line->cap = 5; 309 line->points = malloc(line->count * sizeof(struct point)); 310 for (size_t i = 0; i < line->count; i++) { 311 line->points[i].x = canvas_x + cursor_x; 312 line->points[i].y = canvas_y + cursor_y; 313 } 314 push_event(EVENT_START_RECT); 315 } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "c")) { 316 for (size_t i = 0; i < line_count; i++) 317 free(lines[i].points); 318 line_count = 0; 319 for (size_t i = 0; i < note_count; i++) 320 free(notes[i].text); 321 note_count = 0; 322 event_count = 0; 323 } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "q")) { 324 exit(0); 325 } else if (key == GLFW_KEY_BACKSPACE && last_event() == EVENT_START_NOTE) { 326 if (note_count) { 327 struct note *note = ¬es[note_count - 1]; 328 if (note->len > 1) { 329 note->len -= 1; 330 shrink(¬e->text, note->len, ¬e->cap, 1); 331 } else if (note->len == 1) { 332 pop_event(); 333 } 334 } 335 } 336 } 337} 338 339static void 340draw_texture(int texture, double x, double y) 341{ 342 glBindTexture(GL_TEXTURE_2D, texture); 343 glBegin(GL_QUADS); 344} 345 346static void 347draw_text(const char *text, enum align align, size_t len, GLint x, GLint y) 348{ 349 if (len == 0) return; 350 351 GLint sx = x; 352 size_t i = 0; 353 while (i < len) { 354 size_t k = i; 355 356 double width = glyph_offset[text[0]].x; 357 for (; k < len; k++) { 358 if (text[k] == '\n') break; 359 width += glyph_advance[text[k]].x; 360 } 361 362 if (k > i) { 363 width -= glyph_advance[text[k-1]].x; 364 width += glyph_size[text[k-1]].x; 365 366 x = sx; 367 if (align == CENTER) { 368 x -= width / 2; 369 } else if (align == RIGHT) { 370 x -= width; 371 } 372 373 glEnable(GL_TEXTURE_2D); 374 for (k = i; k < len; k++) { 375 if (text[k] == '\n') break; 376 GLint gx = x + glyph_offset[text[k]].x; 377 GLint gy = y - glyph_offset[text[k]].y; 378 GLint sx = glyph_size[text[k]].x; 379 GLint sy = glyph_size[text[k]].y; 380 glBindTexture(GL_TEXTURE_2D, glyphs[text[k]]); 381 glBegin(GL_QUADS); 382 glTexCoord2d(0, 1); glVertex2i(gx, gy + sy); 383 glTexCoord2d(1, 1); glVertex2i(gx + sx, gy + sy); 384 glTexCoord2d(1, 0); glVertex2i(gx + sx, gy); 385 glTexCoord2d(0, 0); glVertex2i(gx, gy); 386 glEnd(); 387 x += glyph_advance[text[k]].x; 388 } 389 glDisable(GL_TEXTURE_2D); 390 } 391 392 y += glyph_size['0'].y * 1.4; 393 i = k + 1; 394 } 395} 396 397int 398main(void) 399{ 400 glfwSetErrorCallback(glfw_error); 401 402 if (!glfwInit()) die("glfwInit"); 403 404 GLFWwindow *window = glfwCreateWindow(640, 480, "xnote", NULL, NULL); 405 if (!window) die("glfwCreateWindow"); 406 407 glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); 408 glfwSetWindowSizeCallback(window, window_resize_cb); 409 glfwSetCursorPosCallback(window, mouse_move_cb); 410 glfwSetMouseButtonCallback(window, mouse_press_cb); 411 glfwSetKeyCallback(window, key_press_cb); 412 glfwSetCharCallback(window, key_char_cb); 413 glfwMakeContextCurrent(window); 414 415 glClearColor(0, 0, 0, 0); 416 glColor3f(1, 1, 1); 417 glLineWidth(2); 418 glPointSize(2); 419 420 glfwGetCursorPos(window, &cursor_x, &cursor_y); 421 glfwGetWindowSize(window, &window_width, &window_height); 422 423 window_resize_cb(window, window_width, window_height); 424 425 push_event(EVENT_START); 426 427 const char *fontname = getenv("XNOTE_FONT") ?: "Arial"; 428 FcConfig *config = FcInitLoadConfigAndFonts(); 429 FcPattern *pattern = FcNameParse((const FcChar8 *)fontname); 430 FcConfigSubstitute(config, pattern, FcMatchPattern); 431 FcDefaultSubstitute(pattern); 432 FcResult result; 433 FcPattern *font = FcFontMatch(config, pattern, &result); 434 if (!font) die("Failed to load font: %s", fontname); 435 FcChar8 *filepath = NULL; 436 assert(FcPatternGetString(font, FC_FILE, 0, &filepath) == FcResultMatch); 437 int fontsize = 0; 438 assert(FcPatternGetInteger(font, FC_SIZE, 0, &fontsize) == FcResultMatch); 439 440 FT_Library freetype; 441 FT_Face face; 442 assert(!FT_Init_FreeType(&freetype)); 443 assert(!FT_New_Face(freetype, (const char *)filepath, 0, &face)); 444 assert(!FT_Set_Char_Size(face, 0, fontsize * 64, 300, 300)); 445 446 FcPatternDestroy(pattern); 447 FcConfigDestroy(config); 448 449 glGenTextures(128, glyphs); 450 for (size_t c = 0; c < 128; c++) { 451 FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_UInt32)c); 452 assert(!FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER)); 453 glyph_advance[c].x = face->glyph->advance.x >> 6; 454 glyph_advance[c].y = face->glyph->advance.y >> 6; 455 glyph_offset[c].x = face->glyph->bitmap_left; 456 glyph_offset[c].y = face->glyph->bitmap_top; 457 glyph_size[c].x = face->glyph->bitmap.width; 458 glyph_size[c].y = face->glyph->bitmap.rows; 459 glBindTexture(GL_TEXTURE_2D, glyphs[c]); 460 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 461 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 462 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 463 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 464 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); 465 glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); 466 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 467 face->glyph->bitmap.width, face->glyph->bitmap.rows, 468 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 469 face->glyph->bitmap.buffer); 470 } 471 472 while (!glfwWindowShouldClose(window)) { 473 glClear(GL_COLOR_BUFFER_BIT); 474 475 for (size_t i = 0; i < note_count; i++) { 476 struct note *note = ¬es[i]; 477 draw_text(note->text, note->align, note->len, 478 note->x - canvas_x, note->y - canvas_y); 479 } 480 481 for (size_t i = 0; i < line_count; i++) { 482 struct line *line = &lines[i]; 483 glBegin(GL_LINE_STRIP); 484 for (size_t k = 0; k < line->count; k++) 485 glVertex2f(line->points[k].x - canvas_x, 486 line->points[k].y - canvas_y); 487 glEnd(); 488 } 489 490 glBegin(GL_POINTS); 491 glVertex2f(cursor_x, cursor_y); 492 glEnd(); 493 494 glfwSwapBuffers(window); 495 496 glfwPollEvents(); 497 } 498 499 glfwTerminate(); 500 FT_Done_Face(face); 501 FT_Done_FreeType(freetype); 502}