pango: add missing break
[platform/upstream/gstreamer.git] / ext / pango / gstbasetextoverlay.c
1 /* GStreamer
2  * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
3  * Copyright (C) <2003> David Schleef <ds@schleef.org>
4  * Copyright (C) <2006> Julien Moutte <julien@moutte.net>
5  * Copyright (C) <2006> Zeeshan Ali <zeeshan.ali@nokia.com>
6  * Copyright (C) <2006-2008> Tim-Philipp Müller <tim centricular net>
7  * Copyright (C) <2009> Young-Ho Cha <ganadist@gmail.com>
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Library General Public
11  * License as published by the Free Software Foundation; either
12  * version 2 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Library General Public License for more details.
18  *
19  * You should have received a copy of the GNU Library General Public
20  * License along with this library; if not, write to the
21  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22  * Boston, MA 02111-1307, USA.
23  */
24
25 /**
26  * SECTION:element-textoverlay
27  * @see_also: #GstTextRender, #GstClockOverlay, #GstTimeOverlay, #GstSubParse
28  *
29  * This plugin renders text on top of a video stream. This can be either
30  * static text or text from buffers received on the text sink pad, e.g.
31  * as produced by the subparse element. If the text sink pad is not linked,
32  * the text set via the "text" property will be rendered. If the text sink
33  * pad is linked, text will be rendered as it is received on that pad,
34  * honouring and matching the buffer timestamps of both input streams.
35  *
36  * The text can contain newline characters and text wrapping is enabled by
37  * default.
38  *
39  * <refsect2>
40  * <title>Example launch lines</title>
41  * |[
42  * gst-launch -v videotestsrc ! textoverlay text="Room A" valign=top halign=left ! xvimagesink
43  * ]| Here is a simple pipeline that displays a static text in the top left
44  * corner of the video picture
45  * |[
46  * gst-launch -v filesrc location=subtitles.srt ! subparse ! txt.   videotestsrc ! timeoverlay ! textoverlay name=txt shaded-background=yes ! xvimagesink
47  * ]| Here is another pipeline that displays subtitles from an .srt subtitle
48  * file, centered at the bottom of the picture and with a rectangular shading
49  * around the text in the background:
50  * <para>
51  * If you do not have such a subtitle file, create one looking like this
52  * in a text editor:
53  * |[
54  * 1
55  * 00:00:03,000 --> 00:00:05,000
56  * Hello? (3-5s)
57  *
58  * 2
59  * 00:00:08,000 --> 00:00:13,000
60  * Yes, this is a subtitle. Don&apos;t
61  * you like it? (8-13s)
62  *
63  * 3
64  * 00:00:18,826 --> 00:01:02,886
65  * Uh? What are you talking about?
66  * I don&apos;t understand  (18-62s)
67  * ]|
68  * </para>
69  * </refsect2>
70  */
71
72 /* FIXME: alloc segment as part of instance struct */
73
74 #ifdef HAVE_CONFIG_H
75 #include <config.h>
76 #endif
77
78 #include <gst/video/video.h>
79 #include <gst/video/gstvideometa.h>
80
81 #include "gstbasetextoverlay.h"
82 #include "gsttextoverlay.h"
83 #include "gsttimeoverlay.h"
84 #include "gstclockoverlay.h"
85 #include "gsttextrender.h"
86 #include <string.h>
87
88 /* FIXME:
89  *  - use proper strides and offset for I420
90  *  - if text is wider than the video picture, it does not get
91  *    clipped properly during blitting (if wrapping is disabled)
92  *  - make 'shading_value' a property (or enum:  light/normal/dark/verydark)?
93  */
94
95 GST_DEBUG_CATEGORY (pango_debug);
96 #define GST_CAT_DEFAULT pango_debug
97
98 #define DEFAULT_PROP_TEXT       ""
99 #define DEFAULT_PROP_SHADING    FALSE
100 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
101 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
102 #define DEFAULT_PROP_XPAD       25
103 #define DEFAULT_PROP_YPAD       25
104 #define DEFAULT_PROP_DELTAX     0
105 #define DEFAULT_PROP_DELTAY     0
106 #define DEFAULT_PROP_XPOS       0.5
107 #define DEFAULT_PROP_YPOS       0.5
108 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
109 #define DEFAULT_PROP_FONT_DESC  ""
110 #define DEFAULT_PROP_SILENT     FALSE
111 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
112 #define DEFAULT_PROP_WAIT_TEXT  TRUE
113 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
114 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
115 #define DEFAULT_PROP_COLOR      0xffffffff
116 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
117
118 /* make a property of me */
119 #define DEFAULT_SHADING_VALUE    -80
120
121 #define MINIMUM_OUTLINE_OFFSET 1.0
122 #define DEFAULT_SCALE_BASIS    640
123
124 #define COMP_Y(ret, r, g, b) \
125 { \
126    ret = (int) (((19595 * r) >> 16) + ((38470 * g) >> 16) + ((7471 * b) >> 16)); \
127    ret = CLAMP (ret, 0, 255); \
128 }
129
130 #define COMP_U(ret, r, g, b) \
131 { \
132    ret = (int) (-((11059 * r) >> 16) - ((21709 * g) >> 16) + ((32768 * b) >> 16) + 128); \
133    ret = CLAMP (ret, 0, 255); \
134 }
135
136 #define COMP_V(ret, r, g, b) \
137 { \
138    ret = (int) (((32768 * r) >> 16) - ((27439 * g) >> 16) - ((5329 * b) >> 16) + 128); \
139    ret = CLAMP (ret, 0, 255); \
140 }
141
142 #define BLEND(ret, alpha, v0, v1) \
143 { \
144         ret = (v0 * alpha + v1 * (255 - alpha)) / 255; \
145 }
146
147 #define OVER(ret, alphaA, Ca, alphaB, Cb, alphaNew)     \
148 { \
149     gint _tmp; \
150     _tmp = (Ca * alphaA + Cb * alphaB * (255 - alphaA) / 255) / alphaNew; \
151     ret = CLAMP (_tmp, 0, 255); \
152 }
153
154 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
155 # define CAIRO_ARGB_A 3
156 # define CAIRO_ARGB_R 2
157 # define CAIRO_ARGB_G 1
158 # define CAIRO_ARGB_B 0
159 #else
160 # define CAIRO_ARGB_A 0
161 # define CAIRO_ARGB_R 1
162 # define CAIRO_ARGB_G 2
163 # define CAIRO_ARGB_B 3
164 #endif
165
166 enum
167 {
168   PROP_0,
169   PROP_TEXT,
170   PROP_SHADING,
171   PROP_HALIGNMENT,
172   PROP_VALIGNMENT,
173   PROP_XPAD,
174   PROP_YPAD,
175   PROP_DELTAX,
176   PROP_DELTAY,
177   PROP_XPOS,
178   PROP_YPOS,
179   PROP_WRAP_MODE,
180   PROP_FONT_DESC,
181   PROP_SILENT,
182   PROP_LINE_ALIGNMENT,
183   PROP_WAIT_TEXT,
184   PROP_AUTO_ADJUST_SIZE,
185   PROP_VERTICAL_RENDER,
186   PROP_COLOR,
187   PROP_SHADOW,
188   PROP_OUTLINE_COLOR,
189   PROP_LAST
190 };
191
192 #define VIDEO_FORMATS "{ BGRx, RGBx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, \
193     I420, YV12, AYUV, YUY2, UYVY, v308, v210, v216, Y41B, Y42B, Y444, \
194     Y800, Y16, NV12, NV21, UYVP, A420, YUV9, IYU1 }"
195
196 static GstStaticPadTemplate src_template_factory =
197 GST_STATIC_PAD_TEMPLATE ("src",
198     GST_PAD_SRC,
199     GST_PAD_ALWAYS,
200     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
201     );
202
203 static GstStaticPadTemplate video_sink_template_factory =
204 GST_STATIC_PAD_TEMPLATE ("video_sink",
205     GST_PAD_SINK,
206     GST_PAD_ALWAYS,
207     GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS))
208     );
209
210 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
211 static GType
212 gst_base_text_overlay_valign_get_type (void)
213 {
214   static GType base_text_overlay_valign_type = 0;
215   static const GEnumValue base_text_overlay_valign[] = {
216     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
217     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
218     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
219     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position", "position"},
220     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
221     {0, NULL, NULL},
222   };
223
224   if (!base_text_overlay_valign_type) {
225     base_text_overlay_valign_type =
226         g_enum_register_static ("GstBaseTextOverlayVAlign",
227         base_text_overlay_valign);
228   }
229   return base_text_overlay_valign_type;
230 }
231
232 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
233 static GType
234 gst_base_text_overlay_halign_get_type (void)
235 {
236   static GType base_text_overlay_halign_type = 0;
237   static const GEnumValue base_text_overlay_halign[] = {
238     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
239     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
240     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
241     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position", "position"},
242     {0, NULL, NULL},
243   };
244
245   if (!base_text_overlay_halign_type) {
246     base_text_overlay_halign_type =
247         g_enum_register_static ("GstBaseTextOverlayHAlign",
248         base_text_overlay_halign);
249   }
250   return base_text_overlay_halign_type;
251 }
252
253
254 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
255 static GType
256 gst_base_text_overlay_wrap_mode_get_type (void)
257 {
258   static GType base_text_overlay_wrap_mode_type = 0;
259   static const GEnumValue base_text_overlay_wrap_mode[] = {
260     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
261     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
262     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
263     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
264     {0, NULL, NULL},
265   };
266
267   if (!base_text_overlay_wrap_mode_type) {
268     base_text_overlay_wrap_mode_type =
269         g_enum_register_static ("GstBaseTextOverlayWrapMode",
270         base_text_overlay_wrap_mode);
271   }
272   return base_text_overlay_wrap_mode_type;
273 }
274
275 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
276 static GType
277 gst_base_text_overlay_line_align_get_type (void)
278 {
279   static GType base_text_overlay_line_align_type = 0;
280   static const GEnumValue base_text_overlay_line_align[] = {
281     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
282     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
283     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
284     {0, NULL, NULL}
285   };
286
287   if (!base_text_overlay_line_align_type) {
288     base_text_overlay_line_align_type =
289         g_enum_register_static ("GstBaseTextOverlayLineAlign",
290         base_text_overlay_line_align);
291   }
292   return base_text_overlay_line_align_type;
293 }
294
295 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
296 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
297 #define GST_BASE_TEXT_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
298 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
299 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
300 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
301 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
302
303 static GstElementClass *parent_class = NULL;
304 static void gst_base_text_overlay_base_init (gpointer g_class);
305 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
306 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
307     GstBaseTextOverlayClass * klass);
308
309 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
310     element, GstStateChange transition);
311
312 static GstCaps *gst_base_text_overlay_getcaps (GstPad * pad,
313     GstBaseTextOverlay * overlay, GstCaps * filter);
314 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
315     GstCaps * caps);
316 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
317     GstCaps * caps);
318 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
319     GstObject * parent, GstEvent * event);
320 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
321     GstObject * parent, GstQuery * query);
322
323 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
324     GstObject * parent, GstEvent * event);
325 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
326     GstObject * parent, GstQuery * query);
327 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
328     GstObject * parent, GstBuffer * buffer);
329
330 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
331     GstObject * parent, GstEvent * event);
332 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
333     GstObject * parent, GstBuffer * buffer);
334 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
335     GstPad * peer);
336 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad);
337 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
338 static void gst_base_text_overlay_update_render_mode (GstBaseTextOverlay *
339     overlay);
340
341 static void gst_base_text_overlay_finalize (GObject * object);
342 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
343     const GValue * value, GParamSpec * pspec);
344 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
345     GValue * value, GParamSpec * pspec);
346 static void
347 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
348     PangoFontDescription * desc);
349
350 GType
351 gst_base_text_overlay_get_type (void)
352 {
353   static GType type = 0;
354
355   if (g_once_init_enter ((gsize *) & type)) {
356     static const GTypeInfo info = {
357       sizeof (GstBaseTextOverlayClass),
358       (GBaseInitFunc) gst_base_text_overlay_base_init,
359       NULL,
360       (GClassInitFunc) gst_base_text_overlay_class_init,
361       NULL,
362       NULL,
363       sizeof (GstBaseTextOverlay),
364       0,
365       (GInstanceInitFunc) gst_base_text_overlay_init,
366     };
367
368     g_once_init_leave ((gsize *) & type,
369         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
370             0));
371   }
372
373   return type;
374 }
375
376 static gchar *
377 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
378     GstBuffer * video_frame)
379 {
380   return g_strdup (overlay->default_text);
381 }
382
383 static void
384 gst_base_text_overlay_base_init (gpointer g_class)
385 {
386   GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
387   PangoFontMap *fontmap;
388
389   /* Only lock for the subclasses here, the base class
390    * doesn't have this mutex yet and it's not necessary
391    * here */
392   if (klass->pango_lock)
393     g_mutex_lock (klass->pango_lock);
394   fontmap = pango_cairo_font_map_get_default ();
395   klass->pango_context =
396       pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
397   if (klass->pango_lock)
398     g_mutex_unlock (klass->pango_lock);
399 }
400
401 static void
402 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
403 {
404   GObjectClass *gobject_class;
405   GstElementClass *gstelement_class;
406
407   gobject_class = (GObjectClass *) klass;
408   gstelement_class = (GstElementClass *) klass;
409
410   parent_class = g_type_class_peek_parent (klass);
411
412   gobject_class->finalize = gst_base_text_overlay_finalize;
413   gobject_class->set_property = gst_base_text_overlay_set_property;
414   gobject_class->get_property = gst_base_text_overlay_get_property;
415
416   gst_element_class_add_pad_template (gstelement_class,
417       gst_static_pad_template_get (&src_template_factory));
418   gst_element_class_add_pad_template (gstelement_class,
419       gst_static_pad_template_get (&video_sink_template_factory));
420
421   gstelement_class->change_state =
422       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
423
424   klass->pango_lock = g_slice_new (GMutex);
425   g_mutex_init (klass->pango_lock);
426
427   klass->get_text = gst_base_text_overlay_get_text;
428
429   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
430       g_param_spec_string ("text", "text",
431           "Text to be display.", DEFAULT_PROP_TEXT,
432           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
433   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
434       g_param_spec_boolean ("shaded-background", "shaded background",
435           "Whether to shade the background under the text area",
436           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
437   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
438       g_param_spec_enum ("valignment", "vertical alignment",
439           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
440           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
441   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
442       g_param_spec_enum ("halignment", "horizontal alignment",
443           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
444           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
445   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
446       g_param_spec_int ("xpad", "horizontal paddding",
447           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
448           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
449   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
450       g_param_spec_int ("ypad", "vertical padding",
451           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
452           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
453   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
454       g_param_spec_int ("deltax", "X position modifier",
455           "Shift X position to the left or to the right. Unit is pixels.",
456           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
457           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
458   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
459       g_param_spec_int ("deltay", "Y position modifier",
460           "Shift Y position up or down. Unit is pixels.", G_MININT, G_MAXINT,
461           DEFAULT_PROP_DELTAY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
462   /**
463    * GstBaseTextOverlay:xpos
464    *
465    * Horizontal position of the rendered text when using positioned alignment.
466    *
467    * Since: 0.10.31
468    **/
469   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
470       g_param_spec_double ("xpos", "horizontal position",
471           "Horizontal position when using position alignment", 0, 1.0,
472           DEFAULT_PROP_XPOS,
473           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
474   /**
475    * GstBaseTextOverlay:ypos
476    *
477    * Vertical position of the rendered text when using positioned alignment.
478    *
479    * Since: 0.10.31
480    **/
481   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
482       g_param_spec_double ("ypos", "vertical position",
483           "Vertical position when using position alignment", 0, 1.0,
484           DEFAULT_PROP_YPOS,
485           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
486   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
487       g_param_spec_enum ("wrap-mode", "wrap mode",
488           "Whether to wrap the text and if so how.",
489           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
490           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
491   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
492       g_param_spec_string ("font-desc", "font description",
493           "Pango font description of font to be used for rendering. "
494           "See documentation of pango_font_description_from_string "
495           "for syntax.", DEFAULT_PROP_FONT_DESC,
496           G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS));
497   /**
498    * GstBaseTextOverlay:color
499    *
500    * Color of the rendered text.
501    *
502    * Since: 0.10.31
503    **/
504   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
505       g_param_spec_uint ("color", "Color",
506           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
507           DEFAULT_PROP_COLOR,
508           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
509   /**
510    * GstTextOverlay:outline-color
511    *
512    * Color of the outline of the rendered text.
513    *
514    * Since: 0.10.35
515    **/
516   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
517       g_param_spec_uint ("outline-color", "Text Outline Color",
518           "Color to use for outline the text (big-endian ARGB).", 0,
519           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
520           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
521
522   /**
523    * GstBaseTextOverlay:line-alignment
524    *
525    * Alignment of text lines relative to each other (for multi-line text)
526    *
527    * Since: 0.10.15
528    **/
529   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_LINE_ALIGNMENT,
530       g_param_spec_enum ("line-alignment", "line alignment",
531           "Alignment of text lines relative to each other.",
532           GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN, DEFAULT_PROP_LINE_ALIGNMENT,
533           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
534   /**
535    * GstBaseTextOverlay:silent
536    *
537    * If set, no text is rendered. Useful to switch off text rendering
538    * temporarily without removing the textoverlay element from the pipeline.
539    *
540    * Since: 0.10.15
541    **/
542   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
543   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
544       g_param_spec_boolean ("silent", "silent",
545           "Whether to render the text string",
546           DEFAULT_PROP_SILENT,
547           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
548   /**
549    * GstBaseTextOverlay:wait-text
550    *
551    * If set, the video will block until a subtitle is received on the text pad.
552    * If video and subtitles are sent in sync, like from the same demuxer, this
553    * property should be set.
554    *
555    * Since: 0.10.20
556    **/
557   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
558       g_param_spec_boolean ("wait-text", "Wait Text",
559           "Whether to wait for subtitles",
560           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
561
562   g_object_class_install_property (G_OBJECT_CLASS (klass),
563       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
564           "Automatically adjust font size to screen-size.",
565           DEFAULT_PROP_AUTO_ADJUST_SIZE,
566           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
567
568   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
569       g_param_spec_boolean ("vertical-render", "vertical render",
570           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
571           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
572 }
573
574 static void
575 gst_base_text_overlay_finalize (GObject * object)
576 {
577   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
578
579   g_free (overlay->default_text);
580
581   if (overlay->composition) {
582     gst_video_overlay_composition_unref (overlay->composition);
583     overlay->composition = NULL;
584   }
585
586   if (overlay->text_image) {
587     gst_buffer_unref (overlay->text_image);
588     overlay->text_image = NULL;
589   }
590
591   if (overlay->layout) {
592     g_object_unref (overlay->layout);
593     overlay->layout = NULL;
594   }
595
596   if (overlay->text_buffer) {
597     gst_buffer_unref (overlay->text_buffer);
598     overlay->text_buffer = NULL;
599   }
600
601   g_mutex_clear (&overlay->lock);
602   g_cond_clear (&overlay->cond);
603
604   G_OBJECT_CLASS (parent_class)->finalize (object);
605 }
606
607 static void
608 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
609     GstBaseTextOverlayClass * klass)
610 {
611   GstPadTemplate *template;
612   PangoFontDescription *desc;
613
614   /* video sink */
615   template = gst_static_pad_template_get (&video_sink_template_factory);
616   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
617   gst_object_unref (template);
618   gst_pad_set_event_function (overlay->video_sinkpad,
619       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
620   gst_pad_set_chain_function (overlay->video_sinkpad,
621       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
622   gst_pad_set_query_function (overlay->video_sinkpad,
623       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
624   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
625
626   template =
627       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
628       "text_sink");
629   if (template) {
630     /* text sink */
631     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
632
633     gst_pad_set_event_function (overlay->text_sinkpad,
634         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
635     gst_pad_set_chain_function (overlay->text_sinkpad,
636         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
637     gst_pad_set_link_function (overlay->text_sinkpad,
638         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
639     gst_pad_set_unlink_function (overlay->text_sinkpad,
640         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
641     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
642   }
643
644   /* (video) source */
645   template = gst_static_pad_template_get (&src_template_factory);
646   overlay->srcpad = gst_pad_new_from_template (template, "src");
647   gst_object_unref (template);
648   gst_pad_set_event_function (overlay->srcpad,
649       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
650   gst_pad_set_query_function (overlay->srcpad,
651       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
652   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
653
654   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
655   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
656   overlay->layout =
657       pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
658       (overlay)->pango_context);
659   desc =
660       pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
661       (overlay)->pango_context);
662   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
663
664   overlay->color = DEFAULT_PROP_COLOR;
665   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
666   overlay->halign = DEFAULT_PROP_HALIGNMENT;
667   overlay->valign = DEFAULT_PROP_VALIGNMENT;
668   overlay->xpad = DEFAULT_PROP_XPAD;
669   overlay->ypad = DEFAULT_PROP_YPAD;
670   overlay->deltax = DEFAULT_PROP_DELTAX;
671   overlay->deltay = DEFAULT_PROP_DELTAY;
672   overlay->xpos = DEFAULT_PROP_XPOS;
673   overlay->ypos = DEFAULT_PROP_YPOS;
674
675   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
676
677   overlay->want_shading = DEFAULT_PROP_SHADING;
678   overlay->shading_value = DEFAULT_SHADING_VALUE;
679   overlay->silent = DEFAULT_PROP_SILENT;
680   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
681   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
682
683   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
684   overlay->need_render = TRUE;
685   overlay->text_image = NULL;
686   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
687   gst_base_text_overlay_update_render_mode (overlay);
688
689   overlay->text_buffer = NULL;
690   overlay->text_linked = FALSE;
691   g_mutex_init (&overlay->lock);
692   g_cond_init (&overlay->cond);
693   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
694   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
695 }
696
697 static void
698 gst_base_text_overlay_update_wrap_mode (GstBaseTextOverlay * overlay)
699 {
700   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
701     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
702     pango_layout_set_width (overlay->layout, -1);
703   } else {
704     int width;
705
706     if (overlay->auto_adjust_size) {
707       width = DEFAULT_SCALE_BASIS * PANGO_SCALE;
708       if (overlay->use_vertical_render) {
709         width = width * (overlay->height - overlay->ypad * 2) / overlay->width;
710       }
711     } else {
712       width =
713           (overlay->use_vertical_render ? overlay->height : overlay->width) *
714           PANGO_SCALE;
715     }
716
717     GST_DEBUG_OBJECT (overlay, "Set layout width %d", overlay->width);
718     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
719     pango_layout_set_width (overlay->layout, width);
720     pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
721   }
722 }
723
724 static void
725 gst_base_text_overlay_update_render_mode (GstBaseTextOverlay * overlay)
726 {
727   PangoMatrix matrix = PANGO_MATRIX_INIT;
728   PangoContext *context = pango_layout_get_context (overlay->layout);
729
730   if (overlay->use_vertical_render) {
731     pango_matrix_rotate (&matrix, -90);
732     pango_context_set_base_gravity (context, PANGO_GRAVITY_AUTO);
733     pango_context_set_matrix (context, &matrix);
734     pango_layout_set_alignment (overlay->layout, PANGO_ALIGN_LEFT);
735   } else {
736     pango_context_set_base_gravity (context, PANGO_GRAVITY_SOUTH);
737     pango_context_set_matrix (context, &matrix);
738     pango_layout_set_alignment (overlay->layout,
739         (PangoAlignment) overlay->line_align);
740   }
741 }
742
743 static gboolean
744 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
745 {
746   GstStructure *structure;
747   const gchar *format;
748
749   structure = gst_caps_get_structure (caps, 0);
750   format = gst_structure_get_string (structure, "format");
751   overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
752
753   return TRUE;
754 }
755
756 /* FIXME: upstream nego (e.g. when the video window is resized) */
757
758 /* only negotiate/query video overlay composition support for now */
759 static gboolean
760 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay)
761 {
762   GstCaps *target;
763   GstQuery *query;
764   gboolean attach = FALSE;
765
766   GST_DEBUG_OBJECT (overlay, "performing negotiation");
767
768   target = gst_pad_get_current_caps (overlay->srcpad);
769
770   if (!target || gst_caps_is_empty (target))
771     goto no_format;
772
773   /* find supported meta */
774   query = gst_query_new_allocation (target, TRUE);
775
776   if (!gst_pad_peer_query (overlay->srcpad, query)) {
777     /* no problem, we use the query defaults */
778     GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
779   }
780
781   if (gst_query_find_allocation_meta (query,
782           GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
783     attach = TRUE;
784
785   overlay->attach_compo_to_buffer = attach;
786
787   gst_query_unref (query);
788   gst_caps_unref (target);
789
790   return TRUE;
791
792 no_format:
793   {
794     if (target)
795       gst_caps_unref (target);
796     return FALSE;
797   }
798 }
799
800 static gboolean
801 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
802 {
803   GstVideoInfo info;
804   gboolean ret = FALSE;
805
806   if (!gst_video_info_from_caps (&info, caps))
807     goto invalid_caps;
808
809   overlay->info = info;
810   overlay->format = GST_VIDEO_INFO_FORMAT (&info);
811   overlay->width = GST_VIDEO_INFO_WIDTH (&info);
812   overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
813
814   ret = gst_pad_set_caps (overlay->srcpad, caps);
815
816   if (ret) {
817     GST_BASE_TEXT_OVERLAY_LOCK (overlay);
818     g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
819     gst_base_text_overlay_negotiate (overlay);
820     gst_base_text_overlay_update_wrap_mode (overlay);
821     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
822     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
823   }
824
825   return ret;
826
827   /* ERRORS */
828 invalid_caps:
829   {
830     GST_DEBUG_OBJECT (overlay, "could not parse caps");
831     return FALSE;
832   }
833 }
834
835 static void
836 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
837     const GValue * value, GParamSpec * pspec)
838 {
839   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
840
841   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
842   switch (prop_id) {
843     case PROP_TEXT:
844       g_free (overlay->default_text);
845       overlay->default_text = g_value_dup_string (value);
846       overlay->need_render = TRUE;
847       break;
848     case PROP_SHADING:
849       overlay->want_shading = g_value_get_boolean (value);
850       break;
851     case PROP_XPAD:
852       overlay->xpad = g_value_get_int (value);
853       break;
854     case PROP_YPAD:
855       overlay->ypad = g_value_get_int (value);
856       break;
857     case PROP_DELTAX:
858       overlay->deltax = g_value_get_int (value);
859       break;
860     case PROP_DELTAY:
861       overlay->deltay = g_value_get_int (value);
862       break;
863     case PROP_XPOS:
864       overlay->xpos = g_value_get_double (value);
865       break;
866     case PROP_YPOS:
867       overlay->ypos = g_value_get_double (value);
868       break;
869     case PROP_VALIGNMENT:
870       overlay->valign = g_value_get_enum (value);
871       break;
872     case PROP_HALIGNMENT:
873       overlay->halign = g_value_get_enum (value);
874       break;
875     case PROP_WRAP_MODE:
876       overlay->wrap_mode = g_value_get_enum (value);
877       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
878       gst_base_text_overlay_update_wrap_mode (overlay);
879       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
880       break;
881     case PROP_FONT_DESC:
882     {
883       PangoFontDescription *desc;
884       const gchar *fontdesc_str;
885
886       fontdesc_str = g_value_get_string (value);
887       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
888       desc = pango_font_description_from_string (fontdesc_str);
889       if (desc) {
890         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
891         pango_layout_set_font_description (overlay->layout, desc);
892         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
893         pango_font_description_free (desc);
894       } else {
895         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
896             fontdesc_str);
897       }
898       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
899       break;
900     }
901     case PROP_COLOR:
902       overlay->color = g_value_get_uint (value);
903       break;
904     case PROP_OUTLINE_COLOR:
905       overlay->outline_color = g_value_get_uint (value);
906       break;
907     case PROP_SILENT:
908       overlay->silent = g_value_get_boolean (value);
909       break;
910     case PROP_LINE_ALIGNMENT:
911       overlay->line_align = g_value_get_enum (value);
912       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
913       pango_layout_set_alignment (overlay->layout,
914           (PangoAlignment) overlay->line_align);
915       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
916       break;
917     case PROP_WAIT_TEXT:
918       overlay->wait_text = g_value_get_boolean (value);
919       break;
920     case PROP_AUTO_ADJUST_SIZE:
921       overlay->auto_adjust_size = g_value_get_boolean (value);
922       overlay->need_render = TRUE;
923       break;
924     case PROP_VERTICAL_RENDER:
925       overlay->use_vertical_render = g_value_get_boolean (value);
926       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
927       gst_base_text_overlay_update_render_mode (overlay);
928       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
929       overlay->need_render = TRUE;
930       break;
931     default:
932       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
933       break;
934   }
935
936   overlay->need_render = TRUE;
937   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
938 }
939
940 static void
941 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
942     GValue * value, GParamSpec * pspec)
943 {
944   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
945
946   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
947   switch (prop_id) {
948     case PROP_TEXT:
949       g_value_set_string (value, overlay->default_text);
950       break;
951     case PROP_SHADING:
952       g_value_set_boolean (value, overlay->want_shading);
953       break;
954     case PROP_XPAD:
955       g_value_set_int (value, overlay->xpad);
956       break;
957     case PROP_YPAD:
958       g_value_set_int (value, overlay->ypad);
959       break;
960     case PROP_DELTAX:
961       g_value_set_int (value, overlay->deltax);
962       break;
963     case PROP_DELTAY:
964       g_value_set_int (value, overlay->deltay);
965       break;
966     case PROP_XPOS:
967       g_value_set_double (value, overlay->xpos);
968       break;
969     case PROP_YPOS:
970       g_value_set_double (value, overlay->ypos);
971       break;
972     case PROP_VALIGNMENT:
973       g_value_set_enum (value, overlay->valign);
974       break;
975     case PROP_HALIGNMENT:
976       g_value_set_enum (value, overlay->halign);
977       break;
978     case PROP_WRAP_MODE:
979       g_value_set_enum (value, overlay->wrap_mode);
980       break;
981     case PROP_SILENT:
982       g_value_set_boolean (value, overlay->silent);
983       break;
984     case PROP_LINE_ALIGNMENT:
985       g_value_set_enum (value, overlay->line_align);
986       break;
987     case PROP_WAIT_TEXT:
988       g_value_set_boolean (value, overlay->wait_text);
989       break;
990     case PROP_AUTO_ADJUST_SIZE:
991       g_value_set_boolean (value, overlay->auto_adjust_size);
992       break;
993     case PROP_VERTICAL_RENDER:
994       g_value_set_boolean (value, overlay->use_vertical_render);
995       break;
996     case PROP_COLOR:
997       g_value_set_uint (value, overlay->color);
998       break;
999     case PROP_OUTLINE_COLOR:
1000       g_value_set_uint (value, overlay->outline_color);
1001       break;
1002     default:
1003       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1004       break;
1005   }
1006
1007   overlay->need_render = TRUE;
1008   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1009 }
1010
1011 static gboolean
1012 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1013     GstQuery * query)
1014 {
1015   gboolean ret = FALSE;
1016   GstBaseTextOverlay *overlay;
1017
1018   overlay = GST_BASE_TEXT_OVERLAY (parent);
1019
1020   switch (GST_QUERY_TYPE (query)) {
1021     case GST_QUERY_CAPS:
1022     {
1023       GstCaps *filter, *caps;
1024
1025       gst_query_parse_caps (query, &filter);
1026       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1027       gst_query_set_caps_result (query, caps);
1028       gst_caps_unref (caps);
1029       ret = TRUE;
1030       break;
1031     }
1032     default:
1033       ret = gst_pad_peer_query (overlay->video_sinkpad, query);
1034       break;
1035   }
1036
1037   return ret;
1038 }
1039
1040 static gboolean
1041 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1042     GstEvent * event)
1043 {
1044   gboolean ret = FALSE;
1045   GstBaseTextOverlay *overlay = NULL;
1046
1047   overlay = GST_BASE_TEXT_OVERLAY (parent);
1048
1049   switch (GST_EVENT_TYPE (event)) {
1050     case GST_EVENT_SEEK:{
1051       GstSeekFlags flags;
1052
1053       /* We don't handle seek if we have not text pad */
1054       if (!overlay->text_linked) {
1055         GST_DEBUG_OBJECT (overlay, "seek received, pushing upstream");
1056         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1057         goto beach;
1058       }
1059
1060       GST_DEBUG_OBJECT (overlay, "seek received, driving from here");
1061
1062       gst_event_parse_seek (event, NULL, NULL, &flags, NULL, NULL, NULL, NULL);
1063
1064       /* Flush downstream, only for flushing seek */
1065       if (flags & GST_SEEK_FLAG_FLUSH)
1066         gst_pad_push_event (overlay->srcpad, gst_event_new_flush_start ());
1067
1068       /* Mark ourself as flushing, unblock chains */
1069       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1070       overlay->video_flushing = TRUE;
1071       overlay->text_flushing = TRUE;
1072       gst_base_text_overlay_pop_text (overlay);
1073       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1074
1075       /* Seek on each sink pad */
1076       gst_event_ref (event);
1077       ret = gst_pad_push_event (overlay->video_sinkpad, event);
1078       if (ret) {
1079         ret = gst_pad_push_event (overlay->text_sinkpad, event);
1080       } else {
1081         gst_event_unref (event);
1082       }
1083       break;
1084     }
1085     default:
1086       if (overlay->text_linked) {
1087         gst_event_ref (event);
1088         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1089         gst_pad_push_event (overlay->text_sinkpad, event);
1090       } else {
1091         ret = gst_pad_push_event (overlay->video_sinkpad, event);
1092       }
1093       break;
1094   }
1095
1096 beach:
1097
1098   return ret;
1099 }
1100
1101 static GstCaps *
1102 gst_base_text_overlay_getcaps (GstPad * pad, GstBaseTextOverlay * overlay,
1103     GstCaps * filter)
1104 {
1105   GstPad *otherpad;
1106   GstCaps *caps;
1107
1108   if (G_UNLIKELY (!overlay))
1109     return gst_pad_get_pad_template_caps (pad);
1110
1111   if (pad == overlay->srcpad)
1112     otherpad = overlay->video_sinkpad;
1113   else
1114     otherpad = overlay->srcpad;
1115
1116   /* we can do what the peer can */
1117   caps = gst_pad_peer_query_caps (otherpad, filter);
1118   if (caps) {
1119     GstCaps *temp, *templ;
1120
1121     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, caps);
1122
1123     /* filtered against our padtemplate */
1124     templ = gst_pad_get_pad_template_caps (otherpad);
1125     GST_DEBUG_OBJECT (pad, "our template  %" GST_PTR_FORMAT, templ);
1126     temp = gst_caps_intersect_full (caps, templ, GST_CAPS_INTERSECT_FIRST);
1127     GST_DEBUG_OBJECT (pad, "intersected %" GST_PTR_FORMAT, temp);
1128     gst_caps_unref (caps);
1129     gst_caps_unref (templ);
1130     /* this is what we can do */
1131     caps = temp;
1132   } else {
1133     /* no peer, our padtemplate is enough then */
1134     caps = gst_pad_get_pad_template_caps (pad);
1135     if (filter) {
1136       GstCaps *intersection;
1137
1138       intersection =
1139           gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1140       gst_caps_unref (caps);
1141       caps = intersection;
1142     }
1143   }
1144
1145   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1146
1147   return caps;
1148 }
1149
1150 static void
1151 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1152     PangoFontDescription * desc)
1153 {
1154   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1155   overlay->shadow_offset = (double) (font_size) / 13.0;
1156   overlay->outline_offset = (double) (font_size) / 15.0;
1157   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1158     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1159 }
1160
1161 static void
1162 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1163     gint * xpos, gint * ypos)
1164 {
1165   gint width, height;
1166   GstBaseTextOverlayVAlign valign;
1167   GstBaseTextOverlayHAlign halign;
1168
1169   width = overlay->image_width;
1170   height = overlay->image_height;
1171
1172   if (overlay->use_vertical_render)
1173     halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1174   else
1175     halign = overlay->halign;
1176
1177   switch (halign) {
1178     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1179       *xpos = overlay->xpad;
1180       break;
1181     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1182       *xpos = (overlay->width - width) / 2;
1183       break;
1184     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1185       *xpos = overlay->width - width - overlay->xpad;
1186       break;
1187     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1188       *xpos = (gint) (overlay->width * overlay->xpos) - width / 2;
1189       *xpos = CLAMP (*xpos, 0, overlay->width - width);
1190       if (*xpos < 0)
1191         *xpos = 0;
1192       break;
1193     default:
1194       *xpos = 0;
1195   }
1196   *xpos += overlay->deltax;
1197
1198   if (overlay->use_vertical_render)
1199     valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1200   else
1201     valign = overlay->valign;
1202
1203   switch (valign) {
1204     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1205       *ypos = overlay->height - height - overlay->ypad;
1206       break;
1207     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1208       *ypos = overlay->height - (height + overlay->ypad);
1209       break;
1210     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1211       *ypos = overlay->ypad;
1212       break;
1213     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1214       *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1215       *ypos = CLAMP (*ypos, 0, overlay->height - height);
1216       break;
1217     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1218       *ypos = (overlay->height - height) / 2;
1219       break;
1220     default:
1221       *ypos = overlay->ypad;
1222       break;
1223   }
1224   *ypos += overlay->deltay;
1225 }
1226
1227 static inline void
1228 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1229 {
1230   gint xpos, ypos;
1231   GstVideoOverlayRectangle *rectangle;
1232
1233   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1234
1235   if (overlay->text_image) {
1236     gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1237         GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1238         overlay->image_width, overlay->image_height);
1239     rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1240         xpos, ypos, overlay->image_width, overlay->image_height,
1241         GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1242
1243     if (overlay->composition)
1244       gst_video_overlay_composition_unref (overlay->composition);
1245     overlay->composition = gst_video_overlay_composition_new (rectangle);
1246     gst_video_overlay_rectangle_unref (rectangle);
1247
1248   } else if (overlay->composition) {
1249     gst_video_overlay_composition_unref (overlay->composition);
1250     overlay->composition = NULL;
1251   }
1252 }
1253
1254 static void
1255 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1256     const gchar * string, gint textlen)
1257 {
1258   cairo_t *cr;
1259   cairo_surface_t *surface;
1260   PangoRectangle ink_rect, logical_rect;
1261   cairo_matrix_t cairo_matrix;
1262   int width, height;
1263   double scalef = 1.0;
1264   double a, r, g, b;
1265   GstBuffer *buffer;
1266   GstMapInfo map;
1267
1268   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1269
1270   if (overlay->auto_adjust_size) {
1271     /* 640 pixel is default */
1272     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1273   }
1274   pango_layout_set_width (overlay->layout, -1);
1275   /* set text on pango layout */
1276   pango_layout_set_markup (overlay->layout, string, textlen);
1277
1278   /* get subtitle image size */
1279   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1280
1281   width = (logical_rect.width + overlay->shadow_offset) * scalef;
1282
1283   if (width + overlay->deltax >
1284       (overlay->use_vertical_render ? overlay->height : overlay->width)) {
1285     /*
1286      * subtitle image width is larger then overlay width
1287      * so rearrange overlay wrap mode.
1288      */
1289     gst_base_text_overlay_update_wrap_mode (overlay);
1290     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1291     width = overlay->width;
1292   }
1293
1294   height =
1295       (logical_rect.height + logical_rect.y + overlay->shadow_offset) * scalef;
1296   if (height > overlay->height) {
1297     height = overlay->height;
1298   }
1299   if (overlay->use_vertical_render) {
1300     PangoRectangle rect;
1301     PangoContext *context;
1302     PangoMatrix matrix = PANGO_MATRIX_INIT;
1303     int tmp;
1304
1305     context = pango_layout_get_context (overlay->layout);
1306
1307     pango_matrix_rotate (&matrix, -90);
1308
1309     rect.x = rect.y = 0;
1310     rect.width = width;
1311     rect.height = height;
1312     pango_matrix_transform_pixel_rectangle (&matrix, &rect);
1313     matrix.x0 = -rect.x;
1314     matrix.y0 = -rect.y;
1315
1316     pango_context_set_matrix (context, &matrix);
1317
1318     cairo_matrix.xx = matrix.xx;
1319     cairo_matrix.yx = matrix.yx;
1320     cairo_matrix.xy = matrix.xy;
1321     cairo_matrix.yy = matrix.yy;
1322     cairo_matrix.x0 = matrix.x0;
1323     cairo_matrix.y0 = matrix.y0;
1324     cairo_matrix_scale (&cairo_matrix, scalef, scalef);
1325
1326     tmp = height;
1327     height = width;
1328     width = tmp;
1329   } else {
1330     cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1331   }
1332
1333   /* reallocate overlay buffer */
1334   buffer = gst_buffer_new_and_alloc (4 * width * height);
1335   gst_buffer_replace (&overlay->text_image, buffer);
1336   gst_buffer_unref (buffer);
1337
1338   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1339   surface = cairo_image_surface_create_for_data (map.data,
1340       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1341   cr = cairo_create (surface);
1342
1343   /* clear surface */
1344   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1345   cairo_paint (cr);
1346
1347   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1348
1349   if (overlay->want_shading)
1350     cairo_paint_with_alpha (cr, overlay->shading_value);
1351
1352   /* apply transformations */
1353   cairo_set_matrix (cr, &cairo_matrix);
1354
1355   /* FIXME: We use show_layout everywhere except for the surface
1356    * because it's really faster and internally does all kinds of
1357    * caching. Unfortunately we have to paint to a cairo path for
1358    * the outline and this is slow. Once Pango supports user fonts
1359    * we should use them, see
1360    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1361    *
1362    * Idea would the be, to create a cairo user font that
1363    * does shadow, outline, text painting in the
1364    * render_glyph function.
1365    */
1366
1367   /* draw shadow text */
1368   cairo_save (cr);
1369   cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1370   cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1371   pango_cairo_show_layout (cr, overlay->layout);
1372   cairo_restore (cr);
1373
1374   a = (overlay->outline_color >> 24) & 0xff;
1375   r = (overlay->outline_color >> 16) & 0xff;
1376   g = (overlay->outline_color >> 8) & 0xff;
1377   b = (overlay->outline_color >> 0) & 0xff;
1378
1379   /* draw outline text */
1380   cairo_save (cr);
1381   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1382   cairo_set_line_width (cr, overlay->outline_offset);
1383   pango_cairo_layout_path (cr, overlay->layout);
1384   cairo_stroke (cr);
1385   cairo_restore (cr);
1386
1387   a = (overlay->color >> 24) & 0xff;
1388   r = (overlay->color >> 16) & 0xff;
1389   g = (overlay->color >> 8) & 0xff;
1390   b = (overlay->color >> 0) & 0xff;
1391
1392   /* draw text */
1393   cairo_save (cr);
1394   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1395   pango_cairo_show_layout (cr, overlay->layout);
1396   cairo_restore (cr);
1397
1398   cairo_destroy (cr);
1399   cairo_surface_destroy (surface);
1400   gst_buffer_unmap (buffer, &map);
1401   overlay->image_width = width;
1402   overlay->image_height = height;
1403   overlay->baseline_y = ink_rect.y;
1404   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1405
1406   gst_base_text_overlay_set_composition (overlay);
1407 }
1408
1409 #define BOX_XPAD         6
1410 #define BOX_YPAD         6
1411
1412 static inline void
1413 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1414     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1415 {
1416   gint i, j, dest_stride;
1417   guint8 *dest_ptr;
1418
1419   dest_stride = dest->info.stride[0];
1420   dest_ptr = dest->data[0];
1421
1422   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1423   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1424
1425   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1426   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1427
1428   for (i = y0; i < y1; ++i) {
1429     for (j = x0; j < x1; ++j) {
1430       gint y = dest_ptr[(i * dest_stride) + j] + overlay->shading_value;
1431
1432       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1433     }
1434   }
1435 }
1436
1437 static inline void
1438 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1439     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1440 {
1441   gint i, j;
1442   guint dest_stride, pixel_stride;
1443   guint8 *dest_ptr;
1444
1445   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1446   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1447   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1448
1449   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1450   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1451
1452   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1453   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1454
1455   if (x0 != 0)
1456     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1457   if (x1 != 0)
1458     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1459
1460   if (y0 != 0)
1461     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1462   if (y1 != 0)
1463     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1464
1465   for (i = y0; i < y1; i++) {
1466     for (j = x0; j < x1; j++) {
1467       gint y;
1468       gint y_pos;
1469
1470       y_pos = (i * dest_stride) + j * pixel_stride;
1471       y = dest_ptr[y_pos] + overlay->shading_value;
1472
1473       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1474     }
1475   }
1476 }
1477
1478 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1479 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1480 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1481 static inline void
1482 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1483     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1484 {
1485   gint i, j;
1486   guint8 *dest_ptr;
1487
1488   dest_ptr = dest->data[0];
1489
1490   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
1491   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
1492
1493   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
1494   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
1495
1496   for (i = y0; i < y1; i++) {
1497     for (j = x0; j < x1; j++) {
1498       gint y, y_pos, k;
1499
1500       y_pos = (i * 4 * overlay->width) + j * 4;
1501       for (k = 0; k < 4; k++) {
1502         y = dest_ptr[y_pos + k] + overlay->shading_value;
1503         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
1504       }
1505     }
1506   }
1507 }
1508
1509 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
1510 static inline void \
1511 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
1512 gint x0, gint x1, gint y0, gint y1) \
1513 { \
1514   gint i, j;\
1515   guint8 *dest_ptr;\
1516   \
1517   dest_ptr = dest->data[0];\
1518   \
1519   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);\
1520   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);\
1521   \
1522   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);\
1523   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);\
1524   \
1525   for (i = y0; i < y1; i++) {\
1526     for (j = x0; j < x1; j++) {\
1527       gint y, y_pos, k;\
1528       y_pos = (i * 4 * overlay->width) + j * 4;\
1529       for (k = OFFSET; k < 3+OFFSET; k++) {\
1530         y = dest_ptr[y_pos + k] + overlay->shading_value;\
1531         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
1532       }\
1533     }\
1534   }\
1535 }
1536 ARGB_SHADE_FUNCTION (ARGB, 1);
1537 ARGB_SHADE_FUNCTION (ABGR, 1);
1538 ARGB_SHADE_FUNCTION (RGBA, 0);
1539 ARGB_SHADE_FUNCTION (BGRA, 0);
1540
1541 static void
1542 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
1543     const gchar * text, gint textlen)
1544 {
1545   gchar *string;
1546
1547   if (!overlay->need_render) {
1548     GST_DEBUG ("Using previously rendered text.");
1549     return;
1550   }
1551
1552   /* -1 is the whole string */
1553   if (text != NULL && textlen < 0) {
1554     textlen = strlen (text);
1555   }
1556
1557   if (text != NULL) {
1558     string = g_strndup (text, textlen);
1559   } else {                      /* empty string */
1560     string = g_strdup (" ");
1561   }
1562   g_strdelimit (string, "\r\t", ' ');
1563   textlen = strlen (string);
1564
1565   /* FIXME: should we check for UTF-8 here? */
1566
1567   GST_DEBUG ("Rendering '%s'", string);
1568   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
1569
1570   g_free (string);
1571
1572   overlay->need_render = FALSE;
1573 }
1574
1575 static GstFlowReturn
1576 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
1577     GstBuffer * video_frame)
1578 {
1579   gint xpos, ypos;
1580   GstVideoFrame frame;
1581
1582   if (overlay->composition == NULL)
1583     goto done;
1584
1585   if (gst_pad_check_reconfigure (overlay->srcpad))
1586     gst_base_text_overlay_negotiate (overlay);
1587
1588   video_frame = gst_buffer_make_writable (video_frame);
1589
1590   if (overlay->attach_compo_to_buffer) {
1591     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1592     gst_buffer_add_video_overlay_composition_meta (video_frame,
1593         overlay->composition);
1594     /* FIXME: emulate shaded background box if want_shading=true */
1595     goto done;
1596   }
1597
1598   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1599           GST_MAP_READWRITE))
1600     goto invalid_frame;
1601
1602   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1603
1604   /* shaded background box */
1605   if (overlay->want_shading) {
1606     switch (overlay->format) {
1607       case GST_VIDEO_FORMAT_I420:
1608       case GST_VIDEO_FORMAT_NV12:
1609       case GST_VIDEO_FORMAT_NV21:
1610         gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1611             xpos, xpos + overlay->image_width,
1612             ypos, ypos + overlay->image_height);
1613         break;
1614       case GST_VIDEO_FORMAT_AYUV:
1615       case GST_VIDEO_FORMAT_UYVY:
1616         gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1617             xpos, xpos + overlay->image_width,
1618             ypos, ypos + overlay->image_height);
1619         break;
1620       case GST_VIDEO_FORMAT_xRGB:
1621         gst_base_text_overlay_shade_xRGB (overlay, &frame,
1622             xpos, xpos + overlay->image_width,
1623             ypos, ypos + overlay->image_height);
1624         break;
1625       case GST_VIDEO_FORMAT_xBGR:
1626         gst_base_text_overlay_shade_xBGR (overlay, &frame,
1627             xpos, xpos + overlay->image_width,
1628             ypos, ypos + overlay->image_height);
1629         break;
1630       case GST_VIDEO_FORMAT_BGRx:
1631         gst_base_text_overlay_shade_BGRx (overlay, &frame,
1632             xpos, xpos + overlay->image_width,
1633             ypos, ypos + overlay->image_height);
1634         break;
1635       case GST_VIDEO_FORMAT_RGBx:
1636         gst_base_text_overlay_shade_RGBx (overlay, &frame,
1637             xpos, xpos + overlay->image_width,
1638             ypos, ypos + overlay->image_height);
1639         break;
1640       case GST_VIDEO_FORMAT_ARGB:
1641         gst_base_text_overlay_shade_ARGB (overlay, &frame,
1642             xpos, xpos + overlay->image_width,
1643             ypos, ypos + overlay->image_height);
1644         break;
1645       case GST_VIDEO_FORMAT_ABGR:
1646         gst_base_text_overlay_shade_ABGR (overlay, &frame,
1647             xpos, xpos + overlay->image_width,
1648             ypos, ypos + overlay->image_height);
1649         break;
1650       case GST_VIDEO_FORMAT_RGBA:
1651         gst_base_text_overlay_shade_RGBA (overlay, &frame,
1652             xpos, xpos + overlay->image_width,
1653             ypos, ypos + overlay->image_height);
1654         break;
1655       case GST_VIDEO_FORMAT_BGRA:
1656         gst_base_text_overlay_shade_BGRA (overlay, &frame,
1657             xpos, xpos + overlay->image_width,
1658             ypos, ypos + overlay->image_height);
1659         break;
1660       default:
1661         g_assert_not_reached ();
1662     }
1663   }
1664
1665   gst_video_overlay_composition_blend (overlay->composition, &frame);
1666
1667   gst_video_frame_unmap (&frame);
1668
1669 done:
1670
1671   return gst_pad_push (overlay->srcpad, video_frame);
1672
1673   /* ERRORS */
1674 invalid_frame:
1675   {
1676     gst_buffer_unref (video_frame);
1677     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1678     return GST_FLOW_OK;
1679   }
1680 }
1681
1682 static GstPadLinkReturn
1683 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1684 {
1685   GstBaseTextOverlay *overlay;
1686
1687   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1688   if (G_UNLIKELY (!overlay))
1689     return GST_PAD_LINK_REFUSED;
1690
1691   GST_DEBUG_OBJECT (overlay, "Text pad linked");
1692
1693   overlay->text_linked = TRUE;
1694
1695   gst_object_unref (overlay);
1696
1697   return GST_PAD_LINK_OK;
1698 }
1699
1700 static void
1701 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1702 {
1703   GstBaseTextOverlay *overlay;
1704
1705   /* don't use gst_pad_get_parent() here, will deadlock */
1706   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1707
1708   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1709
1710   overlay->text_linked = FALSE;
1711
1712   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1713 }
1714
1715 static gboolean
1716 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1717     GstEvent * event)
1718 {
1719   gboolean ret = FALSE;
1720   GstBaseTextOverlay *overlay = NULL;
1721
1722   overlay = GST_BASE_TEXT_OVERLAY (parent);
1723
1724   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1725
1726   switch (GST_EVENT_TYPE (event)) {
1727     case GST_EVENT_CAPS:
1728     {
1729       GstCaps *caps;
1730
1731       gst_event_parse_caps (event, &caps);
1732       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1733       gst_event_unref (event);
1734       break;
1735     }
1736     case GST_EVENT_SEGMENT:
1737     {
1738       const GstSegment *segment;
1739
1740       overlay->text_eos = FALSE;
1741
1742       gst_event_parse_segment (event, &segment);
1743
1744       if (segment->format == GST_FORMAT_TIME) {
1745         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1746         gst_segment_copy_into (segment, &overlay->text_segment);
1747         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1748             &overlay->text_segment);
1749         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1750       } else {
1751         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1752             ("received non-TIME newsegment event on text input"));
1753       }
1754
1755       gst_event_unref (event);
1756       ret = TRUE;
1757
1758       /* wake up the video chain, it might be waiting for a text buffer or
1759        * a text segment update */
1760       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1761       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1762       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1763       break;
1764     }
1765     case GST_EVENT_GAP:
1766     {
1767       GstClockTime start, duration;
1768
1769       gst_event_parse_gap (event, &start, &duration);
1770       if (GST_CLOCK_TIME_IS_VALID (duration))
1771         start += duration;
1772       /* we do not expect another buffer until after gap,
1773        * so that is our position now */
1774       overlay->text_segment.position = start;
1775
1776       /* wake up the video chain, it might be waiting for a text buffer or
1777        * a text segment update */
1778       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1779       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1780       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1781       break;
1782     }
1783     case GST_EVENT_FLUSH_STOP:
1784       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1785       GST_INFO_OBJECT (overlay, "text flush stop");
1786       overlay->text_flushing = FALSE;
1787       overlay->text_eos = FALSE;
1788       gst_base_text_overlay_pop_text (overlay);
1789       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1790       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1791       gst_event_unref (event);
1792       ret = TRUE;
1793       break;
1794     case GST_EVENT_FLUSH_START:
1795       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1796       GST_INFO_OBJECT (overlay, "text flush start");
1797       overlay->text_flushing = TRUE;
1798       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1799       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1800       gst_event_unref (event);
1801       ret = TRUE;
1802       break;
1803     case GST_EVENT_EOS:
1804       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1805       overlay->text_eos = TRUE;
1806       GST_INFO_OBJECT (overlay, "text EOS");
1807       /* wake up the video chain, it might be waiting for a text buffer or
1808        * a text segment update */
1809       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1810       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1811       gst_event_unref (event);
1812       ret = TRUE;
1813       break;
1814     default:
1815       ret = gst_pad_event_default (pad, parent, event);
1816       break;
1817   }
1818
1819   return ret;
1820 }
1821
1822 static gboolean
1823 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1824     GstEvent * event)
1825 {
1826   gboolean ret = FALSE;
1827   GstBaseTextOverlay *overlay = NULL;
1828
1829   overlay = GST_BASE_TEXT_OVERLAY (parent);
1830
1831   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1832
1833   switch (GST_EVENT_TYPE (event)) {
1834     case GST_EVENT_CAPS:
1835     {
1836       GstCaps *caps;
1837
1838       gst_event_parse_caps (event, &caps);
1839       ret = gst_base_text_overlay_setcaps (overlay, caps);
1840       gst_event_unref (event);
1841       break;
1842     }
1843     case GST_EVENT_SEGMENT:
1844     {
1845       const GstSegment *segment;
1846
1847       GST_DEBUG_OBJECT (overlay, "received new segment");
1848
1849       gst_event_parse_segment (event, &segment);
1850
1851       if (segment->format == GST_FORMAT_TIME) {
1852         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1853             &overlay->segment);
1854
1855         gst_segment_copy_into (segment, &overlay->segment);
1856       } else {
1857         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1858             ("received non-TIME newsegment event on video input"));
1859       }
1860
1861       ret = gst_pad_event_default (pad, parent, event);
1862       break;
1863     }
1864     case GST_EVENT_EOS:
1865       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1866       GST_INFO_OBJECT (overlay, "video EOS");
1867       overlay->video_eos = TRUE;
1868       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1869       ret = gst_pad_event_default (pad, parent, event);
1870       break;
1871     case GST_EVENT_FLUSH_START:
1872       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1873       GST_INFO_OBJECT (overlay, "video flush start");
1874       overlay->video_flushing = TRUE;
1875       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1876       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1877       ret = gst_pad_event_default (pad, parent, event);
1878       break;
1879     case GST_EVENT_FLUSH_STOP:
1880       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1881       GST_INFO_OBJECT (overlay, "video flush stop");
1882       overlay->video_flushing = FALSE;
1883       overlay->video_eos = FALSE;
1884       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1885       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1886       ret = gst_pad_event_default (pad, parent, event);
1887       break;
1888     default:
1889       ret = gst_pad_event_default (pad, parent, event);
1890       break;
1891   }
1892
1893   return ret;
1894 }
1895
1896 static gboolean
1897 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1898     GstQuery * query)
1899 {
1900   gboolean ret = FALSE;
1901   GstBaseTextOverlay *overlay;
1902
1903   overlay = GST_BASE_TEXT_OVERLAY (parent);
1904
1905   switch (GST_QUERY_TYPE (query)) {
1906     case GST_QUERY_CAPS:
1907     {
1908       GstCaps *filter, *caps;
1909
1910       gst_query_parse_caps (query, &filter);
1911       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1912       gst_query_set_caps_result (query, caps);
1913       gst_caps_unref (caps);
1914       ret = TRUE;
1915       break;
1916     }
1917     default:
1918       ret = gst_pad_query_default (pad, parent, query);
1919       break;
1920   }
1921
1922   return ret;
1923 }
1924
1925 /* Called with lock held */
1926 static void
1927 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1928 {
1929   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1930
1931   if (overlay->text_buffer) {
1932     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1933         overlay->text_buffer);
1934     gst_buffer_unref (overlay->text_buffer);
1935     overlay->text_buffer = NULL;
1936   }
1937
1938   /* Let the text task know we used that buffer */
1939   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1940 }
1941
1942 /* We receive text buffers here. If they are out of segment we just ignore them.
1943    If the buffer is in our segment we keep it internally except if another one
1944    is already waiting here, in that case we wait that it gets kicked out */
1945 static GstFlowReturn
1946 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1947     GstBuffer * buffer)
1948 {
1949   GstFlowReturn ret = GST_FLOW_OK;
1950   GstBaseTextOverlay *overlay = NULL;
1951   gboolean in_seg = FALSE;
1952   guint64 clip_start = 0, clip_stop = 0;
1953
1954   overlay = GST_BASE_TEXT_OVERLAY (parent);
1955
1956   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1957
1958   if (overlay->text_flushing) {
1959     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1960     ret = GST_FLOW_FLUSHING;
1961     GST_LOG_OBJECT (overlay, "text flushing");
1962     goto beach;
1963   }
1964
1965   if (overlay->text_eos) {
1966     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1967     ret = GST_FLOW_EOS;
1968     GST_LOG_OBJECT (overlay, "text EOS");
1969     goto beach;
1970   }
1971
1972   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
1973       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1974       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1975       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1976           GST_BUFFER_DURATION (buffer)));
1977
1978   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1979     GstClockTime stop;
1980
1981     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1982       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1983     else
1984       stop = GST_CLOCK_TIME_NONE;
1985
1986     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1987         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1988   } else {
1989     in_seg = TRUE;
1990   }
1991
1992   if (in_seg) {
1993     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1994       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1995     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1996       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1997
1998     /* Wait for the previous buffer to go away */
1999     while (overlay->text_buffer != NULL) {
2000       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2001           GST_DEBUG_PAD_NAME (pad));
2002       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2003       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2004       if (overlay->text_flushing) {
2005         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2006         ret = GST_FLOW_FLUSHING;
2007         goto beach;
2008       }
2009     }
2010
2011     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2012       overlay->text_segment.position = clip_start;
2013
2014     overlay->text_buffer = buffer;
2015     /* That's a new text buffer we need to render */
2016     overlay->need_render = TRUE;
2017
2018     /* in case the video chain is waiting for a text buffer, wake it up */
2019     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2020   }
2021
2022   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2023
2024 beach:
2025
2026   return ret;
2027 }
2028
2029 static GstFlowReturn
2030 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2031     GstBuffer * buffer)
2032 {
2033   GstBaseTextOverlayClass *klass;
2034   GstBaseTextOverlay *overlay;
2035   GstFlowReturn ret = GST_FLOW_OK;
2036   gboolean in_seg = FALSE;
2037   guint64 start, stop, clip_start = 0, clip_stop = 0;
2038   gchar *text = NULL;
2039
2040   overlay = GST_BASE_TEXT_OVERLAY (parent);
2041   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2042
2043   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2044     goto missing_timestamp;
2045
2046   /* ignore buffers that are outside of the current segment */
2047   start = GST_BUFFER_TIMESTAMP (buffer);
2048
2049   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2050     stop = GST_CLOCK_TIME_NONE;
2051   } else {
2052     stop = start + GST_BUFFER_DURATION (buffer);
2053   }
2054
2055   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2056       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2057       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2058
2059   /* segment_clip() will adjust start unconditionally to segment_start if
2060    * no stop time is provided, so handle this ourselves */
2061   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2062     goto out_of_segment;
2063
2064   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2065       &clip_start, &clip_stop);
2066
2067   if (!in_seg)
2068     goto out_of_segment;
2069
2070   /* if the buffer is only partially in the segment, fix up stamps */
2071   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2072     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2073     buffer = gst_buffer_make_writable (buffer);
2074     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2075     if (stop != -1)
2076       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2077   }
2078
2079   /* now, after we've done the clipping, fix up end time if there's no
2080    * duration (we only use those estimated values internally though, we
2081    * don't want to set bogus values on the buffer itself) */
2082   if (stop == -1) {
2083     GstCaps *caps;
2084     GstStructure *s;
2085     gint fps_num, fps_denom;
2086
2087     /* FIXME, store this in setcaps */
2088     caps = gst_pad_get_current_caps (pad);
2089     s = gst_caps_get_structure (caps, 0);
2090     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2091         fps_num && fps_denom) {
2092       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2093       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2094     } else {
2095       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2096       stop = start + 1;         /* we need to assume some interval */
2097     }
2098     gst_caps_unref (caps);
2099   }
2100
2101   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2102
2103 wait_for_text_buf:
2104
2105   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2106
2107   if (overlay->video_flushing)
2108     goto flushing;
2109
2110   if (overlay->video_eos)
2111     goto have_eos;
2112
2113   if (overlay->silent) {
2114     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2115     ret = gst_pad_push (overlay->srcpad, buffer);
2116
2117     /* Update position */
2118     overlay->segment.position = clip_start;
2119
2120     return ret;
2121   }
2122
2123   /* Text pad not linked, rendering internal text */
2124   if (!overlay->text_linked) {
2125     if (klass->get_text) {
2126       text = klass->get_text (overlay, buffer);
2127     } else {
2128       text = g_strdup (overlay->default_text);
2129     }
2130
2131     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2132         "text: '%s'", GST_STR_NULL (text));
2133
2134     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2135
2136     if (text != NULL && *text != '\0') {
2137       /* Render and push */
2138       gst_base_text_overlay_render_text (overlay, text, -1);
2139       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2140     } else {
2141       /* Invalid or empty string */
2142       ret = gst_pad_push (overlay->srcpad, buffer);
2143     }
2144   } else {
2145     /* Text pad linked, check if we have a text buffer queued */
2146     if (overlay->text_buffer) {
2147       gboolean pop_text = FALSE, valid_text_time = TRUE;
2148       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2149       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2150       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2151       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2152       GstClockTime vid_running_time, vid_running_time_end;
2153
2154       /* if the text buffer isn't stamped right, pop it off the
2155        * queue and display it for the current video frame only */
2156       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2157           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2158         GST_WARNING_OBJECT (overlay,
2159             "Got text buffer with invalid timestamp or duration");
2160         pop_text = TRUE;
2161         valid_text_time = FALSE;
2162       } else {
2163         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2164         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2165       }
2166
2167       vid_running_time =
2168           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2169           start);
2170       vid_running_time_end =
2171           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2172           stop);
2173
2174       /* If timestamp and duration are valid */
2175       if (valid_text_time) {
2176         text_running_time =
2177             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2178             text_start);
2179         text_running_time_end =
2180             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2181             text_end);
2182       }
2183
2184       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2185           GST_TIME_ARGS (text_running_time),
2186           GST_TIME_ARGS (text_running_time_end));
2187       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2188           GST_TIME_ARGS (vid_running_time),
2189           GST_TIME_ARGS (vid_running_time_end));
2190
2191       /* Text too old or in the future */
2192       if (valid_text_time && text_running_time_end <= vid_running_time) {
2193         /* text buffer too old, get rid of it and do nothing  */
2194         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2195         pop_text = FALSE;
2196         gst_base_text_overlay_pop_text (overlay);
2197         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2198         goto wait_for_text_buf;
2199       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2200         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2201         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2202         /* Push the video frame */
2203         ret = gst_pad_push (overlay->srcpad, buffer);
2204       } else {
2205         GstMapInfo map;
2206         gchar *in_text;
2207         gsize in_size;
2208
2209         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2210         in_text = (gchar *) map.data;
2211         in_size = map.size;
2212
2213         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2214          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2215          * here on purpose, this is something that needs fixing upstream */
2216         if (!g_utf8_validate (in_text, in_size, NULL)) {
2217           const gchar *end = NULL;
2218
2219           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2220           in_text = g_strndup (in_text, in_size);
2221           while (!g_utf8_validate (in_text, in_size, &end) && end)
2222             *((gchar *) end) = '*';
2223         }
2224
2225         /* Get the string */
2226         if (overlay->have_pango_markup) {
2227           text = g_strndup (in_text, in_size);
2228         } else {
2229           text = g_markup_escape_text (in_text, in_size);
2230         }
2231
2232         if (text != NULL && *text != '\0') {
2233           gint text_len = strlen (text);
2234
2235           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2236                   text[text_len - 1] == '\r')) {
2237             --text_len;
2238           }
2239           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2240           gst_base_text_overlay_render_text (overlay, text, text_len);
2241         } else {
2242           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2243           gst_base_text_overlay_render_text (overlay, " ", 1);
2244         }
2245         if (in_text != (gchar *) map.data)
2246           g_free (in_text);
2247
2248         gst_buffer_unmap (overlay->text_buffer, &map);
2249
2250         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2251         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2252
2253         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2254           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2255           pop_text = TRUE;
2256         }
2257       }
2258       if (pop_text) {
2259         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2260         gst_base_text_overlay_pop_text (overlay);
2261         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2262       }
2263     } else {
2264       gboolean wait_for_text_buf = TRUE;
2265
2266       if (overlay->text_eos)
2267         wait_for_text_buf = FALSE;
2268
2269       if (!overlay->wait_text)
2270         wait_for_text_buf = FALSE;
2271
2272       /* Text pad linked, but no text buffer available - what now? */
2273       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2274         GstClockTime text_start_running_time, text_position_running_time;
2275         GstClockTime vid_running_time;
2276
2277         vid_running_time =
2278             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2279             GST_BUFFER_TIMESTAMP (buffer));
2280         text_start_running_time =
2281             gst_segment_to_running_time (&overlay->text_segment,
2282             GST_FORMAT_TIME, overlay->text_segment.start);
2283         text_position_running_time =
2284             gst_segment_to_running_time (&overlay->text_segment,
2285             GST_FORMAT_TIME, overlay->text_segment.position);
2286
2287         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2288                 vid_running_time < text_start_running_time) ||
2289             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2290                 vid_running_time < text_position_running_time)) {
2291           wait_for_text_buf = FALSE;
2292         }
2293       }
2294
2295       if (wait_for_text_buf) {
2296         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2297         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2298         GST_DEBUG_OBJECT (overlay, "resuming");
2299         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2300         goto wait_for_text_buf;
2301       } else {
2302         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2303         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2304         ret = gst_pad_push (overlay->srcpad, buffer);
2305       }
2306     }
2307   }
2308
2309   g_free (text);
2310
2311   /* Update position */
2312   overlay->segment.position = clip_start;
2313
2314   return ret;
2315
2316 missing_timestamp:
2317   {
2318     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2319     gst_buffer_unref (buffer);
2320     return GST_FLOW_OK;
2321   }
2322
2323 flushing:
2324   {
2325     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2326     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2327     gst_buffer_unref (buffer);
2328     return GST_FLOW_FLUSHING;
2329   }
2330 have_eos:
2331   {
2332     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2333     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2334     gst_buffer_unref (buffer);
2335     return GST_FLOW_EOS;
2336   }
2337 out_of_segment:
2338   {
2339     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2340     gst_buffer_unref (buffer);
2341     return GST_FLOW_OK;
2342   }
2343 }
2344
2345 static GstStateChangeReturn
2346 gst_base_text_overlay_change_state (GstElement * element,
2347     GstStateChange transition)
2348 {
2349   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2350   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2351
2352   switch (transition) {
2353     case GST_STATE_CHANGE_PAUSED_TO_READY:
2354       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2355       overlay->text_flushing = TRUE;
2356       overlay->video_flushing = TRUE;
2357       /* pop_text will broadcast on the GCond and thus also make the video
2358        * chain exit if it's waiting for a text buffer */
2359       gst_base_text_overlay_pop_text (overlay);
2360       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2361       break;
2362     default:
2363       break;
2364   }
2365
2366   ret = parent_class->change_state (element, transition);
2367   if (ret == GST_STATE_CHANGE_FAILURE)
2368     return ret;
2369
2370   switch (transition) {
2371     case GST_STATE_CHANGE_READY_TO_PAUSED:
2372       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2373       overlay->text_flushing = FALSE;
2374       overlay->video_flushing = FALSE;
2375       overlay->video_eos = FALSE;
2376       overlay->text_eos = FALSE;
2377       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2378       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2379       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2380       break;
2381     default:
2382       break;
2383   }
2384
2385   return ret;
2386 }
2387
2388 static gboolean
2389 plugin_init (GstPlugin * plugin)
2390 {
2391   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2392           GST_TYPE_TEXT_OVERLAY) ||
2393       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2394           GST_TYPE_TIME_OVERLAY) ||
2395       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2396           GST_TYPE_CLOCK_OVERLAY) ||
2397       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2398           GST_TYPE_TEXT_RENDER)) {
2399     return FALSE;
2400   }
2401
2402   /*texttestsrc_plugin_init(module, plugin); */
2403
2404   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2405
2406   return TRUE;
2407 }
2408
2409 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2410     pango, "Pango-based text rendering and overlay", plugin_init,
2411     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)