1 /*****************************************************************************
2 * text_layout.c : Text shaping and layout
3 *****************************************************************************
4 * Copyright (C) 2015 VLC authors and VideoLAN
5 * $Id: fc2db46e62ac89b221df4c2c4574a4bf878f48de $
6 *
7 * Authors: Salah-Eddin Shaban <salshaaban@gmail.com>
8 * Laurent Aimar <fenrir@videolan.org>
9 * Sigmund Augdal Helberg <dnumgis@videolan.org>
10 * Gildas Bazin <gbazin@videolan.org>
11 * Jean-Baptiste Kempf <jb@videolan.org>
12 * Naohiro Koriyama <nkoriyama@gmail.com>
13 * David Fuhrmann <dfuhrmann@videolan.org>
14 * Erwan Tulou <erwan10@videolan.org>
15 * Devin Heitmueller <dheitmueller@kernellabs.com>
16 *
17 * This program is free software; you can redistribute it and/or modify it
18 * under the terms of the GNU Lesser General Public License as published by
19 * the Free Software Foundation; either version 2.1 of the License, or
20 * (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Lesser General Public License for more details.
26 *
27 * You should have received a copy of the GNU Lesser General Public License
28 * along with this program; if not, write to the Free Software Foundation, Inc.,
29 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
30 *****************************************************************************/
31
32 /** \ingroup freetype
33 * @{
34 * \file
35 * Text shaping and layout
36 */
37
38 #ifdef HAVE_CONFIG_H
39 # include "config.h"
40 #endif
41
42 #include <vlc_common.h>
43 #include <vlc_filter.h>
44 #include <vlc_text_style.h>
45
46 /* Freetype */
47 #include <ft2build.h>
48 #include FT_FREETYPE_H
49 #include FT_GLYPH_H
50 #include FT_STROKER_H
51 #include FT_SYNTHESIS_H
52
53 /* RTL */
54 #if defined(HAVE_FRIBIDI)
55 # define FRIBIDI_NO_DEPRECATED 1
56 # include <fribidi.h>
57 #endif
58
59 /* Complex Scripts */
60 #if defined(HAVE_HARFBUZZ)
61 # include <hb.h>
62 # include <hb-ft.h>
63 #endif
64
65 #include "freetype.h"
66 #include "text_layout.h"
67 #include "platform_fonts.h"
68
69 #include <stdlib.h>
70
71 /* Win32 */
72 #ifdef _WIN32
73 # undef HAVE_FONTCONFIG
74 # define HAVE_FONT_FALLBACK
75 #endif
76
77 /* FontConfig */
78 #ifdef HAVE_FONTCONFIG
79 # define HAVE_FONT_FALLBACK
80 #endif
81
82 /* Android */
83 #ifdef __ANDROID__
84 # define HAVE_FONT_FALLBACK
85 #endif
86
87 /* Darwin */
88 #ifdef __APPLE__
89 # define HAVE_FONT_FALLBACK
90 #endif
91
92 #ifndef HAVE_FONT_FALLBACK
93 # warning YOU ARE MISSING FONTS FALLBACK. TEXT WILL BE INCORRECT
94 #endif
95
96 /**
97 * Within a paragraph, run_desc_t represents a run of characters
98 * having the same font face, size, and style, Unicode script
99 * and text direction
100 */
101 typedef struct run_desc_t
102 {
103 int i_start_offset;
104 int i_end_offset;
105 FT_Face p_face;
106 const text_style_t *p_style;
107
108 #ifdef HAVE_HARFBUZZ
109 hb_script_t script;
110 hb_direction_t direction;
111 hb_font_t *p_hb_font;
112 hb_buffer_t *p_buffer;
113 hb_glyph_info_t *p_glyph_infos;
114 hb_glyph_position_t *p_glyph_positions;
115 unsigned int i_glyph_count;
116 #endif
117
118 } run_desc_t;
119
120 /**
121 * Glyph bitmaps. Advance and offset are 26.6 values
122 */
123 typedef struct glyph_bitmaps_t
124 {
125 FT_Glyph p_glyph;
126 FT_Glyph p_outline;
127 FT_Glyph p_shadow;
128 FT_BBox glyph_bbox;
129 FT_BBox outline_bbox;
130 FT_BBox shadow_bbox;
131 int i_x_offset;
132 int i_y_offset;
133 int i_x_advance;
134 int i_y_advance;
135 } glyph_bitmaps_t;
136
137 typedef struct paragraph_t
138 {
139 uni_char_t *p_code_points; /**< Unicode code points */
140 int *pi_glyph_indices; /**< Glyph index values within the run's font face */
141 text_style_t **pp_styles;
142 FT_Face *pp_faces; /**< Used to determine run boundaries when performing font fallback */
143 int *pi_run_ids; /**< The run to which each glyph belongs */
144 glyph_bitmaps_t *p_glyph_bitmaps;
145 uint8_t *pi_karaoke_bar;
146 int i_size;
147 run_desc_t *p_runs;
148 int i_runs_count;
149 int i_runs_size;
150
151 #ifdef HAVE_HARFBUZZ
152 hb_script_t *p_scripts;
153 #endif
154
155 #ifdef HAVE_FRIBIDI
156 FriBidiCharType *p_types;
157 #if FRIBIDI_MAJOR_VERSION >= 1
158 FriBidiBracketType *p_btypes;
159 #endif
160 FriBidiLevel *p_levels;
161 FriBidiStrIndex *pi_reordered_indices;
162 FriBidiParType paragraph_type;
163 #endif
164
165 } paragraph_t;
166
FreeLine(line_desc_t * p_line)167 static void FreeLine( line_desc_t *p_line )
168 {
169 for( int i = 0; i < p_line->i_character_count; i++ )
170 {
171 line_character_t *ch = &p_line->p_character[i];
172 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
173 if( ch->p_outline )
174 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
175 if( ch->p_shadow && ch->p_shadow != ch->p_glyph )
176 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
177 }
178
179 free( p_line->p_character );
180 free( p_line );
181 }
182
FreeLines(line_desc_t * p_lines)183 void FreeLines( line_desc_t *p_lines )
184 {
185 for( line_desc_t *p_line = p_lines; p_line != NULL; )
186 {
187 line_desc_t *p_next = p_line->p_next;
188 FreeLine( p_line );
189 p_line = p_next;
190 }
191 }
192
NewLine(int i_count)193 line_desc_t *NewLine( int i_count )
194 {
195 line_desc_t *p_line = malloc( sizeof(*p_line) );
196
197 if( !p_line )
198 return NULL;
199
200 p_line->p_next = NULL;
201 p_line->i_width = 0;
202 p_line->i_base_line = 0;
203 p_line->i_character_count = 0;
204 p_line->i_first_visible_char_index = -1;
205 p_line->i_last_visible_char_index = -2;
206
207 BBoxInit( &p_line->bbox );
208
209 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
210 if( !p_line->p_character )
211 {
212 free( p_line );
213 return NULL;
214 }
215 return p_line;
216 }
217
FixGlyph(FT_Glyph glyph,FT_BBox * p_bbox,FT_Pos i_x_advance,FT_Pos i_y_advance,const FT_Vector * p_pen)218 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox,
219 FT_Pos i_x_advance, FT_Pos i_y_advance,
220 const FT_Vector *p_pen )
221 {
222 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
223 if( p_bbox->xMin >= p_bbox->xMax )
224 {
225 p_bbox->xMin = FT_CEIL(p_pen->x);
226 p_bbox->xMax = FT_CEIL(p_pen->x + i_x_advance);
227 glyph_bmp->left = p_bbox->xMin;
228 }
229 if( p_bbox->yMin >= p_bbox->yMax )
230 {
231 p_bbox->yMax = FT_CEIL(p_pen->y);
232 p_bbox->yMin = FT_CEIL(p_pen->y + i_y_advance);
233 glyph_bmp->top = p_bbox->yMax;
234 }
235 }
236
NewParagraph(filter_t * p_filter,int i_size,const uni_char_t * p_code_points,text_style_t ** pp_styles,uint32_t * pi_k_dates,int i_runs_size)237 static paragraph_t *NewParagraph( filter_t *p_filter,
238 int i_size,
239 const uni_char_t *p_code_points,
240 text_style_t **pp_styles,
241 uint32_t *pi_k_dates,
242 int i_runs_size )
243 {
244 paragraph_t *p_paragraph = calloc( 1, sizeof( paragraph_t ) );
245 if( !p_paragraph )
246 return 0;
247
248 p_paragraph->i_size = i_size;
249 p_paragraph->p_code_points =
250 vlc_alloc( i_size, sizeof( *p_paragraph->p_code_points ) );
251 p_paragraph->pi_glyph_indices =
252 vlc_alloc( i_size, sizeof( *p_paragraph->pi_glyph_indices ) );
253 p_paragraph->pp_styles =
254 vlc_alloc( i_size, sizeof( *p_paragraph->pp_styles ) );
255 p_paragraph->pp_faces =
256 calloc( i_size, sizeof( *p_paragraph->pp_faces ) );
257 p_paragraph->pi_run_ids =
258 calloc( i_size, sizeof( *p_paragraph->pi_run_ids ) );
259 p_paragraph->p_glyph_bitmaps =
260 calloc( i_size, sizeof( *p_paragraph->p_glyph_bitmaps ) );
261 p_paragraph->pi_karaoke_bar =
262 calloc( i_size, sizeof( *p_paragraph->pi_karaoke_bar ) );
263
264 p_paragraph->p_runs = calloc( i_runs_size, sizeof( run_desc_t ) );
265 p_paragraph->i_runs_size = i_runs_size;
266 p_paragraph->i_runs_count = 0;
267
268 if( !p_paragraph->p_code_points || !p_paragraph->pi_glyph_indices
269 || !p_paragraph->pp_styles || !p_paragraph->pp_faces
270 || !p_paragraph->pi_run_ids|| !p_paragraph->p_glyph_bitmaps
271 || !p_paragraph->pi_karaoke_bar || !p_paragraph->p_runs )
272 goto error;
273
274 if( p_code_points )
275 memcpy( p_paragraph->p_code_points, p_code_points,
276 i_size * sizeof( *p_code_points ) );
277 if( pp_styles )
278 memcpy( p_paragraph->pp_styles, pp_styles,
279 i_size * sizeof( *pp_styles ) );
280 if( pi_k_dates )
281 {
282 int64_t i_elapsed = var_GetInteger( p_filter, "spu-elapsed" ) / 1000;
283 for( int i = 0; i < i_size; ++i )
284 {
285 p_paragraph->pi_karaoke_bar[ i ] = pi_k_dates[ i ] >= i_elapsed;
286 }
287 }
288
289 #ifdef HAVE_HARFBUZZ
290 p_paragraph->p_scripts = vlc_alloc( i_size, sizeof( *p_paragraph->p_scripts ) );
291 if( !p_paragraph->p_scripts )
292 goto error;
293 #endif
294
295 #ifdef HAVE_FRIBIDI
296 p_paragraph->p_levels = vlc_alloc( i_size, sizeof( *p_paragraph->p_levels ) );
297 p_paragraph->p_types = vlc_alloc( i_size, sizeof( *p_paragraph->p_types ) );
298 #if FRIBIDI_MAJOR_VERSION >= 1
299 p_paragraph->p_btypes = vlc_alloc( i_size, sizeof( *p_paragraph->p_btypes ) );
300 #endif
301 p_paragraph->pi_reordered_indices =
302 vlc_alloc( i_size, sizeof( *p_paragraph->pi_reordered_indices ) );
303
304 if( !p_paragraph->p_levels || !p_paragraph->p_types
305 || !p_paragraph->pi_reordered_indices )
306 goto error;
307
308 for( int i=0; i<i_size; i++ )
309 p_paragraph->pi_reordered_indices[i] = i;
310
311 int i_direction = var_InheritInteger( p_filter, "freetype-text-direction" );
312 if( i_direction == 0 )
313 p_paragraph->paragraph_type = FRIBIDI_PAR_LTR;
314 else if( i_direction == 1 )
315 p_paragraph->paragraph_type = FRIBIDI_PAR_RTL;
316 else
317 p_paragraph->paragraph_type = FRIBIDI_PAR_ON;
318 #endif
319
320 return p_paragraph;
321
322 error:
323 if( p_paragraph->p_code_points ) free( p_paragraph->p_code_points );
324 if( p_paragraph->pi_glyph_indices ) free( p_paragraph->pi_glyph_indices );
325 if( p_paragraph->pp_styles ) free( p_paragraph->pp_styles );
326 if( p_paragraph->pp_faces ) free( p_paragraph->pp_faces );
327 if( p_paragraph->pi_run_ids ) free( p_paragraph->pi_run_ids );
328 if( p_paragraph->p_glyph_bitmaps ) free( p_paragraph->p_glyph_bitmaps );
329 if (p_paragraph->pi_karaoke_bar ) free( p_paragraph->pi_karaoke_bar );
330 if( p_paragraph->p_runs ) free( p_paragraph->p_runs );
331 #ifdef HAVE_HARFBUZZ
332 if( p_paragraph->p_scripts ) free( p_paragraph->p_scripts );
333 #endif
334 #ifdef HAVE_FRIBIDI
335 if( p_paragraph->p_levels ) free( p_paragraph->p_levels );
336 if( p_paragraph->p_types ) free( p_paragraph->p_types );
337 #if FRIBIDI_MAJOR_VERSION >= 1
338 if( p_paragraph->p_btypes ) free( p_paragraph->p_btypes );
339 #endif
340 if( p_paragraph->pi_reordered_indices )
341 free( p_paragraph->pi_reordered_indices );
342 #endif
343 free( p_paragraph );
344 return 0;
345 }
346
FreeParagraph(paragraph_t * p_paragraph)347 static void FreeParagraph( paragraph_t *p_paragraph )
348 {
349 free( p_paragraph->p_runs );
350 free( p_paragraph->pi_glyph_indices );
351 free( p_paragraph->p_glyph_bitmaps );
352 free( p_paragraph->pi_karaoke_bar );
353 free( p_paragraph->pi_run_ids );
354 free( p_paragraph->pp_faces );
355 free( p_paragraph->pp_styles );
356 free( p_paragraph->p_code_points );
357
358 #ifdef HAVE_HARFBUZZ
359 free( p_paragraph->p_scripts );
360 #endif
361
362 #ifdef HAVE_FRIBIDI
363 free( p_paragraph->pi_reordered_indices );
364 free( p_paragraph->p_types );
365 #if FRIBIDI_MAJOR_VERSION >= 1
366 free( p_paragraph->p_btypes );
367 #endif
368 free( p_paragraph->p_levels );
369 #endif
370
371 free( p_paragraph );
372 }
373
374 #ifdef HAVE_FRIBIDI
AnalyzeParagraph(paragraph_t * p_paragraph)375 static int AnalyzeParagraph( paragraph_t *p_paragraph )
376 {
377 int i_max;
378 fribidi_get_bidi_types( p_paragraph->p_code_points,
379 p_paragraph->i_size,
380 p_paragraph->p_types );
381 #if FRIBIDI_MAJOR_VERSION >= 1
382 fribidi_get_bracket_types( p_paragraph->p_code_points,
383 p_paragraph->i_size,
384 p_paragraph->p_types,
385 p_paragraph->p_btypes );
386 i_max = fribidi_get_par_embedding_levels_ex(
387 p_paragraph->p_types,
388 p_paragraph->p_btypes,
389 p_paragraph->i_size,
390 &p_paragraph->paragraph_type,
391 p_paragraph->p_levels );
392 #else
393 i_max = fribidi_get_par_embedding_levels(
394 p_paragraph->p_types,
395 p_paragraph->i_size,
396 &p_paragraph->paragraph_type,
397 p_paragraph->p_levels );
398 #endif
399 if( i_max == 0 )
400 return VLC_EGENERIC;
401
402 #ifdef HAVE_HARFBUZZ
403 hb_unicode_funcs_t *p_funcs =
404 hb_unicode_funcs_create( hb_unicode_funcs_get_default() );
405 for( int i = 0; i < p_paragraph->i_size; ++i )
406 p_paragraph->p_scripts[ i ] =
407 hb_unicode_script( p_funcs, p_paragraph->p_code_points[ i ] );
408 hb_unicode_funcs_destroy( p_funcs );
409
410 hb_script_t i_last_script;
411 int i_last_script_index = -1;
412 int i_last_set_index = -1;
413
414 /*
415 * For shaping to work, characters that are assigned HB_SCRIPT_COMMON or
416 * HB_SCRIPT_INHERITED should be resolved to the last encountered valid
417 * script value, if any, and to the first one following them otherwise
418 */
419 for( int i = 0; i < p_paragraph->i_size; ++i )
420 {
421 if( p_paragraph->p_scripts[ i ] == HB_SCRIPT_COMMON
422 || p_paragraph->p_scripts[ i ] == HB_SCRIPT_INHERITED)
423 {
424 if( i_last_script_index != -1)
425 {
426 p_paragraph->p_scripts[ i ] = i_last_script;
427 i_last_set_index = i;
428 }
429 }
430 else
431 {
432 for( int j = i_last_set_index + 1; j < i; ++j )
433 p_paragraph->p_scripts[ j ] = p_paragraph->p_scripts[ i ];
434
435 i_last_script = p_paragraph->p_scripts[ i ];
436 i_last_script_index = i;
437 i_last_set_index = i;
438 }
439 }
440 #endif //HAVE_HARFBUZZ
441
442 return VLC_SUCCESS;
443 }
444 #endif //HAVE_FRIBIDI
445
AddRun(filter_t * p_filter,paragraph_t * p_paragraph,int i_start_offset,int i_end_offset,FT_Face p_face,const text_style_t * p_style)446 static int AddRun( filter_t *p_filter,
447 paragraph_t *p_paragraph,
448 int i_start_offset,
449 int i_end_offset,
450 FT_Face p_face,
451 const text_style_t *p_style )
452 {
453 if( i_start_offset >= i_end_offset
454 || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
455 || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
456 {
457 msg_Err( p_filter,
458 "AddRun() invalid parameters. Paragraph size: %d, "
459 "Start offset: %d, End offset: %d",
460 p_paragraph->i_size, i_start_offset, i_end_offset );
461 return VLC_EGENERIC;
462 }
463
464 if( p_paragraph->i_runs_count == p_paragraph->i_runs_size )
465 {
466 run_desc_t *p_new_runs =
467 realloc( p_paragraph->p_runs,
468 p_paragraph->i_runs_size * 2 * sizeof( *p_new_runs ) );
469 if( !p_new_runs )
470 return VLC_ENOMEM;
471
472 memset( p_new_runs + p_paragraph->i_runs_size , 0,
473 p_paragraph->i_runs_size * sizeof( *p_new_runs ) );
474
475 p_paragraph->p_runs = p_new_runs;
476 p_paragraph->i_runs_size *= 2;
477 }
478
479 int i_run_id = p_paragraph->i_runs_count;
480 run_desc_t *p_run = p_paragraph->p_runs + p_paragraph->i_runs_count++;
481 p_run->i_start_offset = i_start_offset;
482 p_run->i_end_offset = i_end_offset;
483 p_run->p_face = p_face;
484
485 if( p_style )
486 p_run->p_style = p_style;
487 else
488 p_run->p_style = p_paragraph->pp_styles[ i_start_offset ];
489
490 #ifdef HAVE_HARFBUZZ
491 p_run->script = p_paragraph->p_scripts[ i_start_offset ];
492 p_run->direction = p_paragraph->p_levels[ i_start_offset ] & 1 ?
493 HB_DIRECTION_RTL : HB_DIRECTION_LTR;
494 #endif
495
496 for( int i = i_start_offset; i < i_end_offset; ++i )
497 p_paragraph->pi_run_ids[ i ] = i_run_id;
498
499 return VLC_SUCCESS;
500 }
501
502 #ifdef HAVE_FONT_FALLBACK
503 /**
504 * Add a run with font fallback, possibly breaking the run further
505 * into runs of glyphs that end up having the same font face.
506 */
AddRunWithFallback(filter_t * p_filter,paragraph_t * p_paragraph,int i_start_offset,int i_end_offset)507 static int AddRunWithFallback( filter_t *p_filter, paragraph_t *p_paragraph,
508 int i_start_offset, int i_end_offset )
509 {
510 if( i_start_offset >= i_end_offset
511 || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
512 || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
513 {
514 msg_Err( p_filter,
515 "AddRunWithFallback() invalid parameters. Paragraph size: %d, "
516 "Start offset: %d, End offset: %d",
517 p_paragraph->i_size, i_start_offset, i_end_offset );
518 return VLC_EGENERIC;
519 }
520
521 const text_style_t *p_style = p_paragraph->pp_styles[ i_start_offset ];
522
523 /* Maximum number of faces to try for each run */
524 #define MAX_FACES 5
525 FT_Face pp_faces[ MAX_FACES ] = {0};
526 FT_Face p_face = NULL;
527
528 pp_faces[ 0 ] = SelectAndLoadFace( p_filter, p_style, 0 );
529
530 for( int i = i_start_offset; i < i_end_offset; ++i )
531 {
532 int i_index = 0;
533 int i_glyph_index = 0;
534
535 #ifdef HAVE_FRIBIDI
536 /*
537 * For white space, punctuation and neutral characters, try to use
538 * the font of the previous character, if any. See #20466.
539 */
540 if( p_face &&
541 ( p_paragraph->p_types[ i ] == FRIBIDI_TYPE_WS
542 || p_paragraph->p_types[ i ] == FRIBIDI_TYPE_CS
543 || p_paragraph->p_types[ i ] == FRIBIDI_TYPE_ON ) )
544 {
545 i_glyph_index = FT_Get_Char_Index( p_face,
546 p_paragraph->p_code_points[ i ] );
547 if( i_glyph_index )
548 {
549 p_paragraph->pp_faces[ i ] = p_face;
550 continue;
551 }
552 }
553 #endif
554
555 do {
556 p_face = pp_faces[ i_index ];
557 if( !p_face )
558 p_face = pp_faces[ i_index ] =
559 SelectAndLoadFace( p_filter, p_style,
560 p_paragraph->p_code_points[ i ] );
561 if( !p_face )
562 continue;
563 i_glyph_index = FT_Get_Char_Index( p_face,
564 p_paragraph->p_code_points[ i ] );
565 if( i_glyph_index )
566 p_paragraph->pp_faces[ i ] = p_face;
567
568 } while( i_glyph_index == 0 && ++i_index < MAX_FACES );
569 }
570
571 int i_run_start = i_start_offset;
572 for( int i = i_start_offset; i <= i_end_offset; ++i )
573 {
574 if( i == i_end_offset
575 || p_paragraph->pp_faces[ i_run_start ] != p_paragraph->pp_faces[ i ] )
576 {
577 if( AddRun( p_filter, p_paragraph, i_run_start, i,
578 p_paragraph->pp_faces[ i_run_start ], NULL ) )
579 return VLC_EGENERIC;
580
581 i_run_start = i;
582 }
583 }
584
585 return VLC_SUCCESS;
586 }
587 #endif
588
FaceStyleEquals(filter_t * p_filter,const text_style_t * p_style1,const text_style_t * p_style2)589 static bool FaceStyleEquals( filter_t *p_filter, const text_style_t *p_style1,
590 const text_style_t *p_style2 )
591 {
592 if( !p_style1 || !p_style2 )
593 return false;
594 if( p_style1 == p_style2 )
595 return true;
596
597 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC | STYLE_HALFWIDTH | STYLE_DOUBLEWIDTH;
598
599 const char *psz_fontname1 = p_style1->i_style_flags & STYLE_MONOSPACED
600 ? p_style1->psz_monofontname : p_style1->psz_fontname;
601
602 const char *psz_fontname2 = p_style2->i_style_flags & STYLE_MONOSPACED
603 ? p_style2->psz_monofontname : p_style2->psz_fontname;
604
605 const int i_size1 = ConvertToLiveSize( p_filter, p_style1 );
606 const int i_size2 = ConvertToLiveSize( p_filter, p_style2 );
607
608 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask)
609 && i_size1 == i_size2
610 && !strcasecmp( psz_fontname1, psz_fontname2 );
611 }
612
613 /**
614 * Segment a paragraph into runs
615 */
ItemizeParagraph(filter_t * p_filter,paragraph_t * p_paragraph)616 static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph )
617 {
618 if( p_paragraph->i_size <= 0 )
619 {
620 msg_Err( p_filter,
621 "ItemizeParagraph() invalid parameters. Paragraph size: %d",
622 p_paragraph->i_size );
623 return VLC_EGENERIC;
624 }
625
626 int i_last_run_start = 0;
627 const text_style_t *p_last_style = p_paragraph->pp_styles[ 0 ];
628
629 #ifdef HAVE_HARFBUZZ
630 hb_script_t last_script = p_paragraph->p_scripts[ 0 ];
631 FriBidiLevel last_level = p_paragraph->p_levels[ 0 ];
632 #endif
633
634 for( int i = 0; i <= p_paragraph->i_size; ++i )
635 {
636 if( i == p_paragraph->i_size
637 #ifdef HAVE_HARFBUZZ
638 || last_script != p_paragraph->p_scripts[ i ]
639 || last_level != p_paragraph->p_levels[ i ]
640 #endif
641 || !FaceStyleEquals( p_filter, p_last_style, p_paragraph->pp_styles[ i ] ) )
642 {
643 int i_ret;
644 #ifdef HAVE_FONT_FALLBACK
645 i_ret = AddRunWithFallback( p_filter, p_paragraph, i_last_run_start, i );
646 #else
647 i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i, NULL, NULL );
648 #endif
649 if( i_ret )
650 return i_ret;
651
652 if( i < p_paragraph->i_size )
653 {
654 i_last_run_start = i;
655 p_last_style = p_paragraph->pp_styles[ i ];
656 #ifdef HAVE_HARFBUZZ
657 last_script = p_paragraph->p_scripts[ i ];
658 last_level = p_paragraph->p_levels[ i ];
659 #endif
660 }
661 }
662 }
663 return VLC_SUCCESS;
664 }
665
666 #ifdef HAVE_HARFBUZZ
667 /**
668 * Shape an itemized paragraph using HarfBuzz.
669 * This is where the glyphs of complex scripts get their positions
670 * (offsets and advance values) and final forms.
671 * Glyph substitutions of base glyphs and diacritics may take place,
672 * so the paragraph size may change.
673 */
ShapeParagraphHarfBuzz(filter_t * p_filter,paragraph_t ** p_old_paragraph)674 static int ShapeParagraphHarfBuzz( filter_t *p_filter,
675 paragraph_t **p_old_paragraph )
676 {
677 paragraph_t *p_paragraph = *p_old_paragraph;
678 paragraph_t *p_new_paragraph = 0;
679 filter_sys_t *p_sys = p_filter->p_sys;
680 int i_total_glyphs = 0;
681 int i_ret = VLC_EGENERIC;
682
683 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
684 {
685 msg_Err( p_filter, "ShapeParagraphHarfBuzz() invalid parameters. "
686 "Paragraph size: %d. Runs count %d",
687 p_paragraph->i_size, p_paragraph->i_runs_count );
688 return VLC_EGENERIC;
689 }
690
691 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
692 {
693 run_desc_t *p_run = p_paragraph->p_runs + i;
694 const text_style_t *p_style = p_run->p_style;
695
696 /*
697 * With HarfBuzz and no font fallback, this is where font faces
698 * are loaded. In the other two paths (shaping with FriBidi or no
699 * shaping at all), faces are loaded in LoadGlyphs().
700 *
701 * If we have font fallback, font faces in all paths will be
702 * loaded in AddRunWithFallback(), except for runs of codepoints
703 * for which no font could be found.
704 */
705 FT_Face p_face = 0;
706 if( !p_run->p_face )
707 {
708 p_face = SelectAndLoadFace( p_filter, p_style, 0 );
709 if( !p_face )
710 {
711 p_face = p_sys->p_face;
712 p_style = p_sys->p_default_style;
713 p_run->p_style = p_style;
714 }
715 p_run->p_face = p_face;
716 }
717 else
718 p_face = p_run->p_face;
719
720 p_run->p_hb_font = hb_ft_font_create( p_face, 0 );
721 if( !p_run->p_hb_font )
722 {
723 msg_Err( p_filter,
724 "ShapeParagraphHarfBuzz(): hb_ft_font_create() error" );
725 goto error;
726 }
727
728 p_run->p_buffer = hb_buffer_create();
729 if( !p_run->p_buffer )
730 {
731 msg_Err( p_filter,
732 "ShapeParagraphHarfBuzz(): hb_buffer_create() error" );
733 goto error;
734 }
735
736 hb_buffer_set_direction( p_run->p_buffer, p_run->direction );
737 hb_buffer_set_script( p_run->p_buffer, p_run->script );
738 #ifdef __OS2__
739 hb_buffer_add_utf16( p_run->p_buffer,
740 p_paragraph->p_code_points + p_run->i_start_offset,
741 p_run->i_end_offset - p_run->i_start_offset, 0,
742 p_run->i_end_offset - p_run->i_start_offset );
743 #else
744 hb_buffer_add_utf32( p_run->p_buffer,
745 p_paragraph->p_code_points + p_run->i_start_offset,
746 p_run->i_end_offset - p_run->i_start_offset, 0,
747 p_run->i_end_offset - p_run->i_start_offset );
748 #endif
749 hb_shape( p_run->p_hb_font, p_run->p_buffer, 0, 0 );
750 p_run->p_glyph_infos =
751 hb_buffer_get_glyph_infos( p_run->p_buffer, &p_run->i_glyph_count );
752 p_run->p_glyph_positions =
753 hb_buffer_get_glyph_positions( p_run->p_buffer, &p_run->i_glyph_count );
754
755 if( p_run->i_glyph_count <= 0 )
756 {
757 msg_Err( p_filter,
758 "ShapeParagraphHarfBuzz() invalid glyph count in shaped run" );
759 goto error;
760 }
761
762 i_total_glyphs += p_run->i_glyph_count;
763 }
764
765 p_new_paragraph = NewParagraph( p_filter, i_total_glyphs, 0, 0, 0,
766 p_paragraph->i_runs_size );
767 if( !p_new_paragraph )
768 {
769 i_ret = VLC_ENOMEM;
770 goto error;
771 }
772 p_new_paragraph->paragraph_type = p_paragraph->paragraph_type;
773
774 int i_index = 0;
775 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
776 {
777 run_desc_t *p_run = p_paragraph->p_runs + i;
778 hb_glyph_info_t *p_infos = p_run->p_glyph_infos;
779 hb_glyph_position_t *p_positions = p_run->p_glyph_positions;
780 for( unsigned int j = 0; j < p_run->i_glyph_count; ++j )
781 {
782 /*
783 * HarfBuzz reverses the order of glyphs in RTL runs. We reverse
784 * it again here to keep the glyphs in their logical order.
785 * For line breaking of paragraphs to work correctly, visual
786 * reordering should be done after line breaking has taken
787 * place.
788 */
789 int i_run_index = p_run->direction == HB_DIRECTION_LTR ?
790 j : p_run->i_glyph_count - 1 - j;
791 int i_source_index =
792 p_infos[ i_run_index ].cluster + p_run->i_start_offset;
793
794 p_new_paragraph->p_code_points[ i_index ] = 0;
795 p_new_paragraph->pi_glyph_indices[ i_index ] =
796 p_infos[ i_run_index ].codepoint;
797 p_new_paragraph->p_scripts[ i_index ] =
798 p_paragraph->p_scripts[ i_source_index ];
799 p_new_paragraph->p_types[ i_index ] =
800 p_paragraph->p_types[ i_source_index ];
801 p_new_paragraph->p_levels[ i_index ] =
802 p_paragraph->p_levels[ i_source_index ];
803 p_new_paragraph->pp_styles[ i_index ] =
804 p_paragraph->pp_styles[ i_source_index ];
805 p_new_paragraph->pi_karaoke_bar[ i_index ] =
806 p_paragraph->pi_karaoke_bar[ i_source_index ];
807 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_offset =
808 p_positions[ i_run_index ].x_offset;
809 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_offset =
810 p_positions[ i_run_index ].y_offset;
811 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_advance =
812 p_positions[ i_run_index ].x_advance;
813 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_advance =
814 p_positions[ i_run_index ].y_advance;
815
816 ++i_index;
817 }
818 if( AddRun( p_filter, p_new_paragraph, i_index - p_run->i_glyph_count,
819 i_index, p_run->p_face, p_run->p_style ) )
820 goto error;
821 }
822
823 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
824 {
825 hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
826 hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
827 }
828 FreeParagraph( *p_old_paragraph );
829 *p_old_paragraph = p_new_paragraph;
830
831 return VLC_SUCCESS;
832
833 error:
834 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
835 {
836 if( p_paragraph->p_runs[ i ].p_hb_font )
837 hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
838 if( p_paragraph->p_runs[ i ].p_buffer )
839 hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
840 }
841
842 if( p_new_paragraph )
843 FreeParagraph( p_new_paragraph );
844
845 return i_ret;
846 }
847 #endif
848
849 #ifdef HAVE_FRIBIDI
850 #ifndef HAVE_HARFBUZZ
851 /**
852 * Shape a paragraph with FriBidi.
853 * Shaping with FriBidi is currently limited to mirroring and simple
854 * Arabic shaping.
855 */
ShapeParagraphFriBidi(filter_t * p_filter,paragraph_t * p_paragraph)856 static int ShapeParagraphFriBidi( filter_t *p_filter, paragraph_t *p_paragraph )
857 {
858
859 if( p_paragraph->i_size <= 0 )
860 {
861 msg_Err( p_filter,
862 "ShapeParagraphFriBidi() invalid parameters. Paragraph size: %d",
863 p_paragraph->i_size );
864 return VLC_EGENERIC;
865 }
866
867 FriBidiJoiningType *p_joining_types =
868 vlc_alloc( p_paragraph->i_size, sizeof( *p_joining_types ) );
869 if( !p_joining_types )
870 return VLC_ENOMEM;
871
872 fribidi_get_joining_types( p_paragraph->p_code_points,
873 p_paragraph->i_size, p_joining_types );
874 fribidi_join_arabic( p_paragraph->p_types, p_paragraph->i_size,
875 p_paragraph->p_levels, p_joining_types );
876 fribidi_shape( FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
877 p_paragraph->p_levels,
878 p_paragraph->i_size,
879 p_joining_types,
880 p_paragraph->p_code_points );
881
882 free( p_joining_types );
883
884 return VLC_SUCCESS;
885 }
886
887 /**
888 * Zero-width invisible characters include Unicode control characters and
889 * zero-width spaces among other things. If not removed they can show up in the
890 * text as squares or other glyphs depending on the font. Zero-width spaces are
891 * inserted when shaping with FriBidi, when it performs glyph substitution for
892 * ligatures.
893 */
RemoveZeroWidthCharacters(paragraph_t * p_paragraph)894 static int RemoveZeroWidthCharacters( paragraph_t *p_paragraph )
895 {
896 for( int i = 0; i < p_paragraph->i_size; ++i )
897 {
898 uni_char_t ch = p_paragraph->p_code_points[ i ];
899 if( ch == 0xfeff
900 || ch == 0x061c
901 || ( ch >= 0x202a && ch <= 0x202e )
902 || ( ch >= 0x2060 && ch <= 0x2069 )
903 || ( ch >= 0x200b && ch <= 0x200f ) )
904 {
905 glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + i;
906 if( p_bitmaps->p_glyph )
907 FT_Done_Glyph( p_bitmaps->p_glyph );
908 if( p_bitmaps->p_outline )
909 FT_Done_Glyph( p_bitmaps->p_outline );
910 p_bitmaps->p_glyph = 0;
911 p_bitmaps->p_outline = 0;
912 p_bitmaps->p_shadow = 0;
913 p_bitmaps->i_x_advance = 0;
914 p_bitmaps->i_y_advance = 0;
915 }
916 }
917
918 return VLC_SUCCESS;
919 }
920
921 /**
922 * Set advance values of non-spacing marks to zero. Diacritics are
923 * not positioned correctly but the text is more readable.
924 * For full shaping HarfBuzz is required.
925 */
ZeroNsmAdvance(paragraph_t * p_paragraph)926 static int ZeroNsmAdvance( paragraph_t *p_paragraph )
927 {
928 for( int i = 0; i < p_paragraph->i_size; ++i )
929 if( p_paragraph->p_types[ i ] == FRIBIDI_TYPE_NSM )
930 {
931 p_paragraph->p_glyph_bitmaps[ i ].i_x_advance = 0;
932 p_paragraph->p_glyph_bitmaps[ i ].i_y_advance = 0;
933 }
934 return VLC_SUCCESS;
935 }
936 #endif
937 #endif
938
939 /**
940 * Load the glyphs of a paragraph. When shaping with HarfBuzz the glyph indices
941 * have already been determined at this point, as well as the advance values.
942 */
LoadGlyphs(filter_t * p_filter,paragraph_t * p_paragraph,bool b_use_glyph_indices,bool b_overwrite_advance,unsigned * pi_max_advance_x)943 static int LoadGlyphs( filter_t *p_filter, paragraph_t *p_paragraph,
944 bool b_use_glyph_indices, bool b_overwrite_advance,
945 unsigned *pi_max_advance_x )
946 {
947 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
948 {
949 msg_Err( p_filter, "LoadGlyphs() invalid parameters. "
950 "Paragraph size: %d. Runs count %d", p_paragraph->i_size,
951 p_paragraph->i_runs_count );
952 return VLC_EGENERIC;
953 }
954
955 filter_sys_t *p_sys = p_filter->p_sys;
956 *pi_max_advance_x = 0;
957
958 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
959 {
960 run_desc_t *p_run = p_paragraph->p_runs + i;
961 const text_style_t *p_style = p_run->p_style;
962 const int i_live_size = ConvertToLiveSize( p_filter, p_style );
963
964 FT_Face p_face = 0;
965 if( !p_run->p_face )
966 {
967 p_face = SelectAndLoadFace( p_filter, p_style,
968 p_paragraph->p_code_points[p_run->i_start_offset] );
969 if( !p_face )
970 {
971 /* Uses the default font and style */
972 p_face = p_sys->p_face;
973 p_style = p_sys->p_default_style;
974 p_run->p_style = p_style;
975 }
976 p_run->p_face = p_face;
977 }
978 else
979 p_face = p_run->p_face;
980
981 if( p_sys->p_stroker && (p_style->i_style_flags & STYLE_OUTLINE) )
982 {
983 double f_outline_thickness =
984 var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
985 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
986 int i_radius = ( i_live_size << 6 ) * f_outline_thickness;
987 FT_Stroker_Set( p_sys->p_stroker,
988 i_radius,
989 FT_STROKER_LINECAP_ROUND,
990 FT_STROKER_LINEJOIN_ROUND, 0 );
991 }
992
993 for( int j = p_run->i_start_offset; j < p_run->i_end_offset; ++j )
994 {
995 int i_glyph_index;
996 if( b_use_glyph_indices )
997 i_glyph_index = p_paragraph->pi_glyph_indices[ j ];
998 else
999 i_glyph_index =
1000 FT_Get_Char_Index( p_face, p_paragraph->p_code_points[ j ] );
1001
1002 glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + j;
1003
1004 #define SKIP_GLYPH( p_bitmaps ) \
1005 { \
1006 p_bitmaps->p_glyph = 0; \
1007 p_bitmaps->p_outline = 0; \
1008 p_bitmaps->p_shadow = 0; \
1009 p_bitmaps->i_x_advance = 0; \
1010 p_bitmaps->i_y_advance = 0; \
1011 continue; \
1012 }
1013
1014 if( !i_glyph_index )
1015 {
1016 uni_char_t codepoint = p_paragraph->p_code_points[ j ];
1017 /*
1018 * If the font has no support for special space characters, use regular
1019 * space glyphs instead of the .notdef glyph.
1020 */
1021 if( codepoint == 0x0009 || codepoint == 0x00A0
1022 || codepoint == 0x1680 || codepoint == 0x3000
1023 || codepoint == 0x202F || codepoint == 0x205F
1024 || ( codepoint >= 0x2000 && codepoint <= 0x200A )
1025 #ifdef HAVE_FRIBIDI
1026 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_WS
1027 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_CS
1028 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_SS
1029 #endif
1030 )
1031 {
1032 i_glyph_index = 3;
1033 }
1034 /* Skip carriage returns */
1035 else if( codepoint == 0x0D
1036 #ifdef HAVE_FRIBIDI
1037 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_BS
1038 #endif
1039 )
1040 SKIP_GLYPH( p_bitmaps )
1041 }
1042
1043 if( FT_Load_Glyph( p_face, i_glyph_index,
1044 FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT )
1045 && FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1046 SKIP_GLYPH( p_bitmaps )
1047
1048 if( ( p_style->i_style_flags & STYLE_BOLD )
1049 && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ) )
1050 FT_GlyphSlot_Embolden( p_face->glyph );
1051 if( ( p_style->i_style_flags & STYLE_ITALIC )
1052 && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ) )
1053 FT_GlyphSlot_Oblique( p_face->glyph );
1054
1055 if( FT_Get_Glyph( p_face->glyph, &p_bitmaps->p_glyph ) )
1056 SKIP_GLYPH( p_bitmaps )
1057
1058 #undef SKIP_GLYPH
1059
1060 if( p_filter->p_sys->p_stroker && (p_style->i_style_flags & STYLE_OUTLINE) )
1061 {
1062 p_bitmaps->p_outline = p_bitmaps->p_glyph;
1063 if( FT_Glyph_Stroke( &p_bitmaps->p_outline,
1064 p_filter->p_sys->p_stroker, 0 ) )
1065 p_bitmaps->p_outline = 0;
1066 }
1067
1068 if( p_style->i_shadow_alpha != STYLE_ALPHA_TRANSPARENT )
1069 p_bitmaps->p_shadow = p_bitmaps->p_outline ?
1070 p_bitmaps->p_outline : p_bitmaps->p_glyph;
1071
1072 if( b_overwrite_advance )
1073 {
1074 p_bitmaps->i_x_advance = p_face->glyph->advance.x;
1075 p_bitmaps->i_y_advance = p_face->glyph->advance.y;
1076 }
1077
1078 unsigned i_x_advance = FT_FLOOR( abs( p_bitmaps->i_x_advance ) );
1079 if( i_x_advance > *pi_max_advance_x )
1080 *pi_max_advance_x = i_x_advance;
1081 }
1082 }
1083 return VLC_SUCCESS;
1084 }
1085
LayoutLine(filter_t * p_filter,paragraph_t * p_paragraph,int i_first_char,int i_last_char,line_desc_t ** pp_line,bool b_grid)1086 static int LayoutLine( filter_t *p_filter,
1087 paragraph_t *p_paragraph,
1088 int i_first_char, int i_last_char,
1089 line_desc_t **pp_line, bool b_grid )
1090 {
1091 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0
1092 || i_first_char < 0 || i_last_char < 0
1093 || i_first_char > i_last_char
1094 || i_last_char >= p_paragraph->i_size )
1095 {
1096 msg_Err( p_filter,
1097 "LayoutLine() invalid parameters. "
1098 "Paragraph size: %d. Runs count: %d. "
1099 "Start char: %d. End char: %d",
1100 p_paragraph->i_size, p_paragraph->i_runs_count,
1101 i_first_char, i_last_char );
1102 return VLC_EGENERIC;
1103 }
1104
1105 filter_sys_t *p_sys = p_filter->p_sys;
1106 int i_last_run = -1;
1107 run_desc_t *p_run = 0;
1108 const text_style_t *p_style = 0;
1109 FT_Face p_face = 0;
1110 FT_Vector pen = { .x = 0, .y = 0 };
1111 int i_line_index = 0;
1112
1113 int i_font_size = 0;
1114 int i_font_width = 0;
1115 int i_font_max_advance_y = 0;
1116 int i_ul_offset = 0;
1117 int i_ul_thickness = 0;
1118
1119 #ifdef HAVE_FRIBIDI
1120 bool b_reordered = ( 0 !=
1121 fribidi_reorder_line( 0, &p_paragraph->p_types[i_first_char],
1122 1 + i_last_char - i_first_char,
1123 0, p_paragraph->paragraph_type,
1124 &p_paragraph->p_levels[i_first_char],
1125 0, &p_paragraph->pi_reordered_indices[i_first_char] ) );
1126 #endif
1127
1128 line_desc_t *p_line = NewLine( 1 + i_last_char - i_first_char );
1129 if( !p_line )
1130 return VLC_ENOMEM;
1131
1132 for( int i = i_first_char; i <= i_last_char; ++i, ++i_line_index )
1133 {
1134 int i_paragraph_index;
1135 #ifdef HAVE_FRIBIDI
1136 if( b_reordered )
1137 i_paragraph_index = p_paragraph->pi_reordered_indices[ i ];
1138 else
1139 #endif
1140 i_paragraph_index = i;
1141
1142 line_character_t *p_ch = p_line->p_character + i_line_index;
1143 p_ch->p_style = p_paragraph->pp_styles[ i_paragraph_index ];
1144
1145 glyph_bitmaps_t *p_bitmaps =
1146 p_paragraph->p_glyph_bitmaps + i_paragraph_index;
1147
1148 if( !p_bitmaps->p_glyph )
1149 {
1150 BBoxInit( &p_ch->bbox );
1151 --i_line_index;
1152 continue;
1153 }
1154
1155 if( i_last_run != p_paragraph->pi_run_ids[ i_paragraph_index ] )
1156 {
1157 i_last_run = p_paragraph->pi_run_ids[ i_paragraph_index ];
1158 p_run = p_paragraph->p_runs + i_last_run;
1159 p_style = p_run->p_style;
1160 p_face = p_run->p_face;
1161
1162 i_font_width = i_font_size = ConvertToLiveSize( p_filter, p_style );
1163 if( p_style->i_style_flags & STYLE_HALFWIDTH )
1164 i_font_width /= 2;
1165 else if( p_style->i_style_flags & STYLE_DOUBLEWIDTH )
1166 i_font_width *= 2;
1167 }
1168
1169 FT_Vector pen_new = {
1170 .x = pen.x + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_x_offset,
1171 .y = pen.y + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_y_offset
1172 };
1173 FT_Vector pen_shadow = {
1174 .x = pen_new.x + p_sys->f_shadow_vector_x * ( i_font_width << 6 ),
1175 .y = pen_new.y + p_sys->f_shadow_vector_y * ( i_font_size << 6 )
1176 };
1177
1178 if( p_bitmaps->p_shadow )
1179 {
1180 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_shadow, FT_RENDER_MODE_NORMAL,
1181 &pen_shadow, 0 ) )
1182 p_bitmaps->p_shadow = 0;
1183 else
1184 FT_Glyph_Get_CBox( p_bitmaps->p_shadow, ft_glyph_bbox_pixels,
1185 &p_bitmaps->shadow_bbox );
1186 }
1187 if( p_bitmaps->p_glyph )
1188 {
1189 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_glyph, FT_RENDER_MODE_NORMAL,
1190 &pen_new, 1 ) )
1191 {
1192 FT_Done_Glyph( p_bitmaps->p_glyph );
1193 if( p_bitmaps->p_outline )
1194 FT_Done_Glyph( p_bitmaps->p_outline );
1195 if( p_bitmaps->p_shadow != p_bitmaps->p_glyph )
1196 FT_Done_Glyph( p_bitmaps->p_shadow );
1197 --i_line_index;
1198 continue;
1199 }
1200 else
1201 FT_Glyph_Get_CBox( p_bitmaps->p_glyph, ft_glyph_bbox_pixels,
1202 &p_bitmaps->glyph_bbox );
1203 }
1204 if( p_bitmaps->p_outline )
1205 {
1206 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_outline, FT_RENDER_MODE_NORMAL,
1207 &pen_new, 1 ) )
1208 {
1209 FT_Done_Glyph( p_bitmaps->p_outline );
1210 p_bitmaps->p_outline = 0;
1211 }
1212 else
1213 FT_Glyph_Get_CBox( p_bitmaps->p_outline, ft_glyph_bbox_pixels,
1214 &p_bitmaps->outline_bbox );
1215 }
1216
1217 FixGlyph( p_bitmaps->p_glyph, &p_bitmaps->glyph_bbox,
1218 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1219 &pen_new );
1220 if( p_bitmaps->p_outline )
1221 FixGlyph( p_bitmaps->p_outline, &p_bitmaps->outline_bbox,
1222 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1223 &pen_new );
1224 if( p_bitmaps->p_shadow )
1225 FixGlyph( p_bitmaps->p_shadow, &p_bitmaps->shadow_bbox,
1226 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1227 &pen_shadow );
1228
1229 int i_line_offset = 0;
1230 int i_line_thickness = 0;
1231
1232 if( p_ch->p_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1233 {
1234 i_line_offset =
1235 labs( FT_FLOOR( FT_MulFix( p_face->underline_position,
1236 p_face->size->metrics.y_scale ) ) );
1237
1238 i_line_thickness =
1239 labs( FT_CEIL( FT_MulFix( p_face->underline_thickness,
1240 p_face->size->metrics.y_scale ) ) );
1241
1242 if( p_ch->p_style->i_style_flags & STYLE_STRIKEOUT )
1243 {
1244 /* Move the baseline to make it strikethrough instead of
1245 * underline. That means that strikethrough takes precedence
1246 */
1247 i_line_offset -=
1248 labs( FT_FLOOR( FT_MulFix( p_face->descender * 2,
1249 p_face->size->metrics.y_scale ) ) );
1250 p_bitmaps->glyph_bbox.yMax =
1251 __MAX( p_bitmaps->glyph_bbox.yMax,
1252 - i_line_offset );
1253 p_bitmaps->glyph_bbox.yMin =
1254 __MIN( p_bitmaps->glyph_bbox.yMin,
1255 i_line_offset - i_line_thickness );
1256 }
1257 else if( i_line_thickness > 0 )
1258 {
1259 p_bitmaps->glyph_bbox.yMin =
1260 __MIN( p_bitmaps->glyph_bbox.yMin,
1261 - i_line_offset - i_line_thickness );
1262
1263 /* The real underline thickness and position are
1264 * updated once the whole line has been parsed */
1265 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1266 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1267 i_line_thickness = -1;
1268 }
1269 }
1270
1271 p_ch->p_glyph = ( FT_BitmapGlyph ) p_bitmaps->p_glyph;
1272 p_ch->p_outline = ( FT_BitmapGlyph ) p_bitmaps->p_outline;
1273 p_ch->p_shadow = ( FT_BitmapGlyph ) p_bitmaps->p_shadow;
1274 p_ch->b_in_karaoke = (p_paragraph->pi_karaoke_bar[ i_paragraph_index ] != 0);
1275
1276 p_ch->i_line_thickness = i_line_thickness;
1277 p_ch->i_line_offset = i_line_offset;
1278
1279 /* Compute bounding box for all glyphs */
1280 p_ch->bbox = p_bitmaps->glyph_bbox;
1281 if( p_bitmaps->p_outline )
1282 BBoxEnlarge( &p_ch->bbox, &p_bitmaps->outline_bbox );
1283 if( p_bitmaps->p_shadow )
1284 BBoxEnlarge( &p_ch->bbox, &p_bitmaps->shadow_bbox );
1285
1286 BBoxEnlarge( &p_line->bbox, &p_ch->bbox );
1287
1288 pen.x += p_bitmaps->i_x_advance;
1289 pen.y += p_bitmaps->i_y_advance;
1290
1291 /* Get max advance for grid mode */
1292 if( b_grid && i_font_max_advance_y == 0 && p_face )
1293 {
1294 i_font_max_advance_y = labs( FT_FLOOR( FT_MulFix( p_face->max_advance_height,
1295 p_face->size->metrics.y_scale ) ) );
1296 }
1297
1298 /* Keep track of blank/spaces in front/end of line */
1299 if( p_ch->p_glyph->bitmap.rows )
1300 {
1301 if( p_line->i_first_visible_char_index < 0 )
1302 p_line->i_first_visible_char_index = i_line_index;
1303 p_line->i_last_visible_char_index = i_line_index;
1304 }
1305 }
1306
1307 p_line->i_width = __MAX( 0, p_line->bbox.xMax - p_line->bbox.xMin );
1308
1309 if( b_grid )
1310 p_line->i_height = i_font_max_advance_y;
1311 else
1312 p_line->i_height = __MAX( 0, p_line->bbox.yMax - p_line->bbox.yMin );
1313
1314 p_line->i_character_count = i_line_index;
1315
1316 if( i_ul_thickness > 0 )
1317 {
1318 for( int i = 0; i < p_line->i_character_count; i++ )
1319 {
1320 line_character_t *ch = &p_line->p_character[i];
1321 if( ch->i_line_thickness < 0 )
1322 {
1323 ch->i_line_offset = i_ul_offset;
1324 ch->i_line_thickness = i_ul_thickness;
1325 }
1326 }
1327 }
1328
1329 *pp_line = p_line;
1330 return VLC_SUCCESS;
1331 }
1332
ReleaseGlyphBitMaps(glyph_bitmaps_t * p_bitmaps)1333 static inline void ReleaseGlyphBitMaps(glyph_bitmaps_t *p_bitmaps)
1334 {
1335 if( p_bitmaps->p_glyph )
1336 FT_Done_Glyph( p_bitmaps->p_glyph );
1337 if( p_bitmaps->p_outline )
1338 FT_Done_Glyph( p_bitmaps->p_outline );
1339 }
1340
IsWhitespaceAt(paragraph_t * p_paragraph,size_t i)1341 static inline bool IsWhitespaceAt( paragraph_t *p_paragraph, size_t i )
1342 {
1343 return ( p_paragraph->p_code_points[ i ] == ' '
1344 #ifdef HAVE_FRIBIDI
1345 || p_paragraph->p_types[ i ] == FRIBIDI_TYPE_WS
1346 #endif
1347 );
1348 }
1349
LayoutParagraph(filter_t * p_filter,paragraph_t * p_paragraph,unsigned i_max_width,unsigned i_max_advance_x,line_desc_t ** pp_lines,bool b_grid,bool b_balance)1350 static int LayoutParagraph( filter_t *p_filter, paragraph_t *p_paragraph,
1351 unsigned i_max_width, unsigned i_max_advance_x,
1352 line_desc_t **pp_lines, bool b_grid, bool b_balance )
1353 {
1354 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
1355 {
1356 msg_Err( p_filter, "LayoutParagraph() invalid parameters. "
1357 "Paragraph size: %d. Runs count %d",
1358 p_paragraph->i_size, p_paragraph->i_runs_count );
1359 return VLC_EGENERIC;
1360 }
1361
1362 /*
1363 * Check max line width to allow for outline and shadow glyphs,
1364 * and any extra width caused by visual reordering
1365 */
1366 if( i_max_width <= i_max_advance_x )
1367 {
1368 msg_Err( p_filter, "LayoutParagraph(): Invalid max width" );
1369 return VLC_EGENERIC;
1370 }
1371
1372 i_max_width <<= 6;
1373 i_max_advance_x <<= 6;
1374
1375 int i_line_start = 0;
1376 FT_Pos i_width = 0;
1377 FT_Pos i_preferred_width = i_max_width;
1378 FT_Pos i_total_width = 0;
1379 FT_Pos i_last_space_width = 0;
1380 int i_last_space = -1;
1381 line_desc_t *p_first_line = NULL;
1382 line_desc_t **pp_line = &p_first_line;
1383
1384 for( int i = 0; i < p_paragraph->i_size; ++i )
1385 {
1386 if( !IsWhitespaceAt( p_paragraph, i ) || i != i_last_space + 1 )
1387 i_total_width += p_paragraph->p_glyph_bitmaps[ i ].i_x_advance;
1388 else
1389 i_last_space = i;
1390 }
1391 i_last_space = -1;
1392
1393 if( i_total_width == 0 )
1394 {
1395 for( int i=0; i < p_paragraph->i_size; ++i )
1396 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1397 return VLC_SUCCESS;
1398 }
1399
1400 if( b_balance )
1401 {
1402 int i_line_count = i_total_width / (i_max_width - i_max_advance_x) + 1;
1403 i_preferred_width = i_total_width / i_line_count;
1404 }
1405
1406 for( int i = 0; i <= p_paragraph->i_size; ++i )
1407 {
1408 if( i == p_paragraph->i_size )
1409 {
1410 if( i_line_start < i )
1411 if( LayoutLine( p_filter, p_paragraph,
1412 i_line_start, i - 1, pp_line, b_grid ) )
1413 goto error;
1414
1415 break;
1416 }
1417
1418 if( IsWhitespaceAt( p_paragraph, i ) )
1419 {
1420 if( i_line_start == i )
1421 {
1422 /*
1423 * Free orphaned white space glyphs not belonging to any lines.
1424 * At this point p_shadow points to either p_glyph or p_outline,
1425 * so we should not free it explicitly.
1426 */
1427 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1428 i_line_start = i + 1;
1429 continue;
1430 }
1431
1432 if( i_last_space == i - 1 )
1433 {
1434 i_last_space = i;
1435 continue;
1436 }
1437
1438 i_last_space = i;
1439 i_last_space_width = i_width;
1440 }
1441
1442 const run_desc_t *p_run = &p_paragraph->p_runs[p_paragraph->pi_run_ids[i]];
1443 const int i_advance_x = p_paragraph->p_glyph_bitmaps[ i ].i_x_advance;
1444
1445 if( ( i_last_space_width + i_advance_x > i_preferred_width &&
1446 p_run->p_style->e_wrapinfo == STYLE_WRAP_DEFAULT )
1447 || i_width + i_advance_x > i_max_width )
1448 {
1449 if( i_line_start == i )
1450 {
1451 /* If wrapping, algorithm would not end shifting lines down.
1452 * Not wrapping, that can't be rendered anymore. */
1453 msg_Dbg( p_filter, "LayoutParagraph(): First glyph width in line exceeds maximum, skipping" );
1454 for( ; i < p_paragraph->i_size; ++i )
1455 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1456 return VLC_SUCCESS;
1457 }
1458
1459 int i_newline_start;
1460 if( i_last_space > i_line_start && p_run->p_style->e_wrapinfo == STYLE_WRAP_DEFAULT )
1461 i_newline_start = i_last_space; /* we break line on last space */
1462 else
1463 i_newline_start = i; /* we break line on last char */
1464
1465 if( LayoutLine( p_filter, p_paragraph, i_line_start,
1466 i_newline_start - 1, pp_line, b_grid ) )
1467 goto error;
1468
1469 /* Handle early end of renderable content;
1470 We're over size and we can't break space */
1471 if( p_run->p_style->e_wrapinfo == STYLE_WRAP_NONE )
1472 {
1473 for( ; i < p_paragraph->i_size; ++i )
1474 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1475 break;
1476 }
1477
1478 pp_line = &( *pp_line )->p_next;
1479
1480 /* If we created a line up to previous space, we only keep the difference for
1481 our current width since that split */
1482 if( i_newline_start == i_last_space )
1483 {
1484 i_width = i_width - i_last_space_width;
1485 if( i_newline_start + 1 < p_paragraph->i_size )
1486 {
1487 i_line_start = i_newline_start + 1;
1488 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i_newline_start ] );
1489 }
1490 else
1491 i_line_start = i_newline_start; // == i
1492 }
1493 else
1494 {
1495 i_width = 0;
1496 i_line_start = i_newline_start;
1497 }
1498 i_last_space_width = 0;
1499 }
1500 i_width += i_advance_x;
1501 }
1502
1503 *pp_lines = p_first_line;
1504 return VLC_SUCCESS;
1505
1506 error:
1507 for( int i = i_line_start; i < p_paragraph->i_size; ++i )
1508 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1509 if( p_first_line )
1510 FreeLines( p_first_line );
1511 return VLC_EGENERIC;
1512 }
1513
LayoutText(filter_t * p_filter,const uni_char_t * psz_text,text_style_t ** pp_styles,uint32_t * pi_k_dates,int i_len,bool b_grid,bool b_balance,unsigned i_max_width,unsigned i_max_height,line_desc_t ** pp_lines,FT_BBox * p_bbox,int * pi_max_face_height)1514 int LayoutText( filter_t *p_filter,
1515 const uni_char_t *psz_text, text_style_t **pp_styles,
1516 uint32_t *pi_k_dates, int i_len,
1517 bool b_grid, bool b_balance,
1518 unsigned i_max_width, unsigned i_max_height,
1519 line_desc_t **pp_lines, FT_BBox *p_bbox, int *pi_max_face_height )
1520 {
1521 line_desc_t *p_first_line = 0;
1522 line_desc_t **pp_line = &p_first_line;
1523 paragraph_t *p_paragraph = 0;
1524 int i_paragraph_start = 0;
1525 unsigned i_total_height = 0;
1526 unsigned i_max_advance_x = 0;
1527 int i_max_face_height = 0;
1528
1529 for( int i = 0; i <= i_len; ++i )
1530 {
1531 if( i == i_len || psz_text[ i ] == '\n' )
1532 {
1533 if( i_paragraph_start == i )
1534 {
1535 i_paragraph_start = i + 1;
1536 continue;
1537 }
1538
1539 p_paragraph = NewParagraph( p_filter, i - i_paragraph_start,
1540 psz_text + i_paragraph_start,
1541 pp_styles + i_paragraph_start,
1542 pi_k_dates ?
1543 pi_k_dates + i_paragraph_start : 0,
1544 20 );
1545 if( !p_paragraph )
1546 {
1547 if( p_first_line ) FreeLines( p_first_line );
1548 return VLC_ENOMEM;
1549 }
1550
1551 #ifdef HAVE_FRIBIDI
1552 if( AnalyzeParagraph( p_paragraph ) )
1553 goto error;
1554 #endif
1555
1556 if( ItemizeParagraph( p_filter, p_paragraph ) )
1557 goto error;
1558
1559 #if defined HAVE_HARFBUZZ
1560 if( ShapeParagraphHarfBuzz( p_filter, &p_paragraph ) )
1561 goto error;
1562
1563 if( LoadGlyphs( p_filter, p_paragraph, true, false, &i_max_advance_x ) )
1564 goto error;
1565
1566 #elif defined HAVE_FRIBIDI
1567 if( ShapeParagraphFriBidi( p_filter, p_paragraph ) )
1568 goto error;
1569 if( LoadGlyphs( p_filter, p_paragraph, false, true, &i_max_advance_x ) )
1570 goto error;
1571 if( RemoveZeroWidthCharacters( p_paragraph ) )
1572 goto error;
1573 if( ZeroNsmAdvance( p_paragraph ) )
1574 goto error;
1575 #else
1576 if( LoadGlyphs( p_filter, p_paragraph, false, true, &i_max_advance_x ) )
1577 goto error;
1578 #endif
1579
1580 if( LayoutParagraph( p_filter, p_paragraph,
1581 i_max_width, i_max_advance_x, pp_line,
1582 b_grid, b_balance ) )
1583 goto error;
1584
1585 FreeParagraph( p_paragraph );
1586 p_paragraph = 0;
1587
1588 for( ; *pp_line; pp_line = &(*pp_line)->p_next )
1589 {
1590 /* only cut at max i_max_height + 1 line due to
1591 * approximate font sizing vs region size */
1592 if( i_max_height > 0 && i_total_height > i_max_height )
1593 {
1594 i_total_height = i_max_height + 1;
1595 line_desc_t *p_todelete = *pp_line;
1596 while( p_todelete ) /* Drop extra lines */
1597 {
1598 line_desc_t *p_next = p_todelete->p_next;
1599 FreeLine( p_todelete );
1600 p_todelete = p_next;
1601 }
1602 *pp_line = NULL;
1603 i = i_len + 1; /* force no more paragraphs */
1604 break; /* ! no p_next ! */
1605 }
1606 else if( (*pp_line)->i_height > i_max_face_height )
1607 {
1608 i_max_face_height = (*pp_line)->i_height;
1609 }
1610 i_total_height += (*pp_line)->i_height;
1611 }
1612 i_paragraph_start = i + 1;
1613 }
1614 }
1615
1616 int i_base_line = 0;
1617 FT_BBox bbox;
1618 BBoxInit( &bbox );
1619
1620 for( line_desc_t *p_line = p_first_line; p_line; p_line = p_line->p_next )
1621 {
1622 p_line->i_base_line = i_base_line;
1623 p_line->bbox.yMin -= i_base_line;
1624 p_line->bbox.yMax -= i_base_line;
1625 BBoxEnlarge( &bbox, &p_line->bbox );
1626
1627 i_base_line += i_max_face_height;
1628 }
1629
1630 *pi_max_face_height = i_max_face_height;
1631 *pp_lines = p_first_line;
1632 *p_bbox = bbox;
1633 return VLC_SUCCESS;
1634
1635 error:
1636 if( p_first_line ) FreeLines( p_first_line );
1637 if( p_paragraph ) FreeParagraph( p_paragraph );
1638 return VLC_EGENERIC;
1639 }
1640
1641