From 07cf695840c012da4055195e377f9bbfd0a254be Mon Sep 17 00:00:00 2001 From: The_miro Date: Tue, 14 Jul 2026 09:27:22 +0200 Subject: [PATCH] feat: add PRINTER_A4 landscape option for A4 printers Dot-matrix printers have no hardware page-rotation, so when PRINTER_A4 is enabled in the new printer.conf, txt2printr.sh rotates the ascii art 90 degrees in software before sending it, and photobooth.py caps its live-preview viewfinder to an A4-landscape character grid (sized from configurable PRINTER_CPI/PRINTER_LPI) instead of just filling the terminal, so the preview matches what actually gets printed. Co-Authored-By: Claude Sonnet 5 --- photobooth.py | 37 +++++++++++++++++++++++++++++++++++-- printer.conf | 19 +++++++++++++++++++ txt2printr.sh | 36 +++++++++++++++++++++++++++++++++--- 3 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 printer.conf diff --git a/photobooth.py b/photobooth.py index 3738e5c..6f70db2 100755 --- a/photobooth.py +++ b/photobooth.py @@ -55,6 +55,7 @@ class Config: webcam = load_conf("webcam.conf") ascii_ = load_conf("ascii.conf") keys = load_conf("keybinds.conf") + printer = load_conf("printer.conf") self.device = webcam.get("DEVICE", "/dev/video0") w, _, h = webcam.get("RESOLUTION", "640x480").partition("x") @@ -79,6 +80,10 @@ class Config: self.flip_x = as_bool(ascii_.get("FLIP_X")) self.flip_y = as_bool(ascii_.get("FLIP_Y")) + self.printer_a4 = as_bool(printer.get("PRINTER_A4")) + self.printer_cpi = float(printer.get("PRINTER_CPI", "10") or 10) + self.printer_lpi = float(printer.get("PRINTER_LPI", "6") or 6) + self.key_capture = keys.get("KEY_CAPTURE", " ") self.key_brightness_up = keys.get("KEY_BRIGHTNESS_UP", "+") self.key_brightness_down = keys.get("KEY_BRIGHTNESS_DOWN", "-") @@ -168,9 +173,37 @@ def capture_label(cfg): return "space" if cfg.key_capture == " " else cfg.key_capture +_A4_LONG_MM = 297.0 +_A4_SHORT_MM = 210.0 +_MM_PER_IN = 25.4 +_PRINT_MARGIN_IN = 0.5 + + +def a4_landscape_chars(cpi, lpi): + """(cols, rows) of an A4 sheet printed sideways, at the given pitch.""" + long_in = _A4_LONG_MM / _MM_PER_IN + short_in = _A4_SHORT_MM / _MM_PER_IN + cols = max(1, round((long_in - 2 * _PRINT_MARGIN_IN) * cpi)) + rows = max(1, round((short_in - 2 * _PRINT_MARGIN_IN) * lpi)) + return cols, rows + + +def box_dims_for(term_rows, term_cols, a4_target): + box_rows, box_cols = max(term_rows - 2, 3), max(term_cols, 10) + if a4_target: + a4_cols, a4_rows = a4_target + box_rows = min(box_rows, a4_rows + 2) + box_cols = min(box_cols, a4_cols + 2) + return box_rows, box_cols + + def main(stdscr): cfg = Config() conv_flags = cfg.converter_flags() + a4_target = ( + a4_landscape_chars(cfg.printer_cpi, cfg.printer_lpi) + if cfg.printer_a4 else None + ) curses.curs_set(0) stdscr.nodelay(True) @@ -190,7 +223,7 @@ def main(stdscr): frame_times = [] message, message_until = "", 0.0 rows, cols = stdscr.getmaxyx() - box_dims = (max(rows - 2, 3), max(cols, 10)) + box_dims = box_dims_for(rows, cols, a4_target) box_win = curses.newwin(box_dims[0], box_dims[1], 0, 0) try: @@ -210,7 +243,7 @@ def main(stdscr): jpg_bytes = buf.tobytes() rows, cols = stdscr.getmaxyx() - box_rows, box_cols = max(rows - 2, 3), max(cols, 10) + box_rows, box_cols = box_dims_for(rows, cols, a4_target) inner_rows, inner_cols = max(box_rows - 2, 1), max(box_cols - 2, 1) if (box_rows, box_cols) != box_dims: diff --git a/printer.conf b/printer.conf new file mode 100644 index 0000000..7d12b71 --- /dev/null +++ b/printer.conf @@ -0,0 +1,19 @@ +# ASCII Photobooth printer configuration +# Sourced by txt2printr.sh and photobooth.py. + +# Is the printer loaded with A4 paper for a landscape printout? (true/false) +# Needle/dot-matrix printers have no hardware page-rotation command: the +# print head only scans across a fixed carriage width while the paper feeds +# in one fixed direction, so there is no way to make the physical page +# itself "landscape". Instead, when this is true: +# - txt2printr.sh rotates the ascii art 90 degrees in software before +# sending it to the printer (turn the printed page 90 degrees to read it) +# - photobooth.py sizes its live-preview viewfinder to an A4-landscape +# character grid instead of just following the terminal size, so what you +# see in the preview matches the shape of what gets printed +PRINTER_A4=false + +# Printer pitch, used only to size the A4 viewfinder above (characters per +# inch / lines per inch). Defaults match common Epson ESC/P draft settings. +PRINTER_CPI=10 +PRINTER_LPI=6 diff --git a/txt2printr.sh b/txt2printr.sh index be00c96..42c33e5 100755 --- a/txt2printr.sh +++ b/txt2printr.sh @@ -4,6 +4,11 @@ # # Usage: ./txt2printr.sh [ascii-art.txt] # Env: PRINTER_DEV - printer device node (default: /dev/lp0) +# Reads printer.conf for PRINTER_A4 (landscape rotation). + +dir="$(dirname "$0")" +[ -f "$dir/printer.conf" ] && source "$dir/printer.conf" +: "${PRINTER_A4:=false}" infile="${1:-ascii-img.txt}" # Source - https://stackoverflow.com/a/638980 @@ -31,8 +36,33 @@ if [ ! -w "$dev" ]; then exit 1 fi +# Rotates the ascii art 90 degrees clockwise so it reads correctly once the +# printed page itself is turned 90 degrees counter-clockwise - the printer's +# scan/feed directions are fixed, so this is the only way to get a landscape +# reading orientation out of it. +rotate90() { + awk ' + { if (length($0) > maxlen) maxlen = length($0); lines[NR] = $0 } + END { + for (c = 1; c <= maxlen; c++) { + row = "" + for (r = NR; r >= 1; r--) { + ch = substr(lines[r], c, 1) + if (ch == "") ch = " " + row = row ch + } + print row + } + } + ' "$infile" +} + { - printf '\x1b\x40' # ESC @ - reset printer (ESC/P and IBM Proprinter compatible) - sed 's/$/\r/' "$infile" # CRLF line endings, needed for raw writes to avoid staircasing - printf '\x0c' # form feed - eject the page + printf '\x1b\x40' # ESC @ - reset printer (ESC/P and IBM Proprinter compatible) + if [ "$PRINTER_A4" = "true" ]; then + rotate90 | sed 's/$/\r/' # CRLF line endings, needed for raw writes to avoid staircasing + else + sed 's/$/\r/' "$infile" + fi + printf '\x0c' # form feed - eject the page } > "$dev"