main
reng 4 months ago
parent 7abd0c2afd
commit db8407c1c3
  1. 1
      src-tauri/Cargo.lock
  2. 1
      src-tauri/Cargo.toml
  3. 64
      src-tauri/src/lib.rs
  4. 156
      src-tauri/src/util.rs
  5. 53
      src/App.jsx
  6. 14
      src/params.js

@ -3731,6 +3731,7 @@ dependencies = [
"image", "image",
"printers", "printers",
"printpdf", "printpdf",
"quick-xml 0.38.0",
"serde", "serde",
"serde_json", "serde_json",
"tauri", "tauri",

@ -29,3 +29,4 @@ printpdf = { version="0.8.2", features= ["png"] }
tauri-plugin-fs = "2" tauri-plugin-fs = "2"
image = "0.25.6" image = "0.25.6"
flate2 = "1.1.2" flate2 = "1.1.2"
quick-xml = "0.38.0"

@ -7,8 +7,7 @@ use winprint::ticket::{
PrintCapabilities, PrintTicket, PrintTicketBuilder, PrintCapabilities, PrintTicket, PrintTicketBuilder,
}; };
// use util::imageToPdf; mod util;
// mod util;
mod raw_convert; mod raw_convert;
#[tauri::command] #[tauri::command]
@ -57,12 +56,19 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
} }
}; };
println!("My Printer: {:?}", my_device.name()); println!("My Printer: {:?}", my_device.name());
let mut map = HashMap::new();
match PrintCapabilities::fetch_xml(&my_device) { match PrintCapabilities::fetch_xml(&my_device) {
Ok(xml) => { Ok(xml) => {
println!("Printer Capabilities fetched successfully."); println!("Printer Capabilities fetched successfully.");
let string_xml = String::from_utf8_lossy(&xml).to_string(); let string_xml = String::from_utf8_lossy(&xml).to_string();
println!("Printer Capabilities XML: {}", string_xml); // println!("Printer Capabilities XML: {}", string_xml);
let quality_opts = util::get_output_quality_options(&string_xml);
println!("DPI Options: {:?}", quality_opts);
map.insert("quality_options".to_string(), quality_opts);
} }
Err(e) => { Err(e) => {
println!("Failed to fetch capabilities: {:?}", e); println!("Failed to fetch capabilities: {:?}", e);
@ -89,7 +95,7 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
println!("Color Options: {:?}", color_opts_value); println!("Color Options: {:?}", color_opts_value);
println!("Size Options: {:?}", size_opts_value); println!("Size Options: {:?}", size_opts_value);
let mut map = HashMap::new();
map.insert("dpi_options".to_string(), dpi_opts_value); map.insert("dpi_options".to_string(), dpi_opts_value);
map.insert("color_options".to_string(), color_opts_value); map.insert("color_options".to_string(), color_opts_value);
map.insert("size_options".to_string(), size_opts_value); map.insert("size_options".to_string(), size_opts_value);
@ -101,28 +107,22 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
} }
} }
} }
fn default_ticket() -> PrintTicket { fn default_ticket(quality: Option<String>) -> PrintTicket {
// let mut ticket = PrintTicket::new(); // let mut ticket = PrintTicket::new();
let _quality = quality.unwrap_or_else(|| "psk:Normal".to_string());
let xml = r#"<?xml version="1.0" encoding="UTF-8"?> let xml =format!(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:PageScalingFit"/> <psf:Option name="ns0001:None" />
</psf:Feature> </psf:Feature>
<psf:Feature name="psk:PageOutputQuality"> <psf:Feature name="psk:PageOutputQuality">
<psf:Option name="psk:High" /> <psf:Option name="{_quality}" />
</psf:Feature>
<psf:Feature name="psk:PageMediaSize">
<psf:Option name="psk:PageMediaSizeA6"/>
</psf:Feature> </psf:Feature>
<psf:Feature name="psk:PageOutputColor"> </psf:PrintTicket>"#);
<psf:Option name="psk:Monochrome"/>
</psf:Feature>
</psf:PrintTicket>"#;
let ticket = PrintTicket::from_xml(xml); let ticket = PrintTicket::from_xml(xml);
ticket ticket
} }
@ -134,10 +134,11 @@ fn print_document(
dpi: String, dpi: String,
color: String, color: String,
size: String, size: String,
quality: Option<String>,
) { ) {
println!( println!(
"Printing document: {}, {}, {}, {}", "Printing document: {}, {}, {}, {}, {}",
document_path, size, dpi, color document_path, size, dpi, color, quality.clone().unwrap_or_else(|| "none".to_string())
); );
let path = Path::new(&document_path); let path = Path::new(&document_path);
@ -154,6 +155,7 @@ fn print_document(
dpi, dpi,
color, color,
size, size,
quality,
); );
} }
Err(e) => { Err(e) => {
@ -211,22 +213,24 @@ fn print_document(
.expect("Color not supported by printer"); .expect("Color not supported by printer");
let mut builder = PrintTicketBuilder::new(&my_device).unwrap(); let mut builder = PrintTicketBuilder::new(&my_device).unwrap();
let default_ticket = default_ticket(quality);
if let Err(e) = builder.merge(default_ticket) {
println!("Failed to merge default ticket: {:?}", e);
}
builder.merge(a6_media).unwrap(); builder.merge(a6_media).unwrap();
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();
// if let Err(e) = builder.merge(default_ticket) {
// println!("Failed to merge default ticket: {:?}", e);
// return;
// }
let ticket = builder.build().unwrap(); let ticket = builder.build().unwrap();
// let xml=ticket.get_xml(); let xml=ticket.get_xml();
// let xml_string = String::from_utf8_lossy(&xml).to_string(); let xml_string = String::from_utf8_lossy(&xml).to_string();
// println!("PrintTicket XML as String: {}", xml_string); println!("PrintTicket XML as String: {}", xml_string);
// return; return;
let path = Path::new(&document_path); let path = Path::new(&document_path);
let printer = PdfiumPrinter::new(my_device); let printer = PdfiumPrinter::new(my_device);

@ -1,70 +1,114 @@
use printpdf::*; // use printpdf::*;
use std::path::Path; use std::path::Path;
use quick_xml::Reader;
use quick_xml::events::{Event, BytesStart};
use std::collections::HashSet;
pub fn imageToPdf(path: String) -> Result<String, String> { // pub fn imageToPdf(path: String) -> Result<String, String> {
let mut doc = PdfDocument::new("image_to_pdf"); // let mut doc = PdfDocument::new("image_to_pdf");
let image_bytes = std::fs::read(&path).map_err(|e| e.to_string())?; // 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 image = RawImage::decode_from_bytes(&image_bytes, &mut Vec::new()).unwrap();
let mut ops = Vec::new(); // let mut ops = Vec::new();
// In the PDF, an image is an `XObject`, identified by a unique `ImageId` // // In the PDF, an image is an `XObject`, identified by a unique `ImageId`
let image_xobject_id = doc.add_image(&image); // let image_xobject_id = doc.add_image(&image);
// Calculate image size in points (1 point = 1/72 inch) // // Calculate image size in points (1 point = 1/72 inch)
let dpi = 300.0; // let dpi = 300.0;
let output_width= 105.0; // A6 width in mm // let output_width= 105.0; // A6 width in mm
let output_height = 148.0; // A6 height in mm // let output_height = 148.0; // A6 height in mm
let mm_to_pt = dpi / 25.4; // Conversion factor // let mm_to_pt = dpi / 25.4; // Conversion factor
let width_pt = output_width * mm_to_pt; // let width_pt = output_width * mm_to_pt;
let height_pt = output_height * mm_to_pt; // let height_pt = output_height * mm_to_pt;
println!("Image size in pixels: {}x{}", image.width, image.height); // println!("Image size in pixels: {}x{}", image.width, image.height);
println!("Dest Image size in points: {}x{}", width_pt, height_pt); // println!("Dest Image size in points: {}x{}", width_pt, height_pt);
// Place the image at (0,0) with the calculated size // // Place the image at (0,0) with the calculated size
ops.push(Op::UseXobject { // ops.push(Op::UseXobject {
id: image_xobject_id.clone(), // id: image_xobject_id.clone(),
transform: XObjectTransform { // transform: XObjectTransform {
translate_x: Some(Pt(0.0)), // translate_x: Some(Pt(0.0)),
translate_y: Some(Pt(0.0)), // translate_y: Some(Pt(0.0)),
dpi: Some(dpi), // dpi: Some(dpi),
..Default::default() // ..Default::default()
}, // },
}); // });
let save_options= PdfSaveOptions { // let save_options= PdfSaveOptions {
optimize: false, // optimize: false,
image_optimization: Some(ImageOptimizationOptions { // image_optimization: Some(ImageOptimizationOptions {
quality: Some(1.0), // quality: Some(1.0),
auto_optimize: Some(false), // auto_optimize: Some(false),
format: Some(ImageCompression::None), // format: Some(ImageCompression::None),
..ImageOptimizationOptions::default() // ..ImageOptimizationOptions::default()
}), // }),
..Default::default() // ..Default::default()
}; // };
let page1 = PdfPage::new(Mm(output_width), Mm(output_height), ops); // let page1 = PdfPage::new(Mm(output_width), Mm(output_height), ops);
let pdf_bytes: Vec<u8> = doc // let pdf_bytes: Vec<u8> = doc
.with_pages(vec![page1]) // .with_pages(vec![page1])
.save(&save_options, &mut Vec::new()); // .save(&save_options, &mut Vec::new());
let pdf_path = Path::new(&path).with_extension("pdf"); // let pdf_path = Path::new(&path).with_extension("pdf");
match std::fs::write(&pdf_path, pdf_bytes) { // match std::fs::write(&pdf_path, pdf_bytes) {
Ok(_) => { // Ok(_) => {
let abs_path = std::fs::canonicalize(&pdf_path) // let abs_path = std::fs::canonicalize(&pdf_path)
.map(|p| p.display().to_string()) // .map(|p| p.display().to_string())
.unwrap_or_else(|_| pdf_path.display().to_string()); // .unwrap_or_else(|_| pdf_path.display().to_string());
println!("PDF written successfully to {}", abs_path); // println!("PDF written successfully to {}", abs_path);
Ok(abs_path) // Ok(abs_path)
}, // },
Err(e) => Err(format!("Failed to write PDF: {}", e)), // Err(e) => Err(format!("Failed to write PDF: {}", e)),
// }
// }
pub fn get_output_quality_options(xml: &str) -> Vec<String> {
let mut reader = Reader::from_str(xml);
// reader.trim_text(true);
// let mut buf = Vec::new();
let mut inside_target_feature = false;
let mut inside_option = false;
let mut current_option_name = String::new();
let mut results = HashSet::new();
loop {
match reader.read_event() {
Ok(Event::Start(ref e)) => {
if e.name().as_ref() == b"psf:Feature" {
if let Some(attr) = e.attributes().filter_map(Result::ok).find(|a| a.key.as_ref() == b"name") {
if attr.unescape_value().unwrap() == "psk:PageOutputQuality" {
inside_target_feature = true;
}
}
} else if inside_target_feature && e.name().as_ref() == b"psf:Option" {
if let Some(attr) = e.attributes().filter_map(Result::ok).find(|a| a.key.as_ref() == b"name") {
results.insert(attr.unescape_value().unwrap().to_string());
}
}
}
Ok(Event::End(ref e)) if e.name().as_ref() == b"psf:Feature" => {
inside_target_feature = false;
}
Ok(Event::Eof) => break,
_ => {}
}
// buf.clear();
} }
}
let mut vec: Vec<String> = results.into_iter().collect();
vec.sort();
vec
}

@ -2,6 +2,15 @@ import { invoke } from "@tauri-apps/api/core";
import { open } from '@tauri-apps/plugin-dialog'; import { open } from '@tauri-apps/plugin-dialog';
import "./App.css"; import "./App.css";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { AvailableQualities } from "./params";
const OptionTypes=[
'dpi_options',
'color_options',
'size_options',
'quality_options',
]
function App() { function App() {
@ -73,6 +82,7 @@ function App() {
dpi: document.getElementById("dpi_options").value, dpi: document.getElementById("dpi_options").value,
color: document.getElementById("color_options").value, color: document.getElementById("color_options").value,
size: document.getElementById("size_options").value, size: document.getElementById("size_options").value,
quality: document.getElementById("quality_options").value,
}) })
.then(() => { .then(() => {
console.log("Print job sent successfully."); console.log("Print job sent successfully.");
@ -101,38 +111,22 @@ function App() {
.then((capabilities) => { .then((capabilities) => {
console.log(`Capabilities for ${printer}:`, capabilities); console.log(`Capabilities for ${printer}:`, capabilities);
for(const optionType of OptionTypes) {
if(capabilities.color_options){ const selectElement = document.getElementById(optionType);
const colorOptionsSelect = document.getElementById("color_options"); if(selectElement) {
colorOptionsSelect.innerHTML = ""; selectElement.innerHTML = ""; // Clear existing options
for(const colorOption of capabilities.color_options) {
const option = document.createElement("option");
option.value = colorOption;
option.textContent = colorOption;
colorOptionsSelect.appendChild(option);
} }
} if(!capabilities[optionType]) continue;
if(capabilities.dpi_options){ for(const option of capabilities[optionType]) {
const dpiOptionsSelect = document.getElementById("dpi_options"); const optionElement = document.createElement("option");
dpiOptionsSelect.innerHTML = ""; optionElement.value = option;
for(const dpiOption of capabilities.dpi_options) { optionElement.textContent = option;
const option = document.createElement("option"); if(selectElement) {
option.value = dpiOption; selectElement.appendChild(optionElement);
option.textContent = dpiOption; }
dpiOptionsSelect.appendChild(option);
} }
}
if(capabilities.size_options){
const sizeOptionsSelect = document.getElementById("size_options");
sizeOptionsSelect.innerHTML = "";
for(const sizeOption of capabilities.size_options) {
const option = document.createElement("option");
option.value = sizeOption;
option.textContent = sizeOption;
sizeOptionsSelect.appendChild(option);
}
}
}
}) })
@ -155,6 +149,7 @@ function App() {
<select id="size_options"></select> <select id="size_options"></select>
<select id="dpi_options"></select> <select id="dpi_options"></select>
<select id="color_options"></select> <select id="color_options"></select>
<select id="quality_options"></select>
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<label id="document_path_label" className="flex-1 break-all">{selectedFile ? `${selectedFile}` : "Select a document to print:"}</label> <label id="document_path_label" className="flex-1 break-all">{selectedFile ? `${selectedFile}` : "Select a document to print:"}</label>
<button onClick={openFile}>open</button> <button onClick={openFile}>open</button>

@ -0,0 +1,14 @@
export const AvailableQualities=[
{ "name": "ns0000:Draft", "displayname": "草稿" },
{ "name": "ns0000:DraftVivid", "displayname": "草稿鮮豔" },
{ "name": "ns0000:Standard", "displayname": "標準" },
{ "name": "ns0000:StandardVivid", "displayname": "標準鮮豔" },
{ "name": "ns0000:HighQuality", "displayname": "高" },
{ "name": "ns0000:AdvancedSetting", "displayname": "自訂" }
];
export const Scales=[
]
Loading…
Cancel
Save