/*=============================================================================
   GNU UnRTF, a command-line program to convert RTF documents to other formats.
   Copyright (C) 2000,2001,2004 by Zachary Smith

   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 3 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

   The maintainer is reachable by electronic mail at daved@physiol.usyd.edu.au
=============================================================================*/


/*----------------------------------------------------------------------
 * Module name:    output
 * Author name:    Zachary Smith
 * Create date:    18 Sep 01
 * Purpose:        Generalized output module
 *----------------------------------------------------------------------
 * Changes:
 * 22 Sep 01, tuorfa@yahoo.com: addition of functions to change font size
 * 22 Sep 01, tuorfa@yahoo.com: added function-level comment blocks
 * 08 Oct 03, daved@physiol.usyd.edu.au: added stdlib.h for linux
 * 25 Sep 04, st001906@hrz1.hrz.tu-darmstadt.de: added stdlib.h for djgpp
 * 29 Mar 05, daved@physiol.usyd.edu.au: changes requested by ZT Smith
 * 06 Jan 06, marcossamaral@terra.com.br: changes in STDOUT
 * 16 Dec 07, daved@physiol.usyd.edu.au: updated to GPL v3
 * 17 Dec 07, daved@physiol.usyd.edu.au: added support for --noremap from
 *		David Santinoli
 * 09 Nov 08, arkadiusz.firus@gmail.com: use iconv
 * 07 Oct 11, jf@dockes.org: replaced op_translate_doublet with
 *	op_translate_buffer and other major changes
 *--------------------------------------------------------------------*/


#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef HAVE_STDIO_H
#include <stdio.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#ifndef _WIN32
#include <arpa/inet.h>
#endif

#include "malloc.h"
#include "defs.h"
#include "error.h"
#include "output.h"
#include "main.h"
#include "convert.h"
#include "attr.h"
#include "util.h"

/*========================================================================
 * Name:	op_create
 * Purpose:	Creates a blank output personality.
 * Args:	None.
 * Returns:	Output personality struct.
 *=======================================================================*/

OutputPersonality *
op_create()
{
	OutputPersonality *new_op;

	new_op = (OutputPersonality *) my_malloc(sizeof(OutputPersonality));
	if (!new_op)
	{
		error_handler("cannot allocate output personality");
	}

	memset((void *) new_op, 0, sizeof(OutputPersonality));
	return new_op;
}

/*========================================================================
 * Name:	op_free
 * Purpose:	Deallocates an output personality, but none of the strings
 *		it points to since they are usually constants.
 * Args:	OutputPersonality.
 * Returns:	None.
 *=======================================================================*/

void
op_free(OutputPersonality *op)
{
	CHECK_PARAM_NOT_NULL(op);

	my_free((void *) op);
}


/*========================================================================
 * Name:	op_translate_char
 * Purpose:	Native encoding value->Unicode number as int
 *    Then transform as specified by op (ie, entity, &#xxx;...)
 *    Currently this is only called with values < 255 (single byte chars or
 *    computation of Symbol code point in fields).
 *    We cache a local iconv descriptor for efficiency.
 * Args:	OutputPersonality, encoding, character.
 * Returns:	malloced string to be freed by caller.
 *=======================================================================*/
const char *
op_translate_char(OutputPersonality *op, char *encoding, int ch)
{
	static my_iconv_t m_desc = MY_ICONV_T_CLEAR;
	static char *m_encoding = NULL;
	char *result = NULL;

	CHECK_PARAM_NOT_NULL(op);

	if (ch < 0 || ch > 255)
	{
		fprintf(stderr, "op_translate_char called with bad char 0x%x\n",
		        ch);
		return NULL;
	}
	if (no_remap_mode == TRUE && ch < 256)
	{
		/* Kept for historical reason, makes no sense in a multi-encoding
		   document */
		result = my_malloc(2);
		if (result) 
		{
			result[0] = ch;
			result[1] = 0;
		}
	}
	else
	{
		if (!m_encoding || strcasecmp(encoding, m_encoding))
		{
			// Using UTF-32BE to avoid BOM
			if (my_iconv_is_valid(m_desc))
			{
				my_iconv_close(m_desc);
			}
			m_desc = my_iconv_open("UTF-32BE", encoding);
			if (!my_iconv_is_valid(m_desc))
			{
				return NULL;
			}
			if (m_encoding)
			{
				my_free(m_encoding);
			}
			m_encoding = my_strdup(encoding);
		}
		size_t inbytes = 1, outbytes = 4;
		char inbuf[1];
		uint32_t outbuf;
		char *icp = inbuf, *ocp = (char *)&outbuf;
		int unicode;
		inbuf[0] = ch;

		if (my_iconv(m_desc, &icp, &inbytes, &ocp, &outbytes) == -1)
		{
			/*            fprintf(stderr, "unrtf: iconv error %s->UTF-32BE for 0x%x\n",
				      encoding, ch);*/
			return NULL;
		}

		/* Conversion from UTF-32BE string to int */
#ifdef _WIN32
		unicode = unrtf_ntohl(outbuf);
#else
		unicode = ntohl(outbuf);
#endif
#if 0
		if (unicode != ch)
			fprintf(stderr, "op_translate: 0x%x -> unicode 0x%x\n",
			        (unsigned)ch, (unsigned)unicode);
#endif
		/* Output Unicode value as decided by output personality */
		result = get_alias(op, unicode); /* Try for ie, entity */
		if (result)
			result = strdup(result);
		if (result == NULL)
		{
			if (ch > 127 && op->unisymbol_print)
			{
				result = assemble_string(op->unisymbol_print, unicode);
			}
			else
			{
				/* Single byte unicode char needs no translation ever?*/
				result = my_malloc(2);
				if (result) 
				{
					result[0] = ch;
					result[1] = 0;
				}
			}
		}
	}

	return result;
}

/*========================================================================
 * Name:	op_translate_buffer
 * Purpose:	Translate buffer built out of \' commands in convert.c
 *   The data comes in as UTF-32BE, which we change to unicode positions
 *   (just the byte order), then transform according to outputpersonality
 *   TOBEDONE: have a way to specify direct UTF-8 output for a personality
 *    (then produce utf-8 in convert.c and do nothing special here.
 *
 * Args:	OutputPersonality, encoding, character.
 * Returns:	malloced string.
 *=======================================================================*/
char *
op_translate_buffer(OutputPersonality *op, char *buffer, int bytescnt)
{
	int wordcnt = bytescnt / 4;
	int i;
	int opos = 0;
	char *out = 0;
	int outsz;
	if ((out = (char *)allocmem(out, 1, &outsz, 1000, 100000)) == NULL)
	{
		fprintf(stderr, "unrtf: out of memory (op_translate_buffer 1, %d)\n", outsz);
		return NULL;
	}
	out[0] = 0;

	for (i = 0; i < wordcnt; i++)
	{
		char tbuf[2];
		const char *result;
		int freeresult = 0;
		int incsz;
#ifdef _WIN32
		int unicode = unrtf_ntohl(*(int *)(buffer + 4 * i));
#else
		int unicode = ntohl(*(int *)(buffer + 4 * i));
#endif

		result = get_alias(op, unicode); /* Try for ie, entity */
		if (result != NULL)
		{
			incsz = strlen(result);
		}
		else
		{
			if (unicode > 127 && op->unisymbol_print)
			{
				result = assemble_string(op->unisymbol_print, unicode);
				freeresult = 1;
				incsz = strlen(result);
			}
			else
			{
				/* Single byte unicode char needs no translation ever?*/
				tbuf[0] = unicode;
				tbuf[1] = 0;
				result = tbuf;
				incsz = 1;
			}
		}
		if (opos + incsz + 1 >= outsz)
		{
			if ((out = (char *)allocmem(out, 1, &outsz, 1000, 100000)) == NULL)
			{
				fprintf(stderr, "unrtf: out of memory (op_translate_buffer 2, %d)\n", outsz);
				return NULL;
			}
		}
		strcpy(out + opos, result);
		if (freeresult)
			my_free(result);
		opos += incsz;
	}
	return out;
}

/*========================================================================
 * Name:	op_begin_std_fontsize
 * Purpose:	Prints whatever is necessary to perform a change in the
 *		current font size.
 * Args:	OutputPersonality, desired size.
 * Returns:	None.
 *=======================================================================*/

void
op_begin_std_fontsize(OutputPersonality *op, int size)
{
	int found_std_expr = FALSE;

	CHECK_PARAM_NOT_NULL(op);

	/* Look for an exact match with a standard point size.
	 */
	switch (size)
	{
	case 8:
		if (op->fontsize8_begin)
		{
			if (safe_printf(0, op->fontsize8_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize8_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 10:
		if (op->fontsize10_begin)
		{
			if (safe_printf(0, op->fontsize10_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize10_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 12:
		if (op->fontsize12_begin)
		{
			if (safe_printf(0, op->fontsize12_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize12_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 14:
		if (op->fontsize14_begin)
		{
			if (safe_printf(0, op->fontsize14_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize14_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 18:
		if (op->fontsize18_begin)
		{
			if (safe_printf(0, op->fontsize18_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize18_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 24:
		if (op->fontsize24_begin)
		{
			if (safe_printf(0, op->fontsize24_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize24_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 36:
		if (op->fontsize36_begin)
		{
			if (safe_printf(0, op->fontsize36_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize36_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	case 48:
		if (op->fontsize48_begin)
		{
			if (safe_printf(0, op->fontsize48_begin))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize48_begin");
			}
			found_std_expr = TRUE;
		}
		break;
	}

	/* If no exact match, try to write out a change to the
	 * exact point size.
	 */
	if (!found_std_expr)
	{
		if (op->fontsize_begin)
		{
			char expr[16];
			sprintf(expr, "%d", size);
			if (safe_printf(1, op->fontsize_begin, expr))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize_begin");
			}
		}
		else
		{
			/* If we cannot write out a change for the exact
			 * point size, we must approximate to a standard
			 * size.
			 */
			if (size < 9 && op->fontsize8_begin)
			{
				if (safe_printf(0, op->fontsize8_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize8_begin");
				}
			}
			else if (size < 11 && op->fontsize10_begin)
			{
				if (safe_printf(0, op->fontsize10_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize10_begin");
				}
			}
			else if (size < 13 && op->fontsize12_begin)
			{
				if (safe_printf(0, op->fontsize12_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize12_begin");
				}
			}
			else if (size < 16 && op->fontsize14_begin)
			{
				if (safe_printf(0, op->fontsize14_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize14_begin");
				}
			}
			else if (size < 21 && op->fontsize18_begin)
			{
				if (safe_printf(0, op->fontsize18_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize18_begin");
				}
			}
			else if (size < 30 && op->fontsize24_begin)
			{
				if (safe_printf(0, op->fontsize24_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize24_begin");
				}
			}
			else if (size < 42 && op->fontsize36_begin)
			{
				if (safe_printf(0, op->fontsize36_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize36_begin");
				}
			}
			else if (size > 40 && op->fontsize48_begin)
			{
				if (safe_printf(0, op->fontsize48_begin))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize48_begin");
				}
			}
			else
			{
				/* If we can't even produce a good approximation,
				 * just try to get a font size near 12 point.
				 */
				if (op->fontsize12_begin)
				{
					if (safe_printf(0, op->fontsize12_begin))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize12_begin");
					}
				}
				else if (op->fontsize14_begin)
				{
					if (safe_printf(0, op->fontsize14_begin))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize14_begin");
					}
				}
				else if (op->fontsize10_begin)
				{
					if (safe_printf(0, op->fontsize10_begin))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize10_begin");
					}
				}
				else if (op->fontsize18_begin)
				{
					if (safe_printf(0, op->fontsize18_begin))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize18_begin");
					}
				}
				else if (op->fontsize8_begin)
				{
					if (safe_printf(0, op->fontsize8_begin))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize8_begin");
					}
				}
				else
				{
					error_handler("output personality lacks sufficient font size change capability");
				}
			}
		}
	}
}


/*========================================================================
 * Name:	op_end_std_fontsize
 * Purpose:	Prints whatever is necessary to perform a change in the
 *		current font size.
 * Args:	OutputPersonality, desired size.
 * Returns:	None.
 *=======================================================================*/

void
op_end_std_fontsize(OutputPersonality *op, int size)
{
	int found_std_expr = FALSE;

	CHECK_PARAM_NOT_NULL(op);

	/* Look for an exact match with a standard point size.
	 */
	switch (size)
	{
	case 8:
		if (op->fontsize8_end)
		{
			if (safe_printf(0, op->fontsize8_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize8_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 10:
		if (op->fontsize10_end)
		{
			if (safe_printf(0, op->fontsize10_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize10_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 12:
		if (op->fontsize12_end)
		{
			if (safe_printf(0, op->fontsize12_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize12_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 14:
		if (op->fontsize14_end)
		{
			if (safe_printf(0, op->fontsize14_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize14_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 18:
		if (op->fontsize18_end)
		{
			if (safe_printf(0, op->fontsize18_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize18_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 24:
		if (op->fontsize24_end)
		{
			if (safe_printf(0, op->fontsize24_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize24_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 36:
		if (op->fontsize36_end)
		{
			if (safe_printf(0, op->fontsize36_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize36_end");
			}
			found_std_expr = TRUE;
		}
		break;
	case 48:
		if (op->fontsize48_end)
		{
			if (safe_printf(0, op->fontsize48_end))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize48_end");
			}
			found_std_expr = TRUE;
		}
		break;
	}

	/* If no exact match, try to write out a change to the
	 * exact point size.
	 */
	if (!found_std_expr)
	{
		if (op->fontsize_end)
		{
			char expr[16];
			sprintf(expr, "%d", size);
			if (safe_printf(1, op->fontsize_end, expr))
			{
				fprintf(stderr, TOO_MANY_ARGS, "fontsize_end");
			}
		}
		else
		{
			/* If we cannot write out a change for the exact
			 * point size, we must approximate to a standard
			 * size.
			 */
			if (size < 9 && op->fontsize8_end)
			{
				if (safe_printf(0, op->fontsize8_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize8_end");
				}
			}
			else if (size < 11 && op->fontsize10_end)
			{
				if (safe_printf(0, op->fontsize10_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize10_end");
				}
			}
			else if (size < 13 && op->fontsize12_end)
			{
				if (safe_printf(0, op->fontsize12_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize12_end");
				}
			}
			else if (size < 16 && op->fontsize14_end)
			{
				if (safe_printf(0, op->fontsize14_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize14_end");
				}
			}
			else if (size < 21 && op->fontsize18_end)
			{
				if (safe_printf(0, op->fontsize18_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize18_end");
				}
			}
			else if (size < 30 && op->fontsize24_end)
			{
				if (safe_printf(0, op->fontsize24_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize24_end");
				}
			}
			else if (size < 42 && op->fontsize36_end)
			{
				if (safe_printf(0, op->fontsize36_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize36_end");
				}
			}
			else if (size > 40 && op->fontsize48_end)
			{
				if (safe_printf(0, op->fontsize48_end))
				{
					fprintf(stderr, TOO_MANY_ARGS, "fontsize48_end");
				}
			}
			else
			{
				/* If we can't even produce a good approximation,
				 * just try to get a font size near 12 point.
				 */
				if (op->fontsize12_end)
				{
					if (safe_printf(0, op->fontsize12_end))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize12_end");
					}
				}
				else if (op->fontsize14_end)
				{
					if (safe_printf(0, op->fontsize14_end))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize14_end");
					}
				}
				else if (op->fontsize10_end)
				{
					if (safe_printf(0, op->fontsize10_end))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize10_end");
					}
				}
				else if (op->fontsize18_end)
				{
					if (safe_printf(0, op->fontsize18_end))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize18_end");
					}
				}
				else if (op->fontsize8_end)
				{
					if (safe_printf(0, op->fontsize8_end))
					{
						fprintf(stderr, TOO_MANY_ARGS, "fontsize8_end");
					}
				}
				else
				{
					error_handler("output personality lacks sufficient font size change capability");
				}
			}
		}
	}
}

#if 1 /* AK3 - AF */
/*========================================================================
 * Name:	add_alias
 * Purpose:	Adds alias (text) for a char number nr.
 * Args:	OutputPersonality, char's number, alias.
 * Returns:	None.
 *=======================================================================*/

void
add_alias(OutputPersonality *op, int nr, char *text)
{
	op->aliases = (Aliases *)add_to_collection((Collection *)op->aliases, nr, text);
}

/*========================================================================
 * Name:	get_alias
 * Purpose:	Search for alias for character number nr.
 * Args:	OutputPersonality, char's number.
 * Returns:	Text alias or NULL if found nothing.
 *=======================================================================*/

const char *
get_alias(OutputPersonality *op, int nr)
{
	return get_from_collection((Collection *)op->aliases, nr);
}
#endif

