Skip to content

Commit

Permalink
PicoVector: Refactor text multiline support.
Browse files Browse the repository at this point in the history
Drop dependence on null terminated strings, and for a final linebreak.

Bound all text processing using the text length.
  • Loading branch information
Gadgetoid committed Oct 9, 2024
1 parent 362b9ef commit 51645da
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 57 deletions.
150 changes: 107 additions & 43 deletions libraries/pico_vector/alright-fonts.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -259,44 +258,62 @@ 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) {
size_t line_length(const char *text, const char *end) {
if(text >= end) return 0;

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;

char *end = strchr(text, '\n');
while(end) {
int width = get_line_width(face, text, tm);
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;

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);

line_len = line_length(line, tend);

while(line_len) {
char *end = line + line_len;

while(true) {
int line_width = get_line_width(face, text, tm);
int line_width = get_line_width(face, line, line_len, tm);

for(char c = *text; text < end; text++, c = *text) {
for(char c = *line; line < end; line++, c = *line) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
continue;
Expand All @@ -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);
}

Expand All @@ -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;
Expand All @@ -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;

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;
}

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);

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;
Expand Down
9 changes: 9 additions & 0 deletions libraries/pico_vector/pico_vector.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
14 changes: 13 additions & 1 deletion micropython/modules/picovector/picovector.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,32 @@ 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);

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) },
};
Expand All @@ -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);
Expand Down
Loading

0 comments on commit 51645da

Please sign in to comment.