Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance: Add API to allow print options #1317

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft

Conversation

mildred
Copy link

@mildred mildred commented Jul 13, 2024

Implement global print options with WebKitGtk2 support and reuse MacOS support from #1259

Previous failed attempt for Windows was #1167

This could allow a print API in Tauri: tauri-apps/tauri#4917

The specific goal I have is to generate PDF files from WebViews in Tauri.

I took the approach of specifying options in an array as it allows to only pass some options of interest and not the full list, and it also allows some platform to support some options and not others. this would be easier to have the feature developped and some options might not be relevant everywhere.

@mildred mildred marked this pull request as ready for review July 13, 2024 12:07
@mildred mildred requested a review from a team as a code owner July 13, 2024 12:07
@mildred
Copy link
Author

mildred commented Jul 13, 2024

Currently there is an issue with printing PDF on webkit. It shows lpr: Error - No default destination.

@mildred mildred marked this pull request as draft July 13, 2024 22:09
@mildred
Copy link
Author

mildred commented Jul 13, 2024

It seems there is easy way to print to PDF in WebkitGtk: https://bugs.webkit.org/show_bug.cgi?id=212814

@mildred
Copy link
Author

mildred commented Jul 17, 2024

Perhaps there is a way to make the PDF generation work by using the PDF printer by name, but there is a problem with i18n in that case as the printer name can change...

I'm not sure how to continue there. Still, it's possible to have the printing options without the PDF generation if that can be of use to other people (it seems it would be looking at tauri-apps/tauri#4917)

@nros
Copy link

nros commented Jul 22, 2024

On windows, you could use the WebView2 function PrintToPdf. It seems as if the used webview-com crate provides an interface to this function.

@pewsheen
Copy link
Contributor

Here's a workable POC for the Windows to print to PDF. I'm unfamiliar with win32 API, it would be nice if this one can give you some ideas.

pub fn print(&self) -> Result<()> {
  // self.eval(
  //   "window.print()",
  //   None::<Box<dyn FnOnce(String) + Send + 'static>>,
  // )
  unsafe {
    if let Ok(webview7) = self.webview.cast::<ICoreWebView2_7>() {
      if let Ok(env) = self.env.cast::<ICoreWebView2Environment6>() {
        let printsettings = env.CreatePrintSettings().unwrap();
        let _ = webview7.PrintToPdf(
          PCWSTR::from_raw(HSTRING::from("C:\\Users\\Jason\\Downloads\\webview2.pdf").as_ptr()),
          &printsettings,
          &PrintToPdfCompletedHandler::create(Box::new(move |_hresult, _is_success| {
            dbg!(_hresult, _is_success);
            Ok(())
          })),
        );
      }
    }

    Ok(())
  }
}

@mildred
Copy link
Author

mildred commented Jul 23, 2024

It seems possible to print to PDF using the print to file printer, and it's possible to discover this printer as webkitgtk does it already, but in a private part of its code...

https://github.com/WebKit/WebKit/blob/c289534e80934cfed8a0f32ac13a3cf70beb0f7f/Source/WebKit/UIProcess/API/gtk/WebKitPrintOperation.cpp#L423-L440

Just below you have the code that uses the "print to file printer" and generates a file

Ideally, in wry, we should be able to run identical code and fetch the printer name to feed it to the printer settings. Here is the code that selects the printer from the printer settings in WebkitGtk (and if not found selects the default printer)

https://github.com/WebKit/WebKit/blob/c289534e80934cfed8a0f32ac13a3cf70beb0f7f/Source/WebKit/UIProcess/API/gtk/WebKitPrintOperation.cpp#L836C28-L836C56

Up until now, I was not able to select correctly the file printer, I could trace the function calls with ltrace -e 'gtk_print*+gtk_page*' but the gtk_print_settings_get_printer function returned NULL.

What's tricky is that the gtk_enumerate_printers is not available to the gtk crate. And the gtk crate is unmaintained. However, the gtk4 crate has the function, but that's not the gtk version wry is built against... :(

Also, trying to use extern C bindings for those gtk functions could work but the G_OBJECT_TYPE_NAME call to get the printer backend name is a C macro and is not available to FFI or extern C :( GObject is tricky and I don't see how without the C proprocessor I can manage to get the type name of a GObject pointer... Else, this would have been doable with something like:

use gtk;

pub struct Printer { }
pub struct PrinterBackend { }
struct UserData {
  printer_name: &str
}

extern "C" {
    // pub fn wry_gtk_find_file_printer(settings: &gtk::PrintSettings) -> &str;
    pub fn gtk_enumerate_printers(cb: Fn(&Printer, &UserData) -> bool, userdata: &UserData, destroy: &UserData, wait: bool);
    pub fn gtk_printer_get_backend(printer: &Printer) -> &PrinterBackend;
    pub fn gtk_printer_get_name(printer: &Printer) -> &str;
    fn g_type_name(backend: &PrinterBackend) -> &str;
}

pub fn find_file_printer() -> String {
  let userdata: UserData;
  unsafe {
    gtk_enumerate_printers(|printer, userdata| {
      let backend = gtk_printer_get_backend(printer);
      let type_name = g_type_name(backend);
      if (type_name == "GtkPrintBackendFile") {
        userdata.printer_name = gtk_printer_get_name(printer);
      }
    }, &userdata, null, true);
  }
  return userdata.printer_name;
}

I'll probably continue investigation later...

@mildred
Copy link
Author

mildred commented Jul 23, 2024

As for Win32, I have no means of testing

@mildred
Copy link
Author

mildred commented Jul 23, 2024

Another problem... with this I found that the printers have all an empty name :(

use gtk;
use std::ptr;

pub struct Printer { }
pub struct PrinterBackend { }
struct UserData {
  printer_name: String
}

extern "C" {
    // pub fn wry_gtk_find_file_printer(settings: &gtk::PrintSettings) -> &str;
    pub fn gtk_enumerate_printers(cb: extern "C" fn(&Printer, &UserData) -> bool, userdata: &UserData, destroy: *const i32, wait: bool);
    pub fn gtk_printer_get_backend(printer: &Printer) -> &PrinterBackend;
    pub fn gtk_printer_get_name(printer: &Printer) -> &str;
    fn g_type_name(backend: &PrinterBackend) -> &str;
}

extern "C" fn printer_enumerate(printer: &Printer, _userdata: &UserData) -> bool {
    unsafe {
        // let backend = gtk_printer_get_backend(printer);
        //let type_name = g_type_name(backend);
        //if type_name == "GtkPrintBackendFile" {
        //  userdata.printer_name = gtk_printer_get_name(printer).to_string();
        //  return true; // stop iteration
        //}
        let printer_name = gtk_printer_get_name(printer);
        println!("Printer: {printer_name}");
        return false; // continue iteration
    }
}

pub fn find_file_printer() -> String {
    let userdata: UserData = UserData { printer_name: "".to_string() };
    unsafe {
        let cb = printer_enumerate as extern "C" fn(&Printer, &UserData) -> bool;
        gtk_enumerate_printers(cb, &userdata, ptr::null(), true);
    }
    return userdata.printer_name;
}

@mildred
Copy link
Author

mildred commented Jul 23, 2024

What I'll probably be doing is to set up a HTTP server and open the browser to localhost on that server and let the user use the print dialog himself... But this PR is still interesting to get a print API rolling...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants