This is a helper on top of a wlr_scene_buffer that will handle text rendering for us.master
							parent
							
								
									869baff252
								
							
						
					
					
						commit
						946fc80945
					
				| @ -0,0 +1,28 @@ | ||||
| #ifndef _SWAY_BUFFER_H | ||||
| #define _SWAY_BUFFER_H | ||||
| #include <wlr/types/wlr_scene.h> | ||||
| 
 | ||||
| struct sway_text_node { | ||||
| 	int width; | ||||
| 	int max_width; | ||||
| 	int height; | ||||
| 	int baseline; | ||||
| 	bool pango_markup; | ||||
| 	float color[4]; | ||||
| 	float background[4]; | ||||
| 
 | ||||
| 	struct wlr_scene_node *node; | ||||
| }; | ||||
| 
 | ||||
| struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, | ||||
| 		char *text, float color[4], bool pango_markup); | ||||
| 
 | ||||
| void sway_text_node_set_color(struct sway_text_node *node, float color[4]); | ||||
| 
 | ||||
| void sway_text_node_set_text(struct sway_text_node *node, char *text); | ||||
| 
 | ||||
| void sway_text_node_set_max_width(struct sway_text_node *node, int max_width); | ||||
| 
 | ||||
| void sway_text_node_set_background(struct sway_text_node *node, float background[4]); | ||||
| 
 | ||||
| #endif | ||||
| @ -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); | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue