//! Module which defines the captioning engine. use std::path::Path; use std::sync::Arc; use model::ImageMacro; use resources::{CachingLoader, Font, FontLoader, Loader, Template, TemplateLoader}; use util::cache::ThreadSafeCache; use super::error::CaptionError; use super::output::CaptionOutput; use super::task::CaptionTask; const DEFAULT_TEMPLATE_CAPACITY: usize = 128; const DEFAULT_FONT_CAPACITY: usize = 16; /// Image captioning engine. /// /// The engine is thread-safe (`Sync`) since normally you'd want the captioning /// to be performed in a background thread. /// /// *Note*: `Engine` implements `Clone` /// by merely cloning a shared reference to the underlying object. #[derive(Clone, Debug)] pub struct Engine where Tl: Loader, Fl: Loader { inner: Arc>, } /// Shared state of the engine that caption tasks have access to. #[derive(Debug)] pub(super) struct Inner where Tl: Loader, Fl: Loader { pub template_loader: CachingLoader, pub font_loader: CachingLoader, } impl From> for Engine where Tl: Loader, Fl: Loader { fn from(inner: Inner) -> Self { Engine{inner: Arc::new(inner)} } } // Constructors. impl Engine { /// Create an Engine which loads templates & fonts from given directory paths. /// When loaded, both resources will be cached in memory (LRU cache). #[inline] pub fn new(template_directory: Dt, font_directory: Df) -> Self where Dt: AsRef, Df: AsRef { Engine::with_loaders( TemplateLoader::new(template_directory), FontLoader::new(font_directory), ) } } impl Engine where Tl: Loader, Fl: Loader { /// Create an Engine that uses given loaders for templates & font. /// When loaded, both resources will be cached in memory (LRU cache). #[inline] pub fn with_loaders(template_loader: Tl, font_loader: Fl) -> Self { Engine::from(Inner{ template_loader: CachingLoader::new(template_loader, DEFAULT_TEMPLATE_CAPACITY), font_loader: CachingLoader::new(font_loader, DEFAULT_FONT_CAPACITY), }) } /// Create an Engine that uses given template & font loaders directly. /// Any caching scheme, if necessary, should be implemented by loaders themselves. #[inline] pub fn with_raw_loaders(template_loader: Tl, font_loader: Fl) -> Self { // Use the phony version of CachingLoader which doesn't actually cache anything, // but provides the same interface yielding Arc. Engine::from(Inner{ template_loader: CachingLoader::phony(template_loader), font_loader: CachingLoader::phony(font_loader), }) } // TODO: add a Builder that also allows to: // * set the capacity of standard template/font caches // * adjust GIF & JPEG quality parameters // * adjust memory limit for GIF decoding // (this will probably warrant a separate Config struct) } // Image macro captioning. impl Engine where Tl: Loader, Fl: Loader { /// Render a given image macro by captioning the template with the specified text(s). /// /// Note that captioning is a CPU-intensive process and can be relatively lengthy, /// especially if the template is an animated GIF. /// It is recommended to execute it in a separate thread. #[inline] pub fn caption(&self, image_macro: ImageMacro) -> Result> { CaptionTask::new(image_macro, self.inner.clone()).perform() } } // Managing resources. impl Engine where Tl: Loader, Fl: Loader { /// Preemptively load a template into engine's cache. pub fn preload_template(&self, name: &str) -> Result<(), Tl::Err> { if !self.inner.template_loader.phony { self.inner.template_loader.load(name)?; } Ok(()) } /// Preemptively load a font into engine's cache. pub fn preload_font(&self, name: &str) -> Result<(), Fl::Err> { if !self.inner.font_loader.phony { self.inner.font_loader.load(name)?; } Ok(()) } /// Return a reference to the internal template cache, if any. /// This can be used to examine cache statistics (hits & misses). pub fn template_cache(&self) -> Option<&ThreadSafeCache> { if self.inner.template_loader.phony { None } else { Some(self.inner.template_loader.cache()) } } /// Return a reference to the internal font cache, if any. /// This can be used to examine cache statistics (hits & misses). pub fn font_cache(&self) -> Option<&ThreadSafeCache> { if self.inner.font_loader.phony { None } else { Some(self.inner.font_loader.cache()) } } } #[cfg(test)] mod tests { use super::Engine; #[test] fn thread_safe() { fn assert_sync() {} fn assert_send() {} assert_sync::(); assert_send::(); } }