basetextoverlay: Add new properties and alignment type for unclamped absolute positions
[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., 51 Franklin St, Fifth Floor,
22  * Boston, MA 02110-1301, USA.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <gst/video/video.h>
30 #include <gst/video/gstvideometa.h>
31
32 #include "gstbasetextoverlay.h"
33 #include "gsttextoverlay.h"
34 #include "gsttimeoverlay.h"
35 #include "gstclockoverlay.h"
36 #include "gsttextrender.h"
37 #include <string.h>
38 #include <math.h>
39
40 /* FIXME:
41  *  - use proper strides and offset for I420
42  *  - if text is wider than the video picture, it does not get
43  *    clipped properly during blitting (if wrapping is disabled)
44  */
45
46 GST_DEBUG_CATEGORY (pango_debug);
47 #define GST_CAT_DEFAULT pango_debug
48
49 #define DEFAULT_PROP_TEXT       ""
50 #define DEFAULT_PROP_SHADING    FALSE
51 #define DEFAULT_PROP_VALIGNMENT GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE
52 #define DEFAULT_PROP_HALIGNMENT GST_BASE_TEXT_OVERLAY_HALIGN_CENTER
53 #define DEFAULT_PROP_XPAD       25
54 #define DEFAULT_PROP_YPAD       25
55 #define DEFAULT_PROP_DELTAX     0
56 #define DEFAULT_PROP_DELTAY     0
57 #define DEFAULT_PROP_XPOS       0.5
58 #define DEFAULT_PROP_YPOS       0.5
59 #define DEFAULT_PROP_WRAP_MODE  GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR
60 #define DEFAULT_PROP_FONT_DESC  ""
61 #define DEFAULT_PROP_SILENT     FALSE
62 #define DEFAULT_PROP_LINE_ALIGNMENT GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER
63 #define DEFAULT_PROP_WAIT_TEXT  TRUE
64 #define DEFAULT_PROP_AUTO_ADJUST_SIZE TRUE
65 #define DEFAULT_PROP_VERTICAL_RENDER  FALSE
66 #define DEFAULT_PROP_DRAW_SHADOW TRUE
67 #define DEFAULT_PROP_DRAW_OUTLINE TRUE
68 #define DEFAULT_PROP_COLOR      0xffffffff
69 #define DEFAULT_PROP_OUTLINE_COLOR 0xff000000
70 #define DEFAULT_PROP_SHADING_VALUE    80
71 #define DEFAULT_PROP_TEXT_X 0
72 #define DEFAULT_PROP_TEXT_Y 0
73 #define DEFAULT_PROP_TEXT_WIDTH 1
74 #define DEFAULT_PROP_TEXT_HEIGHT 1
75
76 #define MINIMUM_OUTLINE_OFFSET 1.0
77 #define DEFAULT_SCALE_BASIS    640
78
79 enum
80 {
81   PROP_0,
82   PROP_TEXT,
83   PROP_SHADING,
84   PROP_SHADING_VALUE,
85   PROP_HALIGNMENT,
86   PROP_VALIGNMENT,
87   PROP_XPAD,
88   PROP_YPAD,
89   PROP_DELTAX,
90   PROP_DELTAY,
91   PROP_XPOS,
92   PROP_YPOS,
93   PROP_X_ABSOLUTE,
94   PROP_Y_ABSOLUTE,
95   PROP_WRAP_MODE,
96   PROP_FONT_DESC,
97   PROP_SILENT,
98   PROP_LINE_ALIGNMENT,
99   PROP_WAIT_TEXT,
100   PROP_AUTO_ADJUST_SIZE,
101   PROP_VERTICAL_RENDER,
102   PROP_COLOR,
103   PROP_DRAW_SHADOW,
104   PROP_DRAW_OUTLINE,
105   PROP_OUTLINE_COLOR,
106   PROP_TEXT_X,
107   PROP_TEXT_Y,
108   PROP_TEXT_WIDTH,
109   PROP_TEXT_HEIGHT,
110   PROP_LAST
111 };
112
113 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
114
115 #define BASE_TEXT_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
116
117 #define BASE_TEXT_OVERLAY_ALL_CAPS BASE_TEXT_OVERLAY_CAPS ";" \
118     GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
119
120 static GstStaticCaps sw_template_caps =
121 GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
122
123 static GstStaticPadTemplate src_template_factory =
124 GST_STATIC_PAD_TEMPLATE ("src",
125     GST_PAD_SRC,
126     GST_PAD_ALWAYS,
127     GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
128     );
129
130 static GstStaticPadTemplate video_sink_template_factory =
131 GST_STATIC_PAD_TEMPLATE ("video_sink",
132     GST_PAD_SINK,
133     GST_PAD_ALWAYS,
134     GST_STATIC_CAPS (BASE_TEXT_OVERLAY_ALL_CAPS)
135     );
136
137 #define GST_TYPE_BASE_TEXT_OVERLAY_VALIGN (gst_base_text_overlay_valign_get_type())
138 static GType
139 gst_base_text_overlay_valign_get_type (void)
140 {
141   static GType base_text_overlay_valign_type = 0;
142   static const GEnumValue base_text_overlay_valign[] = {
143     {GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE, "baseline", "baseline"},
144     {GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM, "bottom", "bottom"},
145     {GST_BASE_TEXT_OVERLAY_VALIGN_TOP, "top", "top"},
146     {GST_BASE_TEXT_OVERLAY_VALIGN_POS, "position",
147         "Absolute position clamped to canvas"},
148     {GST_BASE_TEXT_OVERLAY_VALIGN_CENTER, "center", "center"},
149     {GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE, "absolute", "Absolute position"},
150     {0, NULL, NULL},
151   };
152
153   if (!base_text_overlay_valign_type) {
154     base_text_overlay_valign_type =
155         g_enum_register_static ("GstBaseTextOverlayVAlign",
156         base_text_overlay_valign);
157   }
158   return base_text_overlay_valign_type;
159 }
160
161 #define GST_TYPE_BASE_TEXT_OVERLAY_HALIGN (gst_base_text_overlay_halign_get_type())
162 static GType
163 gst_base_text_overlay_halign_get_type (void)
164 {
165   static GType base_text_overlay_halign_type = 0;
166   static const GEnumValue base_text_overlay_halign[] = {
167     {GST_BASE_TEXT_OVERLAY_HALIGN_LEFT, "left", "left"},
168     {GST_BASE_TEXT_OVERLAY_HALIGN_CENTER, "center", "center"},
169     {GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT, "right", "right"},
170     {GST_BASE_TEXT_OVERLAY_HALIGN_POS, "position",
171         "Absolute position clamped to canvas"},
172     {GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE, "absolute", "Absolute position"},
173     {0, NULL, NULL},
174   };
175
176   if (!base_text_overlay_halign_type) {
177     base_text_overlay_halign_type =
178         g_enum_register_static ("GstBaseTextOverlayHAlign",
179         base_text_overlay_halign);
180   }
181   return base_text_overlay_halign_type;
182 }
183
184
185 #define GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE (gst_base_text_overlay_wrap_mode_get_type())
186 static GType
187 gst_base_text_overlay_wrap_mode_get_type (void)
188 {
189   static GType base_text_overlay_wrap_mode_type = 0;
190   static const GEnumValue base_text_overlay_wrap_mode[] = {
191     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE, "none", "none"},
192     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD, "word", "word"},
193     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_CHAR, "char", "char"},
194     {GST_BASE_TEXT_OVERLAY_WRAP_MODE_WORD_CHAR, "wordchar", "wordchar"},
195     {0, NULL, NULL},
196   };
197
198   if (!base_text_overlay_wrap_mode_type) {
199     base_text_overlay_wrap_mode_type =
200         g_enum_register_static ("GstBaseTextOverlayWrapMode",
201         base_text_overlay_wrap_mode);
202   }
203   return base_text_overlay_wrap_mode_type;
204 }
205
206 #define GST_TYPE_BASE_TEXT_OVERLAY_LINE_ALIGN (gst_base_text_overlay_line_align_get_type())
207 static GType
208 gst_base_text_overlay_line_align_get_type (void)
209 {
210   static GType base_text_overlay_line_align_type = 0;
211   static const GEnumValue base_text_overlay_line_align[] = {
212     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT, "left", "left"},
213     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_CENTER, "center", "center"},
214     {GST_BASE_TEXT_OVERLAY_LINE_ALIGN_RIGHT, "right", "right"},
215     {0, NULL, NULL}
216   };
217
218   if (!base_text_overlay_line_align_type) {
219     base_text_overlay_line_align_type =
220         g_enum_register_static ("GstBaseTextOverlayLineAlign",
221         base_text_overlay_line_align);
222   }
223   return base_text_overlay_line_align_type;
224 }
225
226 #define GST_BASE_TEXT_OVERLAY_GET_LOCK(ov) (&GST_BASE_TEXT_OVERLAY (ov)->lock)
227 #define GST_BASE_TEXT_OVERLAY_GET_COND(ov) (&GST_BASE_TEXT_OVERLAY (ov)->cond)
228 #define GST_BASE_TEXT_OVERLAY_LOCK(ov)     (g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
229 #define GST_BASE_TEXT_OVERLAY_UNLOCK(ov)   (g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
230 #define GST_BASE_TEXT_OVERLAY_WAIT(ov)     (g_cond_wait (GST_BASE_TEXT_OVERLAY_GET_COND (ov), GST_BASE_TEXT_OVERLAY_GET_LOCK (ov)))
231 #define GST_BASE_TEXT_OVERLAY_SIGNAL(ov)   (g_cond_signal (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
232 #define GST_BASE_TEXT_OVERLAY_BROADCAST(ov)(g_cond_broadcast (GST_BASE_TEXT_OVERLAY_GET_COND (ov)))
233
234 static GstElementClass *parent_class = NULL;
235 static void gst_base_text_overlay_base_init (gpointer g_class);
236 static void gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass);
237 static void gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
238     GstBaseTextOverlayClass * klass);
239
240 static GstStateChangeReturn gst_base_text_overlay_change_state (GstElement *
241     element, GstStateChange transition);
242
243 static GstCaps *gst_base_text_overlay_get_videosink_caps (GstPad * pad,
244     GstBaseTextOverlay * overlay, GstCaps * filter);
245 static GstCaps *gst_base_text_overlay_get_src_caps (GstPad * pad,
246     GstBaseTextOverlay * overlay, GstCaps * filter);
247 static gboolean gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay,
248     GstCaps * caps);
249 static gboolean gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay,
250     GstCaps * caps);
251 static gboolean gst_base_text_overlay_src_event (GstPad * pad,
252     GstObject * parent, GstEvent * event);
253 static gboolean gst_base_text_overlay_src_query (GstPad * pad,
254     GstObject * parent, GstQuery * query);
255
256 static gboolean gst_base_text_overlay_video_event (GstPad * pad,
257     GstObject * parent, GstEvent * event);
258 static gboolean gst_base_text_overlay_video_query (GstPad * pad,
259     GstObject * parent, GstQuery * query);
260 static GstFlowReturn gst_base_text_overlay_video_chain (GstPad * pad,
261     GstObject * parent, GstBuffer * buffer);
262
263 static gboolean gst_base_text_overlay_text_event (GstPad * pad,
264     GstObject * parent, GstEvent * event);
265 static GstFlowReturn gst_base_text_overlay_text_chain (GstPad * pad,
266     GstObject * parent, GstBuffer * buffer);
267 static GstPadLinkReturn gst_base_text_overlay_text_pad_link (GstPad * pad,
268     GstObject * parent, GstPad * peer);
269 static void gst_base_text_overlay_text_pad_unlink (GstPad * pad,
270     GstObject * parent);
271 static void gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay);
272
273 static void gst_base_text_overlay_finalize (GObject * object);
274 static void gst_base_text_overlay_set_property (GObject * object, guint prop_id,
275     const GValue * value, GParamSpec * pspec);
276 static void gst_base_text_overlay_get_property (GObject * object, guint prop_id,
277     GValue * value, GParamSpec * pspec);
278
279 static void
280 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
281     PangoFontDescription * desc);
282 static gboolean gst_base_text_overlay_can_handle_caps (GstCaps * incaps);
283
284 static void
285 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay);
286
287 GType
288 gst_base_text_overlay_get_type (void)
289 {
290   static GType type = 0;
291
292   if (g_once_init_enter ((gsize *) & type)) {
293     static const GTypeInfo info = {
294       sizeof (GstBaseTextOverlayClass),
295       (GBaseInitFunc) gst_base_text_overlay_base_init,
296       NULL,
297       (GClassInitFunc) gst_base_text_overlay_class_init,
298       NULL,
299       NULL,
300       sizeof (GstBaseTextOverlay),
301       0,
302       (GInstanceInitFunc) gst_base_text_overlay_init,
303     };
304
305     g_once_init_leave ((gsize *) & type,
306         g_type_register_static (GST_TYPE_ELEMENT, "GstBaseTextOverlay", &info,
307             0));
308   }
309
310   return type;
311 }
312
313 static gchar *
314 gst_base_text_overlay_get_text (GstBaseTextOverlay * overlay,
315     GstBuffer * video_frame)
316 {
317   return g_strdup (overlay->default_text);
318 }
319
320 static void
321 gst_base_text_overlay_base_init (gpointer g_class)
322 {
323   GstBaseTextOverlayClass *klass = GST_BASE_TEXT_OVERLAY_CLASS (g_class);
324   PangoFontMap *fontmap;
325
326   /* Only lock for the subclasses here, the base class
327    * doesn't have this mutex yet and it's not necessary
328    * here */
329   if (klass->pango_lock)
330     g_mutex_lock (klass->pango_lock);
331   fontmap = pango_cairo_font_map_get_default ();
332   klass->pango_context =
333       pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
334   pango_context_set_base_gravity (klass->pango_context, PANGO_GRAVITY_SOUTH);
335   if (klass->pango_lock)
336     g_mutex_unlock (klass->pango_lock);
337 }
338
339 static void
340 gst_base_text_overlay_class_init (GstBaseTextOverlayClass * klass)
341 {
342   GObjectClass *gobject_class;
343   GstElementClass *gstelement_class;
344
345   gobject_class = (GObjectClass *) klass;
346   gstelement_class = (GstElementClass *) klass;
347
348   parent_class = g_type_class_peek_parent (klass);
349
350   gobject_class->finalize = gst_base_text_overlay_finalize;
351   gobject_class->set_property = gst_base_text_overlay_set_property;
352   gobject_class->get_property = gst_base_text_overlay_get_property;
353
354   gst_element_class_add_pad_template (gstelement_class,
355       gst_static_pad_template_get (&src_template_factory));
356   gst_element_class_add_pad_template (gstelement_class,
357       gst_static_pad_template_get (&video_sink_template_factory));
358
359   gstelement_class->change_state =
360       GST_DEBUG_FUNCPTR (gst_base_text_overlay_change_state);
361
362   klass->pango_lock = g_slice_new (GMutex);
363   g_mutex_init (klass->pango_lock);
364
365   klass->get_text = gst_base_text_overlay_get_text;
366
367   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT,
368       g_param_spec_string ("text", "text",
369           "Text to be display.", DEFAULT_PROP_TEXT,
370           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
371   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING,
372       g_param_spec_boolean ("shaded-background", "shaded background",
373           "Whether to shade the background under the text area",
374           DEFAULT_PROP_SHADING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
375   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SHADING_VALUE,
376       g_param_spec_uint ("shading-value", "background shading value",
377           "Shading value to apply if shaded-background is true", 1, 255,
378           DEFAULT_PROP_SHADING_VALUE,
379           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
380   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VALIGNMENT,
381       g_param_spec_enum ("valignment", "vertical alignment",
382           "Vertical alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_VALIGN,
383           DEFAULT_PROP_VALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
384   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_HALIGNMENT,
385       g_param_spec_enum ("halignment", "horizontal alignment",
386           "Horizontal alignment of the text", GST_TYPE_BASE_TEXT_OVERLAY_HALIGN,
387           DEFAULT_PROP_HALIGNMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
388   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPAD,
389       g_param_spec_int ("xpad", "horizontal paddding",
390           "Horizontal paddding when using left/right alignment", 0, G_MAXINT,
391           DEFAULT_PROP_XPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
392   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPAD,
393       g_param_spec_int ("ypad", "vertical padding",
394           "Vertical padding when using top/bottom alignment", 0, G_MAXINT,
395           DEFAULT_PROP_YPAD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
396   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAX,
397       g_param_spec_int ("deltax", "X position modifier",
398           "Shift X position to the left or to the right. Unit is pixels.",
399           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAX,
400           GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
401   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DELTAY,
402       g_param_spec_int ("deltay", "Y position modifier",
403           "Shift Y position up or down. Unit is pixels.",
404           G_MININT, G_MAXINT, DEFAULT_PROP_DELTAY,
405           GST_PARAM_CONTROLLABLE | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
406
407   /**
408    * GstBaseTextOverlay:text-x:
409    *
410    * Resulting X position of font rendering.
411    */
412   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_X,
413       g_param_spec_int ("text-x", "horizontal position.",
414           "Resulting X position of font rendering.", -G_MAXINT,
415           G_MAXINT, DEFAULT_PROP_TEXT_X, G_PARAM_READABLE));
416
417   /**
418    * GstBaseTextOverlay:text-y:
419    *
420    * Resulting Y position of font rendering.
421    */
422   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_Y,
423       g_param_spec_int ("text-y", "vertical position",
424           "Resulting X position of font rendering.", -G_MAXINT,
425           G_MAXINT, DEFAULT_PROP_TEXT_Y, G_PARAM_READABLE));
426
427   /**
428    * GstBaseTextOverlay:text-width:
429    *
430    * Resulting width of font rendering.
431    */
432   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_WIDTH,
433       g_param_spec_uint ("text-width", "width",
434           "Resulting width of font rendering",
435           0, G_MAXINT, DEFAULT_PROP_TEXT_WIDTH, G_PARAM_READABLE));
436
437   /**
438    * GstBaseTextOverlay:text-height:
439    *
440    * Resulting height of font rendering.
441    */
442   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_TEXT_HEIGHT,
443       g_param_spec_uint ("text-height", "height",
444           "Resulting height of font rendering", 0,
445           G_MAXINT, DEFAULT_PROP_TEXT_HEIGHT, G_PARAM_READABLE));
446
447   /**
448    * GstBaseTextOverlay:xpos:
449    *
450    * Horizontal position of the rendered text when using positioned alignment.
451    */
452   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_XPOS,
453       g_param_spec_double ("xpos", "horizontal position",
454           "Horizontal position when using clamped position alignment", 0, 1.0,
455           DEFAULT_PROP_XPOS,
456           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
457   /**
458    * GstBaseTextOverlay:ypos:
459    *
460    * Vertical position of the rendered text when using positioned alignment.
461    */
462   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_YPOS,
463       g_param_spec_double ("ypos", "vertical position",
464           "Vertical position when using clamped position alignment", 0, 1.0,
465           DEFAULT_PROP_YPOS,
466           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
467
468   /**
469    * GstBaseTextOverlay:x-absolute:
470    *
471    * Horizontal position of the rendered text when using absolute alignment.
472    *
473    * Maps the text area to be exactly inside of video canvas for [0, 0] - [1, 1]:
474    *
475    * [0, 0]: Top-Lefts of video and text are aligned
476    * [0.5, 0.5]: Centers are aligned
477    * [1, 1]: Bottom-Rights are aligned
478    *
479    * Values beyond [0, 0] - [1, 1] place the text outside of the video canvas.
480    *
481    * Since: 1.8
482    */
483   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_X_ABSOLUTE,
484       g_param_spec_double ("x-absolute", "horizontal position",
485           "Horizontal position when using absolute alignment", -G_MAXDOUBLE,
486           G_MAXDOUBLE, DEFAULT_PROP_XPOS,
487           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
488   /**
489    * GstBaseTextOverlay:y-absolute:
490    *
491    * See x-absolute.
492    *
493    * Vertical position of the rendered text when using absolute alignment.
494    *
495    * Since: 1.8
496    */
497   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_Y_ABSOLUTE,
498       g_param_spec_double ("y-absolute", "vertical position",
499           "Vertical position when using absolute alignment", -G_MAXDOUBLE,
500           G_MAXDOUBLE, DEFAULT_PROP_YPOS,
501           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
502
503   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WRAP_MODE,
504       g_param_spec_enum ("wrap-mode", "wrap mode",
505           "Whether to wrap the text and if so how.",
506           GST_TYPE_BASE_TEXT_OVERLAY_WRAP_MODE, DEFAULT_PROP_WRAP_MODE,
507           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
508   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_FONT_DESC,
509       g_param_spec_string ("font-desc", "font description",
510           "Pango font description of font to be used for rendering. "
511           "See documentation of pango_font_description_from_string "
512           "for syntax.", DEFAULT_PROP_FONT_DESC,
513           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
514   /**
515    * GstBaseTextOverlay:color:
516    *
517    * Color of the rendered text.
518    */
519   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_COLOR,
520       g_param_spec_uint ("color", "Color",
521           "Color to use for text (big-endian ARGB).", 0, G_MAXUINT32,
522           DEFAULT_PROP_COLOR,
523           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
524   /**
525    * GstTextOverlay:outline-color:
526    *
527    * Color of the outline of the rendered text.
528    */
529   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_OUTLINE_COLOR,
530       g_param_spec_uint ("outline-color", "Text Outline Color",
531           "Color to use for outline the text (big-endian ARGB).", 0,
532           G_MAXUINT32, DEFAULT_PROP_OUTLINE_COLOR,
533           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
534
535   /**
536    * GstBaseTextOverlay:line-alignment:
537    *
538    * Alignment of text lines relative to each other (for multi-line text)
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   /* FIXME 0.11: rename to "visible" or "text-visible" or "render-text" */
552   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_SILENT,
553       g_param_spec_boolean ("silent", "silent",
554           "Whether to render the text string",
555           DEFAULT_PROP_SILENT,
556           G_PARAM_READWRITE | GST_PARAM_CONTROLLABLE | G_PARAM_STATIC_STRINGS));
557   /**
558    * GstBaseTextOverlay:draw-shadow:
559    *
560    * If set, a text shadow is drawn.
561    *
562    * Since: 1.6
563    */
564   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_SHADOW,
565       g_param_spec_boolean ("draw-shadow", "draw-shadow",
566           "Whether to draw shadow",
567           DEFAULT_PROP_DRAW_SHADOW,
568           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
569   /**
570    * GstBaseTextOverlay:draw-outline:
571    *
572    * If set, an outline is drawn.
573    *
574    * Since: 1.6
575    */
576   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_DRAW_OUTLINE,
577       g_param_spec_boolean ("draw-outline", "draw-outline",
578           "Whether to draw outline",
579           DEFAULT_PROP_DRAW_OUTLINE,
580           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
581   /**
582    * GstBaseTextOverlay:wait-text:
583    *
584    * If set, the video will block until a subtitle is received on the text pad.
585    * If video and subtitles are sent in sync, like from the same demuxer, this
586    * property should be set.
587    */
588   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_WAIT_TEXT,
589       g_param_spec_boolean ("wait-text", "Wait Text",
590           "Whether to wait for subtitles",
591           DEFAULT_PROP_WAIT_TEXT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
592
593   g_object_class_install_property (G_OBJECT_CLASS (klass),
594       PROP_AUTO_ADJUST_SIZE, g_param_spec_boolean ("auto-resize", "auto resize",
595           "Automatically adjust font size to screen-size.",
596           DEFAULT_PROP_AUTO_ADJUST_SIZE,
597           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
598
599   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_VERTICAL_RENDER,
600       g_param_spec_boolean ("vertical-render", "vertical render",
601           "Vertical Render.", DEFAULT_PROP_VERTICAL_RENDER,
602           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
603 }
604
605 static void
606 gst_base_text_overlay_finalize (GObject * object)
607 {
608   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
609
610   g_free (overlay->default_text);
611
612   if (overlay->composition) {
613     gst_video_overlay_composition_unref (overlay->composition);
614     overlay->composition = NULL;
615   }
616
617   if (overlay->text_image) {
618     gst_buffer_unref (overlay->text_image);
619     overlay->text_image = NULL;
620   }
621
622   if (overlay->layout) {
623     g_object_unref (overlay->layout);
624     overlay->layout = NULL;
625   }
626
627   if (overlay->text_buffer) {
628     gst_buffer_unref (overlay->text_buffer);
629     overlay->text_buffer = NULL;
630   }
631
632   g_mutex_clear (&overlay->lock);
633   g_cond_clear (&overlay->cond);
634
635   G_OBJECT_CLASS (parent_class)->finalize (object);
636 }
637
638 static void
639 gst_base_text_overlay_init (GstBaseTextOverlay * overlay,
640     GstBaseTextOverlayClass * klass)
641 {
642   GstPadTemplate *template;
643   PangoFontDescription *desc;
644
645   /* video sink */
646   template = gst_static_pad_template_get (&video_sink_template_factory);
647   overlay->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
648   gst_object_unref (template);
649   gst_pad_set_event_function (overlay->video_sinkpad,
650       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_event));
651   gst_pad_set_chain_function (overlay->video_sinkpad,
652       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_chain));
653   gst_pad_set_query_function (overlay->video_sinkpad,
654       GST_DEBUG_FUNCPTR (gst_base_text_overlay_video_query));
655   GST_PAD_SET_PROXY_ALLOCATION (overlay->video_sinkpad);
656   gst_element_add_pad (GST_ELEMENT (overlay), overlay->video_sinkpad);
657
658   template =
659       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
660       "text_sink");
661   if (template) {
662     /* text sink */
663     overlay->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
664
665     gst_pad_set_event_function (overlay->text_sinkpad,
666         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_event));
667     gst_pad_set_chain_function (overlay->text_sinkpad,
668         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_chain));
669     gst_pad_set_link_function (overlay->text_sinkpad,
670         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_link));
671     gst_pad_set_unlink_function (overlay->text_sinkpad,
672         GST_DEBUG_FUNCPTR (gst_base_text_overlay_text_pad_unlink));
673     gst_element_add_pad (GST_ELEMENT (overlay), overlay->text_sinkpad);
674   }
675
676   /* (video) source */
677   template = gst_static_pad_template_get (&src_template_factory);
678   overlay->srcpad = gst_pad_new_from_template (template, "src");
679   gst_object_unref (template);
680   gst_pad_set_event_function (overlay->srcpad,
681       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_event));
682   gst_pad_set_query_function (overlay->srcpad,
683       GST_DEBUG_FUNCPTR (gst_base_text_overlay_src_query));
684   gst_element_add_pad (GST_ELEMENT (overlay), overlay->srcpad);
685
686   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
687   overlay->layout =
688       pango_layout_new (GST_BASE_TEXT_OVERLAY_GET_CLASS
689       (overlay)->pango_context);
690   desc =
691       pango_context_get_font_description (GST_BASE_TEXT_OVERLAY_GET_CLASS
692       (overlay)->pango_context);
693   gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
694
695   overlay->color = DEFAULT_PROP_COLOR;
696   overlay->outline_color = DEFAULT_PROP_OUTLINE_COLOR;
697   overlay->halign = DEFAULT_PROP_HALIGNMENT;
698   overlay->valign = DEFAULT_PROP_VALIGNMENT;
699   overlay->xpad = DEFAULT_PROP_XPAD;
700   overlay->ypad = DEFAULT_PROP_YPAD;
701   overlay->deltax = DEFAULT_PROP_DELTAX;
702   overlay->deltay = DEFAULT_PROP_DELTAY;
703   overlay->xpos = DEFAULT_PROP_XPOS;
704   overlay->ypos = DEFAULT_PROP_YPOS;
705
706   overlay->wrap_mode = DEFAULT_PROP_WRAP_MODE;
707
708   overlay->want_shading = DEFAULT_PROP_SHADING;
709   overlay->shading_value = DEFAULT_PROP_SHADING_VALUE;
710   overlay->silent = DEFAULT_PROP_SILENT;
711   overlay->draw_shadow = DEFAULT_PROP_DRAW_SHADOW;
712   overlay->draw_outline = DEFAULT_PROP_DRAW_OUTLINE;
713   overlay->wait_text = DEFAULT_PROP_WAIT_TEXT;
714   overlay->auto_adjust_size = DEFAULT_PROP_AUTO_ADJUST_SIZE;
715
716   overlay->default_text = g_strdup (DEFAULT_PROP_TEXT);
717   overlay->need_render = TRUE;
718   overlay->text_image = NULL;
719   overlay->use_vertical_render = DEFAULT_PROP_VERTICAL_RENDER;
720
721   overlay->line_align = DEFAULT_PROP_LINE_ALIGNMENT;
722   pango_layout_set_alignment (overlay->layout,
723       (PangoAlignment) overlay->line_align);
724
725   overlay->text_buffer = NULL;
726   overlay->text_linked = FALSE;
727
728   overlay->composition = NULL;
729   overlay->upstream_composition = NULL;
730
731   overlay->width = 1;
732   overlay->height = 1;
733
734   overlay->window_width = 1;
735   overlay->window_height = 1;
736
737   overlay->text_width = DEFAULT_PROP_TEXT_WIDTH;
738   overlay->text_height = DEFAULT_PROP_TEXT_HEIGHT;
739
740   overlay->text_x = DEFAULT_PROP_TEXT_X;
741   overlay->text_y = DEFAULT_PROP_TEXT_Y;
742
743   overlay->render_width = 1;
744   overlay->render_height = 1;
745   overlay->render_scale = 1.0l;
746
747   g_mutex_init (&overlay->lock);
748   g_cond_init (&overlay->cond);
749   gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
750   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
751 }
752
753 static void
754 gst_base_text_overlay_set_wrap_mode (GstBaseTextOverlay * overlay, gint width)
755 {
756   if (overlay->wrap_mode == GST_BASE_TEXT_OVERLAY_WRAP_MODE_NONE) {
757     GST_DEBUG_OBJECT (overlay, "Set wrap mode NONE");
758     pango_layout_set_width (overlay->layout, -1);
759   } else {
760     width = width * PANGO_SCALE;
761
762     GST_DEBUG_OBJECT (overlay, "Set layout width %d", width);
763     GST_DEBUG_OBJECT (overlay, "Set wrap mode    %d", overlay->wrap_mode);
764     pango_layout_set_width (overlay->layout, width);
765   }
766
767   pango_layout_set_wrap (overlay->layout, (PangoWrapMode) overlay->wrap_mode);
768 }
769
770 static gboolean
771 gst_base_text_overlay_setcaps_txt (GstBaseTextOverlay * overlay, GstCaps * caps)
772 {
773   GstStructure *structure;
774   const gchar *format;
775
776   structure = gst_caps_get_structure (caps, 0);
777   format = gst_structure_get_string (structure, "format");
778   overlay->have_pango_markup = (strcmp (format, "pango-markup") == 0);
779
780   return TRUE;
781 }
782
783 /* only negotiate/query video overlay composition support for now */
784 static gboolean
785 gst_base_text_overlay_negotiate (GstBaseTextOverlay * overlay, GstCaps * caps)
786 {
787   gboolean upstream_has_meta = FALSE;
788   gboolean caps_has_meta = FALSE;
789   gboolean alloc_has_meta = FALSE;
790   gboolean attach = FALSE;
791   gboolean ret = TRUE;
792   guint width, height;
793   GstCapsFeatures *f;
794   GstCaps *overlay_caps;
795   GstQuery *query;
796   guint alloc_index;
797
798   GST_DEBUG_OBJECT (overlay, "performing negotiation");
799
800   /* Clear any pending reconfigure to avoid negotiating twice */
801   gst_pad_check_reconfigure (overlay->srcpad);
802
803   if (!caps)
804     caps = gst_pad_get_current_caps (overlay->video_sinkpad);
805   else
806     gst_caps_ref (caps);
807
808   if (!caps || gst_caps_is_empty (caps))
809     goto no_format;
810
811   /* Check if upstream caps have meta */
812   if ((f = gst_caps_get_features (caps, 0))) {
813     upstream_has_meta = gst_caps_features_contains (f,
814         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
815   }
816
817   /* Initialize dimensions */
818   width = overlay->width;
819   height = overlay->height;
820
821   if (upstream_has_meta) {
822     overlay_caps = gst_caps_ref (caps);
823   } else {
824     GstCaps *peercaps;
825
826     /* BaseTransform requires caps for the allocation query to work */
827     overlay_caps = gst_caps_copy (caps);
828     f = gst_caps_get_features (overlay_caps, 0);
829     gst_caps_features_add (f,
830         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
831
832     /* Then check if downstream accept overlay composition in caps */
833     /* FIXME: We should probably check if downstream *prefers* the
834      * overlay meta, and only enforce usage of it if we can't handle
835      * the format ourselves and thus would have to drop the overlays.
836      * Otherwise we should prefer what downstream wants here.
837      */
838     peercaps = gst_pad_peer_query_caps (overlay->srcpad, NULL);
839     caps_has_meta = gst_caps_can_intersect (peercaps, overlay_caps);
840     gst_caps_unref (peercaps);
841
842     GST_DEBUG ("caps have overlay meta %d", caps_has_meta);
843   }
844
845   if (upstream_has_meta || caps_has_meta) {
846     /* Send caps immediatly, it's needed by GstBaseTransform to get a reply
847      * from allocation query */
848     ret = gst_pad_set_caps (overlay->srcpad, overlay_caps);
849
850     /* First check if the allocation meta has compositon */
851     query = gst_query_new_allocation (overlay_caps, FALSE);
852
853     if (!gst_pad_peer_query (overlay->srcpad, query)) {
854       /* no problem, we use the query defaults */
855       GST_DEBUG_OBJECT (overlay, "ALLOCATION query failed");
856
857       /* In case we were flushing, mark reconfigure and fail this method,
858        * will make it retry */
859       if (overlay->video_flushing)
860         ret = FALSE;
861     }
862
863     alloc_has_meta = gst_query_find_allocation_meta (query,
864         GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, &alloc_index);
865
866     GST_DEBUG ("sink alloc has overlay meta %d", alloc_has_meta);
867
868     if (alloc_has_meta) {
869       const GstStructure *params;
870
871       gst_query_parse_nth_allocation_meta (query, alloc_index, &params);
872       if (params) {
873         if (gst_structure_get (params, "width", G_TYPE_UINT, &width,
874                 "height", G_TYPE_UINT, &height, NULL)) {
875           GST_DEBUG ("received window size: %dx%d", width, height);
876           g_assert (width != 0 && height != 0);
877         }
878       }
879     }
880
881     gst_query_unref (query);
882   }
883
884   /* Update render size if needed */
885   overlay->window_width = width;
886   overlay->window_height = height;
887   gst_base_text_overlay_update_render_size (overlay);
888
889   /* For backward compatbility, we will prefer bliting if downstream
890    * allocation does not support the meta. In other case we will prefer
891    * attaching, and will fail the negotiation in the unlikely case we are
892    * force to blit, but format isn't supported. */
893
894   if (upstream_has_meta) {
895     attach = TRUE;
896   } else if (caps_has_meta) {
897     if (alloc_has_meta) {
898       attach = TRUE;
899     } else {
900       /* Don't attach unless we cannot handle the format */
901       attach = !gst_base_text_overlay_can_handle_caps (caps);
902     }
903   } else {
904     ret = gst_base_text_overlay_can_handle_caps (caps);
905   }
906
907   /* If we attach, then pick the overlay caps */
908   if (attach) {
909     GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, overlay_caps);
910     /* Caps where already sent */
911   } else if (ret) {
912     GST_DEBUG_OBJECT (overlay, "Using caps %" GST_PTR_FORMAT, caps);
913     ret = gst_pad_set_caps (overlay->srcpad, caps);
914   }
915
916   overlay->attach_compo_to_buffer = attach;
917
918   if (!ret) {
919     GST_DEBUG_OBJECT (overlay, "negotiation failed, schedule reconfigure");
920     gst_pad_mark_reconfigure (overlay->srcpad);
921   }
922
923   gst_caps_unref (overlay_caps);
924   gst_caps_unref (caps);
925
926   return ret;
927
928 no_format:
929   {
930     if (caps)
931       gst_caps_unref (caps);
932     return FALSE;
933   }
934 }
935
936 static gboolean
937 gst_base_text_overlay_can_handle_caps (GstCaps * incaps)
938 {
939   gboolean ret;
940   GstCaps *caps;
941   static GstStaticCaps static_caps = GST_STATIC_CAPS (BASE_TEXT_OVERLAY_CAPS);
942
943   caps = gst_static_caps_get (&static_caps);
944   ret = gst_caps_is_subset (incaps, caps);
945   gst_caps_unref (caps);
946
947   return ret;
948 }
949
950 static gboolean
951 gst_base_text_overlay_setcaps (GstBaseTextOverlay * overlay, GstCaps * caps)
952 {
953   GstVideoInfo info;
954   gboolean ret = FALSE;
955
956   if (!gst_video_info_from_caps (&info, caps))
957     goto invalid_caps;
958
959   /* Render again if size have changed */
960   if (GST_VIDEO_INFO_WIDTH (&info) != GST_VIDEO_INFO_WIDTH (&overlay->info) ||
961       GST_VIDEO_INFO_HEIGHT (&info) != GST_VIDEO_INFO_HEIGHT (&overlay->info))
962     overlay->need_render = TRUE;
963
964   overlay->info = info;
965   overlay->format = GST_VIDEO_INFO_FORMAT (&info);
966   overlay->width = GST_VIDEO_INFO_WIDTH (&info);
967   overlay->height = GST_VIDEO_INFO_HEIGHT (&info);
968
969   ret = gst_base_text_overlay_negotiate (overlay, caps);
970
971   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
972
973   if (!overlay->attach_compo_to_buffer &&
974       !gst_base_text_overlay_can_handle_caps (caps)) {
975     GST_DEBUG_OBJECT (overlay, "unsupported caps %" GST_PTR_FORMAT, caps);
976     ret = FALSE;
977   }
978   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
979
980   return ret;
981
982   /* ERRORS */
983 invalid_caps:
984   {
985     GST_DEBUG_OBJECT (overlay, "could not parse caps");
986     return FALSE;
987   }
988 }
989
990 static void
991 gst_base_text_overlay_set_property (GObject * object, guint prop_id,
992     const GValue * value, GParamSpec * pspec)
993 {
994   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
995
996   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
997   switch (prop_id) {
998     case PROP_TEXT:
999       g_free (overlay->default_text);
1000       overlay->default_text = g_value_dup_string (value);
1001       break;
1002     case PROP_SHADING:
1003       overlay->want_shading = g_value_get_boolean (value);
1004       break;
1005     case PROP_XPAD:
1006       overlay->xpad = g_value_get_int (value);
1007       break;
1008     case PROP_YPAD:
1009       overlay->ypad = g_value_get_int (value);
1010       break;
1011     case PROP_DELTAX:
1012       overlay->deltax = g_value_get_int (value);
1013       break;
1014     case PROP_DELTAY:
1015       overlay->deltay = g_value_get_int (value);
1016       break;
1017     case PROP_XPOS:
1018       overlay->xpos = g_value_get_double (value);
1019       break;
1020     case PROP_YPOS:
1021       overlay->ypos = g_value_get_double (value);
1022       break;
1023     case PROP_X_ABSOLUTE:
1024       overlay->xpos = g_value_get_double (value);
1025       break;
1026     case PROP_Y_ABSOLUTE:
1027       overlay->ypos = g_value_get_double (value);
1028       break;
1029     case PROP_VALIGNMENT:
1030       overlay->valign = g_value_get_enum (value);
1031       break;
1032     case PROP_HALIGNMENT:
1033       overlay->halign = g_value_get_enum (value);
1034       break;
1035     case PROP_WRAP_MODE:
1036       overlay->wrap_mode = g_value_get_enum (value);
1037       break;
1038     case PROP_FONT_DESC:
1039     {
1040       PangoFontDescription *desc;
1041       const gchar *fontdesc_str;
1042
1043       fontdesc_str = g_value_get_string (value);
1044       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1045       desc = pango_font_description_from_string (fontdesc_str);
1046       if (desc) {
1047         GST_LOG_OBJECT (overlay, "font description set: %s", fontdesc_str);
1048         pango_layout_set_font_description (overlay->layout, desc);
1049         gst_base_text_overlay_adjust_values_with_fontdesc (overlay, desc);
1050         pango_font_description_free (desc);
1051       } else {
1052         GST_WARNING_OBJECT (overlay, "font description parse failed: %s",
1053             fontdesc_str);
1054       }
1055       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1056       break;
1057     }
1058     case PROP_COLOR:
1059       overlay->color = g_value_get_uint (value);
1060       break;
1061     case PROP_OUTLINE_COLOR:
1062       overlay->outline_color = g_value_get_uint (value);
1063       break;
1064     case PROP_SILENT:
1065       overlay->silent = g_value_get_boolean (value);
1066       break;
1067     case PROP_DRAW_SHADOW:
1068       overlay->draw_shadow = g_value_get_boolean (value);
1069       break;
1070     case PROP_DRAW_OUTLINE:
1071       overlay->draw_outline = g_value_get_boolean (value);
1072       break;
1073     case PROP_LINE_ALIGNMENT:
1074       overlay->line_align = g_value_get_enum (value);
1075       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1076       pango_layout_set_alignment (overlay->layout,
1077           (PangoAlignment) overlay->line_align);
1078       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1079       break;
1080     case PROP_WAIT_TEXT:
1081       overlay->wait_text = g_value_get_boolean (value);
1082       break;
1083     case PROP_AUTO_ADJUST_SIZE:
1084       overlay->auto_adjust_size = g_value_get_boolean (value);
1085       break;
1086     case PROP_VERTICAL_RENDER:
1087       overlay->use_vertical_render = g_value_get_boolean (value);
1088       if (overlay->use_vertical_render) {
1089         overlay->valign = GST_BASE_TEXT_OVERLAY_VALIGN_TOP;
1090         overlay->halign = GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT;
1091         overlay->line_align = GST_BASE_TEXT_OVERLAY_LINE_ALIGN_LEFT;
1092         g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1093         pango_layout_set_alignment (overlay->layout,
1094             (PangoAlignment) overlay->line_align);
1095         g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1096       }
1097       break;
1098     case PROP_SHADING_VALUE:
1099       overlay->shading_value = g_value_get_uint (value);
1100       break;
1101     default:
1102       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1103       break;
1104   }
1105
1106   overlay->need_render = TRUE;
1107   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1108 }
1109
1110 static void
1111 gst_base_text_overlay_get_property (GObject * object, guint prop_id,
1112     GValue * value, GParamSpec * pspec)
1113 {
1114   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (object);
1115
1116   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
1117   switch (prop_id) {
1118     case PROP_TEXT:
1119       g_value_set_string (value, overlay->default_text);
1120       break;
1121     case PROP_SHADING:
1122       g_value_set_boolean (value, overlay->want_shading);
1123       break;
1124     case PROP_XPAD:
1125       g_value_set_int (value, overlay->xpad);
1126       break;
1127     case PROP_YPAD:
1128       g_value_set_int (value, overlay->ypad);
1129       break;
1130     case PROP_DELTAX:
1131       g_value_set_int (value, overlay->deltax);
1132       break;
1133     case PROP_DELTAY:
1134       g_value_set_int (value, overlay->deltay);
1135       break;
1136     case PROP_XPOS:
1137       g_value_set_double (value, overlay->xpos);
1138       break;
1139     case PROP_YPOS:
1140       g_value_set_double (value, overlay->ypos);
1141       break;
1142     case PROP_X_ABSOLUTE:
1143       g_value_set_double (value, overlay->xpos);
1144       break;
1145     case PROP_Y_ABSOLUTE:
1146       g_value_set_double (value, overlay->ypos);
1147       break;
1148     case PROP_VALIGNMENT:
1149       g_value_set_enum (value, overlay->valign);
1150       break;
1151     case PROP_HALIGNMENT:
1152       g_value_set_enum (value, overlay->halign);
1153       break;
1154     case PROP_WRAP_MODE:
1155       g_value_set_enum (value, overlay->wrap_mode);
1156       break;
1157     case PROP_SILENT:
1158       g_value_set_boolean (value, overlay->silent);
1159       break;
1160     case PROP_DRAW_SHADOW:
1161       g_value_set_boolean (value, overlay->draw_shadow);
1162       break;
1163     case PROP_DRAW_OUTLINE:
1164       g_value_set_boolean (value, overlay->draw_outline);
1165       break;
1166     case PROP_LINE_ALIGNMENT:
1167       g_value_set_enum (value, overlay->line_align);
1168       break;
1169     case PROP_WAIT_TEXT:
1170       g_value_set_boolean (value, overlay->wait_text);
1171       break;
1172     case PROP_AUTO_ADJUST_SIZE:
1173       g_value_set_boolean (value, overlay->auto_adjust_size);
1174       break;
1175     case PROP_VERTICAL_RENDER:
1176       g_value_set_boolean (value, overlay->use_vertical_render);
1177       break;
1178     case PROP_COLOR:
1179       g_value_set_uint (value, overlay->color);
1180       break;
1181     case PROP_OUTLINE_COLOR:
1182       g_value_set_uint (value, overlay->outline_color);
1183       break;
1184     case PROP_SHADING_VALUE:
1185       g_value_set_uint (value, overlay->shading_value);
1186       break;
1187     case PROP_FONT_DESC:
1188     {
1189       const PangoFontDescription *desc;
1190
1191       g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1192       desc = pango_layout_get_font_description (overlay->layout);
1193       if (!desc)
1194         g_value_set_string (value, "");
1195       else {
1196         g_value_take_string (value, pango_font_description_to_string (desc));
1197       }
1198       g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1199       break;
1200     }
1201     case PROP_TEXT_X:
1202       g_value_set_int (value, overlay->text_x);
1203       break;
1204     case PROP_TEXT_Y:
1205       g_value_set_int (value, overlay->text_y);
1206       break;
1207     case PROP_TEXT_WIDTH:
1208       g_value_set_uint (value, overlay->text_width);
1209       break;
1210     case PROP_TEXT_HEIGHT:
1211       g_value_set_uint (value, overlay->text_height);
1212       break;
1213     default:
1214       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1215       break;
1216   }
1217
1218   overlay->need_render = TRUE;
1219   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
1220 }
1221
1222 static gboolean
1223 gst_base_text_overlay_src_query (GstPad * pad, GstObject * parent,
1224     GstQuery * query)
1225 {
1226   gboolean ret = FALSE;
1227   GstBaseTextOverlay *overlay;
1228
1229   overlay = GST_BASE_TEXT_OVERLAY (parent);
1230
1231   switch (GST_QUERY_TYPE (query)) {
1232     case GST_QUERY_CAPS:
1233     {
1234       GstCaps *filter, *caps;
1235
1236       gst_query_parse_caps (query, &filter);
1237       caps = gst_base_text_overlay_get_src_caps (pad, overlay, filter);
1238       gst_query_set_caps_result (query, caps);
1239       gst_caps_unref (caps);
1240       ret = TRUE;
1241       break;
1242     }
1243     default:
1244       ret = gst_pad_query_default (pad, parent, query);
1245       break;
1246   }
1247
1248   return ret;
1249 }
1250
1251 static void
1252 gst_base_text_overlay_update_render_size (GstBaseTextOverlay * overlay)
1253 {
1254   gdouble video_aspect = (gdouble) overlay->width / (gdouble) overlay->height;
1255   gdouble window_aspect = (gdouble) overlay->window_width /
1256       (gdouble) overlay->window_height;
1257
1258   guint text_buffer_width = 0;
1259   guint text_buffer_height = 0;
1260
1261   if (video_aspect >= window_aspect) {
1262     text_buffer_width = overlay->window_width;
1263     text_buffer_height = window_aspect * overlay->window_height / video_aspect;
1264   } else if (video_aspect < window_aspect) {
1265     text_buffer_width = video_aspect * overlay->window_width / window_aspect;
1266     text_buffer_height = overlay->window_height;
1267   }
1268
1269   if ((overlay->render_width == text_buffer_width) &&
1270       (overlay->render_height == text_buffer_height))
1271     return;
1272
1273   overlay->need_render = TRUE;
1274   overlay->render_width = text_buffer_width;
1275   overlay->render_height = text_buffer_height;
1276   overlay->render_scale = (gdouble) overlay->render_width /
1277       (gdouble) overlay->width;
1278
1279   GST_DEBUG ("updating render dimensions %dx%d from stream %dx%d, window %dx%d "
1280       "and render scale %f", overlay->render_width, overlay->render_height,
1281       overlay->width, overlay->height, overlay->window_width,
1282       overlay->window_height, overlay->render_scale);
1283 }
1284
1285 static gboolean
1286 gst_base_text_overlay_src_event (GstPad * pad, GstObject * parent,
1287     GstEvent * event)
1288 {
1289   GstBaseTextOverlay *overlay;
1290   gboolean ret;
1291
1292   overlay = GST_BASE_TEXT_OVERLAY (parent);
1293
1294   if (overlay->text_linked) {
1295     gst_event_ref (event);
1296     ret = gst_pad_push_event (overlay->video_sinkpad, event);
1297     gst_pad_push_event (overlay->text_sinkpad, event);
1298   } else {
1299     ret = gst_pad_push_event (overlay->video_sinkpad, event);
1300   }
1301
1302   return ret;
1303 }
1304
1305 /**
1306  * gst_base_text_overlay_add_feature_and_intersect:
1307  *
1308  * Creates a new #GstCaps containing the (given caps +
1309  * given caps feature) + (given caps intersected by the
1310  * given filter).
1311  *
1312  * Returns: the new #GstCaps
1313  */
1314 static GstCaps *
1315 gst_base_text_overlay_add_feature_and_intersect (GstCaps * caps,
1316     const gchar * feature, GstCaps * filter)
1317 {
1318   int i, caps_size;
1319   GstCaps *new_caps;
1320
1321   new_caps = gst_caps_copy (caps);
1322
1323   caps_size = gst_caps_get_size (new_caps);
1324   for (i = 0; i < caps_size; i++) {
1325     GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
1326
1327     if (!gst_caps_features_is_any (features)) {
1328       gst_caps_features_add (features, feature);
1329     }
1330   }
1331
1332   gst_caps_append (new_caps, gst_caps_intersect_full (caps,
1333           filter, GST_CAPS_INTERSECT_FIRST));
1334
1335   return new_caps;
1336 }
1337
1338 /**
1339  * gst_base_text_overlay_intersect_by_feature:
1340  *
1341  * Creates a new #GstCaps based on the following filtering rule.
1342  *
1343  * For each individual caps contained in given caps, if the
1344  * caps uses the given caps feature, keep a version of the caps
1345  * with the feature and an another one without. Otherwise, intersect
1346  * the caps with the given filter.
1347  *
1348  * Returns: the new #GstCaps
1349  */
1350 static GstCaps *
1351 gst_base_text_overlay_intersect_by_feature (GstCaps * caps,
1352     const gchar * feature, GstCaps * filter)
1353 {
1354   int i, caps_size;
1355   GstCaps *new_caps;
1356
1357   new_caps = gst_caps_new_empty ();
1358
1359   caps_size = gst_caps_get_size (caps);
1360   for (i = 0; i < caps_size; i++) {
1361     GstStructure *caps_structure = gst_caps_get_structure (caps, i);
1362     GstCapsFeatures *caps_features =
1363         gst_caps_features_copy (gst_caps_get_features (caps, i));
1364     GstCaps *filtered_caps;
1365     GstCaps *simple_caps =
1366         gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
1367     gst_caps_set_features (simple_caps, 0, caps_features);
1368
1369     if (gst_caps_features_contains (caps_features, feature)) {
1370       gst_caps_append (new_caps, gst_caps_copy (simple_caps));
1371
1372       gst_caps_features_remove (caps_features, feature);
1373       filtered_caps = gst_caps_ref (simple_caps);
1374     } else {
1375       filtered_caps = gst_caps_intersect_full (simple_caps, filter,
1376           GST_CAPS_INTERSECT_FIRST);
1377     }
1378
1379     gst_caps_unref (simple_caps);
1380     gst_caps_append (new_caps, filtered_caps);
1381   }
1382
1383   return new_caps;
1384 }
1385
1386 static GstCaps *
1387 gst_base_text_overlay_get_videosink_caps (GstPad * pad,
1388     GstBaseTextOverlay * overlay, GstCaps * filter)
1389 {
1390   GstPad *srcpad = overlay->srcpad;
1391   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1392
1393   if (G_UNLIKELY (!overlay))
1394     return gst_pad_get_pad_template_caps (pad);
1395
1396   if (filter) {
1397     /* filter caps + composition feature + filter caps
1398      * filtered by the software caps. */
1399     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1400     overlay_filter = gst_base_text_overlay_add_feature_and_intersect (filter,
1401         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1402     gst_caps_unref (sw_caps);
1403
1404     GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
1405         overlay_filter);
1406   }
1407
1408   peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
1409
1410   if (overlay_filter)
1411     gst_caps_unref (overlay_filter);
1412
1413   if (peer_caps) {
1414
1415     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
1416
1417     if (gst_caps_is_any (peer_caps)) {
1418       /* if peer returns ANY caps, return filtered src pad template caps */
1419       caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
1420     } else {
1421
1422       /* duplicate caps which contains the composition into one version with
1423        * the meta and one without. Filter the other caps by the software caps */
1424       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1425       caps = gst_base_text_overlay_intersect_by_feature (peer_caps,
1426           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1427       gst_caps_unref (sw_caps);
1428     }
1429
1430     gst_caps_unref (peer_caps);
1431
1432   } else {
1433     /* no peer, our padtemplate is enough then */
1434     caps = gst_pad_get_pad_template_caps (pad);
1435   }
1436
1437   if (filter) {
1438     GstCaps *intersection = gst_caps_intersect_full (filter, caps,
1439         GST_CAPS_INTERSECT_FIRST);
1440     gst_caps_unref (caps);
1441     caps = intersection;
1442   }
1443
1444   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1445
1446   return caps;
1447 }
1448
1449 static GstCaps *
1450 gst_base_text_overlay_get_src_caps (GstPad * pad, GstBaseTextOverlay * overlay,
1451     GstCaps * filter)
1452 {
1453   GstPad *sinkpad = overlay->video_sinkpad;
1454   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
1455
1456   if (G_UNLIKELY (!overlay))
1457     return gst_pad_get_pad_template_caps (pad);
1458
1459   if (filter) {
1460     /* duplicate filter caps which contains the composition into one version
1461      * with the meta and one without. Filter the other caps by the software
1462      * caps */
1463     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1464     overlay_filter =
1465         gst_base_text_overlay_intersect_by_feature (filter,
1466         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1467     gst_caps_unref (sw_caps);
1468   }
1469
1470   peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
1471
1472   if (overlay_filter)
1473     gst_caps_unref (overlay_filter);
1474
1475   if (peer_caps) {
1476
1477     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
1478
1479     if (gst_caps_is_any (peer_caps)) {
1480
1481       /* if peer returns ANY caps, return filtered sink pad template caps */
1482       caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
1483
1484     } else {
1485
1486       /* return upstream caps + composition feature + upstream caps
1487        * filtered by the software caps. */
1488       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
1489       caps = gst_base_text_overlay_add_feature_and_intersect (peer_caps,
1490           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
1491       gst_caps_unref (sw_caps);
1492     }
1493
1494     gst_caps_unref (peer_caps);
1495
1496   } else {
1497     /* no peer, our padtemplate is enough then */
1498     caps = gst_pad_get_pad_template_caps (pad);
1499   }
1500
1501   if (filter) {
1502     GstCaps *intersection;
1503
1504     intersection =
1505         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
1506     gst_caps_unref (caps);
1507     caps = intersection;
1508   }
1509   GST_DEBUG_OBJECT (overlay, "returning  %" GST_PTR_FORMAT, caps);
1510
1511   return caps;
1512 }
1513
1514 static void
1515 gst_base_text_overlay_adjust_values_with_fontdesc (GstBaseTextOverlay * overlay,
1516     PangoFontDescription * desc)
1517 {
1518   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
1519   overlay->shadow_offset = (double) (font_size) / 13.0;
1520   overlay->outline_offset = (double) (font_size) / 15.0;
1521   if (overlay->outline_offset < MINIMUM_OUTLINE_OFFSET)
1522     overlay->outline_offset = MINIMUM_OUTLINE_OFFSET;
1523 }
1524
1525 static void
1526 gst_base_text_overlay_get_pos (GstBaseTextOverlay * overlay,
1527     gint * xpos, gint * ypos)
1528 {
1529   gint width, height;
1530
1531   width = overlay->logical_rect.width;
1532   height = overlay->logical_rect.height;
1533
1534   *xpos = overlay->ink_rect.x - overlay->logical_rect.x;
1535   switch (overlay->halign) {
1536     case GST_BASE_TEXT_OVERLAY_HALIGN_LEFT:
1537       *xpos += overlay->xpad;
1538       *xpos = MAX (0, *xpos);
1539       break;
1540     case GST_BASE_TEXT_OVERLAY_HALIGN_CENTER:
1541       *xpos += (overlay->width - width) / 2;
1542       break;
1543     case GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT:
1544       *xpos += overlay->width - width - overlay->xpad;
1545       *xpos = MIN (overlay->width - overlay->ink_rect.width, *xpos);
1546       break;
1547     case GST_BASE_TEXT_OVERLAY_HALIGN_POS:
1548       *xpos += (gint) (overlay->width * overlay->xpos) - width / 2;
1549       *xpos = CLAMP (*xpos, 0, overlay->width - overlay->ink_rect.width);
1550       if (*xpos < 0)
1551         *xpos = 0;
1552       break;
1553     case GST_BASE_TEXT_OVERLAY_HALIGN_ABSOLUTE:
1554       *xpos = (overlay->width - overlay->text_width) * overlay->xpos;
1555       break;
1556     default:
1557       *xpos = 0;
1558   }
1559   *xpos += overlay->deltax;
1560
1561   *ypos = overlay->ink_rect.y - overlay->logical_rect.y;
1562   switch (overlay->valign) {
1563     case GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM:
1564       /* This will be the same as baseline, if there is enough padding,
1565        * otherwise it will avoid clipping the text */
1566       *ypos += overlay->height - height - overlay->ypad;
1567       *ypos = MIN (overlay->height - overlay->ink_rect.height, *ypos);
1568       break;
1569     case GST_BASE_TEXT_OVERLAY_VALIGN_BASELINE:
1570       *ypos += overlay->height - height - overlay->ypad;
1571       /* Don't clip, this would not respect the base line */
1572       break;
1573     case GST_BASE_TEXT_OVERLAY_VALIGN_TOP:
1574       *ypos += overlay->ypad;
1575       *ypos = MAX (0.0, *ypos);
1576       break;
1577     case GST_BASE_TEXT_OVERLAY_VALIGN_POS:
1578       *ypos = (gint) (overlay->height * overlay->ypos) - height / 2;
1579       *ypos = CLAMP (*ypos, 0, overlay->height - overlay->ink_rect.height);
1580       break;
1581     case GST_BASE_TEXT_OVERLAY_VALIGN_ABSOLUTE:
1582       *ypos = (overlay->height - overlay->text_height) * overlay->ypos;
1583       break;
1584     case GST_BASE_TEXT_OVERLAY_VALIGN_CENTER:
1585       *ypos = (overlay->height - height) / 2;
1586       break;
1587     default:
1588       *ypos = overlay->ypad;
1589       break;
1590   }
1591   *ypos += overlay->deltay;
1592
1593   overlay->text_x = *xpos;
1594   overlay->text_y = *ypos;
1595
1596   GST_DEBUG_OBJECT (overlay, "Placing overlay at (%d, %d)", *xpos, *ypos);
1597 }
1598
1599 static inline void
1600 gst_base_text_overlay_set_composition (GstBaseTextOverlay * overlay)
1601 {
1602   gint xpos, ypos;
1603   GstVideoOverlayRectangle *rectangle;
1604
1605   if (overlay->text_image && overlay->text_width != 1) {
1606     gint render_width, render_height;
1607
1608     gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
1609
1610     render_width = overlay->ink_rect.width;
1611     render_height = overlay->ink_rect.height;
1612
1613     GST_DEBUG ("updating composition for '%s' with window size %dx%d, "
1614         "buffer size %dx%d, render size %dx%d and position (%d, %d)",
1615         overlay->default_text, overlay->window_width, overlay->window_height,
1616         overlay->text_width, overlay->text_height, render_width,
1617         render_height, xpos, ypos);
1618
1619     gst_buffer_add_video_meta (overlay->text_image, GST_VIDEO_FRAME_FLAG_NONE,
1620         GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB,
1621         overlay->text_width, overlay->text_height);
1622
1623     rectangle = gst_video_overlay_rectangle_new_raw (overlay->text_image,
1624         xpos, ypos, render_width, render_height,
1625         GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
1626
1627     if (overlay->composition)
1628       gst_video_overlay_composition_unref (overlay->composition);
1629
1630     overlay->composition = gst_video_overlay_composition_new (rectangle);
1631     gst_video_overlay_rectangle_unref (rectangle);
1632
1633     if (overlay->upstream_composition) {
1634       guint num_overlays =
1635           gst_video_overlay_composition_n_rectangles
1636           (overlay->upstream_composition);
1637
1638       for (guint i = 0; i < num_overlays; i++) {
1639         GstVideoOverlayRectangle *rectangle;
1640         rectangle =
1641             gst_video_overlay_composition_get_rectangle
1642             (overlay->upstream_composition, i);
1643         gst_video_overlay_composition_add_rectangle (overlay->composition,
1644             rectangle);
1645       }
1646     }
1647
1648   } else if (overlay->composition) {
1649     gst_video_overlay_composition_unref (overlay->composition);
1650     overlay->composition = NULL;
1651   }
1652 }
1653
1654 static gboolean
1655 gst_text_overlay_filter_foreground_attr (PangoAttribute * attr, gpointer data)
1656 {
1657   if (attr->klass->type == PANGO_ATTR_FOREGROUND) {
1658     return FALSE;
1659   } else {
1660     return TRUE;
1661   }
1662 }
1663
1664 static void
1665 gst_base_text_overlay_render_pangocairo (GstBaseTextOverlay * overlay,
1666     const gchar * string, gint textlen)
1667 {
1668   cairo_t *cr;
1669   cairo_surface_t *surface;
1670   PangoRectangle ink_rect, logical_rect;
1671   cairo_matrix_t cairo_matrix;
1672   gint unscaled_width, unscaled_height;
1673   gint width, height;
1674   gboolean full_width = FALSE;
1675   double scalef = 1.0;
1676   double a, r, g, b;
1677   gdouble shadow_offset = 0.0;
1678   gdouble outline_offset = 0.0;
1679   gint xpad = 0, ypad = 0;
1680   GstBuffer *buffer;
1681   GstMapInfo map;
1682
1683   g_mutex_lock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1684
1685   if (overlay->auto_adjust_size) {
1686     /* 640 pixel is default */
1687     scalef = (double) (overlay->width) / DEFAULT_SCALE_BASIS;
1688   }
1689
1690   if (overlay->draw_shadow)
1691     shadow_offset = ceil (overlay->shadow_offset);
1692
1693   /* This value is uses as cairo line width, which is the diameter of a pen
1694    * that is circular. That's why only half of it is used to offset */
1695   if (overlay->draw_outline)
1696     outline_offset = ceil (overlay->outline_offset);
1697
1698   if (overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_LEFT ||
1699       overlay->halign == GST_BASE_TEXT_OVERLAY_HALIGN_RIGHT)
1700     xpad = overlay->xpad;
1701
1702   if (overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_TOP ||
1703       overlay->valign == GST_BASE_TEXT_OVERLAY_VALIGN_BOTTOM)
1704     ypad = overlay->ypad;
1705
1706   pango_layout_set_width (overlay->layout, -1);
1707   /* set text on pango layout */
1708   pango_layout_set_markup (overlay->layout, string, textlen);
1709
1710   /* get subtitle image size */
1711   pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1712
1713   unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1714   width = ceil (unscaled_width * scalef);
1715
1716   /*
1717    * subtitle image width can be larger then overlay width
1718    * so rearrange overlay wrap mode.
1719    */
1720   if (overlay->use_vertical_render) {
1721     if (width + ypad > overlay->height) {
1722       width = overlay->height - ypad;
1723       full_width = TRUE;
1724     }
1725   } else if (width + xpad > overlay->width) {
1726     width = overlay->width - xpad;
1727     full_width = TRUE;
1728   }
1729
1730   if (full_width) {
1731     unscaled_width = width / scalef;
1732     gst_base_text_overlay_set_wrap_mode (overlay,
1733         unscaled_width - shadow_offset - outline_offset);
1734     pango_layout_get_pixel_extents (overlay->layout, &ink_rect, &logical_rect);
1735
1736     unscaled_width = ink_rect.width + shadow_offset + outline_offset;
1737     width = ceil (unscaled_width * scalef);
1738   }
1739
1740   unscaled_height = ink_rect.height + shadow_offset + outline_offset;
1741   height = ceil (unscaled_height * scalef);
1742
1743   if (overlay->use_vertical_render) {
1744     if (height + xpad > overlay->width) {
1745       height = overlay->width - xpad;
1746       unscaled_height = width / scalef;
1747     }
1748   } else if (height + ypad > overlay->height) {
1749     height = overlay->height - ypad;
1750     unscaled_height = height / scalef;
1751   }
1752
1753   GST_DEBUG_OBJECT (overlay, "Rendering with ink rect (%d, %d) %dx%d and "
1754       "logical rect (%d, %d) %dx%d", ink_rect.x, ink_rect.y, ink_rect.width,
1755       ink_rect.height, logical_rect.x, logical_rect.y, logical_rect.width,
1756       logical_rect.height);
1757   GST_DEBUG_OBJECT (overlay, "Rendering with width %d and height %d "
1758       "(shadow %f, outline %f)", unscaled_width, unscaled_height,
1759       shadow_offset, outline_offset);
1760
1761
1762   /* Save and scale the rectangles so get_pos() can place the text */
1763   overlay->ink_rect.x =
1764       ceil ((ink_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1765   overlay->ink_rect.y =
1766       ceil ((ink_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1767   overlay->ink_rect.width = width;
1768   overlay->ink_rect.height = height;
1769
1770   overlay->logical_rect.x =
1771       ceil ((logical_rect.x - ceil (outline_offset / 2.0l)) * scalef);
1772   overlay->logical_rect.y =
1773       ceil ((logical_rect.y - ceil (outline_offset / 2.0l)) * scalef);
1774   overlay->logical_rect.width =
1775       ceil ((logical_rect.width + shadow_offset + outline_offset) * scalef);
1776   overlay->logical_rect.height =
1777       ceil ((logical_rect.height + shadow_offset + outline_offset) * scalef);
1778
1779   /* flip the rectangle if doing vertical render */
1780   if (overlay->use_vertical_render) {
1781     PangoRectangle tmp = overlay->ink_rect;
1782
1783     overlay->ink_rect.x = tmp.y;
1784     overlay->ink_rect.y = tmp.x;
1785     overlay->ink_rect.width = tmp.height;
1786     overlay->ink_rect.height = tmp.width;
1787     /* We want the top left corect, but we now have the top right */
1788     overlay->ink_rect.x += overlay->ink_rect.width;
1789
1790     tmp = overlay->logical_rect;
1791     overlay->logical_rect.x = tmp.y;
1792     overlay->logical_rect.y = tmp.x;
1793     overlay->logical_rect.width = tmp.height;
1794     overlay->logical_rect.height = tmp.width;
1795     overlay->logical_rect.x += overlay->logical_rect.width;
1796   }
1797
1798   /* scale to reported window size */
1799   width = ceil (width * overlay->render_scale);
1800   height = ceil (height * overlay->render_scale);
1801   scalef *= overlay->render_scale;
1802
1803   if (width <= 0 || height <= 0) {
1804     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1805     GST_DEBUG_OBJECT (overlay,
1806         "Overlay is outside video frame. Skipping text rendering");
1807     return;
1808   }
1809
1810   if (unscaled_height <= 0 || unscaled_width <= 0) {
1811     g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1812     GST_DEBUG_OBJECT (overlay,
1813         "Overlay is outside video frame. Skipping text rendering");
1814     return;
1815   }
1816   /* Prepare the transformation matrix. Note that the transformation happens
1817    * in reverse order. So for horizontal text, we will translate and then
1818    * scale. This is important to understand which scale shall be used. */
1819   cairo_matrix_init_scale (&cairo_matrix, scalef, scalef);
1820
1821   if (overlay->use_vertical_render) {
1822     gint tmp;
1823
1824     /* tranlate to the center of the image, rotate, and tranlate the rotated
1825      * image back to the right place */
1826     cairo_matrix_translate (&cairo_matrix, unscaled_height / 2.0l,
1827         unscaled_width / 2.0l);
1828     /* 90 degree clockwise rotation which is PI / 2 in radiants */
1829     cairo_matrix_rotate (&cairo_matrix, G_PI_2);
1830     cairo_matrix_translate (&cairo_matrix, -(unscaled_width / 2.0l),
1831         -(unscaled_height / 2.0l));
1832
1833     /* Swap width and height */
1834     tmp = height;
1835     height = width;
1836     width = tmp;
1837   }
1838
1839   cairo_matrix_translate (&cairo_matrix,
1840       ceil (outline_offset / 2.0l) - ink_rect.x,
1841       ceil (outline_offset / 2.0l) - ink_rect.y);
1842
1843   /* reallocate overlay buffer */
1844   buffer = gst_buffer_new_and_alloc (4 * width * height);
1845   gst_buffer_replace (&overlay->text_image, buffer);
1846   gst_buffer_unref (buffer);
1847
1848   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1849   surface = cairo_image_surface_create_for_data (map.data,
1850       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1851   cr = cairo_create (surface);
1852
1853   /* clear surface */
1854   cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
1855   cairo_paint (cr);
1856
1857   cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
1858
1859   /* apply transformations */
1860   cairo_set_matrix (cr, &cairo_matrix);
1861
1862   /* FIXME: We use show_layout everywhere except for the surface
1863    * because it's really faster and internally does all kinds of
1864    * caching. Unfortunately we have to paint to a cairo path for
1865    * the outline and this is slow. Once Pango supports user fonts
1866    * we should use them, see
1867    * https://bugzilla.gnome.org/show_bug.cgi?id=598695
1868    *
1869    * Idea would the be, to create a cairo user font that
1870    * does shadow, outline, text painting in the
1871    * render_glyph function.
1872    */
1873
1874   /* draw shadow text */
1875   if (overlay->draw_shadow) {
1876     PangoAttrList *origin_attr, *filtered_attr, *temp_attr;
1877
1878     /* Store a ref on the original attributes for later restoration */
1879     origin_attr =
1880         pango_attr_list_ref (pango_layout_get_attributes (overlay->layout));
1881     /* Take a copy of the original attributes, because pango_attr_list_filter
1882      * modifies the passed list */
1883     temp_attr = pango_attr_list_copy (origin_attr);
1884     filtered_attr =
1885         pango_attr_list_filter (temp_attr,
1886         gst_text_overlay_filter_foreground_attr, NULL);
1887     pango_attr_list_unref (temp_attr);
1888
1889     cairo_save (cr);
1890     cairo_translate (cr, overlay->shadow_offset, overlay->shadow_offset);
1891     cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 0.5);
1892     pango_layout_set_attributes (overlay->layout, filtered_attr);
1893     pango_cairo_show_layout (cr, overlay->layout);
1894     pango_layout_set_attributes (overlay->layout, origin_attr);
1895     pango_attr_list_unref (filtered_attr);
1896     pango_attr_list_unref (origin_attr);
1897     cairo_restore (cr);
1898   }
1899
1900   /* draw outline text */
1901   if (overlay->draw_outline) {
1902     a = (overlay->outline_color >> 24) & 0xff;
1903     r = (overlay->outline_color >> 16) & 0xff;
1904     g = (overlay->outline_color >> 8) & 0xff;
1905     b = (overlay->outline_color >> 0) & 0xff;
1906
1907     cairo_save (cr);
1908     cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1909     cairo_set_line_width (cr, overlay->outline_offset);
1910     pango_cairo_layout_path (cr, overlay->layout);
1911     cairo_stroke (cr);
1912     cairo_restore (cr);
1913   }
1914
1915   a = (overlay->color >> 24) & 0xff;
1916   r = (overlay->color >> 16) & 0xff;
1917   g = (overlay->color >> 8) & 0xff;
1918   b = (overlay->color >> 0) & 0xff;
1919
1920   /* draw text */
1921   cairo_save (cr);
1922   cairo_set_source_rgba (cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
1923   pango_cairo_show_layout (cr, overlay->layout);
1924   cairo_restore (cr);
1925
1926   cairo_destroy (cr);
1927   cairo_surface_destroy (surface);
1928   gst_buffer_unmap (buffer, &map);
1929   if (width != 0)
1930     overlay->text_width = width;
1931   if (height != 0)
1932     overlay->text_height = height;
1933   g_mutex_unlock (GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay)->pango_lock);
1934
1935   gst_base_text_overlay_set_composition (overlay);
1936 }
1937
1938 static inline void
1939 gst_base_text_overlay_shade_planar_Y (GstBaseTextOverlay * overlay,
1940     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1941 {
1942   gint i, j, dest_stride;
1943   guint8 *dest_ptr;
1944
1945   dest_stride = dest->info.stride[0];
1946   dest_ptr = dest->data[0];
1947
1948   for (i = y0; i < y1; ++i) {
1949     for (j = x0; j < x1; ++j) {
1950       gint y = dest_ptr[(i * dest_stride) + j] - overlay->shading_value;
1951
1952       dest_ptr[(i * dest_stride) + j] = CLAMP (y, 0, 255);
1953     }
1954   }
1955 }
1956
1957 static inline void
1958 gst_base_text_overlay_shade_packed_Y (GstBaseTextOverlay * overlay,
1959     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1960 {
1961   gint i, j;
1962   guint dest_stride, pixel_stride;
1963   guint8 *dest_ptr;
1964
1965   dest_stride = GST_VIDEO_FRAME_COMP_STRIDE (dest, 0);
1966   dest_ptr = GST_VIDEO_FRAME_COMP_DATA (dest, 0);
1967   pixel_stride = GST_VIDEO_FRAME_COMP_PSTRIDE (dest, 0);
1968
1969   if (x0 != 0)
1970     x0 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x0);
1971   if (x1 != 0)
1972     x1 = GST_VIDEO_FORMAT_INFO_SCALE_WIDTH (dest->info.finfo, 0, x1);
1973
1974   if (y0 != 0)
1975     y0 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y0);
1976   if (y1 != 0)
1977     y1 = GST_VIDEO_FORMAT_INFO_SCALE_HEIGHT (dest->info.finfo, 0, y1);
1978
1979   for (i = y0; i < y1; i++) {
1980     for (j = x0; j < x1; j++) {
1981       gint y;
1982       gint y_pos;
1983
1984       y_pos = (i * dest_stride) + j * pixel_stride;
1985       y = dest_ptr[y_pos] - overlay->shading_value;
1986
1987       dest_ptr[y_pos] = CLAMP (y, 0, 255);
1988     }
1989   }
1990 }
1991
1992 #define gst_base_text_overlay_shade_BGRx gst_base_text_overlay_shade_xRGB
1993 #define gst_base_text_overlay_shade_RGBx gst_base_text_overlay_shade_xRGB
1994 #define gst_base_text_overlay_shade_xBGR gst_base_text_overlay_shade_xRGB
1995 static inline void
1996 gst_base_text_overlay_shade_xRGB (GstBaseTextOverlay * overlay,
1997     GstVideoFrame * dest, gint x0, gint x1, gint y0, gint y1)
1998 {
1999   gint i, j;
2000   guint8 *dest_ptr;
2001
2002   dest_ptr = dest->data[0];
2003
2004   for (i = y0; i < y1; i++) {
2005     for (j = x0; j < x1; j++) {
2006       gint y, y_pos, k;
2007
2008       y_pos = (i * 4 * overlay->width) + j * 4;
2009       for (k = 0; k < 4; k++) {
2010         y = dest_ptr[y_pos + k] - overlay->shading_value;
2011         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);
2012       }
2013     }
2014   }
2015 }
2016
2017 /* FIXME: orcify */
2018 static void
2019 gst_base_text_overlay_shade_rgb24 (GstBaseTextOverlay * overlay,
2020     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2021 {
2022   const int pstride = 3;
2023   gint y, x, stride, shading_val, tmp;
2024   guint8 *p;
2025
2026   shading_val = -overlay->shading_value;
2027   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2028
2029   for (y = y0; y < y1; ++y) {
2030     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2031     p += (y * stride) + (x0 * pstride);
2032     for (x = x0; x < x1; ++x) {
2033       tmp = *p + shading_val;
2034       *p++ = CLAMP (tmp, 0, 255);
2035       tmp = *p + shading_val;
2036       *p++ = CLAMP (tmp, 0, 255);
2037       tmp = *p + shading_val;
2038       *p++ = CLAMP (tmp, 0, 255);
2039     }
2040   }
2041 }
2042
2043 static void
2044 gst_base_text_overlay_shade_IYU1 (GstBaseTextOverlay * overlay,
2045     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2046 {
2047   gint y, x, stride, shading_val, tmp;
2048   guint8 *p;
2049
2050   shading_val = -overlay->shading_value;
2051   stride = GST_VIDEO_FRAME_PLANE_STRIDE (frame, 0);
2052
2053   /* IYU1: packed 4:1:1 YUV (Cb-Y0-Y1-Cr-Y2-Y3 ...) */
2054   for (y = y0; y < y1; ++y) {
2055     p = GST_VIDEO_FRAME_PLANE_DATA (frame, 0);
2056     /* move to Y0 or Y1 (we pretend the chroma is the last of the 3 bytes) */
2057     /* FIXME: we're not pixel-exact here if x0 is an odd number, but it's
2058      * unlikely anyone will notice.. */
2059     p += (y * stride) + ((x0 / 2) * 3) + 1;
2060     for (x = x0; x < x1; x += 2) {
2061       tmp = *p + shading_val;
2062       *p++ = CLAMP (tmp, 0, 255);
2063       tmp = *p + shading_val;
2064       *p++ = CLAMP (tmp, 0, 255);
2065       /* skip chroma */
2066       p++;
2067     }
2068   }
2069 }
2070
2071 #define ARGB_SHADE_FUNCTION(name, OFFSET)       \
2072 static inline void \
2073 gst_base_text_overlay_shade_##name (GstBaseTextOverlay * overlay, GstVideoFrame * dest, \
2074 gint x0, gint x1, gint y0, gint y1) \
2075 { \
2076   gint i, j;\
2077   guint8 *dest_ptr;\
2078   \
2079   dest_ptr = dest->data[0];\
2080   \
2081   for (i = y0; i < y1; i++) {\
2082     for (j = x0; j < x1; j++) {\
2083       gint y, y_pos, k;\
2084       y_pos = (i * 4 * overlay->width) + j * 4;\
2085       for (k = OFFSET; k < 3+OFFSET; k++) {\
2086         y = dest_ptr[y_pos + k] - overlay->shading_value;\
2087         dest_ptr[y_pos + k] = CLAMP (y, 0, 255);\
2088       }\
2089     }\
2090   }\
2091 }
2092 ARGB_SHADE_FUNCTION (ARGB, 1);
2093 ARGB_SHADE_FUNCTION (ABGR, 1);
2094 ARGB_SHADE_FUNCTION (RGBA, 0);
2095 ARGB_SHADE_FUNCTION (BGRA, 0);
2096
2097 static void
2098 gst_base_text_overlay_render_text (GstBaseTextOverlay * overlay,
2099     const gchar * text, gint textlen)
2100 {
2101   gchar *string;
2102
2103   if (!overlay->need_render) {
2104     GST_DEBUG ("Using previously rendered text.");
2105     return;
2106   }
2107
2108   /* -1 is the whole string */
2109   if (text != NULL && textlen < 0) {
2110     textlen = strlen (text);
2111   }
2112
2113   if (text != NULL) {
2114     string = g_strndup (text, textlen);
2115   } else {                      /* empty string */
2116     string = g_strdup (" ");
2117   }
2118   g_strdelimit (string, "\r\t", ' ');
2119   textlen = strlen (string);
2120
2121   /* FIXME: should we check for UTF-8 here? */
2122
2123   GST_DEBUG ("Rendering '%s'", string);
2124   gst_base_text_overlay_render_pangocairo (overlay, string, textlen);
2125
2126   g_free (string);
2127
2128   overlay->need_render = FALSE;
2129 }
2130
2131 /* FIXME: should probably be relative to width/height (adjusted for PAR) */
2132 #define BOX_XPAD  6
2133 #define BOX_YPAD  6
2134
2135 static void
2136 gst_base_text_overlay_shade_background (GstBaseTextOverlay * overlay,
2137     GstVideoFrame * frame, gint x0, gint x1, gint y0, gint y1)
2138 {
2139   x0 = CLAMP (x0 - BOX_XPAD, 0, overlay->width);
2140   x1 = CLAMP (x1 + BOX_XPAD, 0, overlay->width);
2141
2142   y0 = CLAMP (y0 - BOX_YPAD, 0, overlay->height);
2143   y1 = CLAMP (y1 + BOX_YPAD, 0, overlay->height);
2144
2145   switch (overlay->format) {
2146     case GST_VIDEO_FORMAT_I420:
2147     case GST_VIDEO_FORMAT_YV12:
2148     case GST_VIDEO_FORMAT_NV12:
2149     case GST_VIDEO_FORMAT_NV21:
2150     case GST_VIDEO_FORMAT_Y41B:
2151     case GST_VIDEO_FORMAT_Y42B:
2152     case GST_VIDEO_FORMAT_Y444:
2153     case GST_VIDEO_FORMAT_YUV9:
2154     case GST_VIDEO_FORMAT_YVU9:
2155     case GST_VIDEO_FORMAT_GRAY8:
2156     case GST_VIDEO_FORMAT_A420:
2157       gst_base_text_overlay_shade_planar_Y (overlay, frame, x0, x1, y0, y1);
2158       break;
2159     case GST_VIDEO_FORMAT_AYUV:
2160     case GST_VIDEO_FORMAT_UYVY:
2161     case GST_VIDEO_FORMAT_YUY2:
2162     case GST_VIDEO_FORMAT_v308:
2163       gst_base_text_overlay_shade_packed_Y (overlay, frame, x0, x1, y0, y1);
2164       break;
2165     case GST_VIDEO_FORMAT_xRGB:
2166       gst_base_text_overlay_shade_xRGB (overlay, frame, x0, x1, y0, y1);
2167       break;
2168     case GST_VIDEO_FORMAT_xBGR:
2169       gst_base_text_overlay_shade_xBGR (overlay, frame, x0, x1, y0, y1);
2170       break;
2171     case GST_VIDEO_FORMAT_BGRx:
2172       gst_base_text_overlay_shade_BGRx (overlay, frame, x0, x1, y0, y1);
2173       break;
2174     case GST_VIDEO_FORMAT_RGBx:
2175       gst_base_text_overlay_shade_RGBx (overlay, frame, x0, x1, y0, y1);
2176       break;
2177     case GST_VIDEO_FORMAT_ARGB:
2178       gst_base_text_overlay_shade_ARGB (overlay, frame, x0, x1, y0, y1);
2179       break;
2180     case GST_VIDEO_FORMAT_ABGR:
2181       gst_base_text_overlay_shade_ABGR (overlay, frame, x0, x1, y0, y1);
2182       break;
2183     case GST_VIDEO_FORMAT_RGBA:
2184       gst_base_text_overlay_shade_RGBA (overlay, frame, x0, x1, y0, y1);
2185       break;
2186     case GST_VIDEO_FORMAT_BGRA:
2187       gst_base_text_overlay_shade_BGRA (overlay, frame, x0, x1, y0, y1);
2188       break;
2189     case GST_VIDEO_FORMAT_BGR:
2190     case GST_VIDEO_FORMAT_RGB:
2191       gst_base_text_overlay_shade_rgb24 (overlay, frame, x0, x1, y0, y1);
2192       break;
2193     case GST_VIDEO_FORMAT_IYU1:
2194       gst_base_text_overlay_shade_IYU1 (overlay, frame, x0, x1, y0, y1);
2195       break;
2196     default:
2197       GST_FIXME_OBJECT (overlay, "implement background shading for format %s",
2198           gst_video_format_to_string (GST_VIDEO_FRAME_FORMAT (frame)));
2199       break;
2200   }
2201 }
2202
2203 static GstFlowReturn
2204 gst_base_text_overlay_push_frame (GstBaseTextOverlay * overlay,
2205     GstBuffer * video_frame)
2206 {
2207   GstVideoFrame frame;
2208
2209   if (overlay->composition == NULL)
2210     goto done;
2211
2212   if (gst_pad_check_reconfigure (overlay->srcpad))
2213     gst_base_text_overlay_negotiate (overlay, NULL);
2214
2215   video_frame = gst_buffer_make_writable (video_frame);
2216
2217   if (overlay->attach_compo_to_buffer) {
2218     GST_DEBUG_OBJECT (overlay, "Attaching text overlay image to video buffer");
2219     gst_buffer_add_video_overlay_composition_meta (video_frame,
2220         overlay->composition);
2221     /* FIXME: emulate shaded background box if want_shading=true */
2222     goto done;
2223   }
2224
2225   if (!gst_video_frame_map (&frame, &overlay->info, video_frame,
2226           GST_MAP_READWRITE))
2227     goto invalid_frame;
2228
2229   /* shaded background box */
2230   if (overlay->want_shading) {
2231     gint xpos, ypos;
2232
2233     gst_base_text_overlay_get_pos (overlay, &xpos, &ypos);
2234
2235     gst_base_text_overlay_shade_background (overlay, &frame,
2236         xpos, xpos + overlay->text_width, ypos, ypos + overlay->text_height);
2237   }
2238
2239   gst_video_overlay_composition_blend (overlay->composition, &frame);
2240
2241   gst_video_frame_unmap (&frame);
2242
2243 done:
2244
2245   return gst_pad_push (overlay->srcpad, video_frame);
2246
2247   /* ERRORS */
2248 invalid_frame:
2249   {
2250     gst_buffer_unref (video_frame);
2251     GST_DEBUG_OBJECT (overlay, "received invalid buffer");
2252     return GST_FLOW_OK;
2253   }
2254 }
2255
2256 static GstPadLinkReturn
2257 gst_base_text_overlay_text_pad_link (GstPad * pad, GstObject * parent,
2258     GstPad * peer)
2259 {
2260   GstBaseTextOverlay *overlay;
2261
2262   overlay = GST_BASE_TEXT_OVERLAY (parent);
2263   if (G_UNLIKELY (!overlay))
2264     return GST_PAD_LINK_REFUSED;
2265
2266   GST_DEBUG_OBJECT (overlay, "Text pad linked");
2267
2268   overlay->text_linked = TRUE;
2269
2270   return GST_PAD_LINK_OK;
2271 }
2272
2273 static void
2274 gst_base_text_overlay_text_pad_unlink (GstPad * pad, GstObject * parent)
2275 {
2276   GstBaseTextOverlay *overlay;
2277
2278   /* don't use gst_pad_get_parent() here, will deadlock */
2279   overlay = GST_BASE_TEXT_OVERLAY (parent);
2280
2281   GST_DEBUG_OBJECT (overlay, "Text pad unlinked");
2282
2283   overlay->text_linked = FALSE;
2284
2285   gst_segment_init (&overlay->text_segment, GST_FORMAT_UNDEFINED);
2286 }
2287
2288 static gboolean
2289 gst_base_text_overlay_text_event (GstPad * pad, GstObject * parent,
2290     GstEvent * event)
2291 {
2292   gboolean ret = FALSE;
2293   GstBaseTextOverlay *overlay = NULL;
2294
2295   overlay = GST_BASE_TEXT_OVERLAY (parent);
2296
2297   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2298
2299   switch (GST_EVENT_TYPE (event)) {
2300     case GST_EVENT_CAPS:
2301     {
2302       GstCaps *caps;
2303
2304       gst_event_parse_caps (event, &caps);
2305       ret = gst_base_text_overlay_setcaps_txt (overlay, caps);
2306       gst_event_unref (event);
2307       break;
2308     }
2309     case GST_EVENT_SEGMENT:
2310     {
2311       const GstSegment *segment;
2312
2313       overlay->text_eos = FALSE;
2314
2315       gst_event_parse_segment (event, &segment);
2316
2317       if (segment->format == GST_FORMAT_TIME) {
2318         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2319         gst_segment_copy_into (segment, &overlay->text_segment);
2320         GST_DEBUG_OBJECT (overlay, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
2321             &overlay->text_segment);
2322         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2323       } else {
2324         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2325             ("received non-TIME newsegment event on text input"));
2326       }
2327
2328       gst_event_unref (event);
2329       ret = TRUE;
2330
2331       /* wake up the video chain, it might be waiting for a text buffer or
2332        * a text segment update */
2333       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2334       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2335       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2336       break;
2337     }
2338     case GST_EVENT_GAP:
2339     {
2340       GstClockTime start, duration;
2341
2342       gst_event_parse_gap (event, &start, &duration);
2343       if (GST_CLOCK_TIME_IS_VALID (duration))
2344         start += duration;
2345       /* we do not expect another buffer until after gap,
2346        * so that is our position now */
2347       overlay->text_segment.position = start;
2348
2349       /* wake up the video chain, it might be waiting for a text buffer or
2350        * a text segment update */
2351       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2352       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2353       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2354
2355       gst_event_unref (event);
2356       ret = TRUE;
2357       break;
2358     }
2359     case GST_EVENT_FLUSH_STOP:
2360       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2361       GST_INFO_OBJECT (overlay, "text flush stop");
2362       overlay->text_flushing = FALSE;
2363       overlay->text_eos = FALSE;
2364       gst_base_text_overlay_pop_text (overlay);
2365       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2366       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2367       gst_event_unref (event);
2368       ret = TRUE;
2369       break;
2370     case GST_EVENT_FLUSH_START:
2371       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2372       GST_INFO_OBJECT (overlay, "text flush start");
2373       overlay->text_flushing = TRUE;
2374       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2375       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2376       gst_event_unref (event);
2377       ret = TRUE;
2378       break;
2379     case GST_EVENT_EOS:
2380       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2381       overlay->text_eos = TRUE;
2382       GST_INFO_OBJECT (overlay, "text EOS");
2383       /* wake up the video chain, it might be waiting for a text buffer or
2384        * a text segment update */
2385       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2386       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2387       gst_event_unref (event);
2388       ret = TRUE;
2389       break;
2390     default:
2391       ret = gst_pad_event_default (pad, parent, event);
2392       break;
2393   }
2394
2395   return ret;
2396 }
2397
2398 static gboolean
2399 gst_base_text_overlay_video_event (GstPad * pad, GstObject * parent,
2400     GstEvent * event)
2401 {
2402   gboolean ret = FALSE;
2403   GstBaseTextOverlay *overlay = NULL;
2404
2405   overlay = GST_BASE_TEXT_OVERLAY (parent);
2406
2407   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
2408
2409   switch (GST_EVENT_TYPE (event)) {
2410     case GST_EVENT_CAPS:
2411     {
2412       GstCaps *caps;
2413
2414       gst_event_parse_caps (event, &caps);
2415       ret = gst_base_text_overlay_setcaps (overlay, caps);
2416       gst_event_unref (event);
2417       break;
2418     }
2419     case GST_EVENT_SEGMENT:
2420     {
2421       const GstSegment *segment;
2422
2423       GST_DEBUG_OBJECT (overlay, "received new segment");
2424
2425       gst_event_parse_segment (event, &segment);
2426
2427       if (segment->format == GST_FORMAT_TIME) {
2428         GST_DEBUG_OBJECT (overlay, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
2429             &overlay->segment);
2430
2431         gst_segment_copy_into (segment, &overlay->segment);
2432       } else {
2433         GST_ELEMENT_WARNING (overlay, STREAM, MUX, (NULL),
2434             ("received non-TIME newsegment event on video input"));
2435       }
2436
2437       ret = gst_pad_event_default (pad, parent, event);
2438       break;
2439     }
2440     case GST_EVENT_EOS:
2441       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2442       GST_INFO_OBJECT (overlay, "video EOS");
2443       overlay->video_eos = TRUE;
2444       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2445       ret = gst_pad_event_default (pad, parent, event);
2446       break;
2447     case GST_EVENT_FLUSH_START:
2448       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2449       GST_INFO_OBJECT (overlay, "video flush start");
2450       overlay->video_flushing = TRUE;
2451       GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2452       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2453       ret = gst_pad_event_default (pad, parent, event);
2454       break;
2455     case GST_EVENT_FLUSH_STOP:
2456       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2457       GST_INFO_OBJECT (overlay, "video flush stop");
2458       overlay->video_flushing = FALSE;
2459       overlay->video_eos = FALSE;
2460       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2461       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2462       ret = gst_pad_event_default (pad, parent, event);
2463       break;
2464     default:
2465       ret = gst_pad_event_default (pad, parent, event);
2466       break;
2467   }
2468
2469   return ret;
2470 }
2471
2472 static gboolean
2473 gst_base_text_overlay_video_query (GstPad * pad, GstObject * parent,
2474     GstQuery * query)
2475 {
2476   gboolean ret = FALSE;
2477   GstBaseTextOverlay *overlay;
2478
2479   overlay = GST_BASE_TEXT_OVERLAY (parent);
2480
2481   switch (GST_QUERY_TYPE (query)) {
2482     case GST_QUERY_CAPS:
2483     {
2484       GstCaps *filter, *caps;
2485
2486       gst_query_parse_caps (query, &filter);
2487       caps = gst_base_text_overlay_get_videosink_caps (pad, overlay, filter);
2488       gst_query_set_caps_result (query, caps);
2489       gst_caps_unref (caps);
2490       ret = TRUE;
2491       break;
2492     }
2493     default:
2494       ret = gst_pad_query_default (pad, parent, query);
2495       break;
2496   }
2497
2498   return ret;
2499 }
2500
2501 /* Called with lock held */
2502 static void
2503 gst_base_text_overlay_pop_text (GstBaseTextOverlay * overlay)
2504 {
2505   g_return_if_fail (GST_IS_BASE_TEXT_OVERLAY (overlay));
2506
2507   if (overlay->text_buffer) {
2508     GST_DEBUG_OBJECT (overlay, "releasing text buffer %p",
2509         overlay->text_buffer);
2510     gst_buffer_unref (overlay->text_buffer);
2511     overlay->text_buffer = NULL;
2512   }
2513
2514   /* Let the text task know we used that buffer */
2515   GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2516 }
2517
2518 /* We receive text buffers here. If they are out of segment we just ignore them.
2519    If the buffer is in our segment we keep it internally except if another one
2520    is already waiting here, in that case we wait that it gets kicked out */
2521 static GstFlowReturn
2522 gst_base_text_overlay_text_chain (GstPad * pad, GstObject * parent,
2523     GstBuffer * buffer)
2524 {
2525   GstFlowReturn ret = GST_FLOW_OK;
2526   GstBaseTextOverlay *overlay = NULL;
2527   gboolean in_seg = FALSE;
2528   guint64 clip_start = 0, clip_stop = 0;
2529
2530   overlay = GST_BASE_TEXT_OVERLAY (parent);
2531
2532   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2533
2534   if (overlay->text_flushing) {
2535     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2536     ret = GST_FLOW_FLUSHING;
2537     GST_LOG_OBJECT (overlay, "text flushing");
2538     goto beach;
2539   }
2540
2541   if (overlay->text_eos) {
2542     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2543     ret = GST_FLOW_EOS;
2544     GST_LOG_OBJECT (overlay, "text EOS");
2545     goto beach;
2546   }
2547
2548   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2549       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2550       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
2551       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
2552           GST_BUFFER_DURATION (buffer)));
2553
2554   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
2555     GstClockTime stop;
2556
2557     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
2558       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
2559     else
2560       stop = GST_CLOCK_TIME_NONE;
2561
2562     in_seg = gst_segment_clip (&overlay->text_segment, GST_FORMAT_TIME,
2563         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
2564   } else {
2565     in_seg = TRUE;
2566   }
2567
2568   if (in_seg) {
2569     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2570       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2571     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
2572       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2573
2574     /* Wait for the previous buffer to go away */
2575     while (overlay->text_buffer != NULL) {
2576       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
2577           GST_DEBUG_PAD_NAME (pad));
2578       GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2579       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
2580       if (overlay->text_flushing) {
2581         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2582         ret = GST_FLOW_FLUSHING;
2583         goto beach;
2584       }
2585     }
2586
2587     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2588       overlay->text_segment.position = clip_start;
2589
2590     overlay->text_buffer = buffer;      /* pass ownership of @buffer */
2591     buffer = NULL;
2592
2593     /* That's a new text buffer we need to render */
2594     overlay->need_render = TRUE;
2595
2596     /* in case the video chain is waiting for a text buffer, wake it up */
2597     GST_BASE_TEXT_OVERLAY_BROADCAST (overlay);
2598   }
2599
2600   GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2601
2602 beach:
2603   if (buffer)
2604     gst_buffer_unref (buffer);
2605
2606   return ret;
2607 }
2608
2609 static GstFlowReturn
2610 gst_base_text_overlay_video_chain (GstPad * pad, GstObject * parent,
2611     GstBuffer * buffer)
2612 {
2613   GstBaseTextOverlayClass *klass;
2614   GstBaseTextOverlay *overlay;
2615   GstFlowReturn ret = GST_FLOW_OK;
2616   gboolean in_seg = FALSE;
2617   guint64 start, stop, clip_start = 0, clip_stop = 0;
2618   gchar *text = NULL;
2619   GstVideoOverlayCompositionMeta *composition_meta;
2620
2621   overlay = GST_BASE_TEXT_OVERLAY (parent);
2622
2623   composition_meta = gst_buffer_get_video_overlay_composition_meta (buffer);
2624   if (composition_meta) {
2625     if (overlay->upstream_composition != composition_meta->overlay) {
2626       GST_DEBUG ("GstVideoOverlayCompositionMeta found.");
2627       overlay->upstream_composition = composition_meta->overlay;
2628       overlay->need_render = TRUE;
2629     }
2630   } else if (overlay->upstream_composition != NULL) {
2631     overlay->upstream_composition = NULL;
2632     overlay->need_render = TRUE;
2633   }
2634
2635   klass = GST_BASE_TEXT_OVERLAY_GET_CLASS (overlay);
2636
2637   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2638     goto missing_timestamp;
2639
2640   /* ignore buffers that are outside of the current segment */
2641   start = GST_BUFFER_TIMESTAMP (buffer);
2642
2643   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2644     stop = GST_CLOCK_TIME_NONE;
2645   } else {
2646     stop = start + GST_BUFFER_DURATION (buffer);
2647   }
2648
2649   GST_LOG_OBJECT (overlay, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2650       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &overlay->segment,
2651       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2652
2653   /* segment_clip() will adjust start unconditionally to segment_start if
2654    * no stop time is provided, so handle this ourselves */
2655   if (stop == GST_CLOCK_TIME_NONE && start < overlay->segment.start)
2656     goto out_of_segment;
2657
2658   in_seg = gst_segment_clip (&overlay->segment, GST_FORMAT_TIME, start, stop,
2659       &clip_start, &clip_stop);
2660
2661   if (!in_seg)
2662     goto out_of_segment;
2663
2664   /* if the buffer is only partially in the segment, fix up stamps */
2665   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2666     GST_DEBUG_OBJECT (overlay, "clipping buffer timestamp/duration to segment");
2667     buffer = gst_buffer_make_writable (buffer);
2668     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2669     if (stop != -1)
2670       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2671   }
2672
2673   /* now, after we've done the clipping, fix up end time if there's no
2674    * duration (we only use those estimated values internally though, we
2675    * don't want to set bogus values on the buffer itself) */
2676   if (stop == -1) {
2677     if (overlay->info.fps_n && overlay->info.fps_d) {
2678       GST_DEBUG_OBJECT (overlay, "estimating duration based on framerate");
2679       stop = start + gst_util_uint64_scale_int (GST_SECOND,
2680           overlay->info.fps_d, overlay->info.fps_n);
2681     } else {
2682       GST_LOG_OBJECT (overlay, "no duration, assuming minimal duration");
2683       stop = start + 1;         /* we need to assume some interval */
2684     }
2685   }
2686
2687   gst_object_sync_values (GST_OBJECT (overlay), GST_BUFFER_TIMESTAMP (buffer));
2688
2689 wait_for_text_buf:
2690
2691   GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2692
2693   if (overlay->video_flushing)
2694     goto flushing;
2695
2696   if (overlay->video_eos)
2697     goto have_eos;
2698
2699   if (overlay->silent) {
2700     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2701     ret = gst_pad_push (overlay->srcpad, buffer);
2702
2703     /* Update position */
2704     overlay->segment.position = clip_start;
2705
2706     return ret;
2707   }
2708
2709   /* Text pad not linked, rendering internal text */
2710   if (!overlay->text_linked) {
2711     if (klass->get_text) {
2712       text = klass->get_text (overlay, buffer);
2713     } else {
2714       text = g_strdup (overlay->default_text);
2715     }
2716
2717     GST_LOG_OBJECT (overlay, "Text pad not linked, rendering default "
2718         "text: '%s'", GST_STR_NULL (text));
2719
2720     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2721
2722     if (text != NULL && *text != '\0') {
2723       /* Render and push */
2724       gst_base_text_overlay_render_text (overlay, text, -1);
2725       ret = gst_base_text_overlay_push_frame (overlay, buffer);
2726     } else {
2727       /* Invalid or empty string */
2728       ret = gst_pad_push (overlay->srcpad, buffer);
2729     }
2730   } else {
2731     /* Text pad linked, check if we have a text buffer queued */
2732     if (overlay->text_buffer) {
2733       gboolean pop_text = FALSE, valid_text_time = TRUE;
2734       GstClockTime text_start = GST_CLOCK_TIME_NONE;
2735       GstClockTime text_end = GST_CLOCK_TIME_NONE;
2736       GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2737       GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2738       GstClockTime vid_running_time, vid_running_time_end;
2739
2740       /* if the text buffer isn't stamped right, pop it off the
2741        * queue and display it for the current video frame only */
2742       if (!GST_BUFFER_TIMESTAMP_IS_VALID (overlay->text_buffer) ||
2743           !GST_BUFFER_DURATION_IS_VALID (overlay->text_buffer)) {
2744         GST_WARNING_OBJECT (overlay,
2745             "Got text buffer with invalid timestamp or duration");
2746         pop_text = TRUE;
2747         valid_text_time = FALSE;
2748       } else {
2749         text_start = GST_BUFFER_TIMESTAMP (overlay->text_buffer);
2750         text_end = text_start + GST_BUFFER_DURATION (overlay->text_buffer);
2751       }
2752
2753       vid_running_time =
2754           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2755           start);
2756       vid_running_time_end =
2757           gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2758           stop);
2759
2760       /* If timestamp and duration are valid */
2761       if (valid_text_time) {
2762         text_running_time =
2763             gst_segment_to_running_time (&overlay->text_segment,
2764             GST_FORMAT_TIME, text_start);
2765         text_running_time_end =
2766             gst_segment_to_running_time (&overlay->text_segment,
2767             GST_FORMAT_TIME, text_end);
2768       }
2769
2770       GST_LOG_OBJECT (overlay, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2771           GST_TIME_ARGS (text_running_time),
2772           GST_TIME_ARGS (text_running_time_end));
2773       GST_LOG_OBJECT (overlay, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2774           GST_TIME_ARGS (vid_running_time),
2775           GST_TIME_ARGS (vid_running_time_end));
2776
2777       /* Text too old or in the future */
2778       if (valid_text_time && text_running_time_end <= vid_running_time) {
2779         /* text buffer too old, get rid of it and do nothing  */
2780         GST_LOG_OBJECT (overlay, "text buffer too old, popping");
2781         pop_text = FALSE;
2782         gst_base_text_overlay_pop_text (overlay);
2783         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2784         goto wait_for_text_buf;
2785       } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2786         GST_LOG_OBJECT (overlay, "text in future, pushing video buf");
2787         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2788         /* Push the video frame */
2789         ret = gst_pad_push (overlay->srcpad, buffer);
2790       } else {
2791         GstMapInfo map;
2792         gchar *in_text;
2793         gsize in_size;
2794
2795         gst_buffer_map (overlay->text_buffer, &map, GST_MAP_READ);
2796         in_text = (gchar *) map.data;
2797         in_size = map.size;
2798
2799         if (in_size > 0) {
2800           /* g_markup_escape_text() absolutely requires valid UTF8 input, it
2801            * might crash otherwise. We don't fall back on GST_SUBTITLE_ENCODING
2802            * here on purpose, this is something that needs fixing upstream */
2803           if (!g_utf8_validate (in_text, in_size, NULL)) {
2804             const gchar *end = NULL;
2805
2806             GST_WARNING_OBJECT (overlay, "received invalid UTF-8");
2807             in_text = g_strndup (in_text, in_size);
2808             while (!g_utf8_validate (in_text, in_size, &end) && end)
2809               *((gchar *) end) = '*';
2810           }
2811
2812           /* Get the string */
2813           if (overlay->have_pango_markup) {
2814             text = g_strndup (in_text, in_size);
2815           } else {
2816             text = g_markup_escape_text (in_text, in_size);
2817           }
2818
2819           if (text != NULL && *text != '\0') {
2820             gint text_len = strlen (text);
2821
2822             while (text_len > 0 && (text[text_len - 1] == '\n' ||
2823                     text[text_len - 1] == '\r')) {
2824               --text_len;
2825             }
2826             GST_DEBUG_OBJECT (overlay, "Rendering text '%*s'", text_len, text);
2827             gst_base_text_overlay_render_text (overlay, text, text_len);
2828           } else {
2829             GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2830             gst_base_text_overlay_render_text (overlay, " ", 1);
2831           }
2832           if (in_text != (gchar *) map.data)
2833             g_free (in_text);
2834         } else {
2835           GST_DEBUG_OBJECT (overlay, "No text to render (empty buffer)");
2836           gst_base_text_overlay_render_text (overlay, " ", 1);
2837         }
2838
2839         gst_buffer_unmap (overlay->text_buffer, &map);
2840
2841         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2842         ret = gst_base_text_overlay_push_frame (overlay, buffer);
2843
2844         if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2845           GST_LOG_OBJECT (overlay, "text buffer not needed any longer");
2846           pop_text = TRUE;
2847         }
2848       }
2849       if (pop_text) {
2850         GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2851         gst_base_text_overlay_pop_text (overlay);
2852         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2853       }
2854     } else {
2855       gboolean wait_for_text_buf = TRUE;
2856
2857       if (overlay->text_eos)
2858         wait_for_text_buf = FALSE;
2859
2860       if (!overlay->wait_text)
2861         wait_for_text_buf = FALSE;
2862
2863       /* Text pad linked, but no text buffer available - what now? */
2864       if (overlay->text_segment.format == GST_FORMAT_TIME) {
2865         GstClockTime text_start_running_time, text_position_running_time;
2866         GstClockTime vid_running_time;
2867
2868         vid_running_time =
2869             gst_segment_to_running_time (&overlay->segment, GST_FORMAT_TIME,
2870             GST_BUFFER_TIMESTAMP (buffer));
2871         text_start_running_time =
2872             gst_segment_to_running_time (&overlay->text_segment,
2873             GST_FORMAT_TIME, overlay->text_segment.start);
2874         text_position_running_time =
2875             gst_segment_to_running_time (&overlay->text_segment,
2876             GST_FORMAT_TIME, overlay->text_segment.position);
2877
2878         if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2879                 vid_running_time < text_start_running_time) ||
2880             (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2881                 vid_running_time < text_position_running_time)) {
2882           wait_for_text_buf = FALSE;
2883         }
2884       }
2885
2886       if (wait_for_text_buf) {
2887         GST_DEBUG_OBJECT (overlay, "no text buffer, need to wait for one");
2888         GST_BASE_TEXT_OVERLAY_WAIT (overlay);
2889         GST_DEBUG_OBJECT (overlay, "resuming");
2890         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2891         goto wait_for_text_buf;
2892       } else {
2893         GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2894         GST_LOG_OBJECT (overlay, "no need to wait for a text buffer");
2895         ret = gst_pad_push (overlay->srcpad, buffer);
2896       }
2897     }
2898   }
2899
2900   g_free (text);
2901
2902   /* Update position */
2903   overlay->segment.position = clip_start;
2904
2905   return ret;
2906
2907 missing_timestamp:
2908   {
2909     GST_WARNING_OBJECT (overlay, "buffer without timestamp, discarding");
2910     gst_buffer_unref (buffer);
2911     return GST_FLOW_OK;
2912   }
2913
2914 flushing:
2915   {
2916     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2917     GST_DEBUG_OBJECT (overlay, "flushing, discarding buffer");
2918     gst_buffer_unref (buffer);
2919     return GST_FLOW_FLUSHING;
2920   }
2921 have_eos:
2922   {
2923     GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2924     GST_DEBUG_OBJECT (overlay, "eos, discarding buffer");
2925     gst_buffer_unref (buffer);
2926     return GST_FLOW_EOS;
2927   }
2928 out_of_segment:
2929   {
2930     GST_DEBUG_OBJECT (overlay, "buffer out of segment, discarding");
2931     gst_buffer_unref (buffer);
2932     return GST_FLOW_OK;
2933   }
2934 }
2935
2936 static GstStateChangeReturn
2937 gst_base_text_overlay_change_state (GstElement * element,
2938     GstStateChange transition)
2939 {
2940   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
2941   GstBaseTextOverlay *overlay = GST_BASE_TEXT_OVERLAY (element);
2942
2943   switch (transition) {
2944     case GST_STATE_CHANGE_PAUSED_TO_READY:
2945       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2946       overlay->text_flushing = TRUE;
2947       overlay->video_flushing = TRUE;
2948       /* pop_text will broadcast on the GCond and thus also make the video
2949        * chain exit if it's waiting for a text buffer */
2950       gst_base_text_overlay_pop_text (overlay);
2951       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2952       break;
2953     default:
2954       break;
2955   }
2956
2957   ret = parent_class->change_state (element, transition);
2958   if (ret == GST_STATE_CHANGE_FAILURE)
2959     return ret;
2960
2961   switch (transition) {
2962     case GST_STATE_CHANGE_READY_TO_PAUSED:
2963       GST_BASE_TEXT_OVERLAY_LOCK (overlay);
2964       overlay->text_flushing = FALSE;
2965       overlay->video_flushing = FALSE;
2966       overlay->video_eos = FALSE;
2967       overlay->text_eos = FALSE;
2968       gst_segment_init (&overlay->segment, GST_FORMAT_TIME);
2969       gst_segment_init (&overlay->text_segment, GST_FORMAT_TIME);
2970       GST_BASE_TEXT_OVERLAY_UNLOCK (overlay);
2971       break;
2972     default:
2973       break;
2974   }
2975
2976   return ret;
2977 }
2978
2979 static gboolean
2980 plugin_init (GstPlugin * plugin)
2981 {
2982   if (!gst_element_register (plugin, "textoverlay", GST_RANK_NONE,
2983           GST_TYPE_TEXT_OVERLAY) ||
2984       !gst_element_register (plugin, "timeoverlay", GST_RANK_NONE,
2985           GST_TYPE_TIME_OVERLAY) ||
2986       !gst_element_register (plugin, "clockoverlay", GST_RANK_NONE,
2987           GST_TYPE_CLOCK_OVERLAY) ||
2988       !gst_element_register (plugin, "textrender", GST_RANK_NONE,
2989           GST_TYPE_TEXT_RENDER)) {
2990     return FALSE;
2991   }
2992
2993   /*texttestsrc_plugin_init(module, plugin); */
2994
2995   GST_DEBUG_CATEGORY_INIT (pango_debug, "pango", 0, "Pango elements");
2996
2997   return TRUE;
2998 }
2999
3000 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR,
3001     pango, "Pango-based text rendering and overlay", plugin_init,
3002     VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)