diff --git a/Cargo.toml b/Cargo.toml index 1a74c1ce5..6f93ae19c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ exclude = [ "/.changes", "/.github", "/audits", "/wry-logo.svg" ] [package.metadata.docs.rs] no-default-features = true -features = [ "drag-drop", "protocol", "os-webview" ] +features = [ "drag-drop", "protocol", "os-webview", "serde" ] targets = [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", @@ -26,7 +26,7 @@ rustdoc-args = [ "--cfg", "docsrs" ] [features] default = [ "drag-drop", "objc-exception", "protocol", "os-webview" ] -serde = [ "dpi/serde" ] +serde = [ "dep:serde", "dpi/serde" ] objc-exception = [ "objc/exception" ] drag-drop = [ ] protocol = [ ] @@ -53,6 +53,7 @@ thiserror = "1.0" http = "1.1" raw-window-handle = { version = "0.6", features = [ "std" ] } dpi = "0.1" +serde = { version = "1", features = ["serde_derive"], optional = true } [target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies] javascriptcore-rs = { version = "=1.1.2", features = [ "v2_28" ], optional = true } diff --git a/src/error.rs b/src/error.rs index 075a893a4..1a8e5ece7 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,8 @@ /// Convenient type alias of Result type for wry. pub type Result = std::result::Result; +use crate::PrintOption; + /// Errors returned by wry. #[non_exhaustive] #[derive(thiserror::Error, Debug)] @@ -61,4 +63,6 @@ pub enum Error { CustomProtocolTaskInvalid, #[error("Failed to register URL scheme: {0}, could be due to invalid URL scheme or the scheme is already registered.")] UrlSchemeRegisterError(String), + #[error("Invalid print option {0}")] + PrintOptionError(PrintOption), } diff --git a/src/lib.rs b/src/lib.rs index 88eb0a389..788920097 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1285,6 +1285,39 @@ impl<'a> WebViewBuilderExtUnix<'a> for WebViewBuilder<'a> { } } +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// Print margins in millimeters +#[derive(Debug, Default, Copy, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct PrintMargin { + pub top: f32, + pub right: f32, + pub bottom: f32, + pub left: f32, +} + +/// The print options +/// +/// When printing, multiple options can be passed to the print function causing it to alter the +/// printing behavior. If a backend does not support a print option, it can return the option as an +/// error using: `return Err(Error::PrintOptionError(opt.clone()))` +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum PrintOption { + Silent(bool), + Margins(PrintMargin) +} + +impl std::fmt::Display for PrintOption { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +impl std::error::Error for PrintOption {} + /// The fundamental type to present a [`WebView`]. /// /// [`WebViewBuilder`] / [`WebView`] are the basic building blocks to construct WebView contents and @@ -1372,7 +1405,11 @@ impl WebView { /// Launch print modal for the webview content. pub fn print(&self) -> Result<()> { - self.webview.print() + self.webview.print_with_options(&[]) + } + + pub fn print_with_options(&self, options: &[PrintOption]) -> Result<()> { + self.webview.print_with_options(options) } /// Open the web inspector which is usually called dev tool. diff --git a/src/webkitgtk/mod.rs b/src/webkitgtk/mod.rs index 5cb548b28..5b11f73e9 100644 --- a/src/webkitgtk/mod.rs +++ b/src/webkitgtk/mod.rs @@ -42,7 +42,7 @@ pub use web_context::WebContextImpl; use crate::{ proxy::ProxyConfig, web_context::WebContext, Error, PageLoadEvent, Rect, Result, - WebViewAttributes, RGBA, + WebViewAttributes, RGBA, PrintOption }; use self::web_context::WebContextExt; @@ -558,9 +558,33 @@ impl InnerWebView { is_inspector_open } - pub fn print(&self) -> Result<()> { - let print = webkit2gtk::PrintOperation::new(&self.webview); - print.run_dialog(None::<>k::Window>); + pub fn print_with_options(&self, options: &[PrintOption]) -> Result<()> { + let page_setup = gtk::PageSetup::new(); + let mut silent: bool = false; + + for opt in options.iter() { + match opt { + PrintOption::Silent(s) => { + silent = *s + }, + PrintOption::Margins(m) => { + page_setup.set_left_margin(f64::from(m.left), gtk::Unit::Mm); + page_setup.set_right_margin(f64::from(m.right), gtk::Unit::Mm); + page_setup.set_top_margin(f64::from(m.top), gtk::Unit::Mm); + page_setup.set_bottom_margin(f64::from(m.bottom), gtk::Unit::Mm); + } + } + } + + let mut print = webkit2gtk::PrintOperation::builder(); + print = print.web_view(&self.webview); + print = print.page_setup(&page_setup); + + if silent { + print.build().print(); + } else { + print.build().run_dialog(None::<>k::Window>); + } Ok(()) } diff --git a/src/webview2/mod.rs b/src/webview2/mod.rs index 3f44f00e9..2d406a4cf 100644 --- a/src/webview2/mod.rs +++ b/src/webview2/mod.rs @@ -1317,7 +1317,11 @@ impl InnerWebView { Ok(()) } - pub fn print(&self) -> Result<()> { + pub fn print_with_options(&self, options: &[PrintOption]) -> Result<()> { + for opt in options.iter() { + return Err(Error::PrintOptionError(opt.clone())) + } + self.eval( "window.print()", None::>, diff --git a/src/wkwebview/mod.rs b/src/wkwebview/mod.rs index 01a0d3477..0e0574658 100644 --- a/src/wkwebview/mod.rs +++ b/src/wkwebview/mod.rs @@ -64,6 +64,7 @@ use crate::{ navigation::{add_navigation_mathods, drop_navigation_methods, set_navigation_methods}, }, Error, PageLoadEvent, Rect, RequestAsyncResponder, Result, WebContext, WebViewAttributes, RGBA, + PrintOption, PrintMargin }; use http::{ @@ -84,17 +85,8 @@ const NS_JSON_WRITING_FRAGMENTS_ALLOWED: u64 = 4; static COUNTER: Counter = Counter::new(); static WEBVIEW_IDS: Lazy>> = Lazy::new(Default::default); -#[derive(Debug, Default, Copy, Clone)] -pub struct PrintMargin { - pub top: f32, - pub right: f32, - pub bottom: f32, - pub left: f32, -} - -#[derive(Debug, Default, Clone)] -pub struct PrintOptions { - pub margins: PrintMargin, +fn mm_to_printer_points(mm: f32) -> f32 { + mm * 2.8452755906 } pub(crate) struct InnerWebView { @@ -1173,11 +1165,20 @@ r#"Object.defineProperty(window, 'ipc', { } } - pub fn print(&self) -> crate::Result<()> { - self.print_with_options(&PrintOptions::default()) - } + pub fn print_with_options(&self, options: &[WebViewPrintOption]) -> Result<()> { + let mut margins: PrintMargin = PrintMargin::default(); + + for opt in options.iter() { + match opt { + PrintOption::Margins(m) => { + margins = m; + }, + _ => { + return Err(Error::PrintOptionError(opt.clone())) + } + } + } - pub fn print_with_options(&self, options: &PrintOptions) -> crate::Result<()> { // Safety: objc runtime calls are unsafe #[cfg(target_os = "macos")] unsafe { @@ -1189,10 +1190,10 @@ r#"Object.defineProperty(window, 'ipc', { // Create a shared print info let print_info: id = msg_send![class!(NSPrintInfo), sharedPrintInfo]; let print_info: id = msg_send![print_info, init]; - let () = msg_send![print_info, setTopMargin:CGFloat::from(options.margins.top)]; - let () = msg_send![print_info, setRightMargin:CGFloat::from(options.margins.right)]; - let () = msg_send![print_info, setBottomMargin:CGFloat::from(options.margins.bottom)]; - let () = msg_send![print_info, setLeftMargin:CGFloat::from(options.margins.left)]; + let () = msg_send![print_info, setTopMargin:CGFloat::from(mm_to_printer_points(margins.top))]; + let () = msg_send![print_info, setRightMargin:CGFloat::from(mm_to_printer_points(margins.right))]; + let () = msg_send![print_info, setBottomMargin:CGFloat::from(mm_to_printer_points(margins.bottom))]; + let () = msg_send![print_info, setLeftMargin:CGFloat::from(mm_to_printer_points(margins.left))]; // Create new print operation from the webview content let print_operation: id = msg_send![self.webview, printOperationWithPrintInfo: print_info]; // Allow the modal to detach from the current thread and be non-blocker