#include #include #include #include #include FT_FREETYPE_H #include #include #include #include #include #include enum align { CENTER, LEFT, RIGHT }; struct point { double x, y; }; struct line { struct point *points; size_t count, cap; }; struct note { double x, y; char *text; enum align align; size_t len, cap; }; enum event { EVENT_START, EVENT_START_LINE, EVENT_START_RECT, EVENT_END_SHAPE, EVENT_START_NOTE, EVENT_MOUSE_MOVE, EVENT_MOUSE_DRAG, }; static struct line *lines = NULL; static size_t line_count = 0; static size_t line_cap = 0; static struct note *notes = NULL; static size_t note_count = 0; static size_t note_cap = 0; static enum event *events = NULL; static size_t event_count = 0; static size_t event_cap = 0; static double canvas_x = 0; static double canvas_y = 0; static double cursor_x = 0; static double cursor_y = 0; static double drag_canvas_x = 0; static double drag_canvas_y = 0; static double drag_cursor_x = 0; static double drag_cursor_y = 0; static int window_width = 0; static int window_height = 0; static GLuint glyphs[128]; static FT_Vector glyph_advance[128]; static FT_Vector glyph_offset[128]; static FT_Vector glyph_size[128]; static void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fputs("xnote: ", stderr); vfprintf(stderr, fmt, ap); if (*fmt && fmt[strlen(fmt)] == ':') perror(NULL); else putc('\n', stderr); va_end(ap); exit(1); } static void * append(void *_array, size_t *count, size_t *cap, size_t size) { uint8_t **array = _array; if (*count == *cap) { if (*cap == 0) *cap = 1; *cap *= 2; *array = realloc(*array, *cap * size); if (!*array) die("realloc:"); } return (void *) (*array + (*count)++ * size); } static void shrink(void *_array, size_t count, size_t *cap, size_t size) { uint8_t **array = _array; if (count == 0) { *array = NULL; *cap = 0; } else { *cap = count; *array = realloc(*array, *cap * size); if (!*array) die("realloc:"); } } static void glfw_error(int rc, const char *error) { die("glfw error (%i): %s", rc, error); } static void push_event(enum event event) { enum event *slot = append(&events, &event_count, &event_cap, sizeof(enum event)); *slot = event; } static enum event pop_event(void) { event_count -= 1; enum event event = events[event_count]; switch (event) { case EVENT_START_LINE: case EVENT_START_RECT: line_count -= 1; free(lines[line_count].points); break; case EVENT_START_NOTE: note_count -= 1; free(notes[note_count].text); break; case EVENT_START: event_count += 1; break; default: break; } return event; } static void undo(void) { while (1) { switch (pop_event()) { case EVENT_START_LINE: case EVENT_START_NOTE: case EVENT_START_RECT: case EVENT_START: break; default: continue; } break; } shrink(&events, event_count, &event_cap, sizeof(enum event)); } static enum event last_event(void) { return events[event_count - 1]; } static void window_resize_cb(GLFWwindow *window, int width, int height) { window_width = width; window_height = height; glViewport(0, 0, width, height); glClear(GL_COLOR_BUFFER_BIT); glLoadIdentity(); glOrtho(0, width, height, 0, -1, 1); } static void mouse_move_cb(GLFWwindow *window, double x, double y) { cursor_x = x; cursor_y = y; if (last_event() == EVENT_START_LINE) { struct line *line = &lines[line_count-1]; if (line->count > 0) { struct point *last_point = &line->points[line->count-1]; if (cursor_x == last_point->x && cursor_y == last_point->y) return; } struct point *point = append(&line->points, &line->count, &line->cap, sizeof(struct point)); point->x = canvas_x + cursor_x; point->y = canvas_y + cursor_y; } else if (last_event() == EVENT_START_RECT) { struct line *line = &lines[line_count-1]; line->points[1].x = canvas_x + cursor_x; line->points[2].x = canvas_x + cursor_x; line->points[2].y = canvas_y + cursor_y; line->points[3].y = canvas_y + cursor_y; } else if (last_event() == EVENT_MOUSE_DRAG) { canvas_x = drag_canvas_x - (cursor_x - drag_cursor_x); canvas_y = drag_canvas_y - (cursor_y - drag_cursor_y); } else if (last_event() != EVENT_MOUSE_MOVE) { push_event(EVENT_MOUSE_MOVE); } } static void mouse_press_cb(GLFWwindow *window, int button, int action, int mods) { if (button == GLFW_MOUSE_BUTTON_LEFT) { if (action == GLFW_PRESS) { struct line *line = append(&lines, &line_count, &line_cap, sizeof(struct line)); line->count = line->cap = 0; line->points = NULL; struct point *point = append(&line->points, &line->count, &line->cap, sizeof(struct point)); point->x = canvas_x + cursor_x; point->y = canvas_y + cursor_y; push_event(EVENT_START_LINE); } else if (action == GLFW_RELEASE) { if (last_event() == EVENT_START_LINE || last_event() == EVENT_START_RECT) { struct line *line = &lines[line_count-1]; shrink(&line->points, line->count, &line->cap, sizeof(struct point)); push_event(EVENT_END_SHAPE); } } } else if (button == GLFW_MOUSE_BUTTON_RIGHT || button == GLFW_MOUSE_BUTTON_MIDDLE) { if (action == GLFW_PRESS) { drag_canvas_x = canvas_x; drag_canvas_y = canvas_y; drag_cursor_x = cursor_x; drag_cursor_y = cursor_y; push_event(EVENT_MOUSE_DRAG); } else if (action == GLFW_RELEASE && last_event() == EVENT_MOUSE_DRAG) { pop_event(); } } } static void key_char_cb(GLFWwindow *window, unsigned int codepoint) { if (codepoint < 10 || codepoint >= 128) return; if (last_event() != EVENT_START_NOTE) { struct note *note = append(¬es, ¬e_count, ¬e_cap, sizeof(struct note)); note->text = NULL; note->align = CENTER; note->len = note->cap = 0; note->x = canvas_x + cursor_x; note->y = canvas_y + cursor_y; push_event(EVENT_START_NOTE); } struct note *note = ¬es[note_count - 1]; *(char *)append(¬e->text, ¬e->len, ¬e->cap, 1) = codepoint; } static void key_press_cb(GLFWwindow *window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { const char *keysym = glfwGetKeyName(key, scancode); if (key == GLFW_KEY_ENTER) { key_char_cb(window, '\n'); return; } if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "z")) { if (event_count) undo(); } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "l") && last_event() == EVENT_START_NOTE) { notes[note_count-1].align = LEFT; } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "r") && last_event() == EVENT_START_NOTE) { notes[note_count-1].align = RIGHT; } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "c") && last_event() == EVENT_START_NOTE) { notes[note_count-1].align = CENTER; } else if (action == GLFW_PRESS && (mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "r")) { struct line *line = append(&lines, &line_count, &line_cap, sizeof(struct line)); line->count = line->cap = 5; line->points = malloc(line->count * sizeof(struct point)); for (size_t i = 0; i < line->count; i++) { line->points[i].x = canvas_x + cursor_x; line->points[i].y = canvas_y + cursor_y; } push_event(EVENT_START_RECT); } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "c")) { for (size_t i = 0; i < line_count; i++) free(lines[i].points); line_count = 0; for (size_t i = 0; i < note_count; i++) free(notes[i].text); note_count = 0; event_count = 0; } else if ((mods & GLFW_MOD_CONTROL) && keysym && !strcmp(keysym, "q")) { exit(0); } else if (key == GLFW_KEY_BACKSPACE && last_event() == EVENT_START_NOTE) { if (note_count) { struct note *note = ¬es[note_count - 1]; if (note->len > 1) { note->len -= 1; shrink(¬e->text, note->len, ¬e->cap, 1); } else if (note->len == 1) { pop_event(); } } } } } static void draw_texture(int texture, double x, double y) { glBindTexture(GL_TEXTURE_2D, texture); glBegin(GL_QUADS); } static void draw_text(const char *text, enum align align, size_t len, GLint x, GLint y) { if (len == 0) return; GLint sx = x; size_t i = 0; while (i < len) { size_t k = i; double width = glyph_offset[text[0]].x; for (; k < len; k++) { if (text[k] == '\n') break; width += glyph_advance[text[k]].x; } if (k > i) { width -= glyph_advance[text[k-1]].x; width += glyph_size[text[k-1]].x; x = sx; if (align == CENTER) { x -= width / 2; } else if (align == RIGHT) { x -= width; } glEnable(GL_TEXTURE_2D); for (k = i; k < len; k++) { if (text[k] == '\n') break; GLint gx = x + glyph_offset[text[k]].x; GLint gy = y - glyph_offset[text[k]].y; GLint sx = glyph_size[text[k]].x; GLint sy = glyph_size[text[k]].y; glBindTexture(GL_TEXTURE_2D, glyphs[text[k]]); glBegin(GL_QUADS); glTexCoord2d(0, 1); glVertex2i(gx, gy + sy); glTexCoord2d(1, 1); glVertex2i(gx + sx, gy + sy); glTexCoord2d(1, 0); glVertex2i(gx + sx, gy); glTexCoord2d(0, 0); glVertex2i(gx, gy); glEnd(); x += glyph_advance[text[k]].x; } glDisable(GL_TEXTURE_2D); } y += glyph_size['0'].y * 1.4; i = k + 1; } } int main(void) { glfwSetErrorCallback(glfw_error); if (!glfwInit()) die("glfwInit"); GLFWwindow *window = glfwCreateWindow(640, 480, "xnote", NULL, NULL); if (!window) die("glfwCreateWindow"); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); glfwSetWindowSizeCallback(window, window_resize_cb); glfwSetCursorPosCallback(window, mouse_move_cb); glfwSetMouseButtonCallback(window, mouse_press_cb); glfwSetKeyCallback(window, key_press_cb); glfwSetCharCallback(window, key_char_cb); glfwMakeContextCurrent(window); glClearColor(0, 0, 0, 0); glColor3f(1, 1, 1); glLineWidth(2); glPointSize(2); glfwGetCursorPos(window, &cursor_x, &cursor_y); glfwGetWindowSize(window, &window_width, &window_height); window_resize_cb(window, window_width, window_height); push_event(EVENT_START); const char *fontname = getenv("XNOTE_FONT") ?: "Arial"; FcConfig *config = FcInitLoadConfigAndFonts(); FcPattern *pattern = FcNameParse((const FcChar8 *)fontname); FcConfigSubstitute(config, pattern, FcMatchPattern); FcDefaultSubstitute(pattern); FcResult result; FcPattern *font = FcFontMatch(config, pattern, &result); if (!font) die("Failed to load font: %s", fontname); FcChar8 *filepath = NULL; assert(FcPatternGetString(font, FC_FILE, 0, &filepath) == FcResultMatch); int fontsize = 0; assert(FcPatternGetInteger(font, FC_SIZE, 0, &fontsize) == FcResultMatch); FT_Library freetype; FT_Face face; assert(!FT_Init_FreeType(&freetype)); assert(!FT_New_Face(freetype, (const char *)filepath, 0, &face)); assert(!FT_Set_Char_Size(face, 0, fontsize * 64, 300, 300)); FcPatternDestroy(pattern); FcConfigDestroy(config); glGenTextures(128, glyphs); for (size_t c = 0; c < 128; c++) { FT_UInt glyph_index = FT_Get_Char_Index(face, (FT_UInt32)c); assert(!FT_Load_Glyph(face, glyph_index, FT_LOAD_RENDER)); glyph_advance[c].x = face->glyph->advance.x >> 6; glyph_advance[c].y = face->glyph->advance.y >> 6; glyph_offset[c].x = face->glyph->bitmap_left; glyph_offset[c].y = face->glyph->bitmap_top; glyph_size[c].x = face->glyph->bitmap.width; glyph_size[c].y = face->glyph->bitmap.rows; glBindTexture(GL_TEXTURE_2D, glyphs[c]); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, face->glyph->bitmap.width, face->glyph->bitmap.rows, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer); } while (!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT); for (size_t i = 0; i < note_count; i++) { struct note *note = ¬es[i]; draw_text(note->text, note->align, note->len, note->x - canvas_x, note->y - canvas_y); } for (size_t i = 0; i < line_count; i++) { struct line *line = &lines[i]; glBegin(GL_LINE_STRIP); for (size_t k = 0; k < line->count; k++) glVertex2f(line->points[k].x - canvas_x, line->points[k].y - canvas_y); glEnd(); } glBegin(GL_POINTS); glVertex2f(cursor_x, cursor_y); glEnd(); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); FT_Done_Face(face); FT_Done_FreeType(freetype); }