#[ derive( Clone, Copy, Debug, PartialEq ) ]
pub struct Paper
{
pub media: &'static str,
pub name: &'static str,
pub width: f32,
pub height: f32,
}
pub const PAPERS: &[ Paper ] = &[
Paper { media: "iso_a4_210x297mm", name: "A4", width: 595.28, height: 841.89 },
Paper { media: "na_letter_8.5x11in", name: "Letter", width: 612.0, height: 792.0 },
Paper { media: "na_legal_8.5x14in", name: "Legal", width: 612.0, height: 1008.0 },
Paper { media: "iso_a3_297x420mm", name: "A3", width: 841.89, height: 1190.55 },
Paper { media: "iso_a5_148x210mm", name: "A5", width: 419.53, height: 595.28 },
];
#[ derive( Clone, Copy, Debug, PartialEq, Eq ) ]
pub enum Orientation
{
Portrait,
Landscape,
}
#[ derive( Clone, Copy, Debug, PartialEq, Eq ) ]
pub enum Margins
{
Default,
None,
Narrow,
Wide,
}
impl Margins
{
pub const ALL: [ Margins; 4 ] = [ Margins::Default, Margins::Narrow, Margins::Wide, Margins::None ];
pub fn points( self ) -> f32
{
const CM: f32 = 72.0 / 2.54;
match self
{
Margins::Default => CM,
Margins::None => 0.0,
Margins::Narrow => CM / 2.0,
Margins::Wide => CM * 2.0,
}
}
}
#[ derive( Clone, Copy, Debug, PartialEq, Eq ) ]
pub enum Sides
{
One,
LongEdge,
ShortEdge,
}
impl Sides
{
pub const ALL: [ Sides; 3 ] = [ Sides::One, Sides::LongEdge, Sides::ShortEdge ];
pub fn ipp( self ) -> &'static str
{
match self
{
Sides::One => "one-sided",
Sides::LongEdge => "two-sided-long-edge",
Sides::ShortEdge => "two-sided-short-edge",
}
}
}
#[ derive( Clone, Debug, PartialEq ) ]
pub struct PrintOptions
{
pub copies: u32,
pub pages: Vec<( u32, u32 )>,
pub paper: Paper,
pub orientation: Orientation,
pub margins: Margins,
pub scale: f32,
pub color: bool,
pub sides: Sides,
pub backgrounds: bool,
}
impl Default for PrintOptions
{
fn default() -> Self
{
Self
{
copies: 1,
pages: Vec::new(),
paper: PAPERS[ 0 ],
orientation: Orientation::Portrait,
margins: Margins::Default,
scale: 1.0,
color: true,
sides: Sides::One,
backgrounds: false,
}
}
}
impl PrintOptions
{
pub const MIN_SCALE: f32 = 0.1;
pub const MAX_SCALE: f32 = 2.0;
pub const MAX_COPIES: u32 = 99;
pub fn paper_size( &self ) -> ( f32, f32 )
{
let ( w, h ) = ( self.paper.width, self.paper.height );
match self.orientation
{
Orientation::Portrait => ( w, h ),
Orientation::Landscape => ( h, w ),
}
}
pub fn margin_points( &self ) -> [ f32; 4 ]
{
[ self.margins.points(); 4 ]
}
pub fn same_layout( &self, other: &PrintOptions ) -> bool
{
self.paper == other.paper && self.orientation == other.orientation && self.margins == other.margins
&& self.scale == other.scale && self.backgrounds == other.backgrounds && self.pages == other.pages
}
}
pub fn parse_pages( text: &str ) -> Option<Vec<( u32, u32 )>>
{
let mut ranges = Vec::new();
for part in text.split( [ ',', ';' ] ).map( str::trim ).filter( |part| !part.is_empty() )
{
let number = |s: &str| s.trim().parse::<u32>().ok().filter( |n| *n > 0 );
let range = match part.split_once( '-' )
{
Some( ( start, end ) ) =>
{
let start = if start.trim().is_empty() { 1 } else { number( start )? };
let end = if end.trim().is_empty() { u32::MAX } else { number( end )? };
( start, end )
}
None =>
{
let page = number( part )?;
( page, page )
}
};
if range.0 > range.1 { return None; }
ranges.push( range );
}
( !ranges.is_empty() ).then_some( ranges )
}
pub fn format_pages( ranges: &[ ( u32, u32 ) ] ) -> String
{
ranges.iter()
.map( |&( start, end )| match ( start, end )
{
( s, e ) if s == e => s.to_string(),
( s, u32::MAX ) => format!( "{s}-" ),
( s, e ) => format!( "{s}-{e}" ),
} )
.collect::<Vec<_>>()
.join( ", " )
}
#[ cfg( test ) ]
mod tests
{
use super::*;
#[ test ]
fn page_lists_parse()
{
assert_eq!( parse_pages( "1-3, 5,8-" ), Some( vec![ ( 1, 3 ), ( 5, 5 ), ( 8, u32::MAX ) ] ) );
assert_eq!( parse_pages( " -2 " ), Some( vec![ ( 1, 2 ) ] ) );
}
#[ test ]
fn bad_page_lists_do_not_parse()
{
for text in [ "", "0", "3-1", "a", "1-b", "1--2", " , " ]
{
assert_eq!( parse_pages( text ), None, "{text}" );
}
}
#[ test ]
fn page_lists_format_back()
{
let ranges = vec![ ( 1, 3 ), ( 5, 5 ), ( 8, u32::MAX ) ];
assert_eq!( parse_pages( &format_pages( &ranges ) ), Some( ranges ) );
}
#[ test ]
fn landscape_turns_the_sheet()
{
let options = PrintOptions { orientation: Orientation::Landscape, ..PrintOptions::default() };
assert_eq!( options.paper_size(), ( PAPERS[ 0 ].height, PAPERS[ 0 ].width ) );
}
#[ test ]
fn copies_and_colour_keep_the_layout()
{
let base = PrintOptions::default();
let other = PrintOptions { copies: 3, color: false, sides: Sides::LongEdge, ..base.clone() };
assert!( base.same_layout( &other ) );
assert!( !base.same_layout( &PrintOptions { scale: 0.8, ..base.clone() } ) );
}
}