qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

qp_draw_text.c (21432B)


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