468 lines
15 KiB
C
468 lines
15 KiB
C
#include "domain.h"
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define RASTER_WIDTH 384
|
|
#define RASTER_BYTES_PER_ROW (RASTER_WIDTH / 8)
|
|
#define CN16_GLYPH_BYTES 32u
|
|
#define CN16_INDEX_ENTRY_BYTES 6u
|
|
#define UTF8_REPLACEMENT_CODEPOINT 0x003Fu
|
|
|
|
extern const uint8_t _binary_cn16_index_bin_start[] asm("_binary_cn16_index_bin_start");
|
|
extern const uint8_t _binary_cn16_index_bin_end[] asm("_binary_cn16_index_bin_end");
|
|
extern const uint8_t _binary_cn16_glyphs_bin_start[] asm("_binary_cn16_glyphs_bin_start");
|
|
extern const uint8_t _binary_cn16_glyphs_bin_end[] asm("_binary_cn16_glyphs_bin_end");
|
|
|
|
/* 5x7 ASCII font table for characters 0x20..0x7E. */
|
|
static const uint8_t font5x7[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, /* space */
|
|
0x00, 0x00, 0x5F, 0x00, 0x00, /* ! */
|
|
0x00, 0x07, 0x00, 0x07, 0x00, /* " */
|
|
0x14, 0x7F, 0x14, 0x7F, 0x14, /* # */
|
|
0x24, 0x2A, 0x7F, 0x2A, 0x12, /* $ */
|
|
0x23, 0x13, 0x08, 0x64, 0x62, /* % */
|
|
0x36, 0x49, 0x55, 0x22, 0x50, /* & */
|
|
0x00, 0x05, 0x03, 0x00, 0x00, /* ' */
|
|
0x00, 0x1C, 0x22, 0x41, 0x00, /* ( */
|
|
0x00, 0x41, 0x22, 0x1C, 0x00, /* ) */
|
|
0x14, 0x08, 0x3E, 0x08, 0x14, /* * */
|
|
0x08, 0x08, 0x3E, 0x08, 0x08, /* + */
|
|
0x00, 0x50, 0x30, 0x00, 0x00, /* , */
|
|
0x08, 0x08, 0x08, 0x08, 0x08, /* - */
|
|
0x00, 0x60, 0x60, 0x00, 0x00, /* . */
|
|
0x20, 0x10, 0x08, 0x04, 0x02, /* / */
|
|
0x3E, 0x51, 0x49, 0x45, 0x3E, /* 0 */
|
|
0x00, 0x42, 0x7F, 0x40, 0x00, /* 1 */
|
|
0x42, 0x61, 0x51, 0x49, 0x46, /* 2 */
|
|
0x21, 0x41, 0x45, 0x4B, 0x31, /* 3 */
|
|
0x18, 0x14, 0x12, 0x7F, 0x10, /* 4 */
|
|
0x27, 0x45, 0x45, 0x45, 0x39, /* 5 */
|
|
0x3C, 0x4A, 0x49, 0x49, 0x30, /* 6 */
|
|
0x01, 0x71, 0x09, 0x05, 0x03, /* 7 */
|
|
0x36, 0x49, 0x49, 0x49, 0x36, /* 8 */
|
|
0x06, 0x49, 0x49, 0x29, 0x1E, /* 9 */
|
|
0x00, 0x36, 0x36, 0x00, 0x00, /* : */
|
|
0x00, 0x56, 0x36, 0x00, 0x00, /* ; */
|
|
0x08, 0x14, 0x22, 0x41, 0x00, /* < */
|
|
0x14, 0x14, 0x14, 0x14, 0x14, /* = */
|
|
0x00, 0x41, 0x22, 0x14, 0x08, /* > */
|
|
0x02, 0x01, 0x51, 0x09, 0x06, /* ? */
|
|
0x32, 0x49, 0x79, 0x41, 0x3E, /* @ */
|
|
0x7E, 0x11, 0x11, 0x11, 0x7E, /* A */
|
|
0x7F, 0x49, 0x49, 0x49, 0x36, /* B */
|
|
0x3E, 0x41, 0x41, 0x41, 0x22, /* C */
|
|
0x7F, 0x41, 0x41, 0x22, 0x1C, /* D */
|
|
0x7F, 0x49, 0x49, 0x49, 0x41, /* E */
|
|
0x7F, 0x09, 0x09, 0x09, 0x01, /* F */
|
|
0x3E, 0x41, 0x49, 0x49, 0x7A, /* G */
|
|
0x7F, 0x08, 0x08, 0x08, 0x7F, /* H */
|
|
0x00, 0x41, 0x7F, 0x41, 0x00, /* I */
|
|
0x20, 0x40, 0x41, 0x3F, 0x01, /* J */
|
|
0x7F, 0x08, 0x14, 0x22, 0x41, /* K */
|
|
0x7F, 0x40, 0x40, 0x40, 0x40, /* L */
|
|
0x7F, 0x02, 0x0C, 0x02, 0x7F, /* M */
|
|
0x7F, 0x04, 0x08, 0x10, 0x7F, /* N */
|
|
0x3E, 0x41, 0x41, 0x41, 0x3E, /* O */
|
|
0x7F, 0x09, 0x09, 0x09, 0x06, /* P */
|
|
0x3E, 0x41, 0x51, 0x21, 0x5E, /* Q */
|
|
0x7F, 0x09, 0x19, 0x29, 0x46, /* R */
|
|
0x46, 0x49, 0x49, 0x49, 0x31, /* S */
|
|
0x01, 0x01, 0x7F, 0x01, 0x01, /* T */
|
|
0x3F, 0x40, 0x40, 0x40, 0x3F, /* U */
|
|
0x1F, 0x20, 0x40, 0x20, 0x1F, /* V */
|
|
0x7F, 0x20, 0x18, 0x20, 0x7F, /* W */
|
|
0x63, 0x14, 0x08, 0x14, 0x63, /* X */
|
|
0x03, 0x04, 0x78, 0x04, 0x03, /* Y */
|
|
0x61, 0x51, 0x49, 0x45, 0x43, /* Z */
|
|
0x00, 0x00, 0x7F, 0x41, 0x41, /* [ */
|
|
0x02, 0x04, 0x08, 0x10, 0x20, /* \\ */
|
|
0x41, 0x41, 0x7F, 0x00, 0x00, /* ] */
|
|
0x04, 0x02, 0x01, 0x02, 0x04, /* ^ */
|
|
0x80, 0x80, 0x80, 0x80, 0x80, /* _ */
|
|
0x00, 0x03, 0x05, 0x00, 0x00, /* ` */
|
|
0x20, 0x54, 0x54, 0x54, 0x78, /* a */
|
|
0x7F, 0x48, 0x44, 0x44, 0x38, /* b */
|
|
0x38, 0x44, 0x44, 0x44, 0x20, /* c */
|
|
0x38, 0x44, 0x44, 0x48, 0x7F, /* d */
|
|
0x38, 0x54, 0x54, 0x54, 0x18, /* e */
|
|
0x08, 0x7E, 0x09, 0x01, 0x02, /* f */
|
|
0x0C, 0x52, 0x52, 0x52, 0x3E, /* g */
|
|
0x7F, 0x08, 0x04, 0x04, 0x78, /* h */
|
|
0x00, 0x44, 0x7D, 0x40, 0x00, /* i */
|
|
0x20, 0x40, 0x44, 0x3D, 0x00, /* j */
|
|
0x7F, 0x10, 0x28, 0x44, 0x00, /* k */
|
|
0x00, 0x41, 0x7F, 0x40, 0x00, /* l */
|
|
0x7C, 0x04, 0x18, 0x04, 0x78, /* m */
|
|
0x7C, 0x08, 0x04, 0x04, 0x78, /* n */
|
|
0x38, 0x44, 0x44, 0x44, 0x38, /* o */
|
|
0x7C, 0x14, 0x14, 0x14, 0x08, /* p */
|
|
0x08, 0x14, 0x14, 0x18, 0x7C, /* q */
|
|
0x7C, 0x08, 0x04, 0x04, 0x08, /* r */
|
|
0x48, 0x54, 0x54, 0x54, 0x20, /* s */
|
|
0x04, 0x3F, 0x44, 0x40, 0x20, /* t */
|
|
0x3C, 0x40, 0x40, 0x20, 0x7C, /* u */
|
|
0x1C, 0x20, 0x40, 0x20, 0x1C, /* v */
|
|
0x3C, 0x40, 0x30, 0x40, 0x3C, /* w */
|
|
0x44, 0x28, 0x10, 0x28, 0x44, /* x */
|
|
0x0C, 0x50, 0x50, 0x50, 0x3C, /* y */
|
|
0x44, 0x64, 0x54, 0x4C, 0x44, /* z */
|
|
0x00, 0x08, 0x36, 0x41, 0x00, /* { */
|
|
0x00, 0x00, 0x7F, 0x00, 0x00, /* | */
|
|
0x00, 0x41, 0x36, 0x08, 0x00, /* } */
|
|
0x08, 0x08, 0x2A, 0x1C, 0x08 /* ~ */
|
|
};
|
|
|
|
static void set_black(uint8_t *buf, uint16_t width, uint16_t x, uint16_t y) {
|
|
if (x >= width) {
|
|
return;
|
|
}
|
|
uint16_t bpr = width / 8;
|
|
size_t idx = (size_t)y * bpr + (x / 8);
|
|
uint8_t bit = (uint8_t)(7 - (x % 8));
|
|
buf[idx] |= (uint8_t)(1u << bit);
|
|
}
|
|
|
|
static size_t cn16_index_count(void) {
|
|
size_t bytes = (size_t)(_binary_cn16_index_bin_end - _binary_cn16_index_bin_start);
|
|
return bytes / CN16_INDEX_ENTRY_BYTES;
|
|
}
|
|
|
|
static bool cn16_read_index_entry(size_t idx, uint32_t *out_cp, uint16_t *out_gid) {
|
|
if (out_cp == NULL || out_gid == NULL) {
|
|
return false;
|
|
}
|
|
size_t count = cn16_index_count();
|
|
if (idx >= count) {
|
|
return false;
|
|
}
|
|
|
|
const uint8_t *p = _binary_cn16_index_bin_start + idx * CN16_INDEX_ENTRY_BYTES;
|
|
*out_cp = ((uint32_t)p[0]) |
|
|
((uint32_t)p[1] << 8) |
|
|
((uint32_t)p[2] << 16) |
|
|
((uint32_t)p[3] << 24);
|
|
*out_gid = (uint16_t)(p[4] | ((uint16_t)p[5] << 8));
|
|
return true;
|
|
}
|
|
|
|
static bool cn16_lookup_glyph(uint32_t codepoint, const uint8_t **out_glyph) {
|
|
if (out_glyph == NULL) {
|
|
return false;
|
|
}
|
|
|
|
size_t count = cn16_index_count();
|
|
if (count == 0) {
|
|
return false;
|
|
}
|
|
|
|
size_t lo = 0;
|
|
size_t hi = count;
|
|
while (lo < hi) {
|
|
size_t mid = lo + ((hi - lo) / 2);
|
|
uint32_t cp = 0;
|
|
uint16_t gid = 0;
|
|
if (!cn16_read_index_entry(mid, &cp, &gid)) {
|
|
return false;
|
|
}
|
|
|
|
if (cp == codepoint) {
|
|
size_t glyph_bytes = (size_t)(_binary_cn16_glyphs_bin_end -
|
|
_binary_cn16_glyphs_bin_start);
|
|
size_t off = (size_t)gid * CN16_GLYPH_BYTES;
|
|
if ((off + CN16_GLYPH_BYTES) > glyph_bytes) {
|
|
return false;
|
|
}
|
|
*out_glyph = _binary_cn16_glyphs_bin_start + off;
|
|
return true;
|
|
}
|
|
if (cp < codepoint) {
|
|
lo = mid + 1;
|
|
} else {
|
|
hi = mid;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void draw_char(uint8_t *buf,
|
|
uint16_t width,
|
|
uint16_t height,
|
|
uint16_t x,
|
|
uint16_t y,
|
|
char ch,
|
|
uint8_t scale) {
|
|
if (ch < 32 || ch > 126) {
|
|
ch = '?';
|
|
}
|
|
|
|
const uint8_t *glyph = &font5x7[(ch - 32) * 5];
|
|
for (uint8_t col = 0; col < 5; ++col) {
|
|
uint8_t bits = glyph[col];
|
|
for (uint8_t row = 0; row < 7; ++row) {
|
|
if (((bits >> row) & 0x01u) == 0) {
|
|
continue;
|
|
}
|
|
for (uint8_t sx = 0; sx < scale; ++sx) {
|
|
for (uint8_t sy = 0; sy < scale; ++sy) {
|
|
uint16_t px = (uint16_t)(x + col * scale + sx);
|
|
uint16_t py = (uint16_t)(y + row * scale + sy);
|
|
if (py < height) {
|
|
set_black(buf, width, px, py);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_cn16_glyph(uint8_t *buf,
|
|
uint16_t width,
|
|
uint16_t height,
|
|
uint16_t x,
|
|
uint16_t y,
|
|
const uint8_t *glyph,
|
|
uint8_t scale) {
|
|
if (glyph == NULL || scale == 0) {
|
|
return;
|
|
}
|
|
|
|
for (uint8_t row = 0; row < 16; ++row) {
|
|
uint16_t bits = ((uint16_t)glyph[row * 2] << 8) | glyph[row * 2 + 1];
|
|
for (uint8_t col = 0; col < 16; ++col) {
|
|
if ((bits & (uint16_t)(1u << (15 - col))) == 0) {
|
|
continue;
|
|
}
|
|
for (uint8_t sx = 0; sx < scale; ++sx) {
|
|
for (uint8_t sy = 0; sy < scale; ++sy) {
|
|
uint16_t px = (uint16_t)(x + col * scale + sx);
|
|
uint16_t py = (uint16_t)(y + row * scale + sy);
|
|
if (py < height && px < width) {
|
|
set_black(buf, width, px, py);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void draw_missing_box(uint8_t *buf,
|
|
uint16_t width,
|
|
uint16_t height,
|
|
uint16_t x,
|
|
uint16_t y,
|
|
uint8_t scale) {
|
|
uint16_t box = (uint16_t)(16 * scale);
|
|
for (uint16_t r = 0; r < box; ++r) {
|
|
for (uint16_t c = 0; c < box; ++c) {
|
|
bool edge = (r < scale) || (c < scale) || (r >= box - scale) || (c >= box - scale);
|
|
if (!edge) {
|
|
continue;
|
|
}
|
|
uint16_t px = (uint16_t)(x + c);
|
|
uint16_t py = (uint16_t)(y + r);
|
|
if (px < width && py < height) {
|
|
set_black(buf, width, px, py);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32_t utf8_decode_one(const unsigned char *s, size_t len, size_t *out_consumed) {
|
|
if (out_consumed == NULL || s == NULL || len == 0) {
|
|
return UTF8_REPLACEMENT_CODEPOINT;
|
|
}
|
|
|
|
uint8_t b0 = s[0];
|
|
if (b0 < 0x80) {
|
|
*out_consumed = 1;
|
|
return b0;
|
|
}
|
|
|
|
if ((b0 & 0xE0u) == 0xC0u && len >= 2) {
|
|
uint8_t b1 = s[1];
|
|
if ((b1 & 0xC0u) == 0x80u) {
|
|
uint32_t cp = ((uint32_t)(b0 & 0x1Fu) << 6) |
|
|
(uint32_t)(b1 & 0x3Fu);
|
|
if (cp >= 0x80u) {
|
|
*out_consumed = 2;
|
|
return cp;
|
|
}
|
|
}
|
|
} else if ((b0 & 0xF0u) == 0xE0u && len >= 3) {
|
|
uint8_t b1 = s[1];
|
|
uint8_t b2 = s[2];
|
|
if ((b1 & 0xC0u) == 0x80u && (b2 & 0xC0u) == 0x80u) {
|
|
uint32_t cp = ((uint32_t)(b0 & 0x0Fu) << 12) |
|
|
((uint32_t)(b1 & 0x3Fu) << 6) |
|
|
(uint32_t)(b2 & 0x3Fu);
|
|
if (cp >= 0x800u && !(cp >= 0xD800u && cp <= 0xDFFFu)) {
|
|
*out_consumed = 3;
|
|
return cp;
|
|
}
|
|
}
|
|
} else if ((b0 & 0xF8u) == 0xF0u && len >= 4) {
|
|
uint8_t b1 = s[1];
|
|
uint8_t b2 = s[2];
|
|
uint8_t b3 = s[3];
|
|
if ((b1 & 0xC0u) == 0x80u && (b2 & 0xC0u) == 0x80u && (b3 & 0xC0u) == 0x80u) {
|
|
uint32_t cp = ((uint32_t)(b0 & 0x07u) << 18) |
|
|
((uint32_t)(b1 & 0x3Fu) << 12) |
|
|
((uint32_t)(b2 & 0x3Fu) << 6) |
|
|
(uint32_t)(b3 & 0x3Fu);
|
|
if (cp >= 0x10000u && cp <= 0x10FFFFu) {
|
|
*out_consumed = 4;
|
|
return cp;
|
|
}
|
|
}
|
|
}
|
|
|
|
*out_consumed = 1;
|
|
return UTF8_REPLACEMENT_CODEPOINT;
|
|
}
|
|
|
|
esp_err_t raster_tools_render_text_384(const char *text,
|
|
uint8_t scale,
|
|
uint8_t line_spacing,
|
|
uint16_t max_height,
|
|
uint16_t *out_width,
|
|
uint16_t *out_height,
|
|
uint8_t **out_raster,
|
|
size_t *out_len,
|
|
char *err,
|
|
size_t err_len) {
|
|
if (text == NULL || out_width == NULL || out_height == NULL ||
|
|
out_raster == NULL || out_len == NULL) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "invalid args");
|
|
}
|
|
return ESP_ERR_INVALID_ARG;
|
|
}
|
|
|
|
if (scale == 0) {
|
|
scale = 1;
|
|
}
|
|
|
|
if (max_height == 0 || max_height > 3000) {
|
|
max_height = 3000;
|
|
}
|
|
|
|
size_t full_len = (size_t)RASTER_BYTES_PER_ROW * max_height;
|
|
uint8_t *buf = (uint8_t *)calloc(1, full_len);
|
|
if (buf == NULL) {
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "no memory");
|
|
}
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
uint16_t cursor_x = 0;
|
|
uint16_t cursor_y = 0;
|
|
uint16_t ascii_w = (uint16_t)(6 * scale);
|
|
uint16_t ascii_h = (uint16_t)(7 * scale);
|
|
uint16_t cjk_w = (uint16_t)(16 * scale);
|
|
uint16_t cjk_h = (uint16_t)(16 * scale);
|
|
uint16_t line_h = (uint16_t)(cjk_h + line_spacing * scale);
|
|
if (line_h == 0) {
|
|
line_h = cjk_h;
|
|
}
|
|
|
|
uint16_t used_bottom = 0;
|
|
bool truncated = false;
|
|
|
|
const unsigned char *p = (const unsigned char *)text;
|
|
size_t remain = strlen(text);
|
|
while (remain > 0) {
|
|
size_t consumed = 0;
|
|
uint32_t cp = utf8_decode_one(p, remain, &consumed);
|
|
if (consumed == 0) {
|
|
break;
|
|
}
|
|
p += consumed;
|
|
remain -= consumed;
|
|
|
|
if (cp == '\r') {
|
|
continue;
|
|
}
|
|
if (cp == '\n') {
|
|
cursor_x = 0;
|
|
cursor_y = (uint16_t)(cursor_y + line_h);
|
|
continue;
|
|
}
|
|
|
|
bool is_ascii = cp >= 0x20u && cp <= 0x7Eu;
|
|
uint16_t draw_w = is_ascii ? ascii_w : cjk_w;
|
|
uint16_t draw_h = is_ascii ? ascii_h : cjk_h;
|
|
uint16_t draw_y = cursor_y;
|
|
if (is_ascii && cjk_h > ascii_h) {
|
|
draw_y = (uint16_t)(cursor_y + (cjk_h - ascii_h) / 2);
|
|
}
|
|
|
|
if ((uint16_t)(cursor_x + draw_w) > RASTER_WIDTH) {
|
|
cursor_x = 0;
|
|
cursor_y = (uint16_t)(cursor_y + line_h);
|
|
draw_y = cursor_y;
|
|
if (is_ascii && cjk_h > ascii_h) {
|
|
draw_y = (uint16_t)(cursor_y + (cjk_h - ascii_h) / 2);
|
|
}
|
|
}
|
|
|
|
if ((uint16_t)(draw_y + draw_h) > max_height) {
|
|
truncated = true;
|
|
break;
|
|
}
|
|
|
|
if (is_ascii) {
|
|
draw_char(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, (char)cp, scale);
|
|
} else {
|
|
const uint8_t *glyph = NULL;
|
|
if (cn16_lookup_glyph(cp, &glyph)) {
|
|
draw_cn16_glyph(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, glyph, scale);
|
|
} else {
|
|
draw_missing_box(buf, RASTER_WIDTH, max_height, cursor_x, draw_y, scale);
|
|
}
|
|
}
|
|
cursor_x = (uint16_t)(cursor_x + draw_w);
|
|
|
|
uint16_t bottom = (uint16_t)(draw_y + draw_h);
|
|
if (bottom > used_bottom) {
|
|
used_bottom = bottom;
|
|
}
|
|
}
|
|
|
|
if (used_bottom == 0) {
|
|
used_bottom = cjk_h;
|
|
}
|
|
|
|
if (used_bottom > max_height) {
|
|
used_bottom = max_height;
|
|
}
|
|
|
|
size_t final_len = (size_t)RASTER_BYTES_PER_ROW * used_bottom;
|
|
uint8_t *final_buf = (uint8_t *)malloc(final_len);
|
|
if (final_buf == NULL) {
|
|
free(buf);
|
|
if (err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "no memory");
|
|
}
|
|
return ESP_ERR_NO_MEM;
|
|
}
|
|
|
|
memcpy(final_buf, buf, final_len);
|
|
free(buf);
|
|
|
|
*out_width = RASTER_WIDTH;
|
|
*out_height = used_bottom;
|
|
*out_raster = final_buf;
|
|
*out_len = final_len;
|
|
|
|
if (truncated && err != NULL && err_len > 0) {
|
|
snprintf(err, err_len, "text truncated due to max height");
|
|
}
|
|
|
|
return ESP_OK;
|
|
}
|