SDL_ttf: cmake: require minimum harfbuzz version (b8526)

From b852666db3a1a2983148f6e6be23572ce5265ed0 Mon Sep 17 00:00:00 2001
From: Anonymous Maarten <[EMAIL REDACTED]>
Date: Wed, 29 Jan 2025 02:09:46 +0100
Subject: [PATCH] cmake: require minimum harfbuzz version

(cherry picked from commit 1dc162336f680c459aadef299b02c099a5533b34)
---
 CMakeLists.txt              | 13 +++++++++++-
 SDL2_ttfConfig.cmake.in     | 14 +++++++------
 cmake/Findharfbuzz.cmake    | 40 ++++++++++++++++++++++++++++++++-----
 cmake/PkgConfigHelper.cmake | 34 +++++++++++++++++++++++++++++++
 4 files changed, 89 insertions(+), 12 deletions(-)
 create mode 100644 cmake/PkgConfigHelper.cmake

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 331f6af6..f21121e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -48,6 +48,7 @@ if ((TARGET SDL2 OR TARGET SDL2-static) AND SDL2_DISABLE_INSTALL)
     set(sdl2ttf_install_enableable OFF)
 endif()
 
+include("${CMAKE_CURRENT_LIST_DIR}/cmake/PkgConfigHelper.cmake")
 include(CMakeDependentOption)
 include(CMakePackageConfigHelpers)
 include(GNUInstallDirs)
@@ -64,6 +65,7 @@ option(SDL2TTF_VENDORED "Use vendored third-party libraries" ${vendored_default}
 set(SDL2TTF_FREETYPE ON)
 set(SDL2TTF_FREETYPE_VENDORED "${SDL2TTF_VENDORED}")
 
+set(HARFBUZZ_REQUIRED_VERSION "2.3.1")
 option(SDL2TTF_HARFBUZZ "Use harfbuzz to improve text shaping" OFF)
 set(SDL2TTF_HARFBUZZ_VENDORED "${SDL2TTF_VENDORED}")
 
@@ -203,7 +205,7 @@ if(SDL2TTF_HARFBUZZ)
         endif()
     else()
         message(STATUS "${PROJECT_NAME}: Using system harfbuzz library")
-        find_package(harfbuzz REQUIRED)
+        find_package(harfbuzz "${HARFBUZZ_REQUIRED_VERSION}" REQUIRED)
         list(APPEND PC_REQUIRES harfbuzz)
     endif()
     target_compile_definitions(SDL2_ttf PRIVATE TTF_USE_HARFBUZZ=1)
@@ -310,6 +312,15 @@ if(SDL2TTF_INSTALL)
         DESTINATION ${SDLTTF_INSTALL_CMAKEDIR}
         COMPONENT devel
     )
+    if(NOT SDLTTF_VENDORED)
+        install(
+            FILES
+                cmake/PkgConfigHelper.cmake
+                cmake/Findharfbuzz.cmake
+            DESTINATION "${SDLTTF_INSTALL_CMAKEDIR}"
+            COMPONENT devel
+        )
+    endif()
     install(EXPORT SDL2_ttfTargets
         FILE SDL2_ttf-${sdl2_ttf_install_name_infix}-targets.cmake
         NAMESPACE SDL2_ttf::
diff --git a/SDL2_ttfConfig.cmake.in b/SDL2_ttfConfig.cmake.in
index 6029542a..b7122fed 100644
--- a/SDL2_ttfConfig.cmake.in
+++ b/SDL2_ttfConfig.cmake.in
@@ -13,9 +13,8 @@ set(SDL2TTF_VENDORED  @SDL2TTF_VENDORED@)
 set(SDL2TTF_HARFBUZZ @SDL2TTF_HARFBUZZ@)
 set(SDL2TTF_FREETYPE @SDL2TTF_FREETYPE@)
 
-set(SDL2TTF_SDL2_REQUIRED_VERSION  @SDL_REQUIRED_VERSION@)
-
-include(CMakeFindDependencyMacro)
+set(SDL2TTF_HARFBUZZ_REQUIRED_VERSION    @HARFBUZZ_REQUIRED_VERSION@)
+set(SDL2TTF_SDL2_REQUIRED_VERSION        @SDL_REQUIRED_VERSION@)
 
 if (EXISTS "${CMAKE_CURRENT_LIST_DIR}/SDL2_ttf-shared-targets.cmake")
     include("${CMAKE_CURRENT_LIST_DIR}/SDL2_ttf-shared-targets.cmake")
@@ -37,14 +36,17 @@ if(NOT SDL2TTF_VENDORED)
     set(_sdl_cmake_module_path "${CMAKE_MODULE_PATH}")
     list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}")
 
+    include(CMakeFindDependencyMacro)
+    include(PkgConfigHelper)
+
     if(TARGET SDL2_ttf::SDL2_ttf-static)
-        if(SDL2TTF_FREETYPE)
+        if(SDL2TTF_FREETYPE AND NOT Freetype::Freetype)
             find_dependency(Freetype)
         endif()
 
-        if(SDL2TTF_HARFBUZZ)
+        if(SDL2TTF_HARFBUZZ AND NOT harfbuzz::harfbuzz)
             list(APPEND harfbuzz_ROOT "${CMAKE_CURRENT_LIST_DIR}")
-            find_dependency(harfbuzz)
+            find_dependency(harfbuzz ${SDL2TTF_HARFBUZZ_REQUIRED_VERSION})
         endif()
     endif()
 
diff --git a/cmake/Findharfbuzz.cmake b/cmake/Findharfbuzz.cmake
index 53407e28..da289ae8 100644
--- a/cmake/Findharfbuzz.cmake
+++ b/cmake/Findharfbuzz.cmake
@@ -3,23 +3,52 @@
 
 include(FindPackageHandleStandardArgs)
 
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_HARFBUZZ QUIET harfbuzz)
+
 find_library(harfbuzz_LIBRARY
     NAMES harfbuzz
+    HINTS ${PC_HARFBUZZ_LIBDIR}
 )
 
 find_path(harfbuzz_INCLUDE_PATH
     NAMES hb.h
     PATH_SUFFIXES harfbuzz
+    HINTS ${PC_HARFBUZZ_INCLUDEDIR}
 )
 
-set(harfbuzz_COMPILE_FLAGS "" CACHE STRING "Extra compile flags of harfbuzz")
+if(PC_HARFBUZZ_FOUND)
+    get_flags_from_pkg_config("${harfbuzz_LIBRARY}" "PC_HARFBUZZ" "_harfbuzz")
+endif()
+
+set(harfbuzz_COMPILE_OPTIONS "${_harfbuzz_compile_options}" CACHE STRING "Extra compile flags of harfbuzz")
 
-set(harfbuzz_LINK_LIBRARIES "" CACHE STRING "Extra link libraries of harfbuzz")
+set(harfbuzz_LINK_LIBRARIES "${_harfbuzz_link_libraries}" CACHE STRING "Extra link libraries of harfbuzz")
 
-set(harfbuzz_LINK_FLAGS "" CACHE STRING "Extra link flags of harfbuzz")
+set(harfbuzz_LINK_OPTIONS "${_harfbuzz_link_options}" CACHE STRING "Extra link flags of harfbuzz")
+
+set(harfbuzz_LINK_DIRECTORIES "${_harfbuzz_link_directories}" CACHE PATH "Extra link directories of harfbuzz")
+
+set(harfbuzz_VERSION "harfbuzz_VERSION-NOTFOUND")
+foreach(_hb_incpath IN LISTS harfbuzz_INCLUDE_PATH)
+    if(IS_DIRECTORY "${_hb_incpath}" AND EXISTS "${_hb_incpath}/hb-version.h")
+        file(READ "${_hb_incpath}/hb-version.h" _hb_version_text)
+        string(REGEX MATCH "#define[ \t]+HB_VERSION_MAJOR[ \t]+([0-9]+)" _hb_major_re "${_hb_version_text}")
+        set(_hb_major "${CMAKE_MATCH_1}")
+        string(REGEX MATCH "#define[ \t]+HB_VERSION_MINOR[ \t]+([0-9]+)" _hb_minor_re "${_hb_version_text}")
+        set(_hb_minor "${CMAKE_MATCH_1}")
+        string(REGEX MATCH "#define[ \t]+HB_VERSION_MICRO[ \t]+([0-9]+)" _hb_micro_re "${_hb_version_text}")
+        set(_hb_micro "${CMAKE_MATCH_1}")
+        if(_hb_major_re AND _hb_minor_re AND _hb_micro_re)
+            set(harfbuzz_VERSION "${_hb_major}.${_hb_minor}.${_hb_micro}")
+            break()
+        endif()
+    endif()
+endforeach()
 
 find_package_handle_standard_args(harfbuzz
     REQUIRED_VARS harfbuzz_LIBRARY harfbuzz_INCLUDE_PATH
+    VERSION_VAR harfbuzz_VERSION
 )
 
 if (harfbuzz_FOUND)
@@ -28,9 +57,10 @@ if (harfbuzz_FOUND)
         set_target_properties(harfbuzz::harfbuzz PROPERTIES
             IMPORTED_LOCATION "${harfbuzz_LIBRARY}"
             INTERFACE_INCLUDE_DIRECTORIES "${harfbuzz_INCLUDE_PATH}"
-            COMPILE_FLAGS "${harfbuzz_COMPILE_FLAGS}"
+            INTERFACE_COMPILE_OPTIONS "${harfbuzz_COMPILE_OPTIONS}"
             INTERFACE_LINK_LIBRARIES "${harfbuzz_LINK_LIBRARIES}"
-            INTERFACE_LINK_FLAGS "${harfbuzz_LINK_FLAGS}"
+            INTERFACE_LINK_OPTIONS "${harfbuzz_LINK_OPTIONS}"
+            INTERFACE_LINK_DIRECTORIES "${harfbuzz_LINK_DIRECTORIES}"
         )
     endif()
 endif()
diff --git a/cmake/PkgConfigHelper.cmake b/cmake/PkgConfigHelper.cmake
new file mode 100644
index 00000000..7070fac7
--- /dev/null
+++ b/cmake/PkgConfigHelper.cmake
@@ -0,0 +1,34 @@
+# Helper for Find modules
+
+function(get_flags_from_pkg_config _library _pc_prefix _out_prefix)
+  if("${_library}" MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$")
+    set(_cflags ${_pc_prefix}_STATIC_CFLAGS_OTHER)
+    set(_link_libraries ${_pc_prefix}_STATIC_LIBRARIES)
+    set(_link_options ${_pc_prefix}_STATIC_LDFLAGS_OTHER)
+    set(_library_dirs ${_pc_prefix}_STATIC_LIBRARY_DIRS)
+  else()
+    set(_cflags ${_pc_prefix}_CFLAGS_OTHER)
+    set(_link_libraries ${_pc_prefix}_LIBRARIES)
+    set(_link_options ${_pc_prefix}_LDFLAGS_OTHER)
+    set(_library_dirs ${_pc_prefix}_LIBRARY_DIRS)
+  endif()
+
+  # The *_LIBRARIES lists always start with the library itself
+  list(POP_FRONT "${_link_libraries}")
+
+  # Work around CMake's flag deduplication when pc files use `-framework A` instead of `-Wl,-framework,A`
+  string(REPLACE "-framework;" "-Wl,-framework," "_filtered_link_options" "${${_link_options}}")
+
+  set(${_out_prefix}_compile_options
+      "${${_cflags}}"
+      PARENT_SCOPE)
+  set(${_out_prefix}_link_libraries
+      "${${_link_libraries}}"
+      PARENT_SCOPE)
+  set(${_out_prefix}_link_options
+      "${_filtered_link_options}"
+      PARENT_SCOPE)
+  set(${_out_prefix}_link_directories
+      "${${_library_dirs}}"
+      PARENT_SCOPE)
+endfunction()