LCOV - code coverage report
Current view: top level - filters - dec_ttxt.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 519 710 73.1 %
Date: 2021-04-29 23:48:07 Functions: 18 18 100.0 %

          Line data    Source code
       1             : /*
       2             :  *                      GPAC - Multimedia Framework C SDK
       3             :  *
       4             :  *                      Authors: Jean Le Feuvre
       5             :  *                      Copyright (c) Telecom ParisTech 2000-2021
       6             :  *                                      All rights reserved
       7             :  *
       8             :  *  This file is part of GPAC / 3GPP/MPEG4 text renderer filter
       9             :  *
      10             :  *  GPAC is free software; you can redistribute it and/or modify
      11             :  *  it under the terms of the GNU Lesser General Public License as published by
      12             :  *  the Free Software Foundation; either version 2, or (at your option)
      13             :  *  any later version.
      14             :  *
      15             :  *  GPAC 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
      18             :  *  GNU Lesser General Public License for more details.
      19             :  *
      20             :  *  You should have received a copy of the GNU Lesser General Public
      21             :  *  License along with this library; see the file COPYING.  If not, write to
      22             :  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
      23             :  *
      24             :  */
      25             : 
      26             : #include <gpac/filters.h>
      27             : #include <gpac/constants.h>
      28             : 
      29             : #include <gpac/internal/isomedia_dev.h>
      30             : #include <gpac/utf.h>
      31             : #include <gpac/nodes_mpeg4.h>
      32             : #include <gpac/internal/compositor_dev.h>
      33             : 
      34             : 
      35             : #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_ISOM)
      36             : 
      37             : 
      38             : /*
      39             :         this decoder is simply a scene decoder generating its own scene graph based on input data,
      40             :         this scene graph is then used as an extra graph by the renderer, and manipulated by the decoder
      41             :         for any time animation handling.
      42             :         Translation from text to MPEG-4 scene graph:
      43             :                 * all modifiers (styles, hilight, etc) are unrolled into chunks forming a unique, linear
      44             :         sequence of text data (startChar, endChar) with associated styles & modifs
      45             :                 * chunks are mapped to classic MPEG-4/VRML text
      46             :                 * these chunks are then gathered in a Form node (supported by 2D and 3D renderers), with
      47             :         text truncation at each newline char.
      48             :                 * the Form then performs all alignment of the chunks
      49             : 
      50             :         It could be possible to use Layout instead of form, BUT layout cannot handle new lines at the time being...
      51             : 
      52             :         Currently supported for 3GP text streams:
      53             :                 * text box positioning & filling, dynamic text box
      54             :                 * text color
      55             :                 * proper alignment (H and V) with horizontal text. Vertical text may not be properly layed out (not fully tested)
      56             :                 * style Records (font, size, fontstyles, and colors change) - any nb per sample supported
      57             :                 * hilighting (static only) with color or reverse video - any nb per sample supported
      58             :                 * hypertext links - any nb per sample supported
      59             :                 * blinking - any nb per sample supported
      60             :                 * complete scrolling: in, out, in+out, up, down, right and left directions. All other
      61             :                 modifiers are supported when scrolling
      62             :                 * scroll delay
      63             : 
      64             :         It does NOT support:
      65             :                 * dynamic hilighting (karaoke)
      66             :                 * wrap
      67             : 
      68             :         The decoder only accepts complete timed text units TTU(1). In band reconfig (TTU(5) is not supported,
      69             :         nor fragmented TTUs (2, 3, 4).
      70             :         UTF16 support should work but MP4Box does not support it at encoding time.
      71             : */
      72             : 
      73             : typedef struct
      74             : {
      75             :         //opts
      76             :         Bool texture, outline;
      77             :         u32 txtw, txth;
      78             : 
      79             :         GF_FilterPid *ipid, *opid;
      80             : 
      81             :         GF_ObjectManager *odm;
      82             :         GF_Scene *scene;
      83             :         u32 dsi_crc;
      84             :         Bool is_tx3g;
      85             :         Bool is_playing, graph_registered, is_eos;
      86             : 
      87             : 
      88             :         GF_TextConfig *cfg;
      89             :         GF_BitStream *bs_r;
      90             : 
      91             :         GF_SceneGraph *scenegraph;
      92             : 
      93             :         /*avoid searching the graph for things we know...*/
      94             :         M_Transform2D *tr_track, *tr_box, *tr_scroll;
      95             :         M_Material2D *mat_track, *mat_box;
      96             :         M_Layer2D *dlist;
      97             :         M_Rectangle *rec_box, *rec_track;
      98             : 
      99             :         M_TimeSensor *ts_blink, *ts_scroll;
     100             :         M_ScalarInterpolator *process_blink, *process_scroll;
     101             :         GF_Route *time_route;
     102             :         GF_List *blink_nodes;
     103             :         u32 scroll_type, scroll_mode;
     104             :         Fixed scroll_time, scroll_delay;
     105             :         Bool timer_active;
     106             : } GF_TTXTDec;
     107             : 
     108             : 
     109             : static void ttd_set_blink_fraction(GF_Node *node, GF_Route *route);
     110             : static void ttd_set_scroll_fraction(GF_Node *node, GF_Route *route);
     111             : static void ttd_reset_display(GF_TTXTDec *ctx);
     112             : 
     113             : /*the WORST thing about 3GP in MPEG4 is positioning of the text track...*/
     114          17 : static void ttd_update_size_info(GF_TTXTDec *ctx)
     115             : {
     116             :         u32 w, h;
     117             :         Bool has_size;
     118             :         s32 offset, thw, thh, vw, vh;
     119             : 
     120          17 :         has_size = gf_sg_get_scene_size_info(ctx->scene->graph, &w, &h);
     121             :         /*no size info is given in main scene, override by associated video size if any, or by text track size*/
     122          17 :         if (!has_size) {
     123           3 :                 if (ctx->cfg->has_vid_info && ctx->cfg->video_width && ctx->cfg->video_height) {
     124           0 :                         gf_sg_set_scene_size_info(ctx->scenegraph, ctx->cfg->video_width, ctx->cfg->video_height, GF_TRUE);
     125           3 :                 } else if (ctx->cfg->text_width && ctx->cfg->text_height) {
     126           0 :                         gf_sg_set_scene_size_info(ctx->scenegraph, ctx->cfg->text_width, ctx->cfg->text_height, GF_TRUE);
     127             :                 } else {
     128           3 :                         gf_sg_set_scene_size_info(ctx->scenegraph, ctx->txtw, ctx->txth, GF_TRUE);
     129             :                 }
     130           3 :                 gf_sg_get_scene_size_info(ctx->scenegraph, &w, &h);
     131           3 :                 if (!w || !h) return;
     132           3 :                 gf_scene_force_size(ctx->scene, w, h);
     133             :         }
     134             : 
     135          17 :         if (!w || !h) return;
     136             :         /*apply*/
     137          17 :         gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE);
     138             :         /*make sure the scene size is big enough to contain the text track after positioning. RESULTS ARE UNDEFINED
     139             :         if offsets are negative: since MPEG-4 uses centered coord system, we must assume video is aligned to top-left*/
     140          17 :         if (ctx->cfg->has_vid_info) {
     141             :                 Bool set_size = GF_FALSE;
     142           0 :                 vw = ctx->cfg->horiz_offset;
     143           0 :                 if (vw<0) vw = 0;
     144           0 :                 vh = ctx->cfg->vert_offset;
     145           0 :                 if (vh<0) vh = 0;
     146           0 :                 if (ctx->cfg->text_width + (u32) vw > w) {
     147           0 :                         w = ctx->cfg->text_width+vw;
     148             :                         set_size = GF_TRUE;
     149             :                 }
     150           0 :                 if (ctx->cfg->text_height + (u32) vh > h) {
     151           0 :                         h = ctx->cfg->text_height+vh;
     152             :                         set_size = GF_TRUE;
     153             :                 }
     154           0 :                 if (set_size) {
     155           0 :                         gf_sg_set_scene_size_info(ctx->scenegraph, w, h, GF_TRUE);
     156           0 :                         gf_scene_force_size(ctx->scene, w, h);
     157             :                 }
     158             :         } else {
     159             :                 /*otherwise override (mainly used for SRT & TTXT file direct loading*/
     160          17 :                 ctx->cfg->text_width = w;
     161          17 :                 ctx->cfg->text_height = h;
     162             :         }
     163             : 
     164             :         /*ok override video size with main scene size*/
     165          17 :         ctx->cfg->video_width = w;
     166          17 :         ctx->cfg->video_height = h;
     167             : 
     168          17 :         vw = (s32) w;
     169          17 :         vh = (s32) h;
     170          17 :         thw = ctx->cfg->text_width / 2;
     171          17 :         thh = ctx->cfg->text_height / 2;
     172             :         /*check translation, we must not get out of scene size - not supported in GPAC*/
     173          17 :         offset = ctx->cfg->horiz_offset - vw/2 + thw;
     174             :         /*safety checks ?
     175             :         if (offset + thw < - vw/2) offset = - vw/2 + thw;
     176             :         else if (offset - thw > vw/2) offset = vw/2 - thw;
     177             :         */
     178          17 :         ctx->tr_track->translation.x = INT2FIX(offset);
     179             : 
     180          17 :         offset = vh/2 - ctx->cfg->vert_offset - thh;
     181             :         /*safety checks ?
     182             :         if (offset + thh > vh/2) offset = vh/2 - thh;
     183             :         else if (offset - thh < -vh/2) offset = -vh/2 + thh;
     184             :         */
     185          17 :         ctx->tr_track->translation.y = INT2FIX(offset);
     186             : 
     187          17 :         gf_node_changed((GF_Node *)ctx->tr_track, NULL);
     188             : }
     189             : 
     190             : static GFINLINE void ttd_add_child(GF_Node *n1, GF_Node *par)
     191             : {
     192          40 :         gf_node_list_add_child( & ((GF_ParentNode *)par)->children, n1);
     193          40 :         gf_node_register(n1, par);
     194             : }
     195             : 
     196             : 
     197         282 : static GFINLINE GF_Node *ttd_create_node(GF_TTXTDec *ttd, u32 tag, const char *def_name)
     198             : {
     199         282 :         GF_Node *n = gf_node_new(ttd->scenegraph, tag);
     200         282 :         if (n) {
     201         282 :                 if (def_name) gf_node_set_id(n, gf_sg_get_next_available_node_id(ttd->scenegraph), def_name);
     202         282 :                 gf_node_init(n);
     203             :         }
     204         282 :         return n;
     205             : }
     206             : 
     207           8 : static GF_Err ttd_configure_pid(GF_Filter *filter, GF_FilterPid *pid, Bool is_remove)
     208             : {
     209           8 :         GF_TTXTDec *ctx = gf_filter_get_udta(filter);
     210             :         GF_Err e;
     211             :         u32 st, codecid, dsi_crc;
     212             :         const GF_PropertyValue *p, *dsi;
     213             : 
     214           8 :         if (is_remove) {
     215           0 :                 if (ctx->opid) {
     216           0 :                         gf_filter_pid_remove(ctx->opid);
     217           0 :                         ctx->opid = NULL;
     218             :                 }
     219           0 :                 ctx->ipid = NULL;
     220           0 :                 return GF_OK;
     221             :         }
     222             :         //TODO: we need to cleanup cap checking upon reconfigure
     223           8 :         if (ctx->ipid && !gf_filter_pid_check_caps(pid)) return GF_NOT_SUPPORTED;
     224             :         assert(!ctx->ipid || (ctx->ipid == pid));
     225             : 
     226             :         st = codecid = 0;
     227           8 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_STREAM_TYPE);
     228           8 :         if (p) st = p->value.uint;
     229           8 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_CODECID);
     230           8 :         if (p) codecid = p->value.uint;
     231             : 
     232           8 :         dsi = gf_filter_pid_get_property(pid, GF_PROP_PID_DECODER_CONFIG);
     233           8 :         if (!dsi) return GF_NOT_SUPPORTED;
     234             : 
     235           8 :         if (st != GF_STREAM_TEXT) return GF_NOT_SUPPORTED;
     236             : 
     237           8 :         dsi_crc = gf_crc_32(dsi->value.data.ptr, dsi->value.data.size);
     238           8 :         if (dsi_crc == ctx->dsi_crc) return GF_OK;
     239           8 :         ctx->dsi_crc = dsi_crc;
     240             : 
     241           8 :         if (ctx->cfg) gf_odf_desc_del((GF_Descriptor *) ctx->cfg);
     242           8 :         ctx->cfg = (GF_TextConfig *) gf_odf_desc_new(GF_ODF_TEXT_CFG_TAG);
     243           8 :         if (codecid == GF_CODECID_TEXT_MPEG4) {
     244           0 :                 e = gf_odf_get_text_config(dsi->value.data.ptr, dsi->value.data.size, codecid, ctx->cfg);
     245           0 :                 if (e) {
     246           0 :                         gf_odf_desc_del((GF_Descriptor *) ctx->cfg);
     247           0 :                         ctx->cfg = NULL;
     248           0 :                         return e;
     249             :                 }
     250           0 :                 ctx->is_tx3g = GF_FALSE;
     251           8 :         } else if (codecid == GF_CODECID_TX3G) {
     252           8 :                 GF_TextSampleDescriptor * sd = gf_odf_tx3g_read(dsi->value.data.ptr, dsi->value.data.size);
     253           8 :                 if (!sd) {
     254           0 :                         gf_odf_desc_del((GF_Descriptor *) ctx->cfg);
     255           0 :                         ctx->cfg = NULL;
     256           0 :                         return GF_NON_COMPLIANT_BITSTREAM;
     257             :                 }
     258           8 :                 gf_list_add(ctx->cfg->sample_descriptions, sd);
     259           8 :                 ctx->is_tx3g = GF_TRUE;
     260             :         }
     261           8 :         p = gf_filter_pid_get_property(pid, GF_PROP_PID_TIMESCALE);
     262           8 :         if (p && !ctx->cfg->timescale) ctx->cfg->timescale = p->value.uint;
     263             : 
     264           8 :         ctx->ipid = pid;
     265           8 :         if (!ctx->opid) {
     266           8 :                 ctx->opid = gf_filter_pid_new(filter);
     267             :         }
     268             : 
     269             :         //copy properties at init or reconfig
     270           8 :         gf_filter_pid_copy_properties(ctx->opid, ctx->ipid);
     271           8 :         gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_STREAM_TYPE, &PROP_UINT(GF_STREAM_TEXT));
     272           8 :         gf_filter_pid_set_property(ctx->opid, GF_PROP_PID_CODECID, &PROP_UINT(GF_CODECID_RAW));
     273             : 
     274           8 :         return GF_OK;
     275             : }
     276             : 
     277           8 : static void ttd_setup_scene(GF_TTXTDec *ctx)
     278             : {
     279             :         GF_Node *root, *n1, *n2;
     280           8 :         if (ctx->scenegraph) return;
     281             : 
     282           8 :         ctx->scenegraph = gf_sg_new_subscene(ctx->scene->graph);
     283             : 
     284           8 :         root = ttd_create_node(ctx, TAG_MPEG4_OrderedGroup, NULL);
     285           8 :         gf_sg_set_root_node(ctx->scenegraph, root);
     286           8 :         gf_node_register(root, NULL);
     287             :         /*root transform*/
     288           8 :         ctx->tr_track = (M_Transform2D *)ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL);
     289             :         ttd_add_child((GF_Node *) ctx->tr_track, root);
     290             : 
     291           8 :         ttd_update_size_info(ctx);
     292             : 
     293             :         /*txt track background*/
     294           8 :         n1 = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL);
     295           8 :         ttd_add_child(n1, (GF_Node *) ctx->tr_track);
     296           8 :         ((M_Shape *)n1)->appearance = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL);
     297           8 :         gf_node_register(((M_Shape *)n1)->appearance, n1);
     298           8 :         ctx->mat_track = (M_Material2D *) ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL);
     299           8 :         ctx->mat_track->filled = 1;
     300           8 :         ctx->mat_track->transparency = 1;
     301           8 :         ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *) ctx->mat_track;
     302           8 :         gf_node_register((GF_Node *) ctx->mat_track, ((M_Shape *)n1)->appearance);
     303           8 :         n2 = ttd_create_node(ctx, TAG_MPEG4_Rectangle, NULL);
     304           8 :         ((M_Rectangle *)n2)->size.x = 0;
     305           8 :         ((M_Rectangle *)n2)->size.y = 0;
     306           8 :         ((M_Shape *)n1)->geometry = n2;
     307           8 :         gf_node_register(n2, n1);
     308           8 :         ctx->rec_track = (M_Rectangle *)n2;
     309             : 
     310             :         /*txt box background*/
     311           8 :         ctx->tr_box = (M_Transform2D *) ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL);
     312           8 :         ttd_add_child((GF_Node*) ctx->tr_box, (GF_Node*)ctx->tr_track);
     313           8 :         n1 = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL);
     314           8 :         ttd_add_child(n1, (GF_Node*)ctx->tr_box);
     315           8 :         ((M_Shape *)n1)->appearance = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL);
     316           8 :         gf_node_register(((M_Shape *)n1)->appearance, n1);
     317           8 :         ctx->mat_box = (M_Material2D *) ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL);
     318           8 :         ctx->mat_box->filled = 1;
     319           8 :         ctx->mat_box->transparency = 1;
     320           8 :         ((M_Appearance *) ((M_Shape *)n1)->appearance)->material = (GF_Node *)ctx->mat_box;
     321           8 :         gf_node_register((GF_Node *)ctx->mat_box, ((M_Shape *)n1)->appearance);
     322           8 :         ctx->rec_box = (M_Rectangle *) ttd_create_node(ctx, TAG_MPEG4_Rectangle, NULL);
     323           8 :         ctx->rec_box->size.x = 0;
     324           8 :         ctx->rec_box->size.y = 0;
     325           8 :         ((M_Shape *)n1)->geometry = (GF_Node *) ctx->rec_box;
     326           8 :         gf_node_register((GF_Node *) ctx->rec_box, n1);
     327             : 
     328           8 :         ctx->dlist = (M_Layer2D *) ttd_create_node(ctx, TAG_MPEG4_Layer2D, NULL);
     329           8 :         ctx->dlist->size.x = ctx->cfg->text_width;
     330           8 :         ctx->dlist->size.y = ctx->cfg->text_height;
     331           8 :         ttd_add_child((GF_Node *)ctx->dlist, (GF_Node *)ctx->tr_box);
     332             : 
     333           8 :         ctx->blink_nodes = gf_list_new();
     334           8 :         ctx->ts_blink = (M_TimeSensor *) ttd_create_node(ctx, TAG_MPEG4_TimeSensor, "TimerBlink");
     335           8 :         ctx->ts_blink->cycleInterval = 0.25;
     336           8 :         ctx->ts_blink->startTime = 0.0;
     337           8 :         ctx->ts_blink->loop = 1;
     338           8 :         ctx->process_blink = (M_ScalarInterpolator *) ttd_create_node(ctx, TAG_MPEG4_ScalarInterpolator, NULL);
     339             :         /*override set_fraction*/
     340           8 :         ctx->process_blink->on_set_fraction = ttd_set_blink_fraction;
     341           8 :         gf_node_set_private((GF_Node *) ctx->process_blink, ctx);
     342             :         /*route from fraction_changed to set_fraction*/
     343           8 :         gf_sg_route_new(ctx->scenegraph, (GF_Node *) ctx->ts_blink, 6, (GF_Node *) ctx->process_blink, 0);
     344             : 
     345           8 :         ctx->ts_scroll = (M_TimeSensor *) ttd_create_node(ctx, TAG_MPEG4_TimeSensor, "TimerScroll");
     346           8 :         ctx->ts_scroll->cycleInterval = 0;
     347           8 :         ctx->ts_scroll->startTime = -1;
     348           8 :         ctx->ts_scroll->loop = 0;
     349           8 :         ctx->process_scroll = (M_ScalarInterpolator *) ttd_create_node(ctx, TAG_MPEG4_ScalarInterpolator, NULL);
     350             :         /*override set_fraction*/
     351           8 :         ctx->process_scroll->on_set_fraction = ttd_set_scroll_fraction;
     352           8 :         gf_node_set_private((GF_Node *) ctx->process_scroll, ctx);
     353             :         /*route from fraction_changed to set_fraction*/
     354           8 :         gf_sg_route_new(ctx->scenegraph, (GF_Node *) ctx->ts_scroll, 6, (GF_Node *) ctx->process_scroll, 0);
     355             : 
     356           8 :         gf_node_register((GF_Node *) ctx->ts_blink, NULL);
     357           8 :         gf_node_register((GF_Node *) ctx->process_blink, NULL);
     358           8 :         gf_node_register((GF_Node *) ctx->ts_scroll, NULL);
     359           8 :         gf_node_register((GF_Node *) ctx->process_scroll, NULL);
     360             : 
     361             : }
     362             : 
     363          16 : static void ttd_reset_scene(GF_TTXTDec *ctx)
     364             : {
     365          16 :         if (!ctx->scenegraph) return;
     366             : 
     367           8 :         gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE);
     368             : 
     369           8 :         gf_node_unregister((GF_Node *) ctx->ts_blink, NULL);
     370           8 :         gf_node_unregister((GF_Node *) ctx->process_blink, NULL);
     371           8 :         gf_node_unregister((GF_Node *) ctx->ts_scroll, NULL);
     372           8 :         gf_node_unregister((GF_Node *) ctx->process_scroll, NULL);
     373             : 
     374           8 :         gf_sg_del(ctx->scenegraph);
     375           8 :         ctx->scenegraph = NULL;
     376           8 :         gf_list_del(ctx->blink_nodes);
     377             : }
     378             : 
     379        1628 : static void ttd_set_blink_fraction(GF_Node *node, GF_Route *route)
     380             : {
     381             :         M_Material2D *m;
     382             :         u32 i;
     383        1628 :         GF_TTXTDec *ctx = (GF_TTXTDec *)gf_node_get_private(node);
     384             : 
     385             :         Bool blink_on = GF_TRUE;
     386        1628 :         if (ctx->process_blink->set_fraction>FIX_ONE/2) blink_on = GF_FALSE;
     387        1628 :         i=0;
     388        3332 :         while ((m = (M_Material2D*)gf_list_enum(ctx->blink_nodes, &i))) {
     389          76 :                 if (m->filled != blink_on) {
     390          21 :                         m->filled = blink_on;
     391          21 :                         gf_node_changed((GF_Node *) m, NULL);
     392             :                 }
     393             :         }
     394        1628 : }
     395             : 
     396         722 : static void ttd_set_scroll_fraction(GF_Node *node, GF_Route *route)
     397             : {
     398             :         Fixed frac;
     399         722 :         GF_TTXTDec *ctx = (GF_TTXTDec *)gf_node_get_private(node);
     400         722 :         frac = ctx->process_scroll->set_fraction;
     401         722 :         if (frac==FIX_ONE) ctx->timer_active = GF_FALSE;
     402         722 :         if (!ctx->tr_scroll) return;
     403             : 
     404           0 :         switch (ctx->scroll_type - 1) {
     405           0 :         case GF_TXT_SCROLL_CREDITS:
     406             :         case GF_TXT_SCROLL_DOWN:
     407           0 :                 ctx->tr_scroll->translation.x = 0;
     408           0 :                 if (ctx->scroll_mode & GF_TXT_SCROLL_IN) {
     409           0 :                         if (frac>ctx->scroll_time) {
     410           0 :                                 ctx->scroll_mode &= ~GF_TXT_SCROLL_IN;
     411           0 :                                 ctx->tr_scroll->translation.y = 0;
     412             :                         } else {
     413           0 :                                 ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time) - ctx->dlist->size.y;
     414             :                         }
     415           0 :                 } else if (ctx->scroll_mode & GF_TXT_SCROLL_OUT) {
     416           0 :                         if (frac < FIX_ONE - ctx->scroll_time) return;
     417           0 :                         frac -= FIX_ONE - ctx->scroll_time;
     418           0 :                         if (ctx->scroll_type - 1 == GF_TXT_SCROLL_DOWN) {
     419           0 :                                 ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time);
     420             :                         } else {
     421           0 :                                 ctx->tr_scroll->translation.y = gf_muldiv(ctx->dlist->size.y, frac, ctx->scroll_time);
     422             :                         }
     423             :                 }
     424           0 :                 if (ctx->scroll_type - 1 == GF_TXT_SCROLL_DOWN) ctx->tr_scroll->translation.y *= -1;
     425             :                 break;
     426           0 :         case GF_TXT_SCROLL_MARQUEE:
     427             :         case GF_TXT_SCROLL_RIGHT:
     428           0 :                 ctx->tr_scroll->translation.y = 0;
     429           0 :                 if (ctx->scroll_mode & GF_TXT_SCROLL_IN) {
     430           0 :                         if (! (ctx->scroll_mode & GF_TXT_SCROLL_OUT)) {
     431           0 :                                 if (frac<ctx->scroll_delay) return;
     432           0 :                                 frac-=ctx->scroll_delay;
     433             :                         }
     434           0 :                         if (frac>ctx->scroll_time) {
     435           0 :                                 ctx->scroll_mode &= ~GF_TXT_SCROLL_IN;
     436           0 :                                 ctx->tr_scroll->translation.x = 0;
     437             :                         } else {
     438           0 :                                 ctx->tr_scroll->translation.x = gf_muldiv(ctx->dlist->size.x, frac, ctx->scroll_time) - ctx->dlist->size.x;
     439             :                         }
     440           0 :                 } else if (ctx->scroll_mode & GF_TXT_SCROLL_OUT) {
     441           0 :                         if (frac < FIX_ONE - ctx->scroll_time) return;
     442           0 :                         frac -= FIX_ONE - ctx->scroll_time;
     443           0 :                         ctx->tr_scroll->translation.x = gf_muldiv(ctx->dlist->size.x, frac, ctx->scroll_time);
     444             :                 }
     445           0 :                 if (ctx->scroll_type - 1 == GF_TXT_SCROLL_MARQUEE) ctx->tr_scroll->translation.x *= -1;
     446             :                 break;
     447             :         default:
     448             :                 break;
     449             :         }
     450           0 :         gf_node_changed((GF_Node *)ctx->tr_scroll, NULL);
     451             : }
     452             : 
     453          62 : static void ttd_reset_display(GF_TTXTDec *ctx)
     454             : {
     455          62 :         gf_list_reset(ctx->blink_nodes);
     456          62 :         gf_node_unregister_children((GF_Node*)ctx->dlist, ctx->dlist->children);
     457          62 :         ctx->dlist->children = NULL;
     458          62 :         gf_node_changed((GF_Node *) ctx->dlist, NULL);
     459          62 :         ctx->tr_scroll = NULL;
     460          62 : }
     461             : 
     462             : static char *ttd_find_font(GF_TextSampleDescriptor *tsd, u32 ID)
     463             : {
     464             :         u32 i;
     465           0 :         for (i=0; i<tsd->font_count; i++) {
     466          26 :                 if (tsd->fonts[i].fontID==ID) return tsd->fonts[i].fontName;
     467             :         }
     468             :         return "SERIF";
     469             : }
     470             : 
     471          36 : static void ttd_add_item(M_Form *form)
     472             : {
     473             :         s32 *new_gr;
     474          36 :         gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &new_gr);
     475          36 :         (*new_gr) = gf_node_list_get_count(form->children);
     476          36 :         gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &new_gr);
     477          36 :         (*new_gr) = -1;
     478             :         /*store line info*/
     479          36 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &new_gr);
     480          36 :         (*new_gr) = gf_node_list_get_count(form->children);
     481          36 : }
     482             : 
     483             : static void ttd_add_line(M_Form *form)
     484             : {
     485             :         s32 *new_gr;
     486          33 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &new_gr);
     487          33 :         (*new_gr) = -1;
     488             : }
     489             : 
     490             : typedef struct
     491             : {
     492             :         u32 start_char, end_char;
     493             :         GF_StyleRecord *srec;
     494             :         Bool is_hilight;
     495             :         u32 hilight_col;        /*0 means RV*/
     496             :         GF_TextHyperTextBox *hlink;
     497             :         Bool has_blink;
     498             :         /*karaoke not done yet*/
     499             :         /*text wrapping is not supported - we will need to move to Layout (rather than form), and modify
     500             :         layout to handle new lines and proper scrolling*/
     501             : } TTDTextChunk;
     502             : 
     503          26 : static void ttd_new_text_chunk(GF_TTXTDec *ctx, GF_TextSampleDescriptor *tsd, M_Form *form, u16 *utf16_txt, TTDTextChunk *tc)
     504             : {
     505             :         GF_Node *txt_model, *n2, *txt_material;
     506             :         M_Text *text;
     507             :         M_FontStyle *fs;
     508             :         char *fontName;
     509             :         char szStyle[1024];
     510             :         u32 fontSize, styleFlags, color, i, start_char;
     511             : 
     512          26 :         if (!tc->srec) {
     513          11 :                 fontName = ttd_find_font(tsd, tsd->default_style.fontID);
     514          11 :                 fontSize = tsd->default_style.font_size;
     515          11 :                 styleFlags = tsd->default_style.style_flags;
     516          11 :                 color = tsd->default_style.text_color;
     517             :         } else {
     518          15 :                 fontName = ttd_find_font(tsd, tc->srec->fontID);
     519          15 :                 fontSize = tc->srec->font_size;
     520          15 :                 styleFlags = tc->srec->style_flags;
     521          15 :                 color = tc->srec->text_color;
     522             :         }
     523             : 
     524             :         /*create base model for text node. It will then be cloned for each text item*/
     525          26 :         txt_model = ttd_create_node(ctx, TAG_MPEG4_Shape, NULL);
     526          26 :         gf_node_register(txt_model, NULL);
     527          26 :         n2 = ttd_create_node(ctx, TAG_MPEG4_Appearance, NULL);
     528          26 :         ((M_Shape *)txt_model)->appearance = n2;
     529          26 :         gf_node_register(n2, txt_model);
     530          26 :         txt_material = ttd_create_node(ctx, TAG_MPEG4_Material2D, NULL);
     531          26 :         ((M_Appearance *)n2)->material = txt_material;
     532          26 :         gf_node_register(txt_material, n2);
     533             : 
     534          26 :         ((M_Material2D *)txt_material)->filled = 1;
     535          26 :         ((M_Material2D *)txt_material)->transparency = FIX_ONE - INT2FIX((color>>24) & 0xFF) / 255;
     536          26 :         ((M_Material2D *)txt_material)->emissiveColor.red = INT2FIX((color>>16) & 0xFF) / 255;
     537          26 :         ((M_Material2D *)txt_material)->emissiveColor.green = INT2FIX((color>>8) & 0xFF) / 255;
     538          26 :         ((M_Material2D *)txt_material)->emissiveColor.blue = INT2FIX((color) & 0xFF) / 255;
     539             :         /*force 0 lineWidth if blinking (the stupid MPEG-4 default values once again..)*/
     540          26 :         if (tc->has_blink) {
     541           1 :                 ((M_Material2D *)txt_material)->lineProps = ttd_create_node(ctx, TAG_MPEG4_LineProperties, NULL);
     542           1 :                 ((M_LineProperties *)((M_Material2D *)txt_material)->lineProps)->width = 0;
     543           1 :                 gf_node_register(((M_Material2D *)txt_material)->lineProps, txt_material);
     544             :         }
     545             : 
     546          26 :         n2 = ttd_create_node(ctx, TAG_MPEG4_Text, NULL);
     547          26 :         ((M_Shape *)txt_model)->geometry = n2;
     548          26 :         gf_node_register(n2, txt_model);
     549             :         text = (M_Text *) n2;
     550          26 :         fs = (M_FontStyle *) ttd_create_node(ctx, TAG_MPEG4_FontStyle, NULL);
     551          26 :         gf_free(fs->family.vals[0]);
     552             : 
     553             :         /*translate default fonts to MPEG-4/VRML names*/
     554          26 :         if (!stricmp(fontName, "Serif")) fs->family.vals[0] = gf_strdup("SERIF");
     555           0 :         else if (!stricmp(fontName, "Sans-Serif")) fs->family.vals[0] = gf_strdup("SANS");
     556           0 :         else if (!stricmp(fontName, "Monospace")) fs->family.vals[0] = gf_strdup("TYPEWRITER");
     557           0 :         else fs->family.vals[0] = gf_strdup(fontName);
     558             : 
     559          26 :         fs->size = INT2FIX(fontSize);
     560          26 :         gf_free(fs->style.buffer);
     561             :         strcpy(szStyle, "");
     562          26 :         if (styleFlags & GF_TXT_STYLE_BOLD) {
     563           8 :                 if (styleFlags & GF_TXT_STYLE_ITALIC) strcpy(szStyle, "BOLDITALIC");
     564             :                 else strcpy(szStyle, "BOLD");
     565          18 :         } else if (styleFlags & GF_TXT_STYLE_ITALIC) strcat(szStyle, "ITALIC");
     566          26 :         if (!strlen(szStyle)) strcpy(szStyle, "PLAIN");
     567             :         /*also underline for URLs*/
     568          26 :         if ((styleFlags & GF_TXT_STYLE_UNDERLINED) || (tc->hlink && tc->hlink->URL)) strcat(szStyle, " UNDERLINED");
     569          26 :         if (styleFlags & GF_TXT_STYLE_STRIKETHROUGH) strcat(szStyle, " STRIKETHROUGH");
     570             : 
     571          26 :         if (tc->is_hilight) {
     572           0 :                 if (tc->hilight_col) {
     573             :                         char szTxt[50];
     574             :                         sprintf(szTxt, " HIGHLIGHT#%x", tc->hilight_col);
     575             :                         strcat(szStyle, szTxt);
     576             :                 } else {
     577             :                         strcat(szStyle, " HIGHLIGHT#RV");
     578             :                 }
     579             :         }
     580             :         /*a better way would be to draw the entire text box in a composite texture & bitmap but we can't really rely
     581             :         on text box size (in MP4Box, it actually defaults to the entire video area) and drawing a too large texture
     582             :         & bitmap could slow down rendering*/
     583          26 :         if (ctx->texture) strcat(szStyle, " TEXTURED");
     584          26 :         if (ctx->outline) strcat(szStyle, " OUTLINED");
     585             : 
     586          26 :         fs->style.buffer = gf_strdup(szStyle);
     587          26 :         fs->horizontal = (tsd->displayFlags & GF_TXT_VERTICAL) ? 0 : 1;
     588          26 :         text->fontStyle = (GF_Node *) fs;
     589          26 :         gf_node_register((GF_Node *)fs, (GF_Node *)text);
     590          26 :         gf_sg_vrml_mf_reset(&text->string, GF_SG_VRML_MFSTRING);
     591             : 
     592          26 :         if (tc->hlink && tc->hlink->URL) {
     593             :                 SFURL *s;
     594           0 :                 M_Anchor *anc = (M_Anchor *) ttd_create_node(ctx, TAG_MPEG4_Anchor, NULL);
     595           0 :                 gf_sg_vrml_mf_append(&anc->url, GF_SG_VRML_MFURL, (void **) &s);
     596           0 :                 s->OD_ID = 0;
     597           0 :                 s->url = gf_strdup(tc->hlink->URL);
     598           0 :                 if (tc->hlink->URL_hint) anc->description.buffer = gf_strdup(tc->hlink->URL_hint);
     599           0 :                 gf_node_list_add_child(& anc->children, txt_model);
     600           0 :                 gf_node_register(txt_model, (GF_Node *)anc);
     601           0 :                 gf_node_unregister(txt_model, NULL);
     602             :                 txt_model = (GF_Node *)anc;
     603           0 :                 gf_node_register((GF_Node *)anc, NULL);
     604             :         }
     605             : 
     606          26 :         start_char = tc->start_char;
     607         561 :         for (i=tc->start_char; i<tc->end_char; i++) {
     608             :                 Bool new_line = GF_FALSE;
     609         535 :                 if ((utf16_txt[i] == '\n') || (utf16_txt[i] == '\r') || (utf16_txt[i] == 0x85) || (utf16_txt[i] == 0x2028) || (utf16_txt[i] == 0x2029))
     610             :                         new_line = GF_TRUE;
     611             : 
     612         525 :                 if (new_line || (i+1==tc->end_char) ) {
     613             :                         SFString *st;
     614             : 
     615          36 :                         if (i+1==tc->end_char) i++;
     616             : 
     617          36 :                         if (i!=start_char) {
     618             :                                 char szLine[5000];
     619             :                                 u32 len;
     620             :                                 s16 wsChunk[5000], *sp;
     621             : 
     622             :                                 /*splitting lines, duplicate node*/
     623             : 
     624          36 :                                 n2 = gf_node_clone(ctx->scenegraph, txt_model, NULL, "", GF_TRUE);
     625          36 :                                 if (tc->hlink && tc->hlink->URL) {
     626           0 :                                         GF_Node *t = ((M_Anchor *)n2)->children->node;
     627           0 :                                         text = (M_Text *) ((M_Shape *)t)->geometry;
     628           0 :                                         txt_material = ((M_Appearance *) ((M_Shape *)t)->appearance)->material;
     629             :                                 } else {
     630          36 :                                         text = (M_Text *) ((M_Shape *)n2)->geometry;
     631          36 :                                         txt_material = ((M_Appearance *) ((M_Shape *)n2)->appearance)->material;
     632             :                                 }
     633          36 :                                 gf_sg_vrml_mf_reset(&text->string, GF_SG_VRML_MFSTRING);
     634          36 :                                 gf_node_list_add_child( &form->children, n2);
     635          36 :                                 gf_node_register(n2, (GF_Node *) form);
     636          36 :                                 ttd_add_item(form);
     637             :                                 /*clone node always register by default*/
     638          36 :                                 gf_node_unregister(n2, NULL);
     639             : 
     640          36 :                                 if (tc->has_blink && txt_material) gf_list_add(ctx->blink_nodes, txt_material);
     641             : 
     642             : 
     643          36 :                                 memcpy(wsChunk, &utf16_txt[start_char], sizeof(s16)*(i-start_char));
     644          36 :                                 wsChunk[i-start_char] = 0;
     645          36 :                                 sp = &wsChunk[0];
     646          36 :                                 len = (u32) gf_utf8_wcstombs(szLine, 5000, (const unsigned short **) &sp);
     647          36 :                                 szLine[len] = 0;
     648             : 
     649          36 :                                 gf_sg_vrml_mf_append(&text->string, GF_SG_VRML_MFSTRING, (void **) &st);
     650          36 :                                 st->buffer = gf_strdup(szLine);
     651             :                         }
     652          36 :                         start_char = i+1;
     653          36 :                         if (new_line) {
     654             :                                 ttd_add_line(form);
     655          10 :                                 if ((utf16_txt[i]=='\r') && (utf16_txt[i+1]=='\n')) i++;
     656             :                         }
     657             :                 }
     658             :         }
     659          26 :         gf_node_unregister(txt_model, NULL);
     660          26 :         return;
     661             : }
     662             : 
     663             : 
     664             : /*mod can be any of TextHighlight, TextKaraoke, TextHyperText, TextBlink*/
     665           1 : static void ttd_split_chunks(GF_TextSample *txt, u32 nb_chars, GF_List *chunks, GF_Box *mod)
     666             : {
     667             :         TTDTextChunk *tc;
     668             :         u32 start_char, end_char;
     669             :         u32 i;
     670           1 :         switch (mod->type) {
     671             :         /*these 3 can be safelly typecasted to the same struct for start/end char*/
     672           1 :         case GF_ISOM_BOX_TYPE_HLIT:
     673             :         case GF_ISOM_BOX_TYPE_HREF:
     674             :         case GF_ISOM_BOX_TYPE_BLNK:
     675           1 :                 start_char = ((GF_TextHighlightBox *)mod)->startcharoffset;
     676           1 :                 end_char = ((GF_TextHighlightBox *)mod)->endcharoffset;
     677             :                 break;
     678             :         case GF_ISOM_BOX_TYPE_KROK:
     679             :         default:
     680           1 :                 return;
     681             :         }
     682             : 
     683           1 :         if (end_char>nb_chars) end_char = nb_chars;
     684             : 
     685           1 :         i=0;
     686           1 :         while ((tc = (TTDTextChunk *)gf_list_enum(chunks, &i))) {
     687           1 :                 if (tc->end_char<=start_char) continue;
     688             :                 /*need to split chunk at begin*/
     689           1 :                 if (tc->start_char<start_char) {
     690             :                         TTDTextChunk *tc2;
     691           0 :                         tc2 = (TTDTextChunk *) gf_malloc(sizeof(TTDTextChunk));
     692             :                         memcpy(tc2, tc, sizeof(TTDTextChunk));
     693           0 :                         tc2->start_char = start_char;
     694           0 :                         tc2->end_char = tc->end_char;
     695           0 :                         tc->end_char = start_char;
     696           0 :                         gf_list_insert(chunks, tc2, i+1);
     697           0 :                         i++;
     698             :                         tc = tc2;
     699             :                 }
     700             :                 /*need to split chunks at end*/
     701           1 :                 if (tc->end_char>end_char) {
     702             :                         TTDTextChunk *tc2;
     703           0 :                         tc2 = (TTDTextChunk *) gf_malloc(sizeof(TTDTextChunk));
     704             :                         memcpy(tc2, tc, sizeof(TTDTextChunk));
     705           0 :                         tc2->start_char = tc->start_char;
     706           0 :                         tc2->end_char = end_char;
     707           0 :                         tc->start_char = end_char;
     708           0 :                         gf_list_insert(chunks, tc2, i);
     709           0 :                         i++;
     710             :                         tc = tc2;
     711             :                 }
     712             :                 /*assign mod*/
     713           1 :                 switch (mod->type) {
     714           0 :                 case GF_ISOM_BOX_TYPE_HLIT:
     715           0 :                         tc->is_hilight = GF_TRUE;
     716           0 :                         if (txt->highlight_color) tc->hilight_col = txt->highlight_color->hil_color;
     717             :                         break;
     718           0 :                 case GF_ISOM_BOX_TYPE_HREF:
     719           0 :                         tc->hlink = (GF_TextHyperTextBox *) mod;
     720             :                         break;
     721           1 :                 case GF_ISOM_BOX_TYPE_BLNK:
     722           1 :                         tc->has_blink = GF_TRUE;
     723             :                         break;
     724             :                 }
     725             :                 /*done*/
     726           1 :                 if (tc->end_char==end_char) return;
     727             :         }
     728             : }
     729             : 
     730             : 
     731          53 : static void ttd_apply_sample(GF_TTXTDec *ctx, GF_TextSample *txt, u32 sample_desc_index, Bool is_utf_16, u32 sample_duration)
     732             : {
     733             :         u32 i, nb_lines, start_idx, count;
     734             :         s32 *id, thw, thh, tw, th, offset;
     735             :         Bool vertical;
     736             :         MFInt32 idx;
     737             :         SFString *s;
     738             :         GF_BoxRecord br;
     739             :         M_Material2D *n;
     740             :         M_Form *form;
     741             :         u16 utf16_text[5000];
     742             :         u32 char_offset, char_count;
     743             :         GF_List *chunks;
     744             :         TTDTextChunk *tc;
     745             :         GF_Box *a;
     746             :         GF_TextSampleDescriptor *td = NULL;
     747             : 
     748             :         /*stop timer sensor*/
     749          53 :         if (gf_list_count(ctx->blink_nodes)) {
     750           1 :                 ctx->ts_blink->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink);
     751           1 :                 gf_node_changed((GF_Node *) ctx->ts_blink, NULL);
     752             :         }
     753          53 :         ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll);
     754          53 :         gf_node_changed((GF_Node *) ctx->ts_scroll, NULL);
     755             :         /*flush routes to avoid getting the set_fraction of the scroll sensor deactivation*/
     756          53 :         gf_sg_activate_routes(ctx->scene->graph);
     757             : 
     758          53 :         ttd_reset_display(ctx);
     759          83 :         if (!sample_desc_index || !txt || !txt->len) return;
     760             : 
     761          23 :         if (ctx->is_tx3g) {
     762          23 :                 td = (GF_TextSampleDescriptor *)gf_list_get(ctx->cfg->sample_descriptions, 0);
     763             :         } else {
     764           0 :                 i=0;
     765           0 :                 while ((td = (GF_TextSampleDescriptor *)gf_list_enum(ctx->cfg->sample_descriptions, &i))) {
     766           0 :                         if (td->sample_index==sample_desc_index) break;
     767             :                         td = NULL;
     768             :                 }
     769             :         }
     770          23 :         if (!td) return;
     771             : 
     772             : 
     773          23 :         vertical = (td->displayFlags & GF_TXT_VERTICAL) ? GF_TRUE : GF_FALSE;
     774             : 
     775             :         /*set back color*/
     776             :         /*do we fill the text box or the entire text track region*/
     777          23 :         if (td->displayFlags & GF_TXT_FILL_REGION) {
     778           0 :                 ctx->mat_box->transparency = FIX_ONE;
     779           0 :                 n = ctx->mat_track;
     780             :         } else {
     781          23 :                 ctx->mat_track->transparency = FIX_ONE;
     782          23 :                 n = ctx->mat_box;
     783             :         }
     784             : 
     785          23 :         n->transparency = FIX_ONE - INT2FIX((td->back_color>>24) & 0xFF) / 255;
     786          23 :         n->emissiveColor.red = INT2FIX((td->back_color>>16) & 0xFF) / 255;
     787          23 :         n->emissiveColor.green = INT2FIX((td->back_color>>8) & 0xFF) / 255;
     788          23 :         n->emissiveColor.blue = INT2FIX((td->back_color) & 0xFF) / 255;
     789          23 :         gf_node_changed((GF_Node *) n, NULL);
     790             : 
     791          23 :         if (txt->box) {
     792           0 :                 br = txt->box->box;
     793             :         } else {
     794          23 :                 br = td->default_pos;
     795             :         }
     796          23 :         if (!br.right || !br.bottom) {
     797             :                 br.top = br.left = 0;
     798          14 :                 br.right = ctx->cfg->text_width;
     799          14 :                 br.bottom = ctx->cfg->text_height;
     800             :         }
     801          23 :         thw = br.right - br.left;
     802          23 :         thh = br.bottom - br.top;
     803          23 :         if (!thw || !thh) {
     804             :                 br.top = br.left = 0;
     805           0 :                 thw = ctx->cfg->text_width;
     806           0 :                 thh = ctx->cfg->text_height;
     807             :         }
     808             : 
     809          23 :         ctx->dlist->size.x = INT2FIX(thw);
     810          23 :         ctx->dlist->size.y = INT2FIX(thh);
     811             : 
     812             :         /*disable backgrounds if not used*/
     813          23 :         if (ctx->mat_track->transparency<FIX_ONE) {
     814           0 :                 if (ctx->rec_track->size.x != ctx->cfg->text_width) {
     815           0 :                         ctx->rec_track->size.x = ctx->cfg->text_width;
     816           0 :                         ctx->rec_track->size.y = ctx->cfg->text_height;
     817           0 :                         gf_node_changed((GF_Node *) ctx->rec_track, NULL);
     818             :                 }
     819          23 :         } else if (ctx->rec_track->size.x) {
     820           0 :                 ctx->rec_track->size.x = ctx->rec_track->size.y = 0;
     821           0 :                 gf_node_changed((GF_Node *) ctx->rec_box, NULL);
     822             :         }
     823             : 
     824          23 :         if (ctx->mat_box->transparency<FIX_ONE) {
     825           0 :                 if (ctx->rec_box->size.x != ctx->dlist->size.x) {
     826           0 :                         ctx->rec_box->size.x = ctx->dlist->size.x;
     827           0 :                         ctx->rec_box->size.y = ctx->dlist->size.y;
     828           0 :                         gf_node_changed((GF_Node *) ctx->rec_box, NULL);
     829             :                 }
     830          23 :         } else if (ctx->rec_box->size.x) {
     831           0 :                 ctx->rec_box->size.x = ctx->rec_box->size.y = 0;
     832           0 :                 gf_node_changed((GF_Node *) ctx->rec_box, NULL);
     833             :         }
     834             : 
     835          23 :         form = (M_Form *) ttd_create_node(ctx, TAG_MPEG4_Form, NULL);
     836          23 :         form->size.x = INT2FIX(thw);
     837          23 :         form->size.y = INT2FIX(thh);
     838             : 
     839          23 :         thw /= 2;
     840          23 :         thh /= 2;
     841          23 :         tw = ctx->cfg->text_width;
     842          23 :         th = ctx->cfg->text_height;
     843             : 
     844             :         /*check translation, we must not get out of scene size - not supported in GPAC*/
     845          23 :         offset = br.left - tw/2 + thw;
     846          23 :         if (offset + thw < - tw/2) offset = - tw/2 + thw;
     847          23 :         else if (offset - thw > tw/2) offset = tw/2 - thw;
     848          23 :         ctx->tr_box->translation.x = INT2FIX(offset);
     849             : 
     850          23 :         offset = th/2 - br.top - thh;
     851          23 :         if (offset + thh > th/2) offset = th/2 - thh;
     852          23 :         else if (offset - thh < -th/2) offset = -th/2 + thh;
     853          23 :         ctx->tr_box->translation.y = INT2FIX(offset);
     854             : 
     855          23 :         gf_node_dirty_set((GF_Node *)ctx->tr_box, 0, GF_TRUE);
     856             : 
     857             : 
     858          23 :         if (ctx->scroll_type) {
     859           0 :                 ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll);
     860           0 :                 gf_node_changed((GF_Node *) ctx->ts_scroll, NULL);
     861             :         }
     862          23 :         ctx->scroll_mode = 0;
     863          23 :         if (td->displayFlags & GF_TXT_SCROLL_IN) ctx->scroll_mode |= GF_TXT_SCROLL_IN;
     864          23 :         if (td->displayFlags & GF_TXT_SCROLL_OUT) ctx->scroll_mode |= GF_TXT_SCROLL_OUT;
     865             : 
     866          23 :         ctx->scroll_type = 0;
     867          23 :         if (ctx->scroll_mode) {
     868           0 :                 ctx->scroll_type = (td->displayFlags & GF_TXT_SCROLL_DIRECTION)>>7;
     869           0 :                 ctx->scroll_type ++;
     870             :         }
     871             :         /*no sample duration, cannot determine scroll rate, so just show*/
     872          23 :         if (!sample_duration) ctx->scroll_type = 0;
     873             :         /*no scroll*/
     874          23 :         if (!ctx->scroll_mode) ctx->scroll_type = 0;
     875             : 
     876          23 :         if (ctx->scroll_type) {
     877           0 :                 ctx->tr_scroll = (M_Transform2D *) ttd_create_node(ctx, TAG_MPEG4_Transform2D, NULL);
     878           0 :                 gf_node_list_add_child( &ctx->dlist->children, (GF_Node*)ctx->tr_scroll);
     879           0 :                 gf_node_register((GF_Node *) ctx->tr_scroll, (GF_Node *) ctx->dlist);
     880           0 :                 gf_node_list_add_child( &ctx->tr_scroll->children, (GF_Node*)form);
     881           0 :                 gf_node_register((GF_Node *) form, (GF_Node *) ctx->tr_scroll);
     882           0 :                 ctx->tr_scroll->translation.x = ctx->tr_scroll->translation.y = (ctx->scroll_mode & GF_TXT_SCROLL_IN) ? -INT2FIX(1000) : 0;
     883             :                 /*if no delay, text is in motion for the duration of the sample*/
     884           0 :                 ctx->scroll_time = FIX_ONE;
     885           0 :                 ctx->scroll_delay = 0;
     886             : 
     887           0 :                 if (txt->scroll_delay) {
     888           0 :                         ctx->scroll_delay = gf_divfix(INT2FIX(txt->scroll_delay->scroll_delay), INT2FIX(sample_duration));
     889           0 :                         if (ctx->scroll_delay>FIX_ONE) ctx->scroll_delay = FIX_ONE;
     890           0 :                         ctx->scroll_time = (FIX_ONE - ctx->scroll_delay);
     891             :                 }
     892             :                 /*if both scroll (in and out), use same scroll duration for both*/
     893           0 :                 if ((ctx->scroll_mode & GF_TXT_SCROLL_IN) && (ctx->scroll_mode & GF_TXT_SCROLL_OUT)) ctx->scroll_time /= 2;
     894             : 
     895             :         } else {
     896          23 :                 gf_node_list_add_child( &ctx->dlist->children, (GF_Node*)form);
     897          23 :                 gf_node_register((GF_Node *) form, (GF_Node *) ctx->dlist);
     898          23 :                 ctx->tr_scroll = NULL;
     899             :         }
     900             : 
     901          23 :         if (is_utf_16) {
     902           0 :                 memcpy((char *) utf16_text, txt->text, sizeof(char) * txt->len);
     903           0 :                 ((char *) utf16_text)[txt->len] = 0;
     904           0 :                 ((char *) utf16_text)[txt->len+1] = 0;
     905           0 :                 char_count = txt->len / 2;
     906             :         } else {
     907          23 :                 char *p = txt->text;
     908          23 :                 char_count = (u32) gf_utf8_mbstowcs(utf16_text, 2500, (const char **) &p);
     909             :         }
     910             : 
     911          23 :         chunks = gf_list_new();
     912             :         /*flatten all modifiers*/
     913          23 :         if (!txt->styles || !txt->styles->entry_count) {
     914          11 :                 GF_SAFEALLOC(tc, TTDTextChunk);
     915          11 :                 if (!tc) {
     916           0 :                         GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TimedText] Failed to allocate text chunk\n"));
     917             :                 } else {
     918          11 :                         tc->end_char = char_count;
     919          11 :                         gf_list_add(chunks, tc);
     920             :                 }
     921             :         } else {
     922             :                 GF_StyleRecord *srec = NULL;
     923             :                 char_offset = 0;
     924          27 :                 for (i=0; i<txt->styles->entry_count; i++) {
     925          15 :                         srec = &txt->styles->styles[i];
     926          15 :                         if (srec->startCharOffset==srec->endCharOffset) continue;
     927             :                         /*handle not continuous modifiers*/
     928          15 :                         if (char_offset < srec->startCharOffset) {
     929           0 :                                 GF_SAFEALLOC(tc, TTDTextChunk);
     930           0 :                                 if (!tc) {
     931           0 :                                         GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TimedText] Failed to allocate text chunk\n"));
     932             :                                 } else {
     933           0 :                                         tc->start_char = char_offset;
     934           0 :                                         tc->end_char = srec->startCharOffset;
     935           0 :                                         gf_list_add(chunks, tc);
     936             :                                 }
     937             :                         }
     938          15 :                         GF_SAFEALLOC(tc, TTDTextChunk);
     939          15 :                         if (!tc) {
     940           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TimedText] Failed to allocate text chunk\n"));
     941             :                         } else {
     942          15 :                                 tc->start_char = srec->startCharOffset;
     943          15 :                                 tc->end_char = srec->endCharOffset;
     944          15 :                                 tc->srec = srec;
     945          15 :                                 gf_list_add(chunks, tc);
     946             :                         }
     947          15 :                         char_offset = srec->endCharOffset;
     948             :                 }
     949             : 
     950          12 :                 if (srec->endCharOffset<char_count) {
     951           0 :                         GF_SAFEALLOC(tc, TTDTextChunk);
     952           0 :                         if (!tc) {
     953           0 :                                 GF_LOG(GF_LOG_ERROR, GF_LOG_CODEC, ("[TimedText] Failed to allocate text chunk\n"));
     954             :                         } else {
     955           0 :                                 tc->start_char = char_offset;
     956           0 :                                 tc->end_char = char_count;
     957           0 :                                 gf_list_add(chunks, tc);
     958             :                         }
     959             :                 }
     960             :         }
     961             :         /*apply all other modifiers*/
     962          23 :         i=0;
     963          47 :         while ((a = (GF_Box*)gf_list_enum(txt->others, &i))) {
     964           1 :                 ttd_split_chunks(txt, char_count, chunks, a);
     965             :         }
     966             : 
     967          49 :         while (gf_list_count(chunks)) {
     968          26 :                 tc = (TTDTextChunk*)gf_list_get(chunks, 0);
     969          26 :                 gf_list_rem(chunks, 0);
     970          26 :                 ttd_new_text_chunk(ctx, td, form, utf16_text, tc);
     971          26 :                 gf_free(tc);
     972             :         }
     973          23 :         gf_list_del(chunks);
     974             : 
     975          23 :         if (form->groupsIndex.vals[form->groupsIndex.count-1] != -1)
     976             :                 ttd_add_line(form);
     977             : 
     978             :         /*rewrite form groupIndex - group is fine (eg one child per group)*/
     979          23 :         idx.count = form->groupsIndex.count;
     980          23 :         idx.vals = form->groupsIndex.vals;
     981          23 :         form->groupsIndex.vals = NULL;
     982          23 :         form->groupsIndex.count = 0;
     983             : 
     984             :         nb_lines = 0;
     985             :         start_idx = 0;
     986          92 :         for (i=0; i<idx.count; i++) {
     987          69 :                 if (idx.vals[i] == -1) {
     988             :                         u32 j;
     989             : 
     990             :                         /*only one item in line, no need for alignment, but still add a group (we could use the
     991             :                         item as a group but that would complicate the alignment generation)*/
     992          33 :                         if (start_idx==i-1) {
     993          30 :                                 gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
     994          30 :                                 (*id) = idx.vals[start_idx];
     995          30 :                                 gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
     996          30 :                                 (*id) = -1;
     997             :                         } else {
     998             :                                 /*spread horizontal 0 pixels (eg align) all items in line*/
     999           3 :                                 gf_sg_vrml_mf_append(&form->constraints, GF_SG_VRML_MFSTRING, (void **) &s);
    1000           3 :                                 s->buffer = gf_strdup(vertical ? "SV 0" : "SH 0");
    1001           9 :                                 for (j=start_idx; j<i; j++) {
    1002           6 :                                         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1003           6 :                                         (*id) = idx.vals[j];
    1004             :                                         /*also add a group for the line, for final justif*/
    1005           6 :                                         gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
    1006           6 :                                         (*id) = idx.vals[j];
    1007             :                                 }
    1008           3 :                                 gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1009           3 :                                 (*id) = -1;
    1010             :                                 /*mark end of group*/
    1011           3 :                                 gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
    1012           3 :                                 (*id) = -1;
    1013             :                         }
    1014          33 :                         start_idx = i+1;
    1015          33 :                         nb_lines ++;
    1016             :                 }
    1017             :         }
    1018          23 :         gf_free(idx.vals);
    1019             : 
    1020             :         /*finally add constraints on lines*/
    1021          23 :         start_idx = gf_node_list_get_count(form->children) + 1;
    1022             :         /*horizontal alignment*/
    1023          23 :         gf_sg_vrml_mf_append(&form->constraints, GF_SG_VRML_MFSTRING, (void **) &s);
    1024          23 :         if (vertical) {
    1025           0 :                 switch (td->vert_justif) {
    1026           0 :                 case 1:
    1027           0 :                         s->buffer = gf_strdup("AV");
    1028           0 :                         break;/*center*/
    1029           0 :                 case -1:
    1030           0 :                         s->buffer = gf_strdup("AB");
    1031           0 :                         break;/*bottom*/
    1032           0 :                 default:
    1033           0 :                         s->buffer = gf_strdup("AT");
    1034           0 :                         break;/*top*/
    1035             :                 }
    1036             :         } else {
    1037          23 :                 switch (td->horiz_justif) {
    1038          23 :                 case 1:
    1039          23 :                         s->buffer = gf_strdup("AH");
    1040          23 :                         break;/*center*/
    1041           0 :                 case -1:
    1042           0 :                         s->buffer = gf_strdup("AR");
    1043           0 :                         break;/*right*/
    1044           0 :                 default:
    1045           0 :                         s->buffer = gf_strdup("AL");
    1046           0 :                         break;/*left*/
    1047             :                 }
    1048             :         }
    1049          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1050          23 :         (*id) = 0;
    1051          56 :         for (i=0; i<nb_lines; i++) {
    1052          33 :                 gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1053          33 :                 (*id) = i+start_idx;
    1054             :         }
    1055          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1056          23 :         (*id) = -1;
    1057             : 
    1058             : 
    1059             :         /*vertical alignment: first align all items vertically, 0 pixel */
    1060          23 :         gf_sg_vrml_mf_append(&form->constraints, GF_SG_VRML_MFSTRING, (void **) &s);
    1061          23 :         s->buffer = gf_strdup(vertical ? "SH 0" : "SV 0");
    1062          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1063          23 :         (*id) = 0;
    1064          56 :         for (i=0; i<nb_lines; i++) {
    1065          33 :                 gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1066          33 :                 (*id) = i+start_idx;
    1067             :         }
    1068          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1069          23 :         (*id) = -1;
    1070             : 
    1071             :         /*define a group with every item drawn*/
    1072          23 :         count = gf_node_list_get_count(form->children);
    1073          59 :         for (i=0; i<count; i++) {
    1074          36 :                 gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
    1075          36 :                 (*id) = i+1;
    1076             :         }
    1077          23 :         gf_sg_vrml_mf_append(&form->groups, GF_SG_VRML_MFINT32, (void **) &id);
    1078          23 :         (*id) = -1;
    1079             : 
    1080          23 :         gf_sg_vrml_mf_append(&form->constraints, GF_SG_VRML_MFSTRING, (void **) &s);
    1081          23 :         if (vertical) {
    1082           0 :                 switch (td->horiz_justif) {
    1083           0 :                 case 1:
    1084           0 :                         s->buffer = gf_strdup("AH");
    1085           0 :                         break;/*center*/
    1086           0 :                 case -1:
    1087           0 :                         s->buffer = gf_strdup("AR");
    1088           0 :                         break;/*right*/
    1089           0 :                 default:
    1090           0 :                         s->buffer = gf_strdup("AL");
    1091           0 :                         break;/*left*/
    1092             :                 }
    1093             :         } else {
    1094          23 :                 switch (td->vert_justif) {
    1095           0 :                 case 1:
    1096           0 :                         s->buffer = gf_strdup("AV");
    1097           0 :                         break;/*center*/
    1098          23 :                 case -1:
    1099          23 :                         s->buffer = gf_strdup("AB");
    1100          23 :                         break;/*bottom*/
    1101           0 :                 default:
    1102           0 :                         s->buffer = gf_strdup("AT");
    1103           0 :                         break;/*top*/
    1104             :                 }
    1105             :         }
    1106          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1107          23 :         (*id) = 0;
    1108          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1109          23 :         (*id) = start_idx + nb_lines;
    1110          23 :         gf_sg_vrml_mf_append(&form->groupsIndex, GF_SG_VRML_MFINT32, (void **) &id);
    1111          23 :         (*id) = -1;
    1112             : 
    1113             : 
    1114          23 :         gf_node_dirty_set((GF_Node *)form, 0, GF_TRUE);
    1115          23 :         gf_node_changed((GF_Node *)form, NULL);
    1116          23 :         gf_node_changed((GF_Node *) ctx->dlist, NULL);
    1117             : 
    1118          23 :         if (gf_list_count(ctx->blink_nodes)) {
    1119             :                 /*restart time sensor*/
    1120           1 :                 ctx->ts_blink->startTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink);
    1121           1 :                 gf_node_changed((GF_Node *) ctx->ts_blink, NULL);
    1122             :         }
    1123             : 
    1124          23 :         ctx->timer_active = GF_TRUE;
    1125             :         /*scroll timer also acts as AU timer*/
    1126          23 :         ctx->ts_scroll->startTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll);
    1127          23 :         ctx->ts_scroll->stopTime = ctx->ts_scroll->startTime - 1.0;
    1128          23 :         ctx->ts_scroll->cycleInterval = sample_duration;
    1129          23 :         ctx->ts_scroll->cycleInterval /= ctx->cfg->timescale;
    1130          23 :         ctx->ts_scroll->cycleInterval -= 0.1;
    1131          23 :         gf_node_changed((GF_Node *) ctx->ts_scroll, NULL);
    1132             : }
    1133             : 
    1134          34 : static void ttd_toggle_display(GF_TTXTDec *ctx)
    1135             : {
    1136          34 :         if (!ctx->scenegraph) return;
    1137             : 
    1138          34 :         if (ctx->is_playing) {
    1139           9 :                 if (!ctx->graph_registered) {
    1140           9 :                         ttd_reset_display(ctx);
    1141           9 :                         ttd_update_size_info(ctx);
    1142           9 :                         gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_FALSE);
    1143           9 :                         ctx->graph_registered = GF_TRUE;
    1144             :                 }
    1145             :          } else {
    1146          25 :                 if (ctx->graph_registered) {
    1147           9 :                         gf_scene_register_extra_graph(ctx->scene, ctx->scenegraph, GF_TRUE);
    1148           9 :                         ctx->graph_registered = GF_FALSE;
    1149             :                 }
    1150             :         }
    1151             : }
    1152             : 
    1153        1655 : static Bool ttd_process_event(GF_Filter *filter, const GF_FilterEvent *evt)
    1154             : {
    1155        1655 :         GF_TTXTDec *ctx = gf_filter_get_udta(filter);
    1156             : 
    1157             :         //check for scene attach
    1158        1655 :         switch (evt->base.type) {
    1159             :         case GF_FEVT_ATTACH_SCENE:
    1160             :                 break;
    1161           8 :         case GF_FEVT_RESET_SCENE:
    1162           8 :                 if (ctx->opid != evt->attach_scene.on_pid) return GF_TRUE;
    1163           8 :                 ctx->is_playing = GF_FALSE;
    1164           8 :                 ttd_toggle_display(ctx);
    1165           8 :                 ttd_reset_scene(ctx);
    1166           8 :                 ctx->scene = NULL;
    1167           8 :                 return GF_TRUE;
    1168           9 :         case GF_FEVT_PLAY:
    1169           9 :                 ctx->is_playing = GF_TRUE;
    1170           9 :                 ttd_toggle_display(ctx);
    1171           9 :                 return GF_FALSE;
    1172           9 :         case GF_FEVT_STOP:
    1173           9 :                 ctx->is_playing = GF_FALSE;
    1174           9 :                 ttd_toggle_display(ctx);
    1175           9 :                 return GF_FALSE;
    1176             :         default:
    1177             :                 return GF_FALSE;
    1178             :         }
    1179           8 :         if (ctx->opid != evt->attach_scene.on_pid) return GF_TRUE;
    1180             : 
    1181           8 :         ctx->odm = evt->attach_scene.object_manager;
    1182           8 :         ctx->scene = ctx->odm->subscene ? ctx->odm->subscene : ctx->odm->parentscene;
    1183             : 
    1184             :         /*timedtext cannot be a root scene object*/
    1185           8 :         if (ctx->odm->subscene) {
    1186           0 :                 ctx->odm = NULL;
    1187           0 :                 ctx->scene = NULL;
    1188             :          } else {
    1189           8 :                  ttd_setup_scene(ctx);
    1190           8 :                  ttd_toggle_display(ctx);
    1191             :          }
    1192             :          return GF_TRUE;
    1193             : }
    1194             : 
    1195        1677 : static GF_Err ttd_process(GF_Filter *filter)
    1196             : {
    1197             :         const char *pck_data;
    1198             :         u32 pck_size, obj_time, timescale;
    1199             :         u64 cts;
    1200             :         GF_FilterPacket *pck;
    1201        1677 :         GF_TTXTDec *ctx = gf_filter_get_udta(filter);
    1202             : 
    1203        1677 :         if (!ctx->scene) {
    1204           0 :                 if (ctx->is_playing) {
    1205           0 :                         gf_filter_pid_set_eos(ctx->opid);
    1206           0 :                         return GF_EOS;
    1207             :                 }
    1208             :                 return GF_OK;
    1209             :         }
    1210             : 
    1211        1677 :         pck = gf_filter_pid_get_packet(ctx->ipid);
    1212        1677 :         if (!pck) {
    1213          18 :                 if (gf_filter_pid_is_eos(ctx->ipid)) {
    1214           3 :                         if (!ctx->is_eos) {
    1215           3 :                                 gf_filter_pid_set_eos(ctx->opid);
    1216           3 :                                 ctx->ts_blink->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_blink);
    1217           3 :                                 gf_node_changed((GF_Node *) ctx->ts_blink, NULL);
    1218           3 :                                 ctx->ts_scroll->stopTime = gf_node_get_scene_time((GF_Node *) ctx->ts_scroll);
    1219           3 :                                 gf_node_changed((GF_Node *) ctx->ts_scroll, NULL);
    1220           3 :                                 ctx->is_eos = GF_TRUE;
    1221             :                         }
    1222             :                         return GF_EOS;
    1223             :                 }
    1224             :                 return GF_OK;
    1225             :         }
    1226        1659 :         ctx->is_eos = GF_FALSE;
    1227             : 
    1228             :         //object clock shall be valid
    1229             :         assert(ctx->odm->ck);
    1230        1659 :         cts = gf_filter_pck_get_cts( pck );
    1231        1659 :         timescale = gf_filter_pck_get_timescale(pck);
    1232             : 
    1233        1659 :         gf_odm_check_buffering(ctx->odm, ctx->ipid);
    1234             : 
    1235             :         //we still process any frame before our clock time even when buffering
    1236        1659 :         obj_time = gf_clock_time(ctx->odm->ck);
    1237        1659 :         if (cts * 1000 > obj_time * timescale) {
    1238        1606 :                 Double ts_offset = (Double) cts;
    1239        1606 :                 ts_offset /= timescale;
    1240             : 
    1241        1606 :                 gf_sc_sys_frame_pending(ctx->scene->compositor, ts_offset, obj_time, filter);
    1242        1606 :                 return GF_OK;
    1243             :         }
    1244             : 
    1245          53 :         pck_data = gf_filter_pck_get_data(pck, &pck_size);
    1246          53 :         gf_bs_reassign_buffer(ctx->bs_r, pck_data, pck_size);
    1247             : 
    1248          53 :         while (gf_bs_available(ctx->bs_r)) {
    1249             :                 GF_TextSample *txt;
    1250             :                 Bool is_utf_16=0;
    1251             :                 u32 type, /*length, */sample_index, sample_duration;
    1252             : 
    1253          53 :                 if (!ctx->is_tx3g) {
    1254           0 :                         is_utf_16 = (Bool)gf_bs_read_int(ctx->bs_r, 1);
    1255           0 :                         gf_bs_read_int(ctx->bs_r, 4);
    1256           0 :                         type = gf_bs_read_int(ctx->bs_r, 3);
    1257           0 :                         /*length = */gf_bs_read_u16(ctx->bs_r);
    1258             : 
    1259             :                         /*currently only full text samples are supported*/
    1260           0 :                         if (type != 1) {
    1261             :                                 return GF_NOT_SUPPORTED;
    1262             :                         }
    1263           0 :                         sample_index = gf_bs_read_u8(ctx->bs_r);
    1264             :                         /*duration*/
    1265           0 :                         sample_duration = gf_bs_read_u24(ctx->bs_r);
    1266             :                 } else {
    1267             :                         sample_index = 1;
    1268             :                         /*duration*/
    1269          53 :                         sample_duration = gf_filter_pck_get_duration(pck);
    1270             :                 }
    1271             :                 /*txt length is parsed with the sample*/
    1272          53 :                 txt = gf_isom_parse_text_sample(ctx->bs_r);
    1273          53 :                 if (!txt) return GF_NON_COMPLIANT_BITSTREAM;
    1274          53 :                 GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("[TTXTDec] Applying new sample - duration %d text \"%s\"\n", sample_duration, txt->text ? txt->text : ""));
    1275          53 :                 ttd_apply_sample(ctx, txt, sample_index, is_utf_16, sample_duration);
    1276          53 :                 gf_isom_delete_text_sample(txt);
    1277             : 
    1278             :                 /*since we support only TTU(1), no need to go on*/
    1279             :                 if (!ctx->is_tx3g) {
    1280             :                         break;
    1281             :                 } else {
    1282             :                         //tx3g mode, single sample per AU
    1283             :                         assert(gf_bs_available(ctx->bs_r)==0);
    1284             :                         break;
    1285             :                 }
    1286             :         }
    1287          53 :         gf_filter_pid_drop_packet(ctx->ipid);
    1288          53 :         return GF_OK;
    1289             : }
    1290             : 
    1291           8 : static GF_Err ttd_initialize(GF_Filter *filter)
    1292             : {
    1293           8 :         GF_TTXTDec *ctx = gf_filter_get_udta(filter);
    1294           8 :         ctx->bs_r = gf_bs_new((char *) "", 1, GF_BITSTREAM_READ);
    1295           8 :         return GF_OK;
    1296             : }
    1297             : 
    1298           8 : void ttd_finalize(GF_Filter *filter)
    1299             : {
    1300           8 :         GF_TTXTDec *ctx = gf_filter_get_udta(filter);
    1301             : 
    1302           8 :         ttd_reset_scene(ctx);
    1303             : 
    1304           8 :         if (ctx->cfg) gf_odf_desc_del((GF_Descriptor *) ctx->cfg);
    1305           8 :         gf_bs_del(ctx->bs_r);
    1306           8 : }
    1307             : 
    1308             : 
    1309             : #define OFFS(_n)        #_n, offsetof(GF_TTXTDec, _n)
    1310             : static const GF_FilterArgs TTXTDecArgs[] =
    1311             : {
    1312             :         { OFFS(texture), "use texturing for output text", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
    1313             :         { OFFS(outline), "draw text outline", GF_PROP_BOOL, "false", NULL, GF_FS_ARG_HINT_ADVANCED},
    1314             :         { OFFS(txtw), "default width in standalone rendering", GF_PROP_UINT, "400", NULL, 0},
    1315             :         { OFFS(txth), "default height in standalone rendering", GF_PROP_UINT, "200", NULL, 0},
    1316             :         {0}
    1317             : };
    1318             : 
    1319             : static const GF_FilterCapability TTXTDecCaps[] =
    1320             : {
    1321             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
    1322             :         CAP_BOOL(GF_CAPS_INPUT_EXCLUDED, GF_PROP_PID_UNFRAMED, GF_TRUE),
    1323             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_TEXT_MPEG4),
    1324             :         CAP_UINT(GF_CAPS_INPUT,GF_PROP_PID_CODECID, GF_CODECID_TX3G),
    1325             :         CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_STREAM_TYPE, GF_STREAM_TEXT),
    1326             :         CAP_UINT(GF_CAPS_OUTPUT, GF_PROP_PID_CODECID, GF_CODECID_RAW),
    1327             : };
    1328             : 
    1329             : GF_FilterRegister TTXTDecRegister = {
    1330             :         .name = "ttxtdec",
    1331             :         GF_FS_SET_DESCRIPTION("TTXT/TX3G decoder")
    1332             :         GF_FS_SET_HELP("This filter decodes TTXT/TX3G streams into a BIFS scene graph of the compositor filter.\n"
    1333             :         "The TTXT documentation is available at https://wiki.gpac.io/TTXT-Format-Documentation\n")
    1334             :         .private_size = sizeof(GF_TTXTDec),
    1335             :         .flags = GF_FS_REG_MAIN_THREAD,
    1336             :         .args = TTXTDecArgs,
    1337             :         .priority = 1,
    1338             :         SETCAPS(TTXTDecCaps),
    1339             :         .initialize = ttd_initialize,
    1340             :         .finalize = ttd_finalize,
    1341             :         .process = ttd_process,
    1342             :         .configure_pid = ttd_configure_pid,
    1343             :         .process_event = ttd_process_event,
    1344             : };
    1345             : 
    1346             : #endif
    1347             : 
    1348        2877 : const GF_FilterRegister *ttxtdec_register(GF_FilterSession *session)
    1349             : {
    1350             : #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_ISOM)
    1351        2877 :         return &TTXTDecRegister;
    1352             : #else
    1353             :         return NULL;
    1354             : #endif
    1355             : }
    1356             : 
    1357             : 
    1358             : 

Generated by: LCOV version 1.13