diff --git a/nomadnet/ui/textui/images/_selftest.py b/nomadnet/ui/textui/images/_selftest.py index e1755bb..ce132c4 100644 --- a/nomadnet/ui/textui/images/_selftest.py +++ b/nomadnet/ui/textui/images/_selftest.py @@ -255,7 +255,7 @@ def test_webp_conversion(): with os.fdopen(fd, "wb") as f: f.write(fixture) d = ImageData(webp_path) - check(not d.ok and "converted image too large" in d.error, + check(not d.ok and "Converted image too large" in d.error, "converted payload capped at %d MiB" % (MAX_CONVERTED_PAYLOAD_BYTES // (1024 * 1024))) os.unlink(webp_path) @@ -414,6 +414,43 @@ def test_widget_layout(image_path): check((c, r) == (27, 10), "height-only after reset -> (27, 10), got (%d, %d)" % (c, r)) w.close() + # native resolution specification ("n"): display at the image's own + # pixel size, quantized to the terminal cell grid. Cell = (1, 2), + # small fixture 64x32 -> native 64 cols, 16 rows. + from nomadnet.ui.textui.images import _webp + small_png = _webp._encode_png_rgba(bytes(64 * 32 * 4), 64, 32) + fd, small_path = tempfile.mkstemp(suffix=".png") + with os.fdopen(fd, "wb") as f: + f.write(small_png) + try: + wn = ImageWidget(small_path, width="n") + wn.render((80,)) # sets _ti_cols/_ti_rows + check((wn._ti_cols, wn._ti_rows) == (64, 16), + "width='n' displays native size (64, 16), got (%d, %d)" + % (wn._ti_cols, wn._ti_rows)) + wn.close() + hn = ImageWidget(small_path, height="n") + hn.render((80,)) + check((hn._ti_cols, hn._ti_rows) == (64, 16), + "height='n' displays native size (64, 16), got (%d, %d)" + % (hn._ti_cols, hn._ti_rows)) + hn.close() + bn = ImageWidget(small_path, width="n", height="n") + bn.render((80,)) + check((bn._ti_cols, bn._ti_rows) == (64, 16), + "width='n' height='n' displays native size (64, 16), got (%d, %d)" + % (bn._ti_cols, bn._ti_rows)) + bn.close() + big = ImageWidget(image_path, width="n") # 1280x960, overflows + c, r = big._display_size(80) + check((c, r) == (80, 30), + "native of a large image clamps to available width (80, 30), got (%d, %d)" % (c, r)) + big.close() + check(raises(lambda: ImageWidget(image_path, width="x"), ValueError), + "width='x' raises ValueError") + finally: + os.unlink(small_path) + # alignment: placement column of a narrowed image inside its canvas w = ImageWidget(image_path, width=40) # default center w.render((80,)) # sets _ti_cols/_ti_rows diff --git a/nomadnet/ui/textui/images/widget.py b/nomadnet/ui/textui/images/widget.py index 9c26f14..30dd3d0 100644 --- a/nomadnet/ui/textui/images/widget.py +++ b/nomadnet/ui/textui/images/widget.py @@ -33,6 +33,7 @@ def _check_align(value): def _check_size_spec(value, name): + # None = auto, int = absolute cells/rows, "NN%" = percent, "n" = native resolution if value is None: return None if isinstance(value, int): @@ -40,9 +41,10 @@ def _check_size_spec(value, name): return value if isinstance(value, str): + if value.lower() == "n": return "n" match = _SIZE_SPEC_RE.fullmatch(value) if not match or float(match.group(1)) <= 0: - raise ValueError("invalid %s specification %r (expected a positive integer or a percent string like '50%%')" % (name, value)) + raise ValueError("invalid %s specification %r (expected a positive integer, a percent string like '50%%' or 'n' for native resolution)" % (name, value)) return value raise TypeError("invalid type for %s (got %s)" % (name, type(value).__name__)) @@ -140,8 +142,8 @@ class ImageWidget(urwid.Widget): width = property( lambda self: self._ti_width, doc="""Display width specification: None (full width), a positive - int (columns) or a percent string ("NN%") of the available layout - width.""", + int (columns), a percent string ("NN%") of the available layout + width, or "n" for native resolution.""", ) @width.setter @@ -152,8 +154,8 @@ class ImageWidget(urwid.Widget): height = property( lambda self: self._ti_height, doc="""Display height specification: None (derived, aspect - preserving), a positive int (rows) or a percent string ("NN%") of - the visible page height.""", + preserving), a positive int (rows), a percent string ("NN%") of + the visible page height, or "n" for native resolution.""", ) @height.setter @@ -185,11 +187,13 @@ class ImageWidget(urwid.Widget): def _raw_width(): if w_spec is None: return float(maxcol) if isinstance(w_spec, int): return float(w_spec) + if w_spec == "n": return iw / cell_w return maxcol * _pct(w_spec) def _raw_height(): if h_spec is None: return None if isinstance(h_spec, int): return float(h_spec) + if h_spec == "n": return ih / cell_h columns, lines = _termlib.get_terminal_size() page = max(1, lines - UI_MARGIN) return page * _pct(h_spec)