From e686669b9d4ea8b5c5cbc23b492189ff732c3b5d Mon Sep 17 00:00:00 2001 From: Michael R Sweet Date: Mon, 10 Feb 2025 21:25:59 -0500 Subject: [PATCH] Save work on libpng PNG loader (Issue #90) --- CHANGES.md | 3 +- configure | 77 ++++++++++++++++--- configure.ac | 30 +++++++- pdfio-content.c | 200 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 290 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 43dea92..a471a58 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,10 @@ Changes in PDFio ================ -v1.?.? - YYYY-MM-DD +v1.5.0 - YYYY-MM-DD ------------------- +- Added support for using libpng to embed PNG images in PDF output (Issue #90) - Updated the pdf2txt example to support font encodings. diff --git a/configure b/configure index dc87439..a9293a7 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.71 for pdfio 1.4.1. +# Generated by GNU Autoconf 2.71 for pdfio 1.5.0. # # Report bugs to . # @@ -610,8 +610,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='pdfio' PACKAGE_TARNAME='pdfio' -PACKAGE_VERSION='1.4.1' -PACKAGE_STRING='pdfio 1.4.1' +PACKAGE_VERSION='1.5.0' +PACKAGE_STRING='pdfio 1.5.0' PACKAGE_BUGREPORT='https://github.com/michaelrsweet/pdfio/issues' PACKAGE_URL='https://www.msweet.org/pdfio' @@ -653,6 +653,7 @@ WARNINGS CSFLAGS LIBPDFIO_STATIC LIBPDFIO +PKGCONFIG_LIBPNG PKGCONFIG_REQUIRES PKGCONFIG_LIBS_PRIVATE PKGCONFIG_LIBS @@ -729,6 +730,7 @@ SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking +enable_libpng enable_static enable_shared enable_debug @@ -1293,7 +1295,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures pdfio 1.4.1 to adapt to many kinds of systems. +\`configure' configures pdfio 1.5.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1359,7 +1361,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of pdfio 1.4.1:";; + short | recursive ) echo "Configuration of pdfio 1.5.0:";; esac cat <<\_ACEOF @@ -1367,6 +1369,8 @@ Optional Features: --disable-option-checking ignore unrecognized --enable/--with options --disable-FEATURE do not include FEATURE (same as --enable-FEATURE=no) --enable-FEATURE[=ARG] include FEATURE [ARG=yes] + --enable-libpng use libpng for pdfioFileCreateImageObjFromFile, + default=auto --disable-static do not install static library --enable-shared install shared library --enable-debug turn on debugging, default=no @@ -1456,7 +1460,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -pdfio configure 1.4.1 +pdfio configure 1.5.0 generated by GNU Autoconf 2.71 Copyright (C) 2021 Free Software Foundation, Inc. @@ -1612,7 +1616,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by pdfio $as_me 1.4.1, which was +It was created by pdfio $as_me 1.5.0, which was generated by GNU Autoconf 2.71. Invocation command line was $ $0$ac_configure_args_raw @@ -2368,9 +2372,9 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu -PDFIO_VERSION="1.4.1" -PDFIO_VERSION_MAJOR="`echo 1.4.1 | awk -F. '{print $1}'`" -PDFIO_VERSION_MINOR="`echo 1.4.1 | awk -F. '{printf("%d\n",$2);}'`" +PDFIO_VERSION="1.5.0" +PDFIO_VERSION_MAJOR="`echo 1.5.0 | awk -F. '{print $1}'`" +PDFIO_VERSION_MINOR="`echo 1.5.0 | awk -F. '{printf("%d\n",$2);}'`" @@ -4099,6 +4103,55 @@ fi fi +# Check whether --enable-libpng was given. +if test ${enable_libpng+y} +then : + enableval=$enable_libpng; +fi + + +PKGCONFIG_LIBPNG="" + + +if test "x$PKGCONFIG" != x -a x$enable_libpng != xno +then : + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for libpng-1.6.x" >&5 +printf %s "checking for libpng-1.6.x... " >&6; } + if $PKGCONFIG --exists libpng16 +then : + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; }; + +printf "%s\n" "#define HAVE_LIBPNG 1" >>confdefs.h + + CPPFLAGS="$($PKGCONFIG --cflags libpng16) -DHAVE_LIBPNG=1 $CPPFLAGS" + LIBS="$($PKGCONFIG --libs libpng16) -lz $LIBS" + PKGCONFIG_LIBPNG="libpng >= 1.6," + PKGCONFIG_LIBS_PRIVATE="$($PKGCONFIG --libs libpng16) $PKGCONFIG_LIBS_PRIVATE" + +else $as_nop + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; }; + if test x$enable_libpng = xyes +then : + + as_fn_error $? "libpng-dev 1.6 or later required for --enable-libpng." "$LINENO" 5 + +fi + +fi + +elif test x$enable_libpng = xyes +then : + + as_fn_error $? "libpng-dev 1.6 or later required for --enable-libpng." "$LINENO" 5 + +fi + + # Check whether --enable-static was given. if test ${enable_static+y} then : @@ -4935,7 +4988,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by pdfio $as_me 1.4.1, which was +This file was extended by pdfio $as_me 1.5.0, which was generated by GNU Autoconf 2.71. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -4991,7 +5044,7 @@ ac_cs_config_escaped=`printf "%s\n" "$ac_cs_config" | sed "s/^ //; s/'/'\\\\\\\\ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -pdfio config.status 1.4.1 +pdfio config.status 1.5.0 configured by $0, generated by GNU Autoconf 2.71, with options \\"\$ac_cs_config\\" diff --git a/configure.ac b/configure.ac index c0d2977..19caff4 100644 --- a/configure.ac +++ b/configure.ac @@ -1,7 +1,7 @@ dnl dnl Configuration script for PDFio dnl -dnl Copyright © 2023-2024 by Michael R Sweet +dnl Copyright © 2023-2025 by Michael R Sweet dnl dnl Licensed under Apache License v2.0. See the file "LICENSE" for more dnl information. @@ -21,7 +21,7 @@ AC_PREREQ([2.70]) dnl Package name and version... -AC_INIT([pdfio], [1.4.1], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio]) +AC_INIT([pdfio], [1.5.0], [https://github.com/michaelrsweet/pdfio/issues], [pdfio], [https://www.msweet.org/pdfio]) PDFIO_VERSION="AC_PACKAGE_VERSION" PDFIO_VERSION_MAJOR="`echo AC_PACKAGE_VERSION | awk -F. '{print $1}'`" @@ -121,6 +121,32 @@ AS_IF([$PKGCONFIG --exists zlib], [ ]) +dnl libpng... +AC_ARG_ENABLE([libpng], AS_HELP_STRING([--enable-libpng], [use libpng for pdfioFileCreateImageObjFromFile, default=auto])) + +PKGCONFIG_LIBPNG="" +AC_SUBST([PKGCONFIG_LIBPNG]) + +AS_IF([test "x$PKGCONFIG" != x -a x$enable_libpng != xno], [ + AC_MSG_CHECKING([for libpng-1.6.x]) + AS_IF([$PKGCONFIG --exists libpng16], [ + AC_MSG_RESULT([yes]); + AC_DEFINE([HAVE_LIBPNG], 1, [Have PNG library?]) + CPPFLAGS="$($PKGCONFIG --cflags libpng16) -DHAVE_LIBPNG=1 $CPPFLAGS" + LIBS="$($PKGCONFIG --libs libpng16) -lz $LIBS" + PKGCONFIG_LIBPNG="libpng >= 1.6," + PKGCONFIG_LIBS_PRIVATE="$($PKGCONFIG --libs libpng16) $PKGCONFIG_LIBS_PRIVATE" + ], [ + AC_MSG_RESULT([no]); + AS_IF([test x$enable_libpng = xyes], [ + AC_MSG_ERROR([libpng-dev 1.6 or later required for --enable-libpng.]) + ]) + ]) +], [test x$enable_libpng = xyes], [ + AC_MSG_ERROR([libpng-dev 1.6 or later required for --enable-libpng.]) +]) + + dnl Library target... AC_ARG_ENABLE([static], AS_HELP_STRING([--disable-static], [do not install static library])) AC_ARG_ENABLE([shared], AS_HELP_STRING([--enable-shared], [install shared library])) diff --git a/pdfio-content.c b/pdfio-content.c index eae9281..aff8567 100644 --- a/pdfio-content.c +++ b/pdfio-content.c @@ -1,7 +1,7 @@ // // Content helper functions for PDFio. // -// Copyright © 2021-2024 by Michael R Sweet. +// Copyright © 2021-2025 by Michael R Sweet. // // Licensed under Apache License v2.0. See the file "LICENSE" for more // information. @@ -11,6 +11,9 @@ #include "pdfio-content.h" #include "pdfio-base-font-widths.h" #include "ttf.h" +#ifdef HAVE_LIBPNG +# include +#endif // HAVE_LIBPNG #include #ifndef M_PI # define M_PI 3.14159265358979323846264338327950288 @@ -57,9 +60,15 @@ typedef pdfio_obj_t *(*_pdfio_image_func_t)(pdfio_dict_t *dict, int fd); static pdfio_obj_t *copy_jpeg(pdfio_dict_t *dict, int fd); static pdfio_obj_t *copy_png(pdfio_dict_t *dict, int fd); static bool create_cp1252(pdfio_file_t *pdf); -static pdfio_obj_t *create_image(pdfio_file_t *pdf, pdfio_dict_t *dict, const unsigned char *data, size_t width, size_t height, size_t num_colors, bool alpha); +static pdfio_obj_t *create_image(pdfio_dict_t *dict, const unsigned char *data, size_t width, size_t height, size_t num_colors, bool alpha); +#ifdef HAVE_LIBPNG +static void png_error_func(png_structp pp, png_const_charp message); +static void png_read_func(png_structp png_ptr, png_bytep data, size_t length); +#endif // HAVE_LIBPNG static void ttf_error_cb(pdfio_file_t *pdf, const char *message); +#ifndef HAVE_LIBPNG static unsigned update_png_crc(unsigned crc, const unsigned char *buffer, size_t length); +#endif // !HAVE_LIBPNG static bool write_string(pdfio_stream_t *st, bool unicode, const char *s, bool *newline); @@ -103,6 +112,7 @@ static int _pdfio_cp1252[] = // CP1252-specific character mapping 0x0178 }; +#ifndef HAVE_LIBPNG static unsigned png_crc_table[256] = // CRC-32 table for PNG files { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, @@ -149,6 +159,7 @@ static unsigned png_crc_table[256] = // CRC-32 table for PNG files 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; +#endif // !HAVE_LIBPNG // @@ -2016,7 +2027,7 @@ pdfioFileCreateImageObjFromData( pdfioDictSetName(dict, "ColorSpace", defcolors[num_colors]); // Create the image object(s)... - return (create_image(pdf, dict, data, width, height, num_colors, alpha)); + return (create_image(dict, data, width, height, num_colors, alpha)); } @@ -2469,8 +2480,145 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary int fd) // I - File descriptor { pdfio_obj_t *obj = NULL; // Object - pdfio_stream_t *st = NULL; // Stream for PNG data +#ifdef HAVE_LIBPNG + png_structp pp = NULL; // PNG read pointer + png_infop info = NULL; // PNG info pointers + png_bytep *rows = NULL; // PNG row pointers + unsigned char *pixels = NULL; // PNG image data + unsigned i, // Looping var + color_type, // PNG color mode + width, // Width in columns + height, // Height in lines + num_colors = 0, // Number of colors + bpp; // Bytes per pixel + bool alpha; // Alpha transparency? + + + // Allocate memory for PNG reader structures... + if ((pp = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)dict->pdf, png_error_func, png_error_func)) == NULL) + { + _pdfioFileError(dict->pdf, "Unable to allocate memory for PNG file: %s", strerror(errno)); + goto finish_png; + } + + if ((info = png_create_info_struct(pp)) == NULL) + { + _pdfioFileError(dict->pdf, "Unable to allocate memory for PNG file: %s", strerror(errno)); + goto finish_png; + } + + if (setjmp(png_jmpbuf(pp))) + { + // If we get here, PNG loading failed and any errors/warnings were logged + // via the corresponding callback functions... + goto finish_png; + } + + // Set max image size to 16384x16384... + png_set_user_limits(pp, 16384, 16384); + + // Read from the file descriptor... + png_set_read_fn(pp, &fd, png_read_func); + + // Don't throw errors with "invalid" sRGB profiles produced by Adobe apps. +# if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) + png_set_option(pp, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); +# endif // PNG_SKIP_sRGB_CHECK_PROFILE && PNG_SET_OPTION_SUPPORTED + + // Get the image dimensions and depth... + png_read_info(pp, info); + + width = png_get_image_width(pp, info); + height = png_get_image_height(pp, info); + color_type = png_get_color_type(pp, info); + + if (color_type & PNG_COLOR_MASK_COLOR) + num_colors = 3; + else + num_colors = 1; + + // Set decoding options... + if (png_get_valid(pp, info, PNG_INFO_tRNS)) + { + // Map transparency to alpha + png_set_tRNS_to_alpha(pp); + color_type |= PNG_COLOR_MASK_ALPHA; + } + + if (png_get_bit_depth(pp, info) > 8) + { + // Strip the bottom bits of 16-bit values + png_set_strip_16(pp); + } + else if (png_get_bit_depth(pp, info) < 8) + { + // Expand 1, 2, and 4-bit values to 8 bits + if (num_colors == 1) + png_set_expand_gray_1_2_4_to_8(pp); + else + png_set_packing(pp); + } + +#if 1 + if (color_type & PNG_COLOR_MASK_PALETTE) + { + // Convert indexed images to RGB... + png_set_palette_to_rgb(pp); + num_colors = 3; + } +#endif // 0 + + alpha = (color_type & PNG_COLOR_MASK_ALPHA) != 0; + bpp = num_colors + (alpha ? 1 : 0); + + // Allocate memory for the image... + if ((pixels = (unsigned char *)calloc(height, width * bpp)) == NULL) + { + _pdfioFileError(dict->pdf, "Unable to allocate memory for PNG image: %s", strerror(errno)); + goto finish_png; + } + + if ((rows = (png_bytep *)calloc((size_t)height, sizeof(png_bytep))) == NULL) + { + _pdfioFileError(dict->pdf, "Unable to allocate memory for PNG image: %s", strerror(errno)); + goto finish_png; + } + + for (i = 0; i < height; i ++) + rows[i] = pixels + i * width * bpp; + + // Read the image... + for (i = png_set_interlace_handling(pp); i > 0; i --) + png_read_rows(pp, rows, NULL, (png_uint_32)height); + + // Grab any color space/palette information... + pdfioDictSetArray(dict, "ColorSpace", pdfioArrayCreateColorFromStandard(dict->pdf, num_colors, PDFIO_CS_SRGB)); + + // Create the image object... + obj = create_image(dict, pixels, width, height, num_colors, alpha); + + finish_png: + + if (pp && info) + { + png_read_end(pp, info); + png_destroy_read_struct(&pp, &info, NULL); + + pp = NULL; + info = NULL; + } + + free(pixels); + pixels = NULL; + + free(rows); + rows = NULL; + + return (obj); + +#else pdfio_dict_t *decode = NULL; // Parameters for PNG decode + pdfio_stream_t *st = NULL; // Stream for PNG data ssize_t bytes; // Bytes read unsigned char buffer[16384]; // Read buffer unsigned i, // Looping var @@ -2850,6 +2998,7 @@ copy_png(pdfio_dict_t *dict, // I - Dictionary } return (NULL); +#endif // HAVE_LIBPNG } @@ -3135,7 +3284,6 @@ create_cp1252(pdfio_file_t *pdf) // I - PDF file static pdfio_obj_t * // O - PDF object or `NULL` on error create_image( - pdfio_file_t *pdf, // I - PDF file pdfio_dict_t *dict, // I - Image dictionary const unsigned char *data, // I - Image data size_t width, // I - Width in columns @@ -3143,6 +3291,8 @@ create_image( size_t num_colors, // I - Number of colors bool alpha) // I - `true` if there is transparency { + pdfio_file_t *pdf = dict->pdf; + // PDF file pdfio_dict_t *mask_dict, // Mask image dictionary *decode; // DecodeParms dictionary pdfio_obj_t *obj, // Image object @@ -3293,6 +3443,44 @@ create_image( } +#ifdef HAVE_LIBPNG +// +// 'png_error_func()' - PNG error message function. +// + +static void +png_error_func( + png_structp pp, // I - PNG pointer + png_const_charp message) // I - Error message +{ + pdfio_file_t *pdf = (pdfio_file_t *)png_get_error_ptr(pp); + // PDF file + + + _pdfioFileError(pdf, "Unable to create image object from PNG file: %s", message); +} + + +// +// 'png_read_func()' - Read from a PNG file. +// + +static void +png_read_func(png_structp pp, // I - PNG pointer + png_bytep data, // I - Read buffer + size_t length) // I - Number of bytes to read +{ + int *fd = (int *)png_get_io_ptr(pp); + // Pointer to file descriptor + ssize_t bytes; // Bytes read + + + if ((bytes = read(*fd, data, length)) < (ssize_t)length) + png_error(pp, "Unable to read from PNG file."); +} +#endif // HAVE_LIBPNG + + // // 'ttf_error_cb()' - Relay a message from the TTF functions. // @@ -3305,6 +3493,7 @@ ttf_error_cb(pdfio_file_t *pdf, // I - PDF file } +#ifndef HAVE_LIBPNG // // 'update_png_crc()' - Update the CRC-32 value for a PNG chunk. // @@ -3324,6 +3513,7 @@ update_png_crc( return (crc); } +#endif // !HAVE_LIBPNG //