mirror of
https://github.com/storytold/winprint.rs.git
synced 2026-10-13 23:17:45 +00:00
docs: initial documents
This commit is contained in:
18 files changed
+269
-1
No files matched your search
+96
@@ -1,8 +1,104 @@
|
||||
#![cfg(windows)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! A crate for printing to a Windows printer device using Windows API.
|
||||
//!
|
||||
//! # Examples
|
||||
//! ## Print a file
|
||||
//! For a simple example presenting how to print a file:
|
||||
//! - Filter for the device you want to use.
|
||||
//! - Wrap the printer device with a printer.
|
||||
//! - Send a file to the printer.
|
||||
//!
|
||||
//! First, get all printer devices via `PrinterDevice::all()` and filter for the device you want to use.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use winprint::printer::PrinterDevice;
|
||||
//!
|
||||
//! fn get_my_device() -> PrinterDevice {
|
||||
//! let printers = PrinterDevice::all().expect("Failed to get printers");
|
||||
//! printers
|
||||
//! .into_iter()
|
||||
//! .find(|x| x.name() == "My Printer")
|
||||
//! .expect("My Printer not found")
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! Then, create a printer and send a file to it. Currently, there are two kinds of printers available:
|
||||
//! - [`printer::XpsPrinter`]: For printing XPS files.
|
||||
//! - [`printer::PdfiumPrinter`]: For printing PDF files via PDFium library. (Feature `pdfium` must be enabled)
|
||||
//!
|
||||
//! **Note**: The concept *`Printer`* here is a warpper of device for printing specific types of data,
|
||||
//! not meaning the printer device.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::path::Path;
|
||||
//! use winprint::printer::FilePrinter;
|
||||
//! use winprint::printer::PrinterDevice;
|
||||
//! use winprint::printer::XpsPrinter;
|
||||
//! # use winprint::test_utils::null_device::thread_local as get_my_device;
|
||||
//!
|
||||
//! let my_device = get_my_device();
|
||||
//! let xps = XpsPrinter::new(my_device);
|
||||
//! let path = Path::new("path/to/test/document.xps");
|
||||
//! # let path_buf = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.xps");
|
||||
//! # let path = path_buf.as_path();
|
||||
//! xps.print(path, Default::default()).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! ## Specify the printing preferences
|
||||
//! Print ticket is a set of options that can be to specify the printing preferences,
|
||||
//! It can be used to set options such as the media size, orientation, and so on.
|
||||
//! If you want to specify the printing preferences, you may use print tickets.
|
||||
//!
|
||||
//! See [Print Schema Specification] for technical details.
|
||||
//!
|
||||
//! Here is an example presenting how to use print tickets with this crate:
|
||||
//! - Fetch print capabilities from the printer device.
|
||||
//! - Filter the capabilities you want to use.
|
||||
//! - Create a print ticket builder for your printer device.
|
||||
//! - Merge the capabilities into the print ticket you are to build.
|
||||
//! - Build the print ticket.
|
||||
//! - Print the file with the print ticket.
|
||||
//!
|
||||
//! ```rust
|
||||
//! use std::path::Path;
|
||||
//! use winprint::printer::FilePrinter;
|
||||
//! use winprint::printer::PrinterDevice;
|
||||
//! use winprint::printer::XpsPrinter;
|
||||
//! use winprint::ticket::PredefinedMediaName;
|
||||
//! use winprint::ticket::PrintCapabilities;
|
||||
//! use winprint::ticket::PrintTicket;
|
||||
//! use winprint::ticket::PrintTicketBuilder;
|
||||
//! # use winprint::test_utils::null_device::thread_local as get_my_device;
|
||||
//!
|
||||
//! let my_device = get_my_device();
|
||||
//! let capabilities = PrintCapabilities::fetch(&my_device).unwrap();
|
||||
//! let a4_media = capabilities
|
||||
//! .page_media_size()
|
||||
//! .find(|x| x.as_predefined_name() == Some(PredefinedMediaName::ISOA4))
|
||||
//! .unwrap();
|
||||
//! let mut builder = PrintTicketBuilder::new(&my_device).unwrap();
|
||||
//! builder.merge(a4_media).unwrap();
|
||||
//! let ticket = builder.build().unwrap();
|
||||
//! let xps = XpsPrinter::new(my_device);
|
||||
//! let path = Path::new("path/to/test/document.xps");
|
||||
//! # let path_buf = Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/test_document.xps");
|
||||
//! # let path = path_buf.as_path();
|
||||
//! xps.print(path, ticket).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! [Print Schema Specification]: https://learn.microsoft.com/en-us/windows/win32/printdocs/printschema
|
||||
//!
|
||||
//! # Features
|
||||
//! - `pdfium`: Enable PDFium support for printing PDF files.
|
||||
|
||||
mod bindings;
|
||||
/// Provides a way to print various types of data to a printer device.
|
||||
pub mod printer;
|
||||
/// Utilities for testing
|
||||
pub mod test_utils;
|
||||
/// Provides a way to specify the printing preferences.
|
||||
pub mod ticket;
|
||||
mod utils;
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -7,6 +7,7 @@ use thiserror::Error;
|
||||
use windows::core::PCWSTR;
|
||||
use windows::Win32::Graphics::Printing::*;
|
||||
#[derive(Clone, Debug)]
|
||||
/// Represents a printer device.
|
||||
pub struct PrinterDevice {
|
||||
name: String,
|
||||
os_server: OsString,
|
||||
@@ -15,12 +16,15 @@ pub struct PrinterDevice {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error occurred while enumerating printer devices.
|
||||
pub enum EnumDeviceError {
|
||||
/// Failed to enum printer device.
|
||||
#[error("Failed to enum printer device: {0}")]
|
||||
FailedToEnumPrinterDevice(windows::core::Error),
|
||||
}
|
||||
|
||||
impl PrinterDevice {
|
||||
/// Fetch all printer devices.
|
||||
pub fn all() -> Result<Vec<Self>, EnumDeviceError> {
|
||||
let mut bytes_needed = 0;
|
||||
let mut count_returned = 0;
|
||||
@@ -91,10 +95,12 @@ impl PrinterDevice {
|
||||
self.os_name.as_ref()
|
||||
}
|
||||
|
||||
/// Check if the printer device is local.
|
||||
pub fn is_local(&self) -> bool {
|
||||
self.os_attributes & PRINTER_ATTRIBUTE_LOCAL != 0
|
||||
}
|
||||
|
||||
/// Check if the printer device is remote.
|
||||
pub fn is_remote(&self) -> bool {
|
||||
self.os_attributes & PRINTER_ATTRIBUTE_NETWORK != 0
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use std::path::Path;
|
||||
|
||||
/// A trait representing a printer that can print files.
|
||||
pub trait FilePrinter {
|
||||
/// The options type for the printer.
|
||||
type Options;
|
||||
/// The error type for the printer.
|
||||
type Error;
|
||||
/// Print the file with the given options.
|
||||
fn print(&self, path: &Path, options: Self::Options) -> std::result::Result<(), Self::Error>;
|
||||
}
|
||||
@@ -21,20 +21,26 @@ use windows::{
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error from [`PdfiumPrinter`].
|
||||
pub enum PdfiumPrinterError {
|
||||
/// Failed to open printer.
|
||||
#[error("Failed to open printer")]
|
||||
FailedToOpenPrinter,
|
||||
/// File I/O error.
|
||||
#[error("File I/O error: {0}")]
|
||||
FileIOError(std::io::Error),
|
||||
/// Print ticket error.
|
||||
#[error("Print Ticker Error: {0}")]
|
||||
PrintTicketError(ToDevModeError),
|
||||
}
|
||||
|
||||
/// A printer that uses Pdfium to print PDF documents.
|
||||
pub struct PdfiumPrinter {
|
||||
printer: PrinterDevice,
|
||||
}
|
||||
|
||||
impl PdfiumPrinter {
|
||||
/// Create a new [`PdfiumPrinter`] for the given printer device.
|
||||
pub fn new(printer: PrinterDevice) -> Self {
|
||||
Self { printer }
|
||||
}
|
||||
|
||||
@@ -17,26 +17,35 @@ use windows::{
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error from [`XpsPrinter`].
|
||||
pub enum XpsPrinterError {
|
||||
/// Failed to create event.
|
||||
#[error("Failed to create event: {0}")]
|
||||
FailedToCreateEvent(windows::core::Error),
|
||||
/// Failed to create object factory.
|
||||
#[error("Failed to create object factory: {0}")]
|
||||
FailedToCreateObjectFactory(windows::core::Error),
|
||||
/// Failed to start job.
|
||||
#[error("Failed to start job: {0}")]
|
||||
FailedToStartJob(windows::core::Error),
|
||||
/// Failed to apply print ticket.
|
||||
#[error("Failed to apply print ticket: {0}")]
|
||||
FailedToApplyPrintTicket(windows::core::Error),
|
||||
/// Failed to write document.
|
||||
#[error("Failed to write document: {0}")]
|
||||
FailedToWriteDocument(windows::core::Error),
|
||||
/// Stream is not available.
|
||||
#[error("Stream is not available")]
|
||||
StreamNotAvailable,
|
||||
}
|
||||
|
||||
/// A printer to print XPS documents.
|
||||
pub struct XpsPrinter {
|
||||
printer: PrinterDevice,
|
||||
}
|
||||
|
||||
impl XpsPrinter {
|
||||
/// Create a new [`XpsPrinter`] for the given printer device.
|
||||
pub fn new(printer: PrinterDevice) -> Self {
|
||||
Self { printer }
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
#![cfg(feature = "test-utils")]
|
||||
/// Provides a auto-managed null printer device for testing.
|
||||
pub mod null_device;
|
||||
@@ -74,6 +74,8 @@ impl Drop for NullPrinterDevice {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a thread-local null printer device.
|
||||
/// This device is automatically managed and to be deleted when the thread ends.
|
||||
pub fn thread_local() -> PrinterDevice {
|
||||
NULL_DEVICE.with(|f| f.get_or_init(NullPrinterDevice::new).printer.clone())
|
||||
}
|
||||
@@ -15,24 +15,31 @@ use windows::{
|
||||
},
|
||||
};
|
||||
|
||||
/// Represents a print ticket builder, which can be used to build a print ticket.
|
||||
pub struct PrintTicketBuilder {
|
||||
xml: Vec<u8>,
|
||||
provider: HPTPROVIDER,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error occurred while building a print ticket.
|
||||
pub enum PrintTicketBuilderError {
|
||||
/// Failed to open print ticket provider.
|
||||
#[error("Failed to open print ticket provider: {0}")]
|
||||
OpenProviderFailed(windows::core::Error),
|
||||
/// Stream not allocated.
|
||||
#[error("Stream not allocated")]
|
||||
StreamNotAllocated,
|
||||
/// Failed to merge print tickets.
|
||||
#[error("Failed to merge print tickets: {0}")]
|
||||
MergePrintTicketsFailed(String, windows::core::Error),
|
||||
/// Failed to decode print ticket.
|
||||
#[error("Failed to decode print ticket: {0}")]
|
||||
DecodePrintTicketFailed(windows::core::Error),
|
||||
}
|
||||
|
||||
impl PrintTicketBuilder {
|
||||
/// Create a new print ticket builder for the given printer device.
|
||||
pub fn new(device: &PrinterDevice) -> Result<Self, PrintTicketBuilderError> {
|
||||
let provider = unsafe {
|
||||
PTOpenProvider(PCWSTR(wchar::to_wide_chars(device.os_name()).as_ptr()), 1)
|
||||
@@ -44,6 +51,7 @@ impl PrintTicketBuilder {
|
||||
})
|
||||
}
|
||||
|
||||
/// Merge the given print ticket into the current print ticket.
|
||||
pub fn merge(&mut self, delta: impl Into<PrintTicket>) -> Result<(), PrintTicketBuilderError> {
|
||||
unsafe {
|
||||
let base = SHCreateMemStream(Some(self.xml.as_ref()))
|
||||
@@ -73,6 +81,7 @@ impl PrintTicketBuilder {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build the print ticket.
|
||||
pub fn build(mut self) -> Result<PrintTicket, PrintTicketBuilderError> {
|
||||
let xml = std::mem::take(&mut self.xml);
|
||||
Ok(PrintTicket { xml })
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
mod print_schema;
|
||||
/// Reader for parse XML bytes as Print Schema.
|
||||
pub mod reader;
|
||||
/// Writer for serialize Print Schema as XML bytes.
|
||||
pub mod writer;
|
||||
|
||||
pub use print_schema::*;
|
||||
|
||||
/// The namespace URI for the Print Schema Framework.
|
||||
pub const NS_PSF: &str =
|
||||
"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework";
|
||||
/// The namespace URI for the Print Schema Keywords.
|
||||
pub const NS_PSK: &str =
|
||||
"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords";
|
||||
/// The namespace URI for the XML Schema.
|
||||
pub const NS_XSD: &str = "http://www.w3.org/2001/XMLSchema";
|
||||
/// The namespace URI for the XML Schema Instance.
|
||||
pub const NS_XSI: &str = "http://www.w3.org/2001/XMLSchema-instance";
|
||||
@@ -2,83 +2,126 @@ use std::fmt;
|
||||
use xml::name::OwnedName;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a Print Schema document.
|
||||
pub enum PrintSchemaDocument {
|
||||
/// Documents that typed as [`PrintCapabilitiesDocument`].
|
||||
PrintCapabilities(PrintCapabilitiesDocument),
|
||||
/// Documents that typed as [`PrintTicketDocument`].
|
||||
PrintTicket(PrintTicketDocument),
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a PrintCapabilities document.
|
||||
pub struct PrintCapabilitiesDocument {
|
||||
/// Properties of the document
|
||||
pub properties: Vec<Property>,
|
||||
/// Parameter definitions
|
||||
pub parameter_defs: Vec<ParameterDef>,
|
||||
/// Features
|
||||
pub features: Vec<PrintFeature>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a PrintTicket document.
|
||||
pub struct PrintTicketDocument {
|
||||
/// Properties of the document
|
||||
pub properties: Vec<Property>,
|
||||
/// Parameter initializations
|
||||
pub parameter_inits: Vec<ParameterInit>,
|
||||
/// Features
|
||||
pub features: Vec<PrintFeature>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a Print Feature.
|
||||
pub struct PrintFeature {
|
||||
/// The name of the feature.
|
||||
#[fmt("{}", self.name)]
|
||||
pub name: OwnedName,
|
||||
/// Properties of the feature
|
||||
pub properties: Vec<Property>,
|
||||
/// Available options
|
||||
pub options: Vec<PrintFeatureOption>,
|
||||
/// Sub-features of the feature
|
||||
pub features: Vec<PrintFeature>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a parameter initialization used in a [`PrintTicketDocument`].
|
||||
pub struct ParameterInit {
|
||||
/// The name of the parameter.
|
||||
#[fmt("{}", self.name)]
|
||||
pub name: OwnedName,
|
||||
/// The value of the parameter.
|
||||
#[fmt("{:?}", self.value)]
|
||||
pub value: PropertyValue,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a parameter definition used in a [`PrintCapabilitiesDocument`].
|
||||
pub struct ParameterDef {
|
||||
/// The name of the parameter.
|
||||
#[fmt("{}", self.name)]
|
||||
pub name: OwnedName,
|
||||
/// Properties of the parameter
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a possible option for a [`PrintFeature`].
|
||||
pub struct PrintFeatureOption {
|
||||
/// The name of the option.
|
||||
#[fmt("{}", self.name.as_ref().map(|x| x.to_string()).unwrap_or("<unnamed>".to_string()))]
|
||||
pub name: Option<OwnedName>,
|
||||
/// Scored-properties of the option
|
||||
pub scored_properties: Vec<ScoredProperty>,
|
||||
/// Properties of the option
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a scored-property.
|
||||
/// A [`ScoredProperty`] declares a property that is intrinsic to an [Option](PrintFeatureOption).
|
||||
/// Such properties should be compared when evaluating how closely a requested Option matches a device-supported Option.
|
||||
pub struct ScoredProperty {
|
||||
/// The name of the scored-property.
|
||||
#[fmt("{}", self.name.as_ref().map(|x| x.to_string()).unwrap_or("<unnamed>".to_string()))]
|
||||
pub name: Option<OwnedName>,
|
||||
/// The parameter that this scored-property depends on.
|
||||
#[fmt("{}", self.parameter_ref.as_ref().map(|x| x.to_string()).unwrap_or("<unnamed>".to_string()))]
|
||||
pub parameter_ref: Option<OwnedName>,
|
||||
/// The value of the scored-property.
|
||||
#[fmt("{}", self.value.as_ref().map(|x| format!("{:?}", x)).unwrap_or("<none>".to_string()))]
|
||||
pub value: Option<PropertyValue>,
|
||||
/// Sub-scored-properties of the scored-property
|
||||
pub scored_properties: Vec<ScoredProperty>,
|
||||
/// Properties of the scored-property
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, fmt_derive::Debug)]
|
||||
/// Represents a property.
|
||||
pub struct Property {
|
||||
/// The name of the property.
|
||||
#[fmt("{}", self.name)]
|
||||
pub name: OwnedName,
|
||||
/// The value of the property.
|
||||
#[fmt("{}", self.value.as_ref().map(|x| format!("{:?}", x)).unwrap_or("<none>".to_string()))]
|
||||
pub value: Option<PropertyValue>,
|
||||
/// Sub-properties of the property
|
||||
pub properties: Vec<Property>,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
/// Represents a property value or a scored-property value.
|
||||
pub enum PropertyValue {
|
||||
/// A string value.
|
||||
String(String),
|
||||
/// An integer value.
|
||||
Integer(i32),
|
||||
/// A qualified name value.
|
||||
QName(OwnedName),
|
||||
/// An unknown-typed value.
|
||||
Unknown(OwnedName, String),
|
||||
}
|
||||
|
||||
@@ -94,6 +137,7 @@ impl fmt::Debug for PropertyValue {
|
||||
}
|
||||
|
||||
impl ParameterDef {
|
||||
/// Get the default value of this parameter.
|
||||
pub fn default_value(&self) -> Option<&PropertyValue> {
|
||||
self.properties
|
||||
.iter()
|
||||
@@ -105,6 +149,7 @@ impl ParameterDef {
|
||||
}
|
||||
|
||||
impl PropertyValue {
|
||||
/// Get the `xsi:type` of this value.
|
||||
pub fn xsi_type(&self) -> OwnedName {
|
||||
match self {
|
||||
PropertyValue::String(_) => OwnedName::qualified("string", super::NS_XSD, Some("xsd")),
|
||||
@@ -115,18 +160,21 @@ impl PropertyValue {
|
||||
PropertyValue::Unknown(n, _) => n.clone(),
|
||||
}
|
||||
}
|
||||
/// Try as [`PropertyValue::String`] value.
|
||||
pub fn string(&self) -> Option<&str> {
|
||||
match self {
|
||||
PropertyValue::String(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Try as [`PropertyValue::Integer`] value.
|
||||
pub fn integer(&self) -> Option<i32> {
|
||||
match self {
|
||||
PropertyValue::Integer(i) => Some(*i),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Try as [`PropertyValue::QName`] value.
|
||||
pub fn qualified_name(&self) -> Option<&OwnedName> {
|
||||
match self {
|
||||
PropertyValue::QName(q) => Some(q),
|
||||
@@ -136,6 +184,7 @@ impl PropertyValue {
|
||||
}
|
||||
|
||||
impl PrintFeatureOption {
|
||||
/// Collect all parameters that this option depends on.
|
||||
pub fn parameters_dependent(&self) -> Vec<OwnedName> {
|
||||
let mut result = vec![];
|
||||
for scored_property in &self.scored_properties {
|
||||
@@ -146,6 +195,7 @@ impl PrintFeatureOption {
|
||||
}
|
||||
|
||||
impl ScoredProperty {
|
||||
/// Collect all parameters that this scored-property depends on.
|
||||
pub fn parameters_dependent(&self) -> Vec<OwnedName> {
|
||||
let mut result = vec![];
|
||||
if let Some(ref parameter_ref) = self.parameter_ref {
|
||||
@@ -156,6 +206,8 @@ impl ScoredProperty {
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// Get the value of this scored-property, or the value of the parameter it references.
|
||||
pub fn value_with<'a>(&'a self, parameters: &'a [ParameterInit]) -> Option<&'a PropertyValue> {
|
||||
if let Some(ref parameter_ref) = self.parameter_ref {
|
||||
parameters
|
||||
@@ -180,8 +232,12 @@ impl From<PrintCapabilitiesDocument> for PrintSchemaDocument {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that have scored-properties.
|
||||
pub trait WithScoredProperties {
|
||||
/// Get the scored properties.
|
||||
fn scored_properties(&self) -> &[ScoredProperty];
|
||||
|
||||
/// Get the scored-property with the given name and namespace.
|
||||
fn get_scored_property(&self, name: &str, namespace: Option<&str>) -> Option<&ScoredProperty> {
|
||||
self.scored_properties().iter().find(|x| {
|
||||
x.name.as_ref().map_or(false, |x| {
|
||||
@@ -203,8 +259,12 @@ impl WithScoredProperties for ScoredProperty {
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for types that have properties.
|
||||
pub trait WithProperties {
|
||||
/// Get the properties.
|
||||
fn properties(&self) -> &[Property];
|
||||
|
||||
/// Get the property with the given name and namespace.
|
||||
fn get_property(&self, name: &str, namespace: Option<&str>) -> Option<&Property> {
|
||||
self.properties()
|
||||
.iter()
|
||||
|
||||
@@ -14,25 +14,40 @@ use xml::{
|
||||
};
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error occurred while parsing print schema.
|
||||
pub enum ParsePrintSchemaError {
|
||||
/// Invalid XML.
|
||||
#[error("Invalid xml: {0}")]
|
||||
InvalidXml(xml::reader::Error),
|
||||
/// Invalid print schema.
|
||||
#[error("Invalid print schema: (at {pos}) {reason}")]
|
||||
InvalidPrintSchema { pos: TextPosition, reason: String },
|
||||
InvalidPrintSchema {
|
||||
/// Position in the document.
|
||||
pos: TextPosition,
|
||||
/// Reason of the error.
|
||||
reason: String,
|
||||
},
|
||||
/// Wrong document type.
|
||||
#[error("Wrong document type: expected {expected} but found {found}")]
|
||||
WrongDocumentType {
|
||||
/// Expected document type.
|
||||
expected: &'static str,
|
||||
/// Found document type.
|
||||
found: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
/// Represents a root element which can be parsed from XML.
|
||||
pub trait ParsableXmlDocument: Sized {
|
||||
/// The error type that can be returned when parsing fails.
|
||||
type Error;
|
||||
|
||||
/// Parse the XML document from the given XML reader.
|
||||
fn parse<R>(reader: &mut EventReader<R>) -> Result<Self, Self::Error>
|
||||
where
|
||||
R: std::io::Read;
|
||||
|
||||
/// Parse the XML document from the given bytes.
|
||||
fn parse_from_bytes(xml: impl AsRef<[u8]>) -> Result<Self, Self::Error> {
|
||||
let mut reader = EventReader::new(Cursor::new(xml));
|
||||
Self::parse(&mut reader)
|
||||
|
||||
@@ -9,12 +9,17 @@ use xml::{
|
||||
impl XmlDocumentRoot for super::PrintCapabilitiesDocument {}
|
||||
impl XmlDocumentRoot for super::PrintTicketDocument {}
|
||||
|
||||
/// A trait for serializing as XML element.
|
||||
pub trait XmlSerializer {
|
||||
/// Collect the namespace URIs and prefixes used in the XML element.
|
||||
fn collect_namespace(&self, ns: &mut Namespace);
|
||||
/// Write the XML element to the writer.
|
||||
fn write_to<W: Write>(&self, writer: &mut EventWriter<W>) -> Result<(), xml::writer::Error>;
|
||||
}
|
||||
|
||||
/// A trait for serializing as XML document.
|
||||
pub trait XmlDocumentRoot: XmlSerializer {
|
||||
/// Convert the struct to XML.
|
||||
fn to_xml(&self) -> Vec<u8> {
|
||||
let mut writer = EmitterConfig::new()
|
||||
.perform_indent(false)
|
||||
|
||||
@@ -1,26 +1,34 @@
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||
/// Represents a media size tuple.
|
||||
pub struct MediaSizeTuple(u32, u32);
|
||||
impl MediaSizeTuple {
|
||||
/// Create a new media size tuple using width and height in micron.
|
||||
pub const fn micron(width: u32, height: u32) -> Self {
|
||||
MediaSizeTuple(width, height)
|
||||
}
|
||||
/// Create a new media size tuple using width and height in millimeter.
|
||||
pub const fn mm(width: u32, height: u32) -> Self {
|
||||
MediaSizeTuple(width * 1000, height * 1000)
|
||||
}
|
||||
/// Get the width in micron.
|
||||
pub const fn width_in_micron(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
/// Get the height in micron.
|
||||
pub const fn height_in_micron(&self) -> u32 {
|
||||
self.1
|
||||
}
|
||||
/// Set the width in micron.
|
||||
pub fn set_width_in_micron(&mut self, width: u32) {
|
||||
self.0 = width;
|
||||
}
|
||||
/// Set the height in micron.
|
||||
pub fn set_height_in_micron(&mut self, height: u32) {
|
||||
self.1 = height;
|
||||
}
|
||||
/// Determine if the media is a roll media.
|
||||
pub fn is_roll(&self) -> bool {
|
||||
// for roll media, either width or height is 0
|
||||
self.0 == 0 || self.1 == 0
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
mod builder;
|
||||
/// Document object model representation of print schema.
|
||||
pub mod document;
|
||||
mod media_size_tuple;
|
||||
mod page_media_size;
|
||||
@@ -13,4 +14,5 @@ pub use predefined_media_name::*;
|
||||
pub use print_capabilities::*;
|
||||
pub use print_ticket::*;
|
||||
|
||||
/// The default print ticket XML.
|
||||
pub const DEFAULT_PRINT_TICKET_XML: &str = r#"<psf:PrintTicket xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" version="1" xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords"></psf:PrintTicket>"#;
|
||||
@@ -8,16 +8,21 @@ use super::{
|
||||
use xml::name::OwnedName;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Represents a page media size.
|
||||
pub struct PageMediaSize {
|
||||
/// The DOM of the option for media size.
|
||||
pub option: PrintFeatureOption,
|
||||
/// The parameters that is referenced by the option.
|
||||
pub parameters: Vec<ParameterInit>,
|
||||
}
|
||||
|
||||
impl PageMediaSize {
|
||||
/// Create a new [`PageMediaSize`] from the given DOM.
|
||||
pub fn new(option: PrintFeatureOption, parameters: Vec<ParameterInit>) -> Self {
|
||||
Self { option, parameters }
|
||||
}
|
||||
|
||||
/// Get the size of the media.
|
||||
pub fn size(&self) -> MediaSizeTuple {
|
||||
let width = self
|
||||
.option
|
||||
@@ -34,6 +39,8 @@ impl PageMediaSize {
|
||||
MediaSizeTuple::micron(width as u32, height as u32)
|
||||
}
|
||||
|
||||
/// Get the predefined name of the media.
|
||||
/// If the media is not predefined, `None` is returned.
|
||||
pub fn as_predefined_name(&self) -> Option<PredefinedMediaName> {
|
||||
self.option
|
||||
.name
|
||||
|
||||
@@ -4,6 +4,8 @@ use strum::EnumString;
|
||||
use xml::name::OwnedName;
|
||||
|
||||
#[derive(EnumString, Debug, PartialEq, Eq, Hash, Clone, Copy)]
|
||||
#[allow(missing_docs)]
|
||||
/// Represents a predefined media name.
|
||||
pub enum PredefinedMediaName {
|
||||
BusinessCard,
|
||||
CreditCard,
|
||||
@@ -178,6 +180,7 @@ pub enum PredefinedMediaName {
|
||||
}
|
||||
|
||||
impl PredefinedMediaName {
|
||||
/// Get predefined media name from the given name.
|
||||
pub fn from_name(name: &OwnedName) -> Option<Self> {
|
||||
if name.namespace_ref() == Some(NS_PSK) {
|
||||
Self::from_str(name.local_name.as_str()).ok()
|
||||
|
||||
@@ -24,25 +24,34 @@ use windows::{
|
||||
use xml::name::OwnedName;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error occurred while fetching print capabilities.
|
||||
pub enum FetchPrintCapabilitiesError {
|
||||
/// Failed to open print ticket provider.
|
||||
#[error("Failed to open print ticket provider: {0}")]
|
||||
OpenProviderFailed(windows::core::Error),
|
||||
/// Stream not allocated.
|
||||
#[error("Stream not allocated")]
|
||||
StreamNotAllocated,
|
||||
/// Cannot get print capabilities.
|
||||
#[error("Cannot get print capabilities: {0}")]
|
||||
CannotGetPrintCapabilities(String, windows::core::Error),
|
||||
/// Failed to read stream.
|
||||
#[error("Failed to read stream: {0}")]
|
||||
ReadStreamFailed(windows::core::Error),
|
||||
/// Failed to parse print capabilities.
|
||||
#[error("Failed to parse print capabilities: {0}")]
|
||||
ParseError(ParsePrintSchemaError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Represents print capabilities.
|
||||
pub struct PrintCapabilities {
|
||||
/// DOM of print capabilities document.
|
||||
pub document: PrintCapabilitiesDocument,
|
||||
}
|
||||
|
||||
impl PrintCapabilities {
|
||||
/// Fetch print capabilities XML (without parsing it) for the given printer device.
|
||||
pub fn fetch_xml(device: &PrinterDevice) -> Result<Vec<u8>, FetchPrintCapabilitiesError> {
|
||||
unsafe {
|
||||
let provider =
|
||||
@@ -71,6 +80,7 @@ impl PrintCapabilities {
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetch and parse print capabilities for the given printer device.
|
||||
pub fn fetch(device: &PrinterDevice) -> Result<PrintCapabilities, FetchPrintCapabilitiesError> {
|
||||
let xml = Self::fetch_xml(device)?;
|
||||
let document = PrintCapabilitiesDocument::parse_from_bytes(xml)
|
||||
@@ -78,6 +88,7 @@ impl PrintCapabilities {
|
||||
Ok(PrintCapabilities { document })
|
||||
}
|
||||
|
||||
/// Defines all parameters with default values.
|
||||
pub fn default_parameters(&self) -> impl Iterator<Item = ParameterInit> + '_ {
|
||||
self.document.parameter_defs.iter().filter_map(|param_def| {
|
||||
param_def
|
||||
@@ -89,6 +100,7 @@ impl PrintCapabilities {
|
||||
})
|
||||
}
|
||||
|
||||
/// Defines the given parameters with default values.
|
||||
pub fn default_parameters_for<'a>(
|
||||
&'a self,
|
||||
filters: &'a [OwnedName],
|
||||
@@ -116,6 +128,7 @@ impl PrintCapabilities {
|
||||
})
|
||||
}
|
||||
|
||||
/// Get all options for the given feature.
|
||||
pub fn options_for_feature<'a>(
|
||||
&'a self,
|
||||
feature_name: &'a str,
|
||||
@@ -138,6 +151,7 @@ impl PrintCapabilities {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all page media sizes.
|
||||
pub fn page_media_size(&self) -> impl Iterator<Item = PageMediaSize> + '_ {
|
||||
self.options_for_feature("PageMediaSize", Some(NS_PSK))
|
||||
.map(|option| {
|
||||
|
||||
@@ -25,6 +25,7 @@ use windows::{
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Represents a print ticket.
|
||||
pub struct PrintTicket {
|
||||
pub(crate) xml: Vec<u8>,
|
||||
}
|
||||
@@ -38,32 +39,46 @@ impl Default for PrintTicket {
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
/// Represents an error occurred while converting print ticket to dev mode.
|
||||
pub enum ToDevModeError {
|
||||
/// Failed to open print ticket provider.
|
||||
#[error("Failed to open print ticket provider: {0}")]
|
||||
OpenProviderFailed(windows::core::Error),
|
||||
/// Stream not allocated.
|
||||
#[error("Stream not allocated")]
|
||||
StreamNotAllocated,
|
||||
/// Failed to convert print ticket to dev mode.
|
||||
#[error("Failed to convert print ticket to dev mode: {0}")]
|
||||
ConvertPrintTicketToDevModeFailed(String, windows::core::Error),
|
||||
/// Failed to open printer.
|
||||
#[error("Failed to open printer: {0}")]
|
||||
FailedToOpenPrinter(windows::core::Error),
|
||||
/// Failed to correct dev mode via [`DocumentProperties`].
|
||||
///
|
||||
/// [`DocumentProperties`]: https://learn.microsoft.com/en-us/windows/win32/printdocs/documentproperties
|
||||
#[error("Failed to correct dev mode via DocumentProperties")]
|
||||
FailedToCorrectDevMode,
|
||||
}
|
||||
|
||||
impl PrintTicket {
|
||||
/// Create a new print ticket from the given XML.
|
||||
pub fn from_xml(xml: impl Into<Vec<u8>>) -> Self {
|
||||
Self { xml: xml.into() }
|
||||
}
|
||||
|
||||
/// Into the XML of the print ticket.
|
||||
pub fn into_xml(self) -> Vec<u8> {
|
||||
self.xml
|
||||
}
|
||||
|
||||
/// Get the XML of the print ticket.
|
||||
pub fn get_xml(&self) -> &[u8] {
|
||||
&self.xml
|
||||
}
|
||||
|
||||
/// Convert the print ticket to [`DEVMODE`] data.
|
||||
///
|
||||
/// [`DEVMODE`]: https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodew
|
||||
pub fn to_dev_mode(&self, device: &PrinterDevice) -> Result<Vec<u8>, ToDevModeError> {
|
||||
unsafe {
|
||||
let provider =
|
||||
|
||||
Reference in new issue
Block a user