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