|
@@ -1,7 +1,7 @@
|
|
|
#!/usr/bin/env python3
|
|
#!/usr/bin/env python3
|
|
|
# -*- coding: utf-8 -*-
|
|
# -*- coding: utf-8 -*-
|
|
|
"""
|
|
"""
|
|
|
-PDF 条码合成工具(带缩放与偏移配置)
|
|
|
|
|
|
|
+PDF 条码合成工具(单文件输入,每页独立输出)
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
import tkinter as tk
|
|
import tkinter as tk
|
|
@@ -29,9 +29,9 @@ class PDFMergeApp:
|
|
|
self.output_var = tk.StringVar()
|
|
self.output_var = tk.StringVar()
|
|
|
|
|
|
|
|
# 缩放与偏移变量
|
|
# 缩放与偏移变量
|
|
|
- self.scale_var = tk.StringVar(value="100") # 相对于模板宽度的百分比
|
|
|
|
|
- self.offset_x_var = tk.StringVar(value="0") # X 偏移(点)
|
|
|
|
|
- self.offset_y_var = tk.StringVar(value="10") # Y 偏移(点)
|
|
|
|
|
|
|
+ self.scale_var = tk.StringVar(value="100")
|
|
|
|
|
+ self.offset_x_var = tk.StringVar(value="0")
|
|
|
|
|
+ self.offset_y_var = tk.StringVar(value="10")
|
|
|
|
|
|
|
|
self.build_ui()
|
|
self.build_ui()
|
|
|
self.load_config()
|
|
self.load_config()
|
|
@@ -44,8 +44,8 @@ class PDFMergeApp:
|
|
|
self.root.rowconfigure(0, weight=1)
|
|
self.root.rowconfigure(0, weight=1)
|
|
|
main_frame.columnconfigure(1, weight=1)
|
|
main_frame.columnconfigure(1, weight=1)
|
|
|
|
|
|
|
|
- # 输入文件夹
|
|
|
|
|
- ttk.Label(main_frame, text="输入文件夹:").grid(row=0, column=0, sticky=tk.W, pady=6)
|
|
|
|
|
|
|
+ # 输入文件
|
|
|
|
|
+ ttk.Label(main_frame, text="输入文件:").grid(row=0, column=0, sticky=tk.W, pady=6)
|
|
|
ttk.Entry(main_frame, textvariable=self.input_var).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=6)
|
|
ttk.Entry(main_frame, textvariable=self.input_var).grid(row=0, column=1, sticky=(tk.W, tk.E), padx=6)
|
|
|
ttk.Button(main_frame, text="浏览…", width=10, command=self.select_input).grid(row=0, column=2, padx=4)
|
|
ttk.Button(main_frame, text="浏览…", width=10, command=self.select_input).grid(row=0, column=2, padx=4)
|
|
|
|
|
|
|
@@ -64,8 +64,7 @@ class PDFMergeApp:
|
|
|
param_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
|
|
param_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=10)
|
|
|
|
|
|
|
|
ttk.Label(param_frame, text="缩放比例(% 模板宽):").grid(row=0, column=0, sticky=tk.W, padx=4)
|
|
ttk.Label(param_frame, text="缩放比例(% 模板宽):").grid(row=0, column=0, sticky=tk.W, padx=4)
|
|
|
- scale_entry = ttk.Entry(param_frame, textvariable=self.scale_var, width=10)
|
|
|
|
|
- scale_entry.grid(row=0, column=1, sticky=tk.W, padx=4)
|
|
|
|
|
|
|
+ ttk.Entry(param_frame, textvariable=self.scale_var, width=10).grid(row=0, column=1, sticky=tk.W, padx=4)
|
|
|
ttk.Label(param_frame, text="例:100 = 宽度填满模板").grid(row=0, column=2, sticky=tk.W, padx=4)
|
|
ttk.Label(param_frame, text="例:100 = 宽度填满模板").grid(row=0, column=2, sticky=tk.W, padx=4)
|
|
|
|
|
|
|
|
ttk.Label(param_frame, text="X 偏移(点):").grid(row=1, column=0, sticky=tk.W, padx=4, pady=6)
|
|
ttk.Label(param_frame, text="X 偏移(点):").grid(row=1, column=0, sticky=tk.W, padx=4, pady=6)
|
|
@@ -90,15 +89,15 @@ class PDFMergeApp:
|
|
|
self.log_text.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=4)
|
|
self.log_text.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=4)
|
|
|
main_frame.rowconfigure(6, weight=1)
|
|
main_frame.rowconfigure(6, weight=1)
|
|
|
|
|
|
|
|
- # 绑定参数变更自动保存
|
|
|
|
|
|
|
+ # 参数变更自动保存
|
|
|
for var in (self.scale_var, self.offset_x_var, self.offset_y_var):
|
|
for var in (self.scale_var, self.offset_x_var, self.offset_y_var):
|
|
|
var.trace_add("write", lambda *args: self.save_config())
|
|
var.trace_add("write", lambda *args: self.save_config())
|
|
|
|
|
|
|
|
# ---------- 路径选择 ----------
|
|
# ---------- 路径选择 ----------
|
|
|
def select_input(self):
|
|
def select_input(self):
|
|
|
- folder = filedialog.askdirectory()
|
|
|
|
|
- if folder:
|
|
|
|
|
- self.input_var.set(folder)
|
|
|
|
|
|
|
+ file_path = filedialog.askopenfilename(filetypes=[("PDF 文件", "*.pdf")])
|
|
|
|
|
+ if file_path:
|
|
|
|
|
+ self.input_var.set(file_path)
|
|
|
self.save_config()
|
|
self.save_config()
|
|
|
|
|
|
|
|
def select_template(self):
|
|
def select_template(self):
|
|
@@ -116,7 +115,7 @@ class PDFMergeApp:
|
|
|
# ---------- 配置持久化 ----------
|
|
# ---------- 配置持久化 ----------
|
|
|
def save_config(self):
|
|
def save_config(self):
|
|
|
config = {
|
|
config = {
|
|
|
- "input_dir": self.input_var.get(),
|
|
|
|
|
|
|
+ "input_file": self.input_var.get(),
|
|
|
"template": self.template_var.get(),
|
|
"template": self.template_var.get(),
|
|
|
"output_dir": self.output_var.get(),
|
|
"output_dir": self.output_var.get(),
|
|
|
"scale_percent": self.scale_var.get(),
|
|
"scale_percent": self.scale_var.get(),
|
|
@@ -135,7 +134,7 @@ class PDFMergeApp:
|
|
|
try:
|
|
try:
|
|
|
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
|
config = json.load(f)
|
|
config = json.load(f)
|
|
|
- self.input_var.set(config.get("input_dir", ""))
|
|
|
|
|
|
|
+ self.input_var.set(config.get("input_file", ""))
|
|
|
self.template_var.set(config.get("template", ""))
|
|
self.template_var.set(config.get("template", ""))
|
|
|
self.output_var.set(config.get("output_dir", ""))
|
|
self.output_var.set(config.get("output_dir", ""))
|
|
|
self.scale_var.set(config.get("scale_percent", "100"))
|
|
self.scale_var.set(config.get("scale_percent", "100"))
|
|
@@ -153,7 +152,6 @@ class PDFMergeApp:
|
|
|
# ---------- 文字提取 ----------
|
|
# ---------- 文字提取 ----------
|
|
|
def extract_text(self, page):
|
|
def extract_text(self, page):
|
|
|
text = page.get_text().strip()
|
|
text = page.get_text().strip()
|
|
|
- print(f"Extracted text: {text}") # Debug print
|
|
|
|
|
if len(text) >= 2:
|
|
if len(text) >= 2:
|
|
|
return text
|
|
return text
|
|
|
try:
|
|
try:
|
|
@@ -168,12 +166,36 @@ class PDFMergeApp:
|
|
|
except Exception:
|
|
except Exception:
|
|
|
return ""
|
|
return ""
|
|
|
|
|
|
|
|
- def sanitize_filename(self, text):
|
|
|
|
|
|
|
+ def get_filename_from_text(self, text):
|
|
|
|
|
+ """
|
|
|
|
|
+ 取第一段 + 第四段文字作为文件名
|
|
|
|
|
+ 如果第四段不存在,只用第一段
|
|
|
|
|
+ 如果第一段也不存在,返回空字符串
|
|
|
|
|
+ """
|
|
|
if not text:
|
|
if not text:
|
|
|
return ""
|
|
return ""
|
|
|
lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
|
|
lines = [ln.strip() for ln in text.splitlines() if ln.strip()]
|
|
|
- name = lines[0] if lines else text
|
|
|
|
|
- name = name[:60]
|
|
|
|
|
|
|
+ if not lines:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ first = lines[0]
|
|
|
|
|
+ fourth = lines[4] if len(lines) >= 5 else ""
|
|
|
|
|
+ fourth = fourth.replace("¨", "0")
|
|
|
|
|
+ fourth = fourth.replace("©", "1")
|
|
|
|
|
+ fourth = fourth.replace("ª", "2")
|
|
|
|
|
+ fourth = fourth.replace("«", "3")
|
|
|
|
|
+ fourth = fourth.replace("°2", "4")
|
|
|
|
|
+ fourth = fourth.replace("°2", "5")
|
|
|
|
|
+ fourth = fourth.replace("®", "6")
|
|
|
|
|
+ fourth = fourth.replace("°2", "7")
|
|
|
|
|
+ fourth = fourth.replace("°", "8")
|
|
|
|
|
+ fourth = fourth.replace("°2", "9")
|
|
|
|
|
+ name = f"{first}_{fourth}" if fourth else first
|
|
|
|
|
+ return self.sanitize_filename(name)
|
|
|
|
|
+
|
|
|
|
|
+ def sanitize_filename(self, text):
|
|
|
|
|
+ if not text:
|
|
|
|
|
+ return ""
|
|
|
|
|
+ name = text[:80] # 限制长度
|
|
|
name = re.sub(r'[\\/:*?"<>|\s]+', "_", name)
|
|
name = re.sub(r'[\\/:*?"<>|\s]+', "_", name)
|
|
|
return name.strip("._")
|
|
return name.strip("._")
|
|
|
|
|
|
|
@@ -186,7 +208,7 @@ class PDFMergeApp:
|
|
|
self.start_btn.config(state=tk.DISABLED)
|
|
self.start_btn.config(state=tk.DISABLED)
|
|
|
self.log_text.delete(1.0, tk.END)
|
|
self.log_text.delete(1.0, tk.END)
|
|
|
|
|
|
|
|
- input_dir = self.input_var.get()
|
|
|
|
|
|
|
+ input_file = self.input_var.get()
|
|
|
template_file = self.template_var.get()
|
|
template_file = self.template_var.get()
|
|
|
output_dir = self.output_var.get()
|
|
output_dir = self.output_var.get()
|
|
|
|
|
|
|
@@ -200,12 +222,12 @@ class PDFMergeApp:
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
- if not all([input_dir, template_file, output_dir]):
|
|
|
|
|
- self.log("❌ 错误:请完整填写输入文件夹、模板文件和输出文件夹")
|
|
|
|
|
|
|
+ if not all([input_file, template_file, output_dir]):
|
|
|
|
|
+ self.log("❌ 错误:请完整填写输入文件、模板文件和输出文件夹")
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
return
|
|
return
|
|
|
- if not os.path.isdir(input_dir):
|
|
|
|
|
- self.log(f"❌ 错误:输入文件夹不存在:{input_dir}")
|
|
|
|
|
|
|
+ if not os.path.isfile(input_file):
|
|
|
|
|
+ self.log(f"❌ 错误:输入文件不存在:{input_file}")
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
return
|
|
return
|
|
|
if not os.path.isfile(template_file):
|
|
if not os.path.isfile(template_file):
|
|
@@ -215,13 +237,7 @@ class PDFMergeApp:
|
|
|
if not os.path.exists(output_dir):
|
|
if not os.path.exists(output_dir):
|
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
|
|
|
- pdf_files = [f for f in os.listdir(input_dir) if f.lower().endswith(".pdf")]
|
|
|
|
|
- if not pdf_files:
|
|
|
|
|
- self.log("⚠ 输入文件夹中没有找到 PDF 文件")
|
|
|
|
|
- self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
- return
|
|
|
|
|
-
|
|
|
|
|
- self.log(f"📁 共找到 {len(pdf_files)} 个 PDF 文件")
|
|
|
|
|
|
|
+ self.log(f"📄 输入文件:{os.path.basename(input_file)}")
|
|
|
self.log(f"⚙ 参数:缩放 {scale_percent}% 宽,偏移 ({offset_x}, {offset_y})\n")
|
|
self.log(f"⚙ 参数:缩放 {scale_percent}% 宽,偏移 ({offset_x}, {offset_y})\n")
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
@@ -236,72 +252,66 @@ class PDFMergeApp:
|
|
|
template_w = template_rect.width
|
|
template_w = template_rect.width
|
|
|
template_h = template_rect.height
|
|
template_h = template_rect.height
|
|
|
|
|
|
|
|
- for pdf_name in pdf_files:
|
|
|
|
|
- pdf_path = os.path.join(input_dir, pdf_name)
|
|
|
|
|
- self.log(f"📄 正在处理:{pdf_name}")
|
|
|
|
|
-
|
|
|
|
|
- try:
|
|
|
|
|
- doc = fitz.open(pdf_path)
|
|
|
|
|
- odd_indices = list(range(0, len(doc), 2))
|
|
|
|
|
-
|
|
|
|
|
- if not odd_indices:
|
|
|
|
|
- self.log(" ⚠ 该文件没有奇数页,已跳过")
|
|
|
|
|
- doc.close()
|
|
|
|
|
- continue
|
|
|
|
|
|
|
+ doc = fitz.open(input_file)
|
|
|
|
|
+ odd_indices = list(range(0, len(doc), 2))
|
|
|
|
|
|
|
|
- safe_name = self.sanitize_filename(self.extract_text(doc[odd_indices[0]]))
|
|
|
|
|
- if not safe_name:
|
|
|
|
|
- safe_name = os.path.splitext(pdf_name)[0]
|
|
|
|
|
-
|
|
|
|
|
- new_doc = fitz.open()
|
|
|
|
|
-
|
|
|
|
|
- for idx in odd_indices:
|
|
|
|
|
- page = doc[idx]
|
|
|
|
|
- page_num = idx + 1
|
|
|
|
|
|
|
+ if not odd_indices:
|
|
|
|
|
+ self.log("⚠ 该文件没有奇数页,已跳过")
|
|
|
|
|
+ doc.close()
|
|
|
|
|
+ template_doc.close()
|
|
|
|
|
+ self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
+ return
|
|
|
|
|
|
|
|
- new_page = new_doc.new_page(width=template_w, height=template_h)
|
|
|
|
|
|
|
+ self.log(f"📑 共 {len(doc)} 页,提取奇数页 {len(odd_indices)} 页\n")
|
|
|
|
|
|
|
|
- # 1) 模板背景
|
|
|
|
|
- new_page.show_pdf_page(template_rect, template_doc, 0, overlay=False)
|
|
|
|
|
|
|
+ for idx in odd_indices:
|
|
|
|
|
+ page = doc[idx]
|
|
|
|
|
+ page_num = idx + 1
|
|
|
|
|
|
|
|
- # 2) 计算输入页目标矩形
|
|
|
|
|
- input_rect = page.rect
|
|
|
|
|
- # 按模板宽度的百分比计算目标宽度
|
|
|
|
|
- target_w = template_w * (scale_percent / 100.0)
|
|
|
|
|
- # 等比例缩放高度
|
|
|
|
|
- scale = target_w / input_rect.width
|
|
|
|
|
- target_h = input_rect.height * scale
|
|
|
|
|
|
|
+ # 从该页提取文字作为文件名
|
|
|
|
|
+ page_text = self.extract_text(page)
|
|
|
|
|
+ safe_name = self.get_filename_from_text(page_text)
|
|
|
|
|
+ if not safe_name:
|
|
|
|
|
+ base = os.path.splitext(os.path.basename(input_file))[0]
|
|
|
|
|
+ safe_name = f"{base}_page{page_num}"
|
|
|
|
|
|
|
|
- target_rect = fitz.Rect(offset_x, offset_y, offset_x + target_w, offset_y + target_h)
|
|
|
|
|
|
|
+ # 创建单页新 PDF
|
|
|
|
|
+ new_doc = fitz.open()
|
|
|
|
|
+ new_page = new_doc.new_page(width=template_w, height=template_h)
|
|
|
|
|
|
|
|
- # 3) 叠加输入页(前景)
|
|
|
|
|
- new_page.show_pdf_page(target_rect, doc, idx, overlay=True)
|
|
|
|
|
|
|
+ # 1) 模板背景
|
|
|
|
|
+ new_page.show_pdf_page(template_rect, template_doc, 0, overlay=False)
|
|
|
|
|
|
|
|
- self.log(f" ✔ 已合成第 {page_num} 页 → 尺寸 {target_w:.1f}×{target_h:.1f} 位置 ({offset_x}, {offset_y})")
|
|
|
|
|
|
|
+ # 2) 计算输入页目标矩形
|
|
|
|
|
+ input_rect = page.rect
|
|
|
|
|
+ target_w = template_w * (scale_percent / 100.0)
|
|
|
|
|
+ scale = target_w / input_rect.width
|
|
|
|
|
+ target_h = input_rect.height * scale
|
|
|
|
|
+ target_rect = fitz.Rect(offset_x, offset_y, offset_x + target_w, offset_y + target_h)
|
|
|
|
|
|
|
|
- # 保存
|
|
|
|
|
- out_name = f"{safe_name}.pdf"
|
|
|
|
|
- out_path = os.path.join(output_dir, out_name)
|
|
|
|
|
- counter = 1
|
|
|
|
|
- while os.path.exists(out_path):
|
|
|
|
|
- stem, ext = os.path.splitext(out_name)
|
|
|
|
|
- out_path = os.path.join(output_dir, f"{stem}_{counter}{ext}")
|
|
|
|
|
- counter += 1
|
|
|
|
|
|
|
+ # 3) 叠加输入页(前景)
|
|
|
|
|
+ new_page.show_pdf_page(target_rect, doc, idx, overlay=True)
|
|
|
|
|
|
|
|
- new_doc.save(out_path)
|
|
|
|
|
- new_doc.close()
|
|
|
|
|
- doc.close()
|
|
|
|
|
|
|
+ # 保存(自动处理重名)
|
|
|
|
|
+ out_name = f"{safe_name}.pdf"
|
|
|
|
|
+ out_path = os.path.join(output_dir, out_name)
|
|
|
|
|
+ counter = 1
|
|
|
|
|
+ while os.path.exists(out_path):
|
|
|
|
|
+ stem, ext = os.path.splitext(out_name)
|
|
|
|
|
+ out_path = os.path.join(output_dir, f"{stem}_{counter}{ext}")
|
|
|
|
|
+ counter += 1
|
|
|
|
|
|
|
|
- self.log(f" 💾 已保存:{os.path.basename(out_path)}\n")
|
|
|
|
|
|
|
+ new_doc.save(out_path)
|
|
|
|
|
+ new_doc.close()
|
|
|
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- self.log(f" ❌ 处理失败:{str(e)}\n")
|
|
|
|
|
|
|
+ self.log(f"✔ 第 {page_num} 页 → {os.path.basename(out_path)}")
|
|
|
|
|
|
|
|
|
|
+ doc.close()
|
|
|
template_doc.close()
|
|
template_doc.close()
|
|
|
- self.log("🎉 全部处理完成!")
|
|
|
|
|
|
|
+ self.log(f"\n🎉 全部完成!共导出 {len(odd_indices)} 个 PDF")
|
|
|
|
|
|
|
|
except Exception as e:
|
|
except Exception as e:
|
|
|
- self.log(f"❌ 发生错误:{str(e)}")
|
|
|
|
|
|
|
+ self.log(f"\n❌ 发生错误:{str(e)}")
|
|
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|