/** * TODO file description */ #include #include #include "lcdbuf.h" /** Initialize the struct */ void LcdBuffer_Init(struct LcdBuffer *self, const struct cgrom_entry *cgrom, const struct cgram_pattern *custom_symbols) { assert(self); assert(cgrom); assert(custom_symbols); LcdBuffer_Clear(self); self->cgrom = cgrom; self->custom_symbols = custom_symbols; } /** Clear the screen */ void LcdBuffer_Clear(struct LcdBuffer *self) { assert(self); memset(self->cgram, 0, sizeof(self->cgram)); memset(self->screen, 32, sizeof(self->screen)); memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); self->full_repaint_required = false; } /** Write what needs to be written to the HW, clear all dirty marks */ void LcdBuffer_Flush(struct LcdBuffer *self) { for (int i = 0; i < 8; i++) { if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) { LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); self->cgram[i].dirty = false; } } if (self->full_repaint_required) { for (int r = 0; r < LINE_NUM; r++) { LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); } memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); } else { for (int e = 0; e < BUFLEN_DIRTY_LIST; e++) { struct DirtyExtent *ext = &self->dirty_extents[e]; if (!ext->count) { continue; } LcdBuffer_IO_WriteAt(ext->row, ext->col, &self->screen[ext->row][ext->col], ext->count); ext->count = 0; // mark the slot as free } } } /** Fully write everything to the display */ void LcdBuffer_FlushAll(struct LcdBuffer *self) { for (int i = 0; i < 8; i++) { if (self->cgram[i].refcount > 0) { LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); } self->cgram[i].dirty = false; } for (int r = 0; r < LINE_NUM; r++) { LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); } memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); self->full_repaint_required = false; } static void mark_dirty(struct LcdBuffer *self, uint8_t row, uint8_t col) { int first_empty_extent_slot = -1; for (int i = 0; i < BUFLEN_DIRTY_LIST; i++) { struct DirtyExtent *ext = &self->dirty_extents[i]; if (ext->count == 0) { // unused if (first_empty_extent_slot == -1) { first_empty_extent_slot = i; } continue; } if (ext->row != row) { // not this row continue; } // this is a filled extent if (ext->col < col && ext->col + ext->count > col) { // already in this extent return; } if (col < ext->col && (ext->col - col) <= 5) { ext->count += (ext->col - col); ext->col = col; return; } if (col >= ext->col + ext->count && (col - ext->col + ext->count) <= 5) { ext->count += (col - ext->col + ext->count); return; } } if (first_empty_extent_slot == -1) { self->full_repaint_required = true; } else { self->dirty_extents[first_empty_extent_slot].col = col; self->dirty_extents[first_empty_extent_slot].row = row; self->dirty_extents[first_empty_extent_slot].count = 1; } } /** Set one utf8 character at a position */ void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8Char ch) { assert(self); assert(row < LINE_NUM); assert(col < LINE_LEN); uint8_t oldchar = self->screen[row][col]; if (oldchar >= 8 && oldchar == ch.uint) { // No change return; } // Fast path for standard ASCII if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH // normal ASCII if (oldchar < 8) { // release refcount on the CGRAM cell self->cgram[oldchar].refcount -= 1; } self->screen[row][col] = ch.uint; goto done_dirty; } // Find if it's in CGROM const struct cgrom_entry *rom = self->cgrom; for (;;) { if (rom->symbol.uint == 0) { // End of the lookup table break; } if (rom->symbol.uint == ch.uint) { // found it! if (oldchar < 8) { // release refcount on the CGRAM cell self->cgram[oldchar].refcount -= 1; } self->screen[row][col] = rom->address; goto done_dirty; } rom++; } // Check if the same custom char is already used - if so, increment refcount and reuse it int first_empty_custom_slot = -1; for (int i = 0; i < 8; i++) { if (self->cgram[i].refcount > 0) { if (self->cgram[i].uint == ch.uint) { if (oldchar == i) { // No change, was already the same custom return; } if (oldchar < 8) { // release refcount on the CGRAM cell self->cgram[oldchar].refcount -= 1; } self->cgram[i].refcount += 1; self->screen[row][col] = i; goto done_dirty; } } else if (first_empty_custom_slot == -1) { first_empty_custom_slot = i; } } // New custom pattern is needed if (oldchar < 8) { // release refcount on the CGRAM cell self->cgram[oldchar].refcount -= 1; } uint32_t index = 0; const struct cgram_pattern *pattern = self->custom_symbols; for (;;) { if (pattern->symbol.uint == 0) { // End of the lookup table break; } if (pattern->symbol.uint == ch.uint) { // found it! if (first_empty_custom_slot == -1) { // Whoops, out of slots. Show a fallback glyph if (oldchar != pattern->fallback) { self->screen[row][col] = pattern->fallback; goto done_dirty; } return; } // Allocate a new slot in the CGRAM self->cgram[first_empty_custom_slot].refcount = 1; self->cgram[first_empty_custom_slot].uint = ch.uint; self->cgram[first_empty_custom_slot].dirty = true; // it should be flushed! self->cgram[first_empty_custom_slot].symbol_index = index; self->screen[row][col] = first_empty_custom_slot; goto done_dirty; } index++; pattern++; } // Fallback, no way to show this glyph self->screen[row][col] = '?'; done_dirty: mark_dirty(self, row, col); } /** Write a UTF8 string at a position */ void LcdBuffer_Write(struct LcdBuffer *self, uint8_t row, uint8_t col, char *utf_string) { struct Utf8Iterator iter; Utf8Iterator_Init(&iter, utf_string); struct Utf8Char uchar; while ((uchar = Utf8Iterator_Next(&iter)).uint) { if (col >= LINE_LEN) { break; } LcdBuffer_Set(self, row, col, uchar); col++; } }