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