fix: use measured page capacity instead of derived CPI/LPI geometry

Real-world testing (pagetest.sh) showed the printer's actual per-page
line/column capacity didn't match what the A4-geometry math predicted,
and the redundant blank second page was traced to the printer
auto-advancing to a new page once content hits the bottom margin,
with our own unconditional trailing form-feed then ejecting that
already-started blank page on top of it.

Replace PRINTER_CPI/PRINTER_LPI with directly-measured
PRINTER_MAX_LINES/PRINTER_MAX_COLS in printer.conf (read via
pagetest.sh), and have txt2printr.sh skip the trailing form-feed
whenever the print job already reaches that line capacity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
master
Amir Alexander Abdelbaki 2026-07-14 09:59:06 +02:00
parent 796b1c4830
commit a7d5b9d1bc
3 changed files with 40 additions and 34 deletions

View File

@ -81,8 +81,8 @@ class Config:
self.flip_y = as_bool(ascii_.get("FLIP_Y")) self.flip_y = as_bool(ascii_.get("FLIP_Y"))
self.printer_a4 = as_bool(printer.get("PRINTER_A4")) self.printer_a4 = as_bool(printer.get("PRINTER_A4"))
self.printer_cpi = float(printer.get("PRINTER_CPI", "10") or 10) self.printer_max_lines = int(printer.get("PRINTER_MAX_LINES", "64") or 64)
self.printer_lpi = float(printer.get("PRINTER_LPI", "6") or 6) self.printer_max_cols = int(printer.get("PRINTER_MAX_COLS", "54") or 54)
self.key_capture = keys.get("KEY_CAPTURE", " ") self.key_capture = keys.get("KEY_CAPTURE", " ")
self.key_brightness_up = keys.get("KEY_BRIGHTNESS_UP", "+") self.key_brightness_up = keys.get("KEY_BRIGHTNESS_UP", "+")
@ -173,34 +173,21 @@ def capture_label(cfg):
return "space" if cfg.key_capture == " " else cfg.key_capture return "space" if cfg.key_capture == " " else cfg.key_capture
_A4_LONG_MM = 297.0 def a4_landscape_chars(max_lines, max_cols):
_A4_SHORT_MM = 210.0
_MM_PER_IN = 25.4
_PRINT_MARGIN_IN = 0.5
def a4_landscape_chars(cpi, lpi):
"""(cols, rows) to capture/preview for an A4 landscape printout. """(cols, rows) to capture/preview for an A4 landscape printout.
txt2printr.sh prints on a normally-fed (portrait) page and rotates the txt2printr.sh prints on a normally-fed (portrait) page and rotates the
ascii art 90 degrees in software; the reader turns the finished sheet ascii art 90 degrees in software; the reader turns the finished sheet
90 degrees to view it. That second turn cancels the software rotation, 90 degrees to view it. That second turn cancels the software rotation,
so this buffer's own dimensions equal the final visual result - but its so this buffer's own dimensions equal the final visual result - but its
width becomes the printed *line count* after rotation, which must fit width becomes the printed *line count* after rotation (bounded by
within one page's line capacity along the long (feed) edge. Height is max_lines, the page's real per-page line capacity) and its height
then derived from the target landscape aspect ratio, corrected for becomes each printed line's length (bounded by max_cols, the real
non-square character cells (cells are cpi/lpi times taller than wide), per-line character capacity). Both are measured directly against the
so the final printout keeps true A4 proportions instead of just filling printer with pagetest.sh rather than derived from paper/pitch math,
however many character cells happen to fit. which doesn't reliably match real-world margins.
""" """
long_in = _A4_LONG_MM / _MM_PER_IN return max_lines, max_cols
short_in = _A4_SHORT_MM / _MM_PER_IN
max_lines = max(1, round((long_in - 2 * _PRINT_MARGIN_IN) * lpi))
max_chars = max(1, round((short_in - 2 * _PRINT_MARGIN_IN) * cpi))
aspect = (long_in / short_in) * (cpi / lpi)
cols = max_lines
rows = max(1, min(max_chars, round(cols / aspect)))
return cols, rows
def box_dims_for(term_rows, term_cols, a4_target): def box_dims_for(term_rows, term_cols, a4_target):
@ -216,7 +203,7 @@ def main(stdscr):
cfg = Config() cfg = Config()
conv_flags = cfg.converter_flags() conv_flags = cfg.converter_flags()
a4_target = ( a4_target = (
a4_landscape_chars(cfg.printer_cpi, cfg.printer_lpi) a4_landscape_chars(cfg.printer_max_lines, cfg.printer_max_cols)
if cfg.printer_a4 else None if cfg.printer_a4 else None
) )

View File

@ -13,7 +13,13 @@
# see in the preview matches the shape of what gets printed # see in the preview matches the shape of what gets printed
PRINTER_A4=false PRINTER_A4=false
# Printer pitch, used only to size the A4 viewfinder above (characters per # Your printer's real per-page capacity in the normal (portrait, unrotated)
# inch / lines per inch). Defaults match common Epson ESC/P draft settings. # orientation, used to size the A4 viewfinder above and to know when a print
PRINTER_CPI=10 # job already fills the page (see txt2printr.sh). Paper geometry and stated
PRINTER_LPI=6 # pitch don't reliably predict this - measure it directly with pagetest.sh:
# ./pagetest.sh && PRINTER_A4=false ./txt2printr.sh pagetest.txt
# then read off the last line number on page 1 (-> PRINTER_MAX_LINES) and
# how many characters of the 64-wide test lines actually printed before
# being cut off (-> PRINTER_MAX_COLS).
PRINTER_MAX_LINES=64
PRINTER_MAX_COLS=54

View File

@ -4,11 +4,13 @@
# #
# Usage: ./txt2printr.sh [ascii-art.txt] # Usage: ./txt2printr.sh [ascii-art.txt]
# Env: PRINTER_DEV - printer device node (default: /dev/lp0) # Env: PRINTER_DEV - printer device node (default: /dev/lp0)
# Reads printer.conf for PRINTER_A4 (landscape rotation). # Reads printer.conf for PRINTER_A4 (landscape rotation) and PRINTER_MAX_LINES
# (per-page line capacity, measured with pagetest.sh).
dir="$(dirname "$0")" dir="$(dirname "$0")"
[ -f "$dir/printer.conf" ] && source "$dir/printer.conf" [ -f "$dir/printer.conf" ] && source "$dir/printer.conf"
: "${PRINTER_A4:=false}" : "${PRINTER_A4:=false}"
: "${PRINTER_MAX_LINES:=64}"
infile="${1:-ascii-img.txt}" infile="${1:-ascii-img.txt}"
# Source - https://stackoverflow.com/a/638980 # Source - https://stackoverflow.com/a/638980
@ -57,12 +59,23 @@ rotate90() {
' "$infile" ' "$infile"
} }
{ body() {
printf '\x1b\x40' # ESC @ - reset printer (ESC/P and IBM Proprinter compatible)
if [ "$PRINTER_A4" = "true" ]; then if [ "$PRINTER_A4" = "true" ]; then
rotate90 | sed 's/$/\r/' # CRLF line endings, needed for raw writes to avoid staircasing rotate90
else else
sed 's/$/\r/' "$infile" cat "$infile"
fi
}
line_count=$(body | wc -l)
{
printf '\x1b\x40' # ESC @ - reset printer (ESC/P and IBM Proprinter compatible)
body | sed 's/$/\r/' # CRLF line endings, needed for raw writes to avoid staircasing
# Skip the form feed once the page is already full: printers commonly
# auto-advance to a new page as soon as content hits the bottom margin,
# so an explicit form feed on top of that just ejects a blank extra page.
if [ "$line_count" -lt "$PRINTER_MAX_LINES" ]; then
printf '\x0c' # form feed - eject the page
fi fi
printf '\x0c' # form feed - eject the page
} > "$dev" } > "$dev"