feat: dividers in word processor, login screen improvements
This commit is contained in:
@@ -41,6 +41,41 @@
|
|||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
|
|
||||||
|
inline int decode_single_byte_codepoint(int codepoint) {
|
||||||
|
if (codepoint < 0 || codepoint > 255) return codepoint;
|
||||||
|
|
||||||
|
switch (codepoint) {
|
||||||
|
case 0x80: return 0x20AC; // Euro Sign
|
||||||
|
case 0x82: return 0x201A; // Single Low-9 Quotation Mark
|
||||||
|
case 0x83: return 0x0192; // Latin Small Letter F With Hook
|
||||||
|
case 0x84: return 0x201E; // Double Low-9 Quotation Mark
|
||||||
|
case 0x85: return 0x2026; // Horizontal Ellipsis
|
||||||
|
case 0x86: return 0x2020; // Dagger
|
||||||
|
case 0x87: return 0x2021; // Double Dagger
|
||||||
|
case 0x88: return 0x02C6; // Modifier Letter Circumflex Accent
|
||||||
|
case 0x89: return 0x2030; // Per Mille Sign
|
||||||
|
case 0x8A: return 0x0160; // Latin Capital Letter S With Caron
|
||||||
|
case 0x8B: return 0x2039; // Single Left-Pointing Angle Quotation Mark
|
||||||
|
case 0x8C: return 0x0152; // Latin Capital Ligature OE
|
||||||
|
case 0x8E: return 0x017D; // Latin Capital Letter Z With Caron
|
||||||
|
case 0x91: return 0x2018; // Left Single Quotation Mark
|
||||||
|
case 0x92: return 0x2019; // Right Single Quotation Mark
|
||||||
|
case 0x93: return 0x201C; // Left Double Quotation Mark
|
||||||
|
case 0x94: return 0x201D; // Right Double Quotation Mark
|
||||||
|
case 0x95: return 0x2022; // Bullet
|
||||||
|
case 0x96: return 0x2013; // En Dash
|
||||||
|
case 0x97: return 0x2014; // Em Dash
|
||||||
|
case 0x98: return 0x02DC; // Small Tilde
|
||||||
|
case 0x99: return 0x2122; // Trade Mark Sign
|
||||||
|
case 0x9A: return 0x0161; // Latin Small Letter S With Caron
|
||||||
|
case 0x9B: return 0x203A; // Single Right-Pointing Angle Quotation Mark
|
||||||
|
case 0x9C: return 0x0153; // Latin Small Ligature OE
|
||||||
|
case 0x9E: return 0x017E; // Latin Small Letter Z With Caron
|
||||||
|
case 0x9F: return 0x0178; // Latin Capital Letter Y With Diaeresis
|
||||||
|
default: return codepoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CachedGlyph {
|
struct CachedGlyph {
|
||||||
uint8_t* bitmap;
|
uint8_t* bitmap;
|
||||||
int width, height;
|
int width, height;
|
||||||
@@ -50,7 +85,7 @@ struct CachedGlyph {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct GlyphCache {
|
struct GlyphCache {
|
||||||
CachedGlyph glyphs[256]; // Latin-1 Supplement (U+0080..U+00FF) included
|
CachedGlyph glyphs[256]; // Single-byte text with Windows-1252 punctuation mapping
|
||||||
int pixel_size;
|
int pixel_size;
|
||||||
float scale;
|
float scale;
|
||||||
int ascent, descent, line_gap;
|
int ascent, descent, line_gap;
|
||||||
@@ -155,12 +190,13 @@ struct TrueTypeFont {
|
|||||||
if (g->loaded) return g;
|
if (g->loaded) return g;
|
||||||
|
|
||||||
g->loaded = true;
|
g->loaded = true;
|
||||||
|
int font_codepoint = decode_single_byte_codepoint(codepoint);
|
||||||
int advance, lsb;
|
int advance, lsb;
|
||||||
stbtt_GetCodepointHMetrics(&info, codepoint, &advance, &lsb);
|
stbtt_GetCodepointHMetrics(&info, font_codepoint, &advance, &lsb);
|
||||||
g->advance = (int)(advance * gc->scale);
|
g->advance = (int)(advance * gc->scale);
|
||||||
|
|
||||||
int x0, y0, x1, y1;
|
int x0, y0, x1, y1;
|
||||||
stbtt_GetCodepointBitmapBox(&info, codepoint, gc->scale, gc->scale,
|
stbtt_GetCodepointBitmapBox(&info, font_codepoint, gc->scale, gc->scale,
|
||||||
&x0, &y0, &x1, &y1);
|
&x0, &y0, &x1, &y1);
|
||||||
g->width = x1 - x0;
|
g->width = x1 - x0;
|
||||||
g->height = y1 - y0;
|
g->height = y1 - y0;
|
||||||
@@ -172,7 +208,7 @@ struct TrueTypeFont {
|
|||||||
if (!g->bitmap)
|
if (!g->bitmap)
|
||||||
return g;
|
return g;
|
||||||
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
|
stbtt_MakeCodepointBitmap(&info, g->bitmap, g->width, g->height,
|
||||||
g->width, gc->scale, gc->scale, codepoint);
|
g->width, gc->scale, gc->scale, font_codepoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
return g;
|
return g;
|
||||||
|
|||||||
@@ -10,6 +10,23 @@ using namespace gui;
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
void draw_login_background(LoginState* ls) {
|
||||||
|
Framebuffer& fb = ls->fb;
|
||||||
|
if (!ls->has_wallpaper) {
|
||||||
|
fb.clear(BG_COLOR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t* dst = fb.buffer();
|
||||||
|
int pitch = fb.pitch();
|
||||||
|
int row_bytes = ls->screen_w * (int)sizeof(uint32_t);
|
||||||
|
for (int y = 0; y < ls->screen_h; y++) {
|
||||||
|
uint32_t* dst_row = (uint32_t*)((uint8_t*)dst + y * pitch);
|
||||||
|
const uint32_t* src_row = ls->bg_wallpaper + y * ls->bg_wallpaper_w;
|
||||||
|
montauk::memcpy(dst_row, src_row, row_bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rect offset_rect(Rect rect, int dx, int dy) {
|
Rect offset_rect(Rect rect, int dx, int dy) {
|
||||||
rect.x += dx;
|
rect.x += dx;
|
||||||
rect.y += dy;
|
rect.y += dy;
|
||||||
@@ -295,13 +312,7 @@ void draw_login_screen(LoginState* ls) {
|
|||||||
LoginLayout lo = layout_login_screen(ls);
|
LoginLayout lo = layout_login_screen(ls);
|
||||||
int sfh = system_font_height();
|
int sfh = system_font_height();
|
||||||
|
|
||||||
if (ls->has_wallpaper) {
|
draw_login_background(ls);
|
||||||
fb.blit(0, 0, ls->bg_wallpaper_w, ls->bg_wallpaper_h, ls->bg_wallpaper);
|
|
||||||
fb.fill_rect_alpha(0, 0, ls->screen_w, ls->screen_h,
|
|
||||||
Color::from_rgba(0, 0, 0, 0x38));
|
|
||||||
} else {
|
|
||||||
fb.clear(BG_COLOR);
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_shadow(fb, lo.card.x, lo.card.y, lo.card.w, lo.card.h, 4, colors::SHADOW);
|
draw_shadow(fb, lo.card.x, lo.card.y, lo.card.w, lo.card.h, 4, colors::SHADOW);
|
||||||
fb.fill_rect(lo.card.x, lo.card.y, lo.card.w, lo.card.h, CARD_BG);
|
fb.fill_rect(lo.card.x, lo.card.y, lo.card.w, lo.card.h, CARD_BG);
|
||||||
|
|||||||
@@ -6,6 +6,18 @@
|
|||||||
|
|
||||||
#include "login.hpp"
|
#include "login.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr uint32_t kLoginOverlayAlpha = 0x38;
|
||||||
|
constexpr uint32_t kLoginOverlayInvAlpha = 255 - kLoginOverlayAlpha;
|
||||||
|
|
||||||
|
static uint8_t dim_component(uint8_t value) {
|
||||||
|
uint32_t scaled = kLoginOverlayInvAlpha * value;
|
||||||
|
return (uint8_t)((scaled + 1 + (scaled >> 8)) >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool load_login_wallpaper(LoginState* ls) {
|
bool load_login_wallpaper(LoginState* ls) {
|
||||||
auto doc = montauk::config::load("desktop");
|
auto doc = montauk::config::load("desktop");
|
||||||
const char* wp = doc.get_string("wallpaper.path", "");
|
const char* wp = doc.get_string("wallpaper.path", "");
|
||||||
@@ -69,10 +81,13 @@ bool load_login_wallpaper(LoginState* ls) {
|
|||||||
if (sx < 0) sx = 0;
|
if (sx < 0) sx = 0;
|
||||||
if (sx >= img_w) sx = img_w - 1;
|
if (sx >= img_w) sx = img_w - 1;
|
||||||
int si = (sy * img_w + sx) * 3;
|
int si = (sy * img_w + sx) * 3;
|
||||||
|
uint8_t r = dim_component(rgb[si]);
|
||||||
|
uint8_t g = dim_component(rgb[si + 1]);
|
||||||
|
uint8_t b = dim_component(rgb[si + 2]);
|
||||||
scaled[y * dst_w + x] = 0xFF000000u
|
scaled[y * dst_w + x] = 0xFF000000u
|
||||||
| ((uint32_t)rgb[si] << 16)
|
| ((uint32_t)r << 16)
|
||||||
| ((uint32_t)rgb[si + 1] << 8)
|
| ((uint32_t)g << 8)
|
||||||
| (uint32_t)rgb[si + 2];
|
| (uint32_t)b;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ extern "C" void _start() {
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
bool mouse_changed = false;
|
bool mouse_changed = false;
|
||||||
bool key_changed = false;
|
bool key_changed = false;
|
||||||
|
bool fast_mouse_path = false;
|
||||||
|
|
||||||
int prev_mouse_x = ls->mouse.x;
|
int prev_mouse_x = ls->mouse.x;
|
||||||
int prev_mouse_y = ls->mouse.y;
|
int prev_mouse_y = ls->mouse.y;
|
||||||
@@ -80,6 +81,7 @@ extern "C" void _start() {
|
|||||||
|| ls->mouse.y != prev_mouse_y
|
|| ls->mouse.y != prev_mouse_y
|
||||||
|| ls->mouse.buttons != prev_mouse_buttons
|
|| ls->mouse.buttons != prev_mouse_buttons
|
||||||
|| ls->mouse.scrollDelta != 0;
|
|| ls->mouse.scrollDelta != 0;
|
||||||
|
fast_mouse_path = mouse_changed || ls->mouse.buttons != 0;
|
||||||
|
|
||||||
while (montauk::is_key_available()) {
|
while (montauk::is_key_available()) {
|
||||||
Montauk::KeyEvent key;
|
Montauk::KeyEvent key;
|
||||||
@@ -93,7 +95,7 @@ extern "C" void _start() {
|
|||||||
if (first_frame || mouse_changed || key_changed) {
|
if (first_frame || mouse_changed || key_changed) {
|
||||||
draw_login_screen(ls);
|
draw_login_screen(ls);
|
||||||
first_frame = false;
|
first_frame = false;
|
||||||
montauk::sleep_ms(4);
|
montauk::sleep_ms(fast_mouse_path ? 1 : 4);
|
||||||
} else {
|
} else {
|
||||||
montauk::sleep_ms(16);
|
montauk::sleep_ms(16);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ static inline int wp_min_int(int a, int b) {
|
|||||||
return a < b ? a : b;
|
return a < b ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool wp_valid_divider_type(uint8_t divider_type) {
|
||||||
|
return divider_type >= PARA_DIVIDER_SINGLE && divider_type <= PARA_DIVIDER_THICK_THIN;
|
||||||
|
}
|
||||||
|
|
||||||
static void wp_init_run(StyledRun* r, uint8_t font_id, uint8_t size, uint8_t flags) {
|
static void wp_init_run(StyledRun* r, uint8_t font_id, uint8_t size, uint8_t flags) {
|
||||||
r->cap = 64;
|
r->cap = 64;
|
||||||
r->text = (char*)montauk::malloc(r->cap);
|
r->text = (char*)montauk::malloc(r->cap);
|
||||||
@@ -44,18 +48,67 @@ void wp_init_paragraph_style(ParagraphStyle* para) {
|
|||||||
para->align = PARA_ALIGN_LEFT;
|
para->align = PARA_ALIGN_LEFT;
|
||||||
para->list_type = PARA_LIST_NONE;
|
para->list_type = PARA_LIST_NONE;
|
||||||
para->line_spacing = 100;
|
para->line_spacing = 100;
|
||||||
para->_pad = 0;
|
para->divider_type = PARA_DIVIDER_NONE;
|
||||||
para->left_indent = 0;
|
para->left_indent = 0;
|
||||||
para->first_line_indent = 0;
|
para->first_line_indent = 0;
|
||||||
para->space_before = 0;
|
para->space_before = 0;
|
||||||
para->space_after = 0;
|
para->space_after = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wp_font_at(WordProcessorState* wp, int abs_pos,
|
static ParagraphStyle wp_make_divider_style(const ParagraphStyle* src, uint8_t divider_type) {
|
||||||
|
ParagraphStyle style = *src;
|
||||||
|
style.align = PARA_ALIGN_LEFT;
|
||||||
|
style.list_type = PARA_LIST_NONE;
|
||||||
|
style.divider_type = divider_type;
|
||||||
|
style.left_indent = 0;
|
||||||
|
style.first_line_indent = 0;
|
||||||
|
style.space_before = 0;
|
||||||
|
style.space_after = 0;
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wp_paragraph_bounds(WordProcessorState* wp, int para_idx,
|
||||||
|
int* out_start, int* out_end, bool* out_has_newline) {
|
||||||
|
if (out_start) *out_start = 0;
|
||||||
|
if (out_end) *out_end = 0;
|
||||||
|
if (out_has_newline) *out_has_newline = false;
|
||||||
|
|
||||||
|
if (para_idx < 0) para_idx = 0;
|
||||||
|
if (para_idx >= wp->paragraph_count) para_idx = wp->paragraph_count - 1;
|
||||||
|
if (para_idx < 0) return;
|
||||||
|
|
||||||
|
int start = 0;
|
||||||
|
int cur_para = 0;
|
||||||
|
while (cur_para < para_idx && start < wp->total_text_len) {
|
||||||
|
while (start < wp->total_text_len && wp_char_at(wp, start) != '\n')
|
||||||
|
start++;
|
||||||
|
if (start < wp->total_text_len && wp_char_at(wp, start) == '\n')
|
||||||
|
start++;
|
||||||
|
cur_para++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int end = start;
|
||||||
|
bool has_newline = false;
|
||||||
|
while (end < wp->total_text_len) {
|
||||||
|
if (wp_char_at(wp, end) == '\n') {
|
||||||
|
has_newline = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
end++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out_start) *out_start = start;
|
||||||
|
if (out_end) *out_end = end;
|
||||||
|
if (out_has_newline) *out_has_newline = has_newline;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wp_font_at(WordProcessorState* wp, int abs_pos, int layout_dpi,
|
||||||
TrueTypeFont** out_font, GlyphCache** out_gc) {
|
TrueTypeFont** out_font, GlyphCache** out_gc) {
|
||||||
if (wp->run_count <= 0) {
|
if (wp->run_count <= 0) {
|
||||||
*out_font = wp_get_font(wp->cur_font_id, wp->cur_flags);
|
*out_font = wp_get_font(wp->cur_font_id, wp->cur_flags);
|
||||||
*out_gc = (*out_font && (*out_font)->valid) ? (*out_font)->get_cache(wp->cur_size) : nullptr;
|
*out_gc = (*out_font && (*out_font)->valid)
|
||||||
|
? (*out_font)->get_cache(wp_points_to_pixels(wp->cur_size, layout_dpi))
|
||||||
|
: nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +118,9 @@ static void wp_font_at(WordProcessorState* wp, int abs_pos,
|
|||||||
if (r < 0) r = 0;
|
if (r < 0) r = 0;
|
||||||
StyledRun* run = &wp->runs[r];
|
StyledRun* run = &wp->runs[r];
|
||||||
*out_font = wp_get_font(run->font_id, run->flags);
|
*out_font = wp_get_font(run->font_id, run->flags);
|
||||||
*out_gc = (*out_font && (*out_font)->valid) ? (*out_font)->get_cache(run->size) : nullptr;
|
*out_gc = (*out_font && (*out_font)->valid)
|
||||||
|
? (*out_font)->get_cache(wp_points_to_pixels(run->size, layout_dpi))
|
||||||
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wp_free_runs(WordProcessorState* wp) {
|
static void wp_free_runs(WordProcessorState* wp) {
|
||||||
@@ -145,6 +200,7 @@ static void wp_on_insert_newline(WordProcessorState* wp, int abs_pos) {
|
|||||||
if (para < 0) para = 0;
|
if (para < 0) para = 0;
|
||||||
if (para >= wp->paragraph_count) para = wp->paragraph_count - 1;
|
if (para >= wp->paragraph_count) para = wp->paragraph_count - 1;
|
||||||
ParagraphStyle style = wp->paragraphs[para];
|
ParagraphStyle style = wp->paragraphs[para];
|
||||||
|
style.divider_type = PARA_DIVIDER_NONE;
|
||||||
wp_insert_paragraph_style(wp, para + 1, &style);
|
wp_insert_paragraph_style(wp, para + 1, &style);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,42 +210,43 @@ static void wp_on_delete_newline(WordProcessorState* wp, int abs_pos) {
|
|||||||
wp_remove_paragraph_style(wp, para + 1);
|
wp_remove_paragraph_style(wp, para + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wp_default_line_metrics(WordProcessorState* wp, int abs_pos, int* out_height, int* out_ascent) {
|
static void wp_default_line_metrics(WordProcessorState* wp, int abs_pos, int layout_dpi,
|
||||||
|
int* out_height, int* out_ascent) {
|
||||||
TrueTypeFont* font = nullptr;
|
TrueTypeFont* font = nullptr;
|
||||||
GlyphCache* gc = nullptr;
|
GlyphCache* gc = nullptr;
|
||||||
if (wp->total_text_len > 0) {
|
if (wp->total_text_len > 0) {
|
||||||
int pos = abs_pos;
|
int pos = abs_pos;
|
||||||
if (pos >= wp->total_text_len) pos = wp->total_text_len - 1;
|
if (pos >= wp->total_text_len) pos = wp->total_text_len - 1;
|
||||||
if (pos < 0) pos = 0;
|
if (pos < 0) pos = 0;
|
||||||
wp_font_at(wp, pos, &font, &gc);
|
wp_font_at(wp, pos, layout_dpi, &font, &gc);
|
||||||
} else {
|
} else {
|
||||||
font = wp_get_font(wp->cur_font_id, wp->cur_flags);
|
font = wp_get_font(wp->cur_font_id, wp->cur_flags);
|
||||||
gc = (font && font->valid) ? font->get_cache(wp->cur_size) : nullptr;
|
gc = (font && font->valid) ? font->get_cache(wp_points_to_pixels(wp->cur_size, layout_dpi)) : nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gc) {
|
if (gc) {
|
||||||
*out_height = gc->line_height;
|
*out_height = gc->line_height;
|
||||||
*out_ascent = gc->ascent;
|
*out_ascent = gc->ascent;
|
||||||
} else {
|
} else {
|
||||||
*out_height = WP_DEFAULT_SIZE;
|
*out_height = wp_points_to_pixels(WP_DEFAULT_SIZE, layout_dpi);
|
||||||
*out_ascent = WP_DEFAULT_SIZE;
|
*out_ascent = *out_height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int wp_char_advance_at(WordProcessorState* wp, int abs_pos, char ch) {
|
static int wp_char_advance_at(WordProcessorState* wp, int abs_pos, int layout_dpi, char ch) {
|
||||||
if (ch == '\n') return 0;
|
if (ch == '\n') return 0;
|
||||||
if (!(ch >= 32 || ch < 0)) return 0;
|
if (!(ch >= 32 || ch < 0)) return 0;
|
||||||
|
|
||||||
TrueTypeFont* font = nullptr;
|
TrueTypeFont* font = nullptr;
|
||||||
GlyphCache* gc = nullptr;
|
GlyphCache* gc = nullptr;
|
||||||
wp_font_at(wp, abs_pos, &font, &gc);
|
wp_font_at(wp, abs_pos, layout_dpi, &font, &gc);
|
||||||
if (!font || !gc) return 8;
|
if (!font || !gc) return wp_points_to_pixels(6, layout_dpi);
|
||||||
|
|
||||||
CachedGlyph* g = font->get_glyph(gc, (unsigned char)ch);
|
CachedGlyph* g = font->get_glyph(gc, (unsigned char)ch);
|
||||||
return g ? g->advance : 8;
|
return g ? g->advance : wp_points_to_pixels(6, layout_dpi);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
|
static void wp_measure_line_range(WordProcessorState* wp, int start, int count, int layout_dpi,
|
||||||
int* out_width, int* out_height, int* out_ascent) {
|
int* out_width, int* out_height, int* out_ascent) {
|
||||||
int width = 0;
|
int width = 0;
|
||||||
int height = 0;
|
int height = 0;
|
||||||
@@ -198,11 +255,11 @@ static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
|
|||||||
for (int i = 0; i < count; i++) {
|
for (int i = 0; i < count; i++) {
|
||||||
char ch = wp_char_at(wp, start + i);
|
char ch = wp_char_at(wp, start + i);
|
||||||
if (ch != '\n')
|
if (ch != '\n')
|
||||||
width += wp_char_advance_at(wp, start + i, ch);
|
width += wp_char_advance_at(wp, start + i, layout_dpi, ch);
|
||||||
|
|
||||||
TrueTypeFont* font = nullptr;
|
TrueTypeFont* font = nullptr;
|
||||||
GlyphCache* gc = nullptr;
|
GlyphCache* gc = nullptr;
|
||||||
wp_font_at(wp, start + i, &font, &gc);
|
wp_font_at(wp, start + i, layout_dpi, &font, &gc);
|
||||||
if (gc) {
|
if (gc) {
|
||||||
if (gc->line_height > height) height = gc->line_height;
|
if (gc->line_height > height) height = gc->line_height;
|
||||||
if (gc->ascent > ascent) ascent = gc->ascent;
|
if (gc->ascent > ascent) ascent = gc->ascent;
|
||||||
@@ -210,7 +267,7 @@ static void wp_measure_line_range(WordProcessorState* wp, int start, int count,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (height == 0)
|
if (height == 0)
|
||||||
wp_default_line_metrics(wp, start, &height, &ascent);
|
wp_default_line_metrics(wp, start, layout_dpi, &height, &ascent);
|
||||||
|
|
||||||
*out_width = width;
|
*out_width = width;
|
||||||
*out_height = height;
|
*out_height = height;
|
||||||
@@ -222,13 +279,41 @@ static int wp_line_spacing_advance(int height, int spacing_percent) {
|
|||||||
return advance < height ? height : advance;
|
return advance < height ? height : advance;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int wp_effective_text_indent(const ParagraphStyle* para, bool first_line) {
|
static int wp_divider_height(uint8_t divider_type, int layout_dpi) {
|
||||||
|
int base = 14;
|
||||||
|
if (divider_type == PARA_DIVIDER_DOUBLE) base = 18;
|
||||||
|
else if (divider_type == PARA_DIVIDER_HEAVY) base = 16;
|
||||||
|
else if (divider_type == PARA_DIVIDER_THIN_THICK || divider_type == PARA_DIVIDER_THICK_THIN) base = 18;
|
||||||
|
int scaled = wp_scale_layout_units(base, layout_dpi);
|
||||||
|
return scaled > 0 ? scaled : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int wp_effective_text_indent(const ParagraphStyle* para, bool first_line, int layout_dpi) {
|
||||||
int indent = para->left_indent;
|
int indent = para->left_indent;
|
||||||
if (indent < 0) indent = 0;
|
if (indent < 0) indent = 0;
|
||||||
if (first_line && para->list_type == PARA_LIST_NONE)
|
if (first_line && para->list_type == PARA_LIST_NONE)
|
||||||
indent += para->first_line_indent;
|
indent += para->first_line_indent;
|
||||||
if (indent < 0) indent = 0;
|
if (indent < 0) indent = 0;
|
||||||
return indent;
|
return wp_scale_layout_units(indent, layout_dpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wp_prepare_paragraph_for_text_insert(WordProcessorState* wp, int abs_pos, char c) {
|
||||||
|
if (c == '\n' || wp->paragraph_count <= 0) return;
|
||||||
|
|
||||||
|
int para = wp_find_paragraph_at(wp, abs_pos);
|
||||||
|
if (para < 0 || para >= wp->paragraph_count) return;
|
||||||
|
|
||||||
|
ParagraphStyle* style = &wp->paragraphs[para];
|
||||||
|
if (style->divider_type == PARA_DIVIDER_NONE) return;
|
||||||
|
|
||||||
|
int para_start = 0;
|
||||||
|
int para_end = 0;
|
||||||
|
wp_paragraph_bounds(wp, para, ¶_start, ¶_end, nullptr);
|
||||||
|
if (para_start != para_end) return;
|
||||||
|
|
||||||
|
style->divider_type = PARA_DIVIDER_NONE;
|
||||||
|
wp->modified = true;
|
||||||
|
wp->wrap_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool wp_ensure_wrap_capacity(WordProcessorState* wp, int needed) {
|
static bool wp_ensure_wrap_capacity(WordProcessorState* wp, int needed) {
|
||||||
@@ -412,6 +497,7 @@ void wp_init_empty_document(WordProcessorState* wp) {
|
|||||||
wp->wrap_line_cap = 0;
|
wp->wrap_line_cap = 0;
|
||||||
wp->wrap_dirty = true;
|
wp->wrap_dirty = true;
|
||||||
wp->last_wrap_width = 0;
|
wp->last_wrap_width = 0;
|
||||||
|
wp->last_wrap_dpi = 0;
|
||||||
|
|
||||||
wp->paragraph_count = 1;
|
wp->paragraph_count = 1;
|
||||||
wp_init_paragraph_style(&wp->paragraphs[0]);
|
wp_init_paragraph_style(&wp->paragraphs[0]);
|
||||||
@@ -433,6 +519,7 @@ void wp_init_empty_document(WordProcessorState* wp) {
|
|||||||
wp->font_dropdown_open = false;
|
wp->font_dropdown_open = false;
|
||||||
wp->size_dropdown_open = false;
|
wp->size_dropdown_open = false;
|
||||||
wp->line_spacing_dropdown_open = false;
|
wp->line_spacing_dropdown_open = false;
|
||||||
|
wp->divider_flyout_open = false;
|
||||||
wp->special_char_flyout_open = false;
|
wp->special_char_flyout_open = false;
|
||||||
|
|
||||||
wp->undo_count = 0;
|
wp->undo_count = 0;
|
||||||
@@ -531,6 +618,7 @@ void wp_insert_char(WordProcessorState* wp, char c) {
|
|||||||
if (c == '\n' && wp->paragraph_count >= WP_MAX_PARAGRAPHS) return;
|
if (c == '\n' && wp->paragraph_count >= WP_MAX_PARAGRAPHS) return;
|
||||||
|
|
||||||
int insert_abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
|
int insert_abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
|
||||||
|
wp_prepare_paragraph_for_text_insert(wp, insert_abs, c);
|
||||||
|
|
||||||
StyledRun* cur = &wp->runs[wp->cursor_run];
|
StyledRun* cur = &wp->runs[wp->cursor_run];
|
||||||
if (wp_same_style(cur, wp->cur_font_id, wp->cur_size, wp->cur_flags)) {
|
if (wp_same_style(cur, wp->cur_font_id, wp->cur_size, wp->cur_flags)) {
|
||||||
@@ -893,16 +981,23 @@ void wp_toggle_list(WordProcessorState* wp, uint8_t list_type) {
|
|||||||
wp_selected_paragraph_range(wp, ¶_s, ¶_e);
|
wp_selected_paragraph_range(wp, ¶_s, ¶_e);
|
||||||
|
|
||||||
bool turn_off = true;
|
bool turn_off = true;
|
||||||
|
bool has_target = false;
|
||||||
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
|
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
|
||||||
|
if (wp->paragraphs[i].divider_type != PARA_DIVIDER_NONE)
|
||||||
|
continue;
|
||||||
|
has_target = true;
|
||||||
if (wp->paragraphs[i].list_type != list_type) {
|
if (wp->paragraphs[i].list_type != list_type) {
|
||||||
turn_off = false;
|
turn_off = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!has_target) return;
|
||||||
|
|
||||||
bool changed = false;
|
bool changed = false;
|
||||||
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
|
for (int i = para_s; i <= para_e && i < wp->paragraph_count; i++) {
|
||||||
ParagraphStyle* para = &wp->paragraphs[i];
|
ParagraphStyle* para = &wp->paragraphs[i];
|
||||||
|
if (para->divider_type != PARA_DIVIDER_NONE)
|
||||||
|
continue;
|
||||||
if (turn_off) {
|
if (turn_off) {
|
||||||
if (para->list_type != PARA_LIST_NONE) {
|
if (para->list_type != PARA_LIST_NONE) {
|
||||||
para->list_type = PARA_LIST_NONE;
|
para->list_type = PARA_LIST_NONE;
|
||||||
@@ -934,6 +1029,74 @@ void wp_toggle_list(WordProcessorState* wp, uint8_t list_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type) {
|
||||||
|
if (!wp_valid_divider_type(divider_type)) return;
|
||||||
|
|
||||||
|
if (wp->has_selection)
|
||||||
|
wp_delete_selection(wp);
|
||||||
|
|
||||||
|
int abs = wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset);
|
||||||
|
int para = wp_find_paragraph_at(wp, abs);
|
||||||
|
if (para < 0) para = 0;
|
||||||
|
if (para >= wp->paragraph_count) para = wp->paragraph_count - 1;
|
||||||
|
|
||||||
|
int para_start = 0;
|
||||||
|
int para_end = 0;
|
||||||
|
bool has_newline = false;
|
||||||
|
wp_paragraph_bounds(wp, para, ¶_start, ¶_end, &has_newline);
|
||||||
|
|
||||||
|
int needed_newlines = 0;
|
||||||
|
if (para_start == para_end) {
|
||||||
|
needed_newlines = has_newline ? 0 : 1;
|
||||||
|
} else if (abs == para_start) {
|
||||||
|
needed_newlines = 1;
|
||||||
|
} else if (abs == para_end) {
|
||||||
|
needed_newlines = has_newline ? 1 : 2;
|
||||||
|
} else {
|
||||||
|
needed_newlines = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wp->paragraph_count + needed_newlines > WP_MAX_PARAGRAPHS)
|
||||||
|
return;
|
||||||
|
if (wp->total_text_len + needed_newlines > WP_MAX_TEXT - 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ParagraphStyle divider_style = wp_make_divider_style(&wp->paragraphs[para], divider_type);
|
||||||
|
|
||||||
|
if (para_start == para_end) {
|
||||||
|
wp->paragraphs[para] = divider_style;
|
||||||
|
wp->modified = true;
|
||||||
|
wp->wrap_dirty = true;
|
||||||
|
|
||||||
|
if (!has_newline) {
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
} else {
|
||||||
|
wp_pos_to_run(wp, para_end + 1, &wp->cursor_run, &wp->cursor_offset);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (abs == para_start) {
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
wp->paragraphs[para] = divider_style;
|
||||||
|
wp_pos_to_run(wp, para_start + 1, &wp->cursor_run, &wp->cursor_offset);
|
||||||
|
} else if (abs == para_end) {
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
if (!has_newline)
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
wp->paragraphs[para + 1] = divider_style;
|
||||||
|
wp_pos_to_run(wp, abs + 2, &wp->cursor_run, &wp->cursor_offset);
|
||||||
|
} else {
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
wp_insert_char(wp, '\n');
|
||||||
|
wp->paragraphs[para + 1] = divider_style;
|
||||||
|
wp_pos_to_run(wp, abs + 2, &wp->cursor_run, &wp->cursor_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
wp->modified = true;
|
||||||
|
wp->wrap_dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
void wp_delete_selection(WordProcessorState* wp) {
|
void wp_delete_selection(WordProcessorState* wp) {
|
||||||
if (!wp->has_selection) return;
|
if (!wp->has_selection) return;
|
||||||
|
|
||||||
@@ -952,13 +1115,14 @@ void wp_delete_selection(WordProcessorState* wp) {
|
|||||||
wp_clear_selection(wp);
|
wp_clear_selection(wp);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi) {
|
||||||
int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W;
|
int wrap_width = content_w - WP_MARGIN * 2 - WP_SCROLLBAR_W;
|
||||||
if (wrap_width < 50) wrap_width = 50;
|
if (wrap_width < 50) wrap_width = 50;
|
||||||
|
|
||||||
if (wrap_width != wp->last_wrap_width) {
|
if (wrap_width != wp->last_wrap_width || layout_dpi != wp->last_wrap_dpi) {
|
||||||
wp->wrap_dirty = true;
|
wp->wrap_dirty = true;
|
||||||
wp->last_wrap_width = wrap_width;
|
wp->last_wrap_width = wrap_width;
|
||||||
|
wp->last_wrap_dpi = layout_dpi;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!wp->wrap_dirty && wp->wrap_lines) return;
|
if (!wp->wrap_dirty && wp->wrap_lines) return;
|
||||||
@@ -991,7 +1155,8 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
para_content_end++;
|
para_content_end++;
|
||||||
}
|
}
|
||||||
|
|
||||||
y += para->space_before;
|
bool is_divider = (para->divider_type != PARA_DIVIDER_NONE && para_start == para_content_end);
|
||||||
|
y += wp_scale_layout_units(para->space_before, layout_dpi);
|
||||||
|
|
||||||
bool first_line = true;
|
bool first_line = true;
|
||||||
int line_start = para_start;
|
int line_start = para_start;
|
||||||
@@ -999,7 +1164,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
if (wp->wrap_line_count >= WP_MAX_WRAP_LINES) break;
|
if (wp->wrap_line_count >= WP_MAX_WRAP_LINES) break;
|
||||||
if (!wp_ensure_wrap_capacity(wp, wp->wrap_line_count + 1)) break;
|
if (!wp_ensure_wrap_capacity(wp, wp->wrap_line_count + 1)) break;
|
||||||
|
|
||||||
int text_indent = wp_effective_text_indent(para, first_line);
|
int text_indent = wp_effective_text_indent(para, first_line, layout_dpi);
|
||||||
int avail_width = wrap_width - text_indent;
|
int avail_width = wrap_width - text_indent;
|
||||||
if (avail_width < 40) avail_width = 40;
|
if (avail_width < 40) avail_width = 40;
|
||||||
|
|
||||||
@@ -1010,7 +1175,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
|
|
||||||
while (scan < para_content_end) {
|
while (scan < para_content_end) {
|
||||||
char ch = wp_char_at(wp, scan);
|
char ch = wp_char_at(wp, scan);
|
||||||
int cw = wp_char_advance_at(wp, scan, ch);
|
int cw = wp_char_advance_at(wp, scan, layout_dpi, ch);
|
||||||
if (width + cw > avail_width && scan > line_start) {
|
if (width + cw > avail_width && scan > line_start) {
|
||||||
if (last_space >= line_start) {
|
if (last_space >= line_start) {
|
||||||
scan = last_space + 1;
|
scan = last_space + 1;
|
||||||
@@ -1028,7 +1193,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (scan == line_start && scan < para_content_end) {
|
if (scan == line_start && scan < para_content_end) {
|
||||||
width += wp_char_advance_at(wp, scan, wp_char_at(wp, scan));
|
width += wp_char_advance_at(wp, scan, layout_dpi, wp_char_at(wp, scan));
|
||||||
scan++;
|
scan++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1039,7 +1204,8 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
int line_width = 0;
|
int line_width = 0;
|
||||||
int line_height = 0;
|
int line_height = 0;
|
||||||
int line_ascent = 0;
|
int line_ascent = 0;
|
||||||
wp_measure_line_range(wp, line_start, char_count, &line_width, &line_height, &line_ascent);
|
wp_measure_line_range(wp, line_start, char_count, layout_dpi,
|
||||||
|
&line_width, &line_height, &line_ascent);
|
||||||
|
|
||||||
WrapLine* line = &wp->wrap_lines[wp->wrap_line_count];
|
WrapLine* line = &wp->wrap_lines[wp->wrap_line_count];
|
||||||
wp_pos_to_run(wp, line_start, &line->run_idx, &line->run_offset);
|
wp_pos_to_run(wp, line_start, &line->run_idx, &line->run_offset);
|
||||||
@@ -1050,6 +1216,7 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
line->width = line_width;
|
line->width = line_width;
|
||||||
line->paragraph_idx = para_idx;
|
line->paragraph_idx = para_idx;
|
||||||
line->list_number = (first_line && para->list_type == PARA_LIST_NUMBER) ? list_number : 0;
|
line->list_number = (first_line && para->list_type == PARA_LIST_NUMBER) ? list_number : 0;
|
||||||
|
line->divider_type = PARA_DIVIDER_NONE;
|
||||||
line->first_in_paragraph = first_line;
|
line->first_in_paragraph = first_line;
|
||||||
|
|
||||||
int extra = wrap_width - text_indent - line_width;
|
int extra = wrap_width - text_indent - line_width;
|
||||||
@@ -1075,25 +1242,35 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
wp_pos_to_run(wp, para_start, &line->run_idx, &line->run_offset);
|
wp_pos_to_run(wp, para_start, &line->run_idx, &line->run_offset);
|
||||||
line->char_count = has_newline ? 1 : 0;
|
line->char_count = has_newline ? 1 : 0;
|
||||||
line->y = y;
|
line->y = y;
|
||||||
wp_default_line_metrics(wp, para_start, &line->height, &line->baseline);
|
|
||||||
line->width = 0;
|
|
||||||
line->paragraph_idx = para_idx;
|
line->paragraph_idx = para_idx;
|
||||||
line->list_number = (para->list_type == PARA_LIST_NUMBER) ? list_number : 0;
|
line->list_number = (is_divider || para->list_type != PARA_LIST_NUMBER) ? 0 : list_number;
|
||||||
|
line->divider_type = is_divider ? para->divider_type : (uint8_t)PARA_DIVIDER_NONE;
|
||||||
line->first_in_paragraph = true;
|
line->first_in_paragraph = true;
|
||||||
|
|
||||||
int text_indent = wp_effective_text_indent(para, true);
|
int text_indent = wp_effective_text_indent(para, true, layout_dpi);
|
||||||
int extra = wrap_width - text_indent;
|
if (is_divider) {
|
||||||
if (extra < 0) extra = 0;
|
line->height = wp_divider_height(para->divider_type, layout_dpi);
|
||||||
int x = WP_MARGIN + text_indent;
|
line->baseline = line->height / 2;
|
||||||
if (para->align == PARA_ALIGN_CENTER) x += extra / 2;
|
line->x = WP_MARGIN + text_indent;
|
||||||
else if (para->align == PARA_ALIGN_RIGHT) x += extra;
|
line->width = wrap_width - text_indent;
|
||||||
line->x = x;
|
if (line->width < 24) line->width = 24;
|
||||||
|
} else {
|
||||||
|
wp_default_line_metrics(wp, para_start, layout_dpi, &line->height, &line->baseline);
|
||||||
|
line->width = 0;
|
||||||
|
|
||||||
|
int extra = wrap_width - text_indent;
|
||||||
|
if (extra < 0) extra = 0;
|
||||||
|
int x = WP_MARGIN + text_indent;
|
||||||
|
if (para->align == PARA_ALIGN_CENTER) x += extra / 2;
|
||||||
|
else if (para->align == PARA_ALIGN_RIGHT) x += extra;
|
||||||
|
line->x = x;
|
||||||
|
}
|
||||||
|
|
||||||
wp->wrap_line_count++;
|
wp->wrap_line_count++;
|
||||||
y = line->y + line->height;
|
y = line->y + line->height;
|
||||||
}
|
}
|
||||||
|
|
||||||
y += para->space_after;
|
y += wp_scale_layout_units(para->space_after, layout_dpi);
|
||||||
|
|
||||||
pos = para_content_end;
|
pos = para_content_end;
|
||||||
if (has_newline) pos++;
|
if (has_newline) pos++;
|
||||||
@@ -1112,11 +1289,12 @@ void wp_recompute_wrap(WordProcessorState* wp, int content_w) {
|
|||||||
line->run_offset = 0;
|
line->run_offset = 0;
|
||||||
line->char_count = 0;
|
line->char_count = 0;
|
||||||
line->y = WP_MARGIN;
|
line->y = WP_MARGIN;
|
||||||
wp_default_line_metrics(wp, 0, &line->height, &line->baseline);
|
wp_default_line_metrics(wp, 0, layout_dpi, &line->height, &line->baseline);
|
||||||
line->x = WP_MARGIN;
|
line->x = WP_MARGIN;
|
||||||
line->width = 0;
|
line->width = 0;
|
||||||
line->paragraph_idx = 0;
|
line->paragraph_idx = 0;
|
||||||
line->list_number = 0;
|
line->list_number = 0;
|
||||||
|
line->divider_type = PARA_DIVIDER_NONE;
|
||||||
line->first_in_paragraph = true;
|
line->first_in_paragraph = true;
|
||||||
|
|
||||||
wp->wrap_line_count = 1;
|
wp->wrap_line_count = 1;
|
||||||
@@ -1285,7 +1463,7 @@ static bool wp_serialize_document(WordProcessorState* wp, uint8_t** out_buf, int
|
|||||||
buf[off++] = para->align;
|
buf[off++] = para->align;
|
||||||
buf[off++] = para->list_type;
|
buf[off++] = para->list_type;
|
||||||
buf[off++] = para->line_spacing;
|
buf[off++] = para->line_spacing;
|
||||||
buf[off++] = 0;
|
buf[off++] = para->divider_type;
|
||||||
wp_write_u16(buf, &off, (uint16_t)para->left_indent);
|
wp_write_u16(buf, &off, (uint16_t)para->left_indent);
|
||||||
wp_write_u16(buf, &off, (uint16_t)para->first_line_indent);
|
wp_write_u16(buf, &off, (uint16_t)para->first_line_indent);
|
||||||
wp_write_u16(buf, &off, (uint16_t)para->space_before);
|
wp_write_u16(buf, &off, (uint16_t)para->space_before);
|
||||||
@@ -1403,7 +1581,7 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
|
|||||||
para->align = buf[off++];
|
para->align = buf[off++];
|
||||||
para->list_type = buf[off++];
|
para->list_type = buf[off++];
|
||||||
para->line_spacing = buf[off++];
|
para->line_spacing = buf[off++];
|
||||||
off++;
|
para->divider_type = buf[off++];
|
||||||
para->left_indent = (int16_t)wp_read_u16(buf, &off);
|
para->left_indent = (int16_t)wp_read_u16(buf, &off);
|
||||||
para->first_line_indent = (int16_t)wp_read_u16(buf, &off);
|
para->first_line_indent = (int16_t)wp_read_u16(buf, &off);
|
||||||
para->space_before = (int16_t)wp_read_u16(buf, &off);
|
para->space_before = (int16_t)wp_read_u16(buf, &off);
|
||||||
@@ -1411,6 +1589,7 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
|
|||||||
|
|
||||||
if (para->align > PARA_ALIGN_RIGHT) para->align = PARA_ALIGN_LEFT;
|
if (para->align > PARA_ALIGN_RIGHT) para->align = PARA_ALIGN_LEFT;
|
||||||
if (para->list_type > PARA_LIST_NUMBER) para->list_type = PARA_LIST_NONE;
|
if (para->list_type > PARA_LIST_NUMBER) para->list_type = PARA_LIST_NONE;
|
||||||
|
if (!wp_valid_divider_type(para->divider_type)) para->divider_type = PARA_DIVIDER_NONE;
|
||||||
|
|
||||||
bool known_spacing = false;
|
bool known_spacing = false;
|
||||||
for (int j = 0; j < WP_LINE_SPACING_OPTION_COUNT; j++) {
|
for (int j = 0; j < WP_LINE_SPACING_OPTION_COUNT; j++) {
|
||||||
@@ -1433,6 +1612,8 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
|
|||||||
wp->scrollbar.scroll_offset = 0;
|
wp->scrollbar.scroll_offset = 0;
|
||||||
wp->modified = false;
|
wp->modified = false;
|
||||||
wp->wrap_dirty = true;
|
wp->wrap_dirty = true;
|
||||||
|
wp->last_wrap_width = 0;
|
||||||
|
wp->last_wrap_dpi = 0;
|
||||||
wp->sel_anchor = 0;
|
wp->sel_anchor = 0;
|
||||||
wp->sel_end = 0;
|
wp->sel_end = 0;
|
||||||
wp->has_selection = false;
|
wp->has_selection = false;
|
||||||
@@ -1440,6 +1621,8 @@ static bool wp_deserialize_document(WordProcessorState* wp, const uint8_t* buf,
|
|||||||
wp->font_dropdown_open = false;
|
wp->font_dropdown_open = false;
|
||||||
wp->size_dropdown_open = false;
|
wp->size_dropdown_open = false;
|
||||||
wp->line_spacing_dropdown_open = false;
|
wp->line_spacing_dropdown_open = false;
|
||||||
|
wp->divider_flyout_open = false;
|
||||||
|
wp->special_char_flyout_open = false;
|
||||||
wp->show_pathbar = false;
|
wp->show_pathbar = false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,17 @@ static int wp_special_char_flyout_x() {
|
|||||||
return dx < 0 ? 0 : dx;
|
return dx < 0 ? 0 : dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int wp_divider_flyout_x() {
|
||||||
|
int dx = WP_BTN_DIVIDER_X + 24 - WP_DIVIDER_FLYOUT_W;
|
||||||
|
if (dx + WP_DIVIDER_FLYOUT_W > g_win_w) dx = g_win_w - WP_DIVIDER_FLYOUT_W;
|
||||||
|
return dx < 0 ? 0 : dx;
|
||||||
|
}
|
||||||
|
|
||||||
static void wp_close_dropdowns(WordProcessorState* wp) {
|
static void wp_close_dropdowns(WordProcessorState* wp) {
|
||||||
wp->font_dropdown_open = false;
|
wp->font_dropdown_open = false;
|
||||||
wp->size_dropdown_open = false;
|
wp->size_dropdown_open = false;
|
||||||
wp->line_spacing_dropdown_open = false;
|
wp->line_spacing_dropdown_open = false;
|
||||||
|
wp->divider_flyout_open = false;
|
||||||
wp->special_char_flyout_open = false;
|
wp->special_char_flyout_open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +66,11 @@ static void wp_insert_special_char(WordProcessorState* wp, uint8_t ch) {
|
|||||||
wp_history_checkpoint(wp);
|
wp_history_checkpoint(wp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wp_insert_divider_choice(WordProcessorState* wp, uint8_t divider_type) {
|
||||||
|
wp_insert_divider(wp, divider_type);
|
||||||
|
wp_history_checkpoint(wp);
|
||||||
|
}
|
||||||
|
|
||||||
static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, int edit_y) {
|
static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, int edit_y) {
|
||||||
int click_y = local_y - edit_y + wp->scrollbar.scroll_offset;
|
int click_y = local_y - edit_y + wp->scrollbar.scroll_offset;
|
||||||
|
|
||||||
@@ -72,6 +84,9 @@ static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
WrapLine* wl = &wp->wrap_lines[target_line];
|
WrapLine* wl = &wp->wrap_lines[target_line];
|
||||||
|
if (wl->divider_type != PARA_DIVIDER_NONE)
|
||||||
|
return wp_wrap_line_start(wp, target_line);
|
||||||
|
|
||||||
int click_x = local_x - wl->x;
|
int click_x = local_x - wl->x;
|
||||||
int ri = wl->run_idx;
|
int ri = wl->run_idx;
|
||||||
int ro = wl->run_offset;
|
int ro = wl->run_offset;
|
||||||
@@ -90,7 +105,7 @@ static int wp_hit_test_text(WordProcessorState* wp, int local_x, int local_y, in
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GlyphCache* gc = font->get_cache(r->size);
|
GlyphCache* gc = font->get_cache(wp_screen_font_pixels(r->size));
|
||||||
int avail = r->len - ro;
|
int avail = r->len - ro;
|
||||||
int to_check = avail < chars_left ? avail : chars_left;
|
int to_check = avail < chars_left ? avail : chars_left;
|
||||||
|
|
||||||
@@ -173,6 +188,19 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wp->divider_flyout_open && wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons)) {
|
||||||
|
int dx = wp_divider_flyout_x();
|
||||||
|
int dy = WP_TOOLBAR_H;
|
||||||
|
int dh = WP_DIVIDER_OPTION_COUNT * WP_DIVIDER_ROW_H + 4;
|
||||||
|
if (local_x >= dx && local_x < dx + WP_DIVIDER_FLYOUT_W && local_y >= dy && local_y < dy + dh) {
|
||||||
|
int idx = (local_y - dy - 2) / WP_DIVIDER_ROW_H;
|
||||||
|
if (idx >= 0 && idx < WP_DIVIDER_OPTION_COUNT)
|
||||||
|
wp_insert_divider_choice(wp, WP_DIVIDER_OPTIONS[idx].type);
|
||||||
|
}
|
||||||
|
wp->divider_flyout_open = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (wp->special_char_flyout_open && wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons)) {
|
if (wp->special_char_flyout_open && wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons)) {
|
||||||
int dx = wp_special_char_flyout_x();
|
int dx = wp_special_char_flyout_x();
|
||||||
int dy = WP_TOOLBAR_H;
|
int dy = WP_TOOLBAR_H;
|
||||||
@@ -222,15 +250,15 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (local_x >= WP_FONT_DD_X && local_x < WP_FONT_DD_X + WP_FONT_DD_W && local_y >= 6 && local_y < 30) {
|
if (local_x >= WP_FONT_DD_X && local_x < WP_FONT_DD_X + WP_FONT_DD_W && local_y >= 6 && local_y < 30) {
|
||||||
wp->font_dropdown_open = !wp->font_dropdown_open;
|
bool open = !wp->font_dropdown_open;
|
||||||
wp->size_dropdown_open = false;
|
wp_close_dropdowns(wp);
|
||||||
wp->line_spacing_dropdown_open = false;
|
wp->font_dropdown_open = open;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (local_x >= WP_SIZE_DD_X && local_x < WP_SIZE_DD_X + WP_SIZE_DD_W && local_y >= 6 && local_y < 30) {
|
if (local_x >= WP_SIZE_DD_X && local_x < WP_SIZE_DD_X + WP_SIZE_DD_W && local_y >= 6 && local_y < 30) {
|
||||||
wp->size_dropdown_open = !wp->size_dropdown_open;
|
bool open = !wp->size_dropdown_open;
|
||||||
wp->font_dropdown_open = false;
|
wp_close_dropdowns(wp);
|
||||||
wp->line_spacing_dropdown_open = false;
|
wp->size_dropdown_open = open;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (local_x >= WP_BTN_ALIGN_L_X && local_x < WP_BTN_ALIGN_L_X + 24 && local_y >= 6 && local_y < 30) {
|
if (local_x >= WP_BTN_ALIGN_L_X && local_x < WP_BTN_ALIGN_L_X + 24 && local_y >= 6 && local_y < 30) {
|
||||||
@@ -276,10 +304,15 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (local_x >= WP_LINE_DD_X && local_x < WP_LINE_DD_X + WP_LINE_DD_W && local_y >= 6 && local_y < 30) {
|
if (local_x >= WP_LINE_DD_X && local_x < WP_LINE_DD_X + WP_LINE_DD_W && local_y >= 6 && local_y < 30) {
|
||||||
wp->line_spacing_dropdown_open = !wp->line_spacing_dropdown_open;
|
bool open = !wp->line_spacing_dropdown_open;
|
||||||
wp->font_dropdown_open = false;
|
wp_close_dropdowns(wp);
|
||||||
wp->size_dropdown_open = false;
|
wp->line_spacing_dropdown_open = open;
|
||||||
wp->special_char_flyout_open = false;
|
return;
|
||||||
|
}
|
||||||
|
if (local_x >= WP_BTN_DIVIDER_X && local_x < WP_BTN_DIVIDER_X + 24 && local_y >= 6 && local_y < 30) {
|
||||||
|
bool open = !wp->divider_flyout_open;
|
||||||
|
wp_close_dropdowns(wp);
|
||||||
|
wp->divider_flyout_open = open;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (local_x >= WP_BTN_SECTION_X && local_x < WP_BTN_SECTION_X + 24 && local_y >= 6 && local_y < 30) {
|
if (local_x >= WP_BTN_SECTION_X && local_x < WP_BTN_SECTION_X + 24 && local_y >= 6 && local_y < 30) {
|
||||||
@@ -303,7 +336,7 @@ void wp_handle_mouse(const Montauk::WinEvent& ev) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
wp_recompute_wrap(wp, g_win_w);
|
wp_recompute_wrap(wp, g_win_w, WP_SCREEN_DPI);
|
||||||
|
|
||||||
if (wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons) &&
|
if (wp_left_pressed(ev.mouse.buttons, ev.mouse.prev_buttons) &&
|
||||||
local_y >= edit_y && local_y < edit_y + text_area_h) {
|
local_y >= edit_y && local_y < edit_y + text_area_h) {
|
||||||
@@ -382,7 +415,7 @@ void wp_handle_key(const Montauk::KeyEvent& key) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wp_close_dropdowns(wp);
|
wp_close_dropdowns(wp);
|
||||||
wp_recompute_wrap(wp, g_win_w);
|
wp_recompute_wrap(wp, g_win_w, WP_SCREEN_DPI);
|
||||||
|
|
||||||
if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) {
|
if (key.ctrl && (key.ascii == 's' || key.ascii == 'S')) {
|
||||||
wp_save_file(wp);
|
wp_save_file(wp);
|
||||||
|
|||||||
@@ -64,6 +64,43 @@ static int wp_print_layout_width() {
|
|||||||
return (WP_PRINT_PAGE_W - WP_PRINT_MARGIN_X * 2) + WP_MARGIN * 2 + WP_SCROLLBAR_W;
|
return (WP_PRINT_PAGE_W - WP_PRINT_MARGIN_X * 2) + WP_MARGIN * 2 + WP_SCROLLBAR_W;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void wp_draw_print_divider(Canvas& c, int x, int y, int w, int h,
|
||||||
|
uint8_t divider_type, Color color) {
|
||||||
|
if (w <= 0 || h <= 0 || divider_type == PARA_DIVIDER_NONE) return;
|
||||||
|
|
||||||
|
int cy = y + h / 2;
|
||||||
|
switch (divider_type) {
|
||||||
|
case PARA_DIVIDER_SINGLE:
|
||||||
|
c.fill_rect(x, cy, w, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DOUBLE:
|
||||||
|
c.fill_rect(x, cy - 2, w, 1, color);
|
||||||
|
c.fill_rect(x, cy + 2, w, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DOTTED:
|
||||||
|
for (int dx = x; dx < x + w; dx += 5)
|
||||||
|
c.fill_rect(dx, cy, 2, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DASHED:
|
||||||
|
for (int dx = x; dx < x + w; dx += 11) {
|
||||||
|
int dash_w = (x + w - dx) < 7 ? (x + w - dx) : 7;
|
||||||
|
if (dash_w > 0) c.fill_rect(dx, cy, dash_w, 1, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_HEAVY:
|
||||||
|
c.fill_rect(x, cy - 1, w, 3, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_THIN_THICK:
|
||||||
|
c.fill_rect(x, cy - 3, w, 1, color);
|
||||||
|
c.fill_rect(x, cy, w, 3, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_THICK_THIN:
|
||||||
|
c.fill_rect(x, cy - 3, w, 3, color);
|
||||||
|
c.fill_rect(x, cy + 2, w, 1, color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void wp_make_base_print_name(WordProcessorState* wp, char* out, int out_len) {
|
static void wp_make_base_print_name(WordProcessorState* wp, char* out, int out_len) {
|
||||||
const char* src = wp->filename[0] ? wp->filename : "document";
|
const char* src = wp->filename[0] ? wp->filename : "document";
|
||||||
int last_dot = -1;
|
int last_dot = -1;
|
||||||
@@ -109,7 +146,7 @@ static bool wp_build_print_page_map(WordProcessorState* wp,
|
|||||||
if (out_line_y) *out_line_y = nullptr;
|
if (out_line_y) *out_line_y = nullptr;
|
||||||
if (out_page_count) *out_page_count = 0;
|
if (out_page_count) *out_page_count = 0;
|
||||||
|
|
||||||
wp_recompute_wrap(wp, wp_print_layout_width());
|
wp_recompute_wrap(wp, wp_print_layout_width(), WP_PRINT_DPI);
|
||||||
if (wp->wrap_line_count <= 0) {
|
if (wp->wrap_line_count <= 0) {
|
||||||
wp_status_copy(err, err_len, "failed to paginate document");
|
wp_status_copy(err, err_len, "failed to paginate document");
|
||||||
return false;
|
return false;
|
||||||
@@ -164,14 +201,17 @@ static void wp_draw_print_list_marker(Canvas& c, WordProcessorState* wp, WrapLin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
|
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
|
||||||
if (para->list_type == PARA_LIST_NONE) return;
|
if (para->divider_type != PARA_DIVIDER_NONE || para->list_type == PARA_LIST_NONE) return;
|
||||||
|
|
||||||
int marker_x = WP_PRINT_MARGIN_X + para->left_indent + para->first_line_indent;
|
int marker_x = WP_PRINT_MARGIN_X + wp_scale_layout_units(para->left_indent + para->first_line_indent, WP_PRINT_DPI);
|
||||||
int min_x = WP_PRINT_MARGIN_X - WP_MARGIN + 4;
|
int min_x = WP_PRINT_MARGIN_X - WP_MARGIN + 4;
|
||||||
if (marker_x < min_x) marker_x = min_x;
|
if (marker_x < min_x) marker_x = min_x;
|
||||||
|
|
||||||
if (para->list_type == PARA_LIST_BULLET) {
|
if (para->list_type == PARA_LIST_BULLET) {
|
||||||
fill_circle(c, marker_x + 7, py + wl->height / 2, 3, colors::BLACK);
|
int bullet_x = marker_x + wp_scale_layout_units(7, WP_PRINT_DPI);
|
||||||
|
int bullet_r = wp_scale_layout_units(3, WP_PRINT_DPI);
|
||||||
|
if (bullet_r < 2) bullet_r = 2;
|
||||||
|
fill_circle(c, bullet_x, py + wl->height / 2, bullet_r, colors::BLACK);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,8 +220,9 @@ static void wp_draw_print_list_marker(Canvas& c, WordProcessorState* wp, WrapLin
|
|||||||
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
|
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
|
||||||
char label[16];
|
char label[16];
|
||||||
snprintf(label, sizeof(label), "%d.", wl->list_number > 0 ? wl->list_number : 1);
|
snprintf(label, sizeof(label), "%d.", wl->list_number > 0 ? wl->list_number : 1);
|
||||||
int top_y = py + (wl->height - run->size) / 2;
|
int font_px = wp_print_font_pixels(run->size);
|
||||||
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::BLACK, run->size);
|
int top_y = py + (wl->height - font_px) / 2;
|
||||||
|
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::BLACK, font_px);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool wp_encode_print_page(WordProcessorState* wp,
|
static bool wp_encode_print_page(WordProcessorState* wp,
|
||||||
@@ -210,6 +251,10 @@ static bool wp_encode_print_page(WordProcessorState* wp,
|
|||||||
WrapLine* wl = &wp->wrap_lines[li];
|
WrapLine* wl = &wp->wrap_lines[li];
|
||||||
int py = line_y[li];
|
int py = line_y[li];
|
||||||
wp_draw_print_list_marker(c, wp, wl, py);
|
wp_draw_print_list_marker(c, wp, wl, py);
|
||||||
|
if (wl->divider_type != PARA_DIVIDER_NONE) {
|
||||||
|
int divider_x = WP_PRINT_MARGIN_X + (wl->x - WP_MARGIN);
|
||||||
|
wp_draw_print_divider(c, divider_x, py, wl->width, wl->height, wl->divider_type, colors::BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
int chars_left = wl->char_count;
|
int chars_left = wl->char_count;
|
||||||
int ri = wl->run_idx;
|
int ri = wl->run_idx;
|
||||||
@@ -228,7 +273,7 @@ static bool wp_encode_print_page(WordProcessorState* wp,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GlyphCache* gc = font->get_cache(r->size);
|
GlyphCache* gc = font->get_cache(wp_print_font_pixels(r->size));
|
||||||
int baseline = py + wl->baseline;
|
int baseline = py + wl->baseline;
|
||||||
|
|
||||||
int avail = r->len - ro;
|
int avail = r->len - ro;
|
||||||
|
|||||||
@@ -41,6 +41,49 @@ static int wp_special_char_flyout_x() {
|
|||||||
return dx < 0 ? 0 : dx;
|
return dx < 0 ? 0 : dx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int wp_divider_flyout_x() {
|
||||||
|
int dx = WP_BTN_DIVIDER_X + 24 - WP_DIVIDER_FLYOUT_W;
|
||||||
|
if (dx + WP_DIVIDER_FLYOUT_W > g_win_w) dx = g_win_w - WP_DIVIDER_FLYOUT_W;
|
||||||
|
return dx < 0 ? 0 : dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void wp_draw_divider_sample(Canvas& c, int x, int y, int w, int h,
|
||||||
|
uint8_t divider_type, Color color) {
|
||||||
|
if (w <= 0 || h <= 0 || divider_type == PARA_DIVIDER_NONE) return;
|
||||||
|
|
||||||
|
int cy = y + h / 2;
|
||||||
|
switch (divider_type) {
|
||||||
|
case PARA_DIVIDER_SINGLE:
|
||||||
|
c.fill_rect(x, cy, w, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DOUBLE:
|
||||||
|
c.fill_rect(x, cy - 2, w, 1, color);
|
||||||
|
c.fill_rect(x, cy + 2, w, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DOTTED:
|
||||||
|
for (int dx = x; dx < x + w; dx += 5)
|
||||||
|
c.fill_rect(dx, cy, 2, 1, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_DASHED:
|
||||||
|
for (int dx = x; dx < x + w; dx += 11) {
|
||||||
|
int dash_w = (x + w - dx) < 7 ? (x + w - dx) : 7;
|
||||||
|
if (dash_w > 0) c.fill_rect(dx, cy, dash_w, 1, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_HEAVY:
|
||||||
|
c.fill_rect(x, cy - 1, w, 3, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_THIN_THICK:
|
||||||
|
c.fill_rect(x, cy - 3, w, 1, color);
|
||||||
|
c.fill_rect(x, cy, w, 3, color);
|
||||||
|
break;
|
||||||
|
case PARA_DIVIDER_THICK_THIN:
|
||||||
|
c.fill_rect(x, cy - 3, w, 3, color);
|
||||||
|
c.fill_rect(x, cy + 2, w, 1, color);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
|
static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
|
||||||
if (!src || !src[0]) {
|
if (!src || !src[0]) {
|
||||||
if (out_len > 0) out[0] = '\0';
|
if (out_len > 0) out[0] = '\0';
|
||||||
@@ -71,7 +114,7 @@ static void wp_fit_ui_text(const char* src, char* out, int out_len, int max_w) {
|
|||||||
|
|
||||||
static ParagraphStyle* wp_current_paragraph_style(WordProcessorState* wp) {
|
static ParagraphStyle* wp_current_paragraph_style(WordProcessorState* wp) {
|
||||||
if (wp->paragraph_count <= 0) {
|
if (wp->paragraph_count <= 0) {
|
||||||
static ParagraphStyle fallback = { PARA_ALIGN_LEFT, PARA_LIST_NONE, 100, 0, 0, 0, 0, 0 };
|
static ParagraphStyle fallback = { PARA_ALIGN_LEFT, PARA_LIST_NONE, 100, PARA_DIVIDER_NONE, 0, 0, 0, 0 };
|
||||||
return &fallback;
|
return &fallback;
|
||||||
}
|
}
|
||||||
int para = wp_find_paragraph_at(wp, wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset));
|
int para = wp_find_paragraph_at(wp, wp_abs_pos(wp, wp->cursor_run, wp->cursor_offset));
|
||||||
@@ -96,18 +139,34 @@ static const char* wp_list_label(uint8_t list_type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char* wp_divider_label(uint8_t divider_type) {
|
||||||
|
switch (divider_type) {
|
||||||
|
case PARA_DIVIDER_SINGLE: return "Single";
|
||||||
|
case PARA_DIVIDER_DOUBLE: return "Double";
|
||||||
|
case PARA_DIVIDER_DOTTED: return "Dotted";
|
||||||
|
case PARA_DIVIDER_DASHED: return "Dashed";
|
||||||
|
case PARA_DIVIDER_HEAVY: return "Heavy";
|
||||||
|
case PARA_DIVIDER_THIN_THICK: return "Thin/Thick";
|
||||||
|
case PARA_DIVIDER_THICK_THIN: return "Thick/Thin";
|
||||||
|
default: return "None";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl, int py) {
|
static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl, int py) {
|
||||||
if (!wl->first_in_paragraph || wl->paragraph_idx < 0 || wl->paragraph_idx >= wp->paragraph_count)
|
if (!wl->first_in_paragraph || wl->paragraph_idx < 0 || wl->paragraph_idx >= wp->paragraph_count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
|
ParagraphStyle* para = &wp->paragraphs[wl->paragraph_idx];
|
||||||
if (para->list_type == PARA_LIST_NONE) return;
|
if (para->divider_type != PARA_DIVIDER_NONE || para->list_type == PARA_LIST_NONE) return;
|
||||||
|
|
||||||
int marker_x = WP_MARGIN + para->left_indent + para->first_line_indent;
|
int marker_x = WP_MARGIN + wp_scale_layout_units(para->left_indent + para->first_line_indent, WP_SCREEN_DPI);
|
||||||
if (marker_x < 4) marker_x = 4;
|
if (marker_x < 4) marker_x = 4;
|
||||||
|
|
||||||
if (para->list_type == PARA_LIST_BULLET) {
|
if (para->list_type == PARA_LIST_BULLET) {
|
||||||
fill_circle(c, marker_x + 7, py + wl->height / 2, 3, colors::TEXT_COLOR);
|
int bullet_x = marker_x + wp_scale_layout_units(7, WP_SCREEN_DPI);
|
||||||
|
int bullet_r = wp_scale_layout_units(3, WP_SCREEN_DPI);
|
||||||
|
if (bullet_r < 2) bullet_r = 2;
|
||||||
|
fill_circle(c, bullet_x, py + wl->height / 2, bullet_r, colors::TEXT_COLOR);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,8 +175,9 @@ static void wp_draw_list_marker(Canvas& c, WordProcessorState* wp, WrapLine* wl,
|
|||||||
if (wp->run_count <= 0) return;
|
if (wp->run_count <= 0) return;
|
||||||
StyledRun* run = &wp->runs[wl->run_idx < wp->run_count ? wl->run_idx : 0];
|
StyledRun* run = &wp->runs[wl->run_idx < wp->run_count ? wl->run_idx : 0];
|
||||||
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
|
TrueTypeFont* font = wp_get_font(run->font_id, run->flags);
|
||||||
int top_y = py + (wl->height - run->size) / 2;
|
int font_px = wp_screen_font_pixels(run->size);
|
||||||
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::TEXT_COLOR, run->size);
|
int top_y = py + (wl->height - font_px) / 2;
|
||||||
|
draw_text(c, font ? font : g_ui_font, marker_x, top_y, label, colors::TEXT_COLOR, font_px);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wp_render() {
|
void wp_render() {
|
||||||
@@ -203,6 +263,17 @@ void wp_render() {
|
|||||||
wp_draw_ui_text(c, WP_LINE_DD_X + 8, (WP_TOOLBAR_H - sfh) / 2, spacing, colors::TEXT_COLOR);
|
wp_draw_ui_text(c, WP_LINE_DD_X + 8, (WP_TOOLBAR_H - sfh) / 2, spacing, colors::TEXT_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.vline(WP_BTN_DIVIDER_X - 4, 4, 28, colors::BORDER);
|
||||||
|
|
||||||
|
Color divider_bg = (wp->divider_flyout_open || cur_para->divider_type != PARA_DIVIDER_NONE) ? btn_active : btn_bg;
|
||||||
|
c.fill_rounded_rect(WP_BTN_DIVIDER_X, 6, 24, 24, 3, divider_bg);
|
||||||
|
uint8_t divider_preview = cur_para->divider_type != PARA_DIVIDER_NONE
|
||||||
|
? cur_para->divider_type
|
||||||
|
: (uint8_t)PARA_DIVIDER_SINGLE;
|
||||||
|
wp_draw_divider_sample(c, WP_BTN_DIVIDER_X + 4, 6, 16, 24,
|
||||||
|
divider_preview,
|
||||||
|
colors::TEXT_COLOR);
|
||||||
|
|
||||||
c.vline(WP_BTN_SECTION_X - 4, 4, 28, colors::BORDER);
|
c.vline(WP_BTN_SECTION_X - 4, 4, 28, colors::BORDER);
|
||||||
|
|
||||||
Color special_bg = wp->special_char_flyout_open ? btn_active : btn_bg;
|
Color special_bg = wp->special_char_flyout_open ? btn_active : btn_bg;
|
||||||
@@ -250,7 +321,7 @@ void wp_render() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int text_area_h = c.h - edit_y - WP_STATUS_H;
|
int text_area_h = c.h - edit_y - WP_STATUS_H;
|
||||||
wp_recompute_wrap(wp, c.w);
|
wp_recompute_wrap(wp, c.w, WP_SCREEN_DPI);
|
||||||
|
|
||||||
wp->scrollbar.bounds = {c.w - WP_SCROLLBAR_W, edit_y, WP_SCROLLBAR_W, text_area_h};
|
wp->scrollbar.bounds = {c.w - WP_SCROLLBAR_W, edit_y, WP_SCROLLBAR_W, text_area_h};
|
||||||
wp->scrollbar.content_height = wp->content_height;
|
wp->scrollbar.content_height = wp->content_height;
|
||||||
@@ -278,6 +349,8 @@ void wp_render() {
|
|||||||
if (py >= edit_y + text_area_h) break;
|
if (py >= edit_y + text_area_h) break;
|
||||||
|
|
||||||
wp_draw_list_marker(c, wp, wl, py);
|
wp_draw_list_marker(c, wp, wl, py);
|
||||||
|
if (wl->divider_type != PARA_DIVIDER_NONE)
|
||||||
|
wp_draw_divider_sample(c, wl->x, py, wl->width, wl->height, wl->divider_type, colors::TEXT_COLOR);
|
||||||
|
|
||||||
int chars_left = wl->char_count;
|
int chars_left = wl->char_count;
|
||||||
int ri = wl->run_idx;
|
int ri = wl->run_idx;
|
||||||
@@ -295,7 +368,7 @@ void wp_render() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
GlyphCache* gc = font->get_cache(r->size);
|
GlyphCache* gc = font->get_cache(wp_screen_font_pixels(r->size));
|
||||||
int baseline = py + wl->baseline;
|
int baseline = py + wl->baseline;
|
||||||
|
|
||||||
int avail = r->len - ro;
|
int avail = r->len - ro;
|
||||||
@@ -369,11 +442,18 @@ void wp_render() {
|
|||||||
|
|
||||||
char status_left[320];
|
char status_left[320];
|
||||||
const char* name = wp->filename[0] ? wp->filename : "Untitled";
|
const char* name = wp->filename[0] ? wp->filename : "Untitled";
|
||||||
snprintf(status_left, sizeof(status_left), " %s%s | %s %dpt | %s | %s | %d%%",
|
if (cur_para->divider_type != PARA_DIVIDER_NONE) {
|
||||||
name, wp->modified ? " *" : "",
|
snprintf(status_left, sizeof(status_left), " %s%s | %s %dpt | Divider: %s | %d%%",
|
||||||
WP_FONT_NAMES[wp->cur_font_id < FONT_COUNT ? wp->cur_font_id : 0], (int)wp->cur_size,
|
name, wp->modified ? " *" : "",
|
||||||
wp_align_label(cur_para->align), wp_list_label(cur_para->list_type),
|
WP_FONT_NAMES[wp->cur_font_id < FONT_COUNT ? wp->cur_font_id : 0], (int)wp->cur_size,
|
||||||
(int)cur_para->line_spacing);
|
wp_divider_label(cur_para->divider_type), (int)cur_para->line_spacing);
|
||||||
|
} else {
|
||||||
|
snprintf(status_left, sizeof(status_left), " %s%s | %s %dpt | %s | %s | %d%%",
|
||||||
|
name, wp->modified ? " *" : "",
|
||||||
|
WP_FONT_NAMES[wp->cur_font_id < FONT_COUNT ? wp->cur_font_id : 0], (int)wp->cur_size,
|
||||||
|
wp_align_label(cur_para->align), wp_list_label(cur_para->list_type),
|
||||||
|
(int)cur_para->line_spacing);
|
||||||
|
}
|
||||||
|
|
||||||
char status_right[32];
|
char status_right[32];
|
||||||
snprintf(status_right, sizeof(status_right), "%d chars ", wp->total_text_len);
|
snprintf(status_right, sizeof(status_right), "%d chars ", wp->total_text_len);
|
||||||
@@ -437,6 +517,24 @@ void wp_render() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wp->divider_flyout_open) {
|
||||||
|
int dx = wp_divider_flyout_x();
|
||||||
|
int dy = WP_TOOLBAR_H;
|
||||||
|
int dw = WP_DIVIDER_FLYOUT_W;
|
||||||
|
int dh = WP_DIVIDER_OPTION_COUNT * WP_DIVIDER_ROW_H + 4;
|
||||||
|
c.fill_rect(dx, dy, dw, dh, colors::MENU_BG);
|
||||||
|
c.rect(dx, dy, dw, dh, colors::BORDER);
|
||||||
|
for (int i = 0; i < WP_DIVIDER_OPTION_COUNT; i++) {
|
||||||
|
int iy = dy + 2 + i * WP_DIVIDER_ROW_H;
|
||||||
|
if (WP_DIVIDER_OPTIONS[i].type == cur_para->divider_type)
|
||||||
|
c.fill_rect(dx + 2, iy, dw - 4, WP_DIVIDER_ROW_H - 2, colors::MENU_HOVER);
|
||||||
|
wp_draw_divider_sample(c, dx + 10, iy, 56, WP_DIVIDER_ROW_H,
|
||||||
|
WP_DIVIDER_OPTIONS[i].type, colors::TEXT_COLOR);
|
||||||
|
wp_draw_ui_text(c, dx + 78, iy + (WP_DIVIDER_ROW_H - sfh) / 2,
|
||||||
|
WP_DIVIDER_OPTIONS[i].label, colors::TEXT_COLOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (wp->special_char_flyout_open) {
|
if (wp->special_char_flyout_open) {
|
||||||
int dx = wp_special_char_flyout_x();
|
int dx = wp_special_char_flyout_x();
|
||||||
int dy = WP_TOOLBAR_H;
|
int dy = WP_TOOLBAR_H;
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ static constexpr int WP_MAX_WRAP_LINES = 4096;
|
|||||||
static constexpr int WP_MAX_PARAGRAPHS = 4096;
|
static constexpr int WP_MAX_PARAGRAPHS = 4096;
|
||||||
static constexpr int WP_DEFAULT_SIZE = 18;
|
static constexpr int WP_DEFAULT_SIZE = 18;
|
||||||
static constexpr int WP_UNDO_MAX = 24;
|
static constexpr int WP_UNDO_MAX = 24;
|
||||||
|
static constexpr int WP_SCREEN_DPI = 96;
|
||||||
|
static constexpr int WP_PRINT_DPI = 150;
|
||||||
static constexpr int WP_PARA_STEP = 12;
|
static constexpr int WP_PARA_STEP = 12;
|
||||||
static constexpr int WP_SPACE_STEP = 6;
|
static constexpr int WP_SPACE_STEP = 6;
|
||||||
static constexpr int WP_LIST_LEFT = 28;
|
static constexpr int WP_LIST_LEFT = 28;
|
||||||
@@ -59,7 +61,10 @@ static constexpr int WP_BTN_OUTDENT_X = 496;
|
|||||||
static constexpr int WP_BTN_INDENT_X = 524;
|
static constexpr int WP_BTN_INDENT_X = 524;
|
||||||
static constexpr int WP_LINE_DD_X = 558;
|
static constexpr int WP_LINE_DD_X = 558;
|
||||||
static constexpr int WP_LINE_DD_W = 48;
|
static constexpr int WP_LINE_DD_W = 48;
|
||||||
static constexpr int WP_BTN_SECTION_X = 614;
|
static constexpr int WP_BTN_DIVIDER_X = 614;
|
||||||
|
static constexpr int WP_BTN_SECTION_X = 646;
|
||||||
|
static constexpr int WP_DIVIDER_ROW_H = 24;
|
||||||
|
static constexpr int WP_DIVIDER_FLYOUT_W = 216;
|
||||||
static constexpr int WP_SPECIAL_CHAR_ROW_H = 24;
|
static constexpr int WP_SPECIAL_CHAR_ROW_H = 24;
|
||||||
static constexpr int WP_SPECIAL_CHAR_FLYOUT_W = 144;
|
static constexpr int WP_SPECIAL_CHAR_FLYOUT_W = 144;
|
||||||
|
|
||||||
@@ -82,20 +87,48 @@ static constexpr int WP_SIZE_OPTION_COUNT = 8;
|
|||||||
inline constexpr int WP_LINE_SPACING_OPTIONS[] = { 100, 125, 150, 200 };
|
inline constexpr int WP_LINE_SPACING_OPTIONS[] = { 100, 125, 150, 200 };
|
||||||
static constexpr int WP_LINE_SPACING_OPTION_COUNT = 4;
|
static constexpr int WP_LINE_SPACING_OPTION_COUNT = 4;
|
||||||
|
|
||||||
|
inline int wp_points_to_pixels(int points, int dpi) {
|
||||||
|
if (points <= 0) points = 1;
|
||||||
|
if (dpi <= 0) dpi = WP_SCREEN_DPI;
|
||||||
|
int px = (points * dpi + 36) / 72;
|
||||||
|
return px > 0 ? px : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int wp_screen_font_pixels(int points) {
|
||||||
|
return wp_points_to_pixels(points, WP_SCREEN_DPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int wp_print_font_pixels(int points) {
|
||||||
|
return wp_points_to_pixels(points, WP_PRINT_DPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int wp_scale_layout_units(int units, int dpi) {
|
||||||
|
if (dpi <= 0) dpi = WP_SCREEN_DPI;
|
||||||
|
int scaled = (units * dpi + (units >= 0 ? WP_SCREEN_DPI / 2 : -WP_SCREEN_DPI / 2)) / WP_SCREEN_DPI;
|
||||||
|
return scaled;
|
||||||
|
}
|
||||||
|
|
||||||
struct WpSpecialCharOption {
|
struct WpSpecialCharOption {
|
||||||
uint8_t code;
|
uint8_t code;
|
||||||
const char* label;
|
const char* label;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct WpDividerOption {
|
||||||
|
uint8_t type;
|
||||||
|
const char* label;
|
||||||
|
};
|
||||||
|
|
||||||
inline constexpr WpSpecialCharOption WP_SPECIAL_CHAR_OPTIONS[] = {
|
inline constexpr WpSpecialCharOption WP_SPECIAL_CHAR_OPTIONS[] = {
|
||||||
{ 0xA7, "Section" },
|
{ 0xA7, "Section" },
|
||||||
{ 0xB6, "Pilcrow" },
|
{ 0xB6, "Pilcrow" },
|
||||||
|
{ 0x96, "En Dash" },
|
||||||
|
{ 0x97, "Em Dash" },
|
||||||
{ 0xA9, "Copyright" },
|
{ 0xA9, "Copyright" },
|
||||||
{ 0xAE, "Registered" },
|
{ 0xAE, "Registered" },
|
||||||
{ 0xB0, "Degree" },
|
{ 0xB0, "Degree" },
|
||||||
{ 0xB1, "Plus/Minus" },
|
{ 0xB1, "Plus/Minus" },
|
||||||
};
|
};
|
||||||
static constexpr int WP_SPECIAL_CHAR_OPTION_COUNT = 6;
|
static constexpr int WP_SPECIAL_CHAR_OPTION_COUNT = 8;
|
||||||
|
|
||||||
enum ParagraphAlign : uint8_t {
|
enum ParagraphAlign : uint8_t {
|
||||||
PARA_ALIGN_LEFT = 0,
|
PARA_ALIGN_LEFT = 0,
|
||||||
@@ -109,6 +142,28 @@ enum ParagraphListType : uint8_t {
|
|||||||
PARA_LIST_NUMBER = 2,
|
PARA_LIST_NUMBER = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum ParagraphDividerType : uint8_t {
|
||||||
|
PARA_DIVIDER_NONE = 0,
|
||||||
|
PARA_DIVIDER_SINGLE = 1,
|
||||||
|
PARA_DIVIDER_DOUBLE = 2,
|
||||||
|
PARA_DIVIDER_DOTTED = 3,
|
||||||
|
PARA_DIVIDER_DASHED = 4,
|
||||||
|
PARA_DIVIDER_HEAVY = 5,
|
||||||
|
PARA_DIVIDER_THIN_THICK = 6,
|
||||||
|
PARA_DIVIDER_THICK_THIN = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline constexpr WpDividerOption WP_DIVIDER_OPTIONS[] = {
|
||||||
|
{ PARA_DIVIDER_SINGLE, "Single Line" },
|
||||||
|
{ PARA_DIVIDER_DOUBLE, "Double Line" },
|
||||||
|
{ PARA_DIVIDER_DOTTED, "Dotted Line" },
|
||||||
|
{ PARA_DIVIDER_DASHED, "Dashed Line" },
|
||||||
|
{ PARA_DIVIDER_HEAVY, "Heavy Line" },
|
||||||
|
{ PARA_DIVIDER_THIN_THICK, "Thin/Thick Rule" },
|
||||||
|
{ PARA_DIVIDER_THICK_THIN, "Thick/Thin Rule" },
|
||||||
|
};
|
||||||
|
static constexpr int WP_DIVIDER_OPTION_COUNT = 7;
|
||||||
|
|
||||||
struct WPFontTable {
|
struct WPFontTable {
|
||||||
TrueTypeFont* fonts[FONT_COUNT][4];
|
TrueTypeFont* fonts[FONT_COUNT][4];
|
||||||
bool loaded;
|
bool loaded;
|
||||||
@@ -134,6 +189,7 @@ struct WrapLine {
|
|||||||
int width;
|
int width;
|
||||||
int paragraph_idx;
|
int paragraph_idx;
|
||||||
int list_number;
|
int list_number;
|
||||||
|
uint8_t divider_type;
|
||||||
bool first_in_paragraph;
|
bool first_in_paragraph;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -141,7 +197,7 @@ struct ParagraphStyle {
|
|||||||
uint8_t align;
|
uint8_t align;
|
||||||
uint8_t list_type;
|
uint8_t list_type;
|
||||||
uint8_t line_spacing;
|
uint8_t line_spacing;
|
||||||
uint8_t _pad;
|
uint8_t divider_type;
|
||||||
int16_t left_indent;
|
int16_t left_indent;
|
||||||
int16_t first_line_indent;
|
int16_t first_line_indent;
|
||||||
int16_t space_before;
|
int16_t space_before;
|
||||||
@@ -277,6 +333,7 @@ struct WordProcessorState {
|
|||||||
int wrap_line_cap;
|
int wrap_line_cap;
|
||||||
bool wrap_dirty;
|
bool wrap_dirty;
|
||||||
int last_wrap_width;
|
int last_wrap_width;
|
||||||
|
int last_wrap_dpi;
|
||||||
|
|
||||||
ParagraphStyle paragraphs[WP_MAX_PARAGRAPHS];
|
ParagraphStyle paragraphs[WP_MAX_PARAGRAPHS];
|
||||||
int paragraph_count;
|
int paragraph_count;
|
||||||
@@ -298,6 +355,7 @@ struct WordProcessorState {
|
|||||||
bool font_dropdown_open;
|
bool font_dropdown_open;
|
||||||
bool size_dropdown_open;
|
bool size_dropdown_open;
|
||||||
bool line_spacing_dropdown_open;
|
bool line_spacing_dropdown_open;
|
||||||
|
bool divider_flyout_open;
|
||||||
bool special_char_flyout_open;
|
bool special_char_flyout_open;
|
||||||
|
|
||||||
UndoSnapshot undo[WP_UNDO_MAX];
|
UndoSnapshot undo[WP_UNDO_MAX];
|
||||||
@@ -362,9 +420,10 @@ void wp_adjust_paragraph_spacing_after(WordProcessorState* wp, int delta);
|
|||||||
void wp_cycle_line_spacing(WordProcessorState* wp);
|
void wp_cycle_line_spacing(WordProcessorState* wp);
|
||||||
void wp_set_line_spacing(WordProcessorState* wp, int value);
|
void wp_set_line_spacing(WordProcessorState* wp, int value);
|
||||||
void wp_toggle_list(WordProcessorState* wp, uint8_t list_type);
|
void wp_toggle_list(WordProcessorState* wp, uint8_t list_type);
|
||||||
|
void wp_insert_divider(WordProcessorState* wp, uint8_t divider_type);
|
||||||
void wp_delete_selection(WordProcessorState* wp);
|
void wp_delete_selection(WordProcessorState* wp);
|
||||||
|
|
||||||
void wp_recompute_wrap(WordProcessorState* wp, int content_w);
|
void wp_recompute_wrap(WordProcessorState* wp, int content_w, int layout_dpi);
|
||||||
int wp_find_wrap_line(WordProcessorState* wp, int abs_pos);
|
int wp_find_wrap_line(WordProcessorState* wp, int abs_pos);
|
||||||
int wp_wrap_line_start(WordProcessorState* wp, int line_idx);
|
int wp_wrap_line_start(WordProcessorState* wp, int line_idx);
|
||||||
void wp_ensure_cursor_visible(WordProcessorState* wp, int view_h);
|
void wp_ensure_cursor_visible(WordProcessorState* wp, int view_h);
|
||||||
|
|||||||
Reference in New Issue
Block a user