cc.c
上传用户:kjfoods
上传日期:2020-07-06
资源大小:29949k
文件大小:36k
源码类别:

midi

开发平台:

Unix_Linux

  1. /*****************************************************************************
  2.  * cc608.c : CC 608/708 subtitles decoder
  3.  *****************************************************************************
  4.  * Copyright (C) 2007 Laurent Aimar
  5.  * $Id: 419d576626823f8374ed551d73c73be5bb465683 $
  6.  *
  7.  * Authors: Laurent Aimar < fenrir # via.ecp.fr>
  8.  *
  9.  * This program is free software; you can redistribute it and/or modify
  10.  * it under the terms of the GNU General Public License as published by
  11.  * the Free Software Foundation; either version 2 of the License, or
  12.  * (at your option) any later version.
  13.  *
  14.  * This program is distributed in the hope that it will be useful,
  15.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17.  * GNU General Public License for more details.
  18.  *
  19.  * You should have received a copy of the GNU General Public License
  20.  * along with this program; if not, write to the Free Software
  21.  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
  22.  *****************************************************************************/
  23. /*****************************************************************************
  24.  * Preamble
  25.  *****************************************************************************/
  26. /* The EIA 608 decoder part has been initialy based on ccextractor (GPL)
  27.  * and rewritten */
  28. /* TODO:
  29.  *  On discontinuity reset the decoder state
  30.  *  Check parity
  31.  *  708 decoding
  32.  */
  33. #ifdef HAVE_CONFIG_H
  34. # include "config.h"
  35. #endif
  36. #include <vlc_common.h>
  37. #include <vlc_plugin.h>
  38. #include <vlc_vout.h>
  39. #include <vlc_codec.h>
  40. #include <vlc_input.h>
  41. #include <vlc_osd.h>
  42. #include <vlc_filter.h>
  43. #include <vlc_image.h>
  44. #include <vlc_charset.h>
  45. #include <vlc_stream.h>
  46. #include <vlc_xml.h>
  47. #include <errno.h>
  48. #include <string.h>
  49. #include <assert.h>
  50. /*****************************************************************************
  51.  * Module descriptor.
  52.  *****************************************************************************/
  53. static int  Open ( vlc_object_t * );
  54. static void Close( vlc_object_t * );
  55. vlc_module_begin ()
  56.     set_shortname( N_("CC 608/708"))
  57.     set_description( N_("Closed Captions decoder") )
  58.     set_capability( "decoder", 50 )
  59.     set_callbacks( Open, Close )
  60. vlc_module_end ()
  61. /*****************************************************************************
  62.  * Local prototypes
  63.  *****************************************************************************/
  64. typedef enum
  65. {
  66.     EIA608_MODE_POPUP = 0,
  67.     EIA608_MODE_ROLLUP_2 = 1,
  68.     EIA608_MODE_ROLLUP_3 = 2,
  69.     EIA608_MODE_ROLLUP_4 = 3,
  70.     EIA608_MODE_PAINTON = 4,
  71.     EIA608_MODE_TEXT = 5
  72. } eia608_mode_t;
  73. typedef enum
  74. {
  75.     EIA608_COLOR_WHITE = 0,
  76.     EIA608_COLOR_GREEN = 1,
  77.     EIA608_COLOR_BLUE = 2,
  78.     EIA608_COLOR_CYAN = 3,
  79.     EIA608_COLOR_RED = 4,
  80.     EIA608_COLOR_YELLOW = 5,
  81.     EIA608_COLOR_MAGENTA = 6,
  82.     EIA608_COLOR_USERDEFINED = 7
  83. } eia608_color_t;
  84. typedef enum
  85. {
  86.     EIA608_FONT_REGULAR    = 0x00,
  87.     EIA608_FONT_ITALICS    = 0x01,
  88.     EIA608_FONT_UNDERLINE  = 0x02,
  89.     EIA608_FONT_UNDERLINE_ITALICS = EIA608_FONT_UNDERLINE | EIA608_FONT_ITALICS
  90. } eia608_font_t;
  91. #define EIA608_SCREEN_ROWS 15
  92. #define EIA608_SCREEN_COLUMNS 32
  93. struct eia608_screen // A CC buffer
  94. {
  95.     uint8_t characters[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1];
  96.     eia608_color_t colors[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1];
  97.     eia608_font_t fonts[EIA608_SCREEN_ROWS][EIA608_SCREEN_COLUMNS+1]; // Extra char at the end for a 0
  98.     int row_used[EIA608_SCREEN_ROWS]; // Any data in row?
  99. };
  100. typedef struct eia608_screen eia608_screen;
  101. typedef struct
  102. {
  103.     /* Current channel (used to reject packet without channel information) */
  104.     int i_channel;
  105.     /* */
  106.     int           i_screen; /* Displayed screen */
  107.     eia608_screen screen[2];
  108.     struct
  109.     {
  110.         int i_row;
  111.         int i_column;
  112.     } cursor;
  113.     /* */
  114.     eia608_mode_t mode;
  115.     eia608_color_t color;
  116.     eia608_font_t font;
  117.     int i_row_rollup;
  118.     /* Last command pair (used to reject duplicated command) */
  119.     struct
  120.     {
  121.         uint8_t d1;
  122.         uint8_t d2;
  123.     } last;
  124. } eia608_t;
  125. static void         Eia608Init( eia608_t * );
  126. static bool   Eia608Parse( eia608_t *h, int i_channel_selected, const uint8_t data[2] );
  127. static char        *Eia608Text( eia608_t *h, bool b_html );
  128. static void         Eia608Exit( eia608_t * );
  129. /* It will be enough up to 63 B frames, which is far too high for
  130.  * broadcast environment */
  131. #define CC_MAX_REORDER_SIZE (64)
  132. struct decoder_sys_t
  133. {
  134.     int i;
  135.     int     i_block;
  136.     block_t *pp_block[CC_MAX_REORDER_SIZE];
  137.     int i_field;
  138.     int i_channel;
  139.     eia608_t eia608;
  140. };
  141. static subpicture_t *Decode( decoder_t *, block_t ** );
  142. /*****************************************************************************
  143.  * Open: probe the decoder and return score
  144.  *****************************************************************************
  145.  * Tries to launch a decoder and return score so that the interface is able
  146.  * to chose.
  147.  *****************************************************************************/
  148. static int Open( vlc_object_t *p_this )
  149. {
  150.     decoder_t     *p_dec = (decoder_t*)p_this;
  151.     decoder_sys_t *p_sys;
  152.     int i_field;
  153.     int i_channel;
  154.     switch( p_dec->fmt_in.i_codec )
  155.     {
  156.         case VLC_FOURCC('c','c','1',' '):
  157.             i_field = 0; i_channel = 1;
  158.             break;
  159.         case VLC_FOURCC('c','c','2',' '):
  160.             i_field = 0; i_channel = 2;
  161.             break;
  162.         case VLC_FOURCC('c','c','3',' '):
  163.             i_field = 1; i_channel = 1;
  164.             break;
  165.         case VLC_FOURCC('c','c','4',' '):
  166.             i_field = 1; i_channel = 2;
  167.             break;
  168.         default:
  169.             return VLC_EGENERIC;
  170.     }
  171.     p_dec->pf_decode_sub = Decode;
  172.     /* Allocate the memory needed to store the decoder's structure */
  173.     p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
  174.     if( p_sys == NULL )
  175.         return VLC_ENOMEM;
  176.     /* init of p_sys */
  177.     p_sys->i_field = i_field;
  178.     p_sys->i_channel = i_channel;
  179.     Eia608Init( &p_sys->eia608 );
  180.     p_dec->fmt_out.i_cat = SPU_ES;
  181.     p_dec->fmt_out.i_codec = VLC_FOURCC('T','E','X','T');
  182.     return VLC_SUCCESS;
  183. }
  184. /****************************************************************************
  185.  * Decode: the whole thing
  186.  ****************************************************************************
  187.  *
  188.  ****************************************************************************/
  189. static void     Push( decoder_t *, block_t * );
  190. static block_t *Pop( decoder_t * );
  191. static subpicture_t *Convert( decoder_t *, block_t * );
  192. static subpicture_t *Decode( decoder_t *p_dec, block_t **pp_block )
  193. {
  194.     if( pp_block && *pp_block )
  195.     {
  196.         Push( p_dec, *pp_block );
  197.         *pp_block = NULL;
  198.     }
  199.     for( ;; )
  200.     {
  201.         block_t *p_block = Pop( p_dec );
  202.         if( !p_block )
  203.             break;
  204.         subpicture_t *p_spu = Convert( p_dec, p_block );
  205.         if( p_spu )
  206.             return p_spu;
  207.     }
  208.     return NULL;
  209. }
  210. /*****************************************************************************
  211.  * CloseDecoder: clean up the decoder
  212.  *****************************************************************************/
  213. static void Close( vlc_object_t *p_this )
  214. {
  215.     decoder_t *p_dec = (decoder_t *)p_this;
  216.     decoder_sys_t *p_sys = p_dec->p_sys;
  217.     int i;
  218.     for( i = 0; i < p_sys->i_block; i++ )
  219.         block_Release( p_sys->pp_block[i] );
  220.     Eia608Exit( &p_sys->eia608 );
  221.     free( p_sys );
  222. }
  223. /*****************************************************************************
  224.  *
  225.  *****************************************************************************/
  226. static void Push( decoder_t *p_dec, block_t *p_block )
  227. {
  228.     decoder_sys_t *p_sys = p_dec->p_sys;
  229.     if( p_sys->i_block >= CC_MAX_REORDER_SIZE )
  230.     {
  231.         msg_Warn( p_dec, "Trashing a CC entry" );
  232.         memmove( &p_sys->pp_block[0], &p_sys->pp_block[1], sizeof(*p_sys->pp_block) * (CC_MAX_REORDER_SIZE-1) );
  233.         p_sys->i_block--;
  234.     }
  235.     p_sys->pp_block[p_sys->i_block++] = p_block;
  236. }
  237. static block_t *Pop( decoder_t *p_dec )
  238. {
  239.     decoder_sys_t *p_sys = p_dec->p_sys;
  240.     block_t *p_block;
  241.     int i_index;
  242.     int i;
  243.     /* XXX Cc captions data are OUT OF ORDER (because we receive them in the bitstream
  244.      * order (ie ordered by video picture dts) instead of the display order.
  245.      *  We will simulate a simple IPB buffer scheme
  246.      * and reorder with pts.
  247.      * XXX it won't work with H264 which use non out of order B picture or MMCO
  248.      */
  249.     /* Wait for a P and output all *previous* picture by pts order (for
  250.      * hierarchical B frames) */
  251.     if( p_sys->i_block <= 1 ||
  252.         ( p_sys->pp_block[p_sys->i_block-1]->i_flags & BLOCK_FLAG_TYPE_B ) )
  253.         return NULL;
  254.     p_block = p_sys->pp_block[i_index = 0];
  255.     if( p_block->i_pts > 0 )
  256.     {
  257.         for( i = 1; i < p_sys->i_block-1; i++ )
  258.         {
  259.             if( p_sys->pp_block[i]->i_pts > 0 && p_block->i_pts > 0 &&
  260.                 p_sys->pp_block[i]->i_pts < p_block->i_pts )
  261.                 p_block = p_sys->pp_block[i_index = i];
  262.         }
  263.     }
  264.     assert( i_index+1 < p_sys->i_block );
  265.     memmove( &p_sys->pp_block[i_index], &p_sys->pp_block[i_index+1], sizeof(*p_sys->pp_block) * ( p_sys->i_block - i_index - 1 ) );
  266.     p_sys->i_block--;
  267.     return p_block;
  268. }
  269. static subpicture_t *Subtitle( decoder_t *p_dec, char *psz_subtitle, char *psz_html, mtime_t i_pts )
  270. {
  271.     //decoder_sys_t *p_sys = p_dec->p_sys;
  272.     subpicture_t *p_spu = NULL;
  273.     video_format_t fmt;
  274.     /* We cannot display a subpicture with no date */
  275.     if( i_pts == 0 )
  276.     {
  277.         msg_Warn( p_dec, "subtitle without a date" );
  278.         return NULL;
  279.     }
  280.     EnsureUTF8( psz_subtitle );
  281.     if( psz_html )
  282.         EnsureUTF8( psz_html );
  283.     /* Create the subpicture unit */
  284.     p_spu = decoder_NewSubpicture( p_dec );
  285.     if( !p_spu )
  286.     {
  287.         msg_Warn( p_dec, "can't get spu buffer" );
  288.         free( psz_subtitle );
  289.         free( psz_html );
  290.         return NULL;
  291.     }
  292.     /* Create a new subpicture region */
  293.     memset( &fmt, 0, sizeof(video_format_t) );
  294.     fmt.i_chroma = VLC_FOURCC('T','E','X','T');
  295.     fmt.i_aspect = 0;
  296.     fmt.i_width = fmt.i_height = 0;
  297.     fmt.i_x_offset = fmt.i_y_offset = 0;
  298.     p_spu->p_region = subpicture_region_New( &fmt );
  299.     if( !p_spu->p_region )
  300.     {
  301.         msg_Err( p_dec, "cannot allocate SPU region" );
  302.         free( psz_subtitle );
  303.         free( psz_html );
  304.         decoder_DeleteSubpicture( p_dec, p_spu );
  305.         return NULL;
  306.     }
  307.     /* Decode and format the subpicture unit */
  308.     /* Normal text subs, easy markup */
  309.     p_spu->p_region->i_align = SUBPICTURE_ALIGN_BOTTOM;// | SUBPICTURE_ALIGN_LEFT;// | p_sys->i_align;
  310.     p_spu->p_region->i_x = 0; //p_sys->i_align ? 20 : 0;
  311.     p_spu->p_region->i_y = 10;
  312.     p_spu->p_region->psz_text = psz_subtitle;
  313.     p_spu->p_region->psz_html = psz_html;
  314.     p_spu->i_start = i_pts;
  315.     p_spu->i_stop = i_pts + 10000000;   /* 10s max */
  316.     p_spu->b_ephemer = true;
  317.     p_spu->b_absolute = false;
  318.     return p_spu;
  319. }
  320. static subpicture_t *Convert( decoder_t *p_dec, block_t *p_block )
  321. {
  322.     assert( p_block );
  323.     decoder_sys_t *p_sys = p_dec->p_sys;
  324.     const int64_t i_pts = p_block->i_pts;
  325.     bool b_changed = false;
  326.     /* TODO do the real decoding here */
  327.     while( p_block->i_buffer >= 3 )
  328.     {
  329.         if( p_block->p_buffer[0] == p_sys->i_field )
  330.             b_changed |= Eia608Parse( &p_sys->eia608, p_sys->i_channel, &p_block->p_buffer[1] );
  331.         p_block->i_buffer -= 3;
  332.         p_block->p_buffer += 3;
  333.     }
  334.     if( p_block )
  335.         block_Release( p_block );
  336.     if( b_changed )
  337.     {
  338.         char *psz_subtitle = Eia608Text( &p_sys->eia608, false );
  339.         char *psz_html     = NULL;//Eia608Text( &p_sys->eia608, true );
  340.         return Subtitle( p_dec, psz_subtitle, psz_html, i_pts );
  341.     }
  342.     return NULL;
  343. }
  344. /*****************************************************************************
  345.  *
  346.  *****************************************************************************/
  347. static const struct {
  348.     eia608_color_t  i_color;
  349.     eia608_font_t   i_font;
  350.     int             i_column;
  351. } pac2_attribs[]= {
  352.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,           0 },
  353.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,         0 },
  354.     { EIA608_COLOR_GREEN,   EIA608_FONT_REGULAR,           0 },
  355.     { EIA608_COLOR_GREEN,   EIA608_FONT_UNDERLINE,         0 },
  356.     { EIA608_COLOR_BLUE,    EIA608_FONT_REGULAR,           0 },
  357.     { EIA608_COLOR_BLUE,    EIA608_FONT_UNDERLINE,         0 },
  358.     { EIA608_COLOR_CYAN,    EIA608_FONT_REGULAR,           0 },
  359.     { EIA608_COLOR_CYAN,    EIA608_FONT_UNDERLINE,         0 },
  360.     { EIA608_COLOR_RED,     EIA608_FONT_REGULAR,           0 },
  361.     { EIA608_COLOR_RED,     EIA608_FONT_UNDERLINE,         0 },
  362.     { EIA608_COLOR_YELLOW,  EIA608_FONT_REGULAR,           0 },
  363.     { EIA608_COLOR_YELLOW,  EIA608_FONT_UNDERLINE,         0 },
  364.     { EIA608_COLOR_MAGENTA, EIA608_FONT_REGULAR,           0 },
  365.     { EIA608_COLOR_MAGENTA, EIA608_FONT_UNDERLINE,         0 },
  366.     { EIA608_COLOR_WHITE,   EIA608_FONT_ITALICS,           0 },
  367.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE_ITALICS, 0 },
  368.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,           0 },
  369.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,         0 },
  370.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,           4 },
  371.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,         4 },
  372.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,           8 },
  373.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,         8 },
  374.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,          12 },
  375.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,        12 },
  376.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,          16 },
  377.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,        16 },
  378.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,          20 },
  379.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,        20 },
  380.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,          24 },
  381.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,        24 },
  382.     { EIA608_COLOR_WHITE,   EIA608_FONT_REGULAR,          28 },
  383.     { EIA608_COLOR_WHITE,   EIA608_FONT_UNDERLINE,        28 } ,
  384. };
  385. #define EIA608_COLOR_DEFAULT EIA608_COLOR_WHITE
  386. static void Eia608Cursor( eia608_t *h, int dx )
  387. {
  388.     h->cursor.i_column += dx;
  389.     if( h->cursor.i_column < 0 )
  390.         h->cursor.i_column = 0;
  391.     else if( h->cursor.i_column > EIA608_SCREEN_COLUMNS-1 )
  392.         h->cursor.i_column = EIA608_SCREEN_COLUMNS-1;
  393. }
  394. static void Eia608ClearScreenRowX( eia608_t *h, int i_screen, int i_row, int x )
  395. {
  396.     eia608_screen *screen = &h->screen[i_screen];
  397.     int i;
  398.     if( x == 0 )
  399.     {
  400.         screen->row_used[i_row] = false;
  401.     }
  402.     else
  403.     {
  404.         screen->row_used[i_row] = false;
  405.         for( i = 0; i < x; i++ )
  406.         {
  407.             if( screen->characters[i_row][i] != ' ' ||
  408.                 screen->colors[i_row][i] != EIA608_COLOR_DEFAULT ||
  409.                 screen->fonts[i_row][i] != EIA608_FONT_REGULAR )
  410.             {
  411.                 screen->row_used[i_row] = true;
  412.                 break;
  413.             }
  414.         }
  415.     }
  416.     for( ; x < EIA608_SCREEN_COLUMNS+1; x++ )
  417.     {
  418.         screen->characters[i_row][x] = x < EIA608_SCREEN_COLUMNS ? ' ' : '';
  419.         screen->colors[i_row][x] = EIA608_COLOR_DEFAULT;
  420.         screen->fonts[i_row][x] = EIA608_FONT_REGULAR;
  421.     }
  422. }
  423. static void Eia608ClearScreenRow( eia608_t *h, int i_screen, int i_row )
  424. {
  425.     Eia608ClearScreenRowX( h, i_screen, i_row, 0 );
  426. }
  427. static void Eia608ClearScreen( eia608_t *h, int i_screen )
  428. {
  429.     int i;
  430.     for( i = 0; i < EIA608_SCREEN_ROWS; i++ )
  431.         Eia608ClearScreenRow( h, i_screen, i );
  432. }
  433. static int Eia608GetWritingScreenIndex( eia608_t *h )
  434. {
  435.     switch( h->mode )
  436.     {
  437.     case EIA608_MODE_POPUP:    // Non displayed screen
  438.         return 1 - h->i_screen;
  439.     case EIA608_MODE_ROLLUP_2: // Displayed screen
  440.     case EIA608_MODE_ROLLUP_3:
  441.     case EIA608_MODE_ROLLUP_4:
  442.     case EIA608_MODE_PAINTON:
  443.         return h->i_screen;
  444.     default:
  445.         /* It cannot happen, else it is a bug */
  446.         assert( 0 );
  447.         return 0;
  448.     }
  449. }
  450. static void Eia608EraseScreen( eia608_t *h, bool b_displayed )
  451. {
  452.     Eia608ClearScreen( h, b_displayed ? h->i_screen : (1-h->i_screen) );
  453. }
  454. static void Eia608Write( eia608_t *h, const uint8_t c )
  455. {
  456.     const int i_row = h->cursor.i_row;
  457.     const int i_column = h->cursor.i_column;
  458.     eia608_screen *screen;
  459.     if( h->mode == EIA608_MODE_TEXT )
  460.         return;
  461.     screen = &h->screen[Eia608GetWritingScreenIndex( h )];
  462.     screen->characters[i_row][i_column] = c;
  463.     screen->colors[i_row][i_column] = h->color;
  464.     screen->fonts[i_row][i_column] = h->font;
  465.     screen->row_used[i_row] = true;
  466.     Eia608Cursor( h, 1 );
  467. }
  468. static void Eia608Erase( eia608_t *h )
  469. {
  470.     const int i_row = h->cursor.i_row;
  471.     const int i_column = h->cursor.i_column - 1;
  472.     eia608_screen *screen;
  473.     if( h->mode == EIA608_MODE_TEXT )
  474.         return;
  475.     if( i_column < 0 )
  476.         return;
  477.     screen = &h->screen[Eia608GetWritingScreenIndex( h )];
  478.     /* FIXME do we need to reset row_used/colors/font ? */
  479.     screen->characters[i_row][i_column] = ' ';
  480.     Eia608Cursor( h, -1 );
  481. }
  482. static void Eia608EraseToEndOfRow( eia608_t *h )
  483. {
  484.     if( h->mode == EIA608_MODE_TEXT )
  485.         return;
  486.     Eia608ClearScreenRowX( h, Eia608GetWritingScreenIndex( h ), h->cursor.i_row, h->cursor.i_column );
  487. }
  488. static void Eia608RollUp( eia608_t *h )
  489. {
  490.     const int i_screen = Eia608GetWritingScreenIndex( h );
  491.     eia608_screen *screen = &h->screen[i_screen];
  492.     int keep_lines;
  493.     int i;
  494.     /* Window size */
  495.     if( h->mode == EIA608_MODE_ROLLUP_2 )
  496.         keep_lines = 2;
  497.     else if( h->mode == EIA608_MODE_ROLLUP_3 )
  498.         keep_lines = 3;
  499.     else if( h->mode == EIA608_MODE_ROLLUP_4 )
  500.         keep_lines = 4;
  501.     else
  502.         return;
  503.     /* Reset the cursor */
  504.     h->cursor.i_column = 0;
  505.     /* Erase lines above our window */
  506.     for( i = 0; i < h->cursor.i_row - keep_lines; i++ )
  507.         Eia608ClearScreenRow( h, i_screen, i );
  508.     /* Move up */
  509.     for( i = 0; i < keep_lines-1; i++ )
  510.     {
  511.         const int i_row = h->cursor.i_row - keep_lines + i + 1;
  512.         if( i_row < 0 )
  513.             continue;
  514.         assert( i_row+1 < EIA608_SCREEN_ROWS );
  515.         memcpy( screen->characters[i_row], screen->characters[i_row+1], sizeof(*screen->characters) );
  516.         memcpy( screen->colors[i_row], screen->colors[i_row+1], sizeof(*screen->colors) );
  517.         memcpy( screen->fonts[i_row], screen->fonts[i_row+1], sizeof(*screen->fonts) );
  518.         screen->row_used[i_row] = screen->row_used[i_row+1];
  519.     }
  520.     /* Reset current row */
  521.     Eia608ClearScreenRow( h, i_screen, h->cursor.i_row );
  522. }
  523. static void Eia608ParseChannel( eia608_t *h, uint8_t d[2] )
  524. {
  525.     /* Check odd parity */
  526.     static const int p4[16] = {
  527.         0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 0
  528.     };
  529.     if( p4[d[0] & 0xf] == p4[d[0] >> 4] ||
  530.         p4[d[1] & 0xf] == p4[ d[1] >> 4] )
  531.     {
  532.         h->i_channel = -1;
  533.         return;
  534.     }
  535.     /* */
  536.     const int d1 = d[0] & 0x7f;
  537.     const int d2 = d[1] & 0x7f;
  538.     if( d1 == 0x14 )
  539.         h->i_channel = 1;
  540.     else if( d1 == 0x1c )
  541.         h->i_channel = 2;
  542.     else if( d1 == 0x15 )
  543.         h->i_channel = 3;
  544.     else if( d1 == 0x1d )
  545.         h->i_channel = 4;
  546. }
  547. static bool Eia608ParseTextAttribute( eia608_t *h, uint8_t d2 )
  548. {
  549.     const int i_index = d2 - 0x20;
  550.     assert( d2 >= 0x20 && d2 <= 0x2f );
  551.     h->color = pac2_attribs[i_index].i_color;
  552.     h->font  = pac2_attribs[i_index].i_font;
  553.     Eia608Cursor( h, 1 );
  554.     return false;
  555. }
  556. static bool Eia608ParseSingle( eia608_t *h, const uint8_t dx )
  557. {
  558.     assert( dx >= 0x20 );
  559.     Eia608Write( h, dx );
  560.     return true;
  561. }
  562. static bool Eia608ParseDouble( eia608_t *h, uint8_t d2 )
  563. {
  564.     assert( d2 >= 0x30 && d2 <= 0x3f );
  565.     Eia608Write( h, d2 + 0x50 ); /* We use charaters 0x80...0x8f */
  566.     return true;
  567. }
  568. static bool Eia608ParseExtended( eia608_t *h, uint8_t d1, uint8_t d2 )
  569. {
  570.     assert( d2 >= 0x20 && d2 <= 0x3f );
  571.     assert( d1 == 0x12 || d1 == 0x13 );
  572.     if( d1 == 0x12 )
  573.         d2 += 0x70; /* We use charaters 0x90-0xaf */
  574.     else
  575.         d2 += 0x90; /* We use charaters 0xb0-0xcf */
  576.     /* The extended characters replace the previous one with a more
  577.      * advanced one */
  578.     Eia608Cursor( h, -1 );
  579.     Eia608Write( h, d2 );
  580.     return true;
  581. }
  582. static bool Eia608ParseCommand0x14( eia608_t *h, uint8_t d2 )
  583. {
  584.     bool b_changed = false;
  585.     switch( d2 )
  586.     {
  587.     case 0x20:  /* Resume caption loading */
  588.         h->mode = EIA608_MODE_POPUP;
  589.         break;
  590.     case 0x21:  /* Backspace */
  591.         Eia608Erase( h );
  592.         b_changed = true;
  593.         break;
  594.     case 0x22:  /* Reserved */
  595.     case 0x23:
  596.         break;
  597.     case 0x24:  /* Delete to end of row */
  598.         Eia608EraseToEndOfRow( h );
  599.         break;
  600.     case 0x25:  /* Rollup 2 */
  601.     case 0x26:  /* Rollup 3 */
  602.     case 0x27:  /* Rollup 4 */
  603.         if( h->mode == EIA608_MODE_POPUP || h->mode == EIA608_MODE_PAINTON )
  604.         {
  605.             Eia608EraseScreen( h, true );
  606.             Eia608EraseScreen( h, false );
  607.             b_changed = true;
  608.         }
  609.         if( d2 == 0x25 )
  610.             h->mode = EIA608_MODE_ROLLUP_2;
  611.         else if( d2 == 0x26 )
  612.             h->mode = EIA608_MODE_ROLLUP_3;
  613.         else
  614.             h->mode = EIA608_MODE_ROLLUP_4;
  615.         h->cursor.i_column = 0;
  616.         h->cursor.i_row = h->i_row_rollup;
  617.         break;
  618.     case 0x28:  /* Flash on */
  619.         /* TODO */
  620.         break;
  621.     case 0x29:  /* Resume direct captionning */
  622.         h->mode = EIA608_MODE_PAINTON;
  623.         break;
  624.     case 0x2a:  /* Text restart */
  625.         /* TODO */
  626.         break;
  627.     case 0x2b: /* Resume text display */
  628.         h->mode = EIA608_MODE_TEXT;
  629.         break;
  630.     case 0x2c: /* Erase displayed memory */
  631.         Eia608EraseScreen( h, true );
  632.         b_changed = true;
  633.         break;
  634.     case 0x2d: /* Carriage return */
  635.         Eia608RollUp(h);
  636.         b_changed = true;
  637.         break;
  638.     case 0x2e: /* Erase non displayed memory */
  639.         Eia608EraseScreen( h, false );
  640.         break;
  641.     case 0x2f: /* End of caption (flip screen if not paint on) */
  642.         if( h->mode != EIA608_MODE_PAINTON )
  643.             h->i_screen = 1 - h->i_screen;
  644.         h->mode = EIA608_MODE_POPUP;
  645.         h->cursor.i_column = 0;
  646.         h->cursor.i_row = 0;
  647.         h->color = EIA608_COLOR_DEFAULT;
  648.         h->font = EIA608_FONT_REGULAR;
  649.         b_changed = true;
  650.         break;
  651.     }
  652.     return b_changed;
  653. }
  654. static bool Eia608ParseCommand0x17( eia608_t *h, uint8_t d2 )
  655. {
  656.     switch( d2 )
  657.     {
  658.     case 0x21:  /* Tab offset 1 */
  659.         Eia608Cursor( h, 1 );
  660.         break;
  661.     case 0x22:  /* Tab offset 2 */
  662.         Eia608Cursor( h, 2 );
  663.         break;
  664.     case 0x23:  /* Tab offset 3 */
  665.         Eia608Cursor( h, 3 );
  666.         break;
  667.     }
  668.     return false;
  669. }
  670. static bool Eia608ParsePac( eia608_t *h, uint8_t d1, uint8_t d2 )
  671. {
  672.     static const int pi_row[] = {
  673.         11, -1, 1, 2, 3, 4, 12, 13, 14, 15, 5, 6, 7, 8, 9, 10
  674.     };
  675.     const int i_row_index = ( (d1<<1) & 0x0e) | ( (d2>>5) & 0x01 );
  676.     assert( d2 >= 0x40 && d2 <= 0x7f );
  677.     if( pi_row[i_row_index] <= 0 )
  678.         return false;
  679.     /* Row */
  680.     if( h->mode != EIA608_MODE_TEXT )
  681.         h->cursor.i_row = pi_row[i_row_index] - 1;
  682.     h->i_row_rollup = pi_row[i_row_index] - 1;
  683.     /* Column */
  684.     if( d2 >= 0x60 )
  685.         d2 -= 0x60;
  686.     else if( d2 >= 0x40 )
  687.         d2 -= 0x40;
  688.     h->cursor.i_column = pac2_attribs[d2].i_column;
  689.     return false;
  690. }
  691. static bool Eia608ParseData( eia608_t *h, uint8_t d1, uint8_t d2 )
  692. {
  693.     bool b_changed = false;
  694.     if( d1 >= 0x18 && d1 <= 0x1f )
  695.         d1 -= 8;
  696. #define ON( d2min, d2max, cmd ) do { if( d2 >= d2min && d2 <= d2max ) b_changed = cmd; } while(0)
  697.     switch( d1 )
  698.     {
  699.     case 0x11:
  700.         ON( 0x20, 0x2f, Eia608ParseTextAttribute( h, d2 ) );
  701.         ON( 0x30, 0x3f, Eia608ParseDouble( h, d2 ) );
  702.         break;
  703.     case 0x12: case 0x13:
  704.         ON( 0x20, 0x3f, Eia608ParseExtended( h, d1, d2 ) );
  705.         break;
  706.     case 0x14: case 0x15:
  707.         ON( 0x20, 0x2f, Eia608ParseCommand0x14( h, d2 ) );
  708.         break;
  709.     case 0x17:
  710.         ON( 0x21, 0x22, Eia608ParseCommand0x17( h, d2 ) );
  711.         ON( 0x2e, 0x2f, Eia608ParseTextAttribute( h, d2 ) );
  712.         break;
  713.     }
  714.     if( d1 == 0x10 )
  715.         ON( 0x40, 0x5f, Eia608ParsePac( h, d1, d2 ) );
  716.     else if( d1 >= 0x11 && d1 <= 0x17 )
  717.         ON( 0x40, 0x7f, Eia608ParsePac( h, d1, d2 ) );
  718. #undef ON
  719.     if( d1 >= 0x20 )
  720.     {
  721.         b_changed = Eia608ParseSingle( h, d1 );
  722.         if( d2 >= 0x20 )
  723.             b_changed |= Eia608ParseSingle( h, d2 );
  724.     }
  725.     return b_changed;
  726. }
  727. static void Eia608TextUtf8( char *psz_utf8, uint8_t c ) // Returns number of bytes used
  728. {
  729. #define E1(c,u) { c, { u, '' } }
  730. #define E2(c,u1,u2) { c, { u1, u2, '' } }
  731. #define E3(c,u1,u2,u3) { c, { u1, u2, u3, '' } }
  732.     static const struct {
  733.         uint8_t c;
  734.         char utf8[3+1];
  735.     } c2utf8[] = {
  736.         // Regular line-21 character set, mostly ASCII except these exceptions
  737.         E2( 0x2a, 0xc3,0xa1), // lowercase a, acute accent
  738.         E2( 0x5c, 0xc3,0xa9), // lowercase e, acute accent
  739.         E2( 0x5e, 0xc3,0xad), // lowercase i, acute accent
  740.         E2( 0x5f, 0xc3,0xb3), // lowercase o, acute accent
  741.         E2( 0x60, 0xc3,0xba), // lowercase u, acute accent
  742.         E2( 0x7b, 0xc3,0xa7), // lowercase c with cedilla
  743.         E2( 0x7c, 0xc3,0xb7), // division symbol
  744.         E2( 0x7d, 0xc3,0x91), // uppercase N tilde
  745.         E2( 0x7e, 0xc3,0xb1), // lowercase n tilde
  746.         // THIS BLOCK INCLUDES THE 16 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
  747.         // THAT COME FROM HI BYTE=0x11 AND LOW BETWEEN 0x30 AND 0x3F
  748.         E2( 0x80, 0xc2,0xae), // Registered symbol (R)
  749.         E2( 0x81, 0xc2,0xb0), // degree sign
  750.         E2( 0x82, 0xc2,0xbd), // 1/2 symbol
  751.         E2( 0x83, 0xc2,0xbf), // Inverted (open) question mark
  752.         E3( 0x84, 0xe2,0x84,0xa2), // Trademark symbol (TM)
  753.         E2( 0x85, 0xc2,0xa2), // Cents symbol
  754.         E2( 0x86, 0xc2,0xa3), // Pounds sterling
  755.         E3( 0x87, 0xe2,0x99,0xaa), // Music note
  756.         E2( 0x88, 0xc3,0xa0), // lowercase a, grave accent
  757.         E1( 0x89, 0x20), // transparent space, we make it regular
  758.         E2( 0x8a, 0xc3,0xa8), // lowercase e, grave accent
  759.         E2( 0x8b, 0xc3,0xa2), // lowercase a, circumflex accent
  760.         E2( 0x8c, 0xc3,0xaa), // lowercase e, circumflex accent
  761.         E2( 0x8d, 0xc3,0xae), // lowercase i, circumflex accent
  762.         E2( 0x8e, 0xc3,0xb4), // lowercase o, circumflex accent
  763.         E2( 0x8f, 0xc3,0xbb), // lowercase u, circumflex accent
  764.         // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
  765.         // THAT COME FROM HI BYTE=0x12 AND LOW BETWEEN 0x20 AND 0x3F
  766.         E2( 0x90, 0xc3,0x81), // capital letter A with acute
  767.         E2( 0x91, 0xc3,0x89), // capital letter E with acute
  768.         E2( 0x92, 0xc3,0x93), // capital letter O with acute
  769.         E2( 0x93, 0xc3,0x9a), // capital letter U with acute
  770.         E2( 0x94, 0xc3,0x9c), // capital letter U with diaresis
  771.         E2( 0x95, 0xc3,0xbc), // lowercase letter U with diaeresis
  772.         E1( 0x96, 0x27), // apostrophe
  773.         E2( 0x97, 0xc1,0xa1), // inverted exclamation mark
  774.         E1( 0x98, 0x2a), // asterisk
  775.         E1( 0x99, 0x27), // apostrophe (yes, duped). See CCADI source code.
  776.         E1( 0x9a, 0x2d), // hyphen-minus
  777.         E2( 0x9b, 0xc2,0xa9), // copyright sign
  778.         E3( 0x9c, 0xe2,0x84,0xa0), // Service mark
  779.         E1( 0x9d, 0x2e), // Full stop (.)
  780.         E1( 0x9e, 0x22), // Quoatation mark
  781.         E1( 0x9f, 0x22), // Quoatation mark
  782.         E2( 0xa0, 0xc3,0x80), // uppercase A, grave accent
  783.         E2( 0xa1, 0xc3,0x82), // uppercase A, circumflex
  784.         E2( 0xa2, 0xc3,0x87), // uppercase C with cedilla
  785.         E2( 0xa3, 0xc3,0x88), // uppercase E, grave accent
  786.         E2( 0xa4, 0xc3,0x8a), // uppercase E, circumflex
  787.         E2( 0xa5, 0xc3,0x8b), // capital letter E with diaresis
  788.         E2( 0xa6, 0xc3,0xab), // lowercase letter e with diaresis
  789.         E2( 0xa7, 0xc3,0x8e), // uppercase I, circumflex
  790.         E2( 0xa8, 0xc3,0x8f), // uppercase I, with diaresis
  791.         E2( 0xa9, 0xc3,0xaf), // lowercase i, with diaresis
  792.         E2( 0xaa, 0xc3,0x94), // uppercase O, circumflex
  793.         E2( 0xab, 0xc3,0x99), // uppercase U, grave accent
  794.         E2( 0xac, 0xc3,0xb9), // lowercase u, grave accent
  795.         E2( 0xad, 0xc3,0x9b), // uppercase U, circumflex
  796.         E2( 0xae, 0xc2,0xab), // LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
  797.         E2( 0xaf, 0xc2,0xbb), // RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
  798.         // THIS BLOCK INCLUDES THE 32 EXTENDED (TWO-BYTE) LINE 21 CHARACTERS
  799.         // THAT COME FROM HI BYTE=0x13 AND LOW BETWEEN 0x20 AND 0x3F
  800.         E2( 0xb0, 0xc3,0x83), // Uppercase A, tilde
  801.         E2( 0xb1, 0xc3,0xa3), // Lowercase a, tilde
  802.         E2( 0xb2, 0xc3,0x8d), // Uppercase I, acute accent
  803.         E2( 0xb3, 0xc3,0x8c), // Uppercase I, grave accent
  804.         E2( 0xb4, 0xc3,0xac), // Lowercase i, grave accent
  805.         E2( 0xb5, 0xc3,0x92), // Uppercase O, grave accent
  806.         E2( 0xb6, 0xc3,0xb2), // Lowercase o, grave accent
  807.         E2( 0xb7, 0xc3,0x95), // Uppercase O, tilde
  808.         E2( 0xb8, 0xc3,0xb5), // Lowercase o, tilde
  809.         E1( 0xb9, 0x7b), // Open curly brace
  810.         E1( 0xba, 0x7d), // Closing curly brace
  811.         E1( 0xbb, 0x5c), // Backslash
  812.         E1( 0xbc, 0x5e), // Caret
  813.         E1( 0xbd, 0x5f), // Underscore
  814.         E2( 0xbe, 0xc2,0xa6), // Pipe (broken bar)
  815.         E1( 0xbf, 0x7e), // Tilde (utf8 code unsure)
  816.         E2( 0xc0, 0xc3,0x84), // Uppercase A, umlaut
  817.         E2( 0xc1, 0xc3,0xa4), // Lowercase A, umlaut
  818.         E2( 0xc2, 0xc3,0x96), // Uppercase O, umlaut
  819.         E2( 0xc3, 0xc3,0xb6), // Lowercase o, umlaut
  820.         E2( 0xc4, 0xc3,0x9f), // Esszett (sharp S)
  821.         E2( 0xc5, 0xc2,0xa5), // Yen symbol
  822.         E2( 0xc6, 0xc2,0xa4), // Currency symbol
  823.         E1( 0xc7, 0x7c), // Vertical bar
  824.         E2( 0xc8, 0xc3,0x85), // Uppercase A, ring
  825.         E2( 0xc9, 0xc3,0xa5), // Lowercase A, ring
  826.         E2( 0xca, 0xc3,0x98), // Uppercase O, slash
  827.         E2( 0xcb, 0xc3,0xb8), // Lowercase o, slash
  828.         E3( 0xcc, 0xe2,0x8c,0x9c), // Upper left corner
  829.         E3( 0xcd, 0xe2,0x8c,0x9d), // Upper right corner
  830.         E3( 0xce, 0xe2,0x8c,0x9e), // Lower left corner
  831.         E3( 0xcf, 0xe2,0x8c,0x9f), // Lower right corner
  832.         E1(0,0)
  833.     };
  834. #undef E3
  835. #undef E2
  836. #undef E1
  837.     static const int i_c2utf8 = sizeof(c2utf8)/sizeof(*c2utf8);
  838.     int i;
  839.     for( i = 0; i < i_c2utf8; i++ )
  840.     {
  841.         if( c2utf8[i].c == c )
  842.             break;
  843.     }
  844.     if( i >= i_c2utf8 )
  845.     {
  846.         psz_utf8[0] = c < 0x80 ? c : '?';   /* Normal : Unsupported */
  847.         psz_utf8[1] = '';
  848.     }
  849.     else
  850.     {
  851.         strcpy( psz_utf8, c2utf8[i].utf8 );
  852.     }
  853. }
  854. static void Eia608Strlcat( char *d, const char *s, int i_max )
  855. {
  856.     if( i_max > 1 )
  857.         strncat( d, s, i_max-1 - strnlen(d, i_max-1));
  858.     if( i_max > 0 )
  859.         d[i_max-1] = '';
  860. }
  861. static void Eia608TextLine( struct eia608_screen *screen, char *psz_text, int i_text_max, int i_row, bool b_html )
  862. {
  863.     const uint8_t *p_char = screen->characters[i_row];
  864.     const eia608_color_t *p_color = screen->colors[i_row];
  865.     const eia608_font_t *p_font = screen->fonts[i_row];
  866.     int i_start;
  867.     int i_end;
  868.     int x;
  869.     eia608_color_t last_color = EIA608_COLOR_DEFAULT;
  870.     bool     b_last_italics = false;
  871.     bool     b_last_underline = false;
  872.     /* Search the start */
  873.     i_start = 0;
  874.     while( i_start < EIA608_SCREEN_COLUMNS-1 && p_char[i_start] == ' ' )
  875.         i_start++;
  876.     /* Search the end */
  877.     i_end = EIA608_SCREEN_COLUMNS-1;
  878.     while( i_end > i_start && p_char[i_end] == ' ' )
  879.         i_end--;
  880.     /* */
  881. #define CAT(t) Eia608Strlcat( psz_text, t, i_text_max )
  882.     for( x = i_start; x <= i_end; x++ )
  883.     {
  884.         eia608_color_t color = p_color[x];
  885.         bool b_italics = p_font[x] & EIA608_FONT_ITALICS;
  886.         bool b_underline = p_font[x] & EIA608_FONT_UNDERLINE;
  887.         char utf8[4];
  888.         /* */
  889.         if( b_html )
  890.         {
  891.             bool b_close_color, b_close_italics, b_close_underline;
  892.             /* We create the tags font / i / u in that orders */
  893.             b_close_color = color != last_color && last_color != EIA608_COLOR_DEFAULT;
  894.             b_close_italics = !b_italics && b_last_italics;
  895.             b_close_underline = !b_underline && b_last_underline;
  896.             /* Be sure to create valid html */
  897.             b_close_italics |= b_last_italics && b_close_color;
  898.             b_close_underline = b_last_underline && ( b_close_italics || b_close_color );
  899.             if( b_close_underline )
  900.                 CAT( "</u>" );
  901.             if( b_close_italics )
  902.                 CAT( "</i>" );
  903.             if( b_close_color )
  904.                 CAT( "</font>" );
  905.             if( color != EIA608_COLOR_DEFAULT && color != last_color)
  906.             {
  907.                 static const char *ppsz_color[] = {
  908.                     "#ffffff",  // white
  909.                     "#00ff00",  // green
  910.                     "#0000ff",  // blue
  911.                     "#00ffff",  // cyan
  912.                     "#ff0000",  // red
  913.                     "#ffff00",  // yellow
  914.                     "#ff00ff",  // magenta
  915.                     "#ffffff",  // user defined XXX we use white
  916.                 };
  917.                 CAT( "<font color=" );
  918.                 CAT( ppsz_color[color] );
  919.                 CAT( ">" );
  920.             }
  921.             if( ( b_close_italics && b_italics ) || ( b_italics && !b_last_italics ) )
  922.                 CAT( "<i>" );
  923.             if( ( b_close_underline && b_underline ) || ( b_underline && !b_last_underline ) )
  924.                 CAT( "<u>" );
  925.         }
  926.         /* */ 
  927.         Eia608TextUtf8( utf8, p_char[x] );
  928.         CAT( utf8 );
  929.         /* */
  930.         b_last_underline = b_underline;
  931.         b_last_italics = b_italics;
  932.         last_color = color;
  933.     }
  934.     if( b_html )
  935.     {
  936.         if( b_last_underline )
  937.             CAT( "</u>" );
  938.         if( b_last_italics )
  939.             CAT( "</i>" );
  940.         if( last_color != EIA608_COLOR_DEFAULT )
  941.             CAT( "</font>" );
  942.     }
  943. #undef CAT
  944. }
  945. /* */
  946. static void Eia608Init( eia608_t *h )
  947. {
  948.     memset( h, 0, sizeof(*h) );
  949.     /* */
  950.     h->i_channel = -1;
  951.     h->i_screen = 0;
  952.     Eia608ClearScreen( h, 0 );
  953.     Eia608ClearScreen( h, 1 );
  954.     /* Cursor for writing text */
  955.     h->cursor.i_column = 0;
  956.     h->cursor.i_row = 0;
  957.     h->last.d1 = 0x00;
  958.     h->last.d2 = 0x00;
  959.     h->mode = EIA608_MODE_POPUP;
  960.     h->color = EIA608_COLOR_DEFAULT;
  961.     h->font = EIA608_FONT_REGULAR;
  962.     h->i_row_rollup = EIA608_SCREEN_ROWS-1;
  963. }
  964. static bool Eia608Parse( eia608_t *h, int i_channel_selected, const uint8_t data[2] )
  965. {
  966.     const uint8_t d1 = data[0] & 0x7f; /* Removed parity bit */
  967.     const uint8_t d2 = data[1] & 0x7f;
  968.     bool b_screen_changed = false;
  969.     if( d1 == 0 && d2 == 0 )
  970.         return false;   /* Ignore padding (parity check are sometimes invalid on them) */
  971.     Eia608ParseChannel( h, data );
  972.     if( h->i_channel != i_channel_selected )
  973.         return false;
  974.     //fprintf( stderr, "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC %x %xn", data[0], data[1] );
  975.     if( d1 >= 0x10 )
  976.     {
  977.         if( d1 >= 0x20 ||
  978.             d1 != h->last.d1 || d2 != h->last.d2 ) /* Command codes can be repeated */
  979.             b_screen_changed = Eia608ParseData( h, d1,d2 );
  980.         h->last.d1 = d1;
  981.         h->last.d2 = d2;
  982.     }
  983.     else if( ( d1 >= 0x01 && d1 <= 0x0E ) || d1 == 0x0F )
  984.     {
  985.         /* XDS block / End of XDS block */
  986.     }
  987.     return b_screen_changed;
  988. }
  989. static char *Eia608Text( eia608_t *h, bool b_html )
  990. {
  991.     const int i_size = EIA608_SCREEN_ROWS * 3 * EIA608_SCREEN_COLUMNS+1;
  992.     struct eia608_screen *screen = &h->screen[h->i_screen];
  993.     bool b_first = true;
  994.     char *psz;
  995.     int i;
  996.     /* We allocate a buffer big enough for normal case */
  997.     psz = malloc( i_size );
  998.     if( !psz )
  999.         return NULL;
  1000.     *psz = '';
  1001.     if( b_html )
  1002.         Eia608Strlcat( psz, "<text>", i_size );
  1003.     for( i = 0; i < EIA608_SCREEN_ROWS; i++ )
  1004.     {
  1005.         if( !screen->row_used[i] )
  1006.             continue;
  1007.         if( !b_first )
  1008.             Eia608Strlcat( psz, b_html ? "<br />" : "n", i_size );
  1009.         b_first = false;
  1010.         Eia608TextLine( screen, psz, i_size, i, b_html );
  1011.     }
  1012.     if( b_html )
  1013.         Eia608Strlcat( psz, "</text>", i_size );
  1014.     return psz;
  1015. }
  1016. static void Eia608Exit( eia608_t *h )
  1017. {
  1018. }