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 :
|