diff --git a/libvips/foreign/foreign.c b/libvips/foreign/foreign.c index 76ae4382..fbd87224 100644 --- a/libvips/foreign/foreign.c +++ b/libvips/foreign/foreign.c @@ -1980,6 +1980,7 @@ vips_foreign_operation_init( void ) extern GType vips_foreign_load_rad_get_type( void ); extern GType vips_foreign_save_rad_file_get_type( void ); extern GType vips_foreign_save_rad_buffer_get_type( void ); + extern GType vips_foreign_save_rad_stream_get_type( void ); extern GType vips_foreign_load_mat_get_type( void ); extern GType vips_foreign_load_ppm_get_type( void ); extern GType vips_foreign_save_ppm_get_type( void ); @@ -2072,6 +2073,7 @@ vips_foreign_operation_init( void ) vips_foreign_load_rad_get_type(); vips_foreign_save_rad_file_get_type(); vips_foreign_save_rad_buffer_get_type(); + vips_foreign_save_rad_stream_get_type(); #endif /*HAVE_RADIANCE*/ #ifdef HAVE_POPPLER diff --git a/libvips/foreign/pforeign.h b/libvips/foreign/pforeign.h index de481f00..7e230771 100644 --- a/libvips/foreign/pforeign.h +++ b/libvips/foreign/pforeign.h @@ -156,8 +156,7 @@ int vips__rad_israd( const char *filename ); int vips__rad_header( const char *filename, VipsImage *out ); int vips__rad_load( const char *filename, VipsImage *out ); -int vips__rad_save( VipsImage *in, const char *filename ); -int vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ); +int vips__rad_save( VipsImage *in, VipsStreamo *output ); extern const char *vips__rad_suffs[]; diff --git a/libvips/foreign/radiance.c b/libvips/foreign/radiance.c index bdcf84c1..caca7041 100644 --- a/libvips/foreign/radiance.c +++ b/libvips/foreign/radiance.c @@ -499,18 +499,6 @@ formatval( /* get format value (return true if format) */ } -static void -fputformat( /* put out a format value */ - const char *s, - FILE *fp -) -{ - fputs(FMTSTR, fp); - fputs(s, fp); - putc('\n', fp); -} - - static int getheader( /* get header from file */ FILE *fp, @@ -923,31 +911,6 @@ rle_scanline_write( COLR *scanline, int width, } } -/* Write a single scanline. buffer is at least MAX_LINE bytes and is used to - * construct the RLE scanline. Don't allocate this on the stack so we don't - * die too horribly on small-stack libc. - */ -static int -scanline_write( unsigned char *buffer, COLR *scanline, int width, FILE *fp ) -{ - if( width < MINELEN || - width > MAXELEN ) - /* Write as a flat scanline. - */ - return( fwrite( scanline, sizeof( COLR ), width, fp ) - width ); - else { - /* An RLE scanline. - */ - int length; - - rle_scanline_write( scanline, width, buffer, &length ); - - g_assert( length <= MAX_LINE ); - - return( fwrite( buffer, 1, length, fp ) - length ); - } -} - /* What we track during radiance file read. */ typedef struct { @@ -1211,11 +1174,7 @@ vips__rad_load( const char *filename, VipsImage *out ) */ typedef struct { VipsImage *in; - - char *filename; - FILE *fout; - - VipsDbuf dbuf; + VipsStreamo *output; char format[256]; double expos; @@ -1223,20 +1182,20 @@ typedef struct { double aspect; RGBPRIMS prims; RESOLU rs; + unsigned char *line; } Write; static void write_destroy( Write *write ) { - VIPS_FREE( write->filename ); - VIPS_FREEF( fclose, write->fout ); - vips_dbuf_destroy( &write->dbuf ); + VIPS_FREE( write->line ); + VIPS_UNREF( write->output ); vips_free( write ); } static Write * -write_new( VipsImage *in ) +write_new( VipsImage *in, VipsStreamo *output ) { Write *write; int i; @@ -1245,11 +1204,8 @@ write_new( VipsImage *in ) return( NULL ); write->in = in; - - write->filename = NULL; - write->fout = NULL; - - vips_dbuf_init( &write->dbuf ); + write->output = output; + g_object_ref( output ); strcpy( write->format, COLRFMT ); write->expos = 1.0; @@ -1265,6 +1221,11 @@ write_new( VipsImage *in ) write->prims[3][0] = CIE_x_w; write->prims[3][1] = CIE_y_w; + if( !(write->line = VIPS_ARRAY( NULL, MAX_LINE, unsigned char )) ) { + write_destroy( write ); + return( NULL ); + } + return( write ); } @@ -1279,7 +1240,8 @@ vips2rad_make_header( Write *write ) vips_image_get_double( write->in, "rad-expos", &write->expos ); if( vips_image_get_typeof( write->in, "rad-aspect" ) ) - vips_image_get_double( write->in, "rad-aspect", &write->aspect ); + vips_image_get_double( write->in, + "rad-aspect", &write->aspect ); if( vips_image_get_typeof( write->in, "rad-format" ) && !vips_image_get_string( write->in, "rad-format", &str ) ) @@ -1292,7 +1254,8 @@ vips2rad_make_header( Write *write ) for( i = 0; i < 3; i++ ) if( vips_image_get_typeof( write->in, colcor_name[i] ) && - !vips_image_get_double( write->in, colcor_name[i], &d ) ) + !vips_image_get_double( write->in, + colcor_name[i], &d ) ) write->colcor[i] = d; for( i = 0; i < 4; i++ ) @@ -1316,16 +1279,53 @@ vips2rad_put_header( Write *write ) { vips2rad_make_header( write ); - fprintf( write->fout, "#?RADIANCE\n" ); + vips_streamo_writef( write->output, "#?RADIANCE\n" ); + vips_streamo_writef( write->output, "%s%s\n", FMTSTR, write->format ); + vips_streamo_writef( write->output, "%s%e\n", EXPOSSTR, write->expos ); + vips_streamo_writef( write->output, + "%s %f %f %f\n", COLCORSTR, + write->colcor[RED], write->colcor[GRN], write->colcor[BLU] ); + vips_streamo_writef( write->output, + "SOFTWARE=vips %s\n", vips_version_string() ); + vips_streamo_writef( write->output, + "%s%f\n", ASPECTSTR, write->aspect ); + vips_streamo_writef( write->output, + "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n", + PRIMARYSTR, + write->prims[RED][CIEX], write->prims[RED][CIEY], + write->prims[GRN][CIEX], write->prims[GRN][CIEY], + write->prims[BLU][CIEX], write->prims[BLU][CIEY], + write->prims[WHT][CIEX], write->prims[WHT][CIEY] ); + vips_streamo_writef( write->output, "\n" ); + vips_streamo_writef( write->output, + "%s", resolu2str( resolu_buf, &write->rs ) ); - fputformat( write->format, write->fout ); - fputexpos( write->expos, write->fout ); - fputcolcor( write->colcor, write->fout ); - fprintf( write->fout, "SOFTWARE=vips %s\n", vips_version_string() ); - fputaspect( write->aspect, write->fout ); - fputprims( write->prims, write->fout ); - fputs( "\n", write->fout ); - fputsresolu( &write->rs, write->fout ); + return( 0 ); +} + +/* Write a single scanline to buffer. + */ +static int +scanline_write( Write *write, COLR *scanline, int width ) +{ + if( width < MINELEN || + width > MAXELEN ) { + /* Too large or small for RLE ... do a simple write. + */ + if( vips_streamo_write( write->output, + scanline, sizeof( COLR ) * width ) ) + return( -1 ); + } + else { + int length; + + /* An RLE scanline. + */ + rle_scanline_write( scanline, width, write->line, &length ); + + if( vips_streamo_write( write->output, write->line, length ) ) + return( -1 ); + } return( 0 ); } @@ -1334,23 +1334,12 @@ static int vips2rad_put_data_block( VipsRegion *region, VipsRect *area, void *a ) { Write *write = (Write *) a; - - size_t size; - unsigned char *buffer; int i; - /* You have to seek back after a write. - */ - buffer = vips_dbuf_get_write( &write->dbuf, &size ); - vips_dbuf_seek( &write->dbuf, 0, SEEK_SET ); - - g_assert( size >= MAX_LINE ); - for( i = 0; i < area->height; i++ ) { VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - if( scanline_write( buffer, - (COLR *) p, area->width, write->fout ) ) + if( scanline_write( write, (COLR *) p, area->width ) ) return( -1 ); } @@ -1367,124 +1356,7 @@ vips2rad_put_data( Write *write ) } int -vips__rad_save( VipsImage *in, const char *filename ) -{ - Write *write; - -#ifdef DEBUG - printf( "vips2rad: writing \"%s\"\n", filename ); -#endif /*DEBUG*/ - - if( vips_image_pio_input( in ) || - vips_check_coding_rad( "vips2rad", in ) ) - return( -1 ); - if( !(write = write_new( in )) ) - return( -1 ); - - write->filename = vips_strdup( NULL, filename ); - write->fout = vips__file_open_write( filename, FALSE ); - - /* scanline_write() needs a buffer to write compressed scanlines to. - * We use the dbuf ... why not. - */ - vips_dbuf_allocate( &write->dbuf, MAX_LINE ); - - if( !write->filename || - !write->fout || - vips2rad_put_header( write ) || - vips2rad_put_data( write ) ) { - write_destroy( write ); - return( -1 ); - } - write_destroy( write ); - - return( 0 ); -} - -static int -vips2rad_put_header_buf( Write *write ) -{ - vips2rad_make_header( write ); - - vips_dbuf_writef( &write->dbuf, "#?RADIANCE\n" ); - vips_dbuf_writef( &write->dbuf, "%s%s\n", FMTSTR, write->format ); - vips_dbuf_writef( &write->dbuf, "%s%e\n", EXPOSSTR, write->expos ); - vips_dbuf_writef( &write->dbuf, "%s %f %f %f\n", - COLCORSTR, - write->colcor[RED], write->colcor[GRN], write->colcor[BLU] ); - vips_dbuf_writef( &write->dbuf, "SOFTWARE=vips %s\n", - vips_version_string() ); - vips_dbuf_writef( &write->dbuf, "%s%f\n", ASPECTSTR, write->aspect ); - vips_dbuf_writef( &write->dbuf, - "%s %.4f %.4f %.4f %.4f %.4f %.4f %.4f %.4f\n", - PRIMARYSTR, - write->prims[RED][CIEX], write->prims[RED][CIEY], - write->prims[GRN][CIEX], write->prims[GRN][CIEY], - write->prims[BLU][CIEX], write->prims[BLU][CIEY], - write->prims[WHT][CIEX], write->prims[WHT][CIEY] ); - vips_dbuf_writef( &write->dbuf, "\n" ); - vips_dbuf_writef( &write->dbuf, "%s", - resolu2str( resolu_buf, &write->rs ) ); - - return( 0 ); -} - -/* Write a single scanline to buffer. - */ -static int -scanline_write_buf( Write *write, COLR *scanline, int width ) -{ - unsigned char *buffer; - size_t size; - int length; - - vips_dbuf_allocate( &write->dbuf, MAX_LINE ); - buffer = vips_dbuf_get_write( &write->dbuf, &size ); - - if( width < MINELEN || - width > MAXELEN ) { - /* Write as a flat scanline. - */ - length = sizeof( COLR ) * width; - memcpy( buffer, scanline, length ); - } - else - /* An RLE scanline. - */ - rle_scanline_write( scanline, width, buffer, &length ); - - vips_dbuf_seek( &write->dbuf, length - size, SEEK_CUR ); - - return( 0 ); -} - -static int -vips2rad_put_data_block_buf( VipsRegion *region, VipsRect *area, void *a ) -{ - Write *write = (Write *) a; - int i; - - for( i = 0; i < area->height; i++ ) { - VipsPel *p = VIPS_REGION_ADDR( region, 0, area->top + i ); - - if( scanline_write_buf( write, (COLR *) p, area->width ) ) - return( -1 ); - } - - return( 0 ); -} - -static int -vips2rad_put_data_buf( Write *write ) -{ - if( vips_sink_disc( write->in, vips2rad_put_data_block_buf, write ) ) - return( -1 ); - - return( 0 ); -} - -int -vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ) +vips__rad_save( VipsImage *in, VipsStreamo *output ) { Write *write; @@ -1495,16 +1367,16 @@ vips__rad_save_buf( VipsImage *in, void **obuf, size_t *olen ) if( vips_image_pio_input( in ) || vips_check_coding_rad( "vips2rad", in ) ) return( -1 ); - if( !(write = write_new( in )) ) + if( !(write = write_new( in, output )) ) return( -1 ); - if( vips2rad_put_header_buf( write ) || - vips2rad_put_data_buf( write ) ) { + if( vips2rad_put_header( write ) || + vips2rad_put_data( write ) ) { write_destroy( write ); return( -1 ); } - *obuf = vips_dbuf_steal( &write->dbuf, olen ); + vips_streamo_finish( output ); write_destroy( write ); diff --git a/libvips/foreign/radsave.c b/libvips/foreign/radsave.c index 4571a9c6..28f6f25b 100644 --- a/libvips/foreign/radsave.c +++ b/libvips/foreign/radsave.c @@ -56,7 +56,6 @@ typedef struct _VipsForeignSaveRad { VipsForeignSave parent_object; - char *filename; } VipsForeignSaveRad; typedef VipsForeignSaveClass VipsForeignSaveRadClass; @@ -85,14 +84,10 @@ static int vips_foreign_save_rad_format_table[10] = { static void vips_foreign_save_rad_class_init( VipsForeignSaveRadClass *class ) { - GObjectClass *gobject_class = G_OBJECT_CLASS( class ); VipsObjectClass *object_class = (VipsObjectClass *) class; VipsForeignClass *foreign_class = (VipsForeignClass *) class; VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; - gobject_class->set_property = vips_object_set_property; - gobject_class->get_property = vips_object_get_property; - object_class->nickname = "radsave_base"; object_class->description = _( "save Radiance" ); @@ -127,12 +122,19 @@ vips_foreign_save_rad_file_build( VipsObject *object ) VipsForeignSave *save = (VipsForeignSave *) object; VipsForeignSaveRadFile *file = (VipsForeignSaveRadFile *) object; + VipsStreamo *output; + if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_file_parent_class )-> build( object ) ) return( -1 ); - if( vips__rad_save( save->ready, file->filename ) ) + if( !(output = vips_streamo_new_from_filename( file->filename )) ) return( -1 ); + if( vips__rad_save( save->ready, output ) ) { + VIPS_UNREF( output ); + return( -1 ); + } + VIPS_UNREF( output ); return( 0 ); } @@ -163,6 +165,60 @@ vips_foreign_save_rad_file_init( VipsForeignSaveRadFile *file ) { } +typedef struct _VipsForeignSaveRadStream { + VipsForeignSaveRad parent_object; + + VipsStreamo *output; +} VipsForeignSaveRadStream; + +typedef VipsForeignSaveRadClass VipsForeignSaveRadStreamClass; + +G_DEFINE_TYPE( VipsForeignSaveRadStream, vips_foreign_save_rad_stream, + vips_foreign_save_rad_get_type() ); + +static int +vips_foreign_save_rad_stream_build( VipsObject *object ) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveRadStream *stream = (VipsForeignSaveRadStream *) object; + + if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_stream_parent_class )-> + build( object ) ) + return( -1 ); + + if( vips__rad_save( save->ready, stream->output ) ) + return( -1 ); + + return( 0 ); +} + +static void +vips_foreign_save_rad_stream_class_init( VipsForeignSaveRadStreamClass *class ) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS( class ); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "radsave_stream"; + object_class->description = _( "save image to Radiance stream" ); + object_class->build = vips_foreign_save_rad_stream_build; + + VIPS_ARG_OBJECT( class, "output", 1, + _( "Output" ), + _( "Stream to save to" ), + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET( VipsForeignSaveRadStream, output ), + VIPS_TYPE_STREAMO ); + +} + +static void +vips_foreign_save_rad_stream_init( VipsForeignSaveRadStream *stream ) +{ +} + typedef struct _VipsForeignSaveRadBuffer { VipsForeignSaveRad parent_object; @@ -179,24 +235,27 @@ vips_foreign_save_rad_buffer_build( VipsObject *object ) { VipsForeignSave *save = (VipsForeignSave *) object; - void *obuf; - size_t olen; + VipsStreamo *output; VipsBlob *blob; if( VIPS_OBJECT_CLASS( vips_foreign_save_rad_buffer_parent_class )-> build( object ) ) return( -1 ); - if( vips__rad_save_buf( save->ready, &obuf, &olen ) ) + if( !(output = vips_streamo_new_memory()) ) return( -1 ); - /* vips__rad_save_buf() makes a buffer that needs g_free(), not - * vips_free(). - */ - blob = vips_blob_new( (VipsCallbackFn) g_free, obuf, olen ); - g_object_set( object, "buffer", blob, NULL ); + if( vips__rad_save( save->ready, output ) ) { + VIPS_UNREF( output ); + return( -1 ); + } + + g_object_get( output, "blob", &blob, NULL ); + g_object_set( save, "buffer", blob, NULL ); vips_area_unref( VIPS_AREA( blob ) ); + VIPS_UNREF( output ); + return( 0 ); } @@ -299,3 +358,28 @@ vips_radsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) return( result ); } + +/** + * vips_radsave_stream: (method) + * @in: image to save + * @output: save image to this stream + * @...: %NULL-terminated list of optional named arguments + * + * As vips_radsave(), but save to a stream. + * + * See also: vips_radsave(). + * + * Returns: 0 on success, -1 on error. + */ +int +vips_radsave_stream( VipsImage *in, VipsStreamo *output, ... ) +{ + va_list ap; + int result; + + va_start( ap, output ); + result = vips_call_split( "radsave_stream", ap, in, output ); + va_end( ap ); + + return( result ); +} diff --git a/libvips/foreign/tiff.c b/libvips/foreign/tiff.c index 41ca0d8d..479e705c 100644 --- a/libvips/foreign/tiff.c +++ b/libvips/foreign/tiff.c @@ -205,12 +205,9 @@ vips__tiff_openin_stream( VipsStreami *input ) printf( "vips__tiff_openin_stream:\n" ); #endif /*DEBUG*/ - /* Unreffed on close(), see above. - */ if( vips_streami_rewind( input ) ) return( NULL ); - g_object_ref( input ); if( !(tiff = TIFFClientOpen( "stream input", "rm", (thandle_t) input, openin_stream_read, @@ -225,6 +222,10 @@ vips__tiff_openin_stream( VipsStreami *input ) return( NULL ); } + /* Unreffed on close(), see above. + */ + g_object_ref( input ); + return( tiff ); } diff --git a/libvips/include/vips/dbuf.h b/libvips/include/vips/dbuf.h index 787b505d..bb8fca47 100644 --- a/libvips/include/vips/dbuf.h +++ b/libvips/include/vips/dbuf.h @@ -68,7 +68,8 @@ size_t vips_dbuf_read( VipsDbuf *dbuf, unsigned char *data, size_t size ); unsigned char *vips_dbuf_get_write( VipsDbuf *dbuf, size_t *size ); gboolean vips_dbuf_write( VipsDbuf *dbuf, const unsigned char *data, size_t size ); -gboolean vips_dbuf_writef( VipsDbuf *dbuf, const char *fmt, ... ); +gboolean vips_dbuf_writef( VipsDbuf *dbuf, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); gboolean vips_dbuf_write_amp( VipsDbuf *dbuf, const char *str ); void vips_dbuf_reset( VipsDbuf *dbuf ); void vips_dbuf_destroy( VipsDbuf *dbuf ); diff --git a/libvips/include/vips/foreign.h b/libvips/include/vips/foreign.h index 9d962b3a..a2594617 100644 --- a/libvips/include/vips/foreign.h +++ b/libvips/include/vips/foreign.h @@ -576,6 +576,8 @@ int vips_radsave( VipsImage *in, const char *filename, ... ) __attribute__((sentinel)); int vips_radsave_buffer( VipsImage *in, void **buf, size_t *len, ... ) __attribute__((sentinel)); +int vips_radsave_stream( VipsImage *in, VipsStreamo *output, ... ) + __attribute__((sentinel)); int vips_pdfload( const char *filename, VipsImage **out, ... ) __attribute__((sentinel)); diff --git a/libvips/include/vips/stream.h b/libvips/include/vips/stream.h index 2fd08af4..66beda1f 100644 --- a/libvips/include/vips/stream.h +++ b/libvips/include/vips/stream.h @@ -184,13 +184,14 @@ typedef struct _VipsStreamiClass { */ /* Read from the stream into the supplied buffer, args exactly as - * read(2). + * read(2). Set errno on error. */ ssize_t (*read)( VipsStreami *, void *, size_t ); - /* Seek to a certain position, args exactly as lseek(2). + /* Seek to a certain position, args exactly as lseek(2). Set errno on + * error. * - * Unseekable streams should just return -1. VipsStreami will then + * Unseekable streams should always return -1. VipsStreami will then * seek by _read()ing bytes into memory as required. */ gint64 (*seek)( VipsStreami *, gint64 offset, int ); @@ -230,6 +231,8 @@ gint64 vips_streami_size( VipsStreami *streami ); (G_TYPE_INSTANCE_GET_CLASS( (obj), \ VIPS_TYPE_STREAMO, VipsStreamoClass )) +#define VIPS_STREAMO_BUFFER_SIZE (4096) + /* Output to something like a socket, pipe or memory area. */ typedef struct _VipsStreamo { @@ -245,16 +248,22 @@ typedef struct _VipsStreamo { */ VipsBlob *blob; + /* Buffer small writes here. + */ + unsigned char output_buffer[VIPS_STREAMO_BUFFER_SIZE]; + unsigned char *write_point; + int bytes_remaining; + } VipsStreamo; typedef struct _VipsStreamoClass { VipsStreamClass parent_class; - /* If defined, output some bytes with this. Otherwise use write(). + /* Write to output. Args exactly as write(2). */ ssize_t (*write)( VipsStreamo *, const void *, size_t ); - /* A complete output image has been generated, so do any clearing up, + /* Output has been generated, so do any clearing up, * eg. copy the bytes we saved in memory to the stream blob. */ void (*finish)( VipsStreamo * ); @@ -269,6 +278,10 @@ VipsStreamo *vips_streamo_new_memory( void ); int vips_streamo_write( VipsStreamo *streamo, const void *data, size_t length ); void vips_streamo_finish( VipsStreamo *streamo ); +int vips_streamo_writef( VipsStreamo *streamo, const char *fmt, ... ) + __attribute__((format(printf, 2, 3))); +int vips_streamo_write_amp( VipsStreamo *streamo, const char *str ); + #ifdef __cplusplus } #endif /*__cplusplus*/ diff --git a/libvips/iofuncs/streamo.c b/libvips/iofuncs/streamo.c index 883711bb..14cf8ad2 100644 --- a/libvips/iofuncs/streamo.c +++ b/libvips/iofuncs/streamo.c @@ -34,9 +34,7 @@ /* TODO * * - filename encoding - * - need printf()-style output? eg. for CSV write ... see dbuf.c - * - make vfuncs just call write() and seek() (for tiff), all other processing - * in the invocation ... cf. streami + * - seem to have some test-suite ref leaks? * - test we can really change all behaviour in the subclass ... add callbacks * as well to make it simpler for language bindings */ @@ -157,37 +155,15 @@ vips_streamo_write_real( VipsStreamo *streamo, const void *data, size_t length ) { VipsStream *stream = VIPS_STREAM( streamo ); - ssize_t len; - VIPS_DEBUG_MSG( "vips_streamo_write_real: %zd bytes\n", length ); - if( streamo->memory ) { - g_byte_array_append( streamo->memory, data, length ); - len = length; - } - else - len = write( stream->descriptor, data, length ); - - return( len ); + return( write( stream->descriptor, data, length ) ); } static void vips_streamo_finish_real( VipsStreamo *streamo ) { VIPS_DEBUG_MSG( "vips_streamo_finish_real:\n" ); - - /* Move the stream buffer into the blob so it can be read out. - */ - if( streamo->memory ) { - unsigned char *data; - size_t length; - - length = streamo->memory->len; - data = g_byte_array_free( streamo->memory, FALSE ); - streamo->memory = NULL; - vips_blob_set( streamo->blob, - (VipsCallbackFn) g_free, data, length ); - } } static void @@ -224,6 +200,8 @@ static void vips_streamo_init( VipsStreamo *streamo ) { streamo->blob = vips_blob_new( NULL, NULL, 0 ); + streamo->bytes_remaining = VIPS_STREAMO_BUFFER_SIZE; + streamo->write_point = streamo->output_buffer; } /** @@ -315,35 +293,107 @@ vips_streamo_new_memory( void ) return( streamo ); } -int -vips_streamo_write( VipsStreamo *streamo, const void *data, size_t length ) +static int +vips_streamo_write_unbuffered( VipsStreamo *streamo, + const void *data, size_t length ) { VipsStreamoClass *class = VIPS_STREAMO_GET_CLASS( streamo ); - VIPS_DEBUG_MSG( "vips_streamo_write: %zd bytes\n", length ); + if( streamo->memory ) + g_byte_array_append( streamo->memory, data, length ); + else + while( length > 0 ) { + ssize_t n; - while( length > 0 ) { - ssize_t n; + n = class->write( streamo, data, length ); - n = class->write( streamo, data, length ); + /* n == 0 isn't strictly an error, but we treat it as + * one to make sure we don't get stuck in this loop. + */ + if( n <= 0 ) { + vips_error_system( errno, + vips_stream_nick( + VIPS_STREAM( streamo ) ), + "%s", _( "write error" ) ); + return( -1 ); + } - /* n == 0 isn't strictly an error, but we treat it as one to - * make sure we don't get stuck in this loop. - */ - if( n <= 0 ) { - vips_error_system( errno, - vips_stream_nick( VIPS_STREAM( streamo ) ), - "%s", _( "write error" ) ); - return( -1 ); + length -= n; + data += n; } - length -= n; - data += n; + return( 0 ); +} + +static int +vips_streamo_flush( VipsStreamo *streamo ) +{ + int bytes_in_buffer = + VIPS_STREAMO_BUFFER_SIZE - streamo->bytes_remaining; + + g_assert( bytes_in_buffer >= 0 ); + g_assert( bytes_in_buffer <= VIPS_STREAMO_BUFFER_SIZE ); + + if( bytes_in_buffer > 0 ) { + if( vips_streamo_write_unbuffered( streamo, + streamo->output_buffer, bytes_in_buffer ) ) + return( -1 ); + streamo->bytes_remaining = VIPS_STREAMO_BUFFER_SIZE; + streamo->write_point = streamo->output_buffer; } return( 0 ); } +/** + * vips_streamo_write: + * @streamo: output stream to operate on + * @buffer: bytes to write + * @length: length of @buffer in bytes + * + * Write @length bytes from @buffer to the output. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_streamo_write( VipsStreamo *streamo, const void *buffer, size_t length ) +{ + VIPS_DEBUG_MSG( "vips_streamo_write: %zd bytes\n", length ); + + if( streamo->bytes_remaining >= length ) { + memcpy( streamo->write_point, buffer, length ); + streamo->bytes_remaining -= length; + streamo->write_point += length; + } + else { + if( vips_streamo_flush( streamo ) ) + return( -1 ); + + if( streamo->bytes_remaining >= length ) { + memcpy( streamo->write_point, buffer, length ); + streamo->bytes_remaining -= length; + streamo->write_point += length; + } + else + /* The buffer is empty and we still have too much + * data. Write directly. + */ + if( vips_streamo_write_unbuffered( streamo, + buffer, length ) ) + return( -1 ); + } + + return( 0 ); +} + +/** + * vips_streamo_finish: + * @streamo: output stream to operate on + * @buffer: bytes to write + * @length: length of @buffer in bytes + * + * Call this at the end of write to make the stream do any cleaning up. + */ void vips_streamo_finish( VipsStreamo *streamo ) { @@ -351,5 +401,112 @@ vips_streamo_finish( VipsStreamo *streamo ) VIPS_DEBUG_MSG( "vips_streamo_finish:\n" ); - class->finish( streamo ); + (void) vips_streamo_flush( streamo ); + + /* Move the stream buffer into the blob so it can be read out. + */ + if( streamo->memory ) { + unsigned char *data; + size_t length; + + length = streamo->memory->len; + data = g_byte_array_free( streamo->memory, FALSE ); + streamo->memory = NULL; + vips_blob_set( streamo->blob, + (VipsCallbackFn) g_free, data, length ); + } + else + class->finish( streamo ); } + +/** + * vips_streamo_writef: + * @streamo: output stream to operate on + * @fmt: printf()-style format string + * @...: arguments to format string + * + * Format the string and write to @streamo. + * + * Returns: 0 on success, and -1 on error. + */ +int +vips_streamo_writef( VipsStreamo *streamo, const char *fmt, ... ) +{ + va_list ap; + char *line; + int result; + + va_start( ap, fmt ); + line = g_strdup_vprintf( fmt, ap ); + va_end( ap ); + + result = vips_streamo_write( streamo, + (unsigned char *) line, strlen( line ) ); + + g_free( line ); + + return( result ); +} + +/** + * vips_streamo_write_amp: + * @streamo: output stream to operate on + * @str: string to write + * + * Write @str to @streamo, but escape stuff that xml hates in text. Our + * argument string is utf-8. + * + * XML rules: + * + * - We must escape &<> + * - Don't escape \n, \t, \r + * - Do escape the other ASCII codes. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_streamo_write_amp( VipsStreamo *streamo, const char *str ) +{ + const char *p; + + for( p = str; *p; p++ ) + if( *p < 32 && + *p != '\n' && + *p != '\t' && + *p != '\r' ) { + /* You'd think we could output "%x;", but xml + * 1.0 parsers barf on that. xml 1.1 allows this, but + * there are almost no parsers. + * + * U+2400 onwards are unicode glyphs for the ASCII + * control characters, so we can use them -- thanks + * electroly. + */ + if( !vips_streamo_writef( streamo, + "&#x%04x;", 0x2400 + *p ) ) + return( -1 ); + } + else if( *p == '<' ) { + if( !vips_streamo_write( streamo, + (guchar *) "<", 4 ) ) + return( -1 ); + } + else if( *p == '>' ) { + if( !vips_streamo_write( streamo, + (guchar *) ">", 4 ) ) + return( -1 ); + } + else if( *p == '&' ) { + if( !vips_streamo_write( streamo, + (guchar *) "&", 5 ) ) + return( -1 ); + } + else { + if( !vips_streamo_write( streamo, + (guchar *) p, 1 ) ) + return( -1 ); + } + + return( 0 ); +} +