1#include "decor.h"
2
3#include "compositor.h"
4#include "internal.h"
5#include "shm.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 union wld_object object;
191 size_t size;
192
193 if (decor_part_is_empty(src)) {
194 memset(dst, 0, sizeof(*dst));
195 return true;
196 }
197
198 size = (size_t)src->stride * src->height;
199 dst->data = malloc(size);
200 if (!dst->data) {
201 return false;
202 }
203 memcpy(dst->data, src->data, size);
204
205 dst->width = src->width;
206 dst->height = src->height;
207 dst->stride = src->stride;
208 object.ptr = dst->data;
209 dst->buffer = wld_import_buffer(swc.shm->context, WLD_OBJECT_DATA, object,
210 src->width, src->height,
211 WLD_FORMAT_ARGB8888, src->stride);
212 if (!dst->buffer) {
213 free(dst->data);
214 memset(dst, 0, sizeof(*dst));
215 return false;
216 }
217
218 return true;
219}
220
221static bool
222copy_decor_parts(struct compositor_view *view, const struct swc_decor_parts *parts)
223{
224 struct decor_part_buffer copied[DECOR_PART_COUNT] = { 0 };
225 uint32_t i;
226
227 for (i = 0; i < DECOR_PART_COUNT; ++i) {
228 if (!copy_decor_part(&copied[i], decor_part_at(parts, i))) {
229 goto error;
230 }
231 }
232
233 close_decor_parts(view);
234 memcpy(view->decor.parts, copied, sizeof(copied));
235 view->decor.parts_key = parts;
236 return true;
237
238error:
239 for (i = 0; i < DECOR_PART_COUNT; ++i) {
240 if (copied[i].buffer) {
241 wld_buffer_unreference(copied[i].buffer);
242 }
243 free(copied[i].data);
244 }
245 return false;
246}
247
248static void
249close_decor_font(struct compositor_view *view)
250{
251 if (view->decor.font) {
252 wld_font_close(view->decor.font);
253 view->decor.font = NULL;
254 }
255 free(view->decor.font_name);
256 view->decor.font_name = NULL;
257}
258
259static void
260close_decor_string(struct compositor_view *view)
261{
262 free(view->decor.string);
263 view->decor.string = NULL;
264}
265
266/* draw decor part by tiling it across the target region.
267 * the part buffer is repeated to fill the entirety of some width x height area,
268 * but only damaged regions are actually rendred*/
269static void
270draw_decor_part(struct wld_renderer *renderer,
271 const struct swc_rectangle *target_geom,
272 struct compositor_view *view, pixman_region32_t *damage,
273 const struct decor_part_buffer *part, int32_t x, int32_t y,
274 uint32_t width, uint32_t height)
275{
276 pixman_region32_t region;
277 pixman_box32_t *boxes;
278 int nboxes;
279
280 if (!part->buffer || !part->width || !part->height || !width || !height) {
281 return;
282 }
283
284 pixman_region32_init_rect(®ion, x, y, width, height);
285 pixman_region32_intersect(®ion, ®ion, damage);
286 pixman_region32_subtract(®ion, ®ion, &view->clip);
287 boxes = pixman_region32_rectangles(®ion, &nboxes);
288
289 for (int i = 0; i < nboxes; ++i) {
290 int32_t rx1 = boxes[i].x1;
291 int32_t ry1 = boxes[i].y1;
292 int32_t rx2 = boxes[i].x2;
293 int32_t ry2 = boxes[i].y2;
294 int32_t start_y = y + ((ry1 - y) / (int32_t)part->height) * (int32_t)part->height;
295
296 if (start_y > ry1) {
297 start_y -= (int32_t)part->height;
298 }
299
300 for (int32_t tile_y = start_y; tile_y < ry2; tile_y += (int32_t)part->height) {
301 int32_t start_x =
302 x + ((rx1 - x) / (int32_t)part->width) * (int32_t)part->width;
303
304 if (start_x > rx1) {
305 start_x -= (int32_t)part->width;
306 }
307
308 for (int32_t tile_x = start_x; tile_x < rx2;
309 tile_x += (int32_t)part->width) {
310 int32_t clip_x1 = MAX(tile_x, rx1);
311 int32_t clip_y1 = MAX(tile_y, ry1);
312 int32_t clip_x2 = MIN(tile_x + (int32_t)part->width, rx2);
313 int32_t clip_y2 = MIN(tile_y + (int32_t)part->height, ry2);
314
315 if (clip_x2 > clip_x1 && clip_y2 > clip_y1) {
316 wld_copy_rectangle(renderer, part->buffer,
317 clip_x1 - target_geom->x,
318 clip_y1 - target_geom->y,
319 clip_x1 - tile_x, clip_y1 - tile_y,
320 (uint32_t)(clip_x2 - clip_x1),
321 (uint32_t)(clip_y2 - clip_y1));
322 }
323 }
324 }
325 }
326
327 pixman_region32_fini(®ion);
328}
329
330bool
331decor_initialize(void)
332{
333 font_context = wld_font_create_context();
334 return font_context != NULL;
335}
336
337void
338decor_finalize(void)
339{
340 if (font_context) {
341 wld_font_destroy_context(font_context);
342 font_context = NULL;
343 }
344}
345
346void
347decor_view_initialize(struct compositor_view *view)
348{
349 memset(&view->decor.text, 0, sizeof(view->decor.text));
350 view->decor.parts_key = NULL;
351 memset(view->decor.parts, 0, sizeof(view->decor.parts));
352 view->decor.string = NULL;
353 view->decor.font_name = NULL;
354 view->decor.font = NULL;
355}
356
357void
358decor_view_finalize(struct compositor_view *view)
359{
360 close_decor_parts(view);
361 close_decor_string(view);
362 close_decor_font(view);
363}
364
365void
366decor_repaint(struct wld_renderer *renderer,
367 const struct swc_rectangle *target_geom,
368 struct compositor_view *view, pixman_region32_t *damage)
369{
370 const struct swc_rectangle *geom = &view->base.geometry;
371 const struct swc_decor_text *text = &view->decor.text;
372 struct wld_font *font = view->decor.font;
373 const char *title = view->decor.string;
374 pixman_region32_t decor_region, content_region;
375 uint32_t title_len, max_width, decor_size;
376 int32_t x, y, base_x, base_y, advance, available_width;
377 struct wld_extents extents;
378 pixman_region32_t title_region;
379 int32_t outer_x = geom->x - (int32_t)view->decor.left;
380 int32_t outer_y = geom->y - (int32_t)view->decor.top;
381 uint32_t outer_width = geom->width + view->decor.left + view->decor.right;
382 uint32_t outer_height = geom->height + view->decor.top + view->decor.bottom;
383 uint32_t tl_width = view->decor.parts[DECOR_PART_TOP_LEFT].width;
384 uint32_t tl_height = view->decor.parts[DECOR_PART_TOP_LEFT].height;
385 uint32_t tr_width = view->decor.parts[DECOR_PART_TOP_RIGHT].width;
386 uint32_t tr_height = view->decor.parts[DECOR_PART_TOP_RIGHT].height;
387 uint32_t bl_width = view->decor.parts[DECOR_PART_BOTTOM_LEFT].width;
388 uint32_t bl_height = view->decor.parts[DECOR_PART_BOTTOM_LEFT].height;
389 uint32_t br_width = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].width;
390 uint32_t br_height = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].height;
391
392 if (!view->decor.top && !view->decor.right && !view->decor.bottom &&
393 !view->decor.left) {
394 return;
395 }
396
397 pixman_region32_init_rect(&decor_region, outer_x, outer_y, outer_width,
398 outer_height);
399 pixman_region32_init_rect(&content_region, geom->x, geom->y, geom->width,
400 geom->height);
401 pixman_region32_subtract(&decor_region, &decor_region, &content_region);
402 pixman_region32_intersect(&decor_region, &decor_region, damage);
403 pixman_region32_subtract(&decor_region, &decor_region, &view->clip);
404 if (pixman_region32_not_empty(&decor_region)) {
405 pixman_region32_translate(&decor_region, -target_geom->x, -target_geom->y);
406 wld_fill_region(renderer, view->decor.color, &decor_region);
407 pixman_region32_translate(&decor_region, target_geom->x, target_geom->y);
408 }
409 pixman_region32_fini(&decor_region);
410 pixman_region32_fini(&content_region);
411
412 draw_decor_part(renderer, target_geom, view, damage,
413 &view->decor.parts[DECOR_PART_TOP_LEFT], outer_x, outer_y,
414 tl_width, tl_height);
415 draw_decor_part(renderer, target_geom, view, damage,
416 &view->decor.parts[DECOR_PART_TOP_RIGHT],
417 outer_x + (int32_t)outer_width - (int32_t)tr_width, outer_y,
418 tr_width, tr_height);
419 draw_decor_part(renderer, target_geom, view, damage,
420 &view->decor.parts[DECOR_PART_BOTTOM_LEFT], outer_x,
421 outer_y + (int32_t)outer_height - (int32_t)bl_height,
422 bl_width, bl_height);
423 draw_decor_part(renderer, target_geom, view, damage,
424 &view->decor.parts[DECOR_PART_BOTTOM_RIGHT],
425 outer_x + (int32_t)outer_width - (int32_t)br_width,
426 outer_y + (int32_t)outer_height - (int32_t)br_height,
427 br_width, br_height);
428
429 if (outer_width > tl_width + tr_width) {
430 draw_decor_part(renderer, target_geom, view, damage,
431 &view->decor.parts[DECOR_PART_TOP],
432 outer_x + (int32_t)tl_width, outer_y,
433 outer_width - tl_width - tr_width, view->decor.top);
434 draw_decor_part(renderer, target_geom, view, damage,
435 &view->decor.parts[DECOR_PART_BOTTOM],
436 outer_x + (int32_t)bl_width,
437 outer_y + (int32_t)outer_height - (int32_t)view->decor.bottom,
438 outer_width - bl_width - br_width, view->decor.bottom);
439 }
440
441 if (outer_height > tl_height + bl_height) {
442 draw_decor_part(renderer, target_geom, view, damage,
443 &view->decor.parts[DECOR_PART_LEFT], outer_x,
444 outer_y + (int32_t)tl_height, view->decor.left,
445 outer_height - tl_height - bl_height);
446 draw_decor_part(renderer, target_geom, view, damage,
447 &view->decor.parts[DECOR_PART_RIGHT],
448 outer_x + (int32_t)outer_width - (int32_t)view->decor.right,
449 outer_y + (int32_t)tr_height, view->decor.right,
450 outer_height - tr_height - br_height);
451 }
452
453 if (!title || !font || !text->enabled) {
454 return;
455 }
456
457 /* calculate text position based on which edge
458 * the decor is on. base_x base_y is the top-left corner,
459 * max_width is available space
460 * decor_size is perpendicular dimension of the edge where text is being drawn */
461 switch (text->edge) {
462 case SWC_DECOR_EDGE_TOP:
463 if (!view->decor.top) {
464 return;
465 }
466 base_x = geom->x - (int32_t)view->decor.left;
467 base_y = geom->y - (int32_t)view->decor.top;
468 max_width = geom->width + view->decor.left + view->decor.right;
469 decor_size = view->decor.top;
470 break;
471 case SWC_DECOR_EDGE_BOTTOM:
472 if (!view->decor.bottom) {
473 return;
474 }
475 base_x = geom->x - (int32_t)view->decor.left;
476 base_y = geom->y + (int32_t)geom->height;
477 max_width = geom->width + view->decor.left + view->decor.right;
478 decor_size = view->decor.bottom;
479 break;
480 case SWC_DECOR_EDGE_LEFT:
481 if (!view->decor.left) {
482 return;
483 }
484 base_x = geom->x - (int32_t)view->decor.left;
485 base_y = geom->y - (int32_t)view->decor.top;
486 max_width = view->decor.left;
487 decor_size = geom->height + view->decor.top + view->decor.bottom;
488 break;
489 case SWC_DECOR_EDGE_RIGHT:
490 if (!view->decor.right) {
491 return;
492 }
493 base_x = geom->x + (int32_t)geom->width;
494 base_y = geom->y - (int32_t)view->decor.top;
495 max_width = view->decor.right;
496 decor_size = geom->height + view->decor.top + view->decor.bottom;
497 break;
498 default:
499 return;
500 }
501
502 x = base_x;
503 y = base_y;
504
505 pixman_region32_init_rect(&title_region, x, y, max_width, decor_size);
506 pixman_region32_intersect(&title_region, &title_region, damage);
507 pixman_region32_subtract(&title_region, &title_region, &view->clip);
508 if (!pixman_region32_not_empty(&title_region)) {
509 pixman_region32_fini(&title_region);
510 return;
511 }
512 pixman_region32_fini(&title_region);
513
514 if (max_width <= text->padding * 2 || decor_size < font->height) {
515 return;
516 }
517 max_width -= text->padding * 2;
518
519 if (text->edge == SWC_DECOR_EDGE_LEFT || text->edge == SWC_DECOR_EDGE_RIGHT) {
520 uint32_t glyph_count, glyph_offset = 0, available_height, total_height;
521
522 if (decor_size <= text->padding * 2) {
523 return;
524 }
525
526 available_height = decor_size - text->padding * 2;
527 title_len = decor_title_stacked_length(font, title, available_height,
528 &glyph_count);
529 if (!title_len) {
530 return;
531 }
532
533 total_height = glyph_count * font->height;
534 y += (int32_t)text->padding;
535 switch (text->align) {
536 case SWC_DECOR_ALIGN_START:
537 break;
538 case SWC_DECOR_ALIGN_CENTER:
539 y += (int32_t)((available_height - total_height) / 2);
540 break;
541 case SWC_DECOR_ALIGN_END:
542 y += (int32_t)(available_height - total_height);
543 break;
544 }
545
546 while (glyph_offset < title_len) {
547 uint32_t glyph_len = utf8_next_len(title, glyph_offset);
548 int32_t glyph_x;
549
550 wld_font_text_extents_n(font, title + glyph_offset,
551 (int32_t)glyph_len, &extents);
552 advance = extents.advance > 0 ? extents.advance : 0;
553 glyph_x = x + (int32_t)text->padding;
554 if ((uint32_t)advance < max_width) {
555 glyph_x += ((int32_t)max_width - advance) / 2;
556 }
557
558 wld_draw_text(renderer, font, text->color,
559 glyph_x + text->offset_x - target_geom->x,
560 y + (int32_t)font->ascent + text->offset_y -
561 target_geom->y,
562 title + glyph_offset, glyph_len, NULL);
563
564 glyph_offset += glyph_len;
565 y += (int32_t)font->height;
566 }
567
568 return;
569 }
570
571 title_len = decor_title_length(font, title, max_width);
572 if (!title_len) {
573 return;
574 }
575 wld_font_text_extents_n(font, title, (int32_t)title_len, &extents);
576 advance = extents.advance > 0 ? extents.advance : 0;
577 available_width = (int32_t)max_width;
578
579 switch (text->align) {
580 case SWC_DECOR_ALIGN_START:
581 x += text->padding;
582 break;
583 case SWC_DECOR_ALIGN_CENTER:
584 x += (int32_t)text->padding + (available_width - advance) / 2;
585 break;
586 case SWC_DECOR_ALIGN_END:
587 x += (int32_t)text->padding + (int32_t)max_width - advance;
588 break;
589 }
590
591 y += (int32_t)((decor_size - font->height) / 2) + (int32_t)font->ascent;
592
593 x += text->offset_x;
594 y += text->offset_y;
595
596 wld_draw_text(renderer, font, text->color, x - target_geom->x,
597 y - target_geom->y, title, title_len, NULL);
598}
599
600void
601decor_view_set(struct compositor_view *view, const struct swc_decor *decor)
602{
603 const char *font_name = NULL;
604 struct wld_font *font = NULL;
605 struct swc_decor_text text = { 0 };
606 char *owned_string = NULL;
607 char *owned_font_name = NULL;
608 const struct swc_decor_parts *parts = NULL;
609 uint32_t color = 0, top = 0, right = 0, bottom = 0, left = 0;
610
611 if (!decor) {
612 goto apply;
613 }
614
615 color = decor->color;
616 top = decor->top;
617 right = decor->right;
618 bottom = decor->bottom;
619 left = decor->left;
620 parts = decor->parts;
621 text = decor->title;
622 if (text.enabled) {
623 font_name = text.font ? text.font : DEFAULT_DECOR_FONT;
624 }
625
626 if (view->decor.color == color && view->decor.top == top &&
627 view->decor.right == right && view->decor.bottom == bottom &&
628 view->decor.left == left &&
629 view->decor.text.enabled == text.enabled &&
630 view->decor.text.edge == text.edge &&
631 view->decor.text.align == text.align &&
632 streq(view->decor.string, text.string) &&
633 view->decor.text.color == text.color &&
634 view->decor.text.padding == text.padding &&
635 view->decor.text.offset_x == text.offset_x &&
636 view->decor.text.offset_y == text.offset_y &&
637 streq(view->decor.font_name, font_name) &&
638 decor_parts_equal(view, parts)) {
639 return;
640 }
641
642 if (text.string) {
643 owned_string = strdup(text.string);
644 }
645
646 if (font_name) {
647 owned_font_name = strdup(font_name);
648 if (owned_font_name && font_context) {
649 font = wld_font_open_name(font_context, font_name);
650 }
651 }
652
653apply:
654 view->decor.color = color;
655 view->decor.top = top;
656 view->decor.right = right;
657 view->decor.bottom = bottom;
658 view->decor.left = left;
659 view->decor.text = text;
660 view->decor.text.string = NULL;
661 view->decor.text.font = NULL;
662 if (!copy_decor_parts(view, parts)) {
663 close_decor_parts(view);
664 }
665 close_decor_string(view);
666 view->decor.string = owned_string;
667 close_decor_font(view);
668 view->decor.font_name = owned_font_name;
669 view->decor.font = font;
670 view->decor.damaged = true;
671}
672
673void
674decor_view_damage(struct compositor_view *view)
675{
676 view->decor.damaged = true;
677}