MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
InkPress.cpp
Go to the documentation of this file.
1#include "InkPress.hpp"
2
3#include "TypeFaceFoundry.hpp"
4#include "TypeSetter.hpp"
5
9
11
13
14namespace MayaFlux::Portal::Text {
15
16namespace {
17
18 constexpr uint32_t k_grow_height_multiplier = 8;
19
20 /**
21 * @brief Pixel-raster result of a single composite pass.
22 *
23 * w is always the render bounds width (== the wrap boundary).
24 * h is the content height: the row count actually written.
25 * cursor_x is the pen x position after the last glyph, for cursor seeding.
26 * pixels is row-major RGBA8, stride == w * 4.
27 */
28 struct CompositeResult {
29 uint32_t h {};
30 uint32_t cursor_x {};
31 uint32_t cursor_y {};
32 std::vector<uint8_t> pixels;
33 };
34
35 /**
36 * @brief Rasterize quads in per-span colors, falling back to a base color.
37 *
38 * Empty @p spans is the flat-color fast path (one rasterize_quads call).
39 * Otherwise each span is rasterized as its own subset (quads whose
40 * byte_offset falls in [min(start,end), max(start,end))) in that
41 * span's color; later spans in the vector paint over earlier ones where
42 * ranges overlap, since rasterize_quads writes pixels rather than
43 * blending. Quads matched by no span are rasterized last in the base
44 * color, so spans always take visual precedence over the fallback.
45 */
46 void rasterize_styled(
47 std::span<const GlyphQuad> quads,
48 GlyphAtlas& atlas,
49 glm::vec4 base_color,
50 std::span<const StyleSpan> spans,
51 uint8_t* dst,
52 uint32_t buf_w,
53 uint32_t buf_h)
54 {
55 if (spans.empty()) {
56 rasterize_quads(quads, atlas, base_color, dst, buf_w, buf_h);
57 return;
58 }
59
60 std::vector<bool> covered(quads.size(), false);
61 std::vector<GlyphQuad> bucket;
62
63 for (const auto& span : spans) {
64 const size_t lo = std::min(span.start, span.end);
65 const size_t hi = std::max(span.start, span.end);
66
67 bucket.clear();
68 for (size_t i = 0; i < quads.size(); ++i) {
69 if (quads[i].byte_offset >= lo && quads[i].byte_offset < hi) {
70 bucket.push_back(quads[i]);
71 covered[i] = true;
72 }
73 }
74 if (!bucket.empty()) {
75 rasterize_quads(bucket, atlas, span.color, dst, buf_w, buf_h);
76 }
77 }
78
79 bucket.clear();
80 for (size_t i = 0; i < quads.size(); ++i) {
81 if (!covered[i]) {
82 bucket.push_back(quads[i]);
83 }
84 }
85 if (!bucket.empty()) {
86 rasterize_quads(bucket, atlas, base_color, dst, buf_w, buf_h);
87 }
88 }
89
90 /**
91 * @brief Fill an existing RGBA8 buffer in place with a solid color.
92 *
93 * @p color's default, fully transparent black, writes bytes identical
94 * to a zero-initialized buffer, so every call site converted to this
95 * helper with its background defaulted keeps prior behavior exactly.
96 */
97 void fill_bytes(uint8_t* dst, size_t byte_count, glm::vec4 color)
98 {
99 const auto r = static_cast<uint8_t>(std::clamp(color.r, 0.F, 1.F) * 255.F);
100 const auto g = static_cast<uint8_t>(std::clamp(color.g, 0.F, 1.F) * 255.F);
101 const auto b = static_cast<uint8_t>(std::clamp(color.b, 0.F, 1.F) * 255.F);
102 const auto a = static_cast<uint8_t>(std::clamp(color.a, 0.F, 1.F) * 255.F);
103
104 for (size_t i = 0; i + 3 < byte_count; i += 4) {
105 dst[i + 0] = r;
106 dst[i + 1] = g;
107 dst[i + 2] = b;
108 dst[i + 3] = a;
109 }
110 }
111
112 /**
113 * @brief Allocate a fresh RGBA8 pixel buffer pre-filled with a solid color.
114 */
115 std::vector<uint8_t> make_filled(size_t byte_count, glm::vec4 color)
116 {
117 std::vector<uint8_t> buf(byte_count);
118 fill_bytes(buf.data(), byte_count, color);
119 return buf;
120 }
121
122 /**
123 * @brief Resolve atlas pointer: use provided atlas or fall back to foundry default.
124 * @return Non-null pointer on success, nullptr if no atlas is available.
125 */
126 GlyphAtlas* resolve_atlas(GlyphAtlas* hint)
127 {
128 if (hint) {
129 return hint;
130 }
132 if (!def) {
134 "InkPress: no atlas available, call set_default_font first");
135 }
136 return def;
137 }
138
139 /**
140 * @brief Lay out and rasterize text into a fresh pixel buffer.
141 *
142 * Always lays out from pen origin (0, 0). The pixel buffer width is
143 * exactly buf_w (== render bounds width). Content that wraps beyond
144 * buf_h is clipped by composite_into().
145 *
146 * @param text UTF-8 string.
147 * @param atlas Source atlas.
148 * @param color Base glyph color for any byte not covered by @p spans.
149 * @param buf_w Buffer width and wrap boundary in pixels.
150 * @param buf_h Buffer height (allocated budget) in pixels.
151 * @param spans Optional per-byte-range color overrides, offsets into @p text.
152 * @param background Fill composited beneath the glyphs. Default fully transparent.
153 * @return CompositeResult, or nullopt when no glyphs are produced.
154 */
155 std::optional<CompositeResult> composite(
156 std::string_view text,
157 GlyphAtlas& atlas,
158 glm::vec4 color,
159 uint32_t buf_w,
160 uint32_t buf_h,
161 float pen_y_start = 0.F,
162 std::span<const StyleSpan> spans = {},
163 glm::vec4 background = { 0.F, 0.F, 0.F, 0.F })
164 {
165 const LayoutResult layout = lay_out(text, atlas, 0.F, pen_y_start, buf_w);
166
167 const auto content_h = static_cast<uint32_t>(std::ceil(layout.final_pen_y))
168 + atlas.line_height();
169 const uint32_t dst_h = std::min(content_h, buf_h);
170
171 if (buf_w == 0 || dst_h == 0) {
172 return std::nullopt;
173 }
174
175 CompositeResult result;
176 result.h = content_h;
177 result.cursor_x = static_cast<uint32_t>(std::ceil(layout.final_pen_x));
178 result.cursor_y = static_cast<uint32_t>(std::ceil(layout.final_pen_y));
179 result.pixels = make_filled(static_cast<size_t>(buf_w) * dst_h * 4, background);
180
181 rasterize_styled(layout.quads, atlas, color, spans, result.pixels.data(), buf_w, dst_h);
182
183 return result;
184 }
185
186 /**
187 * @brief Allocate a TextBuffer from a CompositeResult.
188 *
189 * The GPU texture is always buf_w x budget_h. Content rows from result
190 * are copied in; remaining rows are filled with @p background. The
191 * buffer's own background is set to @p background so repress()/impress()
192 * pick it up automatically on later calls.
193 *
194 * @param result Output of composite().
195 * @param buf_w Texture width == render bounds width.
196 * @param budget_h Allocated texture height (>= result.h).
197 * @param render_bounds Hard render bounds stored on the buffer.
198 * @param text Accumulated text string seeded on the buffer.
199 * @param background Fill for rows beyond the composited content.
200 */
201 std::shared_ptr<Buffers::TextBuffer> make_buffer(
202 const CompositeResult& result,
203 uint32_t buf_w,
204 uint32_t budget_h,
205 glm::uvec2 render_bounds,
206 std::string_view text,
207 glm::vec4 background)
208 {
209 budget_h = std::max(budget_h, result.h);
210
211 const size_t budget_bytes = static_cast<size_t>(buf_w) * budget_h * 4;
212 std::vector<uint8_t> pixels = make_filled(budget_bytes, background);
213
214 const uint32_t copy_h = std::min(result.h,
215 static_cast<uint32_t>(result.pixels.size() / (static_cast<size_t>(buf_w) * 4)));
216 for (uint32_t row = 0; row < copy_h; ++row) {
217 std::memcpy(
218 pixels.data() + static_cast<size_t>(row) * buf_w * 4,
219 result.pixels.data() + static_cast<size_t>(row) * buf_w * 4,
220 static_cast<size_t>(buf_w) * 4);
221 }
222
223 auto buf = std::make_shared<Buffers::TextBuffer>(
224 buf_w, budget_h,
225 Portal::Graphics::ImageFormat::RGBA8,
226 pixels.data());
227
228 buf->set_budget(buf_w, budget_h);
229 buf->set_render_bounds(render_bounds.x, render_bounds.y);
230 buf->set_background(background);
231 buf->set_accumulated_text(text);
232 buf->get_cursor_x() = result.cursor_x;
233 buf->get_cursor_y() = result.cursor_y;
234
236 "InkPress: {}x{} content in {}x{} texture, render bounds {}x{}",
237 buf_w, result.h, buf_w, budget_h, render_bounds.x, render_bounds.y);
238
239 return buf;
240 }
241
242} // namespace
243
245 std::span<const GlyphQuad> quads,
246 GlyphAtlas& atlas,
247 glm::vec4 color,
248 uint8_t* dst,
249 uint32_t buf_w,
250 uint32_t buf_h)
251{
252 const uint8_t cr = static_cast<uint8_t>(std::clamp(color.r, 0.F, 1.F) * 255.F);
253 const uint8_t cg = static_cast<uint8_t>(std::clamp(color.g, 0.F, 1.F) * 255.F);
254 const uint8_t cb = static_cast<uint8_t>(std::clamp(color.b, 0.F, 1.F) * 255.F);
255 const uint8_t ca = static_cast<uint8_t>(std::clamp(color.a, 0.F, 1.F) * 255.F);
256
257 const Kakshya::TextureContainer& atlas_tex = atlas.texture();
258 const std::span<const uint8_t> atlas_pixels = atlas_tex.pixel_bytes(0);
259 const uint32_t atlas_size = atlas.atlas_size();
260
261 for (const auto& q : quads) {
262 const auto gx = static_cast<int32_t>(std::floor(q.x0));
263 const auto gy = static_cast<int32_t>(std::floor(q.y0));
264 const auto gw = static_cast<uint32_t>(std::ceil(q.x1 - q.x0));
265 const auto gh = static_cast<uint32_t>(std::ceil(q.y1 - q.y0));
266
267 const auto src_x = static_cast<uint32_t>(q.uv_x0 * static_cast<float>(atlas_size));
268 const auto src_y = static_cast<uint32_t>(q.uv_y0 * static_cast<float>(atlas_size));
269
270 for (uint32_t row = 0; row < gh; ++row) {
271 const int32_t dst_row = gy + static_cast<int32_t>(row);
272 if (dst_row < 0 || static_cast<uint32_t>(dst_row) >= buf_h) {
273 continue;
274 }
275
276 const uint8_t* src_row = atlas_pixels.data()
277 + static_cast<size_t>(src_y + row) * atlas_size + src_x;
278 uint8_t* dst_row_ptr = dst + static_cast<size_t>(dst_row) * buf_w * 4;
279
280 for (uint32_t col = 0; col < gw; ++col) {
281 const int32_t dst_col = gx + static_cast<int32_t>(col);
282 if (dst_col < 0 || static_cast<uint32_t>(dst_col) >= buf_w) {
283 continue;
284 }
285
286 const uint8_t coverage = src_row[col];
287 const uint32_t src_a = (static_cast<uint32_t>(coverage) * static_cast<uint32_t>(ca)) / 255U;
288
289 uint8_t* px = dst_row_ptr + static_cast<size_t>(dst_col) * 4;
290
291 const uint32_t dst_a = px[3];
292 const uint32_t blended_dst_a = (dst_a * (255U - src_a)) / 255U;
293 const uint32_t out_a = src_a + blended_dst_a;
294
295 if (out_a == 0U) {
296 px[0] = 0;
297 px[1] = 0;
298 px[2] = 0;
299 px[3] = 0;
300 } else {
301 px[0] = static_cast<uint8_t>((cr * src_a + px[0] * blended_dst_a) / out_a);
302 px[1] = static_cast<uint8_t>((cg * src_a + px[1] * blended_dst_a) / out_a);
303 px[2] = static_cast<uint8_t>((cb * src_a + px[2] * blended_dst_a) / out_a);
304 px[3] = static_cast<uint8_t>(out_a);
305 }
306 }
307 }
308 }
309}
310
312 const std::shared_ptr<Buffers::TextBuffer>& target,
313 std::span<const GlyphQuad> quads,
314 glm::vec4 color,
315 GlyphAtlas* atlas_hint)
316{
317 if (!target) {
319 "ink_quads: target buffer is null");
320 return;
321 }
322
323 GlyphAtlas* atlas = resolve_atlas(atlas_hint);
324 if (!atlas) {
325 return;
326 }
327
328 const uint32_t buf_w = target->get_budget_width();
329 const uint32_t buf_h = target->get_budget_height();
330 const size_t buf_bytes = static_cast<size_t>(buf_w) * buf_h * 4;
331
332 thread_local std::vector<uint8_t> pixels;
333 pixels.resize(buf_bytes);
334 fill_bytes(pixels.data(), buf_bytes, target->get_background());
335
336 rasterize_quads(quads, *atlas, color, pixels.data(), buf_w, buf_h);
337 target->set_pixel_data(pixels.data(), buf_bytes);
338}
339
340// =========================================================================
341// press
342// =========================================================================
343
344std::shared_ptr<Buffers::TextBuffer> press(
345 std::string_view text,
346 const PressParams& params)
347{
348 GlyphAtlas* atlas = resolve_atlas(params.atlas);
349 if (!atlas) {
350 return nullptr;
351 }
352
353 const uint32_t buf_w = params.render_bounds.x;
354
355 const auto result = composite(text, *atlas, params.color, buf_w, params.render_bounds.y,
356 static_cast<float>(atlas->line_height()), params.spans, params.background);
357 if (!result) {
359 "press: no glyphs produced for '{}'", std::string(text));
360 return nullptr;
361 }
362
363 const uint32_t budget_h = params.budget_h > 0
364 ? std::max(params.budget_h, result->h)
365 : std::min(result->h * k_grow_height_multiplier, params.render_bounds.y);
366
367 return make_buffer(*result, buf_w, budget_h, params.render_bounds, text, params.background);
368}
369
370std::shared_ptr<Core::VKImage> press(
371 std::string_view text,
372 glm::uvec2 render_bounds,
373 const PressParams& params,
374 const std::shared_ptr<Buffers::VKBuffer>& staging)
375{
376 GlyphAtlas* atlas = resolve_atlas(params.atlas);
377 if (!atlas)
378 return nullptr;
379
380 const uint32_t buf_w = render_bounds.x;
381
382 const uint32_t composite_h = std::max(render_bounds.y,
383 static_cast<uint32_t>(atlas->line_height()));
384
385 const auto result = composite(text, *atlas, params.color, buf_w, composite_h,
386 static_cast<float>(atlas->ascender()), params.spans, params.background);
387
388 if (!result) {
390 "press(AsTexture): no glyphs produced for '{}'", std::string(text));
391 return nullptr;
392 }
393
394 const uint32_t budget_h = composite_h;
395
396 const size_t budget_bytes = static_cast<size_t>(buf_w) * budget_h * 4;
397 std::vector<uint8_t> pixels = make_filled(budget_bytes, params.background);
398
399 const auto copy_h = static_cast<uint32_t>(
400 result->pixels.size() / (static_cast<size_t>(buf_w) * 4));
401
402 for (uint32_t row = 0; row < copy_h; ++row) {
403 std::memcpy(
404 pixels.data() + static_cast<size_t>(row) * buf_w * 4,
405 result->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
406 static_cast<size_t>(buf_w) * 4);
407 }
408
410
411 std::shared_ptr<Core::VKImage> image;
412 if (staging) {
413 image = loom.create_2d_uninitialized(buf_w, budget_h, Portal::Graphics::ImageFormat::RGBA8, 1);
414 if (!image)
415 return nullptr;
416 loom.upload_data(image, pixels.data(), budget_bytes, staging, true);
417 } else {
418 image = loom.create_2d(buf_w, budget_h, Portal::Graphics::ImageFormat::RGBA8,
419 pixels.data(), 1);
420 }
421
423 "press(AsTexture): {}x{} content in {}x{} texture",
424 buf_w, result->h, buf_w, budget_h);
425
426 return image;
427}
428
429// =========================================================================
430// repress
431// =========================================================================
432
434 const std::shared_ptr<Buffers::TextBuffer>& target,
435 std::string_view text,
436 glm::vec4 color,
437 RedrawPolicy policy,
438 std::span<const StyleSpan> spans)
439{
440 if (!target) {
442 "repress: target buffer is null");
443 return false;
444 }
445
446 GlyphAtlas* atlas = resolve_atlas(nullptr);
447 if (!atlas) {
448 return false;
449 }
450
451 target->clear_accumulated_text();
452 target->reset_cursor();
453
454 const uint32_t buf_w = target->get_budget_width();
455 const uint32_t buf_h = target->get_budget_height();
456 const uint32_t bound_h = target->get_render_bounds_h();
457 const glm::vec4 background = target->get_background();
458
459 const auto result = composite(text, *atlas, color, buf_w, bound_h,
460 static_cast<float>(atlas->line_height()), spans, background);
461 if (!result) {
463 "repress: no glyphs produced for '{}'", std::string(text));
464 return false;
465 }
466
467 if (result->h > buf_h && policy == RedrawPolicy::Fit) {
468 const uint32_t new_h = std::min(result->h, bound_h);
469 target->resize_texture(buf_w, new_h);
470 target->set_budget(buf_w, new_h);
471 target->set_pixel_data(result->pixels.data(), result->pixels.size());
472 target->get_cursor_x() = result->cursor_x;
473 target->get_cursor_y() = result->cursor_y;
474 target->set_accumulated_text(text);
475
477 "repress(Fit): resized to {}x{}", buf_w, new_h);
478 return true;
479 }
480
481 const size_t buf_bytes = static_cast<size_t>(buf_w) * buf_h * 4;
482 std::vector<uint8_t> cleared = make_filled(buf_bytes, background);
483
484 const uint32_t copy_h = std::min(result->h, buf_h);
485 for (uint32_t row = 0; row < copy_h; ++row) {
486 std::memcpy(
487 cleared.data() + static_cast<size_t>(row) * buf_w * 4,
488 result->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
489 static_cast<size_t>(buf_w) * 4);
490 }
491
492 target->set_pixel_data(cleared.data(), buf_bytes);
493 target->get_cursor_x() = result->cursor_x;
494 target->get_cursor_y() = result->cursor_y;
495 // target->get_cursor_y() = result->h;
496 target->set_accumulated_text(text);
497
499 "repress(Clip): '{}' -> {}x{} into {}x{} budget",
500 std::string(text), buf_w, result->h, buf_w, buf_h);
501
502 return true;
503}
504
506 std::shared_ptr<Core::VKImage>& target,
507 std::string_view text,
508 const PressParams& params,
509 const std::shared_ptr<Buffers::VKBuffer>& staging)
510{
511 if (!target) {
513 "repress(VKImage): target is null");
514 return false;
515 }
516
517 GlyphAtlas* atlas = resolve_atlas(params.atlas);
518 if (!atlas)
519 return false;
520
521 const uint32_t buf_w = target->get_width();
522 const uint32_t buf_h = target->get_height();
523
524 const uint32_t composite_h = std::max(buf_h,
525 static_cast<uint32_t>(atlas->line_height()));
526
527 const auto result = composite(text, *atlas, params.color, buf_w, composite_h,
528 static_cast<float>(atlas->ascender()), params.spans, params.background);
529 if (!result) {
531 "repress(VKImage): no glyphs produced for '{}'", std::string(text));
532 return false;
533 }
534
536
537 const uint32_t new_h = composite_h;
538 const bool needs_realloc = composite_h != buf_h || buf_w != target->get_width();
539
540 const size_t buf_bytes = static_cast<size_t>(buf_w) * new_h * 4;
541 std::vector<uint8_t> pixels = make_filled(buf_bytes, params.background);
542
543 const uint32_t copy_h = std::min(result->h, new_h);
544 for (uint32_t row = 0; row < copy_h; ++row) {
545 std::memcpy(
546 pixels.data() + static_cast<size_t>(row) * buf_w * 4,
547 result->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
548 static_cast<size_t>(buf_w) * 4);
549 }
550
551 if (needs_realloc) {
552 target = loom.create_2d_uninitialized(buf_w, new_h, Portal::Graphics::ImageFormat::RGBA8, 1);
553 if (!target)
554 return false;
555 if (staging) {
556 loom.upload_data(target, pixels.data(), buf_bytes, staging, true);
557 } else {
558 loom.upload_data(target, pixels.data(), buf_bytes);
559 }
560
562 "repress(VKImage): reallocated {}x{}", buf_w, new_h);
563 return true;
564 }
565
566 if (staging) {
567 loom.upload_data(target, pixels.data(), buf_bytes, staging, true);
568 } else {
569 loom.upload_data(target, pixels.data(), buf_bytes);
570 }
571
573 "repress(VKImage): updated {}x{} in-place", buf_w, buf_h);
574
575 return true;
576}
577
578// =========================================================================
579// impress
580// =========================================================================
581
583 const std::shared_ptr<Buffers::TextBuffer>& target,
584 std::string_view text,
585 glm::vec4 color,
586 std::span<const StyleSpan> spans)
587{
588 if (!target) {
590 "impress: target buffer is null");
592 }
593
594 GlyphAtlas* atlas = resolve_atlas(nullptr);
595 if (!atlas) {
597 }
598
599 const uint32_t buf_w = target->get_budget_width();
600 const uint32_t buf_h = target->get_budget_height();
601 const uint32_t bound_h = target->get_render_bounds_h();
602 const auto pen_x = static_cast<float>(target->get_cursor_x());
603 const auto pen_y = static_cast<float>(target->get_cursor_y());
604
605 target->append_accumulated_text(text);
606
607 const LayoutResult layout = lay_out(text, *atlas, pen_x, pen_y, buf_w);
608
609 const auto pen_y_ceil = static_cast<uint32_t>(std::ceil(layout.final_pen_y));
610 const uint32_t new_cursor_y = pen_y_ceil;
611 const uint32_t content_h = pen_y_ceil + atlas->line_height();
612
613 if (content_h > bound_h) {
615 }
616
617 if (content_h > buf_h) {
618 const uint32_t new_h = std::min(content_h * k_grow_height_multiplier, bound_h);
619 const std::string accumulated = target->get_accumulated_text();
620 const glm::vec4 background = target->get_background();
621
622 const auto full = composite(accumulated, *atlas, color, buf_w, new_h,
623 static_cast<float>(atlas->line_height()), spans, background);
624 if (!full) {
626 }
627
628 target->resize_texture(buf_w, new_h);
629 target->set_budget(buf_w, new_h);
630
631 const size_t buf_bytes = static_cast<size_t>(buf_w) * new_h * 4;
632 std::vector<uint8_t> pixels = make_filled(buf_bytes, background);
633 const uint32_t copy_h = std::min(full->h, new_h);
634 for (uint32_t row = 0; row < copy_h; ++row) {
635 std::memcpy(
636 pixels.data() + static_cast<size_t>(row) * buf_w * 4,
637 full->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
638 static_cast<size_t>(buf_w) * 4);
639 }
640
641 target->set_pixel_data(pixels.data(), buf_bytes);
642 target->get_cursor_x() = full->cursor_x;
643 target->get_cursor_y() = full->cursor_y;
644 target->set_accumulated_text(accumulated);
645
647 "impress: vertical grow -> {}x{}", buf_w, new_h);
648
650 }
651
652 auto& pixel_data = target->get_pixel_data_mutable();
653 rasterize_styled(layout.quads, *atlas, color, spans, pixel_data.data(), buf_w, buf_h);
654 target->mark_pixels_dirty();
655 target->get_cursor_x() = static_cast<uint32_t>(std::ceil(layout.final_pen_x));
656 target->get_cursor_y() = new_cursor_y;
657
659 "impress: '{}' at ({},{}) -> cursor ({},{})",
660 std::string(text),
661 static_cast<uint32_t>(pen_x), static_cast<uint32_t>(pen_y),
662 target->get_cursor_x(), target->get_cursor_y());
663
664 return ImpressResult::Ok;
665}
666
667} // namespace MayaFlux::Portal::Text
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
glm::vec3 color
Definition Decoder.cpp:97
IO::ImageData image
Definition Decoder.cpp:47
const std::vector< float > * pixels
Definition Decoder.cpp:48
uint32_t h
Definition InkPress.cpp:29
uint32_t cursor_y
Definition InkPress.cpp:31
uint32_t cursor_x
Definition InkPress.cpp:30
uint32_t * dst
size_t a
size_t b
double q
uint32_t px
float src_y
float src_x
std::span< const uint8_t > pixel_bytes(uint32_t layer=0) const
Read-only byte-level view over the pixel buffer.
SignalSourceContainer wrapping GPU texture data as addressable pixel bytes.
uint32_t line_height() const
Line advance in pixels for this atlas's pixel_size.
uint32_t ascender() const
Ascender in pixels for this atlas's pixel_size.
uint32_t atlas_size() const
Atlas texture dimension (width == height == atlas_size).
const Kakshya::TextureContainer & texture() const
The atlas texture as a TextureContainer (R8, atlas_size x atlas_size).
Rasterizes and packs glyphs from a FontFace into a TextureContainer.
GlyphAtlas * get_default_glyph_atlas() const
Return the default GlyphAtlas, or nullptr if set_default_font() has not been called successfully.
@ API
API calls from external code.
@ Portal
High-level user-facing API layer.
GeometryFn< float > background(Kinesis::AABB2D bounds, glm::vec3 color, const std::shared_ptr< Core::VKImage > &texture)
TRIANGLE_STRIP background quad for a plot area.
Definition PlotSpec.cpp:88
MAYAFLUX_API TextureLoom & get_texture_manager()
Get the global texture manager instance.
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.
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
LayoutResult lay_out(std::string_view text, GlyphAtlas &atlas, float pen_x, float pen_y, uint32_t wrap_w, uint32_t tab_width)
Lay out a UTF-8 string into a sequence of screen-space quads.
std::vector< GlyphQuad > quads
Result of lay_out(), carrying the quads and the final pen position.
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