aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Orzechowski <alex@ozal.ski>2023-11-23 10:08:28 -0500
committerKirill Primak <vyivel@eclair.cafe>2024-01-18 18:36:54 +0300
commit946fc8094559801bc4be629368ac31025d28165a (patch)
treee320c34a26f9a515b24c7526a9c863408efd799c
parent869baff25221e1a1881e9559453faa43f90da33e (diff)
Introduce sway_text_node
This is a helper on top of a wlr_scene_buffer that will handle text rendering for us.
-rw-r--r--include/sway/sway_text_node.h28
-rw-r--r--sway/meson.build1
-rw-r--r--sway/sway_text_node.c303
3 files changed, 332 insertions, 0 deletions
diff --git a/include/sway/sway_text_node.h b/include/sway/sway_text_node.h
new file mode 100644
index 00000000..0d4209bb
--- /dev/null
+++ b/include/sway/sway_text_node.h
@@ -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
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);
+}