1 #include "render.h"
2
3 #include <stdlib.h>
4 #include <stdio.h>
5 #include <string.h>
6
7 #include <fcft/fcft.h>
8
9 #if defined(FUZZEL_ENABLE_SVG_NANOSVG)
10 #include <nanosvgrast.h>
11 #endif
12
13 #define LOG_MODULE "render"
14 #define LOG_ENABLE_DBG 0
15 #include "log.h"
16 #include "stride.h"
17 #include "wayland.h"
18
19 #define min(x, y) ((x) < (y) ? (x) : (y))
20 #define max(x, y) ((x) > (y) ? (x) : (y))
21
22 struct render {
23 struct render_options options;
24 struct fcft_font *font;
25 enum fcft_subpixel subpixel;
26
27 int scale;
28 float dpi;
29 bool size_font_by_dpi;
30
31 unsigned x_margin;
32 unsigned y_margin;
33 unsigned inner_pad;
34 unsigned border_size;
35 unsigned row_height;
36 unsigned icon_height;
37 };
38
39 static pixman_color_t
rgba2pixman(struct rgba rgba)40 rgba2pixman(struct rgba rgba)
41 {
42 uint16_t r = rgba.r * 65535.0;
43 uint16_t g = rgba.g * 65535.0;
44 uint16_t b = rgba.b * 65535.0;
45 uint16_t a = rgba.a * 65535.0;
46
47 return (pixman_color_t){
48 .red = (uint32_t)r * a / 0xffff,
49 .green = (uint32_t)g * a / 0xffff,
50 .blue = (uint32_t)b * a / 0xffff,
51 .alpha = a,
52 };
53 }
54
55 static int
pt_or_px_as_pixels(const struct render * render,const struct pt_or_px * pt_or_px)56 pt_or_px_as_pixels(const struct render *render, const struct pt_or_px *pt_or_px)
57 {
58 double scale = !render->size_font_by_dpi ? render->scale : 1.;
59 double dpi = render->size_font_by_dpi ? render->dpi : 96.;
60
61 return pt_or_px->px == 0
62 ? round(pt_or_px->pt * scale * dpi / 72.)
63 : pt_or_px->px;
64 }
65
66 void
render_background(const struct render * render,struct buffer * buf)67 render_background(const struct render *render, struct buffer *buf)
68 {
69 bool use_pixman =
70 #if defined(FUZZEL_ENABLE_CAIRO)
71 render->options.border_radius == 0
72 #else
73 true
74 #endif
75 ;
76
77 if (use_pixman) {
78 unsigned bw = render->options.border_size;
79
80 pixman_color_t bg = rgba2pixman(render->options.background_color);
81 pixman_image_fill_rectangles(
82 PIXMAN_OP_SRC, buf->pix, &bg,
83 1, &(pixman_rectangle16_t){
84 bw, bw, buf->width - 2 * bw, buf->height - 2 * bw});
85
86 pixman_color_t border_color = rgba2pixman(render->options.border_color);
87 pixman_image_fill_rectangles(
88 PIXMAN_OP_SRC, buf->pix, &border_color,
89 4, (pixman_rectangle16_t[]){
90 {0, 0, buf->width, bw}, /* top */
91 {0, bw, bw, buf->height - 2 * bw}, /* left */
92 {buf->width - bw, bw, bw, buf->height - 2 * bw}, /* right */
93 {0, buf->height - bw, buf->width, bw} /* bottom */
94 });
95 } else {
96 #if defined(FUZZEL_ENABLE_CAIRO)
97 /*
98 * Lines in cairo are *between* pixels.
99 *
100 * To get a sharp 1px line, we need to draw it with
101 * line-width=2.
102 *
103 * Thus, we need to draw the path offset:ed with half that
104 * (=actual border width).
105 */
106 const double b = render->border_size;
107 const double w = max(buf->width - 2 * b, 0.);
108 const double h = max(buf->height - 2 * b, 0.);
109
110 const double from_degree = M_PI / 180;
111 const double radius = render->options.border_radius;
112
113 /* Path describing an arc:ed rectangle */
114 cairo_new_path(buf->cairo);
115 cairo_arc(buf->cairo, b + w - radius, b + h - radius, radius,
116 0.0 * from_degree, 90.0 * from_degree);
117 cairo_arc(buf->cairo, b + radius, b + h - radius, radius,
118 90.0 * from_degree, 180.0 * from_degree);
119 cairo_arc(buf->cairo, b + radius, b + radius, radius,
120 180.0 * from_degree, 270.0 * from_degree);
121 cairo_arc(buf->cairo, b + w - radius, b + radius, radius,
122 270.0 * from_degree, 360.0 * from_degree);
123 cairo_close_path(buf->cairo);
124
125 /* Border */
126 const struct rgba *bc = &render->options.border_color;
127 cairo_set_operator(buf->cairo, CAIRO_OPERATOR_SOURCE);
128 cairo_set_line_width(buf->cairo, 2 * b);
129 cairo_set_source_rgba(buf->cairo, bc->r, bc->g, bc->b, bc->a);
130 cairo_stroke_preserve(buf->cairo);
131
132 /* Background */
133 const struct rgba *bg = &render->options.background_color;
134 cairo_set_source_rgba(buf->cairo, bg->r, bg->g, bg->b, bg->a);
135 cairo_fill(buf->cairo);
136 #else
137 assert(false);
138 #endif
139 }
140 }
141
142 static void
render_glyph(pixman_image_t * pix,const struct fcft_glyph * glyph,int x,int y,const pixman_color_t * color)143 render_glyph(pixman_image_t *pix, const struct fcft_glyph *glyph, int x, int y, const pixman_color_t *color)
144 {
145 if (pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) {
146 /* Glyph surface is a pre-rendered image (typically a color emoji...) */
147 pixman_image_composite32(
148 PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0,
149 x + glyph->x, y - glyph->y,
150 glyph->width, glyph->height);
151 } else {
152 /* Glyph surface is an alpha mask */
153 pixman_image_t *src = pixman_image_create_solid_fill(color);
154 pixman_image_composite32(
155 PIXMAN_OP_OVER, src, glyph->pix, pix, 0, 0, 0, 0,
156 x + glyph->x, y - glyph->y,
157 glyph->width, glyph->height);
158 pixman_image_unref(src);
159 }
160 }
161
162 void
render_prompt(const struct render * render,struct buffer * buf,const struct prompt * prompt)163 render_prompt(const struct render *render, struct buffer *buf,
164 const struct prompt *prompt)
165 {
166 struct fcft_font *font = render->font;
167 assert(font != NULL);
168
169 const wchar_t *pprompt = prompt_prompt(prompt);
170 const size_t prompt_len = wcslen(pprompt);
171
172 const wchar_t *ptext = prompt_text(prompt);
173 const size_t text_len = wcslen(ptext);
174
175 const enum fcft_subpixel subpixel =
176 (render->options.background_color.a == 1. &&
177 render->options.selection_color.a == 1.)
178 ? render->subpixel : FCFT_SUBPIXEL_NONE;
179
180 int x = render->border_size + render->x_margin;
181 int y = render->border_size + render->y_margin + font->ascent;
182
183 wchar_t prev = 0;
184
185 for (size_t i = 0; i < prompt_len + text_len; i++) {
186 wchar_t wc = i < prompt_len ? pprompt[i] : ptext[i - prompt_len];
187 const struct fcft_glyph *glyph = fcft_glyph_rasterize(font, wc, subpixel);
188 if (glyph == NULL) {
189 prev = wc;
190 continue;
191 }
192
193 long x_kern;
194 fcft_kerning(font, prev, wc, &x_kern, NULL);
195
196 x += x_kern;
197 render_glyph(buf->pix, glyph, x, y, &render->options.pix_text_color);
198 x += glyph->advance.x;
199 if (i >= prompt_len)
200 x += pt_or_px_as_pixels(render, &render->options.letter_spacing);
201
202 /* Cursor */
203 if (prompt_cursor(prompt) + prompt_len - 1 == i) {
204 pixman_image_fill_rectangles(
205 PIXMAN_OP_SRC, buf->pix, &render->options.pix_text_color,
206 1, &(pixman_rectangle16_t){
207 x, y - font->ascent,
208 font->underline.thickness, font->ascent + font->descent});
209 }
210
211 prev = wc;
212 }
213 }
214
215 static void
render_match_text(struct buffer * buf,double * _x,double _y,const wchar_t * text,ssize_t start,size_t length,struct fcft_font * font,enum fcft_subpixel subpixel,int letter_spacing,pixman_color_t regular_color,pixman_color_t match_color,struct fcft_text_run ** run)216 render_match_text(struct buffer *buf, double *_x, double _y,
217 const wchar_t *text, ssize_t start, size_t length,
218 struct fcft_font *font, enum fcft_subpixel subpixel,
219 int letter_spacing,
220 pixman_color_t regular_color, pixman_color_t match_color,
221 struct fcft_text_run **run)
222 {
223 int x = *_x;
224 int y = _y;
225
226 const struct fcft_glyph **glyphs = NULL;
227 int *clusters = NULL;
228 long *kern = NULL;
229 size_t count = 0;
230
231 if (*run == NULL &&
232 (fcft_capabilities() & FCFT_CAPABILITY_TEXT_RUN_SHAPING))
233 {
234 *run = fcft_text_run_rasterize(font, wcslen(text), text, subpixel);
235 }
236
237 if (*run != NULL) {
238 glyphs = (*run)->glyphs;
239 clusters = (*run)->cluster;
240 count = (*run)->count;
241 } else {
242 count = wcslen(text);
243 glyphs = malloc(count * sizeof(glyphs[0]));
244 clusters = malloc(count * sizeof(clusters[0]));
245 kern = malloc(count * sizeof(kern[0]));
246
247 for (size_t i = 0; i < count; i++) {
248 const struct fcft_glyph *glyph = fcft_glyph_rasterize(font, text[i], subpixel);
249 if (glyph == NULL) {
250 glyphs[i] = NULL;
251 continue;
252 }
253
254 if (i > 0)
255 fcft_kerning(font, text[i - 1], text[i], &kern[i], NULL);
256 else
257 kern[i] = 0;
258
259 glyphs[i] = glyph;
260 clusters[i] = i;
261 }
262 }
263
264 for (size_t i = 0; i < count; i++) {
265 bool is_match = start >= 0 && clusters[i] >= start && clusters[i] < start + length;
266 x += kern != NULL ? kern[i] : 0;
267 render_glyph(buf->pix, glyphs[i], x, y, is_match ? &match_color : ®ular_color);
268 x += glyphs[i]->advance.x + letter_spacing;
269 y += glyphs[i]->advance.y;
270 }
271
272 if (*run == NULL) {
273 free(kern);
274 free(clusters);
275 free(glyphs);
276 }
277
278 *_x = x;
279 }
280
281 #if defined(FUZZEL_ENABLE_SVG_LIBRSVG)
282 static void
render_svg_librsvg(const struct icon * icon,int x,int y,int size,struct buffer * buf)283 render_svg_librsvg(const struct icon *icon, int x, int y, int size, struct buffer *buf)
284 {
285 RsvgHandle *svg = icon->svg;
286
287 cairo_save(buf->cairo);
288 cairo_set_operator(buf->cairo, CAIRO_OPERATOR_ATOP);
289
290 #if LIBRSVG_CHECK_VERSION(2, 46, 0)
291 if (cairo_status(buf->cairo) == CAIRO_STATUS_SUCCESS) {
292 const RsvgRectangle viewport = {
293 .x = x,
294 .y = y,
295 .width = size,
296 .height = size,
297 };
298
299 cairo_rectangle(buf->cairo, x, y, size, size);
300 cairo_clip(buf->cairo);
301
302 rsvg_handle_render_document(svg, buf->cairo, &viewport, NULL);
303 }
304 #else
305 RsvgDimensionData dim;
306 rsvg_handle_get_dimensions(svg, &dim);
307
308 const double scale_x = size / dim.width;
309 const double scale_y = size / dim.height;
310 const double scale = scale_x < scale_y ? scale_x : scale_y;
311
312 const double height = dim.height * scale;
313 const double width = dim.width * scale;
314
315 cairo_rectangle(buf->cairo, x, y, height, width);
316 cairo_clip(buf->cairo);
317
318 /* Translate + scale. Note: order matters! */
319 cairo_translate(buf->cairo, x, y);
320 cairo_scale(buf->cairo, scale, scale);
321
322 if (cairo_status(buf->cairo) == CAIRO_STATUS_SUCCESS)
323 rsvg_handle_render_cairo(svg, buf->cairo);
324 #endif
325 cairo_restore(buf->cairo);
326 }
327 #endif /* FUZZEL_ENABLE_SVG_LIBRSVG */
328
329 #if defined(FUZZEL_ENABLE_SVG_NANOSVG)
330 static void
render_svg_nanosvg(struct icon * icon,int x,int y,int size,struct buffer * buf)331 render_svg_nanosvg(struct icon *icon, int x, int y, int size, struct buffer *buf)
332 {
333 #if defined(FUZZEL_ENABLE_CAIRO)
334 cairo_surface_flush(buf->cairo_surface);
335 #endif
336
337 pixman_image_t *img = NULL;
338
339 /* Look for a cached image, at the correct size */
340 tll_foreach(icon->rasterized, it) {
341 if (it->item.size == size) {
342 img = it->item.pix;
343 break;
344 }
345 }
346
347 if (img == NULL) {
348 NSVGimage *svg = icon->svg;
349 struct NSVGrasterizer *rast = nsvgCreateRasterizer();
350
351 if (rast == NULL)
352 return;
353
354 float scale = svg->width > svg->height ? size / svg->width : size / svg->height;
355
356 uint8_t *data = malloc(size * size * 4);
357 nsvgRasterize(rast, svg, 0, 0, scale, data, size, size, size * 4);
358
359 img = pixman_image_create_bits_no_clear(
360 PIXMAN_a8b8g8r8, size, size, (uint32_t *)data, size * 4);
361
362 /* Nanosvg produces non-premultiplied ABGR, while pixman expects
363 * premultiplied */
364 for (uint32_t *abgr = (uint32_t *)data;
365 abgr < (uint32_t *)(data + size * size * 4);
366 abgr++)
367 {
368 uint8_t alpha = (*abgr >> 24) & 0xff;
369 uint8_t blue = (*abgr >> 16) & 0xff;
370 uint8_t green = (*abgr >> 8) & 0xff;
371 uint8_t red = (*abgr >> 0) & 0xff;
372
373 if (alpha == 0xff)
374 continue;
375
376 if (alpha == 0x00)
377 blue = green = red = 0x00;
378 else {
379 blue = blue * alpha / 0xff;
380 green = green * alpha / 0xff;
381 red = red * alpha / 0xff;
382 }
383
384 *abgr = (uint32_t)alpha << 24 | blue << 16 | green << 8 | red;
385 }
386
387 nsvgDeleteRasterizer(rast);
388 tll_push_back(icon->rasterized, ((struct rasterized){img, size}));
389 }
390
391 pixman_image_composite32(
392 PIXMAN_OP_OVER, img, NULL, buf->pix, 0, 0, 0, 0, x, y, size, size);
393
394 #if defined(FUZZEL_ENABLE_CAIRO)
395 cairo_surface_mark_dirty(buf->cairo_surface);
396 #endif
397 }
398 #endif /* FUZZEL_ENABLE_SVG_NANOSVG */
399
400 static void
render_svg(struct icon * icon,int x,int y,int size,struct buffer * buf)401 render_svg(struct icon *icon, int x, int y, int size, struct buffer *buf)
402 {
403 assert(icon->type == ICON_SVG);
404
405 #if defined(FUZZEL_ENABLE_SVG_LIBRSVG)
406 render_svg_librsvg(icon, x, y, size, buf);
407 #elif defined(FUZZEL_ENABLE_SVG_NANOSVG)
408 render_svg_nanosvg(icon, x, y, size, buf);
409 #endif
410 }
411
412 #if defined(FUZZEL_ENABLE_PNG_LIBPNG)
413 static void
render_png_libpng(struct icon * icon,int x,int y,int size,struct buffer * buf)414 render_png_libpng(struct icon *icon, int x, int y, int size, struct buffer *buf)
415 {
416 #if defined(FUZZEL_ENABLE_CAIRO)
417 cairo_surface_flush(buf->cairo_surface);
418 #endif
419
420 pixman_image_t *png = icon->png;
421 pixman_format_code_t fmt = pixman_image_get_format(png);
422 int height = pixman_image_get_height(png);
423 int width = pixman_image_get_width(png);
424
425 if (height > size) {
426 double scale = (double)size / height;
427
428 pixman_f_transform_t _scale_transform;
429 pixman_f_transform_init_scale(&_scale_transform, 1. / scale, 1. / scale);
430
431 pixman_transform_t scale_transform;
432 pixman_transform_from_pixman_f_transform(
433 &scale_transform, &_scale_transform);
434 pixman_image_set_transform(png, &scale_transform);
435
436 int param_count = 0;
437 pixman_kernel_t kernel = PIXMAN_KERNEL_LANCZOS3;
438 pixman_fixed_t *params = pixman_filter_create_separable_convolution(
439 ¶m_count,
440 pixman_double_to_fixed(1. / scale),
441 pixman_double_to_fixed(1. / scale),
442 kernel, kernel,
443 kernel, kernel,
444 pixman_int_to_fixed(1),
445 pixman_int_to_fixed(1));
446
447 if (params != NULL || param_count == 0) {
448 pixman_image_set_filter(
449 png, PIXMAN_FILTER_SEPARABLE_CONVOLUTION,
450 params, param_count);
451 }
452
453 free(params);
454
455 width *= scale;
456 height *= scale;
457
458 int stride = stride_for_format_and_width(fmt, width);
459 uint8_t *data = malloc(height * stride);
460 pixman_image_t *scaled_png = pixman_image_create_bits_no_clear(
461 fmt, width, height, (uint32_t *)data, stride);
462 pixman_image_composite32(
463 PIXMAN_OP_SRC, png, NULL, scaled_png, 0, 0, 0, 0, 0, 0, width, height);
464
465 free(pixman_image_get_data(png));
466 pixman_image_unref(png);
467
468 png = scaled_png;
469 icon->png = png;
470 }
471
472 pixman_image_composite32(
473 PIXMAN_OP_OVER, png, NULL, buf->pix, 0, 0, 0, 0, x, y, width, height);
474
475 #if defined(FUZZEL_ENABLE_CAIRO)
476 cairo_surface_mark_dirty(buf->cairo_surface);
477 #endif
478 }
479 #endif /* FUZZEL_ENABLE_PNG_LIBPNG */
480
481 static void
render_png(struct icon * icon,int x,int y,int size,struct buffer * buf)482 render_png(struct icon *icon, int x, int y, int size, struct buffer *buf)
483 {
484 assert(icon->type == ICON_PNG);
485
486 #if defined(FUZZEL_ENABLE_PNG_LIBPNG)
487 render_png_libpng(icon, x, y, size, buf);
488 #endif
489 }
490
491 void
render_match_list(const struct render * render,struct buffer * buf,const struct prompt * prompt,const struct matches * matches)492 render_match_list(const struct render *render, struct buffer *buf,
493 const struct prompt *prompt, const struct matches *matches)
494 {
495 struct fcft_font *font = render->font;
496 assert(font != NULL);
497
498 const int x_margin = render->x_margin;
499 const int y_margin = render->y_margin;
500 const int inner_pad = render->inner_pad;
501 const int border_size = render->border_size;
502 const size_t match_count = matches_get_count(matches);
503 const size_t selected = matches_get_match_index(matches);
504 const enum fcft_subpixel subpixel =
505 (render->options.background_color.a == 1. &&
506 render->options.selection_color.a == 1.)
507 ? render->subpixel : FCFT_SUBPIXEL_NONE;
508
509 assert(match_count == 0 || selected < match_count);
510
511 const int row_height = render->row_height;
512 const int first_row = 1 * border_size + y_margin + row_height + inner_pad;
513 const int sel_margin = x_margin / 3;
514
515 int y = first_row + (row_height + font->height) / 2 - font->descent;
516
517 for (size_t i = 0; i < match_count; i++) {
518 if (y + font->descent > buf->height - y_margin - border_size) {
519 /* Window too small - happens if the compositor doesn't
520 * respect our requested size */
521 break;
522 }
523
524 const struct match *match = matches_get(matches, i);
525
526 if (i == selected) {
527 /* If currently selected item has a scalable icon, and if
528 * there's "enough" free space, render a large
529 * representation of the icon */
530
531 const double size = min(buf->height * 0.618, buf->width * 0.618);
532 const double img_x = (buf->width - size) / 2.;
533 const double img_y = first_row + (buf->height - size) / 2.;
534
535 const double list_end = first_row + match_count * row_height;
536
537 if (match->application->icon.type == ICON_SVG && img_y > list_end)
538 render_svg(&match->application->icon, img_x, img_y, size, buf);
539
540 pixman_color_t sc = rgba2pixman(render->options.selection_color);
541 pixman_image_fill_rectangles(
542 PIXMAN_OP_SRC, buf->pix, &sc, 1,
543 &(pixman_rectangle16_t){
544 x_margin - sel_margin,
545 first_row + i * row_height,
546 buf->width - 2 * (x_margin - sel_margin),
547 row_height}
548 );
549 }
550
551 double cur_x = border_size + x_margin;
552
553 {
554 struct icon *icon = &match->application->icon;
555 const int size = render->icon_height;
556 const int img_x = cur_x;
557 const int img_y = first_row + i * row_height + (row_height - size) / 2;
558
559 switch (icon->type) {
560 case ICON_NONE:
561 break;
562
563 case ICON_PNG:
564 render_png(icon, img_x, img_y, size, buf);
565 break;
566
567 case ICON_SVG:
568 render_svg(icon, img_x, img_y, size, buf);
569 break;
570 }
571 }
572
573 cur_x += row_height + font->space_advance.x + pt_or_px_as_pixels(
574 render, &render->options.letter_spacing);
575
576 /* Application title */
577 render_match_text(
578 buf, &cur_x, y,
579 match->application->title, match->start_title, wcslen(prompt_text(prompt)),
580 font, subpixel,
581 pt_or_px_as_pixels(render, &render->options.letter_spacing),
582 (i == selected
583 ? render->options.pix_selection_text_color
584 : render->options.pix_text_color),
585 render->options.pix_match_color, &match->application->shaped);
586
587 y += row_height;
588 }
589 }
590
591 struct render *
render_init(const struct render_options * options)592 render_init(const struct render_options *options)
593 {
594 struct render *render = calloc(1, sizeof(*render));
595 *render = (struct render){
596 .options = *options,
597 };
598
599 /* TODO: the one providing the opti3Dons should calculate these */
600 render->options.pix_background_color = rgba2pixman(render->options.background_color);
601 render->options.pix_border_color = rgba2pixman(render->options.border_color);
602 render->options.pix_text_color = rgba2pixman(render->options.text_color);
603 render->options.pix_match_color = rgba2pixman(render->options.match_color);
604 render->options.pix_selection_color = rgba2pixman(render->options.selection_color);
605 render->options.pix_selection_text_color = rgba2pixman(render->options.selection_text_color);
606 return render;
607 }
608
609 void
render_set_subpixel(struct render * render,enum fcft_subpixel subpixel)610 render_set_subpixel(struct render *render, enum fcft_subpixel subpixel)
611 {
612 render->subpixel = subpixel;
613 }
614
615 bool
render_set_font(struct render * render,struct fcft_font * font,int scale,float dpi,bool size_font_by_dpi,int * new_width,int * new_height)616 render_set_font(struct render *render, struct fcft_font *font,
617 int scale, float dpi, bool size_font_by_dpi,
618 int *new_width, int *new_height)
619 {
620 if (font != NULL) {
621 fcft_destroy(render->font);
622 render->font = font;
623 } else {
624 assert(render->font != NULL);
625 font = render->font;
626 }
627
628 render->scale = scale;
629 render->dpi = dpi;
630 render->size_font_by_dpi = size_font_by_dpi;
631
632 const struct fcft_glyph *W = fcft_glyph_rasterize(
633 font, L'W', render->subpixel);
634
635 const unsigned x_margin = render->options.pad.x * scale;
636 const unsigned y_margin = render->options.pad.y * scale;
637 const unsigned inner_pad = render->options.pad.inner * scale;
638
639 const unsigned border_size = render->options.border_size * scale;
640
641 const unsigned row_height = render->options.line_height.px >= 0
642 ? pt_or_px_as_pixels(render, &render->options.line_height)
643 : font->height;
644
645 const unsigned icon_height = max(0, row_height - font->descent);
646
647 const unsigned height =
648 border_size + /* Top border */
649 y_margin +
650 row_height + /* The prompt */
651 inner_pad + /* Padding between prompt and matches */
652 render->options.lines * row_height + /* Matches */
653 y_margin +
654 border_size; /* Bottom border */
655
656 const unsigned width =
657 border_size +
658 x_margin +
659 (max((W->advance.x + pt_or_px_as_pixels(
660 render, &render->options.letter_spacing)),
661 0)
662 * render->options.chars) +
663 x_margin +
664 border_size;
665
666 LOG_DBG("x-margin: %d, y-margin: %d, border: %d, row-height: %d, "
667 "icon-height: %d, height: %d, width: %d, scale: %d",
668 x_margin, y_margin, border_size, row_height, icon_height,
669 height, width, scale);
670
671 render->y_margin = y_margin;
672 render->x_margin = x_margin;
673 render->inner_pad = inner_pad;
674 render->border_size = border_size;
675 render->row_height = row_height;
676 render->icon_height = icon_height;
677
678 if (new_width != NULL)
679 *new_width = width;
680 if (new_height != NULL)
681 *new_height = height;
682
683 return true;
684 }
685
686 void
render_destroy(struct render * render)687 render_destroy(struct render *render)
688 {
689 if (render == NULL)
690 return;
691
692 fcft_destroy(render->font);
693 free(render);
694 }
695