tIME and text position handling

The handling of tIME and text chunks on read now records the location of the
chunks relative to PLTE and IDAT.  Behavior on write is unchanged except that if
the position was recorded on read it will be re-used.

This involves an ABI change to the png_text_struct; a one byte location field is
added (with the same meaning as the one used to record unknown chunk location.)
Because this field is only used on read there is no API change unless a png_info
from a libpng read is passed to a subsequent libpng write (this did not work
very well before 1.7; the tIME chunk could get duplicated.)

png_set_text ignores the new field, resetting it to the current position in the
read or write stream.  On write the position is set to the next location to be
written unless the write has not started (the position is before the signature)
in which case the location is set to PNG_HAVE_PLTE|PNG_AFTER_IDAT.  When the
chunk is written the position is set to the actual write location (effectively
the position is frozen.)

Signed-off-by: John Bowler <jbowler@acm.org>
This commit is contained in:
John Bowler 2015-12-29 15:30:31 -08:00
parent c90572ee77
commit 4253c4d759
7 changed files with 192 additions and 84 deletions

19
png.h
View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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_<chunk>
* called during read this is the current read location, for a
* png_set_<chunk> called during write it is the following write location
* (because the currents at the current location have already been written.)
* For a png_set_<chunk> 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_<chunk> 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

View File

@ -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 */

View File

@ -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

View File

@ -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