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 <noreply@anthropic.com>
master
Amir Alexander Abdelbaki 2026-07-14 09:27:22 +02:00
parent 9d6fa2833f
commit 07cf695840
3 changed files with 87 additions and 5 deletions

View File

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

19
printer.conf Normal file
View File

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

View File

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