-
Notifications
You must be signed in to change notification settings - Fork 502
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
TEST: PicoVector: alright-fonts bringup.
- Loading branch information
Showing
6 changed files
with
471 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
Oops, something went wrong.