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 | |
parent | renderer: Remove in favor of scene_graph (diff) | |
download | sway-946fc8094559801bc4be629368ac31025d28165a.tar.gz sway-946fc8094559801bc4be629368ac31025d28165a.tar.zst sway-946fc8094559801bc4be629368ac31025d28165a.zip |
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.h | 28 | ||||
-rw-r--r-- | sway/meson.build | 1 | ||||
-rw-r--r-- | sway/sway_text_node.c | 303 |
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 @@ | |||
1 | #ifndef _SWAY_BUFFER_H | ||
2 | #define _SWAY_BUFFER_H | ||
3 | #include <wlr/types/wlr_scene.h> | ||
4 | |||
5 | struct sway_text_node { | ||
6 | int width; | ||
7 | int max_width; | ||
8 | int height; | ||
9 | int baseline; | ||
10 | bool pango_markup; | ||
11 | float color[4]; | ||
12 | float background[4]; | ||
13 | |||
14 | struct wlr_scene_node *node; | ||
15 | }; | ||
16 | |||
17 | struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, | ||
18 | char *text, float color[4], bool pango_markup); | ||
19 | |||
20 | void sway_text_node_set_color(struct sway_text_node *node, float color[4]); | ||
21 | |||
22 | void sway_text_node_set_text(struct sway_text_node *node, char *text); | ||
23 | |||
24 | void sway_text_node_set_max_width(struct sway_text_node *node, int max_width); | ||
25 | |||
26 | void sway_text_node_set_background(struct sway_text_node *node, float background[4]); | ||
27 | |||
28 | #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( | |||
10 | 'realtime.c', | 10 | 'realtime.c', |
11 | 'scene_descriptor.c', | 11 | 'scene_descriptor.c', |
12 | 'server.c', | 12 | 'server.c', |
13 | 'sway_text_node.c', | ||
13 | 'swaynag.c', | 14 | 'swaynag.c', |
14 | 'xdg_activation_v1.c', | 15 | 'xdg_activation_v1.c', |
15 | 'xdg_decoration.c', | 16 | '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 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <drm_fourcc.h> | ||
3 | #include <stdio.h> | ||
4 | #include <stdlib.h> | ||
5 | #include <string.h> | ||
6 | #include <wlr/types/wlr_buffer.h> | ||
7 | #include <wlr/interfaces/wlr_buffer.h> | ||
8 | #include "cairo_util.h" | ||
9 | #include "log.h" | ||
10 | #include "pango.h" | ||
11 | #include "sway/config.h" | ||
12 | #include "sway/sway_text_node.h" | ||
13 | |||
14 | struct cairo_buffer { | ||
15 | struct wlr_buffer base; | ||
16 | cairo_surface_t *surface; | ||
17 | cairo_t *cairo; | ||
18 | }; | ||
19 | |||
20 | static void cairo_buffer_handle_destroy(struct wlr_buffer *wlr_buffer) { | ||
21 | struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); | ||
22 | |||
23 | cairo_surface_destroy(buffer->surface); | ||
24 | cairo_destroy(buffer->cairo); | ||
25 | free(buffer); | ||
26 | } | ||
27 | |||
28 | static bool cairo_buffer_handle_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, | ||
29 | uint32_t flags, void **data, uint32_t *format, size_t *stride) { | ||
30 | struct cairo_buffer *buffer = wl_container_of(wlr_buffer, buffer, base); | ||
31 | *data = cairo_image_surface_get_data(buffer->surface); | ||
32 | *stride = cairo_image_surface_get_stride(buffer->surface); | ||
33 | *format = DRM_FORMAT_ARGB8888; | ||
34 | return true; | ||
35 | } | ||
36 | |||
37 | static void cairo_buffer_handle_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { | ||
38 | // This space is intentionally left blank | ||
39 | } | ||
40 | |||
41 | static const struct wlr_buffer_impl cairo_buffer_impl = { | ||
42 | .destroy = cairo_buffer_handle_destroy, | ||
43 | .begin_data_ptr_access = cairo_buffer_handle_begin_data_ptr_access, | ||
44 | .end_data_ptr_access = cairo_buffer_handle_end_data_ptr_access, | ||
45 | }; | ||
46 | |||
47 | struct text_buffer { | ||
48 | struct wlr_scene_buffer *buffer_node; | ||
49 | char *text; | ||
50 | struct sway_text_node props; | ||
51 | |||
52 | bool visible; | ||
53 | float scale; | ||
54 | enum wl_output_subpixel subpixel; | ||
55 | |||
56 | struct wl_listener outputs_update; | ||
57 | struct wl_listener destroy; | ||
58 | }; | ||
59 | |||
60 | static int get_text_width(struct sway_text_node *props) { | ||
61 | if (props->max_width) { | ||
62 | return MIN(props->max_width, props->width); | ||
63 | } | ||
64 | |||
65 | return props->width; | ||
66 | } | ||
67 | |||
68 | static void update_source_box(struct text_buffer *buffer) { | ||
69 | struct sway_text_node *props = &buffer->props; | ||
70 | struct wlr_fbox source_box = { | ||
71 | .x = 0, | ||
72 | .y = 0, | ||
73 | .width = ceil(get_text_width(props) * buffer->scale), | ||
74 | .height = ceil(props->height * buffer->scale), | ||
75 | }; | ||
76 | |||
77 | wlr_scene_buffer_set_source_box(buffer->buffer_node, &source_box); | ||
78 | } | ||
79 | |||
80 | static void render_backing_buffer(struct text_buffer *buffer) { | ||
81 | if (!buffer->visible) { | ||
82 | return; | ||
83 | } | ||
84 | |||
85 | float scale = buffer->scale; | ||
86 | int width = ceil(buffer->props.width * scale); | ||
87 | int height = ceil(buffer->props.height * scale); | ||
88 | float *color = (float *)&buffer->props.color; | ||
89 | float *background = (float *)&buffer->props.background; | ||
90 | PangoContext *pango = NULL; | ||
91 | |||
92 | cairo_font_options_t *fo = cairo_font_options_create(); | ||
93 | cairo_font_options_set_hint_style(fo, CAIRO_HINT_STYLE_FULL); | ||
94 | enum wl_output_subpixel subpixel = buffer->subpixel; | ||
95 | if (subpixel == WL_OUTPUT_SUBPIXEL_NONE || subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) { | ||
96 | cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_GRAY); | ||
97 | } else { | ||
98 | cairo_font_options_set_antialias(fo, CAIRO_ANTIALIAS_SUBPIXEL); | ||
99 | cairo_font_options_set_subpixel_order(fo, to_cairo_subpixel_order(subpixel)); | ||
100 | } | ||
101 | |||
102 | cairo_surface_t *surface = cairo_image_surface_create( | ||
103 | CAIRO_FORMAT_ARGB32, width, height); | ||
104 | cairo_status_t status = cairo_surface_status(surface); | ||
105 | if (status != CAIRO_STATUS_SUCCESS) { | ||
106 | sway_log(SWAY_ERROR, "cairo_image_surface_create failed: %s", | ||
107 | cairo_status_to_string(status)); | ||
108 | goto err; | ||
109 | } | ||
110 | |||
111 | struct cairo_buffer *cairo_buffer = calloc(1, sizeof(*cairo_buffer)); | ||
112 | if (!cairo_buffer) { | ||
113 | sway_log(SWAY_ERROR, "cairo_buffer allocation failed"); | ||
114 | goto err; | ||
115 | } | ||
116 | |||
117 | cairo_t *cairo = cairo_create(surface); | ||
118 | if (!cairo) { | ||
119 | sway_log(SWAY_ERROR, "cairo_create failed"); | ||
120 | free(cairo_buffer); | ||
121 | goto err; | ||
122 | } | ||
123 | |||
124 | cairo_set_antialias(cairo, CAIRO_ANTIALIAS_BEST); | ||
125 | cairo_set_font_options(cairo, fo); | ||
126 | pango = pango_cairo_create_context(cairo); | ||
127 | |||
128 | cairo_set_source_rgba(cairo, background[0], background[1], background[2], background[3]); | ||
129 | cairo_rectangle(cairo, 0, 0, width, height); | ||
130 | cairo_fill(cairo); | ||
131 | |||
132 | cairo_set_source_rgba(cairo, color[0], color[1], color[2], color[3]); | ||
133 | cairo_move_to(cairo, 0, (config->font_baseline - buffer->props.baseline) * scale); | ||
134 | |||
135 | render_text(cairo, config->font_description, scale, buffer->props.pango_markup, | ||
136 | "%s", buffer->text); | ||
137 | |||
138 | cairo_surface_flush(surface); | ||
139 | |||
140 | wlr_buffer_init(&cairo_buffer->base, &cairo_buffer_impl, width, height); | ||
141 | cairo_buffer->surface = surface; | ||
142 | cairo_buffer->cairo = cairo; | ||
143 | |||
144 | wlr_scene_buffer_set_buffer(buffer->buffer_node, &cairo_buffer->base); | ||
145 | wlr_buffer_drop(&cairo_buffer->base); | ||
146 | update_source_box(buffer); | ||
147 | |||
148 | pixman_region32_t opaque; | ||
149 | pixman_region32_init(&opaque); | ||
150 | if (background[3] == 1) { | ||
151 | pixman_region32_union_rect(&opaque, &opaque, 0, 0, | ||
152 | buffer->props.width, buffer->props.height); | ||
153 | } | ||
154 | wlr_scene_buffer_set_opaque_region(buffer->buffer_node, &opaque); | ||
155 | pixman_region32_fini(&opaque); | ||
156 | |||
157 | err: | ||
158 | if (pango) g_object_unref(pango); | ||
159 | cairo_font_options_destroy(fo); | ||
160 | } | ||
161 | |||
162 | static void handle_outputs_update(struct wl_listener *listener, void *data) { | ||
163 | struct text_buffer *buffer = wl_container_of(listener, buffer, outputs_update); | ||
164 | struct wlr_scene_outputs_update_event *event = data; | ||
165 | |||
166 | float scale = 0; | ||
167 | enum wl_output_subpixel subpixel = WL_OUTPUT_SUBPIXEL_UNKNOWN; | ||
168 | |||
169 | for (size_t i = 0; i < event->size; i++) { | ||
170 | struct wlr_scene_output *output = event->active[i]; | ||
171 | if (subpixel == WL_OUTPUT_SUBPIXEL_UNKNOWN) { | ||
172 | subpixel = output->output->subpixel; | ||
173 | } else if (subpixel != output->output->subpixel) { | ||
174 | subpixel = WL_OUTPUT_SUBPIXEL_NONE; | ||
175 | } | ||
176 | |||
177 | if (scale != 0 && scale != output->output->scale) { | ||
178 | // drop down to gray scale if we encounter outputs with different | ||
179 | // scales or else we will have chromatic aberations | ||
180 | subpixel = WL_OUTPUT_SUBPIXEL_NONE; | ||
181 | } | ||
182 | |||
183 | if (scale < output->output->scale) { | ||
184 | scale = output->output->scale; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | buffer->visible = event->size > 0; | ||
189 | |||
190 | if (scale != buffer->scale || subpixel != buffer->subpixel) { | ||
191 | buffer->scale = scale; | ||
192 | buffer->subpixel = subpixel; | ||
193 | render_backing_buffer(buffer); | ||
194 | } | ||
195 | } | ||
196 | |||
197 | static void handle_destroy(struct wl_listener *listener, void *data) { | ||
198 | struct text_buffer *buffer = wl_container_of(listener, buffer, destroy); | ||
199 | |||
200 | wl_list_remove(&buffer->outputs_update.link); | ||
201 | wl_list_remove(&buffer->destroy.link); | ||
202 | |||
203 | free(buffer->text); | ||
204 | free(buffer); | ||
205 | } | ||
206 | |||
207 | static void text_calc_size(struct text_buffer *buffer) { | ||
208 | struct sway_text_node *props = &buffer->props; | ||
209 | |||
210 | cairo_t *c = cairo_create(NULL); | ||
211 | if (!c) { | ||
212 | sway_log(SWAY_ERROR, "cairo_t allocation failed"); | ||
213 | return; | ||
214 | } | ||
215 | |||
216 | cairo_set_antialias(c, CAIRO_ANTIALIAS_BEST); | ||
217 | get_text_size(c, config->font_description, &props->width, NULL, | ||
218 | &props->baseline, 1, props->pango_markup, "%s", buffer->text); | ||
219 | cairo_destroy(c); | ||
220 | |||
221 | wlr_scene_buffer_set_dest_size(buffer->buffer_node, | ||
222 | get_text_width(props), props->height); | ||
223 | } | ||
224 | |||
225 | struct sway_text_node *sway_text_node_create(struct wlr_scene_tree *parent, | ||
226 | char *text, float color[4], bool pango_markup) { | ||
227 | struct text_buffer *buffer = calloc(1, sizeof(*buffer)); | ||
228 | if (!buffer) { | ||
229 | return NULL; | ||
230 | } | ||
231 | |||
232 | struct wlr_scene_buffer *node = wlr_scene_buffer_create(parent, NULL); | ||
233 | if (!node) { | ||
234 | free(buffer); | ||
235 | return NULL; | ||
236 | } | ||
237 | |||
238 | buffer->buffer_node = node; | ||
239 | buffer->props.node = &node->node; | ||
240 | buffer->text = strdup(text); | ||
241 | if (!buffer->text) { | ||
242 | free(buffer); | ||
243 | wlr_scene_node_destroy(&node->node); | ||
244 | return NULL; | ||
245 | } | ||
246 | |||
247 | buffer->props.height = config->font_height; | ||
248 | buffer->props.pango_markup = pango_markup; | ||
249 | memcpy(&buffer->props.color, color, sizeof(*color) * 4); | ||
250 | |||
251 | buffer->destroy.notify = handle_destroy; | ||
252 | wl_signal_add(&node->node.events.destroy, &buffer->destroy); | ||
253 | buffer->outputs_update.notify = handle_outputs_update; | ||
254 | wl_signal_add(&node->events.outputs_update, &buffer->outputs_update); | ||
255 | |||
256 | text_calc_size(buffer); | ||
257 | |||
258 | return &buffer->props; | ||
259 | } | ||
260 | |||
261 | void sway_text_node_set_color(struct sway_text_node *node, float color[4]) { | ||
262 | if (memcmp(&node->color, color, sizeof(*color) * 4) == 0) { | ||
263 | return; | ||
264 | } | ||
265 | |||
266 | memcpy(&node->color, color, sizeof(*color) * 4); | ||
267 | struct text_buffer *buffer = wl_container_of(node, buffer, props); | ||
268 | |||
269 | render_backing_buffer(buffer); | ||
270 | } | ||
271 | |||
272 | void sway_text_node_set_text(struct sway_text_node *node, char *text) { | ||
273 | struct text_buffer *buffer = wl_container_of(node, buffer, props); | ||
274 | if (strcmp(buffer->text, text) == 0) { | ||
275 | return; | ||
276 | } | ||
277 | |||
278 | char *new_text = strdup(text); | ||
279 | if (!new_text) { | ||
280 | return; | ||
281 | } | ||
282 | |||
283 | free(buffer->text); | ||
284 | buffer->text = new_text; | ||
285 | |||
286 | text_calc_size(buffer); | ||
287 | render_backing_buffer(buffer); | ||
288 | } | ||
289 | |||
290 | void sway_text_node_set_max_width(struct sway_text_node *node, int max_width) { | ||
291 | struct text_buffer *buffer = wl_container_of(node, buffer, props); | ||
292 | buffer->props.max_width = max_width; | ||
293 | wlr_scene_buffer_set_dest_size(buffer->buffer_node, | ||
294 | get_text_width(&buffer->props), buffer->props.height); | ||
295 | update_source_box(buffer); | ||
296 | render_backing_buffer(buffer); | ||
297 | } | ||
298 | |||
299 | void sway_text_node_set_background(struct sway_text_node *node, float background[4]) { | ||
300 | struct text_buffer *buffer = wl_container_of(node, buffer, props); | ||
301 | memcpy(&node->background, background, sizeof(*background) * 4); | ||
302 | render_backing_buffer(buffer); | ||
303 | } | ||