199 lines
7.1 KiB
Python
199 lines
7.1 KiB
Python
import tkinter as tk
|
|
from tkinter import simpledialog, messagebox, ttk
|
|
from PIL import Image, ImageTk, ImageDraw, ImageFont
|
|
import barcode
|
|
from barcode.writer import ImageWriter
|
|
import cups
|
|
import os
|
|
|
|
def get_printers():
|
|
conn = cups.Connection()
|
|
return list(conn.getPrinters().keys())
|
|
|
|
def create_label_image(text):
|
|
# Label dimensions for 9x5 cm at 300 DPI
|
|
label_width = 1063 # 9 cm
|
|
label_height = 591 # 5 cm
|
|
|
|
# Outer frame (95% of label, centered)
|
|
outer_frame_width = int(label_width * 0.95)
|
|
outer_frame_height = int(label_height * 0.95)
|
|
outer_frame_x = (label_width - outer_frame_width) // 2
|
|
outer_frame_y = (label_height - outer_frame_height) // 2
|
|
|
|
# Barcode frame (top, inside outer frame)
|
|
barcode_frame_width = int(outer_frame_width * 0.90)
|
|
barcode_frame_height = int(outer_frame_height * 0.60)
|
|
barcode_frame_x = outer_frame_x + (outer_frame_width - barcode_frame_width) // 2
|
|
barcode_frame_y = outer_frame_y
|
|
|
|
# Text frame (immediately below barcode frame)
|
|
text_frame_width = int(outer_frame_width * 0.90)
|
|
text_frame_height = int(outer_frame_height * 0.35)
|
|
text_frame_x = outer_frame_x + (outer_frame_width - text_frame_width) // 2
|
|
gap_between_frames = 5 # or 0 for no gap
|
|
text_frame_y = barcode_frame_y + barcode_frame_height + gap_between_frames
|
|
|
|
# Generate barcode image (no text), at higher resolution
|
|
CODE128 = barcode.get_barcode_class('code128')
|
|
writer_options = {
|
|
"write_text": False,
|
|
"module_width": 0.5, # default is 0.2, increase for higher res
|
|
"module_height": barcode_frame_height, # match frame height
|
|
"quiet_zone": 3.5, # default, can adjust if needed
|
|
"font_size": 0 # no text
|
|
}
|
|
code = CODE128(text, writer=ImageWriter())
|
|
filename = code.save('label_barcode', options=writer_options)
|
|
barcode_img = Image.open(filename)
|
|
|
|
# Now resize barcode to exactly fit barcode frame (stretch, do not keep aspect ratio)
|
|
barcode_resized = barcode_img.resize((barcode_frame_width, barcode_frame_height), Image.LANCZOS)
|
|
|
|
# Create label image
|
|
label_img = Image.new('RGB', (label_width, label_height), 'white')
|
|
|
|
# Paste barcode centered in barcode frame
|
|
label_img.paste(barcode_resized, (barcode_frame_x, barcode_frame_y))
|
|
|
|
# Draw text in text frame, maximize font size to fit frame (keep sharpness)
|
|
font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
|
|
max_font_size = text_frame_height
|
|
min_font_size = 10
|
|
best_font_size = min_font_size
|
|
for font_size in range(min_font_size, max_font_size + 1):
|
|
try:
|
|
font = ImageFont.truetype(font_path, font_size)
|
|
except IOError:
|
|
font = ImageFont.load_default()
|
|
break
|
|
dummy_img = Image.new('RGB', (1, 1))
|
|
dummy_draw = ImageDraw.Draw(dummy_img)
|
|
text_bbox = dummy_draw.textbbox((0, 0), text, font=font)
|
|
text_width = text_bbox[2] - text_bbox[0]
|
|
text_height = text_bbox[3] - text_bbox[1]
|
|
if text_width > text_frame_width or text_height > text_frame_height:
|
|
break
|
|
best_font_size = font_size
|
|
|
|
# Use the best font size found
|
|
try:
|
|
font = ImageFont.truetype(font_path, best_font_size)
|
|
except IOError:
|
|
font = ImageFont.load_default()
|
|
draw = ImageDraw.Draw(label_img)
|
|
text_bbox = draw.textbbox((0, 0), text, font=font)
|
|
text_width = text_bbox[2] - text_bbox[0]
|
|
text_height = text_bbox[3] - text_bbox[1]
|
|
text_x = text_frame_x + (text_frame_width - text_width) // 2
|
|
text_y = text_frame_y + (text_frame_height - text_height) // 2
|
|
draw.text((text_x, text_y), text, font=font, fill='black')
|
|
|
|
os.remove(filename)
|
|
return label_img
|
|
|
|
def print_label(printer_name, text):
|
|
label_img = create_label_image(text)
|
|
label_img.save('final_label.png')
|
|
conn = cups.Connection()
|
|
conn.printFile(printer_name, 'final_label.png', "Label Print", {})
|
|
os.remove('final_label.png')
|
|
|
|
def show_preview(image_path, on_print):
|
|
preview = tk.Toplevel()
|
|
preview.title("Label Preview")
|
|
preview.geometry("1063x591")
|
|
preview.resizable(False, False)
|
|
img = Image.open(image_path)
|
|
img_tk = ImageTk.PhotoImage(img)
|
|
label = tk.Label(preview, image=img_tk)
|
|
label.image = img_tk # Keep reference
|
|
label.pack()
|
|
def print_and_close():
|
|
on_print()
|
|
preview.destroy()
|
|
# Auto-close and print after 4 seconds (4000 ms)
|
|
preview.after(4000, print_and_close)
|
|
preview.grab_set()
|
|
preview.wait_window()
|
|
|
|
def select_printer(printers):
|
|
dialog = tk.Toplevel()
|
|
dialog.title("Select Printer")
|
|
tk.Label(dialog, text="Select a printer:").pack(padx=10, pady=5)
|
|
printer_var = tk.StringVar()
|
|
combo = ttk.Combobox(dialog, textvariable=printer_var, values=printers, state="readonly")
|
|
combo.pack(padx=10, pady=5)
|
|
combo.current(0)
|
|
selected = {'printer': None}
|
|
def on_ok():
|
|
selected['printer'] = printer_var.get()
|
|
dialog.destroy()
|
|
btn = tk.Button(dialog, text="OK", command=on_ok)
|
|
btn.pack(pady=10)
|
|
dialog.grab_set()
|
|
dialog.wait_window()
|
|
return selected['printer']
|
|
|
|
def ask_label_text_with_preview():
|
|
dialog = tk.Toplevel()
|
|
dialog.title("Label Text")
|
|
tk.Label(dialog, text="Enter text for the label:").pack(padx=10, pady=(10, 2))
|
|
text_var = tk.StringVar()
|
|
entry = tk.Entry(dialog, textvariable=text_var, width=40)
|
|
entry.pack(padx=10, pady=2)
|
|
preview_var = tk.BooleanVar(value=True)
|
|
chk = tk.Checkbutton(dialog, text="Show print preview", variable=preview_var)
|
|
chk.pack(padx=10, pady=2)
|
|
result = {'text': None, 'preview': True}
|
|
def on_ok():
|
|
result['text'] = text_var.get()
|
|
result['preview'] = preview_var.get()
|
|
dialog.destroy()
|
|
btn = tk.Button(dialog, text="OK", command=on_ok)
|
|
btn.pack(pady=10)
|
|
entry.focus()
|
|
dialog.grab_set()
|
|
dialog.wait_window()
|
|
return result['text'], result['preview']
|
|
|
|
def show_auto_close_info(title, message, timeout=3000):
|
|
info = tk.Toplevel()
|
|
info.title(title)
|
|
info.geometry("300x100")
|
|
info.resizable(False, False)
|
|
tk.Label(info, text=message, font=("Arial", 12)).pack(expand=True, padx=10, pady=10)
|
|
info.after(timeout, info.destroy)
|
|
info.grab_set()
|
|
info.wait_window()
|
|
|
|
def main():
|
|
root = tk.Tk()
|
|
root.withdraw()
|
|
|
|
printers = get_printers()
|
|
if not printers:
|
|
messagebox.showerror("Error", "No printers found.")
|
|
return
|
|
|
|
printer = select_printer(printers)
|
|
if not printer:
|
|
messagebox.showerror("Error", "No printer selected.")
|
|
return
|
|
|
|
text, do_preview = ask_label_text_with_preview()
|
|
if not text:
|
|
messagebox.showerror("Error", "No text entered.")
|
|
return
|
|
|
|
label_img = create_label_image(text)
|
|
label_img.save('final_label.png')
|
|
if do_preview:
|
|
show_preview('final_label.png', lambda: print_label(printer, text))
|
|
show_auto_close_info("Done", "Label sent to printer.", timeout=2000)
|
|
else:
|
|
print_label(printer, text)
|
|
show_auto_close_info("Done", "Label sent to printer.", timeout=5000)
|
|
|
|
if __name__ == "__main__":
|
|
main() |