diff --git a/png.h b/png.h index 553e0718b..01c1d433e 100644 --- a/png.h +++ b/png.h @@ -692,6 +692,16 @@ typedef png_sPLT_t * * png_sPLT_tpp; * PNG_TEXT_COMPRESSION_zTXt. Note that the "compression value" is not the * same as what appears in the PNG tEXt/zTXt/iTXt chunk's "compression flag" * which is always 0 or 1, or its "compression method" which is always 0. + * + * The location field (added in libpng 1.7.0) records where the text chunk was + * found when png_get_text is used. When png_set_text is used the field in the + * structure passed in is ignored and, instead, the field is set to the current + * write position. + * + * Prior to 1.7.0 the write behavior was the same; the text fields were written + * (once) at the next write_info call, however the read mechanism did not record + * the chunk location so if an info_struct from read was passed to the write + * APIs the text chunks would all be written at the start (before PLTE). */ typedef struct png_text_struct { @@ -700,6 +710,7 @@ typedef struct png_text_struct 0: zTXt, deflate 1: iTXt, none 2: iTXt, deflate */ + png_byte location; /* before/after PLTE or after IDAT */ png_charp key; /* keyword, 1-79 character description of "text" */ png_charp text; /* comment, may be an empty string (ie "") or a NULL pointer */ @@ -717,8 +728,14 @@ typedef png_text * * png_textpp; /* Supported compression types for text in PNG files (tEXt, and zTXt). * The values of the PNG_TEXT_COMPRESSION_ defines should NOT be changed. */ +#ifdef PNG_OLD_COMPRESSION_CODES_SUPPORTED + /* These values were used to prevent double write of text chunks in versions + * prior to 1.7.0. They are never set now; if you need them #define the + * _SUPPORTED macro. + */ #define PNG_TEXT_COMPRESSION_NONE_WR -3 #define PNG_TEXT_COMPRESSION_zTXt_WR -2 +#endif /* OLD_COMPRESSION_CODES */ #define PNG_TEXT_COMPRESSION_NONE -1 #define PNG_TEXT_COMPRESSION_zTXt 0 #define PNG_ITXT_COMPRESSION_NONE 1 @@ -776,7 +793,7 @@ typedef const png_unknown_chunk * png_const_unknown_chunkp; typedef png_unknown_chunk * * png_unknown_chunkpp; #endif -/* Flag values for the unknown chunk location byte. */ +/* Flag values for the chunk location byte. */ #define PNG_HAVE_IHDR 0x01U #define PNG_HAVE_PLTE 0x02U #define PNG_AFTER_IDAT 0x08U diff --git a/pnginfo.h b/pnginfo.h index bc0005d06..a0b5d5c76 100644 --- a/pnginfo.h +++ b/pnginfo.h @@ -118,6 +118,7 @@ struct png_info_def * modified. See the png_time struct for the contents of this struct. */ png_time mod_time; + png_byte time_location; #endif #ifdef PNG_sBIT_SUPPORTED diff --git a/pngpriv.h b/pngpriv.h index 7b00792da..f417bd317 100644 --- a/pngpriv.h +++ b/pngpriv.h @@ -1150,12 +1150,12 @@ PNG_INTERNAL_FUNCTION(void,png_write_hIST,(png_structrp png_ptr, /* Chunks that have keywords */ #ifdef PNG_WRITE_tEXt_SUPPORTED PNG_INTERNAL_FUNCTION(void,png_write_tEXt,(png_structrp png_ptr, - png_const_charp key, png_const_charp text, png_size_t text_len),PNG_EMPTY); + png_const_charp key, png_const_charp text, png_size_t text_len),PNG_EMPTY); #endif #ifdef PNG_WRITE_zTXt_SUPPORTED -PNG_INTERNAL_FUNCTION(void,png_write_zTXt,(png_structrp png_ptr, png_const_charp - key, png_const_charp text, int compression),PNG_EMPTY); +PNG_INTERNAL_FUNCTION(void,png_write_zTXt,(png_structrp png_ptr, + png_const_charp key, png_const_charp text, int compression),PNG_EMPTY); #endif #ifdef PNG_WRITE_iTXt_SUPPORTED diff --git a/pngset.c b/pngset.c index f10951fe5..ab9b3c6bd 100644 --- a/pngset.c +++ b/pngset.c @@ -673,6 +673,77 @@ png_set_iCCP(png_const_structrp png_ptr, png_inforp info_ptr, } #endif +#if defined(PNG_TEXT_SUPPORTED) || defined(PNG_tIME_SUPPORTED) +static png_byte +get_location(png_const_structrp png_ptr) + /* Return the correct location flag for a chunk. For a png_set_ + * called during read this is the current read location, for a + * png_set_ called during write it is the following write location + * (because the currents at the current location have already been written.) + * For a png_set_ called before read starts (none of the 'position' + * mode bits are set) the position is set to the start (PNG_HAVE_IHDR). For + * a png_set_chunk> called before write starts PNG_HAVE_PLTE|PNG_AFTER_IDAT + * are set because we don't know whether this is the main png_info or the one + * for use after the IDAT from png_write_end. + * + * The latter behavior gives compatibility with the old behavior of + * png_set_text; it only wrote text chunks after the PLTE or IDAT and it just + * wrote them once. + */ +{ + if ((png_ptr->mode & PNG_AFTER_IDAT) != 0U) + return PNG_AFTER_IDAT; + + else if ((png_ptr->mode & PNG_HAVE_PLTE) != 0U) + { + /* In a write operation PNG_HAVE_PLTE is set when the chunks before the + * IDAT are written (in png_write_info), so the location needs to be after + * IDAT. In a read the chunk was read after the PLTE but before the IDAT. + */ + if (png_ptr->read_struct) + return PNG_HAVE_PLTE; + + else /* write struct */ + return PNG_AFTER_IDAT; + } + + else if ((png_ptr->mode & PNG_HAVE_IHDR) != 0U) + { + /* For read this means the chunk is between the IHDR and any PLTE; there + * may be none but then there is no order to preserve. + * + * For write png_write_IHDR has been called and that means that the info + * before PLTE has been written, so the chunk goes after. + */ + if (png_ptr->read_struct) + return PNG_HAVE_IHDR; + + else /* write struct */ + return PNG_HAVE_PLTE; + } + + else if ((png_ptr->mode & PNG_HAVE_PNG_SIGNATURE) != 0U) + { + /* This should not happen on read, on write it means that the app has + * started writing so assume the text is meant to go before PLTE. + */ + return PNG_HAVE_IHDR; + } + + /* Either a png_set_ from the application during read before reading + * starts (so mode is 0) or the same for write. + */ + else + { + if (png_ptr->read_struct) + return PNG_HAVE_IHDR; + + else + return PNG_HAVE_PLTE|PNG_AFTER_IDAT; + } +} +#endif /* TEXT || tIME */ + #ifdef PNG_TEXT_SUPPORTED void PNGAPI png_set_text(png_structrp png_ptr, png_inforp info_ptr, @@ -802,6 +873,12 @@ png_set_text_2(png_structrp png_ptr, png_inforp info_ptr, } # endif + /* Record the location for posterity. On the read side this just says + * where the chunk was (approximately). On the write side it says how far + * through the write process libpng was before this API was called. + */ + textp->location = get_location(png_ptr); + if (text_ptr[i].text == NULL || text_ptr[i].text[0] == '\0') { text_length = 0; @@ -919,9 +996,10 @@ png_set_tIME(png_const_structrp png_ptr, png_inforp info_ptr, } info_ptr->mod_time = *mod_time; + info_ptr->time_location = get_location(png_ptr); info_ptr->valid |= PNG_INFO_tIME; } -#endif +#endif /* tIME */ #ifdef PNG_tRNS_SUPPORTED void PNGAPI diff --git a/pngstruct.h b/pngstruct.h index 5ab13be49..071a20fb7 100644 --- a/pngstruct.h +++ b/pngstruct.h @@ -400,9 +400,6 @@ struct png_struct_def unsigned int palette_index_check_disabled :1; /* defaults to 0, 'enabled' */ unsigned int palette_index_check_issued :1; /* error message output */ #endif /* CHECK_FOR_INVALID_INDEX */ -#ifdef PNG_WRITE_tIME_SUPPORTED - unsigned int wrote_tIME :1; /* Stop writing of duplicate tIME chunks */ -#endif /* WRITE_tIME */ #ifdef PNG_READ_tRNS_SUPPORTED png_color_16 trans_color; /* transparent color for non-paletted files */ #endif /* READ_tRNS */ diff --git a/pngwrite.c b/pngwrite.c index fd74abb35..5afb7d8f4 100644 --- a/pngwrite.c +++ b/pngwrite.c @@ -72,6 +72,75 @@ write_unknown_chunks(png_structrp png_ptr, png_const_inforp info_ptr, } #endif /* WRITE_UNKNOWN_CHUNKS */ +#ifdef PNG_WRITE_TEXT_SUPPORTED +static void +png_write_text(png_structrp png_ptr, png_const_inforp info_ptr, png_byte where) + /* Text chunk helper */ +{ + int i; + + /* Check to see if we need to write text chunks */ + for (i = 0; i < info_ptr->num_text; i++) + { + png_debug2(2, "Writing text chunk %d, type %d", i, + info_ptr->text[i].compression); + + /* Text chunks are written at info_ptr->text[i].location, skip the chunk + * if we are not writing at that location: + */ + if ((info_ptr->text[i].location & where) == 0U) + continue; + + switch (info_ptr->text[i].compression) + { + case PNG_ITXT_COMPRESSION_NONE: + case PNG_ITXT_COMPRESSION_zTXt: +# ifdef PNG_WRITE_iTXt_SUPPORTED + /* Write international chunk */ + png_write_iTXt(png_ptr, info_ptr->text[i].compression, + info_ptr->text[i].key, info_ptr->text[i].lang, + info_ptr->text[i].lang_key, info_ptr->text[i].text); +# else /* !WRITE_iTXT */ + png_app_error(png_ptr, "Unable to write international text"); +# endif /* !WRITE_iTXT */ + break; + + case PNG_TEXT_COMPRESSION_zTXt: +# ifdef PNG_WRITE_zTXt_SUPPORTED + /* Write compressed chunk */ + png_write_zTXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, info_ptr->text[i].compression); +# else /* !WRITE_zTXT */ + png_app_error(png_ptr, "Unable to write compressed text"); +# endif /* !WRITE_zTXT */ + break; + + case PNG_TEXT_COMPRESSION_NONE: +# ifdef PNG_WRITE_tEXt_SUPPORTED + /* Write uncompressed chunk */ + png_write_tEXt(png_ptr, info_ptr->text[i].key, + info_ptr->text[i].text, 0); +# else /* !WRITE_tEXt */ + /* Can't get here TODO: why not? */ + png_app_error(png_ptr, "Unable to write uncompressed text"); +# endif /* !WRITE_tEXt */ + break; + + default: + /* This is an internal error because the libpng checking should + * never manage to set any 'compression' except the above values. + */ + impossible("invalid text compression"); + } + + /* The chunk was written, record where. This allows the location to have + * multiple bits set, the first successful write freezes the location. + */ + info_ptr->text[i].location = where; + } +} +#endif /* WRITE_TEXT */ + /* Writes all the PNG information. This is the suggested way to use the * library. If you have a new chunk to add, make a function to write it, * and put it in the correct location here. If you want the chunk written @@ -138,6 +207,12 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) * the application continues writing the PNG. So check the 'invalid' * flag here too. */ +# ifdef PNG_WRITE_tIME_SUPPORTED + if ((info_ptr->valid & PNG_INFO_tIME) != 0 && + (info_ptr->time_location & PNG_HAVE_IHDR) != 0) + png_write_tIME(png_ptr, &(info_ptr->mod_time)); +# endif /* WRITE_tIME */ + # ifdef PNG_WRITE_gAMA_SUPPORTED /* enables GAMMA */ if ((info_ptr->colorspace.flags & PNG_COLORSPACE_INVALID) == 0 && (info_ptr->colorspace.flags & PNG_COLORSPACE_FROM_gAMA) != 0 && @@ -194,6 +269,11 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) png_write_cHRM_fixed(png_ptr, &info_ptr->colorspace.end_points_xy); # endif /* WRITE_cHRM */ +# ifdef PNG_WRITE_TEXT_SUPPORTED + if (info_ptr->num_text > 0) + png_write_text(png_ptr, info_ptr, PNG_HAVE_IHDR); +# endif /* WRITE_TEXT */ + # ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED /* The third arugment must encode only one bit, otherwise chunks will * be written twice because the test in write_unknown_chunks is @@ -208,71 +288,6 @@ png_write_info_before_PLTE(png_structrp png_ptr, png_const_inforp info_ptr) "png_write_info_before_PLTE called more than once"); } -#ifdef PNG_WRITE_TEXT_SUPPORTED -static void -png_write_text(png_structrp png_ptr, png_const_inforp info_ptr) - /* Text chunk helper */ -{ - int i; - - /* Check to see if we need to write text chunks */ - for (i = 0; i < info_ptr->num_text; i++) - { - png_debug2(2, "Writing text chunk %d, type %d", i, - info_ptr->text[i].compression); - - /* An internationalized chunk? */ - if (info_ptr->text[i].compression > 0) - { -# ifdef PNG_WRITE_iTXt_SUPPORTED - /* Write international chunk */ - png_write_iTXt(png_ptr, info_ptr->text[i].compression, - info_ptr->text[i].key, info_ptr->text[i].lang, - info_ptr->text[i].lang_key, info_ptr->text[i].text); -# else /* !WRITE_iTXT */ - png_app_error(png_ptr, "Unable to write international text"); -# endif /* !WRITE_iTXT */ - - /* Mark this chunk as written */ - if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; - else - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; - } - - /* If we want a compressed text chunk */ - else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_zTXt) - { -# ifdef PNG_WRITE_zTXt_SUPPORTED - /* Write compressed chunk */ - png_write_zTXt(png_ptr, info_ptr->text[i].key, - info_ptr->text[i].text, info_ptr->text[i].compression); -# else /* !WRITE_zTXT */ - png_app_error(png_ptr, "Unable to write compressed text"); -# endif /* !WRITE_zTXT */ - - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_zTXt_WR; - } - - else if (info_ptr->text[i].compression == PNG_TEXT_COMPRESSION_NONE) - { -# ifdef PNG_WRITE_tEXt_SUPPORTED - /* Write uncompressed chunk */ - png_write_tEXt(png_ptr, info_ptr->text[i].key, - info_ptr->text[i].text, 0); -# else /* !WRITE_tEXt */ - /* Can't get here TODO: why not? */ - png_app_error(png_ptr, "Unable to write uncompressed text"); -# endif /* !WRITE_tEXt */ - - /* Mark this chunk as written */ - info_ptr->text[i].compression = PNG_TEXT_COMPRESSION_NONE_WR; - } - } -} -#endif /* WRITE_TEXT */ - void PNGAPI png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) { @@ -303,6 +318,11 @@ png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) png_ptr->color_type == PNG_COLOR_TYPE_PALETTE) png_error(png_ptr, "Valid palette required for paletted images"); + /* But always set the mode flag because without this we don't know when to + * write the post-palette text or unknown chunks. + */ + png_ptr->mode |= PNG_HAVE_PLTE; + # ifdef PNG_WRITE_tRNS_SUPPORTED if ((info_ptr->valid & PNG_INFO_tRNS) !=0) { @@ -349,7 +369,8 @@ png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) # endif /* WRITE_pHYs */ # ifdef PNG_WRITE_tIME_SUPPORTED - if ((info_ptr->valid & PNG_INFO_tIME) != 0) + if ((info_ptr->valid & PNG_INFO_tIME) != 0 && + (info_ptr->time_location & PNG_HAVE_PLTE) != 0) png_write_tIME(png_ptr, &(info_ptr->mod_time)); # endif /* WRITE_tIME */ @@ -365,7 +386,7 @@ png_write_info(png_structrp png_ptr, png_const_inforp info_ptr) # ifdef PNG_WRITE_TEXT_SUPPORTED if (info_ptr->num_text > 0) - png_write_text(png_ptr, info_ptr); + png_write_text(png_ptr, info_ptr, PNG_HAVE_PLTE); # endif /* WRITE_TEXT */ # ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED @@ -438,13 +459,14 @@ png_write_end(png_structrp png_ptr, png_inforp info_ptr) { # ifdef PNG_WRITE_tIME_SUPPORTED /* Check to see if user has supplied a time chunk */ - if ((info_ptr->valid & PNG_INFO_tIME) != 0) + if ((info_ptr->valid & PNG_INFO_tIME) != 0 && + (info_ptr->time_location & PNG_AFTER_IDAT) != 0) png_write_tIME(png_ptr, &(info_ptr->mod_time)); # endif # ifdef PNG_WRITE_TEXT_SUPPORTED if (info_ptr->num_text > 0) - png_write_text(png_ptr, info_ptr); + png_write_text(png_ptr, info_ptr, PNG_AFTER_IDAT); # endif /* WRITE_TEXT */ # ifdef PNG_WRITE_UNKNOWN_CHUNKS_SUPPORTED diff --git a/pngwutil.c b/pngwutil.c index 916f02a13..8391e7e80 100644 --- a/pngwutil.c +++ b/pngwutil.c @@ -2218,12 +2218,6 @@ png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) return; } - /* Duplicate tIME chunks are invalid; this works round a bug in png_write_png - * where it would write the tIME chunk once before and once after the IDAT. - */ - if (png_ptr->wrote_tIME) - return; - png_save_uint_16(buf, mod_time->year); buf[2] = mod_time->month; buf[3] = mod_time->day; @@ -2232,7 +2226,6 @@ png_write_tIME(png_structrp png_ptr, png_const_timep mod_time) buf[6] = mod_time->second; png_write_complete_chunk(png_ptr, png_tIME, buf, (png_size_t)7); - png_ptr->wrote_tIME = 1U; } #endif