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 : &regular_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             &param_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