FreeType: fix the rendering of a single caracter
[vlc.git] / modules / text_renderer / freetype / text_layout.c
blob7dd8dd4e37c0adcf4fb7dd287ec55c217ee45871
1 /*****************************************************************************
2 * text_layout.c : Text shaping and layout
3 *****************************************************************************
4 * Copyright (C) 2015 VLC authors and VideoLAN
5 * $Id$
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>
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.
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.
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 *****************************************************************************/
32 /** \ingroup freetype
33 * @{
34 * \file
35 * Text shaping and layout
38 #ifdef HAVE_CONFIG_H
39 # include "config.h"
40 #endif
42 #include <vlc_common.h>
43 #include <vlc_filter.h>
44 #include <vlc_text_style.h>
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
53 /* RTL */
54 #if defined(HAVE_FRIBIDI)
55 # define FRIBIDI_NO_DEPRECATED 1
56 # include <fribidi.h>
57 #endif
59 /* Complex Scripts */
60 #if defined(HAVE_HARFBUZZ)
61 # include <hb.h>
62 # include <hb-ft.h>
63 #endif
65 #include "freetype.h"
66 #include "text_layout.h"
67 #include "platform_fonts.h"
69 /* Win32 */
70 #ifdef _WIN32
71 # undef HAVE_FONTCONFIG
72 # define HAVE_FONT_FALLBACK
73 #endif
75 /* FontConfig */
76 #ifdef HAVE_FONTCONFIG
77 # define HAVE_FONT_FALLBACK
78 #endif
80 /* Android */
81 #ifdef __ANDROID__
82 # define HAVE_FONT_FALLBACK
83 #endif
85 /* Darwin */
86 #ifdef __APPLE__
87 # define HAVE_FONT_FALLBACK
88 #endif
90 #ifndef HAVE_FONT_FALLBACK
91 # warning YOU ARE MISSING FONTS FALLBACK. TEXT WILL BE INCORRECT
92 #endif
94 /**
95 * Within a paragraph, run_desc_t represents a run of characters
96 * having the same font face, size, and style, Unicode script
97 * and text direction
99 typedef struct run_desc_t
101 int i_start_offset;
102 int i_end_offset;
103 FT_Face p_face;
104 const text_style_t *p_style;
106 #ifdef HAVE_HARFBUZZ
107 hb_script_t script;
108 hb_direction_t direction;
109 hb_font_t *p_hb_font;
110 hb_buffer_t *p_buffer;
111 hb_glyph_info_t *p_glyph_infos;
112 hb_glyph_position_t *p_glyph_positions;
113 unsigned int i_glyph_count;
114 #endif
116 } run_desc_t;
119 * Glyph bitmaps. Advance and offset are 26.6 values
121 typedef struct glyph_bitmaps_t
123 FT_Glyph p_glyph;
124 FT_Glyph p_outline;
125 FT_Glyph p_shadow;
126 FT_BBox glyph_bbox;
127 FT_BBox outline_bbox;
128 FT_BBox shadow_bbox;
129 int i_x_offset;
130 int i_y_offset;
131 int i_x_advance;
132 int i_y_advance;
133 } glyph_bitmaps_t;
135 typedef struct paragraph_t
137 uni_char_t *p_code_points; /**< Unicode code points */
138 int *pi_glyph_indices; /**< Glyph index values within the run's font face */
139 text_style_t **pp_styles;
140 FT_Face *pp_faces; /**< Used to determine run boundaries when performing font fallback */
141 int *pi_run_ids; /**< The run to which each glyph belongs */
142 glyph_bitmaps_t *p_glyph_bitmaps;
143 uint8_t *pi_karaoke_bar;
144 int i_size;
145 run_desc_t *p_runs;
146 int i_runs_count;
147 int i_runs_size;
149 #ifdef HAVE_HARFBUZZ
150 hb_script_t *p_scripts;
151 #endif
153 #ifdef HAVE_FRIBIDI
154 FriBidiCharType *p_types;
155 FriBidiLevel *p_levels;
156 FriBidiStrIndex *pi_reordered_indices;
157 FriBidiParType paragraph_type;
158 #endif
160 } paragraph_t;
162 static void FreeLine( line_desc_t *p_line )
164 for( int i = 0; i < p_line->i_character_count; i++ )
166 line_character_t *ch = &p_line->p_character[i];
167 FT_Done_Glyph( (FT_Glyph)ch->p_glyph );
168 if( ch->p_outline )
169 FT_Done_Glyph( (FT_Glyph)ch->p_outline );
170 if( ch->p_shadow )
171 FT_Done_Glyph( (FT_Glyph)ch->p_shadow );
174 free( p_line->p_character );
175 free( p_line );
178 void FreeLines( line_desc_t *p_lines )
180 for( line_desc_t *p_line = p_lines; p_line != NULL; )
182 line_desc_t *p_next = p_line->p_next;
183 FreeLine( p_line );
184 p_line = p_next;
188 line_desc_t *NewLine( int i_count )
190 line_desc_t *p_line = malloc( sizeof(*p_line) );
192 if( !p_line )
193 return NULL;
195 p_line->p_next = NULL;
196 p_line->i_width = 0;
197 p_line->i_base_line = 0;
198 p_line->i_character_count = 0;
199 p_line->i_first_visible_char_index = -1;
200 p_line->i_last_visible_char_index = -2;
202 p_line->bbox.xMin = INT_MAX;
203 p_line->bbox.yMin = INT_MAX;
204 p_line->bbox.xMax = INT_MIN;
205 p_line->bbox.yMax = INT_MIN;
207 p_line->p_character = calloc( i_count, sizeof(*p_line->p_character) );
208 if( !p_line->p_character )
210 free( p_line );
211 return NULL;
213 return p_line;
216 static void FixGlyph( FT_Glyph glyph, FT_BBox *p_bbox,
217 FT_Pos i_x_advance, FT_Pos i_y_advance,
218 const FT_Vector *p_pen )
220 FT_BitmapGlyph glyph_bmp = (FT_BitmapGlyph)glyph;
221 if( p_bbox->xMin >= p_bbox->xMax )
223 p_bbox->xMin = FT_CEIL(p_pen->x);
224 p_bbox->xMax = FT_CEIL(p_pen->x + i_x_advance);
225 glyph_bmp->left = p_bbox->xMin;
227 if( p_bbox->yMin >= p_bbox->yMax )
229 p_bbox->yMax = FT_CEIL(p_pen->y);
230 p_bbox->yMin = FT_CEIL(p_pen->y + i_y_advance);
231 glyph_bmp->top = p_bbox->yMax;
235 static void BBoxEnlarge( FT_BBox *p_max, const FT_BBox *p )
237 p_max->xMin = __MIN(p_max->xMin, p->xMin);
238 p_max->yMin = __MIN(p_max->yMin, p->yMin);
239 p_max->xMax = __MAX(p_max->xMax, p->xMax);
240 p_max->yMax = __MAX(p_max->yMax, p->yMax);
243 static paragraph_t *NewParagraph( filter_t *p_filter,
244 int i_size,
245 const uni_char_t *p_code_points,
246 text_style_t **pp_styles,
247 uint32_t *pi_k_dates,
248 int i_runs_size )
250 paragraph_t *p_paragraph = calloc( 1, sizeof( paragraph_t ) );
251 if( !p_paragraph )
252 return 0;
254 p_paragraph->i_size = i_size;
255 p_paragraph->p_code_points =
256 malloc( i_size * sizeof( *p_paragraph->p_code_points ) );
257 p_paragraph->pi_glyph_indices =
258 malloc( i_size * sizeof( *p_paragraph->pi_glyph_indices ) );
259 p_paragraph->pp_styles =
260 malloc( i_size * sizeof( *p_paragraph->pp_styles ) );
261 p_paragraph->pp_faces =
262 calloc( i_size, sizeof( *p_paragraph->pp_faces ) );
263 p_paragraph->pi_run_ids =
264 calloc( i_size, sizeof( *p_paragraph->pi_run_ids ) );
265 p_paragraph->p_glyph_bitmaps =
266 calloc( i_size, sizeof( *p_paragraph->p_glyph_bitmaps ) );
267 p_paragraph->pi_karaoke_bar =
268 calloc( i_size, sizeof( *p_paragraph->pi_karaoke_bar ) );
270 p_paragraph->p_runs = calloc( i_runs_size, sizeof( run_desc_t ) );
271 p_paragraph->i_runs_size = i_runs_size;
272 p_paragraph->i_runs_count = 0;
274 if( !p_paragraph->p_code_points || !p_paragraph->pi_glyph_indices
275 || !p_paragraph->pp_styles || !p_paragraph->pp_faces
276 || !p_paragraph->pi_run_ids|| !p_paragraph->p_glyph_bitmaps
277 || !p_paragraph->pi_karaoke_bar || !p_paragraph->p_runs )
278 goto error;
280 if( p_code_points )
281 memcpy( p_paragraph->p_code_points, p_code_points,
282 i_size * sizeof( *p_code_points ) );
283 if( pp_styles )
284 memcpy( p_paragraph->pp_styles, pp_styles,
285 i_size * sizeof( *pp_styles ) );
286 if( pi_k_dates )
288 int64_t i_elapsed = var_GetInteger( p_filter, "spu-elapsed" ) / 1000;
289 for( int i = 0; i < i_size; ++i )
291 p_paragraph->pi_karaoke_bar[ i ] = pi_k_dates[ i ] >= i_elapsed;
295 #ifdef HAVE_HARFBUZZ
296 p_paragraph->p_scripts = malloc( i_size * sizeof( *p_paragraph->p_scripts ) );
297 if( !p_paragraph->p_scripts )
298 goto error;
299 #endif
301 #ifdef HAVE_FRIBIDI
302 p_paragraph->p_levels = malloc( i_size * sizeof( *p_paragraph->p_levels ) );
303 p_paragraph->p_types = malloc( i_size * sizeof( *p_paragraph->p_types ) );
304 p_paragraph->pi_reordered_indices =
305 malloc( i_size * sizeof( *p_paragraph->pi_reordered_indices ) );
307 if( !p_paragraph->p_levels || !p_paragraph->p_types
308 || !p_paragraph->pi_reordered_indices )
309 goto error;
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
320 return p_paragraph;
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( p_paragraph->pi_reordered_indices )
338 free( p_paragraph->pi_reordered_indices );
339 #endif
340 free( p_paragraph );
341 return 0;
344 static void FreeParagraph( paragraph_t *p_paragraph )
346 free( p_paragraph->p_runs );
347 free( p_paragraph->pi_glyph_indices );
348 free( p_paragraph->p_glyph_bitmaps );
349 free( p_paragraph->pi_karaoke_bar );
350 free( p_paragraph->pi_run_ids );
351 free( p_paragraph->pp_faces );
352 free( p_paragraph->pp_styles );
353 free( p_paragraph->p_code_points );
355 #ifdef HAVE_HARFBUZZ
356 free( p_paragraph->p_scripts );
357 #endif
359 #ifdef HAVE_FRIBIDI
360 free( p_paragraph->pi_reordered_indices );
361 free( p_paragraph->p_types );
362 free( p_paragraph->p_levels );
363 #endif
365 free( p_paragraph );
368 #ifdef HAVE_FRIBIDI
369 static int AnalyzeParagraph( paragraph_t *p_paragraph )
371 fribidi_get_bidi_types( p_paragraph->p_code_points,
372 p_paragraph->i_size,
373 p_paragraph->p_types );
374 fribidi_get_par_embedding_levels( p_paragraph->p_types,
375 p_paragraph->i_size,
376 &p_paragraph->paragraph_type,
377 p_paragraph->p_levels );
379 #ifdef HAVE_HARFBUZZ
380 hb_unicode_funcs_t *p_funcs = hb_unicode_funcs_get_default();
381 for( int i = 0; i < p_paragraph->i_size; ++i )
382 p_paragraph->p_scripts[ i ] =
383 hb_unicode_script( p_funcs, p_paragraph->p_code_points[ i ] );
385 hb_script_t i_last_script;
386 int i_last_script_index = -1;
387 int i_last_set_index = -1;
390 * For shaping to work, characters that are assigned HB_SCRIPT_COMMON or
391 * HB_SCRIPT_INHERITED should be resolved to the last encountered valid
392 * script value, if any, and to the first one following them otherwise
394 for( int i = 0; i < p_paragraph->i_size; ++i )
396 if( p_paragraph->p_scripts[ i ] == HB_SCRIPT_COMMON
397 || p_paragraph->p_scripts[ i ] == HB_SCRIPT_INHERITED)
399 if( i_last_script_index != -1)
401 p_paragraph->p_scripts[ i ] = i_last_script;
402 i_last_set_index = i;
405 else
407 for( int j = i_last_set_index + 1; j < i; ++j )
408 p_paragraph->p_scripts[ j ] = p_paragraph->p_scripts[ i ];
410 i_last_script = p_paragraph->p_scripts[ i ];
411 i_last_script_index = i;
412 i_last_set_index = i;
415 #endif //HAVE_HARFBUZZ
417 return VLC_SUCCESS;
419 #endif //HAVE_FRIBIDI
421 static int AddRun( filter_t *p_filter,
422 paragraph_t *p_paragraph,
423 int i_start_offset,
424 int i_end_offset,
425 FT_Face p_face,
426 const text_style_t *p_style )
428 if( i_start_offset >= i_end_offset
429 || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
430 || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
432 msg_Err( p_filter,
433 "AddRun() invalid parameters. Paragraph size: %d, "
434 "Start offset: %d, End offset: %d",
435 p_paragraph->i_size, i_start_offset, i_end_offset );
436 return VLC_EGENERIC;
439 if( p_paragraph->i_runs_count == p_paragraph->i_runs_size )
441 run_desc_t *p_new_runs =
442 realloc( p_paragraph->p_runs,
443 p_paragraph->i_runs_size * 2 * sizeof( *p_new_runs ) );
444 if( !p_new_runs )
445 return VLC_ENOMEM;
447 memset( p_new_runs + p_paragraph->i_runs_size , 0,
448 p_paragraph->i_runs_size * sizeof( *p_new_runs ) );
450 p_paragraph->p_runs = p_new_runs;
451 p_paragraph->i_runs_size *= 2;
454 int i_run_id = p_paragraph->i_runs_count;
455 run_desc_t *p_run = p_paragraph->p_runs + p_paragraph->i_runs_count++;
456 p_run->i_start_offset = i_start_offset;
457 p_run->i_end_offset = i_end_offset;
458 p_run->p_face = p_face;
460 if( p_style )
461 p_run->p_style = p_style;
462 else
463 p_run->p_style = p_paragraph->pp_styles[ i_start_offset ];
465 #ifdef HAVE_HARFBUZZ
466 p_run->script = p_paragraph->p_scripts[ i_start_offset ];
467 p_run->direction = p_paragraph->p_levels[ i_start_offset ] & 1 ?
468 HB_DIRECTION_RTL : HB_DIRECTION_LTR;
469 #endif
471 for( int i = i_start_offset; i < i_end_offset; ++i )
472 p_paragraph->pi_run_ids[ i ] = i_run_id;
474 return VLC_SUCCESS;
477 #ifdef HAVE_FONT_FALLBACK
479 * Add a run with font fallback, possibly breaking the run further
480 * into runs of glyphs that end up having the same font face.
482 static int AddRunWithFallback( filter_t *p_filter, paragraph_t *p_paragraph,
483 int i_start_offset, int i_end_offset )
485 if( i_start_offset >= i_end_offset
486 || i_start_offset < 0 || i_start_offset >= p_paragraph->i_size
487 || i_end_offset <= 0 || i_end_offset > p_paragraph->i_size )
489 msg_Err( p_filter,
490 "AddRunWithFallback() invalid parameters. Paragraph size: %d, "
491 "Start offset: %d, End offset: %d",
492 p_paragraph->i_size, i_start_offset, i_end_offset );
493 return VLC_EGENERIC;
496 const text_style_t *p_style = p_paragraph->pp_styles[ i_start_offset ];
498 /* Maximum number of faces to try for each run */
499 #define MAX_FACES 5
500 FT_Face pp_faces[ MAX_FACES ] = {0};
502 pp_faces[ 0 ] = SelectAndLoadFace( p_filter, p_style, 0 );
504 for( int i = i_start_offset; i < i_end_offset; ++i )
506 int i_index = 0;
507 int i_glyph_index = 0;
508 FT_Face p_face = NULL;
509 do {
510 p_face = pp_faces[ i_index ];
511 if( !p_face )
512 p_face = pp_faces[ i_index ] =
513 SelectAndLoadFace( p_filter, p_style,
514 p_paragraph->p_code_points[ i ] );
515 if( !p_face )
516 continue;
517 i_glyph_index = FT_Get_Char_Index( p_face,
518 p_paragraph->p_code_points[ i ] );
519 if( i_glyph_index )
521 p_paragraph->pp_faces[ i ] = p_face;
524 * Move p_face to the beginning of the array. Otherwise strikethrough
525 * lines can appear segmented, being rendered at a certain height
526 * through spaces and at a different height through words.
527 * Skip this step for the specified special characters. See #15840.
529 if( i_index > 0 )
531 uni_char_t codepoint = p_paragraph->p_code_points[ i ];
532 if( codepoint != 0x0009 && codepoint != 0x00A0
533 && codepoint != 0x1680 && codepoint != 0x061C
534 && codepoint != 0x202F && codepoint != 0x205F
535 && codepoint != 0x3000 && codepoint != 0xFEFF
536 && !( codepoint >= 0x2000 && codepoint <= 0x200F )
537 && !( codepoint >= 0x202A && codepoint <= 0x202E )
538 && !( codepoint >= 0x2060 && codepoint <= 0x2069 ) )
540 pp_faces[ i_index ] = pp_faces[ 0 ];
541 pp_faces[ 0 ] = p_face;
546 } while( i_glyph_index == 0 && ++i_index < MAX_FACES );
549 int i_run_start = i_start_offset;
550 for( int i = i_start_offset; i <= i_end_offset; ++i )
552 if( i == i_end_offset
553 || p_paragraph->pp_faces[ i_run_start ] != p_paragraph->pp_faces[ i ] )
555 if( AddRun( p_filter, p_paragraph, i_run_start, i,
556 p_paragraph->pp_faces[ i_run_start ], NULL ) )
557 return VLC_EGENERIC;
559 i_run_start = i;
563 return VLC_SUCCESS;
565 #endif
567 static bool FaceStyleEquals( filter_t *p_filter, const text_style_t *p_style1,
568 const text_style_t *p_style2 )
570 if( !p_style1 || !p_style2 )
571 return false;
572 if( p_style1 == p_style2 )
573 return true;
575 const int i_style_mask = STYLE_BOLD | STYLE_ITALIC | STYLE_HALFWIDTH | STYLE_DOUBLEWIDTH;
577 const char *psz_fontname1 = p_style1->i_style_flags & STYLE_MONOSPACED
578 ? p_style1->psz_monofontname : p_style1->psz_fontname;
580 const char *psz_fontname2 = p_style2->i_style_flags & STYLE_MONOSPACED
581 ? p_style2->psz_monofontname : p_style2->psz_fontname;
583 const int i_size1 = ConvertToLiveSize( p_filter, p_style1 );
584 const int i_size2 = ConvertToLiveSize( p_filter, p_style2 );
586 return (p_style1->i_style_flags & i_style_mask) == (p_style2->i_style_flags & i_style_mask)
587 && i_size1 == i_size2
588 && !strcasecmp( psz_fontname1, psz_fontname2 );
592 * Segment a paragraph into runs
594 static int ItemizeParagraph( filter_t *p_filter, paragraph_t *p_paragraph )
596 if( p_paragraph->i_size <= 0 )
598 msg_Err( p_filter,
599 "ItemizeParagraph() invalid parameters. Paragraph size: %d",
600 p_paragraph->i_size );
601 return VLC_EGENERIC;
604 int i_last_run_start = 0;
605 const text_style_t *p_last_style = p_paragraph->pp_styles[ 0 ];
607 #ifdef HAVE_HARFBUZZ
608 hb_script_t last_script = p_paragraph->p_scripts[ 0 ];
609 FriBidiLevel last_level = p_paragraph->p_levels[ 0 ];
610 #endif
612 for( int i = 0; i <= p_paragraph->i_size; ++i )
614 if( i == p_paragraph->i_size
615 #ifdef HAVE_HARFBUZZ
616 || last_script != p_paragraph->p_scripts[ i ]
617 || last_level != p_paragraph->p_levels[ i ]
618 #endif
619 || !FaceStyleEquals( p_filter, p_last_style, p_paragraph->pp_styles[ i ] ) )
621 int i_ret;
622 #ifdef HAVE_FONT_FALLBACK
623 i_ret = AddRunWithFallback( p_filter, p_paragraph, i_last_run_start, i );
624 #else
625 i_ret = AddRun( p_filter, p_paragraph, i_last_run_start, i, NULL, NULL );
626 #endif
627 if( i_ret )
628 return i_ret;
630 if( i < p_paragraph->i_size )
632 i_last_run_start = i;
633 p_last_style = p_paragraph->pp_styles[ i ];
634 #ifdef HAVE_HARFBUZZ
635 last_script = p_paragraph->p_scripts[ i ];
636 last_level = p_paragraph->p_levels[ i ];
637 #endif
641 return VLC_SUCCESS;
644 #ifdef HAVE_HARFBUZZ
646 * Shape an itemized paragraph using HarfBuzz.
647 * This is where the glyphs of complex scripts get their positions
648 * (offsets and advance values) and final forms.
649 * Glyph substitutions of base glyphs and diacritics may take place,
650 * so the paragraph size may change.
652 static int ShapeParagraphHarfBuzz( filter_t *p_filter,
653 paragraph_t **p_old_paragraph )
655 paragraph_t *p_paragraph = *p_old_paragraph;
656 paragraph_t *p_new_paragraph = 0;
657 filter_sys_t *p_sys = p_filter->p_sys;
658 int i_total_glyphs = 0;
659 int i_ret = VLC_EGENERIC;
661 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
663 msg_Err( p_filter, "ShapeParagraphHarfBuzz() invalid parameters. "
664 "Paragraph size: %d. Runs count %d",
665 p_paragraph->i_size, p_paragraph->i_runs_count );
666 return VLC_EGENERIC;
669 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
671 run_desc_t *p_run = p_paragraph->p_runs + i;
672 const text_style_t *p_style = p_run->p_style;
675 * With HarfBuzz and no font fallback, this is where font faces
676 * are loaded. In the other two paths (shaping with FriBidi or no
677 * shaping at all), faces are loaded in LoadGlyphs().
679 * If we have font fallback, font faces in all paths will be
680 * loaded in AddRunWithFallback(), except for runs of codepoints
681 * for which no font could be found.
683 FT_Face p_face = 0;
684 if( !p_run->p_face )
686 p_face = SelectAndLoadFace( p_filter, p_style, 0 );
687 if( !p_face )
689 p_face = p_sys->p_face;
690 p_style = p_sys->p_default_style;
691 p_run->p_style = p_style;
693 p_run->p_face = p_face;
695 else
696 p_face = p_run->p_face;
698 p_run->p_hb_font = hb_ft_font_create( p_face, 0 );
699 if( !p_run->p_hb_font )
701 msg_Err( p_filter,
702 "ShapeParagraphHarfBuzz(): hb_ft_font_create() error" );
703 goto error;
706 p_run->p_buffer = hb_buffer_create();
707 if( !p_run->p_buffer )
709 msg_Err( p_filter,
710 "ShapeParagraphHarfBuzz(): hb_buffer_create() error" );
711 goto error;
714 hb_buffer_set_direction( p_run->p_buffer, p_run->direction );
715 hb_buffer_set_script( p_run->p_buffer, p_run->script );
716 #ifdef __OS2__
717 hb_buffer_add_utf16( p_run->p_buffer,
718 p_paragraph->p_code_points + p_run->i_start_offset,
719 p_run->i_end_offset - p_run->i_start_offset, 0,
720 p_run->i_end_offset - p_run->i_start_offset );
721 #else
722 hb_buffer_add_utf32( p_run->p_buffer,
723 p_paragraph->p_code_points + p_run->i_start_offset,
724 p_run->i_end_offset - p_run->i_start_offset, 0,
725 p_run->i_end_offset - p_run->i_start_offset );
726 #endif
727 hb_shape( p_run->p_hb_font, p_run->p_buffer, 0, 0 );
728 p_run->p_glyph_infos =
729 hb_buffer_get_glyph_infos( p_run->p_buffer, &p_run->i_glyph_count );
730 p_run->p_glyph_positions =
731 hb_buffer_get_glyph_positions( p_run->p_buffer, &p_run->i_glyph_count );
733 if( p_run->i_glyph_count <= 0 )
735 msg_Err( p_filter,
736 "ShapeParagraphHarfBuzz() invalid glyph count in shaped run" );
737 goto error;
740 i_total_glyphs += p_run->i_glyph_count;
743 p_new_paragraph = NewParagraph( p_filter, i_total_glyphs, 0, 0, 0,
744 p_paragraph->i_runs_size );
745 if( !p_new_paragraph )
747 i_ret = VLC_ENOMEM;
748 goto error;
750 p_new_paragraph->paragraph_type = p_paragraph->paragraph_type;
752 int i_index = 0;
753 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
755 run_desc_t *p_run = p_paragraph->p_runs + i;
756 hb_glyph_info_t *p_infos = p_run->p_glyph_infos;
757 hb_glyph_position_t *p_positions = p_run->p_glyph_positions;
758 for( unsigned int j = 0; j < p_run->i_glyph_count; ++j )
761 * HarfBuzz reverses the order of glyphs in RTL runs. We reverse
762 * it again here to keep the glyphs in their logical order.
763 * For line breaking of paragraphs to work correctly, visual
764 * reordering should be done after line breaking has taken
765 * place.
767 int i_run_index = p_run->direction == HB_DIRECTION_LTR ?
768 j : p_run->i_glyph_count - 1 - j;
769 int i_source_index =
770 p_infos[ i_run_index ].cluster + p_run->i_start_offset;
772 p_new_paragraph->p_code_points[ i_index ] = 0;
773 p_new_paragraph->pi_glyph_indices[ i_index ] =
774 p_infos[ i_run_index ].codepoint;
775 p_new_paragraph->p_scripts[ i_index ] =
776 p_paragraph->p_scripts[ i_source_index ];
777 p_new_paragraph->p_types[ i_index ] =
778 p_paragraph->p_types[ i_source_index ];
779 p_new_paragraph->p_levels[ i_index ] =
780 p_paragraph->p_levels[ i_source_index ];
781 p_new_paragraph->pp_styles[ i_index ] =
782 p_paragraph->pp_styles[ i_source_index ];
783 p_new_paragraph->pi_karaoke_bar[ i_index ] =
784 p_paragraph->pi_karaoke_bar[ i_source_index ];
785 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_offset =
786 p_positions[ i_run_index ].x_offset;
787 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_offset =
788 p_positions[ i_run_index ].y_offset;
789 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_x_advance =
790 p_positions[ i_run_index ].x_advance;
791 p_new_paragraph->p_glyph_bitmaps[ i_index ].i_y_advance =
792 p_positions[ i_run_index ].y_advance;
794 ++i_index;
796 if( AddRun( p_filter, p_new_paragraph, i_index - p_run->i_glyph_count,
797 i_index, p_run->p_face, p_run->p_style ) )
798 goto error;
801 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
803 hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
804 hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
806 FreeParagraph( *p_old_paragraph );
807 *p_old_paragraph = p_new_paragraph;
809 return VLC_SUCCESS;
811 error:
812 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
814 if( p_paragraph->p_runs[ i ].p_hb_font )
815 hb_font_destroy( p_paragraph->p_runs[ i ].p_hb_font );
816 if( p_paragraph->p_runs[ i ].p_buffer )
817 hb_buffer_destroy( p_paragraph->p_runs[ i ].p_buffer );
820 if( p_new_paragraph )
821 FreeParagraph( p_new_paragraph );
823 return i_ret;
825 #endif
827 #ifdef HAVE_FRIBIDI
828 #ifndef HAVE_HARFBUZZ
830 * Shape a paragraph with FriBidi.
831 * Shaping with FriBidi is currently limited to mirroring and simple
832 * Arabic shaping.
834 static int ShapeParagraphFriBidi( filter_t *p_filter, paragraph_t *p_paragraph )
837 if( p_paragraph->i_size <= 0 )
839 msg_Err( p_filter,
840 "ShapeParagraphFriBidi() invalid parameters. Paragraph size: %d",
841 p_paragraph->i_size );
842 return VLC_EGENERIC;
845 FriBidiJoiningType *p_joining_types =
846 malloc( p_paragraph->i_size * sizeof( *p_joining_types ) );
847 if( !p_joining_types )
848 return VLC_ENOMEM;
850 fribidi_get_joining_types( p_paragraph->p_code_points,
851 p_paragraph->i_size, p_joining_types );
852 fribidi_join_arabic( p_paragraph->p_types, p_paragraph->i_size,
853 p_paragraph->p_levels, p_joining_types );
854 fribidi_shape( FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC,
855 p_paragraph->p_levels,
856 p_paragraph->i_size,
857 p_joining_types,
858 p_paragraph->p_code_points );
860 free( p_joining_types );
862 return VLC_SUCCESS;
866 * Zero-width invisible characters include Unicode control characters and
867 * zero-width spaces among other things. If not removed they can show up in the
868 * text as squares or other glyphs depending on the font. Zero-width spaces are
869 * inserted when shaping with FriBidi, when it performs glyph substitution for
870 * ligatures.
872 static int RemoveZeroWidthCharacters( paragraph_t *p_paragraph )
874 for( int i = 0; i < p_paragraph->i_size; ++i )
876 uni_char_t ch = p_paragraph->p_code_points[ i ];
877 if( ch == 0xfeff
878 || ch == 0x061c
879 || ( ch >= 0x202a && ch <= 0x202e )
880 || ( ch >= 0x2060 && ch <= 0x2069 )
881 || ( ch >= 0x200b && ch <= 0x200f ) )
883 glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + i;
884 if( p_bitmaps->p_glyph )
885 FT_Done_Glyph( p_bitmaps->p_glyph );
886 if( p_bitmaps->p_outline )
887 FT_Done_Glyph( p_bitmaps->p_outline );
888 p_bitmaps->p_glyph = 0;
889 p_bitmaps->p_outline = 0;
890 p_bitmaps->p_shadow = 0;
891 p_bitmaps->i_x_advance = 0;
892 p_bitmaps->i_y_advance = 0;
896 return VLC_SUCCESS;
900 * Set advance values of non-spacing marks to zero. Diacritics are
901 * not positioned correctly but the text is more readable.
902 * For full shaping HarfBuzz is required.
904 static int ZeroNsmAdvance( paragraph_t *p_paragraph )
906 for( int i = 0; i < p_paragraph->i_size; ++i )
907 if( p_paragraph->p_types[ i ] == FRIBIDI_TYPE_NSM )
909 p_paragraph->p_glyph_bitmaps[ i ].i_x_advance = 0;
910 p_paragraph->p_glyph_bitmaps[ i ].i_y_advance = 0;
912 return VLC_SUCCESS;
914 #endif
915 #endif
918 * Load the glyphs of a paragraph. When shaping with HarfBuzz the glyph indices
919 * have already been determined at this point, as well as the advance values.
921 static int LoadGlyphs( filter_t *p_filter, paragraph_t *p_paragraph,
922 bool b_use_glyph_indices, bool b_overwrite_advance,
923 unsigned *pi_max_advance_x )
925 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
927 msg_Err( p_filter, "LoadGlyphs() invalid parameters. "
928 "Paragraph size: %d. Runs count %d", p_paragraph->i_size,
929 p_paragraph->i_runs_count );
930 return VLC_EGENERIC;
933 filter_sys_t *p_sys = p_filter->p_sys;
934 *pi_max_advance_x = 0;
936 for( int i = 0; i < p_paragraph->i_runs_count; ++i )
938 run_desc_t *p_run = p_paragraph->p_runs + i;
939 const text_style_t *p_style = p_run->p_style;
940 const int i_live_size = ConvertToLiveSize( p_filter, p_style );
942 FT_Face p_face = 0;
943 if( !p_run->p_face )
945 p_face = SelectAndLoadFace( p_filter, p_style, 0 );
946 if( !p_face )
948 /* Uses the default font and style */
949 p_face = p_sys->p_face;
950 p_style = p_sys->p_default_style;
951 p_run->p_style = p_style;
953 p_run->p_face = p_face;
955 else
956 p_face = p_run->p_face;
958 if( p_sys->p_stroker && (p_style->i_style_flags & STYLE_OUTLINE) )
960 double f_outline_thickness =
961 var_InheritInteger( p_filter, "freetype-outline-thickness" ) / 100.0;
962 f_outline_thickness = VLC_CLIP( f_outline_thickness, 0.0, 0.5 );
963 int i_radius = ( i_live_size << 6 ) * f_outline_thickness;
964 FT_Stroker_Set( p_sys->p_stroker,
965 i_radius,
966 FT_STROKER_LINECAP_ROUND,
967 FT_STROKER_LINEJOIN_ROUND, 0 );
970 for( int j = p_run->i_start_offset; j < p_run->i_end_offset; ++j )
972 int i_glyph_index;
973 if( b_use_glyph_indices )
974 i_glyph_index = p_paragraph->pi_glyph_indices[ j ];
975 else
976 i_glyph_index =
977 FT_Get_Char_Index( p_face, p_paragraph->p_code_points[ j ] );
979 glyph_bitmaps_t *p_bitmaps = p_paragraph->p_glyph_bitmaps + j;
981 #define SKIP_GLYPH( p_bitmaps ) \
983 p_bitmaps->p_glyph = 0; \
984 p_bitmaps->p_outline = 0; \
985 p_bitmaps->p_shadow = 0; \
986 p_bitmaps->i_x_advance = 0; \
987 p_bitmaps->i_y_advance = 0; \
988 continue; \
991 if( !i_glyph_index )
993 uni_char_t codepoint = p_paragraph->p_code_points[ j ];
995 * If the font has no support for special space characters, use regular
996 * space glyphs instead of the .notdef glyph.
998 if( codepoint == 0x0009 || codepoint == 0x00A0
999 || codepoint == 0x1680 || codepoint == 0x3000
1000 || codepoint == 0x202F || codepoint == 0x205F
1001 || ( codepoint >= 0x2000 && codepoint <= 0x200A )
1002 #ifdef HAVE_FRIBIDI
1003 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_WS
1004 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_CS
1005 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_SS
1006 #endif
1009 i_glyph_index = 3;
1011 /* Skip carriage returns */
1012 else if( codepoint == 0x0D
1013 #ifdef HAVE_FRIBIDI
1014 || p_paragraph->p_types[ j ] == FRIBIDI_TYPE_BS
1015 #endif
1017 SKIP_GLYPH( p_bitmaps )
1020 if( FT_Load_Glyph( p_face, i_glyph_index,
1021 FT_LOAD_NO_BITMAP | FT_LOAD_DEFAULT )
1022 && FT_Load_Glyph( p_face, i_glyph_index, FT_LOAD_DEFAULT ) )
1023 SKIP_GLYPH( p_bitmaps )
1025 if( ( p_style->i_style_flags & STYLE_BOLD )
1026 && !( p_face->style_flags & FT_STYLE_FLAG_BOLD ) )
1027 FT_GlyphSlot_Embolden( p_face->glyph );
1028 if( ( p_style->i_style_flags & STYLE_ITALIC )
1029 && !( p_face->style_flags & FT_STYLE_FLAG_ITALIC ) )
1030 FT_GlyphSlot_Oblique( p_face->glyph );
1032 if( FT_Get_Glyph( p_face->glyph, &p_bitmaps->p_glyph ) )
1033 SKIP_GLYPH( p_bitmaps )
1035 #undef SKIP_GLYPH
1037 if( p_filter->p_sys->p_stroker && (p_style->i_style_flags & STYLE_OUTLINE) )
1039 p_bitmaps->p_outline = p_bitmaps->p_glyph;
1040 if( FT_Glyph_StrokeBorder( &p_bitmaps->p_outline,
1041 p_filter->p_sys->p_stroker, 0, 0 ) )
1042 p_bitmaps->p_outline = 0;
1045 if( p_style->i_shadow_alpha != STYLE_ALPHA_TRANSPARENT )
1046 p_bitmaps->p_shadow = p_bitmaps->p_outline ?
1047 p_bitmaps->p_outline : p_bitmaps->p_glyph;
1049 if( b_overwrite_advance )
1051 p_bitmaps->i_x_advance = p_face->glyph->advance.x;
1052 p_bitmaps->i_y_advance = p_face->glyph->advance.y;
1056 int i_max_run_advance_x = FT_FLOOR( FT_MulFix( p_face->max_advance_width, p_face->size->metrics.x_scale ) );
1057 if( i_max_run_advance_x > *pi_max_advance_x )
1058 *pi_max_advance_x = i_max_run_advance_x;
1060 return VLC_SUCCESS;
1063 static int LayoutLine( filter_t *p_filter,
1064 paragraph_t *p_paragraph,
1065 int i_first_char, int i_last_char,
1066 line_desc_t **pp_line, bool b_grid )
1068 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0
1069 || i_first_char < 0 || i_last_char < 0
1070 || i_first_char > i_last_char
1071 || i_last_char >= p_paragraph->i_size )
1073 msg_Err( p_filter,
1074 "LayoutLine() invalid parameters. "
1075 "Paragraph size: %d. Runs count: %d. "
1076 "Start char: %d. End char: %d",
1077 p_paragraph->i_size, p_paragraph->i_runs_count,
1078 i_first_char, i_last_char );
1079 return VLC_EGENERIC;
1082 line_desc_t *p_line = NewLine( 1 + i_last_char - i_first_char );
1084 if( !p_line )
1085 return VLC_ENOMEM;
1087 filter_sys_t *p_sys = p_filter->p_sys;
1088 int i_last_run = -1;
1089 run_desc_t *p_run = 0;
1090 const text_style_t *p_style = 0;
1091 FT_Face p_face = 0;
1092 FT_Vector pen = { .x = 0, .y = 0 };
1093 int i_line_index = 0;
1095 int i_font_size = 0;
1096 int i_font_width = 0;
1097 int i_font_max_advance_y = 0;
1098 int i_ul_offset = 0;
1099 int i_ul_thickness = 0;
1101 #ifdef HAVE_FRIBIDI
1102 fribidi_reorder_line( 0, &p_paragraph->p_types[i_first_char],
1103 1 + i_last_char - i_first_char,
1104 0, p_paragraph->paragraph_type,
1105 &p_paragraph->p_levels[i_first_char],
1106 0, &p_paragraph->pi_reordered_indices[i_first_char] );
1107 #endif
1109 for( int i = i_first_char; i <= i_last_char; ++i, ++i_line_index )
1111 int i_paragraph_index;
1112 #ifdef HAVE_FRIBIDI
1113 i_paragraph_index = p_paragraph->pi_reordered_indices[ i ];
1114 #else
1115 i_paragraph_index = i;
1116 #endif
1118 line_character_t *p_ch = p_line->p_character + i_line_index;
1119 p_ch->p_style = p_paragraph->pp_styles[ i_paragraph_index ];
1121 glyph_bitmaps_t *p_bitmaps =
1122 p_paragraph->p_glyph_bitmaps + i_paragraph_index;
1124 if( !p_bitmaps->p_glyph )
1126 --i_line_index;
1127 continue;
1130 if( i_last_run != p_paragraph->pi_run_ids[ i_paragraph_index ] )
1132 i_last_run = p_paragraph->pi_run_ids[ i_paragraph_index ];
1133 p_run = p_paragraph->p_runs + i_last_run;
1134 p_style = p_run->p_style;
1135 p_face = p_run->p_face;
1137 i_font_width = i_font_size = ConvertToLiveSize( p_filter, p_style );
1138 if( p_style->i_style_flags & STYLE_HALFWIDTH )
1139 i_font_width /= 2;
1140 else if( p_style->i_style_flags & STYLE_DOUBLEWIDTH )
1141 i_font_width *= 2;
1144 FT_Vector pen_new = {
1145 .x = pen.x + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_x_offset,
1146 .y = pen.y + p_paragraph->p_glyph_bitmaps[ i_paragraph_index ].i_y_offset
1148 FT_Vector pen_shadow = {
1149 .x = pen_new.x + p_sys->f_shadow_vector_x * ( i_font_width << 6 ),
1150 .y = pen_new.y + p_sys->f_shadow_vector_y * ( i_font_size << 6 )
1153 if( p_bitmaps->p_shadow )
1155 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_shadow, FT_RENDER_MODE_NORMAL,
1156 &pen_shadow, 0 ) )
1157 p_bitmaps->p_shadow = 0;
1158 else
1159 FT_Glyph_Get_CBox( p_bitmaps->p_shadow, ft_glyph_bbox_pixels,
1160 &p_bitmaps->shadow_bbox );
1162 if( p_bitmaps->p_glyph )
1164 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_glyph, FT_RENDER_MODE_NORMAL,
1165 &pen_new, 1 ) )
1167 FT_Done_Glyph( p_bitmaps->p_glyph );
1168 if( p_bitmaps->p_outline )
1169 FT_Done_Glyph( p_bitmaps->p_outline );
1170 if( p_bitmaps->p_shadow )
1171 FT_Done_Glyph( p_bitmaps->p_shadow );
1172 --i_line_index;
1173 continue;
1175 else
1176 FT_Glyph_Get_CBox( p_bitmaps->p_glyph, ft_glyph_bbox_pixels,
1177 &p_bitmaps->glyph_bbox );
1179 if( p_bitmaps->p_outline )
1181 if( FT_Glyph_To_Bitmap( &p_bitmaps->p_outline, FT_RENDER_MODE_NORMAL,
1182 &pen_new, 1 ) )
1184 FT_Done_Glyph( p_bitmaps->p_outline );
1185 p_bitmaps->p_outline = 0;
1187 else
1188 FT_Glyph_Get_CBox( p_bitmaps->p_outline, ft_glyph_bbox_pixels,
1189 &p_bitmaps->outline_bbox );
1192 FixGlyph( p_bitmaps->p_glyph, &p_bitmaps->glyph_bbox,
1193 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1194 &pen_new );
1195 if( p_bitmaps->p_outline )
1196 FixGlyph( p_bitmaps->p_outline, &p_bitmaps->outline_bbox,
1197 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1198 &pen_new );
1199 if( p_bitmaps->p_shadow )
1200 FixGlyph( p_bitmaps->p_shadow, &p_bitmaps->shadow_bbox,
1201 p_bitmaps->i_x_advance, p_bitmaps->i_y_advance,
1202 &pen_shadow );
1204 int i_line_offset = 0;
1205 int i_line_thickness = 0;
1207 if( p_ch->p_style->i_style_flags & (STYLE_UNDERLINE | STYLE_STRIKEOUT) )
1209 i_line_offset =
1210 abs( FT_FLOOR( FT_MulFix( p_face->underline_position,
1211 p_face->size->metrics.y_scale ) ) );
1213 i_line_thickness =
1214 abs( FT_CEIL( FT_MulFix( p_face->underline_thickness,
1215 p_face->size->metrics.y_scale ) ) );
1217 if( p_ch->p_style->i_style_flags & STYLE_STRIKEOUT )
1219 /* Move the baseline to make it strikethrough instead of
1220 * underline. That means that strikethrough takes precedence
1222 i_line_offset -=
1223 abs( FT_FLOOR( FT_MulFix( p_face->descender * 2,
1224 p_face->size->metrics.y_scale ) ) );
1225 p_bitmaps->glyph_bbox.yMax =
1226 __MAX( p_bitmaps->glyph_bbox.yMax,
1227 - i_line_offset );
1228 p_bitmaps->glyph_bbox.yMin =
1229 __MIN( p_bitmaps->glyph_bbox.yMin,
1230 i_line_offset - i_line_thickness );
1232 else if( i_line_thickness > 0 )
1234 p_bitmaps->glyph_bbox.yMin =
1235 __MIN( p_bitmaps->glyph_bbox.yMin,
1236 - i_line_offset - i_line_thickness );
1238 /* The real underline thickness and position are
1239 * updated once the whole line has been parsed */
1240 i_ul_offset = __MAX( i_ul_offset, i_line_offset );
1241 i_ul_thickness = __MAX( i_ul_thickness, i_line_thickness );
1242 i_line_thickness = -1;
1246 p_ch->p_glyph = ( FT_BitmapGlyph ) p_bitmaps->p_glyph;
1247 p_ch->p_outline = ( FT_BitmapGlyph ) p_bitmaps->p_outline;
1248 p_ch->p_shadow = ( FT_BitmapGlyph ) p_bitmaps->p_shadow;
1249 p_ch->b_in_karaoke = (p_paragraph->pi_karaoke_bar[ i_paragraph_index ] != 0);
1251 p_ch->i_line_thickness = i_line_thickness;
1252 p_ch->i_line_offset = i_line_offset;
1254 BBoxEnlarge( &p_line->bbox, &p_bitmaps->glyph_bbox );
1255 if( p_bitmaps->p_outline )
1256 BBoxEnlarge( &p_line->bbox, &p_bitmaps->outline_bbox );
1257 if( p_bitmaps->p_shadow )
1258 BBoxEnlarge( &p_line->bbox, &p_bitmaps->shadow_bbox );
1260 pen.x += p_bitmaps->i_x_advance;
1261 pen.y += p_bitmaps->i_y_advance;
1263 /* Get max advance for grid mode */
1264 if( b_grid && i_font_max_advance_y == 0 && p_face )
1266 i_font_max_advance_y = abs( FT_FLOOR( FT_MulFix( p_face->max_advance_height,
1267 p_face->size->metrics.y_scale ) ) );
1270 /* Keep track of blank/spaces in front/end of line */
1271 if( p_ch->p_glyph->bitmap.rows )
1273 if( p_line->i_first_visible_char_index < 0 )
1274 p_line->i_first_visible_char_index = i_line_index;
1275 p_line->i_last_visible_char_index = i_line_index;
1279 p_line->i_width = __MAX( 0, p_line->bbox.xMax - p_line->bbox.xMin );
1281 if( b_grid )
1282 p_line->i_height = i_font_max_advance_y;
1283 else
1284 p_line->i_height = __MAX( 0, p_line->bbox.yMax - p_line->bbox.yMin );
1286 p_line->i_character_count = i_line_index;
1288 if( i_ul_thickness > 0 )
1290 for( int i = 0; i < p_line->i_character_count; i++ )
1292 line_character_t *ch = &p_line->p_character[i];
1293 if( ch->i_line_thickness < 0 )
1295 ch->i_line_offset = i_ul_offset;
1296 ch->i_line_thickness = i_ul_thickness;
1301 *pp_line = p_line;
1302 return VLC_SUCCESS;
1305 static inline void ReleaseGlyphBitMaps(glyph_bitmaps_t *p_bitmaps)
1307 if( p_bitmaps->p_glyph )
1308 FT_Done_Glyph( p_bitmaps->p_glyph );
1309 if( p_bitmaps->p_outline )
1310 FT_Done_Glyph( p_bitmaps->p_outline );
1313 static inline bool IsWhitespaceAt( paragraph_t *p_paragraph, size_t i )
1315 return ( p_paragraph->p_code_points[ i ] == ' '
1316 #ifdef HAVE_FRIBIDI
1317 || p_paragraph->p_types[ i ] == FRIBIDI_TYPE_WS
1318 #endif
1322 static int LayoutParagraph( filter_t *p_filter, paragraph_t *p_paragraph,
1323 unsigned i_max_width, unsigned i_max_advance_x,
1324 line_desc_t **pp_lines, bool b_grid, bool b_balance )
1326 if( p_paragraph->i_size <= 0 || p_paragraph->i_runs_count <= 0 )
1328 msg_Err( p_filter, "LayoutParagraph() invalid parameters. "
1329 "Paragraph size: %d. Runs count %d",
1330 p_paragraph->i_size, p_paragraph->i_runs_count );
1331 return VLC_EGENERIC;
1335 * Check max line width to allow for outline and shadow glyphs,
1336 * and any extra width caused by visual reordering
1338 if( i_max_width <= i_max_advance_x )
1340 msg_Err( p_filter, "LayoutParagraph(): Invalid max width" );
1341 return VLC_EGENERIC;
1344 i_max_width <<= 6;
1345 i_max_advance_x <<= 6;
1347 int i_line_start = 0;
1348 FT_Pos i_width = 0;
1349 FT_Pos i_preferred_width = i_max_width;
1350 FT_Pos i_total_width = 0;
1351 FT_Pos i_last_space_width = 0;
1352 int i_last_space = -1;
1353 line_desc_t *p_first_line = NULL;
1354 line_desc_t **pp_line = &p_first_line;
1356 for( int i = 0; i < p_paragraph->i_size; ++i )
1358 #ifdef HAVE_FRIBIDI
1359 p_paragraph->pi_reordered_indices[ i ] = i;
1360 #endif
1361 if( !IsWhitespaceAt( p_paragraph, i ) || i != i_last_space + 1 )
1362 i_total_width += p_paragraph->p_glyph_bitmaps[ i ].i_x_advance;
1363 else
1364 i_last_space = i;
1366 i_last_space = -1;
1368 if( i_total_width == 0 )
1370 for( int i=0; i < p_paragraph->i_size; ++i )
1371 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1372 return VLC_SUCCESS;
1375 if( b_balance )
1377 int i_line_count = i_total_width / (i_max_width - i_max_advance_x) + 1;
1378 i_preferred_width = i_total_width / i_line_count;
1381 for( int i = 0; i <= p_paragraph->i_size; ++i )
1383 if( i == p_paragraph->i_size )
1385 if( i_line_start < i )
1386 if( LayoutLine( p_filter, p_paragraph,
1387 i_line_start, i - 1, pp_line, b_grid ) )
1388 goto error;
1390 break;
1393 if( IsWhitespaceAt( p_paragraph, i ) )
1395 if( i_line_start == i )
1398 * Free orphaned white space glyphs not belonging to any lines.
1399 * At this point p_shadow points to either p_glyph or p_outline,
1400 * so we should not free it explicitly.
1402 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1403 i_line_start = i + 1;
1404 continue;
1407 if( i_last_space == i - 1 )
1409 p_paragraph->p_glyph_bitmaps[ i - 1 ].i_x_advance = 0;
1410 i_last_space = i;
1411 continue;
1414 i_last_space = i;
1415 i_last_space_width = i_width;
1418 const run_desc_t *p_run = &p_paragraph->p_runs[p_paragraph->pi_run_ids[i]];
1419 const int i_advance_x = p_paragraph->p_glyph_bitmaps[ i ].i_x_advance;
1421 if( ( i_last_space_width + i_advance_x > i_preferred_width &&
1422 p_run->p_style->e_wrapinfo == STYLE_WRAP_DEFAULT )
1423 || i_width + i_advance_x > i_max_width )
1425 if( i_line_start == i )
1427 /* If wrapping, algorithm would not end shifting lines down.
1428 * Not wrapping, that can't be rendered anymore. */
1429 msg_Dbg( p_filter, "LayoutParagraph(): First glyph width in line exceeds maximum, skipping" );
1430 for( ; i < p_paragraph->i_size; ++i )
1431 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1432 return VLC_SUCCESS;
1435 int i_newline_start;
1436 if( i_last_space > i_line_start && p_run->p_style->e_wrapinfo == STYLE_WRAP_DEFAULT )
1437 i_newline_start = i_last_space; /* we break line on last space */
1438 else
1439 i_newline_start = i; /* we break line on last char */
1441 if( LayoutLine( p_filter, p_paragraph, i_line_start,
1442 i_newline_start - 1, pp_line, b_grid ) )
1443 goto error;
1445 /* Handle early end of renderable content;
1446 We're over size and we can't break space */
1447 if( p_run->p_style->e_wrapinfo == STYLE_WRAP_NONE )
1449 for( ; i < p_paragraph->i_size; ++i )
1450 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1451 break;
1454 pp_line = &( *pp_line )->p_next;
1456 /* If we created a line up to previous space, we only keep the difference for
1457 our current width since that split */
1458 if( i_newline_start == i_last_space )
1460 i_width = i_width - i_last_space_width;
1461 if( i_newline_start + 1 < p_paragraph->i_size )
1462 i_line_start = i_newline_start + 1;
1463 else
1464 i_line_start = i_newline_start; // == i
1466 else
1468 i_width = 0;
1469 i_line_start = i_newline_start;
1471 i_last_space_width = 0;
1473 i_width += i_advance_x;
1476 *pp_lines = p_first_line;
1477 return VLC_SUCCESS;
1479 error:
1480 for( int i = i_line_start; i < p_paragraph->i_size; ++i )
1481 ReleaseGlyphBitMaps( &p_paragraph->p_glyph_bitmaps[ i ] );
1482 if( p_first_line )
1483 FreeLines( p_first_line );
1484 return VLC_EGENERIC;
1487 int LayoutText( filter_t *p_filter,
1488 const uni_char_t *psz_text, text_style_t **pp_styles,
1489 uint32_t *pi_k_dates, int i_len,
1490 bool b_grid, bool b_balance,
1491 unsigned i_max_width, unsigned i_max_height,
1492 line_desc_t **pp_lines, FT_BBox *p_bbox, int *pi_max_face_height )
1494 line_desc_t *p_first_line = 0;
1495 line_desc_t **pp_line = &p_first_line;
1496 paragraph_t *p_paragraph = 0;
1497 int i_paragraph_start = 0;
1498 unsigned i_total_height = 0;
1499 unsigned i_max_advance_x = 0;
1500 int i_max_face_height = 0;
1502 for( int i = 0; i <= i_len; ++i )
1504 if( i == i_len || psz_text[ i ] == '\n' )
1506 if( i_paragraph_start == i )
1508 i_paragraph_start = i + 1;
1509 continue;
1512 p_paragraph = NewParagraph( p_filter, i - i_paragraph_start,
1513 psz_text + i_paragraph_start,
1514 pp_styles + i_paragraph_start,
1515 pi_k_dates ?
1516 pi_k_dates + i_paragraph_start : 0,
1517 20 );
1518 if( !p_paragraph )
1520 if( p_first_line ) FreeLines( p_first_line );
1521 return VLC_ENOMEM;
1524 #ifdef HAVE_FRIBIDI
1525 if( AnalyzeParagraph( p_paragraph ) )
1526 goto error;
1527 #endif
1529 if( ItemizeParagraph( p_filter, p_paragraph ) )
1530 goto error;
1532 #if defined HAVE_HARFBUZZ
1533 if( ShapeParagraphHarfBuzz( p_filter, &p_paragraph ) )
1534 goto error;
1536 if( LoadGlyphs( p_filter, p_paragraph, true, false, &i_max_advance_x ) )
1537 goto error;
1539 #elif defined HAVE_FRIBIDI
1540 if( ShapeParagraphFriBidi( p_filter, p_paragraph ) )
1541 goto error;
1542 if( LoadGlyphs( p_filter, p_paragraph, false, true, &i_max_advance_x ) )
1543 goto error;
1544 if( RemoveZeroWidthCharacters( p_paragraph ) )
1545 goto error;
1546 if( ZeroNsmAdvance( p_paragraph ) )
1547 goto error;
1548 #else
1549 if( LoadGlyphs( p_filter, p_paragraph, false, true, &i_max_advance_x ) )
1550 goto error;
1551 #endif
1553 if( LayoutParagraph( p_filter, p_paragraph,
1554 i_max_width, i_max_advance_x, pp_line,
1555 b_grid, b_balance ) )
1556 goto error;
1558 FreeParagraph( p_paragraph );
1559 p_paragraph = 0;
1561 for( ; *pp_line; pp_line = &(*pp_line)->p_next )
1563 i_total_height += (*pp_line)->i_height;
1564 if( i_max_height > 0 && i_total_height > i_max_height )
1566 i_total_height = i_max_height + 1;
1567 line_desc_t *p_todelete = *pp_line;
1568 while( p_todelete ) /* Drop extra lines */
1570 line_desc_t *p_next = p_todelete->p_next;
1571 FreeLine( p_todelete );
1572 p_todelete = p_next;
1574 *pp_line = NULL;
1575 i = i_len + 1; /* force no more paragraphs */
1576 break; /* ! no p_next ! */
1578 else if( (*pp_line)->i_height > i_max_face_height )
1580 i_max_face_height = (*pp_line)->i_height;
1583 i_paragraph_start = i + 1;
1587 int i_base_line = 0;
1588 FT_BBox bbox = {
1589 .xMin = INT_MAX,
1590 .yMin = INT_MAX,
1591 .xMax = INT_MIN,
1592 .yMax = INT_MIN
1595 for( line_desc_t *p_line = p_first_line; p_line; p_line = p_line->p_next )
1597 p_line->i_base_line = i_base_line;
1598 p_line->bbox.yMin -= i_base_line;
1599 p_line->bbox.yMax -= i_base_line;
1600 BBoxEnlarge( &bbox, &p_line->bbox );
1602 i_base_line += i_max_face_height;
1605 *pi_max_face_height = i_max_face_height;
1606 *pp_lines = p_first_line;
1607 *p_bbox = bbox;
1608 return VLC_SUCCESS;
1610 error:
1611 if( p_first_line ) FreeLines( p_first_line );
1612 if( p_paragraph ) FreeParagraph( p_paragraph );
1613 return VLC_EGENERIC;