Switch to persistent printer with queue

This commit is contained in:
Joscha 2025-02-27 01:28:08 +01:00
parent 0ea4cf1d22
commit 6555e9c0bd
14 changed files with 221 additions and 76 deletions

View file

@ -1,3 +1,4 @@
mod backlog;
mod calendar; mod calendar;
mod cells; mod cells;
mod chat_message; mod chat_message;
@ -11,21 +12,23 @@ mod typst;
use showbits_common::widgets::{FontStuff, HasFontStuff}; use showbits_common::widgets::{FontStuff, HasFontStuff};
use tokio::sync::mpsc; use tokio::sync::mpsc;
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
pub use self::{ pub use self::{
calendar::CalendarDrawing, cells::CellsDrawing, chat_message::ChatMessageDrawing, backlog::BacklogDrawing, calendar::CalendarDrawing, cells::CellsDrawing,
egg::EggDrawing, image::ImageDrawing, photo::PhotoDrawing, text::TextDrawing, chat_message::ChatMessageDrawing, egg::EggDrawing, image::ImageDrawing, photo::PhotoDrawing,
tictactoe::TicTacToeDrawing, typst::TypstDrawing, text::TextDrawing, tictactoe::TicTacToeDrawing, typst::TypstDrawing,
}; };
pub const FEED: f32 = 64.0;
#[derive(Default)] #[derive(Default)]
pub struct Context { pub struct Context {
font_stuff: FontStuff, font_stuff: FontStuff,
} }
pub trait Drawing { pub trait Drawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()>; fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()>;
} }
pub struct Command(Box<dyn Drawing + Send>); pub struct Command(Box<dyn Drawing + Send>);
@ -44,12 +47,12 @@ impl HasFontStuff for Context {
pub struct Drawer { pub struct Drawer {
rx: mpsc::Receiver<Command>, rx: mpsc::Receiver<Command>,
printer: Printer, printer: PersistentPrinter,
ctx: Context, ctx: Context,
} }
impl Drawer { impl Drawer {
pub fn new(rx: mpsc::Receiver<Command>, printer: Printer) -> Self { pub fn new(rx: mpsc::Receiver<Command>, printer: PersistentPrinter) -> Self {
Self { Self {
rx, rx,
printer, printer,

View file

@ -0,0 +1,12 @@
use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing};
pub struct BacklogDrawing;
impl Drawing for BacklogDrawing {
fn draw(&self, printer: &mut PersistentPrinter, _ctx: &mut Context) -> anyhow::Result<()> {
printer.print_backlog()?;
Ok(())
}
}

View file

@ -9,9 +9,9 @@ use taffy::{
style_helpers::{length, percent, repeat}, style_helpers::{length, percent, repeat},
}; };
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct CalendarDrawing { pub struct CalendarDrawing {
pub year: i16, pub year: i16,
@ -19,7 +19,7 @@ pub struct CalendarDrawing {
} }
impl Drawing for CalendarDrawing { impl Drawing for CalendarDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut date = civil::Date::new(self.year, self.month, 1)?; let mut date = civil::Date::new(self.year, self.month, 1)?;
let mut tree = Tree::<Context>::new(WHITE); let mut tree = Tree::<Context>::new(WHITE);
@ -86,6 +86,7 @@ impl Drawing for CalendarDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -94,7 +95,6 @@ impl Drawing for CalendarDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -3,11 +3,11 @@ use image::{
imageops::{self, FilterType}, imageops::{self, FilterType},
}; };
use showbits_common::{Node, Tree, WidgetExt, color, widgets::Image}; use showbits_common::{Node, Tree, WidgetExt, color, widgets::Image};
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent}; use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent};
use crate::printer::Printer; use crate::{persistent_printer::PersistentPrinter, printer::Printer};
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
const BLACK: Rgba<u8> = Rgba([0, 0, 0, 255]); const BLACK: Rgba<u8> = Rgba([0, 0, 0, 255]);
const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]); const WHITE: Rgba<u8> = Rgba([255, 255, 255, 255]);
@ -49,8 +49,8 @@ pub struct CellsDrawing {
} }
impl Drawing for CellsDrawing { impl Drawing for CellsDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut image = RgbaImage::new(Printer::WIDTH / self.scale, self.rows); let mut image: image::ImageBuffer<Rgba<u8>, Vec<u8>> = RgbaImage::new(Printer::WIDTH / self.scale, self.rows);
// Initialize first line randomly // Initialize first line randomly
for x in 0..image.width() { for x in 0..image.width() {
@ -79,6 +79,7 @@ impl Drawing for CellsDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -86,7 +87,6 @@ impl Drawing for CellsDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -8,7 +8,7 @@ use taffy::{
style_helpers::{length, percent}, style_helpers::{length, percent},
}; };
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing};
@ -18,7 +18,7 @@ pub struct ChatMessageDrawing {
} }
impl Drawing for ChatMessageDrawing { impl Drawing for ChatMessageDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE); let mut tree = Tree::<Context>::new(WHITE);
let max_username_width_in_chars = 32.0; let max_username_width_in_chars = 32.0;

View file

@ -6,11 +6,11 @@ use showbits_common::{
color::{self, WHITE}, color::{self, WHITE},
widgets::{Image, Text}, widgets::{Image, Text},
}; };
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent}; use taffy::{prelude::length, style_helpers::percent, AlignItems, Display, FlexDirection};
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct EggDrawing; pub struct EggDrawing;
@ -21,7 +21,7 @@ fn load_image(bytes: &[u8]) -> RgbaImage {
} }
impl Drawing for EggDrawing { impl Drawing for EggDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut rng = rand::rng(); let mut rng = rand::rng();
// Choose which set of egg images to use // Choose which set of egg images to use
@ -84,6 +84,7 @@ impl Drawing for EggDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -92,7 +93,6 @@ impl Drawing for EggDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -5,11 +5,11 @@ use showbits_common::{
color::{self, BLACK, WHITE}, color::{self, BLACK, WHITE},
widgets::{DitherAlgorithm, Image}, widgets::{DitherAlgorithm, Image},
}; };
use taffy::{AlignItems, Display, FlexDirection, style_helpers::percent}; use taffy::{AlignItems, Display, FlexDirection, prelude::length, style_helpers::percent};
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct ImageDrawing { pub struct ImageDrawing {
pub image: RgbaImage, pub image: RgbaImage,
@ -19,7 +19,7 @@ pub struct ImageDrawing {
} }
impl Drawing for ImageDrawing { impl Drawing for ImageDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut image = self.image.clone(); let mut image = self.image.clone();
if self.bright { if self.bright {
for pixel in image.pixels_mut() { for pixel in image.pixels_mut() {
@ -40,6 +40,7 @@ impl Drawing for ImageDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -47,7 +48,6 @@ impl Drawing for ImageDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -9,9 +9,9 @@ use taffy::{
style_helpers::{length, percent}, style_helpers::{length, percent},
}; };
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct PhotoDrawing { pub struct PhotoDrawing {
pub image: RgbaImage, pub image: RgbaImage,
@ -19,7 +19,7 @@ pub struct PhotoDrawing {
} }
impl Drawing for PhotoDrawing { impl Drawing for PhotoDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE); let mut tree = Tree::<Context>::new(WHITE);
let mut image = self.image.clone(); let mut image = self.image.clone();
@ -45,6 +45,7 @@ impl Drawing for PhotoDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -54,7 +55,6 @@ impl Drawing for PhotoDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -1,14 +1,14 @@
use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Text}; use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Text};
use taffy::style_helpers::percent; use taffy::style_helpers::percent;
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing};
pub struct TextDrawing(pub String); pub struct TextDrawing(pub String);
impl Drawing for TextDrawing { impl Drawing for TextDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE); let mut tree = Tree::<Context>::new(WHITE);
let text = Text::new() let text = Text::new()

View file

@ -8,14 +8,14 @@ use taffy::{
style_helpers::{length, percent, repeat}, style_helpers::{length, percent, repeat},
}; };
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct TicTacToeDrawing; pub struct TicTacToeDrawing;
impl Drawing for TicTacToeDrawing { impl Drawing for TicTacToeDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let block_size = length(128.0); let block_size = length(128.0);
let width = length(2.0); let width = length(2.0);
@ -57,6 +57,7 @@ impl Drawing for TicTacToeDrawing {
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.with_display(Display::Flex) .with_display(Display::Flex)
.with_flex_direction(FlexDirection::Column) .with_flex_direction(FlexDirection::Column)
.with_align_items(Some(AlignItems::Center)) .with_align_items(Some(AlignItems::Center))
@ -66,7 +67,6 @@ impl Drawing for TicTacToeDrawing {
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -1,25 +1,25 @@
use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Typst}; use showbits_common::{Node, Tree, WidgetExt, color::WHITE, widgets::Typst};
use taffy::style_helpers::percent; use taffy::{prelude::length, style_helpers::percent};
use crate::printer::Printer; use crate::persistent_printer::PersistentPrinter;
use super::{Context, Drawing}; use super::{Context, Drawing, FEED};
pub struct TypstDrawing(pub String); pub struct TypstDrawing(pub String);
impl Drawing for TypstDrawing { impl Drawing for TypstDrawing {
fn draw(&self, printer: &mut Printer, ctx: &mut Context) -> anyhow::Result<()> { fn draw(&self, printer: &mut PersistentPrinter, ctx: &mut Context) -> anyhow::Result<()> {
let mut tree = Tree::<Context>::new(WHITE); let mut tree = Tree::<Context>::new(WHITE);
let typst = Typst::new(self.0.clone()).node().register(&mut tree)?; let typst = Typst::new(self.0.clone()).node().register(&mut tree)?;
let root = Node::empty() let root = Node::empty()
.with_size_width(percent(1.0)) .with_size_width(percent(1.0))
.with_padding_bottom(length(FEED))
.and_child(typst) .and_child(typst)
.register(&mut tree)?; .register(&mut tree)?;
printer.print_tree(&mut tree, ctx, root)?; printer.print_tree(&mut tree, ctx, root)?;
printer.feed()?;
Ok(()) Ok(())
} }
} }

View file

@ -1,19 +1,24 @@
mod drawer; mod drawer;
mod persistent_printer;
mod printer; mod printer;
mod server; mod server;
use std::path::PathBuf; use std::{path::PathBuf, time::Duration};
use clap::Parser; use clap::Parser;
use drawer::Drawer; use drawer::{BacklogDrawing, Command};
use printer::Printer;
use tokio::{runtime::Runtime, sync::mpsc}; use tokio::{runtime::Runtime, sync::mpsc};
use self::{drawer::Drawer, persistent_printer::PersistentPrinter};
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {
/// Address the web server will listen at. /// Address the web server will listen at.
addr: String, addr: String,
/// Path to the queue directory.
queue: PathBuf,
/// Path to the printer's USB device file. /// Path to the printer's USB device file.
/// ///
/// Usually, this is located at `/dev/usb/lp0` or a similar location. /// Usually, this is located at `/dev/usb/lp0` or a similar location.
@ -30,11 +35,17 @@ fn main() -> anyhow::Result<()> {
let (tx, rx) = mpsc::channel(3); let (tx, rx) = mpsc::channel(3);
let printer = Printer::new(args.printer, args.export)?; let printer = PersistentPrinter::new(args.printer, args.export, args.queue);
let mut drawer = Drawer::new(rx, printer); let mut drawer = Drawer::new(rx, printer);
let runtime = Runtime::new()?; let runtime = Runtime::new()?;
runtime.spawn(server::run(tx, args.addr)); runtime.spawn(server::run(tx.clone(), args.addr));
runtime.spawn(async move {
loop {
let _ = tx.send(Command::draw(BacklogDrawing)).await;
tokio::time::sleep(Duration::from_secs(1)).await;
}
});
println!("Running"); println!("Running");
drawer.run()?; drawer.run()?;

View file

@ -0,0 +1,135 @@
use std::{fs, io::ErrorKind, path::PathBuf};
use anyhow::{Context, bail};
use image::RgbaImage;
use jiff::Timestamp;
use showbits_common::Tree;
use taffy::{AvailableSpace, NodeId, Size};
use crate::printer::Printer;
pub struct PersistentPrinter {
printer_file: Option<PathBuf>,
export_file: Option<PathBuf>,
queue_dir: PathBuf,
printer: Option<Printer>,
}
impl PersistentPrinter {
pub fn new(
printer_file: Option<PathBuf>,
export_file: Option<PathBuf>,
queue_dir: PathBuf,
) -> Self {
Self {
printer_file,
export_file,
queue_dir,
printer: None,
}
}
fn render_tree_to_image<C>(
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<RgbaImage> {
let available = Size {
width: AvailableSpace::Definite(Printer::WIDTH as f32),
// TODO Maybe MinContent? If not, why not?
height: AvailableSpace::MaxContent,
};
tree.render(ctx, root, available)
}
fn print_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
let Some(printer) = &mut self.printer else {
bail!("no printer found");
};
printer.print_image(image)?;
Ok(())
}
fn reconnect_printer(&mut self) -> anyhow::Result<()> {
let printer = Printer::new(self.printer_file.clone(), self.export_file.clone())?;
self.printer = Some(printer);
Ok(())
}
fn print_image_robustly(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
println!("Printing image");
if self.print_image(image).is_ok() {
return Ok(());
}
println!("First attempt failed, reconnecting and retrying");
self.reconnect_printer()?;
self.print_image(image)?;
Ok(())
}
fn enqueue_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
let now = Timestamp::now();
let path = self.queue_dir.join(format!("{now}.png"));
println!("Enqueuing image {}", path.display());
fs::create_dir_all(&self.queue_dir)
.with_context(|| format!("At {}", self.queue_dir.display()))
.context("Failed to create queue directory")?;
image
.save(&path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to save image to queue")?;
Ok(())
}
pub fn print_tree<C>(
&mut self,
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<()> {
let image = Self::render_tree_to_image(tree, ctx, root)?;
if self.print_image_robustly(&image).is_err() {
self.enqueue_image(&image)?;
}
Ok(())
}
pub fn print_backlog(&mut self) -> anyhow::Result<()> {
let mut files = vec![];
match self.queue_dir.read_dir() {
Err(err) if err.kind() == ErrorKind::NotFound => {}
Err(err) => Err(err)
.with_context(|| format!("At {}", self.queue_dir.display()))
.context("Failed to open queue dir")?,
Ok(dir) => {
for entry in dir {
let entry = entry?;
if entry.file_type()?.is_file() {
files.push(entry.path());
}
}
}
}
files.sort_unstable();
for file in files {
println!("Dequeuing image {}", file.display());
let image: RgbaImage = image::open(&file)?.into_rgba8();
if self.print_image_robustly(&image).is_err() {
return Ok(());
}
fs::remove_file(&file)?;
}
Ok(())
}
}

View file

@ -1,5 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use anyhow::Context;
use escpos::{ use escpos::{
driver::FileDriver, driver::FileDriver,
printer::Printer as EPrinter, printer::Printer as EPrinter,
@ -7,8 +8,7 @@ use escpos::{
utils::{GS, PageCode, Protocol}, utils::{GS, PageCode, Protocol},
}; };
use image::{Rgba, RgbaImage}; use image::{Rgba, RgbaImage};
use showbits_common::{Tree, color}; use showbits_common::color;
use taffy::{AvailableSpace, NodeId, Size};
pub struct Printer { pub struct Printer {
printer: Option<EPrinter<FileDriver>>, printer: Option<EPrinter<FileDriver>>,
@ -44,7 +44,9 @@ impl Printer {
export_path: Option<PathBuf>, export_path: Option<PathBuf>,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let printer = if let Some(path) = printer_path { let printer = if let Some(path) = printer_path {
let driver = FileDriver::open(&path)?; let driver = FileDriver::open(&path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to open printer driver")?;
let protocol = Protocol::default(); let protocol = Protocol::default();
let mut options = PrinterOptions::default(); let mut options = PrinterOptions::default();
options.page_code(Some(Self::PAGE_CODE)); options.page_code(Some(Self::PAGE_CODE));
@ -59,34 +61,16 @@ impl Printer {
}) })
} }
pub fn feed(&mut self) -> anyhow::Result<()> { pub fn print_image(&mut self, image: &RgbaImage) -> anyhow::Result<()> {
if let Some(printer) = &mut self.printer {
printer.init()?.feeds(3)?.print()?;
}
Ok(())
}
pub fn print_tree<C>(
&mut self,
tree: &mut Tree<C>,
ctx: &mut C,
root: NodeId,
) -> anyhow::Result<()> {
let available = Size {
width: AvailableSpace::Definite(Self::WIDTH as f32),
// TODO Maybe MinContent? If not, why not?
height: AvailableSpace::MaxContent,
};
let image = tree.render(ctx, root, available)?;
if let Some(path) = &self.export_path { if let Some(path) = &self.export_path {
image.save(path)?; image
.save(path)
.with_context(|| format!("At {}", path.display()))
.context("Failed to export to-be-printed image")?;
} }
if let Some(printer) = &mut self.printer { if let Some(printer) = &mut self.printer {
Self::print_image_to_printer(printer, &image)?; Self::print_image_to_printer(printer, image).context("Failed to print image")?;
} }
Ok(()) Ok(())