/*
 *  libzvbi - Closed Caption and Teletext rendering
 *
 *  Copyright (C) 2000, 2001, 2002 Michael H. Schimek
 *
 *  Based on code from AleVT 1.5.1
 *  Copyright (C) 1998, 1999 Edgar Toernig <froese@gmx.de>
 *  Copyright (C) 1999 Paul Ortyl <ortylp@from.pl>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  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: exp-gfx.c,v 1.11 2006/02/10 06:25:37 mschimek Exp $ */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>

#include "lang.h"
#include "export.h"
#include "exp-gfx.h"
#include "vt.h" /* VBI_TRANSPARENT_BLACK */

#include "wstfont2.xbm"
#include "ccfont2.xbm"

/* Teletext character cell dimensions - hardcoded (DRCS) */

#define TCW 12
#define TCH 10

#define TCPL (wstfont2_width / TCW * wstfont2_height / TCH)

/* Closed Caption character cell dimensions */

#define CCW 16
#define CCH 26 /* line doubled */

#define CCPL (ccfont2_width / CCW * ccfont2_height / CCH)

static void init_gfx(void) __attribute__ ((constructor));

static void
init_gfx(void)
{
	uint8_t *t, *p;
	int i, j;

	/* de-interleave font image (puts all chars in row 0) */

	if (!(t = malloc(wstfont2_width * wstfont2_height / 8)))
		exit(EXIT_FAILURE);

	for (p = t, i = 0; i < TCH; i++)
		for (j = 0; j < wstfont2_height; p += wstfont2_width / 8, j += TCH)
			memcpy(p, wstfont2_bits + (j + i) * wstfont2_width / 8,
			       wstfont2_width / 8);

	memcpy(wstfont2_bits, t, wstfont2_width * wstfont2_height / 8);
	free (t);

	if (!(t = malloc(ccfont2_width * ccfont2_height / 8)))
		exit(EXIT_FAILURE);

	for (p = t, i = 0; i < CCH; i++)
		for (j = 0; j < ccfont2_height; p += ccfont2_width / 8, j += CCH)
			memcpy(p, ccfont2_bits + (j + i) * ccfont2_width / 8,
			       ccfont2_width / 8);

	memcpy(ccfont2_bits, t, ccfont2_width * ccfont2_height / 8);
	free(t);
}

/**
 * @internal
 * @param c Unicode.
 * @param italic @c TRUE to switch to slanted character set (doesn't affect
 *          Hebrew and Arabic). If this is a G1 block graphic character
 *          switch to separated block mosaic set.
 * 
 * Translate Unicode character to glyph number in wstfont2 image. 
 * 
 * @return
 * Glyph number.
 */
static unsigned int
unicode_wstfont2(unsigned int c, int italic)
{
	static const unsigned short specials[] = {
		0x01B5, 0x2016, 0x01CD, 0x01CE, 0x0229, 0x0251, 0x02DD, 0x02C6,
		0x02C7, 0x02C9, 0x02CA, 0x02CB, 0x02CD, 0x02CF, 0x02D8, 0x02D9,
		0x02DA, 0x02DB, 0x02DC, 0x2014, 0x2018, 0x2019, 0x201C,	0x201D,
		0x20A0, 0x2030, 0x20AA, 0x2122, 0x2126, 0x215B, 0x215C, 0x215D,
		0x215E, 0x2190, 0x2191, 0x2192, 0x2193, 0x25A0, 0x266A, 0xE800,
		0xE75F };
	const unsigned int invalid = 357;
	unsigned int i;

	if (c < 0x0180) {
		if (c < 0x0080) {
			if (c < 0x0020)
				return invalid;
			else /* %3 Basic Latin (ASCII) 0x0020 ... 0x007F */
				c = c - 0x0020 + 0 * 32;
		} else if (c < 0x00A0)
			return invalid;
		else /* %3 Latin-1 Supplement, Latin Extended-A 0x00A0 ... 0x017F */
			c = c - 0x00A0 + 3 * 32;
	} else if (c < 0xEE00) {
		if (c < 0x0460) {
			if (c < 0x03D0) {
				if (c < 0x0370)
					goto special;
				else /* %5 Greek 0x0370 ... 0x03CF */
					c = c - 0x0370 + 12 * 32;
			} else if (c < 0x0400)
				return invalid;
			else /* %5 Cyrillic 0x0400 ... 0x045F */
				c = c - 0x0400 + 15 * 32;
		} else if (c < 0x0620) {
			if (c < 0x05F0) {
				if (c < 0x05D0)
					return invalid;
				else /* %6 Hebrew 0x05D0 ... 0x05EF */
					return c - 0x05D0 + 18 * 32;
			} else if (c < 0x0600)
				return invalid;
			else /* %6 Arabic 0x0600 ... 0x061F */
				return c - 0x0600 + 19 * 32;
		} else if (c >= 0xE600 && c < 0xE740)
			return c - 0xE600 + 19 * 32; /* %6 Arabic (TTX) */
		else
			goto special;
	} else if (c < 0xEF00) { /* %3 G1 Graphics */
		return (c ^ 0x20) - 0xEE00 + 23 * 32;
	} else if (c < 0xF000) { /* %4 G3 Graphics */
		return c - 0xEF20 + 27 * 32;
	} else /* 0xF000 ... 0xF7FF reserved for DRCS */
		return invalid;

	if (italic)
		return c + 31 * 32;
	else
		return c;
special:
	for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++)
		if (specials[i] == c) {
			if (italic)
				return i + 41 * 32;
			else
				return i + 10 * 32;
		}

	return invalid;
}

/**
 * @internal
 * @param c Unicode.
 * @param italic @c TRUE to switch to slanted character set.
 * 
 * Translate Unicode character to glyph number in ccfont2 image. 
 * 
 * @return
 * Glyph number.
 */
static unsigned int
unicode_ccfont2(unsigned int c, int italic)
{
	static const unsigned short specials[] = {
								0x00E1, 0x00E9,
		0x00ED, 0x00F3, 0x00FA, 0x00E7, 0x00F7, 0x00D1, 0x00F1, 0x25A0,
		0x00AE, 0x00B0, 0x00BD, 0x00BF, 0x2122, 0x00A2, 0x00A3, 0x266A,
		0x00E0, 0x0020, 0x00E8, 0x00E2, 0x00EA, 0x00EE, 0x00F4, 0x00FB };
	unsigned int i;

	if (c < 0x0020)
		c = 15; /* invalid */
	else if (c < 0x0080)
		c = c;
	else {
		for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++)
			if (specials[i] == c) {
				c = i + 6;
				goto slant;
			}

		c = 15; /* invalid */
	}

slant:
	if (italic)
		c += 4 * 32;

	return c;
}

/**
 * @internal
 * @param p Plane of @a canvas_type char, short, int.
 * @param i Index.
 *
 * @return
 * Pixel @a i in plane @a p.
 */
#define peek(p, i)							\
((canvas_type == sizeof(uint8_t)) ? ((uint8_t *)(p))[i] :		\
    ((canvas_type == sizeof(uint16_t)) ? ((uint16_t *)(p))[i] :		\
	((uint32_t *)(p))[i]))

/**
 * @internal
 * @param p Plane of @a canvas_type char, short, int.
 * @param i Index.
 * @param v Value.
 * 
 * Set pixel @a i in plane @a p to value @a v.
 */
#define poke(p, i, v)							\
((canvas_type == sizeof(uint8_t)) ? (((uint8_t *)(p))[i] = (v)) :	\
    ((canvas_type == sizeof(uint16_t)) ? (((uint16_t *)(p))[i] = (v)) :	\
	(((uint32_t *)(p))[i] = (v))))

/**
 * @internal
 * @param canvas_type sizeof(char, short, int).
 * @param canvas Pointer to image plane where the character is to be drawed.
 * @param rowstride @a canvas <em>byte</em> distance from line to line.
 * @param pen Pointer to color palette of @a canvas_type (index 0 background
 *   pixels, index 1 foreground pixels).
 * @param font Pointer to font image with width @a cpl x @a cw pixels, height
 *   @a ch pixels, depth one bit, bit '1' is foreground.
 * @param cpl Chars per line (number of characters in @a font image).
 * @param cw Character cell width in pixels.
 * @param ch Character cell height in pixels.
 * @param glyph Glyph number in font image, 0 ... @a cpl - 1.
 * @param bold Draw character bold (font image | font image << 1).
 * @param underline Bit mask of character rows. For each bit
 *   1 << (n = 0 ... @a ch - 1) set all of character row n to
 *   foreground color.
 * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left
 *   and right half), DOUBLE_HEIGHT (draws upper half only),
 *   DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2
 *   (lower half), DOUBLE_SIZE2 (left and right lower half).
 * 
 * Draw one character (function template - define a static version with
 * constant @a canvas_type, @a font, @a cpl, @a cw, @a ch).
 */
static inline void
draw_char(int canvas_type, uint8_t *canvas, int rowstride,
	  uint8_t *pen, uint8_t *font, int cpl, int cw, int ch,
	  int glyph, int bold, unsigned int underline, vbi_size size)
{
	uint8_t *src;
	int shift, x, y;

	bold = !!bold;
	assert(cw >= 8 && cw <= 16);
	assert(ch >= 1 && cw <= 31);

	x = glyph * cw;
	shift = x & 7;
	src = font + (x >> 3);

	switch (size) {
	case VBI_DOUBLE_HEIGHT2:
	case VBI_DOUBLE_SIZE2:
		src += cpl * cw / 8 * ch / 2;
		underline >>= ch / 2;

	case VBI_DOUBLE_HEIGHT:
	case VBI_DOUBLE_SIZE: 
		ch >>= 1;

	default:
		break;
	}

	for (y = 0; y < ch; underline >>= 1, y++) {
		int bits = ~0;

		if (!(underline & 1)) {
#if #cpu (i386)
			bits = (*((uint16_t *) src) >> shift);
#else
                        /* unaligned/little endian */
			bits = ((src[1] * 256 + src[0]) >> shift);
#endif
			bits |= bits << bold;
		}

		switch (size) {
		case VBI_NORMAL_SIZE:
			for (x = 0; x < cw; bits >>= 1, x++)
				poke(canvas, x, peek(pen, bits & 1));

			canvas += rowstride;

			break;

		case VBI_DOUBLE_HEIGHT:
		case VBI_DOUBLE_HEIGHT2:
			for (x = 0; x < cw; bits >>= 1, x++) {
				unsigned int col = peek(pen, bits & 1);

				poke(canvas, x, col);
				poke(canvas, x + rowstride / canvas_type, col);
			}

			canvas += rowstride * 2;

			break;

		case VBI_DOUBLE_WIDTH:
			for (x = 0; x < cw * 2; bits >>= 1, x += 2) {
				unsigned int col = peek(pen, bits & 1);

				poke(canvas, x + 0, col);
				poke(canvas, x + 1, col);
			}

			canvas += rowstride;

			break;

		case VBI_DOUBLE_SIZE:
		case VBI_DOUBLE_SIZE2:
			for (x = 0; x < cw * 2; bits >>= 1, x += 2) {
				unsigned int col = peek(pen, bits & 1);

				poke(canvas, x + 0, col);
				poke(canvas, x + 1, col);
				poke(canvas, x + rowstride / canvas_type + 0, col);
				poke(canvas, x + rowstride / canvas_type + 1, col);
			}

			canvas += rowstride * 2;

			break;

		default:
			break;
		}

		src += cpl * cw / 8;
	}
}

/**
 * @internal
 * @param canvas_type sizeof(char, short, int).
 * @param canvas Pointer to image plane where the character is to be drawed.
 * @param rowstride @a canvas <em>byte</em> distance from line to line.
 * @param pen Pointer to color palette of @a canvas_type (index 0 ... 1 for
 *   depth 1 DRCS, 0 ... 3 for depth 2, 0 ... 15 for depth 4).
 * @param color Offset into color palette.
 * @param font Pointer to DRCS image. Each pixel is coded in four bits, an
 *   index into the color palette, and stored in LE order (i. e. first
 *   pixel 0x0F, second pixel 0xF0). Character size is 12 x 10 pixels,
 *   60 bytes, without padding.
 * @param glyph Glyph number in font image, 0x00 ... 0x3F.
 * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left
 *   and right half), DOUBLE_HEIGHT (draws upper half only),
 *   DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2
 *   (lower half), DOUBLE_SIZE2 (left and right lower half).
 * 
 * Draw one Teletext Dynamically Redefinable Character (function template -
 * define a static version with constant @a canvas_type, @a font).
 */
static inline void
draw_drcs(int canvas_type, uint8_t *canvas, unsigned int rowstride,
	  uint8_t *pen, int color, uint8_t *font, int glyph, vbi_size size)
{
	uint8_t *src;
	unsigned int col;
	int x, y;

	src = font + glyph * 60;
	pen = pen + color * canvas_type;

	switch (size) {
	case VBI_NORMAL_SIZE:
		for (y = 0; y < TCH; canvas += rowstride, y++)
			for (x = 0; x < 12; src++, x += 2) {
				poke(canvas, x + 0, peek(pen, *src & 15));
				poke(canvas, x + 1, peek(pen, *src >> 4));
			}
		break;

	case VBI_DOUBLE_HEIGHT2:
		src += 30;

	case VBI_DOUBLE_HEIGHT:
		for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++)
			for (x = 0; x < 12; src++, x += 2) {
				col = peek(pen, *src & 15);
				poke(canvas, x + 0, col);
				poke(canvas, x + rowstride / canvas_type + 0, col);

				col = peek(pen, *src >> 4);
				poke(canvas, x + 1, col);
				poke(canvas, x + rowstride / canvas_type + 1, col);
			}
		break;

	case VBI_DOUBLE_WIDTH:
		for (y = 0; y < TCH; canvas += rowstride, y++)
			for (x = 0; x < 12 * 2; src++, x += 4) {
				col = peek(pen, *src & 15);
				poke(canvas, x + 0, col);
				poke(canvas, x + 1, col);

				col = peek(pen, *src >> 4);
				poke(canvas, x + 2, col);
				poke(canvas, x + 3, col);
			}
		break;

	case VBI_DOUBLE_SIZE2:
		src += 30;

	case VBI_DOUBLE_SIZE:
		for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++)
			for (x = 0; x < 12 * 2; src++, x += 4) {
				col = peek(pen, *src & 15);
				poke(canvas, x + 0, col);
				poke(canvas, x + 1, col);
				poke(canvas, x + rowstride / canvas_type + 0, col);
				poke(canvas, x + rowstride / canvas_type + 1, col);

				col = peek(pen, *src >> 4);
				poke(canvas, x + 2, col);
				poke(canvas, x + 3, col);
				poke(canvas, x + rowstride / canvas_type + 2, col);
				poke(canvas, x + rowstride / canvas_type + 3, col);
			}
		break;

	default:
		break;
	}
}

/**
 * @internal
 * @param canvas_type sizeof(char, short, int).
 * @param canvas Pointer to image plane where the character is to be drawed.
 * @param rowstride @a canvas <em>byte</em> distance from line to line.
 * @param color Color value of @a canvas_type.
 * @param cw Character width in pixels.
 * @param ch Character height in pixels.
 * 
 * Draw blank character.
 */
static inline void
draw_blank(int canvas_type, uint8_t *canvas, unsigned int rowstride,
	   unsigned int color, int cw, int ch)
{
	int x, y;

	for (y = 0; y < ch; y++) {
		for (x = 0; x < cw; x++)
			poke(canvas, x, color);

		canvas += rowstride;
	}
}

/**
 * @param pg Source vbi_page, see vbi_fetch_cc_page().
 * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) permitted.
 * @param canvas Pointer to destination image (currently an array of vbi_rgba), this
 *   must be at least @a rowstride * @a height * 26 bytes large.
 * @param rowstride @a canvas <em>byte</em> distance from line to line.
 *   If this is -1, pg->columns * 16 * sizeof(vbi_rgba) bytes will be assumed.
 * @param column First source column, 0 ... pg->columns - 1.
 * @param row First source row, 0 ... pg->rows - 1.
 * @param width Number of columns to draw, 1 ... pg->columns.
 * @param height Number of rows to draw, 1 ... pg->rows.
 * 
 * Draw a subsection of a Closed Caption vbi_page. In this mode one
 * character occupies 16 x 26 pixels.
 */
void
vbi_draw_cc_page_region(vbi_page *pg,
			vbi_pixfmt fmt, void *canvas, int rowstride,
			int column, int row, int width, int height)
{
	vbi_rgba pen[2], *canvast = canvas;
	int count, row_adv;
	vbi_char *ac;

	if (fmt != VBI_PIXFMT_RGBA32_LE)
		return;

	if (0) {
		int i, j;

		for (i = 0; i < pg->rows; i++) {
			fprintf(stderr, "%2d: ", i);
			ac = &pg->text[i * pg->columns];
			for (j = 0; j < pg->columns; j++)
				fprintf(stderr, "%d%d%02x ",
					ac[j].foreground,
					ac[j].background,
					ac[j].unicode & 0xFF);
			fprintf(stderr, "\n");
		}
	}

	if (rowstride == -1)
		rowstride = pg->columns * CCW * sizeof(*canvast);

	row_adv = rowstride * CCH - width * CCW * sizeof(*canvast);

	for (; height > 0; height--, row++) {
		ac = &pg->text[row * pg->columns + column];

		for (count = width; count > 0; count--, ac++) {
			pen[0] = pg->color_map[ac->background];
			pen[1] = pg->color_map[ac->foreground];

			draw_char (sizeof (*canvast),
				   (uint8_t *) canvast,
				   rowstride,
				   (uint8_t *) pen,
				   (uint8_t *) ccfont2_bits,
				   CCPL, CCW, CCH,
				   unicode_ccfont2 (ac->unicode, ac->italic),
				   0 /* bold */,
				   (ac->underline
				    * (3 << 24)) /* cell row 24, 25 */,
				   VBI_NORMAL_SIZE);

			canvast += CCW;
		}

		canvast += row_adv / sizeof(*canvast);
	}
}

/**
 * @param pg Source page.
 * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) permitted.
 * @param canvas Pointer to destination image (currently an array of vbi_rgba), this
 *   must be at least @a rowstride * @a height * 10 bytes large.
 * @param rowstride @a canvas <em>byte</em> distance from line to line.
 *   If this is -1, pg->columns * 12 * sizeof(vbi_rgba) bytes will be assumed.
 * @param column First source column, 0 ... pg->columns - 1.
 * @param row First source row, 0 ... pg->rows - 1.
 * @param width Number of columns to draw, 1 ... pg->columns.
 * @param height Number of rows to draw, 1 ... pg->rows.
 * @param reveal If FALSE, draw characters flagged 'concealed' (see vbi_char) as
 *   space (U+0020).
 * @param flash_on If FALSE, draw characters flagged 'blink' (see vbi_char) as
 *   space (U+0020).
 * 
 * Draw a subsection of a Teletext vbi_page. In this mode one
 * character occupies 12 x 10 pixels.
 */
void
vbi_draw_vt_page_region(vbi_page *pg,
			vbi_pixfmt fmt, void *canvas, int rowstride,
			int column, int row, int width, int height,
			int reveal, int flash_on)
{
	vbi_rgba pen[64], *canvast = canvas;
	int count, row_adv;
	int conceal, off, unicode;
	vbi_char *ac;
	int i;

	if (fmt != VBI_PIXFMT_RGBA32_LE)
		return;

	if (0) {
		int i, j;

		for (i = 0; i < pg->rows; i++) {
			fprintf(stderr, "%2d: ", i);
			ac = &pg->text[i * pg->columns];
			for (j = 0; j < pg->columns; j++)
				fprintf(stderr, "%04x ", ac[j].unicode);
			fprintf(stderr, "\n");
		}
	}

	if (rowstride == -1)
		rowstride = pg->columns * 12 * sizeof(*canvast);

	row_adv = rowstride * 10 - width * 12 * sizeof(*canvast);

	conceal = !reveal;
	off = !flash_on;

	if (pg->drcs_clut)
		for (i = 2; i < 2 + 8 + 32; i++)
			pen[i] = pg->color_map[pg->drcs_clut[i]];

	for (; height > 0; height--, row++) {
		ac = &pg->text[row * pg->columns + column];

		for (count = width; count > 0; count--, ac++) {
			if ((ac->conceal & conceal) || (ac->flash & off))
				unicode = 0x0020;
			else
				unicode = ac->unicode;

			pen[0] = pg->color_map[ac->background];
			pen[1] = pg->color_map[ac->foreground];

			switch (ac->size) {
			case VBI_OVER_TOP:
			case VBI_OVER_BOTTOM:
				break;

			default:
				if (vbi_is_drcs(unicode)) {
					uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];

					if (font)
						draw_drcs(sizeof(*canvast), (uint8_t *) canvast, rowstride,
							  (uint8_t *) pen, ac->drcs_clut_offs,
							  font, unicode & 0x3F, ac->size);
					else /* shouldn't happen */
						draw_blank(sizeof(*canvast), (uint8_t *) canvast, rowstride,
							   pen[0], TCW, TCH);
				} else {
					draw_char (sizeof (*canvast),
						   (uint8_t *) canvast,
						   rowstride,
						   (uint8_t *) pen,
						   (uint8_t *) wstfont2_bits,
						   TCPL, TCW, TCH,
						   unicode_wstfont2 (unicode, ac->italic),
						   ac->bold,
						   ac->underline << 9 /* cell row 9 */,
						   ac->size);
				}
			}

			canvast += TCW;
		}

		canvast += row_adv / sizeof(*canvast);
	}
}

/*
 *  This won't scale with proportional spacing or custom fonts,
 *  to be removed.
 */

/**
 * @param w
 * @param h
 *
 * @deprecated
 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
 * Page size is in vbi_page.
 */
void
vbi_get_max_rendered_size(int *w, int *h)
{
  if (w) *w = 41 * TCW;
  if (h) *h = 25 * TCH;
}

/**
 * @param w
 * @param h
 *
 * @deprecated
 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
 */
void
vbi_get_vt_cell_size(int *w, int *h)
{
  if (w) *w = TCW;
  if (h) *h = TCH;
}

/*
 *  Shared export options
 */

typedef struct gfx_instance
{
	vbi_export		export;

	/* Options */
	unsigned		double_height : 1;
	/*
	 *  The raw image contains the same information a real TV
	 *  would show, however a TV overlays the image on both fields.
	 *  So raw pixel aspect is 2:1, and this option will double
	 *  lines adding redundant information. The resulting images
	 *  with pixel aspect 2:2 are still too narrow compared to a
	 *  real TV closer to 4:3 (11 MHz TXT pixel clock), but I
	 *  think one should export raw, not scaled data (which is
	 *  still possible in Zapping using the screenshot plugin).
	 */
} gfx_instance;

static vbi_export *
gfx_new(void)
{
	gfx_instance *gfx;

	if (!(gfx = calloc(1, sizeof(*gfx))))
		return NULL;

	return &gfx->export;
}

static void
gfx_delete(vbi_export *e)
{
	free(PARENT(e, gfx_instance, export));
}


static vbi_option_info
gfx_options[] = {
	VBI_OPTION_BOOL_INITIALIZER
	  ("aspect", N_("Correct aspect ratio"),
	   TRUE, N_("Approach an image aspect ratio similar to "
		    "a real TV. This will double the image size."))
};

#define elements(array) (sizeof(array) / sizeof(array[0]))

static vbi_option_info *
option_enum(vbi_export *e, int index)
{
	e = e;

	if (index < 0 || index >= (int) elements(gfx_options))
		return NULL;
	else
		return gfx_options + index;
}

static vbi_bool
option_get(vbi_export *e, const char *keyword, vbi_option_value *value)
{
	gfx_instance *gfx = PARENT(e, gfx_instance, export);

	if (strcmp(keyword, "aspect") == 0) {
		value->num = gfx->double_height;
	} else {
		vbi_export_unknown_option(e, keyword);
		return FALSE;
	}

	return TRUE;
}

static vbi_bool
option_set(vbi_export *e, const char *keyword, va_list args)
{
	gfx_instance *gfx = PARENT(e, gfx_instance, export);

	if (strcmp(keyword, "aspect") == 0) {
		gfx->double_height = !!va_arg(args, int);
	} else {
		vbi_export_unknown_option(e, keyword);
		return FALSE;
	}

	return TRUE;
}

/*
 *  PPM - Portable Pixmap File (raw)
 */

static vbi_bool
ppm_export(vbi_export *e, FILE *fp, vbi_page *pg)
{
	gfx_instance *gfx = PARENT(e, gfx_instance, export);
	int cw, ch, ww, size, scale, row;
	vbi_rgba *image;
	uint8_t *body;
	int i;

	if (pg->columns < 40) /* caption */ {
		cw = CCW;
		ch = CCH;
		/* characters already line-doubled */
		scale = !!gfx->double_height;
	} else {
		cw = TCW;
		ch = TCH;
		scale = 1 + !!gfx->double_height;
	}

	ww = cw * pg->columns;
	size = ww * ch * 1; /* one character row */

	if (!(image = malloc(size * sizeof(*image)))) {
		vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."),
					size * sizeof(*image) / 1024);
		return FALSE;
	}

	fprintf(fp, "P6 %d %d 255\n",
		cw * pg->columns, (ch * pg->rows / 2) << scale);

	if (ferror(fp))
		goto write_error;

	for (row = 0; row < pg->rows; row++) {
		if (pg->columns < 40)
			vbi_draw_cc_page_region(pg, VBI_PIXFMT_RGBA32_LE, image, -1,
						0, row, pg->columns, 1 /* rows */);
		else
			vbi_draw_vt_page_region(pg, VBI_PIXFMT_RGBA32_LE, image, -1,
						0, row, pg->columns, 1 /* rows */,
						!e->reveal, 1 /* flash_on */);
		body = (uint8_t *) image;

		if (scale == 0)
			for (i = 0; i < size; body += 3, i++) {
				body[0] = ((image[i] & 0xFF) + (image[i + ww] & 0xFF)
					   + 0x01) >> 1;
				body[1] = ((image[i] & 0xFF00) + (image[i + ww] & 0xFF00)
					   + 0x0100) >> 9;
				body[2] = ((image[i] & 0xFF0000) + (image[i + ww] & 0xFF0000)
					   + 0x010000) >> 17;
			}
		else
			for (i = 0; i < size; body += 3, i++) {
				unsigned int n = image[i];

				body[0] = n;
				body[1] = n >> 8;
				body[2] = n >> 16;
			}

		switch (scale) {
			int rows, stride;

		case 0:
			body = (uint8_t *) image;
			rows = ch / 2;
			stride = ww * 3;

			for (i = 0; i < rows; i++, body += stride * 2)
				if (!fwrite(body, stride, 1, fp))
					goto write_error;
			break;

		case 1:
			if (!fwrite(image, size * 3, 1, fp))
				goto write_error;
			break;

		case 2:
			body = (uint8_t *) image;
			stride = cw * pg->columns * 3;

			for (i = 0; i < ch; body += stride, i++) {
				if (!fwrite(body, stride, 1, fp))
					goto write_error;
				if (!fwrite(body, stride, 1, fp))
					goto write_error;
			}

			break;
		}
	}

	free(image);
	image = NULL;

	return TRUE;

write_error:
	vbi_export_write_error(e);

	if (image)
		free(image);

	return FALSE;
}

static vbi_export_info
info_ppm = {
	.keyword	= "ppm",
	.label		= N_("PPM"),
	.tooltip	= N_("Export this page as raw PPM image"),

	.mime_type	= "image/x-portable-pixmap",
	.extension	= "ppm",
};

vbi_export_class
vbi_export_class_ppm = {
	._public		= &info_ppm,
	._new			= gfx_new,
	._delete		= gfx_delete,
	.option_enum		= option_enum,
	.option_get		= option_get,
	.option_set		= option_set,
	.export			= ppm_export
};

VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_ppm)

/*
 *  PNG - Portable Network Graphics File
 */

#ifdef HAVE_LIBPNG

#include "png.h"
#include "setjmp.h"

static void
draw_char_cc_indexed(png_bytep canvas, int rowstride, png_bytep pen,
		     int unicode, vbi_char *ac)
{
	draw_char(sizeof(png_byte), (uint8_t *) canvas, rowstride,
		  (uint8_t *) pen, (uint8_t *) ccfont2_bits, CCPL, CCW, CCH,
		  unicode_ccfont2(unicode, ac->italic), 0 /* bold */,
		  ac->underline * (3 << 24) /* cell row 24, 25 */,
		  VBI_NORMAL_SIZE);
}

static void
draw_char_vt_indexed(png_bytep canvas, int rowstride, png_bytep pen,
		     int unicode, vbi_char *ac)
{
	draw_char(sizeof(png_byte), (uint8_t *) canvas, rowstride,
		  (uint8_t *) pen, (uint8_t *) wstfont2_bits, TCPL, TCW, TCH,
		  unicode_wstfont2(unicode, ac->italic), ac->bold,
		  ac->underline << 9 /* cell row 9 */, ac->size);
}

static void
draw_drcs_indexed(png_bytep canvas, int rowstride, png_bytep pen,
		  uint8_t *font, int glyph, vbi_size size)
{
	draw_drcs(sizeof(png_byte), (uint8_t *) canvas, rowstride,
		  (uint8_t *) pen, 0, font, glyph, size);
}

static vbi_bool
png_export(vbi_export *e, FILE *fp, vbi_page *pg)
{
	gfx_instance *gfx = PARENT(e, gfx_instance, export);
	png_structp png_ptr;
	png_infop info_ptr;
	png_color palette[80];
	png_byte alpha[80];
	png_text text[4];
	char title[80];
	png_bytep *row_pointer;
	png_bytep image;
	int cw, ch, ww, wh, rowstride, scale;
	void (* draw_char_indexed)(png_bytep, int, png_bytep, int, vbi_char *);
	int i;

	if (pg->columns < 40) /* caption */ {
		draw_char_indexed = draw_char_cc_indexed;
		cw = CCW;
		ch = CCH;
		/* characters are already line-doubled */
		scale = !!gfx->double_height;
	} else {
		draw_char_indexed = draw_char_vt_indexed;
		cw = TCW;
		ch = TCH;
		scale = 1 + !!gfx->double_height;
	}

	ww = cw * pg->columns;
	wh = ch * pg->rows;
	rowstride = ww * sizeof(*image);

	if (!(row_pointer = malloc(sizeof(*row_pointer) * wh * 2))) {
		vbi_export_error_printf(e, _("Unable to allocate %d byte buffer."),
					sizeof(*row_pointer) * wh * 2);
		return FALSE;
	}

	if ((image = malloc(wh * ww * sizeof(*image)))) {
		png_bytep canvas = image;
		png_byte pen[128];
		int row, column;
		vbi_char *ac;
		int unicode, conceal = !e->reveal;
		int row_adv;

		row_adv = pg->columns * cw * (ch - 1);

		if (pg->drcs_clut)
			for (i = 2; i < 2 + 8 + 32; i++) {
				pen[i]      = pg->drcs_clut[i]; /* opaque */
				pen[i + 64] = pg->drcs_clut[i] + 40; /* translucent */
			}

		for (row = 0; row < pg->rows; canvas += row_adv, row++) {
			for (column = 0; column < pg->columns ; canvas += cw, column++) {
				ac = &pg->text[row * pg->columns + column];

				if (ac->size == VBI_OVER_TOP
				    || ac->size == VBI_OVER_BOTTOM)
					continue;

				unicode = (ac->conceal & conceal) ? 0x0020u : ac->unicode;

				switch (ac->opacity) {
				case VBI_TRANSPARENT_SPACE:
					/*
					 *  Transparent foreground and background.
					 */
					draw_blank(sizeof(*canvas), (uint8_t *) canvas,
						   rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
					break;

				case VBI_TRANSPARENT_FULL:
					/*
					 *  Transparent background, opaque foreground. Currently not used.
					 *  Mind Teletext level 2.5 foreground and background transparency
					 *  by referencing colormap entry 8, VBI_TRANSPARENT_BLACK.
					 *  The background of multicolor DRCS is ambiguous, so we make
					 *  them opaque.
					 */
					if (vbi_is_drcs(unicode)) {
						uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];

						pen[0] = VBI_TRANSPARENT_BLACK;
						pen[1] = ac->foreground;

						if (font && (draw_char_indexed == draw_char_vt_indexed))
							draw_drcs_indexed(canvas, rowstride, pen,
									  font, unicode & 0x3F, ac->size);
						else /* shouldn't happen */
							draw_blank(sizeof(*canvas), (uint8_t *) canvas,
								   rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
					} else {
						pen[0] = VBI_TRANSPARENT_BLACK;
						pen[1] = ac->foreground;

						draw_char_indexed(canvas, rowstride, pen, unicode, ac);
					}

					break;

				case VBI_SEMI_TRANSPARENT:
					/*
					 *  Translucent background (for 'boxed' text), opaque foreground.
					 *  The background of multicolor DRCS is ambiguous, so we make
					 *  them completely translucent. 
					 */
					if (vbi_is_drcs(unicode)) {
						uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];

						pen[64] = ac->background + 40;
						pen[65] = ac->foreground;

						if (font && (draw_char_indexed == draw_char_vt_indexed))
							draw_drcs_indexed(canvas, rowstride,
									  (uint8_t *)(pen + 64),
									  font, unicode & 0x3F, ac->size);
						else /* shouldn't happen */
							draw_blank(sizeof(*canvas), (uint8_t *) canvas,
								   rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
					} else {
						pen[0] = ac->background + 40; /* translucent */
						pen[1] = ac->foreground;

						draw_char_indexed(canvas, rowstride, pen, unicode, ac);
					}

					break;

				case VBI_OPAQUE:
					pen[0] = ac->background;
					pen[1] = ac->foreground;

					if (vbi_is_drcs(unicode)) {
						uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];

						if (font && (draw_char_indexed == draw_char_vt_indexed))
							draw_drcs_indexed(canvas, rowstride, pen,
									  font, unicode & 0x3F, ac->size);
						else /* shouldn't happen */
							draw_blank(sizeof(*canvas), (uint8_t *) canvas,
								   rowstride, pen[0], cw, ch);
					} else
						draw_char_indexed(canvas, rowstride, pen, unicode, ac);
					break;
				}
			}
		}
	} else {
		vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."),
					wh * ww * sizeof(*image) / 1024);
		free(row_pointer);
		return FALSE;
	}

	/* Now save the image */

	if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL)))
		goto unknown_error;

	if (!(info_ptr = png_create_info_struct(png_ptr))) {
		png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
		goto unknown_error;
	}

	/* avoid possible longjmp breakage due to libpng ugliness */
	/* XXX not portable, to be removed */
	{ int do_write() {
	if (setjmp(png_ptr->jmpbuf))
		return 1;

	png_init_io(png_ptr, fp);

	png_set_IHDR(png_ptr, info_ptr, ww, (wh << scale) >> 1,
		8 /* bit_depth */,
		PNG_COLOR_TYPE_PALETTE,
		(gfx->double_height) ?
			PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
		PNG_COMPRESSION_TYPE_DEFAULT,
		PNG_FILTER_TYPE_DEFAULT);

	/* Could be optimized (or does libpng?) */
	for (i = 0; i < 40; i++) {
		/* opaque */
		palette[i].red   = pg->color_map[i] & 0xFF;
		palette[i].green = (pg->color_map[i] >> 8) & 0xFF;
		palette[i].blue	 = (pg->color_map[i] >> 16) & 0xFF;
		alpha[i]	 = 255;

		/* translucent */
		palette[i + 40]  = palette[i];
		alpha[i + 40]	 = 128;
	}

	alpha[VBI_TRANSPARENT_BLACK] = 0;
	alpha[40 + VBI_TRANSPARENT_BLACK] = 0;

	png_set_PLTE(png_ptr, info_ptr, palette, 80);
	png_set_tRNS(png_ptr, info_ptr, alpha, 80, NULL);

	png_set_gAMA(png_ptr, info_ptr, 1.0 / 2.2);

	{
		int size = 0;

		if (e->network)
			size = snprintf(title, sizeof(title) - 1, "%s ", e->network);
		else
			title[0] = 0;

		/*
		 *  FIXME
		 *  ISO 8859-1 (Latin-1) character set required,
		 *  see png spec for other
		 */
		if (pg->pgno < 0x100) {
			size += snprintf(title + size, sizeof(title) - size - 1,
					 "Closed Caption"); /* no i18n, proper name */
		} else if (pg->subno != VBI_ANY_SUBNO) {
			size += snprintf(title + size, sizeof(title) - size - 1,
					 _("Teletext Page %3x.%x"),
					 pg->pgno, pg->subno);
		} else {
			size += snprintf(title + size, sizeof(title) - size - 1,
					 _("Teletext Page %3x"), pg->pgno);
		}
	}

	memset(text, 0, sizeof(text));

	text[0].key = "Title";
	text[0].text = title;
	text[0].compression = PNG_TEXT_COMPRESSION_NONE;
	text[1].key = "Software";
	text[1].text = e->creator;
	text[1].compression = PNG_TEXT_COMPRESSION_NONE;

	png_set_text(png_ptr, info_ptr, text, 2);

	png_write_info(png_ptr, info_ptr);

	switch (scale) {
	case 0:
		for (i = 0; i < wh / 2; i++)
			row_pointer[i] = image + i * 2 * ww;
		break;

	case 1:
		for (i = 0; i < wh; i++)
			row_pointer[i] = image + i * ww;
		break;

	case 2:
		for (i = 0; i < wh; i++)
			row_pointer[i * 2 + 0] =
			row_pointer[i * 2 + 1] = image + i * ww;
		break;
	}

	png_write_image(png_ptr, row_pointer);

	png_write_end(png_ptr, info_ptr);

	png_destroy_write_struct(&png_ptr, &info_ptr);

	return 0;

	} if (do_write()) goto write_error; }

	free(row_pointer);
	row_pointer = NULL;

	free(image);
	image = NULL;

	return TRUE;

write_error:
	vbi_export_write_error(e);

unknown_error:
	if (row_pointer)
		free(row_pointer);

	if (image)
		free(image);

	return FALSE;
}

static vbi_export_info
info_png = {
	.keyword	= "png",
	.label		= N_("PNG"),
	.tooltip	= N_("Export this page as PNG image"),

	.mime_type	= "image/png",
	.extension	= "png",
};

vbi_export_class
vbi_export_class_png = {
	._public	= &info_png,
	._new		= gfx_new,
	._delete	= gfx_delete,
	.option_enum	= option_enum,
	.option_get	= option_get,
	.option_set	= option_set,
	.export		= png_export
};

VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_png)

#endif /* HAVE_LIBPNG */


syntax highlighted by Code2HTML, v. 0.9.1