ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
ereader_ui_screens.c
Go to the documentation of this file.
1
21
22#include <stddef.h>
23#include <stdint.h>
24#include <string.h>
25
26#include "er_pageturn.h"
27#include "ereader_ui_steps.h"
28#include "figure_fixture.h"
29#include "ra8_box.h"
30#include "ra8_err.h"
31#include "ra8_gfx.h"
32#include "ra8_gfx_font.h"
33#include "ra8_glyph_atlas.h"
34#include "ra8_ui.h"
35#include "ra8_widget.h"
36#include "reflow.h"
37#include "reflow_image.h"
38
40extern const uint8_t g_ra8_font_literata_latin1[];
41extern const unsigned int g_ra8_font_literata_latin1_len;
42
43/* ===========================================================================
44 * Library/Reading box-tree side tables (owned here -- single-screen state).
45 * =========================================================================== */
46
49
51static const char* s_label[k_er_max_nodes];
52
54static uint32_t s_label_col[k_er_max_nodes];
55
57static int16_t s_progress[k_er_max_nodes];
58
60static uint16_t s_lib_node_count;
61
63static uint16_t s_lib_body_first = 2U;
64
71static const char k_er_logo_svg[] =
72 "<svg viewBox=\"0 0 120 80\">"
73 "<rect x=\"0\" y=\"0\" width=\"120\" height=\"80\" fill=\"#283C82\"/>"
74 "<circle cx=\"34\" cy=\"40\" r=\"24\" fill=\"#E6C84B\"/>"
75 "<path d=\"M66 62 L112 62 L89 16 Z\" fill=\"#C03A37\"/>"
76 "</svg>";
77
79typedef enum : uint32_t {
80 k_er_logo_svg_len = (uint32_t)(sizeof(k_er_logo_svg) - 1U),
83
106static ra8_err_t er_image_loader(void* ctx,
107 const char* href,
108 uint32_t href_len,
109 const uint8_t** out_bytes,
110 size_t* out_len)
111{
112 (void)ctx;
113 /* `*.svg` -> the vector logo; everything else -> the baked raster figure. */
114 if ((href_len >= (uint32_t)k_er_ext_len) &&
115 (memcmp(&href[href_len - (uint32_t)k_er_ext_len], ".svg", (size_t)k_er_ext_len) == 0)) {
116 *out_bytes = (const uint8_t*)k_er_logo_svg;
117 *out_len = (size_t)k_er_logo_svg_len;
118 return k_ra8_ok;
119 }
120 *out_bytes = k_er_figure_png;
121 *out_len = (size_t)k_er_figure_png_len;
122 return k_ra8_ok;
123}
124
125/* ===========================================================================
126 * Text helpers
127 * =========================================================================== */
128
129void er_text_left(int32_t x, int32_t y, const char* str, uint32_t color)
130{
131 (void)ra8_gfx_text_out(x, y, str, &ra8_gfx_font_8x16, color, (uint32_t)k_er_paper);
132}
133
134void er_text_right(int32_t right, int32_t y, const char* str, uint32_t color)
135{
136 uint32_t w = 0U;
137 uint32_t h = 0U;
138 if (ra8_gfx_text_size(str, &ra8_gfx_font_8x16, &w, &h) != k_ra8_ok) {
139 return;
140 }
141 er_text_left(right - (int32_t)w, y, str, color);
142}
143
144/* ===========================================================================
145 * Library screen -- built with ra8_box, rendered with ra8_gfx
146 * =========================================================================== */
147
159static void er_reset_side_tables(void)
160{
161 for (uint16_t i = 0U; i < (uint16_t)k_er_max_nodes; ++i) {
162 s_label[i] = nullptr;
163 s_label_col[i] = (uint32_t)k_er_ink;
164 s_progress[i] = -1;
165 }
166}
167
187static ra8_box_t er_leaf(int16_t fixed, uint16_t flex, uint32_t fill, uint32_t border)
188{
189 ra8_box_t n = {};
190 n.kind = (uint8_t)k_ra8_box_leaf;
191 n.fixed = fixed;
192 n.flex = flex;
193 n.fill = fill;
194 n.border = border;
195 n.border_w = (border != (uint32_t)k_ra8_box_no_colour) ? (int16_t)k_er_border_w : (int16_t)0;
196 n.grid_cols = 1U;
197 n.tag = (int16_t)k_ra8_box_none;
198 return n;
199}
200
221static ra8_box_t
222er_container(ra8_box_kind_t kind, int16_t fixed, int16_t pad, int16_t gap, uint8_t cols)
223{
224 ra8_box_t n = {};
225 n.kind = (uint8_t)kind;
226 n.fixed = fixed;
227 n.flex = 1U;
228 n.pad = pad;
229 n.gap = gap;
230 n.grid_cols = (cols >= 1U) ? cols : 1U;
231 n.tag = (int16_t)k_ra8_box_none;
232 return n;
233}
234
249static void er_build_toolbar(ra8_box_tree_t* tree, int16_t parent)
250{
252 (int16_t)k_er_toolbar_h,
253 (int16_t)k_er_pad_ui,
254 (int16_t)k_er_pad_ui,
255 1U);
256 const int16_t tb = ra8_box_add(tree, parent, &tb_t);
257
258 ra8_box_t srch_t = er_leaf(0, 1U, (uint32_t)k_ra8_box_no_colour, (uint32_t)k_er_ink);
259 srch_t.tag = (int16_t)k_er_act_search; /* tap the Search field -> keyboard */
260 const int16_t srch = ra8_box_add(tree, tb, &srch_t);
262 s_label_col[srch] = (uint32_t)k_er_ink_muted;
263
264 const ra8_box_t cnt_t =
265 er_leaf((int16_t)k_er_count_w, 0U, (uint32_t)k_ra8_box_no_colour, (uint32_t)k_er_ink);
266 const int16_t cnt = ra8_box_add(tree, tb, &cnt_t);
268 s_label_col[cnt] = (uint32_t)k_er_ink_muted;
269}
270
286static void er_add_book_tile(ra8_box_tree_t* tree, int16_t grid, const er_book_t* book)
287{
288 ra8_box_t tile_t = er_container(k_ra8_box_stack_v, 0, 0, (int16_t)k_er_tile_gap, 1U);
289 tile_t.tag = (int16_t)k_er_act_open_book; /* the whole card is a tap target */
290 const int16_t tile = ra8_box_add(tree, grid, &tile_t);
291
292 const ra8_box_t cover_t = er_leaf(0, 1U, (uint32_t)k_er_fill, (uint32_t)k_er_ink);
293 (void)ra8_box_add(tree, tile, &cover_t);
294
295 const ra8_box_t lbl_t = er_leaf((int16_t)k_er_card_label_h,
296 0U,
297 (uint32_t)k_ra8_box_no_colour,
298 (uint32_t)k_ra8_box_no_colour);
299 const int16_t title = ra8_box_add(tree, tile, &lbl_t);
300 s_label[title] = book->title;
301 const int16_t auth = ra8_box_add(tree, tile, &lbl_t);
302 s_label[auth] = book->author;
303 s_label_col[auth] = (uint32_t)k_er_ink_muted;
304
305 const ra8_box_t bar_t =
306 er_leaf((int16_t)k_er_card_bar_h, 0U, (uint32_t)k_er_rule_soft, (uint32_t)k_ra8_box_no_colour);
307 const int16_t bar = ra8_box_add(tree, tile, &bar_t);
308 s_progress[bar] = (int16_t)book->pct;
309}
310
325static void er_build_nav(ra8_box_tree_t* tree, int16_t parent)
326{
327 const ra8_box_t nav_t = er_container(k_ra8_box_stack_h, (int16_t)k_er_nav_h, 0, 0, 1U);
328 const int16_t nav = ra8_box_add(tree, parent, &nav_t);
329 for (uint16_t i = 0U; i < (uint16_t)k_er_nav_count; ++i) {
330 ra8_box_t item_t = er_leaf(0, 1U, (uint32_t)k_ra8_box_no_colour, (uint32_t)k_ra8_box_no_colour);
331#ifdef RA8_APP_SETTINGS
332 /* The "Settings" nav item is tappable only when the Settings app is installed
333 * (RA8_APP_SETTINGS): tagging affects target collection, not pixels, so the
334 * default (uninstalled) build's chrome is byte-identical. */
335 if (i == (uint16_t)k_er_nav_idx_setting) {
336 item_t.tag = (int16_t)k_er_act_settings;
337 }
338#endif
339 const int16_t item = ra8_box_add(tree, nav, &item_t);
340 s_label[item] = k_er_nav_items[i];
341 s_label_col[item] = (i == 0U) ? (uint32_t)k_er_ink : (uint32_t)k_er_ink_muted;
342 }
343}
344
346typedef enum : uint32_t {
349
351static char er_lc(char c)
352{
353 return ((c >= 'A') && (c <= 'Z')) ? (char)((c - 'A') + 'a') : c;
354}
355
357static bool er_ci_contains(const char* hay, const char* needle)
358{
359 if (needle[0] == '\0') {
360 return true;
361 }
362 for (uint32_t i = 0U; (i < (uint32_t)k_er_match_scan_max) && (hay[i] != '\0'); i++) {
363 uint32_t j = 0U;
364 while ((j < (uint32_t)k_ra8_kbd_text_max) && (needle[j] != '\0') && (hay[i + j] != '\0') &&
365 (er_lc(hay[i + j]) == er_lc(needle[j]))) {
366 j++;
367 }
368 if (needle[j] == '\0') {
369 return true;
370 }
371 }
372 return false;
373}
374
376static bool er_book_matches(const er_book_t* book)
377{
378 if (!s_query.committed || (s_query.len == 0U)) {
379 return true;
380 }
381 return er_ci_contains(book->title, s_query.buf);
382}
383
398static void er_build_library(ra8_box_tree_t* tree, const ra8_ui_rect_t* frame)
399{
401 (void)ra8_box_tree_init(tree, s_nodes, (uint16_t)k_er_max_nodes);
402
403 const ra8_box_t root_t = er_container(k_ra8_box_stack_v, 0, 0, 0, 1U);
404 const int16_t root = ra8_box_add(tree, (int16_t)k_ra8_box_none, &root_t);
405
406 const ra8_box_t sb_t = er_leaf((int16_t)k_er_statusbar_h,
407 0U,
408 (uint32_t)k_ra8_box_no_colour,
409 (uint32_t)k_ra8_box_no_colour);
410 const int16_t sb = ra8_box_add(tree, root, &sb_t);
412 /* The status bar is one node; everything after it is the body band (the band
413 * widgets in er_render_library split the render at this index). */
414 s_lib_body_first = (uint16_t)(sb + 1);
415
416 er_build_toolbar(tree, root);
417
419 0,
420 (int16_t)k_er_pad_ui,
421 (int16_t)k_er_grid_gap,
422 (uint8_t)k_er_grid_cols);
423 const int16_t grid = ra8_box_add(tree, root, &grid_t);
424 for (uint16_t i = 0U; i < (uint16_t)k_er_book_count; ++i) {
425 if (!er_book_matches(&k_er_books[i])) {
426 continue; /* hidden by the committed search query */
427 }
428 er_add_book_tile(tree, grid, &k_er_books[i]);
429 }
430
431 er_build_nav(tree, root);
432
433 (void)ra8_box_layout(tree, root, frame);
434}
435
450static void er_render_boxnodes(uint16_t from, uint16_t to)
451{
452 for (uint16_t i = from; i < to; ++i) {
453 const ra8_box_t* n = &s_nodes[i];
454 const ra8_ui_rect_t r = n->rect;
455 if (n->fill != (uint32_t)k_ra8_box_no_colour) {
456 (void)ra8_gfx_rect(r.x, r.y, r.w, r.h, n->fill, true);
457 }
458 if ((n->border_w > 0) && (n->border != (uint32_t)k_ra8_box_no_colour)) {
459 (void)ra8_gfx_rect(r.x, r.y, r.w, r.h, n->border, false);
460 }
461 if (s_progress[i] >= 0) {
462 const int32_t fillw = (r.w * (int32_t)s_progress[i]) / (int32_t)k_er_pct_full;
463 (void)ra8_gfx_rect(r.x, r.y, fillw, r.h, (uint32_t)k_er_fill_deep, true);
464 }
465 if (s_label[i] != nullptr) {
466 er_text_left(r.x + (int32_t)k_er_text_pad,
467 r.y + (int32_t)k_er_text_pad,
468 s_label[i],
469 s_label_col[i]);
470 }
471 }
472}
473
487static void er_collect_targets(const ra8_box_tree_t* tree)
488{
489 s_target_count = 0U;
490 for (uint16_t i = 0U; i < tree->count; ++i) {
491 const ra8_box_t* n = &tree->nodes[i];
492 if ((n->tag != (int16_t)k_ra8_box_none) && (s_target_count < (uint16_t)k_er_max_targets)) {
493 s_targets[s_target_count].rect = n->rect;
494 s_targets[s_target_count].action_id = (uint16_t)n->tag;
495 s_targets[s_target_count].reserved = 0U;
497 }
498 }
499}
500
513{
514 (void)w;
515 er_render_boxnodes(0U, s_lib_body_first); /* root (no-op) + the status-bar node */
516 (void)ra8_gfx_rect(0,
517 (int32_t)k_er_statusbar_h - (int32_t)k_er_hair,
518 (int32_t)s_fb.width_px,
519 (int32_t)k_er_hair,
520 (uint32_t)k_er_rule,
521 true);
522 er_text_right((int32_t)s_fb.width_px - (int32_t)k_er_pad_ui,
523 (int32_t)k_er_text_inset_y,
525 (uint32_t)k_er_ink_muted);
526}
527
530{
531 (void)w;
532 er_render_boxnodes(s_lib_body_first, s_lib_node_count); /* toolbar + grid + nav */
533 (void)ra8_gfx_rect(0,
534 (int32_t)s_fb.height_px - (int32_t)k_er_nav_h,
535 (int32_t)s_fb.width_px,
536 (int32_t)k_er_hair,
537 (uint32_t)k_er_rule,
538 true);
539}
540
544
546{
547 (void)ra8_gfx_clear((uint32_t)k_er_paper);
548 ra8_box_tree_t tree;
549 const ra8_ui_rect_t frame = {0, 0, (int32_t)s_fb.width_px, (int32_t)s_fb.height_px};
550 er_build_library(&tree, &frame);
551 er_collect_targets(&tree);
552 s_lib_node_count = tree.count;
553
554 /* Compose the screen from two band widgets (#145): a status bar over the body
555 * (toolbar + grid + nav). The bands occupy disjoint y-ranges, so the widget
556 * composition renders byte-identically to the monolithic box tree -- the
557 * status bar can now be invalidated + partial-flushed on its own. */
558 ra8_widget_t bands[2] = {};
559 bands[0].vt = &k_er_lib_sb_vt;
560 bands[0].fixed = (int16_t)k_er_statusbar_h;
561 bands[0].visible = true;
562 bands[1].vt = &k_er_lib_body_vt;
563 bands[1].flex = 1U;
564 bands[1].visible = true;
565 ra8_box_t band_scratch[3];
566 (void)ra8_widget_layout_stack(bands, 2U, &frame, k_ra8_widget_axis_col, 0, 0, band_scratch, 3U);
569 (void)ra8_widget_render_dirty(bands, 2U);
570}
571
572/* ===========================================================================
573 * Reading screen -- reflow body text (SD font) with a bitmap fallback
574 * =========================================================================== */
575
594static void er_bind_glyph_atlas(void)
595{
596 [[gnu::section(".sdram_data")]] static uint8_t
597 s_glyph_cells[(size_t)k_er_glyph_cells * (size_t)k_er_glyph_cell_bytes];
598 static ra8_keycache_cell_t s_glyph_meta[k_er_glyph_cells];
599 static ra8_glyph_key_t s_glyph_keys[k_er_glyph_cells];
600 static ra8_glyph_dims_t s_glyph_dims[k_er_glyph_cells];
601 /* cppcheck-suppress unassignedVariable ; hash-bucket heads are cleared by
602 ra8_glyph_atlas_init() through the storage pointer; not a direct assignment. */
603 static int32_t s_glyph_buckets[k_er_glyph_buckets];
604 static ra8_glyph_atlas_t s_glyph_atlas;
605 const reflow_glyph_atlas_storage_t glyph_store = {
606 .cell_mem = s_glyph_cells,
607 .cell_bytes = (uint32_t)k_er_glyph_cell_bytes,
608 .cell_count = (uint32_t)k_er_glyph_cells,
609 .meta = s_glyph_meta,
610 .keys = s_glyph_keys,
611 .dims = s_glyph_dims,
612 .buckets = s_glyph_buckets,
613 .bucket_count = (uint32_t)k_er_glyph_buckets,
614 };
615 (void)reflow_set_glyph_atlas(&s_reflow_engine, &s_glyph_atlas, &glyph_store);
616}
617
640static bool
641er_reflow_relayout(int32_t body_w, int32_t body_h, const uint8_t* font_data, uint32_t font_len)
642{
643 if (s_reflow_open) {
645 s_reflow_open = false;
646 }
647 if (reflow_init((uint16_t)body_w,
648 (uint16_t)body_h,
649 font_data,
650 font_len,
651 (uint16_t)k_er_reflow_px,
652 (uint32_t)k_er_reflow_ink,
653 (uint32_t)k_er_reflow_link,
655 return false;
656 }
657 /* Bind the image loader + SDRAM decode arena so the chapter's <img> renders
658 * (decode -> scale -> blit). Without this the engine reserves a placeholder. */
659 static ra8_img_arena_t s_reflow_img_arena;
660 s_reflow_img_arena = (ra8_img_arena_t){.base = s_img_arena_buf,
661 .cap = (size_t)k_er_img_arena,
662 .offset = 0U,
663 .live = 0U};
664 (void)reflow_set_image_loader(&s_reflow_engine, er_image_loader, nullptr, &s_reflow_img_arena);
666
667 uint32_t pages = 0U;
668 const er_chapter_t* chap = &k_er_spine[s_chapter_idx];
669 if (reflow_layout_chapter(&s_reflow_engine, (const uint8_t*)chap->xhtml, chap->len, &pages) !=
670 k_ra8_ok) {
672 return false;
673 }
674 s_reading_pages = pages;
675 s_reflow_open = true;
677 s_reflow_w = body_w;
678 s_reflow_h = body_h;
679 return true;
680}
681
707static bool er_draw_reading_body_reflow(int32_t body_top, int32_t height)
708{
709 /* Reflow from the SD-loaded font if present, else the Latin-1 face baked into
710 * flash (#66) -- so the Reading body shows real proportional text with no card
711 * at all. Only a reflow-engine failure falls through to the bitmap body. */
712 const uint8_t* font_data = s_have_font ? s_font_buf : g_ra8_font_literata_latin1;
713 const uint32_t font_len = s_have_font ? s_font_len : g_ra8_font_literata_latin1_len;
714 const int32_t body_w = (int32_t)s_fb.width_px - ((int32_t)k_er_margin_x * 2);
715 const int32_t body_h =
716 height - (int32_t)k_er_statusbar_h - (int32_t)k_er_footer_h - ((int32_t)k_er_body_gap * 2);
717 if (body_w <= 0) {
718 return false;
719 }
720 if (body_h <= 0) {
721 return false;
722 }
723 /* Re-flow only when the chapter or body geometry changes. Laying a chapter out
724 * (parse XHTML + paginate every page) dwarfs the cost of rasterising one page,
725 * so a same-chapter page turn reuses the cached layout and only renders the
726 * requested page -- otherwise every turn re-parses + re-paginates the whole
727 * chapter, the multi-second page-turn stall. */
728 const bool need_layout = (!s_reflow_open) || (s_reflow_chapter != s_chapter_idx) ||
729 (s_reflow_w != body_w) || (s_reflow_h != body_h);
730 if (need_layout && !er_reflow_relayout(body_w, body_h, font_data, font_len)) {
731 return false;
732 }
733 /* Clamp the current page (the chapter may paginate shorter than the last one)
734 * and rasterise just that page from the cached layout. */
737 }
739 return true;
740}
741
758static bool er_draw_reading_body(int32_t height)
759{
760 const int32_t body_top = (int32_t)k_er_statusbar_h + (int32_t)k_er_body_gap;
761 if (er_draw_reading_body_reflow(body_top, height)) {
762 return true; /* Live proportional text (SD font, else the baked flash font). */
763 }
764 /* Fallback: the bundled 8x16 bitmap font (no SD card / no FONT.OTF). */
765 const int32_t body_bot = height - (int32_t)k_er_footer_h - (int32_t)k_er_body_gap;
766 er_text_left((int32_t)k_er_margin_x, body_top, k_er_chapter, (uint32_t)k_er_ink);
767 int32_t y = body_top + ((int32_t)k_er_blank_lines * (int32_t)k_er_line_h);
768 for (uint16_t i = 0U; i < (uint16_t)k_er_body_line_count; ++i) {
769 if ((y + (int32_t)k_er_glyph_h) > body_bot) {
770 break;
771 }
772 er_text_left((int32_t)k_er_margin_x, y, k_er_body_lines[i], (uint32_t)k_er_ink);
773 y += (int32_t)k_er_line_h;
774 }
775 return false; /* bitmap fallback -- the body is not paginated */
776}
777
779static bool s_read_reflowed = false;
780
783{
784 (void)w;
785 const int32_t width = (int32_t)s_fb.width_px;
786 er_text_left((int32_t)k_er_pad_ui, (int32_t)k_er_text_inset_y, k_er_wordmark, (uint32_t)k_er_ink);
787 const int32_t title_x =
788 (int32_t)k_er_pad_ui + ((int32_t)sizeof(k_er_wordmark) * (int32_t)k_er_glyph_w);
789 er_text_left(title_x, (int32_t)k_er_text_inset_y, k_er_book_title, (uint32_t)k_er_ink_muted);
790 er_text_right(width - (int32_t)k_er_pad_ui,
791 (int32_t)k_er_text_inset_y,
793 (uint32_t)k_er_ink_muted);
794 (void)ra8_gfx_rect(0,
795 (int32_t)k_er_statusbar_h - (int32_t)k_er_hair,
796 width,
797 (int32_t)k_er_hair,
798 (uint32_t)k_er_rule,
799 true);
800}
801
804{
805 (void)w;
806 s_read_reflowed = er_draw_reading_body((int32_t)s_fb.height_px);
807}
808
811{
812 (void)w;
813 const int32_t width = (int32_t)s_fb.width_px;
814 const int32_t height = (int32_t)s_fb.height_px;
815 const int32_t band_top = height - (int32_t)k_er_footer_h;
816 (void)ra8_gfx_rect(0, band_top, width, (int32_t)k_er_hair, (uint32_t)k_er_rule, true);
817 const int32_t text_y = band_top + (int32_t)k_er_progress_gap;
818 er_text_left((int32_t)k_er_pad_ui, text_y, k_er_book_title, (uint32_t)k_er_ink_muted);
819 er_text_right(width - (int32_t)k_er_pad_ui, text_y, k_er_page_label, (uint32_t)k_er_ink_muted);
820 const int32_t track_x = (int32_t)k_er_pad_ui;
821 const int32_t track_w = width - (2 * (int32_t)k_er_pad_ui);
822 const int32_t track_y = height - (int32_t)k_er_progress_gap - (int32_t)k_er_progress_h;
823 (void)ra8_gfx_rect(track_x,
824 track_y,
825 track_w,
826 (int32_t)k_er_progress_h,
827 (uint32_t)k_er_rule_soft,
828 true);
829 /* In the reflow path the bar tracks the live page; the bitmap fallback keeps
830 * the static placeholder ratio. */
831 const int32_t pg_cur =
832 s_read_reflowed ? (int32_t)(s_reading_page + 1U) : (int32_t)k_er_page_current;
833 const int32_t pg_tot = s_read_reflowed ? (int32_t)s_reading_pages : (int32_t)k_er_page_total;
834 const int32_t fill_w = (track_w * pg_cur) / pg_tot;
835 (void)ra8_gfx_rect(track_x,
836 track_y,
837 fill_w,
838 (int32_t)k_er_progress_h,
839 (uint32_t)k_er_fill_deep,
840 true);
841}
842
847
849{
850 (void)ra8_gfx_clear((uint32_t)k_er_paper);
851 const ra8_ui_rect_t frame = {0, 0, (int32_t)s_fb.width_px, (int32_t)s_fb.height_px};
852 ra8_widget_t bands[3] = {};
853 bands[0].vt = &k_er_read_sb_vt;
854 bands[0].fixed = (int16_t)k_er_statusbar_h;
855 bands[0].visible = true;
856 bands[1].vt = &k_er_read_body_vt;
857 bands[1].flex = 1U;
858 bands[1].visible = true;
859 bands[2].vt = &k_er_read_footer_vt;
860 bands[2].fixed = (int16_t)k_er_footer_h;
861 bands[2].visible = true;
862 ra8_box_t band_scratch[4];
863 (void)ra8_widget_layout_stack(bands, 3U, &frame, k_ra8_widget_axis_col, 0, 0, band_scratch, 4U);
864 for (uint16_t i = 0U; i < 3U; ++i) {
866 }
867 (void)ra8_widget_render_dirty(bands, 3U);
868}
869
870/* ===========================================================================
871 * In-content navigation (#110) -- page turns + `<a href>` link following
872 * =========================================================================== */
873
874void er_push_loc(void)
875{
876 if (s_loc_back_count < (uint32_t)k_er_page_back_cap) {
880 }
881}
882
883uint32_t er_spine_count(void)
884{
885 return (uint32_t)k_er_spine_count;
886}
887
889{
890 uint32_t n_chap = s_chapter_idx;
891 uint32_t n_page = s_reading_page;
892 bool crossed = false;
897 dir,
898 &n_chap,
899 &n_page,
900 &crossed)) {
901 return false;
902 }
903 s_chapter_idx = n_chap;
904 s_reading_page = n_page;
906 g_er_turns++;
907 return true;
908}
909
917static bool er_nav_fragment(uint32_t off, uint32_t frag_off, uint32_t frag_len)
918{
919 uint32_t page = 0U;
921 (const char*)&s_reflow_engine.text_pool[off + frag_off],
922 frag_len,
923 &page) != k_ra8_ok) {
924 return false;
925 }
926 if (page == s_reading_page) {
927 return false;
928 }
929 er_push_loc();
930 s_reading_page = page;
931 return true;
932}
933
940static bool er_nav_chapter(uint32_t off, uint32_t path_len)
941{
942 const char* path = (const char*)&s_reflow_engine.text_pool[off];
943 const uint32_t count = er_spine_count();
944 for (uint32_t i = 0U; i < count; ++i) {
945 if ((k_er_spine[i].href_len != path_len) ||
946 (memcmp(path, k_er_spine[i].href, (size_t)path_len) != 0)) {
947 continue;
948 }
949 if (i == s_chapter_idx) {
950 return false;
951 }
952 er_push_loc();
953 s_chapter_idx = i;
954 s_reading_page = 0U;
955 return true;
956 }
957 return false;
958}
959
960bool er_reading_link_tap(int32_t x, int32_t y)
961{
962 const int32_t body_top = (int32_t)k_er_statusbar_h + (int32_t)k_er_body_gap;
963 uint32_t off = 0U;
964 uint32_t len = 0U;
967 x - (int32_t)k_er_margin_x,
968 y - body_top,
969 &off,
970 &len) != k_ra8_ok) {
971 return false;
972 }
974 uint32_t path_len = 0U;
975 uint32_t frag_off = 0U;
976 uint32_t frag_len = 0U;
977 if (reflow_href_split((const char*)&s_reflow_engine.text_pool[off],
978 len,
979 &kind,
980 &path_len,
981 &frag_off,
982 &frag_len) != k_ra8_ok) {
983 return false;
984 }
985 if (kind == k_reflow_href_fragment) {
986 return er_nav_fragment(off, frag_off, frag_len);
987 }
988 if ((kind == k_reflow_href_chapter) || (kind == k_reflow_href_chapter_fragment)) {
989 return er_nav_chapter(off, path_len);
990 }
991 return false;
992}
Pure page-turn + input-region decisions for the e-reader (#78).
static bool er_pageturn_step(uint32_t chapter, uint32_t page, uint32_t pages, uint32_t ch_count, er_dir_t dir, uint32_t *out_chap, uint32_t *out_page, bool *out_crossed)
Advance/retreat the reading location by one page, crossing chapters.
er_dir_t
Page-turn direction produced by an input decision.
Definition er_pageturn.h:34
static const char k_er_logo_svg[]
In-content SVG (#112): a navy field, a gold disc, and a crimson block.
static bool er_book_matches(const er_book_t *book)
Does book pass the committed search query (true if no filter)?
static const ra8_widget_vtable_t k_er_lib_sb_vt
Vtables for the Library status-bar / body band widgets.
static bool er_nav_fragment(uint32_t off, uint32_t frag_off, uint32_t frag_len)
Follow a same-chapter #fragment: jump to the anchored page.
void er_text_right(int32_t right, int32_t y, const char *str, uint32_t color)
Draw a right-anchored text run ending at column right.
static bool er_draw_reading_body_reflow(int32_t body_top, int32_t height)
Render the Reading body through reflow when an SD font is loaded.
er_filter_t
Search-filter scan bound (NASA Rule 2).
@ k_er_match_scan_max
Max title chars scanned for a match.
static void er_build_toolbar(ra8_box_tree_t *tree, int16_t parent)
Add the toolbar (search field + book-count chip) under a parent.
static void er_build_library(ra8_box_tree_t *tree, const ra8_ui_rect_t *frame)
Build the Library box tree (status bar, toolbar, grid, nav).
static void er_reset_side_tables(void)
Reset the per-node label / progress side tables.
static const ra8_widget_vtable_t k_er_read_footer_vt
static bool er_reflow_relayout(int32_t body_w, int32_t body_h, const uint8_t *font_data, uint32_t font_len)
Re-init the reflow engine and lay the current chapter into the cache.
static const ra8_widget_vtable_t k_er_read_sb_vt
Vtables for the Reading status-bar / body / footer band widgets.
static bool s_read_reflowed
Stash: did the body band reflow live text?
static void er_lib_sb_render(ra8_widget_t *w)
Render the full Library screen.
void er_render_reading(void)
Render the full Reading screen (status bar, body, footer).
static uint16_t s_lib_node_count
Library box-node count (set by er_render_library for the band widgets).
static bool er_draw_reading_body(int32_t height)
Paint the Reading body: reflowed text (SD or baked font), else bitmap.
static uint16_t s_lib_body_first
First Library body box-node (root=0, status bar=1, body from here).
static void er_collect_targets(const ra8_box_tree_t *tree)
Collect tap targets from a laid-out tree's tagged nodes.
static void er_build_nav(ra8_box_tree_t *tree, int16_t parent)
Add the bottom navigation strip under a parent.
static ra8_box_t er_leaf(int16_t fixed, uint16_t flex, uint32_t fill, uint32_t border)
Make a leaf box template carrying fill / border.
static void er_read_sb_render(ra8_widget_t *w)
Status-bar band widget: wordmark + book title + status-right + hairline.
uint32_t er_spine_count(void)
Number of chapters in the (mock) spine.
static void er_read_footer_render(ra8_widget_t *w)
Footer band widget: rule + chapter title + page label + progress bar.
static char er_lc(char c)
Lower-case an ASCII letter (identity for non-letters).
er_svg_len_t
SVG fixture size + the .svg extension length for href routing.
@ k_er_logo_svg_len
SVG byte count.
@ k_er_ext_len
Length of ".svg".
static int16_t s_progress[k_er_max_nodes]
Per-node progress percent, or -1 for "not a progress bar".
static void er_lib_body_render(ra8_widget_t *w)
Body band widget: toolbar + book grid + nav nodes + the above-nav rule.
static void er_add_book_tile(ra8_box_tree_t *tree, int16_t grid, const er_book_t *book)
Add one book card (cover, title, author, progress) under a grid.
void er_push_loc(void)
Push the current (chapter, page) on the back-stack, capacity permitting.
static void er_bind_glyph_atlas(void)
Bind the Layer-3 glyph atlas (#164) to the reflow engine.
static bool er_nav_chapter(uint32_t off, uint32_t path_len)
Follow a cross-chapter link: resolve the path against the mock spine.
void er_text_left(int32_t x, int32_t y, const char *str, uint32_t color)
Draw a left-anchored text run in the bundled font.
static void er_render_boxnodes(uint16_t from, uint16_t to)
Render box nodes [from, to) of s_nodes (fills, borders, progress bars, labels).
static ra8_box_t er_container(ra8_box_kind_t kind, int16_t fixed, int16_t pad, int16_t gap, uint8_t cols)
Make a container box template (stack or grid).
static const ra8_widget_vtable_t k_er_lib_body_vt
static uint32_t s_label_col[k_er_max_nodes]
Per-node text colour.
bool er_reading_link_tap(int32_t x, int32_t y)
Follow an in-content <a> tap in the Reading body (#110).
bool er_apply_pageturn(er_dir_t dir)
Apply a page turn to the reading location (touch + button shared path).
static void er_read_body_render(ra8_widget_t *w)
Body band widget: the reflowed (or bitmap-fallback) chapter text.
static bool er_ci_contains(const char *hay, const char *needle)
Case-insensitive: does needle occur within hay?
static ra8_err_t er_image_loader(void *ctx, const char *href, uint32_t href_len, const uint8_t **out_bytes, size_t *out_len)
reflow image loader: resolve any <img src> to the baked figure.
static const ra8_widget_vtable_t k_er_read_body_vt
void er_render_library(void)
Render the full Library screen.
E-reader UI chrome – shared contract between main.c and the per-aspect helper translation units (scre...
@ k_er_spine_count
Number of chapters in k_er_spine.
bool s_have_font
True once an SD font is loaded.
Definition main.c:291
const char k_er_count_text[]
Book-count chip text.
Definition main.c:85
@ k_er_fill
Flat fills / covers (g13).
@ k_er_fill_deep
Heavier fill / progress (g8).
@ k_er_rule
Hairline dividers (g10).
@ k_er_paper
Page background (g15).
@ k_er_rule_soft
Faintest hairlines / track (g12).
@ k_er_ink
Primary text / glyphs (g0).
@ k_er_ink_muted
Secondary text / metadata (g5).
bool s_reflow_open
True while the engine holds a layout.
Definition main.c:280
const char k_er_search_hint[]
Search placeholder.
Definition main.c:84
ra8_ui_target_t s_targets[]
Tap targets for the current screen.
Definition main.c:217
reflow_t s_reflow_engine
reflow engine for the body.
Definition main.c:277
ra8_kbd_text_t s_query
Live search query.
Definition main.c:226
@ k_er_img_arena
SDRAM image-decode scratch.
@ k_er_glyph_cells
Glyph-cache budget.
@ k_er_reflow_ink
Body text colour (near-black ARGB).
@ k_er_glyph_cell_bytes
Bytes per cell.
@ k_er_reflow_link
Anchor colour (cerulean ARGB).
@ k_er_glyph_buckets
Glyph-cache hash buckets.
@ k_er_reflow_px
Body type size (pixels).
const char *const k_er_body_lines[]
Reading-view body paragraph lines (pre-wrapped for the font).
Definition main.c:110
uint32_t s_chapter_idx
Reading chapter index.
Definition main.c:178
uint16_t s_target_count
Tap targets currently populated.
Definition main.c:220
uint32_t s_reflow_chapter
Chapter laid out (cache key).
Definition main.c:282
volatile uint32_t g_er_turns
Count of page turns applied since boot.
Definition main.c:255
const char *const k_er_nav_items[]
Bottom-nav destinations (Library / Store / Notes / Settings).
Definition main.c:91
@ k_er_page_back_cap
Footnote-jump back-stack depth.
@ k_er_max_targets
Max collected tap targets.
const char k_er_page_label[]
Reading page label.
Definition main.c:87
@ k_er_pad_ui
Side padding for bands / grid.
@ k_er_count_w
Toolbar "N books" chip width.
@ k_er_card_label_h
Card title / author row height.
@ k_er_text_inset_y
16 px text centred in a 64 px band.
@ k_er_border_w
Card / control border weight.
@ k_er_text_pad
Inset for text inside a box.
@ k_er_glyph_w
Bundled font cell width.
@ k_er_footer_h
Reading footer band height.
@ k_er_toolbar_h
Library toolbar band height.
@ k_er_card_bar_h
Card progress-bar height.
@ k_er_progress_gap
Gap above the reading progress bar.
@ k_er_nav_h
Library bottom-nav band height.
@ k_er_progress_h
Reading-progress bar height.
@ k_er_line_h
Body line advance.
@ k_er_statusbar_h
Top status-bar band height.
@ k_er_grid_gap
Library grid gap.
@ k_er_tile_gap
Gap between a card's stacked parts.
@ k_er_blank_lines
Blank advance after the heading.
@ k_er_grid_cols
Library grid column count.
@ k_er_margin_x
Reading left/right margin.
@ k_er_body_gap
Gap between band and body text.
@ k_er_hair
Hairline weight.
@ k_er_glyph_h
Bundled font cell height.
display_turn_event_t s_pending_event
Pending refresh event for next flush.
Definition main.c:251
int32_t s_reflow_w
Body width the layout used.
Definition main.c:284
const char k_er_book_title[]
Reading title text.
Definition main.c:86
uint8_t s_img_arena_buf[]
Image-decode bump arena in SDRAM.
Definition main.c:274
@ k_er_page_total
Reading total pages.
@ k_er_pct_full
Percent denominator.
@ k_er_page_current
Reading current page (1-based).
@ k_er_nav_count
Number of bottom-nav destinations.
@ k_er_nav_idx_setting
Index of the "Settings" nav item.
const char k_er_status_right[]
Status-bar right text.
Definition main.c:83
int32_t s_reflow_h
Body height the layout used.
Definition main.c:285
uint32_t s_loc_back_count
Entries used in s_loc_back.
Definition main.c:268
er_loc_t s_loc_back[]
Reading back-stack for link jumps.
Definition main.c:265
uint32_t s_reading_pages
Reading total reflow pages (>= 1).
Definition main.c:232
const char k_er_chapter[]
Bitmap-fallback heading.
Definition main.c:88
const char k_er_wordmark[6]
Wordmark / status text.
Definition main.c:80
uint32_t s_reading_page
Reading current reflow page.
Definition main.c:229
const er_chapter_t k_er_spine[]
Two-chapter mock spine for in-content cross-chapter navigation (#110).
Definition main.c:164
const er_book_t k_er_books[]
Demo shelf (public-domain works).
Definition main.c:96
@ k_er_max_nodes
Upper bound on chrome box nodes.
@ k_er_act_open_book
Library card -> open the reading view.
@ k_er_act_settings
Bottom-nav Settings -> Settings app.
@ k_er_act_search
Toolbar Search -> open the keyboard.
const char k_er_lib_heading[]
Library heading text.
Definition main.c:82
@ k_er_book_count
Number of demo books.
@ k_er_body_line_count
Number of pre-wrapped body lines.
uint32_t s_font_len
Bytes of font read off the card.
Definition main.c:288
static uint8_t s_font_buf[k_pc_font_cap]
Font blob read off the card.
Definition main.c:197
static display_fb_t s_fb
Definition main.c:203
static const char * s_label[k_ch_max_nodes]
Per-node label (NULL = no text), indexed by box-tree node.
Definition main.c:92
Baked 200x130 RGB PNG figure for the Reading screen (#106 Phase 1/2).
static const uint8_t k_er_figure_png[]
Encoded 200x130 RGB PNG figure.
static const uint32_t k_er_figure_png_len
Length of k_er_figure_png in bytes.
static book_node_t s_nodes[k_arena_nodes]
Definition cpu1_main.c:110
const unsigned int g_ra8_font_literata_latin1_len
Length of g_ra8_font_literata_latin1 in bytes.
const unsigned char g_ra8_font_literata_latin1[]
Baked Latin-1 subset of Literata Regular (TrueType/glyf), in flash.
Bounded, allocation-free box-model layout for e-reader chrome.
ra8_err_t ra8_box_tree_init(ra8_box_tree_t *tree, ra8_box_t *storage, uint16_t cap)
Bind a tree builder to caller-owned node storage.
Definition ra8_box.c:284
ra8_err_t ra8_box_layout(ra8_box_tree_t *tree, int16_t root, const ra8_ui_rect_t *frame)
Lay out the tree, filling every node's rect.
Definition ra8_box.c:331
@ k_ra8_box_no_colour
Carried fill/border: absent.
Definition ra8_box.h:58
@ k_ra8_box_none
No child / no sibling link.
Definition ra8_box.h:57
int16_t ra8_box_add(ra8_box_tree_t *tree, int16_t parent, const ra8_box_t *node)
Append a node and link it as a child of parent.
Definition ra8_box.c:297
ra8_box_kind_t
Box layout kind.
Definition ra8_box.h:65
@ k_ra8_box_stack_v
Vertical stack: children stack top->bottom.
Definition ra8_box.h:66
@ k_ra8_box_grid
Fixed-column grid, row-major.
Definition ra8_box.h:68
@ k_ra8_box_leaf
Terminal box (no children laid out).
Definition ra8_box.h:69
@ k_ra8_box_stack_h
Horizontal stack: children left->right.
Definition ra8_box.h:67
@ k_display_event_chapter
Chapter-boundary turn – wants a clean refresh.
@ k_display_event_turn
Ordinary next/prev page turn.
Error Code Definitions for ra8-firmware.
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
int memcmp(const void *a, const void *b, size_t n)
Compare bytes in two memory areas.
Software 2D graphics primitives layered on top of a caller-ownedframebuffer (DRW / D/AVE 2D / GLCDC r...
ra8_err_t ra8_gfx_clear(uint32_t color)
Fill the bound framebuffer's clip region with a single colour.
ra8_err_t ra8_gfx_text_size(const char *str, const ra8_gfx_font_t *font, uint32_t *out_w, uint32_t *out_h)
Compute rendered pixel dimensions of a string.
ra8_err_t ra8_gfx_text_out(int32_t x, int32_t y, const char *str, const ra8_gfx_font_t *font, uint32_t fg_color, uint32_t bg_color)
Render a NUL-terminated ASCII string.
ra8_err_t ra8_gfx_rect(int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color, bool filled)
Draw an axis-aligned rectangle.
Bitmap font descriptor + bundled font handles for ra8_gfx.
const ra8_gfx_font_t ra8_gfx_font_8x16
Bundled 8x16 IBM PC VGA bitmap font, ASCII 0x20..0x7E.
Fixed-RAM-budget glyph cache with LRU eviction (Layer 3, #147).
@ k_ra8_kbd_text_max
Text buffer capacity incl.
Bounded UI interaction core: hit-testing, screen stack, paging.
Zero-heap composable widget layer (dwm-style) over ra8_box + ra8_ui.
@ k_ra8_widget_axis_col
Vertical stack (top -> bottom).
Definition ra8_widget.h:176
ra8_err_t ra8_widget_layout_stack(ra8_widget_t *widgets, uint16_t count, const ra8_ui_rect_t *frame, ra8_widget_axis_t axis, int16_t gap, int16_t pad, ra8_box_t *box_scratch, uint16_t box_cap)
Lay a stack of widgets out inside a frame (delegates to ra8_box).
Definition ra8_widget.c:215
ra8_err_t ra8_widget_invalidate(ra8_widget_t *w, ra8_widget_refresh_t refresh)
Mark a widget dirty with a refresh hint (folds upward in strength).
Definition ra8_widget.c:286
@ k_ra8_widget_refresh_quality
Full / GC16 (whole-area redraw).
Definition ra8_widget.h:101
ra8_err_t ra8_widget_render_dirty(ra8_widget_t *widgets, uint16_t count)
Render every visible + dirty widget, clearing its damage.
Definition ra8_widget.c:331
HTML / CSS reflow + paginate engine for the ra8d2 ereader.
ra8_err_t reflow_hit_test_link(const reflow_t *engine, uint32_t page_idx, int32_t x, int32_t y, uint32_t *out_href_off, uint32_t *out_href_len)
Hit-test a point on a page against the laid-out <a> link rectangles.
Definition reflow_link.c:36
ra8_err_t reflow_layout_chapter(reflow_t *engine, const uint8_t *xhtml_buf, size_t xhtml_len, uint32_t *out_total_pages)
Parse + lay out one chapter of XHTML.
ra8_err_t reflow_find_anchor(const reflow_t *engine, const char *id, uint32_t id_len, uint32_t *out_page)
Find the page of a same-chapter id anchor (for #fragment jumps).
Definition reflow_link.c:88
ra8_err_t reflow_href_split(const char *href, uint32_t len, reflow_href_kind_t *out_kind, uint32_t *out_path_len, uint32_t *out_frag_off, uint32_t *out_frag_len)
Split + classify an <a href> into a path part and a #fragment.
ra8_err_t reflow_init(uint16_t viewport_w, uint16_t viewport_h, const uint8_t *font_data, size_t font_len, uint16_t font_px, uint32_t body_color, uint32_t link_color, reflow_t *out_engine)
Initialise a reflow engine for a given viewport / font.
ra8_err_t reflow_set_glyph_atlas(reflow_t *engine, ra8_glyph_atlas_t *atlas, const reflow_glyph_atlas_storage_t *storage)
Bind a Layer-3 glyph cache to the engine's render path (#164).
ra8_err_t reflow_set_image_loader(reflow_t *engine, reflow_image_loader_fn loader, void *ctx, ra8_img_arena_t *arena)
Bind an <img> byte loader + decode arena to enable image rendering.
ra8_err_t reflow_close(reflow_t *engine)
Release a previously initialized engine.
ra8_err_t reflow_render_page_at(const reflow_t *engine, uint32_t page_idx, int32_t origin_x, int32_t origin_y)
Render one page offset by (origin_x, origin_y) into the bound framebuffer.
Zero-heap raster image decode + scale + blit for reflow (#106).
reflow_href_kind_t
Classification of an in-content <a href> target.
@ k_reflow_href_fragment
"#id" – same-chapter anchor.
@ k_reflow_href_chapter_fragment
"path#id" – chapter + anchor.
@ k_reflow_href_empty
Empty / whitespace-only href.
@ k_reflow_href_chapter
"path" – another chapter, no frag.
One library entry: title, author, reading progress percent.
const char * title
Book title.
const char * author
Author.
uint16_t pct
Reading progress (0..100).
One mock-spine chapter: its XHTML, length, and resolving href.
uint32_t len
Length of xhtml (excl.
const char * xhtml
Chapter XHTML body.
One node in a box tree (caller-owned).
Definition ra8_box.h:90
uint8_t grid_cols
Columns for k_ra8_box_grid (>= 1).
Definition ra8_box.h:92
uint8_t kind
ra8_box_kind_t.
Definition ra8_box.h:91
uint16_t flex
Flex weight when not fixed.
Definition ra8_box.h:94
int16_t fixed
Fixed main-axis extent; 0 => use flex.
Definition ra8_box.h:93
uint32_t border
Border colour, or k_ra8_box_no_colour.
Definition ra8_box.h:98
int16_t tag
Caller tag (e.g.
Definition ra8_box.h:100
int16_t gap
Gap between children.
Definition ra8_box.h:96
int16_t border_w
Border width (0 = no border draw).
Definition ra8_box.h:99
int16_t pad
Inner padding (all four sides).
Definition ra8_box.h:95
ra8_ui_rect_t rect
OUTPUT: computed rectangle.
Definition ra8_box.h:103
uint32_t fill
Background colour, or k_ra8_box_no_colour.
Definition ra8_box.h:97
Append-only builder over caller-owned node storage.
Definition ra8_box.h:115
ra8_box_t * nodes
Caller storage.
Definition ra8_box.h:116
uint16_t count
Nodes used.
Definition ra8_box.h:118
Glyph-cache state (caller-owned; treat as private).
Per-cell user descriptor: the rendered glyph dimensions.
Identifies one rendered glyph.
Caller-owned bump arena backing a single image decode.
Per-cell link metadata (one caller-owned array entry per cell).
Axis-aligned rectangle in framebuffer pixel coordinates.
Definition ra8_ui.h:72
int32_t w
Width (pixels, >= 0).
Definition ra8_ui.h:75
int32_t h
Height (pixels, >= 0).
Definition ra8_ui.h:76
int32_t x
Left edge (pixels).
Definition ra8_ui.h:73
int32_t y
Top edge (pixels).
Definition ra8_ui.h:74
One widget instance (caller-owned, no allocation).
Behaviour table shared by all widgets of one kind.
Definition ra8_widget.h:121
Caller-owned backing storage for the render-path glyph cache (#164).
Definition reflow_api.h:191