cairo/src/cairo-output-stream.c
Chris Wilson 41c8eefc6d [output-stream] Protect against NULL write_func.
Allow the user to specify a NULL write_func for the output stream so that
a dummy surface can be created, for example, for querying target font
options or font extents.

Currently we do not perform any sanity checks at the user entry point and
will generate a mysterious SEGV during cairo_surface_finish() - which may
not immediately be obvious that it is due to a NULL write_func.
2008-10-19 09:36:53 +01:00

707 lines
18 KiB
C

/* 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 the cairo graphics library.
*
* The Initial Developer of the Original Code is Red Hat, Inc.
*
* Author(s):
* Kristian Høgsberg <krh@redhat.com>
*/
#define _BSD_SOURCE /* for snprintf() */
#include "cairoint.h"
#include "cairo-output-stream-private.h"
#include "cairo-compiler-private.h"
#include <stdio.h>
#include <locale.h>
#include <ctype.h>
#include <errno.h>
/* Numbers printed with %f are printed with this number of significant
* digits after the decimal.
*/
#define SIGNIFICANT_DIGITS_AFTER_DECIMAL 6
/* Numbers printed with %g are assumed to only have %CAIRO_FIXED_FRAC_BITS
* bits of precision available after the decimal point.
*
* FIXED_POINT_DECIMAL_DIGITS specifies the minimum number of decimal
* digits after the decimal point required to preserve the available
* precision.
*
* The conversion is:
*
* <programlisting>
* FIXED_POINT_DECIMAL_DIGITS = ceil( CAIRO_FIXED_FRAC_BITS * ln(2)/ln(10) )
* </programlisting>
*
* We can replace ceil(x) with (int)(x+1) since x will never be an
* integer for any likely value of %CAIRO_FIXED_FRAC_BITS.
*/
#define FIXED_POINT_DECIMAL_DIGITS ((int)(CAIRO_FIXED_FRAC_BITS*0.301029996 + 1))
void
_cairo_output_stream_init (cairo_output_stream_t *stream,
cairo_output_stream_write_func_t write_func,
cairo_output_stream_close_func_t close_func)
{
stream->write_func = write_func;
stream->close_func = close_func;
stream->position = 0;
stream->status = CAIRO_STATUS_SUCCESS;
stream->closed = FALSE;
}
cairo_status_t
_cairo_output_stream_fini (cairo_output_stream_t *stream)
{
return _cairo_output_stream_close (stream);
}
const cairo_output_stream_t _cairo_output_stream_nil = {
NULL, /* write_func */
NULL, /* close_func */
0, /* position */
CAIRO_STATUS_NO_MEMORY,
FALSE /* closed */
};
static const cairo_output_stream_t _cairo_output_stream_nil_write_error = {
NULL, /* write_func */
NULL, /* close_func */
0, /* position */
CAIRO_STATUS_WRITE_ERROR,
FALSE /* closed */
};
typedef struct _cairo_output_stream_with_closure {
cairo_output_stream_t base;
cairo_write_func_t write_func;
cairo_close_func_t close_func;
void *closure;
} cairo_output_stream_with_closure_t;
static cairo_status_t
closure_write (cairo_output_stream_t *stream,
const unsigned char *data, unsigned int length)
{
cairo_output_stream_with_closure_t *stream_with_closure =
(cairo_output_stream_with_closure_t *) stream;
if (stream_with_closure->write_func == NULL)
return CAIRO_STATUS_SUCCESS;
return stream_with_closure->write_func (stream_with_closure->closure,
data, length);
}
static cairo_status_t
closure_close (cairo_output_stream_t *stream)
{
cairo_output_stream_with_closure_t *stream_with_closure =
(cairo_output_stream_with_closure_t *) stream;
if (stream_with_closure->close_func != NULL)
return stream_with_closure->close_func (stream_with_closure->closure);
else
return CAIRO_STATUS_SUCCESS;
}
cairo_output_stream_t *
_cairo_output_stream_create (cairo_write_func_t write_func,
cairo_close_func_t close_func,
void *closure)
{
cairo_output_stream_with_closure_t *stream;
stream = malloc (sizeof (cairo_output_stream_with_closure_t));
if (stream == NULL) {
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (&stream->base, closure_write, closure_close);
stream->write_func = write_func;
stream->close_func = close_func;
stream->closure = closure;
return &stream->base;
}
cairo_output_stream_t *
_cairo_output_stream_create_in_error (cairo_status_t status)
{
cairo_output_stream_t *stream;
/* check for the common ones */
if (status == CAIRO_STATUS_NO_MEMORY)
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
if (status == CAIRO_STATUS_WRITE_ERROR)
return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error;
stream = malloc (sizeof (cairo_output_stream_t));
if (stream == NULL) {
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (stream, NULL, NULL);
stream->status = status;
return stream;
}
cairo_status_t
_cairo_output_stream_close (cairo_output_stream_t *stream)
{
cairo_status_t status;
if (stream->closed)
return stream->status;
if (stream == &_cairo_output_stream_nil ||
stream == &_cairo_output_stream_nil_write_error)
{
return stream->status;
}
if (stream->close_func) {
status = stream->close_func (stream);
/* Don't overwrite a pre-existing status failure. */
if (stream->status == CAIRO_STATUS_SUCCESS)
stream->status = status;
}
stream->closed = TRUE;
return stream->status;
}
cairo_status_t
_cairo_output_stream_destroy (cairo_output_stream_t *stream)
{
cairo_status_t status;
assert (stream != NULL);
if (stream == &_cairo_output_stream_nil ||
stream == &_cairo_output_stream_nil_write_error)
{
return stream->status;
}
status = _cairo_output_stream_fini (stream);
free (stream);
return status;
}
void
_cairo_output_stream_write (cairo_output_stream_t *stream,
const void *data, size_t length)
{
if (length == 0)
return;
if (stream->status)
return;
stream->status = stream->write_func (stream, data, length);
stream->position += length;
}
void
_cairo_output_stream_write_hex_string (cairo_output_stream_t *stream,
const unsigned char *data,
size_t length)
{
const char hex_chars[] = "0123456789abcdef";
char buffer[2];
unsigned int i, column;
if (stream->status)
return;
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);
}
}
/* 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
*
* The code in the patch is copyright Red Hat, Inc under the LGPL, but
* has been relicensed under the LGPL/MPL dual license for inclusion
* into cairo (see COPYING). -- Kristian Høgsberg <krh@redhat.com>
*/
static void
_cairo_dtostr (char *buffer, size_t size, double d, cairo_bool_t limited_precision)
{
struct lconv *locale_data;
const char *decimal_point;
int decimal_point_len;
char *p;
int decimal_len;
int num_zeros, decimal_digits;
/* Omit the minus sign from negative zero. */
if (d == 0.0)
d = 0.0;
locale_data = localeconv ();
decimal_point = locale_data->decimal_point;
decimal_point_len = strlen (decimal_point);
assert (decimal_point_len != 0);
if (limited_precision) {
snprintf (buffer, size, "%.*f", FIXED_POINT_DECIMAL_DIGITS, d);
} else {
/* Using "%f" to print numbers less than 0.1 will result in
* reduced precision due to the default 6 digits after the
* decimal point.
*
* For numbers is < 0.1, we print with maximum precision and count
* the number of zeros between the decimal point and the first
* significant digit. We then print the number again with the
* number of decimal places that gives us the required number of
* significant digits. This ensures the number is correctly
* rounded.
*/
if (fabs (d) >= 0.1) {
snprintf (buffer, size, "%f", d);
} else {
snprintf (buffer, size, "%.18f", d);
p = buffer;
if (*p == '+' || *p == '-')
p++;
while (isdigit (*p))
p++;
if (strncmp (p, decimal_point, decimal_point_len) == 0)
p += decimal_point_len;
num_zeros = 0;
while (*p++ == '0')
num_zeros++;
decimal_digits = num_zeros + SIGNIFICANT_DIGITS_AFTER_DECIMAL;
if (decimal_digits < 18)
snprintf (buffer, size, "%.*f", decimal_digits, d);
}
}
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--;
}
}
}
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.
*/
void
_cairo_output_stream_vprintf (cairo_output_stream_t *stream,
const char *fmt, va_list ap)
{
#define SINGLE_FMT_BUFFER_SIZE 32
char buffer[512], single_fmt[SINGLE_FMT_BUFFER_SIZE];
int single_fmt_length;
char *p;
const char *f, *start;
int length_modifier, width;
cairo_bool_t var_width;
if (stream->status)
return;
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;
}
start = f;
f++;
if (*f == '0')
f++;
var_width = FALSE;
if (*f == '*') {
var_width = TRUE;
f++;
}
while (isdigit (*f))
f++;
length_modifier = 0;
if (*f == 'l') {
length_modifier = LENGTH_MODIFIER_LONG;
f++;
}
/* The only format strings exist in the cairo implementation
* itself. So there's an internal consistency problem if any
* of them is larger than our format buffer size. */
single_fmt_length = f - start + 1;
assert (single_fmt_length + 1 <= SINGLE_FMT_BUFFER_SIZE);
/* Reuse the format string for this conversion. */
memcpy (single_fmt, start, single_fmt_length);
single_fmt[single_fmt_length] = '\0';
/* Flush contents of buffer before snprintf()'ing into it. */
_cairo_output_stream_write (stream, buffer, p - buffer);
/* We group signed and unsigned together in this switch, the
* only thing that matters here is the size of the arguments,
* since we're just passing the data through to sprintf(). */
switch (*f | length_modifier) {
case '%':
buffer[0] = *f;
buffer[1] = 0;
break;
case 'd':
case 'u':
case 'o':
case 'x':
case 'X':
if (var_width) {
width = va_arg (ap, int);
snprintf (buffer, sizeof buffer,
single_fmt, width, va_arg (ap, int));
} else {
snprintf (buffer, sizeof buffer, single_fmt, va_arg (ap, int));
}
break;
case 'd' | LENGTH_MODIFIER_LONG:
case 'u' | LENGTH_MODIFIER_LONG:
case 'o' | LENGTH_MODIFIER_LONG:
case 'x' | LENGTH_MODIFIER_LONG:
case 'X' | LENGTH_MODIFIER_LONG:
if (var_width) {
width = va_arg (ap, int);
snprintf (buffer, sizeof buffer,
single_fmt, width, va_arg (ap, long int));
} else {
snprintf (buffer, sizeof buffer,
single_fmt, va_arg (ap, long int));
}
break;
case 's':
snprintf (buffer, sizeof buffer,
single_fmt, va_arg (ap, const char *));
break;
case 'f':
_cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), FALSE);
break;
case 'g':
_cairo_dtostr (buffer, sizeof buffer, va_arg (ap, double), TRUE);
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);
}
void
_cairo_output_stream_printf (cairo_output_stream_t *stream,
const char *fmt, ...)
{
va_list ap;
va_start (ap, fmt);
_cairo_output_stream_vprintf (stream, fmt, ap);
va_end (ap);
}
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. */
typedef struct _stdio_stream {
cairo_output_stream_t base;
FILE *file;
} stdio_stream_t;
static cairo_status_t
stdio_write (cairo_output_stream_t *base,
const unsigned char *data, unsigned int length)
{
stdio_stream_t *stream = (stdio_stream_t *) base;
if (fwrite (data, 1, length, stream->file) != length)
return _cairo_error (CAIRO_STATUS_WRITE_ERROR);
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
stdio_flush (cairo_output_stream_t *base)
{
stdio_stream_t *stream = (stdio_stream_t *) base;
fflush (stream->file);
if (ferror (stream->file))
return _cairo_error (CAIRO_STATUS_WRITE_ERROR);
else
return CAIRO_STATUS_SUCCESS;
}
static cairo_status_t
stdio_close (cairo_output_stream_t *base)
{
cairo_status_t status;
stdio_stream_t *stream = (stdio_stream_t *) base;
status = stdio_flush (base);
fclose (stream->file);
return status;
}
cairo_output_stream_t *
_cairo_output_stream_create_for_file (FILE *file)
{
stdio_stream_t *stream;
if (file == NULL) {
_cairo_error_throw (CAIRO_STATUS_WRITE_ERROR);
return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error;
}
stream = malloc (sizeof *stream);
if (stream == NULL) {
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (&stream->base, stdio_write, stdio_flush);
stream->file = file;
return &stream->base;
}
cairo_output_stream_t *
_cairo_output_stream_create_for_filename (const char *filename)
{
stdio_stream_t *stream;
FILE *file;
if (filename == NULL)
return _cairo_null_stream_create ();
file = fopen (filename, "wb");
if (file == NULL) {
switch (errno) {
case ENOMEM:
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
default:
_cairo_error_throw (CAIRO_STATUS_WRITE_ERROR);
return (cairo_output_stream_t *) &_cairo_output_stream_nil_write_error;
}
}
stream = malloc (sizeof *stream);
if (stream == NULL) {
fclose (file);
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (&stream->base, stdio_write, stdio_close);
stream->file = file;
return &stream->base;
}
typedef struct _memory_stream {
cairo_output_stream_t base;
cairo_array_t array;
} memory_stream_t;
static cairo_status_t
memory_write (cairo_output_stream_t *base,
const unsigned char *data, unsigned int length)
{
memory_stream_t *stream = (memory_stream_t *) base;
return _cairo_array_append_multiple (&stream->array, data, length);
}
static cairo_status_t
memory_close (cairo_output_stream_t *base)
{
memory_stream_t *stream = (memory_stream_t *) base;
_cairo_array_fini (&stream->array);
return CAIRO_STATUS_SUCCESS;
}
cairo_output_stream_t *
_cairo_memory_stream_create (void)
{
memory_stream_t *stream;
stream = malloc (sizeof *stream);
if (stream == NULL) {
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (&stream->base, memory_write, memory_close);
_cairo_array_init (&stream->array, 1);
return &stream->base;
}
void
_cairo_memory_stream_copy (cairo_output_stream_t *base,
cairo_output_stream_t *dest)
{
memory_stream_t *stream = (memory_stream_t *) base;
if (dest->status)
return;
if (base->status) {
dest->status = base->status;
return;
}
_cairo_output_stream_write (dest,
_cairo_array_index (&stream->array, 0),
_cairo_array_num_elements (&stream->array));
}
int
_cairo_memory_stream_length (cairo_output_stream_t *base)
{
memory_stream_t *stream = (memory_stream_t *) base;
return _cairo_array_num_elements (&stream->array);
}
static cairo_status_t
null_write (cairo_output_stream_t *base,
const unsigned char *data, unsigned int length)
{
return CAIRO_STATUS_SUCCESS;
}
cairo_output_stream_t *
_cairo_null_stream_create (void)
{
cairo_output_stream_t *stream;
stream = malloc (sizeof *stream);
if (stream == NULL) {
_cairo_error_throw (CAIRO_STATUS_NO_MEMORY);
return (cairo_output_stream_t *) &_cairo_output_stream_nil;
}
_cairo_output_stream_init (stream, null_write, NULL);
return stream;
}