fix(render,input): guard the zoom gain, clip annotation captions, no-throw dump
Three findings reachable by ordinary use: * `Controller::axis_zoom()` divided by `Plot::ui_scale()` directly, though `Style::device()` documents a non-positive scale as host state that must be tolerated. With `ui_scale == 0` and a negative drag delta, `logical_dy` was `-inf`, the factor `+inf`, and the guard `!(factor > 0.0)` did not reject it: `zoom_x_axis`/`zoom_y_axis` computed `anchor + (bound - anchor) * inf`, and `set_y_range()` stored it verbatim and cleared `auto_scale` -- an axis left non-finite until an auto-fit. A negative scale silently inverted the zoom. A scale that is not positive and finite now means no zoom at all. * `draw_annotations()` stages its caption into the frame's single text batch, which `flush_text()` draws *after* `clear_clip()`, so a caption staged while the axes clip was engaged was rasterised with the viewport clip. An annotation anchored outside the visible range kept a caption with no cross, and in a `Figure` -- whose rows tile the figure -- that caption painted into the neighbouring row. A caption whose anchor is outside `axes` is now skipped with its cross, rather than splitting the frame's one text draw call (which would cost a draw call per row and mis-clip the labels staged outside `axes`). * `dump_frame_ppm()` was the one public entry point not `noexcept`, so a `std::bad_alloc` crossed the API boundary. Its buffers are now sized before anything is on disk and a failure is reported as `ErrorCode::OutOfMemory`, exactly as `TrueTypeFont::from_file()` reports it. Tests: `ui_scale` 0 and -1 leave the axis range finite and unchanged with `auto_scale` intact; an annotation anchored outside a bottom figure row's y range leaves no caption ink in the row above, while an in-range one still draws; a `static_assert` pins the dump's `noexcept`.
This commit is contained in:
1 parent
9aff06acee
commit
2fbd1de7d4
5 files changed
+188
-19
No files matched your search
@@ -284,15 +284,14 @@ class Renderer {
|
||||
/// Writes the framebuffer's viewport region as a binary PPM (P6, 8 bits per
|
||||
/// channel, rows top-down) so a headless render can be looked at without a
|
||||
/// window. Reads the framebuffer that is bound now, which is the one the frame
|
||||
/// was drawn into. Returns false, with `st` filled in, on a bad argument or an
|
||||
/// unreadable pixel.
|
||||
///
|
||||
/// The one entry point here that is not `noexcept`: it sizes its readback hole
|
||||
/// and its output row, so an allocation failure propagates `std::bad_alloc`
|
||||
/// rather than terminating a process that could have carried on without a
|
||||
/// screenshot. A dump is a diagnostic, and no `Status` code could say more than
|
||||
/// the exception already does.
|
||||
bool dump_frame_ppm(const char* path, Status* st = nullptr) const;
|
||||
/// was drawn into. Returns false, with `st` filled in, on a bad argument, an
|
||||
/// unreadable pixel, or an allocation the readback could not make -- which is
|
||||
/// reported as `ErrorCode::OutOfMemory`, exactly as `TrueTypeFont::from_file()`
|
||||
/// reports the same failure. `noexcept` like every other entry point here: the
|
||||
/// buffers are sized before anything is on disk, so the two failures that can
|
||||
/// arise from sizing them are a `Status`, not a `std::bad_alloc` across the API
|
||||
/// boundary and a half-written file.
|
||||
bool dump_frame_ppm(const char* path, Status* st = nullptr) const noexcept;
|
||||
|
||||
private:
|
||||
/// The backend the plot layer sees, at the size the text pipeline draws it:
|
||||
|
||||
Reference in new issue
Block a user