drwl.h (8267B)
1 /* 2 * drwl - https://codeberg.org/sewn/drwl 3 * 4 * Copyright (c) 2023-2025 sewn <sewn@disroot.org> 5 * Copyright (c) 2024 notchoc <notchoc@disroot.org> 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining 8 * a copy of this software and associated documentation files (the 9 * "Software"), to deal in the Software without restriction, including 10 * without limitation the rights to use, copy, modify, merge, publish, 11 * distribute, sublicense, and/or sell copies of the Software, and to 12 * permit persons to whom the Software is furnished to do so, subject to 13 * the following conditions: 14 * 15 * The above copyright notice and this permission notice shall be 16 * included in all copies or substantial portions of the Software. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 22 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 24 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 * 26 * The UTF-8 Decoder included is from Bjoern Hoehrmann: 27 * Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de> 28 * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details. 29 */ 30 #pragma once 31 32 #include <stdlib.h> 33 #include <fcft/fcft.h> 34 #include <pixman-1/pixman.h> 35 36 enum { ColFg, ColBg }; /* colorscheme index */ 37 38 typedef struct fcft_font Fnt; 39 typedef pixman_image_t Img; 40 41 typedef struct { 42 Img *image; 43 Fnt *font; 44 uint32_t *scheme; 45 } Drwl; 46 47 #define UTF8_ACCEPT 0 48 #define UTF8_REJECT 12 49 #define UTF8_INVALID 0xFFFD 50 51 static const uint8_t utf8d[] = { 52 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 53 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 54 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 55 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 56 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 57 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 58 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 59 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8, 60 61 0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12, 62 12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12, 63 12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12, 64 12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12, 65 12,36,12,12,12,12,12,12,12,12,12,12, 66 }; 67 68 static inline uint32_t 69 utf8decode(uint32_t *state, uint32_t *codep, uint8_t byte) 70 { 71 uint32_t type = utf8d[byte]; 72 73 *codep = (*state != UTF8_ACCEPT) ? 74 (byte & 0x3fu) | (*codep << 6) : 75 (0xff >> type) & (byte); 76 77 *state = utf8d[256 + *state + type]; 78 return *state; 79 } 80 81 static int 82 drwl_init(void) 83 { 84 return fcft_init(FCFT_LOG_COLORIZE_AUTO, 0, FCFT_LOG_CLASS_ERROR); 85 } 86 87 static Drwl * 88 drwl_create(void) 89 { 90 Drwl *drwl; 91 92 if (!(drwl = calloc(1, sizeof(Drwl)))) 93 return NULL; 94 95 return drwl; 96 } 97 98 static void 99 drwl_setfont(Drwl *drwl, Fnt *font) 100 { 101 if (drwl) 102 drwl->font = font; 103 } 104 105 static void 106 drwl_setimage(Drwl *drwl, Img *image) 107 { 108 if (drwl) 109 drwl->image = image; 110 } 111 112 static Fnt * 113 drwl_font_create(Drwl *drwl, size_t count, 114 const char *names[static count], const char *attributes) 115 { 116 Fnt *font = fcft_from_name(count, names, attributes); 117 if (drwl) 118 drwl_setfont(drwl, font); 119 return font; 120 } 121 122 static void 123 drwl_font_destroy(Fnt *font) 124 { 125 fcft_destroy(font); 126 } 127 128 static inline pixman_color_t 129 convert_color(uint32_t clr) 130 { 131 return (pixman_color_t){ 132 ((clr >> 24) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, 133 ((clr >> 16) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, 134 ((clr >> 8) & 0xFF) * 0x101 * (clr & 0xFF) / 0xFF, 135 (clr & 0xFF) * 0x101 136 }; 137 } 138 139 static void 140 drwl_setscheme(Drwl *drwl, uint32_t *scm) 141 { 142 if (drwl) 143 drwl->scheme = scm; 144 } 145 146 static Img * 147 drwl_image_create(Drwl *drwl, unsigned int w, unsigned int h, uint32_t *bits) 148 { 149 Img *image; 150 pixman_region32_t clip; 151 152 image = pixman_image_create_bits_no_clear( 153 PIXMAN_a8r8g8b8, w, h, bits, w * 4); 154 if (!image) 155 return NULL; 156 pixman_region32_init_rect(&clip, 0, 0, w, h); 157 pixman_image_set_clip_region32(image, &clip); 158 pixman_region32_fini(&clip); 159 160 if (drwl) 161 drwl_setimage(drwl, image); 162 return image; 163 } 164 165 static void 166 drwl_rect(Drwl *drwl, 167 int x, int y, unsigned int w, unsigned int h, 168 int filled, int invert) 169 { 170 pixman_color_t clr; 171 if (!drwl || !drwl->scheme || !drwl->image) 172 return; 173 174 clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); 175 if (filled) 176 pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 1, 177 &(pixman_rectangle16_t){x, y, w, h}); 178 else 179 pixman_image_fill_rectangles(PIXMAN_OP_SRC, drwl->image, &clr, 4, 180 (pixman_rectangle16_t[4]){ 181 { x, y, w, 1 }, 182 { x, y + h - 1, w, 1 }, 183 { x, y, 1, h }, 184 { x + w - 1, y, 1, h }}); 185 } 186 187 static int 188 drwl_text(Drwl *drwl, 189 int x, int y, unsigned int w, unsigned int h, 190 unsigned int lpad, const char *text, int invert) 191 { 192 int ty; 193 int render = x || y || w || h; 194 long x_kern; 195 uint32_t cp = 0, last_cp = 0, state; 196 pixman_color_t clr; 197 pixman_image_t *fg_pix = NULL; 198 int noellipsis = 0; 199 const struct fcft_glyph *glyph, *eg = NULL; 200 int fcft_subpixel_mode = FCFT_SUBPIXEL_DEFAULT; 201 202 if (!drwl || (render && (!drwl->scheme || !w || !drwl->image)) || !text || !drwl->font) 203 return 0; 204 205 if (!render) { 206 w = invert ? invert : ~invert; 207 } else { 208 clr = convert_color(drwl->scheme[invert ? ColBg : ColFg]); 209 fg_pix = pixman_image_create_solid_fill(&clr); 210 211 drwl_rect(drwl, x, y, w, h, 1, !invert); 212 213 x += lpad; 214 w -= lpad; 215 } 216 217 if (render && (drwl->scheme[ColBg] & 0xFF) != 0xFF) 218 fcft_subpixel_mode = FCFT_SUBPIXEL_NONE; 219 220 if (render) 221 eg = fcft_rasterize_char_utf32(drwl->font, 0x2026 /* … */, fcft_subpixel_mode); 222 223 for (const char *p = text, *pp; pp = p, *p; p++) { 224 for (state = UTF8_ACCEPT; *p && 225 utf8decode(&state, &cp, *p) > UTF8_REJECT; p++) 226 ; 227 if (!*p || state == UTF8_REJECT) { 228 cp = UTF8_INVALID; 229 if (p > pp) 230 p--; 231 } 232 233 glyph = fcft_rasterize_char_utf32(drwl->font, cp, fcft_subpixel_mode); 234 if (!glyph) 235 continue; 236 237 x_kern = 0; 238 if (last_cp) 239 fcft_kerning(drwl->font, last_cp, cp, &x_kern, NULL); 240 last_cp = cp; 241 242 ty = y + (h - drwl->font->height) / 2 + drwl->font->ascent; 243 244 if (render && !noellipsis && x_kern + glyph->advance.x + eg->advance.x > w && 245 *(p + 1) != '\0') { 246 /* cannot fit ellipsis after current codepoint */ 247 if (drwl_text(drwl, 0, 0, 0, 0, 0, pp, 0) + x_kern <= w) { 248 noellipsis = 1; 249 } else { 250 w -= eg->advance.x; 251 pixman_image_composite32( 252 PIXMAN_OP_OVER, fg_pix, eg->pix, drwl->image, 0, 0, 0, 0, 253 x + eg->x, ty - eg->y, eg->width, eg->height); 254 } 255 } 256 257 if ((x_kern + glyph->advance.x) > w) 258 break; 259 260 x += x_kern; 261 262 if (render && pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8) 263 /* pre-rendered glyphs (eg. emoji) */ 264 pixman_image_composite32( 265 PIXMAN_OP_OVER, glyph->pix, NULL, drwl->image, 0, 0, 0, 0, 266 x + glyph->x, ty - glyph->y, glyph->width, glyph->height); 267 else if (render) 268 pixman_image_composite32( 269 PIXMAN_OP_OVER, fg_pix, glyph->pix, drwl->image, 0, 0, 0, 0, 270 x + glyph->x, ty - glyph->y, glyph->width, glyph->height); 271 272 x += glyph->advance.x; 273 w -= glyph->advance.x; 274 } 275 276 if (render) 277 pixman_image_unref(fg_pix); 278 279 return x + (render ? w : 0); 280 } 281 282 static unsigned int 283 drwl_font_getwidth(Drwl *drwl, const char *text) 284 { 285 if (!drwl || !drwl->font || !text) 286 return 0; 287 return drwl_text(drwl, 0, 0, 0, 0, 0, text, 0); 288 } 289 290 static unsigned int 291 drwl_font_getwidth_clamp(Drwl *drwl, const char *text, unsigned int n) 292 { 293 unsigned int tmp = 0; 294 if (drwl && drwl->font && text && n) 295 tmp = drwl_text(drwl, 0, 0, 0, 0, 0, text, n); 296 return tmp < n ? tmp : n; 297 } 298 299 static void 300 drwl_image_destroy(Img *image) 301 { 302 pixman_image_unref(image); 303 } 304 305 static void 306 drwl_destroy(Drwl *drwl) 307 { 308 if (drwl->font) 309 drwl_font_destroy(drwl->font); 310 if (drwl->image) 311 drwl_image_destroy(drwl->image); 312 free(drwl); 313 } 314 315 static void 316 drwl_fini(void) 317 { 318 fcft_fini(); 319 }