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

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

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

@ -7,8 +7,7 @@ use winprint::ticket::{
PrintCapabilities, PrintTicket, PrintTicketBuilder,
};
// use util::imageToPdf;
// mod util;
mod util;
mod raw_convert;
#[tauri::command]
@ -58,11 +57,18 @@ fn get_printer_capabilities(device_name: String) -> HashMap<String, Vec<String>>
};
println!("My Printer: {:?}", my_device.name());
let mut map = HashMap::new();
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);
// 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) => {
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!("Size Options: {:?}", size_opts_value);
let mut map = HashMap::new();
map.insert("dpi_options".to_string(), dpi_opts_value);
map.insert("color_options".to_string(), color_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 _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"
xmlns:psf="http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework"
xmlns:psk="http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords">
<psf:Feature name="psk:PageScaling">
<psf:Option name="psk:PageScalingFit"/>
<psf:Option name="ns0001:None" />
</psf:Feature>
<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 name="psk:PageOutputColor">
<psf:Option name="psk:Monochrome"/>
</psf:Feature>
</psf:PrintTicket>"#;
</psf:PrintTicket>"#);
let ticket = PrintTicket::from_xml(xml);
ticket
}
@ -134,10 +134,11 @@ fn print_document(
dpi: String,
color: String,
size: String,
quality: Option<String>,
) {
println!(
"Printing document: {}, {}, {}, {}",
document_path, size, dpi, color
"Printing document: {}, {}, {}, {}, {}",
document_path, size, dpi, color, quality.clone().unwrap_or_else(|| "none".to_string())
);
let path = Path::new(&document_path);
@ -154,6 +155,7 @@ fn print_document(
dpi,
color,
size,
quality,
);
}
Err(e) => {
@ -211,22 +213,24 @@ fn print_document(
.expect("Color not supported by printer");
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(supported_dpi).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 xml=ticket.get_xml();
// let xml_string = String::from_utf8_lossy(&xml).to_string();
// println!("PrintTicket XML as String: {}", xml_string);
// return;
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 printer = PdfiumPrinter::new(my_device);

@ -1,70 +1,114 @@
use printpdf::*;
// 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 = dpi / 25.4; // Conversion factor
let width_pt = output_width * mm_to_pt;
let height_pt = output_height * mm_to_pt;
println!("Image size in pixels: {}x{}", image.width, image.height);
println!("Dest 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)),
dpi: Some(dpi),
..Default::default()
},
});
let save_options= PdfSaveOptions {
optimize: false,
image_optimization: Some(ImageOptimizationOptions {
quality: Some(1.0),
auto_optimize: Some(false),
format: Some(ImageCompression::None),
..ImageOptimizationOptions::default()
}),
..Default::default()
};
let page1 = PdfPage::new(Mm(output_width), Mm(output_height), ops);
let pdf_bytes: Vec<u8> = doc
.with_pages(vec![page1])
.save(&save_options, &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)),
use quick_xml::Reader;
use quick_xml::events::{Event, BytesStart};
use std::collections::HashSet;
// 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 = dpi / 25.4; // Conversion factor
// let width_pt = output_width * mm_to_pt;
// let height_pt = output_height * mm_to_pt;
// println!("Image size in pixels: {}x{}", image.width, image.height);
// println!("Dest 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)),
// dpi: Some(dpi),
// ..Default::default()
// },
// });
// let save_options= PdfSaveOptions {
// optimize: false,
// image_optimization: Some(ImageOptimizationOptions {
// quality: Some(1.0),
// auto_optimize: Some(false),
// format: Some(ImageCompression::None),
// ..ImageOptimizationOptions::default()
// }),
// ..Default::default()
// };
// let page1 = PdfPage::new(Mm(output_width), Mm(output_height), ops);
// let pdf_bytes: Vec<u8> = doc
// .with_pages(vec![page1])
// .save(&save_options, &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)),
// }
// }
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 "./App.css";
import { useEffect, useState } from "react";
import { AvailableQualities } from "./params";
const OptionTypes=[
'dpi_options',
'color_options',
'size_options',
'quality_options',
]
function App() {
@ -73,6 +82,7 @@ function App() {
dpi: document.getElementById("dpi_options").value,
color: document.getElementById("color_options").value,
size: document.getElementById("size_options").value,
quality: document.getElementById("quality_options").value,
})
.then(() => {
console.log("Print job sent successfully.");
@ -101,38 +111,22 @@ function App() {
.then((capabilities) => {
console.log(`Capabilities for ${printer}:`, capabilities);
if(capabilities.color_options){
const colorOptionsSelect = document.getElementById("color_options");
colorOptionsSelect.innerHTML = "";
for(const colorOption of capabilities.color_options) {
const option = document.createElement("option");
option.value = colorOption;
option.textContent = colorOption;
colorOptionsSelect.appendChild(option);
for(const optionType of OptionTypes) {
const selectElement = document.getElementById(optionType);
if(selectElement) {
selectElement.innerHTML = ""; // Clear existing options
}
}
if(capabilities.dpi_options){
const dpiOptionsSelect = document.getElementById("dpi_options");
dpiOptionsSelect.innerHTML = "";
for(const dpiOption of capabilities.dpi_options) {
const option = document.createElement("option");
option.value = dpiOption;
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);
if(!capabilities[optionType]) continue;
for(const option of capabilities[optionType]) {
const optionElement = document.createElement("option");
optionElement.value = option;
optionElement.textContent = option;
if(selectElement) {
selectElement.appendChild(optionElement);
}
}
}
})
@ -155,6 +149,7 @@ function App() {
<select id="size_options"></select>
<select id="dpi_options"></select>
<select id="color_options"></select>
<select id="quality_options"></select>
<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>
<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