#include "logic/page_render.h" #include #include #include "logic/bitmap_format.h" #include "logic/builtin_fonts.h" static Font usableFont(PageResources &resources, const char *name) { Font font = resources.font(name); if (validFont(font.data, font.size) && font.scale >= 1) return font; return builtinFont(*findBuiltinFont(FONT_NORMAL_NAME)); } static void clearBox(uint8_t *bitmap, int x, int y, int width, int height) { for (int yy = y; yy < y + height; yy++) for (int xx = x; xx < x + width; xx++) setBitmapPixel(bitmap, xx, yy, false); } // The earlier of two "next change" times (0 = none) static uint32_t sooner(uint32_t a, uint32_t b) { if (a == 0) return b; if (b == 0) return a; return a < b ? a : b; } // Image: a bitmap drawn with its top left corner at x, y. With an entity the name is the // element's text followed by the state, or followed by "default" when there is no bitmap // for the state. static void drawImage(const PageElement &e, PageResources &resources, uint8_t *bitmap, ElementBox &box) { uint8_t image[BITMAP_BYTES]; bool found; if (e.entity[0]) { char name[64]; const char *state = resources.haValue(e); snprintf(name, sizeof(name), "%s%s", e.text, state ? state : ""); found = state && resources.background(name, image); if (!found) { snprintf(name, sizeof(name), "%sdefault", e.text); found = resources.background(name, image); } } else found = resources.background(e.text, image); box = ElementBox(); box.x = e.x; box.y = e.y; if (!found) return; // The box of its dots int left = BITMAP_WIDTH, right = -1, top = BITMAP_HEIGHT, bottom = -1; for (int y = 0; y < BITMAP_HEIGHT; y++) for (int x = 0; x < BITMAP_WIDTH; x++) if (bitmapPixel(image, x, y)) { left = x < left ? x : left; right = x > right ? x : right; top = y < top ? y : top; bottom = y > bottom ? y : bottom; } if (right < 0) return; box.x = e.x + left; box.y = e.y + top; box.width = right - left + 1; box.height = bottom - top + 1; if (e.overlay == Overlay::Box) clearBox(bitmap, box.x - 1, box.y - 1, box.width + 2, box.height + 2); for (int y = top; y <= bottom; y++) for (int x = left; x <= right; x++) if (bitmapPixel(image, x, y)) { int bx = e.x + x, by = e.y + y; bool on = e.overlay == Overlay::Invert ? !bitmapPixel(bitmap, bx, by) : true; setBitmapPixel(bitmap, bx, by, on); } } // A text element; returns the ms until it scrolls on (0: it doesn't scroll) static uint32_t drawTextElement(const PageElement &e, const char *text, uint32_t now_ms, PageResources &resources, uint8_t *bitmap, ElementBox &box) { Font font = usableFont(resources, e.font); int width = textWidth(font, text), height = fontHeight(font); DrawMode mode = e.overlay == Overlay::Invert ? DrawMode::Invert : DrawMode::Set; // Without scrolling the element is as wide as its text, otherwise the window int area = e.scroll_width ? e.scroll_width : width; int left = e.x; if (e.align == Align::Center) left -= area / 2; else if (e.align == Align::Right) left -= area; box.x = left; box.y = e.y; box.width = area; box.height = height; if (e.overlay == Overlay::Box) clearBox(bitmap, left - 1, e.y - 1, area + 2, height + 2); if (width <= area) { // Fits: aligned in the window like the element int x = left; if (e.align == Align::Center) x += (area - width) / 2; else if (e.align == Align::Right) x += area - width; drawText(bitmap, font, x, e.y, text, mode); return 0; } // Scrolls from right to left, the next round follows after SCROLL_GAP dots uint32_t period = width + SCROLL_GAP; uint64_t steps = (uint64_t)now_ms * e.scroll_speed / 1000; int offset = (int)(steps % period); drawText(bitmap, font, left - offset, e.y, text, mode, left, left + area); drawText(bitmap, font, left - offset + (int)period, e.y, text, mode, left, left + area); // Until the next step uint64_t next_step_ms = ((steps + 1) * 1000 + e.scroll_speed - 1) / e.scroll_speed; uint32_t wait = (uint32_t)(next_step_ms - now_ms); return wait ? wait : 1; } uint32_t renderPage(const DynamicPage &page, const LocalTime &time, uint32_t now_ms, PageResources &resources, uint8_t *bitmap, ElementBox *boxes) { memset(bitmap, 0, BITMAP_BYTES); const PageSlot *slot = activeSlot(page, time.hour * 60 + time.minute); if (slot && slot->bitmap[0] && !resources.background(slot->bitmap, bitmap)) memset(bitmap, 0, BITMAP_BYTES); uint32_t next = 0; bool running = resources.stopwatchRunning(); uint32_t stopwatch_ms = resources.stopwatchMs(); for (int i = 0; i < page.element_count && i < PAGE_ELEMENTS; i++) { const PageElement &e = page.elements[i]; ElementBox box; if (e.type == ElementType::Image) drawImage(e, resources, bitmap, box); else { ElementValues values; if (e.type == ElementType::HaValue) values.ha_value = resources.haValue(e); values.stopwatch_ms = stopwatch_ms; char text[80]; elementText(e, time, values, text, sizeof(text)); next = sooner(next, drawTextElement(e, text, now_ms, resources, bitmap, box)); if (elementShowsSeconds(e, time, running)) // the stopwatch's seconds count from its start next = sooner(next, e.type == ElementType::Stopwatch ? 1000 - stopwatch_ms % 1000 : 1000 - (time.millisecond % 1000)); else if (e.type == ElementType::Stopwatch && running) // H:MM: the next stopwatch minute next = sooner(next, 60000 - stopwatch_ms % 60000); } if (boxes) boxes[i] = box; } return next; }