diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a927fa9524..e63416de1c 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,10 +37,10 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - + - name: Install dependencies run: | - sudo eatmydata apt-get -y install libexpat1-dev zlib1g-dev libbrotli-dev libinih-dev + sudo eatmydata apt-get -y install libfmt-dev libexpat1-dev zlib1g-dev libbrotli-dev libinih-dev # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/on_PR_mac_matrix.yml b/.github/workflows/on_PR_mac_matrix.yml index ef5d2286d7..ab1318af43 100644 --- a/.github/workflows/on_PR_mac_matrix.yml +++ b/.github/workflows/on_PR_mac_matrix.yml @@ -25,7 +25,7 @@ jobs: - name: Install dependencies run: | - brew install ninja inih googletest + brew install ninja fmt inih googletest - name: Build run: | diff --git a/.github/workflows/on_PR_mac_special_builds.yml b/.github/workflows/on_PR_mac_special_builds.yml index 95ec0b1fe6..8cd7280436 100644 --- a/.github/workflows/on_PR_mac_special_builds.yml +++ b/.github/workflows/on_PR_mac_special_builds.yml @@ -20,7 +20,7 @@ jobs: - name: Install dependencies run: | - brew install ninja inih googletest + brew install ninja fmt inih googletest - name: Build run: | diff --git a/.github/workflows/on_PR_meson.yaml b/.github/workflows/on_PR_meson.yaml index 7aff389112..89b6590ee2 100644 --- a/.github/workflows/on_PR_meson.yaml +++ b/.github/workflows/on_PR_meson.yaml @@ -115,6 +115,7 @@ jobs: cc:p cmake:p curl:p + fmt:p gtest:p libinih:p meson:p @@ -126,6 +127,27 @@ jobs: meson setup "${{github.workspace}}/build" -Dauto_features=${{matrix.deps}} -Dwarning_level=3 -Dcpp_std=c++20 meson compile -C "${{github.workspace}}/build" --verbose meson test -C "${{github.workspace}}/build" --verbose + Cygwin: + runs-on: windows-latest + defaults: + run: + shell: msys2 {0} + steps: + - uses: actions/checkout@v4 + - uses: msys2/setup-msys2@v2 + with: + msystem: 'MSYS' + install: >- + cmake + gcc + meson + ninja + pkgconf + - name: Compile and Test + run: | + meson setup build -Dwarning_level=3 -Dcpp_std=gnu++20 + meson compile -C build --verbose + meson test -C build --verbose MacOS: runs-on: macos-latest name: macOS-deps=${{matrix.deps}} @@ -141,7 +163,7 @@ jobs: - name: Compile and Test run: | - meson setup "${{github.workspace}}/build" -Dauto_features=${{matrix.deps}} -Dwarning_level=3 -Dnls=disabled -Db_sanitize=address,undefined + meson setup "${{github.workspace}}/build" -Dauto_features=${{matrix.deps}} -Dwarning_level=3 -Dcpp_std=c++20 -Dnls=disabled -Db_sanitize=address,undefined meson compile -C "${{github.workspace}}/build" --verbose meson test -C "${{github.workspace}}/build" --verbose FreeBSD: @@ -151,7 +173,7 @@ jobs: - uses: vmactions/freebsd-vm@v1 with: prepare: | - pkg install -y cmake curl ninja meson gettext pkgconf googletest expat inih brotli + pkg install -y cmake curl ninja meson gettext pkgconf googletest expat inih brotli libfmt run: | meson setup "${{github.workspace}}/build" -Dwarning_level=3 -Dcpp_std=c++20 meson compile -C "${{github.workspace}}/build" --verbose diff --git a/.github/workflows/on_PR_windows_matrix.yml b/.github/workflows/on_PR_windows_matrix.yml index bf9c6d447c..b738ed1c2a 100644 --- a/.github/workflows/on_PR_windows_matrix.yml +++ b/.github/workflows/on_PR_windows_matrix.yml @@ -115,6 +115,7 @@ jobs: libiconv:p libinih:p zlib:p + fmt:p - name: Build run: | @@ -176,6 +177,7 @@ jobs: cmake --preset base_windows \ -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ -DBUILD_SHARED_LIBS=${{matrix.shared_libraries}} \ + -DCMAKE_CXX_STANDARD=20 \ -DCONAN_AUTO_INSTALL=OFF \ -DEXIV2_BUILD_SAMPLES=OFF \ -DEXIV2_BUILD_UNIT_TESTS=OFF \ diff --git a/.github/workflows/on_push_BasicWinLinMac.yml b/.github/workflows/on_push_BasicWinLinMac.yml index 2a70780d3c..6398b68423 100644 --- a/.github/workflows/on_push_BasicWinLinMac.yml +++ b/.github/workflows/on_push_BasicWinLinMac.yml @@ -94,7 +94,7 @@ jobs: - name: Install dependencies run: | - brew install ninja inih googletest + brew install ninja fmt inih googletest - name: Build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 01a704b112..727318056f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,11 @@ endif() include_directories(${CMAKE_BINARY_DIR}) # Make the exv_conf.h file visible for the full project +check_cxx_symbol_exists(__cpp_lib_format "format" HAVE_STD_FORMAT) +if(NOT HAVE_STD_FORMAT) + find_package(fmt REQUIRED) +endif() + if(EXIV2_ENABLE_XMP) add_subdirectory(xmpsdk) endif() diff --git a/ci/install_dependencies.sh b/ci/install_dependencies.sh index 3cca9e1482..1e35fd2e8f 100755 --- a/ci/install_dependencies.sh +++ b/ci/install_dependencies.sh @@ -41,46 +41,46 @@ distro_id=$(grep '^ID=' /etc/os-release|awk -F = '{print $2}'|sed 's/\"//g') case "$distro_id" in 'fedora') - dnf -y --refresh install gcc-c++ clang cmake ninja-build expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel gmock-devel glibc-langpack-en inih-devel + dnf -y --refresh install gcc-c++ clang cmake ninja-build expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel gmock-devel glibc-langpack-en inih-devel fmt-devel ;; 'debian') apt-get update - apt-get install -y cmake ninja-build g++ clang libexpat1-dev zlib1g-dev libbrotli-dev libssh-dev libcurl4-openssl-dev libgmock-dev libxml2-utils libinih-dev + apt-get install -y cmake ninja-build g++ clang libexpat1-dev zlib1g-dev libbrotli-dev libssh-dev libcurl4-openssl-dev libgmock-dev libxml2-utils libinih-dev libfmt-dev # debian_build_gtest ;; 'arch') pacman --noconfirm -Syu - pacman --noconfirm --needed -S gcc clang cmake ninja expat zlib brotli libssh curl gtest libinih + pacman --noconfirm --needed -S gcc clang cmake ninja expat zlib brotli libssh curl gtest libinih fmt ;; 'ubuntu') apt-get update - apt-get install -y cmake ninja-build g++ clang libexpat1-dev zlib1g-dev libbrotli-dev libssh-dev libcurl4-openssl-dev libgmock-dev libxml2-utils libinih-dev + apt-get install -y cmake ninja-build g++ clang libexpat1-dev zlib1g-dev libbrotli-dev libssh-dev libcurl4-openssl-dev libgmock-dev libxml2-utils libinih-dev libfmt-dev # debian_build_gtest ;; 'alpine') apk update - apk add gcc g++ clang cmake samurai expat-dev zlib-dev brotli-dev libssh-dev curl-dev gtest gtest-dev gmock libintl gettext-dev libxml2-utils inih-dev inih-inireader-dev + apk add gcc g++ clang cmake samurai expat-dev zlib-dev brotli-dev libssh-dev curl-dev gtest gtest-dev gmock libintl gettext-dev libxml2-utils inih-dev inih-inireader-dev fmt-dev ;; 'rhel') dnf clean all - dnf -y install gcc-c++ clang cmake ninja-build expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel inih-devel + dnf -y install gcc-c++ clang cmake ninja-build expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel inih-devel fmt-devel ;; 'centos') dnf clean all - dnf -y install gcc-c++ clang cmake expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel git + dnf -y install gcc-c++ clang cmake expat-devel zlib-devel brotli-devel libssh-devel libcurl-devel git fmt-devel dnf -y --enablerepo=crb install ninja-build meson centos_build_inih ;; 'opensuse-tumbleweed') zypper --non-interactive refresh - zypper --non-interactive install gcc-c++ clang cmake ninja libexpat-devel zlib-devel libbrotli-devel libssh-devel libcurl-devel gmock libxml2-tools libinih-devel + zypper --non-interactive install gcc-c++ clang cmake ninja libexpat-devel zlib-devel libbrotli-devel libssh-devel libcurl-devel gmock libxml2-tools libinih-devel libfmt-devel ;; *) echo "Sorry, no predefined dependencies for your distribution $distro_id exist yet" diff --git a/conanfile.py b/conanfile.py index b859944a89..332b1d00b3 100644 --- a/conanfile.py +++ b/conanfile.py @@ -26,6 +26,8 @@ def requirements(self): self.requires('inih/55') + self.requires('fmt/10.1.1') + if self.options.webready: self.requires('libcurl/7.85.0') diff --git a/include/exiv2/asfvideo.hpp b/include/exiv2/asfvideo.hpp index 1b1f3fd17e..67f4619f6d 100644 --- a/include/exiv2/asfvideo.hpp +++ b/include/exiv2/asfvideo.hpp @@ -82,7 +82,7 @@ class EXIV2API AsfVideo : public Image { // Constructor to create a GUID object from a byte array explicit GUIDTag(const uint8_t* bytes); - std::string to_string(); + std::string to_string() const; bool operator<(const GUIDTag& other) const; }; diff --git a/meson.build b/meson.build index faceff630c..96a2588c02 100644 --- a/meson.build +++ b/meson.build @@ -6,7 +6,7 @@ project( default_options: ['warning_level=0', 'cpp_std=c++17'], ) -cargs = [] +cargs = ['-D_GNU_SOURCE'] cpp = meson.get_compiler('cpp') if host_machine.system() == 'windows' and get_option('default_library') != 'static' cargs += '-DEXIV2API=__declspec(dllexport)' @@ -44,7 +44,7 @@ cdata.set('EXV_PACKAGE_VERSION', '@0@.@1@'.format(meson.project_version(), cdata cdata.set('EXV_PACKAGE_STRING', '@0@ @1@'.format(meson.project_name(), cdata.get('PROJECT_VERSION'))) cdata.set('EXV_HAVE_STRERROR_R', cpp.has_function('strerror_r')) -cdata.set('EXV_STRERROR_R_CHAR_P', not cpp.compiles('#include \nint strerror_r(int,char*,size_t);int main(){}')) +cdata.set('EXV_STRERROR_R_CHAR_P', not cpp.compiles('#define _GNU_SOURCE\n#include \nint strerror_r(int,char*,size_t);int main(){}')) cdata.set('EXV_ENABLE_BMFF', get_option('bmff')) cdata.set('EXV_HAVE_LENSDATA', get_option('lensdata')) @@ -54,6 +54,10 @@ deps = [] deps += cpp.find_library('ws2_32', required: host_machine.system() == 'windows') deps += cpp.find_library('procstat', required: host_machine.system() == 'freebsd') +if not cpp.has_header_symbol('format', '__cpp_lib_format') + deps += dependency('fmt') +endif + if cpp.get_argument_syntax() == 'gcc' and cpp.version().version_compare('<9') if host_machine.system() == 'linux' and cpp.get_define('_LIBCPP_VERSION', prefix: '#include ') == '' deps += cpp.find_library('stdc++fs') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 93e362a2be..ad221cfdcb 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -251,6 +251,12 @@ else() target_link_libraries(exiv2lib PRIVATE psapi ws2_32 shell32) endif() +if(NOT HAVE_STD_FORMAT) + target_link_libraries(exiv2lib PRIVATE fmt::fmt) + target_link_libraries(exiv2lib_int PRIVATE fmt::fmt) + list(APPEND requires_private_list "fmt") +endif() + if(EXIV2_ENABLE_PNG) target_link_libraries(exiv2lib PRIVATE ZLIB::ZLIB) target_include_directories(exiv2lib_int PRIVATE ${ZLIB_INCLUDE_DIR}) diff --git a/src/asfvideo.cpp b/src/asfvideo.cpp index 2674cfc9ad..35f2294aa7 100644 --- a/src/asfvideo.cpp +++ b/src/asfvideo.cpp @@ -12,6 +12,7 @@ #include "error.hpp" #include "futils.hpp" #include "helper_functions.hpp" +#include "image_int.hpp" #include "utils.hpp" // ***************************************************************************** // class member definitions @@ -58,24 +59,12 @@ AsfVideo::GUIDTag::GUIDTag(const uint8_t* bytes) { } } -std::string AsfVideo::GUIDTag::to_string() { - // Convert each field of the GUID structure to a string - std::stringstream ss; - ss << std::hex << std::setw(8) << std::setfill('0') << data1_ << "-"; - ss << std::hex << std::setw(4) << std::setfill('0') << data2_ << "-"; - ss << std::hex << std::setw(4) << std::setfill('0') << data3_ << "-"; - - for (size_t i = 0; i < 8; i++) { - if (i == 2) { - ss << "-"; - } - ss << std::hex << std::setw(2) << std::setfill('0') << static_cast(data4_[i]); - } - +std::string AsfVideo::GUIDTag::to_string() const { // Concatenate all strings into a single string // Convert the string to uppercase // Example of output 399595EC-8667-4E2D-8FDB-98814CE76C1E - return Internal::upper(ss.str()); + return stringFormat("{:08X}-{:04X}-{:04X}-{:02X}{:02X}-{:02X}{:02X}{:02X}{:02X}{:02X}{:02X}", data1_, data2_, data3_, + data4_[0], data4_[1], data4_[2], data4_[3], data4_[4], data4_[5], data4_[6], data4_[7]); } bool AsfVideo::GUIDTag::operator<(const GUIDTag& other) const { diff --git a/src/basicio.cpp b/src/basicio.cpp index 0a905078af..686ecfeca4 100644 --- a/src/basicio.cpp +++ b/src/basicio.cpp @@ -930,9 +930,7 @@ std::string XPathIo::writeDataToFile(const std::string& orgPath) { // generating the name for temp file. std::time_t timestamp = std::time(nullptr); - std::stringstream ss; - ss << timestamp << XPathIo::TEMP_FILE_EXT; - std::string path = ss.str(); + auto path = stringFormat("{}{}", timestamp, XPathIo::TEMP_FILE_EXT); if (prot == pStdin) { if (isatty(fileno(stdin))) @@ -1437,9 +1435,7 @@ void HttpIo::HttpImpl::getDataByRange(size_t lowBlock, size_t highBlock, std::st request["verb"] = "GET"; std::string errors; if (lowBlock != std::numeric_limits::max() && highBlock != std::numeric_limits::max()) { - std::stringstream ss; - ss << "Range: bytes=" << lowBlock * blockSize_ << "-" << (((highBlock + 1) * blockSize_) - 1) << "\r\n"; - request["header"] = ss.str(); + request["header"] = stringFormat("Range: bytes={}-{}", lowBlock * blockSize_, (highBlock + 1) * (blockSize_ - 1)); } int serverCode = http(request, responseDic, errors); @@ -1480,15 +1476,10 @@ void HttpIo::HttpImpl::writeRemote(const byte* data, size_t size, size_t from, s // url encode const std::string urlencodeData = urlencode(encodeData.data()); - std::stringstream ss; - ss << "path=" << hostInfo_.Path << "&" - << "from=" << from << "&" - << "to=" << to << "&" - << "data=" << urlencodeData; - std::string postData = ss.str(); + auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo_.Path, from, to, urlencodeData); // create the header - ss.str(""); + std::stringstream ss; ss << "Content-Length: " << postData.length() << "\n" << "Content-Type: application/x-www-form-urlencoded\n" << "\n" @@ -1616,9 +1607,7 @@ void CurlIo::CurlImpl::getDataByRange(size_t lowBlock, size_t highBlock, std::st // curl_easy_setopt(curl_, CURLOPT_VERBOSE, 1); // debugging mode if (lowBlock != std::numeric_limits::max() && highBlock != std::numeric_limits::max()) { - std::stringstream ss; - ss << lowBlock * blockSize_ << "-" << (((highBlock + 1) * blockSize_) - 1); - std::string range = ss.str(); + auto range = stringFormat("{}-{}", lowBlock * blockSize_, (highBlock + 1) * (blockSize_ - 1)); curl_easy_setopt(curl_, CURLOPT_RANGE, range.c_str()); } @@ -1662,12 +1651,7 @@ void CurlIo::CurlImpl::writeRemote(const byte* data, size_t size, size_t from, s base64encode(data, size, encodeData.data(), encodeLength); // url encode const std::string urlencodeData = urlencode(encodeData.data()); - std::stringstream ss; - ss << "path=" << hostInfo.Path << "&" - << "from=" << from << "&" - << "to=" << to << "&" - << "data=" << urlencodeData; - std::string postData = ss.str(); + auto postData = stringFormat("path={}&from={}&to={}&data={}", hostInfo.Path, from, to, urlencodeData); curl_easy_setopt(curl_, CURLOPT_POSTFIELDS, postData.c_str()); // Perform the request, res will get the return code. diff --git a/src/bmffimage.cpp b/src/bmffimage.cpp index e17fd6cb54..390c3105a0 100644 --- a/src/bmffimage.cpp +++ b/src/bmffimage.cpp @@ -80,7 +80,7 @@ bool enableBMFF(bool) { } std::string Iloc::toString() const { - return Internal::stringFormat("ID = %u from,length = %u,%u", ID_, start_, length_); + return stringFormat("ID = {} from,length = {},{}", ID_, start_, length_); } BmffImage::BmffImage(BasicIo::UniquePtr io, bool /* create */, size_t max_box_depth) : @@ -276,7 +276,7 @@ uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintS if (bTrace) { bLF = true; out << Internal::indent(depth) << "Exiv2::BmffImage::boxHandler: " << toAscii(box_type) - << Internal::stringFormat(" %8zd->%" PRIu64 " ", address, box_length); + << stringFormat(" {:8}->{} ", address, box_length); } if (box_length == 1) { @@ -373,7 +373,7 @@ uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintS id = " *** XMP ***"; } if (bTrace) { - out << Internal::stringFormat("ID = %3d ", ID) << name << " " << id; + out << stringFormat("ID = {:3} {} {}", ID, name, id); } } break; @@ -451,8 +451,7 @@ uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintS uint32_t ldata = data.read_uint32(skip + step - 4, endian_); if (bTrace) { out << Internal::indent(depth) - << Internal::stringFormat("%8zd | %8zd | ID | %4u | %6u,%6u", address + skip, step, ID, offset, ldata) - << '\n'; + << stringFormat("{:8} | {:8} | ID | {:4} | {:6},{:6}\n", address + skip, step, ID, offset, ldata); } // save data for post-processing in meta box if (offset && ldata && ID != unknownID_) { @@ -470,7 +469,7 @@ uint64_t BmffImage::boxHandler(std::ostream& out /* = std::cout*/, Exiv2::PrintS uint32_t height = data.read_uint32(skip, endian_); skip += 4; if (bTrace) { - out << "pixelWidth_, pixelHeight_ = " << Internal::stringFormat("%d, %d", width, height); + out << stringFormat("pixelWidth_, pixelHeight_ = {}, {}", width, height); } // HEIC files can have multiple ispe records // Store largest width/height @@ -685,8 +684,8 @@ void BmffImage::parseCr3Preview(const DataBuf& data, std::ostream& out, bool bTr return "application/octet-stream"; }(); if (bTrace) { - out << Internal::stringFormat("width,height,size = %zu,%zu,%zu", nativePreview.width_, nativePreview.height_, - nativePreview.size_); + out << stringFormat("width,height,size = {},{},{}", nativePreview.width_, nativePreview.height_, + nativePreview.size_); } nativePreviews_.push_back(std::move(nativePreview)); } diff --git a/src/futils.cpp b/src/futils.cpp index 403df1fb28..b907c4583d 100644 --- a/src/futils.cpp +++ b/src/futils.cpp @@ -386,7 +386,7 @@ std::string getProcessPath() { return "unknown"; // pathbuf not big enough auto path = fs::path(pathbuf); #elif defined(__sun__) - auto path = fs::read_symlink(Internal::stringFormat("/proc/%d/path/a.out", getpid())); + auto path = fs::read_symlink(stringFormat("/proc/{}/path/a.out", getpid())); #elif defined(__unix__) auto path = fs::read_symlink("/proc/self/exe"); #endif diff --git a/src/image.cpp b/src/image.cpp index 92dc50707e..53f5fb23ca 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -352,8 +352,7 @@ void Image::printIFDStructure(BasicIo& io, std::ostream& out, Exiv2::PrintStruct throw Error(ErrorCode::kerTiffDirectoryTooLarge); if (bFirst && bPrint) { - out << Internal::indent(depth) << Internal::stringFormat("STRUCTURE OF TIFF FILE (%c%c): ", c, c) << io.path() - << '\n'; + out << Internal::indent(depth) << stringFormat("STRUCTURE OF TIFF FILE ({}{}): {}\n", c, c, io.path()); } // Read the dictionary @@ -437,11 +436,11 @@ void Image::printIFDStructure(BasicIo& io, std::ostream& out, Exiv2::PrintStruct if (bPrint) { const size_t address = start + 2 + (i * 12); - const std::string offsetString = bOffsetIsPointer ? Internal::stringFormat("%10u", offset) : ""; + const std::string offsetString = bOffsetIsPointer ? stringFormat("{:9}", offset) : ""; out << Internal::indent(depth) - << Internal::stringFormat("%8zu | %#06x %-28s |%10s |%9u |%10s | ", address, tag, tagName(tag).c_str(), - typeName(type), count, offsetString.c_str()); + << stringFormat("{:8} | {:#06x} {:<28} | {:>9} | {:>8} | {:9} | ", address, tag, tagName(tag).c_str(), + typeName(type), count, offsetString); if (isShortType(type)) { for (size_t k = 0; k < kount; k++) { out << sp << byteSwap2(buf, k * size, bSwap); diff --git a/src/image_int.cpp b/src/image_int.cpp index bd3b3773cd..a253a76a9a 100644 --- a/src/image_int.cpp +++ b/src/image_int.cpp @@ -9,32 +9,6 @@ #include namespace Exiv2::Internal { -std::string stringFormat(const char* format, ...) { - std::string result; - std::vector buffer; - size_t need = std::strlen(format) * 8; // initial guess - int rc = -1; - - // vsnprintf writes at most size (2nd parameter) bytes (including \0) - // returns the number of bytes required for the formatted string excluding \0 - // the following loop goes through: - // one iteration (if 'need' was large enough for the for formatted string) - // or two iterations (after the first call to vsnprintf we know the required length) - do { - buffer.resize(need + 1); - va_list args; // variable arg list - va_start(args, format); // args start after format - rc = vsnprintf(buffer.data(), buffer.size(), format, args); - va_end(args); // free the args - if (rc > 0) - need = static_cast(rc); - } while (buffer.size() <= need); - - if (rc > 0) - result = std::string(buffer.data(), need); - return result; -} - [[nodiscard]] std::string indent(size_t i) { return std::string(2 * i, ' '); } diff --git a/src/image_int.hpp b/src/image_int.hpp index 75e6428fb6..8dbe43d381 100644 --- a/src/image_int.hpp +++ b/src/image_int.hpp @@ -12,12 +12,14 @@ #include // for ostream, basic_ostream::put #include -#if defined(__MINGW32__) -#define ATTRIBUTE_FORMAT_PRINTF __attribute__((format(__MINGW_PRINTF_FORMAT, 1, 2))) -#elif defined(__GNUC__) -#define ATTRIBUTE_FORMAT_PRINTF __attribute__((format(printf, 1, 2))) +#if __has_include() +#include +#endif +#ifndef __cpp_lib_format +#include +#define stringFormat fmt::format #else -#define ATTRIBUTE_FORMAT_PRINTF +#define stringFormat std::format #endif // ***************************************************************************** @@ -26,11 +28,6 @@ namespace Exiv2::Internal { // ***************************************************************************** // class definitions -/*! - @brief format a string in the pattern of \em sprintf \em . - */ -std::string stringFormat(const char* format, ...) ATTRIBUTE_FORMAT_PRINTF; - /*! * @brief Helper struct for binary data output via @ref binaryToString. * diff --git a/src/jp2image.cpp b/src/jp2image.cpp index 35a9300123..6ddc183ca5 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -413,8 +413,7 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, si Internal::enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); if (bPrint) { - out << Internal::stringFormat("%8zd | %8zd | ", position - boxHSize, static_cast(box.length)) - << toAscii(box.type) << " | "; + out << stringFormat("{:8} | {:8} | {} | ", position - boxHSize, box.length, toAscii(box.type)); bLF = true; if (box.type == kJp2BoxTypeClose) lf(out, bLF); @@ -456,8 +455,8 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, si DataBuf data(subBox.length - boxHSize); io_->read(data.data(), data.size()); if (bPrint) { - out << Internal::stringFormat("%8zu | %8u | sub:", address, subBox.length) << toAscii(subBox.type) - << " | " << Internal::binaryToString(makeSlice(data, 0, std::min(30, data.size()))); + out << stringFormat("{:8} | {:8} | sub:{} | ", address, subBox.length, toAscii(subBox.type)) + << Internal::binaryToString(makeSlice(data, 0, std::min(30, data.size()))); bLF = true; } diff --git a/src/jpgimage.cpp b/src/jpgimage.cpp index 114a3aeaca..28ac89dfc8 100644 --- a/src/jpgimage.cpp +++ b/src/jpgimage.cpp @@ -329,7 +329,7 @@ void JpegBase::readMetadata() { #define REPORT_MARKER \ if ((option == kpsBasic || option == kpsRecursive)) \ - out << Internal::stringFormat("%8zd | 0xff%02x %-5s", io_->tell() - 2, marker, nm[marker].c_str()) + out << stringFormat("{:8} | 0xff{:02x} {:<5}", io_->tell() - 2, marker, nm[marker].c_str()) void JpegBase::printStructure(std::ostream& out, PrintStructureOption option, size_t depth) { if (io_->open() != 0) @@ -395,7 +395,7 @@ void JpegBase::printStructure(std::ostream& out, PrintStructureOption option, si } if (bPrint && markerHasLength(marker)) - out << Internal::stringFormat(" | %7d ", size); + out << stringFormat(" | {:7} ", size); // print signature for APPn if (marker >= app0_ && marker <= (app0_ | 0x0F)) { @@ -470,7 +470,7 @@ void JpegBase::printStructure(std::ostream& out, PrintStructureOption option, si enforce(size >= 16, "Buffer too small to extract chunk information."); const int chunk = buf.read_uint8(2 + 12); const int chunks = buf.read_uint8(2 + 13); - out << Internal::stringFormat(" chunk %d/%d", chunk, chunks); + out << stringFormat(" chunk {}/{}", chunk, chunks); } } diff --git a/src/pngimage.cpp b/src/pngimage.cpp index cf936b0739..542ffa9b66 100644 --- a/src/pngimage.cpp +++ b/src/pngimage.cpp @@ -256,10 +256,8 @@ void PngImage::printStructure(std::ostream& out, PrintStructureOption option, si enforce(bufRead == 4, ErrorCode::kerFailedToReadImageData); io_->seek(restore, BasicIo::beg); // restore file pointer - out << Internal::stringFormat("%8d | %-5s |%8d | ", static_cast(address), chType, dataOffset) - << dataString - << Internal::stringFormat(" | 0x%02x%02x%02x%02x", checksum[0], checksum[1], checksum[2], checksum[3]) - << '\n'; + out << stringFormat("{:8} | {:<5} |{:8} | {}", address, chType, dataOffset, dataString) + << stringFormat(" | 0x{:02x}{:02x}{:02x}{:02x}\n", checksum[0], checksum[1], checksum[2], checksum[3]); } // chunk type diff --git a/src/rafimage.cpp b/src/rafimage.cpp index e42e16f658..3e5ffa138c 100644 --- a/src/rafimage.cpp +++ b/src/rafimage.cpp @@ -81,7 +81,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si if (bPrint) { io_->seek(0, BasicIo::beg); // rewind size_t address = io_->tell(); - constexpr auto format = " %9zu | %9" PRIu32 " | "; + constexpr auto format = " {:9} | {:9} | "; { out << Internal::indent(depth) << "STRUCTURE OF RAF FILE: " << io().path() << '\n'; @@ -92,7 +92,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si io_->readOrThrow(magicdata, 16); magicdata[16] = 0; { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 16U) // 0 + out << Internal::indent(depth) << stringFormat(format, address, 16) // 0 << " magic : " << reinterpret_cast(magicdata) << '\n'; } @@ -101,7 +101,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si io_->read(data1, 4); data1[4] = 0; { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 4U) // 16 + out << Internal::indent(depth) << stringFormat(format, address, 4) // 16 << " data1 : " << std::string(reinterpret_cast(&data1)) << '\n'; } @@ -110,7 +110,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si io_->read(data2, 8); data2[8] = 0; { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 8U) // 20 + out << Internal::indent(depth) << stringFormat(format, address, 8) // 20 << " data2 : " << std::string(reinterpret_cast(&data2)) << '\n'; } @@ -119,7 +119,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si io_->read(camdata, 32); camdata[32] = 0; { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 32U) // 28 + out << Internal::indent(depth) << stringFormat(format, address, 32) // 28 << " camera : " << std::string(reinterpret_cast(&camdata)) << '\n'; } @@ -128,7 +128,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si io_->read(dir_version, 4); dir_version[4] = 0; { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 4U) // 60 + out << Internal::indent(depth) << stringFormat(format, address, 4) // 60 << " version : " << std::string(reinterpret_cast(&dir_version)) << '\n'; } @@ -136,7 +136,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si DataBuf unknown(20); io_->readOrThrow(unknown.data(), unknown.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, 20U) + out << Internal::indent(depth) << stringFormat(format, address, 20) << " unknown : " << Internal::binaryToString(makeSlice(unknown, 0, unknown.size())) << '\n'; } @@ -154,10 +154,8 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si std::stringstream j_len; j_off << jpg_img_off; j_len << jpg_img_len; - out << Internal::indent(depth) << Internal::stringFormat(format, address, 4U) << " JPEG offset : " << j_off.str() - << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address2, 4U) << " JPEG length : " << j_len.str() - << '\n'; + out << Internal::indent(depth) << stringFormat(format, address, 4) << " JPEG offset : " << j_off.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address2, 4) << " JPEG length : " << j_len.str() << '\n'; } // RAFs can carry the payload in one or two parts @@ -176,10 +174,10 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si std::stringstream c_len; c_off << meta_off[i]; c_len << meta_len[i]; - out << Internal::indent(depth) << Internal::stringFormat(format, address, 4U) << "meta offset" << i + 1 << " : " + out << Internal::indent(depth) << stringFormat(format, address, 4) << "meta offset" << i + 1 << " : " << c_off.str() << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address2, 4U) << "meta length" << i + 1 - << " : " << c_len.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address2, 4) << "meta length" << i + 1 << " : " + << c_len.str() << '\n'; } address = io_->tell(); @@ -208,16 +206,16 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si c_comp << comp[i]; c_size << cfa_size[i]; c_data << cfa_data[i]; - out << Internal::indent(depth) << Internal::stringFormat(format, address, 4U) << " CFA offset" << i + 1 << " : " + out << Internal::indent(depth) << stringFormat(format, address, 4U) << " CFA offset" << i + 1 << " : " << c_off.str() << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address2, 4U) << " CFA length" << i + 1 - << " : " << c_len.str() << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address3, 4U) << "compression" << i + 1 - << " : " << c_comp.str() << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address4, 4U) << " CFA chunk" << i + 1 - << " : " << c_size.str() << '\n'; - out << Internal::indent(depth) << Internal::stringFormat(format, address5, 4U) << " unknown" << i + 1 - << " : " << c_data.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address2, 4U) << " CFA length" << i + 1 << " : " + << c_len.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address3, 4U) << "compression" << i + 1 << " : " + << c_comp.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address4, 4U) << " CFA chunk" << i + 1 << " : " + << c_size.str() << '\n'; + out << Internal::indent(depth) << stringFormat(format, address5, 4U) << " unknown" << i + 1 << " : " + << c_data.str() << '\n'; } } @@ -226,7 +224,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si DataBuf payload(16); // header is different from chunks io_->readOrThrow(payload.data(), payload.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, jpg_img_len) + out << Internal::indent(depth) << stringFormat(format, address, jpg_img_len) << " JPEG data : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n'; } @@ -234,7 +232,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si address = io_->tell(); io_->readOrThrow(payload.data(), payload.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, meta_len[0]) + out << Internal::indent(depth) << stringFormat(format, address, meta_len[0]) << " meta data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n'; } @@ -243,7 +241,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si address = io_->tell(); io_->readOrThrow(payload.data(), payload.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, meta_len[1]) + out << Internal::indent(depth) << stringFormat(format, address, meta_len[1]) << " meta data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n'; } } @@ -252,7 +250,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si address = io_->tell(); io_->readOrThrow(payload.data(), payload.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, cfa_len[0]) + out << Internal::indent(depth) << stringFormat(format, address, cfa_len[0]) << " CFA data1 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n'; } @@ -261,7 +259,7 @@ void RafImage::printStructure(std::ostream& out, PrintStructureOption option, si address = io_->tell(); io_->readOrThrow(payload.data(), payload.size()); { - out << Internal::indent(depth) << Internal::stringFormat(format, address, cfa_len[1]) // cfa_off + out << Internal::indent(depth) << stringFormat(format, address, cfa_len[1]) // cfa_off << " CFA data2 : " << Internal::binaryToString(makeSlice(payload, 0, payload.size())) << '\n'; } } diff --git a/src/webpimage.cpp b/src/webpimage.cpp index 0586543acb..1ba7f8fe11 100644 --- a/src/webpimage.cpp +++ b/src/webpimage.cpp @@ -433,8 +433,7 @@ void WebPImage::printStructure(std::ostream& out, PrintStructureOption option, s io_->read(payload.data(), payload.size()); if (bPrint) { - out << Internal::indent(depth) - << Internal::stringFormat(" %s | %8u | %8u | ", chunkId.c_str(), size, static_cast(offset)) + out << Internal::indent(depth) << stringFormat(" {} | {:8} | {:8} | ", chunkId.c_str(), size, offset) << Internal::binaryToString(makeSlice(payload, 0, payload.size() > 32 ? 32 : payload.size())) << '\n'; } diff --git a/subprojects/fmt.wrap b/subprojects/fmt.wrap new file mode 100644 index 0000000000..bc109cc3b0 --- /dev/null +++ b/subprojects/fmt.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = fmt-10.1.1 +source_url = https://github.com/fmtlib/fmt/archive/10.1.1.tar.gz +source_filename = fmt-10.1.1.tar.gz +source_hash = 78b8c0a72b1c35e4443a7e308df52498252d1cefc2b08c9a97bc9ee6cfe61f8b +patch_filename = fmt_10.1.1-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_10.1.1-1/get_patch +patch_hash = adec33acaf87c0859c52b242a44bc71c3427751da3f1adaed511f4186794a42f +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_10.1.1-1/fmt-10.1.1.tar.gz +wrapdb_version = 10.1.1-1 + +[provide] +fmt = fmt_dep diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index 0f7c505eb2..f68fbc8e22 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -44,6 +44,10 @@ target_compile_definitions(unit_tests PRIVATE exiv2lib_STATIC TESTDATA_PATH="${P target_link_libraries(unit_tests PRIVATE exiv2lib GTest::gmock_main std::filesystem) +if(NOT HAVE_STD_FORMAT) + target_link_libraries(unit_tests PRIVATE fmt::fmt) +endif() + if(EXIV2_ENABLE_INIH) target_link_libraries(unit_tests PRIVATE inih::libinih inih::inireader) endif()