128 lines
5.5 KiB
C++
128 lines
5.5 KiB
C++
#include <lvgl.h>
|
|
#include <cassert>
|
|
#include <climits>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
#include "ui-touch/ChatHashtag.h"
|
|
|
|
static uint32_t COLOR_CHAT_LINK = 0x4EA1FF;
|
|
static size_t layout_calls = 0;
|
|
static uint16_t trackedVisualPos(const char* text, char** visual, uint16_t length,
|
|
lv_base_dir_t dir, uint32_t logical, bool* rtl) {
|
|
++layout_calls;
|
|
return _lv_bidi_get_visual_pos(text, visual, length, dir, logical, rtl);
|
|
}
|
|
#define _lv_bidi_get_visual_pos trackedVisualPos
|
|
#include "implementation.inc"
|
|
#undef _lv_bidi_get_visual_pos
|
|
|
|
struct DrawnGlyph {
|
|
lv_point_t pos;
|
|
uint32_t letter;
|
|
};
|
|
static std::vector<DrawnGlyph> drawn;
|
|
static void captureLetter(lv_draw_ctx_t*, const lv_draw_label_dsc_t*, const lv_point_t* pos, uint32_t letter) {
|
|
drawn.push_back({*pos, letter});
|
|
}
|
|
static void flushDisplay(lv_disp_drv_t* driver, const lv_area_t*, lv_color_t*) {
|
|
lv_disp_flush_ready(driver);
|
|
}
|
|
|
|
static void check(const char* text, lv_base_dir_t dir, lv_text_align_t align,
|
|
lv_coord_t width, lv_coord_t letter_space, const char* expected_links) {
|
|
lv_obj_t* parent = lv_obj_create(lv_scr_act());
|
|
lv_obj_remove_style_all(parent);
|
|
lv_obj_set_size(parent, 320, 800);
|
|
lv_obj_t* plain = lv_label_create(parent);
|
|
lv_obj_set_style_text_font(plain, &lv_font_montserrat_14, LV_PART_MAIN);
|
|
lv_obj_set_style_base_dir(plain, dir, LV_PART_MAIN);
|
|
lv_obj_set_style_text_align(plain, align, LV_PART_MAIN);
|
|
lv_obj_set_style_text_letter_space(plain, letter_space, LV_PART_MAIN);
|
|
lv_obj_set_style_text_line_space(plain, 3, LV_PART_MAIN);
|
|
lv_obj_set_width(plain, width);
|
|
lv_label_set_text(plain, text);
|
|
lv_obj_t* overlay = chatCreateLinkLabel(parent, plain, text, &lv_font_montserrat_14,
|
|
lv_color_white(), width, 0, 0);
|
|
assert(overlay);
|
|
lv_obj_update_layout(parent);
|
|
assert(lv_obj_get_height(overlay) == lv_obj_get_height(plain));
|
|
assert(lv_obj_get_style_text_opa(plain, LV_PART_MAIN) == LV_OPA_COVER);
|
|
|
|
lv_area_t area;
|
|
lv_obj_get_content_coords(plain, &area);
|
|
lv_area_t clip = {0, 0, 319, 799};
|
|
lv_draw_ctx_t ctx{};
|
|
ctx.clip_area = &clip;
|
|
ctx.draw_letter = captureLetter;
|
|
lv_draw_label_dsc_t dsc;
|
|
lv_draw_label_dsc_init(&dsc);
|
|
dsc.font = &lv_font_montserrat_14;
|
|
dsc.bidi_dir = dir;
|
|
dsc.align = align;
|
|
dsc.letter_space = letter_space;
|
|
dsc.line_space = 3;
|
|
drawn.clear();
|
|
lv_draw_label(&ctx, &dsc, &area, lv_label_get_text(plain), nullptr);
|
|
const auto normal = drawn;
|
|
drawn.clear();
|
|
lv_event_send(overlay, LV_EVENT_DRAW_MAIN_END, &ctx);
|
|
assert(!drawn.empty());
|
|
size_t expected_count = _lv_txt_encoded_get_char_id(expected_links, strlen(expected_links));
|
|
assert(drawn.size() == expected_count);
|
|
for (const auto& glyph : drawn) {
|
|
bool matched = false;
|
|
for (const auto& original : normal) {
|
|
if (glyph.letter == original.letter && glyph.pos.x == original.pos.x && glyph.pos.y == original.pos.y)
|
|
matched = true;
|
|
}
|
|
if (!matched) std::fprintf(stderr, "glyph U+%04X at %d,%d not in LVGL output: %s\n",
|
|
glyph.letter, glyph.pos.x, glyph.pos.y, text);
|
|
assert(matched);
|
|
}
|
|
uint32_t byte = 0;
|
|
for (const auto& glyph : drawn)
|
|
assert(glyph.letter == _lv_txt_encoded_next(expected_links, &byte));
|
|
const size_t calls_after_layout = layout_calls;
|
|
for (int frame = 0; frame < 10; ++frame) {
|
|
lv_obj_set_y(parent, frame);
|
|
lv_obj_update_layout(parent);
|
|
drawn.clear();
|
|
lv_event_send(overlay, LV_EVENT_DRAW_MAIN_END, &ctx);
|
|
assert(layout_calls == calls_after_layout);
|
|
assert(drawn.size() == expected_count);
|
|
}
|
|
lv_obj_set_style_text_letter_space(plain, letter_space + 1, LV_PART_MAIN);
|
|
lv_obj_update_layout(parent);
|
|
lv_event_send(overlay, LV_EVENT_DRAW_MAIN_END, &ctx);
|
|
assert(layout_calls > calls_after_layout);
|
|
lv_obj_del(parent);
|
|
}
|
|
|
|
int main() {
|
|
lv_init();
|
|
static lv_color_t buffer[320 * 20];
|
|
static lv_disp_draw_buf_t draw_buffer;
|
|
lv_disp_draw_buf_init(&draw_buffer, buffer, nullptr, 320 * 20);
|
|
lv_disp_drv_t driver;
|
|
lv_disp_drv_init(&driver);
|
|
driver.hor_res = 320;
|
|
driver.ver_res = 800;
|
|
driver.draw_buf = &draw_buffer;
|
|
driver.flush_cb = flushDisplay;
|
|
lv_disp_drv_register(&driver);
|
|
check("plain # test #mesh tail", LV_BASE_DIR_LTR, LV_TEXT_ALIGN_LEFT, 240, 0, "#mesh");
|
|
check("plain https://example.com/path#section tail #mesh", LV_BASE_DIR_LTR,
|
|
LV_TEXT_ALIGN_LEFT, 95, 0, "https://example.com/path#section#mesh");
|
|
check("plain #mesh and #radio tail", LV_BASE_DIR_LTR, LV_TEXT_ALIGN_CENTER, 110, 2, "#mesh#radio");
|
|
check("plain 32.123, 34.456 tail", LV_BASE_DIR_LTR, LV_TEXT_ALIGN_RIGHT, 240, 0, "32.123, 34.456");
|
|
const char* rtl = "\xD7\xA9\xD7\x9C\xD7\x95\xD7\x9D #mesh \xD7\xA2\xD7\x95\xD7\x93";
|
|
check(rtl, LV_BASE_DIR_AUTO, LV_TEXT_ALIGN_AUTO, 240, 0, "#mesh");
|
|
check(rtl, LV_BASE_DIR_RTL, LV_TEXT_ALIGN_RIGHT, 90, 2, "#mesh");
|
|
check("\xD7\xA9\xD7\x9C\xD7\x95\xD7\x9D https://example.com/#section tail #mesh",
|
|
LV_BASE_DIR_AUTO, LV_TEXT_ALIGN_AUTO, 110, 0, "https://example.com/#section#mesh");
|
|
check("\xD9\x85\xD8\xB1\xD8\xAD\xD8\xA8\xD8\xA7 #mesh tail", LV_BASE_DIR_AUTO,
|
|
LV_TEXT_ALIGN_AUTO, 240, 0, "#mesh");
|
|
check("plain #mesh tail", LV_BASE_DIR_RTL, LV_TEXT_ALIGN_CENTER, 100, 1, "#mesh");
|
|
std::puts("PASS: link-only glyphs, hashes, LTR/RTL, shaping, wrapping, alignment and scroll cache");
|
|
} |