use std::process::Command;
use std::{sync::mpsc, io::Write};
use std::thread;
use std::fs::{File};
use std::env;
use std::path::{Path, PathBuf};
use crate::declare::{PrintOptions, PrintHtmlOptions, PrintPdfUrlOptions};
use crate::{ fsys::remove_file};
fn create_file(path: String, bin: &[u8]) -> std::io::Result<()> {
let mut f = File::create(format!("{}sm.exe", path))?;
f.write_all(bin)?;
f.sync_all()?;
Ok(())
}
pub fn init_windows() {
let sm = include_bytes!("bin/sm");
let dir: std::path::PathBuf = env::temp_dir();
let result: Result<(), std::io::Error> = create_file(dir.display().to_string(),sm);
if result.is_err() {
panic!("Gagal")
}
}
pub fn get_printers() -> String {
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
let output = Command::new("powershell")
.args(["-Command", "[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Printer | Select-Object Name, DriverName, JobCount, PrintProcessor, PortName, ShareName, ComputerName, PrinterStatus, Shared, Type, Priority | ConvertTo-Json"])
.output().unwrap();
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
sender.send(output_string).unwrap();
});
let result: String = receiver.recv().unwrap();
return result;
}
pub fn get_printers_by_name(printername: String) -> String {
let (sender, receiver) = mpsc::channel();
thread::spawn(move || {
let output = Command::new("powershell")
.args(["-Command", &format!("[Console]::OutputEncoding = [System.Text.Encoding]::UTF8; Get-Printer -Name '{}' | Select-Object Name, DriverName, JobCount, PrintProcessor, PortName, ShareName, ComputerName, PrinterStatus, Shared, Type, Priority | ConvertTo-Json", printername)])
.output().unwrap();
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
sender.send(output_string).unwrap();
});
let result: String = receiver.recv().unwrap();
return result;
}
pub fn print_pdf (options: PrintOptions) -> String {
println!("options id {}", options.id);
println!("options print_settings {:?}", options.print_settings);
let dir: std::path::PathBuf = env::temp_dir();
let printer = options.printer;
let print_settings = options.print_settings;
let shell_command = if printer.is_empty() || printer == "default" {
format!("{}sm.exe -print-to-default -silent \"{}\"", dir.display(), options.path)
} else {
if print_settings.is_empty() {
format!("{}sm.exe -print-to \"{}\" -silent \"{}\"", dir.display(), printer, options.path)
} else {
format!("{}sm.exe -print-to \"{}\" -print-settings \"{}\" -silent \"{}\"", dir.display(), printer, print_settings, options.path)
}
};
let (sender, receiver) = mpsc::channel();
println!("{}", shell_command);
thread::spawn(move || {
let output = Command::new("powershell").args([shell_command]).output().unwrap();
sender.send(String::from_utf8(output.stdout).unwrap()).unwrap();
});
let result = receiver.recv().unwrap();
if options.remove_after_print == Some(true) {
let _ = remove_file(&options.path);
}
return result;
}
pub fn print_html(options: PrintHtmlOptions) -> String {
match print_html_internal(options) {
Ok(result) => result,
Err(e) => {
eprintln!("HTML 打印失败: {}", e);
format!("打印失败: {}", e)
}
}
}
fn generate_temp_file_path(extension: &str) -> Result<PathBuf, String> {
let temp_dir = env::temp_dir();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|e| format!("获取时间戳失败: {}", e))?
.as_nanos();
let filename = format!("tauri_printer_{}_{}.{}", std::process::id(), timestamp, extension);
Ok(temp_dir.join(filename))
}
fn print_html_internal(options: PrintHtmlOptions) -> Result<String, String> {
if options.html.trim().is_empty() {
return Err("HTML 内容不能为空".to_string());
}
check_wkhtmltopdf_availability()?;
let html_path = generate_temp_file_path("html")?;
let pdf_path = generate_temp_file_path("pdf")?;
println!("html_path: {:?}, pdf_path: {:?}", html_path, pdf_path);
std::fs::write(&html_path, &options.html)
.map_err(|e| format!("写入 HTML 内容失败: {}", e))?;
let args = build_wkhtmltopdf_args(&options, &html_path, &pdf_path)?;
println!("wkhtmltopdf args: {:?}", args);
let conversion_result = execute_wkhtmltopdf(&args);
if let Err(e) = conversion_result {
let _ = remove_file(&html_path.to_string_lossy());
return Err(e);
}
if !pdf_path.exists() {
let _ = remove_file(&html_path.to_string_lossy());
return Err("PDF 文件生成失败".to_string());
}
println!("PDF 文件生成成功: {:?}", pdf_path);
let print_options = PrintOptions {
id: options.id,
path: pdf_path.to_string_lossy().to_string(),
printer: options.printer,
print_settings: options.print_settings,
remove_after_print: options.remove_after_print,
};
let result = print_pdf(print_options);
let _ = remove_file(&html_path.to_string_lossy());
Ok(result)
}
fn check_wkhtmltopdf_availability() -> Result<(), String> {
Command::new("wkhtmltopdf")
.arg("--version")
.output()
.map_err(|_| "wkhtmltopdf 未安装或不在 PATH 中。请先安装 wkhtmltopdf。".to_string())?;
Ok(())
}
fn build_wkhtmltopdf_args(
options: &PrintHtmlOptions,
html_path: &Path,
pdf_path: &Path,
) -> Result<Vec<String>, String> {
let mut args = vec![
"--encoding".to_string(),
"UTF-8".to_string(),
"--enable-local-file-access".to_string(),
"--disable-smart-shrinking".to_string(), "--print-media-type".to_string(),
"--no-pdf-compression".to_string(), "--load-error-handling".to_string(),
"ignore".to_string(), "--load-media-error-handling".to_string(),
"ignore".to_string(), ];
let default_margin = "10mm";
args.extend([
"--margin-top".to_string(),
default_margin.to_string(),
"--margin-right".to_string(),
default_margin.to_string(),
"--margin-bottom".to_string(),
default_margin.to_string(),
"--margin-left".to_string(),
default_margin.to_string(),
]);
if let (Some(width), Some(height)) = (options.page_width, options.page_height) {
args.extend([
"--page-width".to_string(),
format!("{}mm", width),
"--page-height".to_string(),
format!("{}mm", height)
]);
} else if let Some(ref page_size) = options.page_size {
args.extend(["--page-size".to_string(), page_size.clone()]);
} else {
args.extend(["--page-size".to_string(), "A4".to_string()]);
}
if let Some(ref margin) = options.margin {
let unit = margin.unit.as_deref().unwrap_or("mm");
if let Some(top) = margin.top {
args.extend(["--margin-top".to_string(), format!("{}{}", top, unit)]);
}
if let Some(right) = margin.right {
args.extend(["--margin-right".to_string(), format!("{}{}", right, unit)]);
}
if let Some(bottom) = margin.bottom {
args.extend(["--margin-bottom".to_string(), format!("{}{}", bottom, unit)]);
}
if let Some(left) = margin.left {
args.extend(["--margin-left".to_string(), format!("{}{}", left, unit)]);
}
}
args.push(html_path.to_string_lossy().to_string());
args.push(pdf_path.to_string_lossy().to_string());
Ok(args)
}
fn execute_wkhtmltopdf(args: &[String]) -> Result<(), String> {
let output = Command::new("wkhtmltopdf")
.args(args)
.output()
.map_err(|e| format!("执行 wkhtmltopdf 失败: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
return Err(format!(
"wkhtmltopdf 转换失败 (退出码: {})\n标准错误: {}\n标准输出: {}",
output.status.code().unwrap_or(-1),
stderr,
stdout
));
}
Ok(())
}
pub fn get_jobs(printername: String) -> String {
let output = Command::new("powershell").args([format!("Get-PrintJob -PrinterName \"{}\" | Select-Object DocumentName,Id,TotalPages,Position,Size,SubmmitedTime,UserName,PagesPrinted,JobTime,ComputerName,Datatype,PrinterName,Priority,SubmittedTime,JobStatus | ConvertTo-Json", printername)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn get_jobs_by_id(printername: String, jobid: String) -> String {
let output = Command::new("powershell").args([format!("Get-PrintJob -PrinterName \"{}\" -ID \"{}\" | Select-Object DocumentName,Id,TotalPages,Position,Size,SubmmitedTime,UserName,PagesPrinted,JobTime,ComputerName,Datatype,PrinterName,Priority,SubmittedTime,JobStatus | ConvertTo-Json", printername, jobid)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn resume_job(printername: String, jobid: String) -> String {
let output = Command::new("powershell").args([format!("Resume-PrintJob -PrinterName \"{}\" -ID \"{}\" ", printername, jobid)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn restart_job(printername: String, jobid: String) -> String {
let output = Command::new("powershell").args([format!("Restart-PrintJob -PrinterName \"{}\" -ID \"{}\" ", printername, jobid)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn pause_job(printername: String, jobid: String) -> String {
let output = Command::new("powershell").args([format!("Suspend-PrintJob -PrinterName \"{}\" -ID \"{}\" ", printername, jobid)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn remove_job(printername: String, jobid: String) -> String {
let output = Command::new("powershell").args([format!("Remove-PrintJob -PrinterName \"{}\" -ID \"{}\" ", printername, jobid)]).output().unwrap();
return String::from_utf8(output.stdout).unwrap();
}
pub fn print_pdf_from_url(options: PrintPdfUrlOptions) -> String {
match print_pdf_from_url_internal(options) {
Ok(result) => result,
Err(e) => {
eprintln!("PDF URL打印失败: {}", e);
format!("打印失败: {}", e)
}
}
}
fn print_pdf_from_url_internal(options: PrintPdfUrlOptions) -> Result<String, String> {
println!("打印配置pdf from url:{}", options.url);
if options.url.trim().is_empty() {
return Err("PDF URL不能为空".to_string());
}
if !options.url.to_lowercase().ends_with(".pdf") && !options.url.contains("pdf") {
println!("警告: URL可能不是PDF文件: {}", options.url);
}
let pdf_path = generate_temp_file_path("pdf")
.map_err(|e| format!("生成临时文件路径失败: {}", e))?;
println!("开始下载PDF: {} -> {:?}", options.url, pdf_path);
download_pdf_from_url(&options.url, &pdf_path, options.timeout_seconds.unwrap_or(30))?;
if !pdf_path.exists() {
return Err("PDF文件下载失败".to_string());
}
println!("PDF下载成功: {:?}", pdf_path);
let print_options = PrintOptions {
id: options.id,
path: pdf_path.to_string_lossy().to_string(),
printer: options.printer,
print_settings: options.print_settings,
remove_after_print: options.remove_after_print,
};
let result = print_pdf(print_options);
Ok(result)
}
fn download_pdf_from_url(url: &str, output_path: &Path, timeout_seconds: u64) -> Result<(), String> {
let powershell_script = format!(
"try {{ \
$ProgressPreference = 'SilentlyContinue'; \
Invoke-WebRequest -Uri '{}' -OutFile '{}' -TimeoutSec {} -UseBasicParsing; \
Write-Output 'Download completed successfully' \
}} catch {{ \
Write-Error $_.Exception.Message \
}}",
url,
output_path.to_string_lossy(),
timeout_seconds
);
println!("执行下载命令: {}", powershell_script);
let output = Command::new("powershell")
.args(["-Command", &powershell_script])
.output()
.map_err(|e| format!("执行PowerShell命令失败: {}", e))?;
let stdout = String::from_utf8(output.stdout).unwrap_or_default();
let stderr = String::from_utf8(output.stderr).unwrap_or_default();
println!("下载输出: {}", stdout);
if !stderr.is_empty() {
println!("下载错误: {}", stderr);
}
if !output.status.success() {
return Err(format!("下载失败: {}", stderr));
}
if !stdout.contains("Download completed successfully") && !output_path.exists() {
return Err("下载未成功完成".to_string());
}
Ok(())
}