|
|
|
@@ -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))
|
|
|
|
|