cadmus_core/assets.rs
1//! Embedded documentation assets.
2//!
3//! This module provides access to documentation files embedded in the binary
4//! at compile time using rust-embed.
5
6use rust_embed::Embed;
7use rust_embed::EmbeddedFile;
8
9#[cfg(debug_assertions)]
10use std::sync::OnceLock;
11
12/// Cached documentation bytes to prevent repeated memory leaks in debug builds.
13///
14/// In debug builds, rust-embed reads files from disk and returns `Cow::Owned`,
15/// requiring us to leak the data to get a 'static reference. This cache ensures
16/// the leak only happens once.
17#[cfg(debug_assertions)]
18static DOCUMENTATION_CACHE: OnceLock<&'static [u8]> = OnceLock::new();
19
20/// Embedded documentation EPUB file.
21///
22/// Contains the Cadmus documentation EPUB file generated from mdbook.
23/// Only the EPUB file is embedded, not the entire folder.
24///
25/// # Example
26///
27/// ```
28/// use cadmus_core::assets::DocumentationAssets;
29///
30/// let epub = DocumentationAssets::get_documentation();
31/// assert!(!epub.data.is_empty());
32/// ```
33#[derive(Embed)]
34#[folder = "../../docs/book/epub/"]
35#[include = "Cadmus Documentation.epub"]
36pub struct DocumentationAssets;
37
38impl DocumentationAssets {
39 /// Returns the embedded documentation EPUB file.
40 ///
41 /// # Panics
42 ///
43 /// Panics if the documentation EPUB file is not found in embedded assets.
44 /// This should never happen in a properly built binary.
45 ///
46 /// # Example
47 ///
48 /// ```
49 /// use cadmus_core::assets::DocumentationAssets;
50 ///
51 /// let epub = DocumentationAssets::get_documentation();
52 /// // The EPUB data is available as a byte slice
53 /// let data: &[u8] = &epub.data;
54 /// ```
55 pub fn get_documentation() -> EmbeddedFile {
56 Self::get("Cadmus Documentation.epub")
57 .expect("Documentation EPUB not found in embedded assets")
58 }
59}
60
61/// Opens the embedded documentation in a Reader view.
62///
63/// This helper function is shared between the app and emulator to avoid code duplication.
64/// It retrieves the embedded EPUB, creates a Reader, and returns it.
65///
66/// The EPUB data is accessed without copying (zero-copy). In release builds, the data
67/// is embedded as a static reference. In debug builds, the data is loaded from disk
68/// and leaked to obtain a static reference, which is acceptable since the documentation
69/// is loaded once and lives for the entire program duration.
70///
71/// # Arguments
72///
73/// * `rect` - The rectangle defining the display area for the reader
74/// * `hub` - The event hub for sending update events
75/// * `context` - The application context containing display settings and fonts
76///
77/// # Returns
78///
79/// Returns `Some(Reader)` if the documentation was successfully opened,
80/// or `None` if there was an error parsing the EPUB.
81///
82/// # Example
83///
84/// ```no_run
85/// use cadmus_core::assets::open_documentation;
86/// use cadmus_core::context::Context;
87/// use cadmus_core::view::Hub;
88/// use cadmus_core::geom::Rectangle;
89/// use std::sync::mpsc::channel;
90///
91/// // Note: In actual use, context and hub are provided by the application.
92/// // This example shows the API pattern.
93/// # fn example(rect: Rectangle, hub: &Hub, context: &mut Context) {
94/// if let Some(reader) = open_documentation(rect, hub, context) {
95/// // Documentation opened successfully
96/// // The reader can be used to display the embedded EPUB
97/// }
98/// # }
99/// ```
100pub fn open_documentation(
101 rect: crate::geom::Rectangle,
102 hub: &crate::view::Hub,
103 context: &mut crate::context::Context,
104) -> Option<crate::view::reader::Reader> {
105 #[cfg(debug_assertions)]
106 let static_bytes = DOCUMENTATION_CACHE.get_or_init(|| {
107 let epub_file = DocumentationAssets::get_documentation();
108 match epub_file.data {
109 std::borrow::Cow::Borrowed(bytes) => bytes,
110 std::borrow::Cow::Owned(vec) => Box::leak(vec.into_boxed_slice()),
111 }
112 });
113
114 #[cfg(not(debug_assertions))]
115 let static_bytes = {
116 let epub_file = DocumentationAssets::get_documentation();
117 match epub_file.data {
118 std::borrow::Cow::Borrowed(bytes) => bytes,
119 std::borrow::Cow::Owned(_) => {
120 unreachable!("Owned data should not occur in release builds")
121 }
122 }
123 };
124
125 crate::view::reader::Reader::from_embedded_epub(rect, static_bytes, hub, context)
126}