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