From cfe8b3c0969b0ddef1134e2e29a286cc6aa4b298 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Thu, 17 Aug 2023 13:46:13 +0100 Subject: [PATCH] PicoVector: Text rotation support. --- libraries/pico_vector/alright_fonts.cpp | 30 ++++++++ libraries/pico_vector/alright_fonts.hpp | 1 + libraries/pico_vector/pico_vector.cpp | 76 +++++++++++++++++++ libraries/pico_vector/pico_vector.hpp | 1 + micropython/modules/picovector/picovector.cpp | 11 ++- 5 files changed, 116 insertions(+), 3 deletions(-) diff --git a/libraries/pico_vector/alright_fonts.cpp b/libraries/pico_vector/alright_fonts.cpp index 071f53dc..9a6a9131 100644 --- a/libraries/pico_vector/alright_fonts.cpp +++ b/libraries/pico_vector/alright_fonts.cpp @@ -41,6 +41,36 @@ namespace alright_fonts { } } + void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat3_t transform) { + if(tm.face.glyphs.count(codepoint) == 1) { + glyph_t glyph = tm.face.glyphs[codepoint]; + + // scale is a fixed point 16:16 value, our font data is already scaled to + // -128..127 so to get the pixel size we want we can just shift the + // users requested size up one bit + unsigned scale = tm.size << 9; + + std::vector> contours; + + for(auto i = 0u; i < glyph.contours.size(); i++) { + unsigned int count = glyph.contours[i].count; + point_t *points = (point_t *)malloc(sizeof(point_t) * count); + for(auto j = 0u; j < count; j++) { + point_t point(glyph.contours[i].points[j].x, glyph.contours[i].points[j].y); + point *= transform; + points[j] = point_t(point.x, point.y); + } + contours.emplace_back(points, count); + } + + pretty_poly::draw_polygon(contours, origin, scale); + + for(auto contour : contours) { + free(contour.points); + } + } + } + /* load functions */ diff --git a/libraries/pico_vector/alright_fonts.hpp b/libraries/pico_vector/alright_fonts.hpp index 4882beba..0bff4629 100644 --- a/libraries/pico_vector/alright_fonts.hpp +++ b/libraries/pico_vector/alright_fonts.hpp @@ -70,4 +70,5 @@ namespace alright_fonts { */ void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin); + void render_character(text_metrics_t &tm, uint16_t codepoint, pretty_poly::point_t origin, pretty_poly::mat3_t transform); } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.cpp b/libraries/pico_vector/pico_vector.cpp index 1cc318db..0e13d60d 100644 --- a/libraries/pico_vector/pico_vector.cpp +++ b/libraries/pico_vector/pico_vector.cpp @@ -109,4 +109,80 @@ namespace pimoroni { return Point(caret.x, caret.y); } + + Point PicoVector::text(std::string_view text, Point origin, float angle) { + // TODO: Normalize types somehow, so we're not converting? + pretty_poly::point_t caret(0, 0); + + // Prepare a transformation matrix for character and offset rotation + angle = 2 * M_PI * (angle / 360.0f); + pretty_poly::mat3_t transform = pretty_poly::mat3_t::rotation(angle); + + // Align text from the bottom left + caret.y += text_metrics.line_height; + caret *= transform; + + pretty_poly::point_t space; + pretty_poly::point_t carriage_return(0, text_metrics.line_height); + + space.x = alright_fonts::measure_character(text_metrics, ' ').w; + if (space.x == 0) { + space.x = text_metrics.word_spacing; + } + + space *= transform; + carriage_return *= transform; + + size_t i = 0; + + while(i < text.length()) { + size_t next_space = text.find(' ', i + 1); + + if(next_space == std::string::npos) { + next_space = text.length(); + } + + size_t next_linebreak = text.find('\n', i + 1); + + if(next_linebreak == std::string::npos) { + next_linebreak = text.length(); + } + + size_t next_break = std::min(next_space, next_linebreak); + + uint16_t word_width = 0; + for(size_t j = i; j < next_break; j++) { + word_width += alright_fonts::measure_character(text_metrics, text[j]).w; + word_width += text_metrics.letter_spacing; + } + + if(caret.x != 0 && caret.x + word_width > graphics->clip.w) { + caret -= carriage_return; + carriage_return.x = 0; + } + + for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) { + if (text[j] == '\n') { // Linebreak + caret -= carriage_return; + carriage_return.x = 0; + } else if (text[j] == ' ') { // Space + caret += space; + carriage_return += space; + } else { + alright_fonts::render_character(text_metrics, text[j], pretty_poly::point_t(origin.x + caret.x, origin.y + caret.y), transform); + } + pretty_poly::point_t advance( + alright_fonts::measure_character(text_metrics, text[j]).w + text_metrics.letter_spacing, + 0 + ); + advance *= transform; + caret += advance; + carriage_return += advance; + } + + i = next_break + 1; + } + + return Point(caret.x, caret.y); + } } \ No newline at end of file diff --git a/libraries/pico_vector/pico_vector.hpp b/libraries/pico_vector/pico_vector.hpp index 11ccebd3..c4450fd4 100644 --- a/libraries/pico_vector/pico_vector.hpp +++ b/libraries/pico_vector/pico_vector.hpp @@ -70,6 +70,7 @@ namespace pimoroni { void translate(pretty_poly::contour_t &contour, Point translation); Point text(std::string_view text, Point origin); + Point text(std::string_view text, Point origin, float angle); void polygon(std::vector> contours, Point origin = Point(0, 0), int scale=65536); diff --git a/micropython/modules/picovector/picovector.cpp b/micropython/modules/picovector/picovector.cpp index c3157246..a442027b 100644 --- a/micropython/modules/picovector/picovector.cpp +++ b/micropython/modules/picovector/picovector.cpp @@ -325,12 +325,13 @@ mp_obj_t VECTOR_set_antialiasing(mp_obj_t self_in, mp_obj_t aa) { } mp_obj_t VECTOR_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 }; + 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_REQUIRED | MP_ARG_INT }, - { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT } + { MP_QSTR_y, MP_ARG_REQUIRED | MP_ARG_INT }, + { MP_QSTR_angle, MP_ARG_OBJ, {.u_obj = mp_const_none} } }; mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -349,7 +350,11 @@ 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; - self->vector->text(t, Point(x, y)); + if(args[ARG_angle].u_obj == mp_const_none) { + self->vector->text(t, Point(x, y)); + } else { + self->vector->text(t, Point(x, y), mp_obj_get_float(args[ARG_angle].u_obj)); + } return mp_const_none; }