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  * Copyright (C) <2015> British Broadcasting Corporation <dash@rd.bbc.co.uk>
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Library General Public
12  * License as published by the Free Software Foundation; either
13  * version 2 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Library General Public License for more details.
19  *
20  * You should have received a copy of the GNU Library General Public
21  * License along with this library; if not, write to the
22  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25 
26 /**
27  * SECTION:element-ttmlrender
28  * @title: ttmlrender
29  *
30  * Renders timed text on top of a video stream. It receives text in buffers
31  * from a ttmlparse element; each text string is in its own #GstMemory within
32  * the GstBuffer, and the styling and layout associated with each text string
33  * is in metadata attached to the #GstBuffer.
34  *
35  * ## Example launch lines
36  * |[
37  * gst-launch-1.0 filesrc location=<media file location> ! video/quicktime ! qtdemux name=q ttmlrender name=r q. ! queue ! h264parse ! avdec_h264 ! autovideoconvert ! r.video_sink filesrc location=<subtitle file location> blocksize=16777216 ! queue ! ttmlparse ! r.text_sink r. ! ximagesink q. ! queue ! aacparse ! avdec_aac ! audioconvert ! alsasink
38  * ]| Parse and render TTML subtitles contained in a single XML file over an
39  * MP4 stream containing H.264 video and AAC audio:
40  *
41  */
42 
43 #include <gst/video/video.h>
44 #include <gst/video/gstvideometa.h>
45 #include <gst/video/video-overlay-composition.h>
46 #include <pango/pangocairo.h>
47 
48 #include <string.h>
49 #include <math.h>
50 
51 #include "gstttmlrender.h"
52 #include "subtitle.h"
53 #include "subtitlemeta.h"
54 
55 #define VIDEO_FORMATS GST_VIDEO_OVERLAY_COMPOSITION_BLEND_FORMATS
56 
57 #define TTML_RENDER_CAPS GST_VIDEO_CAPS_MAKE (VIDEO_FORMATS)
58 
59 #define TTML_RENDER_ALL_CAPS TTML_RENDER_CAPS ";" \
60     GST_VIDEO_CAPS_MAKE_WITH_FEATURES ("ANY", GST_VIDEO_FORMATS_ALL)
61 
62 GST_DEBUG_CATEGORY_EXTERN (ttmlrender_debug);
63 #define GST_CAT_DEFAULT ttmlrender_debug
64 
65 static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (TTML_RENDER_CAPS);
66 
67 static GstStaticPadTemplate src_template_factory =
68 GST_STATIC_PAD_TEMPLATE ("src",
69     GST_PAD_SRC,
70     GST_PAD_ALWAYS,
71     GST_STATIC_CAPS (TTML_RENDER_ALL_CAPS)
72     );
73 
74 static GstStaticPadTemplate video_sink_template_factory =
75 GST_STATIC_PAD_TEMPLATE ("video_sink",
76     GST_PAD_SINK,
77     GST_PAD_ALWAYS,
78     GST_STATIC_CAPS (TTML_RENDER_ALL_CAPS)
79     );
80 
81 static GstStaticPadTemplate text_sink_template_factory =
82 GST_STATIC_PAD_TEMPLATE ("text_sink",
83     GST_PAD_SINK,
84     GST_PAD_ALWAYS,
85     GST_STATIC_CAPS ("text/x-raw(meta:GstSubtitleMeta)")
86     );
87 
88 #define GST_TTML_RENDER_GET_LOCK(ov) (&GST_TTML_RENDER (ov)->lock)
89 #define GST_TTML_RENDER_GET_COND(ov) (&GST_TTML_RENDER (ov)->cond)
90 #define GST_TTML_RENDER_LOCK(ov)     (g_mutex_lock (GST_TTML_RENDER_GET_LOCK (ov)))
91 #define GST_TTML_RENDER_UNLOCK(ov)   (g_mutex_unlock (GST_TTML_RENDER_GET_LOCK (ov)))
92 #define GST_TTML_RENDER_WAIT(ov)     (g_cond_wait (GST_TTML_RENDER_GET_COND (ov), GST_TTML_RENDER_GET_LOCK (ov)))
93 #define GST_TTML_RENDER_SIGNAL(ov)   (g_cond_signal (GST_TTML_RENDER_GET_COND (ov)))
94 #define GST_TTML_RENDER_BROADCAST(ov)(g_cond_broadcast (GST_TTML_RENDER_GET_COND (ov)))
95 
96 
97 typedef enum
98 {
99   GST_TTML_DIRECTION_INLINE,
100   GST_TTML_DIRECTION_BLOCK
101 } GstTtmlDirection;
102 
103 
104 typedef struct
105 {
106   guint line_height;
107   guint baseline_offset;
108 } BlockMetrics;
109 
110 
111 typedef struct
112 {
113   guint height;
114   guint baseline;
115 } FontMetrics;
116 
117 
118 typedef struct
119 {
120   guint first_index;
121   guint last_index;
122 } CharRange;
123 
124 
125 /* @pango_font_size is the font size you would need to tell pango in order that
126  * the actual rendered height of @text matches the text height in @element's
127  * style set. */
128 typedef struct
129 {
130   GstSubtitleElement *element;
131   guint pango_font_size;
132   FontMetrics pango_font_metrics;
133   gchar *text;
134 } UnifiedElement;
135 
136 
137 typedef struct
138 {
139   GPtrArray *unified_elements;
140   GstSubtitleStyleSet *style_set;
141   gchar *joined_text;
142 } UnifiedBlock;
143 
144 
145 static GstElementClass *parent_class = NULL;
146 static void gst_ttml_render_base_init (gpointer g_class);
147 static void gst_ttml_render_class_init (GstTtmlRenderClass * klass);
148 static void gst_ttml_render_init (GstTtmlRender * render,
149     GstTtmlRenderClass * klass);
150 
151 static GstStateChangeReturn gst_ttml_render_change_state (GstElement *
152     element, GstStateChange transition);
153 
154 static GstCaps *gst_ttml_render_get_videosink_caps (GstPad * pad,
155     GstTtmlRender * render, GstCaps * filter);
156 static GstCaps *gst_ttml_render_get_src_caps (GstPad * pad,
157     GstTtmlRender * render, GstCaps * filter);
158 static gboolean gst_ttml_render_setcaps (GstTtmlRender * render,
159     GstCaps * caps);
160 static gboolean gst_ttml_render_src_event (GstPad * pad,
161     GstObject * parent, GstEvent * event);
162 static gboolean gst_ttml_render_src_query (GstPad * pad,
163     GstObject * parent, GstQuery * query);
164 
165 static gboolean gst_ttml_render_video_event (GstPad * pad,
166     GstObject * parent, GstEvent * event);
167 static gboolean gst_ttml_render_video_query (GstPad * pad,
168     GstObject * parent, GstQuery * query);
169 static GstFlowReturn gst_ttml_render_video_chain (GstPad * pad,
170     GstObject * parent, GstBuffer * buffer);
171 
172 static gboolean gst_ttml_render_text_event (GstPad * pad,
173     GstObject * parent, GstEvent * event);
174 static GstFlowReturn gst_ttml_render_text_chain (GstPad * pad,
175     GstObject * parent, GstBuffer * buffer);
176 static GstPadLinkReturn gst_ttml_render_text_pad_link (GstPad * pad,
177     GstObject * parent, GstPad * peer);
178 static void gst_ttml_render_text_pad_unlink (GstPad * pad, GstObject * parent);
179 static void gst_ttml_render_pop_text (GstTtmlRender * render);
180 
181 static void gst_ttml_render_finalize (GObject * object);
182 
183 static gboolean gst_ttml_render_can_handle_caps (GstCaps * incaps);
184 
185 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_new
186     (GstBuffer * image, gint x, gint y, guint width, guint height);
187 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_new_empty
188     (void);
189 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_copy
190     (GstTtmlRenderRenderedImage * image);
191 static void gst_ttml_render_rendered_image_free
192     (GstTtmlRenderRenderedImage * image);
193 static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_combine
194     (GstTtmlRenderRenderedImage * image1, GstTtmlRenderRenderedImage * image2);
195 static GstTtmlRenderRenderedImage *gst_ttml_render_stitch_images (GPtrArray *
196     images, GstTtmlDirection direction);
197 
198 static gboolean gst_ttml_render_color_is_transparent (GstSubtitleColor * color);
199 
200 GType
gst_ttml_render_get_type(void)201 gst_ttml_render_get_type (void)
202 {
203   static GType type = 0;
204 
205   if (g_once_init_enter ((gsize *) & type)) {
206     static const GTypeInfo info = {
207       sizeof (GstTtmlRenderClass),
208       (GBaseInitFunc) gst_ttml_render_base_init,
209       NULL,
210       (GClassInitFunc) gst_ttml_render_class_init,
211       NULL,
212       NULL,
213       sizeof (GstTtmlRender),
214       0,
215       (GInstanceInitFunc) gst_ttml_render_init,
216     };
217 
218     g_once_init_leave ((gsize *) & type,
219         g_type_register_static (GST_TYPE_ELEMENT, "GstTtmlRender", &info, 0));
220   }
221 
222   return type;
223 }
224 
225 static void
gst_ttml_render_base_init(gpointer g_class)226 gst_ttml_render_base_init (gpointer g_class)
227 {
228   GstTtmlRenderClass *klass = GST_TTML_RENDER_CLASS (g_class);
229   PangoFontMap *fontmap;
230 
231   /* Only lock for the subclasses here, the base class
232    * doesn't have this mutex yet and it's not necessary
233    * here */
234   if (klass->pango_lock)
235     g_mutex_lock (klass->pango_lock);
236   fontmap = pango_cairo_font_map_get_default ();
237   klass->pango_context =
238       pango_font_map_create_context (PANGO_FONT_MAP (fontmap));
239   if (klass->pango_lock)
240     g_mutex_unlock (klass->pango_lock);
241 }
242 
243 static void
gst_ttml_render_class_init(GstTtmlRenderClass * klass)244 gst_ttml_render_class_init (GstTtmlRenderClass * klass)
245 {
246   GObjectClass *gobject_class;
247   GstElementClass *gstelement_class;
248 
249   gobject_class = (GObjectClass *) klass;
250   gstelement_class = (GstElementClass *) klass;
251 
252   parent_class = g_type_class_peek_parent (klass);
253 
254   gobject_class->finalize = gst_ttml_render_finalize;
255 
256   gst_element_class_add_pad_template (gstelement_class,
257       gst_static_pad_template_get (&src_template_factory));
258   gst_element_class_add_pad_template (gstelement_class,
259       gst_static_pad_template_get (&video_sink_template_factory));
260   gst_element_class_add_pad_template (gstelement_class,
261       gst_static_pad_template_get (&text_sink_template_factory));
262 
263   gst_element_class_set_static_metadata (gstelement_class,
264       "TTML subtitle renderer", "Overlay/Subtitle",
265       "Renders timed-text subtitles on top of video buffers",
266       "David Schleef <ds@schleef.org>, Zeeshan Ali <zeeshan.ali@nokia.com>, "
267       "Chris Bass <dash@rd.bbc.co.uk>");
268 
269   gstelement_class->change_state =
270       GST_DEBUG_FUNCPTR (gst_ttml_render_change_state);
271 
272   klass->pango_lock = g_slice_new (GMutex);
273   g_mutex_init (klass->pango_lock);
274 }
275 
276 static void
gst_ttml_render_finalize(GObject * object)277 gst_ttml_render_finalize (GObject * object)
278 {
279   GstTtmlRender *render = GST_TTML_RENDER (object);
280 
281   if (render->compositions) {
282     g_list_free_full (render->compositions,
283         (GDestroyNotify) gst_video_overlay_composition_unref);
284     render->compositions = NULL;
285   }
286 
287   if (render->text_buffer) {
288     gst_buffer_unref (render->text_buffer);
289     render->text_buffer = NULL;
290   }
291 
292   if (render->layout) {
293     g_object_unref (render->layout);
294     render->layout = NULL;
295   }
296 
297   g_mutex_clear (&render->lock);
298   g_cond_clear (&render->cond);
299 
300   G_OBJECT_CLASS (parent_class)->finalize (object);
301 }
302 
303 static void
gst_ttml_render_init(GstTtmlRender * render,GstTtmlRenderClass * klass)304 gst_ttml_render_init (GstTtmlRender * render, GstTtmlRenderClass * klass)
305 {
306   GstPadTemplate *template;
307 
308   /* video sink */
309   template = gst_static_pad_template_get (&video_sink_template_factory);
310   render->video_sinkpad = gst_pad_new_from_template (template, "video_sink");
311   gst_object_unref (template);
312   gst_pad_set_event_function (render->video_sinkpad,
313       GST_DEBUG_FUNCPTR (gst_ttml_render_video_event));
314   gst_pad_set_chain_function (render->video_sinkpad,
315       GST_DEBUG_FUNCPTR (gst_ttml_render_video_chain));
316   gst_pad_set_query_function (render->video_sinkpad,
317       GST_DEBUG_FUNCPTR (gst_ttml_render_video_query));
318   GST_PAD_SET_PROXY_ALLOCATION (render->video_sinkpad);
319   gst_element_add_pad (GST_ELEMENT (render), render->video_sinkpad);
320 
321   template =
322       gst_element_class_get_pad_template (GST_ELEMENT_CLASS (klass),
323       "text_sink");
324   if (template) {
325     /* text sink */
326     render->text_sinkpad = gst_pad_new_from_template (template, "text_sink");
327 
328     gst_pad_set_event_function (render->text_sinkpad,
329         GST_DEBUG_FUNCPTR (gst_ttml_render_text_event));
330     gst_pad_set_chain_function (render->text_sinkpad,
331         GST_DEBUG_FUNCPTR (gst_ttml_render_text_chain));
332     gst_pad_set_link_function (render->text_sinkpad,
333         GST_DEBUG_FUNCPTR (gst_ttml_render_text_pad_link));
334     gst_pad_set_unlink_function (render->text_sinkpad,
335         GST_DEBUG_FUNCPTR (gst_ttml_render_text_pad_unlink));
336     gst_element_add_pad (GST_ELEMENT (render), render->text_sinkpad);
337   }
338 
339   /* (video) source */
340   template = gst_static_pad_template_get (&src_template_factory);
341   render->srcpad = gst_pad_new_from_template (template, "src");
342   gst_object_unref (template);
343   gst_pad_set_event_function (render->srcpad,
344       GST_DEBUG_FUNCPTR (gst_ttml_render_src_event));
345   gst_pad_set_query_function (render->srcpad,
346       GST_DEBUG_FUNCPTR (gst_ttml_render_src_query));
347   gst_element_add_pad (GST_ELEMENT (render), render->srcpad);
348 
349   g_mutex_lock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
350 
351   render->wait_text = TRUE;
352   render->need_render = TRUE;
353   render->text_buffer = NULL;
354   render->text_linked = FALSE;
355 
356   render->compositions = NULL;
357   render->layout =
358       pango_layout_new (GST_TTML_RENDER_GET_CLASS (render)->pango_context);
359 
360   g_mutex_init (&render->lock);
361   g_cond_init (&render->cond);
362   gst_segment_init (&render->segment, GST_FORMAT_TIME);
363   g_mutex_unlock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
364 }
365 
366 
367 /* only negotiate/query video render composition support for now */
368 static gboolean
gst_ttml_render_negotiate(GstTtmlRender * render,GstCaps * caps)369 gst_ttml_render_negotiate (GstTtmlRender * render, GstCaps * caps)
370 {
371   GstQuery *query;
372   gboolean attach = FALSE;
373   gboolean caps_has_meta = TRUE;
374   gboolean ret;
375   GstCapsFeatures *f;
376   GstCaps *original_caps;
377   gboolean original_has_meta = FALSE;
378   gboolean allocation_ret = TRUE;
379 
380   GST_DEBUG_OBJECT (render, "performing negotiation");
381 
382   gst_pad_check_reconfigure (render->srcpad);
383 
384   if (!caps)
385     caps = gst_pad_get_current_caps (render->video_sinkpad);
386   else
387     gst_caps_ref (caps);
388 
389   if (!caps || gst_caps_is_empty (caps))
390     goto no_format;
391 
392   original_caps = caps;
393 
394   /* Try to use the render meta if possible */
395   f = gst_caps_get_features (caps, 0);
396 
397   /* if the caps doesn't have the render meta, we query if downstream
398    * accepts it before trying the version without the meta
399    * If upstream already is using the meta then we can only use it */
400   if (!f
401       || !gst_caps_features_contains (f,
402           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION)) {
403     GstCaps *overlay_caps;
404 
405     /* In this case we added the meta, but we can work without it
406      * so preserve the original caps so we can use it as a fallback */
407     overlay_caps = gst_caps_copy (caps);
408 
409     f = gst_caps_get_features (overlay_caps, 0);
410     gst_caps_features_add (f,
411         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION);
412 
413     ret = gst_pad_peer_query_accept_caps (render->srcpad, overlay_caps);
414     GST_DEBUG_OBJECT (render, "Downstream accepts the render meta: %d", ret);
415     if (ret) {
416       gst_caps_unref (caps);
417       caps = overlay_caps;
418 
419     } else {
420       /* fallback to the original */
421       gst_caps_unref (overlay_caps);
422       caps_has_meta = FALSE;
423     }
424   } else {
425     original_has_meta = TRUE;
426   }
427   GST_DEBUG_OBJECT (render, "Using caps %" GST_PTR_FORMAT, caps);
428   ret = gst_pad_set_caps (render->srcpad, caps);
429 
430   if (ret) {
431     /* find supported meta */
432     query = gst_query_new_allocation (caps, FALSE);
433 
434     if (!gst_pad_peer_query (render->srcpad, query)) {
435       /* no problem, we use the query defaults */
436       GST_DEBUG_OBJECT (render, "ALLOCATION query failed");
437       allocation_ret = FALSE;
438     }
439 
440     if (caps_has_meta && gst_query_find_allocation_meta (query,
441             GST_VIDEO_OVERLAY_COMPOSITION_META_API_TYPE, NULL))
442       attach = TRUE;
443 
444     gst_query_unref (query);
445   }
446 
447   if (!allocation_ret && render->video_flushing) {
448     ret = FALSE;
449   } else if (original_caps && !original_has_meta && !attach) {
450     if (caps_has_meta) {
451       /* Some elements (fakesink) claim to accept the meta on caps but won't
452          put it in the allocation query result, this leads below
453          check to fail. Prevent this by removing the meta from caps */
454       gst_caps_unref (caps);
455       caps = gst_caps_ref (original_caps);
456       ret = gst_pad_set_caps (render->srcpad, caps);
457       if (ret && !gst_ttml_render_can_handle_caps (caps))
458         ret = FALSE;
459     }
460   }
461 
462   if (!ret) {
463     GST_DEBUG_OBJECT (render, "negotiation failed, schedule reconfigure");
464     gst_pad_mark_reconfigure (render->srcpad);
465   }
466 
467   gst_caps_unref (caps);
468 
469   if (!ret)
470     gst_pad_mark_reconfigure (render->srcpad);
471 
472   return ret;
473 
474 no_format:
475   {
476     if (caps)
477       gst_caps_unref (caps);
478     gst_pad_mark_reconfigure (render->srcpad);
479     return FALSE;
480   }
481 }
482 
483 static gboolean
gst_ttml_render_can_handle_caps(GstCaps * incaps)484 gst_ttml_render_can_handle_caps (GstCaps * incaps)
485 {
486   gboolean ret;
487   GstCaps *caps;
488   static GstStaticCaps static_caps = GST_STATIC_CAPS (TTML_RENDER_CAPS);
489 
490   caps = gst_static_caps_get (&static_caps);
491   ret = gst_caps_is_subset (incaps, caps);
492   gst_caps_unref (caps);
493 
494   return ret;
495 }
496 
497 static gboolean
gst_ttml_render_setcaps(GstTtmlRender * render,GstCaps * caps)498 gst_ttml_render_setcaps (GstTtmlRender * render, GstCaps * caps)
499 {
500   GstVideoInfo info;
501   gboolean ret = FALSE;
502 
503   if (!gst_video_info_from_caps (&info, caps))
504     goto invalid_caps;
505 
506   render->info = info;
507   render->format = GST_VIDEO_INFO_FORMAT (&info);
508   render->width = GST_VIDEO_INFO_WIDTH (&info);
509   render->height = GST_VIDEO_INFO_HEIGHT (&info);
510 
511   ret = gst_ttml_render_negotiate (render, caps);
512 
513   GST_TTML_RENDER_LOCK (render);
514   g_mutex_lock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
515   if (!gst_ttml_render_can_handle_caps (caps)) {
516     GST_DEBUG_OBJECT (render, "unsupported caps %" GST_PTR_FORMAT, caps);
517     ret = FALSE;
518   }
519 
520   g_mutex_unlock (GST_TTML_RENDER_GET_CLASS (render)->pango_lock);
521   GST_TTML_RENDER_UNLOCK (render);
522 
523   return ret;
524 
525   /* ERRORS */
526 invalid_caps:
527   {
528     GST_DEBUG_OBJECT (render, "could not parse caps");
529     return FALSE;
530   }
531 }
532 
533 
534 static gboolean
gst_ttml_render_src_query(GstPad * pad,GstObject * parent,GstQuery * query)535 gst_ttml_render_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
536 {
537   gboolean ret = FALSE;
538   GstTtmlRender *render;
539 
540   render = GST_TTML_RENDER (parent);
541 
542   switch (GST_QUERY_TYPE (query)) {
543     case GST_QUERY_CAPS:
544     {
545       GstCaps *filter, *caps;
546 
547       gst_query_parse_caps (query, &filter);
548       caps = gst_ttml_render_get_src_caps (pad, render, filter);
549       gst_query_set_caps_result (query, caps);
550       gst_caps_unref (caps);
551       ret = TRUE;
552       break;
553     }
554     default:
555       ret = gst_pad_query_default (pad, parent, query);
556       break;
557   }
558 
559   return ret;
560 }
561 
562 static gboolean
gst_ttml_render_src_event(GstPad * pad,GstObject * parent,GstEvent * event)563 gst_ttml_render_src_event (GstPad * pad, GstObject * parent, GstEvent * event)
564 {
565   GstTtmlRender *render;
566   gboolean ret;
567 
568   render = GST_TTML_RENDER (parent);
569 
570   if (render->text_linked) {
571     gst_event_ref (event);
572     ret = gst_pad_push_event (render->video_sinkpad, event);
573     gst_pad_push_event (render->text_sinkpad, event);
574   } else {
575     ret = gst_pad_push_event (render->video_sinkpad, event);
576   }
577 
578   return ret;
579 }
580 
581 /**
582  * gst_ttml_render_add_feature_and_intersect:
583  *
584  * Creates a new #GstCaps containing the (given caps +
585  * given caps feature) + (given caps intersected by the
586  * given filter).
587  *
588  * Returns: the new #GstCaps
589  */
590 static GstCaps *
gst_ttml_render_add_feature_and_intersect(GstCaps * caps,const gchar * feature,GstCaps * filter)591 gst_ttml_render_add_feature_and_intersect (GstCaps * caps,
592     const gchar * feature, GstCaps * filter)
593 {
594   int i, caps_size;
595   GstCaps *new_caps;
596 
597   new_caps = gst_caps_copy (caps);
598 
599   caps_size = gst_caps_get_size (new_caps);
600   for (i = 0; i < caps_size; i++) {
601     GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
602 
603     if (!gst_caps_features_is_any (features)) {
604       gst_caps_features_add (features, feature);
605     }
606   }
607 
608   gst_caps_append (new_caps, gst_caps_intersect_full (caps,
609           filter, GST_CAPS_INTERSECT_FIRST));
610 
611   return new_caps;
612 }
613 
614 /**
615  * gst_ttml_render_intersect_by_feature:
616  *
617  * Creates a new #GstCaps based on the following filtering rule.
618  *
619  * For each individual caps contained in given caps, if the
620  * caps uses the given caps feature, keep a version of the caps
621  * with the feature and an another one without. Otherwise, intersect
622  * the caps with the given filter.
623  *
624  * Returns: the new #GstCaps
625  */
626 static GstCaps *
gst_ttml_render_intersect_by_feature(GstCaps * caps,const gchar * feature,GstCaps * filter)627 gst_ttml_render_intersect_by_feature (GstCaps * caps,
628     const gchar * feature, GstCaps * filter)
629 {
630   int i, caps_size;
631   GstCaps *new_caps;
632 
633   new_caps = gst_caps_new_empty ();
634 
635   caps_size = gst_caps_get_size (caps);
636   for (i = 0; i < caps_size; i++) {
637     GstStructure *caps_structure = gst_caps_get_structure (caps, i);
638     GstCapsFeatures *caps_features =
639         gst_caps_features_copy (gst_caps_get_features (caps, i));
640     GstCaps *filtered_caps;
641     GstCaps *simple_caps =
642         gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
643     gst_caps_set_features (simple_caps, 0, caps_features);
644 
645     if (gst_caps_features_contains (caps_features, feature)) {
646       gst_caps_append (new_caps, gst_caps_copy (simple_caps));
647 
648       gst_caps_features_remove (caps_features, feature);
649       filtered_caps = gst_caps_ref (simple_caps);
650     } else {
651       filtered_caps = gst_caps_intersect_full (simple_caps, filter,
652           GST_CAPS_INTERSECT_FIRST);
653     }
654 
655     gst_caps_unref (simple_caps);
656     gst_caps_append (new_caps, filtered_caps);
657   }
658 
659   return new_caps;
660 }
661 
662 static GstCaps *
gst_ttml_render_get_videosink_caps(GstPad * pad,GstTtmlRender * render,GstCaps * filter)663 gst_ttml_render_get_videosink_caps (GstPad * pad,
664     GstTtmlRender * render, GstCaps * filter)
665 {
666   GstPad *srcpad = render->srcpad;
667   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
668 
669   if (G_UNLIKELY (!render))
670     return gst_pad_get_pad_template_caps (pad);
671 
672   if (filter) {
673     /* filter caps + composition feature + filter caps
674      * filtered by the software caps. */
675     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
676     overlay_filter = gst_ttml_render_add_feature_and_intersect (filter,
677         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
678     gst_caps_unref (sw_caps);
679 
680     GST_DEBUG_OBJECT (render, "render filter %" GST_PTR_FORMAT, overlay_filter);
681   }
682 
683   peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
684 
685   if (overlay_filter)
686     gst_caps_unref (overlay_filter);
687 
688   if (peer_caps) {
689 
690     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
691 
692     if (gst_caps_is_any (peer_caps)) {
693       /* if peer returns ANY caps, return filtered src pad template caps */
694       caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
695     } else {
696 
697       /* duplicate caps which contains the composition into one version with
698        * the meta and one without. Filter the other caps by the software caps */
699       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
700       caps = gst_ttml_render_intersect_by_feature (peer_caps,
701           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
702       gst_caps_unref (sw_caps);
703     }
704 
705     gst_caps_unref (peer_caps);
706 
707   } else {
708     /* no peer, our padtemplate is enough then */
709     caps = gst_pad_get_pad_template_caps (pad);
710   }
711 
712   if (filter) {
713     GstCaps *intersection = gst_caps_intersect_full (filter, caps,
714         GST_CAPS_INTERSECT_FIRST);
715     gst_caps_unref (caps);
716     caps = intersection;
717   }
718 
719   GST_DEBUG_OBJECT (render, "returning  %" GST_PTR_FORMAT, caps);
720 
721   return caps;
722 }
723 
724 static GstCaps *
gst_ttml_render_get_src_caps(GstPad * pad,GstTtmlRender * render,GstCaps * filter)725 gst_ttml_render_get_src_caps (GstPad * pad, GstTtmlRender * render,
726     GstCaps * filter)
727 {
728   GstPad *sinkpad = render->video_sinkpad;
729   GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
730 
731   if (G_UNLIKELY (!render))
732     return gst_pad_get_pad_template_caps (pad);
733 
734   if (filter) {
735     /* duplicate filter caps which contains the composition into one version
736      * with the meta and one without. Filter the other caps by the software
737      * caps */
738     GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
739     overlay_filter =
740         gst_ttml_render_intersect_by_feature (filter,
741         GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
742     gst_caps_unref (sw_caps);
743   }
744 
745   peer_caps = gst_pad_peer_query_caps (sinkpad, overlay_filter);
746 
747   if (overlay_filter)
748     gst_caps_unref (overlay_filter);
749 
750   if (peer_caps) {
751 
752     GST_DEBUG_OBJECT (pad, "peer caps  %" GST_PTR_FORMAT, peer_caps);
753 
754     if (gst_caps_is_any (peer_caps)) {
755 
756       /* if peer returns ANY caps, return filtered sink pad template caps */
757       caps = gst_caps_copy (gst_pad_get_pad_template_caps (sinkpad));
758 
759     } else {
760 
761       /* return upstream caps + composition feature + upstream caps
762        * filtered by the software caps. */
763       GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
764       caps = gst_ttml_render_add_feature_and_intersect (peer_caps,
765           GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
766       gst_caps_unref (sw_caps);
767     }
768 
769     gst_caps_unref (peer_caps);
770 
771   } else {
772     /* no peer, our padtemplate is enough then */
773     caps = gst_pad_get_pad_template_caps (pad);
774   }
775 
776   if (filter) {
777     GstCaps *intersection;
778 
779     intersection =
780         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
781     gst_caps_unref (caps);
782     caps = intersection;
783   }
784   GST_DEBUG_OBJECT (render, "returning  %" GST_PTR_FORMAT, caps);
785 
786   return caps;
787 }
788 
789 
790 static GstFlowReturn
gst_ttml_render_push_frame(GstTtmlRender * render,GstBuffer * video_frame)791 gst_ttml_render_push_frame (GstTtmlRender * render, GstBuffer * video_frame)
792 {
793   GstVideoFrame frame;
794   GList *compositions = render->compositions;
795 
796   if (compositions == NULL) {
797     GST_CAT_DEBUG (ttmlrender_debug, "No compositions.");
798     goto done;
799   }
800 
801   if (gst_pad_check_reconfigure (render->srcpad)) {
802     if (!gst_ttml_render_negotiate (render, NULL)) {
803       gst_pad_mark_reconfigure (render->srcpad);
804       gst_buffer_unref (video_frame);
805       if (GST_PAD_IS_FLUSHING (render->srcpad))
806         return GST_FLOW_FLUSHING;
807       else
808         return GST_FLOW_NOT_NEGOTIATED;
809     }
810   }
811 
812   video_frame = gst_buffer_make_writable (video_frame);
813 
814   if (!gst_video_frame_map (&frame, &render->info, video_frame,
815           GST_MAP_READWRITE))
816     goto invalid_frame;
817 
818   while (compositions) {
819     GstVideoOverlayComposition *composition = compositions->data;
820     gst_video_overlay_composition_blend (composition, &frame);
821     compositions = compositions->next;
822   }
823 
824   gst_video_frame_unmap (&frame);
825 
826 done:
827 
828   return gst_pad_push (render->srcpad, video_frame);
829 
830   /* ERRORS */
831 invalid_frame:
832   {
833     gst_buffer_unref (video_frame);
834     GST_DEBUG_OBJECT (render, "received invalid buffer");
835     return GST_FLOW_OK;
836   }
837 }
838 
839 static GstPadLinkReturn
gst_ttml_render_text_pad_link(GstPad * pad,GstObject * parent,GstPad * peer)840 gst_ttml_render_text_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
841 {
842   GstTtmlRender *render;
843 
844   render = GST_TTML_RENDER (parent);
845   if (G_UNLIKELY (!render))
846     return GST_PAD_LINK_REFUSED;
847 
848   GST_DEBUG_OBJECT (render, "Text pad linked");
849 
850   render->text_linked = TRUE;
851 
852   return GST_PAD_LINK_OK;
853 }
854 
855 static void
gst_ttml_render_text_pad_unlink(GstPad * pad,GstObject * parent)856 gst_ttml_render_text_pad_unlink (GstPad * pad, GstObject * parent)
857 {
858   GstTtmlRender *render;
859 
860   /* don't use gst_pad_get_parent() here, will deadlock */
861   render = GST_TTML_RENDER (parent);
862 
863   GST_DEBUG_OBJECT (render, "Text pad unlinked");
864 
865   render->text_linked = FALSE;
866 
867   gst_segment_init (&render->text_segment, GST_FORMAT_UNDEFINED);
868 }
869 
870 static gboolean
gst_ttml_render_text_event(GstPad * pad,GstObject * parent,GstEvent * event)871 gst_ttml_render_text_event (GstPad * pad, GstObject * parent, GstEvent * event)
872 {
873   gboolean ret = FALSE;
874   GstTtmlRender *render = NULL;
875 
876   render = GST_TTML_RENDER (parent);
877 
878   GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
879 
880   switch (GST_EVENT_TYPE (event)) {
881     case GST_EVENT_SEGMENT:
882     {
883       const GstSegment *segment;
884 
885       render->text_eos = FALSE;
886 
887       gst_event_parse_segment (event, &segment);
888 
889       if (segment->format == GST_FORMAT_TIME) {
890         GST_TTML_RENDER_LOCK (render);
891         gst_segment_copy_into (segment, &render->text_segment);
892         GST_DEBUG_OBJECT (render, "TEXT SEGMENT now: %" GST_SEGMENT_FORMAT,
893             &render->text_segment);
894         GST_TTML_RENDER_UNLOCK (render);
895       } else {
896         GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
897             ("received non-TIME newsegment event on text input"));
898       }
899 
900       gst_event_unref (event);
901       ret = TRUE;
902 
903       /* wake up the video chain, it might be waiting for a text buffer or
904        * a text segment update */
905       GST_TTML_RENDER_LOCK (render);
906       GST_TTML_RENDER_BROADCAST (render);
907       GST_TTML_RENDER_UNLOCK (render);
908       break;
909     }
910     case GST_EVENT_GAP:
911     {
912       GstClockTime start, duration;
913 
914       gst_event_parse_gap (event, &start, &duration);
915       if (GST_CLOCK_TIME_IS_VALID (duration))
916         start += duration;
917       /* we do not expect another buffer until after gap,
918        * so that is our position now */
919       render->text_segment.position = start;
920 
921       /* wake up the video chain, it might be waiting for a text buffer or
922        * a text segment update */
923       GST_TTML_RENDER_LOCK (render);
924       GST_TTML_RENDER_BROADCAST (render);
925       GST_TTML_RENDER_UNLOCK (render);
926 
927       gst_event_unref (event);
928       ret = TRUE;
929       break;
930     }
931     case GST_EVENT_FLUSH_STOP:
932       GST_TTML_RENDER_LOCK (render);
933       GST_INFO_OBJECT (render, "text flush stop");
934       render->text_flushing = FALSE;
935       render->text_eos = FALSE;
936       gst_ttml_render_pop_text (render);
937       gst_segment_init (&render->text_segment, GST_FORMAT_TIME);
938       GST_TTML_RENDER_UNLOCK (render);
939       gst_event_unref (event);
940       ret = TRUE;
941       break;
942     case GST_EVENT_FLUSH_START:
943       GST_TTML_RENDER_LOCK (render);
944       GST_INFO_OBJECT (render, "text flush start");
945       render->text_flushing = TRUE;
946       GST_TTML_RENDER_BROADCAST (render);
947       GST_TTML_RENDER_UNLOCK (render);
948       gst_event_unref (event);
949       ret = TRUE;
950       break;
951     case GST_EVENT_EOS:
952       GST_TTML_RENDER_LOCK (render);
953       render->text_eos = TRUE;
954       GST_INFO_OBJECT (render, "text EOS");
955       /* wake up the video chain, it might be waiting for a text buffer or
956        * a text segment update */
957       GST_TTML_RENDER_BROADCAST (render);
958       GST_TTML_RENDER_UNLOCK (render);
959       gst_event_unref (event);
960       ret = TRUE;
961       break;
962     default:
963       ret = gst_pad_event_default (pad, parent, event);
964       break;
965   }
966 
967   return ret;
968 }
969 
970 static gboolean
gst_ttml_render_video_event(GstPad * pad,GstObject * parent,GstEvent * event)971 gst_ttml_render_video_event (GstPad * pad, GstObject * parent, GstEvent * event)
972 {
973   gboolean ret = FALSE;
974   GstTtmlRender *render = NULL;
975 
976   render = GST_TTML_RENDER (parent);
977 
978   GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event));
979 
980   switch (GST_EVENT_TYPE (event)) {
981     case GST_EVENT_CAPS:
982     {
983       GstCaps *caps;
984       gint prev_width = render->width;
985       gint prev_height = render->height;
986 
987       gst_event_parse_caps (event, &caps);
988       ret = gst_ttml_render_setcaps (render, caps);
989       if (render->width != prev_width || render->height != prev_height)
990         render->need_render = TRUE;
991       gst_event_unref (event);
992       break;
993     }
994     case GST_EVENT_SEGMENT:
995     {
996       const GstSegment *segment;
997 
998       GST_DEBUG_OBJECT (render, "received new segment");
999 
1000       gst_event_parse_segment (event, &segment);
1001 
1002       if (segment->format == GST_FORMAT_TIME) {
1003         GST_DEBUG_OBJECT (render, "VIDEO SEGMENT now: %" GST_SEGMENT_FORMAT,
1004             &render->segment);
1005 
1006         gst_segment_copy_into (segment, &render->segment);
1007       } else {
1008         GST_ELEMENT_WARNING (render, STREAM, MUX, (NULL),
1009             ("received non-TIME newsegment event on video input"));
1010       }
1011 
1012       ret = gst_pad_event_default (pad, parent, event);
1013       break;
1014     }
1015     case GST_EVENT_EOS:
1016       GST_TTML_RENDER_LOCK (render);
1017       GST_INFO_OBJECT (render, "video EOS");
1018       render->video_eos = TRUE;
1019       GST_TTML_RENDER_UNLOCK (render);
1020       ret = gst_pad_event_default (pad, parent, event);
1021       break;
1022     case GST_EVENT_FLUSH_START:
1023       GST_TTML_RENDER_LOCK (render);
1024       GST_INFO_OBJECT (render, "video flush start");
1025       render->video_flushing = TRUE;
1026       GST_TTML_RENDER_BROADCAST (render);
1027       GST_TTML_RENDER_UNLOCK (render);
1028       ret = gst_pad_event_default (pad, parent, event);
1029       break;
1030     case GST_EVENT_FLUSH_STOP:
1031       GST_TTML_RENDER_LOCK (render);
1032       GST_INFO_OBJECT (render, "video flush stop");
1033       render->video_flushing = FALSE;
1034       render->video_eos = FALSE;
1035       gst_segment_init (&render->segment, GST_FORMAT_TIME);
1036       GST_TTML_RENDER_UNLOCK (render);
1037       ret = gst_pad_event_default (pad, parent, event);
1038       break;
1039     default:
1040       ret = gst_pad_event_default (pad, parent, event);
1041       break;
1042   }
1043 
1044   return ret;
1045 }
1046 
1047 static gboolean
gst_ttml_render_video_query(GstPad * pad,GstObject * parent,GstQuery * query)1048 gst_ttml_render_video_query (GstPad * pad, GstObject * parent, GstQuery * query)
1049 {
1050   gboolean ret = FALSE;
1051   GstTtmlRender *render;
1052 
1053   render = GST_TTML_RENDER (parent);
1054 
1055   switch (GST_QUERY_TYPE (query)) {
1056     case GST_QUERY_CAPS:
1057     {
1058       GstCaps *filter, *caps;
1059 
1060       gst_query_parse_caps (query, &filter);
1061       caps = gst_ttml_render_get_videosink_caps (pad, render, filter);
1062       gst_query_set_caps_result (query, caps);
1063       gst_caps_unref (caps);
1064       ret = TRUE;
1065       break;
1066     }
1067     default:
1068       ret = gst_pad_query_default (pad, parent, query);
1069       break;
1070   }
1071 
1072   return ret;
1073 }
1074 
1075 /* Called with lock held */
1076 static void
gst_ttml_render_pop_text(GstTtmlRender * render)1077 gst_ttml_render_pop_text (GstTtmlRender * render)
1078 {
1079   g_return_if_fail (GST_IS_TTML_RENDER (render));
1080 
1081   if (render->text_buffer) {
1082     GST_DEBUG_OBJECT (render, "releasing text buffer %p", render->text_buffer);
1083     gst_buffer_unref (render->text_buffer);
1084     render->text_buffer = NULL;
1085   }
1086 
1087   /* Let the text task know we used that buffer */
1088   GST_TTML_RENDER_BROADCAST (render);
1089 }
1090 
1091 /* We receive text buffers here. If they are out of segment we just ignore them.
1092    If the buffer is in our segment we keep it internally except if another one
1093    is already waiting here, in that case we wait that it gets kicked out */
1094 static GstFlowReturn
gst_ttml_render_text_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)1095 gst_ttml_render_text_chain (GstPad * pad, GstObject * parent,
1096     GstBuffer * buffer)
1097 {
1098   GstFlowReturn ret = GST_FLOW_OK;
1099   GstTtmlRender *render = NULL;
1100   gboolean in_seg = FALSE;
1101   guint64 clip_start = 0, clip_stop = 0;
1102 
1103   render = GST_TTML_RENDER (parent);
1104 
1105   GST_TTML_RENDER_LOCK (render);
1106 
1107   if (render->text_flushing) {
1108     GST_TTML_RENDER_UNLOCK (render);
1109     ret = GST_FLOW_FLUSHING;
1110     GST_LOG_OBJECT (render, "text flushing");
1111     goto beach;
1112   }
1113 
1114   if (render->text_eos) {
1115     GST_TTML_RENDER_UNLOCK (render);
1116     ret = GST_FLOW_EOS;
1117     GST_LOG_OBJECT (render, "text EOS");
1118     goto beach;
1119   }
1120 
1121   GST_LOG_OBJECT (render, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
1122       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &render->segment,
1123       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer)),
1124       GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (buffer) +
1125           GST_BUFFER_DURATION (buffer)));
1126 
1127   if (G_LIKELY (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))) {
1128     GstClockTime stop;
1129 
1130     if (G_LIKELY (GST_BUFFER_DURATION_IS_VALID (buffer)))
1131       stop = GST_BUFFER_TIMESTAMP (buffer) + GST_BUFFER_DURATION (buffer);
1132     else
1133       stop = GST_CLOCK_TIME_NONE;
1134 
1135     in_seg = gst_segment_clip (&render->text_segment, GST_FORMAT_TIME,
1136         GST_BUFFER_TIMESTAMP (buffer), stop, &clip_start, &clip_stop);
1137   } else {
1138     in_seg = TRUE;
1139   }
1140 
1141   if (in_seg) {
1142     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1143       GST_BUFFER_TIMESTAMP (buffer) = clip_start;
1144     else if (GST_BUFFER_DURATION_IS_VALID (buffer))
1145       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
1146 
1147     /* Wait for the previous buffer to go away */
1148     while (render->text_buffer != NULL) {
1149       GST_DEBUG ("Pad %s:%s has a buffer queued, waiting",
1150           GST_DEBUG_PAD_NAME (pad));
1151       GST_TTML_RENDER_WAIT (render);
1152       GST_DEBUG ("Pad %s:%s resuming", GST_DEBUG_PAD_NAME (pad));
1153       if (render->text_flushing) {
1154         GST_TTML_RENDER_UNLOCK (render);
1155         ret = GST_FLOW_FLUSHING;
1156         goto beach;
1157       }
1158     }
1159 
1160     if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
1161       render->text_segment.position = clip_start;
1162 
1163     render->text_buffer = buffer;
1164     /* That's a new text buffer we need to render */
1165     render->need_render = TRUE;
1166 
1167     /* in case the video chain is waiting for a text buffer, wake it up */
1168     GST_TTML_RENDER_BROADCAST (render);
1169   }
1170 
1171   GST_TTML_RENDER_UNLOCK (render);
1172 
1173 beach:
1174 
1175   return ret;
1176 }
1177 
1178 
1179 /* Caller needs to free returned string after use. */
1180 static gchar *
gst_ttml_render_color_to_string(GstSubtitleColor color)1181 gst_ttml_render_color_to_string (GstSubtitleColor color)
1182 {
1183 #if PANGO_VERSION_CHECK (1,38,0)
1184   return g_strdup_printf ("#%02x%02x%02x%02x",
1185       color.r, color.g, color.b, color.a);
1186 #else
1187   return g_strdup_printf ("#%02x%02x%02x", color.r, color.g, color.b);
1188 #endif
1189 }
1190 
1191 
1192 static GstBuffer *
gst_ttml_render_draw_rectangle(guint width,guint height,GstSubtitleColor color)1193 gst_ttml_render_draw_rectangle (guint width, guint height,
1194     GstSubtitleColor color)
1195 {
1196   GstMapInfo map;
1197   cairo_surface_t *surface;
1198   cairo_t *cairo_state;
1199   GstBuffer *buffer = gst_buffer_new_allocate (NULL, 4 * width * height, NULL);
1200 
1201   gst_buffer_map (buffer, &map, GST_MAP_READWRITE);
1202   surface = cairo_image_surface_create_for_data (map.data,
1203       CAIRO_FORMAT_ARGB32, width, height, width * 4);
1204   cairo_state = cairo_create (surface);
1205 
1206   /* clear surface */
1207   cairo_set_operator (cairo_state, CAIRO_OPERATOR_CLEAR);
1208   cairo_paint (cairo_state);
1209   cairo_set_operator (cairo_state, CAIRO_OPERATOR_OVER);
1210 
1211   cairo_save (cairo_state);
1212   cairo_set_source_rgba (cairo_state, color.r / 255.0, color.g / 255.0,
1213       color.b / 255.0, color.a / 255.0);
1214   cairo_paint (cairo_state);
1215   cairo_restore (cairo_state);
1216   cairo_destroy (cairo_state);
1217   cairo_surface_destroy (surface);
1218   gst_buffer_unmap (buffer, &map);
1219 
1220   return buffer;
1221 }
1222 
1223 
1224 static void
gst_ttml_render_char_range_free(CharRange * range)1225 gst_ttml_render_char_range_free (CharRange * range)
1226 {
1227   g_slice_free (CharRange, range);
1228 }
1229 
1230 
1231 /* Choose fonts for generic fontnames based upon IMSC1 and HbbTV specs. */
1232 static gchar *
gst_ttml_render_resolve_generic_fontname(const gchar * name)1233 gst_ttml_render_resolve_generic_fontname (const gchar * name)
1234 {
1235   if ((g_strcmp0 (name, "default") == 0)) {
1236     return
1237         g_strdup ("TiresiasScreenfont,Liberation Mono,Courier New,monospace");
1238   } else if ((g_strcmp0 (name, "monospace") == 0)) {
1239     return g_strdup ("Letter Gothic,Liberation Mono,Courier New,monospace");
1240   } else if ((g_strcmp0 (name, "sansSerif") == 0)) {
1241     return g_strdup ("TiresiasScreenfont,sans");
1242   } else if ((g_strcmp0 (name, "serif") == 0)) {
1243     return g_strdup ("serif");
1244   } else if ((g_strcmp0 (name, "monospaceSansSerif") == 0)) {
1245     return g_strdup ("Letter Gothic,monospace");
1246   } else if ((g_strcmp0 (name, "monospaceSerif") == 0)) {
1247     return g_strdup ("Courier New,Liberation Mono,monospace");
1248   } else if ((g_strcmp0 (name, "proportionalSansSerif") == 0)) {
1249     return g_strdup ("TiresiasScreenfont,Arial,Helvetica,Liberation Sans,sans");
1250   } else if ((g_strcmp0 (name, "proportionalSerif") == 0)) {
1251     return g_strdup ("serif");
1252   } else {
1253     return NULL;
1254   }
1255 }
1256 
1257 
1258 static gchar *
gst_ttml_render_get_text_from_buffer(GstBuffer * buf,guint index)1259 gst_ttml_render_get_text_from_buffer (GstBuffer * buf, guint index)
1260 {
1261   GstMapInfo map;
1262   GstMemory *mem;
1263   gchar *buf_text = NULL;
1264 
1265   mem = gst_buffer_get_memory (buf, index);
1266   if (!mem) {
1267     GST_CAT_ERROR (ttmlrender_debug, "Failed to access memory at index %u.",
1268         index);
1269     return NULL;
1270   }
1271 
1272   if (!gst_memory_map (mem, &map, GST_MAP_READ)) {
1273     GST_CAT_ERROR (ttmlrender_debug, "Failed to map memory at index %u.",
1274         index);
1275     goto map_fail;
1276   }
1277 
1278   buf_text = g_strndup ((const gchar *) map.data, map.size);
1279   if (!g_utf8_validate (buf_text, -1, NULL)) {
1280     GST_CAT_ERROR (ttmlrender_debug, "Text in buffer us not valid UTF-8");
1281     g_free (buf_text);
1282     buf_text = NULL;
1283   }
1284 
1285   gst_memory_unmap (mem, &map);
1286 map_fail:
1287   gst_memory_unref (mem);
1288   return buf_text;
1289 }
1290 
1291 
1292 static void
gst_ttml_render_unified_element_free(UnifiedElement * unified_element)1293 gst_ttml_render_unified_element_free (UnifiedElement * unified_element)
1294 {
1295   if (!unified_element)
1296     return;
1297 
1298   gst_subtitle_element_unref (unified_element->element);
1299   g_free (unified_element->text);
1300   g_slice_free (UnifiedElement, unified_element);
1301 }
1302 
1303 
1304 static UnifiedElement *
gst_ttml_render_unified_element_copy(const UnifiedElement * unified_element)1305 gst_ttml_render_unified_element_copy (const UnifiedElement * unified_element)
1306 {
1307   UnifiedElement *ret;
1308 
1309   if (!unified_element)
1310     return NULL;
1311 
1312   ret = g_slice_new0 (UnifiedElement);
1313   ret->element = gst_subtitle_element_ref (unified_element->element);
1314   ret->pango_font_size = unified_element->pango_font_size;
1315   ret->pango_font_metrics.height = unified_element->pango_font_metrics.height;
1316   ret->pango_font_metrics.baseline =
1317       unified_element->pango_font_metrics.baseline;
1318   ret->text = g_strdup (unified_element->text);
1319 
1320   return ret;
1321 }
1322 
1323 
1324 static void
gst_ttml_render_unified_block_free(UnifiedBlock * unified_block)1325 gst_ttml_render_unified_block_free (UnifiedBlock * unified_block)
1326 {
1327   if (!unified_block)
1328     return;
1329 
1330   gst_subtitle_style_set_unref (unified_block->style_set);
1331   g_ptr_array_unref (unified_block->unified_elements);
1332   g_free (unified_block->joined_text);
1333   g_slice_free (UnifiedBlock, unified_block);
1334 }
1335 
1336 
1337 static UnifiedElement *
gst_ttml_render_unified_block_get_element(const UnifiedBlock * block,guint index)1338 gst_ttml_render_unified_block_get_element (const UnifiedBlock * block,
1339     guint index)
1340 {
1341   if (index >= block->unified_elements->len)
1342     return NULL;
1343   else
1344     return g_ptr_array_index (block->unified_elements, index);
1345 }
1346 
1347 
1348 static UnifiedBlock *
gst_ttml_render_unified_block_copy(const UnifiedBlock * block)1349 gst_ttml_render_unified_block_copy (const UnifiedBlock * block)
1350 {
1351   UnifiedBlock *ret;
1352   gint i;
1353 
1354   if (!block)
1355     return NULL;
1356 
1357   ret = g_slice_new0 (UnifiedBlock);
1358   ret->joined_text = g_strdup (block->joined_text);
1359   ret->style_set = gst_subtitle_style_set_ref (block->style_set);
1360   ret->unified_elements = g_ptr_array_new_with_free_func ((GDestroyNotify)
1361       gst_ttml_render_unified_element_free);
1362 
1363   for (i = 0; i < block->unified_elements->len; ++i) {
1364     UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1365     UnifiedElement *ue_copy = gst_ttml_render_unified_element_copy (ue);
1366     g_ptr_array_add (ret->unified_elements, ue_copy);
1367   }
1368 
1369   return ret;
1370 }
1371 
1372 
1373 static guint
gst_ttml_render_unified_block_element_count(const UnifiedBlock * block)1374 gst_ttml_render_unified_block_element_count (const UnifiedBlock * block)
1375 {
1376   return block->unified_elements->len;
1377 }
1378 
1379 
1380 /*
1381  * Generates pango-markup'd version of @text that would make pango render it
1382  * with the styling specified by @style_set.
1383  */
1384 static gchar *
gst_ttml_render_generate_pango_markup(GstSubtitleStyleSet * style_set,guint font_height,const gchar * text)1385 gst_ttml_render_generate_pango_markup (GstSubtitleStyleSet * style_set,
1386     guint font_height, const gchar * text)
1387 {
1388   gchar *ret, *font_family, *font_size, *fgcolor;
1389   const gchar *font_style, *font_weight, *underline;
1390   gchar *escaped_text = g_markup_escape_text (text, -1);
1391 
1392   fgcolor = gst_ttml_render_color_to_string (style_set->color);
1393   font_size = g_strdup_printf ("%u", font_height);
1394   font_family =
1395       gst_ttml_render_resolve_generic_fontname (style_set->font_family);
1396   if (!font_family)
1397     font_family = g_strdup (style_set->font_family);
1398   font_style = (style_set->font_style ==
1399       GST_SUBTITLE_FONT_STYLE_NORMAL) ? "normal" : "italic";
1400   font_weight = (style_set->font_weight ==
1401       GST_SUBTITLE_FONT_WEIGHT_NORMAL) ? "normal" : "bold";
1402   underline = (style_set->text_decoration ==
1403       GST_SUBTITLE_TEXT_DECORATION_UNDERLINE) ? "single" : "none";
1404 
1405   ret = g_strconcat ("<span "
1406       "fgcolor=\"", fgcolor, "\" ",
1407       "font=\"", font_size, "px\" ",
1408       "font_family=\"", font_family, "\" ",
1409       "font_style=\"", font_style, "\" ",
1410       "font_weight=\"", font_weight, "\" ",
1411       "underline=\"", underline, "\" ", ">", escaped_text, "</span>", NULL);
1412 
1413   g_free (fgcolor);
1414   g_free (font_family);
1415   g_free (font_size);
1416   g_free (escaped_text);
1417   return ret;
1418 }
1419 
1420 
1421 /*
1422  * Unfortunately, pango does not expose accurate metrics about fonts (their
1423  * maximum height and baseline position), so we need to calculate this
1424  * information ourselves by examining the ink rectangle of a string containing
1425  * characters that extend to the maximum height/depth of the font.
1426  */
1427 static FontMetrics
gst_ttml_render_get_pango_font_metrics(GstTtmlRender * render,GstSubtitleStyleSet * style_set,guint font_size)1428 gst_ttml_render_get_pango_font_metrics (GstTtmlRender * render,
1429     GstSubtitleStyleSet * style_set, guint font_size)
1430 {
1431   PangoRectangle ink_rect;
1432   gchar *string;
1433   FontMetrics ret;
1434 
1435   string = gst_ttml_render_generate_pango_markup (style_set, font_size,
1436       "Áĺľď¿gqy");
1437   pango_layout_set_markup (render->layout, string, strlen (string));
1438   pango_layout_get_pixel_extents (render->layout, &ink_rect, NULL);
1439   g_free (string);
1440 
1441   ret.height = ink_rect.height;
1442   ret.baseline = PANGO_PIXELS (pango_layout_get_baseline (render->layout))
1443       - ink_rect.y;
1444   return ret;
1445 }
1446 
1447 
1448 /*
1449  * Return the font size that you would need to pass to pango in order that the
1450  * font applied to @element would be rendered at the text height applied to
1451  * @element.
1452  */
1453 static guint
gst_ttml_render_get_pango_font_size(GstTtmlRender * render,const GstSubtitleElement * element)1454 gst_ttml_render_get_pango_font_size (GstTtmlRender * render,
1455     const GstSubtitleElement * element)
1456 {
1457   guint desired_font_size =
1458       (guint) ceil (element->style_set->font_size * render->height);
1459   guint font_size = desired_font_size;
1460   guint rendered_height = G_MAXUINT;
1461   FontMetrics metrics;
1462 
1463   while (rendered_height > desired_font_size) {
1464     metrics =
1465         gst_ttml_render_get_pango_font_metrics (render, element->style_set,
1466         font_size);
1467     rendered_height = metrics.height;
1468     --font_size;
1469   }
1470 
1471   return font_size + 1;
1472 }
1473 
1474 
1475 /*
1476  * Reunites each element in @block with its text, as extracted from @buf. Also
1477  * stores the concatenated text from all contained elements to facilitate
1478  * future processing.
1479  */
1480 static UnifiedBlock *
gst_ttml_render_unify_block(GstTtmlRender * render,const GstSubtitleBlock * block,GstBuffer * buf)1481 gst_ttml_render_unify_block (GstTtmlRender * render,
1482     const GstSubtitleBlock * block, GstBuffer * buf)
1483 {
1484   UnifiedBlock *ret = g_slice_new0 (UnifiedBlock);
1485   guint i;
1486 
1487   ret->unified_elements = g_ptr_array_new_with_free_func ((GDestroyNotify)
1488       gst_ttml_render_unified_element_free);
1489   ret->style_set = gst_subtitle_style_set_ref (block->style_set);
1490   ret->joined_text = g_strdup ("");
1491 
1492   for (i = 0; i < gst_subtitle_block_get_element_count (block); ++i) {
1493     gchar *text;
1494     UnifiedElement *ue = g_slice_new0 (UnifiedElement);
1495     ue->element =
1496         gst_subtitle_element_ref (gst_subtitle_block_get_element (block, i));
1497     ue->pango_font_size =
1498         gst_ttml_render_get_pango_font_size (render, ue->element);
1499     ue->pango_font_metrics =
1500         gst_ttml_render_get_pango_font_metrics (render, ue->element->style_set,
1501         ue->pango_font_size);
1502     ue->text =
1503         gst_ttml_render_get_text_from_buffer (buf, ue->element->text_index);
1504     g_ptr_array_add (ret->unified_elements, ue);
1505 
1506     text = g_strjoin (NULL, ret->joined_text, ue->text, NULL);
1507     g_free (ret->joined_text);
1508     ret->joined_text = text;
1509   }
1510 
1511   return ret;
1512 }
1513 
1514 
1515 /*
1516  * Returns index of nearest breakpoint before @index in @block's text. If no
1517  * breakpoints are found, returns -1.
1518  */
1519 static gint
gst_ttml_render_get_nearest_breakpoint(const UnifiedBlock * block,guint index)1520 gst_ttml_render_get_nearest_breakpoint (const UnifiedBlock * block, guint index)
1521 {
1522   const gchar *end = block->joined_text + index - 1;
1523 
1524   while ((end = g_utf8_find_prev_char (block->joined_text, end))) {
1525     gchar buf[6] = { 0 };
1526     gunichar u = g_utf8_get_char (end);
1527     gint nbytes = g_unichar_to_utf8 (u, buf);
1528 
1529     if (nbytes == 1 && (buf[0] == 0x20 || buf[0] == 0x9 || buf[0] == 0xD))
1530       return end - block->joined_text;
1531   }
1532 
1533   return -1;
1534 }
1535 
1536 
1537 /* Return the pango markup representation of all the elements in @block. */
1538 static gchar *
gst_ttml_render_generate_block_markup(const UnifiedBlock * block)1539 gst_ttml_render_generate_block_markup (const UnifiedBlock * block)
1540 {
1541   gchar *joined_text, *old_text;
1542   guint element_count = gst_ttml_render_unified_block_element_count (block);
1543   guint i;
1544 
1545   joined_text = g_strdup ("");
1546 
1547   for (i = 0; i < element_count; ++i) {
1548     UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1549     gchar *element_markup =
1550         gst_ttml_render_generate_pango_markup (ue->element->style_set,
1551         ue->pango_font_size, ue->text);
1552 
1553     old_text = joined_text;
1554     joined_text = g_strconcat (joined_text, element_markup, NULL);
1555     GST_CAT_DEBUG (ttmlrender_debug, "Joined text is now: %s", joined_text);
1556 
1557     g_free (element_markup);
1558     g_free (old_text);
1559   }
1560 
1561   return joined_text;
1562 }
1563 
1564 
1565 /*
1566  * Returns a set of character ranges, which correspond to the ranges of
1567  * characters from @block that should be rendered on each generated line area.
1568  * Essentially, this function determines line breaking and wrapping.
1569  */
1570 static GPtrArray *
gst_ttml_render_get_line_char_ranges(GstTtmlRender * render,const UnifiedBlock * block,guint width,gboolean wrap)1571 gst_ttml_render_get_line_char_ranges (GstTtmlRender * render,
1572     const UnifiedBlock * block, guint width, gboolean wrap)
1573 {
1574   gint start_index = 0;
1575   GPtrArray *line_ranges = g_ptr_array_new_with_free_func ((GDestroyNotify)
1576       gst_ttml_render_char_range_free);
1577   PangoRectangle ink_rect;
1578   gchar *markup;
1579   gint i;
1580 
1581   /* Handle hard breaks in block text. */
1582   while (start_index < strlen (block->joined_text)) {
1583     CharRange *range = g_slice_new0 (CharRange);
1584     gchar *c = block->joined_text + start_index;
1585     while (*c != '\0' && *c != '\n')
1586       ++c;
1587     range->first_index = start_index;
1588     range->last_index = (c - block->joined_text) - 1;
1589     g_ptr_array_add (line_ranges, range);
1590     start_index = range->last_index + 2;
1591   }
1592 
1593   if (!wrap)
1594     return line_ranges;
1595 
1596   GST_CAT_LOG (ttmlrender_debug,
1597       "After handling breaks, we have the following ranges:");
1598   for (i = 0; i < line_ranges->len; ++i) {
1599     CharRange *range = g_ptr_array_index (line_ranges, i);
1600     GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u  last:%u", i,
1601         range->first_index, range->last_index);
1602   }
1603 
1604   markup = gst_ttml_render_generate_block_markup (block);
1605   pango_layout_set_markup (render->layout, markup, strlen (markup));
1606   pango_layout_set_width (render->layout, -1);
1607 
1608   pango_layout_get_pixel_extents (render->layout, &ink_rect, NULL);
1609   GST_CAT_LOG (ttmlrender_debug, "Layout extents - x:%d  y:%d  w:%d  h:%d",
1610       ink_rect.x, ink_rect.y, ink_rect.width, ink_rect.height);
1611 
1612   /* For each range, wrap if it extends beyond allowed width. */
1613   for (i = 0; i < line_ranges->len; ++i) {
1614     CharRange *range, *new_range;
1615     gint max_line_extent;
1616     gint end_index = 0;
1617     gint trailing;
1618     PangoRectangle rect;
1619     gboolean within_line;
1620 
1621     do {
1622       range = g_ptr_array_index (line_ranges, i);
1623       GST_CAT_LOG (ttmlrender_debug,
1624           "Seeing if we need to wrap range[%d] - start:%u  end:%u", i,
1625           range->first_index, range->last_index);
1626 
1627       pango_layout_index_to_pos (render->layout, range->first_index, &rect);
1628       GST_CAT_LOG (ttmlrender_debug, "First char at x:%d  y:%d", rect.x,
1629           rect.y);
1630 
1631       max_line_extent = rect.x + (PANGO_SCALE * width);
1632       GST_CAT_LOG (ttmlrender_debug, "max_line_extent: %d",
1633           PANGO_PIXELS (max_line_extent));
1634 
1635       within_line =
1636           pango_layout_xy_to_index (render->layout, max_line_extent, rect.y,
1637           &end_index, &trailing);
1638 
1639       GST_CAT_LOG (ttmlrender_debug, "Index nearest to breakpoint: %d",
1640           end_index);
1641 
1642       if (within_line) {
1643         end_index = gst_ttml_render_get_nearest_breakpoint (block, end_index);
1644 
1645         if (end_index > range->first_index) {
1646           new_range = g_slice_new0 (CharRange);
1647           new_range->first_index = end_index + 1;
1648           new_range->last_index = range->last_index;
1649           GST_CAT_LOG (ttmlrender_debug,
1650               "Wrapping line %d; added new range - start:%u  end:%u", i,
1651               new_range->first_index, new_range->last_index);
1652 
1653           range->last_index = end_index;
1654           GST_CAT_LOG (ttmlrender_debug,
1655               "Modified last_index of existing range; range is now start:%u  "
1656               "end:%u", range->first_index, range->last_index);
1657 
1658           g_ptr_array_insert (line_ranges, ++i, new_range);
1659         } else {
1660           GST_CAT_DEBUG (ttmlrender_debug,
1661               "Couldn't find a suitable breakpoint");
1662           within_line = FALSE;
1663         }
1664       }
1665     } while (within_line);
1666   }
1667 
1668   g_free (markup);
1669   return line_ranges;
1670 }
1671 
1672 
1673 /*
1674  * Returns the index of the element in @block containing the character at index
1675  * @char_index in @block's text. If @offset is not NULL, sets it to the
1676  * character offset of @char_index within the element where it is found.
1677  */
1678 static gint
gst_ttml_render_get_element_index(const UnifiedBlock * block,const gint char_index,gint * offset)1679 gst_ttml_render_get_element_index (const UnifiedBlock * block,
1680     const gint char_index, gint * offset)
1681 {
1682   gint count = 0;
1683   gint i;
1684 
1685   if ((char_index < 0) || (char_index >= strlen (block->joined_text)))
1686     return -1;
1687 
1688   for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
1689     UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
1690     if ((char_index >= count) && (char_index < (count + strlen (ue->text)))) {
1691       if (offset)
1692         *offset = char_index - count;
1693       break;
1694     }
1695     count += strlen (ue->text);
1696   }
1697 
1698   return i;
1699 }
1700 
1701 
1702 static guint
gst_ttml_render_strip_leading_spaces(gchar ** string)1703 gst_ttml_render_strip_leading_spaces (gchar ** string)
1704 {
1705   gchar *c = *string;
1706 
1707   while (c) {
1708     gchar buf[6] = { 0 };
1709     gunichar u = g_utf8_get_char (c);
1710     gint nbytes = g_unichar_to_utf8 (u, buf);
1711 
1712     if ((nbytes == 1) && (buf[0] == 0x20))
1713       c = g_utf8_find_next_char (c, c + strlen (*string));
1714     else
1715       break;
1716   }
1717 
1718   if (!c) {
1719     GST_CAT_DEBUG (ttmlrender_debug,
1720         "All characters would be removed from string.");
1721     return 0;
1722   } else if (c > *string) {
1723     gchar *tmp = *string;
1724     *string = g_strdup (c);
1725     GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp,
1726         *string);
1727     g_free (tmp);
1728   }
1729 
1730   return strlen (*string);
1731 }
1732 
1733 
1734 static guint
gst_ttml_render_strip_trailing_spaces(gchar ** string)1735 gst_ttml_render_strip_trailing_spaces (gchar ** string)
1736 {
1737   gchar *c = *string + strlen (*string) - 1;
1738   gint nbytes;
1739 
1740   while (c) {
1741     gchar buf[6] = { 0 };
1742     gunichar u = g_utf8_get_char (c);
1743     nbytes = g_unichar_to_utf8 (u, buf);
1744 
1745     if ((nbytes == 1) && (buf[0] == 0x20))
1746       c = g_utf8_find_prev_char (*string, c);
1747     else
1748       break;
1749   }
1750 
1751   if (!c) {
1752     GST_CAT_DEBUG (ttmlrender_debug,
1753         "All characters would be removed from string.");
1754     return 0;
1755   } else {
1756     gchar *tmp = *string;
1757     *string = g_strndup (*string, (c - *string) + nbytes);
1758     GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp,
1759         *string);
1760     g_free (tmp);
1761   }
1762 
1763   return strlen (*string);
1764 }
1765 
1766 
1767 /*
1768  * Treating each block in @blocks as a separate line area, conditionally strips
1769  * space characters from the beginning and end of each line. This function
1770  * implements the suppress-at-line-break="auto" and
1771  * white-space-treatment="ignore-if-surrounding-linefeed" behaviours (specified
1772  * by TTML section 7.2.3) for elements at the start and end of lines that have
1773  * xml:space="default" applied to them. If stripping whitespace from a block
1774  * removes all elements of that block, the block will be removed from @blocks.
1775  * Returns the number of remaining blocks.
1776  */
1777 static guint
gst_ttml_render_handle_whitespace(GPtrArray * blocks)1778 gst_ttml_render_handle_whitespace (GPtrArray * blocks)
1779 {
1780   gint i;
1781 
1782   for (i = 0; i < blocks->len; ++i) {
1783     UnifiedBlock *ub = g_ptr_array_index (blocks, i);
1784     UnifiedElement *ue;
1785     guint remaining_chars = 0;
1786 
1787     /* Remove leading spaces from line area. */
1788     while ((gst_ttml_render_unified_block_element_count (ub) > 0)
1789         && (remaining_chars == 0)) {
1790       ue = gst_ttml_render_unified_block_get_element (ub, 0);
1791       if (!ue->element->suppress_whitespace)
1792         break;
1793       remaining_chars = gst_ttml_render_strip_leading_spaces (&ue->text);
1794 
1795       if (remaining_chars == 0) {
1796         g_ptr_array_remove_index (ub->unified_elements, 0);
1797         GST_CAT_DEBUG (ttmlrender_debug, "Removed first element from block");
1798       }
1799     }
1800 
1801     remaining_chars = 0;
1802 
1803     /* Remove trailing spaces from line area. */
1804     while ((gst_ttml_render_unified_block_element_count (ub) > 0)
1805         && (remaining_chars == 0)) {
1806       ue = gst_ttml_render_unified_block_get_element (ub,
1807           gst_ttml_render_unified_block_element_count (ub) - 1);
1808       if (!ue->element->suppress_whitespace)
1809         break;
1810       remaining_chars = gst_ttml_render_strip_trailing_spaces (&ue->text);
1811 
1812       if (remaining_chars == 0) {
1813         g_ptr_array_remove_index (ub->unified_elements,
1814             gst_ttml_render_unified_block_element_count (ub) - 1);
1815         GST_CAT_DEBUG (ttmlrender_debug, "Removed last element from block");
1816       }
1817     }
1818 
1819     if (gst_ttml_render_unified_block_element_count (ub) == 0)
1820       g_ptr_array_remove_index (blocks, i--);
1821   }
1822 
1823   return blocks->len;
1824 }
1825 
1826 
1827 /*
1828  * Splits a single UnifiedBlock, @block, into an array of separate
1829  * UnifiedBlocks, according to the character ranges given in @char_ranges.
1830  * Each resulting UnifiedBlock will contain only the elements to which belong
1831  * the characters in its corresponding character range; the text of the first
1832  * and last element in the block will be clipped of any characters before and
1833  * after, respectively, the first and last characters in the corresponding
1834  * range.
1835  */
1836 static GPtrArray *
gst_ttml_render_split_block(UnifiedBlock * block,GPtrArray * char_ranges)1837 gst_ttml_render_split_block (UnifiedBlock * block, GPtrArray * char_ranges)
1838 {
1839   GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify)
1840       gst_ttml_render_unified_block_free);
1841   gint i;
1842 
1843   for (i = 0; i < char_ranges->len; ++i) {
1844     gint index;
1845     gint first_offset = 0;
1846     gint last_offset = 0;
1847     CharRange *range = g_ptr_array_index (char_ranges, i);
1848     UnifiedBlock *clone = gst_ttml_render_unified_block_copy (block);
1849     UnifiedElement *ue;
1850     gchar *tmp;
1851 
1852     GST_CAT_LOG (ttmlrender_debug, "range start:%u  end:%u", range->first_index,
1853         range->last_index);
1854     index =
1855         gst_ttml_render_get_element_index (clone, range->last_index,
1856         &last_offset);
1857     GST_CAT_LOG (ttmlrender_debug, "Last char in range is in element %d",
1858         index);
1859 
1860     if (index < 0) {
1861       GST_CAT_WARNING (ttmlrender_debug, "Range end not found in block text.");
1862       gst_ttml_render_unified_block_free (clone);
1863       continue;
1864     }
1865 
1866     /* Remove elements that are after the one that contains the range end. */
1867     GST_CAT_LOG (ttmlrender_debug, "There are %d elements in cloned block.",
1868         gst_ttml_render_unified_block_element_count (clone));
1869     while (gst_ttml_render_unified_block_element_count (clone) > (index + 1)) {
1870       GST_CAT_LOG (ttmlrender_debug, "Removing last element in cloned block.");
1871       g_ptr_array_remove_index (clone->unified_elements, index + 1);
1872     }
1873 
1874     index =
1875         gst_ttml_render_get_element_index (clone, range->first_index,
1876         &first_offset);
1877     GST_CAT_LOG (ttmlrender_debug, "First char in range is in element %d",
1878         index);
1879 
1880     if (index < 0) {
1881       GST_CAT_WARNING (ttmlrender_debug,
1882           "Range start not found in block text.");
1883       gst_ttml_render_unified_block_free (clone);
1884       continue;
1885     }
1886 
1887     /* Remove elements that are before the one that contains the range start. */
1888     while (index > 0) {
1889       GST_CAT_LOG (ttmlrender_debug, "Removing first element in cloned block");
1890       g_ptr_array_remove_index (clone->unified_elements, 0);
1891       --index;
1892     }
1893 
1894     /* Remove characters from first element that are before the range start. */
1895     ue = gst_ttml_render_unified_block_get_element (clone, 0);
1896     if (first_offset > 0) {
1897       tmp = ue->text;
1898       ue->text = g_strdup (ue->text + first_offset);
1899       GST_CAT_DEBUG (ttmlrender_debug,
1900           "First element text has been clipped to \"%s\"", ue->text);
1901       g_free (tmp);
1902 
1903       if (gst_ttml_render_unified_block_element_count (clone) == 1)
1904         last_offset -= first_offset;
1905     }
1906 
1907     /* Remove characters from last element that are after the range end. */
1908     ue = gst_ttml_render_unified_block_get_element (clone,
1909         gst_ttml_render_unified_block_element_count (clone) - 1);
1910     if (last_offset < (strlen (ue->text) - 1)) {
1911       tmp = ue->text;
1912       ue->text = g_strndup (ue->text, last_offset + 1);
1913       GST_CAT_DEBUG (ttmlrender_debug,
1914           "Last element text has been clipped to \"%s\"", ue->text);
1915       g_free (tmp);
1916     }
1917 
1918     if (gst_ttml_render_unified_block_element_count (clone) > 0)
1919       g_ptr_array_add (ret, clone);
1920     else
1921       gst_ttml_render_unified_block_free (clone);
1922   }
1923 
1924   if (ret->len == 0) {
1925     GST_CAT_DEBUG (ttmlrender_debug, "No elements remain in clone.");
1926     g_ptr_array_unref (ret);
1927     ret = NULL;
1928   }
1929   return ret;
1930 }
1931 
1932 
1933 /* Render the text in a pango-markup string. */
1934 static GstTtmlRenderRenderedImage *
gst_ttml_render_draw_text(GstTtmlRender * render,const gchar * text,guint line_height,guint baseline_offset)1935 gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text,
1936     guint line_height, guint baseline_offset)
1937 {
1938   GstTtmlRenderRenderedImage *ret;
1939   cairo_surface_t *surface, *cropped_surface;
1940   cairo_t *cairo_state, *cropped_state;
1941   GstMapInfo map;
1942   PangoRectangle logical_rect, ink_rect;
1943   guint buf_width, buf_height;
1944   gint stride;
1945   gint bounding_box_x1, bounding_box_x2, bounding_box_y1, bounding_box_y2;
1946   gint baseline;
1947 
1948   ret = gst_ttml_render_rendered_image_new_empty ();
1949 
1950   pango_layout_set_markup (render->layout, text, strlen (text));
1951   GST_CAT_DEBUG (ttmlrender_debug, "Layout text: \"%s\"",
1952       pango_layout_get_text (render->layout));
1953   pango_layout_set_width (render->layout, -1);
1954 
1955   pango_layout_get_pixel_extents (render->layout, &ink_rect, &logical_rect);
1956 
1957   baseline = PANGO_PIXELS (pango_layout_get_baseline (render->layout));
1958 
1959   bounding_box_x1 = MIN (logical_rect.x, ink_rect.x);
1960   bounding_box_x2 = MAX (logical_rect.x + logical_rect.width,
1961       ink_rect.x + ink_rect.width);
1962   bounding_box_y1 = MIN (logical_rect.y, ink_rect.y);
1963   bounding_box_y2 = MAX (logical_rect.y + logical_rect.height,
1964       ink_rect.y + ink_rect.height);
1965 
1966   surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1967       (bounding_box_x2 - bounding_box_x1), (bounding_box_y2 - bounding_box_y1));
1968   cairo_state = cairo_create (surface);
1969   cairo_set_operator (cairo_state, CAIRO_OPERATOR_CLEAR);
1970   cairo_paint (cairo_state);
1971   cairo_set_operator (cairo_state, CAIRO_OPERATOR_OVER);
1972 
1973   cairo_save (cairo_state);
1974   pango_cairo_show_layout (cairo_state, render->layout);
1975   cairo_restore (cairo_state);
1976 
1977   buf_width = bounding_box_x2 - bounding_box_x1;
1978   buf_height = ink_rect.height;
1979   GST_CAT_DEBUG (ttmlrender_debug, "Output buffer width: %u  height: %u",
1980       buf_width, buf_height);
1981 
1982   ret->image = gst_buffer_new_allocate (NULL, 4 * buf_width * buf_height, NULL);
1983   gst_buffer_memset (ret->image, 0, 0U, 4 * buf_width * buf_height);
1984   gst_buffer_map (ret->image, &map, GST_MAP_READWRITE);
1985 
1986   stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, buf_width);
1987   cropped_surface =
1988       cairo_image_surface_create_for_data (map.data, CAIRO_FORMAT_ARGB32,
1989       (bounding_box_x2 - bounding_box_x1), ink_rect.height, stride);
1990   cropped_state = cairo_create (cropped_surface);
1991   cairo_set_source_surface (cropped_state, surface, -bounding_box_x1,
1992       -ink_rect.y);
1993   cairo_rectangle (cropped_state, 0, 0, buf_width, buf_height);
1994   cairo_fill (cropped_state);
1995 
1996   cairo_destroy (cairo_state);
1997   cairo_surface_destroy (surface);
1998   cairo_destroy (cropped_state);
1999   cairo_surface_destroy (cropped_surface);
2000   gst_buffer_unmap (ret->image, &map);
2001 
2002   ret->width = buf_width;
2003   ret->height = buf_height;
2004   ret->x = 0;
2005   ret->y = MAX (0, (gint) baseline_offset - (baseline - ink_rect.y));
2006   return ret;
2007 }
2008 
2009 
2010 static GstTtmlRenderRenderedImage *
gst_ttml_render_render_block_elements(GstTtmlRender * render,UnifiedBlock * block,BlockMetrics block_metrics)2011 gst_ttml_render_render_block_elements (GstTtmlRender * render,
2012     UnifiedBlock * block, BlockMetrics block_metrics)
2013 {
2014   GPtrArray *inline_images = g_ptr_array_new_with_free_func (
2015       (GDestroyNotify) gst_ttml_render_rendered_image_free);
2016   GstTtmlRenderRenderedImage *ret = NULL;
2017   guint line_padding =
2018       (guint) ceil (block->style_set->line_padding * render->width);
2019   gint i;
2020 
2021   for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2022     UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2023     gchar *markup;
2024     GstTtmlRenderRenderedImage *text_image, *bg_image, *combined_image;
2025     guint bg_offset, bg_width, bg_height;
2026     GstBuffer *background;
2027 
2028     markup = gst_ttml_render_generate_pango_markup (ue->element->style_set,
2029         ue->pango_font_size, ue->text);
2030     text_image = gst_ttml_render_draw_text (render, markup,
2031         block_metrics.line_height, block_metrics.baseline_offset);
2032     g_free (markup);
2033 
2034     if (!block->style_set->fill_line_gap) {
2035       bg_offset =
2036           block_metrics.baseline_offset - ue->pango_font_metrics.baseline;
2037       bg_height = ue->pango_font_metrics.height;
2038     } else {
2039       bg_offset = 0;
2040       bg_height = block_metrics.line_height;
2041     }
2042     bg_width = text_image->width;
2043 
2044     if (line_padding > 0) {
2045       if (i == 0) {
2046         text_image->x += line_padding;
2047         bg_width += line_padding;
2048       }
2049       if (i == (gst_ttml_render_unified_block_element_count (block) - 1))
2050         bg_width += line_padding;
2051     }
2052 
2053     background = gst_ttml_render_draw_rectangle (bg_width, bg_height,
2054         ue->element->style_set->background_color);
2055     bg_image = gst_ttml_render_rendered_image_new (background, 0,
2056         bg_offset, bg_width, bg_height);
2057     combined_image = gst_ttml_render_rendered_image_combine (bg_image,
2058         text_image);
2059     gst_ttml_render_rendered_image_free (bg_image);
2060     gst_ttml_render_rendered_image_free (text_image);
2061     g_ptr_array_add (inline_images, combined_image);
2062   }
2063 
2064   ret = gst_ttml_render_stitch_images (inline_images,
2065       GST_TTML_DIRECTION_INLINE);
2066   GST_CAT_DEBUG (ttmlrender_debug,
2067       "Stitched line image - x:%d  y:%d  w:%u  h:%u",
2068       ret->x, ret->y, ret->width, ret->height);
2069   g_ptr_array_unref (inline_images);
2070   return ret;
2071 }
2072 
2073 
2074 /*
2075  * Align the images in @lines according to the multi_row_align and text_align
2076  * settings in @style_set.
2077  */
2078 static void
gst_ttml_render_align_line_areas(GPtrArray * lines,const GstSubtitleStyleSet * style_set)2079 gst_ttml_render_align_line_areas (GPtrArray * lines,
2080     const GstSubtitleStyleSet * style_set)
2081 {
2082   guint longest_line_width = 0;
2083   gint i;
2084 
2085   for (i = 0; i < lines->len; ++i) {
2086     GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i);
2087     if (line->width > longest_line_width)
2088       longest_line_width = line->width;
2089   }
2090 
2091   for (i = 0; i < lines->len; ++i) {
2092     GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i);
2093 
2094     switch (style_set->multi_row_align) {
2095       case GST_SUBTITLE_MULTI_ROW_ALIGN_CENTER:
2096         line->x += (gint) round ((longest_line_width - line->width) / 2.0);
2097         break;
2098       case GST_SUBTITLE_MULTI_ROW_ALIGN_END:
2099         line->x += (longest_line_width - line->width);
2100         break;
2101       case GST_SUBTITLE_MULTI_ROW_ALIGN_AUTO:
2102         switch (style_set->text_align) {
2103           case GST_SUBTITLE_TEXT_ALIGN_CENTER:
2104             line->x += (gint) round ((longest_line_width - line->width) / 2.0);
2105             break;
2106           case GST_SUBTITLE_TEXT_ALIGN_END:
2107           case GST_SUBTITLE_TEXT_ALIGN_RIGHT:
2108             line->x += (longest_line_width - line->width);
2109             break;
2110           default:
2111             break;
2112         }
2113         break;
2114       default:
2115         break;
2116     }
2117   }
2118 }
2119 
2120 
2121 /*
2122  * Renders each UnifiedBlock in @blocks, and sets the positions of the
2123  * resulting images according to the line height in @metrics and the alignment
2124  * settings in @style_set.
2125  */
2126 static GPtrArray *
gst_ttml_render_layout_blocks(GstTtmlRender * render,GPtrArray * blocks,BlockMetrics metrics,const GstSubtitleStyleSet * style_set)2127 gst_ttml_render_layout_blocks (GstTtmlRender * render, GPtrArray * blocks,
2128     BlockMetrics metrics, const GstSubtitleStyleSet * style_set)
2129 {
2130   GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify)
2131       gst_ttml_render_rendered_image_free);
2132   gint i;
2133 
2134   for (i = 0; i < blocks->len; ++i) {
2135     UnifiedBlock *block = g_ptr_array_index (blocks, i);
2136 
2137     GstTtmlRenderRenderedImage *line =
2138         gst_ttml_render_render_block_elements (render, block,
2139         metrics);
2140     line->y += (i * metrics.line_height);
2141     g_ptr_array_add (ret, line);
2142   }
2143 
2144   gst_ttml_render_align_line_areas (ret, style_set);
2145   return ret;
2146 }
2147 
2148 
2149 /* If any of an array of elements has line wrapping enabled, returns TRUE. */
2150 static gboolean
gst_ttml_render_elements_are_wrapped(GPtrArray * elements)2151 gst_ttml_render_elements_are_wrapped (GPtrArray * elements)
2152 {
2153   GstSubtitleElement *element;
2154   guint i;
2155 
2156   for (i = 0; i < elements->len; ++i) {
2157     element = g_ptr_array_index (elements, i);
2158     if (element->style_set->wrap_option == GST_SUBTITLE_WRAPPING_ON)
2159       return TRUE;
2160   }
2161 
2162   return FALSE;
2163 }
2164 
2165 
2166 /*
2167  * Return the descender (in pixels) shared by the greatest number of glyphs in
2168  * @block.
2169  */
2170 static guint
gst_ttml_render_get_most_frequent_descender(GstTtmlRender * render,UnifiedBlock * block)2171 gst_ttml_render_get_most_frequent_descender (GstTtmlRender * render,
2172     UnifiedBlock * block)
2173 {
2174   GHashTable *count_table = g_hash_table_new (g_direct_hash, g_direct_equal);
2175   GHashTableIter iter;
2176   gpointer key, value;
2177   guint max_count = 0;
2178   guint ret = 0;
2179   gint i;
2180 
2181   for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2182     UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2183     guint descender =
2184         ue->pango_font_metrics.height - ue->pango_font_metrics.baseline;
2185     guint count;
2186 
2187     if (g_hash_table_contains (count_table, GUINT_TO_POINTER (descender))) {
2188       count = GPOINTER_TO_UINT (g_hash_table_lookup (count_table,
2189               GUINT_TO_POINTER (descender)));
2190       GST_CAT_LOG (ttmlrender_debug,
2191           "Table already contains %u glyphs with descender %u; increasing "
2192           "that count to %ld", count, descender,
2193           count + g_utf8_strlen (ue->text, -1));
2194       count += g_utf8_strlen (ue->text, -1);
2195     } else {
2196       count = g_utf8_strlen (ue->text, -1);
2197       GST_CAT_LOG (ttmlrender_debug,
2198           "No glyphs with descender %u; adding entry to table with count of %u",
2199           descender, count);
2200     }
2201 
2202     g_hash_table_insert (count_table,
2203         GUINT_TO_POINTER (descender), GUINT_TO_POINTER (count));
2204   }
2205 
2206   g_hash_table_iter_init (&iter, count_table);
2207   while (g_hash_table_iter_next (&iter, &key, &value)) {
2208     guint descender = GPOINTER_TO_UINT (key);
2209     guint count = GPOINTER_TO_UINT (value);
2210 
2211     if (count > max_count) {
2212       max_count = count;
2213       ret = descender;
2214     }
2215   }
2216 
2217   g_hash_table_unref (count_table);
2218   return ret;
2219 }
2220 
2221 
2222 static BlockMetrics
gst_ttml_render_get_block_metrics(GstTtmlRender * render,UnifiedBlock * block)2223 gst_ttml_render_get_block_metrics (GstTtmlRender * render, UnifiedBlock * block)
2224 {
2225   BlockMetrics ret;
2226 
2227   /*
2228    * The specified behaviour in TTML when lineHeight is "normal" is different
2229    * from the behaviour when a percentage is given. In the former case, the
2230    * line height is a percentage (the TTML spec recommends 125%) of the largest
2231    * font size that is applied to the spans within the block; in the latter
2232    * case, the line height is the given percentage of the font size that is
2233    * applied to the block itself.
2234    */
2235   if (block->style_set->line_height < 0) {      /* lineHeight="normal" case */
2236     guint max_text_height = 0;
2237     guint descender = 0;
2238     guint i;
2239 
2240     for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) {
2241       UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i);
2242 
2243       if (ue->pango_font_metrics.height > max_text_height) {
2244         max_text_height = ue->pango_font_metrics.height;
2245         descender =
2246             ue->pango_font_metrics.height - ue->pango_font_metrics.baseline;
2247       }
2248     }
2249 
2250     GST_CAT_LOG (ttmlrender_debug, "Max descender: %u   Max text height: %u",
2251         descender, max_text_height);
2252     ret.line_height = (guint) ceil (max_text_height * 1.25);
2253     ret.baseline_offset = (guint) ((max_text_height + ret.line_height) / 2.0)
2254         - descender;
2255   } else {
2256     guint descender;
2257     guint font_size;
2258 
2259     descender = gst_ttml_render_get_most_frequent_descender (render, block);
2260     GST_CAT_LOG (ttmlrender_debug,
2261         "Got most frequent descender value of %u pixels.", descender);
2262     font_size = (guint) ceil (block->style_set->font_size * render->height);
2263     ret.line_height = (guint) ceil (font_size * block->style_set->line_height);
2264     ret.baseline_offset = (guint) ((font_size + ret.line_height) / 2.0)
2265         - descender;
2266   }
2267 
2268   return ret;
2269 }
2270 
2271 
2272 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_new(GstBuffer * image,gint x,gint y,guint width,guint height)2273 gst_ttml_render_rendered_image_new (GstBuffer * image, gint x, gint y,
2274     guint width, guint height)
2275 {
2276   GstTtmlRenderRenderedImage *ret;
2277 
2278   ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2279 
2280   ret->image = image;
2281   ret->x = x;
2282   ret->y = y;
2283   ret->width = width;
2284   ret->height = height;
2285 
2286   return ret;
2287 }
2288 
2289 
2290 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_new_empty(void)2291 gst_ttml_render_rendered_image_new_empty (void)
2292 {
2293   return gst_ttml_render_rendered_image_new (NULL, 0, 0, 0, 0);
2294 }
2295 
2296 
2297 static inline GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_copy(GstTtmlRenderRenderedImage * image)2298 gst_ttml_render_rendered_image_copy (GstTtmlRenderRenderedImage * image)
2299 {
2300   GstTtmlRenderRenderedImage *ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2301 
2302   ret->image = gst_buffer_ref (image->image);
2303   ret->x = image->x;
2304   ret->y = image->y;
2305   ret->width = image->width;
2306   ret->height = image->height;
2307 
2308   return ret;
2309 }
2310 
2311 
2312 static void
gst_ttml_render_rendered_image_free(GstTtmlRenderRenderedImage * image)2313 gst_ttml_render_rendered_image_free (GstTtmlRenderRenderedImage * image)
2314 {
2315   if (!image)
2316     return;
2317   gst_buffer_unref (image->image);
2318   g_slice_free (GstTtmlRenderRenderedImage, image);
2319 }
2320 
2321 
2322 /*
2323  * Combines two rendered image into a single image. The order of arguments is
2324  * significant: @image2 will be rendered on top of @image1.
2325  */
2326 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_combine(GstTtmlRenderRenderedImage * image1,GstTtmlRenderRenderedImage * image2)2327 gst_ttml_render_rendered_image_combine (GstTtmlRenderRenderedImage * image1,
2328     GstTtmlRenderRenderedImage * image2)
2329 {
2330   GstTtmlRenderRenderedImage *ret;
2331   GstMapInfo map1, map2, map_dest;
2332   cairo_surface_t *sfc1, *sfc2, *sfc_dest;
2333   cairo_t *state_dest;
2334 
2335   if (!image1 && !image2)
2336     return NULL;
2337   if (image1 && !image2)
2338     return gst_ttml_render_rendered_image_copy (image1);
2339   if (image2 && !image1)
2340     return gst_ttml_render_rendered_image_copy (image2);
2341 
2342   ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2343 
2344   /* Work out dimensions of combined image. */
2345   ret->x = MIN (image1->x, image2->x);
2346   ret->y = MIN (image1->y, image2->y);
2347   ret->width = MAX (image1->x + image1->width, image2->x + image2->width)
2348       - ret->x;
2349   ret->height = MAX (image1->y + image1->height, image2->y + image2->height)
2350       - ret->y;
2351 
2352   GST_CAT_LOG (ttmlrender_debug, "Dimensions of combined image:  x:%u  y:%u  "
2353       "width:%u  height:%u", ret->x, ret->y, ret->width, ret->height);
2354 
2355   /* Create cairo_surface from src images. */
2356   gst_buffer_map (image1->image, &map1, GST_MAP_READ);
2357   sfc1 =
2358       cairo_image_surface_create_for_data (map1.data, CAIRO_FORMAT_ARGB32,
2359       image1->width, image1->height,
2360       cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image1->width));
2361 
2362   gst_buffer_map (image2->image, &map2, GST_MAP_READ);
2363   sfc2 =
2364       cairo_image_surface_create_for_data (map2.data, CAIRO_FORMAT_ARGB32,
2365       image2->width, image2->height,
2366       cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image2->width));
2367 
2368   /* Create cairo_surface for resultant image. */
2369   ret->image = gst_buffer_new_allocate (NULL, 4 * ret->width * ret->height,
2370       NULL);
2371   gst_buffer_memset (ret->image, 0, 0U, 4 * ret->width * ret->height);
2372   gst_buffer_map (ret->image, &map_dest, GST_MAP_READWRITE);
2373   sfc_dest =
2374       cairo_image_surface_create_for_data (map_dest.data, CAIRO_FORMAT_ARGB32,
2375       ret->width, ret->height,
2376       cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, ret->width));
2377   state_dest = cairo_create (sfc_dest);
2378 
2379   /* Blend image1 into destination surface. */
2380   cairo_set_source_surface (state_dest, sfc1, image1->x - ret->x,
2381       image1->y - ret->y);
2382   cairo_rectangle (state_dest, image1->x - ret->x, image1->y - ret->y,
2383       image1->width, image1->height);
2384   cairo_fill (state_dest);
2385 
2386   /* Blend image2 into destination surface. */
2387   cairo_set_source_surface (state_dest, sfc2, image2->x - ret->x,
2388       image2->y - ret->y);
2389   cairo_rectangle (state_dest, image2->x - ret->x, image2->y - ret->y,
2390       image2->width, image2->height);
2391   cairo_fill (state_dest);
2392 
2393   /* Return destination image. */
2394   cairo_destroy (state_dest);
2395   cairo_surface_destroy (sfc1);
2396   cairo_surface_destroy (sfc2);
2397   cairo_surface_destroy (sfc_dest);
2398   gst_buffer_unmap (image1->image, &map1);
2399   gst_buffer_unmap (image2->image, &map2);
2400   gst_buffer_unmap (ret->image, &map_dest);
2401 
2402   return ret;
2403 }
2404 
2405 
2406 static GstTtmlRenderRenderedImage *
gst_ttml_render_rendered_image_crop(GstTtmlRenderRenderedImage * image,gint x,gint y,guint width,guint height)2407 gst_ttml_render_rendered_image_crop (GstTtmlRenderRenderedImage * image,
2408     gint x, gint y, guint width, guint height)
2409 {
2410   GstTtmlRenderRenderedImage *ret;
2411   GstMapInfo map_src, map_dest;
2412   cairo_surface_t *sfc_src, *sfc_dest;
2413   cairo_t *state_dest;
2414 
2415   if ((x <= image->x) && (y <= image->y) && (width >= image->width)
2416       && (height >= image->height))
2417     return gst_ttml_render_rendered_image_copy (image);
2418 
2419   if (image->x >= (x + (gint) width)
2420       || (image->x + (gint) image->width) <= x
2421       || image->y >= (y + (gint) height)
2422       || (image->y + (gint) image->height) <= y) {
2423     GST_CAT_WARNING (ttmlrender_debug,
2424         "Crop rectangle doesn't intersect image.");
2425     return NULL;
2426   }
2427 
2428   ret = g_slice_new0 (GstTtmlRenderRenderedImage);
2429 
2430   ret->x = MAX (image->x, x);
2431   ret->y = MAX (image->y, y);
2432   ret->width = MIN ((image->x + image->width) - ret->x, (x + width) - ret->x);
2433   ret->height = MIN ((image->y + image->height) - ret->y,
2434       (y + height) - ret->y);
2435 
2436   GST_CAT_LOG (ttmlrender_debug, "Dimensions of cropped image:  x:%u  y:%u  "
2437       "width:%u  height:%u", ret->x, ret->y, ret->width, ret->height);
2438 
2439   /* Create cairo_surface from src image. */
2440   gst_buffer_map (image->image, &map_src, GST_MAP_READ);
2441   sfc_src =
2442       cairo_image_surface_create_for_data (map_src.data, CAIRO_FORMAT_ARGB32,
2443       image->width, image->height,
2444       cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, image->width));
2445 
2446   /* Create cairo_surface for cropped image. */
2447   ret->image = gst_buffer_new_allocate (NULL, 4 * ret->width * ret->height,
2448       NULL);
2449   gst_buffer_memset (ret->image, 0, 0U, 4 * ret->width * ret->height);
2450   gst_buffer_map (ret->image, &map_dest, GST_MAP_READWRITE);
2451   sfc_dest =
2452       cairo_image_surface_create_for_data (map_dest.data, CAIRO_FORMAT_ARGB32,
2453       ret->width, ret->height,
2454       cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, ret->width));
2455   state_dest = cairo_create (sfc_dest);
2456 
2457   /* Copy section of image1 into destination surface. */
2458   cairo_set_source_surface (state_dest, sfc_src, (image->x - ret->x),
2459       (image->y - ret->y));
2460   cairo_rectangle (state_dest, 0, 0, ret->width, ret->height);
2461   cairo_fill (state_dest);
2462 
2463   cairo_destroy (state_dest);
2464   cairo_surface_destroy (sfc_src);
2465   cairo_surface_destroy (sfc_dest);
2466   gst_buffer_unmap (image->image, &map_src);
2467   gst_buffer_unmap (ret->image, &map_dest);
2468 
2469   return ret;
2470 }
2471 
2472 
2473 static gboolean
gst_ttml_render_color_is_transparent(GstSubtitleColor * color)2474 gst_ttml_render_color_is_transparent (GstSubtitleColor * color)
2475 {
2476   return (color->a == 0);
2477 }
2478 
2479 
2480 /*
2481  * Overlays a set of rendered images to return a single image. Order is
2482  * significant: later entries in @images are rendered on top of earlier
2483  * entries.
2484  */
2485 static GstTtmlRenderRenderedImage *
gst_ttml_render_overlay_images(GPtrArray * images)2486 gst_ttml_render_overlay_images (GPtrArray * images)
2487 {
2488   GstTtmlRenderRenderedImage *ret = NULL;
2489   gint i;
2490 
2491   for (i = 0; i < images->len; ++i) {
2492     GstTtmlRenderRenderedImage *tmp = ret;
2493     ret = gst_ttml_render_rendered_image_combine (ret,
2494         g_ptr_array_index (images, i));
2495     gst_ttml_render_rendered_image_free (tmp);
2496   }
2497 
2498   return ret;
2499 }
2500 
2501 
2502 /*
2503  * Takes a set of images and renders them as a single image, where all the
2504  * images are arranged contiguously in the direction given by @direction. Note
2505  * that the positions of the images in @images will be altered.
2506  */
2507 static GstTtmlRenderRenderedImage *
gst_ttml_render_stitch_images(GPtrArray * images,GstTtmlDirection direction)2508 gst_ttml_render_stitch_images (GPtrArray * images, GstTtmlDirection direction)
2509 {
2510   guint cur_offset = 0;
2511   GstTtmlRenderRenderedImage *ret = NULL;
2512   gint i;
2513 
2514   for (i = 0; i < images->len; ++i) {
2515     GstTtmlRenderRenderedImage *block;
2516     block = g_ptr_array_index (images, i);
2517 
2518     if (direction == GST_TTML_DIRECTION_BLOCK) {
2519       block->y += cur_offset;
2520       cur_offset = block->y + block->height;
2521     } else {
2522       block->x += cur_offset;
2523       cur_offset = block->x + block->width;
2524     }
2525   }
2526 
2527   ret = gst_ttml_render_overlay_images (images);
2528 
2529   if (ret) {
2530     if (direction == GST_TTML_DIRECTION_BLOCK)
2531       GST_CAT_LOG (ttmlrender_debug, "Height of stitched image: %u",
2532           ret->height);
2533     else
2534       GST_CAT_LOG (ttmlrender_debug, "Width of stitched image: %u", ret->width);
2535     ret->image = gst_buffer_make_writable (ret->image);
2536   }
2537   return ret;
2538 }
2539 
2540 
2541 static GstTtmlRenderRenderedImage *
gst_ttml_render_render_text_block(GstTtmlRender * render,const GstSubtitleBlock * block,GstBuffer * text_buf,guint width,gboolean overflow)2542 gst_ttml_render_render_text_block (GstTtmlRender * render,
2543     const GstSubtitleBlock * block, GstBuffer * text_buf, guint width,
2544     gboolean overflow)
2545 {
2546   UnifiedBlock *unified_block;
2547   BlockMetrics metrics;
2548   gboolean wrap;
2549   guint line_padding;
2550   GPtrArray *ranges;
2551   GPtrArray *split_blocks;
2552   GPtrArray *images;
2553   GstTtmlRenderRenderedImage *rendered_block = NULL;
2554   gint i;
2555 
2556   unified_block = gst_ttml_render_unify_block (render, block, text_buf);
2557   metrics = gst_ttml_render_get_block_metrics (render, unified_block);
2558   wrap = gst_ttml_render_elements_are_wrapped (block->elements);
2559 
2560   line_padding = (guint) ceil (block->style_set->line_padding * render->width);
2561   ranges = gst_ttml_render_get_line_char_ranges (render, unified_block, width -
2562       (2 * line_padding), wrap);
2563 
2564   for (i = 0; i < ranges->len; ++i) {
2565     CharRange *range = g_ptr_array_index (ranges, i);
2566     GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u  last:%u", i,
2567         range->first_index, range->last_index);
2568   }
2569 
2570   split_blocks = gst_ttml_render_split_block (unified_block, ranges);
2571   if (split_blocks) {
2572     guint blocks_remining = gst_ttml_render_handle_whitespace (split_blocks);
2573     GST_CAT_DEBUG (ttmlrender_debug,
2574         "There are %u blocks remaining after whitespace handling.",
2575         blocks_remining);
2576 
2577     if (blocks_remining > 0) {
2578       images = gst_ttml_render_layout_blocks (render, split_blocks, metrics,
2579           unified_block->style_set);
2580       rendered_block = gst_ttml_render_overlay_images (images);
2581       g_ptr_array_unref (images);
2582     }
2583     g_ptr_array_unref (split_blocks);
2584   }
2585 
2586   g_ptr_array_unref (ranges);
2587   gst_ttml_render_unified_block_free (unified_block);
2588   return rendered_block;
2589 }
2590 
2591 
2592 static GstVideoOverlayComposition *
gst_ttml_render_compose_overlay(GstTtmlRenderRenderedImage * image)2593 gst_ttml_render_compose_overlay (GstTtmlRenderRenderedImage * image)
2594 {
2595   GstVideoOverlayRectangle *rectangle;
2596   GstVideoOverlayComposition *ret = NULL;
2597 
2598   gst_buffer_add_video_meta (image->image, GST_VIDEO_FRAME_FLAG_NONE,
2599       GST_VIDEO_OVERLAY_COMPOSITION_FORMAT_RGB, image->width, image->height);
2600 
2601   rectangle = gst_video_overlay_rectangle_new_raw (image->image, image->x,
2602       image->y, image->width, image->height,
2603       GST_VIDEO_OVERLAY_FORMAT_FLAG_PREMULTIPLIED_ALPHA);
2604 
2605   ret = gst_video_overlay_composition_new (rectangle);
2606   gst_video_overlay_rectangle_unref (rectangle);
2607   return ret;
2608 }
2609 
2610 
2611 static GstVideoOverlayComposition *
gst_ttml_render_render_text_region(GstTtmlRender * render,GstSubtitleRegion * region,GstBuffer * text_buf)2612 gst_ttml_render_render_text_region (GstTtmlRender * render,
2613     GstSubtitleRegion * region, GstBuffer * text_buf)
2614 {
2615   guint region_x, region_y, region_width, region_height;
2616   guint window_x, window_y, window_width, window_height;
2617   guint padding_start, padding_end, padding_before, padding_after;
2618   GPtrArray *rendered_blocks =
2619       g_ptr_array_new_with_free_func (
2620       (GDestroyNotify) gst_ttml_render_rendered_image_free);
2621   GstTtmlRenderRenderedImage *region_image = NULL;
2622   GstVideoOverlayComposition *ret = NULL;
2623   guint i;
2624 
2625   region_width = (guint) (round (region->style_set->extent_w * render->width));
2626   region_height =
2627       (guint) (round (region->style_set->extent_h * render->height));
2628   region_x = (guint) (round (region->style_set->origin_x * render->width));
2629   region_y = (guint) (round (region->style_set->origin_y * render->height));
2630 
2631   padding_start =
2632       (guint) (round (region->style_set->padding_start * render->width));
2633   padding_end =
2634       (guint) (round (region->style_set->padding_end * render->width));
2635   padding_before =
2636       (guint) (round (region->style_set->padding_before * render->height));
2637   padding_after =
2638       (guint) (round (region->style_set->padding_after * render->height));
2639 
2640   /* "window" here refers to the section of the region that we're allowed to
2641    * render into, i.e., the region minus padding. */
2642   window_x = region_x + padding_start;
2643   window_y = region_y + padding_before;
2644   window_width = region_width - (padding_start + padding_end);
2645   window_height = region_height - (padding_before + padding_after);
2646 
2647   GST_CAT_DEBUG (ttmlrender_debug,
2648       "Padding: start: %u  end: %u  before: %u  after: %u",
2649       padding_start, padding_end, padding_before, padding_after);
2650 
2651   /* Render region background, if non-transparent. */
2652   if (!gst_ttml_render_color_is_transparent (&region->style_set->
2653           background_color)) {
2654     GstBuffer *bg_rect;
2655 
2656     bg_rect = gst_ttml_render_draw_rectangle (region_width, region_height,
2657         region->style_set->background_color);
2658     region_image = gst_ttml_render_rendered_image_new (bg_rect, region_x,
2659         region_y, region_width, region_height);
2660   }
2661 
2662   /* Render each block and append to list. */
2663   for (i = 0; i < gst_subtitle_region_get_block_count (region); ++i) {
2664     const GstSubtitleBlock *block;
2665     GstTtmlRenderRenderedImage *rendered_block, *block_bg_image, *tmp;
2666     GstBuffer *block_bg_buf;
2667     gint block_height;
2668 
2669     block = gst_subtitle_region_get_block (region, i);
2670     rendered_block = gst_ttml_render_render_text_block (render, block, text_buf,
2671         window_width, TRUE);
2672 
2673     if (!rendered_block)
2674       continue;
2675 
2676     GST_CAT_LOG (ttmlrender_debug, "rendered_block - x:%d  y:%d  w:%u  h:%u",
2677         rendered_block->x, rendered_block->y, rendered_block->width,
2678         rendered_block->height);
2679 
2680     switch (block->style_set->text_align) {
2681       case GST_SUBTITLE_TEXT_ALIGN_CENTER:
2682         rendered_block->x
2683             += (gint) round ((window_width - rendered_block->width) / 2.0);
2684         break;
2685 
2686       case GST_SUBTITLE_TEXT_ALIGN_RIGHT:
2687       case GST_SUBTITLE_TEXT_ALIGN_END:
2688         rendered_block->x += (window_width - rendered_block->width);
2689         break;
2690 
2691       default:
2692         break;
2693     }
2694 
2695     tmp = rendered_block;
2696 
2697     block_height = rendered_block->height + (2 * rendered_block->y);
2698     block_bg_buf = gst_ttml_render_draw_rectangle (window_width,
2699         block_height, block->style_set->background_color);
2700     block_bg_image = gst_ttml_render_rendered_image_new (block_bg_buf, 0, 0,
2701         window_width, block_height);
2702     rendered_block = gst_ttml_render_rendered_image_combine (block_bg_image,
2703         rendered_block);
2704     gst_ttml_render_rendered_image_free (tmp);
2705     gst_ttml_render_rendered_image_free (block_bg_image);
2706 
2707     rendered_block->y = 0;
2708     g_ptr_array_add (rendered_blocks, rendered_block);
2709   }
2710 
2711   if (rendered_blocks->len > 0) {
2712     GstTtmlRenderRenderedImage *blocks_image, *tmp;
2713 
2714     blocks_image = gst_ttml_render_stitch_images (rendered_blocks,
2715         GST_TTML_DIRECTION_BLOCK);
2716     blocks_image->x += window_x;
2717 
2718     switch (region->style_set->display_align) {
2719       case GST_SUBTITLE_DISPLAY_ALIGN_BEFORE:
2720         blocks_image->y = window_y;
2721         break;
2722       case GST_SUBTITLE_DISPLAY_ALIGN_CENTER:
2723         blocks_image->y = region_y + ((gint) ((region_height + padding_before)
2724                 - (padding_after + blocks_image->height))) / 2;
2725         break;
2726       case GST_SUBTITLE_DISPLAY_ALIGN_AFTER:
2727         blocks_image->y = (region_y + region_height)
2728             - (padding_after + blocks_image->height);
2729         break;
2730     }
2731 
2732     if ((region->style_set->overflow == GST_SUBTITLE_OVERFLOW_MODE_HIDDEN)
2733         && ((blocks_image->height > window_height)
2734             || (blocks_image->width > window_width))) {
2735       GstTtmlRenderRenderedImage *tmp = blocks_image;
2736       blocks_image = gst_ttml_render_rendered_image_crop (blocks_image,
2737           window_x, window_y, window_width, window_height);
2738       gst_ttml_render_rendered_image_free (tmp);
2739     }
2740 
2741     tmp = region_image;
2742     region_image =
2743         gst_ttml_render_rendered_image_combine (region_image, blocks_image);
2744     gst_ttml_render_rendered_image_free (tmp);
2745     gst_ttml_render_rendered_image_free (blocks_image);
2746   }
2747 
2748   if (region_image) {
2749     ret = gst_ttml_render_compose_overlay (region_image);
2750     gst_ttml_render_rendered_image_free (region_image);
2751   }
2752 
2753   g_ptr_array_unref (rendered_blocks);
2754   return ret;
2755 }
2756 
2757 
2758 static GstFlowReturn
gst_ttml_render_video_chain(GstPad * pad,GstObject * parent,GstBuffer * buffer)2759 gst_ttml_render_video_chain (GstPad * pad, GstObject * parent,
2760     GstBuffer * buffer)
2761 {
2762   GstTtmlRender *render;
2763   GstFlowReturn ret = GST_FLOW_OK;
2764   gboolean in_seg = FALSE;
2765   guint64 start, stop, clip_start = 0, clip_stop = 0;
2766   gchar *text = NULL;
2767 
2768   render = GST_TTML_RENDER (parent);
2769 
2770   if (!GST_BUFFER_TIMESTAMP_IS_VALID (buffer))
2771     goto missing_timestamp;
2772 
2773   /* ignore buffers that are outside of the current segment */
2774   start = GST_BUFFER_TIMESTAMP (buffer);
2775 
2776   if (!GST_BUFFER_DURATION_IS_VALID (buffer)) {
2777     stop = GST_CLOCK_TIME_NONE;
2778   } else {
2779     stop = start + GST_BUFFER_DURATION (buffer);
2780   }
2781 
2782   GST_LOG_OBJECT (render, "%" GST_SEGMENT_FORMAT "  BUFFER: ts=%"
2783       GST_TIME_FORMAT ", end=%" GST_TIME_FORMAT, &render->segment,
2784       GST_TIME_ARGS (start), GST_TIME_ARGS (stop));
2785 
2786   /* segment_clip() will adjust start unconditionally to segment_start if
2787    * no stop time is provided, so handle this ourselves */
2788   if (stop == GST_CLOCK_TIME_NONE && start < render->segment.start)
2789     goto out_of_segment;
2790 
2791   in_seg = gst_segment_clip (&render->segment, GST_FORMAT_TIME, start, stop,
2792       &clip_start, &clip_stop);
2793 
2794   if (!in_seg)
2795     goto out_of_segment;
2796 
2797   /* if the buffer is only partially in the segment, fix up stamps */
2798   if (clip_start != start || (stop != -1 && clip_stop != stop)) {
2799     GST_DEBUG_OBJECT (render, "clipping buffer timestamp/duration to segment");
2800     buffer = gst_buffer_make_writable (buffer);
2801     GST_BUFFER_TIMESTAMP (buffer) = clip_start;
2802     if (stop != -1)
2803       GST_BUFFER_DURATION (buffer) = clip_stop - clip_start;
2804   }
2805 
2806   /* now, after we've done the clipping, fix up end time if there's no
2807    * duration (we only use those estimated values internally though, we
2808    * don't want to set bogus values on the buffer itself) */
2809   if (stop == -1) {
2810     if (render->info.fps_n && render->info.fps_d) {
2811       GST_DEBUG_OBJECT (render, "estimating duration based on framerate");
2812       stop = start + gst_util_uint64_scale_int (GST_SECOND,
2813           render->info.fps_d, render->info.fps_n);
2814     } else {
2815       GST_LOG_OBJECT (render, "no duration, assuming minimal duration");
2816       stop = start + 1;         /* we need to assume some interval */
2817     }
2818   }
2819 
2820   gst_object_sync_values (GST_OBJECT (render), GST_BUFFER_TIMESTAMP (buffer));
2821 
2822 wait_for_text_buf:
2823 
2824   GST_TTML_RENDER_LOCK (render);
2825 
2826   if (render->video_flushing)
2827     goto flushing;
2828 
2829   if (render->video_eos)
2830     goto have_eos;
2831 
2832   /* Text pad not linked; push input video frame */
2833   if (!render->text_linked) {
2834     GST_LOG_OBJECT (render, "Text pad not linked");
2835     GST_TTML_RENDER_UNLOCK (render);
2836     ret = gst_pad_push (render->srcpad, buffer);
2837     goto not_linked;
2838   }
2839 
2840   /* Text pad linked, check if we have a text buffer queued */
2841   if (render->text_buffer) {
2842     gboolean pop_text = FALSE, valid_text_time = TRUE;
2843     GstClockTime text_start = GST_CLOCK_TIME_NONE;
2844     GstClockTime text_end = GST_CLOCK_TIME_NONE;
2845     GstClockTime text_running_time = GST_CLOCK_TIME_NONE;
2846     GstClockTime text_running_time_end = GST_CLOCK_TIME_NONE;
2847     GstClockTime vid_running_time, vid_running_time_end;
2848 
2849     /* if the text buffer isn't stamped right, pop it off the
2850      * queue and display it for the current video frame only */
2851     if (!GST_BUFFER_TIMESTAMP_IS_VALID (render->text_buffer) ||
2852         !GST_BUFFER_DURATION_IS_VALID (render->text_buffer)) {
2853       GST_WARNING_OBJECT (render,
2854           "Got text buffer with invalid timestamp or duration");
2855       pop_text = TRUE;
2856       valid_text_time = FALSE;
2857     } else {
2858       text_start = GST_BUFFER_TIMESTAMP (render->text_buffer);
2859       text_end = text_start + GST_BUFFER_DURATION (render->text_buffer);
2860     }
2861 
2862     vid_running_time =
2863         gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME, start);
2864     vid_running_time_end =
2865         gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME, stop);
2866 
2867     /* If timestamp and duration are valid */
2868     if (valid_text_time) {
2869       text_running_time =
2870           gst_segment_to_running_time (&render->text_segment,
2871           GST_FORMAT_TIME, text_start);
2872       text_running_time_end =
2873           gst_segment_to_running_time (&render->text_segment,
2874           GST_FORMAT_TIME, text_end);
2875     }
2876 
2877     GST_LOG_OBJECT (render, "T: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2878         GST_TIME_ARGS (text_running_time),
2879         GST_TIME_ARGS (text_running_time_end));
2880     GST_LOG_OBJECT (render, "V: %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT,
2881         GST_TIME_ARGS (vid_running_time), GST_TIME_ARGS (vid_running_time_end));
2882 
2883     /* Text too old or in the future */
2884     if (valid_text_time && text_running_time_end <= vid_running_time) {
2885       /* text buffer too old, get rid of it and do nothing  */
2886       GST_LOG_OBJECT (render, "text buffer too old, popping");
2887       pop_text = FALSE;
2888       gst_ttml_render_pop_text (render);
2889       GST_TTML_RENDER_UNLOCK (render);
2890       goto wait_for_text_buf;
2891     } else if (valid_text_time && vid_running_time_end <= text_running_time) {
2892       GST_LOG_OBJECT (render, "text in future, pushing video buf");
2893       GST_TTML_RENDER_UNLOCK (render);
2894       /* Push the video frame */
2895       ret = gst_pad_push (render->srcpad, buffer);
2896     } else {
2897       if (render->need_render) {
2898         GstSubtitleRegion *region = NULL;
2899         GstSubtitleMeta *subtitle_meta = NULL;
2900         guint i;
2901 
2902         if (render->compositions) {
2903           g_list_free_full (render->compositions,
2904               (GDestroyNotify) gst_video_overlay_composition_unref);
2905           render->compositions = NULL;
2906         }
2907 
2908         subtitle_meta = gst_buffer_get_subtitle_meta (render->text_buffer);
2909         if (!subtitle_meta) {
2910           GST_CAT_WARNING (ttmlrender_debug, "Failed to get subtitle meta.");
2911         } else {
2912           for (i = 0; i < subtitle_meta->regions->len; ++i) {
2913             GstVideoOverlayComposition *composition;
2914             region = g_ptr_array_index (subtitle_meta->regions, i);
2915             composition = gst_ttml_render_render_text_region (render, region,
2916                 render->text_buffer);
2917             if (composition) {
2918               render->compositions = g_list_append (render->compositions,
2919                   composition);
2920             }
2921           }
2922         }
2923         render->need_render = FALSE;
2924       }
2925 
2926       GST_TTML_RENDER_UNLOCK (render);
2927       ret = gst_ttml_render_push_frame (render, buffer);
2928 
2929       if (valid_text_time && text_running_time_end <= vid_running_time_end) {
2930         GST_LOG_OBJECT (render, "text buffer not needed any longer");
2931         pop_text = TRUE;
2932       }
2933     }
2934     if (pop_text) {
2935       GST_TTML_RENDER_LOCK (render);
2936       gst_ttml_render_pop_text (render);
2937       GST_TTML_RENDER_UNLOCK (render);
2938     }
2939   } else {
2940     gboolean wait_for_text_buf = TRUE;
2941 
2942     if (render->text_eos)
2943       wait_for_text_buf = FALSE;
2944 
2945     if (!render->wait_text)
2946       wait_for_text_buf = FALSE;
2947 
2948     /* Text pad linked, but no text buffer available - what now? */
2949     if (render->text_segment.format == GST_FORMAT_TIME) {
2950       GstClockTime text_start_running_time, text_position_running_time;
2951       GstClockTime vid_running_time;
2952 
2953       vid_running_time =
2954           gst_segment_to_running_time (&render->segment, GST_FORMAT_TIME,
2955           GST_BUFFER_TIMESTAMP (buffer));
2956       text_start_running_time =
2957           gst_segment_to_running_time (&render->text_segment,
2958           GST_FORMAT_TIME, render->text_segment.start);
2959       text_position_running_time =
2960           gst_segment_to_running_time (&render->text_segment,
2961           GST_FORMAT_TIME, render->text_segment.position);
2962 
2963       if ((GST_CLOCK_TIME_IS_VALID (text_start_running_time) &&
2964               vid_running_time < text_start_running_time) ||
2965           (GST_CLOCK_TIME_IS_VALID (text_position_running_time) &&
2966               vid_running_time < text_position_running_time)) {
2967         wait_for_text_buf = FALSE;
2968       }
2969     }
2970 
2971     if (wait_for_text_buf) {
2972       GST_DEBUG_OBJECT (render, "no text buffer, need to wait for one");
2973       GST_TTML_RENDER_WAIT (render);
2974       GST_DEBUG_OBJECT (render, "resuming");
2975       GST_TTML_RENDER_UNLOCK (render);
2976       goto wait_for_text_buf;
2977     } else {
2978       GST_TTML_RENDER_UNLOCK (render);
2979       GST_LOG_OBJECT (render, "no need to wait for a text buffer");
2980       ret = gst_pad_push (render->srcpad, buffer);
2981     }
2982   }
2983 
2984 not_linked:
2985   g_free (text);
2986 
2987   /* Update position */
2988   render->segment.position = clip_start;
2989 
2990   return ret;
2991 
2992 missing_timestamp:
2993   {
2994     GST_WARNING_OBJECT (render, "buffer without timestamp, discarding");
2995     gst_buffer_unref (buffer);
2996     return GST_FLOW_OK;
2997   }
2998 
2999 flushing:
3000   {
3001     GST_TTML_RENDER_UNLOCK (render);
3002     GST_DEBUG_OBJECT (render, "flushing, discarding buffer");
3003     gst_buffer_unref (buffer);
3004     return GST_FLOW_FLUSHING;
3005   }
3006 have_eos:
3007   {
3008     GST_TTML_RENDER_UNLOCK (render);
3009     GST_DEBUG_OBJECT (render, "eos, discarding buffer");
3010     gst_buffer_unref (buffer);
3011     return GST_FLOW_EOS;
3012   }
3013 out_of_segment:
3014   {
3015     GST_DEBUG_OBJECT (render, "buffer out of segment, discarding");
3016     gst_buffer_unref (buffer);
3017     return GST_FLOW_OK;
3018   }
3019 }
3020 
3021 static GstStateChangeReturn
gst_ttml_render_change_state(GstElement * element,GstStateChange transition)3022 gst_ttml_render_change_state (GstElement * element, GstStateChange transition)
3023 {
3024   GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
3025   GstTtmlRender *render = GST_TTML_RENDER (element);
3026 
3027   switch (transition) {
3028     case GST_STATE_CHANGE_PAUSED_TO_READY:
3029       GST_TTML_RENDER_LOCK (render);
3030       render->text_flushing = TRUE;
3031       render->video_flushing = TRUE;
3032       /* pop_text will broadcast on the GCond and thus also make the video
3033        * chain exit if it's waiting for a text buffer */
3034       gst_ttml_render_pop_text (render);
3035       GST_TTML_RENDER_UNLOCK (render);
3036       break;
3037     default:
3038       break;
3039   }
3040 
3041   ret = parent_class->change_state (element, transition);
3042   if (ret == GST_STATE_CHANGE_FAILURE)
3043     return ret;
3044 
3045   switch (transition) {
3046     case GST_STATE_CHANGE_READY_TO_PAUSED:
3047       GST_TTML_RENDER_LOCK (render);
3048       render->text_flushing = FALSE;
3049       render->video_flushing = FALSE;
3050       render->video_eos = FALSE;
3051       render->text_eos = FALSE;
3052       gst_segment_init (&render->segment, GST_FORMAT_TIME);
3053       gst_segment_init (&render->text_segment, GST_FORMAT_TIME);
3054       GST_TTML_RENDER_UNLOCK (render);
3055       break;
3056     default:
3057       break;
3058   }
3059 
3060   return ret;
3061 }
3062