Commit graph

2 commits

Author SHA1 Message Date
Ryan Turner
64ec7a1d70 gfx: add gfx_printBufferClipped
Adds gfx_printBufferClipped: a variant of gfx_printBuffer that accepts
an explicit right-edge limit (max_x) and a vertical clip window
[clip_top_y, clip_bot_y].  Word-wrap fires when the next glyph would
exceed max_x.  Pixels outside the clip window are suppressed without
affecting layout, so a negative start.y can be used to implement smooth
text scrolling.

gfx_printBuffer now delegates entirely to gfx_printBufferClipped with
clip_top_y=0 and clip_bot_y=CONFIG_SCREEN_HEIGHT-1.  The old pixel guard
used strict > 0 comparisons, which excluded pixels at row 0 and column 0.
The new implementation renders those pixels; callers that relied on the
old exclusion will see a behaviour change.

Compared to a naive port of gfx_printBuffer, this implementation
addresses several correctness issues:

- Control characters '\n' and '\r' are checked before the glyph array
  is indexed, avoiding undefined behaviour on fonts whose first codepoint
  is at or above 0x20 (e.g. TomThumb, first=0x20), since '\n' (0x0A)
  would produce a negative index.

- get_line_size uses <= instead of < when accumulating glyph widths so
  that a glyph whose xAdvance exactly equals max_width is counted.  The
  render loop uses strict > to trigger wrap, so the old < mismatch caused
  text_size.x to be under-reported by one glyph whenever a line was an
  exact fit, and misplaced CENTER/RIGHT lines in that case.

- For TEXT_ALIGN_LEFT, line_size is recomputed on every '\n' before the
  max_line_size comparison.  Without this, max_line_size was stuck at the
  first-line width for the entire call, so text_size.x was wrong whenever
  a subsequent line was wider.

- saved_start_y is int16_t to match point_t.y, allowing correct
  text_size.y computation when start.y is negative (scroll offset).

- text_size.y is computed as (start.y - saved_start_y) + line_h,
  matching the sign fix introduced for gfx_printBuffer.

- Pixels above row 0 (py < 0) are suppressed before the uint16_t cast
  passed to gfx_setPixel, preventing a silent wrap to a large row index.

- max_line_size tracks the widest line across all line breaks and wraps;
  text_size.x reflects the widest line, not just the last.

Extend tests/unit/gfx_text.cpp with return-value tests covering:
single-line height, two-line height delta equals yAdvance, negative
start.y scroll offset, negative clip_top_y, exact-fit line width
(get_line_size <= boundary), max line width from a wider first line,
max line width from a wider second line (TEXT_ALIGN_LEFT regression),
glyph at clip_bot_y not skipped, and TEXT_ALIGN_RIGHT per-line reset.

Co-authored-by: GitHub Copilot <copilot@github.com>
Signed-off-by: Ryan Turner <ryan@turnrye.com>
2026-07-25 21:11:18 +02:00
Ryan Turner
9e472454a1 gfx: add gfx_measureText, accept max_width in get_line_size
Refactor the static helper get_line_size to accept an explicit max_width
parameter instead of the hardcoded CONFIG_SCREEN_WIDTH cap.  All three
call sites in gfx_printBuffer are updated to pass CONFIG_SCREEN_WIDTH,
preserving existing behaviour.

Add gfx_measureText: a pure-computation function that simulates the same
word-wrap pass as gfx_printBufferClipped (to be added next) without
touching the display.  It returns the total pixel height of the laid-out
text block and accepts a char_count limit so callers can compute the
y-coordinate of an arbitrary cursor position for scroll-offset
calculations.

Add tests/unit/gfx_text.cpp and register the gfx_text_test executable in
meson.build.  The test suite covers single-line strings, multi-line
strings via explicit newlines, char_count truncation, and word-wrap
height growth.

Co-authored-by: GitHub Copilot <copilot@github.com>
Signed-off-by: Ryan Turner <ryan@turnrye.com>
2026-07-25 21:11:15 +02:00