Files
AI_Printer/components/domain/src/raster_tools.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;
}