You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

457 lines
21 KiB

  1. // Copyright 2021 Nick Brassel (@tzarc)
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <quantum.h>
  4. #include <utf8.h>
  5. #include "qp_internal.h"
  6. #include "qp_draw.h"
  7. #include "qp_comms.h"
  8. #include "qff.h"
  9. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  10. // QFF font handles
  11. typedef struct qff_font_handle_t {
  12. painter_font_desc_t base;
  13. bool validate_ok;
  14. bool has_ascii_table;
  15. uint16_t num_unicode_glyphs;
  16. uint8_t bpp;
  17. bool has_palette;
  18. bool is_panel_native;
  19. painter_compression_t compression_scheme;
  20. union {
  21. qp_stream_t stream;
  22. qp_memory_stream_t mem_stream;
  23. #ifdef QP_STREAM_HAS_FILE_IO
  24. qp_file_stream_t file_stream;
  25. #endif // QP_STREAM_HAS_FILE_IO
  26. };
  27. #if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  28. bool owns_buffer;
  29. void *buffer;
  30. #endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  31. } qff_font_handle_t;
  32. static qff_font_handle_t font_descriptors[QUANTUM_PAINTER_NUM_FONTS] = {0};
  33. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  34. // Helper: load font from stream
  35. static painter_font_handle_t qp_load_font_internal(bool (*stream_factory)(qff_font_handle_t *font, void *arg), void *arg) {
  36. qp_dprintf("qp_load_font: entry\n");
  37. qff_font_handle_t *font = NULL;
  38. // Find a free slot
  39. for (int i = 0; i < QUANTUM_PAINTER_NUM_FONTS; ++i) {
  40. if (!font_descriptors[i].validate_ok) {
  41. font = &font_descriptors[i];
  42. break;
  43. }
  44. }
  45. // Drop out if not found
  46. if (!font) {
  47. qp_dprintf("qp_load_font: fail (no free slot)\n");
  48. return NULL;
  49. }
  50. if (!stream_factory(font, arg)) {
  51. qp_dprintf("qp_load_font: fail (could not create stream)\n");
  52. return NULL;
  53. }
  54. // Now that we know the length, validate the input data
  55. if (!qff_validate_stream(&font->stream)) {
  56. qp_dprintf("qp_load_font: fail (failed validation)\n");
  57. return NULL;
  58. }
  59. #if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  60. // Clear out any existing data
  61. font->owns_buffer = false;
  62. font->buffer = NULL;
  63. void *ram_buffer = malloc(font->mem_stream.length);
  64. if (ram_buffer == NULL) {
  65. qp_dprintf("qp_load_font: could not allocate enough RAM for font, falling back to original\n");
  66. } else {
  67. do {
  68. // Copy the data into RAM
  69. if (qp_stream_read(ram_buffer, 1, font->mem_stream.length, &font->mem_stream) != font->mem_stream.length) {
  70. qp_dprintf("qp_load_font: could not copy from flash to RAM, falling back to original\n");
  71. break;
  72. }
  73. // Create the new stream with the new buffer
  74. font->buffer = ram_buffer;
  75. font->owns_buffer = true;
  76. font->mem_stream = qp_make_memory_stream(font->buffer, font->mem_stream.length);
  77. } while (0);
  78. }
  79. // Free the buffer if we were unable to recreate the RAM copy.
  80. if (ram_buffer != NULL && !font->owns_buffer) {
  81. free(ram_buffer);
  82. }
  83. #endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  84. // Read the info (parsing already successful above, no need to check return value)
  85. qff_read_font_descriptor(&font->stream, &font->base.line_height, &font->has_ascii_table, &font->num_unicode_glyphs, &font->bpp, &font->has_palette, &font->is_panel_native, &font->compression_scheme, NULL);
  86. if (!qp_internal_bpp_capable(font->bpp)) {
  87. qp_dprintf("qp_load_font: fail (image bpp too high (%d), check QUANTUM_PAINTER_SUPPORTS_256_PALETTE or QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS)\n", (int)font->bpp);
  88. qp_close_font((painter_font_handle_t)font);
  89. return NULL;
  90. }
  91. // Validation success, we can return the handle
  92. font->validate_ok = true;
  93. qp_dprintf("qp_load_font: ok\n");
  94. return (painter_font_handle_t)font;
  95. }
  96. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  97. // Quantum Painter External API: qp_load_font_mem
  98. static inline bool font_mem_stream_factory(qff_font_handle_t *font, void *arg) {
  99. void *buffer = arg;
  100. // Assume we can read the graphics descriptor
  101. font->mem_stream = qp_make_memory_stream(buffer, sizeof(qff_font_descriptor_v1_t));
  102. // Update the length of the stream to match, and rewind to the start
  103. font->mem_stream.length = qff_get_total_size(&font->stream);
  104. font->mem_stream.position = 0;
  105. return true;
  106. }
  107. painter_font_handle_t qp_load_font_mem(const void *buffer) {
  108. return qp_load_font_internal(font_mem_stream_factory, (void *)buffer);
  109. }
  110. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  111. // Quantum Painter External API: qp_close_font
  112. bool qp_close_font(painter_font_handle_t font) {
  113. qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
  114. if (!qff_font || !qff_font->validate_ok) {
  115. qp_dprintf("qp_close_font: fail (invalid font)\n");
  116. return false;
  117. }
  118. #if QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  119. // Nuke the buffer, if required
  120. if (qff_font->owns_buffer) {
  121. free(qff_font->buffer);
  122. qff_font->buffer = NULL;
  123. qff_font->owns_buffer = false;
  124. }
  125. #endif // QUANTUM_PAINTER_LOAD_FONTS_TO_RAM
  126. // Free up this font for use elsewhere.
  127. qp_stream_close(&qff_font->stream);
  128. qff_font->validate_ok = false;
  129. return true;
  130. }
  131. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  132. // Helpers
  133. // Callback to be invoked for each codepoint detected in the UTF8 input string
  134. typedef bool (*code_point_handler)(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg);
  135. // Helper that sets up the palette (if required) and returns the offset in the stream that the data starts
  136. static inline bool qp_drawtext_prepare_font_for_render(painter_device_t device, qff_font_handle_t *qff_font, qp_pixel_t fg_hsv888, qp_pixel_t bg_hsv888, uint32_t *data_offset) {
  137. painter_driver_t *driver = (painter_driver_t *)device;
  138. // Drop out if we can't actually place the data we read out anywhere
  139. if (!data_offset) {
  140. qp_dprintf("Failed to prepare stream for read, output info buffer unavailable\n");
  141. return false;
  142. }
  143. // Work out where we're reading from
  144. uint32_t offset = sizeof(qff_font_descriptor_v1_t);
  145. if (qff_font->has_ascii_table) {
  146. offset += sizeof(qff_ascii_glyph_table_v1_t);
  147. }
  148. if (qff_font->num_unicode_glyphs > 0) {
  149. offset += sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * 6);
  150. }
  151. // Handle palette if needed
  152. const uint16_t palette_entries = 1u << qff_font->bpp;
  153. bool needs_pixconvert = false;
  154. if (qff_font->has_palette) {
  155. // If this font has a palette, we need to read it out and set up the pixel lookup table
  156. qp_stream_setpos(&qff_font->stream, offset);
  157. if (!qp_internal_load_qgf_palette(&qff_font->stream, qff_font->bpp)) {
  158. return false;
  159. }
  160. // Skip this block, as far as offset calculations go
  161. offset += sizeof(qgf_palette_v1_t) + (palette_entries * 3);
  162. needs_pixconvert = true;
  163. } else {
  164. // Interpolate from fg/bg
  165. int16_t palette_entries = 1 << qff_font->bpp;
  166. needs_pixconvert = qp_internal_interpolate_palette(fg_hsv888, bg_hsv888, palette_entries);
  167. }
  168. if (needs_pixconvert) {
  169. // Convert the palette to native format
  170. if (!driver->driver_vtable->palette_convert(device, palette_entries, qp_internal_global_pixel_lookup_table)) {
  171. qp_dprintf("qp_drawtext_recolor: fail (could not convert pixels to native)\n");
  172. qp_comms_stop(device);
  173. return false;
  174. }
  175. }
  176. *data_offset = offset;
  177. return true;
  178. }
  179. static inline bool qp_drawtext_prepare_glyph_for_render(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t *width) {
  180. if (code_point >= 0x20 && code_point < 0x7F && qff_font->has_ascii_table) {
  181. // Do ascii table
  182. qff_ascii_glyph_v1_t glyph_info;
  183. uint32_t glyph_info_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
  184. + sizeof(qgf_block_header_v1_t) // Skip the ascii table header
  185. + (code_point - 0x20) * sizeof(qff_ascii_glyph_v1_t); // Jump direct to the data offset based on the glyph index
  186. if (qp_stream_setpos(&qff_font->stream, glyph_info_offset) < 0) {
  187. qp_dprintf("Failed to set stream position while reading ascii glyph info\n");
  188. return false;
  189. }
  190. if (qp_stream_read(&glyph_info, sizeof(qff_ascii_glyph_v1_t), 1, &qff_font->stream) != 1) {
  191. qp_dprintf("Failed to read glyph info\n");
  192. return false;
  193. }
  194. uint8_t glyph_width = (uint8_t)(glyph_info.value & QFF_GLYPH_WIDTH_MASK);
  195. uint32_t glyph_offset = ((glyph_info.value & QFF_GLYPH_OFFSET_MASK) >> QFF_GLYPH_WIDTH_BITS);
  196. uint32_t data_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
  197. + (qff_font->has_ascii_table ? sizeof(qff_ascii_glyph_table_v1_t) : 0) // Skip the ascii table
  198. + (qff_font->num_unicode_glyphs > 0 ? (sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * sizeof(qff_unicode_glyph_v1_t))) : 0) // Skip the unicode table
  199. + (qff_font->has_palette ? (sizeof(qgf_palette_v1_t) + ((1 << qff_font->bpp) * sizeof(qgf_palette_entry_v1_t))) : 0) // Skip the palette
  200. + sizeof(qgf_block_header_v1_t) // Skip the data block header
  201. + glyph_offset; // Jump to the specified glyph offset
  202. if (qp_stream_setpos(&qff_font->stream, data_offset) < 0) {
  203. qp_dprintf("Failed to set stream position while preparing ascii glyph data\n");
  204. return false;
  205. }
  206. *width = glyph_width;
  207. return true;
  208. } else {
  209. // Do unicode table, which may include singular ascii glyphs if full ascii table isn't specified
  210. uint32_t glyph_info_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
  211. + (qff_font->has_ascii_table ? sizeof(qff_ascii_glyph_table_v1_t) : 0) // Skip the ascii table
  212. + sizeof(qgf_block_header_v1_t); // Skip the unicode block header
  213. if (qp_stream_setpos(&qff_font->stream, glyph_info_offset) < 0) {
  214. qp_dprintf("Failed to set stream position while preparing glyph data\n");
  215. return false;
  216. }
  217. qff_unicode_glyph_v1_t glyph_info;
  218. for (uint16_t i = 0; i < qff_font->num_unicode_glyphs; ++i) {
  219. if (qp_stream_read(&glyph_info, sizeof(qff_unicode_glyph_v1_t), 1, &qff_font->stream) != 1) {
  220. qp_dprintf("Failed to set stream position while reading unicode glyph info\n");
  221. return false;
  222. }
  223. if (glyph_info.code_point == code_point) {
  224. uint8_t glyph_width = (uint8_t)(glyph_info.value & QFF_GLYPH_WIDTH_MASK);
  225. uint32_t glyph_offset = ((glyph_info.value & QFF_GLYPH_OFFSET_MASK) >> QFF_GLYPH_WIDTH_BITS);
  226. uint32_t data_offset = sizeof(qff_font_descriptor_v1_t) // Skip the font descriptor
  227. + (qff_font->has_ascii_table ? sizeof(qff_ascii_glyph_table_v1_t) : 0) // Skip the ascii table
  228. + (qff_font->num_unicode_glyphs > 0 ? (sizeof(qff_unicode_glyph_table_v1_t) + (qff_font->num_unicode_glyphs * sizeof(qff_unicode_glyph_v1_t))) : 0) // Skip the unicode table
  229. + (qff_font->has_palette ? (sizeof(qgf_palette_v1_t) + ((1 << qff_font->bpp) * sizeof(qgf_palette_entry_v1_t))) : 0) // Skip the palette
  230. + sizeof(qgf_block_header_v1_t) // Skip the data block header
  231. + glyph_offset; // Jump to the specified glyph offset
  232. if (qp_stream_setpos(&qff_font->stream, data_offset) < 0) {
  233. qp_dprintf("Failed to set stream position while preparing unicode glyph data\n");
  234. return false;
  235. }
  236. *width = glyph_width;
  237. return true;
  238. }
  239. }
  240. // Not found
  241. qp_dprintf("Failed to find unicode glyph info\n");
  242. return false;
  243. }
  244. return false;
  245. }
  246. // Function to iterate over each UTF8 codepoint, invoking the callback for each decoded glyph
  247. static inline bool qp_iterate_code_points(qff_font_handle_t *qff_font, const char *str, code_point_handler handler, void *cb_arg) {
  248. while (*str) {
  249. int32_t code_point = 0;
  250. str = decode_utf8(str, &code_point);
  251. if (code_point < 0) {
  252. qp_dprintf("Invalid unicode code point decoded. Cannot render.\n");
  253. return false;
  254. }
  255. uint8_t width;
  256. if (!qp_drawtext_prepare_glyph_for_render(qff_font, code_point, &width)) {
  257. qp_dprintf("Failed to prepare glyph for rendering.\n");
  258. return false;
  259. }
  260. if (!handler(qff_font, code_point, width, qff_font->base.line_height, cb_arg)) {
  261. qp_dprintf("Failed to execute glyph handler.\n");
  262. return false;
  263. }
  264. }
  265. return true;
  266. }
  267. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  268. // String width calculation
  269. // Callback state
  270. typedef struct code_point_iter_calcwidth_state_t {
  271. int16_t width;
  272. } code_point_iter_calcwidth_state_t;
  273. // Codepoint handler callback: width calc
  274. static inline bool qp_font_code_point_handler_calcwidth(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg) {
  275. code_point_iter_calcwidth_state_t *state = (code_point_iter_calcwidth_state_t *)cb_arg;
  276. // Increment the overall width by this glyph's width
  277. state->width += width;
  278. return true;
  279. }
  280. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  281. // String drawing implementation
  282. // Callback state
  283. typedef struct code_point_iter_drawglyph_state_t {
  284. painter_device_t device;
  285. int16_t xpos;
  286. int16_t ypos;
  287. qp_internal_byte_input_callback input_callback;
  288. qp_internal_byte_input_state_t * input_state;
  289. qp_internal_pixel_output_state_t *output_state;
  290. } code_point_iter_drawglyph_state_t;
  291. // Codepoint handler callback: drawing
  292. static inline bool qp_font_code_point_handler_drawglyph(qff_font_handle_t *qff_font, uint32_t code_point, uint8_t width, uint8_t height, void *cb_arg) {
  293. code_point_iter_drawglyph_state_t *state = (code_point_iter_drawglyph_state_t *)cb_arg;
  294. painter_driver_t * driver = (painter_driver_t *)state->device;
  295. // Reset the input state's RLE mode -- the stream should already be correctly positioned by qp_iterate_code_points()
  296. state->input_state->rle.mode = MARKER_BYTE; // ignored if not using RLE
  297. // Reset the output state
  298. state->output_state->pixel_write_pos = 0;
  299. // Configure where we're going to be rendering to
  300. driver->driver_vtable->viewport(state->device, state->xpos, state->ypos, state->xpos + width - 1, state->ypos + height - 1);
  301. // Move the x-position for the next glyph
  302. state->xpos += width;
  303. // Decode the pixel data for the glyph, and stream it
  304. uint32_t pixel_count = ((uint32_t)width) * height;
  305. return qp_internal_appender(state->device, qff_font->bpp, pixel_count, state->input_callback, state->input_state);
  306. }
  307. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  308. // Quantum Painter External API: qp_textwidth
  309. int16_t qp_textwidth(painter_font_handle_t font, const char *str) {
  310. qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
  311. if (!qff_font || !qff_font->validate_ok) {
  312. qp_dprintf("qp_textwidth: fail (invalid font)\n");
  313. return false;
  314. }
  315. // Create the codepoint iterator state
  316. code_point_iter_calcwidth_state_t state = {.width = 0};
  317. // Iterate each codepoint, return the calculated width if successful.
  318. return qp_iterate_code_points(qff_font, str, qp_font_code_point_handler_calcwidth, &state) ? state.width : 0;
  319. }
  320. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  321. // Quantum Painter External API: qp_drawtext
  322. int16_t qp_drawtext(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str) {
  323. // Offload to the recolor variant, substituting fg=white bg=black.
  324. // Traditional LCDs with those colors will need to manually invoke qp_drawtext_recolor with the colors reversed.
  325. return qp_drawtext_recolor(device, x, y, font, str, 0, 0, 255, 0, 0, 0);
  326. }
  327. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  328. // Quantum Painter External API: qp_drawtext_recolor
  329. int16_t qp_drawtext_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_font_handle_t font, const char *str, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg) {
  330. qp_dprintf("qp_drawtext_recolor: entry\n");
  331. painter_driver_t *driver = (painter_driver_t *)device;
  332. if (!driver || !driver->validate_ok) {
  333. qp_dprintf("qp_drawtext_recolor: fail (validation_ok == false)\n");
  334. return 0;
  335. }
  336. qff_font_handle_t *qff_font = (qff_font_handle_t *)font;
  337. if (!qff_font || !qff_font->validate_ok) {
  338. qp_dprintf("qp_drawtext_recolor: fail (invalid font)\n");
  339. return false;
  340. }
  341. if (!qp_comms_start(device)) {
  342. qp_dprintf("qp_drawtext_recolor: fail (could not start comms)\n");
  343. return 0;
  344. }
  345. // Set up the byte input state and input callback
  346. qp_internal_byte_input_state_t input_state = {.device = device, .src_stream = &qff_font->stream};
  347. qp_internal_byte_input_callback input_callback = qp_internal_prepare_input_state(&input_state, qff_font->compression_scheme);
  348. if (input_callback == NULL) {
  349. qp_dprintf("qp_drawtext_recolor: fail (invalid font compression scheme)\n");
  350. qp_comms_stop(device);
  351. return false;
  352. }
  353. // Set up the pixel output state
  354. qp_internal_pixel_output_state_t output_state = {.device = device, .pixel_write_pos = 0, .max_pixels = qp_internal_num_pixels_in_buffer(device)};
  355. // Set up the codepoint iteration state
  356. code_point_iter_drawglyph_state_t state = {// Common
  357. .device = device,
  358. .xpos = x,
  359. .ypos = y,
  360. // Input
  361. .input_callback = input_callback,
  362. .input_state = &input_state,
  363. // Output
  364. .output_state = &output_state};
  365. qp_pixel_t fg_hsv888 = {.hsv888 = {.h = hue_fg, .s = sat_fg, .v = val_fg}};
  366. qp_pixel_t bg_hsv888 = {.hsv888 = {.h = hue_bg, .s = sat_bg, .v = val_bg}};
  367. uint32_t data_offset;
  368. if (!qp_drawtext_prepare_font_for_render(driver, qff_font, fg_hsv888, bg_hsv888, &data_offset)) {
  369. qp_dprintf("qp_drawtext_recolor: fail (failed to prepare font for rendering)\n");
  370. qp_comms_stop(device);
  371. return false;
  372. }
  373. // Iterate the codepoints with the drawglyph callback
  374. bool ret = qp_iterate_code_points(qff_font, str, qp_font_code_point_handler_drawglyph, &state);
  375. qp_dprintf("qp_drawtext_recolor: %s\n", ret ? "ok" : "fail");
  376. qp_comms_stop(device);
  377. return ret ? (state.xpos - x) : 0;
  378. }