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}