소스 검색

no message

master
DESKTOP-064TTA1\Fai LUK 3 일 전
부모
커밋
aff2254a95
2개의 변경된 파일200개의 추가작업 그리고 81개의 파일을 삭제
  1. +193
    -80
      python/Bag2.py
  2. +7
    -1
      python/installAndExe.txt

+ 193
- 80
python/Bag2.py 파일 보기

@@ -39,13 +39,19 @@ except ImportError:
win32gui = None # type: ignore[assignment]

try:
from PIL import Image, ImageDraw, ImageFont
from PIL import Image, ImageDraw, ImageFont, ImageOps
try:
from PIL import ImageWin # type: ignore
except Exception:
ImageWin = None # type: ignore[assignment]
import qrcode
_HAS_PIL_QR = True
except ImportError:
Image = None # type: ignore[assignment]
ImageDraw = None # type: ignore[assignment]
ImageFont = None # type: ignore[assignment]
ImageOps = None # type: ignore[assignment]
ImageWin = None # type: ignore[assignment]
qrcode = None # type: ignore[assignment]
_HAS_PIL_QR = False

@@ -205,21 +211,22 @@ def generate_zpl_dataflex(
label_esc = _zpl_escape(label_line)
# QR payload: prefer JSON {"itemId":..., "stockInLineId":...} when both present; else fall back to lot/batch text
if item_id is not None and stock_in_line_id is not None:
qr_value = _zpl_escape(json.dumps({"itemId": item_id, "stockInLineId": stock_in_line_id}))
qr_payload = json.dumps({"itemId": item_id, "stockInLineId": stock_in_line_id})
else:
qr_value = _zpl_escape(label_line if label_line else batch_no.strip())
qr_payload = label_line if label_line else batch_no.strip()
qr_value = _zpl_escape(qr_payload)
return f"""^XA
^CI28
^PW700
^LL500
^PO N
^FO10,20
^BQR,4,7^FD{qr_value}^FS
^BQN,2,4^FDQA,{qr_value}^FS
^FO170,20
^A@R,72,72,{font_regular}^FD{desc}^FS
^FO0,290
^FO0,200
^A@R,72,72,{font_regular}^FD{label_esc}^FS
^FO75,290
^FO55,200
^A@R,88,88,{font_bold}^FD{code}^FS
^XZ"""

@@ -262,22 +269,52 @@ def generate_zpl_label_small(
^XZ"""


# Label image size (pixels) for 標簽機 image printing; words drawn bigger for readability
LABEL_IMAGE_W = 520
LABEL_IMAGE_H = 520
LABEL_PADDING = 20
LABEL_FONT_NAME_SIZE = 38 # item name (bigger)
LABEL_FONT_CODE_SIZE = 44 # item code (bigger)
LABEL_FONT_BATCH_SIZE = 30 # batch/lot line
LABEL_QR_SIZE = 140 # QR module size in pixels
# Label image size (pixels) for 標簽機 image printing.
# Enlarged for readability (approx +90% scale).
LABEL_IMAGE_W = 720
LABEL_IMAGE_H = 530
LABEL_PADDING = 23
LABEL_FONT_NAME_SIZE = 42
LABEL_FONT_CODE_SIZE = 49
LABEL_FONT_BATCH_SIZE = 34
LABEL_QR_SIZE = 210


def _get_chinese_font(size: int) -> Optional["ImageFont.FreeTypeFont"]:
"""Return a Chinese-capable font for PIL, or None to use default."""
if ImageFont is None:
return None
# Prefer Traditional Chinese fonts on Windows
for name in ("Microsoft JhengHei UI", "Microsoft JhengHei", "MingLiU", "SimHei", "Microsoft YaHei", "SimSun"):
# Prefer real font files on Windows (font *names* may fail and silently fallback).
if os.name == "nt":
fonts_dir = os.path.join(os.environ.get("WINDIR", r"C:\Windows"), "Fonts")
for rel in (
"msjh.ttc", # Microsoft JhengHei
"msjhl.ttc", # Microsoft JhengHei Light
"msjhbd.ttc", # Microsoft JhengHei Bold
"mingliu.ttc",
"mingliub.ttc",
"kaiu.ttf",
"msyh.ttc", # Microsoft YaHei
"msyhbd.ttc",
"simhei.ttf",
"simsun.ttc",
):
p = os.path.join(fonts_dir, rel)
try:
if os.path.exists(p):
return ImageFont.truetype(p, size)
except (OSError, IOError):
continue
# Fallback: try common font names (may still work depending on Pillow build)
for name in (
"Microsoft JhengHei UI",
"Microsoft JhengHei",
"MingLiU",
"MingLiU_HKSCS",
"Microsoft YaHei",
"SimHei",
"SimSun",
):
try:
return ImageFont.truetype(name, size)
except (OSError, IOError):
@@ -328,23 +365,60 @@ def render_label_to_image(
name_str = (item_name or "—").strip()
max_name_w = LABEL_IMAGE_W - x_right - LABEL_PADDING
if font_name:
# Wrap by text width (Pillow 8+ textbbox) or by char count
# Wrap rule: after 7 "words" (excl. parentheses). ()() not counted; +=*/. and A–Z/a–z count as 0.5.
def _wrap_text(text: str, font, max_width: int) -> list:
ignore = set("()()")
half = set("+=*/.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
max_count = 6.5
lines: list[str] = []
current: list[str] = []
count = 0.0

for ch in text:
if ch == "\n":
lines.append("".join(current).strip())
current = []
count = 0.0
continue

ch_count = 0.0 if ch in ignore else (0.5 if ch in half else 1.0)
if count + ch_count > max_count and current:
lines.append("".join(current).strip())
current = []
count = 0.0

current.append(ch)
count += ch_count

if current:
lines.append("".join(current).strip())

# Max 2 rows for item name. If still long, keep everything in row 2.
if len(lines) > 2:
lines = [lines[0], "".join(lines[1:]).strip()]

# Safety: if any line still exceeds pixel width, wrap by width as well.
if hasattr(draw, "textbbox"):
words = list(text)
lines, line = [], []
for c in words:
line.append(c)
bbox = draw.textbbox((0, 0), "".join(line), font=font)
if bbox[2] - bbox[0] > max_width and len(line) > 1:
lines.append("".join(line[:-1]))
line = [line[-1]]
if line:
lines.append("".join(line))
return lines
# Fallback: ~12 chars per line for Chinese
chunk = 12
return [text[i : i + chunk] for i in range(0, len(text), chunk)]
out: list[str] = []
for ln in lines:
buf: list[str] = []
for ch in ln:
buf.append(ch)
bbox = draw.textbbox((0, 0), "".join(buf), font=font)
if bbox[2] - bbox[0] > max_width and len(buf) > 1:
out.append("".join(buf[:-1]).strip())
buf = [buf[-1]]
if buf:
out.append("".join(buf).strip())
out = [x for x in out if x]
if len(out) > 2:
out = [out[0], "".join(out[1:]).strip()]
return out

lines = [x for x in lines if x]
if len(lines) > 2:
lines = [lines[0], "".join(lines[1:]).strip()]
return lines
lines = _wrap_text(name_str, font_name, max_name_w)
for i, ln in enumerate(lines):
draw.text((x_right, y_line + i * (LABEL_FONT_NAME_SIZE + 4)), ln, font=font_name, fill="black")
@@ -368,6 +442,32 @@ def render_label_to_image(
return img


def _image_to_zpl_gfa(pil_image: "Image.Image") -> str:
"""
Convert a PIL image into ZPL ^GFA (ASCII hex) so we can print Chinese reliably
on ZPL printers (USB/Windows printer or COM) without relying on GDI drivers.
"""
if Image is None or ImageOps is None:
raise RuntimeError("Pillow is required for image-to-ZPL conversion.")
# Convert to 1-bit monochrome bitmap. Invert so '1' bits represent black in ZPL.
img_bw = ImageOps.invert(pil_image.convert("L")).convert("1")
w, h = img_bw.size
bytes_per_row = (w + 7) // 8
raw = img_bw.tobytes()
total = bytes_per_row * h
# Ensure length matches expected (Pillow should already pack per row).
if len(raw) != total:
raw = raw[:total].ljust(total, b"\x00")
hex_data = raw.hex().upper()
return f"""^XA
^PW{w}
^LL{h}
^FO0,0
^GFA,{total},{total},{bytes_per_row},{hex_data}
^FS
^XZ"""


def send_image_to_label_printer(printer_name: str, pil_image: "Image.Image") -> None:
"""
Send a PIL Image to 標簽機 via Windows GDI (so Chinese and graphics print correctly).
@@ -380,38 +480,58 @@ def send_image_to_label_printer(printer_name: str, pil_image: "Image.Image") ->
raise RuntimeError("Image printing is only supported for a Windows printer name (e.g. TSC TTP-246M Pro).")
if win32print is None or win32ui is None or win32con is None or win32gui is None:
raise RuntimeError("pywin32 is required. Run: pip install pywin32")
# Draw image to printer DC via temp BMP (GDI uses BMP)
with tempfile.NamedTemporaryFile(suffix=".bmp", delete=False) as f:
tmp_bmp = f.name
dc = win32ui.CreateDC()
dc.CreatePrinterDC(dest)
dc.StartDoc("FPSMS Label")
dc.StartPage()
try:
pil_image.save(tmp_bmp, "BMP")
hbm = win32gui.LoadImage(
0, tmp_bmp, win32con.IMAGE_BITMAP, 0, 0,
win32con.LR_LOADFROMFILE | win32con.LR_CREATEDIBSECTION,
)
if hbm == 0:
raise RuntimeError("Failed to load label image as bitmap.")
dc = win32ui.CreateDC()
dc.CreatePrinterDC(dest)
dc.StartDoc("FPSMS Label")
dc.StartPage()
bmp_w = pil_image.width
bmp_h = pil_image.height
# Scale-to-fit printable area (important for smaller physical labels).
try:
mem_dc = win32ui.CreateDCFromHandle(win32gui.CreateCompatibleDC(dc.GetSafeHdc()))
# PyCBitmap.FromHandle works across pywin32 versions
bmp = getattr(win32ui, "CreateBitmapFromHandle", lambda h: win32ui.PyCBitmap.FromHandle(h))(hbm)
mem_dc.SelectObject(bmp)
bmp_w = pil_image.width
bmp_h = pil_image.height
dc.StretchBlt((0, 0), (bmp_w, bmp_h), mem_dc, (0, 0), (bmp_w, bmp_h), win32con.SRCCOPY)
finally:
win32gui.DeleteObject(hbm)
page_w = int(dc.GetDeviceCaps(win32con.HORZRES))
page_h = int(dc.GetDeviceCaps(win32con.VERTRES))
except Exception:
page_w, page_h = bmp_w, bmp_h
if page_w <= 0 or page_h <= 0:
page_w, page_h = bmp_w, bmp_h
scale = min(page_w / max(1, bmp_w), page_h / max(1, bmp_h))
out_w = max(1, int(bmp_w * scale))
out_h = max(1, int(bmp_h * scale))
x0 = max(0, (page_w - out_w) // 2)
y0 = max(0, (page_h - out_h) // 2)

# Most reliable: render via Pillow ImageWin directly to printer DC.
if ImageWin is not None:
dib = ImageWin.Dib(pil_image.convert("RGB"))
dib.draw(dc.GetHandleOutput(), (x0, y0, x0 + out_w, y0 + out_h))
else:
# Fallback: Draw image to printer DC via temp BMP (GDI uses BMP)
with tempfile.NamedTemporaryFile(suffix=".bmp", delete=False) as f:
tmp_bmp = f.name
try:
pil_image.save(tmp_bmp, "BMP")
hbm = win32gui.LoadImage(
0, tmp_bmp, win32con.IMAGE_BITMAP, 0, 0,
win32con.LR_LOADFROMFILE | win32con.LR_CREATEDIBSECTION,
)
if hbm == 0:
raise RuntimeError("Failed to load label image as bitmap.")
try:
mem_dc = win32ui.CreateDCFromHandle(win32gui.CreateCompatibleDC(dc.GetSafeHdc()))
bmp = getattr(win32ui, "CreateBitmapFromHandle", lambda h: win32ui.PyCBitmap.FromHandle(h))(hbm)
mem_dc.SelectObject(bmp)
dc.StretchBlt((x0, y0), (out_w, out_h), mem_dc, (0, 0), (bmp_w, bmp_h), win32con.SRCCOPY)
finally:
win32gui.DeleteObject(hbm)
finally:
try:
os.unlink(tmp_bmp)
except OSError:
pass
finally:
dc.EndPage()
dc.EndDoc()
finally:
try:
os.unlink(tmp_bmp)
except OSError:
pass


def run_dataflex_continuous_print(
@@ -1333,27 +1453,20 @@ def main() -> None:
lot_no = j.get("lotNo")
n = 100 if count == -1 else count
try:
# Prefer image printing so Chinese displays correctly; words are bigger
if _HAS_PIL_QR and os.name == "nt" and not com.upper().startswith("COM"):
label_img = render_label_to_image(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
for i in range(n):
send_image_to_label_printer(com, label_img)
if i < n - 1:
time.sleep(0.5)
else:
zpl = generate_zpl_label_small(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
for i in range(n):
send_zpl_to_label_printer(com, zpl)
if i < n - 1:
time.sleep(0.5)
# Always render to image (Chinese OK), then send as ZPL graphic (^GFA).
# This is more reliable than Windows GDI and works for both Windows printer name and COM.
if not _HAS_PIL_QR:
raise RuntimeError("請先安裝 Pillow + qrcode(pip install Pillow qrcode[pil])。")
label_img = render_label_to_image(
b, item_code, item_name,
item_id=item_id, stock_in_line_id=stock_in_line_id,
lot_no=lot_no,
)
zpl_img = _image_to_zpl_gfa(label_img)
for i in range(n):
send_zpl_to_label_printer(com, zpl_img)
if i < n - 1:
time.sleep(0.5)
msg = f"已送出列印:{n} 張標簽" if count != -1 else f"已送出列印:{n} 張標簽 (連續)"
messagebox.showinfo("標簽機", msg)
except Exception as err:


+ 7
- 1
python/installAndExe.txt 파일 보기

@@ -5,4 +5,10 @@ py -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py

python -m pip install pyinstaller
python -m pip install --upgrade pyinstaller
python -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py
python -m PyInstaller --onefile --windowed --name "Bag1" Bag1.py


pip install Pillow "qrcode[pil]"


py -m pip install Pillow "qrcode[pil]"

불러오는 중...
취소
저장