use std::collections::HashMap;
use super::{ Margins, Orientation, Paper, PrintOptions, Sides, PAPERS };
const MM_PER_POINT: f64 = 25.4 / 72.0;
#[ derive( Clone, Debug, PartialEq ) ]
pub enum PageValue
{
Text( String ),
Number( f64 ),
}
pub fn settings( options: &PrintOptions, printer: Option<&str> ) -> Vec<( &'static str, String )>
{
let mut out = vec![
( "n-copies", options.copies.to_string() ),
( "orientation", orientation( options.orientation ).to_string() ),
( "scale", format!( "{}", ( options.scale * 100.0 ).round() ) ),
( "use-color", options.color.to_string() ),
( "duplex", match options.sides { Sides::One => "simplex", Sides::LongEdge => "horizontal", Sides::ShortEdge => "vertical" }.to_string() ),
( "print-pages", if options.pages.is_empty() { "all" } else { "ranges" }.to_string() ),
];
if !options.pages.is_empty()
{
let ranges = options.pages.iter()
.map( |&( start, end )| if end == u32::MAX { format!( "{}-", start - 1 ) } else if start == end { ( start - 1 ).to_string() } else { format!( "{}-{}", start - 1, end - 1 ) } )
.collect::<Vec<_>>()
.join( "," );
out.push( ( "page-ranges", ranges ) );
}
if let Some( printer ) = printer { out.push( ( "printer", printer.to_string() ) ); }
out
}
pub fn page_setup( options: &PrintOptions ) -> Vec<( &'static str, PageValue )>
{
let paper = options.paper;
let margin = options.margins.points() as f64 * MM_PER_POINT;
let mm = |points: f32| PageValue::Number( points as f64 * MM_PER_POINT );
vec![
( "PPDName", PageValue::Text( paper.name.to_string() ) ),
( "Name", PageValue::Text( gtk_name( &paper ) ) ),
( "DisplayName", PageValue::Text( paper.name.to_string() ) ),
( "Width", mm( paper.width ) ),
( "Height", mm( paper.height ) ),
( "MarginTop", PageValue::Number( margin ) ),
( "MarginBottom", PageValue::Number( margin ) ),
( "MarginLeft", PageValue::Number( margin ) ),
( "MarginRight", PageValue::Number( margin ) ),
( "Orientation", PageValue::Text( orientation( options.orientation ).to_string() ) ),
]
}
pub fn options( settings: &HashMap<String, String>, page: &HashMap<String, PageValue>, base: &PrintOptions ) -> PrintOptions
{
let mut out = base.clone();
let text = |key: &str| settings.get( key ).map( |v| v.trim() );
if let Some( copies ) = text( "n-copies" ).and_then( |v| v.parse::<u32>().ok() ) { out.copies = copies.clamp( 1, PrintOptions::MAX_COPIES ); }
if let Some( scale ) = text( "scale" ).and_then( |v| v.parse::<f32>().ok() ).filter( |s| s.is_finite() )
{
out.scale = ( scale / 100.0 ).clamp( PrintOptions::MIN_SCALE, PrintOptions::MAX_SCALE );
}
if let Some( color ) = text( "use-color" ) { out.color = color != "false"; }
match text( "duplex" )
{
Some( "horizontal" ) => out.sides = Sides::LongEdge,
Some( "vertical" ) => out.sides = Sides::ShortEdge,
Some( "simplex" ) => out.sides = Sides::One,
_ => {}
}
match text( "print-pages" )
{
Some( "ranges" ) => if let Some( ranges ) = text( "page-ranges" ).and_then( ranges ) { out.pages = ranges; },
Some( _ ) => out.pages.clear(),
None => {}
}
let page_text = |key: &str| match page.get( key ) { Some( PageValue::Text( t ) ) => Some( t.as_str() ), _ => None };
let page_mm = |key: &str| match page.get( key ) { Some( PageValue::Number( n ) ) if n.is_finite() => Some( *n ), _ => None };
if let Some( orientation ) = page_text( "Orientation" ).or( text( "orientation" ) )
{
out.orientation = if orientation.contains( "landscape" ) { Orientation::Landscape } else { Orientation::Portrait };
}
let named = page_text( "PPDName" ).and_then( |n| PAPERS.iter().find( |p| p.name.eq_ignore_ascii_case( n ) ) )
.or_else( || page_text( "Name" ).and_then( |n| PAPERS.iter().find( |p| gtk_name( p ) == n || p.media == n ) ) );
let sized = || match ( page_mm( "Width" ), page_mm( "Height" ) )
{
( Some( w ), Some( h ) ) => PAPERS.iter().find( |p| ( p.width as f64 * MM_PER_POINT - w ).abs() < 1.5 && ( p.height as f64 * MM_PER_POINT - h ).abs() < 1.5 ),
_ => None,
};
if let Some( paper ) = named.or_else( sized ) { out.paper = *paper; }
if let Some( top ) = page_mm( "MarginTop" )
{
let points = top / MM_PER_POINT;
out.margins = *Margins::ALL.iter()
.min_by( |a, b| ( a.points() as f64 - points ).abs().total_cmp( &( b.points() as f64 - points ).abs() ) )
.unwrap_or( &Margins::Default );
}
out
}
fn ranges( text: &str ) -> Option<Vec<( u32, u32 )>>
{
let shifted = text.split( ',' )
.map( str::trim )
.filter( |part| !part.is_empty() )
.map( |part|
{
let one = |s: &str| s.trim().parse::<u32>().ok().map( |n| ( n + 1 ).to_string() );
match part.split_once( '-' )
{
Some( ( start, end ) ) if end.trim().is_empty() => Some( format!( "{}-", one( start )? ) ),
Some( ( start, end ) ) => Some( format!( "{}-{}", one( start )?, one( end )? ) ),
None => one( part ),
}
} )
.collect::<Option<Vec<_>>>()?
.join( "," );
super::parse_pages( &shifted )
}
fn orientation( orientation: Orientation ) -> &'static str
{
match orientation
{
Orientation::Portrait => "portrait",
Orientation::Landscape => "landscape",
}
}
fn gtk_name( paper: &Paper ) -> String
{
paper.media.rsplit_once( '_' ).map_or( paper.media, |( name, _ )| name ).to_string()
}
#[ cfg( test ) ]
mod tests
{
use super::*;
fn round_trip( options: &PrintOptions ) -> PrintOptions
{
let settings: HashMap<String, String> = settings( options, Some( "office" ) ).into_iter().map( |( k, v )| ( k.to_string(), v ) ).collect();
let page: HashMap<String, PageValue> = page_setup( options ).into_iter().map( |( k, v )| ( k.to_string(), v ) ).collect();
super::options( &settings, &page, &PrintOptions::default() )
}
#[ test ]
fn options_survive_the_portal()
{
let options = PrintOptions
{
copies: 3,
pages: vec![ ( 1, 3 ), ( 5, 5 ), ( 8, u32::MAX ) ],
paper: PAPERS[ 1 ],
orientation: Orientation::Landscape,
margins: Margins::Wide,
scale: 0.8,
color: false,
sides: Sides::ShortEdge,
backgrounds: false,
};
assert_eq!( round_trip( &options ), options );
}
#[ test ]
fn page_ranges_count_from_zero()
{
let settings = settings( &PrintOptions { pages: vec![ ( 1, 3 ), ( 5, 5 ) ], ..PrintOptions::default() }, None );
assert!( settings.contains( &( "page-ranges", "0-2,4".to_string() ) ) );
}
#[ test ]
fn gtk_names_a4_without_its_size()
{
assert_eq!( gtk_name( &PAPERS[ 0 ] ), "iso_a4" );
}
}