1 /******************************************************************************
2 Copyright (C) 2014 by Nibbles
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17
18 #include <obs-module.h>
19 #include <util/platform.h>
20 #include <ft2build.h>
21 #include FT_FREETYPE_H
22 #include <sys/stat.h>
23 #include "text-freetype2.h"
24 #include "obs-convenience.h"
25
26 float offsets[16] = {-2.0f, 0.0f, 0.0f, -2.0f, 2.0f, 0.0f, 2.0f, 0.0f,
27 0.0f, 2.0f, 0.0f, 2.0f, -2.0f, 0.0f, -2.0f, 0.0f};
28
29 extern uint32_t texbuf_w, texbuf_h;
30
draw_outlines(struct ft2_source * srcdata)31 void draw_outlines(struct ft2_source *srcdata)
32 {
33 // Horrible (hopefully temporary) solution for outlines.
34 uint32_t *tmp;
35
36 struct gs_vb_data *vdata = gs_vertexbuffer_get_data(srcdata->vbuf);
37
38 if (!srcdata->text)
39 return;
40
41 tmp = vdata->colors;
42 vdata->colors = srcdata->colorbuf;
43
44 gs_matrix_push();
45 for (int32_t i = 0; i < 8; i++) {
46 gs_matrix_translate3f(offsets[i * 2], offsets[(i * 2) + 1],
47 0.0f);
48 draw_uv_vbuffer(srcdata->vbuf, srcdata->tex,
49 srcdata->draw_effect,
50 (uint32_t)wcslen(srcdata->text) * 6);
51 }
52 gs_matrix_identity();
53 gs_matrix_pop();
54
55 vdata->colors = tmp;
56 }
57
draw_drop_shadow(struct ft2_source * srcdata)58 void draw_drop_shadow(struct ft2_source *srcdata)
59 {
60 // Horrible (hopefully temporary) solution for drop shadow.
61 uint32_t *tmp;
62
63 struct gs_vb_data *vdata = gs_vertexbuffer_get_data(srcdata->vbuf);
64
65 if (!srcdata->text)
66 return;
67
68 tmp = vdata->colors;
69 vdata->colors = srcdata->colorbuf;
70
71 gs_matrix_push();
72 gs_matrix_translate3f(4.0f, 4.0f, 0.0f);
73 draw_uv_vbuffer(srcdata->vbuf, srcdata->tex, srcdata->draw_effect,
74 (uint32_t)wcslen(srcdata->text) * 6);
75 gs_matrix_identity();
76 gs_matrix_pop();
77
78 vdata->colors = tmp;
79 }
80
set_up_vertex_buffer(struct ft2_source * srcdata)81 void set_up_vertex_buffer(struct ft2_source *srcdata)
82 {
83 FT_UInt glyph_index = 0;
84 uint32_t x = 0, space_pos = 0, word_width = 0;
85 size_t len;
86
87 if (!srcdata->text)
88 return;
89
90 if (srcdata->custom_width >= 100)
91 srcdata->cx = srcdata->custom_width;
92 else
93 srcdata->cx = get_ft2_text_width(srcdata->text, srcdata);
94 srcdata->cy = srcdata->max_h;
95
96 obs_enter_graphics();
97 if (srcdata->vbuf != NULL) {
98 gs_vertbuffer_t *tmpvbuf = srcdata->vbuf;
99 srcdata->vbuf = NULL;
100 gs_vertexbuffer_destroy(tmpvbuf);
101 }
102
103 if (*srcdata->text == 0) {
104 obs_leave_graphics();
105 return;
106 }
107
108 srcdata->vbuf =
109 create_uv_vbuffer((uint32_t)wcslen(srcdata->text) * 6, true);
110
111 if (srcdata->custom_width <= 100)
112 goto skip_word_wrap;
113 if (!srcdata->word_wrap)
114 goto skip_word_wrap;
115
116 len = wcslen(srcdata->text);
117
118 for (uint32_t i = 0; i <= len; i++) {
119 if (i == wcslen(srcdata->text))
120 goto eos_check;
121
122 if (srcdata->text[i] != L' ' && srcdata->text[i] != L'\n')
123 goto next_char;
124
125 eos_check:;
126 if (x + word_width > srcdata->custom_width) {
127 if (space_pos != 0)
128 srcdata->text[space_pos] = L'\n';
129 x = 0;
130 }
131 if (i == wcslen(srcdata->text))
132 goto eos_skip;
133
134 x += word_width;
135 word_width = 0;
136 if (srcdata->text[i] == L'\n')
137 x = 0;
138 if (srcdata->text[i] == L' ')
139 space_pos = i;
140 next_char:;
141 glyph_index =
142 FT_Get_Char_Index(srcdata->font_face, srcdata->text[i]);
143 word_width += src_glyph->xadv;
144 eos_skip:;
145 }
146
147 skip_word_wrap:;
148 fill_vertex_buffer(srcdata);
149 obs_leave_graphics();
150 }
151
fill_vertex_buffer(struct ft2_source * srcdata)152 void fill_vertex_buffer(struct ft2_source *srcdata)
153 {
154 struct gs_vb_data *vdata = gs_vertexbuffer_get_data(srcdata->vbuf);
155 if (vdata == NULL || !srcdata->text)
156 return;
157
158 struct vec2 *tvarray = (struct vec2 *)vdata->tvarray[0].array;
159 uint32_t *col = (uint32_t *)vdata->colors;
160
161 FT_UInt glyph_index = 0;
162
163 uint32_t dx = 0, dy = srcdata->max_h, max_y = dy;
164 uint32_t cur_glyph = 0;
165 uint32_t offset = 0;
166 size_t len = wcslen(srcdata->text);
167
168 if (srcdata->outline_text) {
169 offset = 2;
170 dx = offset;
171 }
172
173 if (srcdata->colorbuf != NULL) {
174 bfree(srcdata->colorbuf);
175 srcdata->colorbuf = NULL;
176 }
177 srcdata->colorbuf =
178 bzalloc(sizeof(uint32_t) * wcslen(srcdata->text) * 6);
179 for (size_t i = 0; i < len * 6; i++) {
180 srcdata->colorbuf[i] = 0xFF000000;
181 }
182
183 for (size_t i = 0; i < len; i++) {
184 add_linebreak:;
185 if (srcdata->text[i] != L'\n')
186 goto draw_glyph;
187 dx = offset;
188 i++;
189 dy += srcdata->max_h + 4;
190 if (i == wcslen(srcdata->text))
191 goto skip_glyph;
192 if (srcdata->text[i] == L'\n')
193 goto add_linebreak;
194 draw_glyph:;
195 // Skip filthy dual byte Windows line breaks
196 if (srcdata->text[i] == L'\r')
197 goto skip_glyph;
198
199 glyph_index =
200 FT_Get_Char_Index(srcdata->font_face, srcdata->text[i]);
201 if (src_glyph == NULL)
202 goto skip_glyph;
203
204 if (srcdata->custom_width < 100)
205 goto skip_custom_width;
206
207 if (dx + src_glyph->xadv > srcdata->custom_width) {
208 dx = offset;
209 dy += srcdata->max_h + 4;
210 }
211
212 skip_custom_width:;
213
214 set_v3_rect(vdata->points + (cur_glyph * 6),
215 (float)dx + (float)src_glyph->xoff,
216 (float)dy - (float)src_glyph->yoff,
217 (float)src_glyph->w, (float)src_glyph->h);
218 set_v2_uv(tvarray + (cur_glyph * 6), src_glyph->u, src_glyph->v,
219 src_glyph->u2, src_glyph->v2);
220 set_rect_colors2(col + (cur_glyph * 6), srcdata->color[0],
221 srcdata->color[1]);
222 dx += src_glyph->xadv;
223 if (dy - (float)src_glyph->yoff + src_glyph->h > max_y)
224 max_y = dy - src_glyph->yoff + src_glyph->h;
225 cur_glyph++;
226 skip_glyph:;
227 }
228
229 srcdata->cy = max_y;
230 }
231
cache_standard_glyphs(struct ft2_source * srcdata)232 void cache_standard_glyphs(struct ft2_source *srcdata)
233 {
234 for (uint32_t i = 0; i < num_cache_slots; i++) {
235 if (srcdata->cacheglyphs[i] != NULL) {
236 bfree(srcdata->cacheglyphs[i]);
237 srcdata->cacheglyphs[i] = NULL;
238 }
239 }
240
241 srcdata->texbuf_x = 0;
242 srcdata->texbuf_y = 0;
243
244 cache_glyphs(srcdata, L"abcdefghijklmnopqrstuvwxyz"
245 L"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
246 L"!@#$%^&*()-_=+,<.>/?\\|[]{}`~ \'\"\0");
247 }
248
get_render_mode(struct ft2_source * srcdata)249 FT_Render_Mode get_render_mode(struct ft2_source *srcdata)
250 {
251 return srcdata->antialiasing ? FT_RENDER_MODE_NORMAL
252 : FT_RENDER_MODE_MONO;
253 }
254
load_glyph(struct ft2_source * srcdata,const FT_UInt glyph_index,const FT_Render_Mode render_mode)255 void load_glyph(struct ft2_source *srcdata, const FT_UInt glyph_index,
256 const FT_Render_Mode render_mode)
257 {
258 const FT_Int32 load_mode = render_mode == FT_RENDER_MODE_MONO
259 ? FT_LOAD_TARGET_MONO
260 : FT_LOAD_DEFAULT;
261 FT_Load_Glyph(srcdata->font_face, glyph_index, load_mode);
262 }
263
init_glyph(FT_GlyphSlot slot,const uint32_t dx,const uint32_t dy,const uint32_t g_w,const uint32_t g_h)264 struct glyph_info *init_glyph(FT_GlyphSlot slot, const uint32_t dx,
265 const uint32_t dy, const uint32_t g_w,
266 const uint32_t g_h)
267 {
268 struct glyph_info *glyph = bzalloc(sizeof(struct glyph_info));
269 glyph->u = (float)dx / (float)texbuf_w;
270 glyph->u2 = (float)(dx + g_w) / (float)texbuf_w;
271 glyph->v = (float)dy / (float)texbuf_h;
272 glyph->v2 = (float)(dy + g_h) / (float)texbuf_h;
273 glyph->w = g_w;
274 glyph->h = g_h;
275 glyph->yoff = slot->bitmap_top;
276 glyph->xoff = slot->bitmap_left;
277 glyph->xadv = slot->advance.x >> 6;
278
279 return glyph;
280 }
281
get_pixel_value(const unsigned char * buf_row,FT_Render_Mode render_mode,const uint32_t x)282 uint8_t get_pixel_value(const unsigned char *buf_row,
283 FT_Render_Mode render_mode, const uint32_t x)
284 {
285 if (render_mode == FT_RENDER_MODE_NORMAL) {
286 return buf_row[x];
287 }
288
289 const uint32_t byte_index = x / 8;
290 const uint8_t bit_index = x % 8;
291 const bool pixel_set = (buf_row[byte_index] >> (7 - bit_index)) & 1;
292 return pixel_set ? 255 : 0;
293 }
294
rasterize(struct ft2_source * srcdata,FT_GlyphSlot slot,const FT_Render_Mode render_mode,const uint32_t dx,const uint32_t dy)295 void rasterize(struct ft2_source *srcdata, FT_GlyphSlot slot,
296 const FT_Render_Mode render_mode, const uint32_t dx,
297 const uint32_t dy)
298 {
299 /**
300 * The pitch's absolute value is the number of bytes taken by one bitmap
301 * row, including padding.
302 *
303 * Source: https://www.freetype.org/freetype2/docs/reference/ft2-basic_types.html
304 */
305 const int pitch = abs(slot->bitmap.pitch);
306
307 for (uint32_t y = 0; y < slot->bitmap.rows; y++) {
308 const uint32_t row_start = y * pitch;
309 const uint32_t row = (dy + y) * texbuf_w;
310
311 for (uint32_t x = 0; x < slot->bitmap.width; x++) {
312 const uint32_t row_pixel_position = dx + x;
313 const uint8_t pixel_value =
314 get_pixel_value(&slot->bitmap.buffer[row_start],
315 render_mode, x);
316 srcdata->texbuf[row_pixel_position + row] = pixel_value;
317 }
318 }
319 }
320
cache_glyphs(struct ft2_source * srcdata,wchar_t * cache_glyphs)321 void cache_glyphs(struct ft2_source *srcdata, wchar_t *cache_glyphs)
322 {
323 if (!srcdata->font_face || !cache_glyphs)
324 return;
325
326 FT_GlyphSlot slot = srcdata->font_face->glyph;
327
328 uint32_t dx = srcdata->texbuf_x;
329 uint32_t dy = srcdata->texbuf_y;
330
331 int32_t cached_glyphs = 0;
332 const size_t len = wcslen(cache_glyphs);
333
334 const FT_Render_Mode render_mode = get_render_mode(srcdata);
335
336 for (size_t i = 0; i < len; i++) {
337 const FT_UInt glyph_index =
338 FT_Get_Char_Index(srcdata->font_face, cache_glyphs[i]);
339
340 if (src_glyph != NULL) {
341 continue;
342 }
343
344 load_glyph(srcdata, glyph_index, render_mode);
345 FT_Render_Glyph(slot, render_mode);
346
347 const uint32_t g_w = slot->bitmap.width;
348 const uint32_t g_h = slot->bitmap.rows;
349
350 if (srcdata->max_h < g_h) {
351 srcdata->max_h = g_h;
352 }
353
354 if (dx + g_w >= texbuf_w) {
355 dx = 0;
356 dy += srcdata->max_h + 1;
357 }
358
359 if (dy + g_h >= texbuf_h) {
360 blog(LOG_WARNING,
361 "Out of space trying to render glyphs");
362 break;
363 }
364
365 src_glyph = init_glyph(slot, dx, dy, g_w, g_h);
366 rasterize(srcdata, slot, render_mode, dx, dy);
367
368 dx += (g_w + 1);
369 if (dx >= texbuf_w) {
370 dx = 0;
371 dy += srcdata->max_h;
372 }
373
374 cached_glyphs++;
375 }
376
377 srcdata->texbuf_x = dx;
378 srcdata->texbuf_y = dy;
379
380 if (cached_glyphs > 0) {
381
382 obs_enter_graphics();
383
384 if (srcdata->tex != NULL) {
385 gs_texture_t *tmp_texture = srcdata->tex;
386 srcdata->tex = NULL;
387 gs_texture_destroy(tmp_texture);
388 }
389
390 srcdata->tex = gs_texture_create(
391 texbuf_w, texbuf_h, GS_A8, 1,
392 (const uint8_t **)&srcdata->texbuf, 0);
393
394 obs_leave_graphics();
395 }
396 }
397
get_modified_timestamp(char * filename)398 time_t get_modified_timestamp(char *filename)
399 {
400 struct stat stats;
401
402 // stat is apparently terrifying and horrible, but we only call it once
403 // every second at most.
404 if (os_stat(filename, &stats) != 0)
405 return -1;
406
407 return stats.st_mtime;
408 }
409
remove_cr(wchar_t * source)410 static void remove_cr(wchar_t *source)
411 {
412 int j = 0;
413 for (int i = 0; source[i] != '\0'; ++i) {
414 if (source[i] != L'\r') {
415 source[j++] = source[i];
416 }
417 }
418 source[j] = '\0';
419 }
420
load_text_from_file(struct ft2_source * srcdata,const char * filename)421 void load_text_from_file(struct ft2_source *srcdata, const char *filename)
422 {
423 FILE *tmp_file = NULL;
424 uint32_t filesize = 0;
425 char *tmp_read = NULL;
426 uint16_t header = 0;
427 size_t bytes_read;
428
429 tmp_file = os_fopen(filename, "rb");
430 if (tmp_file == NULL) {
431 if (!srcdata->file_load_failed) {
432 blog(LOG_WARNING, "Failed to open file %s", filename);
433 srcdata->file_load_failed = true;
434 }
435 return;
436 }
437 fseek(tmp_file, 0, SEEK_END);
438 filesize = (uint32_t)ftell(tmp_file);
439 fseek(tmp_file, 0, SEEK_SET);
440 bytes_read = fread(&header, 2, 1, tmp_file);
441
442 if (bytes_read == 2 && header == 0xFEFF) {
443 // File is already in UTF-16 format
444 if (srcdata->text != NULL) {
445 bfree(srcdata->text);
446 srcdata->text = NULL;
447 }
448 srcdata->text = bzalloc(filesize);
449 bytes_read = fread(srcdata->text, filesize - 2, 1, tmp_file);
450
451 bfree(tmp_read);
452 fclose(tmp_file);
453
454 return;
455 }
456
457 fseek(tmp_file, 0, SEEK_SET);
458
459 tmp_read = bzalloc(filesize + 1);
460 bytes_read = fread(tmp_read, filesize, 1, tmp_file);
461 fclose(tmp_file);
462
463 if (srcdata->text != NULL) {
464 bfree(srcdata->text);
465 srcdata->text = NULL;
466 }
467 srcdata->text = bzalloc((strlen(tmp_read) + 1) * sizeof(wchar_t));
468 os_utf8_to_wcs(tmp_read, strlen(tmp_read), srcdata->text,
469 (strlen(tmp_read) + 1));
470
471 remove_cr(srcdata->text);
472 bfree(tmp_read);
473 }
474
read_from_end(struct ft2_source * srcdata,const char * filename)475 void read_from_end(struct ft2_source *srcdata, const char *filename)
476 {
477 FILE *tmp_file = NULL;
478 uint32_t filesize = 0, cur_pos = 0, log_lines = 0;
479 char *tmp_read = NULL;
480 uint16_t value = 0, line_breaks = 0;
481 size_t bytes_read;
482 char bvalue;
483
484 bool utf16 = false;
485
486 tmp_file = fopen(filename, "rb");
487 if (tmp_file == NULL) {
488 if (!srcdata->file_load_failed) {
489 blog(LOG_WARNING, "Failed to open file %s", filename);
490 srcdata->file_load_failed = true;
491 }
492 return;
493 }
494 bytes_read = fread(&value, 2, 1, tmp_file);
495
496 if (bytes_read == 2 && value == 0xFEFF)
497 utf16 = true;
498
499 fseek(tmp_file, 0, SEEK_END);
500 filesize = (uint32_t)ftell(tmp_file);
501 cur_pos = filesize;
502 log_lines = srcdata->log_lines;
503
504 while (line_breaks <= log_lines && cur_pos != 0) {
505 if (!utf16)
506 cur_pos--;
507 else
508 cur_pos -= 2;
509 fseek(tmp_file, cur_pos, SEEK_SET);
510
511 if (!utf16) {
512 bytes_read = fread(&bvalue, 1, 1, tmp_file);
513 if (bytes_read == 1 && bvalue == '\n')
514 line_breaks++;
515 } else {
516 bytes_read = fread(&value, 2, 1, tmp_file);
517 if (bytes_read == 2 && value == L'\n')
518 line_breaks++;
519 }
520 }
521
522 if (cur_pos != 0)
523 cur_pos += (utf16) ? 2 : 1;
524
525 fseek(tmp_file, cur_pos, SEEK_SET);
526
527 if (utf16) {
528 if (srcdata->text != NULL) {
529 bfree(srcdata->text);
530 srcdata->text = NULL;
531 }
532 srcdata->text = bzalloc(filesize - cur_pos);
533 bytes_read =
534 fread(srcdata->text, (filesize - cur_pos), 1, tmp_file);
535
536 remove_cr(srcdata->text);
537 bfree(tmp_read);
538 fclose(tmp_file);
539
540 return;
541 }
542
543 tmp_read = bzalloc((filesize - cur_pos) + 1);
544 bytes_read = fread(tmp_read, filesize - cur_pos, 1, tmp_file);
545 fclose(tmp_file);
546
547 if (srcdata->text != NULL) {
548 bfree(srcdata->text);
549 srcdata->text = NULL;
550 }
551 srcdata->text = bzalloc((strlen(tmp_read) + 1) * sizeof(wchar_t));
552 os_utf8_to_wcs(tmp_read, strlen(tmp_read), srcdata->text,
553 (strlen(tmp_read) + 1));
554
555 remove_cr(srcdata->text);
556 bfree(tmp_read);
557 }
558
get_ft2_text_width(wchar_t * text,struct ft2_source * srcdata)559 uint32_t get_ft2_text_width(wchar_t *text, struct ft2_source *srcdata)
560 {
561 if (!text) {
562 return 0;
563 }
564
565 FT_GlyphSlot slot = srcdata->font_face->glyph;
566 uint32_t w = 0, max_w = 0;
567 const size_t len = wcslen(text);
568 for (size_t i = 0; i < len; i++) {
569 const FT_UInt glyph_index =
570 FT_Get_Char_Index(srcdata->font_face, text[i]);
571
572 load_glyph(srcdata, glyph_index, get_render_mode(srcdata));
573
574 if (text[i] == L'\n')
575 w = 0;
576 else {
577 w += slot->advance.x >> 6;
578 if (w > max_w)
579 max_w = w;
580 }
581 }
582
583 return max_w;
584 }
585