diff --git a/ext/ttml/gstttmlrender.c b/ext/ttml/gstttmlrender.c index 96e31fb49e..ba18a1b8e8 100644 --- a/ext/ttml/gstttmlrender.c +++ b/ext/ttml/gstttmlrender.c @@ -101,6 +101,13 @@ typedef enum } GstTtmlDirection; +typedef struct +{ + guint line_height; + guint baseline_offset; +} BlockMetrics; + + typedef struct { guint height; @@ -156,9 +163,13 @@ static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_copy (GstTtmlRenderRenderedImage * image); static void gst_ttml_render_rendered_image_free (GstTtmlRenderRenderedImage * image); +static GstTtmlRenderRenderedImage *gst_ttml_render_rendered_image_combine + (GstTtmlRenderRenderedImage * image1, GstTtmlRenderRenderedImage * image2); static GstTtmlRenderRenderedImage *gst_ttml_render_stitch_images (GPtrArray * images, GstTtmlDirection direction); +static gboolean gst_ttml_render_color_is_transparent (GstSubtitleColor * color); + GType gst_ttml_render_get_type (void) { @@ -1272,11 +1283,35 @@ typedef struct static void gst_ttml_render_unified_element_free (UnifiedElement * unified_element) { + if (!unified_element) + return; + + gst_subtitle_element_unref (unified_element->element); g_free (unified_element->text); g_slice_free (UnifiedElement, unified_element); } +static UnifiedElement * +gst_ttml_render_unified_element_copy (const UnifiedElement * unified_element) +{ + UnifiedElement *ret; + + if (!unified_element) + return NULL; + + ret = g_slice_new0 (UnifiedElement); + ret->element = gst_subtitle_element_ref (unified_element->element); + ret->pango_font_size = unified_element->pango_font_size; + ret->pango_font_metrics.height = unified_element->pango_font_metrics.height; + ret->pango_font_metrics.baseline = + unified_element->pango_font_metrics.baseline; + ret->text = g_strdup (unified_element->text); + + return ret; +} + + typedef struct { GPtrArray *unified_elements; @@ -1309,39 +1344,35 @@ gst_ttml_render_unified_block_get_element (const UnifiedBlock * block, } -static void -gst_ttml_render_handle_whitespace (UnifiedBlock * block) +static UnifiedBlock * +gst_ttml_render_unified_block_copy (const UnifiedBlock * block) { - UnifiedElement *last = NULL; - UnifiedElement *cur = gst_ttml_render_unified_block_get_element (block, 0); - UnifiedElement *next = gst_ttml_render_unified_block_get_element (block, 1); - guint i; + UnifiedBlock *ret; + gint i; - for (i = 2; cur; ++i) { - if (cur->element->suppress_whitespace) { - if (!last || (g_strcmp0 (last->text, "\n") == 0)) { - /* Strip leading whitespace. */ - if (cur->text[0] == 0x20) { - gchar *tmp = cur->text; - GST_CAT_LOG (ttmlrender_debug, "Stripping leading whitespace."); - cur->text = g_strdup (cur->text + 1); - g_free (tmp); - } - } - if (!next || (g_strcmp0 (next->text, "\n") == 0)) { - /* Strip trailing whitespace. */ - if (cur->text[strlen (cur->text) - 1] == 0x20) { - gchar *tmp = cur->text; - GST_CAT_LOG (ttmlrender_debug, "Stripping trailing whitespace."); - cur->text = g_strndup (cur->text, strlen (cur->text) - 1); - g_free (tmp); - } - } - } - last = cur; - cur = next; - next = gst_ttml_render_unified_block_get_element (block, i); + if (!block) + return NULL; + + ret = g_slice_new0 (UnifiedBlock); + ret->joined_text = g_strdup (block->joined_text); + ret->style_set = gst_subtitle_style_set_ref (block->style_set); + ret->unified_elements = g_ptr_array_new_with_free_func ((GDestroyNotify) + gst_ttml_render_unified_element_free); + + for (i = 0; i < block->unified_elements->len; ++i) { + UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i); + UnifiedElement *ue_copy = gst_ttml_render_unified_element_copy (ue); + g_ptr_array_add (ret->unified_elements, ue_copy); } + + return ret; +} + + +static guint +gst_ttml_render_unified_block_element_count (const UnifiedBlock * block) +{ + return block->unified_elements->len; } @@ -1475,132 +1506,432 @@ gst_ttml_render_unify_block (GstTtmlRender * render, } -/* From the elements within @block, generate a string of the subtitle text - * marked-up using pango-markup. Also, store the ranges of characters belonging - * to the text of each element in @char_ranges. */ -static gchar * -gst_ttml_render_generate_marked_up_string (GstTtmlRender * render, - const GstSubtitleBlock * block, GstBuffer * text_buf, - GPtrArray * char_ranges) +/* + * Returns index of nearest breakpoint before @index in @block's text. If no + * breakpoints are found, returns -1. + */ +static gint +gst_ttml_render_get_nearest_breakpoint (const UnifiedBlock * block, guint index) { - gchar *escaped_text, *joined_text, *old_text, *font_family, *font_size, - *fgcolor; - const gchar *font_style, *font_weight, *underline; - guint total_text_length = 0U; - guint element_count = gst_subtitle_block_get_element_count (block); - UnifiedBlock *unified_block; + const gchar *end = block->joined_text + index - 1; + + while ((end = g_utf8_find_prev_char (block->joined_text, end))) { + gchar buf[6] = { 0 }; + gunichar u = g_utf8_get_char (end); + gint nbytes = g_unichar_to_utf8 (u, buf); + + if (nbytes == 1 && (buf[0] == 0x20 || buf[0] == 0x9 || buf[0] == 0xD)) + return end - block->joined_text; + } + + return -1; +} + + +/* Return the pango markup representation of all the elements in @block. */ +static gchar * +gst_ttml_render_generate_block_markup (const UnifiedBlock * block) +{ + gchar *joined_text, *old_text; + guint element_count = gst_ttml_render_unified_block_element_count (block); guint i; joined_text = g_strdup (""); - unified_block = gst_ttml_render_unify_block (render, block, text_buf); - gst_ttml_render_handle_whitespace (unified_block); for (i = 0; i < element_count; ++i) { - CharRange *range = g_slice_new0 (CharRange); - UnifiedElement *unified_element = - gst_ttml_render_unified_block_get_element (unified_block, i); - - escaped_text = g_markup_escape_text (unified_element->text, -1); - GST_CAT_DEBUG (ttmlrender_debug, "Escaped text is: \"%s\"", escaped_text); - range->first_index = total_text_length; - - fgcolor = - gst_ttml_render_color_to_string (unified_element->element-> - style_set->color); - font_size = - g_strdup_printf ("%u", - (guint) (round (unified_element->element->style_set->font_size * - render->height))); - font_family = - gst_ttml_render_resolve_generic_fontname (unified_element-> - element->style_set->font_family); - if (!font_family) - font_family = g_strdup (unified_element->element->style_set->font_family); - font_style = - (unified_element->element->style_set->font_style == - GST_SUBTITLE_FONT_STYLE_NORMAL) ? "normal" : "italic"; - font_weight = - (unified_element->element->style_set->font_weight == - GST_SUBTITLE_FONT_WEIGHT_NORMAL) ? "normal" : "bold"; - underline = - (unified_element->element->style_set->text_decoration == - GST_SUBTITLE_TEXT_DECORATION_UNDERLINE) ? "single" : "none"; + UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i); + gchar *element_markup = + gst_ttml_render_generate_pango_markup (ue->element->style_set, + ue->pango_font_size, ue->text); old_text = joined_text; - joined_text = g_strconcat (joined_text, - "", escaped_text, "", NULL); + joined_text = g_strconcat (joined_text, element_markup, NULL); GST_CAT_DEBUG (ttmlrender_debug, "Joined text is now: %s", joined_text); - total_text_length += strlen (unified_element->text); - range->last_index = total_text_length - 1; - GST_CAT_DEBUG (ttmlrender_debug, - "First character index: %u; last character " "index: %u", - range->first_index, range->last_index); - g_ptr_array_insert (char_ranges, i, range); - + g_free (element_markup); g_free (old_text); - g_free (escaped_text); - g_free (fgcolor); - g_free (font_family); - g_free (font_size); } - gst_ttml_render_unified_block_free (unified_block); return joined_text; } -/* Render the text in a pango-markup string. */ -static GstTtmlRenderRenderedText * -gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text, - guint max_width, PangoAlignment alignment, guint line_height, - guint max_font_size, gboolean wrap) +/* + * Returns a set of character ranges, which correspond to the ranges of + * characters from @block that should be rendered on each generated line area. + * Essentially, this function determines line breaking and wrapping. + */ +static GPtrArray * +gst_ttml_render_get_line_char_ranges (GstTtmlRender * render, + const UnifiedBlock * block, guint width, gboolean wrap) { - GstTtmlRenderRenderedText *ret; + gint start_index = 0; + GPtrArray *line_ranges = g_ptr_array_new_with_free_func ((GDestroyNotify) + gst_ttml_render_char_range_free); + PangoRectangle ink_rect; + gchar *markup; + gint i; + + /* Handle hard breaks in block text. */ + while (start_index < strlen (block->joined_text)) { + CharRange *range = g_slice_new0 (CharRange); + gchar *c = block->joined_text + start_index; + while (*c != '\0' && *c != '\n') + ++c; + range->first_index = start_index; + range->last_index = (c - block->joined_text) - 1; + g_ptr_array_add (line_ranges, range); + start_index = range->last_index + 2; + } + + if (!wrap) + return line_ranges; + + GST_CAT_LOG (ttmlrender_debug, + "After handling breaks, we have the following ranges:"); + for (i = 0; i < line_ranges->len; ++i) { + CharRange *range = g_ptr_array_index (line_ranges, i); + GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u last:%u", i, + range->first_index, range->last_index); + } + + markup = gst_ttml_render_generate_block_markup (block); + pango_layout_set_markup (render->layout, markup, strlen (markup)); + pango_layout_set_width (render->layout, -1); + + pango_layout_get_pixel_extents (render->layout, &ink_rect, NULL); + GST_CAT_LOG (ttmlrender_debug, "Layout extents - x:%d y:%d w:%d h:%d", + ink_rect.x, ink_rect.y, ink_rect.width, ink_rect.height); + + /* For each range, wrap if it extends beyond allowed width. */ + for (i = 0; i < line_ranges->len; ++i) { + CharRange *range, *new_range; + gint max_line_extent; + gint end_index = 0; + gint trailing; + PangoRectangle rect; + gboolean within_line; + + do { + range = g_ptr_array_index (line_ranges, i); + GST_CAT_LOG (ttmlrender_debug, + "Seeing if we need to wrap range[%d] - start:%u end:%u", i, + range->first_index, range->last_index); + + pango_layout_index_to_pos (render->layout, range->first_index, &rect); + GST_CAT_LOG (ttmlrender_debug, "First char at x:%d y:%d", rect.x, + rect.y); + + max_line_extent = rect.x + (PANGO_SCALE * width); + GST_CAT_LOG (ttmlrender_debug, "max_line_extent: %d", + PANGO_PIXELS (max_line_extent)); + + within_line = + pango_layout_xy_to_index (render->layout, max_line_extent, rect.y, + &end_index, &trailing); + + GST_CAT_LOG (ttmlrender_debug, "Index nearest to breakpoint: %d", + end_index); + + if (within_line) { + end_index = gst_ttml_render_get_nearest_breakpoint (block, end_index); + + if (end_index > range->first_index) { + new_range = g_slice_new0 (CharRange); + new_range->first_index = end_index + 1; + new_range->last_index = range->last_index; + GST_CAT_LOG (ttmlrender_debug, + "Wrapping line %d; added new range - start:%u end:%u", i, + new_range->first_index, new_range->last_index); + + range->last_index = end_index; + GST_CAT_LOG (ttmlrender_debug, + "Modified last_index of existing range; range is now start:%u " + "end:%u", range->first_index, range->last_index); + + g_ptr_array_insert (line_ranges, ++i, new_range); + } else { + GST_CAT_DEBUG (ttmlrender_debug, + "Couldn't find a suitable breakpoint"); + within_line = FALSE; + } + } + } while (within_line); + } + + g_free (markup); + return line_ranges; +} + + +/* + * Returns the index of the element in @block containing the character at index + * @char_index in @block's text. If @offset is not NULL, sets it to the + * character offset of @char_index within the element where it is found. + */ +static gint +gst_ttml_render_get_element_index (const UnifiedBlock * block, + const gint char_index, gint * offset) +{ + gint count = 0; + gint i; + + if ((char_index < 0) || (char_index >= strlen (block->joined_text))) + return -1; + + for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) { + UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i); + if ((char_index >= count) && (char_index < (count + strlen (ue->text)))) { + if (offset) + *offset = char_index - count; + break; + } + count += strlen (ue->text); + } + + return i; +} + + +static guint +gst_ttml_render_strip_leading_spaces (gchar ** string) +{ + gchar *c = *string; + + while (c) { + gchar buf[6] = { 0 }; + gunichar u = g_utf8_get_char (c); + gint nbytes = g_unichar_to_utf8 (u, buf); + + if ((nbytes == 1) && (buf[0] == 0x20)) + c = g_utf8_find_next_char (c, c + strlen (*string)); + else + break; + } + + if (!c) { + GST_CAT_DEBUG (ttmlrender_debug, + "All characters would be removed from string."); + return 0; + } else if (c > *string) { + gchar *tmp = *string; + *string = g_strdup (c); + GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp, + *string); + g_free (tmp); + } + + return strlen (*string); +} + + +static guint +gst_ttml_render_strip_trailing_spaces (gchar ** string) +{ + gchar *c = *string + strlen (*string) - 1; + gint nbytes; + + while (c) { + gchar buf[6] = { 0 }; + gunichar u = g_utf8_get_char (c); + nbytes = g_unichar_to_utf8 (u, buf); + + if ((nbytes == 1) && (buf[0] == 0x20)) + c = g_utf8_find_prev_char (*string, c); + else + break; + } + + if (!c) { + GST_CAT_DEBUG (ttmlrender_debug, + "All characters would be removed from string."); + return 0; + } else { + gchar *tmp = *string; + *string = g_strndup (*string, (c - *string) + nbytes); + GST_CAT_DEBUG (ttmlrender_debug, "Replacing text \"%s\" with \"%s\"", tmp, + *string); + g_free (tmp); + } + + return strlen (*string); +} + + +/* + * Treating each block in @blocks as a separate line area, conditionally strips + * space characters from the beginning and end of each line. This function + * implements the suppress-at-line-break="auto" and + * white-space-treatment="ignore-if-surrounding-linefeed" behaviours (specified + * by TTML section 7.2.3) for elements at the start and end of lines that have + * xml:space="default" applied to them. If stripping whitespace from a block + * removes all elements of that block, the block will be removed from @blocks. + * Returns the number of remaining blocks. + */ +static guint +gst_ttml_render_handle_whitespace (GPtrArray * blocks) +{ + gint i; + + for (i = 0; i < blocks->len; ++i) { + UnifiedBlock *ub = g_ptr_array_index (blocks, i); + UnifiedElement *ue; + guint remaining_chars = 0; + + /* Remove leading spaces from line area. */ + while ((gst_ttml_render_unified_block_element_count (ub) > 0) + && (remaining_chars == 0)) { + ue = gst_ttml_render_unified_block_get_element (ub, 0); + if (!ue->element->suppress_whitespace) + break; + remaining_chars = gst_ttml_render_strip_leading_spaces (&ue->text); + + if (remaining_chars == 0) { + g_ptr_array_remove_index (ub->unified_elements, 0); + GST_CAT_DEBUG (ttmlrender_debug, "Removed first element from block"); + } + } + + remaining_chars = 0; + + /* Remove trailing spaces from line area. */ + while ((gst_ttml_render_unified_block_element_count (ub) > 0) + && (remaining_chars == 0)) { + ue = gst_ttml_render_unified_block_get_element (ub, + gst_ttml_render_unified_block_element_count (ub) - 1); + if (!ue->element->suppress_whitespace) + break; + remaining_chars = gst_ttml_render_strip_trailing_spaces (&ue->text); + + if (remaining_chars == 0) { + g_ptr_array_remove_index (ub->unified_elements, + gst_ttml_render_unified_block_element_count (ub) - 1); + GST_CAT_DEBUG (ttmlrender_debug, "Removed last element from block"); + } + } + + if (gst_ttml_render_unified_block_element_count (ub) == 0) + g_ptr_array_remove_index (blocks, i--); + } + + return blocks->len; +} + + +/* + * Splits a single UnifiedBlock, @block, into an array of separate + * UnifiedBlocks, according to the character ranges given in @char_ranges. + * Each resulting UnifiedBlock will contain only the elements to which belong + * the characters in its corresponding character range; the text of the first + * and last element in the block will be clipped of any characters before and + * after, respectively, the first and last characters in the corresponding + * range. + */ +static GPtrArray * +gst_ttml_render_split_block (UnifiedBlock * block, GPtrArray * char_ranges) +{ + GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify) + gst_ttml_render_unified_block_free); + gint i; + + for (i = 0; i < char_ranges->len; ++i) { + gint index, first_offset, last_offset; + CharRange *range = g_ptr_array_index (char_ranges, i); + UnifiedBlock *clone = gst_ttml_render_unified_block_copy (block); + UnifiedElement *ue; + gchar *tmp; + + GST_CAT_LOG (ttmlrender_debug, "range start:%u end:%u", range->first_index, + range->last_index); + index = + gst_ttml_render_get_element_index (clone, range->last_index, + &last_offset); + GST_CAT_LOG (ttmlrender_debug, "Last char in range is in element %d", + index); + + /* Remove elements that are after the one that contains the range end. */ + GST_CAT_LOG (ttmlrender_debug, "There are %d elements in cloned block.", + gst_ttml_render_unified_block_element_count (clone)); + while (gst_ttml_render_unified_block_element_count (clone) > (index + 1)) { + GST_CAT_LOG (ttmlrender_debug, "Removing last element in cloned block."); + g_ptr_array_remove_index (clone->unified_elements, index + 1); + } + + index = + gst_ttml_render_get_element_index (clone, range->first_index, + &first_offset); + GST_CAT_LOG (ttmlrender_debug, "First char in range is in element %d", + index); + + /* Remove elements that are before the one that contains the range start. */ + while (index > 0) { + GST_CAT_LOG (ttmlrender_debug, "Removing first element in cloned block"); + g_ptr_array_remove_index (clone->unified_elements, 0); + --index; + } + + /* Remove characters from first element that are before the range start. */ + ue = gst_ttml_render_unified_block_get_element (clone, 0); + if (first_offset > 0) { + tmp = ue->text; + ue->text = g_strdup (ue->text + first_offset); + GST_CAT_DEBUG (ttmlrender_debug, + "First element text has been clipped to \"%s\"", ue->text); + g_free (tmp); + + if (gst_ttml_render_unified_block_element_count (clone) == 1) + last_offset -= first_offset; + } + + /* Remove characters from last element that are after the range end. */ + ue = gst_ttml_render_unified_block_get_element (clone, + gst_ttml_render_unified_block_element_count (clone) - 1); + if (last_offset < (strlen (ue->text) - 1)) { + tmp = ue->text; + ue->text = g_strndup (ue->text, last_offset + 1); + GST_CAT_DEBUG (ttmlrender_debug, + "Last element text has been clipped to \"%s\"", ue->text); + g_free (tmp); + } + + if (gst_ttml_render_unified_block_element_count (clone) > 0) + g_ptr_array_add (ret, clone); + } + + if (ret->len == 0) { + GST_CAT_DEBUG (ttmlrender_debug, "No elements remain in clone."); + g_ptr_array_unref (ret); + ret = NULL; + } + return ret; +} + + +/* Render the text in a pango-markup string. */ +static GstTtmlRenderRenderedImage * +gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text, + guint line_height, guint baseline_offset) +{ + GstTtmlRenderRenderedImage *ret; cairo_surface_t *surface, *cropped_surface; cairo_t *cairo_state, *cropped_state; GstMapInfo map; PangoRectangle logical_rect, ink_rect; - gint spacing = 0; guint buf_width, buf_height; gint stride; - PangoLayoutLine *line; - PangoRectangle line_extents; gint bounding_box_x1, bounding_box_x2, bounding_box_y1, bounding_box_y2; + gint baseline; - ret = g_slice_new0 (GstTtmlRenderRenderedText); - ret->text_image = gst_ttml_render_rendered_image_new_empty (); + ret = gst_ttml_render_rendered_image_new_empty (); pango_layout_set_markup (render->layout, text, strlen (text)); - GST_CAT_DEBUG (ttmlrender_debug, "Layout text: %s", + GST_CAT_DEBUG (ttmlrender_debug, "Layout text: \"%s\"", pango_layout_get_text (render->layout)); - if (wrap) { - pango_layout_set_width (render->layout, max_width * PANGO_SCALE); - pango_layout_set_wrap (render->layout, PANGO_WRAP_WORD_CHAR); - } else { - pango_layout_set_width (render->layout, -1); - } - - pango_layout_set_alignment (render->layout, alignment); - line = pango_layout_get_line_readonly (render->layout, 0); - pango_layout_line_get_pixel_extents (line, NULL, &line_extents); - - GST_CAT_LOG (ttmlrender_debug, "Requested line_height: %u", line_height); - spacing = line_height - line_extents.height; - pango_layout_set_spacing (render->layout, PANGO_SCALE * spacing); - GST_CAT_LOG (ttmlrender_debug, "Line spacing set to %d", - pango_layout_get_spacing (render->layout) / PANGO_SCALE); + pango_layout_set_width (render->layout, -1); pango_layout_get_pixel_extents (render->layout, &ink_rect, &logical_rect); - GST_CAT_DEBUG (ttmlrender_debug, "logical_rect.x: %d logical_rect.y: %d " - "logical_rect.width: %d logical_rect.height: %d", logical_rect.x, - logical_rect.y, logical_rect.width, logical_rect.height); + + baseline = PANGO_PIXELS (pango_layout_get_baseline (render->layout)); bounding_box_x1 = MIN (logical_rect.x, ink_rect.x); bounding_box_x2 = MAX (logical_rect.x + logical_rect.width, @@ -1609,40 +1940,33 @@ gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text, bounding_box_y2 = MAX (logical_rect.y + logical_rect.height, ink_rect.y + ink_rect.height); - surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, bounding_box_x2, - bounding_box_y2); + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + (bounding_box_x2 - bounding_box_x1), (bounding_box_y2 - bounding_box_y1)); cairo_state = cairo_create (surface); cairo_set_operator (cairo_state, CAIRO_OPERATOR_CLEAR); cairo_paint (cairo_state); cairo_set_operator (cairo_state, CAIRO_OPERATOR_OVER); - /* Render layout. */ cairo_save (cairo_state); pango_cairo_show_layout (cairo_state, render->layout); cairo_restore (cairo_state); buf_width = bounding_box_x2 - bounding_box_x1; - buf_height = (bounding_box_y2 - bounding_box_y1) + spacing; + buf_height = ink_rect.height; GST_CAT_DEBUG (ttmlrender_debug, "Output buffer width: %u height: %u", buf_width, buf_height); - /* Depending on whether the text is wrapped and its alignment, the image - * created by rendering a PangoLayout will contain more than just the - * rendered text: it may also contain blankspace around the rendered text. - * The following code crops blankspace from around the rendered text, - * returning only the rendered text itself in a GstBuffer. */ - ret->text_image->image = - gst_buffer_new_allocate (NULL, 4 * buf_width * buf_height, NULL); - gst_buffer_memset (ret->text_image->image, 0, 0U, 4 * buf_width * buf_height); - gst_buffer_map (ret->text_image->image, &map, GST_MAP_READWRITE); + ret->image = gst_buffer_new_allocate (NULL, 4 * buf_width * buf_height, NULL); + gst_buffer_memset (ret->image, 0, 0U, 4 * buf_width * buf_height); + gst_buffer_map (ret->image, &map, GST_MAP_READWRITE); stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, buf_width); cropped_surface = cairo_image_surface_create_for_data (map.data, CAIRO_FORMAT_ARGB32, - buf_width, buf_height, stride); + (bounding_box_x2 - bounding_box_x1), ink_rect.height, stride); cropped_state = cairo_create (cropped_surface); cairo_set_source_surface (cropped_state, surface, -bounding_box_x1, - -(bounding_box_y1 - spacing / 2.0)); + -ink_rect.y); cairo_rectangle (cropped_state, 0, 0, buf_width, buf_height); cairo_fill (cropped_state); @@ -1650,12 +1974,145 @@ gst_ttml_render_draw_text (GstTtmlRender * render, const gchar * text, cairo_surface_destroy (surface); cairo_destroy (cropped_state); cairo_surface_destroy (cropped_surface); - gst_buffer_unmap (ret->text_image->image, &map); + gst_buffer_unmap (ret->image, &map); - ret->text_image->width = buf_width; - ret->text_image->height = buf_height; - ret->horiz_offset = bounding_box_x1; + ret->width = buf_width; + ret->height = buf_height; + ret->x = 0; + ret->y = MAX (0, baseline_offset - (baseline - ink_rect.y)); + return ret; +} + +static GstTtmlRenderRenderedImage * +gst_ttml_render_render_block_elements (GstTtmlRender * render, + UnifiedBlock * block, BlockMetrics block_metrics) +{ + GPtrArray *inline_images = g_ptr_array_new_with_free_func ( + (GDestroyNotify) gst_ttml_render_rendered_image_free); + GstTtmlRenderRenderedImage *ret = NULL; + guint line_padding = + (guint) ceil (block->style_set->line_padding * render->width); + gint i; + + for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) { + UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i); + gchar *markup; + GstTtmlRenderRenderedImage *text_image, *bg_image, *combined_image; + guint bg_offset, bg_width, bg_height; + GstBuffer *background; + + markup = gst_ttml_render_generate_pango_markup (ue->element->style_set, + ue->pango_font_size, ue->text); + text_image = gst_ttml_render_draw_text (render, markup, + block_metrics.line_height, block_metrics.baseline_offset); + g_free (markup); + + bg_offset = 0; + bg_height = block_metrics.line_height; + bg_width = text_image->width; + + if (line_padding > 0) { + if (i == 0) { + text_image->x += line_padding; + bg_width += line_padding; + } + if (i == (gst_ttml_render_unified_block_element_count (block) - 1)) + bg_width += line_padding; + } + + background = gst_ttml_render_draw_rectangle (bg_width, bg_height, + ue->element->style_set->background_color); + bg_image = gst_ttml_render_rendered_image_new (background, 0, + bg_offset, bg_width, bg_height); + combined_image = gst_ttml_render_rendered_image_combine (bg_image, + text_image); + gst_ttml_render_rendered_image_free (bg_image); + gst_ttml_render_rendered_image_free (text_image); + g_ptr_array_add (inline_images, combined_image); + } + + ret = gst_ttml_render_stitch_images (inline_images, + GST_TTML_DIRECTION_INLINE); + GST_CAT_DEBUG (ttmlrender_debug, + "Stitched line image - x:%d y:%d w:%u h:%u", + ret->x, ret->y, ret->width, ret->height); + g_ptr_array_unref (inline_images); + return ret; +} + + +/* + * Align the images in @lines according to the multi_row_align and text_align + * settings in @style_set. + */ +static void +gst_ttml_render_align_line_areas (GPtrArray * lines, + const GstSubtitleStyleSet * style_set) +{ + guint longest_line_width = 0; + gint i; + + for (i = 0; i < lines->len; ++i) { + GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i); + if (line->width > longest_line_width) + longest_line_width = line->width; + } + + for (i = 0; i < lines->len; ++i) { + GstTtmlRenderRenderedImage *line = g_ptr_array_index (lines, i); + + switch (style_set->multi_row_align) { + case GST_SUBTITLE_MULTI_ROW_ALIGN_CENTER: + line->x += (gint) round ((longest_line_width - line->width) / 2.0); + break; + case GST_SUBTITLE_MULTI_ROW_ALIGN_END: + line->x += (longest_line_width - line->width); + break; + case GST_SUBTITLE_MULTI_ROW_ALIGN_AUTO: + switch (style_set->text_align) { + case GST_SUBTITLE_TEXT_ALIGN_CENTER: + line->x += (gint) round ((longest_line_width - line->width) / 2.0); + break; + case GST_SUBTITLE_TEXT_ALIGN_END: + case GST_SUBTITLE_TEXT_ALIGN_RIGHT: + line->x += (longest_line_width - line->width); + break; + default: + break; + } + break; + default: + break; + } + } +} + + +/* + * Renders each UnifiedBlock in @blocks, and sets the positions of the + * resulting images according to the line height in @metrics and the alignment + * settings in @style_set. + */ +static GPtrArray * +gst_ttml_render_layout_blocks (GstTtmlRender * render, GPtrArray * blocks, + BlockMetrics metrics, const GstSubtitleStyleSet * style_set) +{ + GPtrArray *ret = g_ptr_array_new_with_free_func ((GDestroyNotify) + gst_ttml_render_rendered_image_free); + gint i; + + for (i = 0; i < blocks->len; ++i) { + UnifiedBlock *block = g_ptr_array_index (blocks, i); + + GstTtmlRenderRenderedImage *line = + gst_ttml_render_render_block_elements (render, block, + metrics); + line->y += (i * metrics.line_height); + g_ptr_array_add (ret, line); + } + + gst_ttml_render_align_line_areas (ret, style_set); return ret; } @@ -1677,21 +2134,76 @@ gst_ttml_render_elements_are_wrapped (GPtrArray * elements) } -/* Return the maximum font size used in an array of elements. */ -static gdouble -gst_ttml_render_get_max_font_size (GPtrArray * elements) +/* + * Return the descender (in pixels) shared by the greatest number of glyphs in + * @block. + */ +static guint +gst_ttml_render_get_most_frequent_descender (GstTtmlRender * render, + UnifiedBlock * block) { - GstSubtitleElement *element; - guint i; - gdouble max_size = 0.0; + GHashTable *count_table = g_hash_table_new (g_direct_hash, g_direct_equal); + GHashTableIter iter; + gpointer key, value; + guint max_count = 0; + guint ret = 0; + gint i; - for (i = 0; i < elements->len; ++i) { - element = g_ptr_array_index (elements, i); - if (element->style_set->font_size > max_size) - max_size = element->style_set->font_size; + for (i = 0; i < gst_ttml_render_unified_block_element_count (block); ++i) { + UnifiedElement *ue = gst_ttml_render_unified_block_get_element (block, i); + guint descender = + ue->pango_font_metrics.height - ue->pango_font_metrics.baseline; + guint count; + + if (g_hash_table_contains (count_table, GUINT_TO_POINTER (descender))) { + count = GPOINTER_TO_UINT (g_hash_table_lookup (count_table, + GUINT_TO_POINTER (descender))); + GST_CAT_LOG (ttmlrender_debug, + "Table already contains %u glyphs with descender %u; increasing " + "that count to %ld", count, descender, + count + g_utf8_strlen (ue->text, -1)); + count += g_utf8_strlen (ue->text, -1); + } else { + count = g_utf8_strlen (ue->text, -1); + GST_CAT_LOG (ttmlrender_debug, + "No glyphs with descender %u; adding entry to table with count of %u", + descender, count); + } + + g_hash_table_insert (count_table, + GUINT_TO_POINTER (descender), GUINT_TO_POINTER (count)); } - return max_size; + g_hash_table_iter_init (&iter, count_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + guint descender = GPOINTER_TO_UINT (key); + guint count = GPOINTER_TO_UINT (value); + + if (count > max_count) { + max_count = count; + ret = descender; + } + } + + g_hash_table_unref (count_table); + return ret; +} + + +static BlockMetrics +gst_ttml_render_get_block_metrics (GstTtmlRender * render, UnifiedBlock * block) +{ + BlockMetrics ret; + guint descender = gst_ttml_render_get_most_frequent_descender (render, block); + guint font_size = (guint) ceil (block->style_set->font_size * render->height); + + ret.line_height = (guint) ceil (font_size * block->style_set->line_height); + ret.baseline_offset = (guint) ((font_size + ret.line_height) / 2.0) + - descender; + GST_CAT_DEBUG (ttmlrender_debug, + "Got most frequent descender value of %u pixels.", descender); + + return ret; } @@ -1756,6 +2268,8 @@ gst_ttml_render_rendered_image_combine (GstTtmlRenderRenderedImage * image1, cairo_surface_t *sfc1, *sfc2, *sfc_dest; cairo_t *state_dest; + if (!image1 && !image2) + return NULL; if (image1 && !image2) return gst_ttml_render_rendered_image_copy (image1); if (image2 && !image1) @@ -1899,160 +2413,6 @@ gst_ttml_render_color_is_transparent (GstSubtitleColor * color) } -/* Render the background rectangles to be placed behind each element. */ -static GstTtmlRenderRenderedImage * -gst_ttml_render_render_element_backgrounds (const GstSubtitleBlock * block, - GPtrArray * char_ranges, PangoLayout * layout, guint origin_x, - guint origin_y, guint line_height, guint line_padding, guint horiz_offset) -{ - gint first_line, last_line, cur_line; - guint padding; - PangoLayoutLine *line; - PangoRectangle first_char_pos, last_char_pos, line_extents; - CharRange *range; - const GstSubtitleElement *element; - guint rect_width; - GstBuffer *rectangle; - guint first_char_start, last_char_end; - guint i; - GstTtmlRenderRenderedImage *ret = NULL; - - for (i = 0; i < char_ranges->len; ++i) { - range = g_ptr_array_index (char_ranges, i); - element = gst_subtitle_block_get_element (block, i); - - GST_CAT_LOG (ttmlrender_debug, "First char index: %u Last char index: %u", - range->first_index, range->last_index); - pango_layout_index_to_pos (layout, range->first_index, &first_char_pos); - pango_layout_index_to_pos (layout, range->last_index, &last_char_pos); - pango_layout_index_to_line_x (layout, range->first_index, 1, - &first_line, NULL); - pango_layout_index_to_line_x (layout, range->last_index, 0, - &last_line, NULL); - - first_char_start = PANGO_PIXELS (first_char_pos.x) - horiz_offset; - last_char_end = PANGO_PIXELS (last_char_pos.x + last_char_pos.width) - - horiz_offset; - - GST_CAT_LOG (ttmlrender_debug, "First char start: %u Last char end: %u", - first_char_start, last_char_end); - GST_CAT_LOG (ttmlrender_debug, "First line: %u Last line: %u", first_line, - last_line); - - for (cur_line = first_line; cur_line <= last_line; ++cur_line) { - guint line_start, line_end; - guint area_start, area_end; - gint first_char_index; - PangoRectangle line_pos; - padding = 0; - - line = pango_layout_get_line (layout, cur_line); - pango_layout_line_get_pixel_extents (line, NULL, &line_extents); - - pango_layout_line_x_to_index (line, 0, &first_char_index, NULL); - pango_layout_index_to_pos (layout, first_char_index, &line_pos); - GST_CAT_LOG (ttmlrender_debug, "First char index:%d position_X:%d " - "position_Y:%d", first_char_index, PANGO_PIXELS (line_pos.x), - PANGO_PIXELS (line_pos.y)); - - line_start = PANGO_PIXELS (line_pos.x) - horiz_offset; - line_end = (PANGO_PIXELS (line_pos.x) + line_extents.width) - - horiz_offset; - - GST_CAT_LOG (ttmlrender_debug, "line_extents.x:%d line_extents.y:%d " - "line_extents.width:%d line_extents.height:%d", line_extents.x, - line_extents.y, line_extents.width, line_extents.height); - GST_CAT_LOG (ttmlrender_debug, "cur_line:%u line start:%u line end:%u " - "first_char_start: %u last_char_end: %u", cur_line, line_start, - line_end, first_char_start, last_char_end); - - if ((cur_line == first_line) && (first_char_start != line_start)) { - area_start = first_char_start + line_padding; - GST_CAT_LOG (ttmlrender_debug, - "First line, but there is preceding text in line."); - } else { - GST_CAT_LOG (ttmlrender_debug, - "Area contains first text on the line; adding padding..."); - ++padding; - area_start = line_start; - } - - if ((cur_line == last_line) && (last_char_end != line_end)) { - GST_CAT_LOG (ttmlrender_debug, - "Last line, but there is following text in line."); - area_end = last_char_end + line_padding; - } else { - GST_CAT_LOG (ttmlrender_debug, - "Area contains last text on the line; adding padding..."); - ++padding; - area_end = line_end + (2 * line_padding); - } - - rect_width = (area_end - area_start); - - if (rect_width > 0) { /*
s will result in zero-width rectangle */ - GstTtmlRenderRenderedImage *image, *tmp; - rectangle = gst_ttml_render_draw_rectangle (rect_width, line_height, - element->style_set->background_color); - image = gst_ttml_render_rendered_image_new (rectangle, - origin_x + area_start, - origin_y + (cur_line * line_height), rect_width, line_height); - tmp = ret; - ret = gst_ttml_render_rendered_image_combine (ret, image); - if (tmp) - gst_ttml_render_rendered_image_free (tmp); - gst_ttml_render_rendered_image_free (image); - } - } - } - - return ret; -} - - -static PangoAlignment -gst_ttml_render_get_alignment (GstSubtitleStyleSet * style_set) -{ - PangoAlignment align = PANGO_ALIGN_LEFT; - - switch (style_set->multi_row_align) { - case GST_SUBTITLE_MULTI_ROW_ALIGN_START: - align = PANGO_ALIGN_LEFT; - break; - case GST_SUBTITLE_MULTI_ROW_ALIGN_CENTER: - align = PANGO_ALIGN_CENTER; - break; - case GST_SUBTITLE_MULTI_ROW_ALIGN_END: - align = PANGO_ALIGN_RIGHT; - break; - case GST_SUBTITLE_MULTI_ROW_ALIGN_AUTO: - switch (style_set->text_align) { - case GST_SUBTITLE_TEXT_ALIGN_START: - case GST_SUBTITLE_TEXT_ALIGN_LEFT: - align = PANGO_ALIGN_LEFT; - break; - case GST_SUBTITLE_TEXT_ALIGN_CENTER: - align = PANGO_ALIGN_CENTER; - break; - case GST_SUBTITLE_TEXT_ALIGN_END: - case GST_SUBTITLE_TEXT_ALIGN_RIGHT: - align = PANGO_ALIGN_RIGHT; - break; - default: - GST_CAT_ERROR (ttmlrender_debug, "Illegal textAlign value (%d)", - style_set->text_align); - break; - } - break; - default: - GST_CAT_ERROR (ttmlrender_debug, "Illegal multiRowAlign value (%d)", - style_set->multi_row_align); - break; - } - return align; -} - - /* * Overlays a set of rendered images to return a single image. Order is * significant: later entries in @images are rendered on top of earlier @@ -2114,100 +2474,54 @@ gst_ttml_render_stitch_images (GPtrArray * images, GstTtmlDirection direction) } -static void -gst_ttml_render_rendered_text_free (GstTtmlRenderRenderedText * text) -{ - if (text->text_image) - gst_ttml_render_rendered_image_free (text->text_image); - g_slice_free (GstTtmlRenderRenderedText, text); -} - - static GstTtmlRenderRenderedImage * gst_ttml_render_render_text_block (GstTtmlRender * render, const GstSubtitleBlock * block, GstBuffer * text_buf, guint width, gboolean overflow) { - GPtrArray *char_ranges = g_ptr_array_new_with_free_func ((GDestroyNotify) - gst_ttml_render_char_range_free); - gchar *marked_up_string; - PangoAlignment alignment; - guint max_font_size; - guint line_height; + UnifiedBlock *unified_block; + BlockMetrics metrics; + gboolean wrap; guint line_padding; - gint text_offset = 0; - GstTtmlRenderRenderedText *rendered_text; - GstTtmlRenderRenderedImage *backgrounds = NULL; - GstTtmlRenderRenderedImage *ret; + GPtrArray *ranges; + GPtrArray *split_blocks; + GPtrArray *images; + GstTtmlRenderRenderedImage *rendered_block = NULL; + gint i; - /* Join text from elements to form a single marked-up string. */ - marked_up_string = gst_ttml_render_generate_marked_up_string (render, block, - text_buf, char_ranges); + unified_block = gst_ttml_render_unify_block (render, block, text_buf); + metrics = gst_ttml_render_get_block_metrics (render, unified_block); + wrap = gst_ttml_render_elements_are_wrapped (block->elements); - max_font_size = (guint) (gst_ttml_render_get_max_font_size (block->elements) - * render->height); - GST_CAT_DEBUG (ttmlrender_debug, "Max font size: %u", max_font_size); - line_height = (guint) round (block->style_set->line_height * max_font_size); + line_padding = (guint) ceil (block->style_set->line_padding * render->width); + ranges = gst_ttml_render_get_line_char_ranges (render, unified_block, width - + (2 * line_padding), wrap); - line_padding = (guint) (block->style_set->line_padding * render->width); - alignment = gst_ttml_render_get_alignment (block->style_set); - - /* Render text to buffer. */ - rendered_text = gst_ttml_render_draw_text (render, marked_up_string, - (width - (2 * line_padding)), alignment, line_height, max_font_size, - gst_ttml_render_elements_are_wrapped (block->elements)); - - switch (block->style_set->text_align) { - case GST_SUBTITLE_TEXT_ALIGN_START: - case GST_SUBTITLE_TEXT_ALIGN_LEFT: - text_offset = line_padding; - break; - case GST_SUBTITLE_TEXT_ALIGN_CENTER: - text_offset = ((gint) width - rendered_text->text_image->width); - text_offset /= 2; - break; - case GST_SUBTITLE_TEXT_ALIGN_END: - case GST_SUBTITLE_TEXT_ALIGN_RIGHT: - text_offset = (gint) width - - (rendered_text->text_image->width + line_padding); - break; + for (i = 0; i < ranges->len; ++i) { + CharRange *range = g_ptr_array_index (ranges, i); + GST_CAT_LOG (ttmlrender_debug, "ranges[%d] first:%u last:%u", i, + range->first_index, range->last_index); } - rendered_text->text_image->x = text_offset; + split_blocks = gst_ttml_render_split_block (unified_block, ranges); + if (split_blocks) { + guint blocks_remining = gst_ttml_render_handle_whitespace (split_blocks); + GST_CAT_DEBUG (ttmlrender_debug, + "There are %u blocks remaining after whitespace handling.", + blocks_remining); - /* Render background rectangles, if any. */ - backgrounds = gst_ttml_render_render_element_backgrounds (block, char_ranges, - render->layout, text_offset - line_padding, 0, - (guint) round (block->style_set->line_height * max_font_size), - line_padding, rendered_text->horiz_offset); - - /* Render block background, if non-transparent. */ - if (!gst_ttml_render_color_is_transparent (&block->style_set-> - background_color)) { - GstTtmlRenderRenderedImage *block_background; - GstTtmlRenderRenderedImage *tmp = backgrounds; - - GstBuffer *block_bg_image = gst_ttml_render_draw_rectangle (width, - backgrounds->height, block->style_set->background_color); - block_background = gst_ttml_render_rendered_image_new (block_bg_image, 0, - 0, width, backgrounds->height); - backgrounds = gst_ttml_render_rendered_image_combine (block_background, - backgrounds); - gst_ttml_render_rendered_image_free (tmp); - gst_ttml_render_rendered_image_free (block_background); + if (blocks_remining > 0) { + images = gst_ttml_render_layout_blocks (render, split_blocks, metrics, + unified_block->style_set); + rendered_block = gst_ttml_render_overlay_images (images); + g_ptr_array_unref (images); + } + g_ptr_array_unref (split_blocks); } - /* Combine text and background images. */ - ret = gst_ttml_render_rendered_image_combine (backgrounds, - rendered_text->text_image); - gst_ttml_render_rendered_image_free (backgrounds); - gst_ttml_render_rendered_text_free (rendered_text); - - g_free (marked_up_string); - g_ptr_array_unref (char_ranges); - GST_CAT_DEBUG (ttmlrender_debug, "block width: %u block height: %u", - ret->width, ret->height); - return ret; + g_ptr_array_unref (ranges); + gst_ttml_render_unified_block_free (unified_block); + return rendered_block; } @@ -2241,7 +2555,6 @@ gst_ttml_render_render_text_region (GstTtmlRender * render, g_ptr_array_new_with_free_func ( (GDestroyNotify) gst_ttml_render_rendered_image_free); GstTtmlRenderRenderedImage *region_image = NULL; - GstTtmlRenderRenderedImage *blocks_image; GstVideoOverlayComposition *ret = NULL; guint i; @@ -2291,11 +2604,51 @@ gst_ttml_render_render_text_region (GstTtmlRender * render, rendered_block = gst_ttml_render_render_text_block (render, block, text_buf, window_width, TRUE); + if (!rendered_block) + continue; + + GST_CAT_LOG (ttmlrender_debug, "rendered_block - x:%d y:%d w:%u h:%u", + rendered_block->x, rendered_block->y, rendered_block->width, + rendered_block->height); + + switch (block->style_set->text_align) { + case GST_SUBTITLE_TEXT_ALIGN_CENTER: + rendered_block->x + += (gint) round ((window_width - rendered_block->width) / 2.0); + break; + + case GST_SUBTITLE_TEXT_ALIGN_RIGHT: + case GST_SUBTITLE_TEXT_ALIGN_END: + rendered_block->x += (window_width - rendered_block->width); + break; + + default: + break; + } + + if (!gst_ttml_render_color_is_transparent (&block->style_set-> + background_color)) { + /* Draw block background rectangle and render block image over it */ + GstTtmlRenderRenderedImage *tmp = rendered_block; + GstBuffer *block_bg_buf; + GstTtmlRenderRenderedImage *block_bg_image; + + block_bg_buf = gst_ttml_render_draw_rectangle (window_width, + rendered_block->height, block->style_set->background_color); + block_bg_image = gst_ttml_render_rendered_image_new (block_bg_buf, 0, + rendered_block->y, window_width, rendered_block->height); + rendered_block = gst_ttml_render_rendered_image_combine (block_bg_image, + rendered_block); + gst_ttml_render_rendered_image_free (tmp); + gst_ttml_render_rendered_image_free (block_bg_image); + } + + rendered_block->y = 0; g_ptr_array_add (rendered_blocks, rendered_block); } if (rendered_blocks->len > 0) { - GstTtmlRenderRenderedImage *tmp; + GstTtmlRenderRenderedImage *blocks_image, *tmp; blocks_image = gst_ttml_render_stitch_images (rendered_blocks, GST_TTML_DIRECTION_BLOCK); @@ -2325,22 +2678,13 @@ gst_ttml_render_render_text_region (GstTtmlRender * render, } tmp = region_image; - if (region_image || blocks_image) { - region_image = - gst_ttml_render_rendered_image_combine (region_image, blocks_image); - } else { - GST_CAT_DEBUG (ttmlrender_debug, "Nothing to render"); - return NULL; - } - - if (tmp) - gst_ttml_render_rendered_image_free (tmp); + region_image = + gst_ttml_render_rendered_image_combine (region_image, blocks_image); + gst_ttml_render_rendered_image_free (tmp); gst_ttml_render_rendered_image_free (blocks_image); } if (region_image) { - GST_CAT_DEBUG (ttmlrender_debug, "Height of rendered region: %u", - region_image->height); ret = gst_ttml_render_compose_overlay (region_image); gst_ttml_render_rendered_image_free (region_image); } diff --git a/ext/ttml/gstttmlrender.h b/ext/ttml/gstttmlrender.h index 480c7484a9..7550db1fe2 100644 --- a/ext/ttml/gstttmlrender.h +++ b/ext/ttml/gstttmlrender.h @@ -49,7 +49,6 @@ G_BEGIN_DECLS typedef struct _GstTtmlRender GstTtmlRender; typedef struct _GstTtmlRenderClass GstTtmlRenderClass; typedef struct _GstTtmlRenderRenderedImage GstTtmlRenderRenderedImage; -typedef struct _GstTtmlRenderRenderedText GstTtmlRenderRenderedText; struct _GstTtmlRenderRenderedImage { GstBuffer *image; @@ -59,16 +58,6 @@ struct _GstTtmlRenderRenderedImage { guint height; }; -struct _GstTtmlRenderRenderedText { - GstTtmlRenderRenderedImage *text_image; - - /* The coordinates in @layout will be offset horizontally with respect to the - * position of those characters in @text_image. Store that offset here so - * that the information in @layout can be used to locate the position and - * extent of text areas in @text_image. */ - guint horiz_offset; -}; - struct _GstTtmlRender { GstElement element;