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