1 /*****************************************************************************
2 * svg.c : Put SVG on the video
3 *****************************************************************************
4 * Copyright (C) 2002, 2003 VLC authors and VideoLAN
6 * Authors: Olivier Aubert <oaubert@lisi.univ-lyon1.fr>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option ) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_subpicture.h>
36 #include <vlc_strings.h>
38 #include <sys/types.h>
42 #include <glib/gstdio.h>
43 #include <glib-object.h> /* g_object_unref( ) */
44 #include <librsvg/rsvg.h>
45 #include <cairo/cairo.h>
47 /*****************************************************************************
49 *****************************************************************************/
50 static int Create ( vlc_object_t
* );
51 static void Destroy ( vlc_object_t
* );
52 static int RenderText( filter_t
*p_filter
, subpicture_region_t
*p_region_out
,
53 subpicture_region_t
*p_region_in
,
54 const vlc_fourcc_t
* );
58 char *psz_file_template
;
59 const char *psz_token
;
62 /*****************************************************************************
64 *****************************************************************************/
66 #define SVG_TEMPLATE_BODY_TOKEN "<!--$SVGBODY$-->"
67 #define SVG_TEMPLATE_BODY_TOKEN_L 16
69 #define TEMPLATE_TEXT N_( "SVG template file" )
70 #define TEMPLATE_LONGTEXT N_( "Location of a file holding a SVG template "\
71 "for automatic string conversion" )
74 set_category( CAT_INPUT
)
75 set_category( SUBCAT_INPUT_SCODEC
)
76 set_capability( "text renderer", 99 )
78 add_string( "svg-template-file", "", TEMPLATE_TEXT
, TEMPLATE_LONGTEXT
, true )
79 set_callbacks( Create
, Destroy
)
82 static void svg_RescaletoFit ( filter_t
*, int *width
, int *height
, float * );
83 static picture_t
* svg_RenderPicture ( filter_t
*p_filter
, const char * );
85 static void svg_LoadTemplate( filter_t
*p_filter
)
87 filter_sys_t
*p_sys
= p_filter
->p_sys
;
88 char *psz_template
= NULL
;
89 char *psz_filename
= var_InheritString( p_filter
, "svg-template-file" );
90 if( psz_filename
&& psz_filename
[0] )
92 /* Read the template */
93 FILE *file
= vlc_fopen( psz_filename
, "rt" );
96 msg_Warn( p_filter
, "SVG template file %s does not exist.",
102 if( fstat( fileno( file
), &s
) || ((signed)s
.st_size
) < 0 )
104 msg_Err( p_filter
, "SVG template invalid" );
108 msg_Dbg( p_filter
, "reading %ld bytes from template %s",
109 (unsigned long)s
.st_size
, psz_filename
);
111 psz_template
= malloc( s
.st_size
+ 1 );
114 psz_template
[ s
.st_size
] = 0;
115 ssize_t i_read
= fread( psz_template
, s
.st_size
, 1, file
);
118 free( psz_template
);
126 free( psz_filename
);
130 p_sys
->psz_token
= strstr( psz_template
, SVG_TEMPLATE_BODY_TOKEN
);
131 if( !p_sys
->psz_token
)
133 msg_Err( p_filter
, "'%s' not found in SVG template", SVG_TEMPLATE_BODY_TOKEN
);
134 free( psz_template
);
136 else *((char*)p_sys
->psz_token
) = 0;
139 p_sys
->psz_file_template
= psz_template
;
142 static char *svg_GetDocument( filter_t
*p_filter
, int i_width
, int i_height
, const char *psz_body
)
144 filter_sys_t
*p_sys
= p_filter
->p_sys
;
146 VLC_UNUSED(i_width
);VLC_UNUSED(i_height
);
148 if( p_sys
->psz_file_template
)
150 if( asprintf( &psz_result
, "%s%s%s",
151 p_sys
->psz_file_template
,
153 &p_sys
->psz_token
[SVG_TEMPLATE_BODY_TOKEN_L
] ) < 0 )
158 /* Either there was no file, or there was an error.
159 Use the default value */
160 const char *psz_temp
= "<?xml version='1.0' encoding='UTF-8' standalone='no'?>"
161 "<svg preserveAspectRatio='xMinYMin meet'>" // viewBox='0 0 %d %d'>"
162 "<rect fill='none' width='100%%' height='100%%'></rect>"
163 "<text fill='white' font-family='sans-serif' font-size='32px'>%s</text>"
165 if( asprintf( &psz_result
, psz_temp
, /*i_width, i_height,*/ psz_body
) < 0 )
172 /*****************************************************************************
173 * Create: allocates svg video thread output method
174 *****************************************************************************
175 * This function allocates and initializes a vout method.
176 *****************************************************************************/
178 static int Create( vlc_object_t
*p_this
)
180 filter_t
*p_filter
= ( filter_t
* )p_this
;
182 filter_sys_t
*p_sys
= calloc( 1, sizeof(*p_sys
) );
185 p_filter
->p_sys
= p_sys
;
187 p_filter
->pf_render
= RenderText
;
188 svg_LoadTemplate( p_filter
);
190 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
197 /*****************************************************************************
198 * Destroy: destroy Clone video thread output method
199 *****************************************************************************
200 * Clean up all data and library connections
201 *****************************************************************************/
202 static void Destroy( vlc_object_t
*p_this
)
204 filter_t
*p_filter
= ( filter_t
* )p_this
;
205 filter_sys_t
*p_sys
= p_filter
->p_sys
;
206 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
209 free( p_sys
->psz_file_template
);
213 static void svg_RescaletoFit( filter_t
*p_filter
, int *width
, int *height
, float *scale
)
217 if( *width
> 0 && *height
> 0 )
219 if( (unsigned)*width
> p_filter
->fmt_out
.video
.i_visible_width
)
220 *scale
= (1.0 * p_filter
->fmt_out
.video
.i_visible_width
/ *width
);
222 if( (unsigned)*height
> p_filter
->fmt_out
.video
.i_visible_height
)
224 float y_scale
= (1.0 * p_filter
->fmt_out
.video
.i_visible_height
/ *height
);
225 if( y_scale
< *scale
)
234 static picture_t
* svg_RenderPicture( filter_t
*p_filter
,
235 const char *psz_svgdata
)
237 RsvgHandle
*p_handle
;
238 GError
*error
= NULL
;
240 p_handle
= rsvg_handle_new_from_data( (const guint8
*)psz_svgdata
,
241 strlen( psz_svgdata
), &error
);
244 msg_Err( p_filter
, "error while rendering SVG: %s", error
->message
);
248 RsvgDimensionData dim
;
249 rsvg_handle_get_dimensions( p_handle
, &dim
);
251 svg_RescaletoFit( p_filter
, &dim
.width
, &dim
.height
, &scale
);
253 /* Create a new subpicture region */
255 video_format_Init( &fmt
, VLC_CODEC_BGRA
); /* CAIRO_FORMAT_ARGB32 == VLC_CODEC_BGRA, go figure */
256 fmt
.i_bits_per_pixel
= 32;
257 fmt
.i_chroma
= VLC_CODEC_BGRA
;
258 fmt
.i_width
= fmt
.i_visible_width
= dim
.width
;
259 fmt
.i_height
= fmt
.i_visible_height
= dim
.height
;
261 picture_t
*p_picture
= picture_NewFromFormat( &fmt
);
264 video_format_Clean( &fmt
);
265 g_object_unref( G_OBJECT( p_handle
) );
268 memset( p_picture
->p
[0].p_pixels
, 0x00, p_picture
->p
[0].i_pitch
* p_picture
->p
[0].i_lines
);
270 cairo_surface_t
* surface
= cairo_image_surface_create_for_data( p_picture
->p
->p_pixels
,
272 fmt
.i_width
, fmt
.i_height
,
273 p_picture
->p
[0].i_pitch
);
276 g_object_unref( G_OBJECT( p_handle
) );
277 picture_Release( p_picture
);
281 cairo_t
*cr
= cairo_create( surface
);
284 msg_Err( p_filter
, "error while creating cairo surface" );
285 cairo_surface_destroy( surface
);
286 g_object_unref( G_OBJECT( p_handle
) );
287 picture_Release( p_picture
);
291 if( ! rsvg_handle_render_cairo( p_handle
, cr
) )
293 msg_Err( p_filter
, "error while rendering SVG" );
295 cairo_surface_destroy( surface
);
296 g_object_unref( G_OBJECT( p_handle
) );
297 picture_Release( p_picture
);
302 cairo_surface_destroy( surface
);
303 g_object_unref( G_OBJECT( p_handle
) );
308 static char * SegmentsToSVG( text_segment_t
*p_segment
, int i_height
, int *pi_total_size
)
310 char *psz_result
= NULL
;
312 i_height
= 6 * i_height
/ 100;
315 for( ; p_segment
; p_segment
= p_segment
->p_next
)
317 char *psz_prev
= psz_result
;
318 char *psz_encoded
= vlc_xml_encode( p_segment
->psz_text
);
319 if( asprintf( &psz_result
, "%s<tspan x='0' dy='%upx'>%s</tspan>\n",
320 (psz_prev
) ? psz_prev
: "",
327 *pi_total_size
+= i_height
;
333 static int RenderText( filter_t
*p_filter
, subpicture_region_t
*p_region_out
,
334 subpicture_region_t
*p_region_in
,
335 const vlc_fourcc_t
*p_chroma_list
)
338 if( !p_region_in
|| !p_region_out
|| !p_region_in
->p_text
)
341 for( size_t i
=0; p_chroma_list
[i
]; i
++ )
343 if( p_chroma_list
[i
] == VLC_CODEC_BGRA
)
345 if( p_chroma_list
[i
] == 0 )
349 p_region_out
->i_x
= p_region_in
->i_x
;
350 p_region_out
->i_y
= p_region_in
->i_y
;
352 unsigned i_width
= p_filter
->fmt_out
.video
.i_visible_width
;
353 if( (unsigned) p_region_out
->i_x
<= i_width
)
354 i_width
-= p_region_out
->i_x
;
356 unsigned i_height
= p_filter
->fmt_out
.video
.i_visible_height
;
357 if( (unsigned) p_region_out
->i_y
<= i_height
)
358 i_height
-= p_region_out
->i_y
;
360 if( i_height
== 0 || i_width
== 0 )
364 /* Check if the data is SVG or pure text. In the latter case,
365 convert the text to SVG. FIXME: find a better test */
366 if( p_region_in
->p_text
&& strstr( p_region_in
->p_text
->psz_text
, "<svg" ) )
368 psz_svg
= strdup( p_region_in
->p_text
->psz_text
);
372 /* Data is text. Convert to SVG */
374 psz_svg
= SegmentsToSVG( p_region_in
->p_text
, i_height
, &i_total
);
377 char *psz_doc
= svg_GetDocument( p_filter
, i_width
, i_total
, psz_svg
);
386 picture_t
*p_picture
= svg_RenderPicture( p_filter
, psz_svg
);
392 p_region_out
->p_picture
= p_picture
;
393 video_format_Clean( &p_region_out
->fmt
);
394 video_format_Copy( &p_region_out
->fmt
, &p_picture
->format
);