cea708overlay: fix HCR interpretation
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / closedcaption / gstcea708decoder.c
1 /* GStreamer
2  * Copyright (C) 2013 CableLabs, Louisville, CO 80027
3  * Copyright (C) 2015 Samsung Electronics Co., Ltd.
4  *     @Author: Chengjun Wang <cjun.wang@samsung.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 #include <gst/gst.h>
22 #include <pango/pangocairo.h>
23 #include <gstcea708decoder.h>
24 #include <string.h>
25
26 #define GST_CAT_DEFAULT gst_cea708_decoder_debug
27 GST_DEBUG_CATEGORY (gst_cea708_decoder_debug);
28
29 void
30 gst_cea708_decoder_init_debug (void)
31 {
32   GST_DEBUG_CATEGORY_INIT (gst_cea708_decoder_debug, "cc708decoder", 0,
33       "CEA708 Closed Caption Decoder");
34 }
35
36 /* 708 colors are defined by 2 bits each for R,G,&B for a total of 64 color combinations */
37 static const gchar *color_names[] = {
38   "black",
39   "white",
40   "red",
41   "green",
42   "blue",
43   "yellow",
44   "magenta",
45   "cyan",
46   NULL
47 };
48
49 static const gchar *font_names[] = {
50   "serif",
51   "courier",
52   "times new roman",
53   "helvetica",
54   "Arial",
55   "Dom Casual",
56   "Coronet",
57   "Gothic",
58   NULL
59 };
60
61 static const gchar *pen_size_names[] = {
62   "30",                         /*"small" */
63   "36",                         /*"medium" */
64   "42",                         /*"large" */
65   NULL
66 };
67
68 /* G2 table defined in EIA/CEA-708 Spec */
69 static const gunichar g2_table[CC_MAX_CODE_SET_SIZE] = {
70   ' ', 0xA0, 0, 0, 0, 0x2026, 0, 0,
71   0, 0, 0x160, 0, 0x152, 0, 0, 0,
72   0x2588, 0x2018, 0x2019, 0x201c, 0x201d, 0xB7, 0, 0,
73   0, 0x2122, 0x161, 0, 0x153, 0x2120, 0, 0x178,
74   0, 0, 0, 0, 0, 0, 0, 0,
75   0, 0, 0, 0, 0, 0, 0, 0,
76   0, 0, 0, 0, 0, 0, 0, 0,
77   0, 0, 0, 0, 0, 0, 0, 0,
78   0, 0, 0, 0, 0, 0, 0, 0,
79   0, 0, 0, 0, 0, 0, 0, 0,
80   0, 0, 0, 0, 0, 0, 0x215b, 0x215c,
81   0x215d, 0x215e, 0x2502, 0x2510, 0x2514, 0x2500, 0x2518, 0x250c,
82 };
83
84 static void gst_cea708dec_print_command_name (Cea708Dec * decoder, guint8 c);
85 static void gst_cea708dec_render_pangocairo (cea708Window * window);
86 static void
87 gst_cea708dec_adjust_values_with_fontdesc (cea708Window * window,
88     PangoFontDescription * desc);
89 static gint
90 gst_cea708dec_text_list_add (GSList ** text_list,
91     gint len, const gchar * format, ...);
92 static const PangoAlignment gst_cea708dec_get_align_mode (guint8 justify_mode);
93 static const gchar *gst_cea708dec_get_color_name (guint8 color);
94 static guint8 gst_cea708dec_map_minimum_color (guint8 color);
95 static void
96 gst_cea708dec_set_pen_color (Cea708Dec * decoder,
97     guint8 * dtvcc_buffer, int index);
98 static void
99 gst_cea708dec_set_window_attributes (Cea708Dec * decoder,
100     guint8 * dtvcc_buffer, int index);
101 static void
102 gst_cea708dec_set_pen_style (Cea708Dec * decoder, guint8 pen_style_id);
103 static void
104 gst_cea708dec_set_window_style (Cea708Dec * decoder, guint8 style_id);
105 static void
106 gst_cea708dec_define_window (Cea708Dec * decoder,
107     guint8 * dtvcc_buffer, int index);
108 static inline void
109 pango_span_markup_init (cea708PangoSpanControl * span_control);
110 static inline void
111 pango_span_markup_start (cea708PangoSpanControl * span_control,
112     gchar * line_buffer, guint16 * index);
113 static inline void
114 pango_span_markup_txt (cea708PangoSpanControl * span_control,
115     gchar * line_buffer, guint16 * index);
116 static inline void
117 pango_span_markup_end (cea708PangoSpanControl * span_control,
118     gchar * line_buffer, guint16 * index);
119 static void
120 gst_cea708dec_show_pango_window (Cea708Dec * decoder, guint window_id);
121 static void
122 gst_cea708dec_clear_window_text (Cea708Dec * decoder, guint window_id);
123 static void
124 gst_cea708dec_scroll_window_up (Cea708Dec * decoder, guint window_id);
125 static void gst_cea708dec_init_window (Cea708Dec * decoder, guint window_id);
126 static void gst_cea708dec_clear_window (Cea708Dec * decoder, cea708Window * w);
127 static void
128 gst_cea708dec_set_pen_attributes (Cea708Dec * decoder,
129     guint8 * dtvcc_buffer, int index);
130 static void
131 gst_cea708dec_for_each_window (Cea708Dec * decoder,
132     guint8 window_list, VisibilityControl visibility_control,
133     const gchar * log_message, void (*function) (Cea708Dec * decoder,
134         guint window_id));
135 static void
136 gst_cea708dec_process_command (Cea708Dec * decoder,
137     guint8 * dtvcc_buffer, int index);
138 static void get_cea708dec_bufcat (gpointer data, gpointer whole_buf);
139 static gboolean
140 gst_cea708dec_render_text (Cea708Dec * decoder, GSList ** text_list,
141     gint length, guint window_id);
142 static void gst_cea708dec_window_add_char (Cea708Dec * decoder, gunichar c);
143 static void
144 gst_cea708dec_process_c2 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
145     int index);
146 static void
147 gst_cea708dec_process_g2 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
148     int index);
149 static void
150 gst_cea708dec_process_c3 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
151     int index);
152 static void
153 gst_cea708dec_process_g3 (Cea708Dec * decoder, guint8 * dtvcc_buffer,
154     int index);
155 static void
156 gst_cea708dec_process_dtvcc_byte (Cea708Dec * decoder,
157     guint8 * dtvcc_buffer, int index);
158
159 /* For debug, print name of 708 command */
160 Cea708Dec *
161 gst_cea708dec_create (PangoContext * pango_context)
162 {
163   int i;
164   Cea708Dec *decoder = g_malloc (sizeof (Cea708Dec));;
165   memset (decoder, 0, sizeof (Cea708Dec));
166
167   /* Initialize 708 variables */
168   for (i = 0; i < MAX_708_WINDOWS; i++) {
169     decoder->cc_windows[i] = g_malloc (sizeof (cea708Window));
170     gst_cea708dec_init_window (decoder, i);
171   }
172   decoder->desired_service = 1;
173   decoder->use_ARGB = FALSE;
174   decoder->pango_context = pango_context;
175   return decoder;
176 }
177
178 void
179 gst_cea708dec_free (Cea708Dec * dec)
180 {
181   int i;
182
183   for (i = 0; i < MAX_708_WINDOWS; i++) {
184     cea708Window *window = dec->cc_windows[i];
185     gst_cea708dec_clear_window (dec, window);
186     g_free (window);
187   }
188   memset (dec, 0, sizeof (Cea708Dec));
189   g_free (dec);
190 }
191
192 void
193 gst_cea708dec_set_service_number (Cea708Dec * decoder, gint8 desired_service)
194 {
195   int i = 0;
196   gint8 previous_desired_service;
197   previous_desired_service = decoder->desired_service;
198   decoder->desired_service = desired_service;
199   /* If there has been a change in the desired service number, then clear
200    * the windows for the new service. */
201   if (decoder->desired_service != previous_desired_service) {
202     for (i = 0; i < MAX_708_WINDOWS; i++) {
203       gst_cea708dec_init_window (decoder, i);
204     }
205     decoder->current_window = 0;
206   }
207 }
208
209 gboolean
210 gst_cea708dec_process_dtvcc_packet (Cea708Dec * decoder, guint8 * dtvcc_buffer,
211     gsize dtvcc_size)
212 {
213   guint i;
214   gboolean need_render = FALSE;
215   cea708Window *window = NULL;
216   guint window_id;
217
218   /* Service block header (see CEA-708 6.2.1) */
219   guint8 block_size;
220   guint8 service_number;
221
222   guint parse_index = 0;
223 #ifndef GST_DISABLE_GST_DEBUG
224   guint8 sequence_number = (dtvcc_buffer[parse_index] & 0xC0) >> 6;
225   guint8 pkt_size = DTVCC_PKT_SIZE (dtvcc_buffer[parse_index] & 0x3F);
226 #endif
227
228   parse_index += 1;
229
230   block_size = dtvcc_buffer[parse_index] & 0x1F;
231   service_number = (dtvcc_buffer[parse_index] & 0xE0) >> 5;
232   parse_index += 1;
233
234   if (service_number == 7) {
235     /* Get extended service number */
236     service_number = dtvcc_buffer[parse_index] & 0x3F;
237     parse_index += 1;
238   }
239
240   GST_LOG ("full_size:%" G_GSIZE_FORMAT
241       " size=%d seq=%d block_size=%d service_num=%d", dtvcc_size, pkt_size,
242       sequence_number, block_size, service_number);
243
244
245   /* Process desired_service cc data */
246   if (decoder->desired_service == service_number) {
247     for (i = 0; i < block_size; i++) {
248       /* The Dtvcc buffer contains a stream of commands, command parameters,
249        * and characters which are the actual captions. Process commands and
250        * store captions in simulated 708 windows: */
251       gst_cea708dec_process_dtvcc_byte (decoder, dtvcc_buffer, parse_index + i);
252     }
253
254     for (window_id = 0; window_id < 8; window_id++) {
255       window = decoder->cc_windows[window_id];
256       GST_LOG ("window #%02d deleted:%d visible:%d updated:%d", window_id,
257           window->deleted, window->visible, window->updated);
258       if (!window->updated) {
259         continue;
260       }
261       need_render = TRUE;
262     }
263   }
264
265   return need_render;
266 }
267
268 static void
269 gst_cea708dec_process_dtvcc_byte (Cea708Dec * decoder,
270     guint8 * dtvcc_buffer, int index)
271 {
272   guint8 c = dtvcc_buffer[index];
273
274   if (decoder->output_ignore) {
275     /* Ignore characters/parameters after a command. */
276     /* GST_TRACE ("[%d] ignore %X", decoder->output_ignore, c); */
277     decoder->output_ignore--;
278     return;
279   }
280   GST_DEBUG ("processing 0x%02X", c);
281
282   if (c >= 0x00 && c <= 0x1F) { /* C0 */
283     if (c == 0x03) {            /* ETX */
284       gst_cea708dec_process_command (decoder, dtvcc_buffer, index);
285     } else if (c == 0x00 || c == 0x08 || c == 0x0C || c == 0x0D || c == 0x0E) {
286       gst_cea708dec_window_add_char (decoder, c);
287     } else if (c == 0x10) {     /* EXT1 */
288       guint8 next_c = dtvcc_buffer[index + 1];
289       if (next_c >= 0x00 && next_c <= 0x1F) {   /* C2 */
290         gst_cea708dec_process_c2 (decoder, dtvcc_buffer, index + 1);
291       } else if (next_c >= 0x20 && next_c <= 0x7F) {    /* G2 */
292         gst_cea708dec_process_g2 (decoder, dtvcc_buffer, index + 1);
293       } else if (next_c >= 0x80 && next_c <= 0x9F) {    /* C3 */
294         gst_cea708dec_process_c3 (decoder, dtvcc_buffer, index + 1);
295       } else if (next_c >= 0xA0 && next_c <= 0xFF) {    /* G3 */
296         gst_cea708dec_process_g3 (decoder, dtvcc_buffer, index + 1);
297       }
298     } else if (c > 0x10 && c < 0x18) {
299       decoder->output_ignore = 1;
300       GST_INFO ("do not support 0x11-0x17");
301     } else if (c >= 0x18) {     /* P16 */
302       /*P16 do not support now */
303       decoder->output_ignore = 2;
304       GST_INFO ("do not support 0x18-0x1F");
305     }
306   } else if ((c >= 0x20) && (c <= 0x7F)) {      /* G0 */
307     if (c == 0x7F) {
308       gst_cea708dec_window_add_char (decoder, CC_SPECIAL_CODE_MUSIC_NOTE);
309     } else {
310       gst_cea708dec_window_add_char (decoder, c);
311     }
312   } else if ((c >= 0x80) && (c <= 0x9F)) {      /* C1 */
313     gst_cea708dec_process_command (decoder, dtvcc_buffer, index);
314   } else if ((c >= 0xA0) && (c <= 0xFF)) {      /* G1 */
315     gst_cea708dec_window_add_char (decoder, c);
316   }
317 }
318
319 /* For debug, print name of 708 command */
320 static void
321 gst_cea708dec_print_command_name (Cea708Dec * decoder, guint8 c)
322 {
323   gchar buffer[32];
324   const gchar *command = NULL;
325
326   switch (c) {
327     case CC_COMMAND_ETX:
328       command = (const gchar *) "End of text";
329       break;
330
331     case CC_COMMAND_CW0:
332     case CC_COMMAND_CW1:
333     case CC_COMMAND_CW2:
334     case CC_COMMAND_CW3:
335     case CC_COMMAND_CW4:
336     case CC_COMMAND_CW5:
337     case CC_COMMAND_CW6:
338     case CC_COMMAND_CW7:
339       /* Set current window, no parameters */
340       g_snprintf (buffer, sizeof (buffer), "Set current window %d", c & 0x3);
341       command = buffer;
342       break;
343
344     case CC_COMMAND_CLW:
345       command = (const gchar *) "Clear windows";
346       break;
347
348     case CC_COMMAND_DSW:
349       command = (const gchar *) "Display windows";
350       break;
351
352     case CC_COMMAND_HDW:
353       command = (const gchar *) "Hide windows";
354       break;
355
356     case CC_COMMAND_TGW:
357       command = (const gchar *) "Toggle windows";
358       break;
359
360     case CC_COMMAND_DLW:
361       command = (const gchar *) "Delete windows";
362       break;
363
364     case CC_COMMAND_DLY:
365       command = (const gchar *) "Delay";
366       break;
367
368     case CC_COMMAND_DLC:
369       command = (const gchar *) "Delay cancel";
370       break;
371
372     case CC_COMMAND_RST:
373       command = (const gchar *) "Reset";
374       break;
375
376     case CC_COMMAND_SPA:
377       command = (const gchar *) "Set pen attributes";
378       break;
379
380     case CC_COMMAND_SPC:
381       command = (const gchar *) "Set pen color";
382       break;
383
384     case CC_COMMAND_SPL:
385       command = (const gchar *) "Set pen location";
386       break;
387
388     case CC_COMMAND_SWA:
389       command = (const gchar *) "Set window attributes";
390       break;
391
392     case CC_COMMAND_DF0:
393     case CC_COMMAND_DF1:
394     case CC_COMMAND_DF2:
395     case CC_COMMAND_DF3:
396     case CC_COMMAND_DF4:
397     case CC_COMMAND_DF5:
398     case CC_COMMAND_DF6:
399     case CC_COMMAND_DF7:
400       g_snprintf (buffer, sizeof (buffer), "define window %d", c & 0x3);
401       command = buffer;
402       break;
403
404     default:
405       if ((c > 0x80) && (c < 0x9F))
406         command = (const gchar *) "Unknown";
407       break;
408   }                             /* switch */
409
410   if (NULL != command) {
411     GST_LOG ("Process 708 command (%02X): %s", c, command);
412   }
413 }
414
415 static void
416 gst_cea708dec_render_pangocairo (cea708Window * window)
417 {
418   cairo_t *crt;
419   cairo_surface_t *surf;
420   cairo_t *shadow;
421   cairo_surface_t *surf_shadow;
422   PangoRectangle ink_rec, logical_rec;
423   gint width, height;
424
425   pango_layout_get_pixel_extents (window->layout, &ink_rec, &logical_rec);
426
427   width = logical_rec.width + window->shadow_offset;
428   height = logical_rec.height + logical_rec.y + window->shadow_offset;
429
430   surf_shadow = cairo_image_surface_create (CAIRO_FORMAT_A8, width, height);
431   shadow = cairo_create (surf_shadow);
432
433   /* clear shadow surface */
434   cairo_set_operator (shadow, CAIRO_OPERATOR_CLEAR);
435   cairo_paint (shadow);
436   cairo_set_operator (shadow, CAIRO_OPERATOR_OVER);
437
438   /* draw shadow text */
439   cairo_save (shadow);
440   cairo_set_source_rgba (shadow, 0.0, 0.0, 0.0, 0.5);
441   cairo_translate (shadow, window->shadow_offset, window->shadow_offset);
442   pango_cairo_show_layout (shadow, window->layout);
443   cairo_restore (shadow);
444
445   /* draw outline text */
446   cairo_save (shadow);
447   cairo_set_source_rgb (shadow, 0.0, 0.0, 0.0);
448   cairo_set_line_width (shadow, window->outline_offset);
449   pango_cairo_layout_path (shadow, window->layout);
450   cairo_stroke (shadow);
451   cairo_restore (shadow);
452
453   cairo_destroy (shadow);
454
455   window->text_image = g_realloc (window->text_image, 4 * width * height);
456
457   surf = cairo_image_surface_create_for_data (window->text_image,
458       CAIRO_FORMAT_ARGB32, width, height, width * 4);
459   crt = cairo_create (surf);
460   cairo_set_operator (crt, CAIRO_OPERATOR_CLEAR);
461   cairo_paint (crt);
462   cairo_set_operator (crt, CAIRO_OPERATOR_OVER);
463
464   /* set default color */
465   cairo_set_source_rgb (crt, 1.0, 1.0, 1.0);
466
467   cairo_save (crt);
468   /* draw text */
469   pango_cairo_show_layout (crt, window->layout);
470   cairo_restore (crt);
471
472   /* composite shadow with offset */
473   cairo_set_operator (crt, CAIRO_OPERATOR_DEST_OVER);
474   cairo_set_source_surface (crt, surf_shadow, 0.0, 0.0);
475   cairo_paint (crt);
476
477   cairo_destroy (crt);
478   cairo_surface_destroy (surf_shadow);
479   cairo_surface_destroy (surf);
480   window->image_width = width;
481   window->image_height = height;
482 }
483
484 static void
485 gst_cea708dec_adjust_values_with_fontdesc (cea708Window * window,
486     PangoFontDescription * desc)
487 {
488   gint font_size = pango_font_description_get_size (desc) / PANGO_SCALE;
489
490   window->shadow_offset = (double) (font_size) / 13.0;
491   window->outline_offset = (double) (font_size) / 15.0;
492   if (window->outline_offset < MINIMUM_OUTLINE_OFFSET)
493     window->outline_offset = MINIMUM_OUTLINE_OFFSET;
494 }
495
496 static gint
497 gst_cea708dec_text_list_add (GSList ** text_list,
498     gint len, const gchar * format, ...)
499 {
500   va_list args;
501   gchar *str;
502
503   va_start (args, format);
504
505   str = g_malloc0 (len);
506   len = g_vsnprintf (str, len, format, args);
507   *text_list = g_slist_append (*text_list, str);
508   GST_LOG ("added %p str[%d]: %s", str, len, str);
509
510   va_end (args);
511   return len;
512 }
513
514 static const PangoAlignment
515 gst_cea708dec_get_align_mode (guint8 justify_mode)
516 {
517   guint align_mode = PANGO_ALIGN_LEFT;
518
519   switch (justify_mode) {
520     case JUSTIFY_LEFT:
521       align_mode = PANGO_ALIGN_LEFT;
522       break;
523     case JUSTIFY_RIGHT:
524       align_mode = PANGO_ALIGN_RIGHT;
525       break;
526     case JUSTIFY_CENTER:
527       align_mode = PANGO_ALIGN_CENTER;
528       break;
529     case JUSTIFY_FULL:
530     default:
531       align_mode = PANGO_ALIGN_LEFT;
532   }
533   return align_mode;
534 }
535
536 static const gchar *
537 gst_cea708dec_get_color_name (guint8 color)
538 {
539   guint index = 0;
540
541   switch (color) {
542     case CEA708_COLOR_BLACK:
543       index = COLOR_TYPE_BLACK;
544       break;
545     case CEA708_COLOR_WHITE:
546       index = COLOR_TYPE_WHITE;
547       break;
548     case CEA708_COLOR_RED:
549       index = COLOR_TYPE_RED;
550       break;
551     case CEA708_COLOR_GREEN:
552       index = COLOR_TYPE_GREEN;
553       break;
554     case CEA708_COLOR_BLUE:
555       index = COLOR_TYPE_BLUE;
556       break;
557     case CEA708_COLOR_YELLOW:
558       index = COLOR_TYPE_YELLOW;
559       break;
560     case CEA708_COLOR_MAGENTA:
561       index = COLOR_TYPE_MAGENTA;
562       break;
563     case CEA708_COLOR_CYAN:
564       index = COLOR_TYPE_CYAN;
565       break;
566     default:
567       break;
568   }
569
570   return color_names[index];
571 }
572
573 static guint8
574 gst_cea708dec_map_minimum_color (guint8 color)
575 {
576   /*According to spec minimum color list define */
577   /*check R */
578   switch ((color & 0x30) >> 4) {
579     case 1:
580       color &= 0xF;
581       break;
582     case 3:
583       color &= 0x2F;
584       break;
585     default:
586       break;
587   }
588   /*check G */
589   switch ((color & 0xC) >> 2) {
590     case 1:
591       color &= 0x33;
592       break;
593     case 3:
594       color &= 0x3B;
595       break;
596     default:
597       break;
598   }
599   /*check B */
600   switch (color & 0x3) {
601     case 1:
602       color &= 0x3C;
603       break;
604     case 3:
605       color &= 0x3E;
606       break;
607     default:
608       break;
609   }
610
611   return color;
612 }
613
614 static void
615 gst_cea708dec_set_pen_color (Cea708Dec * decoder,
616     guint8 * dtvcc_buffer, int index)
617 {
618   cea708Window *window = decoder->cc_windows[decoder->current_window];
619
620   /* format
621      fo1 fo0 fr1 fr0 fg1 fg0 fb1 fb0
622      bo1 bo0 br1 br0 bg1 bg0 bb1 bb0
623      0 0 er1 er0 eg1 eg0 eb1 eb0 */
624   window->pen_color.fg_color =
625       gst_cea708dec_map_minimum_color (dtvcc_buffer[index] & 0x3F);
626   window->pen_color.fg_opacity = (dtvcc_buffer[index] & 0xC0) >> 6;
627   window->pen_color.bg_color =
628       gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 1] & 0x3F);
629   window->pen_color.bg_opacity = (dtvcc_buffer[index + 1] & 0xC0) >> 6;
630   window->pen_color.edge_color =
631       gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 2] & 0x3F);
632   GST_LOG ("pen_color fg=0x%x fg_op=0x%x bg=0x%x bg_op=0x%x edge=0x%x",
633       window->pen_color.fg_color, window->pen_color.fg_opacity,
634       window->pen_color.bg_color, window->pen_color.bg_opacity,
635       window->pen_color.edge_color);
636 }
637
638 static void
639 gst_cea708dec_set_window_attributes (Cea708Dec * decoder,
640     guint8 * dtvcc_buffer, int index)
641 {
642   cea708Window *window = decoder->cc_windows[decoder->current_window];
643
644   /* format
645      fo1 fo0 fr1 fr0 fg1 fg0 fb1 fb0
646      bt1 bt0 br1 br0 bg1 bg0 bb1 bb0
647      bt2 ww  pd1 pd0 sd1 sd0 j1  j0
648      es3 es2 es1 es0 ed1 ed0 de1 de0 */
649   window->fill_color =
650       gst_cea708dec_map_minimum_color (dtvcc_buffer[index] & 0x3F);
651   window->fill_opacity = (dtvcc_buffer[index] & 0xC0) >> 6;
652   window->border_color =
653       gst_cea708dec_map_minimum_color (dtvcc_buffer[index + 1] & 0x3F);
654   window->border_type =
655       ((dtvcc_buffer[index + 1] & 0xC0) >> 6) | ((dtvcc_buffer[index +
656               2] & 0x80) >> 5);
657   window->word_wrap = (dtvcc_buffer[index + 2] & 0x40) ? TRUE : FALSE;
658   window->justify_mode = dtvcc_buffer[index + 2] & 0x3;
659   window->scroll_direction = (dtvcc_buffer[index + 2] & 0xC) >> 2;
660   window->print_direction = (dtvcc_buffer[index + 2] & 0x30) >> 2;
661   window->display_effect = (dtvcc_buffer[index + 3] & 0x3);
662   window->effect_direction = (dtvcc_buffer[index + 3] & 0xC);
663   window->effect_speed = (dtvcc_buffer[index + 3] & 0xF0) >> 4;
664
665   GST_LOG ("Print direction = %d", window->print_direction);
666 }
667
668 static void
669 gst_cea708dec_set_pen_style (Cea708Dec * decoder, guint8 pen_style_id)
670 {
671   cea708Window *window = decoder->cc_windows[decoder->current_window];
672
673   window->pen_attributes.pen_size = PEN_SIZE_STANDARD;
674   window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
675   window->pen_attributes.offset = PEN_OFFSET_NORMAL;
676   window->pen_attributes.italics = FALSE;
677   window->pen_attributes.underline = FALSE;
678   window->pen_attributes.edge_type = EDGE_TYPE_NONE;
679   window->pen_color.fg_color = CEA708_COLOR_WHITE;
680   window->pen_color.fg_opacity = SOLID;
681   window->pen_color.bg_color = CEA708_COLOR_BLACK;
682   window->pen_color.bg_opacity = SOLID;
683   window->pen_color.edge_color = CEA708_COLOR_BLACK;
684
685   /* CEA-708 predefined pen style ids */
686   switch (pen_style_id) {
687     default:
688     case PEN_STYLE_DEFAULT:
689       window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
690       break;
691
692     case PEN_STYLE_MONO_SERIF:
693       window->pen_attributes.font_style = FONT_STYLE_MONO_SERIF;
694       break;
695
696     case PEN_STYLE_PROP_SERIF:
697       window->pen_attributes.font_style = FONT_STYLE_PROP_SERIF;
698       break;
699
700     case PEN_STYLE_MONO_SANS:
701       window->pen_attributes.font_style = FONT_STYLE_MONO_SANS;
702       break;
703
704     case PEN_STYLE_PROP_SANS:
705       window->pen_attributes.font_style = FONT_STYLE_PROP_SANS;
706       break;
707
708     case PEN_STYLE_MONO_SANS_TRANSPARENT:
709       window->pen_attributes.font_style = FONT_STYLE_MONO_SANS;
710       window->pen_color.bg_opacity = TRANSPARENT;
711       break;
712
713     case PEN_STYLE_PROP_SANS_TRANSPARENT:
714       window->pen_attributes.font_style = FONT_STYLE_PROP_SANS;
715       window->pen_color.bg_opacity = TRANSPARENT;
716       break;
717   }
718 }
719
720 static void
721 gst_cea708dec_set_window_style (Cea708Dec * decoder, guint8 style_id)
722 {
723   cea708Window *window = decoder->cc_windows[decoder->current_window];
724
725   /* set the 'normal' styles first, then deviate in special cases below... */
726   window->justify_mode = JUSTIFY_LEFT;
727   window->print_direction = PRINT_DIR_LEFT_TO_RIGHT;
728   window->scroll_direction = SCROLL_DIR_BOTTOM_TO_TOP;
729   window->word_wrap = FALSE;
730   window->effect_direction = EFFECT_DIR_LEFT_TO_RIGHT;
731   window->display_effect = DISPLAY_EFFECT_SNAP;
732   window->effect_speed = 0;
733   window->fill_color = CEA708_COLOR_BLACK;
734   window->fill_opacity = SOLID;
735
736   /* CEA-708 predefined window style ids */
737   switch (style_id) {
738     default:
739     case WIN_STYLE_NORMAL:
740       break;
741
742     case WIN_STYLE_TRANSPARENT:
743       window->fill_opacity = TRANSPARENT;
744       break;
745
746     case WIN_STYLE_NORMAL_CENTERED:
747       window->justify_mode = JUSTIFY_CENTER;
748       break;
749
750     case WIN_STYLE_NORMAL_WORD_WRAP:
751       window->word_wrap = TRUE;
752       break;
753
754     case WIN_STYLE_TRANSPARENT_WORD_WRAP:
755       window->fill_opacity = TRANSPARENT;
756       window->word_wrap = TRUE;
757       break;
758
759     case WIN_STYLE_TRANSPARENT_CENTERED:
760       window->fill_opacity = TRANSPARENT;
761       window->justify_mode = JUSTIFY_CENTER;
762       break;
763
764     case WIN_STYLE_ROTATED:
765       window->print_direction = PRINT_DIR_TOP_TO_BOTTOM;
766       window->scroll_direction = SCROLL_DIR_RIGHT_TO_LEFT;
767       break;
768   }
769 }
770
771 /* Define window - window size, window style, pen style, anchor position, etc */
772 static void
773 gst_cea708dec_define_window (Cea708Dec * decoder,
774     guint8 * dtvcc_buffer, int index)
775 {
776   cea708Window *window = decoder->cc_windows[decoder->current_window];
777   guint8 priority = 0;
778   guint8 anchor_point = 0;
779   guint8 relative_position = 0;
780   guint8 anchor_vertical = 0;
781   guint8 anchor_horizontal = 0;
782   guint8 row_count = 0;
783   guint8 column_count = 0;
784   guint8 row_lock = 0;
785   guint8 column_lock = 0;
786   gboolean visible = FALSE;
787   guint8 style_id = 0;
788   guint8 pen_style_id = 0;
789 #ifndef GST_DISABLE_GST_DEBUG
790   guint v_anchor = 0;
791   guint h_anchor = 0;
792 #endif
793
794   GST_LOG ("current_window=%d", decoder->current_window);
795   GST_LOG ("dtvcc_buffer %02x %02x %02x %02x %02x %02x",
796       dtvcc_buffer[index + 0], dtvcc_buffer[index + 1],
797       dtvcc_buffer[index + 2], dtvcc_buffer[index + 3],
798       dtvcc_buffer[index + 4], dtvcc_buffer[index + 5]);
799
800   /* Initialize window structure */
801   if (NULL != window) {
802     if (window->deleted) {
803       /* Spec says on window create (but not re-definition) the pen position
804        * must be reset to 0
805        * TODO: also set all text positions to the fill color */
806       window->deleted = FALSE;
807       window->pen_row = 0;
808       window->pen_col = 0;
809     }
810     /* format of parameters:
811        0 0 v rl cl p2 p1 p0
812        rp av7 av6 av5 av4 av3 av1 av0
813        ah7 ah6 ah5 ah4 ah3 ah2 ah1 ah0
814        ap3 ap2 ap1 ap0 rc3 rc2 rc1 rc0
815        0 0 cc5 cc4 cc3 cc2 cc1 cc0
816        0 0 ws2 ws1 ws0 ps2 ps1 ps0 */
817
818     /* parameter byte 0 */
819     priority = dtvcc_buffer[index] & 0x7;
820     column_lock = (dtvcc_buffer[index] & 0x8) ? TRUE : FALSE;
821     row_lock = (dtvcc_buffer[index] & 0x10) ? TRUE : FALSE;
822     visible = (dtvcc_buffer[index] & 0x20) ? TRUE : FALSE;
823
824     /* parameter byte 1 */
825     relative_position = (dtvcc_buffer[index + 1] & 0x80) ? TRUE : FALSE;
826     anchor_vertical = dtvcc_buffer[index + 1] & 0x7F;
827
828     /* parameter byte 2 */
829     anchor_horizontal = dtvcc_buffer[index + 2];
830
831     /* parameter byte 3 */
832     anchor_point = (dtvcc_buffer[index + 3] & 0xF0) >> 4;
833     row_count = (dtvcc_buffer[index + 3] & 0xF) + 1;
834
835     /* parameter byte 4 */
836     column_count = (dtvcc_buffer[index + 4] & 0x3F) + 1;
837
838     /* parameter byte 5 */
839     style_id = (dtvcc_buffer[index + 5] & 0x38) >> 3;
840     pen_style_id = dtvcc_buffer[index + 5] & 0x7;
841
842     window->screen_vertical = anchor_vertical;
843     window->screen_horizontal = anchor_horizontal;
844
845     if (relative_position == FALSE) {
846       /* If position is in absolute coords, convert to percent */
847       if (decoder->width == 0 || decoder->height == 0) {
848         window->screen_vertical /= 100;
849         window->screen_horizontal /= 100;
850       } else if ((decoder->width * 9) % (decoder->height * 16) == 0) {
851         window->screen_vertical /= SCREEN_HEIGHT_16_9;
852         window->screen_horizontal /= SCREEN_WIDTH_16_9;
853       } else if ((decoder->width * 3) % (decoder->height * 4) == 0) {
854         window->screen_vertical /= SCREEN_HEIGHT_4_3;
855         window->screen_horizontal /= SCREEN_WIDTH_4_3;
856       } else {
857         window->screen_vertical /= 100;
858         window->screen_horizontal /= 100;
859       }
860       window->screen_vertical *= 100;
861       window->screen_horizontal *= 100;
862     }
863
864     window->priority = priority;
865     window->anchor_point = anchor_point;
866     window->relative_position = relative_position;
867     window->anchor_vertical = anchor_vertical;
868     window->anchor_horizontal = anchor_horizontal;
869     window->row_count = row_count;
870     window->column_count = column_count;
871     window->row_lock = row_lock;
872     window->column_lock = column_lock;
873     window->visible = visible;
874
875     /* Make sure row/col limits are not too large */
876     if (window->row_count > WINDOW_MAX_ROWS) {
877       GST_WARNING ("window row count %d is too large", window->row_count);
878       window->row_count = WINDOW_MAX_ROWS;
879     }
880
881     if (window->column_count > WINDOW_MAX_COLS) {
882       GST_WARNING ("window column count %d is too large", window->column_count);
883       window->column_count = WINDOW_MAX_COLS;
884     }
885
886     if (style_id != 0) {
887       window->style_id = style_id;
888     }
889
890     if (pen_style_id != 0) {
891       window->pen_style_id = pen_style_id;
892     }
893
894     gst_cea708dec_set_window_style (decoder, window->style_id);
895     gst_cea708dec_set_pen_style (decoder, window->pen_style_id);
896   }
897
898   GST_LOG ("priority=%d anchor=%d relative_pos=%d anchor_v=%d anchor_h=%d",
899       window->priority,
900       window->anchor_point,
901       window->relative_position,
902       window->anchor_vertical, window->anchor_horizontal);
903
904   GST_LOG ("row_count=%d col_count=%d row_lock=%d col_lock=%d visible=%d",
905       window->row_count,
906       window->column_count,
907       window->row_lock, window->column_lock, window->visible);
908
909   GST_LOG ("style_id=%d pen_style_id=%d screenH=%f screenV=%f v_offset=%d "
910       "h_offset=%d v_anchor=%d h_anchor=%d",
911       window->style_id,
912       window->pen_style_id,
913       window->screen_horizontal,
914       window->screen_vertical,
915       window->v_offset, window->h_offset, v_anchor, h_anchor);
916 }
917
918 static inline void
919 pango_span_markup_init (cea708PangoSpanControl * span_control)
920 {
921   memset (span_control, 0, sizeof (cea708PangoSpanControl));
922   span_control->size = PEN_SIZE_STANDARD;
923   span_control->fg_color = CEA708_COLOR_WHITE;
924   span_control->bg_color = CEA708_COLOR_INVALID;
925   span_control->size = PEN_SIZE_STANDARD;
926   span_control->font_style = FONT_STYLE_DEFAULT;
927 }
928
929 static inline void
930 pango_span_markup_start (cea708PangoSpanControl * span_control,
931     gchar * line_buffer, guint16 * index)
932 {
933   GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
934       span_control->span_start_flag, span_control->span_end_flag,
935       span_control->span_txt_flag);
936   if (!span_control->span_start_flag) {
937     g_strlcat (line_buffer, CEA708_PANGO_SPAN_MARKUP_START, LINEBUFFER_SIZE);
938     *index += strlen (CEA708_PANGO_SPAN_MARKUP_START);
939     span_control->span_start_flag = TRUE;
940     span_control->span_end_flag = FALSE;
941   } else {
942     GST_WARNING ("warning span start  !!!");
943   }
944 }
945
946 static inline void
947 pango_span_markup_txt (cea708PangoSpanControl * span_control,
948     gchar * line_buffer, guint16 * index)
949 {
950   GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
951       span_control->span_start_flag, span_control->span_end_flag,
952       span_control->span_txt_flag);
953   if (span_control->span_start_flag && !span_control->span_txt_flag) {
954     line_buffer[*index] = '>';
955     *index = *index + 1;
956     span_control->span_txt_flag = TRUE;
957   } else {
958     GST_WARNING ("warning span txt  !!!");
959   }
960 }
961
962 static inline void
963 pango_span_markup_end (cea708PangoSpanControl * span_control,
964     gchar * line_buffer, guint16 * index)
965 {
966   GST_LOG ("span_control start_flag:%d end_flag:%d txt_flag:%d",
967       span_control->span_start_flag, span_control->span_end_flag,
968       span_control->span_txt_flag);
969   if (span_control->span_start_flag && !span_control->span_end_flag) {
970     g_strlcat (line_buffer, CEA708_PANGO_SPAN_MARKUP_END, LINEBUFFER_SIZE);
971     *index = *index + strlen (CEA708_PANGO_SPAN_MARKUP_END);
972     span_control->span_start_flag = FALSE;
973     span_control->span_txt_flag = FALSE;
974     span_control->span_end_flag = TRUE;
975   } else {
976     GST_WARNING ("line_buffer=%s", line_buffer);
977     GST_WARNING ("warning span end  !!!");
978   }
979 }
980
981 /* FIXME: Convert to GString ! */
982 static void
983 gst_cea708dec_show_pango_window (Cea708Dec * decoder, guint window_id)
984 {
985   cea708Window *window = decoder->cc_windows[window_id];
986   gint16 row, col;
987   gboolean display = FALSE;     /* = TRUE when text lines should be written */
988   gchar line_buffer[LINEBUFFER_SIZE];
989   gchar outchar_utf8[CC_UTF8_MAX_LENGTH + 1] = { 0 };
990   guint8 utf8_char_length;
991   gint16 i, j;
992   gint16 right_index;           /* within a single line of window text, the
993                                  * index of the rightmost non-blank character */
994   guint16 index;
995   guint len = 0;
996
997   cea708PangoSpanControl span_control;
998   const gchar *fg_color = NULL;
999   const gchar *bg_color = NULL;
1000   const gchar *pen_size = NULL;
1001   const gchar *font = NULL;
1002
1003   GST_DEBUG ("window #%02d (visible:%d)", window_id, window->visible);
1004
1005   window->updated = TRUE;
1006
1007   if (!window->visible) {
1008     GST_DEBUG ("Window is not visible, skipping rendering");
1009     return;
1010   }
1011
1012   for (row = 0; row < window->row_count; row++) {
1013     for (col = 0; col < window->column_count; col++) {
1014       GST_LOG ("window->text[%d][%d].c '%c'", row, col,
1015           window->text[row][col].c);
1016       if (window->text[row][col].c != ' ') {
1017         /* If there is a non-blank line, then display from there */
1018         display = TRUE;
1019       }
1020     }
1021   }
1022
1023   if (!display) {
1024     GST_DEBUG ("No visible text, skipping rendering");
1025     return;
1026   }
1027
1028   for (row = 0; row < window->row_count; row++) {
1029     for (col = 0; col < window->column_count; col++) {
1030       if (window->text[row][col].c != ' ') {
1031
1032         memset (line_buffer, '\0', LINEBUFFER_SIZE);
1033         pango_span_markup_init (&span_control);
1034         /* Find the rightmost non-blank character on this line: */
1035         for (i = right_index = WINDOW_MAX_COLS - 1; i >= col; i--) {
1036           if (window->text[row][i].c != ' ') {
1037             right_index = i;
1038             break;
1039           }
1040         }
1041
1042         /* Copy all of the characters in this row, from the current position
1043          * to the right edge */
1044         for (i = 0, index = 0;
1045             (i <= right_index) && (index < LINEBUFFER_SIZE - 15); i++) {
1046           cea708char *current = &window->text[row][i];
1047           GST_LOG ("Adding row=%d i=%d c=%c %d", row,
1048               i, current->c, current->c);
1049
1050           do {
1051             GST_MEMDUMP ("line_buffer", (guint8 *) line_buffer, index);
1052             GST_INFO
1053                 ("text[%d][%d] '%c' underline:%d , italics:%d , font_style:%d , pen_size : %d",
1054                 row, i, current->c,
1055                 current->pen_attributes.underline,
1056                 current->pen_attributes.italics,
1057                 current->pen_attributes.font_style,
1058                 current->pen_attributes.pen_size);
1059             GST_INFO ("text[%d][%d] '%c' pen_color fg:0x%02X bg:0x%02x", row, i,
1060                 current->c, current->pen_color.fg_color,
1061                 current->pen_color.bg_color);
1062             GST_INFO
1063                 ("span_control: span_next_flag = %d, underline = %d, italics = %d, font_style = %d, size = %d, fg_color = 0x%02X, bg_color = 0x%02X",
1064                 span_control.span_next_flag, span_control.underline,
1065                 span_control.italics, span_control.font_style,
1066                 span_control.size, span_control.fg_color,
1067                 span_control.bg_color);
1068
1069             if ((current->pen_attributes.underline != span_control.underline)
1070                 || (current->pen_attributes.italics != span_control.italics)
1071                 || (current->pen_attributes.font_style !=
1072                     span_control.font_style)
1073                 || (current->pen_attributes.pen_size != span_control.size)
1074                 || (current->pen_color.fg_color != span_control.fg_color)
1075                 || (current->pen_color.bg_color != span_control.bg_color)
1076                 ) {
1077               GST_LOG ("Markup changed");
1078
1079               /* check end span to next span start */
1080               if (!span_control.span_next_flag) {
1081                 pango_span_markup_end (&span_control, line_buffer, &index);
1082                 if (span_control.span_end_flag) {
1083                   pango_span_markup_init (&span_control);
1084                   span_control.span_next_flag = TRUE;
1085                   GST_INFO ("continue check next span !!!");
1086                   continue;
1087                 }
1088               }
1089
1090               pango_span_markup_start (&span_control, line_buffer, &index);
1091
1092               /* Check for transitions to/from underline: */
1093               if (current->pen_attributes.underline) {
1094                 g_strlcat (line_buffer,
1095                     CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE,
1096                     sizeof (line_buffer));
1097                 index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_UNDERLINE_SINGLE);
1098                 span_control.underline = TRUE;
1099               }
1100
1101               /* Check for transitions to/from italics: */
1102               if (current->pen_attributes.italics) {
1103                 g_strlcat (line_buffer,
1104                     CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC,
1105                     sizeof (line_buffer));
1106                 index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_STYLE_ITALIC);
1107                 span_control.italics = TRUE;
1108               }
1109
1110               /* FIXME : Something is totally wrong with the way fonts
1111                * are being handled. Shouldn't the font description (if
1112                * specified by the user) be written for everything ? */
1113               if (!decoder->default_font_desc) {
1114                 font = font_names[current->pen_attributes.font_style];
1115
1116                 if (font) {
1117                   g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_FONT,
1118                       sizeof (line_buffer));
1119                   index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_FONT);
1120                   line_buffer[index++] = 0x27;  /* ' */
1121                   g_strlcat (line_buffer, font, sizeof (line_buffer));
1122                   index += strlen (font);
1123                   span_control.font_style = current->pen_attributes.font_style;
1124
1125                   /* Check for transitions to/from pen size  */
1126                   pen_size = pen_size_names[current->pen_attributes.pen_size];
1127
1128                   line_buffer[index++] = ' ';
1129                   g_strlcat (line_buffer, pen_size, sizeof (line_buffer));
1130                   index += strlen (pen_size);
1131                   line_buffer[index++] = 0x27;  /* ' */
1132                   span_control.size = current->pen_attributes.pen_size;
1133                 }
1134               }
1135               /* Regardless of the above, we want to remember the latest changes */
1136               span_control.font_style = current->pen_attributes.font_style;
1137               span_control.size = current->pen_attributes.pen_size;
1138               ;
1139               /* Check for transitions to/from foreground color  */
1140               fg_color =
1141                   gst_cea708dec_get_color_name (current->pen_color.fg_color);
1142               if (fg_color && current->pen_color.bg_opacity != TRANSPARENT) {
1143                 g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND,
1144                     sizeof (line_buffer));
1145                 index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_FOREGROUND);
1146                 line_buffer[index++] = 0x27;    /* ' */
1147                 g_strlcat (line_buffer, fg_color, sizeof (line_buffer));
1148                 index += strlen (fg_color);
1149                 line_buffer[index++] = 0x27;    /* ' */
1150                 span_control.fg_color = current->pen_color.fg_color;
1151                 GST_DEBUG ("span_control.fg_color updated to 0x%02x",
1152                     span_control.fg_color);
1153               } else
1154                 GST_DEBUG
1155                     ("span_control.fg_color was NOT updated (still 0x%02x)",
1156                     span_control.fg_color);
1157
1158               /* Check for transitions to/from background color  */
1159               bg_color =
1160                   gst_cea708dec_get_color_name (current->pen_color.bg_color);
1161               if (bg_color && current->pen_color.bg_opacity != TRANSPARENT) {
1162                 g_strlcat (line_buffer, CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND,
1163                     sizeof (line_buffer));
1164                 index += strlen (CEA708_PANGO_SPAN_ATTRIBUTES_BACKGROUND);
1165                 line_buffer[index++] = 0x27;    /* ' */
1166                 g_strlcat (line_buffer, bg_color, sizeof (line_buffer));
1167                 index += strlen (bg_color);
1168                 line_buffer[index++] = 0x27;    /* ' */
1169                 span_control.bg_color = current->pen_color.bg_color;
1170                 GST_DEBUG ("span_control.bg_color updated to 0x%02x",
1171                     span_control.bg_color);
1172               } else
1173                 GST_DEBUG
1174                     ("span_control.bg_color was NOT updated (still 0x%02x)",
1175                     span_control.bg_color);
1176
1177
1178               /*span text start */
1179               pango_span_markup_txt (&span_control, line_buffer, &index);
1180               GST_INFO ("span_next_flag = %d", span_control.span_next_flag);
1181             }
1182             span_control.span_next_flag = FALSE;
1183           } while (span_control.span_next_flag);
1184
1185
1186           /* Finally write the character */
1187
1188           j = 0;
1189
1190
1191           switch (current->c) {
1192             case '&':
1193               g_snprintf (&(line_buffer[index]),
1194                   sizeof (line_buffer) - index - 1, "&amp;");
1195               index += 5;
1196               break;
1197
1198             case '<':
1199               g_snprintf (&(line_buffer[index]),
1200                   sizeof (line_buffer) - index - 1, "&lt;");
1201               index += 4;
1202               break;
1203
1204             case '>':
1205               g_snprintf (&(line_buffer[index]),
1206                   sizeof (line_buffer) - index - 1, "&gt;");
1207               index += 4;
1208               break;
1209
1210             case '\'':
1211               g_snprintf (&(line_buffer[index]),
1212                   sizeof (line_buffer) - index - 1, "&apos;");
1213               index += 6;
1214               break;
1215
1216             case '"':
1217               g_snprintf (&(line_buffer[index]),
1218                   sizeof (line_buffer) - index - 1, "&quot;");
1219               index += 6;
1220               break;
1221
1222             default:
1223               /* FIXME : Use g_string_append_unichar() when switched to GString */
1224               utf8_char_length = g_unichar_to_utf8 (current->c, outchar_utf8);
1225               while (utf8_char_length > 0) {
1226                 line_buffer[index++] = outchar_utf8[j++];
1227                 utf8_char_length--;
1228               }
1229           }
1230
1231         }
1232
1233         /* pango markup span mode ends */
1234         if (span_control.underline || span_control.italics
1235             || (span_control.font_style != FONT_STYLE_DEFAULT)
1236             || (span_control.size != PEN_SIZE_STANDARD)
1237             || (span_control.fg_color != CEA708_COLOR_WHITE)
1238             || (span_control.bg_color != CEA708_COLOR_INVALID)
1239             ) {
1240           pango_span_markup_end (&span_control, line_buffer, &index);
1241           pango_span_markup_init (&span_control);
1242         }
1243
1244         GST_LOG ("adding row[%d]: %s\nlength:%d", row, line_buffer, index);
1245
1246         if (row != window->row_count - 1) {
1247           line_buffer[index++] = '\n';
1248         }
1249
1250         len +=
1251             gst_cea708dec_text_list_add (&decoder->text_list, index + 1, "%s",
1252             line_buffer);
1253         break;
1254       }
1255     }
1256
1257     if (col == window->column_count && row != window->row_count - 1) {
1258       len +=
1259           gst_cea708dec_text_list_add (&decoder->text_list, strlen ("\n") + 1,
1260           "\n");
1261     }
1262   }
1263
1264   if (len == 0) {
1265     GST_LOG ("window %d had no text", window_id);
1266   } else {
1267     /* send text to output pad */
1268     gst_cea708dec_render_text (decoder, &decoder->text_list, len, window_id);
1269   }
1270 }
1271
1272 static void
1273 gst_cea708dec_clear_window_text (Cea708Dec * decoder, guint window_id)
1274 {
1275   cea708Window *window = decoder->cc_windows[window_id];
1276   guint row, col;
1277
1278   for (row = 0; row < WINDOW_MAX_ROWS; row++) {
1279     for (col = 0; col < WINDOW_MAX_COLS; col++) {
1280       window->text[row][col].c = ' ';
1281       window->text[row][col].justify_mode = window->justify_mode;
1282       window->text[row][col].pen_attributes = window->pen_attributes;
1283       window->text[row][col].pen_color = window->pen_color;
1284     }
1285   }
1286 }
1287
1288 static void
1289 gst_cea708dec_scroll_window_up (Cea708Dec * decoder, guint window_id)
1290 {
1291   cea708Window *window = decoder->cc_windows[window_id];
1292   guint row, col;
1293
1294   /* This function should be called to scroll the window up if bottom to
1295    * top scrolling is enabled and a carraige-return is encountered, or
1296    * word-wrapping */
1297   GST_LOG_OBJECT (decoder, "called for window: %d", window_id);
1298
1299   /* start at row 1 to copy into row 0 (scrolling up) */
1300   for (row = 1; row < WINDOW_MAX_ROWS; row++) {
1301     for (col = 0; col < WINDOW_MAX_COLS; col++) {
1302       window->text[row - 1][col] = window->text[row][col];
1303     }
1304   }
1305
1306   /* Clear the bottom row: */
1307   row = WINDOW_MAX_ROWS - 1;
1308   for (col = 0; col < WINDOW_MAX_COLS; col++) {
1309     window->text[row][col].c = ' ';
1310     window->text[row][col].justify_mode = window->justify_mode;
1311     window->text[row][col].pen_attributes = window->pen_attributes;
1312     window->text[row][col].pen_color = window->pen_color;
1313   }
1314 }
1315
1316 static void
1317 gst_cea708dec_clear_window (Cea708Dec * decoder, cea708Window * window)
1318 {
1319   g_free (window->text_image);
1320   memset (window, 0, sizeof (cea708Window));
1321 }
1322
1323 static void
1324 gst_cea708dec_init_window (Cea708Dec * decoder, guint window_id)
1325 {
1326   cea708Window *window = decoder->cc_windows[window_id];
1327
1328   if (window_id >= MAX_708_WINDOWS) {
1329     GST_ERROR ("window_id outside of range %d", window_id);
1330     return;
1331   }
1332
1333   window->priority = 0;
1334   window->anchor_point = 0;
1335   window->relative_position = 0;
1336   window->anchor_vertical = 0;
1337   window->anchor_horizontal = 0;
1338   window->screen_vertical = 0;
1339   window->screen_horizontal = 0;
1340
1341   window->row_count = WINDOW_MAX_ROWS;
1342   window->column_count = WINDOW_MAX_COLS;
1343   window->row_lock = 0;
1344   window->column_lock = 0;
1345   window->visible = FALSE;
1346   window->style_id = 0;
1347   window->pen_style_id = 0;
1348   window->deleted = TRUE;
1349   window->pen_color.fg_color = CEA708_COLOR_WHITE;
1350   window->pen_color.fg_opacity = SOLID;
1351   window->pen_color.bg_color = CEA708_COLOR_BLACK;
1352   window->pen_color.bg_opacity = SOLID;
1353   window->pen_color.edge_color = CEA708_COLOR_BLACK;
1354
1355   window->pen_attributes.pen_size = PEN_SIZE_STANDARD;
1356   window->pen_attributes.font_style = FONT_STYLE_DEFAULT;
1357   window->pen_attributes.offset = PEN_OFFSET_NORMAL;
1358   window->pen_attributes.italics = FALSE;
1359   window->pen_attributes.text_tag = TAG_DIALOG;
1360   window->pen_attributes.underline = FALSE;
1361   window->pen_attributes.edge_type = EDGE_TYPE_NONE;
1362
1363   /* Init pen position */
1364   window->pen_row = 0;
1365   window->pen_col = 0;
1366
1367   /* Initialize text array to all spaces. When sending window text, only
1368    * send if there are non-blank rows */
1369   gst_cea708dec_clear_window_text (decoder, window_id);
1370
1371   /* window attributes */
1372   window->justify_mode = JUSTIFY_LEFT;
1373   window->print_direction = PRINT_DIR_LEFT_TO_RIGHT;
1374   window->scroll_direction = SCROLL_DIR_BOTTOM_TO_TOP;
1375   window->word_wrap = FALSE;
1376   window->display_effect = DISPLAY_EFFECT_SNAP;
1377   window->effect_direction = EFFECT_DIR_LEFT_TO_RIGHT;
1378   window->effect_speed = 0;
1379   window->fill_color = CEA708_COLOR_BLACK;
1380   window->fill_opacity = TRANSPARENT;
1381   window->border_type = BORDER_TYPE_NONE;
1382   window->border_color = CEA708_COLOR_BLACK;
1383
1384   window->v_offset = 0;
1385   window->h_offset = 0;
1386   window->layout = NULL;
1387   window->shadow_offset = 0;
1388   window->outline_offset = 0;
1389   window->image_width = 0;
1390   window->image_height = 0;
1391   window->text_image = NULL;
1392
1393 }
1394
1395 static void
1396 gst_cea708dec_set_pen_attributes (Cea708Dec * decoder,
1397     guint8 * dtvcc_buffer, int index)
1398 {
1399   cea708Window *window = decoder->cc_windows[decoder->current_window];
1400
1401   /* format
1402      tt3 tt2 tt1 tt0 o1 o0 s1 s0
1403      i u et2 et1 et0 fs2 fs1 fs0 */
1404   window->pen_attributes.pen_size = dtvcc_buffer[index] & 0x3;
1405   window->pen_attributes.text_tag = (dtvcc_buffer[index] & 0xF0) >> 4;
1406   window->pen_attributes.offset = (dtvcc_buffer[index] & 0xC0) >> 2;
1407   window->pen_attributes.font_style = dtvcc_buffer[index + 1] & 0x7;
1408   window->pen_attributes.italics =
1409       ((dtvcc_buffer[index + 1] & 0x80) >> 7) ? TRUE : FALSE;
1410   window->pen_attributes.underline =
1411       ((dtvcc_buffer[index + 1] & 0x40) >> 6) ? TRUE : FALSE;
1412   window->pen_attributes.edge_type = (dtvcc_buffer[index + 1] & 0x38) >> 3;
1413
1414   GST_LOG ("pen_size=%d font=%d text_tag=%d offset=%d",
1415       window->pen_attributes.pen_size,
1416       window->pen_attributes.font_style,
1417       window->pen_attributes.text_tag, window->pen_attributes.offset);
1418
1419   GST_LOG ("italics=%d underline=%d edge_type=%d",
1420       window->pen_attributes.italics,
1421       window->pen_attributes.underline, window->pen_attributes.edge_type);
1422 }
1423
1424 static void
1425 gst_cea708dec_for_each_window (Cea708Dec * decoder,
1426     guint8 window_list, VisibilityControl visibility_control,
1427     const gchar * log_message, void (*function) (Cea708Dec * decoder,
1428         guint window_id))
1429 {
1430   guint i;
1431
1432   GST_LOG ("window_list: %02x", window_list);
1433
1434   for (i = 0; i < MAX_708_WINDOWS; i++) {
1435     if (WINDOW_IN_LIST_IS_ACTIVE (window_list)) {
1436       GST_LOG ("%s[%d]:%d %s v_offset=%d h_offset=%d",
1437           log_message, i, WINDOW_IN_LIST_IS_ACTIVE (window_list),
1438           (decoder->cc_windows[i]->visible) ? "visible" : "hidden",
1439           decoder->cc_windows[i]->v_offset, decoder->cc_windows[i]->h_offset);
1440       switch (visibility_control) {
1441         default:
1442         case NO_CHANGE:
1443           break;
1444
1445         case SWITCH_TO_HIDE:
1446           decoder->cc_windows[i]->visible = FALSE;
1447           break;
1448
1449         case SWITCH_TO_SHOW:
1450           decoder->cc_windows[i]->visible = TRUE;
1451           break;
1452
1453         case TOGGLE:
1454           decoder->cc_windows[i]->visible = !decoder->cc_windows[i]->visible;
1455           break;
1456       }
1457
1458       if (NULL != function) {
1459         function (decoder, i);
1460       }
1461     }
1462
1463     window_list >>= 1;
1464   }
1465 }
1466
1467 static void
1468 gst_cea708dec_process_command (Cea708Dec * decoder,
1469     guint8 * dtvcc_buffer, int index)
1470 {
1471   cea708Window *window = decoder->cc_windows[decoder->current_window];
1472   guint8 c = dtvcc_buffer[index];
1473   guint8 window_list = dtvcc_buffer[index + 1]; /* always the first arg (if any) */
1474
1475   /* Process command codes */
1476   gst_cea708dec_print_command_name (decoder, c);
1477   switch (c) {
1478     case CC_COMMAND_ETX:       /* End of text */
1479       window->visible = TRUE;
1480       gst_cea708dec_show_pango_window (decoder, decoder->current_window);
1481       return;
1482
1483     case CC_COMMAND_CW0:       /* Set current window */
1484     case CC_COMMAND_CW1:
1485     case CC_COMMAND_CW2:
1486     case CC_COMMAND_CW3:
1487     case CC_COMMAND_CW4:
1488     case CC_COMMAND_CW5:
1489     case CC_COMMAND_CW6:
1490     case CC_COMMAND_CW7:
1491       decoder->current_window = c & 0x03;
1492       GST_LOG ("Current window=%d", decoder->current_window);
1493       return;
1494
1495     case CC_COMMAND_CLW:       /* Clear windows */
1496       decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
1497
1498       /* Clear window data */
1499       gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
1500           "clear_window", gst_cea708dec_clear_window_text);
1501       return;
1502
1503     case CC_COMMAND_DSW:       /* Display windows */
1504       decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
1505
1506       /* Show window */
1507       gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
1508           "display_window", gst_cea708dec_show_pango_window);
1509       return;
1510
1511     case CC_COMMAND_HDW:       /* Hide windows */
1512       decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
1513
1514       /* Hide window */
1515       gst_cea708dec_for_each_window (decoder, window_list, SWITCH_TO_HIDE,
1516           "hide_window", NULL);
1517       return;
1518
1519     case CC_COMMAND_TGW:       /* Toggle windows */
1520       decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
1521
1522       /* Toggle windows - hide displayed windows, display hidden windows */
1523       gst_cea708dec_for_each_window (decoder, window_list, TOGGLE,
1524           "toggle_window", gst_cea708dec_show_pango_window);
1525       return;
1526
1527     case CC_COMMAND_DLW:       /* Delete windows */
1528       decoder->output_ignore = 1;       /* 1 byte parameter = windowmap */
1529
1530       /* Delete window */
1531       gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
1532           "delete_window", gst_cea708dec_init_window);
1533       return;
1534
1535     case CC_COMMAND_DLY:       /* Delay */
1536       decoder->output_ignore = 1;       /* 1 byte parameter = delay in 1/10 sec */
1537       /* TODO: - process this command. */
1538       return;
1539
1540     case CC_COMMAND_DLC:       /* Delay cancel */
1541       /* TODO: - process this command. */
1542       return;
1543
1544       /* Reset */
1545     case CC_COMMAND_RST:
1546       /* Reset - cancel any delay, delete all windows */
1547       window_list = 0xFF;       /* all windows... */
1548
1549       /* Delete window */
1550       gst_cea708dec_for_each_window (decoder, window_list, NO_CHANGE,
1551           "reset_window", gst_cea708dec_init_window);
1552       return;
1553
1554     case CC_COMMAND_SPA:       /* Set pen attributes */
1555       decoder->output_ignore = 2;       /* 2 byte parameter = pen attributes */
1556       gst_cea708dec_set_pen_attributes (decoder, dtvcc_buffer, index + 1);
1557       return;
1558
1559     case CC_COMMAND_SPC:       /* Set pen color */
1560       decoder->output_ignore = 3;       /* 3 byte parameter = color & opacity */
1561       gst_cea708dec_set_pen_color (decoder, dtvcc_buffer, index + 1);
1562       return;
1563
1564     case CC_COMMAND_SPL:       /* Set pen location */
1565       /* Set pen location - row, column address within the current window */
1566       decoder->output_ignore = 2;       /* 2 byte parameter = row, col */
1567       window->pen_row = dtvcc_buffer[index + 1] & 0xF;
1568       window->pen_col = dtvcc_buffer[index + 2] & 0x3F;
1569       GST_LOG ("Pen location: row=%d col=%d", window->pen_row, window->pen_col);
1570       return;
1571
1572     case CC_COMMAND_SWA:       /* Set window attributes */
1573       /* Set window attributes - color, word wrap, border, scroll effect, etc */
1574       decoder->output_ignore = 4;       /* 4 byte parameter = window attributes */
1575       gst_cea708dec_set_window_attributes (decoder, dtvcc_buffer, index + 1);
1576       return;
1577
1578     case CC_COMMAND_DF0:       /* Define window */
1579     case CC_COMMAND_DF1:
1580     case CC_COMMAND_DF2:
1581     case CC_COMMAND_DF3:
1582     case CC_COMMAND_DF4:
1583     case CC_COMMAND_DF5:
1584     case CC_COMMAND_DF6:
1585     case CC_COMMAND_DF7:
1586     {
1587       window_list = 0xFF;       /* all windows... */
1588
1589       /* set window - size, style, pen style, anchor position, etc. */
1590       decoder->output_ignore = 6;       /* 6 byte parameter = window definition */
1591       decoder->current_window = c & 0x7;
1592       gst_cea708dec_define_window (decoder, dtvcc_buffer, index + 1);
1593       return;
1594     }
1595   }
1596 }
1597
1598 static void
1599 get_cea708dec_bufcat (gpointer data, gpointer whole_buf)
1600 {
1601   gchar *buf = whole_buf;
1602   strcat ((gchar *) buf, data);
1603   g_free (data);
1604 }
1605
1606 static gboolean
1607 gst_cea708dec_render_text (Cea708Dec * decoder, GSList ** text_list,
1608     gint length, guint window_id)
1609 {
1610   gchar *out_str = NULL;
1611   PangoAlignment align_mode;
1612   PangoFontDescription *desc;
1613   gchar *font_desc;
1614   cea708Window *window = decoder->cc_windows[window_id];
1615
1616   if (length > 0) {
1617     out_str = g_malloc0 (length + 1);
1618     memset (out_str, 0, length + 1);
1619
1620     g_slist_foreach (*text_list, get_cea708dec_bufcat, out_str);
1621     GST_LOG ("rendering '%s'", out_str);
1622     g_slist_free (*text_list);
1623     window->layout = pango_layout_new (decoder->pango_context);
1624     align_mode = gst_cea708dec_get_align_mode (window->justify_mode);
1625     pango_layout_set_alignment (window->layout, (PangoAlignment) align_mode);
1626     pango_layout_set_markup (window->layout, out_str, length);
1627     if (!decoder->default_font_desc)
1628       font_desc = g_strdup_printf ("%s %s", font_names[0], pen_size_names[1]);
1629     else
1630       font_desc = g_strdup (decoder->default_font_desc);
1631     desc = pango_font_description_from_string (font_desc);
1632     if (desc) {
1633       GST_INFO ("font description set: %s", font_desc);
1634       pango_layout_set_font_description (window->layout, desc);
1635       gst_cea708dec_adjust_values_with_fontdesc (window, desc);
1636       pango_font_description_free (desc);
1637       gst_cea708dec_render_pangocairo (window);
1638     } else {
1639       GST_ERROR ("font description parse failed: %s", font_desc);
1640     }
1641     g_free (font_desc);
1642     g_free (out_str);
1643     /* data freed in slist loop!
1644      *g_slist_free_full (*text_list, g_free); */
1645     *text_list = NULL;
1646     return TRUE;
1647   }
1648
1649   return FALSE;
1650 }
1651
1652 static void
1653 gst_cea708dec_window_add_char (Cea708Dec * decoder, gunichar c)
1654 {
1655   cea708Window *window = decoder->cc_windows[decoder->current_window];
1656   gint16 pen_row;
1657   gint16 pen_col;
1658
1659   /* Add one character to the current window, using current pen location.
1660    * Wrap pen location if necessary */
1661   if (c == 0)                   /* NULL */
1662     return;
1663
1664   if (c == 0x0E) {              /* HCR,moves the pen location to the beginning of the current line and deletes its contents */
1665     for (pen_col = window->pen_col; pen_col >= 0; pen_col--) {
1666       window->text[window->pen_row][pen_col].c = ' ';
1667     }
1668     window->pen_col = 0;
1669     return;
1670   }
1671
1672   if (c == 0x08) {              /* BS */
1673     switch (window->print_direction) {
1674       case PRINT_DIR_LEFT_TO_RIGHT:
1675         if (window->pen_col) {
1676           window->pen_col--;
1677         }
1678         break;
1679
1680       case PRINT_DIR_RIGHT_TO_LEFT:
1681         window->pen_col++;
1682         break;
1683
1684       case PRINT_DIR_TOP_TO_BOTTOM:
1685         if (window->pen_row) {
1686           window->pen_row--;
1687         }
1688         break;
1689
1690       case PRINT_DIR_BOTTOM_TO_TOP:
1691         window->pen_row++;
1692         break;
1693     }
1694     pen_row = window->pen_row;
1695     pen_col = window->pen_col;
1696     window->text[pen_row][pen_col].c = ' ';
1697     return;
1698   }
1699
1700   if (c == 0x0C) {              /* FF clears the screen and moves the pen location to (0,0) */
1701     window->pen_row = 0;
1702     window->pen_col = 0;
1703     gst_cea708dec_clear_window_text (decoder, decoder->current_window);
1704     return;
1705   }
1706
1707   if (c == 0x0D) {
1708     GST_DEBUG
1709         ("carriage return, window->word_wrap=%d,window->scroll_direction=%d",
1710         window->word_wrap, window->scroll_direction);
1711     window->pen_col = 0;
1712     window->pen_row++;
1713   }
1714
1715   if (window->pen_col >= window->column_count) {
1716     window->pen_col = 0;
1717     window->pen_row++;
1718   }
1719   /* Wrap row position if too large */
1720   if (window->pen_row >= window->row_count) {
1721     if (window->scroll_direction == SCROLL_DIR_BOTTOM_TO_TOP) {
1722       gst_cea708dec_scroll_window_up (decoder, decoder->current_window);
1723     }
1724     window->pen_row = window->row_count - 1;
1725     GST_WARNING ("pen row exceed window row count,scroll up");
1726   }
1727
1728   if ((c != '\r') && (c != '\n')) {
1729     pen_row = window->pen_row;
1730     pen_col = window->pen_col;
1731
1732     GST_LOG ("[text x=%d y=%d fgcolor=%d win=%d vis=%d] '%c' 0x%02X", pen_col,
1733         pen_row, window->pen_color.fg_color, decoder->current_window,
1734         window->visible, c, c);
1735
1736     /* Each cell in the window should get the current pen color and
1737      * attributes as it is written */
1738     window->text[pen_row][pen_col].c = c;
1739     window->text[pen_row][pen_col].justify_mode = window->justify_mode;
1740     window->text[pen_row][pen_col].pen_color = window->pen_color;
1741     window->text[pen_row][pen_col].pen_attributes = window->pen_attributes;
1742
1743     switch (window->print_direction) {
1744       case PRINT_DIR_LEFT_TO_RIGHT:
1745         window->pen_col++;
1746         break;
1747
1748       case PRINT_DIR_RIGHT_TO_LEFT:
1749         if (window->pen_col) {
1750           window->pen_col--;
1751         }
1752         break;
1753
1754       case PRINT_DIR_TOP_TO_BOTTOM:
1755         window->pen_row++;
1756         break;
1757
1758       case PRINT_DIR_BOTTOM_TO_TOP:
1759         if (window->pen_row) {
1760           window->pen_row--;
1761         }
1762         break;
1763     }                           /* switch (print_direction) */
1764   }
1765 }
1766
1767 static void
1768 gst_cea708dec_process_c2 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
1769 {
1770   guint8 c = dtvcc_buffer[index];
1771   if (c >= 0x00 && c <= 0x07) {
1772     decoder->output_ignore = 1;
1773   } else if (c >= 0x08 && c <= 0x0F) {
1774     decoder->output_ignore = 2;
1775   } else if (c >= 0x10 && c <= 0x17) {
1776     decoder->output_ignore = 3;
1777   } else if (c >= 0x18 && c <= 0x1F) {
1778     decoder->output_ignore = 4;
1779   }
1780 }
1781
1782 static void
1783 gst_cea708dec_process_g2 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
1784 {
1785   guint8 c = dtvcc_buffer[index];
1786   gst_cea708dec_window_add_char (decoder, g2_table[c - 0x20]);
1787   decoder->output_ignore = 1;
1788 }
1789
1790 static void
1791 gst_cea708dec_process_c3 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
1792 {
1793   guint8 c = dtvcc_buffer[index];
1794   int command_length = 0;
1795   if (c >= 0x80 && c <= 0x87) {
1796     decoder->output_ignore = 5;
1797   } else if (c >= 0x88 && c <= 0x8F) {
1798     decoder->output_ignore = 6;
1799   } else if (c >= 0x90 && c <= 0x9F) {
1800     command_length = dtvcc_buffer[index + 1] & 0x3F;
1801     decoder->output_ignore = command_length + 2;
1802   }
1803 }
1804
1805 static void
1806 gst_cea708dec_process_g3 (Cea708Dec * decoder, guint8 * dtvcc_buffer, int index)
1807 {
1808   gst_cea708dec_window_add_char (decoder, 0x5F);
1809   decoder->output_ignore = 1;
1810 }
1811
1812 void
1813 gst_cea708dec_set_video_width_height (Cea708Dec * decoder, gint width,
1814     gint height)
1815 {
1816   decoder->width = width;
1817   decoder->height = height;
1818 }