From b7b24205fc2e88278bfc6477c9f3f2eb8c59f369 Mon Sep 17 00:00:00 2001
From: Vito Caputo <vcaputo@pengaru.com>
Date: Tue, 3 Oct 2023 17:15:43 -0700
Subject: modules/asc: add justify= and [hv]offset= settings

justify= now supports "aligned" and "offsetted", justify=aligned
being the existing behavior where you would specify
halign={left,right,center} and valign={top,bottom,center}.

When justify=offsetted is specified however, {valign=,halign=}
are unused and instead {hoffset=,voffset=} are expected, as
either hoffset=auto/voffset=auto for automagic offsetting
according to the x/y coords, or explicit offsetting using -1..+1
normalized fractional values serving as coordinates within the
rendered text's rectangle where to anchor the x/y coordinate.

By using halign=auto,valign=auto one can carelessly vary the x/y
coordinates using the taps (i.e. via rkt) without having to deal
with justification concerns (modulo large texts that can't
possibly fit), as the offsets will automatically adapt according
to the coordinates.
---
 src/modules/asc/asc.c | 202 +++++++++++++++++++++++++++++++++++++++-----------
 1 file changed, 157 insertions(+), 45 deletions(-)

(limited to 'src/modules')

diff --git a/src/modules/asc/asc.c b/src/modules/asc/asc.c
index d47ac6d..98455a7 100644
--- a/src/modules/asc/asc.c
+++ b/src/modules/asc/asc.c
@@ -16,29 +16,31 @@
 
 /* This is intended primarily for diagnostic purposes or as a stand-in you'd eventually
  * replace with something a more visually interesting font/style.
- *
- * TODO:
- *      - Maybe add a dynamic justification mode where the h/v alignment offsets are
- *        just the inverse of the normalized x/y coordinates.  This requires extending
- *        libs/txt to support precise offsetting when rendering as an alternative to the
- *        enum'd txt_align_t variant.  But it would allow a tapped x/y coordinate user to
- *        sweep the coordinates edge-to-edge with the text smoothly adjusting its offset
- *        throughout the sweep so it doesn't extend off-screen at the nearest edge.
  */
 
 #define	ASC_DEFAULT_STRING		"Hello rototiller!"
+#define ASC_DEFAULT_JUSTIFY		ASC_JUSTIFY_ALIGNED
 #define ASC_DEFAULT_HALIGN		TXT_HALIGN_CENTER
 #define ASC_DEFAULT_VALIGN		TXT_VALIGN_CENTER
+#define ASC_DEFAULT_HOFFSET		"auto"
+#define ASC_DEFAULT_VOFFSET		"auto"
 #define ASC_DEFAULT_X			0
 #define ASC_DEFAULT_Y			0
 
 
+typedef enum asc_justify_t {
+	ASC_JUSTIFY_ALIGNED,
+	ASC_JUSTIFY_OFFSETTED,
+	ASC_JUSITFY_CNT
+} asc_justify_t;
+
 typedef struct asc_setup_t {
 	til_setup_t	til_setup;
 
 	const char	*string;
-	txt_halign_t	halign;
-	txt_valign_t	valign;
+	asc_justify_t	justify;
+	txt_halign_t	halign, valign;
+	float		hoffset, voffset;
 	float		x, y;
 } asc_setup_t;
 
@@ -47,13 +49,16 @@ typedef struct asc_context_t {
 
 	struct {
 		til_tap_t		x, y;
+		til_tap_t		hoffset, voffset;
 	}			taps;
 
 	struct {
 		float			x, y;
+		float			hoffset, voffset;
 	}			vars;
 
 	float			*x, *y;
+	float			*hoffset, *voffset;
 
 	txt_t			*txt;
 } asc_context_t;
@@ -71,9 +76,22 @@ static void asc_update_taps(asc_context_t *ctxt, til_stream_t *stream)
 	else
 		ctxt->vars.y = *ctxt->y;
 
-	/* XXX: maybe clamp to -1.0..+1.0 ?  It's not a crash risk since txt_render_fragment()
+	/* XXX: maybe clamp to -1.0..+1.0 ?  It's not a crash risk since txt_render_fragment_aligned()
 	 * clips to the fragment by using ...put_pixel_checked() *shrug*
 	 */
+
+	if (((asc_setup_t *)ctxt->til_module_context.setup)->justify != ASC_JUSTIFY_OFFSETTED)
+		return;
+
+	if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.hoffset))
+		*ctxt->hoffset = ((asc_setup_t *)ctxt->til_module_context.setup)->hoffset;
+	else
+		ctxt->vars.hoffset = *ctxt->hoffset;
+
+	if (!til_stream_tap_context(stream, &ctxt->til_module_context, NULL, &ctxt->taps.voffset))
+		*ctxt->voffset = ((asc_setup_t *)ctxt->til_module_context.setup)->voffset;
+	else
+		ctxt->vars.voffset = *ctxt->voffset;
 }
 
 
@@ -92,6 +110,11 @@ static til_module_context_t * asc_create_context(const til_module_t *module, til
 	ctxt->taps.x = til_tap_init_float(ctxt, &ctxt->x, 1, &ctxt->vars.x, "x");
 	ctxt->taps.y = til_tap_init_float(ctxt, &ctxt->y, 1, &ctxt->vars.y, "y");
 
+	if (((asc_setup_t *)setup)->justify == ASC_JUSTIFY_OFFSETTED) {
+		ctxt->taps.hoffset = til_tap_init_float(ctxt, &ctxt->hoffset, 1, &ctxt->vars.hoffset, "hoffset");
+		ctxt->taps.voffset = til_tap_init_float(ctxt, &ctxt->voffset, 1, &ctxt->vars.voffset, "voffset");
+	}
+
 	asc_update_taps(ctxt, stream);
 
 	return &ctxt->til_module_context;
@@ -108,13 +131,35 @@ static void asc_render_fragment(til_module_context_t *context, til_stream_t *str
 
 	til_fb_fragment_clear(fragment);
 
-	txt_render_fragment_aligned(ctxt->txt, fragment, 0xffffffff,
-				    ctxt->vars.x * ((float)fragment->frame_width) * .5f + .5f * ((float)fragment->frame_width),
-				    ctxt->vars.y * ((float)fragment->frame_height) * .5f + .5f * ((float)fragment->frame_height),
-				    (txt_align_t){
-					.horiz = s->halign,
-					.vert = s->valign
-				    });
+	switch (s->justify) {
+	case ASC_JUSTIFY_ALIGNED:
+		return txt_render_fragment_aligned(ctxt->txt, fragment, 0xffffffff,
+						   ctxt->vars.x * ((float)fragment->frame_width) * .5f + .5f * ((float)fragment->frame_width),
+						   ctxt->vars.y * ((float)fragment->frame_height) * .5f + .5f * ((float)fragment->frame_height),
+						   (txt_align_t){
+							.horiz = s->halign,
+							.vert = s->valign
+						   });
+
+	case ASC_JUSTIFY_OFFSETTED: {
+		float	hoffset = ctxt->vars.hoffset,
+			voffset = ctxt->vars.voffset;
+
+		if (isnan(hoffset))
+			hoffset = ctxt->vars.x;
+
+		if (isnan(voffset))
+			voffset = ctxt->vars.y;
+
+		return txt_render_fragment_offsetted(ctxt->txt, fragment, 0xffffffff,
+						     ctxt->vars.x * ((float)fragment->frame_width) * .5f + .5f * ((float)fragment->frame_width),
+						     ctxt->vars.y * ((float)fragment->frame_height) * .5f + .5f * ((float)fragment->frame_height),
+						     hoffset, voffset);
+	}
+
+	default:
+		assert(0);
+	}
 }
 
 
@@ -146,9 +191,15 @@ til_module_t	asc_module = {
 static int asc_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
 {
 	til_setting_t	*string;
-	til_setting_t	*valign;
-	til_setting_t	*halign;
+	til_setting_t	*justify;
+	til_setting_t	*valign, *voffset;
+	til_setting_t	*halign, *hoffset;
 	til_setting_t	*x, *y;
+	const char	*justify_values[] = {
+				"aligned",
+				"offsetted",
+				NULL
+			};
 	const char	*valign_values[] = {
 				"center",
 				"top",
@@ -178,33 +229,78 @@ static int asc_setup(const til_settings_t *settings, til_setting_t **res_setting
 
 	r = til_settings_get_and_describe_setting(settings,
 						&(til_setting_spec_t){
-							.name = "Vertical alignment",
-							.key = "valign",
+							.name = "Justification",
+							.key = "justify",
 							/* .regex = "" TODO */
-							.preferred = valign_values[ASC_DEFAULT_VALIGN],
-							.values = valign_values,
+							.preferred = justify_values[ASC_DEFAULT_JUSTIFY],
+							.values = justify_values,
 							.annotations = NULL
 						},
-						&valign,
+						&justify,
 						res_setting,
 						res_desc);
 	if (r)
 		return r;
 
-	r = til_settings_get_and_describe_setting(settings,
-						&(til_setting_spec_t){
-							.name = "Horizontal alignment",
-							.key = "halign",
-							/* .regex = "" TODO */
-							.preferred = halign_values[ASC_DEFAULT_HALIGN],
-							.values = halign_values,
-							.annotations = NULL
-						},
-						&halign,
-						res_setting,
-						res_desc);
-	if (r)
-		return r;
+	if (!strcasecmp(justify->value, justify_values[ASC_JUSTIFY_ALIGNED])) {
+		r = til_settings_get_and_describe_setting(settings,
+							&(til_setting_spec_t){
+								.name = "Vertical alignment",
+								.key = "valign",
+								/* .regex = "" TODO */
+								.preferred = valign_values[ASC_DEFAULT_VALIGN],
+								.values = valign_values,
+								.annotations = NULL
+							},
+							&valign,
+							res_setting,
+							res_desc);
+		if (r)
+			return r;
+
+		r = til_settings_get_and_describe_setting(settings,
+							&(til_setting_spec_t){
+								.name = "Horizontal alignment",
+								.key = "halign",
+								/* .regex = "" TODO */
+								.preferred = halign_values[ASC_DEFAULT_HALIGN],
+								.values = halign_values,
+								.annotations = NULL
+							},
+							&halign,
+							res_setting,
+							res_desc);
+		if (r)
+			return r;
+	} else {
+		r = til_settings_get_and_describe_setting(settings,
+							&(til_setting_spec_t){
+								.name = "Vertical offset [-1.0...1.0] or 'auto'",
+								.key = "voffset",
+								/* .regex = "" TODO */
+								.preferred = ASC_DEFAULT_VOFFSET,
+								.annotations = NULL
+							},
+							&voffset,
+							res_setting,
+							res_desc);
+		if (r)
+			return r;
+
+		r = til_settings_get_and_describe_setting(settings,
+							&(til_setting_spec_t){
+								.name = "Horizontal offset [-1.0...1.0] or 'auto'",
+								.key = "hoffset",
+								/* .regex = "" TODO */
+								.preferred = ASC_DEFAULT_HOFFSET,
+								.annotations = NULL
+							},
+							&hoffset,
+							res_setting,
+							res_desc);
+		if (r)
+			return r;
+	}
 
 	r = til_settings_get_and_describe_setting(settings,
 						&(til_setting_spec_t){
@@ -245,13 +341,29 @@ static int asc_setup(const til_settings_t *settings, til_setting_t **res_setting
 		if (!setup->string)
 			return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, string, res_setting, -ENOMEM);
 
-		r = til_value_to_pos(halign_values, halign->value, (unsigned *)&setup->halign);
-		if (r < 0)
-			return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, halign, res_setting, -EINVAL);
-
-		r = til_value_to_pos(valign_values, valign->value, (unsigned *)&setup->valign);
+		r = til_value_to_pos(justify_values, justify->value, (unsigned *)&setup->justify);
 		if (r < 0)
-			return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, valign, res_setting, -EINVAL);
+			return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, justify, res_setting, -EINVAL);
+
+		if (setup->justify == ASC_JUSTIFY_ALIGNED) {
+			r = til_value_to_pos(halign_values, halign->value, (unsigned *)&setup->halign);
+			if (r < 0)
+				return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, halign, res_setting, -EINVAL);
+
+			r = til_value_to_pos(valign_values, valign->value, (unsigned *)&setup->valign);
+			if (r < 0)
+				return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, valign, res_setting, -EINVAL);
+		} else {
+			if (!strcasecmp(hoffset->value, "auto"))
+				setup->hoffset = NAN;
+			else if (sscanf(hoffset->value, "%f", &setup->hoffset) != 1)
+				return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, hoffset, res_setting, -EINVAL);
+
+			if (!strcasecmp(voffset->value, "auto"))
+				setup->voffset = NAN;
+			else if (sscanf(voffset->value, "%f", &setup->voffset) != 1)
+				return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, voffset, res_setting, -EINVAL);
+		}
 
 		if (sscanf(x->value, "%f", &setup->x) != 1)
 			return til_setup_free_with_failed_setting_ret_err(&setup->til_setup, x, res_setting, -EINVAL);
-- 
cgit v1.2.3