From 4215901196cee8f07b226faea2ff94c6338b367d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:33:55 +0000 Subject: [PATCH 1/2] Fix text source filling all available space by disabling word_wrap, custom_width and use_custom_bgcolor Agent-Logs-Url: https://github.com/Octelys/achievements-tracker-plugin/sessions/a4002fbf-a299-4ac6-904c-a3a52f022f77 Co-authored-by: kzryzstof <38137839+kzryzstof@users.noreply.github.com> --- src/sources/common/text_source.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/sources/common/text_source.c b/src/sources/common/text_source.c index 837cc23..d0354ca 100644 --- a/src/sources/common/text_source.c +++ b/src/sources/common/text_source.c @@ -177,6 +177,16 @@ static obs_data_t *create_private_obs_source_settings(text_source_t *text_source obs_data_set_bool(settings, "outline", false); obs_data_set_bool(settings, "drop_shadow", false); + // Disable word wrap and custom width so the source auto-sizes to the + // rendered text. Without these, some OBS builds default to filling the + // full canvas width, making alignment via OBS transforms impossible. + obs_data_set_bool(settings, "word_wrap", false); + obs_data_set_int(settings, "custom_width", 0); + + // Guard against GDI+ installations that default use_custom_bgcolor to + // true, which would paint an opaque background behind the text. + obs_data_set_bool(settings, "use_custom_bgcolor", false); + return settings; } From 5c8baf02f6b5a8284b5e8444e9a202df24e7f1ca Mon Sep 17 00:00:00 2001 From: kzryzstof Date: Fri, 22 May 2026 19:45:40 -0400 Subject: [PATCH 2/2] Add horizontal text alignment and fixed canvas width configuration options to text sources --- src/common/types.h | 31 +++++++++++++ src/sources/common/text_source.c | 77 +++++++++++++++++++++++++++++++- src/sources/common/text_source.h | 9 ++++ 3 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/common/types.h b/src/common/types.h index 18ce3c5..a3751f6 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -104,6 +104,21 @@ typedef struct auto_visibility_config { float fade_duration; } auto_visibility_config_t; +/** + * @brief Horizontal alignment for text within its source canvas. + * + * Controls where text is drawn horizontally when the source canvas + * (source_width) is wider than the rendered text. + */ +typedef enum text_align { + /** Text starts at the left edge; the source grows to the right. */ + TEXT_ALIGN_LEFT = 0, + /** Text is centred within the canvas. */ + TEXT_ALIGN_CENTER, + /** Text ends at the right edge; the source grows to the left. */ + TEXT_ALIGN_RIGHT, +} text_align_t; + /** * @brief Common configuration for text-based sources. * @@ -121,6 +136,22 @@ typedef struct text_source_config { uint32_t inactive_top_color; uint32_t inactive_bottom_color; auto_visibility_config_t auto_visibility; + /** + * Horizontal alignment of text within the canvas. + * + * Only meaningful when source_width > 0. When source_width == 0 the text + * auto-sizes to the content and alignment has no effect. + */ + text_align_t alignment; + /** + * Fixed canvas width in pixels reported by get_width(). + * + * Set to 0 (default) to auto-size the canvas to the rendered text width. + * Set to a positive value to create a fixed-width canvas. The text is then + * drawn at its natural pixel size and shifted horizontally according to + * the alignment field. + */ + uint32_t source_width; } text_source_config_t; /** diff --git a/src/sources/common/text_source.c b/src/sources/common/text_source.c index d0354ca..70ba85b 100644 --- a/src/sources/common/text_source.c +++ b/src/sources/common/text_source.c @@ -325,6 +325,10 @@ bool text_source_update_text(text_source_t *text_source, bool *force_reload, con return false; } + /* Keep the cached canvas width in sync with the config so get_width() + * can return it without needing the config pointer. */ + text_source->source_width = config->source_width; + if (!text_source->current_text) { initiate_fade_in_transition(text_source, text, use_active_color); } else if (text_source->current_text && strcmp(text_source->current_text, text) != 0) { @@ -353,7 +357,6 @@ bool text_source_update_text(text_source_t *text_source, bool *force_reload, con void text_source_render(text_source_t *text_source, const text_source_config_t *config, gs_effect_t *effect) { - UNUSED_PARAMETER(config); UNUSED_PARAMETER(effect); if (!text_source || !text_source->private_obs_source) { @@ -370,9 +373,55 @@ void text_source_render(text_source_t *text_source, const text_source_config_t * obs_data_release(settings); } + /* Compute x-offset for alignment. + * + * The text renders at its natural pixel width inside the canvas reported + * by get_width(). When source_width == 0 the canvas equals the text width + * so the offset is always 0 (left). When source_width > 0 the offset is + * derived from the alignment setting: + * + * LEFT – text starts at x = 0 (grows right) + * CENTER – text is centred in the canvas + * RIGHT – text ends at x = canvas_width (grows left) + * + * Note: OBS scene-item scale is applied on top of all of this by the OBS + * compositor. Keep the scene-item scale at 1:1 if you want the text to + * appear at exactly its configured pixel size. + */ + const uint32_t text_w = obs_source_get_width(text_source->private_obs_source); + const uint32_t canvas_w = config->source_width > 0 ? config->source_width : text_w; + float x_off = 0.0f; + + if (canvas_w > text_w) { + switch (config->alignment) { + case TEXT_ALIGN_CENTER: + x_off = (float)(canvas_w - text_w) * 0.5f; + break; + case TEXT_ALIGN_RIGHT: + x_off = (float)(canvas_w - text_w); + break; + case TEXT_ALIGN_LEFT: + default: + x_off = 0.0f; + break; + } + } + + /* Push a translation matrix so the inner source is drawn at the correct + * horizontal position within the canvas. The matrix is popped immediately + * after to avoid affecting subsequent draw calls by the OBS compositor. */ + if (x_off != 0.0f) { + gs_matrix_push(); + gs_matrix_translate3f(x_off, 0.0f, 0.0f); + } + // Opacity is handled by updating the text color's alpha channel in tick() obs_source_video_render(text_source->private_obs_source); + if (x_off != 0.0f) { + gs_matrix_pop(); + } + text_source->transition.last_opacity = final_opacity; } @@ -428,6 +477,16 @@ void text_source_add_properties(obs_properties_t *props, bool supports_inactive_ obs_properties_add_color(props, "text_inactive_bottom_color", "Inactive text color (Bottom)"); } + // Canvas width (0 = auto-size to text) + obs_properties_add_int(props, "text_source_width", "Canvas width (0 = auto)", 0, 8192, 1); + + // Horizontal alignment within the canvas + obs_property_t *align_list = + obs_properties_add_list(props, "text_alignment", "Text alignment", OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_INT); + obs_property_list_add_int(align_list, "Left", TEXT_ALIGN_LEFT); + obs_property_list_add_int(align_list, "Center", TEXT_ALIGN_CENTER); + obs_property_list_add_int(align_list, "Right", TEXT_ALIGN_RIGHT); + auto_visibility_add_toggle_property(props); } @@ -485,12 +544,28 @@ void text_source_update_properties(obs_data_t *settings, text_source_config_t *c if (auto_visibility_update_toggle(settings, &config->auto_visibility)) { *must_reload = true; } + + if (obs_data_has_user_value(settings, "text_source_width")) { + config->source_width = (uint32_t)obs_data_get_int(settings, "text_source_width"); + *must_reload = true; + } + + if (obs_data_has_user_value(settings, "text_alignment")) { + config->alignment = (text_align_t)obs_data_get_int(settings, "text_alignment"); + *must_reload = true; + } } uint32_t text_source_get_width(text_source_t *base) { if (!base || !base->private_obs_source) { return 0; } + /* When the caller has configured a fixed canvas width, report it so that + * OBS allocates the correct bounding box for the source in the scene. + * Otherwise fall back to the text's natural pixel width. */ + if (base->source_width > 0) { + return base->source_width; + } return obs_source_get_width(base->private_obs_source); } diff --git a/src/sources/common/text_source.h b/src/sources/common/text_source.h index e486b17..aa4a8db 100644 --- a/src/sources/common/text_source.h +++ b/src/sources/common/text_source.h @@ -74,6 +74,15 @@ typedef struct text_source { char *current_text; bool use_active_color; + /** + * Fixed canvas width in pixels (mirrors text_source_config_t::source_width). + * + * Kept here so get_width() can return it without requiring the caller to + * pass config through every time. Updated by text_source_update_text(). + * 0 means auto-size to text width. + */ + uint32_t source_width; + } text_source_t; /**