Append a UTF-8 string into an existing TextBuffer at the current cursor.
Does not clear existing content. Composites the new run into the pre-allocated budget region and advances the cursor. No VKImage reallocation occurs while the run fits within the vertical budget.
When the run would push the cursor past the vertical budget the texture is grown by k_grow_height_multiplier, the full accumulated text is recomposited, and ImpressResult::Overflow is returned. The caller is responsible for rebuilding prior content after an overflow if accumulated state is not sufficient.
468{
469 if (!target) {
470 MF_ERROR(Journal::Component::Portal, Journal::Context::API,
471 "impress: target buffer is null");
472 return ImpressResult::Overflow;
473 }
474
475 GlyphAtlas* atlas = resolve_atlas(nullptr);
476 if (!atlas) {
477 return ImpressResult::Overflow;
478 }
479
480 const uint32_t buf_w = target->get_budget_width();
481 const uint32_t buf_h = target->get_budget_height();
482 const uint32_t bound_h = target->get_render_bounds_h();
483 const auto pen_x = static_cast<float>(target->get_cursor_x());
484 const auto pen_y = static_cast<float>(target->get_cursor_y());
485
486 target->append_accumulated_text(text);
487
488 const LayoutResult layout = lay_out(text, *atlas, pen_x, pen_y, buf_w);
489
490 if (layout.quads.empty()) {
491 MF_WARN(Journal::Component::Portal, Journal::Context::API,
492 "impress: no glyphs produced for '{}'", std::string(text));
493 return ImpressResult::Ok;
494 }
495
496 const auto pen_y_ceil = static_cast<uint32_t>(std::ceil(layout.final_pen_y));
497 const uint32_t new_cursor_y = pen_y_ceil;
498 const uint32_t content_h = pen_y_ceil + atlas->line_height();
499
500 if (content_h > bound_h) {
501 return ImpressResult::Overflow;
502 }
503
504 if (content_h > buf_h) {
505 const uint32_t new_h = std::min(content_h * k_grow_height_multiplier, bound_h);
506 const std::string accumulated = target->get_accumulated_text();
507
508 const auto full = composite(accumulated, *atlas, color, buf_w, new_h);
509 if (!full) {
510 return ImpressResult::Overflow;
511 }
512
513 target->resize_texture(buf_w, new_h);
514 target->set_budget(buf_w, new_h);
515
516 const size_t buf_bytes = static_cast<size_t>(buf_w) * new_h * 4;
517 std::vector<uint8_t>
pixels(buf_bytes, 0);
518 const uint32_t copy_h = std::min(full->h, new_h);
519 for (uint32_t row = 0; row < copy_h; ++row) {
520 std::memcpy(
521 pixels.data() +
static_cast<size_t>(row) * buf_w * 4,
522 full->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
523 static_cast<size_t>(buf_w) * 4);
524 }
525
526 target->set_pixel_data(
pixels.data(), buf_bytes);
527 target->get_cursor_x() = full->cursor_x;
528 target->get_cursor_y() = full->h;
529 target->set_accumulated_text(accumulated);
530
531 MF_DEBUG(Journal::Component::Portal, Journal::Context::API,
532 "impress: vertical grow -> {}x{}", buf_w, new_h);
533
534 return ImpressResult::Overflow;
535 }
536
537 auto& pixel_data = target->get_pixel_data_mutable();
538 rasterize_quads(layout.quads, *atlas, color, pixel_data.data(), buf_w, buf_h);
539 target->mark_pixels_dirty();
540 target->get_cursor_x() = static_cast<uint32_t>(std::ceil(layout.final_pen_x));
541 target->get_cursor_y() = new_cursor_y;
542
543 MF_DEBUG(Journal::Component::Portal, Journal::Context::API,
544 "impress: '{}' at ({},{}) -> cursor ({},{})",
545 std::string(text),
546 static_cast<uint32_t>(pen_x), static_cast<uint32_t>(pen_y),
547 target->get_cursor_x(), target->get_cursor_y());
548
549 return ImpressResult::Ok;
550}
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< uint8_t > pixels
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.