diff --git a/libraries/pico_vector/alright-fonts.h b/libraries/pico_vector/alright-fonts.h index 03166bd1..a773c810 100644 --- a/libraries/pico_vector/alright-fonts.h +++ b/libraries/pico_vector/alright-fonts.h @@ -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; diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index c982dd0e..2e88d875 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -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); diff --git a/micropython/modules/picovector/picovector.c b/micropython/modules/picovector/picovector.c index bc87a0dd..5e025c65 100644 --- a/micropython/modules/picovector/picovector.c +++ b/micropython/modules/picovector/picovector.c @@ -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); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index fec61fc0..9b82f143 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -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); diff --git a/micropython/modules/picovector/picovector.h b/micropython/modules/picovector/picovector.h index 9eb46fad..cff66761 100644 --- a/micropython/modules/picovector/picovector.h +++ b/micropython/modules/picovector/picovector.h @@ -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);