Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
vlc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Redmine
Redmine
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Metrics
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
videolan
vlc
Commits
57cb3e53
Commit
57cb3e53
authored
Apr 17, 2015
by
Salah-Eddin Shaban
Committed by
Jean-Baptiste Kempf
May 01, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
freetype: use HarfBuzz for text shaping
Signed-off-by:
Jean-Baptiste Kempf
<
jb@videolan.org
>
parent
063cba9f
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1559 additions
and
749 deletions
+1559
-749
configure.ac
configure.ac
+16
-1
modules/text_renderer/Makefile.am
modules/text_renderer/Makefile.am
+6
-1
modules/text_renderer/freetype.c
modules/text_renderer/freetype.c
+137
-747
modules/text_renderer/freetype.h
modules/text_renderer/freetype.h
+72
-0
modules/text_renderer/text_layout.c
modules/text_renderer/text_layout.c
+1273
-0
modules/text_renderer/text_layout.h
modules/text_renderer/text_layout.h
+55
-0
No files found.
configure.ac
View file @
57cb3e53
...
...
@@ -3102,6 +3102,8 @@ AC_ARG_ENABLE(freetype,
[ --enable-freetype freetype support (default auto)])
AC_ARG_ENABLE(fribidi,
[ --enable-fribidi fribidi support (default auto)])
AC_ARG_ENABLE(harfbuzz,
[ --enable-harfbuzz harfbuzz support (default auto)])
AC_ARG_ENABLE(fontconfig,
[ --enable-fontconfig fontconfig support (default auto)])
...
...
@@ -3130,6 +3132,7 @@ AC_ARG_WITH([default-monospace-font-family],
have_freetype="no"
have_fontconfig="no"
have_fribidi="no"
have_harfbuzz="no"
if test "${enable_freetype}" != "no"; then
PKG_CHECK_MODULES(FREETYPE, freetype2, [
...
...
@@ -3152,7 +3155,18 @@ if test "${enable_freetype}" != "no"; then
have_fribidi="yes"
VLC_ADD_CPPFLAGS([skins2], [${FRIBIDI_CFLAGS} -DHAVE_FRIBIDI])
VLC_ADD_LIBS([skins2], [${FRIBIDI_LIBS}])
],[AC_MSG_WARN([${FRIBIDI_PKG_ERRORS}. Bidirectional support will be disabled in FreeType.])])
],[AC_MSG_WARN([${FRIBIDI_PKG_ERRORS}. Bidirectional text and complex scripts (Arabic, Farsi, Thai...) will be disabled in FreeType.])])
fi
dnl harfbuzz support
if test "${have_fribidi}" != "no"; then
if test "${enable_harfbuzz}" != "no"; then
PKG_CHECK_MODULES(HARFBUZZ, harfbuzz, [
have_harfbuzz="yes"
VLC_ADD_CPPFLAGS([skins2], [${HARFBUZZ_CFLAGS} -DHAVE_HARFBUZZ])
VLC_ADD_LIBS([skins2], [${HARFBUZZ_LIBS}])
],[AC_MSG_WARN([${HARFBUZZ_PKG_ERRORS}. Support for complex scripts (Arabic, Farsi, Thai...) will be disabled in FreeType.])])
fi
fi
],[
AS_IF([test -n "${enable_freetype}"],[
...
...
@@ -3164,6 +3178,7 @@ fi
AM_CONDITIONAL([HAVE_FREETYPE], [test "${have_freetype}" = "yes"])
AM_CONDITIONAL([HAVE_FONTCONFIG], [test "${have_fontconfig}" = "yes"])
AM_CONDITIONAL([HAVE_FRIBIDI], [test "${have_fribidi}" = "yes"])
AM_CONDITIONAL([HAVE_HARFBUZZ], [test "${have_harfbuzz}" = "yes"])
dnl
...
...
modules/text_renderer/Makefile.am
View file @
57cb3e53
...
...
@@ -6,7 +6,8 @@ text_LTLIBRARIES = libtdummy_plugin.la
libfreetype_plugin_la_SOURCES
=
\
text_renderer/text_renderer.c text_renderer/text_renderer.h
\
text_renderer/platform_fonts.c text_renderer/platform_fonts.h
\
text_renderer/freetype.c
text_renderer/freetype.c text_renderer/freetype.h
\
text_renderer/text_layout.c text_renderer/text_layout.h
libfreetype_plugin_la_CPPFLAGS
=
$(AM_CPPFLAGS)
$(FREETYPE_CFLAGS)
libfreetype_plugin_la_LIBADD
=
$(LIBM)
$(FREETYPE_LIBS)
if
HAVE_FREETYPE
...
...
@@ -20,6 +21,10 @@ if HAVE_FRIBIDI
libfreetype_plugin_la_CPPFLAGS
+=
$(FRIBIDI_CFLAGS)
-DHAVE_FRIBIDI
libfreetype_plugin_la_LIBADD
+=
$(FRIBIDI_LIBS)
endif
if
HAVE_HARFBUZZ
libfreetype_plugin_la_CPPFLAGS
+=
$(HARFBUZZ_CFLAGS)
-DHAVE_HARFBUZZ
libfreetype_plugin_la_LIBADD
+=
$(HARFBUZZ_LIBS)
endif
libfreetype_plugin_la_LDFLAGS
=
$(AM_LDFLAGS)
$(FREETYPE_LDFLAGS)
-rpath
'
$(textdir)
'
if
HAVE_DARWIN
libfreetype_plugin_la_LDFLAGS
+=
-Wl
,-framework,Carbon
...
...
modules/text_renderer/freetype.c
View file @
57cb3e53
...
...
@@ -48,18 +48,6 @@
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_STROKER_H
#include FT_SYNTHESIS_H
#define FT_FLOOR(X) ((X & -64) >> 6)
#define FT_CEIL(X) (((X + 63) & -64) >> 6)
#ifndef FT_MulFix
#define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
/* RTL */
#if defined(HAVE_FRIBIDI)
# include <fribidi/fribidi.h>
#endif
/* apple stuff */
#ifdef __APPLE__
...
...
@@ -85,6 +73,8 @@
#include "text_renderer.h"
#include "platform_fonts.h"
#include "freetype.h"
#include "text_layout.h"
/*****************************************************************************
* Module descriptor
...
...
@@ -130,6 +120,9 @@ static void Destroy( vlc_object_t * );
#define SHADOW_ANGLE_TEXT N_("Shadow angle")
#define SHADOW_DISTANCE_TEXT N_("Shadow distance")
#define TEXT_DIRECTION_TEXT N_("Text direction")
#define TEXT_DIRECTION_LONGTEXT N_("Paragraph base direction for the Unicode bi-directional algorithm.")
static
const
int
pi_sizes
[]
=
{
20
,
18
,
16
,
12
,
6
};
static
const
char
*
const
ppsz_sizes_text
[]
=
{
...
...
@@ -155,6 +148,15 @@ static const char *const ppsz_outline_thickness[] = {
N_
(
"None"
),
N_
(
"Thin"
),
N_
(
"Normal"
),
N_
(
"Thick"
),
};
#ifdef HAVE_FRIBIDI
static
const
int
pi_text_direction
[]
=
{
0
,
1
,
2
,
};
static
const
char
*
const
ppsz_text_direction
[]
=
{
N_
(
"Left to right"
),
N_
(
"Right to left"
),
N_
(
"Auto"
),
};
#endif
vlc_module_begin
()
set_shortname
(
N_
(
"Text renderer"
))
set_description
(
N_
(
"Freetype2 font renderer"
)
)
...
...
@@ -231,69 +233,19 @@ vlc_module_begin ()
add_bool
(
"freetype-yuvp"
,
false
,
YUVP_TEXT
,
YUVP_LONGTEXT
,
true
)
#ifdef HAVE_FRIBIDI
add_integer_with_range
(
"freetype-text-direction"
,
0
,
0
,
2
,
TEXT_DIRECTION_TEXT
,
TEXT_DIRECTION_LONGTEXT
,
false
)
change_integer_list
(
pi_text_direction
,
ppsz_text_direction
)
change_safe
()
#endif
set_capability
(
"text renderer"
,
100
)
add_shortcut
(
"text"
)
set_callbacks
(
Create
,
Destroy
)
vlc_module_end
()
/*****************************************************************************
* Local prototypes
*****************************************************************************/
typedef
struct
{
FT_BitmapGlyph
p_glyph
;
FT_BitmapGlyph
p_outline
;
FT_BitmapGlyph
p_shadow
;
uint32_t
i_color
;
/* ARGB color */
int
i_line_offset
;
/* underline/strikethrough offset */
int
i_line_thickness
;
/* underline/strikethrough thickness */
}
line_character_t
;
typedef
struct
line_desc_t
line_desc_t
;
struct
line_desc_t
{
line_desc_t
*
p_next
;
int
i_width
;
int
i_height
;
int
i_base_line
;
int
i_character_count
;
line_character_t
*
p_character
;
};
/*****************************************************************************
* filter_sys_t: freetype local data
*****************************************************************************
* This structure is part of the video output thread descriptor.
* It describes the freetype specific properties of an output thread.
*****************************************************************************/
struct
filter_sys_t
{
FT_Library
p_library
;
/* handle to library */
FT_Face
p_face
;
/* handle to face object */
FT_Stroker
p_stroker
;
/* handle to path stroker object */
xml_reader_t
*
p_xml
;
/* vlc xml parser */
text_style_t
style
;
/* Current Style */
/* More styles... */
float
f_shadow_vector_x
;
float
f_shadow_vector_y
;
int
i_default_font_size
;
/* Attachments */
input_attachment_t
**
pp_font_attachments
;
int
i_font_attachments
;
char
*
(
*
pf_select
)
(
filter_t
*
,
const
char
*
family
,
bool
bold
,
bool
italic
,
int
size
,
int
*
index
);
};
/* */
static
void
YUVFromRGB
(
uint32_t
i_argb
,
uint8_t
*
pi_y
,
uint8_t
*
pi_u
,
uint8_t
*
pi_v
)
...
...
@@ -908,52 +860,6 @@ static inline int RenderAXYZ( filter_t *p_filter,
static
void
FreeLine
(
line_desc_t
*
p_line
)
{
for
(
int
i
=
0
;
i
<
p_line
->
i_character_count
;
i
++
)
{
line_character_t
*
ch
=
&
p_line
->
p_character
[
i
];
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_glyph
);
if
(
ch
->
p_outline
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_outline
);
if
(
ch
->
p_shadow
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_shadow
);
}
free
(
p_line
->
p_character
);
free
(
p_line
);
}
static
void
FreeLines
(
line_desc_t
*
p_lines
)
{
for
(
line_desc_t
*
p_line
=
p_lines
;
p_line
!=
NULL
;
)
{
line_desc_t
*
p_next
=
p_line
->
p_next
;
FreeLine
(
p_line
);
p_line
=
p_next
;
}
}
static
line_desc_t
*
NewLine
(
int
i_count
)
{
line_desc_t
*
p_line
=
malloc
(
sizeof
(
*
p_line
)
);
if
(
!
p_line
)
return
NULL
;
p_line
->
p_next
=
NULL
;
p_line
->
i_width
=
0
;
p_line
->
i_base_line
=
0
;
p_line
->
i_character_count
=
0
;
p_line
->
p_character
=
calloc
(
i_count
,
sizeof
(
*
p_line
->
p_character
)
);
if
(
!
p_line
->
p_character
)
{
free
(
p_line
);
return
NULL
;
}
return
p_line
;
}
static
FT_Face
LoadEmbeddedFace
(
filter_sys_t
*
p_sys
,
const
text_style_t
*
p_style
)
{
...
...
@@ -987,634 +893,6 @@ static FT_Face LoadEmbeddedFace( filter_sys_t *p_sys, const text_style_t *p_styl
return
NULL
;
}
static
FT_Face
LoadFace
(
filter_t
*
p_filter
,
const
text_style_t
*
p_style
)
{
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
/* Look for a match amongst our attachments first */
FT_Face
p_face
=
LoadEmbeddedFace
(
p_sys
,
p_style
);
/* Load system wide font otheriwse */
if
(
!
p_face
)
{
int
i_idx
=
0
;
char
*
psz_fontfile
=
NULL
;
if
(
p_sys
->
pf_select
)
psz_fontfile
=
p_sys
->
pf_select
(
p_filter
,
p_style
->
psz_fontname
,
(
p_style
->
i_style_flags
&
STYLE_BOLD
)
!=
0
,
(
p_style
->
i_style_flags
&
STYLE_ITALIC
)
!=
0
,
-
1
,
&
i_idx
);
else
psz_fontfile
=
NULL
;
if
(
!
psz_fontfile
)
return
NULL
;
if
(
*
psz_fontfile
==
'\0'
)
{
msg_Warn
(
p_filter
,
"We were not able to find a matching font:
\"
%s
\"
(%s %s),"
" so using default font"
,
p_style
->
psz_fontname
,
(
p_style
->
i_style_flags
&
STYLE_BOLD
)
?
"Bold"
:
""
,
(
p_style
->
i_style_flags
&
STYLE_ITALIC
)
?
"Italic"
:
""
);
p_face
=
NULL
;
}
else
{
if
(
FT_New_Face
(
p_sys
->
p_library
,
psz_fontfile
,
i_idx
,
&
p_face
)
)
p_face
=
NULL
;
}
free
(
psz_fontfile
);
}
if
(
!
p_face
)
return
NULL
;
if
(
FT_Select_Charmap
(
p_face
,
ft_encoding_unicode
)
)
{
/* We've loaded a font face which is unhelpful for actually
* rendering text - fallback to the default one.
*/
FT_Done_Face
(
p_face
);
return
NULL
;
}
return
p_face
;
}
static
int
GetGlyph
(
filter_t
*
p_filter
,
FT_Glyph
*
pp_glyph
,
FT_BBox
*
p_glyph_bbox
,
FT_Glyph
*
pp_outline
,
FT_BBox
*
p_outline_bbox
,
FT_Glyph
*
pp_shadow
,
FT_BBox
*
p_shadow_bbox
,
FT_Face
p_face
,
int
i_glyph_index
,
int
i_style_flags
,
FT_Vector
*
p_pen
,
FT_Vector
*
p_pen_shadow
)
{
if
(
FT_Load_Glyph
(
p_face
,
i_glyph_index
,
FT_LOAD_NO_BITMAP
|
FT_LOAD_DEFAULT
)
&&
FT_Load_Glyph
(
p_face
,
i_glyph_index
,
FT_LOAD_DEFAULT
)
)
{
msg_Err
(
p_filter
,
"unable to render text FT_Load_Glyph failed"
);
return
VLC_EGENERIC
;
}
/* Do synthetic styling now that Freetype supports it;
* ie. if the font we have loaded is NOT already in the
* style that the tags want, then switch it on; if they
* are then don't. */
if
((
i_style_flags
&
STYLE_BOLD
)
&&
!
(
p_face
->
style_flags
&
FT_STYLE_FLAG_BOLD
))
FT_GlyphSlot_Embolden
(
p_face
->
glyph
);
if
((
i_style_flags
&
STYLE_ITALIC
)
&&
!
(
p_face
->
style_flags
&
FT_STYLE_FLAG_ITALIC
))
FT_GlyphSlot_Oblique
(
p_face
->
glyph
);
FT_Glyph
glyph
;
if
(
FT_Get_Glyph
(
p_face
->
glyph
,
&
glyph
)
)
{
msg_Err
(
p_filter
,
"unable to render text FT_Get_Glyph failed"
);
return
VLC_EGENERIC
;
}
FT_Glyph
outline
=
NULL
;
if
(
p_filter
->
p_sys
->
p_stroker
)
{
outline
=
glyph
;
if
(
FT_Glyph_StrokeBorder
(
&
outline
,
p_filter
->
p_sys
->
p_stroker
,
0
,
0
)
)
outline
=
NULL
;
}
FT_Glyph
shadow
=
NULL
;
if
(
p_filter
->
p_sys
->
style
.
i_shadow_alpha
>
0
)
{
shadow
=
outline
?
outline
:
glyph
;
if
(
FT_Glyph_To_Bitmap
(
&
shadow
,
FT_RENDER_MODE_NORMAL
,
p_pen_shadow
,
0
)
)
{
shadow
=
NULL
;
}
else
{
FT_Glyph_Get_CBox
(
shadow
,
ft_glyph_bbox_pixels
,
p_shadow_bbox
);
}
}
*
pp_shadow
=
shadow
;
if
(
FT_Glyph_To_Bitmap
(
&
glyph
,
FT_RENDER_MODE_NORMAL
,
p_pen
,
1
)
)
{
FT_Done_Glyph
(
glyph
);
if
(
outline
)
FT_Done_Glyph
(
outline
);
if
(
shadow
)
FT_Done_Glyph
(
shadow
);
return
VLC_EGENERIC
;
}
FT_Glyph_Get_CBox
(
glyph
,
ft_glyph_bbox_pixels
,
p_glyph_bbox
);
*
pp_glyph
=
glyph
;
if
(
outline
)
{
FT_Glyph_To_Bitmap
(
&
outline
,
FT_RENDER_MODE_NORMAL
,
p_pen
,
1
);
FT_Glyph_Get_CBox
(
outline
,
ft_glyph_bbox_pixels
,
p_outline_bbox
);
}
*
pp_outline
=
outline
;
return
VLC_SUCCESS
;
}
static
void
FixGlyph
(
FT_Glyph
glyph
,
FT_BBox
*
p_bbox
,
FT_Face
face
,
const
FT_Vector
*
p_pen
)
{
FT_BitmapGlyph
glyph_bmp
=
(
FT_BitmapGlyph
)
glyph
;
if
(
p_bbox
->
xMin
>=
p_bbox
->
xMax
)
{
p_bbox
->
xMin
=
FT_CEIL
(
p_pen
->
x
);
p_bbox
->
xMax
=
FT_CEIL
(
p_pen
->
x
+
face
->
glyph
->
advance
.
x
);
glyph_bmp
->
left
=
p_bbox
->
xMin
;
}
if
(
p_bbox
->
yMin
>=
p_bbox
->
yMax
)
{
p_bbox
->
yMax
=
FT_CEIL
(
p_pen
->
y
);
p_bbox
->
yMin
=
FT_CEIL
(
p_pen
->
y
+
face
->
glyph
->
advance
.
y
);
glyph_bmp
->
top
=
p_bbox
->
yMax
;
}
}
static
void
BBoxEnlarge
(
FT_BBox
*
p_max
,
const
FT_BBox
*
p
)
{
p_max
->
xMin
=
__MIN
(
p_max
->
xMin
,
p
->
xMin
);
p_max
->
yMin
=
__MIN
(
p_max
->
yMin
,
p
->
yMin
);
p_max
->
xMax
=
__MAX
(
p_max
->
xMax
,
p
->
xMax
);
p_max
->
yMax
=
__MAX
(
p_max
->
yMax
,
p
->
yMax
);
}
static
int
ProcessLines
(
filter_t
*
p_filter
,
line_desc_t
**
pp_lines
,
FT_BBox
*
p_bbox
,
int
*
pi_max_face_height
,
uni_char_t
*
psz_text
,
text_style_t
**
pp_styles
,
uint32_t
*
pi_k_dates
,
int
i_len
)
{
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
uni_char_t
*
p_fribidi_string
=
NULL
;
text_style_t
**
pp_fribidi_styles
=
NULL
;
int
*
p_new_positions
=
NULL
;
#if defined(HAVE_FRIBIDI)
{
int
*
p_old_positions
;
int
start_pos
,
pos
=
0
;
pp_fribidi_styles
=
calloc
(
i_len
,
sizeof
(
*
pp_fribidi_styles
)
);
p_fribidi_string
=
malloc
(
(
i_len
+
1
)
*
sizeof
(
*
p_fribidi_string
)
);
p_old_positions
=
malloc
(
(
i_len
+
1
)
*
sizeof
(
*
p_old_positions
)
);
p_new_positions
=
malloc
(
(
i_len
+
1
)
*
sizeof
(
*
p_new_positions
)
);
if
(
!
pp_fribidi_styles
||
!
p_fribidi_string
||
!
p_old_positions
||
!
p_new_positions
)
{
free
(
p_old_positions
);
free
(
p_new_positions
);
free
(
p_fribidi_string
);
free
(
pp_fribidi_styles
);
return
VLC_ENOMEM
;
}
/* Do bidi conversion line-by-line */
while
(
pos
<
i_len
)
{
while
(
pos
<
i_len
)
{
if
(
psz_text
[
pos
]
!=
'\n'
)
break
;
p_fribidi_string
[
pos
]
=
psz_text
[
pos
];
pp_fribidi_styles
[
pos
]
=
pp_styles
[
pos
];
p_new_positions
[
pos
]
=
pos
;
++
pos
;
}
start_pos
=
pos
;
while
(
pos
<
i_len
)
{
if
(
psz_text
[
pos
]
==
'\n'
)
break
;
++
pos
;
}
if
(
pos
>
start_pos
)
{
#if (FRIBIDI_MINOR_VERSION < 19) && (FRIBIDI_MAJOR_VERSION == 0)
FriBidiCharType
base_dir
=
FRIBIDI_TYPE_LTR
;
#else
FriBidiParType
base_dir
=
FRIBIDI_PAR_LTR
;
#endif
fribidi_log2vis
((
FriBidiChar
*
)
psz_text
+
start_pos
,
pos
-
start_pos
,
&
base_dir
,
(
FriBidiChar
*
)
p_fribidi_string
+
start_pos
,
p_new_positions
+
start_pos
,
p_old_positions
,
NULL
);
for
(
int
j
=
start_pos
;
j
<
pos
;
j
++
)
{
pp_fribidi_styles
[
j
]
=
pp_styles
[
start_pos
+
p_old_positions
[
j
-
start_pos
]
];
p_new_positions
[
j
]
+=
start_pos
;
}
}
}
p_fribidi_string
[
i_len
]
=
0
;
free
(
p_old_positions
);
pp_styles
=
pp_fribidi_styles
;
psz_text
=
p_fribidi_string
;
}
#endif
/* Work out the karaoke */
uint8_t
*
pi_karaoke_bar
=
NULL
;
if
(
pi_k_dates
)
{
pi_karaoke_bar
=
malloc
(
i_len
*
sizeof
(
*
pi_karaoke_bar
));
if
(
pi_karaoke_bar
)
{
int64_t
i_elapsed
=
var_GetTime
(
p_filter
,
"spu-elapsed"
)
/
1000
;
for
(
int
i
=
0
;
i
<
i_len
;
i
++
)
{
unsigned
i_bar
=
p_new_positions
?
p_new_positions
[
i
]
:
i
;
pi_karaoke_bar
[
i_bar
]
=
pi_k_dates
[
i
]
>=
i_elapsed
;
}
}
}
free
(
p_new_positions
);
*
pi_max_face_height
=
0
;
*
pp_lines
=
NULL
;
line_desc_t
**
pp_line_next
=
pp_lines
;
FT_BBox
bbox
=
{
.
xMin
=
INT_MAX
,
.
yMin
=
INT_MAX
,
.
xMax
=
INT_MIN
,
.
yMax
=
INT_MIN
,
};
int
i_face_height_previous
=
0
;
int
i_base_line
=
0
;
const
text_style_t
*
p_previous_style
=
NULL
;
FT_Face
p_face
=
NULL
;
for
(
int
i_start
=
0
;
i_start
<
i_len
;
)
{
/* Compute the length of the current text line */
int
i_length
=
0
;
while
(
i_start
+
i_length
<
i_len
&&
psz_text
[
i_start
+
i_length
]
!=
'\n'
)
i_length
++
;
/* Render the text line (or the begining if too long) into 0 or 1 glyph line */
line_desc_t
*
p_line
=
i_length
>
0
?
NewLine
(
i_length
)
:
NULL
;
int
i_index
=
i_start
;
FT_Vector
pen
=
{
.
x
=
0
,
.
y
=
0
,
};
int
i_face_height
=
0
;
FT_BBox
line_bbox
=
{
.
xMin
=
INT_MAX
,
.
yMin
=
INT_MAX
,
.
xMax
=
INT_MIN
,
.
yMax
=
INT_MIN
,
};
int
i_ul_offset
=
0
;
int
i_ul_thickness
=
0
;
typedef
struct
{
int
i_index
;
FT_Vector
pen
;
FT_BBox
line_bbox
;
int
i_face_height
;
int
i_ul_offset
;
int
i_ul_thickness
;
}
break_point_t
;
break_point_t
break_point
;
break_point_t
break_point_fallback
;
#define SAVE_BP(dst) do { \
dst.i_index = i_index; \
dst.pen = pen; \
dst.line_bbox = line_bbox; \
dst.i_face_height = i_face_height; \
dst.i_ul_offset = i_ul_offset; \
dst.i_ul_thickness = i_ul_thickness; \
} while(0)
SAVE_BP
(
break_point
);
SAVE_BP
(
break_point_fallback
);
while
(
i_index
<
i_start
+
i_length
)
{
/* Split by common FT_Face + Size */
const
text_style_t
*
p_current_style
=
pp_styles
[
i_index
];
int
i_part_length
=
0
;
while
(
i_index
+
i_part_length
<
i_start
+
i_length
)
{
const
text_style_t
*
p_style
=
pp_styles
[
i_index
+
i_part_length
];
if
(
!
FaceStyleEquals
(
p_style
,
p_current_style
)
||
p_style
->
i_font_size
!=
p_current_style
->
i_font_size
)
break
;
i_part_length
++
;
}
/* (Re)load/reconfigure the face if needed */
if
(
!
FaceStyleEquals
(
p_current_style
,
p_previous_style
)
)
{
if
(
p_face
)
FT_Done_Face
(
p_face
);
p_previous_style
=
NULL
;
p_face
=
LoadFace
(
p_filter
,
p_current_style
);
}
FT_Face
p_current_face
=
p_face
?
p_face
:
p_sys
->
p_face
;
if
(
!
p_previous_style
||
p_previous_style
->
i_font_size
!=
p_current_style
->
i_font_size
||
((
p_previous_style
->
i_style_flags
^
p_current_style
->
i_style_flags
)
&
STYLE_HALFWIDTH
)
)
{
int
i_font_width
=
(
p_current_style
->
i_style_flags
&
STYLE_HALFWIDTH
)
?
p_current_style
->
i_font_size
/
2
:
p_current_style
->
i_font_size
;
if
(
FT_Set_Pixel_Sizes
(
p_current_face
,
i_font_width
,
p_current_style
->
i_font_size
)
)
msg_Err
(
p_filter
,
"Failed to set font size to %d"
,
p_current_style
->
i_font_size
);
if
(
p_sys
->
p_stroker
)
{
double
f_outline_thickness
=
var_InheritInteger
(
p_filter
,
"freetype-outline-thickness"
)
/
100
.
0
;
f_outline_thickness
=
VLC_CLIP
(
f_outline_thickness
,
0
.
0
,
0
.
5
);
int
i_radius
=
(
p_current_style
->
i_font_size
<<
6
)
*
f_outline_thickness
;
FT_Stroker_Set
(
p_sys
->
p_stroker
,
i_radius
,
FT_STROKER_LINECAP_ROUND
,
FT_STROKER_LINEJOIN_ROUND
,
0
);
}
}
p_previous_style
=
p_current_style
;
i_face_height
=
__MAX
(
i_face_height
,
FT_CEIL
(
FT_MulFix
(
p_current_face
->
height
,
p_current_face
->
size
->
metrics
.
y_scale
)));
/* Render the part */
bool
b_break_line
=
false
;
int
i_glyph_last
=
0
;
FT_Vector
advance
=
{
.
x
=
0
,
.
y
=
0
,
};
while
(
i_part_length
>
0
)
{
const
text_style_t
*
p_glyph_style
=
pp_styles
[
i_index
];
uni_char_t
character
=
psz_text
[
i_index
];
int
i_glyph_index
=
FT_Get_Char_Index
(
p_current_face
,
character
);
/* If the missing glyph is U+FEFF (ZERO WIDTH NO-BREAK SPACE) */
/* we can safely ignore it. Otherwise extra squares show up */
/* in Arabic text. */
if
(
i_glyph_index
==
0
&&
character
==
0xFEFF
)
goto
next
;
/* These are the most common Arabic diacritics */
#define DIACRITIC( a ) ( a >= 0x064B && a <= 0x0653 )
/* Diacritics should be rendered over the preceding base glyph */
if
(
DIACRITIC
(
character
)
)
{
pen
.
x
-=
advance
.
x
;
pen
.
y
-=
advance
.
y
;
}
/* Get kerning vector */
FT_Vector
kerning
=
{
.
x
=
0
,
.
y
=
0
};
if
(
FT_HAS_KERNING
(
p_current_face
)
&&
i_glyph_last
!=
0
&&
i_glyph_index
!=
0
)
{
FT_Get_Kerning
(
p_current_face
,
i_glyph_last
,
i_glyph_index
,
ft_kerning_default
,
&
kerning
);
}
if
(
p_glyph_style
->
i_spacing
>
0
&&
i_glyph_last
!=
0
&&
i_glyph_index
!=
0
)
{
kerning
.
x
=
(
p_glyph_style
->
i_spacing
)
<<
6
;
}
/* Get the glyph bitmap and its bounding box and all the associated properties */
FT_Vector
pen_new
=
{
.
x
=
pen
.
x
+
kerning
.
x
,
.
y
=
pen
.
y
+
kerning
.
y
,
};
int
i_font_width
=
(
p_current_style
->
i_style_flags
&
STYLE_HALFWIDTH
)
?
p_current_style
->
i_font_size
/
2
:
p_current_style
->
i_font_size
;
FT_Vector
pen_shadow_new
=
{
.
x
=
pen_new
.
x
+
p_sys
->
f_shadow_vector_x
*
(
i_font_width
<<
6
),
.
y
=
pen_new
.
y
+
p_sys
->
f_shadow_vector_y
*
(
p_current_style
->
i_font_size
<<
6
),
};
FT_Glyph
glyph
;
FT_BBox
glyph_bbox
;
FT_Glyph
outline
;
FT_BBox
outline_bbox
;
FT_Glyph
shadow
;
FT_BBox
shadow_bbox
;
if
(
GetGlyph
(
p_filter
,
&
glyph
,
&
glyph_bbox
,
&
outline
,
&
outline_bbox
,
&
shadow
,
&
shadow_bbox
,
p_current_face
,
i_glyph_index
,
p_glyph_style
->
i_style_flags
,
&
pen_new
,
&
pen_shadow_new
)
)
goto
next
;
FixGlyph
(
glyph
,
&
glyph_bbox
,
p_current_face
,
&
pen_new
);
if
(
outline
)
FixGlyph
(
outline
,
&
outline_bbox
,
p_current_face
,
&
pen_new
);
if
(
shadow
)
FixGlyph
(
shadow
,
&
shadow_bbox
,
p_current_face
,
&
pen_shadow_new
);
/* FIXME and what about outline */
bool
b_karaoke
=
pi_karaoke_bar
&&
pi_karaoke_bar
[
i_index
]
!=
0
;
uint32_t
i_color
=
b_karaoke
?
(
p_glyph_style
->
i_karaoke_background_color
|
(
p_glyph_style
->
i_karaoke_background_alpha
<<
24
))
:
(
p_glyph_style
->
i_font_color
|
(
p_glyph_style
->
i_font_alpha
<<
24
));
int
i_line_offset
=
0
;
int
i_line_thickness
=
0
;
if
(
p_glyph_style
->
i_style_flags
&
(
STYLE_UNDERLINE
|
STYLE_STRIKEOUT
)
)
{
i_line_offset
=
abs
(
FT_FLOOR
(
FT_MulFix
(
p_current_face
->
underline_position
,
p_current_face
->
size
->
metrics
.
y_scale
))
);
i_line_thickness
=
abs
(
FT_CEIL
(
FT_MulFix
(
p_current_face
->
underline_thickness
,
p_current_face
->
size
->
metrics
.
y_scale
))
);
if
(
p_glyph_style
->
i_style_flags
&
STYLE_STRIKEOUT
)
{
/* Move the baseline to make it strikethrough instead of
* underline. That means that strikethrough takes precedence
*/
i_line_offset
-=
abs
(
FT_FLOOR
(
FT_MulFix
(
p_current_face
->
descender
*
2
,
p_current_face
->
size
->
metrics
.
y_scale
))
);
}
else
if
(
i_line_thickness
>
0
)
{
glyph_bbox
.
yMin
=
__MIN
(
glyph_bbox
.
yMin
,
-
i_line_offset
-
i_line_thickness
);
/* The real underline thickness and position are
* updated once the whole line has been parsed */
i_ul_offset
=
__MAX
(
i_ul_offset
,
i_line_offset
);
i_ul_thickness
=
__MAX
(
i_ul_thickness
,
i_line_thickness
);
i_line_thickness
=
-
1
;
}
}
FT_BBox
line_bbox_new
=
line_bbox
;
BBoxEnlarge
(
&
line_bbox_new
,
&
glyph_bbox
);
if
(
outline
)
BBoxEnlarge
(
&
line_bbox_new
,
&
outline_bbox
);
if
(
shadow
)
BBoxEnlarge
(
&
line_bbox_new
,
&
shadow_bbox
);
b_break_line
=
i_index
>
i_start
&&
line_bbox_new
.
xMax
-
line_bbox_new
.
xMin
>=
(
int
)
p_filter
->
fmt_out
.
video
.
i_visible_width
;
if
(
b_break_line
)
{
FT_Done_Glyph
(
glyph
);
if
(
outline
)
FT_Done_Glyph
(
outline
);
if
(
shadow
)
FT_Done_Glyph
(
shadow
);
break_point_t
*
p_bp
=
NULL
;
if
(
break_point
.
i_index
>
i_start
)
p_bp
=
&
break_point
;
else
if
(
break_point_fallback
.
i_index
>
i_start
)
p_bp
=
&
break_point_fallback
;
if
(
p_bp
)
{
msg_Dbg
(
p_filter
,
"Breaking line"
);
for
(
int
i
=
p_bp
->
i_index
;
i
<
i_index
;
i
++
)
{
line_character_t
*
ch
=
&
p_line
->
p_character
[
i
-
i_start
];
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_glyph
);
if
(
ch
->
p_outline
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_outline
);
if
(
ch
->
p_shadow
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_shadow
);
}
p_line
->
i_character_count
=
p_bp
->
i_index
-
i_start
;
i_index
=
p_bp
->
i_index
;
pen
=
p_bp
->
pen
;
line_bbox
=
p_bp
->
line_bbox
;
i_face_height
=
p_bp
->
i_face_height
;
i_ul_offset
=
p_bp
->
i_ul_offset
;
i_ul_thickness
=
p_bp
->
i_ul_thickness
;
}
else
{
msg_Err
(
p_filter
,
"Breaking unbreakable line"
);
}
break
;
}
p_line
->
p_character
[
p_line
->
i_character_count
++
]
=
(
line_character_t
){
.
p_glyph
=
(
FT_BitmapGlyph
)
glyph
,
.
p_outline
=
(
FT_BitmapGlyph
)
outline
,
.
p_shadow
=
(
FT_BitmapGlyph
)
shadow
,
.
i_color
=
i_color
,
.
i_line_offset
=
i_line_offset
,
.
i_line_thickness
=
i_line_thickness
,
};
/* Diacritics do not determine advance values. We use */
/* the advance values from the last encountered base glyph, */
/* since multiple diacritics may follow a single base glyph. */
if
(
!
DIACRITIC
(
character
)
)
{
advance
.
x
=
p_current_face
->
glyph
->
advance
.
x
;
advance
.
y
=
p_current_face
->
glyph
->
advance
.
y
;
}
pen
.
x
=
pen_new
.
x
+
advance
.
x
;
pen
.
y
=
pen_new
.
y
+
advance
.
y
;
line_bbox
=
line_bbox_new
;
next:
i_glyph_last
=
i_glyph_index
;
i_part_length
--
;
i_index
++
;
if
(
character
==
' '
||
character
==
'\t'
)
SAVE_BP
(
break_point
);
else
if
(
character
==
160
)
SAVE_BP
(
break_point_fallback
);
}
if
(
b_break_line
)
break
;
}
#undef SAVE_BP
/* Update our baseline */
if
(
i_face_height_previous
>
0
)
i_base_line
+=
__MAX
(
i_face_height
,
i_face_height_previous
);
if
(
i_face_height
>
0
)
i_face_height_previous
=
i_face_height
;
/* Update the line bbox with the actual base line */
if
(
line_bbox
.
yMax
>
line_bbox
.
yMin
)
{
line_bbox
.
yMin
-=
i_base_line
;
line_bbox
.
yMax
-=
i_base_line
;
}
BBoxEnlarge
(
&
bbox
,
&
line_bbox
);
/* Terminate and append the line */
if
(
p_line
)
{
p_line
->
i_width
=
__MAX
(
line_bbox
.
xMax
-
line_bbox
.
xMin
,
0
);
p_line
->
i_base_line
=
i_base_line
;
p_line
->
i_height
=
__MAX
(
i_face_height
,
i_face_height_previous
);
if
(
i_ul_thickness
>
0
)
{
for
(
int
i
=
0
;
i
<
p_line
->
i_character_count
;
i
++
)
{
line_character_t
*
ch
=
&
p_line
->
p_character
[
i
];
if
(
ch
->
i_line_thickness
<
0
)
{
ch
->
i_line_offset
=
i_ul_offset
;
ch
->
i_line_thickness
=
i_ul_thickness
;
}
}
}
*
pp_line_next
=
p_line
;
pp_line_next
=
&
p_line
->
p_next
;
}
*
pi_max_face_height
=
__MAX
(
*
pi_max_face_height
,
i_face_height
);
/* Skip what we have rendered and the line delimitor if present */
i_start
=
i_index
;
if
(
i_start
<
i_len
&&
psz_text
[
i_start
]
==
'\n'
)
i_start
++
;
if
(
bbox
.
yMax
-
bbox
.
yMin
>=
(
int
)
p_filter
->
fmt_out
.
video
.
i_visible_height
)
{
msg_Err
(
p_filter
,
"Truncated too high subtitle"
);
break
;
}
}
if
(
p_face
)
FT_Done_Face
(
p_face
);
free
(
pp_fribidi_styles
);
free
(
p_fribidi_string
);
free
(
pi_karaoke_bar
);
*
p_bbox
=
bbox
;
return
VLC_SUCCESS
;
}
static
xml_reader_t
*
GetXMLReader
(
filter_t
*
p_filter
,
stream_t
*
p_sub
)
{
xml_reader_t
*
p_xml_reader
=
p_filter
->
p_sys
->
p_xml
;
...
...
@@ -1766,9 +1044,9 @@ static int RenderCommon( filter_t *p_filter, subpicture_region_t *p_region_out,
if
(
!
rv
&&
i_text_length
>
0
)
{
rv
=
ProcessLines
(
p_filter
,
&
p_lines
,
&
bbox
,
&
i_max_face_height
,
psz_text
,
pp_styles
,
pi_k_durations
,
i_text_length
);
rv
=
LayoutText
(
p_filter
,
&
p_lines
,
&
bbox
,
&
i_max_face_height
,
psz_text
,
pp_styles
,
pi_k_durations
,
i_text_length
);
}
p_region_out
->
i_x
=
p_region_in
->
i_x
;
...
...
@@ -2025,6 +1303,12 @@ static int Create( vlc_object_t *p_this )
if
(
Init_FT
(
p_this
,
psz_fontfile
,
fontindex
,
f_outline_thickness
)
!=
VLC_SUCCESS
)
goto
error
;
int
i_faces_size
=
20
;
p_sys
->
faces_cache
.
p_faces
=
malloc
(
i_faces_size
*
sizeof
(
*
p_sys
->
faces_cache
.
p_faces
)
);
p_sys
->
faces_cache
.
p_styles
=
malloc
(
i_faces_size
*
sizeof
(
*
p_sys
->
faces_cache
.
p_styles
)
);
p_sys
->
faces_cache
.
i_cache_size
=
i_faces_size
;
p_sys
->
faces_cache
.
i_faces_count
=
0
;
p_sys
->
pp_font_attachments
=
NULL
;
p_sys
->
i_font_attachments
=
0
;
...
...
@@ -2055,6 +1339,7 @@ static void Destroy_FT( vlc_object_t *p_this )
if
(
p_sys
->
p_stroker
)
FT_Stroker_Done
(
p_sys
->
p_stroker
);
FT_Done_Face
(
p_sys
->
p_face
);
FT_Done_FreeType
(
p_sys
->
p_library
);
}
...
...
@@ -2069,6 +1354,15 @@ static void Destroy( vlc_object_t *p_this )
filter_t
*
p_filter
=
(
filter_t
*
)
p_this
;
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
faces_cache_t
*
p_cache
=
&
p_sys
->
faces_cache
;
for
(
int
i
=
0
;
i
<
p_cache
->
i_faces_count
;
++
i
)
{
FT_Done_Face
(
p_cache
->
p_faces
[
i
]
);
free
(
p_cache
->
p_styles
[
i
].
psz_fontname
);
}
free
(
p_sys
->
faces_cache
.
p_faces
);
free
(
p_sys
->
faces_cache
.
p_styles
);
if
(
p_sys
->
pp_font_attachments
)
{
for
(
int
k
=
0
;
k
<
p_sys
->
i_font_attachments
;
k
++
)
...
...
@@ -2084,3 +1378,99 @@ static void Destroy( vlc_object_t *p_this )
Destroy_FT
(
p_this
);
free
(
p_sys
);
}
FT_Face
LoadFace
(
filter_t
*
p_filter
,
const
text_style_t
*
p_style
)
{
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
faces_cache_t
*
p_cache
=
&
p_sys
->
faces_cache
;
for
(
int
i
=
0
;
i
<
p_cache
->
i_faces_count
;
++
i
)
if
(
FaceStyleEquals
(
&
p_cache
->
p_styles
[
i
],
p_style
)
&&
p_cache
->
p_styles
[
i
].
i_font_size
==
p_style
->
i_font_size
&&
!
(
(
p_cache
->
p_styles
[
i
].
i_style_flags
^
p_style
->
i_style_flags
)
&
STYLE_HALFWIDTH
)
)
return
p_cache
->
p_faces
[
i
];
/* Look for a match amongst our attachments first */
FT_Face
p_face
=
LoadEmbeddedFace
(
p_sys
,
p_style
);
/* Load system wide font otheriwse */
if
(
!
p_face
)
{
int
i_idx
=
0
;
char
*
psz_fontfile
=
NULL
;
if
(
p_sys
->
pf_select
)
psz_fontfile
=
p_sys
->
pf_select
(
p_filter
,
p_style
->
psz_fontname
,
(
p_style
->
i_style_flags
&
STYLE_BOLD
)
!=
0
,
(
p_style
->
i_style_flags
&
STYLE_ITALIC
)
!=
0
,
-
1
,
&
i_idx
);
else
psz_fontfile
=
NULL
;
if
(
!
psz_fontfile
)
return
NULL
;
if
(
*
psz_fontfile
==
'\0'
)
{
msg_Warn
(
p_filter
,
"We were not able to find a matching font:
\"
%s
\"
(%s %s),"
" so using default font"
,
p_style
->
psz_fontname
,
(
p_style
->
i_style_flags
&
STYLE_BOLD
)
?
"Bold"
:
""
,
(
p_style
->
i_style_flags
&
STYLE_ITALIC
)
?
"Italic"
:
""
);
p_face
=
NULL
;
}
else
{
if
(
FT_New_Face
(
p_sys
->
p_library
,
psz_fontfile
,
i_idx
,
&
p_face
)
)
p_face
=
NULL
;
}
free
(
psz_fontfile
);
}
if
(
!
p_face
)
return
NULL
;
if
(
FT_Select_Charmap
(
p_face
,
ft_encoding_unicode
)
)
{
/* We've loaded a font face which is unhelpful for actually
* rendering text - fallback to the default one.
*/
FT_Done_Face
(
p_face
);
return
NULL
;
}
if
(
p_cache
->
i_faces_count
==
p_cache
->
i_cache_size
)
{
FT_Face
*
p_new_faces
=
realloc
(
p_cache
->
p_faces
,
p_cache
->
i_cache_size
*
2
*
sizeof
(
*
p_cache
->
p_faces
)
);
if
(
!
p_new_faces
)
{
FT_Done_Face
(
p_face
);
return
NULL
;
}
p_cache
->
p_faces
=
p_new_faces
;
text_style_t
*
p_new_styles
=
realloc
(
p_cache
->
p_styles
,
p_cache
->
i_cache_size
*
2
*
sizeof
(
*
p_cache
->
p_styles
)
)
;
if
(
!
p_new_styles
)
{
FT_Done_Face
(
p_face
);
return
NULL
;
}
p_cache
->
p_styles
=
p_new_styles
;
p_cache
->
i_cache_size
*=
2
;
}
text_style_t
*
p_face_style
=
p_cache
->
p_styles
+
p_cache
->
i_faces_count
;
p_face_style
->
i_font_size
=
p_style
->
i_font_size
;
p_face_style
->
i_style_flags
=
p_style
->
i_style_flags
;
p_face_style
->
psz_fontname
=
strdup
(
p_style
->
psz_fontname
);
p_cache
->
p_faces
[
p_cache
->
i_faces_count
]
=
p_face
;
++
p_cache
->
i_faces_count
;
return
p_face
;
}
modules/text_renderer/freetype.h
0 → 100644
View file @
57cb3e53
/*****************************************************************************
* freetype.h : Put text on the video, using freetype2
*****************************************************************************
* Copyright (C) 2015 VLC authors and VideoLAN
* $Id$
*
* Authors: Salah-Eddin Shaban <salshaaban@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
typedef
struct
faces_cache_t
{
FT_Face
*
p_faces
;
text_style_t
*
p_styles
;
int
i_faces_count
;
int
i_cache_size
;
}
faces_cache_t
;
/*****************************************************************************
* filter_sys_t: freetype local data
*****************************************************************************
* This structure is part of the video output thread descriptor.
* It describes the freetype specific properties of an output thread.
*****************************************************************************/
struct
filter_sys_t
{
FT_Library
p_library
;
/* handle to library */
FT_Face
p_face
;
/* handle to face object */
FT_Stroker
p_stroker
;
/* handle to path stroker object */
xml_reader_t
*
p_xml
;
/* vlc xml parser */
text_style_t
style
;
/* Current Style */
/* More styles... */
float
f_shadow_vector_x
;
float
f_shadow_vector_y
;
int
i_default_font_size
;
/* Attachments */
input_attachment_t
**
pp_font_attachments
;
int
i_font_attachments
;
/* Font faces cache */
faces_cache_t
faces_cache
;
char
*
(
*
pf_select
)
(
filter_t
*
,
const
char
*
family
,
bool
bold
,
bool
italic
,
int
size
,
int
*
index
);
};
#define FT_FLOOR(X) ((X & -64) >> 6)
#define FT_CEIL(X) (((X + 63) & -64) >> 6)
#ifndef FT_MulFix
#define FT_MulFix(v, s) (((v)*(s))>>16)
#endif
FT_Face
LoadFace
(
filter_t
*
p_filter
,
const
text_style_t
*
p_style
);
modules/text_renderer/text_layout.c
0 → 100644
View file @
57cb3e53
/*****************************************************************************
* text_layout.c : Text shaping and layout
*****************************************************************************
* Copyright (C) 2015 VLC authors and VideoLAN
* $Id$
*
* Authors: Salah-Eddin Shaban <salshaaban@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
#include <vlc_common.h>
#include <vlc_filter.h>
#include <vlc_text_style.h>
/* Freetype */
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_STROKER_H
#include FT_SYNTHESIS_H
/* RTL */
#if defined(HAVE_FRIBIDI)
# include <fribidi/fribidi.h>
#endif
/* Complex Scripts */
#if defined(HAVE_HARFBUZZ)
# include <hb.h>
# include <hb-ft.h>
#endif
#include "text_renderer.h"
#include "text_layout.h"
#include "freetype.h"
/*
* Within a paragraph, run_desc_t represents a run of characters
* having the same font face, size, and style, Unicode script
* and text direction
*/
typedef
struct
run_desc_t
{
int
i_start_offset
;
int
i_end_offset
;
FT_Face
p_face
;
text_style_t
*
p_style
;
#ifdef HAVE_HARFBUZZ
hb_script_t
script
;
hb_direction_t
direction
;
hb_font_t
*
p_hb_font
;
hb_buffer_t
*
p_buffer
;
hb_glyph_info_t
*
p_glyph_infos
;
hb_glyph_position_t
*
p_glyph_positions
;
unsigned
int
i_glyph_count
;
#endif
}
run_desc_t
;
/*
* Glyph bitmaps. Advance and offset are 26.6 values
*/
typedef
struct
glyph_bitmaps_t
{
FT_Glyph
p_glyph
;
FT_Glyph
p_outline
;
FT_Glyph
p_shadow
;
FT_BBox
glyph_bbox
;
FT_BBox
outline_bbox
;
FT_BBox
shadow_bbox
;
int
i_x_offset
;
int
i_y_offset
;
int
i_x_advance
;
int
i_y_advance
;
}
glyph_bitmaps_t
;
typedef
struct
paragraph_t
{
uni_char_t
*
p_code_points
;
//Unicode code points
int
*
pi_glyph_indices
;
//Glyph index values within the run's font face
text_style_t
**
pp_styles
;
int
*
pi_run_ids
;
//The run to which each glyph belongs
glyph_bitmaps_t
*
p_glyph_bitmaps
;
uint8_t
*
pi_karaoke_bar
;
int
i_size
;
run_desc_t
*
p_runs
;
int
i_runs_count
;
int
i_runs_size
;
#ifdef HAVE_HARFBUZZ
hb_script_t
*
p_scripts
;
#endif
#ifdef HAVE_FRIBIDI
FriBidiCharType
*
p_types
;
FriBidiLevel
*
p_levels
;
FriBidiStrIndex
*
pi_reordered_indices
;
FriBidiParType
paragraph_type
;
#endif
}
paragraph_t
;
void
FreeLine
(
line_desc_t
*
p_line
)
{
for
(
int
i
=
0
;
i
<
p_line
->
i_character_count
;
i
++
)
{
line_character_t
*
ch
=
&
p_line
->
p_character
[
i
];
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_glyph
);
if
(
ch
->
p_outline
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_outline
);
if
(
ch
->
p_shadow
)
FT_Done_Glyph
(
(
FT_Glyph
)
ch
->
p_shadow
);
}
free
(
p_line
->
p_character
);
free
(
p_line
);
}
void
FreeLines
(
line_desc_t
*
p_lines
)
{
for
(
line_desc_t
*
p_line
=
p_lines
;
p_line
!=
NULL
;
)
{
line_desc_t
*
p_next
=
p_line
->
p_next
;
FreeLine
(
p_line
);
p_line
=
p_next
;
}
}
line_desc_t
*
NewLine
(
int
i_count
)
{
line_desc_t
*
p_line
=
malloc
(
sizeof
(
*
p_line
)
);
if
(
!
p_line
)
return
NULL
;
p_line
->
p_next
=
NULL
;
p_line
->
i_width
=
0
;
p_line
->
i_base_line
=
0
;
p_line
->
i_character_count
=
0
;
p_line
->
bbox
.
xMin
=
INT_MAX
;
p_line
->
bbox
.
yMin
=
INT_MAX
;
p_line
->
bbox
.
xMax
=
INT_MIN
;
p_line
->
bbox
.
yMax
=
INT_MIN
;
p_line
->
p_character
=
calloc
(
i_count
,
sizeof
(
*
p_line
->
p_character
)
);
if
(
!
p_line
->
p_character
)
{
free
(
p_line
);
return
NULL
;
}
return
p_line
;
}
static
void
FixGlyph
(
FT_Glyph
glyph
,
FT_BBox
*
p_bbox
,
FT_Face
face
,
const
FT_Vector
*
p_pen
)
{
FT_BitmapGlyph
glyph_bmp
=
(
FT_BitmapGlyph
)
glyph
;
if
(
p_bbox
->
xMin
>=
p_bbox
->
xMax
)
{
p_bbox
->
xMin
=
FT_CEIL
(
p_pen
->
x
);
p_bbox
->
xMax
=
FT_CEIL
(
p_pen
->
x
+
face
->
glyph
->
advance
.
x
);
glyph_bmp
->
left
=
p_bbox
->
xMin
;
}
if
(
p_bbox
->
yMin
>=
p_bbox
->
yMax
)
{
p_bbox
->
yMax
=
FT_CEIL
(
p_pen
->
y
);
p_bbox
->
yMin
=
FT_CEIL
(
p_pen
->
y
+
face
->
glyph
->
advance
.
y
);
glyph_bmp
->
top
=
p_bbox
->
yMax
;
}
}
static
void
BBoxEnlarge
(
FT_BBox
*
p_max
,
const
FT_BBox
*
p
)
{
p_max
->
xMin
=
__MIN
(
p_max
->
xMin
,
p
->
xMin
);
p_max
->
yMin
=
__MIN
(
p_max
->
yMin
,
p
->
yMin
);
p_max
->
xMax
=
__MAX
(
p_max
->
xMax
,
p
->
xMax
);
p_max
->
yMax
=
__MAX
(
p_max
->
yMax
,
p
->
yMax
);
}
static
paragraph_t
*
NewParagraph
(
filter_t
*
p_filter
,
int
i_size
,
uni_char_t
*
p_code_points
,
text_style_t
**
pp_styles
,
uint32_t
*
pi_k_dates
,
int
i_runs_size
)
{
paragraph_t
*
p_paragraph
=
calloc
(
1
,
sizeof
(
paragraph_t
)
);
if
(
!
p_paragraph
)
return
0
;
p_paragraph
->
i_size
=
i_size
;
p_paragraph
->
p_code_points
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
p_code_points
)
);
p_paragraph
->
pi_glyph_indices
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
pi_glyph_indices
)
);
p_paragraph
->
pp_styles
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
pp_styles
)
);
p_paragraph
->
pi_run_ids
=
calloc
(
i_size
,
sizeof
(
*
p_paragraph
->
pi_run_ids
)
);
p_paragraph
->
p_glyph_bitmaps
=
calloc
(
i_size
,
sizeof
(
*
p_paragraph
->
p_glyph_bitmaps
)
);
p_paragraph
->
pi_karaoke_bar
=
calloc
(
i_size
,
sizeof
(
*
p_paragraph
->
pi_karaoke_bar
)
);
p_paragraph
->
p_runs
=
calloc
(
i_runs_size
,
sizeof
(
run_desc_t
)
);
p_paragraph
->
i_runs_size
=
i_runs_size
;
p_paragraph
->
i_runs_count
=
0
;
if
(
!
p_paragraph
->
p_code_points
||
!
p_paragraph
->
pi_glyph_indices
||
!
p_paragraph
->
pp_styles
||
!
p_paragraph
->
pi_run_ids
||
!
p_paragraph
->
p_glyph_bitmaps
||
!
p_paragraph
->
pi_karaoke_bar
)
goto
error
;
if
(
p_code_points
)
memcpy
(
p_paragraph
->
p_code_points
,
p_code_points
,
i_size
*
sizeof
(
*
p_code_points
)
);
if
(
pp_styles
)
memcpy
(
p_paragraph
->
pp_styles
,
pp_styles
,
i_size
*
sizeof
(
*
pp_styles
)
);
if
(
pi_k_dates
)
{
int64_t
i_elapsed
=
var_GetTime
(
p_filter
,
"spu-elapsed"
)
/
1000
;
for
(
int
i
=
0
;
i
<
i_size
;
++
i
)
{
p_paragraph
->
pi_karaoke_bar
[
i
]
=
pi_k_dates
[
i
]
>=
i_elapsed
;
}
}
#ifdef HAVE_HARFBUZZ
p_paragraph
->
p_scripts
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
p_scripts
)
);
if
(
!
p_paragraph
->
p_scripts
)
goto
error
;
#endif
#ifdef HAVE_FRIBIDI
p_paragraph
->
p_levels
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
p_levels
)
);
p_paragraph
->
p_types
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
p_types
)
);
p_paragraph
->
pi_reordered_indices
=
malloc
(
i_size
*
sizeof
(
*
p_paragraph
->
pi_reordered_indices
)
);
if
(
!
p_paragraph
->
p_levels
||
!
p_paragraph
->
p_types
||
!
p_paragraph
->
pi_reordered_indices
)
goto
error
;
int
i_direction
=
var_InheritInteger
(
p_filter
,
"freetype-text-direction"
);
if
(
i_direction
==
0
)
p_paragraph
->
paragraph_type
=
FRIBIDI_PAR_LTR
;
else
if
(
i_direction
==
1
)
p_paragraph
->
paragraph_type
=
FRIBIDI_PAR_RTL
;
else
p_paragraph
->
paragraph_type
=
FRIBIDI_PAR_ON
;
#endif
return
p_paragraph
;
error:
if
(
p_paragraph
->
p_code_points
)
free
(
p_paragraph
->
p_code_points
);
if
(
p_paragraph
->
pi_glyph_indices
)
free
(
p_paragraph
->
pi_glyph_indices
);
if
(
p_paragraph
->
pp_styles
)
free
(
p_paragraph
->
pp_styles
);
if
(
p_paragraph
->
pi_run_ids
)
free
(
p_paragraph
->
pi_run_ids
);
if
(
p_paragraph
->
p_glyph_bitmaps
)
free
(
p_paragraph
->
p_glyph_bitmaps
);
if
(
p_paragraph
->
pi_karaoke_bar
)
free
(
p_paragraph
->
pi_karaoke_bar
);
if
(
p_paragraph
->
p_runs
)
free
(
p_paragraph
->
p_runs
);
#ifdef HAVE_HARFBUZZ
if
(
p_paragraph
->
p_scripts
)
free
(
p_paragraph
->
p_scripts
);
#endif
#ifdef HAVE_FRIBIDI
if
(
p_paragraph
->
p_levels
)
free
(
p_paragraph
->
p_levels
);
if
(
p_paragraph
->
p_types
)
free
(
p_paragraph
->
p_types
);
if
(
p_paragraph
->
pi_reordered_indices
)
free
(
p_paragraph
->
pi_reordered_indices
);
#endif
free
(
p_paragraph
);
return
0
;
}
static
void
FreeParagraph
(
paragraph_t
*
p_paragraph
)
{
free
(
p_paragraph
->
p_runs
);
free
(
p_paragraph
->
pi_glyph_indices
);
free
(
p_paragraph
->
p_glyph_bitmaps
);
free
(
p_paragraph
->
pi_karaoke_bar
);
free
(
p_paragraph
->
pi_run_ids
);
free
(
p_paragraph
->
pp_styles
);
free
(
p_paragraph
->
p_code_points
);
#ifdef HAVE_HARFBUZZ
free
(
p_paragraph
->
p_scripts
);
#endif
#ifdef HAVE_FRIBIDI
free
(
p_paragraph
->
pi_reordered_indices
);
free
(
p_paragraph
->
p_types
);
free
(
p_paragraph
->
p_levels
);
#endif
free
(
p_paragraph
);
}
#ifdef HAVE_FRIBIDI
static
int
AnalyzeParagraph
(
paragraph_t
*
p_paragraph
)
{
fribidi_get_bidi_types
(
p_paragraph
->
p_code_points
,
p_paragraph
->
i_size
,
p_paragraph
->
p_types
);
fribidi_get_par_embedding_levels
(
p_paragraph
->
p_types
,
p_paragraph
->
i_size
,
&
p_paragraph
->
paragraph_type
,
p_paragraph
->
p_levels
);
#ifdef HAVE_HARFBUZZ
hb_unicode_funcs_t
*
p_funcs
=
hb_unicode_funcs_get_default
();
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_size
;
++
i
)
p_paragraph
->
p_scripts
[
i
]
=
hb_unicode_script
(
p_funcs
,
p_paragraph
->
p_code_points
[
i
]
);
hb_script_t
i_last_script
;
int
i_last_script_index
=
-
1
;
int
i_last_set_index
=
-
1
;
/*
* For shaping to work, characters that are assigned HB_SCRIPT_COMMON or
* HB_SCRIPT_INHERITED should be resolved to the last encountered valid
* script value, if any, and to the first one following them otherwise
*/
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_size
;
++
i
)
{
if
(
p_paragraph
->
p_scripts
[
i
]
==
HB_SCRIPT_COMMON
||
p_paragraph
->
p_scripts
[
i
]
==
HB_SCRIPT_INHERITED
)
{
if
(
i_last_script_index
!=
-
1
)
{
p_paragraph
->
p_scripts
[
i
]
=
i_last_script
;
i_last_set_index
=
i
;
}
}
else
{
for
(
int
j
=
i_last_set_index
+
1
;
j
<
i
;
++
j
)
p_paragraph
->
p_scripts
[
j
]
=
p_paragraph
->
p_scripts
[
i
];
i_last_script
=
p_paragraph
->
p_scripts
[
i
];
i_last_script_index
=
i
;
i_last_set_index
=
i
;
}
}
#endif //HAVE_HARFBUZZ
return
VLC_SUCCESS
;
}
#endif //HAVE_FRIBIDI
static
int
AddRun
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
,
int
i_start_offset
,
int
i_end_offset
,
FT_Face
p_face
)
{
if
(
i_start_offset
>=
i_end_offset
||
i_start_offset
<
0
||
i_start_offset
>=
p_paragraph
->
i_size
||
i_end_offset
<=
0
||
i_end_offset
>
p_paragraph
->
i_size
)
{
msg_Err
(
p_filter
,
"AddRun() invalid parameters. Paragraph size: %d, "
"Start offset: %d, End offset: %d"
,
p_paragraph
->
i_size
,
i_start_offset
,
i_end_offset
);
return
VLC_EGENERIC
;
}
if
(
p_paragraph
->
i_runs_count
==
p_paragraph
->
i_runs_size
)
{
run_desc_t
*
p_new_runs
=
realloc
(
p_paragraph
->
p_runs
,
p_paragraph
->
i_runs_size
*
2
*
sizeof
(
*
p_new_runs
)
);
if
(
!
p_new_runs
)
return
VLC_ENOMEM
;
p_paragraph
->
p_runs
=
p_new_runs
;
p_paragraph
->
i_runs_size
*=
2
;
}
int
i_run_id
=
p_paragraph
->
i_runs_count
;
run_desc_t
*
p_run
=
p_paragraph
->
p_runs
+
p_paragraph
->
i_runs_count
++
;
p_run
->
i_start_offset
=
i_start_offset
;
p_run
->
i_end_offset
=
i_end_offset
;
p_run
->
p_style
=
p_paragraph
->
pp_styles
[
i_start_offset
];
p_run
->
p_face
=
p_face
;
#ifdef HAVE_HARFBUZZ
p_run
->
script
=
p_paragraph
->
p_scripts
[
i_start_offset
];
p_run
->
direction
=
p_paragraph
->
p_levels
[
i_start_offset
]
&
1
?
HB_DIRECTION_RTL
:
HB_DIRECTION_LTR
;
#endif
for
(
int
i
=
i_start_offset
;
i
<
i_end_offset
;
++
i
)
p_paragraph
->
pi_run_ids
[
i
]
=
i_run_id
;
return
VLC_SUCCESS
;
}
/*
* Segment a paragraph into runs
*/
static
int
ItemizeParagraph
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
)
{
if
(
p_paragraph
->
i_size
<=
0
)
{
msg_Err
(
p_filter
,
"ItemizeParagraph() invalid parameters. Paragraph size: %d"
,
p_paragraph
->
i_size
);
return
VLC_EGENERIC
;
}
int
i_last_run_start
=
0
;
text_style_t
*
p_last_style
=
p_paragraph
->
pp_styles
[
0
];
#ifdef HAVE_HARFBUZZ
hb_script_t
last_script
=
p_paragraph
->
p_scripts
[
0
];
FriBidiLevel
last_level
=
p_paragraph
->
p_levels
[
0
];
#endif
for
(
int
i
=
0
;
i
<=
p_paragraph
->
i_size
;
++
i
)
{
if
(
i
==
p_paragraph
->
i_size
#ifdef HAVE_HARFBUZZ
||
last_script
!=
p_paragraph
->
p_scripts
[
i
]
||
last_level
!=
p_paragraph
->
p_levels
[
i
]
#endif
||
p_last_style
->
i_font_size
!=
p_paragraph
->
pp_styles
[
i
]
->
i_font_size
||
(
(
p_last_style
->
i_style_flags
^
p_paragraph
->
pp_styles
[
i
]
->
i_style_flags
)
&
STYLE_HALFWIDTH
)
||!
FaceStyleEquals
(
p_last_style
,
p_paragraph
->
pp_styles
[
i
]
)
)
{
int
i_ret
=
AddRun
(
p_filter
,
p_paragraph
,
i_last_run_start
,
i
,
0
);
if
(
i_ret
)
return
i_ret
;
if
(
i
<
p_paragraph
->
i_size
)
{
i_last_run_start
=
i
;
p_last_style
=
p_paragraph
->
pp_styles
[
i
];
#ifdef HAVE_HARFBUZZ
last_script
=
p_paragraph
->
p_scripts
[
i
];
last_level
=
p_paragraph
->
p_levels
[
i
];
#endif
}
}
}
return
VLC_SUCCESS
;
}
/*
* Shape an itemized paragraph using HarfBuzz.
* This is where the glyphs of complex scripts get their positions
* (offsets and advance values) and final forms.
* Glyph substitutions of base glyphs and diacritics may take place,
* so the paragraph size may change.
*/
#ifdef HAVE_HARFBUZZ
static
int
ShapeParagraphHarfBuzz
(
filter_t
*
p_filter
,
paragraph_t
**
p_old_paragraph
)
{
paragraph_t
*
p_paragraph
=
*
p_old_paragraph
;
paragraph_t
*
p_new_paragraph
=
0
;
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
int
i_total_glyphs
=
0
;
int
i_ret
=
VLC_EGENERIC
;
if
(
p_paragraph
->
i_size
<=
0
||
p_paragraph
->
i_runs_count
<=
0
)
{
msg_Err
(
p_filter
,
"ShapeParagraphHarfBuzz() invalid parameters. "
"Paragraph size: %d. Runs count %d"
,
p_paragraph
->
i_size
,
p_paragraph
->
i_runs_count
);
return
VLC_EGENERIC
;
}
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_runs_count
;
++
i
)
{
run_desc_t
*
p_run
=
p_paragraph
->
p_runs
+
i
;
text_style_t
*
p_style
=
p_run
->
p_style
;
/*
* When using HarfBuzz, this is where font faces are loaded.
* In the other two paths (shaping with FriBidi or no
* shaping at all), faces are loaded in LoadGlyphs()
*/
FT_Face
p_face
=
0
;
if
(
!
p_run
->
p_face
)
{
p_face
=
LoadFace
(
p_filter
,
p_style
);
if
(
!
p_face
)
p_face
=
p_sys
->
p_face
;
p_run
->
p_face
=
p_face
;
}
else
p_face
=
p_run
->
p_face
;
int
i_font_width
=
p_style
->
i_style_flags
&
STYLE_HALFWIDTH
?
p_style
->
i_font_size
/
2
:
p_style
->
i_font_size
;
if
(
FT_Set_Pixel_Sizes
(
p_face
,
i_font_width
,
p_style
->
i_font_size
)
)
msg_Err
(
p_filter
,
"Failed to set font size to %d"
,
p_style
->
i_font_size
);
p_run
->
p_hb_font
=
hb_ft_font_create
(
p_face
,
0
);
if
(
!
p_run
->
p_hb_font
)
{
msg_Err
(
p_filter
,
"ShapeParagraphHarfBuzz(): hb_ft_font_create() error"
);
i_ret
=
VLC_EGENERIC
;
goto
error
;
}
p_run
->
p_buffer
=
hb_buffer_create
();
if
(
!
p_run
->
p_buffer
)
{
msg_Err
(
p_filter
,
"ShapeParagraphHarfBuzz(): hb_buffer_create() error"
);
i_ret
=
VLC_EGENERIC
;
goto
error
;
}
hb_buffer_set_direction
(
p_run
->
p_buffer
,
p_run
->
direction
);
hb_buffer_set_script
(
p_run
->
p_buffer
,
p_run
->
script
);
#ifdef __OS2__
hb_buffer_add_utf16
(
p_run
->
p_buffer
,
p_paragraph
->
p_code_points
+
p_run
->
i_start_offset
,
p_run
->
i_end_offset
-
p_run
->
i_start_offset
,
0
,
p_run
->
i_end_offset
-
p_run
->
i_start_offset
);
#else
hb_buffer_add_utf32
(
p_run
->
p_buffer
,
p_paragraph
->
p_code_points
+
p_run
->
i_start_offset
,
p_run
->
i_end_offset
-
p_run
->
i_start_offset
,
0
,
p_run
->
i_end_offset
-
p_run
->
i_start_offset
);
#endif
hb_shape
(
p_run
->
p_hb_font
,
p_run
->
p_buffer
,
0
,
0
);
p_run
->
p_glyph_infos
=
hb_buffer_get_glyph_infos
(
p_run
->
p_buffer
,
&
p_run
->
i_glyph_count
);
p_run
->
p_glyph_positions
=
hb_buffer_get_glyph_positions
(
p_run
->
p_buffer
,
&
p_run
->
i_glyph_count
);
i_total_glyphs
+=
p_run
->
i_glyph_count
;
}
if
(
i_total_glyphs
<=
0
)
{
msg_Err
(
p_filter
,
"ShapeParagraphHarfBuzz() error. Shaped glyphs' count: %d"
,
i_total_glyphs
);
i_ret
=
VLC_EGENERIC
;
goto
error
;
}
p_new_paragraph
=
NewParagraph
(
p_filter
,
i_total_glyphs
,
0
,
0
,
0
,
p_paragraph
->
i_runs_size
);
if
(
!
p_new_paragraph
)
{
i_ret
=
VLC_ENOMEM
;
goto
error
;
}
p_new_paragraph
->
paragraph_type
=
p_paragraph
->
paragraph_type
;
int
i_index
=
0
;
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_runs_count
;
++
i
)
{
run_desc_t
*
p_run
=
p_paragraph
->
p_runs
+
i
;
hb_glyph_info_t
*
p_infos
=
p_run
->
p_glyph_infos
;
hb_glyph_position_t
*
p_positions
=
p_run
->
p_glyph_positions
;
for
(
unsigned
int
j
=
0
;
j
<
p_run
->
i_glyph_count
;
++
j
)
{
/*
* HarfBuzz reverses the order of glyphs in RTL runs. We reverse
* it again here to keep the glyphs in theirs logical order.
* For line breaking of paragraphs to work correctly, visual
* reordering should be done after line breaking has taken
* place.
*/
int
i_run_index
=
p_run
->
direction
==
HB_DIRECTION_LTR
?
j
:
p_run
->
i_glyph_count
-
1
-
j
;
int
i_source_index
=
p_infos
[
i_run_index
].
cluster
+
p_run
->
i_start_offset
;
p_new_paragraph
->
p_code_points
[
i_index
]
=
0
;
p_new_paragraph
->
pi_glyph_indices
[
i_index
]
=
p_infos
[
i_run_index
].
codepoint
;
p_new_paragraph
->
p_scripts
[
i_index
]
=
p_paragraph
->
p_scripts
[
i_source_index
];
p_new_paragraph
->
p_types
[
i_index
]
=
p_paragraph
->
p_types
[
i_source_index
];
p_new_paragraph
->
p_levels
[
i_index
]
=
p_paragraph
->
p_levels
[
i_source_index
];
p_new_paragraph
->
pp_styles
[
i_index
]
=
p_paragraph
->
pp_styles
[
i_source_index
];
p_new_paragraph
->
pi_karaoke_bar
[
i_index
]
=
p_paragraph
->
pi_karaoke_bar
[
i_source_index
];
p_new_paragraph
->
p_glyph_bitmaps
[
i_index
].
i_x_offset
=
p_positions
[
i_run_index
].
x_offset
;
p_new_paragraph
->
p_glyph_bitmaps
[
i_index
].
i_y_offset
=
p_positions
[
i_run_index
].
y_offset
;
p_new_paragraph
->
p_glyph_bitmaps
[
i_index
].
i_x_advance
=
p_positions
[
i_run_index
].
x_advance
;
p_new_paragraph
->
p_glyph_bitmaps
[
i_index
].
i_y_advance
=
p_positions
[
i_run_index
].
y_advance
;
++
i_index
;
}
AddRun
(
p_filter
,
p_new_paragraph
,
i_index
-
p_run
->
i_glyph_count
,
i_index
,
p_run
->
p_face
);
}
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_runs_count
;
++
i
)
{
hb_font_destroy
(
p_paragraph
->
p_runs
[
i
].
p_hb_font
);
hb_buffer_destroy
(
p_paragraph
->
p_runs
[
i
].
p_buffer
);
}
FreeParagraph
(
*
p_old_paragraph
);
*
p_old_paragraph
=
p_new_paragraph
;
return
VLC_SUCCESS
;
error:
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_runs_count
;
++
i
)
{
if
(
p_paragraph
->
p_runs
[
i
].
p_hb_font
)
hb_font_destroy
(
p_paragraph
->
p_runs
[
i
].
p_hb_font
);
if
(
p_paragraph
->
p_runs
[
i
].
p_buffer
)
hb_buffer_destroy
(
p_paragraph
->
p_runs
[
i
].
p_buffer
);
}
if
(
p_new_paragraph
)
FreeParagraph
(
p_new_paragraph
);
return
i_ret
;
}
#endif
/*
* Shape a paragraph with FriBidi.
* Shaping with FriBidi is currently limited to mirroring and simple
* Arabic shaping.
*/
#ifdef HAVE_FRIBIDI
#ifndef HAVE_HARFBUZZ
static
int
ShapeParagraphFriBidi
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
)
{
if
(
p_paragraph
->
i_size
<=
0
)
{
msg_Err
(
p_filter
,
"ShapeParagraphFriBidi() invalid parameters. Paragraph size: %d"
,
p_paragraph
->
i_size
);
return
VLC_EGENERIC
;
}
FriBidiJoiningType
*
p_joining_types
=
malloc
(
p_paragraph
->
i_size
*
sizeof
(
*
p_joining_types
)
);
if
(
!
p_joining_types
)
return
VLC_ENOMEM
;
fribidi_get_joining_types
(
p_paragraph
->
p_code_points
,
p_paragraph
->
i_size
,
p_joining_types
);
fribidi_join_arabic
(
p_paragraph
->
p_types
,
p_paragraph
->
i_size
,
p_paragraph
->
p_levels
,
p_joining_types
);
fribidi_shape
(
FRIBIDI_FLAGS_DEFAULT
|
FRIBIDI_FLAGS_ARABIC
,
p_paragraph
->
p_levels
,
p_paragraph
->
i_size
,
p_joining_types
,
p_paragraph
->
p_code_points
);
free
(
p_joining_types
);
return
VLC_SUCCESS
;
}
/*
* Zero-width invisible characters include Unicode control characters and
* zero-width spaces among other things. If not removed they can show up in the
* text as squares or other glyphs depending on the font. Zero-width spaces are
* inserted when shaping with FriBidi, when it performs glyph substitution for
* ligatures.
*/
static
int
RemoveZeroWidthCharacters
(
paragraph_t
*
p_paragraph
)
{
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_size
;
++
i
)
{
uni_char_t
ch
=
p_paragraph
->
p_code_points
[
i
];
if
(
ch
==
0xfeff
||
ch
==
0x061c
||
(
ch
>=
0x202a
&&
ch
<=
0x202e
)
||
(
ch
>=
0x2060
&&
ch
<=
0x2069
)
||
(
ch
>=
0x200b
&&
ch
<=
0x200f
)
)
{
glyph_bitmaps_t
*
p_bitmaps
=
p_paragraph
->
p_glyph_bitmaps
+
i
;
if
(
p_bitmaps
->
p_glyph
)
FT_Done_Glyph
(
p_bitmaps
->
p_glyph
);
if
(
p_bitmaps
->
p_outline
)
FT_Done_Glyph
(
p_bitmaps
->
p_outline
);
p_bitmaps
->
p_glyph
=
0
;
p_bitmaps
->
p_outline
=
0
;
p_bitmaps
->
p_shadow
=
0
;
p_bitmaps
->
i_x_advance
=
0
;
p_bitmaps
->
i_y_advance
=
0
;
}
}
return
VLC_SUCCESS
;
}
/*
* Set advance values of non-spacing marks to zero. Diacritics are
* not positioned correctly but the text is more readable.
* For full shaping HarfBuzz is required.
*/
static
int
ZeroNsmAdvance
(
paragraph_t
*
p_paragraph
)
{
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_size
;
++
i
)
if
(
p_paragraph
->
p_types
[
i
]
==
FRIBIDI_TYPE_NSM
)
{
p_paragraph
->
p_glyph_bitmaps
[
i
].
i_x_advance
=
0
;
p_paragraph
->
p_glyph_bitmaps
[
i
].
i_y_advance
=
0
;
}
return
VLC_SUCCESS
;
}
#endif
#endif
/*
* Load the glyphs of a paragraph. When shaping with HarfBuzz the glyph indices
* have already been determined at this point, as well as the advance values.
*/
static
int
LoadGlyphs
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
,
bool
b_use_glyph_indices
,
bool
b_overwrite_advance
)
{
if
(
p_paragraph
->
i_size
<=
0
||
p_paragraph
->
i_runs_count
<=
0
)
{
msg_Err
(
p_filter
,
"LoadGlyphs() invalid parameters. "
"Paragraph size: %d. Runs count %d"
,
p_paragraph
->
i_size
,
p_paragraph
->
i_runs_count
);
return
VLC_EGENERIC
;
}
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_runs_count
;
++
i
)
{
run_desc_t
*
p_run
=
p_paragraph
->
p_runs
+
i
;
text_style_t
*
p_style
=
p_run
->
p_style
;
FT_Face
p_face
;
if
(
!
p_run
->
p_face
)
{
p_face
=
LoadFace
(
p_filter
,
p_style
);
if
(
!
p_face
)
p_face
=
p_sys
->
p_face
;
p_run
->
p_face
=
p_face
;
}
else
p_face
=
p_run
->
p_face
;
int
i_font_width
=
p_style
->
i_style_flags
&
STYLE_HALFWIDTH
?
p_style
->
i_font_size
/
2
:
p_style
->
i_font_size
;
if
(
FT_Set_Pixel_Sizes
(
p_face
,
i_font_width
,
p_style
->
i_font_size
)
)
msg_Err
(
p_filter
,
"Failed to set font size to %d"
,
p_style
->
i_font_size
);
if
(
p_sys
->
p_stroker
)
{
double
f_outline_thickness
=
var_InheritInteger
(
p_filter
,
"freetype-outline-thickness"
)
/
100
.
0
;
f_outline_thickness
=
VLC_CLIP
(
f_outline_thickness
,
0
.
0
,
0
.
5
);
int
i_radius
=
(
p_style
->
i_font_size
<<
6
)
*
f_outline_thickness
;
FT_Stroker_Set
(
p_sys
->
p_stroker
,
i_radius
,
FT_STROKER_LINECAP_ROUND
,
FT_STROKER_LINEJOIN_ROUND
,
0
);
}
for
(
int
j
=
p_run
->
i_start_offset
;
j
<
p_run
->
i_end_offset
;
++
j
)
{
int
i_glyph_index
;
if
(
b_use_glyph_indices
)
i_glyph_index
=
p_paragraph
->
pi_glyph_indices
[
j
];
else
i_glyph_index
=
FT_Get_Char_Index
(
p_face
,
p_paragraph
->
p_code_points
[
j
]
);
glyph_bitmaps_t
*
p_bitmaps
=
p_paragraph
->
p_glyph_bitmaps
+
j
;
if
(
FT_Load_Glyph
(
p_face
,
i_glyph_index
,
FT_LOAD_NO_BITMAP
|
FT_LOAD_DEFAULT
)
&&
FT_Load_Glyph
(
p_face
,
i_glyph_index
,
FT_LOAD_DEFAULT
)
)
{
p_bitmaps
->
p_glyph
=
0
;
p_bitmaps
->
p_outline
=
0
;
p_bitmaps
->
p_shadow
=
0
;
p_bitmaps
->
i_x_advance
=
0
;
p_bitmaps
->
i_y_advance
=
0
;
continue
;
}
if
(
(
p_style
->
i_style_flags
&
STYLE_BOLD
)
&&
!
(
p_face
->
style_flags
&
FT_STYLE_FLAG_BOLD
)
)
FT_GlyphSlot_Embolden
(
p_face
->
glyph
);
if
(
(
p_style
->
i_style_flags
&
STYLE_ITALIC
)
&&
!
(
p_face
->
style_flags
&
FT_STYLE_FLAG_ITALIC
)
)
FT_GlyphSlot_Oblique
(
p_face
->
glyph
);
if
(
FT_Get_Glyph
(
p_face
->
glyph
,
&
p_bitmaps
->
p_glyph
)
)
{
p_bitmaps
->
p_glyph
=
0
;
p_bitmaps
->
p_outline
=
0
;
p_bitmaps
->
p_shadow
=
0
;
p_bitmaps
->
i_x_advance
=
0
;
p_bitmaps
->
i_y_advance
=
0
;
continue
;
}
if
(
p_filter
->
p_sys
->
p_stroker
)
{
p_bitmaps
->
p_outline
=
p_bitmaps
->
p_glyph
;
if
(
FT_Glyph_StrokeBorder
(
&
p_bitmaps
->
p_outline
,
p_filter
->
p_sys
->
p_stroker
,
0
,
0
)
)
p_bitmaps
->
p_outline
=
0
;
}
if
(
p_filter
->
p_sys
->
style
.
i_shadow_alpha
>
0
)
p_bitmaps
->
p_shadow
=
p_bitmaps
->
p_outline
?
p_bitmaps
->
p_outline
:
p_bitmaps
->
p_glyph
;
if
(
b_overwrite_advance
)
{
p_bitmaps
->
i_x_advance
=
p_face
->
glyph
->
advance
.
x
;
p_bitmaps
->
i_y_advance
=
p_face
->
glyph
->
advance
.
y
;
}
}
}
return
VLC_SUCCESS
;
}
static
int
NewLayoutLine
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
,
int
i_start_offset
,
int
i_end_offset
,
line_desc_t
**
pp_line
)
{
if
(
p_paragraph
->
i_size
<=
0
||
p_paragraph
->
i_runs_count
<=
0
||
i_start_offset
>=
i_end_offset
||
i_start_offset
<
0
||
i_start_offset
>=
p_paragraph
->
i_size
||
i_end_offset
<=
0
||
i_end_offset
>
p_paragraph
->
i_size
)
{
msg_Err
(
p_filter
,
"NewLayoutLine() invalid parameters. "
"Paragraph size: %d. Runs count: %d. "
"Start offset: %d. End offset: %d"
,
p_paragraph
->
i_size
,
p_paragraph
->
i_runs_count
,
i_start_offset
,
i_end_offset
);
return
VLC_EGENERIC
;
}
line_desc_t
*
p_line
=
NewLine
(
i_end_offset
-
i_start_offset
);
filter_sys_t
*
p_sys
=
p_filter
->
p_sys
;
int
i_last_run
=
-
1
;
run_desc_t
*
p_run
=
0
;
text_style_t
*
p_style
=
0
;
FT_Face
p_face
=
0
;
FT_Vector
pen
=
{
.
x
=
0
,
.
y
=
0
};
int
i_line_index
=
0
;
int
i_font_width
=
0
;
int
i_ul_offset
=
0
;
int
i_ul_thickness
=
0
;
#ifdef HAVE_FRIBIDI
fribidi_reorder_line
(
0
,
p_paragraph
->
p_types
+
i_start_offset
,
i_end_offset
-
i_start_offset
,
0
,
p_paragraph
->
paragraph_type
,
p_paragraph
->
p_levels
+
i_start_offset
,
0
,
p_paragraph
->
pi_reordered_indices
+
i_start_offset
);
#endif
for
(
int
i
=
i_start_offset
;
i
<
i_end_offset
;
++
i
,
++
i_line_index
)
{
int
i_paragraph_index
;
#ifdef HAVE_FRIBIDI
i_paragraph_index
=
p_paragraph
->
pi_reordered_indices
[
i
];
#else
i_paragraph_index
=
i
;
#endif
line_character_t
*
p_ch
=
p_line
->
p_character
+
i_line_index
;
glyph_bitmaps_t
*
p_bitmaps
=
p_paragraph
->
p_glyph_bitmaps
+
i_paragraph_index
;
if
(
!
p_bitmaps
->
p_glyph
)
{
--
i_line_index
;
continue
;
}
if
(
i_last_run
!=
p_paragraph
->
pi_run_ids
[
i_paragraph_index
]
)
{
i_last_run
=
p_paragraph
->
pi_run_ids
[
i_paragraph_index
];
p_run
=
p_paragraph
->
p_runs
+
i_last_run
;
p_style
=
p_run
->
p_style
;
p_face
=
p_run
->
p_face
;
i_font_width
=
p_style
->
i_style_flags
&
STYLE_HALFWIDTH
?
p_style
->
i_font_size
/
2
:
p_style
->
i_font_size
;
}
FT_Vector
pen_new
=
{
.
x
=
pen
.
x
+
p_paragraph
->
p_glyph_bitmaps
[
i_paragraph_index
].
i_x_offset
,
.
y
=
pen
.
y
+
p_paragraph
->
p_glyph_bitmaps
[
i_paragraph_index
].
i_y_offset
};
FT_Vector
pen_shadow
=
{
.
x
=
pen_new
.
x
+
p_sys
->
f_shadow_vector_x
*
(
i_font_width
<<
6
),
.
y
=
pen_new
.
y
+
p_sys
->
f_shadow_vector_y
*
(
p_style
->
i_font_size
<<
6
)
};
if
(
p_bitmaps
->
p_shadow
)
{
if
(
FT_Glyph_To_Bitmap
(
&
p_bitmaps
->
p_shadow
,
FT_RENDER_MODE_NORMAL
,
&
pen_shadow
,
0
)
)
p_bitmaps
->
p_shadow
=
0
;
else
FT_Glyph_Get_CBox
(
p_bitmaps
->
p_shadow
,
ft_glyph_bbox_pixels
,
&
p_bitmaps
->
shadow_bbox
);
}
if
(
p_bitmaps
->
p_glyph
)
{
if
(
FT_Glyph_To_Bitmap
(
&
p_bitmaps
->
p_glyph
,
FT_RENDER_MODE_NORMAL
,
&
pen_new
,
1
)
)
{
FT_Done_Glyph
(
p_bitmaps
->
p_glyph
);
if
(
p_bitmaps
->
p_outline
)
FT_Done_Glyph
(
p_bitmaps
->
p_outline
);
if
(
p_bitmaps
->
p_shadow
)
FT_Done_Glyph
(
p_bitmaps
->
p_shadow
);
--
i_line_index
;
continue
;
}
else
FT_Glyph_Get_CBox
(
p_bitmaps
->
p_glyph
,
ft_glyph_bbox_pixels
,
&
p_bitmaps
->
glyph_bbox
);
}
if
(
p_bitmaps
->
p_outline
)
{
if
(
FT_Glyph_To_Bitmap
(
&
p_bitmaps
->
p_outline
,
FT_RENDER_MODE_NORMAL
,
&
pen_new
,
1
)
)
{
FT_Done_Glyph
(
p_bitmaps
->
p_outline
);
p_bitmaps
->
p_outline
=
0
;
}
else
FT_Glyph_Get_CBox
(
p_bitmaps
->
p_outline
,
ft_glyph_bbox_pixels
,
&
p_bitmaps
->
outline_bbox
);
}
FixGlyph
(
p_bitmaps
->
p_glyph
,
&
p_bitmaps
->
glyph_bbox
,
p_face
,
&
pen_new
);
if
(
p_bitmaps
->
p_outline
)
FixGlyph
(
p_bitmaps
->
p_outline
,
&
p_bitmaps
->
outline_bbox
,
p_face
,
&
pen_new
);
if
(
p_bitmaps
->
p_shadow
)
FixGlyph
(
p_bitmaps
->
p_shadow
,
&
p_bitmaps
->
shadow_bbox
,
p_face
,
&
pen_shadow
);
int
i_line_offset
=
0
;
int
i_line_thickness
=
0
;
text_style_t
*
p_glyph_style
=
p_paragraph
->
pp_styles
[
i_paragraph_index
];
if
(
p_glyph_style
->
i_style_flags
&
(
STYLE_UNDERLINE
|
STYLE_STRIKEOUT
)
)
{
i_line_offset
=
abs
(
FT_FLOOR
(
FT_MulFix
(
p_face
->
underline_position
,
p_face
->
size
->
metrics
.
y_scale
)
)
);
i_line_thickness
=
abs
(
FT_CEIL
(
FT_MulFix
(
p_face
->
underline_thickness
,
p_face
->
size
->
metrics
.
y_scale
)
)
);
if
(
p_glyph_style
->
i_style_flags
&
STYLE_STRIKEOUT
)
{
/* Move the baseline to make it strikethrough instead of
* underline. That means that strikethrough takes precedence
*/
i_line_offset
-=
abs
(
FT_FLOOR
(
FT_MulFix
(
p_face
->
descender
*
2
,
p_face
->
size
->
metrics
.
y_scale
)
)
);
}
else
if
(
i_line_thickness
>
0
)
{
p_bitmaps
->
glyph_bbox
.
yMin
=
__MIN
(
p_bitmaps
->
glyph_bbox
.
yMin
,
-
i_line_offset
-
i_line_thickness
);
/* The real underline thickness and position are
* updated once the whole line has been parsed */
i_ul_offset
=
__MAX
(
i_ul_offset
,
i_line_offset
);
i_ul_thickness
=
__MAX
(
i_ul_thickness
,
i_line_thickness
);
i_line_thickness
=
-
1
;
}
}
p_ch
->
p_glyph
=
(
FT_BitmapGlyph
)
p_bitmaps
->
p_glyph
;
p_ch
->
p_outline
=
(
FT_BitmapGlyph
)
p_bitmaps
->
p_outline
;
p_ch
->
p_shadow
=
(
FT_BitmapGlyph
)
p_bitmaps
->
p_shadow
;
bool
b_karaoke
=
p_paragraph
->
pi_karaoke_bar
[
i_paragraph_index
]
!=
0
;
p_ch
->
i_color
=
b_karaoke
?
p_paragraph
->
pp_styles
[
i_paragraph_index
]
->
i_karaoke_background_color
|
p_paragraph
->
pp_styles
[
i_paragraph_index
]
->
i_karaoke_background_alpha
<<
24
:
p_paragraph
->
pp_styles
[
i_paragraph_index
]
->
i_font_color
|
p_paragraph
->
pp_styles
[
i_paragraph_index
]
->
i_font_alpha
<<
24
;
p_ch
->
i_line_thickness
=
i_line_thickness
;
p_ch
->
i_line_offset
=
i_line_offset
;
BBoxEnlarge
(
&
p_line
->
bbox
,
&
p_bitmaps
->
glyph_bbox
);
if
(
p_bitmaps
->
p_outline
)
BBoxEnlarge
(
&
p_line
->
bbox
,
&
p_bitmaps
->
outline_bbox
);
if
(
p_bitmaps
->
p_shadow
)
BBoxEnlarge
(
&
p_line
->
bbox
,
&
p_bitmaps
->
shadow_bbox
);
pen
.
x
+=
p_bitmaps
->
i_x_advance
;
pen
.
y
+=
p_bitmaps
->
i_y_advance
;
}
p_line
->
i_width
=
__MAX
(
0
,
p_line
->
bbox
.
xMax
-
p_line
->
bbox
.
xMin
);
p_line
->
i_height
=
__MAX
(
0
,
p_line
->
bbox
.
yMax
-
p_line
->
bbox
.
yMin
);
p_line
->
i_character_count
=
i_line_index
;
if
(
i_ul_thickness
>
0
)
{
for
(
int
i
=
0
;
i
<
p_line
->
i_character_count
;
i
++
)
{
line_character_t
*
ch
=
&
p_line
->
p_character
[
i
];
if
(
ch
->
i_line_thickness
<
0
)
{
ch
->
i_line_offset
=
i_ul_offset
;
ch
->
i_line_thickness
=
i_ul_thickness
;
}
}
}
*
pp_line
=
p_line
;
return
VLC_SUCCESS
;
}
static
int
LayoutParagraph
(
filter_t
*
p_filter
,
paragraph_t
*
p_paragraph
,
int
i_max_width
,
line_desc_t
**
pp_lines
)
{
if
(
p_paragraph
->
i_size
<=
0
||
p_paragraph
->
i_runs_count
<=
0
)
{
msg_Err
(
p_filter
,
"LayoutParagraph() invalid parameters. "
"Paragraph size: %d. Runs count %d"
,
p_paragraph
->
i_size
,
p_paragraph
->
i_runs_count
);
return
VLC_EGENERIC
;
}
int
i_line_start
=
0
;
FT_Pos
i_width
=
0
;
int
i_last_space
=
-
1
;
line_desc_t
*
p_first_line
=
0
;
line_desc_t
**
pp_line
=
&
p_first_line
;
#ifdef HAVE_FRIBIDI
for
(
int
i
=
0
;
i
<
p_paragraph
->
i_size
;
++
i
)
p_paragraph
->
pi_reordered_indices
[
i
]
=
i
;
#endif
for
(
int
i
=
0
;
i
<=
p_paragraph
->
i_size
;
++
i
)
{
if
(
i
==
p_paragraph
->
i_size
)
{
if
(
i_line_start
<
i
)
if
(
NewLayoutLine
(
p_filter
,
p_paragraph
,
i_line_start
,
i
,
pp_line
)
)
goto
error
;
break
;
}
if
(
p_paragraph
->
p_code_points
[
i
]
==
' '
#ifdef HAVE_FRIBIDI
||
p_paragraph
->
p_types
[
i
]
==
FRIBIDI_TYPE_WS
#endif
)
{
if
(
i_line_start
==
i
)
{
/*
* Free orphaned white space glyphs not belonging to any lines.
* At this point p_shadow points to either p_glyph or p_outline,
* so we should not free it explicitly.
*/
if
(
p_paragraph
->
p_glyph_bitmaps
[
i
].
p_glyph
)
FT_Done_Glyph
(
p_paragraph
->
p_glyph_bitmaps
[
i
].
p_glyph
);
if
(
p_paragraph
->
p_glyph_bitmaps
[
i
].
p_outline
)
FT_Done_Glyph
(
p_paragraph
->
p_glyph_bitmaps
[
i
].
p_outline
);
i_line_start
=
i
+
1
;
continue
;
}
if
(
i_last_space
==
i
-
1
)
{
p_paragraph
->
p_glyph_bitmaps
[
i
-
1
].
i_x_advance
=
0
;
i_last_space
=
i
;
continue
;
}
i_last_space
=
i
;
}
i_width
+=
p_paragraph
->
p_glyph_bitmaps
[
i
].
i_x_advance
;
if
(
FT_CEIL
(
i_width
)
>=
i_max_width
)
{
if
(
i_line_start
==
i
)
{
msg_Err
(
p_filter
,
"LayoutParagraph(): Width of single glyph exceeds maximum"
);
goto
error
;
}
int
i_end_offset
;
if
(
i_last_space
>
i_line_start
)
i_end_offset
=
i_last_space
;
else
i_end_offset
=
i
;
if
(
NewLayoutLine
(
p_filter
,
p_paragraph
,
i_line_start
,
i_end_offset
,
pp_line
)
)
goto
error
;
pp_line
=
&
(
*
pp_line
)
->
p_next
;
i_line_start
=
i_end_offset
;
i
=
i_line_start
-
1
;
i_width
=
0
;
}
}
*
pp_lines
=
p_first_line
;
return
VLC_SUCCESS
;
error:
if
(
p_first_line
)
FreeLines
(
p_first_line
);
return
VLC_EGENERIC
;
}
int
LayoutText
(
filter_t
*
p_filter
,
line_desc_t
**
pp_lines
,
FT_BBox
*
p_bbox
,
int
*
pi_max_face_height
,
uni_char_t
*
psz_text
,
text_style_t
**
pp_styles
,
uint32_t
*
pi_k_dates
,
int
i_len
)
{
line_desc_t
*
p_first_line
=
0
;
line_desc_t
**
pp_line
=
&
p_first_line
;
paragraph_t
*
p_paragraph
=
0
;
int
i_paragraph_start
=
0
;
int
i_max_height
=
0
;
for
(
int
i
=
0
;
i
<=
i_len
;
++
i
)
{
if
(
i
==
i_len
||
psz_text
[
i
]
==
'\n'
)
{
if
(
i_paragraph_start
==
i
)
{
i_paragraph_start
=
i
+
1
;
continue
;
}
p_paragraph
=
NewParagraph
(
p_filter
,
i
-
i_paragraph_start
,
psz_text
+
i_paragraph_start
,
pp_styles
+
i_paragraph_start
,
pi_k_dates
?
pi_k_dates
+
i_paragraph_start
:
0
,
20
);
if
(
!
p_paragraph
)
{
if
(
p_first_line
)
FreeLines
(
p_first_line
);
return
VLC_ENOMEM
;
}
#ifdef HAVE_FRIBIDI
if
(
AnalyzeParagraph
(
p_paragraph
)
)
goto
error
;
#endif
if
(
ItemizeParagraph
(
p_filter
,
p_paragraph
)
)
goto
error
;
#if defined HAVE_HARFBUZZ
if
(
ShapeParagraphHarfBuzz
(
p_filter
,
&
p_paragraph
)
)
goto
error
;
if
(
LoadGlyphs
(
p_filter
,
p_paragraph
,
true
,
false
)
)
goto
error
;
#elif defined HAVE_FRIBIDI
if
(
ShapeParagraphFriBidi
(
p_filter
,
p_paragraph
)
)
goto
error
;
if
(
LoadGlyphs
(
p_filter
,
p_paragraph
,
false
,
true
)
)
goto
error
;
if
(
RemoveZeroWidthCharacters
(
p_paragraph
)
)
goto
error
;
if
(
ZeroNsmAdvance
(
p_paragraph
)
)
goto
error
;
#else
if
(
LoadGlyphs
(
p_filter
,
p_paragraph
,
false
,
true
)
)
goto
error
;
#endif
/*
* Set max line width to allow for outline and shadow glyphs,
* and any extra width caused by visual reordering
*/
int
i_max_width
=
(
int
)
p_filter
->
fmt_out
.
video
.
i_visible_width
-
2
*
p_filter
->
p_sys
->
style
.
i_font_size
;
if
(
LayoutParagraph
(
p_filter
,
p_paragraph
,
i_max_width
,
pp_line
)
)
goto
error
;
for
(
;
*
pp_line
;
pp_line
=
&
(
*
pp_line
)
->
p_next
)
i_max_height
=
__MAX
(
i_max_height
,
(
*
pp_line
)
->
i_height
);
i_paragraph_start
=
i
+
1
;
}
}
int
i_base_line
=
0
;
FT_BBox
bbox
=
{
.
xMin
=
INT_MAX
,
.
yMin
=
INT_MAX
,
.
xMax
=
INT_MIN
,
.
yMax
=
INT_MIN
};
for
(
line_desc_t
*
p_line
=
p_first_line
;
p_line
;
p_line
=
p_line
->
p_next
)
{
p_line
->
i_base_line
=
i_base_line
;
p_line
->
bbox
.
yMin
-=
i_base_line
;
p_line
->
bbox
.
yMax
-=
i_base_line
;
BBoxEnlarge
(
&
bbox
,
&
p_line
->
bbox
);
i_base_line
+=
i_max_height
;
}
*
pp_lines
=
p_first_line
;
*
p_bbox
=
bbox
;
*
pi_max_face_height
=
i_max_height
;
return
VLC_SUCCESS
;
error:
if
(
p_first_line
)
FreeLines
(
p_first_line
);
if
(
p_paragraph
)
FreeParagraph
(
p_paragraph
);
return
VLC_EGENERIC
;
}
modules/text_renderer/text_layout.h
0 → 100644
View file @
57cb3e53
/*****************************************************************************
* text_layout.h : Text shaping and layout
*****************************************************************************
* Copyright (C) 2015 VLC authors and VideoLAN
* $Id$
*
* Authors: Salah-Eddin Shaban <salshaaban@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation, Inc.,
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
typedef
struct
{
FT_BitmapGlyph
p_glyph
;
FT_BitmapGlyph
p_outline
;
FT_BitmapGlyph
p_shadow
;
uint32_t
i_color
;
/* ARGB color */
int
i_line_offset
;
/* underline/strikethrough offset */
int
i_line_thickness
;
/* underline/strikethrough thickness */
}
line_character_t
;
typedef
struct
line_desc_t
line_desc_t
;
struct
line_desc_t
{
line_desc_t
*
p_next
;
int
i_width
;
int
i_height
;
int
i_base_line
;
int
i_character_count
;
line_character_t
*
p_character
;
FT_BBox
bbox
;
};
void
FreeLine
(
line_desc_t
*
p_line
);
void
FreeLines
(
line_desc_t
*
p_lines
);
line_desc_t
*
NewLine
(
int
i_count
);
int
LayoutText
(
filter_t
*
p_filter
,
line_desc_t
**
pp_lines
,
FT_BBox
*
p_bbox
,
int
*
pi_max_face_height
,
uni_char_t
*
psz_text
,
text_style_t
**
pp_styles
,
uint32_t
*
pi_k_dates
,
int
i_len
);
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment