pango: adjust to modified overlay composition API
[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   if (overlay->composition == NULL)
1596     goto done;
1597
1598   video_frame = gst_buffer_make_writable (video_frame);
1599
1600   if (overlay->attach_compo_to_buffer) {
1601     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
1602     gst_buffer_add_video_overlay_composition_meta (video_frame,
1603         overlay->composition);
1604     /* FIXME: emulate shaded background box if want_shading=true */
1605     goto done;
1606   }
1607
1608   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
1609           GST_MAP_READWRITE))
1610     goto invalid_frame;
1611
1612   gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1613
1614   /* shaded background box */
1615   if (overlay->want_shading) {
1616     switch (overlay->format) {
1617       case GST_VIDEO_FORMAT_I420:
1618       case GST_VIDEO_FORMAT_NV12:
1619       case GST_VIDEO_FORMAT_NV21:
1620         gst_base_text_overlay_shade_planar_Y (overlay, &frame,
1621             xpos, xpos + overlay->image_width,
1622             ypos, ypos + overlay->image_height);
1623         break;
1624       case GST_VIDEO_FORMAT_AYUV:
1625       case GST_VIDEO_FORMAT_UYVY:
1626         gst_base_text_overlay_shade_packed_Y (overlay, &frame,
1627             xpos, xpos + overlay->image_width,
1628             ypos, ypos + overlay->image_height);
1629         break;
1630       case GST_VIDEO_FORMAT_xRGB:
1631         gst_base_text_overlay_shade_xRGB (overlay, &frame,
1632             xpos, xpos + overlay->image_width,
1633             ypos, ypos + overlay->image_height);
1634         break;
1635       case GST_VIDEO_FORMAT_xBGR:
1636         gst_base_text_overlay_shade_xBGR (overlay, &frame,
1637             xpos, xpos + overlay->image_width,
1638             ypos, ypos + overlay->image_height);
1639         break;
1640       case GST_VIDEO_FORMAT_BGRx:
1641         gst_base_text_overlay_shade_BGRx (overlay, &frame,
1642             xpos, xpos + overlay->image_width,
1643             ypos, ypos + overlay->image_height);
1644         break;
1645       case GST_VIDEO_FORMAT_RGBx:
1646         gst_base_text_overlay_shade_RGBx (overlay, &frame,
1647             xpos, xpos + overlay->image_width,
1648             ypos, ypos + overlay->image_height);
1649         break;
1650       case GST_VIDEO_FORMAT_ARGB:
1651         gst_base_text_overlay_shade_ARGB (overlay, &frame,
1652             xpos, xpos + overlay->image_width,
1653             ypos, ypos + overlay->image_height);
1654         break;
1655       case GST_VIDEO_FORMAT_ABGR:
1656         gst_base_text_overlay_shade_ABGR (overlay, &frame,
1657             xpos, xpos + overlay->image_width,
1658             ypos, ypos + overlay->image_height);
1659         break;
1660       case GST_VIDEO_FORMAT_RGBA:
1661         gst_base_text_overlay_shade_RGBA (overlay, &frame,
1662             xpos, xpos + overlay->image_width,
1663             ypos, ypos + overlay->image_height);
1664         break;
1665       case GST_VIDEO_FORMAT_BGRA:
1666         gst_base_text_overlay_shade_BGRA (overlay, &frame,
1667             xpos, xpos + overlay->image_width,
1668             ypos, ypos + overlay->image_height);
1669         break;
1670       default:
1671         g_assert_not_reached ();
1672     }
1673   }
1674
1675   gst_video_overlay_composition_blend (overlay->composition, &frame);
1676
1677   gst_video_frame_unmap (&frame);
1678
1679 done:
1680
1681   return gst_pad_push (overlay->srcpad, video_frame);
1682
1683   /* ERRORS */
1684 invalid_frame:
1685   {
1686     gst_buffer_unref (video_frame);
1687     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
1688     return GST_FLOW_OK;
1689   }
1690 }
1691
1692 static GstPadLinkReturn
1693 gst_base_text_overlay_text_pad_link (GstPad * pad, GstPad * peer)
1694 {
1695   GstBaseTextOverlay *overlay;
1696
1697   overlay = GST_BASE_TEXT_OVERLAY (gst_pad_get_parent (pad));
1698   if (G_UNLIKELY (!overlay))
1699     return GST_PAD_LINK_REFUSED;
1700
1701   GST_DEBUG_OBJECT (overlay, "Text pad linked");
1702
1703   overlay->text_linked = TRUE;
1704
1705   gst_object_unref (overlay);
1706
1707   return GST_PAD_LINK_OK;
1708 }
1709
1710 static void
1711 gst_base_text_overlay_text_pad_unlink (GstPad * pad)
1712 {
1713   GstBaseTextOverlay *overlay;
1714
1715   /* don't use gst_pad_get_parent() here, will deadlock */
1716   overlay = GST_BASE_TEXT_OVERLAY (GST_PAD_PARENT (pad));
1717
1718   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
1719
1720   overlay->text_linked = FALSE;
1721
1722   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
1723 }
1724
1725 static gboolean
1726 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
1727     GstEvent * event)
1728 {
1729   gboolean ret = FALSE;
1730   GstBaseTextOverlay *overlay = NULL;
1731
1732   overlay = GST_BASE_TEXT_OVERLAY (parent);
1733
1734   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1735
1736   switch (GST_EVENT_TYPE (event)) {
1737     case GST_EVENT_CAPS:
1738     {
1739       GstCaps *caps;
1740
1741       gst_event_parse_caps (event, &caps);
1742       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
1743       gst_event_unref (event);
1744       break;
1745     }
1746     case GST_EVENT_SEGMENT:
1747     {
1748       const GstSegment *segment;
1749
1750       overlay->text_eos = FALSE;
1751
1752       gst_event_parse_segment (event, &segment);
1753
1754       if (segment->format == GST_FORMAT_TIME) {
1755         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1756         gst_segment_copy_into (segment, &overlay->text_segment);
1757         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
1758             &overlay->text_segment);
1759         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1760       } else {
1761         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1762             ("received non-TIME newsegment event on text input"));
1763       }
1764
1765       gst_event_unref (event);
1766       ret = TRUE;
1767
1768       /* wake up the video chain, it might be waiting for a text buffer or
1769        * a text segment update */
1770       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1771       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1772       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1773       break;
1774     }
1775     case GST_EVENT_FLUSH_STOP:
1776       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1777       GST_INFO_OBJECT (overlay, "text flush stop");
1778       overlay->text_flushing = FALSE;
1779       overlay->text_eos = FALSE;
1780       gst_base_text_overlay_pop_text (overlay);
1781       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
1782       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1783       gst_event_unref (event);
1784       ret = TRUE;
1785       break;
1786     case GST_EVENT_FLUSH_START:
1787       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1788       GST_INFO_OBJECT (overlay, "text flush start");
1789       overlay->text_flushing = TRUE;
1790       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1791       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1792       gst_event_unref (event);
1793       ret = TRUE;
1794       break;
1795     case GST_EVENT_EOS:
1796       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1797       overlay->text_eos = TRUE;
1798       GST_INFO_OBJECT (overlay, "text EOS");
1799       /* wake up the video chain, it might be waiting for a text buffer or
1800        * a text segment update */
1801       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1802       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1803       gst_event_unref (event);
1804       ret = TRUE;
1805       break;
1806     default:
1807       ret = gst_pad_event_default (pad, parent, event);
1808       break;
1809   }
1810
1811   return ret;
1812 }
1813
1814 static gboolean
1815 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
1816     GstEvent * event)
1817 {
1818   gboolean ret = FALSE;
1819   GstBaseTextOverlay *overlay = NULL;
1820
1821   overlay = GST_BASE_TEXT_OVERLAY (parent);
1822
1823   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
1824
1825   switch (GST_EVENT_TYPE (event)) {
1826     case GST_EVENT_CAPS:
1827     {
1828       GstCaps *caps;
1829
1830       gst_event_parse_caps (event, &caps);
1831       ret = gst_base_text_overlay_setcaps (overlay, caps);
1832       gst_event_unref (event);
1833       break;
1834     }
1835     case GST_EVENT_SEGMENT:
1836     {
1837       const GstSegment *segment;
1838
1839       GST_DEBUG_OBJECT (overlay, "received new segment");
1840
1841       gst_event_parse_segment (event, &segment);
1842
1843       if (segment->format == GST_FORMAT_TIME) {
1844         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1845             &overlay->segment);
1846
1847         gst_segment_copy_into (segment, &overlay->segment);
1848       } else {
1849         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
1850             ("received non-TIME newsegment event on video input"));
1851       }
1852
1853       ret = gst_pad_event_default (pad, parent, event);
1854       break;
1855     }
1856     case GST_EVENT_EOS:
1857       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1858       GST_INFO_OBJECT (overlay, "video EOS");
1859       overlay->video_eos = TRUE;
1860       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1861       ret = gst_pad_event_default (pad, parent, event);
1862       break;
1863     case GST_EVENT_FLUSH_START:
1864       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1865       GST_INFO_OBJECT (overlay, "video flush start");
1866       overlay->video_flushing = TRUE;
1867       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1868       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1869       ret = gst_pad_event_default (pad, parent, event);
1870       break;
1871     case GST_EVENT_FLUSH_STOP:
1872       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1873       GST_INFO_OBJECT (overlay, "video flush stop");
1874       overlay->video_flushing = FALSE;
1875       overlay->video_eos = FALSE;
1876       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
1877       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1878       ret = gst_pad_event_default (pad, parent, event);
1879       break;
1880     default:
1881       ret = gst_pad_event_default (pad, parent, event);
1882       break;
1883   }
1884
1885   return ret;
1886 }
1887
1888 static gboolean
1889 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
1890     GstQuery * query)
1891 {
1892   gboolean ret = FALSE;
1893   GstBaseTextOverlay *overlay;
1894
1895   overlay = GST_BASE_TEXT_OVERLAY (parent);
1896
1897   switch (GST_QUERY_TYPE (query)) {
1898     case GST_QUERY_CAPS:
1899     {
1900       GstCaps *filter, *caps;
1901
1902       gst_query_parse_caps (query, &filter);
1903       caps = gst_base_text_overlay_getcaps (pad, overlay, filter);
1904       gst_query_set_caps_result (query, caps);
1905       gst_caps_unref (caps);
1906       ret = TRUE;
1907       break;
1908     }
1909     default:
1910       ret = gst_pad_query_default (pad, parent, query);
1911       break;
1912   }
1913
1914   return ret;
1915 }
1916
1917 /* Called with lock held */
1918 static void
1919 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
1920 {
1921   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
1922
1923   if (overlay->text_buffer) {
1924     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
1925         overlay->text_buffer);
1926     gst_buffer_unref (overlay->text_buffer);
1927     overlay->text_buffer = NULL;
1928   }
1929
1930   /* Let the text task know we used that buffer */
1931   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
1932 }
1933
1934 /* We receive text buffers here. If they are out of segment we just ignore them.
1935    If the buffer is in our segment we keep it internally except if another one
1936    is already waiting here, in that case we wait that it gets kicked out */
1937 static GstFlowReturn
1938 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
1939     GstBuffer * buffer)
1940 {
1941   GstFlowReturn ret = GST_FLOW_OK;
1942   GstBaseTextOverlay *overlay = NULL;
1943   gboolean in_seg = FALSE;
1944   guint64 clip_start = 0, clip_stop = 0;
1945
1946   overlay = GST_BASE_TEXT_OVERLAY (parent);
1947
1948   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1949
1950   if (overlay->text_flushing) {
1951     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1952     ret = GST_FLOW_FLUSHING;
1953     GST_LOG_OBJECT (overlay, "text flushing");
1954     goto beach;
1955   }
1956
1957   if (overlay->text_eos) {
1958     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1959     ret = GST_FLOW_EOS;
1960     GST_LOG_OBJECT (overlay, "text EOS");
1961     goto beach;
1962   }
1963
1964   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
1965       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
1966       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1967       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1968           GST_BUFFER_DURATION (buffer)));
1969
1970   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1971     GstClockTime stop;
1972
1973     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1974       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1975     else
1976       stop = GST_CLOCK_TIME_NONE;
1977
1978     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
1979         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1980   } else {
1981     in_seg = TRUE;
1982   }
1983
1984   if (in_seg) {
1985     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1986       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1987     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1988       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1989
1990     /* Wait for the previous buffer to go away */
1991     while (overlay->text_buffer != NULL) {
1992       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1993           GST_DEBUG_PAD_NAME (pad));
1994       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
1995       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1996       if (overlay->text_flushing) {
1997         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1998         ret = GST_FLOW_FLUSHING;
1999         goto beach;
2000       }
2001     }
2002
2003     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2004       overlay->text_segment.position = clip_start;
2005
2006     overlay->text_buffer = buffer;
2007     /* That's a new text buffer we need to render */
2008     overlay->need_render = TRUE;
2009
2010     /* in case the video chain is waiting for a text buffer, wake it up */
2011     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2012   }
2013
2014   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2015
2016 beach:
2017
2018   return ret;
2019 }
2020
2021 static GstFlowReturn
2022 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2023     GstBuffer * buffer)
2024 {
2025   GstBaseTextOverlayClass *klass;
2026   GstBaseTextOverlay *overlay;
2027   GstFlowReturn ret = GST_FLOW_OK;
2028   gboolean in_seg = FALSE;
2029   guint64 start, stop, clip_start = 0, clip_stop = 0;
2030   gchar *text = NULL;
2031
2032   overlay = GST_BASE_TEXT_OVERLAY (parent);
2033   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2034
2035   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2036     goto missing_timestamp;
2037
2038   /* ignore buffers that are outside of the current segment */
2039   start = GST_BUFFER_TIMESTAMP (buffer);
2040
2041   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2042     stop = GST_CLOCK_TIME_NONE;
2043   } else {
2044     stop = start + GST_BUFFER_DURATION (buffer);
2045   }
2046
2047   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2048       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2049       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2050
2051   /* segment_clip() will adjust start unconditionally to segment_start if
2052    * no stop time is provided, so handle this ourselves */
2053   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2054     goto out_of_segment;
2055
2056   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2057       &clip_start, &clip_stop);
2058
2059   if (!in_seg)
2060     goto out_of_segment;
2061
2062   /* if the buffer is only partially in the segment, fix up stamps */
2063   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2064     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2065     buffer = gst_buffer_make_writable (buffer);
2066     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2067     if (stop != -1)
2068       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2069   }
2070
2071   /* now, after we've done the clipping, fix up end time if there's no
2072    * duration (we only use those estimated values internally though, we
2073    * don't want to set bogus values on the buffer itself) */
2074   if (stop == -1) {
2075     GstCaps *caps;
2076     GstStructure *s;
2077     gint fps_num, fps_denom;
2078
2079     /* FIXME, store this in setcaps */
2080     caps = gst_pad_get_current_caps (pad);
2081     s = gst_caps_get_structure (caps, 0);
2082     if (gst_structure_get_fraction (s, "framerate", &fps_num, &fps_denom) &&
2083         fps_num && fps_denom) {
2084       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2085       stop = start + gst_util_uint64_scale_int (GST_SECOND, fps_denom, fps_num);
2086     } else {
2087       GST_WARNING_OBJECT (overlay, "no duration, assuming minimal duration");
2088       stop = start + 1;         /* we need to assume some interval */
2089     }
2090     gst_caps_unref (caps);
2091   }
2092
2093   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2094
2095 wait_for_text_buf:
2096
2097   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2098
2099   if (overlay->video_flushing)
2100     goto flushing;
2101
2102   if (overlay->video_eos)
2103     goto have_eos;
2104
2105   if (overlay->silent) {
2106     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2107     ret = gst_pad_push (overlay->srcpad, buffer);
2108
2109     /* Update position */
2110     overlay->segment.position = clip_start;
2111
2112     return ret;
2113   }
2114
2115   /* Text pad not linked, rendering internal text */
2116   if (!overlay->text_linked) {
2117     if (klass->get_text) {
2118       text = klass->get_text (overlay, buffer);
2119     } else {
2120       text = g_strdup (overlay->default_text);
2121     }
2122
2123     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2124         "text: '%s'", GST_STR_NULL (text));
2125
2126     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2127
2128     if (text != NULL && *text != '\0') {
2129       /* Render and push */
2130       gst_base_text_overlay_render_text (overlay, text, -1);
2131       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2132     } else {
2133       /* Invalid or empty string */
2134       ret = gst_pad_push (overlay->srcpad, buffer);
2135     }
2136   } else {
2137     /* Text pad linked, check if we have a text buffer queued */
2138     if (overlay->text_buffer) {
2139       gboolean pop_text = FALSE, valid_text_time = TRUE;
2140       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2141       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2142       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2143       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2144       GstClockTime vid_running_time, vid_running_time_end;
2145
2146       /* if the text buffer isn't stamped right, pop it off the
2147        * queue and display it for the current video frame only */
2148       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2149           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2150         GST_WARNING_OBJECT (overlay,
2151             "Got text buffer with invalid timestamp or duration");
2152         pop_text = TRUE;
2153         valid_text_time = FALSE;
2154       } else {
2155         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2156         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2157       }
2158
2159       vid_running_time =
2160           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2161           start);
2162       vid_running_time_end =
2163           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2164           stop);
2165
2166       /* If timestamp and duration are valid */
2167       if (valid_text_time) {
2168         text_running_time =
2169             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2170             text_start);
2171         text_running_time_end =
2172             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2173             text_end);
2174       }
2175
2176       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2177           GST_TIME_ARGS (text_running_time),
2178           GST_TIME_ARGS (text_running_time_end));
2179       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2180           GST_TIME_ARGS (vid_running_time),
2181           GST_TIME_ARGS (vid_running_time_end));
2182
2183       /* Text too old or in the future */
2184       if (valid_text_time && text_running_time_end <= vid_running_time) {
2185         /* text buffer too old, get rid of it and do nothing  */
2186         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2187         pop_text = FALSE;
2188         gst_base_text_overlay_pop_text (overlay);
2189         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2190         goto wait_for_text_buf;
2191       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2192         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2193         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2194         /* Push the video frame */
2195         ret = gst_pad_push (overlay->srcpad, buffer);
2196       } else {
2197         GstMapInfo map;
2198         gchar *in_text;
2199         gsize in_size;
2200
2201         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2202         in_text = (gchar *) map.data;
2203         in_size = map.size;
2204
2205         /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2206          * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2207          * here on purpose, this is something that needs fixing upstream */
2208         if (!g_utf8_validate (in_text, in_size, NULL)) {
2209           const gchar *end = NULL;
2210
2211           GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2212           in_text = g_strndup (in_text, in_size);
2213           while (!g_utf8_validate (in_text, in_size, &end) && end)
2214             *((gchar *) end) = '*';
2215         }
2216
2217         /* Get the string */
2218         if (overlay->have_pango_markup) {
2219           text = g_strndup (in_text, in_size);
2220         } else {
2221           text = g_markup_escape_text (in_text, in_size);
2222         }
2223
2224         if (text != NULL && *text != '\0') {
2225           gint text_len = strlen (text);
2226
2227           while (text_len > 0 && (text[text_len - 1] == '\n' ||
2228                   text[text_len - 1] == '\r')) {
2229             --text_len;
2230           }
2231           GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2232           gst_base_text_overlay_render_text (overlay, text, text_len);
2233         } else {
2234           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2235           gst_base_text_overlay_render_text (overlay, " ", 1);
2236         }
2237         if (in_text != (gchar *) map.data)
2238           g_free (in_text);
2239
2240         gst_buffer_unmap (overlay->text_buffer, &map);
2241
2242         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2243         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2244
2245         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2246           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2247           pop_text = TRUE;
2248         }
2249       }
2250       if (pop_text) {
2251         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2252         gst_base_text_overlay_pop_text (overlay);
2253         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2254       }
2255     } else {
2256       gboolean wait_for_text_buf = TRUE;
2257
2258       if (overlay->text_eos)
2259         wait_for_text_buf = FALSE;
2260
2261       if (!overlay->wait_text)
2262         wait_for_text_buf = FALSE;
2263
2264       /* Text pad linked, but no text buffer available - what now? */
2265       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2266         GstClockTime text_start_running_time, text_position_running_time;
2267         GstClockTime vid_running_time;
2268
2269         vid_running_time =
2270             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2271             GST_BUFFER_TIMESTAMP (buffer));
2272         text_start_running_time =
2273             gst_segment_to_running_time (&overlay->text_segment,
2274             GST_FORMAT_TIME, overlay->text_segment.start);
2275         text_position_running_time =
2276             gst_segment_to_running_time (&overlay->text_segment,
2277             GST_FORMAT_TIME, overlay->text_segment.position);
2278
2279         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2280                 vid_running_time < text_start_running_time) ||
2281             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2282                 vid_running_time < text_position_running_time)) {
2283           wait_for_text_buf = FALSE;
2284         }
2285       }
2286
2287       if (wait_for_text_buf) {
2288         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2289         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2290         GST_DEBUG_OBJECT (overlay, "resuming");
2291         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2292         goto wait_for_text_buf;
2293       } else {
2294         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2295         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2296         ret = gst_pad_push (overlay->srcpad, buffer);
2297       }
2298     }
2299   }
2300
2301   g_free (text);
2302
2303   /* Update position */
2304   overlay->segment.position = clip_start;
2305
2306   return ret;
2307
2308 missing_timestamp:
2309   {
2310     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2311     gst_buffer_unref (buffer);
2312     return GST_FLOW_OK;
2313   }
2314
2315 flushing:
2316   {
2317     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2318     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2319     gst_buffer_unref (buffer);
2320     return GST_FLOW_FLUSHING;
2321   }
2322 have_eos:
2323   {
2324     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2325     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2326     gst_buffer_unref (buffer);
2327     return GST_FLOW_EOS;
2328   }
2329 out_of_segment:
2330   {
2331     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2332     gst_buffer_unref (buffer);
2333     return GST_FLOW_OK;
2334   }
2335 }
2336
2337 static GstStateChangeReturn
2338 gst_base_text_overlay_change_state (GstElement * element,
2339     GstStateChange transition)
2340 {
2341   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2342   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2343
2344   switch (transition) {
2345     case GST_STATE_CHANGE_PAUSED_TO_READY:
2346       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2347       overlay->text_flushing = TRUE;
2348       overlay->video_flushing = TRUE;
2349       /* pop_text will broadcast on the GCond and thus also make the video
2350        * chain exit if it's waiting for a text buffer */
2351       gst_base_text_overlay_pop_text (overlay);
2352       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2353       break;
2354     default:
2355       break;
2356   }
2357
2358   ret = parent_class->change_state (element, transition);
2359   if (ret == GST_STATE_CHANGE_FAILURE)
2360     return ret;
2361
2362   switch (transition) {
2363     case GST_STATE_CHANGE_READY_TO_PAUSED:
2364       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2365       overlay->text_flushing = FALSE;
2366       overlay->video_flushing = FALSE;
2367       overlay->video_eos = FALSE;
2368       overlay->text_eos = FALSE;
2369       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2370       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2371       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2372       break;
2373     default:
2374       break;
2375   }
2376
2377   return ret;
2378 }
2379
2380 static gboolean
2381 plugin_init (GstPlugin * plugin)
2382 {
2383   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2384           GST_TYPE_TEXT_OVERLAY) ||
2385       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2386           GST_TYPE_TIME_OVERLAY) ||
2387       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2388           GST_TYPE_CLOCK_OVERLAY) ||
2389       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2390           GST_TYPE_TEXT_RENDER)) {
2391     return FALSE;
2392   }
2393
2394   /*texttestsrc_plugin_init(module, plugin); */
2395
2396   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2397
2398   return TRUE;
2399 }
2400
2401 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
2402     pango, "Pango-based text rendering and overlay", plugin_init,
2403     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)