1#include "decor.h"
2
3#include "compositor.h"
4#include "drm.h"
5#include "internal.h"
6#include "swc.h"
7#include "util.h"
8#include "window.h"
9
10#include <pixman.h>
11#include <stdlib.h>
12#include <string.h>
13#include <wld/wld.h>
14
15#define DEFAULT_DECOR_FONT "sans-serif:size=10"
16
17static struct wld_font_context *font_context;
18
19enum decor_part_index {
20 DECOR_PART_TOP_LEFT,
21 DECOR_PART_TOP,
22 DECOR_PART_TOP_RIGHT,
23 DECOR_PART_LEFT,
24 DECOR_PART_RIGHT,
25 DECOR_PART_BOTTOM_LEFT,
26 DECOR_PART_BOTTOM,
27 DECOR_PART_BOTTOM_RIGHT,
28 DECOR_PART_COUNT,
29};
30
31static uint32_t
32decor_title_length(struct wld_font *font, const char *title, uint32_t max_width)
33{
34 uint32_t len = 0;
35 struct wld_extents extents;
36
37 if (!title || !max_width) {
38 return 0;
39 }
40
41 while (title[len]) {
42 uint32_t next = len + 1;
43
44 /* we skip utf-8 continuation bytes */
45 while ((title[next] & 0xc0) == 0x80) {
46 next++;
47 }
48
49 wld_font_text_extents_n(font, title, (int32_t)next, &extents);
50 if (extents.advance > max_width) {
51 break;
52 }
53 len = next;
54 }
55
56 return len;
57}
58
59static uint32_t
60utf8_next_len(const char *text, uint32_t offset)
61{
62 uint32_t next = offset + 1;
63
64 while ((text[next] & 0xc0) == 0x80) {
65 next++;
66 }
67
68 return next - offset;
69}
70
71static uint32_t
72decor_title_stacked_length(struct wld_font *font, const char *title,
73 uint32_t max_height, uint32_t *glyph_count)
74{
75 uint32_t len = 0, count = 0;
76
77 if (!title || !font->height) {
78 *glyph_count = 0;
79 return 0;
80 }
81
82 while (title[len] && (count + 1) * font->height <= max_height) {
83 len += utf8_next_len(title, len);
84 count++;
85 }
86
87 *glyph_count = count;
88 return len;
89}
90
91static bool
92streq(const char *a, const char *b)
93{
94 if (!a || !b) {
95 return a == b;
96 }
97 return strcmp(a, b) == 0;
98}
99
100static const struct swc_decor_part *
101decor_part_at(const struct swc_decor_parts *parts, enum decor_part_index index)
102{
103 if (!parts) {
104 return NULL;
105 }
106
107 switch (index) {
108 case DECOR_PART_TOP_LEFT:
109 return &parts->top_left;
110 case DECOR_PART_TOP:
111 return &parts->top;
112 case DECOR_PART_TOP_RIGHT:
113 return &parts->top_right;
114 case DECOR_PART_LEFT:
115 return &parts->left;
116 case DECOR_PART_RIGHT:
117 return &parts->right;
118 case DECOR_PART_BOTTOM_LEFT:
119 return &parts->bottom_left;
120 case DECOR_PART_BOTTOM:
121 return &parts->bottom;
122 case DECOR_PART_BOTTOM_RIGHT:
123 return &parts->bottom_right;
124 case DECOR_PART_COUNT:
125 break;
126 }
127
128 return NULL;
129}
130
131static void
132close_decor_parts(struct compositor_view *view)
133{
134 for (uint32_t i = 0; i < DECOR_PART_COUNT; ++i) {
135 if (view->decor.parts[i].buffer) {
136 wld_buffer_unreference(view->decor.parts[i].buffer);
137 view->decor.parts[i].buffer = NULL;
138 }
139 free(view->decor.parts[i].data);
140 view->decor.parts[i].data = NULL;
141 view->decor.parts[i].width = 0;
142 view->decor.parts[i].height = 0;
143 view->decor.parts[i].stride = 0;
144 }
145 view->decor.parts_key = NULL;
146}
147
148static bool
149decor_part_is_empty(const struct swc_decor_part *part)
150{
151 return !part || !part->data || !part->width || !part->height ||
152 !part->stride;
153}
154
155static bool
156decor_part_equal(const struct decor_part_buffer *owned,
157 const struct swc_decor_part *part)
158{
159 size_t size;
160
161 if (decor_part_is_empty(part)) {
162 return !owned->data && !owned->buffer && !owned->width && !owned->height &&
163 !owned->stride;
164 }
165
166 if (!owned->data || owned->width != part->width ||
167 owned->height != part->height || owned->stride != part->stride) {
168 return false;
169 }
170
171 size = (size_t)part->stride * part->height;
172 return memcmp(owned->data, part->data, size) == 0;
173}
174
175static bool
176decor_parts_equal(struct compositor_view *view, const struct swc_decor_parts *parts)
177{
178 for (uint32_t i = 0; i < DECOR_PART_COUNT; ++i) {
179 if (!decor_part_equal(&view->decor.parts[i], decor_part_at(parts, i))) {
180 return false;
181 }
182 }
183
184 return true;
185}
186
187static bool
188copy_decor_part(struct decor_part_buffer *dst, const struct swc_decor_part *src)
189{
190 size_t row_size, size;
191
192 if (decor_part_is_empty(src)) {
193 memset(dst, 0, sizeof(*dst));
194 return true;
195 }
196
197 row_size = (size_t)src->width * 4;
198 if (src->stride < row_size ||
199 (src->height && src->stride > SIZE_MAX / src->height)) {
200 return false;
201 }
202 size = (size_t)src->stride * src->height;
203 dst->data = malloc(size);
204 if (!dst->data) {
205 return false;
206 }
207 memcpy(dst->data, src->data, size);
208
209 dst->width = src->width;
210 dst->height = src->height;
211 dst->stride = src->stride;
212 /* DRM renderers only allow read support for their native buffers not pixman/shmbuffers */
213 dst->buffer = wld_create_buffer(swc.drm->context, src->width, src->height,
214 WLD_FORMAT_ARGB8888, WLD_FLAG_MAP);
215 if (!dst->buffer) {
216 free(dst->data);
217 memset(dst, 0, sizeof(*dst));
218 return false;
219 }
220
221 if (dst->buffer->pitch < row_size || !wld_map(dst->buffer)) {
222 wld_buffer_unreference(dst->buffer);
223 free(dst->data);
224 memset(dst, 0, sizeof(*dst));
225 return false;
226 }
227 for (uint32_t y = 0; y < src->height; ++y) {
228 memcpy((uint8_t *)dst->buffer->map + (size_t)y * dst->buffer->pitch,
229 (uint8_t *)dst->data + (size_t)y * src->stride, row_size);
230 }
231 wld_unmap(dst->buffer);
232
233 return true;
234}
235
236static bool
237copy_decor_parts(struct compositor_view *view, const struct swc_decor_parts *parts)
238{
239 struct decor_part_buffer copied[DECOR_PART_COUNT] = { 0 };
240 uint32_t i;
241
242 for (i = 0; i < DECOR_PART_COUNT; ++i) {
243 if (!copy_decor_part(&copied[i], decor_part_at(parts, i))) {
244 goto error;
245 }
246 }
247
248 close_decor_parts(view);
249 memcpy(view->decor.parts, copied, sizeof(copied));
250 view->decor.parts_key = parts;
251 return true;
252
253error:
254 for (i = 0; i < DECOR_PART_COUNT; ++i) {
255 if (copied[i].buffer) {
256 wld_buffer_unreference(copied[i].buffer);
257 }
258 free(copied[i].data);
259 }
260 return false;
261}
262
263static void
264close_decor_font(struct compositor_view *view)
265{
266 if (view->decor.font) {
267 wld_font_close(view->decor.font);
268 view->decor.font = NULL;
269 }
270 free(view->decor.font_name);
271 view->decor.font_name = NULL;
272}
273
274static void
275close_decor_string(struct compositor_view *view)
276{
277 free(view->decor.string);
278 view->decor.string = NULL;
279}
280
281/* draw decor part by tiling it across the target region.
282 * the part buffer is repeated to fill the entirety of some width x height area,
283 * but only damaged regions are actually rendered */
284static void
285draw_decor_part(struct wld_renderer *renderer,
286 const struct swc_rectangle *target_geom,
287 struct compositor_view *view, pixman_region32_t *damage,
288 const struct decor_part_buffer *part, int32_t x, int32_t y,
289 uint32_t width, uint32_t height)
290{
291 pixman_region32_t region;
292 pixman_box32_t *boxes;
293 int nboxes;
294
295 if (!part->buffer || !part->width || !part->height || !width || !height) {
296 return;
297 }
298
299 pixman_region32_init_rect(®ion, x, y, width, height);
300 pixman_region32_intersect(®ion, ®ion, damage);
301 pixman_region32_subtract(®ion, ®ion, &view->clip);
302 boxes = pixman_region32_rectangles(®ion, &nboxes);
303
304 for (int i = 0; i < nboxes; ++i) {
305 int32_t rx1 = boxes[i].x1;
306 int32_t ry1 = boxes[i].y1;
307 int32_t rx2 = boxes[i].x2;
308 int32_t ry2 = boxes[i].y2;
309 int32_t start_y = y + ((ry1 - y) / (int32_t)part->height) * (int32_t)part->height;
310
311 if (start_y > ry1) {
312 start_y -= (int32_t)part->height;
313 }
314
315 for (int32_t tile_y = start_y; tile_y < ry2; tile_y += (int32_t)part->height) {
316 int32_t start_x =
317 x + ((rx1 - x) / (int32_t)part->width) * (int32_t)part->width;
318
319 if (start_x > rx1) {
320 start_x -= (int32_t)part->width;
321 }
322
323 for (int32_t tile_x = start_x; tile_x < rx2;
324 tile_x += (int32_t)part->width) {
325 int32_t clip_x1 = MAX(tile_x, rx1);
326 int32_t clip_y1 = MAX(tile_y, ry1);
327 int32_t clip_x2 = MIN(tile_x + (int32_t)part->width, rx2);
328 int32_t clip_y2 = MIN(tile_y + (int32_t)part->height, ry2);
329
330 if (clip_x2 > clip_x1 && clip_y2 > clip_y1) {
331 wld_copy_rectangle(renderer, part->buffer,
332 clip_x1 - target_geom->x,
333 clip_y1 - target_geom->y,
334 clip_x1 - tile_x, clip_y1 - tile_y,
335 (uint32_t)(clip_x2 - clip_x1),
336 (uint32_t)(clip_y2 - clip_y1));
337 }
338 }
339 }
340 }
341
342 pixman_region32_fini(®ion);
343}
344
345bool
346decor_initialize(void)
347{
348 font_context = wld_font_create_context();
349 return font_context != NULL;
350}
351
352void
353decor_finalize(void)
354{
355 if (font_context) {
356 wld_font_destroy_context(font_context);
357 font_context = NULL;
358 }
359}
360
361void
362decor_view_initialize(struct compositor_view *view)
363{
364 memset(&view->decor.text, 0, sizeof(view->decor.text));
365 view->decor.parts_key = NULL;
366 memset(view->decor.parts, 0, sizeof(view->decor.parts));
367 view->decor.string = NULL;
368 view->decor.font_name = NULL;
369 view->decor.font = NULL;
370}
371
372void
373decor_view_finalize(struct compositor_view *view)
374{
375 close_decor_parts(view);
376 close_decor_string(view);
377 close_decor_font(view);
378}
379
380void
381decor_repaint(struct wld_renderer *renderer,
382 const struct swc_rectangle *target_geom,
383 struct compositor_view *view, pixman_region32_t *damage)
384{
385 const struct swc_rectangle *geom = &view->base.geometry;
386 const struct swc_decor_text *text = &view->decor.text;
387 struct wld_font *font = view->decor.font;
388 const char *title = view->decor.string;
389 pixman_region32_t decor_region, content_region;
390 uint32_t title_len, max_width, decor_size;
391 int32_t x, y, base_x, base_y, advance, available_width;
392 struct wld_extents extents;
393 pixman_region32_t title_region;
394 int32_t outer_x = geom->x - (int32_t)view->decor.left;
395 int32_t outer_y = geom->y - (int32_t)view->decor.top;
396 uint32_t outer_width = geom->width + view->decor.left + view->decor.right;
397 uint32_t outer_height = geom->height + view->decor.top + view->decor.bottom;
398 uint32_t tl_width = view->decor.parts[DECOR_PART_TOP_LEFT].width;
399 uint32_t tl_height = view->decor.parts[DECOR_PART_TOP_LEFT].height;
400 uint32_t tr_width = view->decor.parts[DECOR_PART_TOP_RIGHT].width;
401 uint32_t tr_height = view->decor.parts[DECOR_PART_TOP_RIGHT].height;
402 uint32_t bl_width = view->decor.parts[DECOR_PART_BOTTOM_LEFT].width;
403 uint32_t bl_height = view->decor.parts[DECOR_PART_BOTTOM_LEFT].height;
404 uint32_t br_width = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].width;
405 uint32_t br_height = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].height;
406
407 if (!view->decor.top && !view->decor.right && !view->decor.bottom &&
408 !view->decor.left) {
409 return;
410 }
411
412 pixman_region32_init_rect(&decor_region, outer_x, outer_y, outer_width,
413 outer_height);
414 pixman_region32_init_rect(&content_region, geom->x, geom->y, geom->width,
415 geom->height);
416 pixman_region32_subtract(&decor_region, &decor_region, &content_region);
417 pixman_region32_intersect(&decor_region, &decor_region, damage);
418 pixman_region32_subtract(&decor_region, &decor_region, &view->clip);
419 if (pixman_region32_not_empty(&decor_region)) {
420 pixman_region32_translate(&decor_region, -target_geom->x, -target_geom->y);
421 wld_fill_region(renderer, view->decor.color, &decor_region);
422 pixman_region32_translate(&decor_region, target_geom->x, target_geom->y);
423 }
424 pixman_region32_fini(&decor_region);
425 pixman_region32_fini(&content_region);
426
427 draw_decor_part(renderer, target_geom, view, damage,
428 &view->decor.parts[DECOR_PART_TOP_LEFT], outer_x, outer_y,
429 tl_width, tl_height);
430 draw_decor_part(renderer, target_geom, view, damage,
431 &view->decor.parts[DECOR_PART_TOP_RIGHT],
432 outer_x + (int32_t)outer_width - (int32_t)tr_width, outer_y,
433 tr_width, tr_height);
434 draw_decor_part(renderer, target_geom, view, damage,
435 &view->decor.parts[DECOR_PART_BOTTOM_LEFT], outer_x,
436 outer_y + (int32_t)outer_height - (int32_t)bl_height,
437 bl_width, bl_height);
438 draw_decor_part(renderer, target_geom, view, damage,
439 &view->decor.parts[DECOR_PART_BOTTOM_RIGHT],
440 outer_x + (int32_t)outer_width - (int32_t)br_width,
441 outer_y + (int32_t)outer_height - (int32_t)br_height,
442 br_width, br_height);
443
444 if (outer_width > tl_width + tr_width) {
445 draw_decor_part(renderer, target_geom, view, damage,
446 &view->decor.parts[DECOR_PART_TOP],
447 outer_x + (int32_t)tl_width, outer_y,
448 outer_width - tl_width - tr_width, view->decor.top);
449 draw_decor_part(renderer, target_geom, view, damage,
450 &view->decor.parts[DECOR_PART_BOTTOM],
451 outer_x + (int32_t)bl_width,
452 outer_y + (int32_t)outer_height - (int32_t)view->decor.bottom,
453 outer_width - bl_width - br_width, view->decor.bottom);
454 }
455
456 if (outer_height > tl_height + bl_height) {
457 draw_decor_part(renderer, target_geom, view, damage,
458 &view->decor.parts[DECOR_PART_LEFT], outer_x,
459 outer_y + (int32_t)tl_height, view->decor.left,
460 outer_height - tl_height - bl_height);
461 draw_decor_part(renderer, target_geom, view, damage,
462 &view->decor.parts[DECOR_PART_RIGHT],
463 outer_x + (int32_t)outer_width - (int32_t)view->decor.right,
464 outer_y + (int32_t)tr_height, view->decor.right,
465 outer_height - tr_height - br_height);
466 }
467
468 if (!title || !font || !text->enabled) {
469 return;
470 }
471
472 /* calculate text position based on which edge
473 * the decor is on. base_x base_y is the top-left corner,
474 * max_width is available space
475 * decor_size is perpendicular dimension of the edge where text is being drawn */
476 switch (text->edge) {
477 case SWC_DECOR_EDGE_TOP:
478 if (!view->decor.top) {
479 return;
480 }
481 base_x = geom->x - (int32_t)view->decor.left;
482 base_y = geom->y - (int32_t)view->decor.top;
483 max_width = geom->width + view->decor.left + view->decor.right;
484 decor_size = view->decor.top;
485 break;
486 case SWC_DECOR_EDGE_BOTTOM:
487 if (!view->decor.bottom) {
488 return;
489 }
490 base_x = geom->x - (int32_t)view->decor.left;
491 base_y = geom->y + (int32_t)geom->height;
492 max_width = geom->width + view->decor.left + view->decor.right;
493 decor_size = view->decor.bottom;
494 break;
495 case SWC_DECOR_EDGE_LEFT:
496 if (!view->decor.left) {
497 return;
498 }
499 base_x = geom->x - (int32_t)view->decor.left;
500 base_y = geom->y - (int32_t)view->decor.top;
501 max_width = view->decor.left;
502 decor_size = geom->height + view->decor.top + view->decor.bottom;
503 break;
504 case SWC_DECOR_EDGE_RIGHT:
505 if (!view->decor.right) {
506 return;
507 }
508 base_x = geom->x + (int32_t)geom->width;
509 base_y = geom->y - (int32_t)view->decor.top;
510 max_width = view->decor.right;
511 decor_size = geom->height + view->decor.top + view->decor.bottom;
512 break;
513 default:
514 return;
515 }
516
517 x = base_x;
518 y = base_y;
519
520 pixman_region32_init_rect(&title_region, x, y, max_width, decor_size);
521 pixman_region32_intersect(&title_region, &title_region, damage);
522 pixman_region32_subtract(&title_region, &title_region, &view->clip);
523 if (!pixman_region32_not_empty(&title_region)) {
524 pixman_region32_fini(&title_region);
525 return;
526 }
527 pixman_region32_fini(&title_region);
528
529 if (max_width <= text->padding * 2 || decor_size < font->height) {
530 return;
531 }
532 max_width -= text->padding * 2;
533
534 if (text->edge == SWC_DECOR_EDGE_LEFT || text->edge == SWC_DECOR_EDGE_RIGHT) {
535 uint32_t glyph_count, glyph_offset = 0, available_height, total_height;
536
537 if (decor_size <= text->padding * 2) {
538 return;
539 }
540
541 available_height = decor_size - text->padding * 2;
542 title_len = decor_title_stacked_length(font, title, available_height,
543 &glyph_count);
544 if (!title_len) {
545 return;
546 }
547
548 total_height = glyph_count * font->height;
549 y += (int32_t)text->padding;
550 switch (text->align) {
551 case SWC_DECOR_ALIGN_START:
552 break;
553 case SWC_DECOR_ALIGN_CENTER:
554 y += (int32_t)((available_height - total_height) / 2);
555 break;
556 case SWC_DECOR_ALIGN_END:
557 y += (int32_t)(available_height - total_height);
558 break;
559 }
560
561 while (glyph_offset < title_len) {
562 uint32_t glyph_len = utf8_next_len(title, glyph_offset);
563 int32_t glyph_x;
564
565 wld_font_text_extents_n(font, title + glyph_offset,
566 (int32_t)glyph_len, &extents);
567 advance = extents.advance > 0 ? extents.advance : 0;
568 glyph_x = x + (int32_t)text->padding;
569 if ((uint32_t)advance < max_width) {
570 glyph_x += ((int32_t)max_width - advance) / 2;
571 }
572
573 wld_draw_text(renderer, font, text->color,
574 glyph_x + text->offset_x - target_geom->x,
575 y + (int32_t)font->ascent + text->offset_y -
576 target_geom->y,
577 title + glyph_offset, glyph_len, NULL);
578
579 glyph_offset += glyph_len;
580 y += (int32_t)font->height;
581 }
582
583 return;
584 }
585
586 title_len = decor_title_length(font, title, max_width);
587 if (!title_len) {
588 return;
589 }
590 wld_font_text_extents_n(font, title, (int32_t)title_len, &extents);
591 advance = extents.advance > 0 ? extents.advance : 0;
592 available_width = (int32_t)max_width;
593
594 switch (text->align) {
595 case SWC_DECOR_ALIGN_START:
596 x += text->padding;
597 break;
598 case SWC_DECOR_ALIGN_CENTER:
599 x += (int32_t)text->padding + (available_width - advance) / 2;
600 break;
601 case SWC_DECOR_ALIGN_END:
602 x += (int32_t)text->padding + (int32_t)max_width - advance;
603 break;
604 }
605
606 y += (int32_t)((decor_size - font->height) / 2) + (int32_t)font->ascent;
607
608 x += text->offset_x;
609 y += text->offset_y;
610
611 wld_draw_text(renderer, font, text->color, x - target_geom->x,
612 y - target_geom->y, title, title_len, NULL);
613}
614
615void
616decor_view_set(struct compositor_view *view, const struct swc_decor *decor)
617{
618 const char *font_name = NULL;
619 struct wld_font *font = NULL;
620 struct swc_decor_text text = { 0 };
621 char *owned_string = NULL;
622 char *owned_font_name = NULL;
623 const struct swc_decor_parts *parts = NULL;
624 uint32_t color = 0, top = 0, right = 0, bottom = 0, left = 0;
625
626 if (!decor) {
627 goto apply;
628 }
629
630 color = decor->color;
631 top = decor->top;
632 right = decor->right;
633 bottom = decor->bottom;
634 left = decor->left;
635 parts = decor->parts;
636 text = decor->title;
637 if (text.enabled) {
638 font_name = text.font ? text.font : DEFAULT_DECOR_FONT;
639 }
640
641 if (view->decor.color == color && view->decor.top == top &&
642 view->decor.right == right && view->decor.bottom == bottom &&
643 view->decor.left == left &&
644 view->decor.text.enabled == text.enabled &&
645 view->decor.text.edge == text.edge &&
646 view->decor.text.align == text.align &&
647 streq(view->decor.string, text.string) &&
648 view->decor.text.color == text.color &&
649 view->decor.text.padding == text.padding &&
650 view->decor.text.offset_x == text.offset_x &&
651 view->decor.text.offset_y == text.offset_y &&
652 streq(view->decor.font_name, font_name) &&
653 decor_parts_equal(view, parts)) {
654 return;
655 }
656
657 if (text.string) {
658 owned_string = strdup(text.string);
659 }
660
661 if (font_name) {
662 owned_font_name = strdup(font_name);
663 if (owned_font_name && font_context) {
664 font = wld_font_open_name(font_context, font_name);
665 }
666 }
667
668apply:
669 view->decor.color = color;
670 view->decor.top = top;
671 view->decor.right = right;
672 view->decor.bottom = bottom;
673 view->decor.left = left;
674 view->decor.text = text;
675 view->decor.text.string = NULL;
676 view->decor.text.font = NULL;
677 if (!copy_decor_parts(view, parts)) {
678 close_decor_parts(view);
679 }
680 close_decor_string(view);
681 view->decor.string = owned_string;
682 close_decor_font(view);
683 view->decor.font_name = owned_font_name;
684 view->decor.font = font;
685 view->decor.damaged = true;
686}
687
688void
689decor_view_damage(struct compositor_view *view)
690{
691 view->decor.damaged = true;
692}