// Copyright (c) 2023-2026 Chris (boreddevnl) // This software is released under the GNU General Public License v3.0. See LICENSE file for details. // This header needs to maintain in any file it is present in, as per the GPL license terms. #include "libc/syscall.h" #include "libc/libui.h" #include "libc/stdlib.h" #include #define COLOR_DARK_PANEL 0xFF202020 #define COLOR_DARK_BG 0xFF121212 #define COLOR_DARK_TEXT 0xFFE0E0E0 #define COLOR_DARK_TITLEBAR 0xFF303030 #define COLOR_BLACK 0xFF000000 #define COLOR_BLUE 0xFF4A90E2 #define COLOR_WHITE 0xFFFFFFFF #define COLOR_TOOLBAR_BTN 0xFF404040 #define COLOR_TOOLBAR_BTN_ACTIVE 0xFF707070 #define MAX_FONTS 16 static char font_names[MAX_FONTS][64]; static int font_count = 0; static int current_font_idx = 0; static float current_font_size = 15.0f; static uint32_t current_text_color = COLOR_BLACK; static int active_dialog = 0; static char dialog_input[256]; static int dialog_input_len = 0; static int active_dropdown = 0; static uint32_t const palette[] = { COLOR_BLACK, 0xFFE74C3C, 0xFF3498DB, 0xFF2ECC71, 0xFF95A5A6 }; static _Bool is_bold = 0; static _Bool is_italic = 0; static _Bool is_underline = 0; static int align_mode = 0; static float line_spacing = 1.0f; static _Bool is_dragging = 0; static _Bool selection_started = 0; static int sel_start_para = -1, sel_start_run = -1, sel_start_pos = -1; static int sel_end_para = -1, sel_end_run = -1, sel_end_pos = -1; static int current_page_size = 0; // 0=A4, 1=A3, 2=A2 static const int page_widths[] = { 595, 842, 1191 }; static const int page_heights[] = { 842, 1191, 1684 }; #define MAX_PARAGRAPHS 256 #define MAX_RUNS_PER_PARAGRAPH 64 #define MAX_RUN_TEXT 128 typedef struct { char text[MAX_RUN_TEXT]; int len; _Bool bold; _Bool italic; _Bool underline; int font_idx; float font_size; uint32_t color; } TextRun; typedef struct { TextRun runs[MAX_RUNS_PER_PARAGRAPH]; int run_count; int align; float spacing; } Paragraph; #define MAX_UNDO_STATES 10 typedef struct { Paragraph paragraphs[MAX_PARAGRAPHS]; int para_count; int cursor_para; int cursor_run; int cursor_pos; } UndoState; static UndoState undo_stack[MAX_UNDO_STATES]; static int undo_head = 0; static int undo_tail = 0; static Paragraph paragraphs[MAX_PARAGRAPHS]; static int para_count = 1; static int cursor_para = 0; static int cursor_run = 0; static int cursor_pos = 0; static char open_filename[256] = ""; static _Bool file_modified = 0; static int scroll_y = 0; static _Bool is_dragging_scrollbar = 0; static int scrollbar_drag_offset_y = 0; static _Bool is_in_selection(int p, int r, int c); static int win_w = 800; static int win_h = 600; static size_t string_len(const char *str) { size_t l = 0; while(str[l]) l++; return l; } static void string_copy(char *dest, const char *src) { while(*src) *dest++ = *src++; *dest = 0; } static void load_fonts(void) { FAT32_FileInfo entries[MAX_FONTS]; int count = sys_list("/Library/Fonts", entries, MAX_FONTS); font_count = 0; for(int i = 0; i < count; i++) { if (!entries[i].is_directory) { int len = string_len(entries[i].name); if (len > 4 && entries[i].name[len-4] == '.' && entries[i].name[len-3] == 't' && entries[i].name[len-2] == 't' && entries[i].name[len-1] == 'f') { string_copy(font_names[font_count], entries[i].name); font_count++; if (font_count >= MAX_FONTS) break; } } } if (font_count == 0) { string_copy(font_names[0], "firamono.ttf"); font_count = 1; } } static int last_set_font_idx = -1; static void set_active_font(ui_window_t win, int idx) { if (idx < 0 || idx >= font_count) return; if (idx != last_set_font_idx) { char full_path[128]; string_copy(full_path, "/Library/Fonts/"); char *d = full_path + string_len(full_path); string_copy(d, font_names[idx]); ui_set_font(win, full_path); last_set_font_idx = idx; } } static void ensure_cursor_visible(ui_window_t win); static void save_undo_state(void) { UndoState *s = &undo_stack[undo_head]; s->para_count = para_count; s->cursor_para = cursor_para; s->cursor_run = cursor_run; s->cursor_pos = cursor_pos; for(int i=0; iparagraphs[i] = paragraphs[i]; } undo_head = (undo_head + 1) % MAX_UNDO_STATES; if (undo_head == undo_tail) { undo_tail = (undo_tail + 1) % MAX_UNDO_STATES; } } static void perform_undo(void) { if (undo_head == undo_tail) return; undo_head = (undo_head - 1 + MAX_UNDO_STATES) % MAX_UNDO_STATES; UndoState *s = &undo_stack[undo_head]; para_count = s->para_count; cursor_para = s->cursor_para; cursor_run = s->cursor_run; cursor_pos = s->cursor_pos; for(int i=0; iparagraphs[i]; } } static void init_doc(void) { para_count = 1; cursor_para = 0; cursor_run = 0; cursor_pos = 0; scroll_y = 0; for (int i=0; ilen = 0; r->text[0] = 0; r->bold = is_bold; r->italic = is_italic; r->underline = is_underline; r->font_idx = current_font_idx; r->font_size = current_font_size; r->color = current_text_color; } static void update_formatting_state(void) { if (cursor_para != -1 && cursor_run != -1) { if (cursor_para < para_count && cursor_run < paragraphs[cursor_para].run_count) { TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; is_bold = r->bold; is_italic = r->italic; is_underline = r->underline; current_font_idx = r->font_idx; current_font_size = r->font_size; current_text_color = r->color; align_mode = paragraphs[cursor_para].align; } } } static void handle_arrows(ui_window_t win, char c) { if (c == 17) { if (cursor_para > 0) { cursor_para--; cursor_run = paragraphs[cursor_para].run_count - 1; if (cursor_run < 0) cursor_run = 0; cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; } } else if (c == 18) { if (cursor_para < para_count - 1) { cursor_para++; cursor_run = 0; cursor_pos = 0; } } else if (c == 19) { if (cursor_pos > 0) { cursor_pos--; } else if (cursor_run > 0) { cursor_run--; cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; } else if (cursor_para > 0) { cursor_para--; cursor_run = paragraphs[cursor_para].run_count - 1; if (cursor_run < 0) cursor_run = 0; cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; } } else if (c == 20) { if (cursor_pos < paragraphs[cursor_para].runs[cursor_run].len) { cursor_pos++; } else if (cursor_run < paragraphs[cursor_para].run_count - 1) { cursor_run++; cursor_pos = 0; } else if (cursor_para < para_count - 1) { cursor_para++; cursor_run = 0; cursor_pos = 0; } } update_formatting_state(); ensure_cursor_visible(win); } static void split_run(int p_idx, int r_idx, int pos) { Paragraph *p = ¶graphs[p_idx]; if (pos <= 0 || pos >= p->runs[r_idx].len) return; if (p->run_count >= MAX_RUNS_PER_PARAGRAPH) return; for (int i = p->run_count; i > r_idx + 1; i--) { p->runs[i] = p->runs[i-1]; } p->run_count++; TextRun *r1 = &p->runs[r_idx]; TextRun *r2 = &p->runs[r_idx+1]; r2->bold = r1->bold; r2->italic = r1->italic; r2->underline = r1->underline; r2->font_idx = r1->font_idx; r2->font_size = r1->font_size; r2->color = r1->color; r2->len = r1->len - pos; for(int i=0; ilen; i++) r2->text[i] = r1->text[pos + i]; r2->text[r2->len] = 0; r1->len = pos; r1->text[pos] = 0; } static void delete_selection(void) { if (sel_start_para == -1 || sel_end_para == -1) return; int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; } if (s_p == e_p && s_r == e_r && s_c == e_c) { sel_start_para = -1; sel_end_para = -1; return; } save_undo_state(); split_run(e_p, e_r, e_c); split_run(s_p, s_r, s_c); if (s_c > 0) s_r++; if (s_p == e_p && e_r >= s_r) e_r++; for (int p = s_p; p <= e_p; p++) { Paragraph *para = ¶graphs[p]; int start_r = (p == s_p) ? s_r : 0; int end_r = (p == e_p) ? e_r - 1 : para->run_count - 1; for (int r = start_r; r <= end_r; r++) { if (r >= para->run_count) break; para->runs[r].len = 0; } } cursor_para = s_p; cursor_run = s_r; cursor_pos = 0; sel_start_para = -1; sel_end_para = -1; file_modified = 1; } static void insert_char(ui_window_t win, char c) { _Bool has_selection = 0; if (sel_start_para != -1 && sel_end_para != -1) { if (!(sel_start_para == sel_end_para && sel_start_run == sel_end_run && sel_start_pos == sel_end_pos)) { has_selection = 1; } } if (has_selection) { delete_selection(); if (c == '\b') return; } else { if (sel_start_para != -1) { sel_start_para = -1; sel_end_para = -1; } } if (c < 32 && c != '\n' && c != '\b') { if (c >= 17 && c <= 20) { handle_arrows(win, c); } return; } if (c == '\b') { save_undo_state(); if (cursor_pos > 0) { TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; for(int i=cursor_pos-1; ilen; i++) r->text[i] = r->text[i+1]; r->len--; cursor_pos--; if (r->len == 0 && paragraphs[cursor_para].run_count > 1) { for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; } paragraphs[cursor_para].run_count--; if (cursor_run >= paragraphs[cursor_para].run_count) { cursor_run = paragraphs[cursor_para].run_count - 1; } cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; } } else if (cursor_run > 0) { if (paragraphs[cursor_para].runs[cursor_run].len == 0 && paragraphs[cursor_para].run_count > 1) { for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; } paragraphs[cursor_para].run_count--; } cursor_run--; if (cursor_run < 0) cursor_run = 0; TextRun *r = ¶graphs[cursor_para].runs[cursor_run]; if (r->len > 0) { r->len--; cursor_pos = r->len; if (r->len == 0 && paragraphs[cursor_para].run_count > 1) { for(int i = cursor_run; i < paragraphs[cursor_para].run_count - 1; i++) { paragraphs[cursor_para].runs[i] = paragraphs[cursor_para].runs[i+1]; } paragraphs[cursor_para].run_count--; if (cursor_run >= paragraphs[cursor_para].run_count) cursor_run = paragraphs[cursor_para].run_count - 1; if (cursor_run < 0) cursor_run = 0; cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; } } else { cursor_pos = 0; } } else if (cursor_para > 0) { Paragraph *prev = ¶graphs[cursor_para - 1]; Paragraph *curr = ¶graphs[cursor_para]; cursor_para--; cursor_run = prev->run_count - 1; if (cursor_run < 0) cursor_run = 0; TextRun *r = &prev->runs[cursor_run]; cursor_pos = r->len; for (int i = 0; i < curr->run_count; i++) { if (curr->runs[i].len == 0 && i == 0 && curr->run_count == 1) continue; if (prev->run_count < MAX_RUNS_PER_PARAGRAPH) { prev->runs[prev->run_count] = curr->runs[i]; prev->run_count++; } } for(int i=cursor_para+1; i= MAX_PARAGRAPHS) return; for(int i=para_count; i>cursor_para+1; i--) paragraphs[i] = paragraphs[i-1]; para_count++; Paragraph *next = ¶graphs[cursor_para+1]; next->align = align_mode; next->spacing = line_spacing; next->run_count = 1; TextRun *nr = &next->runs[0]; nr->len = 0; nr->text[0] = 0; nr->bold = is_bold; nr->italic = is_italic; nr->underline = is_underline; nr->font_idx = current_font_idx; nr->font_size = current_font_size; nr->color = current_text_color; cursor_para++; cursor_run = 0; cursor_pos = 0; file_modified = 1; ensure_cursor_visible(win); return; } Paragraph *p = ¶graphs[cursor_para]; if (p->run_count == 0) p->run_count = 1; TextRun *r = &p->runs[cursor_run]; if (r->bold != is_bold || r->italic != is_italic || r->underline != is_underline || r->font_idx != current_font_idx || r->font_size != current_font_size || r->color != current_text_color) { if (cursor_pos > 0 && cursor_pos < r->len) { split_run(cursor_para, cursor_run, cursor_pos); } if (cursor_pos == 0 && r->len > 0) { if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; p->run_count++; r = &p->runs[cursor_run]; r->len = 0; r->text[0] = 0; } } else if (cursor_pos == r->len && r->len > 0) { if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { cursor_run++; for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; p->run_count++; r = &p->runs[cursor_run]; r->len = 0; r->text[0] = 0; cursor_pos = 0; } } r->bold = is_bold; r->italic = is_italic; r->underline = is_underline; r->font_idx = current_font_idx; r->font_size = current_font_size; r->color = current_text_color; } if (c == ' ') save_undo_state(); if (r->len < MAX_RUN_TEXT - 1) { for(int i=r->len; i>cursor_pos; i--) r->text[i] = r->text[i-1]; r->text[cursor_pos] = c; r->len++; cursor_pos++; file_modified = 1; } ensure_cursor_visible(win); } static bool str_contains(const char *haystack, const char *needle) { if (!haystack || !needle) return false; int hlen = string_len(haystack); int nlen = string_len(needle); for(int i=0; i<=hlen-nlen; i++) { bool match = true; for(int j=0; j= 0 && current_page_size <= 2) { *pw = page_widths[current_page_size]; *ph = page_heights[current_page_size]; } else { *pw = 595; *ph = 842; } } static void export_pdf(void) { char name[256]; if (open_filename[0] == 0) string_copy(name, "document.pdf"); else string_copy(name, open_filename); int fd = sys_open(name, "w"); if (fd < 0) return; int offset = 0; int xref[1024]; // Increase xref size to handle more objects int obj_count = 1; #define WRITE_STR(s) do { sys_write_fs(fd, s, string_len(s)); offset += string_len(s); } while(0) WRITE_STR("%PDF-1.4\n"); int catalog_obj = obj_count++; xref[catalog_obj] = offset; WRITE_STR("1 0 obj\n<< /Type /Catalog /Pages 2 0 R >>\nendobj\n"); int pages_obj = obj_count++; int page_obj_ids[256]; int total_pdf_pages = 0; int resources_obj = obj_count++; xref[resources_obj] = offset; WRITE_STR("3 0 obj\n<< /ProcSet [/PDF /Text] /Font << "); for(int i=1; i<=12; i++) { WRITE_STR("/F"); char f_n[16]; itoa(i, f_n); WRITE_STR(f_n); WRITE_STR(" "); char fo_n[16]; itoa(3+i, fo_n); WRITE_STR(fo_n); WRITE_STR(" 0 R "); } WRITE_STR(">> >>\nendobj\n"); // Write 12 fonts const char *base_fonts[12] = { "Helvetica", "Helvetica-Bold", "Helvetica-Oblique", "Helvetica-BoldOblique", "Times-Roman", "Times-Bold", "Times-Italic", "Times-BoldItalic", "Courier", "Courier-Bold", "Courier-Oblique", "Courier-BoldOblique" }; for(int i=0; i<12; i++) { xref[obj_count++] = offset; // 4 to 15 char num[16]; itoa(obj_count - 1, num); WRITE_STR(num); WRITE_STR(" 0 obj\n<< /Type /Font /Subtype /Type1 /BaseFont /"); WRITE_STR(base_fonts[i]); WRITE_STR(" >>\nendobj\n"); } static char stream[65536]; stream[0] = 0; int slen = 0; #define S_WRITE(s) do { string_copy(stream + slen, s); slen += string_len(s); } while(0) int pw, ph; get_page_size(&pw, &ph); // Initial page page_obj_ids[total_pdf_pages++] = obj_count; xref[obj_count++] = offset; // Page obj int current_page_obj = obj_count - 1; int current_contents_obj = obj_count; // We will write the Page object: char num1[16]; itoa(current_page_obj, num1); WRITE_STR(num1); WRITE_STR(" 0 obj\n<< /Type /Page /Parent 2 0 R /MediaBox [0 0 "); char num2[16]; itoa(pw, num2); WRITE_STR(num2); WRITE_STR(" "); char num3[16]; itoa(ph, num3); WRITE_STR(num3); WRITE_STR("] /Contents "); char num4[16]; itoa(current_contents_obj, num4); WRITE_STR(num4); WRITE_STR(" 0 R /Resources 3 0 R >>\nendobj\n"); // Starting Contents xref[obj_count++] = offset; // Contents obj current_contents_obj = obj_count - 1; char cont_buf[64]; itoa(current_contents_obj, cont_buf); WRITE_STR(cont_buf); WRITE_STR(" 0 obj\n"); // We will leave length empty and calculate exactly later int length_placeholder_offset = offset; WRITE_STR("<< /Length 00000000 >>\nstream\n"); // 8 chars for length S_WRITE("BT\n"); float cur_y = (float)ph - 42.0f; // 42 is top margin float bottom_margin = 40.0f; for(int p=0; prun_count; r++) { TextRun *run_m = ¶->runs[r]; if (run_m->len > 0) { const char *ttf_m = font_names[run_m->font_idx]; float char_w = (str_contains(ttf_m, "mono") || str_contains(ttf_m, "fira")) ? (run_m->font_size * 0.6f) : (run_m->font_size * 0.45f); tw += (int)(char_w * run_m->len); if (run_m->bold) tw += run_m->len; } } int px = 20; if (para->align == 1) px = (pw - tw) / 2; else if (para->align == 2) px = pw - 20 - tw; float max_lh = 15.0f; for(int r=0; rrun_count; r++) { if (para->runs[r].font_size > max_lh) max_lh = para->runs[r].font_size; } if (cur_y - max_lh < bottom_margin) { S_WRITE("ET\n"); sys_write_fs(fd, stream, slen); offset += slen; sys_write_fs(fd, "\nendstream\nendobj\n", 18); offset += 18; // Patch length int current_offset = sys_seek(fd, 0, 1); sys_seek(fd, length_placeholder_offset + 12, 0); // "12" is the offset to '00000000' part of "/Length 00000000" char num_len[16]; itoa(slen, num_len); int pad = 8 - (int)string_len(num_len); for(int k=0; k>\nendobj\n"); xref[obj_count++] = offset; // New Contents obj current_contents_obj = obj_count - 1; char n_cont_buf[64]; itoa(current_contents_obj, n_cont_buf); WRITE_STR(n_cont_buf); WRITE_STR(" 0 obj\n"); length_placeholder_offset = offset; WRITE_STR("<< /Length 00000000 >>\nstream\n"); S_WRITE("BT\n"); } char tm_buf[64]; int tlen = 0; string_copy(tm_buf, "1 0 0 1 "); tlen += 8; char num[16]; itoa(px, num); string_copy(tm_buf + tlen, num); tlen += string_len(num); tm_buf[tlen++] = ' '; itoa((int)cur_y, num); string_copy(tm_buf + tlen, num); tlen += string_len(num); string_copy(tm_buf + tlen, " Tm\n"); tlen += 4; tm_buf[tlen] = 0; S_WRITE(tm_buf); char align_cmt[32]; string_copy(align_cmt, "%ALIGN_0\n"); align_cmt[7] = '0' + para->align; S_WRITE(align_cmt); for(int r=0; rrun_count; r++) { TextRun *run = ¶->runs[r]; if (run->len > 0) { int base_idx = 0; const char *ttf = font_names[run->font_idx]; if (str_contains(ttf, "mono") || str_contains(ttf, "console") || str_contains(ttf, "fira")) base_idx = 8; else if (str_contains(ttf, "serif") || str_contains(ttf, "times") || str_contains(ttf, "georgia")) base_idx = 4; int style = 0; if (run->bold && run->italic) style = 3; else if (run->italic) style = 2; else if (run->bold) style = 1; int fkey = base_idx + style + 1; char fbuf[128]; int flen = 0; string_copy(fbuf, "/F"); flen = 2; char keynum[16]; itoa(fkey, keynum); string_copy(fbuf + flen, keynum); flen += string_len(keynum); fbuf[flen++] = ' '; char sizenum[16]; itoa((int)run->font_size, sizenum); string_copy(fbuf + flen, sizenum); flen += string_len(sizenum); string_copy(fbuf + flen, " Tf\n"); flen += 4; int rr = (run->color >> 16) & 0xFF; int gg = (run->color >> 8) & 0xFF; int bb = run->color & 0xFF; append_pdf_float(fbuf, &flen, rr); append_pdf_float(fbuf, &flen, gg); append_pdf_float(fbuf, &flen, bb); fbuf[flen++] = 'r'; fbuf[flen++] = 'g'; fbuf[flen++] = '\n'; string_copy(fbuf + flen, "%FMT_"); flen += 5; fbuf[flen++] = run->bold ? '1' : '0'; fbuf[flen++] = run->italic ? '1' : '0'; fbuf[flen++] = run->underline ? '1' : '0'; uint32_t c = run->color; for(int i=7; i>=0; i--) { int nibble = c & 0xF; fbuf[flen+i] = (nibble < 10) ? ('0' + nibble) : ('A' + (nibble - 10)); c >>= 4; } flen += 8; for(int i=0; i<(int)string_len(sizenum); i++) fbuf[flen++] = sizenum[i]; fbuf[flen++] = '\n'; fbuf[flen] = 0; S_WRITE(fbuf); S_WRITE("("); for(int i=0; ilen; i++) { if (run->text[i] == '(' || run->text[i] == ')' || run->text[i] == '\\') { stream[slen++] = '\\'; } stream[slen++] = run->text[i]; } stream[slen] = 0; S_WRITE(") Tj\n"); } } cur_y -= (max_lh + 5.0f); } S_WRITE("ET\n"); // Finalize the last pending page stream sys_write_fs(fd, stream, slen); offset += slen; sys_write_fs(fd, "\nendstream\nendobj\n", 18); offset += 18; // Patch length of last page int current_offset = sys_seek(fd, 0, 1); sys_seek(fd, length_placeholder_offset + 12, 0); char num_len[16]; itoa(slen, num_len); int pad = 8 - (int)string_len(num_len); for(int k=0; k>\nendobj\n"); int final_xref_offset = offset; WRITE_STR("xref\n0 "); char num_total_obj[16]; itoa(total_objects, num_total_obj); WRITE_STR(num_total_obj); WRITE_STR("\n0000000000 65535 f \n"); for(int i=1; i>\nstartxref\n"); char xnum[16]; itoa(final_xref_offset, xnum); WRITE_STR(xnum); WRITE_STR("\n%%EOF\n"); sys_close(fd); file_modified = 0; } static void load_file(ui_window_t win, const char* path) { int fd = sys_open(path, "r"); if (fd < 0) return; int size = sys_size(fd); if(size <= 0) { sys_close(fd); return; } if (size > 65535) size = 65535; static char buf[65536]; sys_read(fd, buf, size); sys_close(fd); buf[size] = 0; init_doc(); string_copy(open_filename, path); cursor_para = 0; cursor_run = 0; cursor_pos = 0; para_count = 1; paragraphs[0].run_count = 0; if (buf[0] == '%' && buf[1] == 'P' && buf[2] == 'D' && buf[3] == 'F') { int i=0; is_bold = 0; is_italic = 0; is_underline = 0; current_font_size = 15.0f; current_text_color = COLOR_BLACK; current_font_idx = 0; while(i < size) { if(buf[i] == '%' && i+7 < size && buf[i+1] == 'A' && buf[i+2] == 'L' && buf[i+3] == 'I' && buf[i+4] == 'G' && buf[i+5] == 'N' && buf[i+6] == '_') { align_mode = buf[i+7] - '0'; paragraphs[cursor_para].align = align_mode; i += 8; while(i < size && buf[i] != '\n') i++; } else if (buf[i] == '/' && i+1 < size && buf[i+1] == 'F') { int start = i; i += 2; int fkey = 0; while(i < size && buf[i] >= '0' && buf[i] <= '9') { fkey = fkey * 10 + (buf[i++] - '0'); } while(i < size && buf[i] == ' ') i++; int fsize = 0; while(i < size && buf[i] >= '0' && buf[i] <= '9') { fsize = fsize * 10 + (buf[i++] - '0'); } while(i < size && buf[i] == ' ') i++; if (i + 1 < size && buf[i] == 'T' && buf[i+1] == 'f') { if (fsize >= 8) current_font_size = (float)fsize; if (fkey >= 1 && fkey <= 12) { int base = (fkey - 1) / 4; int style = (fkey - 1) % 4; is_bold = (style == 1 || style == 3); is_italic = (style == 2 || style == 3); const char *target = (base == 2) ? "mono" : (base == 1 ? "serif" : "sans"); for(int f=0; f= '0' && hex <= '9') val = hex - '0'; else if (hex >= 'A' && hex <= 'F') val = hex - 'A' + 10; else if (hex >= 'a' && hex <= 'f') val = hex - 'a' + 10; c = (c << 4) | val; } current_text_color = c; int fsize = 0; while(i < size && buf[i] != '\n' && buf[i] >= '0' && buf[i] <= '9') { fsize = fsize * 10 + (buf[i++] - '0'); } if (fsize >= 8) current_font_size = (float)fsize; } while(i < size && buf[i] != '\n') i++; } else if(buf[i] == '(') { i++; while(i < size && buf[i] != ')') { if (buf[i] == '\\' && i+1 < size) i++; insert_char(win, buf[i]); i++; } } else if ((buf[i] == 'T' && buf[i+1] == 'd') || (buf[i] == 'T' && buf[i+1] == 'm')) { insert_char(win, '\n'); i += 2; } else { i++; } } } else { for(int i=0; i= 3 && icon_type <= 6) { for(int i=0; i<4; i++) { int lw = (i%2 == 0) ? 12 : 8; if (icon_type == 6) lw = 12; int lx = x + 6; if (icon_type == 4) lx = x + 6 + (12-lw)/2; if (icon_type == 5) lx = x + 6 + (12-lw); ui_draw_rect(win, lx, y+6 + i*3, lw, 2, COLOR_WHITE); } } else if (icon_type == 7) { ui_draw_rect(win, x+6, y+11, 12, 2, COLOR_WHITE); } else if (icon_type == 8) { ui_draw_rect(win, x+6, y+11, 12, 2, COLOR_WHITE); ui_draw_rect(win, x+11, y+6, 2, 12, COLOR_WHITE); } else if (icon_type == 9) { ui_draw_rect(win, x+12, y+12, 6, 2, COLOR_WHITE); ui_draw_rect(win, x+18, y+14, 2, 4, COLOR_WHITE); ui_draw_rect(win, x+12, y+18, 6, 2, COLOR_WHITE); ui_draw_rect(win, x+10, y+10, 2, 6, COLOR_WHITE); ui_draw_rect(win, x+8, y+12, 2, 2, COLOR_WHITE); } else if (icon_type == 10) { int cx = x + w/2; int cy = y + h/2; ui_draw_rect(win, cx-6, cy-6, 12, 12, COLOR_WHITE); ui_draw_rect(win, cx-5, cy-5, 10, 10, COLOR_DARK_BG); ui_draw_rect(win, cx-4, cy-6, 8, 4, COLOR_WHITE); ui_draw_rect(win, cx-2, cy+2, 4, 4, COLOR_WHITE); } } static void draw_toolbar(ui_window_t win) { ui_draw_rect(win, 0, 0, win_w, 40, COLOR_DARK_PANEL); draw_btn_icon(win, 10, 8, 24, 24, 0, is_bold); draw_btn_icon(win, 40, 8, 24, 24, 1, is_italic); draw_btn_icon(win, 70, 8, 24, 24, 2, is_underline); ui_draw_rect(win, 100, 8, 2, 24, COLOR_DARK_BG); draw_btn_icon(win, 110, 8, 24, 24, 3, align_mode == 0); draw_btn_icon(win, 140, 8, 24, 24, 4, align_mode == 1); draw_btn_icon(win, 170, 8, 24, 24, 5, align_mode == 2); draw_btn_icon(win, 200, 8, 24, 24, 6, align_mode == 3); ui_draw_rect(win, 230, 8, 2, 24, COLOR_DARK_BG); ui_draw_rounded_rect_filled(win, 240, 8, 120, 24, 4, COLOR_TOOLBAR_BTN); ui_draw_string(win, 245, 12, font_names[current_font_idx], COLOR_WHITE); ui_draw_rect(win, 240+105, 18, 4, 2, COLOR_WHITE); ui_draw_rounded_rect_filled(win, 365, 8, 24, 24, 4, current_text_color); ui_draw_rect(win, 365, 8, 24, 24, COLOR_WHITE); ui_draw_rounded_rect_filled(win, 366, 9, 22, 22, 3, current_text_color); draw_btn_icon(win, 395, 8, 24, 24, 7, 0); char size_str[16]; int isize = (int)current_font_size; int didx = 0; if(isize >= 10) { size_str[didx++] = (isize/10) + '0'; } size_str[didx++] = (isize%10) + '0'; size_str[didx] = 0; ui_draw_string(win, 425, 12, size_str, COLOR_WHITE); draw_btn_icon(win, 445, 8, 24, 24, 8, 0); draw_btn_icon(win, 485, 8, 30, 24, 9, 0); ui_draw_rect(win, 520, 8, 2, 24, COLOR_DARK_BG); ui_draw_rounded_rect_filled(win, 530, 8, 50, 24, 4, COLOR_TOOLBAR_BTN); const char *ps_str = (current_page_size == 0) ? "A4" : (current_page_size == 1 ? "A3" : "A2"); ui_draw_string(win, 545, 12, ps_str, COLOR_WHITE); ui_draw_rect(win, 530+35, 18, 4, 2, COLOR_WHITE); draw_btn_icon(win, 590, 8, 40, 24, 10, file_modified); } static void draw_dropdowns(ui_window_t win) { if (active_dropdown == 1) { ui_draw_rect(win, 240, 32, 120, font_count * 20, COLOR_DARK_PANEL); ui_draw_rect(win, 240, 32, 120, font_count * 20, COLOR_DARK_BG); for(int i=0; i 1.0f) scale = 1.0f; // Don't scale up if window is huge int page_w = (int)(pw * scale); int page_h = (int)(ph * scale); int doc_x = 20 + (doc_view_w - page_w) / 2; int doc_y = 50 - scroll_y; ui_draw_rect(win, 0, 40, win_w, win_h - 40, COLOR_DARK_BG); int cur_y = doc_y + 10; int current_page = 0; // Draw first page background int bg_y = doc_y + current_page * (page_h + 20); int draw_h = page_h; if (bg_y < 40) { draw_h -= (40 - bg_y); bg_y = 40; } if (draw_h > 0 && bg_y < win_h) { ui_draw_rect(win, doc_x, bg_y, page_w, draw_h, COLOR_WHITE); } cur_y = doc_y + current_page * (page_h + 20) + 10; for(int p=0; prun_count) { int line_w = 0; int max_h = 16; int end_run = start_run; int end_char = start_char; int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0; while(r_idx < para->run_count) { TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx); int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh; while(c_idx < run->len) { char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; } if (line_w + cw > page_w - 20) { break; } line_w += cw; c_idx++; } if (c_idx < run->len) break; r_idx++; c_idx = 0; } if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) { if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; } else { end_run = r_idx; end_char = c_idx; } } else { end_run = para->run_count; end_char = 0; } int line_h = (int)(max_h * para->spacing) + 4; if (cur_y + line_h > doc_y + current_page * (page_h + 20) + page_h - 10) { current_page++; int next_bg_y = doc_y + current_page * (page_h + 20); int next_draw_h = page_h; if (next_bg_y < 40) { next_draw_h -= (40 - next_bg_y); next_bg_y = 40; } if (next_draw_h > 0 && next_bg_y < win_h) { ui_draw_rect(win, doc_x, next_bg_y, page_w, next_draw_h, COLOR_WHITE); } cur_y = doc_y + current_page * (page_h + 20) + 10; } int cur_x = doc_x + 10; if (para->align == 1) { cur_x = doc_x + 10 + (page_w - 20 - line_w) / 2; } else if (para->align == 2) { cur_x = doc_x + 10 + (page_w - 20 - line_w); } int d_run = start_run; int d_char = start_char; int line_cursor_x = -1; while(d_run < end_run || (d_run == end_run && d_char < end_char)) { TextRun *run = ¶->runs[d_run]; int chars_to_draw; if (d_run == end_run) chars_to_draw = end_char - d_char; else chars_to_draw = run->len - d_char; if (p == cursor_para && d_run == cursor_run && cursor_pos >= d_char && cursor_pos <= d_char + chars_to_draw) { char sub[128]; for(int i=0; itext[d_char + i]; sub[cursor_pos - d_char] = 0; line_cursor_x = cur_x + ui_get_string_width_scaled(sub, run->font_size); } if (chars_to_draw > 0) { set_active_font(win, run->font_idx); int run_h = ui_get_font_height_scaled(run->font_size); int y_offset = 0; if (max_h > run_h) y_offset = max_h - run_h; int text_y = cur_y + y_offset; if (text_y + run_h > 40 && text_y < win_h) { for(int i=0; itext[d_char + i], 0}; _Bool in_sel = is_in_selection(p, d_run, d_char + i); uint32_t text_col = in_sel ? COLOR_WHITE : run->color; int cw = ui_get_string_width_scaled(buf, run->font_size); if (text_y + run_h > 40) { if (in_sel) { ui_draw_rect(win, cur_x, text_y + 4, cw, run_h, COLOR_BLUE); } if (run->italic) { ui_draw_string_scaled_sloped(win, cur_x, text_y, buf, text_col, run->font_size, 0.2f); if (run->bold) ui_draw_string_scaled_sloped(win, cur_x+1, text_y, buf, text_col, run->font_size, 0.2f); } else { ui_draw_string_scaled(win, cur_x, text_y, buf, text_col, run->font_size); if (run->bold) ui_draw_string_scaled(win, cur_x+1, text_y, buf, text_col, run->font_size); } if (run->underline) { ui_draw_rect(win, cur_x, cur_y + max_h - 2, cw, 1, text_col); } } cur_x += cw; } } else { char buf[128]; for(int i=0; itext[d_char + i]; buf[chars_to_draw] = 0; cur_x += ui_get_string_width_scaled(buf, run->font_size); } } d_char = 0; d_run++; } if (p == cursor_para && (d_run == cursor_run || (end_run == cursor_run && end_char == cursor_pos))) { if (line_cursor_x == -1) line_cursor_x = cur_x; } if (line_cursor_x != -1 && cur_y > 40 && cur_y < win_h) { if (cursor_para < para_count && cursor_run < paragraphs[cursor_para].run_count) { set_active_font(win, paragraphs[cursor_para].runs[cursor_run].font_idx); int fh = ui_get_font_height_scaled(paragraphs[cursor_para].runs[cursor_run].font_size); int c_offset = 0; if (max_h > fh) c_offset = max_h - fh; ui_draw_rect(win, line_cursor_x, cur_y + c_offset, 2, fh, COLOR_BLACK); } else { ui_draw_rect(win, line_cursor_x, cur_y, 2, max_h, COLOR_BLACK); } } cur_y += (int)(max_h * para->spacing) + 4; start_run = end_run; start_char = end_char; if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } } } } set_active_font(win, 0); int content_h = current_page * (page_h + 20) + page_h + 20; if (content_h > win_h - 40) { int sb_x = win_w - 12; int sb_w = 12; int sb_h = win_h - 40; float ratio = (float)(win_h - 40) / (float)content_h; int thumb_h = (int)(sb_h * ratio); if (thumb_h < 20) thumb_h = 20; int max_scroll = content_h - (win_h - 40); if (scroll_y > max_scroll) scroll_y = max_scroll; int thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h)); ui_draw_rect(win, sb_x, 40, sb_w, sb_h, 0xFF303030); ui_draw_rounded_rect_filled(win, sb_x+2, thumb_y+2, sb_w-4, thumb_h-4, 4, 0xFF606060); } } static void ensure_cursor_visible(ui_window_t win) { int pw, ph; get_page_size(&pw, &ph); int doc_view_w = win_w - 40; float scale = (float)doc_view_w / (float)pw; if (scale > 1.0f) scale = 1.0f; int page_w = (int)(pw * scale); int page_h = (int)(ph * scale); int cur_y = 10; int current_page = 0; int target_y = -1; for(int p=0; prun_count) { int max_h = 16; int r_idx = start_run; int c_idx = start_char; int end_run = start_run; int end_char = start_char; int line_w = 0; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0; while(r_idx < para->run_count) { TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx); int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh; while(c_idx < run->len) { char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; } if (line_w + cw > page_w - 20) break; line_w += cw; c_idx++; } if (c_idx < run->len) break; r_idx++; c_idx = 0; } if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) { if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; } else { end_run = r_idx; end_char = c_idx; } } else { end_run = para->run_count; end_char = 0; } int line_h = (int)(max_h * para->spacing) + 4; if (cur_y + line_h > current_page * (page_h + 20) + page_h - 10) { current_page++; cur_y = current_page * (page_h + 20) + 10; } if (p == cursor_para) { if (cursor_run >= start_run && cursor_run <= end_run) { _Bool is_in = 0; if (cursor_run == start_run && cursor_run == end_run) { if (cursor_pos >= start_char && cursor_pos <= end_char) is_in = 1; } else if (cursor_run == start_run) { if (cursor_pos >= start_char) is_in = 1; } else if (cursor_run == end_run) { if (cursor_pos <= end_char) is_in = 1; } else { is_in = 1; } if (is_in) target_y = cur_y; } } cur_y += line_h; start_run = end_run; start_char = end_char; } } if (target_y != -1) { if (target_y - scroll_y < 50) scroll_y = target_y - 50; else if (target_y - scroll_y > win_h - 120) scroll_y = target_y - (win_h - 120); if (scroll_y < 0) scroll_y = 0; } } static void update_selection(int p, int r, int char_pos) { sel_end_para = p; sel_end_run = r; sel_end_pos = char_pos; } static void apply_style_to_selection(void) { if (sel_start_para == -1 || sel_end_para == -1) { Paragraph *p = ¶graphs[cursor_para]; if (p->run_count == 0) p->run_count = 1; TextRun *r = &p->runs[cursor_run]; if (r->len == 0) { r->bold = is_bold; r->italic = is_italic; r->underline = is_underline; r->font_idx = current_font_idx; r->font_size = current_font_size; r->color = current_text_color; } else { if (r->bold == is_bold && r->italic == is_italic && r->underline == is_underline && r->font_idx == current_font_idx && r->font_size == current_font_size && r->color == current_text_color) { return; } if (p->run_count < MAX_RUNS_PER_PARAGRAPH) { if (cursor_pos > 0 && cursor_pos < r->len) { split_run(cursor_para, cursor_run, cursor_pos); cursor_run++; } if (cursor_pos > 0) { cursor_run++; } for(int i = p->run_count; i > cursor_run; i--) p->runs[i] = p->runs[i-1]; p->run_count++; TextRun *nr = &p->runs[cursor_run]; nr->len = 0; nr->text[0] = 0; nr->bold = is_bold; nr->italic = is_italic; nr->underline = is_underline; nr->font_idx = current_font_idx; nr->font_size = current_font_size; nr->color = current_text_color; cursor_pos = 0; } } return; } int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; } if (s_p == e_p && s_r == e_r && s_c == e_c) return; save_undo_state(); split_run(e_p, e_r, e_c); split_run(s_p, s_r, s_c); if (s_c > 0) s_r++; if (s_p == e_p && e_r >= s_r) e_r++; sel_start_para = s_p; sel_start_run = s_r; sel_start_pos = 0; sel_end_para = e_p; sel_end_run = e_r - 1; if (sel_end_run >= 0 && sel_end_run < paragraphs[sel_end_para].run_count) { sel_end_pos = paragraphs[sel_end_para].runs[sel_end_run].len; } else { sel_end_pos = 0; } for (int p = s_p; p <= e_p; p++) { Paragraph *para = ¶graphs[p]; int start_r = (p == s_p) ? s_r : 0; int end_r = (p == e_p) ? e_r - 1 : para->run_count - 1; for (int r = start_r; r <= end_r; r++) { if (r >= para->run_count) break; TextRun *run = ¶->runs[r]; run->bold = is_bold; run->italic = is_italic; run->underline = is_underline; run->font_idx = current_font_idx; run->font_size = current_font_size; run->color = current_text_color; } } file_modified = 1; } static void apply_align_to_selection(int mode) { align_mode = mode; active_dropdown = 0; if (sel_start_para != -1 && sel_end_para != -1) { int s = sel_start_para < sel_end_para ? sel_start_para : sel_end_para; int e = sel_start_para > sel_end_para ? sel_start_para : sel_end_para; for (int p = s; p <= e; p++) { paragraphs[p].align = mode; } } else if (cursor_para != -1) { paragraphs[cursor_para].align = mode; } file_modified = 1; } static void handle_click(ui_window_t win, int x, int y) { if (active_dialog == 1) { int dw = 300; int dh = 150; int dx = (win_w - dw)/2; int dy = (win_h - dh)/2; if (y >= dy+100 && y <= dy+130) { if (x >= dx+10 && x <= dx+110) { active_dialog = 0; } else if (x >= dx+dw-110 && x <= dx+dw-10) { string_copy(open_filename, dialog_input); export_pdf(); active_dialog = 0; } } return; } if (active_dropdown == 1) { if (x >= 240 && x < 360 && y >= 32 && y < 32 + font_count*20) { current_font_idx = (y - 32) / 20; apply_style_to_selection(); } active_dropdown = 0; return; } if (active_dropdown == 2) { int p_count = sizeof(palette)/sizeof(uint32_t); if (x >= 365 && x < 405 && y >= 32 && y < 32 + p_count*20) { current_text_color = palette[(y - 32) / 20]; apply_style_to_selection(); } active_dropdown = 0; return; } if (active_dropdown == 3) { if (x >= 530 && x < 580 && y >= 32 && y < 32 + 3*20) { current_page_size = (y - 32) / 20; printf("Selected page size: %d\n", current_page_size); } active_dropdown = 0; return; } if (y < 40) { if (x >= 10 && x < 34) { is_bold = !is_bold; active_dropdown = 0; apply_style_to_selection(); } else if (x >= 40 && x < 64) { is_italic = !is_italic; active_dropdown = 0; apply_style_to_selection(); } else if (x >= 70 && x < 94) { is_underline = !is_underline; active_dropdown = 0; apply_style_to_selection(); } else if (x >= 110 && x < 134) { apply_align_to_selection(0); } else if (x >= 140 && x < 164) { apply_align_to_selection(1); } else if (x >= 170 && x < 194) { apply_align_to_selection(2); } else if (x >= 200 && x < 224) { apply_align_to_selection(3); } else if (x >= 240 && x < 360) { active_dropdown = 1; } else if (x >= 365 && x < 389) { active_dropdown = 2; } else if (x >= 395 && x < 419) { if (current_font_size > 8.0f) current_font_size -= 1.0f; active_dropdown = 0; apply_style_to_selection(); } else if (x >= 445 && x < 469) { if (current_font_size < 72.0f) current_font_size += 1.0f; active_dropdown = 0; apply_style_to_selection(); } else if (x >= 485 && x < 515) { perform_undo(); active_dropdown = 0; } else if (x >= 530 && x < 580) { active_dropdown = 3; } else if (x >= 590 && x < 630) { active_dialog = 1; string_copy(dialog_input, open_filename[0] ? open_filename : "document.pdf"); dialog_input_len = string_len(dialog_input); active_dropdown = 0; } } else { int pw, ph; get_page_size(&pw, &ph); int doc_view_w = win_w - 40; float scale = (float)doc_view_w / (float)pw; if (scale > 1.0f) scale = 1.0f; int page_w = (int)(pw * scale); int page_h = (int)(ph * scale); int content_h = 0; int dummy_y = 10; int dummy_page = 0; for(int p=0; prun_count) { int max_h = 16; int end_run = start_run; int end_char = start_char; int line_w = 0; int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0; while(r_idx < para->run_count) { TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx); int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh; while(c_idx < run->len) { char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; } if (line_w + cw > page_w - 20) break; line_w += cw; c_idx++; } if (c_idx < run->len) break; r_idx++; c_idx = 0; } if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) { if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; } else { end_run = r_idx; end_char = c_idx; } } else { end_run = para->run_count; end_char = 0; } int line_h = (int)(max_h * para->spacing) + 4; if (dummy_y + line_h > dummy_page * (page_h + 20) + page_h - 10) { dummy_page++; dummy_y = dummy_page * (page_h + 20) + 10; } dummy_y += line_h; start_run = end_run; start_char = end_char; if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } } } } content_h = dummy_page * (page_h + 20) + page_h + 20; int sb_x = win_w - 12; int sb_w = 12; int sb_h = win_h - 40; int thumb_y = 40; int thumb_h = 0; int max_scroll = 0; if (content_h > win_h - 40) { float ratio = (float)(win_h - 40) / (float)content_h; thumb_h = (int)(sb_h * ratio); if (thumb_h < 20) thumb_h = 20; max_scroll = content_h - (win_h - 40); if (scroll_y > max_scroll) scroll_y = max_scroll; thumb_y = 40 + (int)(((float)scroll_y / max_scroll) * (sb_h - thumb_h)); } if (content_h > win_h - 40 && x >= sb_x && x < sb_x + sb_w) { if (y >= thumb_y && y < thumb_y + thumb_h) { is_dragging_scrollbar = 1; scrollbar_drag_offset_y = y - thumb_y; } else { if (y < thumb_y) scroll_y -= (win_h - 40); else scroll_y += (win_h - 40); if (scroll_y < 0) scroll_y = 0; if (scroll_y > max_scroll) scroll_y = max_scroll; } return; } int doc_x = 20 + (doc_view_w - page_w) / 2; int doc_y = 50 - scroll_y; int target_y = y; int target_x = x; int cur_y = doc_y + 10; int current_page = 0; for(int p=0; prun_count) { int line_w = 0; int max_h = 16; int end_run = start_run; int end_char = start_char; int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0; while(r_idx < para->run_count) { TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx); int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh; while(c_idx < run->len) { char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; } if (line_w + cw > page_w - 20) break; line_w += cw; c_idx++; } if (c_idx < run->len) break; r_idx++; c_idx = 0; } if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) { if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; } else { end_run = r_idx; end_char = c_idx; } } else { end_run = para->run_count; end_char = 0; } int line_h = (int)(max_h * para->spacing) + 4; if (cur_y + line_h > doc_y + current_page * (page_h + 20) + page_h - 10) { current_page++; cur_y = doc_y + current_page * (page_h + 20) + 10; } if (target_y >= cur_y && target_y < cur_y + line_h) { int cur_x = doc_x + 10; if (para->align == 1) cur_x = doc_x + 10 + (page_w - 20 - line_w) / 2; else if (para->align == 2) cur_x = doc_x + 10 + (page_w - 20 - line_w); int d_run = start_run; int d_char = start_char; while(d_run < end_run || (d_run == end_run && d_char < end_char)) { TextRun *run = ¶->runs[d_run]; set_active_font(win, run->font_idx); int chars_to_draw = (d_run == end_run) ? (end_char - d_char) : (run->len - d_char); for(int i=0; itext[d_char + i], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (target_x >= cur_x && target_x < cur_x + cw/2) { cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i; if (selection_started) { sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i; sel_end_para = -1; selection_started = 0; } else if (is_dragging) { update_selection(p, d_run, d_char + i); } return; } else if (target_x >= cur_x + cw/2 && target_x < cur_x + cw) { cursor_para = p; cursor_run = d_run; cursor_pos = d_char + i + 1; if (selection_started) { sel_start_para = p; sel_start_run = d_run; sel_start_pos = d_char + i + 1; sel_end_para = -1; selection_started = 0; } else if (is_dragging) { update_selection(p, d_run, d_char + i + 1); } return; } cur_x += cw; } d_char = 0; d_run++; } if (target_x >= cur_x) { cursor_para = p; if (end_run < para->run_count) { cursor_run = end_run; cursor_pos = end_char; } else { cursor_run = end_run - 1; if(cursor_run<0) cursor_run=0; cursor_pos = para->runs[cursor_run].len; } if (selection_started) { sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; } else if (is_dragging) { update_selection(cursor_para, cursor_run, cursor_pos); } } else if (target_x < cur_x - line_w) { cursor_para = p; cursor_run = start_run; cursor_pos = start_char; if (selection_started) { sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; } else if (is_dragging) { update_selection(cursor_para, cursor_run, cursor_pos); } } return; } cur_y += line_h; start_run = end_run; start_char = end_char; if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } } } } cursor_para = para_count - 1; cursor_run = paragraphs[cursor_para].run_count - 1; if (cursor_run < 0) cursor_run = 0; cursor_pos = paragraphs[cursor_para].runs[cursor_run].len; if (selection_started) { sel_start_para = cursor_para; sel_start_run = cursor_run; sel_start_pos = cursor_pos; sel_end_para = -1; selection_started = 0; } else if (is_dragging) { update_selection(cursor_para, cursor_run, cursor_pos); } } } static _Bool is_in_selection(int p, int r, int c) { if (sel_start_para == -1 || sel_end_para == -1) return 0; int s_p = sel_start_para, s_r = sel_start_run, s_c = sel_start_pos; int e_p = sel_end_para, e_r = sel_end_run, e_c = sel_end_pos; if (e_p < s_p || (e_p == s_p && e_r < s_r) || (e_p == s_p && e_r == s_r && e_c < s_c)) { s_p = sel_end_para; s_r = sel_end_run; s_c = sel_end_pos; e_p = sel_start_para; e_r = sel_start_run; e_c = sel_start_pos; } if (p < s_p || p > e_p) return 0; if (p == s_p && p == e_p) { if (r < s_r || r > e_r) return 0; if (r == s_r && r == e_r) return c >= s_c && c < e_c; if (r == s_r) return c >= s_c; if (r == e_r) return c < e_c; return 1; } if (p == s_p) { if (r < s_r) return 0; if (r == s_r) return c >= s_c; return 1; } if (p == e_p) { if (r > e_r) return 0; if (r == e_r) return c < e_c; return 1; } return 1; } int main(int argc, char **argv) { (void)argc; (void)argv; ui_window_t win = ui_window_create("BoredWord", 100, 100, win_w, win_h); if (!win) return 1; ui_window_set_resizable(win, 1); load_fonts(); set_active_font(win, 0); init_doc(); if (argc > 1) { load_file(win, argv[1]); } gui_event_t ev; _Bool needs_repaint = 1; while(1) { while (ui_get_event(win, &ev)) { if (ev.type == GUI_EVENT_PAINT) { needs_repaint = 1; } else if (ev.type == GUI_EVENT_RESIZE) { win_w = ev.arg1; win_h = ev.arg2; needs_repaint = 1; } else if (ev.type == GUI_EVENT_MOUSE_WHEEL) { scroll_y -= ev.arg2 * 30; // arg2 is scroll amount if (scroll_y < 0) scroll_y = 0; needs_repaint = 1; } else if (ev.type == GUI_EVENT_MOUSE_DOWN) { if (ev.arg1 >= 0 && ev.arg1 < win_w && ev.arg2 >= 0 && ev.arg2 < win_h) { if (ev.arg2 < 40 || active_dialog == 1 || active_dropdown != 0) { handle_click(win, ev.arg1, ev.arg2); } else { is_dragging = 1; selection_started = 1; sel_start_para = -1; sel_end_para = -1; handle_click(win, ev.arg1, ev.arg2); selection_started = 0; update_formatting_state(); } } needs_repaint = 1; needs_repaint = 1; } else if (ev.type == GUI_EVENT_MOUSE_UP) { is_dragging = 0; is_dragging_scrollbar = 0; needs_repaint = 1; } else if (ev.type == GUI_EVENT_MOUSE_MOVE) { if (is_dragging_scrollbar) { int pw, ph; get_page_size(&pw, &ph); int doc_view_w = win_w - 40; float scale = (float)doc_view_w / (float)pw; if (scale > 1.0f) scale = 1.0f; int page_w = (int)(pw * scale); int page_h = (int)(ph * scale); int content_h = 0; int dummy_y = 10; int dummy_page = 0; for(int p=0; prun_count) { int max_h = 16; int end_run = start_run; int end_char = start_char; int line_w = 0; int r_idx = start_run; int c_idx = start_char; int last_space_run = -1; int last_space_char = -1; int last_space_w = 0; while(r_idx < para->run_count) { TextRun *run = ¶->runs[r_idx]; set_active_font(win, run->font_idx); int fh = ui_get_font_height_scaled(run->font_size); if (fh > max_h) max_h = fh; while(c_idx < run->len) { char buf[2] = {run->text[c_idx], 0}; int cw = ui_get_string_width_scaled(buf, run->font_size); if (run->text[c_idx] == ' ') { last_space_run = r_idx; last_space_char = c_idx; last_space_w = line_w + cw; } if (line_w + cw > page_w - 20) break; line_w += cw; c_idx++; } if (c_idx < run->len) break; r_idx++; c_idx = 0; } if (r_idx < para->run_count || (r_idx == para->run_count - 1 && c_idx < para->runs[r_idx].len)) { if (last_space_run != -1 && (last_space_run > start_run || last_space_char > start_char)) { end_run = last_space_run; end_char = last_space_char; line_w = last_space_w; } else { end_run = r_idx; end_char = c_idx; } } else { end_run = para->run_count; end_char = 0; } int line_h = (int)(max_h * para->spacing) + 4; if (dummy_y + line_h > dummy_page * (page_h + 20) + page_h - 10) { dummy_page++; dummy_y = dummy_page * (page_h + 20) + 10; } dummy_y += line_h; start_run = end_run; start_char = end_char; if (start_run < para->run_count && para->runs[start_run].text[start_char] == ' ') { start_char++; if (start_char >= para->runs[start_run].len) { start_char = 0; start_run++; } } } } content_h = dummy_page * (page_h + 20) + page_h + 20; if (content_h > win_h - 40) { int sb_h = win_h - 40; float ratio = (float)(win_h - 40) / (float)content_h; int thumb_h = (int)(sb_h * ratio); if (thumb_h < 20) thumb_h = 20; int max_scroll = content_h - (win_h - 40); int new_thumb_y = ev.arg2 - scrollbar_drag_offset_y; if (new_thumb_y < 40) new_thumb_y = 40; if (new_thumb_y > 40 + sb_h - thumb_h) new_thumb_y = 40 + sb_h - thumb_h; scroll_y = (int)(((float)(new_thumb_y - 40) / (sb_h - thumb_h)) * max_scroll); needs_repaint = 1; } } else if (is_dragging && ev.arg2 >= 40 && active_dialog == 0 && active_dropdown == 0) { handle_click(win, ev.arg1, ev.arg2); needs_repaint = 1; } } else if (ev.type == GUI_EVENT_CLICK) { needs_repaint = 1; } else if (ev.type == GUI_EVENT_KEY) { if (active_dialog == 1) { char c = (char)ev.arg1; if (c == '\b' && dialog_input_len > 0) { dialog_input[--dialog_input_len] = 0; } else if (c >= 32 && c < 127 && dialog_input_len < 127) { dialog_input[dialog_input_len++] = c; dialog_input[dialog_input_len] = 0; } } else { insert_char(win, (char)ev.arg1); } needs_repaint = 1; } else if (ev.type == GUI_EVENT_CLOSE) { sys_exit(0); } } if (needs_repaint) { draw_document(win); draw_toolbar(win); draw_dropdowns(win); draw_dialogs(win); ui_mark_dirty(win, 0, 0, win_w, win_h); needs_repaint = 0; } else { sys_yield(); } } return 0; }