convert to pdf

main
reng 4 months ago
parent 7159fdb110
commit a97a98c74f
  1. 10
      package-lock.json
  2. 1
      package.json
  3. 2
      src-tauri/2
  4. 978
      src-tauri/Cargo.lock
  5. 2
      src-tauri/Cargo.toml
  6. 3
      src-tauri/capabilities/default.json
  7. 93
      src-tauri/src/lib.rs
  8. 57
      src-tauri/src/util.rs
  9. 2
      src/App.jsx

10
package-lock.json generated

@ -11,6 +11,7 @@
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"
@ -1547,6 +1548,15 @@
"@tauri-apps/api": "^2.6.0" "@tauri-apps/api": "^2.6.0"
} }
}, },
"node_modules/@tauri-apps/plugin-fs": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-fs/-/plugin-fs-2.4.1.tgz",
"integrity": "sha512-vJlKZVGF3UAFGoIEVT6Oq5L4HGDCD78WmA4uhzitToqYiBKWAvZR61M6zAyQzHqLs0ADemkE4RSy/5sCmZm6ZQ==",
"license": "MIT OR Apache-2.0",
"dependencies": {
"@tauri-apps/api": "^2.6.0"
}
},
"node_modules/@tauri-apps/plugin-opener": { "node_modules/@tauri-apps/plugin-opener": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.4.0.tgz", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.4.0.tgz",

@ -13,6 +13,7 @@
"@tailwindcss/vite": "^4.1.11", "@tailwindcss/vite": "^4.1.11",
"@tauri-apps/api": "^2", "@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "^2.3.2", "@tauri-apps/plugin-dialog": "^2.3.2",
"@tauri-apps/plugin-fs": "^2.4.1",
"@tauri-apps/plugin-opener": "^2", "@tauri-apps/plugin-opener": "^2",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1" "react-dom": "^18.3.1"

@ -1,5 +1,5 @@
added 1 package, and audited 169 packages in 3s added 1 package, and audited 170 packages in 4s
25 packages are looking for funding 25 packages are looking for funding
run `npm fund` for details run `npm fund` for details

978
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -25,3 +25,5 @@ serde_json = "1"
printers = "2.2.0" printers = "2.2.0"
winprint = "0.2.0" winprint = "0.2.0"
tauri-plugin-dialog = "2" tauri-plugin-dialog = "2"
printpdf = { version="0.8.2", features= ["png"] }
tauri-plugin-fs = "2"

@ -8,6 +8,7 @@
"permissions": [ "permissions": [
"core:default", "core:default",
"opener:default", "opener:default",
"dialog:default" "dialog:default",
"fs:default"
] ]
} }

@ -3,10 +3,13 @@ use std::path::Path;
use winprint::printer::{FilePrinter, ImagePrinter, PdfiumPrinter, PrinterDevice}; use winprint::printer::{FilePrinter, ImagePrinter, PdfiumPrinter, PrinterDevice};
use winprint::ticket::{ use winprint::ticket::{
FeatureOptionPackWithPredefined, PredefinedMediaName, PrintCapabilities, PrintTicket, FeatureOptionPackWithPredefined, PredefinedMediaName, PredefinedPageOutputColor,
PrintTicketBuilder,PredefinedPageOutputColor PrintCapabilities, PrintTicket, PrintTicketBuilder,
}; };
// use util::imageToPdf;
mod util;
#[tauri::command] #[tauri::command]
fn get_all_printers() -> Vec<String> { fn get_all_printers() -> Vec<String> {
let printers = PrinterDevice::all().expect("Failed to get printers"); let printers = PrinterDevice::all().expect("Failed to get printers");
@ -31,6 +34,18 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
} }
}; };
println!("My Printer: {:?}", my_device.name()); println!("My Printer: {:?}", my_device.name());
match PrintCapabilities::fetch_xml(&my_device) {
Ok(xml) => {
println!("Printer Capabilities fetched successfully.");
let string_xml = String::from_utf8_lossy(&xml).to_string();
println!("Printer Capabilities XML: {}", string_xml);
}
Err(e) => {
println!("Failed to fetch capabilities: {:?}", e);
}
}
match PrintCapabilities::fetch(&my_device) { match PrintCapabilities::fetch(&my_device) {
Ok(capabilities) => { Ok(capabilities) => {
let dpi_opts = capabilities.page_resolutions(); let dpi_opts = capabilities.page_resolutions();
@ -66,26 +81,42 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
fn default_ticket() -> PrintTicket { fn default_ticket() -> PrintTicket {
// let mut ticket = PrintTicket::new(); // let mut ticket = PrintTicket::new();
let xml=r#"<?xml version="1.0" encoding="UTF-8"?> let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
<psf:PrintTicket version="1" <psf:PrintTicket version="1"
xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework" xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"
xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords"> xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords">
<psf:Feature name="psk:PageScaling"> <psf:Feature name="psk:PageScaling">
<psf:Option name="psk:FitToPage" /> <psf:Option name="psk:PageScalingFit"/>
</psf:Feature> </psf:Feature>
<psf:Feature name="psk:PageOutputQuality"> <psf:Feature name="psk:PageOutputQuality">
<psf:Option name="psk:High" /> <psf:Option name="psk:High" />
</psf:Feature> </psf:Feature>
<psf:Feature name="psk:PageMediaSize">
<psf:Option name="psk:PageMediaSizeA6"/>
</psf:Feature>
<psf:Feature name="psk:PageOutputColor">
<psf:Option name="psk:Monochrome"/>
</psf:Feature>
</psf:PrintTicket>"#; </psf:PrintTicket>"#;
let ticket = PrintTicket::from_xml(xml); let ticket = PrintTicket::from_xml(xml);
ticket ticket
} }
#[tauri::command] #[tauri::command]
fn print_document(device_name: String, document_path: String, dpi: String, color: String, size: String) { fn print_document(
println!("Printing document: {}, {}, {}, {}", document_path, size, dpi, color); device_name: String,
document_path: String,
dpi: String,
color: String,
size: String,
) {
println!(
"Printing document: {}, {}, {}, {}",
document_path, size, dpi, color
);
let my_device = match get_my_device(device_name.clone()) { let my_device = match get_my_device(device_name.clone()) {
Some(device) => device, Some(device) => device,
None => { None => {
@ -98,7 +129,8 @@ fn print_document(device_name: String, document_path: String, dpi: String, color
let capabilities = PrintCapabilities::fetch(&my_device).unwrap(); let capabilities = PrintCapabilities::fetch(&my_device).unwrap();
// size // size
let parsed_size = size.parse::<PredefinedMediaName>() let parsed_size = size
.parse::<PredefinedMediaName>()
.expect("Invalid media size name"); .expect("Invalid media size name");
let a6_media = capabilities let a6_media = capabilities
.page_media_sizes() .page_media_sizes()
@ -117,8 +149,11 @@ fn print_document(device_name: String, document_path: String, dpi: String, color
.expect("DPI not supported by printer"); .expect("DPI not supported by printer");
// color // color
let parsed_color =capabilities.page_output_colors() let parsed_color = capabilities
.find(|x| x.as_predefined_name() == Some(color.parse::<PredefinedPageOutputColor>().unwrap())) .page_output_colors()
.find(|x| {
x.as_predefined_name() == Some(color.parse::<PredefinedPageOutputColor>().unwrap())
})
.map(|x| x.as_predefined_name().unwrap()) .map(|x| x.as_predefined_name().unwrap())
.expect("Invalid color name"); .expect("Invalid color name");
@ -132,23 +167,40 @@ fn print_document(device_name: String, document_path: String, dpi: String, color
builder.merge(supported_dpi).unwrap(); builder.merge(supported_dpi).unwrap();
builder.merge(supported_color).unwrap(); builder.merge(supported_color).unwrap();
let default_ticket = default_ticket(); // let default_ticket = default_ticket();
if let Err(e) = builder.merge(default_ticket) { // if let Err(e) = builder.merge(default_ticket) {
println!("Failed to merge default ticket: {:?}", e); // println!("Failed to merge default ticket: {:?}", e);
return; // return;
} // }
let ticket = builder.build().unwrap(); let ticket = builder.build().unwrap();
// let xml=ticket.get_xml();
// let xml_string = String::from_utf8_lossy(&xml).to_string();
// println!("PrintTicket XML as String: {}", xml_string);
// return;
let path = Path::new(&document_path); let path = Path::new(&document_path);
let printer = PdfiumPrinter::new(my_device);
if path
.extension()
.map(|ext| ext.eq_ignore_ascii_case("pdf"))
.unwrap_or(false)
{
if path.extension().map(|ext| ext.eq_ignore_ascii_case("pdf")).unwrap_or(false) {
let printer=PdfiumPrinter::new(my_device);
printer.print(path, ticket).unwrap(); printer.print(path, ticket).unwrap();
} else { } else {
let printer=ImagePrinter::new(my_device); match util::imageToPdf(document_path.clone()) {
printer.print(path, ticket).unwrap(); Ok(pdf_path) => {
// Print the converted PDF using the printer
printer.print(Path::new(&pdf_path), ticket).unwrap();
}
Err(e) => {
println!("Error converting image to PDF: {}", e);
}
}
}; };
// let theprinter = PdfiumPrinter::new(my_device); // let theprinter = PdfiumPrinter::new(my_device);
@ -158,6 +210,7 @@ fn print_document(device_name: String, document_path: String, dpi: String, color
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
tauri::Builder::default() tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init()) .plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![

@ -0,0 +1,57 @@
use printpdf::*;
use std::path::Path;
pub fn imageToPdf(path: String) -> Result<String, String> {
let mut doc = PdfDocument::new("image_to_pdf");
let image_bytes = std::fs::read(&path).map_err(|e| e.to_string())?;
let image = RawImage::decode_from_bytes(&image_bytes, &mut Vec::new()).unwrap();
let mut ops = Vec::new();
// In the PDF, an image is an `XObject`, identified by a unique `ImageId`
let image_xobject_id = doc.add_image(&image);
// Calculate image size in points (1 point = 1/72 inch)
// let dpi = 300.0;
// let output_width= 105.0; // A6 width in mm
// let output_height = 148.0; // A6 height in mm
// let mm_to_pt = 1.0 / 25.4; // Conversion factor
// let width_pt = output_width * mm_to_pt * dpi / image.width as f64;
// let height_pt = output_height * mm_to_pt * dpi / image.height as f64;
// println!("Image size in points: {}x{}", width_pt, height_pt);
// Place the image at (0,0) with the calculated size
ops.push(Op::UseXobject {
id: image_xobject_id.clone(),
transform: XObjectTransform {
translate_x: Some(Pt(0.0)),
translate_y: Some(Pt(0.0)),
scale_x: Some(1.0),
scale_y: Some(1.0),
dpi: Some(300.0),
..Default::default()
},
});
let page1 = PdfPage::new(Mm(105.0), Mm(148.0), ops);
let pdf_bytes: Vec<u8> = doc
.with_pages(vec![page1])
.save(&PdfSaveOptions::default(), &mut Vec::new());
let pdf_path = Path::new(&path).with_extension("pdf");
match std::fs::write(&pdf_path, pdf_bytes) {
Ok(_) => {
let abs_path = std::fs::canonicalize(&pdf_path)
.map(|p| p.display().to_string())
.unwrap_or_else(|_| pdf_path.display().to_string());
println!("PDF written successfully to {}", abs_path);
Ok(abs_path)
},
Err(e) => Err(format!("Failed to write PDF: {}", e)),
}
}

@ -69,8 +69,8 @@ function App() {
open({ open({
multiple: false, multiple: false,
filters: [ filters: [
{ name: 'PDF Files', extensions: ['pdf'] },
{ name: 'Image Files', extensions: ['png', 'jpg', 'jpeg'] }, { name: 'Image Files', extensions: ['png', 'jpg', 'jpeg'] },
{ name: 'PDF Files', extensions: ['pdf'] },
] ]
}).then((filePath) => { }).then((filePath) => {
if (filePath) { if (filePath) {

Loading…
Cancel
Save