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.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.printer_max_lines = int(printer.get("PRINTER_MAX_LINES", "64") or 64)
self.printer_max_cols = int(printer.get("PRINTER_MAX_COLS", "54") or 54)
self.key_capture = keys.get("KEY_CAPTURE", " ")
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
_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):
def a4_landscape_chars(max_lines, max_cols):
"""(cols, rows) to capture/preview for an A4 landscape printout.
txt2printr.sh prints on a normally-fed (portrait) page and rotates the
ascii art 90 degrees in software; the reader turns the finished sheet
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
width becomes the printed *line count* after rotation, which must fit
within one page's line capacity along the long (feed) edge. Height is
then derived from the target landscape aspect ratio, corrected for
non-square character cells (cells are cpi/lpi times taller than wide),
so the final printout keeps true A4 proportions instead of just filling
however many character cells happen to fit.
width becomes the printed *line count* after rotation (bounded by
max_lines, the page's real per-page line capacity) and its height
becomes each printed line's length (bounded by max_cols, the real
per-line character capacity). Both are measured directly against the
printer with pagetest.sh rather than derived from paper/pitch math,
which doesn't reliably match real-world margins.
"""
long_in = _A4_LONG_MM / _MM_PER_IN
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
return max_lines, max_cols
def box_dims_for(term_rows, term_cols, a4_target):
@ -216,7 +203,7 @@ def main(stdscr):
cfg = Config()
conv_flags = cfg.converter_flags()
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
)

View File

@ -13,7 +13,13 @@
# 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
# Your printer's real per-page capacity in the normal (portrait, unrotated)
# orientation, used to size the A4 viewfinder above and to know when a print
# job already fills the page (see txt2printr.sh). Paper geometry and stated
# 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]
# 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")"
[ -f "$dir/printer.conf" ] && source "$dir/printer.conf"
: "${PRINTER_A4:=false}"
: "${PRINTER_MAX_LINES:=64}"
infile="${1:-ascii-img.txt}"
# Source - https://stackoverflow.com/a/638980
@ -57,12 +59,23 @@ rotate90() {
' "$infile"
}
{
printf '\x1b\x40' # ESC @ - reset printer (ESC/P and IBM Proprinter compatible)
body() {
if [ "$PRINTER_A4" = "true" ]; then
rotate90 | sed 's/$/\r/' # CRLF line endings, needed for raw writes to avoid staircasing
rotate90
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
printf '\x0c' # form feed - eject the page
} > "$dev"