MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
InkPress.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "GlyphAtlas.hpp"
5
7
8struct GlyphQuad;
9
10/**
11 * @brief Policy controlling TextBuffer reuse behaviour in repress().
12 */
13enum class RedrawPolicy : uint8_t {
14 Clip, ///< Replace content. Truncate to existing budget bounds if text exceeds them.
15 Fit ///< Replace content. Reallocate GPU texture if text exceeds existing budget.
16};
17
18/**
19 * @brief Result of an impress() call.
20 *
21 * Ok and Overflow are both success states from the caller's perspective:
22 * the text was composited in both cases. Overflow additionally signals that
23 * the vertical budget was exceeded, the texture was reallocated, and all
24 * previous content has been cleared. The caller is responsible for
25 * rebuilding the full accumulated string if continuity is required.
26 */
27enum class ImpressResult : uint8_t {
28 Ok, ///< Run composited at cursor. No GPU state change.
29 Overflow ///< Vertical budget exceeded. Texture reallocated. Previous content cleared.
30};
31
32/**
33 * @brief A colored byte range within the text passed to a composite call.
34 *
35 * start/end are byte offsets into that call's own text argument, matching
36 * GlyphQuad::byte_offset as produced by lay_out() for that same text, not
37 * offsets into any longer accumulated history. Quads whose byte_offset falls
38 * in [min(start,end), max(start,end)) are rasterized in this span's color
39 * instead of the call's base color. Spans may overlap; where they do, the
40 * later entry in the vector wins for the overlapping quads. Quads matched by
41 * no span keep the call's base color.
42 */
43struct StyleSpan {
44 size_t start;
45 size_t end;
46 glm::vec4 color;
47};
48
49/**
50 * @brief Construction parameters for press().
51 *
52 * Atlas and render bounds are fixed at press() time and owned by the
53 * resulting TextBuffer. repress() and impress() read them from the buffer
54 * and do not accept a PressParams argument.
55 *
56 * budget_h controls the pre-allocated texture height. When zero the
57 * framework applies a k_grow_height_multiplier heuristic over the initial
58 * content height to reduce early vertical reallocation under growing text.
59 * Set to a concrete value to skip the heuristic and allocate exactly.
60 *
61 * Width is always allocated to render_bounds.x, since the layout engine wraps
62 * at that boundary, so no content can ever exceed it and a narrower
63 * horizontal allocation serves no purpose.
64 */
66 /// @brief Glyph atlas to use. Null selects the TypeFaceFoundry default at call time.
67 GlyphAtlas* atlas { nullptr };
68
69 /// @brief RGBA color applied to all glyphs not covered by a span in `spans`.
70 glm::vec4 color { 1.F, 1.F, 1.F, 1.F };
71
72 /**
73 * @brief RGBA fill composited beneath every glyph. Default fully
74 * transparent, matching behavior before this field existed.
75 *
76 * Stored on the resulting TextBuffer (TextBuffer::set_background) at
77 * press() time, the same way atlas and render bounds persist:
78 * repress(TextBuffer&, ...) and impress() read it back automatically on
79 * every subsequent call, so a caller sets it once. The VKImage-returning
80 * overloads (press(text, render_bounds, params), repress(VKImage&, ...))
81 * have no buffer to persist it on, so those read background fresh from
82 * @p params on every call instead.
83 */
84 glm::vec4 background { 0.F, 0.F, 0.F, 0.F };
85
86 /// @brief Hard render bounds in pixels.
87 /// impress() wraps at x and returns Overflow when y is exhausted.
88 glm::uvec2 render_bounds { 1280, 720 };
89
90 /// @brief Initial vertical budget in pixels. Zero applies the grow heuristic.
91 uint32_t budget_h { 0 };
92
93 /// @brief Optional per-byte-range color overrides. Empty applies `color` uniformly.
94 std::vector<StyleSpan> spans;
95};
96
97/**
98 * @brief Write glyph quads into a caller-provided RGBA8 pixel buffer.
99 *
100 * Applies coverage-multiplied alpha blend per glyph cell, compositing over
101 * whatever is already in @p dst rather than overwriting it - a glyph's
102 * bounding box includes its own zero-coverage interior (the hole in an
103 * 'o' or 'e'), so composited blending is what keeps a pre-filled
104 * background intact under that hole instead of a low-coverage pixel
105 * punching it transparent. The destination buffer must be row-major RGBA8
106 * with stride == buf_w * 4 bytes. Quads that fall outside
107 * [0, buf_w) x [0, buf_h) are clipped per pixel.
108 *
109 * The typical usage pattern is:
110 * @code
111 * auto layout = lay_out(text, atlas, 0.F, 0.F, wrap_w);
112 * for (auto& q : layout.quads) { ... } // per-character transforms
113 * rasterize_quads(layout.quads, atlas, color, pixels, w, h);
114 * @endcode
115 *
116 * @param quads Quads produced by lay_out(), optionally mutated by the caller.
117 * @param atlas Source atlas for coverage bitmaps.
118 * @param color RGBA glyph color in [0, 1].
119 * @param dst Destination RGBA8 buffer. Stride is buf_w * 4 bytes.
120 * @param buf_w Buffer width in pixels.
121 * @param buf_h Buffer height in pixels.
122 */
123MAYAFLUX_API void rasterize_quads(
124 std::span<const GlyphQuad> quads,
125 GlyphAtlas& atlas,
126 glm::vec4 color,
127 uint8_t* dst,
128 uint32_t buf_w,
129 uint32_t buf_h);
130
131/**
132 * @brief Rasterize a mutated quad span into an existing TextBuffer.
133 *
134 * Clears the buffer's pixel region to target->get_background(), rasterizes
135 * @p quads via rasterize_quads(), and marks the buffer dirty for GPU
136 * upload. The scratch pixel buffer is thread-local and reused across
137 * calls, so no heap allocation occurs after the first call at a given
138 * buffer size.
139 *
140 * Typical usage:
141 * @code
142 * auto layout = Portal::Text::create_layout(text, 0.F, 0.F, wrap_w);
143 * // ... per-quad mutation ...
144 * Portal::Text::ink_quads(text_buf, layout->quads, color);
145 * @endcode
146 *
147 * @p quads' UV coordinates are only meaningful against the atlas they were
148 * laid out from. @p atlas null (the default) resolves to whatever the
149 * TypeFaceFoundry default atlas is *at this call*, not at layout time: if
150 * something else has called set_default_font() since, or the atlas has
151 * grown, a held LayoutResult's quads will sample the wrong texture. Pass
152 * the same atlas the quads were laid out against explicitly whenever it
153 * might not still be the current default by the time this runs.
154 *
155 * @param target TextBuffer to write into. Dimensions are read from the buffer.
156 * @param quads Quads produced by create_layout(), optionally mutated by the caller.
157 * @param color RGBA glyph color in [0, 1].
158 * @param atlas Atlas the quads' UVs are relative to. Null selects the
159 * TypeFaceFoundry default at call time.
160 */
161MAYAFLUX_API void ink_quads(
162 const std::shared_ptr<Buffers::TextBuffer>& target,
163 std::span<const GlyphQuad> quads,
164 glm::vec4 color,
165 GlyphAtlas* atlas = nullptr);
166
167/**
168 * @brief Composite a UTF-8 string into a new TextBuffer.
169 *
170 * The returned TextBuffer has width == params.render_bounds.x and height
171 * equal to either the explicit params.budget_h or the heuristic initial
172 * allocation, whichever is larger than the content height.
173 *
174 * @param text UTF-8 string to composite.
175 * @param params Construction parameters. Default produces a growing buffer
176 * at 1280x720 render bounds using the default atlas.
177 * @return Initialized TextBuffer, or nullptr on failure.
178 */
179[[nodiscard]] MAYAFLUX_API std::shared_ptr<Buffers::TextBuffer> press(
180 std::string_view text,
181 const PressParams& params = {});
182
183/**
184 * @brief Composite a UTF-8 string directly into a new GPU texture.
185 *
186 * No TextBuffer, no quad geometry, no registration required.
187 * The returned VKImage is RGBA8, shader-read layout, ready for binding
188 * into a FormaBuffer or any other descriptor slot.
189 *
190 * @param text UTF-8 string to composite.
191 * @param render_bounds Texture dimensions and wrap boundary in pixels.
192 * @param params atlas, color, budget_h. render_bounds field ignored.
193 * @param staging Persistent host-visible staging buffer to upload
194 * through. Sized by the caller beforehand (e.g. via
195 * Buffers::ensure_image_staging_capacity()) and passed
196 * again on every call for the same label/texture to
197 * avoid allocating a fresh one each time; nullptr
198 * falls back to an internal one-shot staging
199 * allocation (the prior behaviour).
200 * @return GPU-resident VKImage, or nullptr on failure.
201 */
202[[nodiscard]] MAYAFLUX_API std::shared_ptr<Core::VKImage> press(
203 std::string_view text,
204 glm::uvec2 render_bounds,
205 const PressParams& params = {},
206 const std::shared_ptr<Buffers::VKBuffer>& staging = nullptr);
207
208/**
209 * @brief Re-composite a UTF-8 string into an existing TextBuffer.
210 *
211 * Always clears the buffer to target->get_background() before compositing.
212 * Render bounds and atlas are read from the target buffer. When the target
213 * has a pre-allocated vertical budget the compositing bounds are the budget
214 * dimensions, so no VKImage rebuild occurs as long as content fits.
215 *
216 * @param target Existing TextBuffer to write into.
217 * @param text UTF-8 string to composite.
218 * @param color RGBA glyph color for any byte not covered by @p spans.
219 * @param policy Controls reallocation behaviour when text exceeds budget.
220 * @param spans Optional per-byte-range color overrides, offsets into @p text.
221 * @return True on success.
222 */
223MAYAFLUX_API bool repress(
224 const std::shared_ptr<Buffers::TextBuffer>& target,
225 std::string_view text,
226 glm::vec4 color = { 1.F, 1.F, 1.F, 1.F },
228 std::span<const StyleSpan> spans = {});
229
230/**
231 * @brief Re-composite a UTF-8 string into an existing GPU texture.
232 *
233 * Uploads in-place when content fits within the existing image dimensions.
234 * Reallocates and updates @p target when content height exceeds image height.
235 *
236 * @param target VKImage to update. May be replaced on reallocation.
237 * @param text UTF-8 string to composite.
238 * @param params render_bounds, atlas, color.
239 * @param staging Persistent host-visible staging buffer to upload through.
240 * Sized by the caller beforehand (e.g. via
241 * Buffers::ensure_image_staging_capacity()) and passed again
242 * on every call for the same @p target to avoid allocating a
243 * fresh one each time; nullptr falls back to an internal
244 * one-shot staging allocation (the prior behaviour).
245 * @return True on success.
246 */
247MAYAFLUX_API bool repress(
248 std::shared_ptr<Core::VKImage>& target,
249 std::string_view text,
250 const PressParams& params = {},
251 const std::shared_ptr<Buffers::VKBuffer>& staging = nullptr);
252
253/**
254 * @brief Append a UTF-8 string into an existing TextBuffer at the current cursor.
255 *
256 * Does not clear existing content. Composites the new run into the
257 * pre-allocated budget region and advances the cursor. No VKImage
258 * reallocation occurs while the run fits within the vertical budget.
259 *
260 * When the run would push the cursor past the vertical budget the texture
261 * is grown by k_grow_height_multiplier, the full accumulated text is
262 * recomposited against target->get_background(), and ImpressResult::Overflow
263 * is returned. The caller is responsible for rebuilding prior content after
264 * an overflow if accumulated state is not sufficient.
265 *
266 * When the cursor would exceed render_bounds_h the run is rejected and
267 * ImpressResult::Overflow is returned without reallocation.
268 *
269 * The atlas is always the TypeFaceFoundry default. If a non-default atlas
270 * was used at press() time, impress() will use the default instead. Mixing
271 * atlases on the same TextBuffer is undefined behaviour.
272 *
273 * @p spans is offsets into this call's @p text, matching the byte_offset
274 * lay_out() assigns for this run. On the Overflow-and-regrow path the full
275 * accumulated text is recomposited using @p color and @p spans from this
276 * call alone; prior runs' own colors/spans are not retained, the same
277 * pre-existing limitation @p color already has on that path.
278 *
279 * @param target Existing TextBuffer to append into.
280 * @param text UTF-8 string to composite.
281 * @param color RGBA glyph color for any byte not covered by @p spans.
282 * @param spans Optional per-byte-range color overrides, offsets into @p text.
283 * @return ImpressResult::Ok or ImpressResult::Overflow.
284 */
285MAYAFLUX_API ImpressResult impress(
286 const std::shared_ptr<Buffers::TextBuffer>& target,
287 std::string_view text,
288 glm::vec4 color = { 1.F, 1.F, 1.F, 1.F },
289 std::span<const StyleSpan> spans = {});
290
291} // namespace MayaFlux::Portal::Text
glm::vec3 color
Definition Decoder.cpp:97
uint32_t * dst
Rasterizes and packs glyphs from a FontFace into a TextureContainer.
void rasterize_quads(std::span< const GlyphQuad > quads, GlyphAtlas &atlas, glm::vec4 color, uint8_t *dst, uint32_t buf_w, uint32_t buf_h)
Write glyph quads into a caller-provided RGBA8 pixel buffer.
Definition InkPress.cpp:244
void ink_quads(const std::shared_ptr< Buffers::TextBuffer > &target, std::span< const GlyphQuad > quads, glm::vec4 color, GlyphAtlas *atlas_hint)
Rasterize a mutated quad span into an existing TextBuffer.
Definition InkPress.cpp:311
RedrawPolicy
Policy controlling TextBuffer reuse behaviour in repress().
Definition InkPress.hpp:13
@ Fit
Replace content. Reallocate GPU texture if text exceeds existing budget.
@ Clip
Replace content. Truncate to existing budget bounds if text exceeds them.
bool repress(const std::shared_ptr< Buffers::TextBuffer > &target, std::string_view text, glm::vec4 color, RedrawPolicy policy, std::span< const StyleSpan > spans)
Re-composite a UTF-8 string into an existing TextBuffer.
Definition InkPress.cpp:433
ImpressResult
Result of an impress() call.
Definition InkPress.hpp:27
@ Overflow
Vertical budget exceeded. Texture reallocated. Previous content cleared.
@ Ok
Run composited at cursor. No GPU state change.
ImpressResult impress(const std::shared_ptr< Buffers::TextBuffer > &target, std::string_view text, glm::vec4 color, std::span< const StyleSpan > spans)
Append a UTF-8 string into an existing TextBuffer at the current cursor.
Definition InkPress.cpp:582
std::shared_ptr< Buffers::TextBuffer > press(std::string_view text, const PressParams &params)
Composite a UTF-8 string into a new TextBuffer.
Definition InkPress.cpp:344
GlyphAtlas * atlas
Glyph atlas to use. Null selects the TypeFaceFoundry default at call time.
Definition InkPress.hpp:67
glm::vec4 color
RGBA color applied to all glyphs not covered by a span in spans.
Definition InkPress.hpp:70
uint32_t budget_h
Initial vertical budget in pixels. Zero applies the grow heuristic.
Definition InkPress.hpp:91
std::vector< StyleSpan > spans
Optional per-byte-range color overrides. Empty applies color uniformly.
Definition InkPress.hpp:94
glm::uvec2 render_bounds
Hard render bounds in pixels.
Definition InkPress.hpp:88
glm::vec4 background
RGBA fill composited beneath every glyph.
Definition InkPress.hpp:84
Construction parameters for press().
Definition InkPress.hpp:65
A colored byte range within the text passed to a composite call.
Definition InkPress.hpp:43