39907a39e702a2d9da40a4fa3d7318c2bed86a69
[vlc.git] / modules / text_renderer / svg.c
1 /*****************************************************************************
2  * svg.c : Put SVG on the video
3  *****************************************************************************
4  * Copyright (C) 2002, 2003 VLC authors and VideoLAN
5  *
6  * Authors: Olivier Aubert <oaubert@lisi.univ-lyon1.fr>
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
22
23 /*****************************************************************************
24  * Preamble
25  *****************************************************************************/
26
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_fs.h>
34 #include <vlc_filter.h>
35 #include <vlc_subpicture.h>
36 #include <vlc_strings.h>
37
38 #include <sys/types.h>
39 #include <unistd.h>
40
41 #include <glib.h>
42 #include <glib/gstdio.h>
43 #include <glib-object.h>                                  /* g_object_unref( ) */
44 #include <librsvg/rsvg.h>
45 #include <cairo/cairo.h>
46
47 /*****************************************************************************
48  * Local prototypes
49  *****************************************************************************/
50 static int  Create    ( filter_t * );
51 static void Destroy   ( filter_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 * );
55
56 typedef struct
57 {
58     char *psz_file_template;
59     const char *psz_token;
60 } filter_sys_t;
61
62 /*****************************************************************************
63  * Module descriptor
64  *****************************************************************************/
65
66 #define SVG_TEMPLATE_BODY_TOKEN   "<!--$SVGBODY$-->"
67 #define SVG_TEMPLATE_BODY_TOKEN_L 16
68
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" )
72
73 vlc_module_begin ()
74     set_category( CAT_INPUT )
75     set_subcategory( SUBCAT_INPUT_SCODEC )
76     add_shortcut( "svg" )
77     add_string( "svg-template-file", "", TEMPLATE_TEXT, TEMPLATE_LONGTEXT, true )
78     set_callback_text_renderer( Create, 99 )
79 vlc_module_end ()
80
81 static void svg_RescaletoFit  ( filter_t *, int *width, int *height, float * );
82 static picture_t * svg_RenderPicture ( filter_t *p_filter, const char * );
83
84 static void svg_LoadTemplate( filter_t *p_filter )
85 {
86     filter_sys_t *p_sys = p_filter->p_sys;
87     char *psz_template = NULL;
88     char *psz_filename = var_InheritString( p_filter, "svg-template-file" );
89     if( psz_filename && psz_filename[0] )
90     {
91         /* Read the template */
92         FILE *file = vlc_fopen( psz_filename, "rt" );
93         if( !file )
94         {
95             msg_Warn( p_filter, "SVG template file %s does not exist.",
96                                          psz_filename );
97         }
98         else
99         {
100             struct stat s;
101             if( fstat( fileno( file ), &s ) || ((signed)s.st_size) < 0 )
102             {
103                 msg_Err( p_filter, "SVG template invalid" );
104             }
105             else
106             {
107                 msg_Dbg( p_filter, "reading %ld bytes from template %s",
108                          (unsigned long)s.st_size, psz_filename );
109
110                 psz_template = malloc( s.st_size + 1 );
111                 if( psz_template )
112                 {
113                     psz_template[ s.st_size ] = 0;
114                     ssize_t i_read = fread( psz_template, s.st_size, 1, file );
115                     if( i_read != 1 )
116                     {
117                         free( psz_template );
118                         psz_template = NULL;
119                     }
120                 }
121             }
122             fclose( file );
123         }
124     }
125     free( psz_filename );
126
127     if( psz_template )
128     {
129         p_sys->psz_token = strstr( psz_template, SVG_TEMPLATE_BODY_TOKEN );
130         if( !p_sys->psz_token )
131         {
132             msg_Err( p_filter, "'%s' not found in SVG template", SVG_TEMPLATE_BODY_TOKEN );
133             free( psz_template );
134         }
135         else *((char*)p_sys->psz_token) = 0;
136     }
137
138     p_sys->psz_file_template = psz_template;
139 }
140
141 static char *svg_GetDocument( filter_t *p_filter, int i_width, int i_height, const char *psz_body )
142 {
143     filter_sys_t *p_sys = p_filter->p_sys;
144     char *psz_result;
145     VLC_UNUSED(i_width);VLC_UNUSED(i_height);
146
147     if( p_sys->psz_file_template )
148     {
149         if( asprintf( &psz_result, "%s%s%s",
150                       p_sys->psz_file_template,
151                       psz_body,
152                       &p_sys->psz_token[SVG_TEMPLATE_BODY_TOKEN_L] ) < 0 )
153             psz_result = NULL;
154     }
155     else
156     {
157         /* Either there was no file, or there was an error.
158            Use the default value */
159         const char *psz_temp = "<?xml version='1.0' encoding='UTF-8' standalone='no'?>"
160                     "<svg preserveAspectRatio='xMinYMin meet'>" // viewBox='0 0 %d %d'>"
161                     "<rect fill='none' width='100%%' height='100%%'></rect>"
162                     "<text fill='white' font-family='sans-serif' font-size='32px'>%s</text>"
163                     "</svg>";
164         if( asprintf( &psz_result, psz_temp, /*i_width, i_height,*/ psz_body ) < 0 )
165             psz_result = NULL;
166     }
167
168     return psz_result;
169 }
170
171 static const struct vlc_filter_operations filter_ops = {
172     .render = RenderText, .close = Destroy,
173 };
174
175 /*****************************************************************************
176  * Create: allocates svg video thread output method
177  *****************************************************************************
178  * This function allocates and initializes a  vout method.
179  *****************************************************************************/
180
181 static int Create( filter_t *p_filter )
182 {
183     filter_sys_t *p_sys = calloc( 1, sizeof(*p_sys) );
184     if( !p_sys )
185         return VLC_ENOMEM;
186     p_filter->p_sys = p_sys;
187
188     p_filter->ops = &filter_ops;
189     svg_LoadTemplate( p_filter );
190
191 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
192     g_type_init( );
193 #endif
194
195     return VLC_SUCCESS;
196 }
197
198 /*****************************************************************************
199  * Destroy: destroy Clone video thread output method
200  *****************************************************************************
201  * Clean up all data and library connections
202  *****************************************************************************/
203 static void Destroy( filter_t *p_filter )
204 {
205     filter_sys_t *p_sys = p_filter->p_sys;
206 #if (GLIB_MAJOR_VERSION < 2 || GLIB_MINOR_VERSION < 36)
207     rsvg_term();
208 #endif
209     free( p_sys->psz_file_template );
210     free( p_sys );
211 }
212
213 static void svg_RescaletoFit( filter_t *p_filter, int *width, int *height, float *scale )
214 {
215     *scale = 1.0;
216
217     if( *width > 0 && *height > 0 )
218     {
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);
221
222         if( (unsigned)*height > p_filter->fmt_out.video.i_visible_height )
223         {
224             float y_scale = (1.0 * p_filter->fmt_out.video.i_visible_height / *height);
225             if( y_scale < *scale )
226                 *scale = y_scale;
227         }
228
229         *width *= *scale;
230         *height *= *scale;
231     }
232 }
233
234 static picture_t * svg_RenderPicture( filter_t *p_filter,
235                                       const char *psz_svgdata )
236 {
237     RsvgHandle *p_handle;
238     GError *error = NULL;
239
240     p_handle = rsvg_handle_new_from_data( (const guint8 *)psz_svgdata,
241                                           strlen( psz_svgdata ), &error );
242     if( !p_handle )
243     {
244         msg_Err( p_filter, "error while rendering SVG: %s", error->message );
245         return NULL;
246     }
247
248     RsvgDimensionData dim;
249     rsvg_handle_get_dimensions( p_handle, &dim );
250     float scale;
251     svg_RescaletoFit( p_filter, &dim.width, &dim.height, &scale );
252
253     /* Create a new subpicture region */
254     video_format_t fmt;
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;
260
261     picture_t *p_picture = picture_NewFromFormat( &fmt );
262     if( !p_picture )
263     {
264         video_format_Clean( &fmt );
265         g_object_unref( G_OBJECT( p_handle ) );
266         return NULL;
267     }
268     memset( p_picture->p[0].p_pixels, 0x00, p_picture->p[0].i_pitch * p_picture->p[0].i_lines );
269
270     cairo_surface_t* surface = cairo_image_surface_create_for_data( p_picture->p->p_pixels,
271                                                                     CAIRO_FORMAT_ARGB32,
272                                                                     fmt.i_width, fmt.i_height,
273                                                                     p_picture->p[0].i_pitch );
274     if( !surface )
275     {
276         g_object_unref( G_OBJECT( p_handle ) );
277         picture_Release( p_picture );
278         return NULL;
279     }
280
281     cairo_t *cr = cairo_create( surface );
282     if( !cr )
283     {
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 );
288         return NULL;
289     }
290
291     if( ! rsvg_handle_render_cairo( p_handle, cr ) )
292     {
293         msg_Err( p_filter, "error while rendering SVG" );
294         cairo_destroy( cr );
295         cairo_surface_destroy( surface );
296         g_object_unref( G_OBJECT( p_handle ) );
297         picture_Release( p_picture );
298         return NULL;
299     }
300
301     cairo_destroy( cr );
302     cairo_surface_destroy( surface );
303     g_object_unref( G_OBJECT( p_handle ) );
304
305     return p_picture;
306 }
307
308 static char * SegmentsToSVG( text_segment_t *p_segment, int i_height, int *pi_total_size )
309 {
310     char *psz_result = NULL;
311
312     i_height = 6 * i_height / 100;
313     *pi_total_size = 0;
314
315     for( ; p_segment; p_segment = p_segment->p_next )
316     {
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 : "",
321                                     i_height,
322                                     psz_encoded ) < 0 )
323             psz_result = NULL;
324         free( psz_prev );
325         free( psz_encoded );
326
327         *pi_total_size += i_height;
328     }
329
330     return psz_result;
331 }
332
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 )
336 {
337     /* Sanity check */
338     if( !p_region_in || !p_region_out || !p_region_in->p_text )
339         return VLC_EGENERIC;
340
341     for( size_t i=0; p_chroma_list[i]; i++ )
342     {
343         if( p_chroma_list[i] == VLC_CODEC_BGRA )
344             break;
345         if( p_chroma_list[i] == 0 )
346             return VLC_EGENERIC;
347     }
348
349     p_region_out->i_x = p_region_in->i_x;
350     p_region_out->i_y = p_region_in->i_y;
351
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;
355
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;
359
360     if( i_height == 0 || i_width == 0 )
361         return VLC_EGENERIC;
362
363     char *psz_svg;
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" ) )
367     {
368         psz_svg = strdup( p_region_in->p_text->psz_text );
369     }
370     else
371     {
372         /* Data is text. Convert to SVG */
373         int i_total;
374         psz_svg = SegmentsToSVG( p_region_in->p_text, i_height, &i_total );
375         if( psz_svg )
376         {
377             char *psz_doc = svg_GetDocument( p_filter, i_width, i_total, psz_svg );
378             free( psz_svg );
379             psz_svg = psz_doc;
380         }
381     }
382
383     if( !psz_svg )
384         return VLC_EGENERIC;
385
386     picture_t *p_picture = svg_RenderPicture( p_filter, psz_svg );
387
388     free( psz_svg );
389
390     if (p_picture)
391     {
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 );
395         return VLC_SUCCESS;
396     }
397     return VLC_EGENERIC;
398 }