diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index 48f2751..0233660 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -45,7 +45,7 @@ jobs: source ./.venv/bin/activate uv pip install weasyprint - - name: Export demo presentation as PDF + - name: Export demo presentation as PDF and HTML run: | cat >/tmp/config.yaml < Result { + fn new(grid: TerminalGrid) -> Result { let mut rows = Vec::new(); rows.push(String::from("
")); for (y, row) in grid.rows.into_iter().enumerate() { @@ -58,11 +54,11 @@ impl HtmlSlide { other => current_string.push(other), } if let Some(image) = grid.images.get(&(y as u16, x as u16)) { - let image_path = content_manager.persist_image(&image.image)?; - let image_path_str = image_path.display(); + let TerminalImage::Raw(raw_image) = image.image.image() else { panic!("not in raw image mode") }; + let image_contents = raw_image.to_inline_html(); let width_pixels = (image.width_columns as f64 * FONT_SIZE as f64 * FONT_SIZE_WIDTH).ceil(); let image_tag = format!( - "" + "" ); current_string.push_str(&image_tag); } @@ -86,35 +82,11 @@ impl HtmlSlide { pub(crate) struct ContentManager { output_directory: OutputDirectory, - image_count: usize, } impl ContentManager { pub(crate) fn new(output_directory: OutputDirectory) -> Self { - Self { output_directory, image_count: 0 } - } - - fn persist_image(&mut self, image: &Image) -> Result { - match image.source.clone() { - ImageSource::Filesystem(path) => Ok(path), - ImageSource::Generated => { - let mut buffer = Vec::new(); - let dimensions = image.image().dimensions(); - let TerminalImage::Ascii(image) = image.image() else { panic!("not in ascii mode") }; - let image = image.image(); - PngEncoder::new(&mut buffer).write_image( - image.as_bytes(), - dimensions.0, - dimensions.1, - image.color().into(), - )?; - let name = format!("img-{}.png", self.image_count); - let path = self.output_directory.path().join(name); - fs::write(&path, buffer)?; - self.image_count += 1; - Ok(path) - } - } + Self { output_directory } } fn persist_file(&self, name: &str, data: &[u8]) -> io::Result { @@ -155,7 +127,7 @@ impl ExportRenderer { engine.render(slide.iter_operations())?; let grid = terminal.into_contents(); - let slide = HtmlSlide::new(grid, &mut self.content_manager)?; + let slide = HtmlSlide::new(grid)?; if self.background_color.is_none() { self.background_color.clone_from(&slide.background_color); } diff --git a/src/main.rs b/src/main.rs index a8e638d..1b162bf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -293,8 +293,8 @@ impl CoreComponents { } fn select_graphics_mode(cli: &Cli, config: &Config) -> GraphicsMode { - if cli.export_pdf { - GraphicsMode::AsciiBlocks + if cli.export_pdf | cli.export_html { + GraphicsMode::Raw } else { let protocol = cli.image_protocol.as_ref().unwrap_or(&config.defaults.image_protocol); match GraphicsMode::try_from(protocol) { diff --git a/src/terminal/image/mod.rs b/src/terminal/image/mod.rs index 4467c9c..fd59721 100644 --- a/src/terminal/image/mod.rs +++ b/src/terminal/image/mod.rs @@ -1,7 +1,6 @@ +use self::printer::{ImageProperties, TerminalImage}; use image::DynamicImage; use protocols::ascii::AsciiImage; - -use self::printer::{ImageProperties, TerminalImage}; use std::{ fmt::Debug, ops::Deref, @@ -43,6 +42,7 @@ impl Image { TerminalImage::Ascii(image) => image.clone(), TerminalImage::Kitty(image) => DynamicImage::from(image.as_rgba8()).into(), TerminalImage::Iterm(image) => DynamicImage::from(image.as_rgba8()).into(), + TerminalImage::Raw(_) => unreachable!("raw is only used for exports"), #[cfg(feature = "sixel")] TerminalImage::Sixel(image) => DynamicImage::from(image.as_rgba8()).into(), }; diff --git a/src/terminal/image/printer.rs b/src/terminal/image/printer.rs index fe66813..f279a9e 100644 --- a/src/terminal/image/printer.rs +++ b/src/terminal/image/printer.rs @@ -4,6 +4,7 @@ use super::{ ascii::{AsciiImage, AsciiPrinter}, iterm::{ItermImage, ItermPrinter}, kitty::{KittyImage, KittyMode, KittyPrinter}, + raw::{RawImage, RawPrinter}, }, }; use crate::{ @@ -54,6 +55,7 @@ pub(crate) enum TerminalImage { Kitty(KittyImage), Iterm(ItermImage), Ascii(AsciiImage), + Raw(RawImage), #[cfg(feature = "sixel")] Sixel(super::protocols::sixel::SixelImage), } @@ -64,6 +66,7 @@ impl ImageProperties for TerminalImage { Self::Kitty(image) => image.dimensions(), Self::Iterm(image) => image.dimensions(), Self::Ascii(image) => image.dimensions(), + Self::Raw(image) => image.dimensions(), #[cfg(feature = "sixel")] Self::Sixel(image) => image.dimensions(), } @@ -74,6 +77,7 @@ pub enum ImagePrinter { Kitty(KittyPrinter), Iterm(ItermPrinter), Ascii(AsciiPrinter), + Raw(RawPrinter), Null, #[cfg(feature = "sixel")] Sixel(super::protocols::sixel::SixelPrinter), @@ -91,6 +95,7 @@ impl ImagePrinter { GraphicsMode::Kitty { mode, inside_tmux } => Self::new_kitty(mode, inside_tmux)?, GraphicsMode::Iterm2 => Self::new_iterm(), GraphicsMode::AsciiBlocks => Self::new_ascii(), + GraphicsMode::Raw => Self::new_raw(), #[cfg(feature = "sixel")] GraphicsMode::Sixel => Self::new_sixel()?, }; @@ -109,6 +114,10 @@ impl ImagePrinter { Self::Ascii(AsciiPrinter) } + fn new_raw() -> Self { + Self::Raw(RawPrinter) + } + #[cfg(feature = "sixel")] fn new_sixel() -> Result { Ok(Self::Sixel(super::protocols::sixel::SixelPrinter::new()?)) @@ -124,6 +133,7 @@ impl PrintImage for ImagePrinter { Self::Iterm(printer) => TerminalImage::Iterm(printer.register(spec)?), Self::Ascii(printer) => TerminalImage::Ascii(printer.register(spec)?), Self::Null => return Err(RegisterImageError::Unsupported), + Self::Raw(printer) => TerminalImage::Raw(printer.register(spec)?), #[cfg(feature = "sixel")] Self::Sixel(printer) => TerminalImage::Sixel(printer.register(spec)?), }; @@ -139,6 +149,7 @@ impl PrintImage for ImagePrinter { (Self::Iterm(printer), TerminalImage::Iterm(image)) => printer.print(image, options, terminal), (Self::Ascii(printer), TerminalImage::Ascii(image)) => printer.print(image, options, terminal), (Self::Null, _) => Ok(()), + (Self::Raw(printer), TerminalImage::Raw(image)) => printer.print(image, options, terminal), #[cfg(feature = "sixel")] (Self::Sixel(printer), TerminalImage::Sixel(image)) => printer.print(image, options, terminal), _ => Err(PrintImageError::Unsupported), @@ -165,6 +176,7 @@ impl fmt::Debug for ImageRegistry { ImagePrinter::Iterm(_) => "Iterm", ImagePrinter::Ascii(_) => "Ascii", ImagePrinter::Null => "Null", + ImagePrinter::Raw(_) => "Raw", #[cfg(feature = "sixel")] ImagePrinter::Sixel(_) => "Sixel", }; diff --git a/src/terminal/image/protocols/ascii.rs b/src/terminal/image/protocols/ascii.rs index 145aa26..b8972b7 100644 --- a/src/terminal/image/protocols/ascii.rs +++ b/src/terminal/image/protocols/ascii.rs @@ -36,10 +36,6 @@ impl AsciiImage { cached_sizes.insert(cache_key, image.into_rgba8()); } } - - pub(crate) fn image(&self) -> &DynamicImage { - &self.inner.image - } } impl ImageProperties for AsciiImage { diff --git a/src/terminal/image/protocols/mod.rs b/src/terminal/image/protocols/mod.rs index f007ff3..f9f5dbf 100644 --- a/src/terminal/image/protocols/mod.rs +++ b/src/terminal/image/protocols/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod ascii; pub(crate) mod iterm; pub(crate) mod kitty; +pub(crate) mod raw; #[cfg(feature = "sixel")] pub(crate) mod sixel; diff --git a/src/terminal/image/protocols/raw.rs b/src/terminal/image/protocols/raw.rs new file mode 100644 index 0000000..dd9c20e --- /dev/null +++ b/src/terminal/image/protocols/raw.rs @@ -0,0 +1,61 @@ +use crate::terminal::{ + image::printer::{ImageProperties, ImageSpec, PrintImage, PrintImageError, PrintOptions, RegisterImageError}, + printer::TerminalIo, +}; +use base64::{Engine, engine::general_purpose::STANDARD}; +use image::{GenericImageView, ImageEncoder, ImageFormat, codecs::png::PngEncoder}; +use std::fs; + +pub(crate) struct RawImage { + contents: Vec, + format: ImageFormat, + width: u32, + height: u32, +} + +impl RawImage { + pub(crate) fn to_inline_html(&self) -> String { + let mime_type = self.format.to_mime_type(); + let data = STANDARD.encode(&self.contents); + format!("data:{mime_type};base64,{data}") + } +} + +impl ImageProperties for RawImage { + fn dimensions(&self) -> (u32, u32) { + (self.width, self.height) + } +} + +pub(crate) struct RawPrinter; + +impl PrintImage for RawPrinter { + type Image = RawImage; + + fn register(&self, spec: ImageSpec) -> Result { + let image = match spec { + ImageSpec::Generated(image) => { + let mut contents = Vec::new(); + let encoder = PngEncoder::new(&mut contents); + let (width, height) = image.dimensions(); + encoder.write_image(image.as_bytes(), width, height, image.color().into())?; + RawImage { contents, format: ImageFormat::Png, width, height } + } + ImageSpec::Filesystem(path) => { + let contents = fs::read(path)?; + let format = image::guess_format(&contents)?; + let image = image::load_from_memory_with_format(&contents, format)?; + let (width, height) = image.dimensions(); + RawImage { contents, format, width, height } + } + }; + Ok(image) + } + + fn print(&self, _image: &Self::Image, _options: &PrintOptions, _terminal: &mut T) -> Result<(), PrintImageError> + where + T: TerminalIo, + { + Err(PrintImageError::Other("raw images can't be printed".into())) + } +} diff --git a/src/terminal/mod.rs b/src/terminal/mod.rs index fc14024..bb90d97 100644 --- a/src/terminal/mod.rs +++ b/src/terminal/mod.rs @@ -15,6 +15,7 @@ pub enum GraphicsMode { inside_tmux: bool, }, AsciiBlocks, + Raw, #[cfg(feature = "sixel")] Sixel, }