From b26b9fe123d3b2291c3d4c2ac4d51f8528f334c4 Mon Sep 17 00:00:00 2001 From: Mikei386 <44135113+Mikei386@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:35:05 +0200 Subject: [PATCH] Fix copy half-cut print handling --- README.md | 2 +- app/app.py | 77 +++++++++++++++++++++++++++++++++------ app/static/mobile.css | 7 ++++ app/static/style.css | 6 +++ app/templates/index.html | 3 +- app/templates/mobile.html | 3 +- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index efd8274..3a0a913 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Umgebungsvariablen: - `LABEL_DPI=180` passt zum PT-P700. - `LABEL_DIR=/data/labels` speichert erzeugte PNG-Dateien. -Wenn `ptouch-print` auf deinem Setup andere Optionen braucht, nur `PRINT_COMMAND` anpassen. Der Platzhalter `{image}` wird von der App ersetzt. Wenn dein Kommando selbst Kopien unterstuetzt, kannst du zusaetzlich `{copies}` verwenden; sonst druckt die App mehrere Kopien durch mehrere Druckaufrufe. Fuer Schnittoptionen kannst du optional `{cut_args}` im Kommando platzieren; ohne Platzhalter haengt die App die Schnittargumente automatisch an. +Wenn `ptouch-print` auf deinem Setup andere Optionen braucht, nur `PRINT_COMMAND` anpassen. Der Platzhalter `{image}` wird von der App ersetzt. Bei `Halbschnitt: Ein` nutzt die App fuer mehrere Kopien `ptouch-print --copies` und erzwingt automatisch Kettendruck, damit der Drucker Kopiengrenzen fuer Halbschnitte kennt und nicht nach jeder Kopie voll schneidet. Bei `Halbschnitt: Aus` oder wenn `--copies` nicht verfuegbar ist, werden Kopien als ein langes Druckbild gerendert. Fuer Schnittoptionen kannst du optional `{cut_args}` im Kommando platzieren; ohne Platzhalter haengt die App die Schnittargumente automatisch an. Hinweis: Der PT-P700-Halbschnitt ist in upstream `ptouch-print` nicht als CLI-Option vorhanden. Dieses Projekt patcht `ptouch-print` beim Containerstart um `--halfcut` und `--no-halfcut`. diff --git a/app/app.py b/app/app.py index 20e9d58..56b3d89 100644 --- a/app/app.py +++ b/app/app.py @@ -37,6 +37,7 @@ PRINT_COMMAND = os.environ.get("PRINT_COMMAND", DEFAULT_PRINT_COMMAND) PRINT_ENABLED = os.environ.get("PRINT_ENABLED", "1").lower() in {"1", "true", "yes", "on"} DPI = int(os.environ.get("LABEL_DPI", "180")) PTOUCH_SUPPORTS_HALFCUT: bool | None = None +PTOUCH_SUPPORTS_COPIES: bool | None = None TAPE_WIDTHS_MM = [3.5, 6, 9, 12, 18, 24] PRINTABLE_TAPE_PX = { @@ -223,7 +224,7 @@ def draw_cutmark(draw: ImageDraw.ImageDraw, width_px: int, height_px: int, scale y += dash + gap -def render_design(design: dict[str, Any]) -> Path: +def render_design_image(design: dict[str, Any]) -> Image.Image: tape_width = float(design.get("tapeWidthMm", 24)) if tape_width not in TAPE_WIDTHS_MM: raise ValueError("Ungueltige Bandbreite.") @@ -267,6 +268,17 @@ def render_design(design: dict[str, Any]) -> Path: if design.get("cutMode") in {"cutmark", "cutmark-chain"}: draw_cutmark(draw, image.width, image.height, scale) + return image + + +def render_design(design: dict[str, Any], copies: int = 1) -> Path: + image = render_design_image(design) + if copies > 1: + combined = new_label_image(image.width * copies, image.height) + for index in range(copies): + combined.paste(image, (index * image.width, 0)) + image = combined + output = LABEL_DIR / f"label-{uuid.uuid4().hex}.png" image.save(output) return output @@ -288,9 +300,35 @@ def ptouch_supports_halfcut() -> bool: return PTOUCH_SUPPORTS_HALFCUT -def cut_args_for_design(design: dict[str, Any]) -> str: +def ptouch_supports_copies() -> bool: + global PTOUCH_SUPPORTS_COPIES + if PTOUCH_SUPPORTS_COPIES is not None: + return PTOUCH_SUPPORTS_COPIES + result = subprocess.run( + ["ptouch-print", "--help"], + capture_output=True, + check=False, + text=True, + timeout=10, + ) + help_text = f"{result.stdout}\n{result.stderr}" + PTOUCH_SUPPORTS_COPIES = "--copies" in help_text + return PTOUCH_SUPPORTS_COPIES + + +def effective_cut_mode(design: dict[str, Any], copies: int) -> str: + cut_mode = str(design.get("cutMode", "auto")) + if copies > 1 and design.get("halfcut", "on") == "on": + if cut_mode == "cutmark": + return "cutmark-chain" + if cut_mode == "auto": + return "chain" + return cut_mode + + +def cut_args_for_design(design: dict[str, Any], copies: int) -> str: args = [] - if design.get("cutMode") in {"chain", "cutmark-chain"}: + if effective_cut_mode(design, copies) in {"chain", "cutmark-chain"}: args.append("--chain") halfcut = design.get("halfcut", "on") if halfcut == "off": @@ -307,8 +345,11 @@ def cut_args_for_design(design: dict[str, Any]) -> str: def run_print_command(image_path: Path, copies: int, design: dict[str, Any]) -> subprocess.CompletedProcess[str]: - cut_args = cut_args_for_design(design) - command = PRINT_COMMAND.format(image=shlex.quote(str(image_path)), copies=copies, cut_args=cut_args) + cut_args = cut_args_for_design(design, copies) + command_copies = copies if "{copies}" in PRINT_COMMAND else 1 + command = PRINT_COMMAND.format(image=shlex.quote(str(image_path)), copies=command_copies, cut_args=cut_args) + if copies > 1 and "{copies}" not in PRINT_COMMAND and ptouch_supports_copies(): + command = f"{command} --copies {copies}" if cut_args and "{cut_args}" not in PRINT_COMMAND: command = f"{command} {cut_args}" process = subprocess.Popen( @@ -333,9 +374,21 @@ def run_print_command(image_path: Path, copies: int, design: dict[str, Any]) -> def print_label(image_path: Path, copies: int, design: dict[str, Any]) -> list[subprocess.CompletedProcess[str]]: - if "{copies}" in PRINT_COMMAND: - return [run_print_command(image_path, copies, design)] - return [run_print_command(image_path, 1, design) for _ in range(copies)] + return [run_print_command(image_path, copies, design)] + + +def should_render_copies_as_strip(design: dict[str, Any], copies: int) -> bool: + if copies <= 1: + return False + if design.get("halfcut", "on") == "on": + if ptouch_supports_copies() or "{copies}" in PRINT_COMMAND: + return False + raise RuntimeError( + "Mehrere Kopien mit Halbschnitt brauchen einen echten Kopien-Druckjob. " + "Ein langes PNG kann keine Halbschnitte zwischen den Etiketten ausloesen. " + "Bitte eine ptouch-print-Version mit --copies verwenden." + ) + return True def detect_tape_width() -> tuple[float | None, str]: @@ -428,7 +481,7 @@ def mobile(): def api_preview(): try: design, _copies = design_from_request() - image_path = render_design(design) + image_path = render_design(design, 1) return jsonify({"ok": True, "image": f"/labels/{image_path.name}"}) except Exception as exc: return jsonify({"ok": False, "error": str(exc)}), 400 @@ -438,12 +491,14 @@ def api_preview(): def api_print(): try: design, copies = design_from_request() - image_path = render_design(design) + render_copies = copies if should_render_copies_as_strip(design, copies) else 1 + print_copies = 1 if render_copies > 1 else copies + image_path = render_design(design, render_copies) if PRINT_ENABLED: if not PRINT_LOCK.acquire(blocking=False): raise RuntimeError("Es laeuft bereits ein Druckjob. Bitte warten oder haengenden ptouch-print-Prozess beenden.") try: - results = print_label(image_path, copies, design) + results = print_label(image_path, print_copies, design) failed = next((result for result in results if result.returncode != 0), None) if failed: raise RuntimeError(format_print_error(failed)) diff --git a/app/static/mobile.css b/app/static/mobile.css index b16ee37..0d686a4 100644 --- a/app/static/mobile.css +++ b/app/static/mobile.css @@ -167,6 +167,13 @@ button.primary { color: #a7e8af; } +.field-note { + margin: 0; + color: #b7b7b7; + font-size: 13px; + line-height: 1.35; +} + .bottom-actions { position: fixed; left: 0; diff --git a/app/static/style.css b/app/static/style.css index 24fcdbe..9345abf 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -157,6 +157,12 @@ textarea { line-height: 1.4; } +.field-note { + color: #b7b7b7; + font-size: 13px; + line-height: 1.35; +} + .sidebar label { display: grid; gap: 6px; diff --git a/app/templates/index.html b/app/templates/index.html index 8e267d1..0f6245c 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -72,7 +72,7 @@ Schnitt @@ -84,6 +84,7 @@ +

Bei mehreren Kopien mit Halbschnitt wird automatisch Kettendruck verwendet.

diff --git a/app/templates/mobile.html b/app/templates/mobile.html index ebb2f49..58ea6b8 100644 --- a/app/templates/mobile.html +++ b/app/templates/mobile.html @@ -71,7 +71,7 @@ Schnitt @@ -83,6 +83,7 @@ +

Bei mehreren Kopien mit Halbschnitt wird automatisch Kettendruck verwendet.