#include "raster_tools.h" #include #include #include #include #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; }