ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
epub_chapter.c
Go to the documentation of this file.
1
24
25#include <stddef.h>
26#include <stdint.h>
27#include <stdlib.h>
28#include <string.h>
29
30#include "miniz.h"
31
32/* stb_truetype is a single-header library; the implementation lives
33 * in apps/shared_libs/third_party/stb/stb_truetype_impl.c so that clang-tidy's
34 * third_party path exclusion keeps stb's dense magic-number / huge-
35 * function code out of the lint pass. We include only declarations
36 * here. */
37#include "epub.h"
38#include "epub_internal.h"
39#include "ra8_attributes.h"
40#include "ra8_err.h"
41#include "ra8_stbtt_guard.h"
42#include "stb_truetype.h"
43
44/* ---------------------------------------------------------------------------
45 * Internal limits.
46 * ---------------------------------------------------------------------------
47 */
48
53typedef enum : uint16_t {
56
57/* ---------------------------------------------------------------------------
58 * Helpers.
59 * ---------------------------------------------------------------------------
60 */
61
78{
79 return (w < 0) || (h < 0);
80}
81
97RA8_PRIV bool priv_epub_book_not_ready(uint8_t in_use, uint8_t zip_archive_active)
98{
99 return (in_use == 0U) || (zip_archive_active == 0U);
100}
101
118RA8_PRIV void priv_epub_join_path(const char* dir, const char* name, char* dst, size_t cap)
119{
120 if ((dst == nullptr) || (cap == 0U)) {
121 return;
122 }
123 dst[0] = '\0';
124 size_t off = 0U;
125 if (dir != nullptr) {
126 while ((off < (cap - 1U)) && (dir[off] != '\0')) {
127 dst[off] = dir[off];
128 ++off;
129 }
130 }
131 if (name != nullptr) {
132 size_t i = 0U;
133 while ((off < (cap - 1U)) && (name[i] != '\0')) {
134 dst[off] = name[i];
135 ++off;
136 ++i;
137 }
138 }
139 dst[off] = '\0';
140}
141
165static ra8_err_t internal_locate_extract(mz_zip_archive* zip,
166 const char* prefixed_path,
167 const char* bare_path,
168 uint8_t* out_buf,
169 size_t max_len,
170 size_t* got_len)
171{
172 int32_t file_idx = mz_zip_reader_locate_file(zip, prefixed_path, nullptr, 0U);
173 if (file_idx < 0) {
174 file_idx = mz_zip_reader_locate_file(zip, bare_path, nullptr, 0U);
175 }
176 if (file_idx < 0) {
177 return k_ra8_err_not_found;
178 }
179 mz_zip_archive_file_stat st;
180 /* Name lookup does not fully parse extended-size metadata, so malformed
181 * central-directory fields may still fail at this validation boundary. */
182 if (mz_zip_reader_file_stat(zip, (mz_uint)file_idx, &st) == MZ_FALSE) {
184 }
185 const ra8_err_t gerr = priv_epub_zip_guard_entry(&st);
186 if (gerr != k_ra8_ok) {
187 return gerr; /* lying header / declared bomb: reject before inflation */
188 }
189 if ((size_t)st.m_uncomp_size > max_len) {
190 return k_ra8_err_no_mem;
191 }
192 /* Defensive: extract can only fail here if the compressed stream is
193 * corrupt (bad CRC or LZ data), which requires a malformed archive. */
194 if (mz_zip_reader_extract_to_mem(zip, (mz_uint)file_idx, out_buf, max_len, 0U) == MZ_FALSE) {
196 }
197 *got_len = (size_t)st.m_uncomp_size;
198 return k_ra8_ok;
199}
200
219static ra8_err_t internal_font_init(const epub_book_t* book, stbtt_fontinfo* out_font)
220{
221 /* Guard against malformed font blobs: stbtt_GetFontOffsetForIndex
222 * returns -1 for non-TTF input, and stbtt_InitFont dereferences past
223 * the buffer when handed that offset. */
224 const int32_t offset = stbtt_GetFontOffsetForIndex(book->font_data, 0);
225 if (offset < 0) {
227 }
228 /* Bound-check the sfnt table directory before stbtt_InitFont walks it: the
229 * font bytes are attacker-controlled (an EPUB @font-face / epub_set_font
230 * blob) and stb_truetype reads the directory with no length check (#217). */
231 if (!ra8_stbtt_sfnt_dir_in_bounds(book->font_data, book->font_size, (uint32_t)offset)) {
233 }
234 if (stbtt_InitFont(out_font, book->font_data, (int)book->font_size, offset) == 0) {
236 }
237 return k_ra8_ok;
238}
239
265static ra8_err_t internal_render_into(const stbtt_fontinfo* font,
266 int32_t codepoint,
267 float font_size,
268 uint8_t* out_bitmap,
269 size_t max_pixels,
270 uint32_t* out_w,
271 uint32_t* out_h)
272{
273 /* No-allocation glyph rasterisation. stbtt_GetCodepointBitmap()
274 * calls STBTT_malloc internally; instead use the two-step "Box"
275 * + "Make" path that writes into the caller's buffer. NASA Rule 3
276 * compliance. */
277 const float scale = stbtt_ScaleForPixelHeight(font, font_size);
278 int x0 = 0;
279 int y0 = 0;
280 int x1 = 0;
281 int y1 = 0;
282 stbtt_GetCodepointBitmapBox(font, codepoint, scale, scale, &x0, &y0, &x1, &y1);
283 const int w = x1 - x0;
284 const int h = y1 - y0;
285 /* The raw glyf bounding box remains untrusted after the font directory has
286 * initialized; reject reversed coordinates before the signed dimensions are
287 * converted to size_t. */
288 if (priv_epub_glyph_dim_invalid(w, h)) {
290 }
291 const size_t total = (size_t)w * (size_t)h;
292 if (total > max_pixels) {
293 return k_ra8_err_no_mem;
294 }
295 if (total > 0U) {
296 /* Stride = w (tightly packed alpha-8). MakeCodepointBitmap writes
297 * directly into out_bitmap with no malloc. */
298 stbtt_MakeCodepointBitmap(font, out_bitmap, w, h, w, scale, scale, codepoint);
299 }
300 *out_w = (uint32_t)w;
301 *out_h = (uint32_t)h;
302 return k_ra8_ok;
303}
304
305/* ---------------------------------------------------------------------------
306 * Public API.
307 * ---------------------------------------------------------------------------
308 */
309
310ra8_err_t epub_get_chapter_count(const epub_book_t* book, uint16_t* out_count)
311{
312 if ((book == nullptr) || (out_count == nullptr)) {
313 return k_ra8_err_null_ptr;
314 }
315 if (book->in_use == 0U) {
317 }
318 *out_count = book->chapter_count;
319 return k_ra8_ok;
320}
321
323 uint16_t idx,
324 uint8_t* out_xhtml,
325 size_t max_len,
326 size_t* got_len)
327{
328 if ((book == nullptr) || (out_xhtml == nullptr) || (got_len == nullptr)) {
329 return k_ra8_err_null_ptr;
330 }
331 *got_len = 0U;
334 }
335 if (max_len == 0U) {
337 }
338 if (idx >= book->chapter_count) {
340 }
341
342 char full_path[k_epub_max_path_len];
343 priv_epub_join_path(book->opf_dir, book->chapter_paths[idx], full_path, sizeof(full_path));
344
345 void* const zip_storage = &book->zip_archive_storage[0];
346 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
347 return internal_locate_extract(zip,
348 full_path,
349 book->chapter_paths[idx],
350 out_xhtml,
351 max_len,
352 got_len);
353}
354
355ra8_err_t epub_get_toc_kind(const epub_book_t* book, uint8_t* out_kind)
356{
357 if ((book == nullptr) || (out_kind == nullptr)) {
358 return k_ra8_err_null_ptr;
359 }
360 if (book->in_use == 0U) {
362 }
363 *out_kind = book->toc_kind;
364 return k_ra8_ok;
365}
366
367ra8_err_t epub_get_toc_count(const epub_book_t* book, uint16_t* out_count)
368{
369 if ((book == nullptr) || (out_count == nullptr)) {
370 return k_ra8_err_null_ptr;
371 }
372 if (book->in_use == 0U) {
374 }
375 *out_count = book->toc_count;
376 return k_ra8_ok;
377}
378
379ra8_err_t epub_get_toc_entry(const epub_book_t* book, uint16_t idx, epub_toc_entry_t* out_entry)
380{
381 if ((book == nullptr) || (out_entry == nullptr)) {
382 return k_ra8_err_null_ptr;
383 }
384 if (book->in_use == 0U) {
386 }
387 if (idx >= book->toc_count) {
389 }
390 *out_entry = book->toc[idx];
391 return k_ra8_ok;
392}
393
395epub_toc_entry_to_chapter(const epub_book_t* book, uint16_t toc_idx, uint16_t* out_chapter_idx)
396{
397 if ((book == nullptr) || (out_chapter_idx == nullptr)) {
398 return k_ra8_err_null_ptr;
399 }
400 if (book->in_use == 0U) {
402 }
403 if (toc_idx >= book->toc_count) {
405 }
406
407 /* Match the entry href up to (but excluding) any "#fragment": the spine
408 * stores whole-document paths, while a TOC href may target an anchor. */
409 const char* const href = book->toc[toc_idx].href;
410 size_t base_len = 0U;
411 while ((href[base_len] != '\0') && (href[base_len] != '#')) {
412 ++base_len;
413 }
414
415 for (uint16_t i = 0U; i < book->chapter_count; i++) {
416 const char* path = book->chapter_paths[i];
417 if (strlen(path) != base_len) {
418 continue;
419 }
420 if (strncmp(path, href, base_len) != 0) {
421 continue;
422 }
423 *out_chapter_idx = i;
424 return k_ra8_ok;
425 }
426 return k_ra8_err_not_found;
427}
428
430{
431 if ((book == nullptr) || (out_meta == nullptr)) {
432 return k_ra8_err_null_ptr;
433 }
434 if (book->in_use == 0U) {
436 }
437 out_meta->title = book->title;
438 out_meta->author = book->author;
439 out_meta->language = book->language;
440 out_meta->identifier = book->identifier;
441 return k_ra8_ok;
442}
443
444ra8_err_t epub_get_cover_image(epub_book_t* book, uint8_t* out_buf, size_t max_len, size_t* got_len)
445{
446 if ((book == nullptr) || (out_buf == nullptr) || (got_len == nullptr)) {
447 return k_ra8_err_null_ptr;
448 }
449 *got_len = 0U;
452 }
453 if (max_len == 0U) {
455 }
456 if (book->cover_path[0] == '\0') {
457 return k_ra8_err_not_found;
458 }
459
460 char full_path[k_epub_max_path_len];
461 priv_epub_join_path(book->opf_dir, book->cover_path, full_path, sizeof(full_path));
462
463 void* const zip_storage = &book->zip_archive_storage[0];
464 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
465 return internal_locate_extract(zip, full_path, book->cover_path, out_buf, max_len, got_len);
466}
467
469 const char* path,
470 uint8_t* out_buf,
471 size_t max_len,
472 size_t* got_len)
473{
474 if ((book == nullptr) || (path == nullptr) || (out_buf == nullptr) || (got_len == nullptr)) {
475 return k_ra8_err_null_ptr;
476 }
477 *got_len = 0U;
480 }
481 if (max_len == 0U) {
483 }
484
485 char full_path[k_epub_max_path_len];
486 priv_epub_join_path(book->opf_dir, path, full_path, sizeof(full_path));
487
488 void* const zip_storage = &book->zip_archive_storage[0];
489 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
490 return internal_locate_extract(zip, full_path, path, out_buf, max_len, got_len);
491}
492
493ra8_err_t epub_get_embedded_font_count(const epub_book_t* book, uint16_t* out_count)
494{
495 if ((book == nullptr) || (out_count == nullptr)) {
496 return k_ra8_err_null_ptr;
497 }
498 if (book->in_use == 0U) {
500 }
501 *out_count = book->embedded_font_count;
502 return k_ra8_ok;
503}
504
505uint16_t epub_manifest_count(const epub_book_t* book)
506{
507 if (book == nullptr) {
508 return 0U;
509 }
510 if (book->in_use == 0U) {
511 return 0U;
512 }
513 return book->manifest_count;
514}
515
516const epub_manifest_item_t* epub_manifest_item(const epub_book_t* book, uint16_t index)
517{
518 if ((book == nullptr) || (book->in_use == 0U)) {
519 return nullptr;
520 }
521 if (index >= book->manifest_count) {
522 return nullptr;
523 }
524 return &book->manifest[index];
525}
526
528 uint16_t idx,
529 uint8_t* out_buf,
530 size_t max_len,
531 size_t* got_len)
532{
533 if ((book == nullptr) || (out_buf == nullptr) || (got_len == nullptr)) {
534 return k_ra8_err_null_ptr;
535 }
536 *got_len = 0U;
539 }
540 if (max_len == 0U) {
542 }
543 if (idx >= book->embedded_font_count) {
545 }
546
547 char full_path[k_epub_max_path_len];
548 priv_epub_join_path(book->opf_dir, book->embedded_font_paths[idx], full_path, sizeof(full_path));
549
550 void* const zip_storage = &book->zip_archive_storage[0];
551 mz_zip_archive* zip = (mz_zip_archive*)zip_storage;
552 return internal_locate_extract(zip,
553 full_path,
554 book->embedded_font_paths[idx],
555 out_buf,
556 max_len,
557 got_len);
558}
559
560ra8_err_t epub_set_font(epub_book_t* book, const uint8_t* font_data, size_t font_size)
561{
562 if ((book == nullptr) || (font_data == nullptr)) {
563 return k_ra8_err_null_ptr;
564 }
565 if (book->in_use == 0U) {
567 }
568 if (font_size < (size_t)k_epub_min_font_bytes) {
570 }
571 book->font_data = font_data;
572 book->font_size = font_size;
573 return k_ra8_ok;
574}
575
577 int32_t codepoint,
578 float font_size,
579 uint8_t* out_bitmap,
580 size_t max_pixels,
581 uint32_t* out_w,
582 uint32_t* out_h)
583{
584 if ((book == nullptr) || (out_bitmap == nullptr) || (out_w == nullptr) || (out_h == nullptr)) {
585 return k_ra8_err_null_ptr;
586 }
587 *out_w = 0U;
588 *out_h = 0U;
589 if ((book->in_use == 0U) || (book->font_data == nullptr)) {
591 }
592 if (font_size <= 0.0F) {
594 }
595
596 stbtt_fontinfo font;
597 ra8_err_t err = internal_font_init(book, &font);
598 if (err != k_ra8_ok) {
599 return err;
600 }
601 return internal_render_into(&font, codepoint, font_size, out_bitmap, max_pixels, out_w, out_h);
602}
EPUB (.epub) reader and chapter iterator for ra8-firmware.
@ k_epub_max_path_len
Max href length (incl.
Definition epub.h:89
uint16_t epub_manifest_count(const epub_book_t *book)
Number of <manifest> <item> entries retained (#151).
ra8_err_t epub_set_font(epub_book_t *book, const uint8_t *font_data, size_t font_size)
Attach a TTF font blob to be used by epub_render_glyph().
const epub_manifest_item_t * epub_manifest_item(const epub_book_t *book, uint16_t index)
Borrow the manifest item at index (OPF document order, #151).
ra8_err_t epub_toc_entry_to_chapter(const epub_book_t *book, uint16_t toc_idx, uint16_t *out_chapter_idx)
Resolve a TOC entry to the spine chapter index it points into.
ra8_err_t epub_get_embedded_font_count(const epub_book_t *book, uint16_t *out_count)
Count the fonts the EPUB ships in its OPF manifest (#109).
static ra8_err_t internal_locate_extract(mz_zip_archive *zip, const char *prefixed_path, const char *bare_path, uint8_t *out_buf, size_t max_len, size_t *got_len)
Locate-then-extract a file from the open zip into a caller buffer.
ra8_err_t epub_get_cover_image(epub_book_t *book, uint8_t *out_buf, size_t max_len, size_t *got_len)
Copy the raw bytes of the cover image into the caller's buffer.
bool priv_epub_glyph_dim_invalid(int w, int h)
Pure glyph-dim-invalid predicate.
epub_chapter_internal_t
Implementation-only sizing constants.
@ k_epub_min_font_bytes
Smallest TTF blob we'll accept.
ra8_err_t epub_render_glyph(const epub_book_t *book, int32_t codepoint, float font_size, uint8_t *out_bitmap, size_t max_pixels, uint32_t *out_w, uint32_t *out_h)
Rasterise a single Unicode code point into an alpha-8 bitmap.
ra8_err_t epub_get_toc_kind(const epub_book_t *book, uint8_t *out_kind)
Report which navigation document the TOC was parsed from.
ra8_err_t epub_get_metadata(const epub_book_t *book, epub_metadata_t *out_meta)
Return Dublin Core metadata strings.
bool priv_epub_book_not_ready(uint8_t in_use, uint8_t zip_archive_active)
Pure book-not-ready predicate.
ra8_err_t epub_load_chapter(epub_book_t *book, uint16_t idx, uint8_t *out_xhtml, size_t max_len, size_t *got_len)
Extract the raw XHTML bytes of one chapter into the caller's buffer.
ra8_err_t epub_get_toc_entry(const epub_book_t *book, uint16_t idx, epub_toc_entry_t *out_entry)
Copy one table-of-contents entry into the caller's struct.
static ra8_err_t internal_font_init(const epub_book_t *book, stbtt_fontinfo *out_font)
Initialise an stbtt_fontinfo from the book's font blob.
static ra8_err_t internal_render_into(const stbtt_fontinfo *font, int32_t codepoint, float font_size, uint8_t *out_bitmap, size_t max_pixels, uint32_t *out_w, uint32_t *out_h)
Rasterise the code point and copy into the caller buffer.
void priv_epub_join_path(const char *dir, const char *name, char *dst, size_t cap)
Concatenate dir + name into dst, NUL-terminated.
ra8_err_t epub_get_embedded_font(epub_book_t *book, uint16_t idx, uint8_t *out_buf, size_t max_len, size_t *got_len)
Extract one EPUB-shipped font's bytes into a caller buffer (#109).
ra8_err_t epub_get_chapter_count(const epub_book_t *book, uint16_t *out_count)
Report how many chapters the spine contains.
ra8_err_t epub_get_toc_count(const epub_book_t *book, uint16_t *out_count)
Report how many table-of-contents entries the book exposes.
ra8_err_t epub_get_resource(epub_book_t *book, const char *path, uint8_t *out_buf, size_t max_len, size_t *got_len)
Copy the raw bytes of an arbitrary archive resource into the caller's buffer (#140 external styleshee...
Test-access surface for epub internal helpers (MC/DC).
ra8_err_t priv_epub_zip_guard_entry(const mz_zip_archive_file_stat *st)
Guard one ZIP entry's declared sizes against the policy.
Annotation-attribute framework macros for ra8-firmware.
#define RA8_PRIV
Module-private helper: shared across TUs but only inside one library.
#define RA8_INTERNAL
Marker that a function is intended to be static (file-local).
Error Code Definitions for ra8-firmware.
@ k_ra8_err_no_mem
Static buffer exhausted (no dynamic memory on this project).
Definition ra8_err.h:142
@ k_ra8_err_out_of_range
Sensor or peripheral output out of valid range.
Definition ra8_err.h:337
@ k_ra8_err_invalid_arg
Invalid function argument.
Definition ra8_err.h:152
@ k_ra8_err_validation_failed
Validation rule failed (caller-supplied invariant not satisfied).
Definition ra8_err.h:459
@ k_ra8_err_not_initialized
Module not initialized – _init() not yet called successfully.
Definition ra8_err.h:235
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ k_ra8_err_null_ptr
Pointer was NULL where a valid pointer was required.
Definition ra8_err.h:478
@ k_ra8_err_not_found
Requested item not found (lookup / search missed).
Definition ra8_err.h:173
@ k_ra8_err_invalid_size
Invalid size parameter (too large, too small, or misaligned).
Definition ra8_err.h:167
ra8_err_codes_t ra8_err_t
Canonical error-return type used by every ra8-firmware API.
Definition ra8_err.h:546
int strncmp(const char *s1, const char *s2, size_t n)
Compare two strings up to a specified length.
size_t strlen(const char *s)
Calculate string length.
sfnt (TrueType/OpenType) table-directory bounds guard for stb_truetype.
bool ra8_stbtt_sfnt_dir_in_bounds(const uint8_t *data, size_t len, uint32_t fontstart)
Verify that a font's sfnt table directory lies within its buffer.
Opened EPUB book.
Definition epub.h:286
char author[k_epub_meta_len]
Dublin Core <dc:creator>.
Definition epub.h:327
char chapter_paths[k_epub_max_chapters][k_epub_max_path_len]
Manifest hrefs (relative to OPF dir).
Definition epub.h:323
size_t font_size
TTF blob length.
Definition epub.h:355
char language[k_epub_meta_len]
Dublin Core <dc:language>.
Definition epub.h:328
char cover_path[k_epub_max_path_len]
Path to cover image (or empty).
Definition epub.h:332
uint16_t chapter_count
Spine length actually stored.
Definition epub.h:321
char title[k_epub_meta_len]
Dublin Core <dc:title>.
Definition epub.h:326
uint16_t toc_count
Number of entries stored in toc.
Definition epub.h:350
uint8_t toc_kind
epub_toc_kind_t: source of the TOC.
Definition epub.h:349
epub_manifest_item_t manifest[k_epub_max_manifest]
Items, OPF order.
Definition epub.h:342
uint16_t embedded_font_count
Manifest font items found (<= cap).
Definition epub.h:335
epub_toc_entry_t toc[k_epub_max_toc]
Flattened TOC, document order.
Definition epub.h:351
uint16_t manifest_count
<manifest> <item> entries stored (<= cap).
Definition epub.h:340
uint8_t zip_archive_storage[k_epub_zip_archive_bytes]
Zip archive storage.
Definition epub.h:304
uint8_t zip_archive_active
1 = mz_zip_reader_init succeeded.
Definition epub.h:305
char identifier[k_epub_meta_len]
Dublin Core <dc:identifier> (unique book id).
Definition epub.h:329
uint8_t in_use
1 = open, 0 = closed.
Definition epub.h:358
char opf_dir[k_epub_max_path_len]
Directory portion of the OPF.
Definition epub.h:345
char embedded_font_paths[k_epub_max_fonts][k_epub_max_path_len]
Font hrefs (rel.
Definition epub.h:337
const uint8_t * font_data
TTF blob; NULL if unset.
Definition epub.h:354
One <manifest> <item> entry, retained in OPF document order.
Definition epub.h:260
Dublin Core metadata bundle returned by epub_get_metadata().
Definition epub.h:370
const char * identifier
NUL-terminated unique id; "" if absent.
Definition epub.h:374
const char * author
NUL-terminated creator; "" if absent.
Definition epub.h:372
const char * title
NUL-terminated title; "" if absent.
Definition epub.h:371
const char * language
NUL-terminated language tag; "" if absent.
Definition epub.h:373
One titled entry in the table of contents.
Definition epub.h:142
char href[k_epub_max_path_len]
Target href (rel.
Definition epub.h:144