diff options
| author | Alexander Orzechowski <alex@ozal.ski> | 2023-11-23 10:08:28 -0500 | 
|---|---|---|
| committer | Kirill Primak <vyivel@eclair.cafe> | 2024-01-18 18:36:54 +0300 | 
| commit | 946fc8094559801bc4be629368ac31025d28165a (patch) | |
| tree | e320c34a26f9a515b24c7526a9c863408efd799c /sway | |
| parent | 869baff25221e1a1881e9559453faa43f90da33e (diff) | |
| download | sway-946fc8094559801bc4be629368ac31025d28165a.tar.xz | |
Introduce sway_text_node
This is a helper on top of a wlr_scene_buffer that will handle text
rendering for us.
Diffstat (limited to 'sway')
| -rw-r--r-- | sway/meson.build | 1 | ||||
| -rw-r--r-- | sway/sway_text_node.c | 303 | 
2 files changed, 304 insertions, 0 deletions
diff --git a/sway/meson.build b/sway/meson.build index 04b0dd93..110de58c 100644 --- a/sway/meson.build +++ b/sway/meson.build @@ -10,6 +10,7 @@ sway_sources = files(  	'realtime.c',  	'scene_descriptor.c',  	'server.c', +	'sway_text_node.c',  	'swaynag.c',  	'xdg_activation_v1.c',  	'xdg_decoration.c', diff --git a/sway/sway_text_node.c b/sway/sway_text_node.c new file mode 100644 index 00000000..b9a77d94 --- /dev/null +++ b/sway/sway_text_node.c @@ -0,0 +1,303 @@ +#define _POSIX_C_SOURCE 200809L +#include <drm_fourcc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wlr/types/wlr_buffer.h> +#include <wlr/interfaces/wlr_buffer.h> +#include "cairo_util.h" +#include "log.h" +#include "pango.h" +#include "sway/config.h" +#include "sway/sway_text_node.h" + +struct cairo_buffer { +	struct wlr_buffer base; +	cairo_surface_t *surface; +	cairo_t *cairo; +}; + +static void cairo_buffer_handle_destroy(struct wlr_buffer *wlr_buffer) { +	struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); + +	cairo_surface_destroy(buffer->surface); +	cairo_destroy(buffer->cairo); +	free(buffer); +} + +static bool cairo_buffer_handle_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, +		uint32_t flags, void **data, uint32_t *format, size_t *stride) { +	struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); +	*data = cairo_image_surface_get_data(buffer->surface); +	*stride = cairo_image_surface_get_stride(buffer->surface); +	*format = DRM_FORMAT_ARGB8888; +	return true; +} + +static void cairo_buffer_handle_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { +	// This space is intentionally left blank +} + +static const struct wlr_buffer_impl cairo_buffer_impl = { +	.destroy = cairo_buffer_handle_destroy, +	.begin_data_ptr_access = cairo_buffer_handle_begin_data_ptr_access, +	.end_data_ptr_access = cairo_buffer_handle_end_data_ptr_access, +}; + +struct text_buffer { +	struct wlr_scene_buffer *buffer_node; +	char *text; +	struct sway_text_node props; + +	bool visible; +	float scale; +	enum wl_output_subpixel subpixel; + +	struct wl_listener outputs_update; +	struct wl_listener destroy; +}; + +static int get_text_width(struct sway_text_node *props) { +	if (props->max_width) { +		return MIN(props->max_width, props->width); +	} + +	return props->width; +} + +static void update_source_box(struct text_buffer *buffer) { +	struct sway_text_node *props = &buffer->props; +	struct wlr_fbox source_box = { +		.x = 0, +		.y = 0, +		.width = ceil(get_text_width(props) * buffer->scale), +		.height = ceil(props->height * buffer->scale), +	}; + +	wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box); +} + +static void render_backing_buffer(struct text_buffer *buffer) { +	if (!buffer->visible) { +		return; +	} + +	float scale = buffer->scale; +	int width = ceil(buffer->props.width * scale); +	int height = ceil(buffer->props.height * scale); +	float *color = (float *)&buffer->props.color; +	float *background = (float *)&buffer->props.background; +	PangoContext *pango = NULL; + +	cairo_font_options_t *fo = cairo_font_options_create(); +	cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); +	enum wl_output_subpixel subpixel = buffer->subpixel; +	if (subpixel == WL_OUTPUT_SUBPIXEL_NONE || subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) { +		cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY); +	} else { +		cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); +		cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel)); +	} + +	cairo_surface_t *surface = cairo_image_surface_create( +			CAIRO_FORMAT_ARGB32, width, height); +	cairo_status_t status = cairo_surface_status(surface); +	if (status != CAIRO_STATUS_SUCCESS) { +		sway_log(SWAY_ERROR, "cairo_image_surface_create failed: %s", +			cairo_status_to_string(status)); +		goto err; +	} + +	struct cairo_buffer *cairo_buffer = calloc(1, sizeof(*cairo_buffer)); +	if (!cairo_buffer) { +		sway_log(SWAY_ERROR, "cairo_buffer allocation failed"); +		goto err; +	} + +	cairo_t *cairo = cairo_create(surface); +	if (!cairo) { +		sway_log(SWAY_ERROR, "cairo_create failed"); +		free(cairo_buffer); +		goto err; +	} + +	cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); +	cairo_set_font_options(cairo, fo); +	pango = pango_cairo_create_context(cairo); + +	cairo_set_source_rgba(cairo, background[0], background[1], background[2], background[3]); +	cairo_rectangle(cairo, 0, 0, width, height); +	cairo_fill(cairo); + +	cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]); +	cairo_move_to(cairo, 0, (config->font_baseline - buffer->props.baseline) * scale); + +	render_text(cairo, config->font_description, scale, buffer->props.pango_markup, +		"%s", buffer->text); + +	cairo_surface_flush(surface); + +	wlr_buffer_init(&cairo_buffer->base, &cairo_buffer_impl, width, height); +	cairo_buffer->surface = surface; +	cairo_buffer->cairo = cairo; + +	wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base); +	wlr_buffer_drop(&cairo_buffer->base); +	update_source_box(buffer); + +	pixman_region32_t opaque; +	pixman_region32_init(&opaque); +	if (background[3] == 1) { +		pixman_region32_union_rect(&opaque, &opaque, 0, 0, +			buffer->props.width, buffer->props.height); +	} +	wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque); +	pixman_region32_fini(&opaque); + +err: +	if (pango) g_object_unref(pango); +	cairo_font_options_destroy(fo); +} + +static void handle_outputs_update(struct wl_listener *listener, void *data) { +	struct text_buffer *buffer = wl_container_of(listener, buffer, outputs_update); +	struct wlr_scene_outputs_update_event *event = data; + +	float scale = 0; +	enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + +	for (size_t i = 0; i < event->size; i++) { +		struct wlr_scene_output *output = event->active[i]; +		if (subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) { +			subpixel = output->output->subpixel; +		} else if (subpixel != output->output->subpixel) { +			subpixel = WL_OUTPUT_SUBPIXEL_NONE; +		} + +		if (scale != 0 && scale != output->output->scale) { +			// drop down to gray scale if we encounter outputs with different +			// scales or else we will have chromatic aberations +			subpixel = WL_OUTPUT_SUBPIXEL_NONE; +		} + +		if (scale < output->output->scale) { +			scale = output->output->scale; +		} +	} + +	buffer->visible = event->size > 0; + +	if (scale != buffer->scale || subpixel != buffer->subpixel) { +		buffer->scale = scale; +		buffer->subpixel = subpixel; +		render_backing_buffer(buffer); +	} +} + +static void handle_destroy(struct wl_listener *listener, void *data) { +	struct text_buffer *buffer = wl_container_of(listener, buffer, destroy); + +	wl_list_remove(&buffer->outputs_update.link); +	wl_list_remove(&buffer->destroy.link); + +	free(buffer->text); +	free(buffer); +} + +static void text_calc_size(struct text_buffer *buffer) { +	struct sway_text_node *props = &buffer->props; + +	cairo_t *c = cairo_create(NULL); +	if (!c) { +		sway_log(SWAY_ERROR, "cairo_t allocation failed"); +		return; +	} + +	cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); +	get_text_size(c, config->font_description, &props->width, NULL, +		&props->baseline, 1, props->pango_markup, "%s", buffer->text); +	cairo_destroy(c); + +	wlr_scene_buffer_set_dest_size(buffer->buffer_node, +		get_text_width(props), props->height); +} + +struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, +		char *text, float color[4], bool pango_markup) { +	struct text_buffer *buffer = calloc(1, sizeof(*buffer)); +	if (!buffer) { +		return NULL; +	} + +	struct wlr_scene_buffer *node = wlr_scene_buffer_create(parent, NULL); +	if (!node) { +		free(buffer); +		return NULL; +	} + +	buffer->buffer_node = node; +	buffer->props.node = &node->node; +	buffer->text = strdup(text); +	if (!buffer->text) { +		free(buffer); +		wlr_scene_node_destroy(&node->node); +		return NULL; +	} + +	buffer->props.height = config->font_height; +	buffer->props.pango_markup = pango_markup; +	memcpy(&buffer->props.color, color, sizeof(*color) * 4); + +	buffer->destroy.notify = handle_destroy; +	wl_signal_add(&node->node.events.destroy, &buffer->destroy); +	buffer->outputs_update.notify = handle_outputs_update; +	wl_signal_add(&node->events.outputs_update, &buffer->outputs_update); + +	text_calc_size(buffer); + +	return &buffer->props; +} + +void sway_text_node_set_color(struct sway_text_node *node, float color[4]) { +	if (memcmp(&node->color, color, sizeof(*color) * 4) == 0) { +		return; +	} + +	memcpy(&node->color, color, sizeof(*color) * 4); +	struct text_buffer *buffer = wl_container_of(node, buffer, props); + +	render_backing_buffer(buffer); +} + +void sway_text_node_set_text(struct sway_text_node *node, char *text) { +	struct text_buffer *buffer = wl_container_of(node, buffer, props); +	if (strcmp(buffer->text, text) == 0) { +		return; +	} + +	char *new_text = strdup(text); +	if (!new_text) { +		return; +	} + +	free(buffer->text); +	buffer->text = new_text; + +	text_calc_size(buffer); +	render_backing_buffer(buffer); +} + +void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) { +	struct text_buffer *buffer = wl_container_of(node, buffer, props); +	buffer->props.max_width = max_width; +	wlr_scene_buffer_set_dest_size(buffer->buffer_node, +		get_text_width(&buffer->props), buffer->props.height); +	update_source_box(buffer); +	render_backing_buffer(buffer); +} + +void sway_text_node_set_background(struct sway_text_node *node, float background[4]) { +	struct text_buffer *buffer = wl_container_of(node, buffer, props); +	memcpy(&node->background, background, sizeof(*background) * 4); +	render_backing_buffer(buffer); +}  | 
