main shrub/neuswc / libswc / decor.c
  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(&region, x, y, width, height);
300	pixman_region32_intersect(&region, &region, damage);
301	pixman_region32_subtract(&region, &region, &view->clip);
302	boxes = pixman_region32_rectangles(&region, &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(&region);
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}