ra8-firmware 0.1.0
Bare-metal firmware for the Renesas RA8 family (RA8D2 / RA8P1)
Loading...
Searching...
No Matches
mdl_export_epub_meta.c
Go to the documentation of this file.
1
14
15#include <stdio.h>
16#include <string.h>
17
18#include "mdl_export.h"
20#include "mdl_sanitize.h"
21#include "miniz.h"
22#include "ra8_attributes.h"
23
31
33typedef enum : uint64_t {
34 k_uuid_fnv_prime = 1099511628211ULL,
35 k_uuid_seed_one = 0xCBF29CE484222325ULL,
36 k_uuid_seed_two = 7809847782465536322ULL,
38
54
70RA8_INTERNAL static uint64_t internal_uuid_hash_text(uint64_t hash, const char* text)
71{
72 uint64_t state = hash;
73 for (size_t index = 0U; text[index] != '\0'; ++index) {
74 state ^= (uint64_t)(uint8_t)text[index];
75 state *= (uint64_t)k_uuid_fnv_prime;
76 }
77 state ^= (uint64_t)(uint8_t)k_uuid_separator;
78 return state * (uint64_t)k_uuid_fnv_prime;
79}
80
95RA8_INTERNAL static void
96internal_generate_uuid(char* out, size_t cap, const mdl_export_meta_t* meta)
97{
99 if (meta != nullptr) {
100 m = *meta;
101 } else {
102 mdl_meta_init(&m);
103 }
104 const char* fields[] =
106 uint64_t h1 = (uint64_t)k_uuid_seed_one;
107 uint64_t h2 = (uint64_t)k_uuid_seed_two;
108 for (size_t i = 0U; i < (sizeof(fields) / sizeof(fields[0])); ++i) {
109 h1 = internal_uuid_hash_text(h1, fields[i]);
110 h2 = internal_uuid_hash_text(h2, fields[(sizeof(fields) / sizeof(fields[0])) - 1U - i]);
111 }
112 uint8_t b[k_uuid_byte_count];
113 for (size_t i = 0U; i < (size_t)k_uuid_half_bytes; ++i) {
114 const size_t shift = (size_t)k_uuid_top_shift - (i * (size_t)k_epub_uuid_byte_bits);
115 b[i] = (uint8_t)(h1 >> shift);
116 b[i + (size_t)k_uuid_half_bytes] = (uint8_t)(h2 >> shift);
117 }
122 (void)snprintf(out,
123 cap,
124 "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%"
125 "02x%02x%02x%02x%02x",
126 b[0],
127 b[1],
128 b[2],
129 b[3],
130 b[4],
131 b[5],
132 b[6],
133 b[7],
134 b[8],
135 b[9],
137 b[k_uuid_node_offset + 1U],
138 b[k_uuid_node_offset + 2U],
139 b[k_uuid_node_offset + 3U],
140 b[k_uuid_node_offset + 4U],
142}
143
158
173{
174 text->creators[0] = '\0';
175 if (meta->writer[0] != '\0') {
176 char escaped[k_mdl_meta_name_max * 6U];
177 char fragment[(k_mdl_meta_name_max * 6U) + k_epub_fragment_slack];
178 (void)mdl_xml_escape(meta->writer, escaped, sizeof(escaped));
179 (void)
180 snprintf(fragment, sizeof(fragment), "<dc:creator opf:role=\"aut\">%s</dc:creator>", escaped);
181 (void)priv_mdl_epub_str_cat(text->creators, sizeof(text->creators), fragment);
182 }
183 if (meta->artist[0] != '\0') {
184 char escaped[k_mdl_meta_name_max * 6U];
185 char fragment[(k_mdl_meta_name_max * 6U) + k_epub_fragment_slack];
186 (void)mdl_xml_escape(meta->artist, escaped, sizeof(escaped));
187 (void)
188 snprintf(fragment, sizeof(fragment), "<dc:creator opf:role=\"art\">%s</dc:creator>", escaped);
189 (void)priv_mdl_epub_str_cat(text->creators, sizeof(text->creators), fragment);
190 }
191 if (text->creators[0] == '\0') {
192 (void)snprintf(text->creators, sizeof(text->creators), "<dc:creator>mdl</dc:creator>");
193 }
194}
195
213{
214 text->description[0] = '\0';
215 if (meta->summary[0] != '\0') {
216 char escaped[k_mdl_meta_summary_max * 6U];
217 (void)mdl_xml_escape(meta->summary, escaped, sizeof(escaped));
218 (void)snprintf(text->description,
219 sizeof(text->description),
220 "<dc:description>%s</dc:description>",
221 escaped);
222 }
223 text->source[0] = '\0';
224 if (meta->source_url[0] != '\0') {
225 char escaped[k_mdl_meta_url_max * 6U];
226 if (!mdl_xml_escape(meta->source_url, escaped, sizeof(escaped))) {
228 }
229 const int written =
230 snprintf(text->source, sizeof(text->source), "<dc:source>%s</dc:source>", escaped);
231 if (!priv_mdl_export_snprintf_fit(written, sizeof(text->source))) {
233 }
234 }
235 return k_ra8_ok;
236}
237
255{
256 char raw_id[k_mdl_meta_id_max];
257 if (meta->identifier[0] != '\0') {
258 (void)snprintf(raw_id, sizeof(raw_id), "%s", meta->identifier);
259 } else {
260 internal_generate_uuid(raw_id, sizeof(raw_id), meta);
261 }
262 if (!mdl_xml_escape(raw_id, text->identifier, sizeof(text->identifier)) ||
263 !mdl_xml_escape(meta->language, text->language, sizeof(text->language)) ||
264 !mdl_xml_escape(meta->modified, text->modified, sizeof(text->modified))) {
266 }
267 const char* raw_title;
268 if (meta->chapter_title[0] != '\0') {
269 raw_title = meta->chapter_title;
270 } else if (meta->series_title[0] != '\0') {
271 raw_title = meta->series_title;
272 } else {
273 raw_title = "chapter";
274 }
275 if (!mdl_xml_escape(raw_title, text->title, sizeof(text->title))) {
276 (void)snprintf(text->title, sizeof(text->title), "chapter");
277 }
278 text->progression = (meta->reading_direction == k_mdl_read_rtl) ? "rtl" : "ltr";
280 return internal_epub_prepare_optional(meta, text);
281}
282
308 const char* mani,
309 const char* spine,
310 const char* nav,
311 size_t page_count,
312 const mdl_epub_meta_text_t* text,
313 char* opf,
314 size_t opf_cap,
315 char* navdoc,
316 size_t nav_cap)
317{
318 const size_t opf_need = strlen(mani) + strlen(spine) + strlen(text->creators) +
319 strlen(text->description) + strlen(text->source) +
320 (size_t)k_epub_base_bytes;
321 const size_t nav_need = strlen(nav) + (size_t)k_epub_base_bytes;
322 if ((opf_need > opf_cap) || (nav_need > nav_cap)) {
324 }
325 const int opf_n =
326 snprintf(opf,
327 opf_need,
328 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
329 "<package xmlns=\"http://www.idpf.org/2007/opf\" version=\"3.0\" "
330 "unique-identifier=\"bookid\"><metadata "
331 "xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
332 "xmlns:opf=\"http://www.idpf.org/2007/opf\">"
333 "<dc:identifier id=\"bookid\">%s</dc:identifier>"
334 "<dc:title>%s</dc:title>%s%s%s<dc:language>%s</dc:language>"
335 "<meta property=\"dcterms:modified\">%s</meta>"
336 "<meta property=\"schema:numberOfPages\">%zu</meta>"
337 "</metadata><manifest><item id=\"nav\" href=\"nav.xhtml\" "
338 "media-type=\"application/xhtml+xml\" properties=\"nav\"/>%s</manifest>"
339 "<spine page-progression-direction=\"%s\">%s</spine></package>",
340 text->identifier,
341 text->title,
342 text->creators,
343 text->description,
344 text->source,
345 text->language,
346 text->modified,
347 page_count,
348 mani,
349 text->progression,
350 spine);
351 const int nav_n = snprintf(navdoc,
352 nav_need,
353 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
354 "<html xmlns=\"http://www.w3.org/1999/xhtml\" "
355 "xmlns:epub=\"http://www.idpf.org/2007/ops\"><head><title>Contents"
356 "</title></head><body><nav epub:type=\"toc\"><ol>%s</ol></nav>"
357 "</body></html>",
358 nav);
359 const bool ok = priv_mdl_export_snprintf_fit(opf_n, opf_need) &&
360 priv_mdl_export_snprintf_fit(nav_n, nav_need) &&
361 priv_mdl_epub_add_str(zip, "OEBPS/content.opf", opf) &&
362 priv_mdl_epub_add_str(zip, "OEBPS/nav.xhtml", navdoc) &&
363 (mz_zip_writer_finalize_archive(zip) != MZ_FALSE);
364 return ok ? k_ra8_ok : k_ra8_fail;
365}
366
368 const char* mani,
369 const char* spine,
370 const char* nav,
371 size_t page_count,
372 const mdl_export_meta_t* meta,
373 char* opf,
374 size_t opf_buf_cap,
375 char* navdoc,
376 size_t nav_buf_cap)
377{
378 mdl_export_meta_t resolved;
379 if (meta != nullptr) {
380 resolved = *meta;
381 } else {
382 mdl_meta_init(&resolved);
383 }
385 mdl_epub_meta_text_t text = {};
386 if (rc == k_ra8_ok) {
387 rc = internal_epub_prepare_text(&resolved, &text);
388 }
389 if (rc != k_ra8_ok) {
390 return rc;
391 }
392 return internal_epub_render_meta(zip,
393 mani,
394 spine,
395 nav,
396 page_count,
397 &text,
398 opf,
399 opf_buf_cap,
400 navdoc,
401 nav_buf_cap);
402}
Package a downloaded chapter folder into a reader-openable container.
@ k_mdl_meta_id_max
Stable publication identifier max bytes.
Definition mdl_export.h:82
@ k_mdl_meta_title_max
Title (series or chapter) buffer max bytes.
Definition mdl_export.h:76
@ k_mdl_meta_name_max
Person name (writer/artist) buffer max bytes.
Definition mdl_export.h:78
@ k_mdl_meta_date_max
ISO-8601 modified timestamp max bytes.
Definition mdl_export.h:83
@ k_mdl_meta_summary_max
Summary description buffer max bytes.
Definition mdl_export.h:77
@ k_mdl_meta_lang_max
BCP-47 language tag buffer max bytes.
Definition mdl_export.h:81
@ k_mdl_meta_url_max
Absolute source URL max bytes, including NUL.
Definition mdl_export.h:79
@ k_mdl_read_rtl
Right-to-left page progression.
Definition mdl_export.h:89
void mdl_meta_init(mdl_export_meta_t *meta)
Initialise a metadata struct to empty/default values.
bool priv_mdl_epub_add_str(mz_zip_archive *zip, const char *name, const char *body)
Add an in-memory string as a stored ZIP entry.
bool priv_mdl_epub_str_cat(char *dst, size_t cap, const char *text)
Append text to NUL-terminated dst; report whether it fully fit.
Private bounded EPUB3 writer contracts for the media downloader.
@ k_epub_base_bytes
Fixed opf/nav overhead.
ra8_err_t priv_mdl_epub_add_meta(mz_zip_archive *zip, const char *mani, const char *spine, const char *nav, size_t page_count, const mdl_export_meta_t *meta, char *opf, size_t opf_buf_cap, char *navdoc, size_t nav_buf_cap)
Build EPUB package metadata and finalize the archive.
mdl_uuid_hash_t
FNV-1a values used to derive deterministic publication UUIDs.
@ k_uuid_fnv_prime
FNV-1a 64-bit prime.
@ k_uuid_seed_one
Primary FNV offset basis.
@ k_uuid_seed_two
Independent second seed.
static uint64_t internal_uuid_hash_text(uint64_t hash, const char *text)
Mix one NUL-terminated metadata field into a UUID hash.
static void internal_generate_uuid(char *out, size_t cap, const mdl_export_meta_t *meta)
Derive a stable RFC-4122-shaped identifier from canonical metadata.
static ra8_err_t internal_epub_prepare_text(const mdl_export_meta_t *meta, mdl_epub_meta_text_t *text)
Prepare all escaped EPUB metadata text.
static ra8_err_t internal_epub_render_meta(mz_zip_archive *zip, const char *mani, const char *spine, const char *nav, size_t page_count, const mdl_epub_meta_text_t *text, char *opf, size_t opf_cap, char *navdoc, size_t nav_cap)
Render and append prepared EPUB package documents.
static void internal_epub_prepare_creators(const mdl_export_meta_t *meta, mdl_epub_meta_text_t *text)
Render the EPUB creator fragments with their original fallback.
static ra8_err_t internal_epub_prepare_optional(const mdl_export_meta_t *meta, mdl_epub_meta_text_t *text)
Render optional EPUB description and source fragments.
mdl_uuid_layout_t
RFC 4122 UUID byte layout, masks, and version/variant bits.
@ k_uuid_version_five
RFC 4122 version-five field bits.
@ k_uuid_half_bytes
Bytes contributed by each hash.
@ k_uuid_separator
Separator mixed between hash fields.
@ k_uuid_version_mask
Mask retaining non-version bits.
@ k_uuid_version_byte
UUID version field byte index.
@ k_uuid_variant_mask
Mask retaining non-variant bits.
@ k_uuid_last_byte
Final UUID byte index.
@ k_uuid_node_offset
First byte of the UUID node field.
@ k_uuid_variant_byte
UUID variant field byte index.
@ k_uuid_top_shift
Shift selecting the top hash byte.
@ k_uuid_variant_rfc4122
RFC 4122 variant field bits.
@ k_uuid_byte_count
Bytes in an RFC 4122 UUID.
mdl_epub_text_bounds_t
Bounded EPUB text expansion sizes.
@ k_epub_fragment_slack
Fixed bytes around an XML fragment.
@ k_epub_creator_slack
Fixed bytes around all creators.
@ k_epub_uuid_byte_bits
Bits consumed per UUID hash byte.
@ k_epub_creator_factor
Writer plus artist expansion factor.
ra8_err_t priv_mdl_export_validate_source_url(const char *url)
Validate an optional bounded source-attribution URL.
bool priv_mdl_export_snprintf_fit(int written, size_t cap)
Report whether an snprintf result fit its destination.
Neutralise untrusted names before they reach a filesystem or XML sink.
bool mdl_xml_escape(const char *src, char *out, size_t cap)
XML-escape src into out, failing rather than truncating.
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).
@ k_ra8_fail
Generic unspecified failure.
Definition ra8_err.h:133
@ k_ra8_ok
Success – operation completed with all postconditions satisfied.
Definition ra8_err.h:119
@ 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
size_t strlen(const char *s)
Calculate string length.
Escaped and rendered EPUB publication metadata.
char description[(k_mdl_meta_summary_max *6U)+k_epub_fragment_slack]
Optional escaped <dc:description> fragment.
char modified[k_mdl_meta_date_max *6U]
Escaped modification date.
char identifier[k_mdl_meta_id_max *6U]
Escaped identifier.
char language[k_mdl_meta_lang_max *6U]
Escaped language.
char creators[(k_mdl_meta_name_max *k_epub_creator_factor)+k_epub_creator_slack]
Complete escaped <dc:creator> fragments.
char source[(k_mdl_meta_url_max *6U)+k_epub_fragment_slack]
Optional escaped <dc:source> fragment.
char title[k_mdl_meta_title_max *6U]
Escaped display title.
const char * progression
OPF page progression token.
Rich series and chapter metadata for export containers (ComicInfo.xml, EPUB OPF).
Definition mdl_export.h:96
char chapter_title[k_mdl_meta_title_max]
Chapter title.
Definition mdl_export.h:101
mdl_reading_direction_t reading_direction
Fixed-layout page progression.
Definition mdl_export.h:107
char identifier[k_mdl_meta_id_max]
Stable identifier; derived when empty.
Definition mdl_export.h:108
char language[k_mdl_meta_lang_max]
BCP-47 language tag (default "en").
Definition mdl_export.h:106
char cover_path[k_mdl_meta_path_max]
Trusted verified local cover path/name.
Definition mdl_export.h:104
char writer[k_mdl_meta_name_max]
Writer / Author.
Definition mdl_export.h:99
char artist[k_mdl_meta_name_max]
Artist / Illustrator.
Definition mdl_export.h:100
char modified[k_mdl_meta_date_max]
Deterministic ISO-8601 modified time.
Definition mdl_export.h:109
char source_url[k_mdl_meta_url_max]
Validated absolute HTTP(S) source URL.
Definition mdl_export.h:103
char summary[k_mdl_meta_summary_max]
Summary / description.
Definition mdl_export.h:98
char series_title[k_mdl_meta_title_max]
Series title.
Definition mdl_export.h:97