PicoVector: Refactor text multiline support.

Drop dependence on null terminated strings, and for a final linebreak.

Bound all text processing using the text length.
pull/1064/head
Phil Howard 2024-10-09 15:06:51 +01:00
rodzic 983370f916
commit 089e5df243
5 zmienionych plików z 184 dodań i 57 usunięć

Wyświetl plik

@ -98,14 +98,14 @@ typedef struct {
float line_height; // spacing between lines (%)
float letter_spacing; // spacing between characters (%)
float word_spacing; // spacing between words (%)
af_align_t align; // horizontal and vertical alignment
unsigned int align; // horizontal and vertical alignment
pp_mat3_t *transform; // arbitrary transformation
} af_text_metrics_t;
bool af_load_font_file(AF_FILE file, af_face_t *face);
void af_render_character(af_face_t *face, const char codepoint, af_text_metrics_t *tm);
void af_render(af_face_t *face, const char *text, af_text_metrics_t *tm);
pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm);
void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm);
pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm);
#ifdef AF_USE_PRETTY_POLY
#endif
@ -240,10 +240,9 @@ void af_render_character(af_face_t *face, const char c, af_text_metrics_t *tm) {
af_render_glyph(glyph, tm);
}
int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) {
int get_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) {
int line_width = 0;
char *end = strchr(text, '\n');
if (!end) end = (char *)text + strlen(text);
char *end = (char *)text + tlen;
for(char c = *text; text < end; text++, c = *text) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
@ -259,29 +258,44 @@ int get_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) {
return line_width;
}
int get_max_line_width(af_face_t *face, const char *text, af_text_metrics_t *tm) {
int max_width = 0;
size_t line_length(const char *text, const char *end) {
if(text >= end) return 0;
char *end = strchr(text, '\n');
while(end) {
int width = get_line_width(face, text, tm);
char *line_ending = (char *)memchr(text, '\n', end - text);
if(line_ending == NULL || line_ending > end) {
line_ending = (char *)end;
}
return line_ending - text;
}
int get_max_line_width(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) {
int max_width = 0;
char *line = (char *)text;
char *tend = line + tlen;
size_t line_len = line_length(line, tend);
while(line_len) {
int width = get_line_width(face, line, line_len, tm);
max_width = max_width < width ? width : max_width;
text = end + 1;
end = strchr(text, '\n');
line += line_len + 1;
line_len = line_length(line, tend);
}
return max_width;
}
void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) {
char *line = (char *)text;
char *tend = line + tlen;
size_t line_len = 0;
pp_mat3_t *old = pp_transform(NULL);
float line_height = (tm->line_height * 128.0f) / 100.0f;
float scale = tm->size / 128.0f;
// find maximum line length
int max_line_width = get_max_line_width(face, text, tm);
struct {
float x, y;
} caret;
@ -289,14 +303,17 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t
caret.x = 0;
caret.y = 0;
char *done = (char *)text + tlen;
char *end = strchr(text, '\n');
if (!end) end = done;
// find maximum line length
int max_line_width = get_max_line_width(face, text, tlen, tm);
while(true) {
int line_width = get_line_width(face, text, tm);
line_len = line_length(line, tend);
for(char c = *text; text < end; text++, c = *text) {
while(line_len) {
char *end = line + line_len;
int line_width = get_line_width(face, line, line_len, tm);
for(char c = *line; line < end; line++, c = *line) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
continue;
@ -306,11 +323,11 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t
pp_mat3_scale(&caret_transform, scale, scale);
pp_mat3_translate(&caret_transform, caret.x, caret.y);
if(tm->align == AF_H_ALIGN_CENTER) {
if(tm->align & AF_H_ALIGN_CENTER) {
pp_mat3_translate(&caret_transform, (max_line_width - line_width) / 2, 0);
}
if(tm->align == AF_H_ALIGN_RIGHT) {
if(tm->align & AF_H_ALIGN_RIGHT) {
pp_mat3_translate(&caret_transform, (max_line_width - line_width), 0);
}
@ -326,10 +343,8 @@ void af_render(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t
}
text = end + 1;
if (*text == '\0' || text > done) break;
end = strchr(text, '\n');
if (!end) end = (char *)text + tlen;
line += 1; // Skip over \n
line_len = line_length(line, tend);
caret.x = 0;
caret.y += line_height;
@ -344,26 +359,75 @@ void _af_render(af_face_t *face, const char *text, af_text_metrics_t *tm) {
af_render(face, text, strlen(text), tm);
}
pp_rect_t af_measure(af_face_t *face, const char *text, af_text_metrics_t *tm) {
pp_rect_t af_measure(af_face_t *face, const char *text, size_t tlen, af_text_metrics_t *tm) {
pp_rect_t result;
bool first = true;
pp_mat3_t t = *tm->transform;
char *line = (char *)text;
char *tend = line + tlen;
size_t line_len = 0;
for(size_t i = 0; i < strlen(text); i++) {
af_glyph_t *glyph = find_glyph(face, text[i]);
if(!glyph) {
continue;
}
pp_rect_t r = {glyph->x, glyph->y, glyph->x + glyph->w, glyph->y + glyph->h};
r = pp_rect_transform(&r, &t);
pp_mat3_translate(&t, glyph->advance, 0);
float line_height = (tm->line_height * 128.0f) / 100.0f;
float scale = tm->size / 128.0f;
struct {
float x, y;
} caret;
caret.x = 0;
caret.y = 0;
// find maximum line length
int max_line_width = get_max_line_width(face, text, tlen, tm);
line_len = line_length(line, tend);
while(line_len) {
char *end = line + line_len;
int line_width = get_line_width(face, line, line_len, tm);
for(char c = *line; line < end; line++, c = *line) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
continue;
}
pp_mat3_t caret_transform = *tm->transform;
pp_mat3_scale(&caret_transform, scale, scale);
pp_mat3_translate(&caret_transform, caret.x, caret.y);
if(tm->align & AF_H_ALIGN_CENTER) {
pp_mat3_translate(&caret_transform, (max_line_width - line_width) / 2, 0);
}
if(tm->align & AF_H_ALIGN_RIGHT) {
pp_mat3_translate(&caret_transform, (max_line_width - line_width), 0);
}
pp_rect_t r = {glyph->x, glyph->y, glyph->x + glyph->w, glyph->y + glyph->h};
//pp_rect_t r = af_glyph_bounds(glyph, tm);
r = pp_rect_transform(&r, &caret_transform);
if(first) {
result = r;
first = false;
} else {
result = pp_rect_merge(&result, &r);
}
if(c == L' ') {
caret.x += (glyph->advance * tm->word_spacing) / 100.0f;
} else {
caret.x += (glyph->advance * tm->letter_spacing) / 100.0f;
}
if(first) {
result = r;
first = false;
}else{
result = pp_rect_merge(&result, &r);
}
line += 1; // Skip over \n
line_len = line_length(line, tend);
caret.x = 0;
caret.y += line_height;
}
return result;

Wyświetl plik

@ -79,6 +79,15 @@ namespace pimoroni {
text_metrics.line_height = font_line_height;
}
void set_font_align(unsigned int font_align) {
text_metrics.align = font_align;
}
pp_rect_t measure_text(std::string_view text, pp_mat3_t *t) {
text_metrics.transform = t;
return af_measure(text_metrics.face, text.data(), text.size(), &text_metrics);
}
bool set_font(std::string_view font_path, unsigned int font_size) {
if(text_metrics.face) {
af_free(text_metrics.face->glyphs);

Wyświetl plik

@ -80,11 +80,13 @@ MP_DEFINE_CONST_OBJ_TYPE(
/* PicoVector */
static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_text_obj, 4, VECTOR_text);
static MP_DEFINE_CONST_FUN_OBJ_KW(VECTOR_measure_text_obj, 2, VECTOR_measure_text);
static MP_DEFINE_CONST_FUN_OBJ_3(VECTOR_set_font_obj, VECTOR_set_font);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_size_obj, VECTOR_set_font_size);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_word_spacing_obj, VECTOR_set_font_word_spacing);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_letter_spacing_obj, VECTOR_set_font_letter_spacing);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_line_height_obj, VECTOR_set_font_line_height);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_font_align_obj, VECTOR_set_font_align);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_antialiasing_obj, VECTOR_set_antialiasing);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_transform_obj, VECTOR_set_transform);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip);
@ -92,15 +94,18 @@ static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_set_clip_obj, VECTOR_set_clip);
static MP_DEFINE_CONST_FUN_OBJ_2(VECTOR_draw_obj, VECTOR_draw);
static const mp_rom_map_elem_t VECTOR_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) },
{ MP_ROM_QSTR(MP_QSTR_measure_text), MP_ROM_PTR(&VECTOR_measure_text_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font), MP_ROM_PTR(&VECTOR_set_font_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font_size), MP_ROM_PTR(&VECTOR_set_font_size_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font_word_spacing), MP_ROM_PTR(&VECTOR_set_font_word_spacing_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font_letter_spacing), MP_ROM_PTR(&VECTOR_set_font_letter_spacing_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font_line_height), MP_ROM_PTR(&VECTOR_set_font_line_height_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_font_align), MP_ROM_PTR(&VECTOR_set_font_align_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_antialiasing), MP_ROM_PTR(&VECTOR_set_antialiasing_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_transform), MP_ROM_PTR(&VECTOR_set_transform_obj) },
{ MP_ROM_QSTR(MP_QSTR_set_clip), MP_ROM_PTR(&VECTOR_set_clip_obj) },
{ MP_ROM_QSTR(MP_QSTR_text), MP_ROM_PTR(&VECTOR_text_obj) },
{ MP_ROM_QSTR(MP_QSTR_draw), MP_ROM_PTR(&VECTOR_draw_obj) },
};
@ -127,6 +132,13 @@ static const mp_map_elem_t VECTOR_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_ANTIALIAS_X16), MP_ROM_INT(2) },
{ MP_ROM_QSTR(MP_QSTR_ANTIALIAS_FAST), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_ANTIALIAS_BEST), MP_ROM_INT(2) },
{ MP_ROM_QSTR(MP_QSTR_HALIGN_LEFT), MP_ROM_INT(0) },
{ MP_ROM_QSTR(MP_QSTR_HALIGN_CENTER), MP_ROM_INT(1) },
{ MP_ROM_QSTR(MP_QSTR_HALIGN_RIGHT), MP_ROM_INT(2) },
{ MP_ROM_QSTR(MP_QSTR_VALIGN_TOP), MP_ROM_INT(8) },
{ MP_ROM_QSTR(MP_QSTR_VALIGN_MIDDLE), MP_ROM_INT(16) },
{ MP_ROM_QSTR(MP_QSTR_VALIGN_BOTTOM), MP_ROM_INT(32) },
};
static MP_DEFINE_CONST_DICT(mp_module_VECTOR_globals, VECTOR_globals_table);

Wyświetl plik

@ -572,7 +572,6 @@ mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in) {
mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
int font_size = mp_obj_get_int(size);
(void)font_size;
@ -591,7 +590,6 @@ mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) {
mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
int font_size = mp_obj_get_int(size);
self->vector->set_font_size(font_size);
@ -600,7 +598,6 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) {
mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t spacing) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
self->vector->set_font_word_spacing(mp_obj_get_int(spacing));
return mp_const_none;
@ -608,7 +605,6 @@ mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t spacing) {
mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t spacing) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
self->vector->set_font_letter_spacing(mp_obj_get_int(spacing));
return mp_const_none;
@ -616,15 +612,66 @@ mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t spacing) {
mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t spacing) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
self->vector->set_font_line_height(mp_obj_get_int(spacing));
return mp_const_none;
}
mp_obj_t VECTOR_set_font_align(mp_obj_t self_in, mp_obj_t align) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
self->vector->set_font_align(mp_obj_get_int(align));
return mp_const_none;
}
mp_obj_t VECTOR_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
enum { ARG_self, ARG_text, ARG_x, ARG_y, ARG_angle };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_text, MP_ARG_REQUIRED | MP_ARG_OBJ },
{ MP_QSTR_x, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_y, MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} }
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t);
mp_obj_t text_obj = args[ARG_text].u_obj;
if(!mp_obj_is_str_or_bytes(text_obj)) mp_raise_TypeError("text: string required");
GET_STR_DATA_LEN(text_obj, str, str_len);
const std::string_view t((const char *)str, str_len);
int x = args[ARG_x].u_int;
int y = args[ARG_y].u_int;
pp_mat3_t tt = pp_mat3_identity();
if(args[ARG_angle].u_obj != mp_const_none) {
pp_mat3_rotate(&tt, mp_obj_get_float(args[ARG_angle].u_obj));
}
pp_mat3_translate(&tt, (float)x, (float)y);
pp_rect_t bounds = self->vector->measure_text(t, &tt);
// TODO: Should probably add the transformations available to text here?
mp_obj_t tuple[4];
tuple[0] = mp_picovector_set_point_type((int)(bounds.x));
tuple[1] = mp_picovector_set_point_type((int)(bounds.y));
tuple[2] = mp_picovector_set_point_type((int)(bounds.w));
tuple[3] = mp_picovector_set_point_type((int)(bounds.h));
return mp_obj_new_tuple(4, tuple);
}
mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in) {
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(self_in, _VECTOR_obj_t);
(void)self;
picovector_point_type x = self->vector->graphics->bounds.x;
picovector_point_type y = self->vector->graphics->bounds.y;
@ -668,7 +715,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
_VECTOR_obj_t *self = MP_OBJ_TO_PTR2(args[ARG_self].u_obj, _VECTOR_obj_t);
(void)self;
mp_obj_t text_obj = args[ARG_text].u_obj;
@ -680,8 +726,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
int x = args[ARG_x].u_int;
int y = args[ARG_y].u_int;
(void)x;
(void)y;
pp_mat3_t tt = pp_mat3_identity();
@ -690,10 +734,6 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
}
pp_mat3_translate(&tt, (float)x, (float)y);
//pp_mat3_mul(&tt, _pp_transform);
//mp_printf(&mp_plat_print, "self->vector->text()\n");
//__printf_debug_flush();
self->vector->text(t, &tt);

Wyświetl plik

@ -34,11 +34,13 @@ extern mp_obj_t TRANSFORM_reset(mp_obj_t self_in);
extern mp_obj_t VECTOR_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args);
extern mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t VECTOR_measure_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
extern mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size);
extern mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size);
extern mp_obj_t VECTOR_set_font_word_spacing(mp_obj_t self_in, mp_obj_t size);
extern mp_obj_t VECTOR_set_font_letter_spacing(mp_obj_t self_in, mp_obj_t size);
extern mp_obj_t VECTOR_set_font_line_height(mp_obj_t self_in, mp_obj_t size);
extern mp_obj_t VECTOR_set_font_align(mp_obj_t self_in, mp_obj_t align);
extern mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa);
extern mp_obj_t VECTOR_set_transform(mp_obj_t self_in, mp_obj_t transform_in);
extern mp_obj_t VECTOR_set_clip(mp_obj_t self_in, mp_obj_t clip_in);