use super::DxgiPrintContext;
use super::DxgiPrintContextError;
use crate::printer::FilePrinter;
use crate::printer::PrinterDevice;
use crate::ticket::PrintTicket;
use std::path::Path;
use thiserror::Error;
use windows::core::HSTRING;
use windows::Data::Pdf::PdfDocument;
use windows::Storage::StorageFile;
use windows::Win32::Graphics::Direct2D::Common::D2D_SIZE_F;
use windows::Win32::System::WinRT::Pdf::PdfCreateRenderer;
use windows::Win32::System::WinRT::Pdf::PDF_RENDER_PARAMS;
#[derive(Error, Debug)]
pub enum WinPdfPrinterError {
#[error("DXGI print context error")]
DxgiPrintContextError(#[from] DxgiPrintContextError),
#[error("Invalid path")]
InvalidPath(#[source] std::io::Error),
#[error("Failed to open the document")]
FailedToOpenDocument(#[source] windows::core::Error),
#[error("Render error")]
RenderError(#[source] windows::core::Error),
}
pub struct WinPdfPrinter {
printer: PrinterDevice,
}
impl WinPdfPrinter {
pub fn new(printer: PrinterDevice) -> Self {
Self { printer }
}
}
impl FilePrinter for WinPdfPrinter {
type Options = PrintTicket;
type Error = WinPdfPrinterError;
fn print(
&self,
path: &Path,
options: PrintTicket,
) -> std::result::Result<(), WinPdfPrinterError> {
let context = DxgiPrintContext::new(
&self.printer,
&options,
path.file_name().unwrap_or(path.as_ref()),
)?;
let dxgi_device = &context.dxgi_device;
let print_control = &context.print_control;
let d2d_context = &context.d2d_context;
unsafe {
let absolute_path =
std::path::absolute(path).map_err(WinPdfPrinterError::InvalidPath)?;
let file = StorageFile::GetFileFromPathAsync(&HSTRING::from(absolute_path.as_path()))
.map_err(WinPdfPrinterError::FailedToOpenDocument)?
.get()
.map_err(WinPdfPrinterError::FailedToOpenDocument)?;
let pdf_document = PdfDocument::LoadFromFileAsync(&file)
.map_err(WinPdfPrinterError::FailedToOpenDocument)?
.get()
.map_err(WinPdfPrinterError::FailedToOpenDocument)?;
let pdf_renderer =
PdfCreateRenderer(dxgi_device).map_err(WinPdfPrinterError::RenderError)?;
let pdf_render_options = PDF_RENDER_PARAMS {
IgnoreHighContrast: true.into(),
..Default::default()
};
let page_count = pdf_document
.PageCount()
.map_err(WinPdfPrinterError::RenderError)?;
for i in 0..page_count {
let pdf_page = pdf_document
.GetPage(i)
.map_err(WinPdfPrinterError::RenderError)?;
let command_list = d2d_context
.CreateCommandList()
.map_err(WinPdfPrinterError::RenderError)?;
d2d_context.SetTarget(&command_list);
d2d_context.BeginDraw();
pdf_renderer
.RenderPageToDeviceContext(&pdf_page, d2d_context, Some(&pdf_render_options))
.map_err(WinPdfPrinterError::RenderError)?;
d2d_context
.EndDraw(None, None)
.map_err(WinPdfPrinterError::RenderError)?;
command_list
.Close()
.map_err(WinPdfPrinterError::RenderError)?;
let pdf_size = pdf_page.Size().map_err(WinPdfPrinterError::RenderError)?;
let dx_size = D2D_SIZE_F {
width: pdf_size.Width as f32,
height: pdf_size.Height as f32,
};
print_control
.AddPage(&command_list, dx_size, None, None, None)
.map_err(WinPdfPrinterError::RenderError)?;
}
}
context.close_and_wait()?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::WinPdfPrinter;
use crate::{printer::FilePrinter, test_utils::null_device};
use std::path::Path;
#[test]
fn print_simple_pdf_document() {
let device = null_device::thread_local();
let pdf = WinPdfPrinter::new(device);
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.pdf");
pdf.print(path.as_path(), Default::default()).unwrap();
}
}