TEST: PicoVector: alright-fonts bringup.

feature/picovector2-electric-boogaloo
Phil Howard 2024-04-18 12:37:33 +01:00
rodzic 21e9817347
commit 7cd9187d80
6 zmienionych plików z 471 dodań i 43 usunięć

Wyświetl plik

@ -0,0 +1,16 @@
#include <stdlib.h>
#include <stdint.h>
extern "C" {
void* fileio_open(const char* filename);
void fileio_close(void* fhandle);
size_t fileio_read(void* fhandle, void *buf, size_t len);
int fileio_getc(void* fhandle);
size_t fileio_tell(void* fhandle);
size_t fileio_seek(void* fhandle, size_t pos);
}

Wyświetl plik

@ -0,0 +1,5 @@
extern "C" {
void *af_malloc(size_t size);
void *af_realloc(void *p, size_t size);
void af_free(void *p);
}

Wyświetl plik

@ -0,0 +1,361 @@
/*
Alright Fonts 🖍 - a font format for embedded and low resource platforms.
Jonathan Williamson, August 2022
Examples, source, and more: https://github.com/lowfatcode/pretty-poly
MIT License https://github.com/lowfatcode/pretty-poly/blob/main/LICENSE
An easy way to render high quality text in embedded applications running
on resource constrained microcontrollers such as the Cortex M0 and up.
- OTF and TTF support: generate efficient packed fonts easily
- Minimal data: ~4kB (40 bytes per char) for printable ASCII set (Roboto)
- Tunable: trade off file size, contour complexity, and visual quality
- Metrics: advance and bounding box for fast layout
- UTF-8 or ASCII: support for non ASCII like Kanji or Cyrillic
- Fixed scale: coords scaled to ^2 bounds for fast scaling (no divide)
- C17 header only library: simply copy the header file into your project
- Customised font packs: include only the characters you need
- Simple outlines: all paths are simply polylines for easy rendering
- Easy antialiasing: combine with Pretty Poly for quick results!
*/
#ifndef AF_INCLUDE_H
#define AF_INCLUDE_H
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>
#include <stdbool.h>
#include <wchar.h>
#ifdef AF_MALLOC
#ifndef PP_MALLOC
#define PP_MALLOC(size) AF_MALLOC(size)
#define PP_REALLOC(p, size) AF_REALLOC(p, size)
#define PP_FREE(p) AF_FREE(p)
#endif // PP_MALLOC
#endif // AF_MALLOC
#ifndef AF_MALLOC
#define AF_MALLOC(size) malloc(size)
#define AF_REALLOC(p, size) realloc(p, size)
#define AF_FREE(p) free(p)
#endif // AF_MALLOC
#ifndef AF_FILE
#define AF_FILE FILE*
#define AF_FREAD(p, size, nmemb, stream) fread(p, size, nmemb, stream)
#define AF_FGETC(stream) fgetc(stream)
#endif
#include "pretty-poly.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int8_t x, y;
} af_point_t;
pp_point_t af_point_transform(pp_point_t *p, pp_mat3_t *m);
typedef struct {
uint8_t point_count;
af_point_t *points;
} af_path_t;
typedef struct {
char codepoint;
int8_t x, y, w, h;
int8_t advance;
uint8_t path_count;
af_path_t *paths;
} af_glyph_t;
typedef struct {
uint16_t flags;
uint16_t glyph_count;
af_glyph_t *glyphs;
} af_face_t;
typedef enum {
AF_H_ALIGN_LEFT = 0, AF_H_ALIGN_CENTER = 1, AF_H_ALIGN_RIGHT = 2,
AF_H_ALIGN_JUSTIFY = 4,
AF_V_ALIGN_TOP = 8, AF_V_ALIGN_MIDDLE = 16, AF_V_ALIGN_BOTTOM = 32
} af_align_t;
typedef struct {
af_face_t *face; // font
float size; // text size in pixels
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
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, wchar_t codepoint, af_text_metrics_t *tm);
void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm);
pp_rect_t af_measure(af_face_t *face, const wchar_t *text, af_text_metrics_t *tm);
#ifdef AF_USE_PRETTY_POLY
#endif
#ifdef __cplusplus
}
#endif
#ifdef AF_IMPLEMENTATION
/*
helper functions
*/
// big endian file reading helpers
uint16_t ru16(AF_FILE file) {uint8_t w[2]; AF_FREAD((char *) w, 1, 2, file); return (uint16_t)w[0] << 8 | w[1];}
int16_t rs16(AF_FILE file) {uint8_t w[2]; AF_FREAD((char *) w, 1, 2, file); return (uint16_t)w[0] << 8 | w[1];}
uint32_t ru32(AF_FILE file) {uint8_t dw[4]; AF_FREAD((char *)dw, 1, 4, file); return (uint32_t)dw[0] << 24 | (uint32_t)dw[1] << 16 | (uint32_t)dw[2] << 8 | dw[3];}
uint8_t ru8(AF_FILE file) {return AF_FGETC(file);}
int8_t rs8(AF_FILE file) {return AF_FGETC(file);}
bool af_load_font_file(AF_FILE file, af_face_t *face) {
// check header magic bytes are present
char marker[4]; AF_FREAD(marker, 1, 4, file);
if(memcmp(marker, "af!?", 4) != 0) {
return false; // doesn't start with magic marker
}
// extract flags and ensure none set
face->flags = ru16(file);
if(face->flags != 0) {
return false; // unknown flags set
}
// number of glyphs, paths, and points in font
uint16_t glyph_count = ru16(file);
uint16_t path_count = ru16(file);
uint16_t point_count = ru16(file);
// allocate buffer to store font glyph, path, and point data
void *buffer = AF_MALLOC(sizeof(af_glyph_t) * glyph_count + \
sizeof( af_path_t) * path_count + \
sizeof(af_point_t) * point_count);
af_glyph_t *glyphs = (af_glyph_t *) buffer;
af_path_t *paths = ( af_path_t *)(glyphs + (sizeof(af_glyph_t) * glyph_count));
af_point_t *points = (af_point_t *)( paths + (sizeof( af_path_t) * path_count));
// load glyph dictionary
face->glyph_count = glyph_count;
face->glyphs = glyphs;
for(int i = 0; i < glyph_count; i++) {
af_glyph_t *glyph = &face->glyphs[i];
glyph->codepoint = ru16(file);
glyph->x = rs8(file);
glyph->y = rs8(file);
glyph->w = ru8(file);
glyph->h = ru8(file);
glyph->advance = ru8(file);
glyph->path_count = ru8(file);
glyph->paths = paths;
paths += sizeof(af_path_t) * glyph->path_count;
}
// load the glyph paths
for(int i = 0; i < glyph_count; i++) {
af_glyph_t *glyph = &face->glyphs[i];
for(int j = 0; j < glyph->path_count; j++) {
af_path_t *path = &glyph->paths[j];
path->point_count = ru8(file);
path->points = points;
points += sizeof(af_point_t) * path->point_count;
}
}
// load the glyph points
for(int i = 0; i < glyph_count; i++) {
af_glyph_t *glyph = &face->glyphs[i];
for(int j = 0; j < glyph->path_count; j++) {
af_path_t *path = &glyph->paths[j];
for(int k = 0; k < path->point_count; k++) {
af_point_t *point = &path->points[k];
point->x = ru8(file);
point->y = ru8(file);
}
}
}
return true;
}
af_glyph_t *find_glyph(af_face_t *face, wchar_t c) {
for(int i = 0; i < face->glyph_count; i++) {
if(face->glyphs[i].codepoint == c) {
return &face->glyphs[i];
}
}
return NULL;
}
void af_render_glyph(af_glyph_t* glyph, af_text_metrics_t *tm) {
assert(glyph != NULL);
pp_poly_t poly;
poly.count = glyph->path_count;
poly.paths = (pp_path_t *)AF_MALLOC(poly.count * sizeof(pp_path_t));
for(uint32_t i = 0; i < poly.count; i++) {
pp_path_t *path = &poly.paths[i];
path->count = glyph->paths[i].point_count;
path->points = (pp_point_t *)AF_MALLOC(glyph->paths[i].point_count * sizeof(pp_point_t));
for(uint32_t j = 0; j < path->count; j++) {
pp_point_t *point = &path->points[j];
point->x = glyph->paths[i].points[j].x;
point->y = glyph->paths[i].points[j].y;
}
}
pp_render(&poly);
for(uint32_t i = 0; i < poly.count; i++) {
pp_path_t *path = &poly.paths[i];
free(path->points);
}
free(poly.paths);
}
void af_render_character(af_face_t *face, wchar_t c, af_text_metrics_t *tm) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
return;
}
af_render_glyph(glyph, tm);
}
int get_line_width(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) {
int line_width = 0;
wchar_t *end = wcschr(text, L'\n');
for(wchar_t c = *text; text < end; text++, c = *text) {
af_glyph_t *glyph = find_glyph(face, c);
if(!glyph) {
continue;
}
if(c == L' ') {
line_width += (glyph->advance * tm->word_spacing) / 100.0f;
} else {
line_width += (glyph->advance * tm->letter_spacing) / 100.0f;
}
}
return line_width;
}
int get_max_line_width(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) {
int max_width = 0;
wchar_t *end = wcschr(text, L'\n');
while(end) {
int width = get_line_width(face, text, tm);
max_width = max_width < width ? width : max_width;
text = end + 1;
end = wcschr(text, L'\n');
}
return max_width;
}
void af_render(af_face_t *face, wchar_t *text, af_text_metrics_t *tm) {
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;
wchar_t *end = wcschr(text, L'\n');
while(end) {
int line_width = get_line_width(face, text, tm);
for(wchar_t c = *text; text < end; text++, c = *text) {
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_transform(&caret_transform);
af_render_glyph(glyph, tm);
if(c == L' ') {
caret.x += (glyph->advance * tm->word_spacing) / 100.0f;
} else {
caret.x += (glyph->advance * tm->letter_spacing) / 100.0f;
}
}
text = end + 1;
end = wcschr(text, L'\n');
caret.x = 0;
caret.y += line_height;
}
pp_transform(old);
}
pp_rect_t af_measure(af_face_t *face, const wchar_t *text, af_text_metrics_t *tm) {
pp_rect_t result;
bool first = true;
pp_mat3_t t = *tm->transform;
for(size_t i = 0; i < wcslen(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);
}
}
return result;
}
#endif // AF_IMPLEMENTATION
#endif // AF_INCLUDE_H

Wyświetl plik

@ -1,4 +1,5 @@
#define PP_IMPLEMENTATION
#define AF_IMPLEMENTATION
#include "pico_vector.hpp"
#include <vector>
@ -55,7 +56,7 @@ namespace pimoroni {
}
}
pp_point_t PicoVector::text(std::string_view text, pp_point_t offset, pp_mat3_t *t) {
pp_point_t PicoVector::text(std::wstring_view text, pp_point_t offset, pp_mat3_t *t) {
pp_point_t caret = {0, 0};
// Align text from the bottom left
@ -68,7 +69,9 @@ namespace pimoroni {
pp_point_t space;
pp_point_t carriage_return = {0, -(PP_COORD_TYPE)text_metrics.line_height};
space.x = alright_fonts::measure_character(text_metrics, ' ').w;
wchar_t spc = L' ';
space.x = af_measure(text_metrics.face, &spc, &text_metrics).w;
if (space.x == 0) {
space.x = text_metrics.word_spacing;
}
@ -97,7 +100,7 @@ namespace pimoroni {
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 += af_measure(text_metrics.face, &text[j], &text_metrics).w;
word_width += text_metrics.letter_spacing;
}
@ -107,17 +110,22 @@ namespace pimoroni {
}
for(size_t j = i; j < std::min(next_break + 1, text.length()); j++) {
if (text[j] == '\n') { // Linebreak
if (text[j] == L'\n') { // Linebreak
caret = pp_point_sub(&caret, &carriage_return);
carriage_return = initial_carriage_return;
} else if (text[j] == ' ') { // Space
} else if (text[j] == L' ') { // Space
caret = pp_point_add(&caret, &space);
carriage_return = pp_point_add(&carriage_return, &space);
} else {
alright_fonts::render_character(text_metrics, text[j], caret, t);
// apply the caret offset...
pp_mat3_t pos = pp_mat3_identity();
pp_mat3_mul(&pos, t);
pp_mat3_translate(&pos, caret.x, caret.y);
text_metrics.transform = &pos;
af_render_character(text_metrics.face, text[j], &text_metrics);
}
pp_point_t advance = {
(PP_COORD_TYPE)alright_fonts::measure_character(text_metrics, text[j]).w + text_metrics.letter_spacing,
(PP_COORD_TYPE)af_measure(text_metrics.face, (const wchar_t *)text[j], &text_metrics).w + text_metrics.letter_spacing,
(PP_COORD_TYPE)0
};
advance = pp_point_transform(&advance, t);

Wyświetl plik

@ -1,5 +1,21 @@
#include "af-file-io.h"
#include "af-memory.h"
#define AF_FILE void*
#define AF_FREAD(p, size, nmemb, stream) fileio_read(stream, p, nmemb)
#define AF_FGETC(stream) fileio_getc(stream)
#define AF_MALLOC(size) af_malloc(size)
#define AF_REALLOC(p, size) af_realloc(p, size)
#define AF_FREE(p) af_free(p)
#define PP_MALLOC(size) af_malloc(size)
#define PP_REALLOC(p, size) af_realloc(p, size)
#define PP_FREE(p) af_free(p)
#include "pretty-poly.h"
#include "alright_fonts.hpp"
#include "alright-fonts.h"
#include "pico_graphics.hpp"
pp_rect_t pp_contour_bounds(const pp_path_t *c);
@ -12,7 +28,7 @@ namespace pimoroni {
class PicoVector {
private:
static PicoGraphics *graphics;
alright_fonts::text_metrics_t text_metrics;
af_text_metrics_t text_metrics;
static constexpr uint8_t alpha_map[4] {0, 128, 192, 255};
public:
@ -66,18 +82,21 @@ namespace pimoroni {
}
void set_font_size(unsigned int font_size) {
text_metrics.set_size(font_size);
text_metrics.size = font_size;
}
bool set_font(std::string_view font_path, unsigned int font_size) {
bool result = text_metrics.face.load(font_path);
//bool result = text_metrics.face.load(font_path);
void* font = fileio_open(font_path.data());
af_load_font_file(font, text_metrics.face);
bool result = false;
set_font_size(font_size);
return result;
}
pp_point_t text(std::string_view text, pp_point_t origin, pp_mat3_t *t);
pp_point_t text(std::wstring_view text, pp_point_t origin, pp_mat3_t *t);
void transform(pp_path_t *path, pp_mat3_t *t);
void transform(pp_poly_t *poly, pp_mat3_t *t);

Wyświetl plik

@ -29,10 +29,27 @@ typedef struct _PATH_obj_t {
pp_path_t path;
} _PATH_obj_t;
file_io::file_io(std::string_view filename) {
mp_obj_t fn = mp_obj_new_str(filename.data(), (mp_uint_t)filename.size());
void *af_malloc(size_t size) {
mp_printf(&mp_plat_print, "af_malloc %lu\n", size);
mp_event_handle_nowait();
return m_tracked_calloc(sizeof(uint8_t), size);
}
//mp_printf(&mp_plat_print, "Opening file %s\n", filename.data());
void *af_realloc(void *p, size_t size) {
return NULL;
}
void af_free(void *p) {
mp_printf(&mp_plat_print, "af_free\n");
mp_event_handle_nowait();
m_tracked_free(p);
}
void* fileio_open(const char *filename) {
mp_obj_t fn = mp_obj_new_str(filename, (mp_uint_t)strlen(filename));
mp_printf(&mp_plat_print, "Opening file %s\n", filename);
mp_event_handle_nowait();
mp_obj_t args[2] = {
fn,
@ -43,34 +60,39 @@ file_io::file_io(std::string_view filename) {
// example tuple response: (32768, 0, 0, 0, 0, 0, 5153, 1654709815, 1654709815, 1654709815)
mp_obj_t stat = mp_vfs_stat(fn);
mp_obj_tuple_t *tuple = MP_OBJ_TO_PTR2(stat, mp_obj_tuple_t);
filesize = mp_obj_get_int(tuple->items[6]);
int filesize = mp_obj_get_int(tuple->items[6]);
mp_printf(&mp_plat_print, "Size %lu\n", filesize);
mp_obj_t fhandle = mp_vfs_open(MP_ARRAY_SIZE(args), &args[0], (mp_map_t *)&mp_const_empty_map);
this->state = (void *)fhandle;
return (void*)fhandle;
}
file_io::~file_io() {
mp_stream_close((mp_obj_t)this->state);
void fileio_close(void* fhandle) {
mp_stream_close((mp_obj_t)fhandle);
}
size_t file_io::read(void *buf, size_t len) {
//mp_printf(&mp_plat_print, "Reading %lu bytes\n", len);
mp_obj_t fhandle = this->state;
size_t fileio_read(void* fhandle, void *buf, size_t len) {
mp_printf(&mp_plat_print, "Reading %lu bytes\n", len);
int error;
return mp_stream_read_exactly(fhandle, buf, len, &error);
return mp_stream_read_exactly((mp_obj_t)fhandle, buf, len, &error);
}
size_t file_io::tell() {
mp_obj_t fhandle = this->state;
int fileio_getc(void* fhandle) {
unsigned char buf;
fileio_read((mp_obj_t)fhandle, &buf, 1);
return (int)buf;
}
size_t fileio_tell(void* fhandle) {
struct mp_stream_seek_t seek_s;
seek_s.offset = 0;
seek_s.whence = SEEK_CUR;
const mp_stream_p_t *stream_p = mp_get_stream(fhandle);
const mp_stream_p_t *stream_p = mp_get_stream((mp_obj_t)fhandle);
int error;
mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
mp_uint_t res = stream_p->ioctl((mp_obj_t)fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
if (res == MP_STREAM_ERROR) {
mp_raise_OSError(error);
}
@ -78,21 +100,16 @@ size_t file_io::tell() {
return seek_s.offset;
}
bool file_io::fail() {
return false;
}
// Re-implementation of stream.c/STATIC mp_obj_t stream_seek(size_t n_args, const mp_obj_t *args)
size_t file_io::seek(size_t pos) {
mp_obj_t fhandle = this->state;
size_t fileio_seek(void* fhandle, size_t pos) {
struct mp_stream_seek_t seek_s;
seek_s.offset = pos;
seek_s.whence = SEEK_SET;
const mp_stream_p_t *stream_p = mp_get_stream(fhandle);
const mp_stream_p_t *stream_p = mp_get_stream((mp_obj_t)fhandle);
int error;
mp_uint_t res = stream_p->ioctl(fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
mp_uint_t res = stream_p->ioctl((mp_obj_t)fhandle, MP_STREAM_SEEK, (mp_uint_t)(uintptr_t)&seek_s, &error);
if (res == MP_STREAM_ERROR) {
mp_raise_OSError(error);
}
@ -347,7 +364,8 @@ mp_obj_t VECTOR_set_font(mp_obj_t self_in, mp_obj_t font, mp_obj_t size) {
if (mp_obj_is_str(font)) {
// TODO: Implement when Alright Fonts rewrite is ready
//result = self->vector->set_font(mp_obj_to_string_r(font), font_size);
GET_STR_DATA_LEN(font, str, str_len);
result = self->vector->set_font((const char*)str, font_size);
}
else {
@ -362,7 +380,7 @@ mp_obj_t VECTOR_set_font_size(mp_obj_t self_in, mp_obj_t size) {
int font_size = mp_obj_get_int(size);
(void)font_size;
// TODO: Implement when Alright Fonts rewrite is ready
//self->vector->set_font_size(font_size);
self->vector->set_font_size(font_size);
return mp_const_none;
}
@ -395,20 +413,21 @@ mp_obj_t VECTOR_text(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args)
GET_STR_DATA_LEN(text_obj, str, str_len);
const std::string_view t((const char*)str, str_len);
const std::wstring_view t((const wchar_t *)str, str_len);
int x = args[ARG_x].u_int;
int y = args[ARG_y].u_int;
(void)x;
(void)y;
if(args[ARG_angle].u_obj == mp_const_none) {
// TODO: Implement when Alright Fonts rewrite is ready
//self->vector->text(t, Point(x, y));
} else {
//self->vector->text(t, Point(x, y), mp_obj_get_float(args[ARG_angle].u_obj));
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));
}
self->vector->text(t, {(float)x, (float)y}, &tt);
return mp_const_none;
}