From f9fc1ff7a8423d8dfc5e8ff0981080ec53550e30 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Mon, 17 Oct 2022 15:26:27 -0400 Subject: [PATCH] Introduce sway_text_buffer This is a helper on top of a wlr_scene_buffer that will handle text rendering for us. --- include/sway/sway_text_buffer.h | 25 +++ sway/meson.build | 1 + sway/sway_text_buffer.c | 340 ++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 include/sway/sway_text_buffer.h create mode 100644 sway/sway_text_buffer.c diff --git a/include/sway/sway_text_buffer.h b/include/sway/sway_text_buffer.h new file mode 100644 index 000000000..5248defc1 --- /dev/null +++ b/include/sway/sway_text_buffer.h @@ -0,0 +1,25 @@ +#ifndef _SWAY_BUFFER_H +#define _SWAY_BUFFER_H +#include + +struct sway_text_node { + int width; + int max_width; + int height; + int baseline; + bool pango_markup; + float color[4]; + + struct wlr_scene_node *node; +}; + +struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, + char *text, const float *color, bool pango_markup); + +void sway_text_node_set_color(struct sway_text_node *node, const float *color); + +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); + +#endif diff --git a/sway/meson.build b/sway/meson.build index 85f82aba4..656d4bd50 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_buffer.c', 'swaynag.c', 'xdg_activation_v1.c', 'xdg_decoration.c', diff --git a/sway/sway_text_buffer.c b/sway/sway_text_buffer.c new file mode 100644 index 000000000..85ee4233c --- /dev/null +++ b/sway/sway_text_buffer.c @@ -0,0 +1,340 @@ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include "cairo_util.h" +#include "pango.h" +#include "sway/sway_text_buffer.h" +#include "sway/config.h" +#include "log.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; + + float scale; + enum wl_output_subpixel subpixel; + + struct wl_list outputs; // text_buffer_output.link + + struct wl_listener output_enter; + struct wl_listener output_leave; + struct wl_listener destroy; +}; + +struct text_buffer_output { + struct wl_list link; + struct wlr_output *output; + struct text_buffer *text_buffer; + + struct wl_listener commit; +}; + +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) { + float scale = buffer->scale; + int width = ceil(buffer->props.width * scale); + int height = ceil(buffer->props.height * scale); + float *color = (float *) &buffer->props.color; + 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; + } + + cairo_t *cairo = cairo_create(surface); + cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); + cairo_set_font_options(cairo, fo); + pango = pango_cairo_create_context(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); + + struct cairo_buffer *cairo_buffer = calloc(1, sizeof(struct cairo_buffer)); + 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); + +err: + if (pango) g_object_unref(pango); + cairo_font_options_destroy(fo); +} + +static void ensure_backing_buffer(struct text_buffer *buffer) { + float scale = 0; + enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; + + struct text_buffer_output *output; + wl_list_for_each(output, &buffer->outputs, link) { + 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; + } + } + + // no outputs + if (scale == 0) { + return; + } + + if (scale != buffer->scale || subpixel != buffer->subpixel) { + buffer->scale = scale; + buffer->subpixel = subpixel; + render_backing_buffer(buffer); + } +} + +static void handle_output_commit(struct wl_listener *listener, void *data) { + struct text_buffer_output *output = wl_container_of(listener, output, commit); + struct wlr_output_event_commit *event = data; + + if (event->committed & (WLR_OUTPUT_STATE_SCALE | WLR_OUTPUT_STATE_SUBPIXEL)) { + ensure_backing_buffer(output->text_buffer); + } +} + +static void handle_output_enter(struct wl_listener *listener, void *data) { + struct text_buffer *buffer = wl_container_of(listener, buffer, output_enter); + struct wlr_scene_output *output = data; + struct text_buffer_output *buffer_output = + calloc(1, sizeof(struct text_buffer_output)); + if (!buffer_output) { + return; + } + + buffer_output->text_buffer = buffer; + + buffer_output->commit.notify = handle_output_commit; + wl_signal_add(&output->output->events.commit, &buffer_output->commit); + + buffer_output->output = output->output; + wl_list_insert(&buffer->outputs, &buffer_output->link); + ensure_backing_buffer(buffer); +} + +static void text_buffer_output_destroy(struct text_buffer_output *output) { + if (!output) { + return; + } + + wl_list_remove(&output->link); + wl_list_remove(&output->commit.link); + free(output); +} + +static struct text_buffer_output *get_text_output_from_wlr_output( + struct text_buffer *buffer, struct wlr_output *wlr_output) { + struct text_buffer_output *output; + wl_list_for_each(output, &buffer->outputs, link) { + if (output->output == wlr_output) { + return output; + } + } + return NULL; +} + +static void handle_output_leave(struct wl_listener *listener, void *data) { + struct text_buffer *buffer = wl_container_of(listener, buffer, output_leave); + struct wlr_scene_output *scene_output = data; + + struct text_buffer_output *output = get_text_output_from_wlr_output( + buffer, scene_output->output); + text_buffer_output_destroy(output); + ensure_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->output_enter.link); + wl_list_remove(&buffer->output_leave.link); + wl_list_remove(&buffer->destroy.link); + + struct text_buffer_output *output, *tmp_output; + wl_list_for_each_safe(output, tmp_output, &buffer->outputs, link) { + text_buffer_output_destroy(output); + } + + 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); + 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, const float *color, bool pango_markup) { + struct text_buffer *buffer = calloc(1, sizeof(struct text_buffer)); + if (buffer == NULL) { + 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; + } + + wl_list_init(&buffer->outputs); + + buffer->props.height = config->font_height; + buffer->props.pango_markup = pango_markup; + memcpy(&buffer->props.color, color, sizeof(float) * 4); + + buffer->destroy.notify = handle_destroy; + wl_signal_add(&node->node.events.destroy, &buffer->destroy); + buffer->output_enter.notify = handle_output_enter; + wl_signal_add(&node->events.output_enter, &buffer->output_enter); + buffer->output_leave.notify = handle_output_leave; + wl_signal_add(&node->events.output_leave, &buffer->output_leave); + + text_calc_size(buffer); + + return &buffer->props; +} + +void sway_text_node_set_color(struct sway_text_node *node, const float *color) { + if (memcmp(&node->color, color, sizeof(float) * 4) == 0) { + return; + } + + memcpy(&node->color, color, sizeof(float) * 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); +}