/*
* libzvbi - Teletext formatter
*
* Copyright (C) 2000, 2001 Michael H. Schimek
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* $Id: teletext.c,v 1.23 2006/05/24 04:46:45 mschimek Exp $ */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "site_def.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h> /* strncasecmp */
#include <ctype.h>
#include <assert.h>
#include "bcd.h"
#include "vt.h"
#include "export.h"
#include "vbi.h"
#include "hamm.h"
#include "lang.h"
extern const char _zvbi_intl_domainname[];
#include "intl-priv.h"
#define DEBUG 0
#if DEBUG
#define printv(templ, args...) fprintf(stderr, templ ,##args)
#else
#define printv(templ, args...) \
do { \
if (0) \
fprintf(stderr, templ ,##args); \
} while (0)
#endif
#define ROWS 25
#define COLUMNS 40
#define EXT_COLUMNS 41
#define LAST_ROW ((ROWS - 1) * EXT_COLUMNS)
/*
* FLOF navigation
*/
static const vbi_color
flof_link_col[4] = { VBI_RED, VBI_GREEN, VBI_YELLOW, VBI_CYAN };
static inline void
flof_navigation_bar(vbi_page *pg, vt_page *vtp)
{
vbi_char ac;
int n, i, k, ii;
memset(&ac, 0, sizeof(ac));
ac.foreground = VBI_WHITE;
ac.background = VBI_BLACK;
ac.opacity = pg->page_opacity[1];
ac.unicode = 0x0020;
for (i = 0; i < EXT_COLUMNS; i++)
pg->text[LAST_ROW + i] = ac;
ac.link = TRUE;
for (i = 0; i < 4; i++) {
ii = i * 10 + 3;
for (k = 0; k < 3; k++) {
n = ((vtp->data.lop.link[i].pgno >> ((2 - k) * 4)) & 15) + '0';
if (n > '9')
n += 'A' - '9';
ac.unicode = n;
ac.foreground = flof_link_col[i];
pg->text[LAST_ROW + ii + k] = ac;
pg->nav_index[ii + k] = i;
}
pg->nav_link[i].pgno = vtp->data.lop.link[i].pgno;
pg->nav_link[i].subno = vtp->data.lop.link[i].subno;
}
}
static inline void
flof_links(vbi_page *pg, vt_page *vtp)
{
vbi_char *acp = pg->text + LAST_ROW;
int i, j, k, col = -1, start = 0;
for (i = 0; i < COLUMNS + 1; i++) {
if (i == COLUMNS || (acp[i].foreground & 7) != col) {
for (k = 0; k < 4; k++)
if ((int) flof_link_col[k] == col)
break;
if (k < 4 && !NO_PAGE(vtp->data.lop.link[k].pgno)) {
/* Leading and trailing spaces not sensitive */
for (j = i - 1; j >= start && acp[j].unicode == 0x0020; j--);
for (; j >= start; j--) {
acp[j].link = TRUE;
pg->nav_index[j] = k;
}
pg->nav_link[k].pgno = vtp->data.lop.link[k].pgno;
pg->nav_link[k].subno = vtp->data.lop.link[k].subno;
}
if (i >= COLUMNS)
break;
col = acp[i].foreground & 7;
start = i;
}
if (start == i && acp[i].unicode == 0x0020)
start++;
}
}
/*
* TOP navigation
*/
static void character_set_designation(struct vbi_font_descr **font,
vt_extension *ext, vt_page *vtp);
static void screen_color(vbi_page *pg, int flags, int color);
static vbi_bool
top_label(vbi_decoder *vbi, vbi_page *pg, struct vbi_font_descr *font,
int index, int pgno, int foreground, int ff)
{
int column = index * 13 + 1;
vt_page *vtp;
vbi_char *acp;
ait_entry *ait;
int i, j;
acp = &pg->text[LAST_ROW + column];
for (i = 0; i < 8; i++)
if (vbi->vt.btt_link[i].type == 2) {
vtp = vbi_cache_get(vbi,
vbi->vt.btt_link[i].pgno,
vbi->vt.btt_link[i].subno, 0x3f7f);
if (!vtp) {
printv("top ait page %x not cached\n", vbi->vt.btt_link[i].pgno);
continue;
} else if (vtp->function != PAGE_FUNCTION_AIT) {
printv("no ait page %x\n", vtp->pgno);
continue;
}
for (ait = vtp->data.ait, j = 0; j < 46; ait++, j++)
if (ait->page.pgno == pgno) {
pg->nav_link[index].pgno = pgno;
pg->nav_link[index].subno = VBI_ANY_SUBNO;
for (i = 11; i >= 0; i--)
if (ait->text[i] > 0x20)
break;
if (ff && (i <= (11 - ff))) {
acp += (11 - ff - i) >> 1;
column += (11 - ff - i) >> 1;
acp[i + 1].link = TRUE;
pg->nav_index[column + i + 1] = index;
acp[i + 2].unicode = 0x003E;
acp[i + 2].foreground = foreground;
acp[i + 2].link = TRUE;
pg->nav_index[column + i + 2] = index;
if (ff > 1) {
acp[i + 3].unicode = 0x003E;
acp[i + 3].foreground = foreground;
acp[i + 3].link = TRUE;
pg->nav_index[column + i + 3] = index;
}
} else {
acp += (11 - i) >> 1;
column += (11 - i) >> 1;
}
for (; i >= 0; i--) {
acp[i].unicode = vbi_teletext_unicode(font->G0, font->subset,
(ait->text[i] < 0x20) ? 0x20 : ait->text[i]);
acp[i].foreground = foreground;
acp[i].link = TRUE;
pg->nav_index[column + i] = index;
}
return TRUE;
}
}
return FALSE;
}
static __inline__ vbi_pgno
add_modulo (vbi_pgno pgno,
int incr)
{
return ((pgno - 0x100 + incr) & 0x7FF) + 0x100;
}
static inline void
top_navigation_bar(vbi_decoder *vbi, vbi_page *pg,
vt_page *vtp)
{
vbi_char ac;
vbi_pgno pgno1;
int i, got;
printv("PAGE MIP/BTT: %d\n", vbi->vt.page_info[vtp->pgno - 0x100].code);
memset(&ac, 0, sizeof(ac));
ac.foreground = 32 + VBI_WHITE;
ac.background = 32 + VBI_BLACK;
ac.opacity = pg->page_opacity[1];
ac.unicode = 0x0020;
for (i = 0; i < EXT_COLUMNS; i++)
pg->text[LAST_ROW + i] = ac;
if (pg->page_opacity[1] != VBI_OPAQUE)
return;
pgno1 = add_modulo (vtp->pgno, 1);
for (i = vtp->pgno; i != pgno1; i = add_modulo (i, -1))
if (vbi->vt.page_info[i - 0x100].code == VBI_TOP_BLOCK ||
vbi->vt.page_info[i - 0x100].code == VBI_TOP_GROUP) {
top_label(vbi, pg, pg->font[0], 0, i, 32 + VBI_WHITE, 0);
break;
}
for (i = pgno1, got = FALSE; i != vtp->pgno; i = add_modulo (i, 1))
switch (vbi->vt.page_info[i - 0x100].code) {
case VBI_TOP_BLOCK:
top_label(vbi, pg, pg->font[0], 2, i, 32 + VBI_YELLOW, 2);
return;
case VBI_TOP_GROUP:
if (!got) {
top_label(vbi, pg, pg->font[0], 1, i, 32 + VBI_GREEN, 1);
got = TRUE;
}
break;
}
}
static ait_entry *
next_ait(vbi_decoder *vbi, int pgno, int subno, vt_page **mvtp)
{
vt_page *vtp;
ait_entry *ait, *mait = NULL;
int mpgno = 0xFFF, msubno = 0xFFFF;
int i, j;
*mvtp = NULL;
for (i = 0; i < 8; i++) {
if (vbi->vt.btt_link[i].type == 2) {
vtp = vbi_cache_get(vbi,
vbi->vt.btt_link[i].pgno,
vbi->vt.btt_link[i].subno, 0x3f7f);
if (!vtp) {
printv("top ait page %x not cached\n", vbi->vt.btt_link[i].pgno);
continue;
} else if (vtp->function != PAGE_FUNCTION_AIT) {
printv("no ait page %x\n", vtp->pgno);
continue;
}
for (ait = vtp->data.ait, j = 0; j < 46; ait++, j++) {
if (!ait->page.pgno)
break;
if (ait->page.pgno < pgno
|| (ait->page.pgno == pgno && ait->page.subno <= subno))
continue;
if (ait->page.pgno > mpgno
|| (ait->page.pgno == mpgno && ait->page.subno > msubno))
continue;
mait = ait;
mpgno = ait->page.pgno;
msubno = ait->page.subno;
*mvtp = vtp;
}
}
}
return mait;
}
static int
top_index(vbi_decoder *vbi, vbi_page *pg, int subno)
{
vt_page *vtp;
vbi_char ac, *acp;
ait_entry *ait;
int i, j, k, n, lines;
int xpgno, xsubno;
vt_extension *ext;
char *index_str;
pg->vbi = vbi;
subno = vbi_bcd2dec(subno);
pg->rows = ROWS;
pg->columns = EXT_COLUMNS;
pg->dirty.y0 = 0;
pg->dirty.y1 = ROWS - 1;
pg->dirty.roll = 0;
ext = &vbi->vt.magazine[0].extension;
screen_color(pg, 0, 32 + VBI_BLUE);
vbi_transp_colormap(vbi, pg->color_map, ext->color_map, 40);
pg->drcs_clut = ext->drcs_clut;
pg->page_opacity[0] = VBI_OPAQUE;
pg->page_opacity[1] = VBI_OPAQUE;
pg->boxed_opacity[0] = VBI_OPAQUE;
pg->boxed_opacity[1] = VBI_OPAQUE;
memset(pg->drcs, 0, sizeof(pg->drcs));
memset(&ac, 0, sizeof(ac));
ac.foreground = VBI_BLACK; // 32 + VBI_BLACK;
ac.background = 32 + VBI_BLUE;
ac.opacity = VBI_OPAQUE;
ac.unicode = 0x0020;
ac.size = VBI_NORMAL_SIZE;
for (i = 0; i < EXT_COLUMNS * ROWS; i++)
pg->text[i] = ac;
ac.size = VBI_DOUBLE_SIZE;
/* FIXME */
/* TRANSLATORS: Title of TOP Index page,
for now please Latin-1 or ASCII only */
index_str = _("TOP Index");
for (i = 0; index_str[i]; i++) {
ac.unicode = index_str[i];
pg->text[1 * EXT_COLUMNS + 2 + i * 2] = ac;
}
ac.size = VBI_NORMAL_SIZE;
acp = &pg->text[4 * EXT_COLUMNS];
lines = 17;
xpgno = 0;
xsubno = 0;
while ((ait = next_ait(vbi, xpgno, xsubno, &vtp))) {
xpgno = ait->page.pgno;
xsubno = ait->page.subno;
/* No docs, correct? */
character_set_designation(pg->font, ext, vtp);
if (subno > 0) {
if (lines-- == 0) {
subno--;
lines = 17;
}
continue;
} else if (lines-- <= 0)
continue;
for (i = 11; i >= 0; i--)
if (ait->text[i] > 0x20)
break;
switch (vbi->vt.page_info[ait->page.pgno - 0x100].code) {
case VBI_TOP_GROUP:
k = 3;
break;
default:
k = 1;
}
for (j = 0; j <= i; j++) {
acp[k + j].unicode = vbi_teletext_unicode(pg->font[0]->G0,
pg->font[0]->subset, (ait->text[j] < 0x20) ? 0x20 : ait->text[j]);
}
for (k += i + 2; k <= 33; k++)
acp[k].unicode = '.';
for (j = 0; j < 3; j++) {
n = ((ait->page.pgno >> ((2 - j) * 4)) & 15) + '0';
if (n > '9')
n += 'A' - '9';
acp[j + 35].unicode = n;
}
acp += EXT_COLUMNS;
}
return 1;
}
struct pex26 {
signed month : 8; /* 0 ... 11 */
signed day : 8; /* 0 ... 30 */
signed at1 : 16; /* min since 00:00 */
signed at2 : 16; /* min since 00:00 */
signed length : 16; /* min */
unsigned x26_cni : 16; /* see tables.c */
unsigned pty : 8;
signed lto : 8; /* +- 1/4 hr */
signed row : 8; /* title 1 ... 23 */
signed column : 8; /* title 0 ... 39 */
unsigned caf : 1;
unsigned : 15;
};
static void
dump_pex26(struct pex26 *pt, int n)
{
int i;
for (i = 0; i < n; i++, pt++)
fprintf(stderr, "%2d: %02d-%02d %d:%02d (%d:%02d) +%d, "
"cni=%04x pty=%02x lto=%d tit=%d:%d caf=%d\n",
i, pt->month, pt->day,
pt->at1 / 60, pt->at1 % 60,
pt->at2 / 60, pt->at2 % 60,
pt->length,
pt->x26_cni, pt->pty, pt->lto,
pt->row, pt->column,
pt->caf);
}
#if 0
/*
type pre text ____ post ____
/ \
AT-1 + zz.zz + % <
AT-1 + zz.zz-zz.zz + % <
PTL ++ title ++ %% :: <
AT-1 % zz.zz + % <
PW*) % hh
LTO % 0zz + % <
LTO % 9zz + % <
AT-2 % zzzz + % <
CNI*) % hhzzz + % <
AD*) % zzzzzz + % <
PTL %% title ++ %% :: <
AT-2 : zzzz + % <
AD : zzzzzz + % <
PW :% hh + % <
PTY :% Fhh + % <
AT-2 :% zzzz + % <
CNI :% hhzzz + % <
AD :% zzzzzz + % <
+ colour code
: magenta
% conceal
*) permitted when CNI, AD, PW combine
Note ETS 300 231 Table 4 is wrong: '%' = 0x18; ',' = '+' | '%' | '<'
*/
/* to be rewritten */
struct program_entry {
int start;
int stop;
int at2;
int ad;
int cni;
int pty;
int lto;
uint16_t title[200];
};
#define PMA_COLOUR /* 0x01, 0x02, 0x03, 0x04, 0x06, 0x07 */
#define PMA_MAGENTA 0x05
#define PMA_CONCEAL 0x18
#define IS_PMA_CTRL(c) (((c) >= 0x01 && (c) <= 0x07) || (c) == PMA_CONCEAL)
static int
bcd2time(int bcd)
{
int sec = bcd & 15;
int min = (bcd >> 8) & 15;
if (sec > 9 || min > 9 || (bcd & 0x00FF) > 0x0059)
return -1;
//#warning hour check
return sec * 1 + min * 60
+ ((bcd >> 4) & 15) * 10
+ ((bcd >> 12) & 15) * 600;
}
static int
pdc_method_a(vbi_page *pg, vt_page *vtp, struct program_entry *pe)
{
int row, column;
int i;
// memset(pe, -1, sizeof(*pe));
i = 40;
for (row = 1; row <= 23; row++) {
for (column = 0; column <= 38;) {
int ctrl1 = vbi_parity(vtp->data.lop.raw[row][column]);
int ctrl2 = vbi_parity(vtp->data.lop.raw[row][column + 1]);
fprintf(stderr, "%d %d %02x %02x\n", row, column, ctrl1, ctrl2);
if ((ctrl1 | ctrl2) < 0) {
return 0; /* hamming error */
} else if (!IS_PMA_CTRL(ctrl1)) {
column++;
continue;
}
if (ctrl1 == ctrl2 && ctrl1 != PMA_MAGENTA) {
fprintf(stderr, "PTL %d %d\n", row, column);
/* title */
column += 2;fprintf(stderr, "%d %d %02x %02x\n", row, column, ctrl1, ctrl2);
} else {
/* numeral */
int digits, sep, value;
fprintf(stderr, "NUM %d %d\n", row, column);
column += (ctrl1 == PMA_MAGENTA && ctrl2 == PMA_CONCEAL) ? 2 : 1;
sep = 0;
value = 0;
for (digits = 0; column < 40; column++) {
int c = vbi_parity(vtp->data.lop.raw[row][column]);
if (IS_PMA_CTRL(c)) {
break;
} else if (c >= 0x30 && c <= 0x39) {
value = value * 16 + c - 0x30;
digits++;
} else if (c >= 0x41 && c <= 0x46) {
if (digits >= 3)
goto invalid_pattern;
value = value * 16 + c + (0x0A - 0x41);
digits++;
} else if (c == 0x2E) {
if (digits != 2 && digits != 6)
goto invalid_pattern;
sep |= 1 << digits;
} else if (c == 0x2D) {
if (digits != 4)
goto invalid_pattern;
sep |= 1 << 4;
} else
goto invalid_pattern;
}
if (sep) {
if (ctrl1 == PMA_MAGENTA)
goto invalid_pattern;
if (ctrl1 == PMA_CONCEAL && digits != 4)
goto invalid_pattern;
}
switch (digits) {
int start, stop;
case 2:
/* Actually ctrl1 only permitted when combined */
if (ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL)
goto invalid_pattern;
fprintf(stderr, "PW %02x\n", value);
/* PW */
break;
case 3:
if (ctrl1 == PMA_CONCEAL) {
if (value >= 0x100 && value < 0x900)
goto invalid_pattern;
fprintf(stderr, "LTO %03x\n", value);
/* LTO */
} else if (ctrl2 == PMA_CONCEAL) {
if ((value -= 0xF00) < 0)
goto invalid_pattern;
fprintf(stderr, "PTY %02x\n", value);
/* PTY */
} else
goto invalid_pattern;
case 4:
start = bcd2time(value);
if (start < 0)
goto invalid_pattern;
if (sep) {
if (ctrl1 == PMA_MAGENTA)
goto invalid_pattern;
fprintf(stderr, "AT-1 %04x\n", value);
; /* AT-1 short */
} else if (ctrl1 == PMA_MAGENTA || ctrl1 == PMA_CONCEAL) {
fprintf(stderr, "AT-2 %04x\n", value);
; /* AT-2 */
} else
goto invalid_pattern;
break;
case 5:
/* Actually ctrl1 only permitted when combined */
if ((ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL)
|| (value & 0x00F00) > 0x00900)
goto invalid_pattern;
/* CNI */
fprintf(stderr, "CNI %05x\n", value);
break;
case 6:
/* Actually ctrl1 only permitted when combined */
if (ctrl1 != PMA_CONCEAL && ctrl2 != PMA_CONCEAL)
goto invalid_pattern;
/* AD */
fprintf(stderr, "AD %06x\n", value);
break;
case 8:
start = bcd2time(value >> 16);
stop = bcd2time(value);
if ((start | stop) < 0
|| ctrl1 == PMA_MAGENTA || ctrl1 == PMA_CONCEAL
|| sep != ((1 << 2) + (1 << 4) + (1 << 6)))
goto invalid_pattern;
/* AT-1 long */
fprintf(stderr, "AT1 %08x\n", value);
break;
default:
invalid_pattern:
continue;
}
}
}
}
return 0; /* invalid */
}
#endif
/*
* Zapzilla navigation
*/
static int
keyword(vbi_link *ld, uint8_t *p, int column,
int pgno, int subno, int *back)
{
uint8_t *s = p + column;
int i, j, k, l;
ld->type = VBI_LINK_NONE;
ld->name[0] = 0;
ld->url[0] = 0;
ld->pgno = 0;
ld->subno = VBI_ANY_SUBNO;
*back = 0;
if (isdigit(*s)) {
for (i = 0; isdigit(s[i]); i++)
ld->pgno = ld->pgno * 16 + (s[i] & 15);
if (isdigit(s[-1]) || i > 3)
return i;
if (i == 3) {
if (ld->pgno >= 0x100 && ld->pgno <= 0x899)
ld->type = VBI_LINK_PAGE;
return i;
}
if (s[i] != '/' && s[i] != ':')
return i;
s += i += 1;
for (ld->subno = j = 0; isdigit(s[j]); j++)
ld->subno = ld->subno * 16 + (s[j] & 15);
if (j > 1 || subno != ld->pgno || ld->subno > 0x99)
return i + j;
if (ld->pgno == ld->subno)
ld->subno = 0x01;
else
ld->subno = vbi_add_bcd(ld->pgno, 0x01);
ld->type = VBI_LINK_SUBPAGE;
ld->pgno = pgno;
return i + j;
} else if (!strncasecmp((char *) s, "https://", i = 8)) {
ld->type = VBI_LINK_HTTP;
} else if (!strncasecmp((char *) s, "http://", i = 7)) {
ld->type = VBI_LINK_HTTP;
} else if (!strncasecmp((char *) s, "www.", i = 4)) {
ld->type = VBI_LINK_HTTP;
strcpy((char *) ld->url, "http://");
} else if (!strncasecmp((char *) s, "ftp://", i = 6)) {
ld->type = VBI_LINK_FTP;
} else if (*s == '@' || *s == 0xA7) {
ld->type = VBI_LINK_EMAIL;
strcpy((char *) ld->url, "mailto:");
i = 1;
} else if (!strncasecmp((char *) s, "(at)", i = 4)) {
ld->type = VBI_LINK_EMAIL;
strcpy((char *) ld->url, "mailto:");
} else if (!strncasecmp((char *) s, "(a)", i = 3)) {
ld->type = VBI_LINK_EMAIL;
strcpy((char *) ld->url, "mailto:");
} else
return 1;
for (j = k = l = 0;;) {
// RFC 1738
while (isalnum(s[i + j]) || strchr("%&/=?+-~:;@_", s[i + j])) {
j++;
l++;
}
if (s[i + j] == '.') {
if (l < 1)
return i;
l = 0;
j++;
k++;
} else
break;
}
if (k < 1 || l < 1) {
ld->type = VBI_LINK_NONE;
return i;
}
k = 0;
if (ld->type == VBI_LINK_EMAIL) {
for (; isalnum(s[k - 1]) || strchr("-~._", s[k - 1]); k--);
if (k == 0) {
ld->type = VBI_LINK_NONE;
return i;
}
*back = k;
strncat((char *) ld->url, (char *) s + k, -k);
strcat((char *) ld->url, "@");
strncat((char *) ld->url, (char *) s + i, j);
} else
strncat((char *) ld->url, (char *) s + k, i + j - k);
return i + j;
}
static inline void
zap_links(vbi_page *pg, int row)
{
unsigned char buffer[43]; /* One row, two spaces on the sides and NUL */
vbi_link ld;
vbi_char *acp;
vbi_bool link[43];
int i, j, n, b;
acp = &pg->text[row * EXT_COLUMNS];
for (i = j = 0; i < COLUMNS; i++) {
if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM)
continue;
buffer[j + 1] = (acp[i].unicode >= 0x20 && acp[i].unicode <= 0xFF) ?
acp[i].unicode : 0x20;
j++;
}
buffer[0] = ' ';
buffer[j + 1] = ' ';
buffer[j + 2] = 0;
for (i = 0; i < COLUMNS; i += n) {
n = keyword(&ld, buffer, i + 1,
pg->pgno, pg->subno, &b);
for (j = b; j < n; j++)
link[i + j] = (ld.type != VBI_LINK_NONE);
}
for (i = j = 0; i < COLUMNS; i++) {
acp[i].link = link[j];
if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM)
continue;
j++;
}
}
/**
* @param pg With vbi_fetch_vt_page() obtained vbi_page.
* @param column Column 0 ... pg->columns - 1 of the character in question.
* @param row Row 0 ... pg->rows - 1 of the character in question.
* @param ld Place to store information about the link.
*
* A vbi_page (in practice only Teletext pages) may contain hyperlinks
* such as HTTP URLs, e-mail addresses or links to other pages. Characters
* being part of a hyperlink have a set vbi_char->link flag, this function
* returns a more verbose description of the link.
*/
void
vbi_resolve_link(vbi_page *pg, int column, int row, vbi_link *ld)
{
unsigned char buffer[43];
vbi_char *acp;
int i, j, b;
assert(column >= 0 && column < EXT_COLUMNS);
ld->nuid = pg->nuid;
acp = &pg->text[row * EXT_COLUMNS];
if (row == (ROWS - 1) && acp[column].link) {
i = pg->nav_index[column];
ld->type = VBI_LINK_PAGE;
ld->pgno = pg->nav_link[i].pgno;
ld->subno = pg->nav_link[i].subno;
return;
}
if (row < 1 || row > 23 || column >= COLUMNS || pg->pgno < 0x100) {
ld->type = VBI_LINK_NONE;
return;
}
for (i = j = b = 0; i < COLUMNS; i++) {
if (acp[i].size == VBI_OVER_TOP || acp[i].size == VBI_OVER_BOTTOM)
continue;
if (i < column && !acp[i].link)
j = b = -1;
buffer[j + 1] = (acp[i].unicode >= 0x20 && acp[i].unicode <= 0xFF) ?
acp[i].unicode : 0x20;
if (b <= 0) {
if (buffer[j + 1] == ')' && j > 2) {
if (!strncasecmp((char *) buffer + j + 1 - 3, "(at", 3))
b = j - 3;
else if (!strncasecmp((char *) buffer + j + 1 - 2, "(a", 2))
b = j - 2;
} else if (buffer[j + 1] == '@' || buffer[j + 1] == 167)
b = j;
}
j++;
}
buffer[0] = ' ';
buffer[j + 1] = ' ';
buffer[j + 2] = 0;
keyword(ld, buffer, 1, pg->pgno, pg->subno, &i);
if (ld->type == VBI_LINK_NONE)
keyword(ld, buffer, b + 1, pg->pgno, pg->subno, &i);
}
/**
* @param pg With vbi_fetch_vt_page() obtained vbi_page.
* @param ld Place to store information about the link.
*
* All Teletext pages have a built-in home link, by default
* page 100, but can also be the magazine intro page or another
* page selected by the editor.
*/
void
vbi_resolve_home(vbi_page *pg, vbi_link *ld)
{
if (pg->pgno < 0x100) {
ld->type = VBI_LINK_NONE;
return;
}
ld->type = VBI_LINK_PAGE;
ld->pgno = pg->nav_link[5].pgno;
ld->subno = pg->nav_link[5].subno;
}
static inline void
ait_title(vbi_decoder *vbi, vt_page *vtp, ait_entry *ait, char *buf)
{
struct vbi_font_descr *font[2];
int i;
character_set_designation(font, &vbi->vt.magazine[0].extension, vtp);
for (i = 11; i >= 0; i--)
if (ait->text[i] > 0x20)
break;
buf[i + 1] = 0;
for (; i >= 0; i--) {
unsigned int unicode = vbi_teletext_unicode(
font[0]->G0, font[0]->subset,
(ait->text[i] < 0x20) ? 0x20 : ait->text[i]);
buf[i] = (unicode >= 0x20 && unicode <= 0xFF) ? unicode : 0x20;
}
}
/**
* @param vbi Initialized vbi decoding context.
* @param pgno Page number, see vbi_pgno.
* @param subno Subpage number.
* @param buf Place to store the title, Latin-1 format, at least
* 41 characters including the terminating zero.
*
* Given a Teletext page number this function tries to deduce a
* page title for bookmarks or other purposes, mainly from navigation
* data. (XXX TODO: FLOF)
*
* @return
* @c TRUE if a title has been found.
*/
vbi_bool
vbi_page_title(vbi_decoder *vbi, int pgno, int subno, char *buf)
{
vt_page *vtp;
ait_entry *ait;
int i, j;
subno = subno;
if (vbi->vt.top) {
for (i = 0; i < 8; i++)
if (vbi->vt.btt_link[i].type == 2) {
vtp = vbi_cache_get(vbi,
vbi->vt.btt_link[i].pgno,
vbi->vt.btt_link[i].subno, 0x3f7f);
if (!vtp) {
printv("p/t top ait page %x not cached\n", vbi->vt.btt_link[i].pgno);
continue;
} else if (vtp->function != PAGE_FUNCTION_AIT) {
printv("p/t no ait page %x\n", vtp->pgno);
continue;
}
for (ait = vtp->data.ait, j = 0; j < 46; ait++, j++)
if (ait->page.pgno == pgno) {
ait_title(vbi, vtp, ait, buf);
return TRUE;
}
}
} else {
/* find a FLOF link and the corresponding label */
}
return FALSE;
}
/*
* Teletext page formatting
*/
static void
character_set_designation(struct vbi_font_descr **font,
vt_extension *ext, vt_page *vtp)
{
int i;
#ifdef libzvbi_TTX_OVERRIDE_CHAR_SET
font[0] = vbi_font_descriptors + libzvbi_TTX_OVERRIDE_CHAR_SET;
font[1] = vbi_font_descriptors + libzvbi_TTX_OVERRIDE_CHAR_SET;
fprintf(stderr, "override char set with %d\n",
libzvbi_TTX_OVERRIDE_CHAR_SET);
#else
font[0] = vbi_font_descriptors + 0;
font[1] = vbi_font_descriptors + 0;
for (i = 0; i < 2; i++) {
int char_set = ext->char_set[i];
if (VALID_CHARACTER_SET(char_set))
font[i] = vbi_font_descriptors + char_set;
char_set = (char_set & ~7) + vtp->national;
if (VALID_CHARACTER_SET(char_set))
font[i] = vbi_font_descriptors + char_set;
}
#endif
}
static void
screen_color(vbi_page *pg, int flags, int color)
{
pg->screen_color = color;
if (color == VBI_TRANSPARENT_BLACK
|| (flags & (C5_NEWSFLASH | C6_SUBTITLE)))
pg->screen_opacity = VBI_TRANSPARENT_SPACE;
else
pg->screen_opacity = VBI_OPAQUE;
}
#define elements(array) (sizeof(array) / sizeof(array[0]))
static vt_triplet *
resolve_obj_address(vbi_decoder *vbi, object_type type,
int pgno, object_address address, page_function function,
int *remaining)
{
int s1, packet, pointer;
vt_page *vtp;
vt_triplet *trip;
int i;
s1 = address & 15;
packet = ((address >> 7) & 3);
i = ((address >> 5) & 3) * 3 + type;
printv("obj invocation, source page %03x/%04x, "
"pointer packet %d triplet %d\n", pgno, s1, packet + 1, i);
vtp = vbi_cache_get(vbi, pgno, s1, 0x000F);
if (!vtp) {
printv("... page not cached\n");
return 0;
}
if (vtp->function == PAGE_FUNCTION_UNKNOWN) {
if (!(vtp = vbi_convert_page(vbi, vtp, TRUE, function))) {
printv("... no g/pop page or hamming error\n");
return 0;
}
} else if (vtp->function == PAGE_FUNCTION_POP)
vtp->function = function;
else if (vtp->function != function) {
printv("... source page wrong function %d, expected %d\n",
vtp->function, function);
return 0;
}
pointer = vtp->data.pop.pointer[packet * 24 + i * 2 + ((address >> 4) & 1)];
printv("... triplet pointer %d\n", pointer);
if (pointer > 506) {
printv("... triplet pointer out of bounds (%d)\n", pointer);
return 0;
}
if (DEBUG) {
packet = (pointer / 13) + 3;
if (packet <= 25)
printv("... object start in packet %d, triplet %d (pointer %d)\n",
packet, pointer % 13, pointer);
else
printv("... object start in packet 26/%d, triplet %d (pointer %d)\n",
packet - 26, pointer % 13, pointer);
}
trip = vtp->data.pop.triplet + pointer;
*remaining = elements(vtp->data.pop.triplet) - (pointer+1);
printv("... obj def: ad 0x%02x mo 0x%04x dat %d=0x%x\n",
trip->address, trip->mode, trip->data, trip->data);
address ^= trip->address << 7;
address ^= trip->data;
if (trip->mode != (type + 0x14) || (address & 0x1FF)) {
printv("... no object definition\n");
return 0;
}
return trip + 1;
}
/* FIXME: panels */
static vbi_bool
enhance(vbi_decoder *vbi, vt_magazine *mag, vt_extension *ext,
vbi_page *pg, vt_page *vtp,
object_type type, vt_triplet *p,
int max_triplets,
int inv_row, int inv_column,
vbi_wst_level max_level, vbi_bool header_only,
struct pex26 *ptable)
{
vbi_char ac, mac, *acp;
int active_column, active_row;
int offset_column, offset_row;
int row_color, next_row_color;
struct vbi_font_descr *font;
int invert;
int drcs_s1[2];
struct pex26 *pt, ptmp;
int pdc_hr;
/* XXX nested function not portable, to be removed */
void
flush(int column)
{
int row = inv_row + active_row;
int i;
if (row >= ROWS)
return;
if (type == OBJ_TYPE_PASSIVE && !mac.unicode) {
active_column = column;
return;
}
printv("flush [%04x%c,F%d%c,B%d%c,S%d%c,O%d%c,H%d%c] %d ... %d\n",
ac.unicode, mac.unicode ? '*' : ' ',
ac.foreground, mac.foreground ? '*' : ' ',
ac.background, mac.background ? '*' : ' ',
ac.size, mac.size ? '*' : ' ',
ac.opacity, mac.opacity ? '*' : ' ',
ac.flash, mac.flash ? '*' : ' ',
active_column, column - 1);
for (i = inv_column + active_column; i < inv_column + column;) {
vbi_char c;
if (i > 39)
break;
c = acp[i];
if (mac.underline) {
int u = ac.underline;
if (!mac.unicode)
ac.unicode = c.unicode;
if (vbi_is_gfx(ac.unicode)) {
if (u)
ac.unicode &= ~0x20; /* separated */
else
ac.unicode |= 0x20; /* contiguous */
mac.unicode = ~0;
u = 0;
}
c.underline = u;
}
if (mac.foreground)
c.foreground = (ac.foreground == VBI_TRANSPARENT_BLACK) ?
row_color : ac.foreground;
if (mac.background)
c.background = (ac.background == VBI_TRANSPARENT_BLACK) ?
row_color : ac.background;
if (invert) {
int t = c.foreground;
c.foreground = c.background;
c.background = t;
}
if (mac.opacity)
c.opacity = ac.opacity;
if (mac.flash)
c.flash = ac.flash;
if (mac.conceal)
c.conceal = ac.conceal;
if (mac.unicode) {
c.unicode = ac.unicode;
mac.unicode = 0;
if (mac.size)
c.size = ac.size;
else if (c.size > VBI_DOUBLE_SIZE)
c.size = VBI_NORMAL_SIZE;
}
acp[i] = c;
if (type == OBJ_TYPE_PASSIVE)
break;
i++;
if (type != OBJ_TYPE_PASSIVE
&& type != OBJ_TYPE_ADAPTIVE) {
int raw;
raw = (row == 0 && i < 9) ?
0x20 : vbi_unpar8 (vtp->data.lop.raw[row][i - 1]);
/* set-after spacing attributes cancelling non-spacing */
switch (raw) {
case 0x00 ... 0x07: /* alpha + foreground color */
case 0x10 ... 0x17: /* mosaic + foreground color */
printv("... fg term %d %02x\n", i, raw);
mac.foreground = 0;
mac.conceal = 0;
break;
case 0x08: /* flash */
mac.flash = 0;
break;
case 0x0A: /* end box */
case 0x0B: /* start box */
if (i < COLUMNS && vbi_unpar8 (vtp->data.lop.raw[row][i]) == raw) {
printv("... boxed term %d %02x\n", i, raw);
mac.opacity = 0;
}
break;
case 0x0D: /* double height */
case 0x0E: /* double width */
case 0x0F: /* double size */
printv("... size term %d %02x\n", i, raw);
mac.size = 0;
break;
}
if (i > 39)
break;
raw = (row == 0 && i < 8) ?
0x20 : vbi_unpar8 (vtp->data.lop.raw[row][i]);
/* set-at spacing attributes cancelling non-spacing */
switch (raw) {
case 0x09: /* steady */
mac.flash = 0;
break;
case 0x0C: /* normal size */
printv("... size term %d %02x\n", i, raw);
mac.size = 0;
break;
case 0x18: /* conceal */
mac.conceal = 0;
break;
/*
* Non-spacing underlined/separated display attribute
* cannot be cancelled by a subsequent spacing attribute.
*/
case 0x1C: /* black background */
case 0x1D: /* new background */
printv("... bg term %d %02x\n", i, raw);
mac.background = 0;
break;
}
}
}
active_column = column;
}
/* XXX nested function not portable, to be removed */
void
flush_row(void)
{
if (type == OBJ_TYPE_PASSIVE || type == OBJ_TYPE_ADAPTIVE)
flush(active_column + 1);
else
flush(COLUMNS);
if (type != OBJ_TYPE_PASSIVE)
memset(&mac, 0, sizeof(mac));
}
active_column = 0;
active_row = 0;
acp = &pg->text[(inv_row + 0) * EXT_COLUMNS];
offset_column = 0;
offset_row = 0;
row_color =
next_row_color = ext->def_row_color;
drcs_s1[0] = 0; /* global */
drcs_s1[1] = 0; /* normal */
memset(&ac, 0, sizeof(ac));
memset(&mac, 0, sizeof(mac));
invert = 0;
if (type == OBJ_TYPE_PASSIVE) {
ac.foreground = VBI_WHITE;
ac.background = VBI_BLACK;
ac.opacity = pg->page_opacity[1];
mac.foreground = ~0;
mac.background = ~0;
mac.opacity = ~0;
mac.size = ~0;
mac.underline = ~0;
mac.conceal = ~0;
mac.flash = ~0;
}
font = pg->font[0];
if (ptable) {
ptmp.month = -1;
ptmp.at1 = -1; /* n/a */
ptmp.length = 0;
ptmp.x26_cni = 0;
ptmp.pty = 0;
ptmp.lto = 0;
pt = ptable - 1;
} else
pt = &ptmp;
pdc_hr = 0;
for (; max_triplets>0; p++, max_triplets--) {
if (p->address >= COLUMNS) {
/*
* Row address triplets
*/
int s = p->data >> 5;
int row = (p->address - COLUMNS) ? : (ROWS - 1);
int column = 0;
if (pdc_hr)
return FALSE; /* invalid */
switch (p->mode) {
case 0x00: /* full screen color */
if (max_level >= VBI_WST_LEVEL_2p5
&& s == 0 && type <= OBJ_TYPE_ACTIVE)
screen_color(pg, vtp->flags, p->data & 0x1F);
break;
case 0x07: /* address display row 0 */
if (p->address != 0x3F)
break; /* reserved, no position */
row = 0;
/* fall through */
case 0x01: /* full row color */
row_color = next_row_color;
if (s == 0) {
row_color = p->data & 0x1F;
next_row_color = ext->def_row_color;
} else if (s == 3) {
row_color =
next_row_color = p->data & 0x1F;
}
goto set_active;
case 0x02: /* reserved */
case 0x03: /* reserved */
break;
case 0x04: /* set active position */
if (max_level >= VBI_WST_LEVEL_2p5) {
if (p->data >= COLUMNS)
break; /* reserved */
column = p->data;
}
if (row > active_row)
row_color = next_row_color;
set_active:
if (header_only && row > 0) {
for (;max_triplets>1; p++, max_triplets--)
if (p[1].address >= COLUMNS) {
if (p[1].mode == 0x07)
break;
else if ((unsigned int) p[1].mode >= 0x1F)
goto terminate;
}
break;
}
printv("enh set_active row %d col %d\n", row, column);
if (row > active_row)
flush_row();
active_row = row;
active_column = column;
acp = &pg->text[(inv_row + active_row) * EXT_COLUMNS];
break;
case 0x05: /* reserved */
case 0x06: /* reserved */
break;
case 0x08: /* PDC data - Country of Origin and Programme Source */
ptmp.x26_cni = p->address * 256 + p->data;
break;
case 0x09: /* PDC data - Month and Day */
ptmp.month = (p->address & 15) - 1;
ptmp.day = (p->data >> 4) * 10 + (p->data & 15) - 1;
break;
case 0x0A: /* PDC data - Cursor Row and Announced Starting Time Hours */
if (!ptable) {
break;
} else if ((ptmp.month | ptmp.x26_cni) < 0) {
return FALSE;
} else if ((ptable - pt) > 22) {
return FALSE;
}
*++pt = ptmp;
/* fall through */
case 0x0B: /* PDC data - Cursor Row and Announced Finishing Time Hours */
s = (p->data & 15) * 60;
if (p->mode == 0x0A) {
pt->at2 = ((p->data & 0x30) >> 4) * 600 + s;
pt->length = 0;
pt->row = row;
pt->caf = !!(p->data & 0x40);
} else {
pt->length = ((p->data & 0x70) >> 4) * 600 + s;
}
pdc_hr = p->mode;
break;
case 0x0C: /* PDC data - Cursor Row and Local Time Offset */
ptmp.lto = (p->data & 0x40) ? ((~0x7F) | p->data) : p->data;
break;
case 0x0D: /* PDC data - Series Identifier and Series Code */
if (p->address == 0x30) {
break;
}
pt->pty = 0x80 + p->data;
break;
case 0x0E: /* reserved */
case 0x0F: /* reserved */
break;
case 0x10: /* origin modifier */
if (max_level < VBI_WST_LEVEL_2p5)
break;
if (p->data >= 72)
break; /* invalid */
offset_column = p->data;
offset_row = p->address - COLUMNS;
printv("enh origin modifier col %+d row %+d\n",
offset_column, offset_row);
break;
case 0x11 ... 0x13: /* object invocation */
{
int source = (p->address >> 3) & 3;
object_type new_type = p->mode & 3;
vt_triplet *trip;
int remaining_max_triplets = 0;
if (max_level < VBI_WST_LEVEL_2p5)
break;
printv("enh obj invocation source %d type %d\n", source, new_type);
if (new_type <= type) { /* 13.2++ */
printv("... priority violation\n");
break;
}
if (source == 0) /* illegal */
break;
else if (source == 1) { /* local */
int designation = (p->data >> 4) + ((p->address & 1) << 4);
int triplet = p->data & 15;
if (type != LOCAL_ENHANCEMENT_DATA || triplet > 12)
break; /* invalid */
printv("... local obj %d/%d\n", designation, triplet);
if (!(vtp->enh_lines & 1)) {
printv("... no packet %d\n", designation);
return FALSE;
}
trip = vtp->data.enh_lop.enh + designation * 13 + triplet;
remaining_max_triplets = elements(vtp->data.enh_lop.enh) - (designation* 13 + triplet);
}
else /* global / public */
{
page_function function;
int pgno, i = 0;
if (source == 3) {
function = PAGE_FUNCTION_GPOP;
pgno = vtp->data.lop.link[24].pgno;
if (NO_PAGE(pgno)) {
if (max_level < VBI_WST_LEVEL_3p5
|| NO_PAGE(pgno = mag->pop_link[8].pgno))
pgno = mag->pop_link[0].pgno;
} else
printv("... X/27/4 GPOP overrides MOT\n");
} else {
function = PAGE_FUNCTION_POP;
pgno = vtp->data.lop.link[25].pgno;
if (NO_PAGE(pgno)) {
if ((i = mag->pop_lut[vtp->pgno & 0xFF]) == 0) {
printv("... MOT pop_lut empty\n");
return FALSE; /* has no link (yet) */
}
if (max_level < VBI_WST_LEVEL_3p5
|| NO_PAGE(pgno = mag->pop_link[i + 8].pgno))
pgno = mag->pop_link[i + 0].pgno;
} else
printv("... X/27/4 POP overrides MOT\n");
}
if (NO_PAGE(pgno)) {
printv("... dead MOT link %d\n", i);
return FALSE; /* has no link (yet) */
}
printv("... %s obj\n", (source == 3) ? "global" : "public");
trip = resolve_obj_address(vbi, new_type, pgno,
(p->address << 7) + p->data, function,
&remaining_max_triplets);
if (!trip)
return FALSE;
}
row = inv_row + active_row;
column = inv_column + active_column;
if (!enhance(vbi, mag, ext, pg, vtp, new_type, trip,
remaining_max_triplets,
row + offset_row, column + offset_column,
max_level, header_only, NULL))
return FALSE;
printv("... object done\n");
offset_row = 0;
offset_column = 0;
break;
}
case 0x14: /* reserved */
break;
case 0x15 ... 0x17: /* object definition */
flush_row();
printv("enh obj definition 0x%02x 0x%02x\n", p->mode, p->data);
printv("enh terminated\n");
goto swedish;
case 0x18: /* drcs mode */
printv("enh DRCS mode 0x%02x\n", p->data);
drcs_s1[p->data >> 6] = p->data & 15;
break;
case 0x19 ... 0x1E: /* reserved */
break;
case 0x1F: /* termination marker */
default:
terminate:
flush_row();
printv("enh terminated %02x\n", p->mode);
goto swedish;
}
} else {
/*
* Column address triplets
*/
int s = p->data >> 5;
int column = p->address;
int unicode;
switch (p->mode) {
case 0x00: /* foreground color */
if (max_level >= VBI_WST_LEVEL_2p5 && s == 0) {
if (column > active_column)
flush(column);
ac.foreground = p->data & 0x1F;
mac.foreground = ~0;
printv("enh col %d foreground %d\n", active_column, ac.foreground);
}
break;
case 0x01: /* G1 block mosaic character */
if (max_level >= VBI_WST_LEVEL_2p5) {
if (column > active_column)
flush(column);
if (p->data & 0x20) {
unicode = 0xEE00 + p->data; /* G1 contiguous */
goto store;
} else if (p->data >= 0x40) {
unicode = vbi_teletext_unicode(
font->G0, NO_SUBSET, p->data);
goto store;
}
}
break;
case 0x0B: /* G3 smooth mosaic or line drawing character */
if (max_level < VBI_WST_LEVEL_2p5)
break;
/* fall through */
case 0x02: /* G3 smooth mosaic or line drawing character */
if (p->data >= 0x20) {
if (column > active_column)
flush(column);
unicode = 0xEF00 + p->data;
goto store;
}
break;
case 0x03: /* background color */
if (max_level >= VBI_WST_LEVEL_2p5 && s == 0) {
if (column > active_column)
flush(column);
ac.background = p->data & 0x1F;
mac.background = ~0;
printv("enh col %d background %d\n", active_column, ac.background);
}
break;
case 0x04: /* reserved */
case 0x05: /* reserved */
break;
case 0x06: /* PDC data - Cursor Column and Announced Starting */
/* and Finishing Time Minutes */
if (!ptable)
break;
s = (p->data >> 4) * 10 + (p->data & 15);
if (pdc_hr == 0x0A) {
pt->at2 += s;
if (pt > ptable && pt[-1].length == 0) {
pt[-1].length = pt->at2 - pt[-1].at2;
if (pt->at2 < pt[-1].at2)
pt[-1].length += 24 * 60;
if (pt[-1].length >= 12 * 60) {
/* bullshit */
pt[-1] = pt[0];
pt--;
}
}
} else if (pdc_hr == 0x0B) {
pt->length += s;
if (pt->length >= 4 * 600) {
pt->length -= 4 * 600;
} else {
if (pt->length < pt->at2)
pt->length += 24 * 60;
pt->length -= pt->at2;
}
} else {
return FALSE;
}
pt->column = column;
pdc_hr = 0;
break;
case 0x07: /* additional flash functions */
if (max_level >= VBI_WST_LEVEL_2p5) {
if (column > active_column)
flush(column);
/*
* Only one flash function (if any) implemented:
* Mode 1 - Normal flash to background color
* Rate 0 - Slow rate (1 Hz)
*/
ac.flash = !!(p->data & 3);
mac.flash = ~0;
printv("enh col %d flash 0x%02x\n", active_column, p->data);
}
break;
case 0x08: /* modified G0 and G2 character set designation */
if (max_level >= VBI_WST_LEVEL_2p5) {
if (column > active_column)
flush(column);
if (VALID_CHARACTER_SET(p->data))
font = vbi_font_descriptors + p->data;
else
font = pg->font[0];
printv("enh col %d modify character set %d\n", active_column, p->data);
}
break;
case 0x09: /* G0 character */
if (max_level >= VBI_WST_LEVEL_2p5 && p->data >= 0x20) {
if (column > active_column)
flush(column);
unicode = vbi_teletext_unicode(font->G0, NO_SUBSET, p->data);
goto store;
}
break;
case 0x0A: /* reserved */
break;
case 0x0C: /* display attributes */
if (max_level < VBI_WST_LEVEL_2p5)
break;
if (column > active_column)
flush(column);
ac.size = ((p->data & 0x40) ? VBI_DOUBLE_WIDTH : 0)
+ ((p->data & 1) ? VBI_DOUBLE_HEIGHT : 0);
mac.size = ~0;
if (p->data & 2) {
if (vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE))
ac.opacity = VBI_SEMI_TRANSPARENT;
else
ac.opacity = VBI_TRANSPARENT_SPACE;
} else
ac.opacity = pg->page_opacity[1];
mac.opacity = ~0;
ac.conceal = !!(p->data & 4);
mac.conceal = ~0;
/* (p->data & 8) reserved */
invert = p->data & 0x10;
ac.underline = !!(p->data & 0x20);
mac.underline = ~0;
printv("enh col %d display attr 0x%02x\n", active_column, p->data);
break;
case 0x0D: /* drcs character invocation */
{
int normal = p->data >> 6;
int offset = p->data & 0x3F;
vt_page *dvtp;
page_function function;
int pgno, page, i = 0;
if (max_level < VBI_WST_LEVEL_2p5)
break;
if (offset >= 48)
break; /* invalid */
if (column > active_column)
flush(column);
page = normal * 16 + drcs_s1[normal];
printv("enh col %d DRCS %d/0x%02x\n", active_column, page, p->data);
/* if (!pg->drcs[page]) */ {
if (!normal) {
function = PAGE_FUNCTION_GDRCS;
pgno = vtp->data.lop.link[26].pgno;
if (NO_PAGE(pgno)) {
if (max_level < VBI_WST_LEVEL_3p5
|| NO_PAGE(pgno = mag->drcs_link[8]))
pgno = mag->drcs_link[0];
} else
printv("... X/27/4 GDRCS overrides MOT\n");
} else {
function = PAGE_FUNCTION_DRCS;
pgno = vtp->data.lop.link[25].pgno;
if (NO_PAGE(pgno)) {
if ((i = mag->drcs_lut[vtp->pgno & 0xFF]) == 0) {
printv("... MOT drcs_lut empty\n");
return FALSE; /* has no link (yet) */
}
if (max_level < VBI_WST_LEVEL_3p5
|| NO_PAGE(pgno = mag->drcs_link[i + 8]))
pgno = mag->drcs_link[i + 0];
} else
printv("... X/27/4 DRCS overrides MOT\n");
}
if (NO_PAGE(pgno)) {
printv("... dead MOT link %d\n", i);
return FALSE; /* has no link (yet) */
}
printv("... %s drcs from page %03x/%04x\n",
normal ? "normal" : "global", pgno, drcs_s1[normal]);
dvtp = vbi_cache_get(vbi,
pgno, drcs_s1[normal], 0x000F);
if (!dvtp) {
printv("... page not cached\n");
return FALSE;
}
if (dvtp->function == PAGE_FUNCTION_UNKNOWN) {
if (!(dvtp = vbi_convert_page(vbi, dvtp, TRUE, function))) {
printv("... no g/drcs page or hamming error\n");
return FALSE;
}
} else if (dvtp->function == PAGE_FUNCTION_DRCS) {
dvtp->function = function;
} else if (dvtp->function != function) {
printv("... source page wrong function %d, expected %d\n",
dvtp->function, function);
return FALSE;
}
if (dvtp->data.drcs.invalid & (1ULL << offset)) {
printv("... invalid drcs, prob. tx error\n");
return FALSE;
}
pg->drcs[page] = dvtp->data.drcs.bits[0];
}
unicode = 0xF000 + (page << 6) + offset;
goto store;
}
case 0x0E: /* font style */
{
int italic, bold, proportional;
int col, row, count;
vbi_char *acp;
if (max_level < VBI_WST_LEVEL_3p5)
break;
row = inv_row + active_row;
count = (p->data >> 4) + 1;
acp = &pg->text[row * EXT_COLUMNS];
proportional = (p->data >> 0) & 1;
bold = (p->data >> 1) & 1;
italic = (p->data >> 2) & 1;
while (row < ROWS && count > 0) {
for (col = inv_column + column; col < COLUMNS; col++) {
acp[col].italic = italic;
acp[col].bold = bold;
acp[col].proportional = proportional;
}
acp += EXT_COLUMNS;
row++;
count--;
}
printv("enh col %d font style 0x%02x\n", active_column, p->data);
break;
}
case 0x0F: /* G2 character */
if (p->data >= 0x20) {
if (column > active_column)
flush(column);
unicode = vbi_teletext_unicode(font->G2, NO_SUBSET, p->data);
goto store;
}
break;
case 0x10 ... 0x1F: /* characters including diacritical marks */
if (p->data >= 0x20) {
if (column > active_column)
flush(column);
unicode = vbi_teletext_composed_unicode(
p->mode - 0x10, p->data);
store:
printv("enh row %d col %d print 0x%02x/0x%02x -> 0x%04x\n",
active_row, active_column, p->mode, p->data,
unicode);
ac.unicode = unicode;
mac.unicode = ~0;
}
break;
}
}
}
swedish:
if (ptable) {
if (pt >= ptable && (pdc_hr || pt->length == 0))
pt--; /* incomplete start or end tag */
if (1)
dump_pex26(ptable, pt - ptable + 1);
}
if (0) {
acp = pg->text;
for (active_row = 0; active_row < ROWS; active_row++) {
printv("%2d: ", active_row);
for (active_column = 0; active_column < COLUMNS; acp++, active_column++) {
printv("%04x ", acp->unicode);
}
printv("\n");
acp += EXT_COLUMNS - COLUMNS;
}
}
return TRUE;
}
static void
post_enhance(vbi_page *pg, int display_rows)
{
int last_row = MIN(display_rows, ROWS) - 2;
vbi_char ac, *acp;
int column, row;
acp = pg->text;
for (row = 0; row <= last_row; row++) {
for (column = 0; column < COLUMNS; acp++, column++) {
if (1)
printv("%c", _vbi_to_ascii (acp->unicode));
else
printv("%04xF%dB%dS%dO%d ", acp->unicode,
acp->foreground, acp->background,
acp->size, acp->opacity);
if (acp->opacity == VBI_TRANSPARENT_SPACE
|| (acp->foreground == VBI_TRANSPARENT_BLACK
&& acp->background == VBI_TRANSPARENT_BLACK)) {
acp->opacity = VBI_TRANSPARENT_SPACE;
acp->unicode = 0x0020;
} else if (acp->background == VBI_TRANSPARENT_BLACK) {
acp->opacity = VBI_SEMI_TRANSPARENT;
}
/* transparent foreground not implemented */
switch (acp->size) {
case VBI_NORMAL_SIZE:
if (row < last_row
&& (acp[EXT_COLUMNS].size == VBI_DOUBLE_HEIGHT2
|| acp[EXT_COLUMNS].size == VBI_DOUBLE_SIZE2)) {
acp[EXT_COLUMNS].unicode = 0x0020;
acp[EXT_COLUMNS].size = VBI_NORMAL_SIZE;
}
if (column < 39
&& (acp[1].size == VBI_OVER_TOP
|| acp[1].size == VBI_OVER_BOTTOM)) {
acp[1].unicode = 0x0020;
acp[1].size = VBI_NORMAL_SIZE;
}
break;
case VBI_DOUBLE_HEIGHT:
if (row < last_row) {
ac = acp[0];
ac.size = VBI_DOUBLE_HEIGHT2;
acp[EXT_COLUMNS] = ac;
}
break;
case VBI_DOUBLE_SIZE:
if (row < last_row) {
ac = acp[0];
ac.size = VBI_DOUBLE_SIZE2;
acp[EXT_COLUMNS] = ac;
ac.size = VBI_OVER_BOTTOM;
acp[EXT_COLUMNS + 1] = ac;
}
/* fall through */
case VBI_DOUBLE_WIDTH:
if (column < 39) {
ac = acp[0];
ac.size = VBI_OVER_TOP;
acp[1] = ac;
}
break;
default:
break;
}
}
printv("\n");
acp += EXT_COLUMNS - COLUMNS;
}
}
static inline vbi_bool
default_object_invocation(vbi_decoder *vbi, vt_magazine *mag,
vt_extension *ext, vbi_page *pg, vt_page *vtp,
vbi_wst_level max_level, vbi_bool header_only)
{
vt_pop_link *pop;
int i, order;
if (!(i = mag->pop_lut[vtp->pgno & 0xFF]))
return FALSE; /* has no link (yet) */
pop = mag->pop_link + i + 8;
if (max_level < VBI_WST_LEVEL_3p5 || NO_PAGE(pop->pgno)) {
pop = mag->pop_link + i;
if (NO_PAGE(pop->pgno)) {
printv("default object has dead MOT pop link %d\n", i);
return FALSE;
}
}
order = pop->default_obj[0].type > pop->default_obj[1].type;
for (i = 0; i < 2; i++) {
object_type type = pop->default_obj[i ^ order].type;
vt_triplet *trip;
int remaining_max_triplets;
if (type == OBJ_TYPE_NONE)
continue;
printv("default object #%d invocation, type %d\n", i ^ order, type);
trip = resolve_obj_address(vbi, type, pop->pgno,
pop->default_obj[i ^ order].address, PAGE_FUNCTION_POP,
&remaining_max_triplets);
if (!trip)
return FALSE;
if (!enhance(vbi, mag, ext, pg, vtp, type, trip,
remaining_max_triplets, 0, 0, max_level,
header_only, NULL))
return FALSE;
}
return TRUE;
}
/**
* @internal
* @param vbi Initialized vbi_decoder context.
* @param pg Place to store the formatted page.
* @param vtp Raw Teletext page.
* @param max_level Format the page at this Teletext implementation level.
* @param display_rows Number of rows to format, between 1 ... 25.
* @param navigation Analyse the page and add navigation links,
* including TOP and FLOF.
*
* Format a page @a pg from a raw Teletext page @a vtp. This function is
* used internally by libzvbi only.
*
* @return
* @c TRUE if the page could be formatted.
*/
int
vbi_format_vt_page(vbi_decoder *vbi,
vbi_page *pg, vt_page *vtp,
vbi_wst_level max_level,
int display_rows, vbi_bool navigation)
{
char buf[16];
vt_magazine *mag;
vt_extension *ext;
int column, row, i;
if (vtp->function != PAGE_FUNCTION_LOP &&
vtp->function != PAGE_FUNCTION_TRIGGER)
return FALSE;
printv("\nFormatting page %03x/%04x pg=%p lev=%d rows=%d nav=%d\n",
vtp->pgno, vtp->subno, pg, max_level, display_rows, navigation);
display_rows = SATURATE(display_rows, 1, ROWS);
pg->vbi = vbi;
pg->nuid = vbi->network.ev.network.nuid;
pg->pgno = vtp->pgno;
pg->subno = vtp->subno;
pg->rows = display_rows;
pg->columns = EXT_COLUMNS;
pg->dirty.y0 = 0;
pg->dirty.y1 = ROWS - 1;
pg->dirty.roll = 0;
mag = (max_level <= VBI_WST_LEVEL_1p5) ?
vbi->vt.magazine : vbi->vt.magazine + (vtp->pgno >> 8);
if (vtp->data.lop.ext)
ext = &vtp->data.ext_lop.ext;
else
ext = &mag->extension;
/* Character set designation */
character_set_designation(pg->font, ext, vtp);
/* Colors */
screen_color(pg, vtp->flags, ext->def_screen_color);
vbi_transp_colormap(vbi, pg->color_map, ext->color_map, 40);
pg->drcs_clut = ext->drcs_clut;
/* Opacity */
pg->page_opacity[1] =
(vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE | C10_INHIBIT_DISPLAY)) ?
VBI_TRANSPARENT_SPACE : VBI_OPAQUE;
pg->boxed_opacity[1] =
(vtp->flags & C10_INHIBIT_DISPLAY) ?
VBI_TRANSPARENT_SPACE : VBI_SEMI_TRANSPARENT;
if (vtp->flags & C7_SUPPRESS_HEADER) {
pg->page_opacity[0] = VBI_TRANSPARENT_SPACE;
pg->boxed_opacity[0] = VBI_TRANSPARENT_SPACE;
} else {
pg->page_opacity[0] = pg->page_opacity[1];
pg->boxed_opacity[0] = pg->boxed_opacity[1];
}
/* DRCS */
memset(pg->drcs, 0, sizeof(pg->drcs));
/* Current page number in header */
sprintf(buf, "\2%x.%02x\7", vtp->pgno, vtp->subno & 0xff);
/* Level 1 formatting */
i = 0;
pg->double_height_lower = 0;
for (row = 0; row < display_rows; row++) {
struct vbi_font_descr *font;
int mosaic_unicodes; /* 0xEE00 separate, 0xEE20 contiguous */
int held_mosaic_unicode;
int esc;
vbi_bool hold, mosaic;
vbi_bool double_height, wide_char;
vbi_char ac, *acp = &pg->text[row * EXT_COLUMNS];
held_mosaic_unicode = 0xEE20; /* G1 block mosaic, blank, contiguous */
memset(&ac, 0, sizeof(ac));
ac.unicode = 0x0020;
ac.foreground = ext->foreground_clut + VBI_WHITE;
ac.background = ext->background_clut + VBI_BLACK;
mosaic_unicodes = 0xEE20; /* contiguous */
ac.opacity = pg->page_opacity[row > 0];
font = pg->font[0];
esc = 0;
hold = FALSE;
mosaic = FALSE;
double_height = FALSE;
wide_char = FALSE;
acp[COLUMNS] = ac; /* artificial column 41 */
for (column = 0; column < COLUMNS; ++column) {
int raw;
if (row == 0 && column < 8) {
raw = buf[column];
i++;
} else if ((raw = vbi_unpar8 (vtp->data.lop.raw[0][i++])) < 0)
raw = ' ';
/* set-at spacing attributes */
switch (raw) {
case 0x09: /* steady */
ac.flash = FALSE;
break;
case 0x0C: /* normal size */
ac.size = VBI_NORMAL_SIZE;
break;
case 0x18: /* conceal */
ac.conceal = TRUE;
break;
case 0x19: /* contiguous mosaics */
mosaic_unicodes = 0xEE20;
break;
case 0x1A: /* separated mosaics */
mosaic_unicodes = 0xEE00;
break;
case 0x1C: /* black background */
ac.background = ext->background_clut + VBI_BLACK;
break;
case 0x1D: /* new background */
ac.background = ext->background_clut + (ac.foreground & 7);
break;
case 0x1E: /* hold mosaic */
hold = TRUE;
break;
}
if (raw <= 0x1F)
ac.unicode = (hold & mosaic) ? held_mosaic_unicode : 0x0020;
else
if (mosaic && (raw & 0x20)) {
held_mosaic_unicode = mosaic_unicodes + raw - 0x20;
ac.unicode = held_mosaic_unicode;
} else
ac.unicode = vbi_teletext_unicode(font->G0,
font->subset, raw);
if (!wide_char) {
acp[column] = ac;
wide_char = /*!!*/(ac.size & VBI_DOUBLE_WIDTH);
if (wide_char && column < (COLUMNS - 1)) {
acp[column + 1] = ac;
acp[column + 1].size = VBI_OVER_TOP;
}
} else
wide_char = FALSE;
/* set-after spacing attributes */
switch (raw) {
case 0x00 ... 0x07: /* alpha + foreground color */
ac.foreground = ext->foreground_clut + (raw & 7);
ac.conceal = FALSE;
mosaic = FALSE;
break;
case 0x08: /* flash */
ac.flash = TRUE;
break;
case 0x0A: /* end box */
if (column < (COLUMNS - 1)
&& vbi_unpar8 (vtp->data.lop.raw[0][i]) == 0x0a)
ac.opacity = pg->page_opacity[row > 0];
break;
case 0x0B: /* start box */
if (column < (COLUMNS - 1)
&& vbi_unpar8 (vtp->data.lop.raw[0][i]) == 0x0b)
ac.opacity = pg->boxed_opacity[row > 0];
break;
case 0x0D: /* double height */
if (row <= 0 || row >= 23)
break;
ac.size = VBI_DOUBLE_HEIGHT;
double_height = TRUE;
break;
case 0x0E: /* double width */
printv("spacing col %d row %d double width\n", column, row);
if (column < (COLUMNS - 1))
ac.size = VBI_DOUBLE_WIDTH;
break;
case 0x0F: /* double size */
printv("spacing col %d row %d double size\n", column, row);
if (column >= (COLUMNS - 1) || row <= 0 || row >= 23)
break;
ac.size = VBI_DOUBLE_SIZE;
double_height = TRUE;
break;
case 0x10 ... 0x17: /* mosaic + foreground color */
ac.foreground = ext->foreground_clut + (raw & 7);
ac.conceal = FALSE;
mosaic = TRUE;
break;
case 0x1F: /* release mosaic */
hold = FALSE;
break;
case 0x1B: /* ESC */
font = pg->font[esc ^= 1];
break;
}
}
if (double_height) {
for (column = 0; column < EXT_COLUMNS; column++) {
ac = acp[column];
switch (ac.size) {
case VBI_DOUBLE_HEIGHT:
ac.size = VBI_DOUBLE_HEIGHT2;
acp[EXT_COLUMNS + column] = ac;
break;
case VBI_DOUBLE_SIZE:
ac.size = VBI_DOUBLE_SIZE2;
acp[EXT_COLUMNS + column] = ac;
ac.size = VBI_OVER_BOTTOM;
acp[EXT_COLUMNS + (++column)] = ac;
break;
default: /* NORMAL, DOUBLE_WIDTH, OVER_TOP */
ac.size = VBI_NORMAL_SIZE;
ac.unicode = 0x0020;
acp[EXT_COLUMNS + column] = ac;
break;
}
}
i += COLUMNS;
row++;
pg->double_height_lower |= 1 << row;
}
}
if (0) {
if (row < ROWS) {
vbi_char ac;
memset(&ac, 0, sizeof(ac));
ac.foreground = ext->foreground_clut + VBI_WHITE;
ac.background = ext->background_clut + VBI_BLACK;
ac.opacity = pg->page_opacity[1];
ac.unicode = 0x0020;
for (i = row * EXT_COLUMNS; i < ROWS * EXT_COLUMNS; i++)
pg->text[i] = ac;
}
}
/* Local enhancement data and objects */
if (max_level >= VBI_WST_LEVEL_1p5 && display_rows > 0) {
vbi_page page;
vbi_bool success;
memcpy(&page, pg, sizeof(page));
if (!(vtp->flags & (C5_NEWSFLASH | C6_SUBTITLE))) {
pg->boxed_opacity[0] = VBI_TRANSPARENT_SPACE;
pg->boxed_opacity[1] = VBI_TRANSPARENT_SPACE;
}
if (vtp->enh_lines & 1) {
printv("enhancement packets %08x\n", vtp->enh_lines);
success = enhance(vbi, mag, ext, pg, vtp, LOCAL_ENHANCEMENT_DATA,
vtp->data.enh_lop.enh, elements(vtp->data.enh_lop.enh),
0, 0, max_level, display_rows == 1, NULL);
// here --^
} else
success = default_object_invocation(vbi, mag, ext, pg, vtp,
max_level, display_rows == 1);
if (success) {
if (max_level >= VBI_WST_LEVEL_2p5)
post_enhance(pg, display_rows);
} else
memcpy(pg, &page, sizeof(*pg));
}
/* Navigation */
if (navigation) {
pg->nav_link[5].pgno = vbi->vt.initial_page.pgno;
pg->nav_link[5].subno = vbi->vt.initial_page.subno;
for (row = 1; row < MIN(ROWS - 1, display_rows); row++)
zap_links(pg, row);
if (display_rows >= ROWS) {
if (vtp->data.lop.flof) {
if (vtp->data.lop.link[5].pgno >= 0x100
&& vtp->data.lop.link[5].pgno <= 0x899
&& (vtp->data.lop.link[5].pgno & 0xFF) != 0xFF) {
pg->nav_link[5].pgno = vtp->data.lop.link[5].pgno;
pg->nav_link[5].subno = vtp->data.lop.link[5].subno;
}
if (vtp->lop_lines & (1 << 24))
flof_links(pg, vtp);
else
flof_navigation_bar(pg, vtp);
} else if (vbi->vt.top)
top_navigation_bar(vbi, pg, vtp);
// pdc_method_a(pg, vtp, NULL);
}
}
if (0) {
vbi_char *acp;
for (row = ROWS - 1, acp = pg->text + EXT_COLUMNS * row; row < ROWS; row++) {
fprintf(stderr, "%2d: ", row);
for (column = 0; column < COLUMNS; acp++, column++) {
fprintf(stderr, "%04x ", acp->unicode);
}
fprintf(stderr, "\n");
acp += EXT_COLUMNS - COLUMNS;
}
}
return TRUE;
}
/**
* @param vbi Initialized vbi_decoder context.
* @param pg Place to store the formatted page.
* @param pgno Page number of the page to fetch, see vbi_pgno.
* @param subno Subpage number to fetch (optional @c VBI_ANY_SUBNO).
* @param max_level Format the page at this Teletext implementation level.
* @param display_rows Number of rows to format, between 1 ... 25.
* @param navigation Analyse the page and add navigation links,
* including TOP and FLOF.
*
* Fetches a Teletext page designated by @a pgno and @a subno from the
* cache, formats and stores it in @a pg. Formatting is limited to row
* 0 ... @a display_rows - 1 inclusive. The really useful values
* are 1 (format header only) or 25 (everything). Likewise
* @a navigation can be used to save unnecessary formatting time.
*
* Although safe to do, this function is not supposed to be called from
* an event handler since rendering may block decoding for extended
* periods of time.
*
* @return
* @c FALSE if the page is not cached or could not be formatted
* for other reasons, for instance is a data page not intended for
* display. Level 2.5/3.5 pages which could not be formatted e. g.
* due to referencing data pages not in cache are formatted at a
* lower level.
*/
vbi_bool
vbi_fetch_vt_page(vbi_decoder *vbi, vbi_page *pg,
vbi_pgno pgno, vbi_subno subno,
vbi_wst_level max_level,
int display_rows, vbi_bool navigation)
{
vt_page *vtp;
int row;
switch (pgno) {
case 0x900:
if (subno == VBI_ANY_SUBNO)
subno = 0;
if (!vbi->vt.top || !top_index(vbi, pg, subno))
return FALSE;
pg->nuid = vbi->network.ev.network.nuid;
pg->pgno = 0x900;
pg->subno = subno;
post_enhance(pg, ROWS);
for (row = 1; row < ROWS; row++)
zap_links(pg, row);
return TRUE;
default:
vtp = vbi_cache_get(vbi, pgno, subno, -1);
if (!vtp)
return FALSE;
return vbi_format_vt_page(vbi, pg, vtp,
max_level, display_rows, navigation);
}
}
syntax highlighted by Code2HTML, v. 0.9.1