cairo-output-stream.c   [plain text]


/* cairo_output_stream.c: Output stream abstraction
 * 
 * Copyright © 2005 Red Hat, Inc
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is cairo_output_stream.c as distributed with the
 *   cairo graphics library.
 *
 * The Initial Developer of the Original Code is Red Hat, Inc.
 *
 * Author(s):
 *	Kristian Høgsberg <krh@redhat.com>
 */

#include <stdio.h>
#include <locale.h>
#include <ctype.h>
#include "cairoint.h"

#ifdef _MSC_VER
#define snprintf _snprintf
#endif /* _MSC_VER */

struct _cairo_output_stream {
    cairo_write_func_t		write_data;
    void			*closure;
    cairo_bool_t		owns_closure_is_file;
    unsigned long		position;
    cairo_status_t		status;
};

cairo_output_stream_t *
_cairo_output_stream_create (cairo_write_func_t		write_data,
			     void			*closure)
{
    cairo_output_stream_t *stream;

    stream = malloc (sizeof (cairo_output_stream_t));
    if (stream == NULL)
	return NULL;

    stream->write_data = write_data;
    stream->closure = closure;
    stream->owns_closure_is_file = FALSE;
    stream->position = 0;
    stream->status = CAIRO_STATUS_SUCCESS;

    return stream;
}

void
_cairo_output_stream_destroy (cairo_output_stream_t *stream)
{
    if (stream->owns_closure_is_file) {
	FILE *file = stream->closure;
	fflush (file);
	fclose (file);
    }
    free (stream);
}

cairo_status_t
_cairo_output_stream_write (cairo_output_stream_t *stream,
			    const void *data, size_t length)
{
    if (length == 0)
	return CAIRO_STATUS_SUCCESS;

    stream->status = stream->write_data (stream->closure, data, length);
    stream->position += length;

    return stream->status;
}

void
_cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
				       const char *data,
				       size_t length)
{
    const char hex_chars[] = "0123456789abcdef";
    char buffer[2];
    int i, column;

    for (i = 0, column = 0; i < length; i++, column++) {
	if (column == 38) {
	    _cairo_output_stream_write (stream, "\n", 1);
	    column = 0;
	}
	buffer[0] = hex_chars[(data[i] >> 4) & 0x0f];
	buffer[1] = hex_chars[data[i] & 0x0f];
	_cairo_output_stream_write (stream, buffer, 2);
    }
}

static cairo_bool_t
convert_four_tuple (const unsigned char *four_tuple, char five_tuple[5])
{
    cairo_bool_t all_zero;
    uint32_t value;
    int digit, i;
    
    value = four_tuple[0] << 24 | four_tuple[1] << 16 | four_tuple[2] << 8 | four_tuple[3];
    all_zero = TRUE;
    for (i = 0; i < 5; i++) {
	digit = value % 85;
	if (digit != 0)
	    all_zero = FALSE;
	five_tuple[4-i] = digit + 33;
	value = value / 85;
    }
    return all_zero;
}

void
_cairo_output_stream_write_base85_string (cairo_output_stream_t *stream,
					  const char *data,
					  size_t length)
{
    unsigned char *ptr;
    unsigned char four_tuple[4];
    char five_tuple[5];
    int column;
    
    ptr = (unsigned char *)data;
    column = 0;
    while (length > 0) {
	if (length >= 4) {
	    if (convert_four_tuple (ptr, five_tuple)) {
		column += 1;
		_cairo_output_stream_write (stream, "z", 1);
	    } else {
		column += 5;
		_cairo_output_stream_write (stream, five_tuple, 5);
	    }
	    length -= 4;
	    ptr += 4;
	} else { /* length < 4 */
	    memset (four_tuple, 0, 4);
	    memcpy (four_tuple, ptr, length);
	    convert_four_tuple (four_tuple, five_tuple);
	    column += length + 1;
	    _cairo_output_stream_write (stream, five_tuple, length + 1);
	    length = 0;
	}
	if (column >= 72) {
	    _cairo_output_stream_write (stream, "\n", 1);
	    column = 0;
	}
    }

    if (column > 0) {
	_cairo_output_stream_write (stream, "\n", 1);
    }
}



/* Format a double in a locale independent way and trim trailing
 * zeros.  Based on code from Alex Larson <alexl@redhat.com>.
 * http://mail.gnome.org/archives/gtk-devel-list/2001-October/msg00087.html
 */

int
_cairo_dtostr (char *buffer, size_t size, double d)
{
  struct lconv *locale_data;
  const char *decimal_point;
  int decimal_point_len;
  char *p;
  int decimal_len;

  snprintf (buffer, size, "%f", d);
    
  locale_data = localeconv ();
  decimal_point = locale_data->decimal_point;
  decimal_point_len = strlen (decimal_point);
  
  assert (decimal_point_len != 0);
  p = buffer;
			    
  if (*p == '+' || *p == '-')
      p++;

  while (isdigit (*p))
      p++;
					
  if (strncmp (p, decimal_point, decimal_point_len) == 0) {
      *p = '.';
      decimal_len = strlen (p + decimal_point_len);
      memmove (p + 1, p + decimal_point_len, decimal_len);
      p[1 + decimal_len] = 0;

      /* Remove trailing zeros and decimal point if possible. */
      for (p = p + decimal_len; *p == '0'; p--)
	  *p = 0;

      if (*p == '.') {
	  *p = 0;
	  p--;
      }
  }
					        
  return p + 1 - buffer;
}


enum {
    LENGTH_MODIFIER_LONG = 0x100
};

/* Here's a limited reimplementation of printf.  The reason for doing
 * this is primarily to special case handling of doubles.  We want
 * locale independent formatting of doubles and we want to trim
 * trailing zeros.  This is handled by dtostr() above, and the code
 * below handles everything else by calling snprintf() to do the
 * formatting.  This functionality is only for internal use and we
 * only implement the formats we actually use.
 */

cairo_status_t
_cairo_output_stream_vprintf (cairo_output_stream_t *stream,
			      const char *fmt, va_list ap)
{
    char buffer[512];
    char *p;
    const char *f;
    int length_modifier;

    f = fmt;
    p = buffer;
    while (*f != '\0') {
	if (p == buffer + sizeof (buffer)) {
	    _cairo_output_stream_write (stream, buffer, sizeof (buffer));
	    p = buffer;
	}

	if (*f != '%') {
	    *p++ = *f++;
	    continue;
	}

	f++;

	_cairo_output_stream_write (stream, buffer, p - buffer);
	p = buffer;

	length_modifier = 0;
	if (*f == 'l') {
	    length_modifier = LENGTH_MODIFIER_LONG;
	    f++;
	}

	switch (*f | length_modifier) {
	case '%':
	    buffer[0] = *f;
	    buffer[1] = 0;
	    break;
	case 'd':
	    snprintf (buffer, sizeof buffer, "%d", va_arg (ap, int));
	    break;
	case 'd' | LENGTH_MODIFIER_LONG:
	    snprintf (buffer, sizeof buffer, "%ld", va_arg (ap, long int));
	    break;
	case 'u':
	    snprintf (buffer, sizeof buffer, "%u", va_arg (ap, unsigned int));
	    break;
	case 'u' | LENGTH_MODIFIER_LONG:
	    snprintf (buffer, sizeof buffer, "%lu", va_arg (ap, long unsigned int));
	    break;
	case 'o':
	    snprintf (buffer, sizeof buffer, "%o", va_arg (ap, int));
	    break;
	case 's':
	    snprintf (buffer, sizeof buffer, "%s", va_arg (ap, const char *));
	    break;
	case 'f':
	    _cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double));
	    break;
	case 'c':
	    buffer[0] = va_arg (ap, int);
	    buffer[1] = 0;
	    break;
	default:
	    ASSERT_NOT_REACHED;
	}
	p = buffer + strlen (buffer);
	f++;
    }
    
    _cairo_output_stream_write (stream, buffer, p - buffer);

    return stream->status;
}

cairo_status_t
_cairo_output_stream_printf (cairo_output_stream_t *stream,
			     const char *fmt, ...)
{
    va_list ap;
    cairo_status_t status;    

    va_start (ap, fmt);

    status = _cairo_output_stream_vprintf (stream, fmt, ap);

    va_end (ap);

    return status;
}

long
_cairo_output_stream_get_position (cairo_output_stream_t *stream)
{
    return stream->position;
}

cairo_status_t
_cairo_output_stream_get_status (cairo_output_stream_t *stream)
{
    return stream->status;
}


/* Maybe this should be a configure time option, so embedded targets
 * don't have to pull in stdio. */

static cairo_status_t
stdio_write (void *closure, const unsigned char *data, unsigned int length)
{
    FILE *fp = closure;

    if (fwrite (data, 1, length, fp) == length)
	return CAIRO_STATUS_SUCCESS;

    return CAIRO_STATUS_WRITE_ERROR;
}

cairo_output_stream_t *
_cairo_output_stream_create_for_file (const char *filename)
{
    FILE *fp;
    cairo_output_stream_t *stream;

    fp = fopen (filename, "wb");
    if (fp == NULL)
	return NULL;
    
    stream = _cairo_output_stream_create (stdio_write, fp);

    if (stream)
	stream->owns_closure_is_file = TRUE;
    else
	fclose (fp);

    return stream;
}