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