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.
342{
343 if (!target) {
344 MF_ERROR(Journal::Component::Portal, Journal::Context::API,
345 "impress: target buffer is null");
346 return ImpressResult::Overflow;
347 }
348
349 GlyphAtlas* atlas = resolve_atlas(nullptr);
350 if (!atlas) {
351 return ImpressResult::Overflow;
352 }
353
354 const uint32_t buf_w = target->get_budget_width();
355 const uint32_t buf_h = target->get_budget_height();
356 const uint32_t bound_h = target->get_render_bounds_h();
357 const auto pen_x = static_cast<float>(target->get_cursor_x());
358 const auto pen_y = static_cast<float>(target->get_cursor_y());
359
360 target->append_accumulated_text(text);
361
362 const LayoutResult layout = lay_out(text, *atlas, pen_x, pen_y, buf_w);
363
364 if (layout.quads.empty()) {
365 MF_WARN(Journal::Component::Portal, Journal::Context::API,
366 "impress: no glyphs produced for '{}'", std::string(text));
367 return ImpressResult::Ok;
368 }
369
370 const auto pen_y_ceil = static_cast<uint32_t>(std::ceil(layout.final_pen_y));
371 const uint32_t new_cursor_y = pen_y_ceil;
372 const uint32_t content_h = pen_y_ceil + atlas->line_height();
373
374 if (content_h > bound_h) {
375 return ImpressResult::Overflow;
376 }
377
378 if (content_h > buf_h) {
379 const uint32_t new_h = std::min(content_h * k_grow_height_multiplier, bound_h);
380 const std::string accumulated = target->get_accumulated_text();
381
382 const auto full = composite(accumulated, *atlas,
color, buf_w, new_h);
383 if (!full) {
384 return ImpressResult::Overflow;
385 }
386
387 target->resize_texture(buf_w, new_h);
388 target->set_budget(buf_w, new_h);
389
390 const size_t buf_bytes = static_cast<size_t>(buf_w) * new_h * 4;
391 std::vector<uint8_t>
pixels(buf_bytes, 0);
392 const uint32_t copy_h = std::min(full->h, new_h);
393 for (uint32_t row = 0; row < copy_h; ++row) {
394 std::memcpy(
395 pixels.data() +
static_cast<size_t>(row) * buf_w * 4,
396 full->pixels.data() + static_cast<size_t>(row) * buf_w * 4,
397 static_cast<size_t>(buf_w) * 4);
398 }
399
400 target->set_pixel_data(
pixels.data(), buf_bytes);
401 target->get_cursor_x() = full->cursor_x;
402 target->get_cursor_y() = full->h;
403 target->set_accumulated_text(accumulated);
404
405 MF_DEBUG(Journal::Component::Portal, Journal::Context::API,
406 "impress: vertical grow -> {}x{}", buf_w, new_h);
407
408 return ImpressResult::Overflow;
409 }
410
411 auto& pixel_data = target->get_pixel_data_mutable();
413 target->mark_pixels_dirty();
414 target->get_cursor_x() = static_cast<uint32_t>(std::ceil(layout.final_pen_x));
415 target->get_cursor_y() = new_cursor_y;
416
417 MF_DEBUG(Journal::Component::Portal, Journal::Context::API,
418 "impress: '{}' at ({},{}) -> cursor ({},{})",
419 std::string(text),
420 static_cast<uint32_t>(pen_x), static_cast<uint32_t>(pen_y),
421 target->get_cursor_x(), target->get_cursor_y());
422
423 return ImpressResult::Ok;
424}
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
#define MF_DEBUG(comp, ctx,...)
std::vector< uint8_t > pixels
std::optional< glm::vec3 > color
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.