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