ptouch_rs/
lib.rs

1mod tape_stats;
2use nusb::{Interface, transfer::RequestBuffer};
3pub use tape_stats::{TapeInfo, TapeSize};
4
5mod command;
6mod media_type;
7pub(crate) mod nom_utils;
8mod printer_stats;
9mod status_type;
10mod tape_color;
11mod text_color;
12
13pub use command::{Commands, Status};
14pub use media_type::MediaType;
15pub use printer_stats::{PrinterFlags, PrinterInfo, PrinterType};
16pub use status_type::StatusType;
17pub use tape_color::TapeColor;
18pub use text_color::TextColor;
19
20#[derive(Debug, thiserror::Error)]
21pub enum Error {
22  #[error(transparent)]
23  Nusb(#[from] nusb::Error),
24  #[error(transparent)]
25  NusbTransfer(#[from] nusb::transfer::TransferError),
26  #[error(transparent)]
27  Nom(#[from] nom::Err<nom::error::Error<Vec<u8>>>),
28  #[error("Printer not found")]
29  PrinterNotFound,
30  #[error("Invalid tape size reported: {0}")]
31  InvalidTapeSize(u8),
32  #[error("Printer status: {0:?}")]
33  Status(StatusType),
34  #[error("No tape loaded")]
35  NoTapeLoaded,
36}
37
38#[derive(Debug)]
39pub struct Printer {
40  interface: PrinterInterface,
41  status: Status,
42  ty: PrinterType,
43}
44
45struct PrinterInterface {
46  interface: Interface,
47}
48
49impl std::fmt::Debug for PrinterInterface {
50  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51    f.debug_struct("PrinterInterface")
52      .field("interface", &"<usb interface>")
53      .finish()
54  }
55}
56
57impl PrinterInterface {
58  async fn send(&self, data: impl Into<Vec<u8>>) -> Result<(), Error> {
59    self.interface.bulk_out(0x02, data.into()).await.status?;
60
61    Ok(())
62  }
63
64  async fn receive(&self, data: impl Into<Vec<u8>>) -> Result<Vec<u8>, Error> {
65    self.send(data.into()).await?;
66
67    let buf = RequestBuffer::new(32);
68    let res = self.interface.bulk_in(0x81, buf).await.into_result()?;
69
70    Ok(res)
71  }
72}
73
74impl Printer {
75  pub async fn open() -> Result<Self, Error> {
76    let found = nusb::list_devices()?.find_map(|device| {
77      let printer = PrinterType::from_usb(device.vendor_id(), device.product_id())?;
78
79      Some((device, printer))
80    });
81
82    let (device, ty) = match found {
83      Some(ty) => ty,
84      None => Err(Error::PrinterNotFound)?,
85    };
86
87    let device = device.open()?;
88    let interface = device.detach_and_claim_interface(0)?;
89
90    let interface = PrinterInterface { interface };
91
92    let mut init = vec![0; 102];
93    init[100] = 0x1b;
94    init[101] = 0x40;
95    interface.send(init).await?;
96
97    let status = Commands::status(&interface).await?;
98
99    Ok(Printer {
100      interface,
101      ty,
102      status,
103    })
104  }
105
106  pub async fn reload_status(&mut self) -> Result<Status, Error> {
107    let status = Commands::status(&self.interface).await?;
108    self.status = status.clone();
109
110    Ok(status)
111  }
112
113  pub fn ty(&self) -> PrinterType {
114    self.ty
115  }
116
117  pub fn status(&self) -> Status {
118    self.status.clone()
119  }
120
121  fn flags_contains(&self, flag: PrinterFlags) -> bool {
122    self.ty.info().flags.contains(flag)
123  }
124
125  async fn send(&self, data: impl Into<Vec<u8>>) -> Result<(), Error> {
126    self.interface.send(data).await
127  }
128
129  pub async fn print(&self, image: image::DynamicImage) -> Result<(), Error> {
130    if self.status.media_type == MediaType::None {
131      Err(Error::NoTapeLoaded)?;
132    }
133
134    if self.status.status_type != StatusType::Ok {
135      Err(Error::Status(self.status.status_type))?;
136    }
137
138    if self.flags_contains(PrinterFlags::RasterPackBits) {
139      Commands::pack_bits(self).await?;
140    }
141
142    Commands::raster_start(self).await?;
143
144    if self.flags_contains(PrinterFlags::UseInfoCmd) {
145      Commands::info(self, image.width()).await?;
146    }
147
148    if self.flags_contains(PrinterFlags::D460BTMagic) {
149      Commands::d460bt_magic(self, false).await?;
150    }
151
152    if self.flags_contains(PrinterFlags::HasPrecut) {
153      Commands::precut(self, true).await?;
154    }
155
156    Commands::raster_line(self, image).await?;
157
158    Commands::finalize(self, false).await?;
159
160    Ok(())
161  }
162}