/* Copyright (C) 1994 Aladdin Enterprises.  All rights reserved. */

/*$Id: gsdsc.c $ */
/* Parse DSC comments from a PostScript file. */
#include "stdpre.h"
#include <stdio.h>
#include <malloc.h>
#include <math.h>
#ifndef SEEK_SET
#  define SEEK_SET 0
#endif

/* Define the maximum length of a line in a DSC-conforming file. */
/* This is 255 characters + 2 for newline + 1 for null. */
#define line_size 258

/* Test whether a line begins with %%. */
#define is_dsc_comment(line) ((line)[0] == '%' && (line)[1] == '%')

/* Test whether a string begins with a given prefix. */
#define has_prefix(str, pre) !strncmp(str, pre, strlen(pre))
/* Faster test if prefix is a literal string. */
#define has_lit_prefix(str, litpre) !strncmp(str, litpre, sizeof(litpre) - 1)
/* Test whether a line is a specific DSC comment. */
#define has_dsc_prefix(line, litpre)\
  (is_dsc_comment(line) && has_lit_prefix(line, litpre))

/* Define a bounding box structure. */
#define LLX 0
#define LLY 1
#define URX 2
#define URY 3

/* ------ Internal routines ------ */

/* Allocate a block with malloc, exiting on failure. */
private void *
dsc_malloc(size_t size)
{
    void *blk = malloc(size);

    if (blk == 0) {
	fprintf(stderr, "Unable to allocate object; giving up.\n");
	exit(255);
    }
    return blk;
}

/* Copy a given amount of data from one file to another (to != NULL) or */
/* skip a given amount of data in a file without using fseek (to == NULL). */
#define fcpy_size 5000
private void
fcpy(FILE * to, FILE * from, long len)
{
    char buf[fcpy_size];
    size_t count;

    for (; len > 0; len -= count) {
	count = (len > fcpy_size ? fcpy_size : len);
	fread(buf, sizeof(char), count, from);

	if (to != NULL)
	    fwrite(buf, sizeof(char), count, to);
    }
}
#undef fcpy_size

/*
 * Copy (or skip) a %%BeginBinary/%%EndBinary section.
 * Return the number of bytes copied or skipped.
 */
private long
copy_binary(FILE * to, FILE * from, const char *line)
{
    long count;

    if (sscanf(line + 14, "%ld", &count) == 1)
	fcpy(to, from, count);
    else
	count = 0;
    return count;
}

/*
 * Copy (or skip) a %%BeginData/%%EndData section.
 * Return the number of bytes copied or skipped.
 */
private long
copy_data(FILE * to, FILE * from, const char *line)
{
    long count, amount = 0;
    char str[line_size];

    str[0] = '\0';
    if (sscanf(line + 12, "%ld %*s %s", &count, str) >= 1) {
	if (!strcmp(str, "Lines"))
	    for (; count > 0; --count) {
		if (!fgets(str, line_size, from))
		    break;
		amount += strlen(str);
		if (to != NULL)
		    fputs(str, to);
	} else {
	    amount = count;
	    fcpy(to, from, count);
	}
    }
    return amount;
}

/*
 * Read the next line from the input.  Skip over embedded data, and also
 * skip sections (such as procsets, fonts, and included documents) that
 * are not part of the main document flow.
 * Return a pointer to the input line, or NULL if we reached EOF.
 */
private void skip_region(P4(char *, FILE *, long *, const char *));
private char *
next_line(char *line, FILE * in, long *pstart, long *plen)
{
    if (pstart)
	*pstart = ftell(in);
    *plen = 0;
    for (;;) {
	if (!fgets(line, line_size, in)) {	/* EOF */
	    line[0] = 0;
	    return 0;
	}
	*plen += strlen(line);
	if (!has_dsc_comment(line, "Begin"))
	    break;
	else if (has_lit_prefix(line + 7, "Binary:")) {
	    *plen += copy_binary(NULL, in, line);
	    skip_region(line, in, plen, "EndBinary");
	} else if (has_lit_prefix(line + 7, "Data:")) {
	    *plen += copy_data(NULL, in, line);
	    skip_region(line, in, plen, "EndData");
	} else if (has_lit_prefix(line + 7, "Feature:"))
	    skip_region(line, in, plen, "EndFeature");
	else if (has_lit_prefix(line + 7, "File:"))
	    skip_region(line, in, plen, "EndFile");
	else if (has_lit_prefix(line + 7, "Font:"))
	    skip_region(line, in, plen, "EndFont");
	else if (has_lit_prefix(line + 7, "ProcSet:"))
	    skip_region(line, in, plen, "EndProcSet");
	else if (has_lit_prefix(line + 7, "Resource:"))
	    skip_region(line, in, plen, "EndResource");
    }
    return line;
}
/*
 * Skip a region of the PostScript file up to a given %%End comment.
 */
private void
skip_region(char *line, FILE * in, long *plen, const char *end_comment)
{
    do {
	long len;

	if (next_line(line, in, NULL, &len) == NULL)
	    break;
	*plen += len;
    }
    while (!has_dsc_prefix(line, end_comment));
}

/*
 * Scan a bounding box argument.  Return 1 iff we found one.
 */
private int
scan_bbox(const char *line, int bbox[4])
{
    float fx0, fy0, fx1, fy1;

    if (sscanf(line, "%d %d %d %d", &bbox[LLX], &bbox[LLY],
	       &bbox[URX], &bbox[URY]) == 4
	)
	return 1;
    if (sscanf(line, "%f %f %f %f", &fx0, &fy0, &fx1, &fy1) != 4)
	return 0;
    bbox[LLX] = (int)floor(fx0);
    bbox[LLY] = (int)floor(fy0);
    bbox[URX] = (int)ceil(fx1);
    bbox[URY] = (int)ceil(fy1);
    return 1;
}

/*
 * Scan a text argument, recognizing escapes if it is a parenthesized string.
 * If the string is not parenthesized then if rest = 1, take the rest of
 * the line as the argument; if rest = 0, only take up to the next whitespace.
 */
#define scan_text_arg(line, endp) scan_text(line, endp, 0)
#define scan_line_arg(line, endp) scan_text(line, endp, 1)
private const char *
scan_text(const char *line, const char **endp, int rest)
{
    char buf[line_size];
    const char *lp = line;
    char *bp = buf;
    char *arg;

    while (*lp == ' ' || *lp == '\t')
	lp++;
    if (*lp == '(') {
	int level = 1;

	lp++;
	for (;;)
	    switch (*bp++ = *lp++) {
		case '\\':
		    switch (*lp++) {
			case 'n':
			    bp[-1] = '\n';
			    break;
			case 'r':
			    bp[-1] = '\r';
			    break;
			case 't':
			    bp[-1] = '\t';
			    break;
			case 'b':
			    bp[-1] = '\b';
			    break;
			case 'f':
			    bp[-1] = '\f';
			    break;
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			    {
				int c = lp[-1] - '0';
				int d = *lp;

				if (d >= '0' && d <= '7') {
				    c = (c << 3) + d - '0';
				    d = *++lp;
				    if (d >= '0' && d <= '7') {
					c = (c << 3) + d - '0';
					lp++;
				    }
				}
				bp[-1] = c;
				break;
			    }
			case 0:	/* unexpected EOF */
			    lp--;
			    goto out;
			default:
			    bp[-1] = lp[-1];
		    }
		    break;
		case '(':
		    ++level;
		    break;
		case ')':
		    if (--level)
			break;
		case 0:
		    goto out;
		default:
		    ;
	    }
      out:--bp;
    } else {			/* Not quoted. */
	while (!(*lp == 0 || *lp == '\n' ||
		 (!rest && (*lp == ' ' || *lp == '\t')))
	    )
	    *bp++ = *lp++;
	if (bp == buf)		/* empty */
	    return NULL;
    }
    *bp = 0;
    if (endp)
	*endp = lp;
    arg = dsc_malloc(bp - buf + 1);
    strcpy(arg, buf);
    return arg;
}

/* ------ Public routines ------ */

/*
 * Copy a section of a DSC-conforming PostScript file.
 * Detect %%(Begin|End)(Binary|Data) comments and copy the intervening
 * data as binary if necessary.  If a sentinel is specified,
 * stop copying when we reach a line that begins with the sentinel.
 * If start < 0, don't seek before copying.
 */
private int dsc_copy_section(P6(FILE * from, FILE * to, long start, long end,
				char *line, const char *sentinel));
void
dsc_copy(FILE * from, FILE * to, long start, long end)
{
    char line[line_size];

    dsc_copy_section(from, to, start, end, line, NULL);
}
char *
dsc_copy_until(FILE * from, FILE * to, long start, long end,
	       const char *sentinel)
{
    char line[line_size];
    int found = dsc_copy_section(from, to, start, end, line, sentinel);

    if (found) {
	char *ret = dsc_malloc(strlen(line) + 1);

	strcpy(ret, line);
	return ret;
    }
    return NULL;
}
private int
dsc_copy_section(FILE * from, FILE * to, long start, long end,
		 char *line, const char *sentinel)
{
    int sent_len = (sentinel == 0 ? 0 : strlen(sentinel));

    if (start >= 0)
	fseek(from, start, SEEK_SET);
    while (ftell(from) < end) {
	long count;

	fgets(line, line_size, from);
	if (sent_len) {
	    if (!strncmp(line, sentinel, sent_len))
		return 1;
	}
	fputs(line, to);
	if (!has_dsc_prefix(line, "Begin"));
	else if (has_lit_prefix(line + 7, "Binary:"))
	    copy_binary(to, from, line);
	else if (has_lit_prefix(line + 7, "Data:"))
	    copy_data(to, from, line);
    }
    return 0;
}
