Harden screen rendering
- SHA
883069f8b736cffb013b625da8fed7b9b6876247- Parents
-
7946628 - Tree
9ebdb03
883069f
883069f8b736cffb013b625da8fed7b9b68762477946628
9ebdb03| Status | File | + | - |
|---|---|---|---|
| M |
README.md
|
4 | 1 |
| M |
src/fgof_screen.f90
|
16 | 3 |
| M |
test/test_screen_render_ansi.f90
|
3 | 2 |
| M |
test/test_screen_render_edges.f90
|
16 | 0 |
README.mdmodified@@ -38,6 +38,8 @@ Tracked today: | ||
| 38 | 38 | - explicit cursor-state ANSI output |
| 39 | 39 | - tracked example programs for full-frame and diff rendering |
| 40 | 40 | - render-edge coverage for empty screens and cursor-only diffs |
| 41 | +- full-frame ANSI output with explicit row addressing | |
| 42 | +- render-time sanitization for non-printable control glyphs | |
| 41 | 43 | - CI on macOS and Ubuntu, including direct example execution |
| 42 | 44 | - row-first, column-second grid semantics pinned down in tests |
| 43 | 45 | - focused scaffold coverage in `fpm test` |
@@ -92,10 +94,11 @@ Current semantics: | ||
| 92 | 94 | - `diff_screen(previous, current)` compares two virtual frames and reports cell damage separately from size or cursor changes |
| 93 | 95 | - `screen_diff%damage` uses one bounding rectangle plus a `changed_cells` count for the changed frame area |
| 94 | 96 | - newly added or removed visible cells from resizes count as changed damage even when their glyphs are blank |
| 95 | -- `render_screen_ansi()` emits a whole-frame ANSI repaint with clear/home semantics and final cursor restoration | |
| 97 | +- `render_screen_ansi()` emits a whole-frame ANSI repaint with clear/home semantics, explicit row addressing, and final cursor restoration | |
| 96 | 98 | - `render_screen_diff_ansi()` emits a damage-scoped ANSI repaint and uses blank cells to erase removed content after shrinks |
| 97 | 99 | - `render_cursor_ansi()` emits the final cursor move plus visibility state for a screen buffer |
| 98 | 100 | - ANSI row rendering tracks style transitions explicitly and resets back to default style when needed |
| 101 | +- ANSI renderers replace ASCII control glyphs with `?` so screen cells cannot inject raw control bytes into the output stream | |
| 99 | 102 | |
| 100 | 103 | ## Build And Test |
| 101 | 104 | |
src/fgof_screen.f90modified@@ -247,8 +247,8 @@ contains | ||
| 247 | 247 | |
| 248 | 248 | if (allocated(buffer%cells)) then |
| 249 | 249 | do row = 1, size(buffer%cells, 1) |
| 250 | + output = output // move_cursor_ansi(row, 1) | |
| 250 | 251 | output = output // render_current_row_ansi(buffer, row, 1, size(buffer%cells, 2)) |
| 251 | - if (row < size(buffer%cells, 1)) output = output // new_line("a") | |
| 252 | 252 | end do |
| 253 | 253 | end if |
| 254 | 254 | |
@@ -415,7 +415,7 @@ contains | ||
| 415 | 415 | end if |
| 416 | 416 | current_key = cell_key |
| 417 | 417 | end if |
| 418 | - output = output // cell%glyph | |
| 418 | + output = output // renderable_glyph(cell%glyph) | |
| 419 | 419 | end do |
| 420 | 420 | |
| 421 | 421 | if (len(current_key) > 0) then |
@@ -449,7 +449,7 @@ contains | ||
| 449 | 449 | end if |
| 450 | 450 | current_key = cell_key |
| 451 | 451 | end if |
| 452 | - output = output // cell%glyph | |
| 452 | + output = output // renderable_glyph(cell%glyph) | |
| 453 | 453 | end do |
| 454 | 454 | |
| 455 | 455 | if (len(current_key) > 0) then |
@@ -567,4 +567,17 @@ contains | ||
| 567 | 567 | text = trim(scratch) |
| 568 | 568 | end function integer_text |
| 569 | 569 | |
| 570 | + function renderable_glyph(glyph) result(output) | |
| 571 | + character(len=1), intent(in) :: glyph | |
| 572 | + character(len=1) :: output | |
| 573 | + integer :: code | |
| 574 | + | |
| 575 | + code = iachar(glyph) | |
| 576 | + if (code < 32 .or. code == 127) then | |
| 577 | + output = "?" | |
| 578 | + else | |
| 579 | + output = glyph | |
| 580 | + end if | |
| 581 | + end function renderable_glyph | |
| 582 | + | |
| 570 | 583 | end module fgof_screen |
test/test_screen_render_ansi.f90modified@@ -28,9 +28,10 @@ program test_screen_render_ansi | ||
| 28 | 28 | call set_cursor(previous, 2, 1) |
| 29 | 29 | |
| 30 | 30 | expected = esc // "?25l" // esc // "2J" // esc // "H" // & |
| 31 | - "AB" // new_line("a") // "CD" // esc // "0m" // esc // "2;1H" // esc // "?25h" | |
| 31 | + esc // "1;1H" // "AB" // esc // "2;1H" // "CD" // & | |
| 32 | + esc // "0m" // esc // "2;1H" // esc // "?25h" | |
| 32 | 33 | rendered = render_screen_ansi(previous) |
| 33 | - if (rendered /= expected) error stop "render_screen_ansi should render a full blank-style frame deterministically" | |
| 34 | + if (rendered /= expected) error stop "render_screen_ansi should use explicit row addressing for full-frame repaints" | |
| 34 | 35 | |
| 35 | 36 | style = clear_screen_style() |
| 36 | 37 | style%fg = 33 |
test/test_screen_render_edges.f90modified@@ -1,6 +1,7 @@ | ||
| 1 | 1 | program test_screen_render_edges |
| 2 | 2 | use fgof_screen, only : & |
| 3 | 3 | allocate_screen, & |
| 4 | + put_glyph, & | |
| 4 | 5 | render_screen_ansi, & |
| 5 | 6 | render_screen_diff_ansi, & |
| 6 | 7 | set_cursor |
@@ -35,4 +36,19 @@ program test_screen_render_edges | ||
| 35 | 36 | rendered = render_screen_diff_ansi(previous, current) |
| 36 | 37 | expected = esc // "?25l" |
| 37 | 38 | if (rendered /= expected) error stop "cursor-visibility-only diffs should render only the visibility change" |
| 39 | + | |
| 40 | + current = allocate_screen(2, 1) | |
| 41 | + call put_glyph(current, 1, 1, achar(27)) | |
| 42 | + call put_glyph(current, 1, 2, new_line("a")) | |
| 43 | + rendered = render_screen_ansi(current) | |
| 44 | + expected = esc // "?25l" // esc // "2J" // esc // "H" // & | |
| 45 | + esc // "1;1H" // "??" // esc // "0m" // esc // "1;1H" // esc // "?25h" | |
| 46 | + if (rendered /= expected) error stop "full renders should sanitize control glyphs before emitting ANSI output" | |
| 47 | + | |
| 48 | + previous = allocate_screen(2, 1) | |
| 49 | + current = previous | |
| 50 | + call put_glyph(current, 1, 2, achar(9)) | |
| 51 | + rendered = render_screen_diff_ansi(previous, current) | |
| 52 | + expected = esc // "?25l" // esc // "1;2H" // "?" // esc // "0m" // esc // "1;1H" // esc // "?25h" | |
| 53 | + if (rendered /= expected) error stop "diff renders should sanitize control glyphs before emitting ANSI output" | |
| 38 | 54 | end program test_screen_render_edges |