modules/QtPDF/CMakeLists.txt

modules/QtPDF/CMakeLists.txt

Setup and User Options

Set project name.

PROJECT(QtPDF)

We require CMake v2.8 or greater because we don't do any testing with earlier versions. In CMake v2.8.6, the AUTOMOC feature was introduced which simplifies the interaction with Qt.

CMAKE_MINIMUM_REQUIRED(VERSION 2.8.6)
CMAKE_POLICY(VERSION 2.8.6)

Silence warning about linking to qtmain.lib statically on Windows

IF(POLICY CMP0020)
  cmake_policy(SET CMP0020 NEW)
ENDIF()

Silence warning about using @rpath on OS X.

if(POLICY CMP0042)
  cmake_policy(SET CMP0042 NEW)
endif()

SET(CMAKE_COLOR_MAKEFILE ON)
SET(CMAKE_AUTOMOC TRUE)

Always add the current source and binary directories to the header include path when compiling.

SET(CMAKE_INCLUDE_CURRENT_DIR ON)

Determine Version Numbers

set(PROJECT_VERSION "0.1")

Declare Project Options

For now, default to a debug build.

IF ( NOT CMAKE_BUILD_TYPE )
  SET(CMAKE_BUILD_TYPE "Debug")
ENDIF ()

By default, we build a shared lib...

IF ( NOT DEFINED BUILD_SHARED_LIBS )
  SET(BUILD_SHARED_LIBS ON)
ENDIF ()

...with poppler-qt...

IF ( NOT DEFINED WITH_POPPLERQT )
  SET(WITH_POPPLERQT ON)
ENDIF ()

...but without MuPDF

IF ( NOT DEFINED WITH_MUPDF )
  SET(WITH_MUPDF OFF)
ENDIF ()

...and the viewer programs

IF ( NOT DEFINED QTPDF_VIEWER )
  SET(QTPDF_VIEWER ON)
ENDIF ()

OPTION(QTPDF_VIEWER "Build PDF viewer application" ${QTPDF_VIEWER})

OPTION(WITH_POPPLER "Build Poppler Qt backend" ${WITH_POPPLERQT})

MuPDF backend is a bit immature, so we don't bother with it by default right now.

OPTION(WITH_MUPDF "Build MuPDF backend" ${WITH_MUPDF})


OPTION(BUILD_SHARED_LIBS "Build shared library" ${BUILD_SHARED_LIBS})

Dependency Configuration

Make the contents of CMake/Modules available. Among other things, this directory contains scripts that locate project components such as Poppler.

LIST(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMake/Modules)

Core Components

set(DESIRED_QT_VERSION "" CACHE STRING "Pick a version of Qt to use: 4 or 5")
MARK_AS_ADVANCED(DESIRED_QT_VERSION)

Check for Qt5 by default (unless another Qt version was already found, e.g., by a parent project)

IF (DESIRED_QT_VERSION MATCHES 5 OR (NOT DESIRED_QT_VERSION AND (QT_VERSION_MAJOR MATCHES 5 OR NOT QT_VERSION_MAJOR)))
  find_package(Qt5Core QUIET)
  find_package(Qt5Widgets QUIET)
  find_package(Qt5Concurrent QUIET)
  find_package(Qt5Xml QUIET)

  IF( ${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
    find_package(Qt5Test QUIET)
  ENDIF()
  
  IF( WIN32 AND NOT BUILD_SHARED_LIBS )

For static Windows builds, we also need to pull in the Qt5 Platform support library, which is not exposed to CMake properly, unfortunately

    GET_TARGET_PROPERTY(QT_LIB_DIR "${Qt5Widgets_LIBRARIES}" LOCATION)
    GET_FILENAME_COMPONENT(QT_LIB_DIR "${QT_LIB_DIR}" PATH)
    FIND_LIBRARY(Qt5Platform_LIBRARIES Qt5PlatformSupport
      HINTS "${QT_LIB_DIR}"
    )
    FIND_LIBRARY(Qt5QWindows_LIBRARIES qwindows
      HINTS "${QT_LIB_DIR}/../plugins/platforms"
    )
    ADD_DEFINITIONS(-DSTATIC_QT5)
  ENDIF()

  IF(Qt5Core_FOUND AND Qt5Widgets_FOUND AND Qt5Concurrent_FOUND AND Qt5Core_FOUND AND (NOT ${CMAKE_BUILD_TYPE} STREQUAL "Debug" OR (Qt5Test_FOUND AND Qt5Xml_FOUND)))
  	SET(QT5_FOUND TRUE)
    SET(QT_INCLUDE_DIR "${Qt5Core_INCLUDE_DIRS} ${Qt5Widgets_INCLUDE_DIRS} ${Qt5Concurrent_INCLUDE_DIRS}")
    SET(QT_LIBRARIES ${Qt5Core_LIBRARIES} ${Qt5QWindows_LIBRARIES} ${Qt5Platform_LIBRARIES} ${Qt5Widgets_LIBRARIES} ${Qt5Concurrent_LIBRARIES} ${Qt5Core_LIBRARIES} ${Qt5Xml_LIBRARIES})
    SET(QT_TEST_LIBRARIES ${Qt5Test_LIBRARIES} ${Qt5Core_LIBRARIES})

Note: Qt5 only sets Qt5WidgetsVERSION, etc., but not QTVERSION_MAJOR, etc. which is used here.

    string(REGEX REPLACE "^([0-9]+).*$" "\\1" QT_VERSION_MAJOR "${Qt5Widgets_VERSION}")
    string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*$" "\\1" QT_VERSION_MINOR  "${Qt5Widgets_VERSION}")
    string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" QT_VERSION_PATCH "${Qt5Widgets_VERSION}")
  ENDIF()
  

Define version-dependent wrappers

  MACRO(QT_ADD_RESOURCES)
    QT5_ADD_RESOURCES(${ARGV})
  ENDMACRO(QT_ADD_RESOURCES)
ENDIF()

If Qt5 is not found (or not desired), check for Qt4

IF (DESIRED_QT_VERSION MATCHES 4 OR NOT QT5_FOUND)

Declare Qt libraries required by this project.

  SET (QT_COMPONENTS
    QtCore
    QtGui
    QtXML
  )
  IF( ${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
    LIST(APPEND QT_COMPONENTS QtTest)
  ENDIF()

Locate Qt. INCLUDE(UseQt4) brings in a set of macros that helps us deal with Qt-specific tasks such as compiling resources or running moc.

Qt 4.6.0 or newer is required for many pieces of functionality used in the code.

  FIND_PACKAGE(Qt4 4.6.0 COMPONENTS ${QT_COMPONENTS} QUIET)
  INCLUDE(UseQt4)

TODO: QtTest is included in the list of "normal" libraries for debug builds. Therefore, QTTESTLIBRARIES is not set here. Can we improve this somehow (i.e., can we pull in QtTest only for programs that actually use it)?

Define version-dependent wrappers

  MACRO(QT_ADD_RESOURCES)
    QT4_ADD_RESOURCES(${ARGV})
  ENDMACRO(QT_ADD_RESOURCES)
ENDIF()

IF (NOT QT4_FOUND AND NOT QT5_FOUND)
  MESSAGE(FATAL_ERROR "Could not find required Qt >= 4.6. Please install Qt (see http://qt-project.org/) and/or adjust DESIRED_QT_VERSION.")
ENDIF()

Expose the major version number of Qt to the preprocessor. This is necessary to include the correct Qt headers (as QTVERSION is not defined before any Qt headers are included)

ADD_DEFINITIONS(-DQT_VERSION_MAJOR=${QT_VERSION_MAJOR})


FIND_PACKAGE(ZLIB REQUIRED)

Aggregate library names and include directories into variables for easy access.

SET(QTPDF_INCLUDE_DIRS
  ${QT_INCLUDE_DIR}
  ${ZLIB_INCLUDE_DIR}
  ${POPPLER_QT_INCLUDE_DIR}
)

SET(QTPDF_LIBS
  ${QT_LIBRARIES}
  ${ZLIB_LIBRARIES}
)

Backend Components

IF( WITH_POPPLERQT )

  FIND_PACKAGE(Poppler REQUIRED)
  LIST(APPEND QTPDF_INCLUDE_DIRS ${POPPLER_QT_INCLUDE_DIR})

In case poppler is linked statically, its libs have to be given to the linker first for some strange reason or symbols won't be properly resolved. At least, this is the case with the MinGW linker.

  SET(QTPDF_LIBS ${POPPLER_LIBRARIES} ${QTPDF_LIBS})

The only thing Poppler should need is the location of the include directories in order to access header files. The library loader should be able to find libfontconfig on standard search paths.

  IF ( POPPLER_NEEDS_FONTCONFIG )
    FIND_PACKAGE(Fontconfig REQUIRED)
    LIST(APPEND QTPDF_INCLUDE_DIRS ${FONTCONFIG_INCLUDE_DIR})
  ENDIF ()

ENDIF()

IF( WITH_MUPDF )

  FIND_PACKAGE(JPEG REQUIRED)
  FIND_PACKAGE(Freetype REQUIRED)
  FIND_PACKAGE(JBig2Dec REQUIRED)
  FIND_PACKAGE(OpenJPEG REQUIRED)
  FIND_PACKAGE(MuPDF REQUIRED)

  LIST(APPEND QTPDF_INCLUDE_DIRS
    ${JPEG_INCLUDE_DIR}
    ${FREETYPE_INCLUDE_DIR}
    ${JBIG2DEC_INCLUDE_DIR}
    ${OPENJPEG_INCLUDE_DIR}
    ${MUPDF_INCLUDE_DIR}
  )

  LIST(APPEND QTPDF_LIBS
    ${JPEG_LIBRARIES}
    ${FREETYPE_LIBRARIES}
    ${JBIG2DEC_LIBRARIES}
    ${OPENJPEG_LIBRARIES}
  )

Since the MuPDF libraries are static libs, they have to be given to the linker first for some strange reason or symbols won't be properly resolved. At least, this is the case with the MinGW linker.

  SET(QTPDF_LIBS ${MUPDF_LIBRARIES} ${QTPDF_LIBS})

setlocale() is necessary for the MuPDF backend (at least for locales not using '.' as a decimal point)

  INCLUDE( CheckIncludeFiles )
  INCLUDE( CheckFunctionExists )
  CHECK_INCLUDE_FILES(locale.h HAVE_LOCALE_H)
  CHECK_FUNCTION_EXISTS(setlocale HAVE_SETLOCALE)
  IF( HAVE_LOCALE_H AND HAVE_SETLOCALE )
    ADD_DEFINITIONS(-DHAVE_LOCALE_H)
  ENDIF()

ENDIF()

Update Header Templates

Building

Common setup.

INCLUDE_DIRECTORIES(
  ${QTPDF_INCLUDE_DIRS}
  ${CMAKE_CURRENT_SOURCE_DIR}/src
  ${CMAKE_CURRENT_SOURCE_DIR}/src/backend
)

IF( ${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
  ADD_DEFINITIONS(-DDEBUG -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_BYTEARRAY)
  SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall")
ENDIF()

Library

SET(QTPDF_SRCS
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentView.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentWidget.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentTools.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFBackend.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFTransitions.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFActions.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFAnnotations.cpp
)

SET(QTPDF_HDRS
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentView.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentWidget.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFDocumentTools.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFBackend.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFTransitions.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFActions.h
  ${CMAKE_CURRENT_SOURCE_DIR}/src/PDFAnnotations.h
)

FIXME: Is -fPIC required/appropriate for all situations/platforms?

IF( NOT WIN32 )
  SET(QTPDF_FLAGS "-fPIC")
ENDIF()

IF( WITH_POPPLERQT )
  LIST(APPEND QTPDF_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/backends/PopplerQtBackend.cpp)
  LIST(APPEND QTPDF_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/src/backends/PopplerQtBackend.h)
  SET(QTPDF_FLAGS "${QTPDF_FLAGS} -DUSE_POPPLERQT")
ENDIF()

IF( WITH_MUPDF )
  LIST(APPEND QTPDF_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/src/backends/MuPDFBackend.cpp)
  LIST(APPEND QTPDF_HDRS ${CMAKE_CURRENT_SOURCE_DIR}/src/backends/MuPDFBackend.h)
  SET(QTPDF_FLAGS "${QTPDF_FLAGS} -DUSE_MUPDF")
ENDIF()

Create translations

INCLUDE( TranslationMacros )
FILE(GLOB TRANSLATIONS_SOURCES trans/*.ts)
FILE(GLOB TRANSLATIONS_QM trans/*.qm)
LIST(SORT TRANSLATIONS_SOURCES)
QT_ADD_QM_TRANSLATIONS(QTPDF_QM ${TRANSLATIONS_QM})

CREATE_QT_PRO_FILE("${CMAKE_CURRENT_SOURCE_DIR}/trans/QtPDF_trans.pro" "src" ${QTPDF_SRCS} ${QTPDF_HDRS} ${TRANSLATIONS_SOURCES})

Icons

SET(QTPDF_RCS
  ${CMAKE_CURRENT_SOURCE_DIR}/QtPDF_icons.qrc
)
QT_ADD_RESOURCES(QTPDF_RESOURCES ${QTPDF_RCS})


ADD_LIBRARY(qtpdf
  ${QTPDF_SRCS}
  ${QTPDF_RESOURCES}
  ${QTPDF_QM}
)

set(QTPDF_ADDITIONAL_LIBS "" CACHE STRING "Additional libraries not found by CMake")
MARK_AS_ADVANCED(QTPDF_ADDITIONAL_LIBS)

TARGET_LINK_LIBRARIES(qtpdf ${QTPDF_LIBS} ${QTPDF_ADDITIONAL_LIBS})

SET_TARGET_PROPERTIES(qtpdf PROPERTIES
  COMPILE_FLAGS "${QTPDF_FLAGS}"
)

Inform parent scope (if any) what to do to use QtPDF

SET(QTPDF_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/src PARENT_SCOPE)

${CMAKECURRENTSOURCE_DIR}/src/backend

Viewers

IF ( QTPDF_VIEWER )

Both viewers use a common set of source code files. Preprocessor definitions toggle the backend-sensitive bits.

SET(PDFVIEWER_SRCS
  ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
  ${CMAKE_CURRENT_SOURCE_DIR}/PDFViewer.cpp
)

QT_ADD_RESOURCES(PDFVIEWER_RESOURCES
  ${CMAKE_CURRENT_SOURCE_DIR}/icons.qrc
)

SET(QTPDFTEST_SRCS
  ${CMAKE_CURRENT_SOURCE_DIR}/unit-tests/TestQtPDF.cpp
)



IF( WITH_POPPLERQT )
  ADD_EXECUTABLE(poppler-qt${QT_VERSION_MAJOR}_viewer
    ${PDFVIEWER_SRCS}
    ${PDFVIEWER_RESOURCES}
  )

NOTE: Setting properties on a target sets those properties on all source files that are built to create that target. Other targets can re-use the sources and build with different flags. Pretty handy.

  SET_TARGET_PROPERTIES(poppler-qt${QT_VERSION_MAJOR}_viewer PROPERTIES
    COMPILE_FLAGS "-DUSE_POPPLERQT ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}"
  )

  TARGET_LINK_LIBRARIES(poppler-qt${QT_VERSION_MAJOR}_viewer qtpdf)

  IF( ${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
    ADD_EXECUTABLE(poppler-qt${QT_VERSION_MAJOR}_test
      ${QTPDFTEST_SRCS}
    )
    SET_TARGET_PROPERTIES(poppler-qt${QT_VERSION_MAJOR}_test PROPERTIES
      COMPILE_FLAGS "-DUSE_POPPLERQT ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}"
      RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/unit-tests
    )
    TARGET_LINK_LIBRARIES(poppler-qt${QT_VERSION_MAJOR}_test ${QT_TEST_LIBRARIES} qtpdf)
  ENDIF()
ENDIF()

IF( WITH_MUPDF )
  ADD_EXECUTABLE(mupdf_viewer
    ${PDFVIEWER_SRCS}
    ${PDFVIEWER_RESOURCES}
  )

  SET_TARGET_PROPERTIES(mupdf_viewer PROPERTIES
    COMPILE_FLAGS "-DUSE_MUPDF ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}"
  )

  TARGET_LINK_LIBRARIES(mupdf_viewer qtpdf)

  IF( ${CMAKE_BUILD_TYPE} STREQUAL "Debug" )
    ADD_EXECUTABLE(mupdf_test
      ${QTPDFTEST_SRCS}
    )
    SET_TARGET_PROPERTIES(mupdf_test PROPERTIES
      COMPILE_FLAGS "-DUSE_MUPDF ${Qt5Widgets_EXECUTABLE_COMPILE_FLAGS}"
      RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/unit-tests
    )
    TARGET_LINK_LIBRARIES(mupdf_test ${QT_TEST_LIBRARIES} qtpdf)
  ENDIF()
ENDIF()

ENDIF() # QTPDF_VIEWER

Packaging

Summary

This section displays a nice configuration summary for the user.

These macros borrowed from the Poppler CMake scripts. They add some nice formatting to configuration info.

MACRO(CONFIG_INFO what value)
  STRING(LENGTH ${what} length_what)
  MATH(EXPR left_char "35 - ${length_what}")
  SET(blanks)
  FOREACH(_i RANGE 1 ${left_char})
    SET(blanks "${blanks} ")
  ENDFOREACH()

  MESSAGE("  ${what}:${blanks} ${value}")
ENDMACRO()

MACRO(CONFIG_YESNO what enabled)
  IF(${enabled})
    SET(enabled_string "yes")
  ELSE(${enabled})
    SET(enabled_string "no")
  ENDIF()

  CONFIG_INFO("${what}" "${enabled_string}")
ENDMACRO()

Print out configuration summary.

MESSAGE("${PROJECT_NAME} has been configured:\n")

CONFIG_INFO("Version" ${PROJECT_VERSION})
CONFIG_INFO("Qt version" "${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}.${QT_VERSION_PATCH}")
CONFIG_INFO("Compiler optimization" ${CMAKE_BUILD_TYPE})
message("")

CONFIG_YESNO("Poppler-Qt backend" WITH_POPPLERQT)
CONFIG_YESNO("MuPDF backend" WITH_MUPDF)
CONFIG_YESNO("Shared library" BUILD_SHARED_LIBS)
CONFIG_YESNO("Viewer application" QTPDF_VIEWER)

message("")
message("  ${PROJECT_NAME} will be installed to:")
message("      ${CMAKE_INSTALL_PREFIX}")
message("")