mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-21 21:55:38 +00:00
Added a PDF viewer
Also added the PoDoFo and Poppler libraries as dependencies
This commit is contained in:
parent
177dd23b7c
commit
bd2c229476
66
CMake/Packages/FindPoDoFo.cmake
Normal file
66
CMake/Packages/FindPoDoFo.cmake
Normal file
|
@ -0,0 +1,66 @@
|
|||
# - Try to find the PoDoFo library
|
||||
#
|
||||
# Windows users MUST set when building:
|
||||
#
|
||||
# PoDoFo_USE_SHARED - whether use PoDoFo as shared library
|
||||
#
|
||||
# Once done this will define:
|
||||
#
|
||||
# PoDoFo_FOUND - system has the PoDoFo library
|
||||
# PoDoFo_INCLUDE_DIRS - the PoDoFo include directory
|
||||
# PoDoFo_LIBRARIES - the libraries needed to use PoDoFo
|
||||
# PoDoFo_DEFINITIONS - the definitions needed to use PoDoFo
|
||||
#
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# SPDX-FileCopyrightText: 2016 Pino Toscano <pino@kde.org>
|
||||
|
||||
|
||||
find_path(PoDoFo_INCLUDE_DIRS
|
||||
NAMES podofo/podofo.h
|
||||
)
|
||||
find_library(PoDoFo_LIBRARIES
|
||||
NAMES libpodofo podofo
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(PoDoFo DEFAULT_MSG PoDoFo_LIBRARIES PoDoFo_INCLUDE_DIRS)
|
||||
|
||||
set(PoDoFo_DEFINITIONS)
|
||||
if(PoDoFo_FOUND)
|
||||
if(WIN32)
|
||||
if(NOT DEFINED PoDoFo_USE_SHARED)
|
||||
message(SEND_ERROR "Win32 users MUST set PoDoFo_USE_SHARED")
|
||||
message(SEND_ERROR "Set -DPoDoFo_USE_SHARED=0 if linking to a static library PoDoFo")
|
||||
message(SEND_ERROR "or -DPoDoFo_USE_SHARED=1 if linking to a DLL build of PoDoFo")
|
||||
message(FATAL_ERROR "PoDoFo_USE_SHARED unset on win32 build")
|
||||
else()
|
||||
if(PoDoFo_USE_SHARED)
|
||||
set(PoDoFo_DEFINITIONS "${PoDoFo_DEFINITIONS} -DUSING_SHARED_PODOFO")
|
||||
endif(PoDoFo_USE_SHARED)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# PoDoFo-0.9.5 unconditionally includes openssl/opensslconf.h in a public
|
||||
# header. The fix is in https://sourceforge.net/p/podofo/code/1830/ and will
|
||||
# hopefully be released soon with 0.9.6. Note that krename doesn't use
|
||||
# OpenSSL in any way.
|
||||
file(STRINGS "${PoDoFo_INCLUDE_DIRS}/podofo/base/podofo_config.h" PoDoFo_MAJOR_VER_LINE REGEX "^#define[ \t]+PODOFO_VERSION_MAJOR[ \t]+[0-9]+$")
|
||||
file(STRINGS "${PoDoFo_INCLUDE_DIRS}/podofo/base/podofo_config.h" PoDoFo_MINOR_VER_LINE REGEX "^#define[ \t]+PODOFO_VERSION_MINOR[ \t]+[0-9]+$")
|
||||
file(STRINGS "${PoDoFo_INCLUDE_DIRS}/podofo/base/podofo_config.h" PoDoFo_PATCH_VER_LINE REGEX "^#define[ \t]+PODOFO_VERSION_PATCH[ \t]+[0-9]+$")
|
||||
string(REGEX REPLACE "^#define[ \t]+PODOFO_VERSION_MAJOR[ \t]+([0-9]+)$" "\\1" PoDoFo_MAJOR_VER "${PoDoFo_MAJOR_VER_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+PODOFO_VERSION_MINOR[ \t]+([0-9]+)$" "\\1" PoDoFo_MINOR_VER "${PoDoFo_MINOR_VER_LINE}")
|
||||
string(REGEX REPLACE "^#define[ \t]+PODOFO_VERSION_PATCH[ \t]+([0-9]+)$" "\\1" PoDoFo_PATCH_VER "${PoDoFo_PATCH_VER_LINE}")
|
||||
set(PoDoFo_VERSION "${PoDoFo_MAJOR_VER}.${PoDoFo_MINOR_VER}.${PoDoFo_PATCH_VER}")
|
||||
if(PoDoFo_VERSION VERSION_EQUAL "0.9.5")
|
||||
find_package(OpenSSL)
|
||||
if (OpenSSL_FOUND)
|
||||
message("OpenSSL found, which is required for this version of PoDofo (0.9.5)")
|
||||
set(PoDoFo_INCLUDE_DIRS ${PoDoFo_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR})
|
||||
else()
|
||||
unset(PoDoFo_FOUND)
|
||||
message("OpenSSL NOT found, which is required for this version of PoDofo (0.9.5)")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(PoDoFo_INCLUDE_DIRS PoDoFo_LIBRARIES PoDoFo_DEFINITIONS)
|
178
CMake/Packages/FindPoppler.cmake
Normal file
178
CMake/Packages/FindPoppler.cmake
Normal file
|
@ -0,0 +1,178 @@
|
|||
# - Try to find Poppler and specified components: {cpp, Qt4, Qt5}
|
||||
# Once done this will define:
|
||||
#
|
||||
# POPPLER_FOUND - system has Poppler and specified components
|
||||
# POPPLER_INCLUDE_DIRS - The include directories for Poppler headers
|
||||
# POPPLER_LIBRARIES - Link these to use Poppler
|
||||
# POPPLER_NEEDS_FONTCONFIG - A boolean indicating if libpoppler depends on libfontconfig
|
||||
# POPPLER_HAS_XPDF - A boolean indicating if libpoppler headers are available
|
||||
# POPPLER_INCLUDE_DIR - the include directory for libpoppler XPDF headers
|
||||
#
|
||||
# Redistribution and use of this file is allowed according to the terms of the
|
||||
# MIT license. For details see the file COPYING-CMAKE-MODULES.
|
||||
|
||||
if( POPPLER_LIBRARIES )
|
||||
# in cache already
|
||||
set( Poppler_FIND_QUIETLY TRUE )
|
||||
endif( POPPLER_LIBRARIES )
|
||||
|
||||
# Check which components we need to find
|
||||
list(FIND Poppler_FIND_COMPONENTS "cpp" FIND_POS)
|
||||
if(${FIND_POS} EQUAL -1)
|
||||
set(FIND_CPP FALSE)
|
||||
else()
|
||||
set(FIND_CPP TRUE)
|
||||
endif()
|
||||
|
||||
list(FIND Poppler_FIND_COMPONENTS "Qt4" FIND_POS)
|
||||
if(${FIND_POS} EQUAL -1)
|
||||
set(FIND_QT4 FALSE)
|
||||
else()
|
||||
set(FIND_QT4 TRUE)
|
||||
endif()
|
||||
|
||||
list(FIND Poppler_FIND_COMPONENTS "Qt5" FIND_POS)
|
||||
if(${FIND_POS} EQUAL -1)
|
||||
set(FIND_QT5 FALSE)
|
||||
else()
|
||||
set(FIND_QT5 TRUE)
|
||||
endif()
|
||||
|
||||
# Default values
|
||||
set(POPPLER_FOUND FALSE)
|
||||
set(POPPLER_INCLUDE_DIRS)
|
||||
set(POPPLER_LIBRARIES)
|
||||
set(POPPLER_REQUIRED "POPPLER_LIBRARY")
|
||||
|
||||
# use pkg-config to get the directories and then use these values
|
||||
# in the find_path() and find_library() calls
|
||||
if( NOT WIN32 )
|
||||
find_package(PkgConfig)
|
||||
|
||||
pkg_check_modules(POPPLER_PKG QUIET poppler)
|
||||
if( FIND_CPP )
|
||||
pkg_check_modules(POPPLER_CPP_PKG QUIET poppler-cpp)
|
||||
endif()
|
||||
if( FIND_QT4 )
|
||||
pkg_check_modules(POPPLER_QT4_PKG QUIET poppler-qt4)
|
||||
endif()
|
||||
if( FIND_QT5 )
|
||||
pkg_check_modules(POPPLER_QT5_PKG QUIET poppler-qt5)
|
||||
endif()
|
||||
endif( NOT WIN32 )
|
||||
|
||||
# Check for Poppler headers (optional)
|
||||
find_path( POPPLER_INCLUDE_DIR NAMES poppler-config.h PATH_SUFFIXES poppler )
|
||||
if( NOT( POPPLER_INCLUDE_DIR ) )
|
||||
#if( NOT Poppler_FIND_QUIETLY )
|
||||
# message( STATUS "Could not find poppler-config.h, recompile Poppler with "
|
||||
# "ENABLE_XPDF_HEADERS to link against libpoppler directly." )
|
||||
#endif()
|
||||
set( POPPLER_HAS_XPDF FALSE )
|
||||
else()
|
||||
set( POPPLER_HAS_XPDF TRUE )
|
||||
list(APPEND POPPLER_INCLUDE_DIRS ${POPPLER_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
# Find libpoppler (Required)
|
||||
find_library( POPPLER_LIBRARY NAMES poppler ${POPPLER_CPP_PKG_LIBRARIES}
|
||||
HINTS ${POPPLER_PKG_LIBDIR} ${POPPLER_CPP_PKG_LIBDIR} )
|
||||
if( NOT(POPPLER_LIBRARY) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find libpoppler." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else( NOT(POPPLER_LIBRARY) )
|
||||
list(APPEND POPPLER_LIBRARIES ${POPPLER_LIBRARY})
|
||||
|
||||
# Scan poppler libraries for dependencies on Fontconfig
|
||||
include(GetPrerequisites)
|
||||
mark_as_advanced(gp_cmd)
|
||||
GET_PREREQUISITES("${POPPLER_LIBRARY}" POPPLER_PREREQS 1 0 "" "")
|
||||
if("${POPPLER_PREREQS}" MATCHES "fontconfig")
|
||||
set(POPPLER_NEEDS_FONTCONFIG TRUE)
|
||||
else()
|
||||
set(POPPLER_NEEDS_FONTCONFIG FALSE)
|
||||
endif()
|
||||
|
||||
# cpp Component
|
||||
if( FIND_CPP )
|
||||
list(APPEND POPPLER_REQUIRED POPPLER_CPP_INCLUDE_DIR POPPLER_CPP_LIBRARY)
|
||||
find_path( POPPLER_CPP_INCLUDE_DIR NAMES poppler-version.h
|
||||
HINTS ${POPPLER_PKG_INCLUDEDIR} ${POPPLER_CPP_PKG_INCLUDEDIR}
|
||||
PATH_SUFFIXES cpp poppler/cpp )
|
||||
if( NOT(POPPLER_CPP_INCLUDE_DIR) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find Poppler cpp wrapper headers." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_INCLUDE_DIRS ${POPPLER_CPP_INCLUDE_DIR})
|
||||
endif()
|
||||
find_library(
|
||||
POPPLER_CPP_LIBRARY NAMES poppler-cpp ${POPPLER_CPP_PKG_LIBRARIES}
|
||||
HINTS ${POPPLER_PKG_LIBDIR} ${POPPLER_CPP_PKG_LIBDIR} )
|
||||
if( NOT(POPPLER_CPP_LIBRARY) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find libpoppler-cpp." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_LIBRARIES ${POPPLER_CPP_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Qt4 Component
|
||||
if( FIND_QT4 )
|
||||
list(APPEND POPPLER_REQUIRED POPPLER_QT4_INCLUDE_DIR POPPLER_QT4_LIBRARY)
|
||||
find_path(POPPLER_QT4_INCLUDE_DIR NAMES poppler-qt4.h poppler-link.h
|
||||
HINTS ${POPPLER_PKG_INCLUDEDIR} ${POPPLER_CPP_QT4_INCLUDEDIR}
|
||||
PATH_SUFFIXES qt4 poppler/qt4 )
|
||||
if( NOT(POPPLER_QT4_INCLUDE_DIR) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find Poppler-Qt4 headers." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_INCLUDE_DIRS ${POPPLER_QT4_INCLUDE_DIR})
|
||||
endif()
|
||||
find_library(
|
||||
POPPLER_QT4_LIBRARY NAMES poppler-qt4 ${POPPLER_QT4_PKG_LIBRARIES}
|
||||
HINTS ${POPPLER_PKG_LIBDIR} ${POPPLER_QT4_PKG_LIBDIR} )
|
||||
if( NOT(POPPLER_QT4_LIBRARY) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find libpoppler-qt4." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_LIBRARIES ${POPPLER_QT4_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Qt5 Component
|
||||
if( FIND_QT5 )
|
||||
list(APPEND POPPLER_REQUIRED POPPLER_QT5_INCLUDE_DIR POPPLER_QT5_LIBRARY)
|
||||
find_path(POPPLER_QT5_INCLUDE_DIR NAMES poppler-qt5.h poppler-link.h
|
||||
HINTS ${POPPLER_QT5_INCLUDEDIR} ${POPPLER_QT5_PKG_INCLUDEDIR}
|
||||
PATH_SUFFIXES qt5 poppler/qt5 )
|
||||
if( NOT(POPPLER_QT5_INCLUDE_DIR) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message( STATUS "Could not find Poppler-Qt5 headers." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_INCLUDE_DIRS ${POPPLER_QT5_INCLUDE_DIR})
|
||||
endif()
|
||||
find_library(
|
||||
POPPLER_QT5_LIBRARY NAMES poppler-qt5 ${POPPLER_QT5_PKG_LIBRARIES}
|
||||
HINTS ${POPPLER_PKG_LIBDIR} ${POPPLER_QT5_PKG_LIBDIR} )
|
||||
if( NOT(POPPLER_QT5_LIBRARY) )
|
||||
if( NOT Poppler_FIND_QUIETLY )
|
||||
message(STATUS "Could not find libpoppler-qt5." )
|
||||
endif( NOT Poppler_FIND_QUIETLY )
|
||||
else()
|
||||
list(APPEND POPPLER_LIBRARIES ${POPPLER_QT5_LIBRARY})
|
||||
endif()
|
||||
endif()
|
||||
endif( NOT(POPPLER_LIBRARY) )
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Poppler DEFAULT_MSG ${POPPLER_REQUIRED})
|
||||
|
||||
mark_as_advanced(POPPLER_CPP_INCLUDE_DIR POPPLER_QT4_INCLUDE_DIR
|
||||
POPPLER_QT5_INCLUDE_DIR POPPLER_LIBRARIES POPPLER_CPP_LIBRARY
|
||||
POPPLER_QT4_LIBRARY POPPLER_QT5_LIBRARY)
|
|
@ -135,6 +135,7 @@ elseif(NOT EMSCRIPTEN)
|
|||
find_package(FreeImage REQUIRED)
|
||||
find_package(Freetype REQUIRED)
|
||||
find_package(Libgit2 REQUIRED)
|
||||
find_package(PoDoFo REQUIRED)
|
||||
find_package(Pugixml REQUIRED)
|
||||
find_package(SDL2 REQUIRED)
|
||||
endif()
|
||||
|
@ -455,6 +456,7 @@ else()
|
|||
${FreeImage_INCLUDE_DIRS}
|
||||
${FREETYPE_INCLUDE_DIRS}
|
||||
${GIT2_INCLUDE_PATH}
|
||||
${PoDoFo_INCLUDE_DIRS}
|
||||
${PUGIXML_INCLUDE_DIRS}
|
||||
${SDL2_INCLUDE_DIR})
|
||||
endif()
|
||||
|
@ -571,6 +573,7 @@ else()
|
|||
${FreeImage_LIBRARIES}
|
||||
${FREETYPE_LIBRARIES}
|
||||
${GIT2_LIBRARY}
|
||||
${PoDoFo_LIBRARIES}
|
||||
${PUGIXML_LIBRARIES}
|
||||
${SDL2_LIBRARY})
|
||||
endif()
|
||||
|
@ -626,10 +629,13 @@ set(EXECUTABLE_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE)
|
|||
set(LIBRARY_OUTPUT_PATH ${dir} CACHE PATH "Build directory" FORCE)
|
||||
|
||||
# Add each component.
|
||||
add_subdirectory(es-pdf-converter)
|
||||
add_subdirectory(external)
|
||||
add_subdirectory(es-core)
|
||||
add_subdirectory(es-app)
|
||||
|
||||
# Make sure rlottie is built before es-core and set lottie2gif to not be built.
|
||||
# Make sure that es-pdf-convert is built first, and then that rlottie is built before es-core.
|
||||
# Also set lottie2gif to not be built.
|
||||
add_dependencies(lunasvg es-pdf-convert)
|
||||
add_dependencies(es-core rlottie)
|
||||
set_target_properties(lottie2gif PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1)
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# EmulationStation Desktop Edition
|
||||
# CMakeLists.txt (es-app)
|
||||
#
|
||||
# CMake configuration for es-app.
|
||||
# CMake configuration for es-app
|
||||
# Also contains the application packaging configuration.
|
||||
#
|
||||
|
||||
|
@ -21,6 +21,7 @@ set(ES_HEADERS
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/PDFViewer.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Screensaver.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UIModeController.h
|
||||
|
@ -70,6 +71,7 @@ set(ES_SOURCES
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/MetaData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/MiximageGenerator.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/PlatformId.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/PDFViewer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Screensaver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/SystemData.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/UIModeController.cpp
|
||||
|
@ -228,6 +230,7 @@ elseif(APPLE)
|
|||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ../Resources)
|
||||
else()
|
||||
install(TARGETS emulationstation RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
install(TARGETS es-pdf-convert RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES Linux)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/assets/emulationstation.6.gz
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/share/man/man6)
|
||||
|
|
|
@ -47,6 +47,14 @@ void MediaViewer::stopMediaViewer()
|
|||
mImages.clear();
|
||||
}
|
||||
|
||||
void MediaViewer::launchPDFViewer()
|
||||
{
|
||||
if (mGame->getManualPath() != "") {
|
||||
Window::getInstance()->stopMediaViewer();
|
||||
Window::getInstance()->startPDFViewer(mGame);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaViewer::update(int deltaTime)
|
||||
{
|
||||
if (mVideo)
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
|
||||
bool startMediaViewer(FileData* game) override;
|
||||
void stopMediaViewer() override;
|
||||
void launchPDFViewer() override;
|
||||
|
||||
void update(int deltaTime) override;
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
|
268
es-app/src/PDFViewer.cpp
Normal file
268
es-app/src/PDFViewer.cpp
Normal file
|
@ -0,0 +1,268 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// PDFViewer.cpp
|
||||
//
|
||||
// Parses PDF documents using the PoDoFo library and renders pages using the Poppler
|
||||
// library via the external es-pdf-convert binary.
|
||||
//
|
||||
|
||||
#include "PDFViewer.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "Sound.h"
|
||||
#include "utils/FileSystemUtil.h"
|
||||
|
||||
#define DEBUG_PDF_CONVERSION false
|
||||
|
||||
PDFViewer::PDFViewer()
|
||||
: mRenderer {Renderer::getInstance()}
|
||||
{
|
||||
Window::getInstance()->setPDFViewer(this);
|
||||
mTexture = TextureResource::get("");
|
||||
mPages.clear();
|
||||
mPageImage.reset();
|
||||
}
|
||||
|
||||
bool PDFViewer::startPDFViewer(FileData* game)
|
||||
{
|
||||
mManualPath = game->getManualPath();
|
||||
|
||||
if (!Utils::FileSystem::exists(mManualPath)) {
|
||||
LOG(LogError) << "No PDF manual found for game \"" << game->getName() << "\"";
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(LogDebug) << "PDFViewer::startPDFViewer(): Opening document \"" << mManualPath << "\"";
|
||||
|
||||
PoDoFo::PdfMemDocument pdf;
|
||||
mPages.clear();
|
||||
mPageCount = 0;
|
||||
mCurrentPage = 0;
|
||||
mScaleFactor = 1.0f;
|
||||
|
||||
try {
|
||||
pdf.Load(mManualPath.c_str());
|
||||
}
|
||||
catch (PoDoFo::PdfError& e) {
|
||||
LOG(LogError) << "PDFViewer: Couldn't load file \"" << mManualPath << "\", PoDoFo error \""
|
||||
<< e.what() << ": " << e.ErrorMessage(e.GetError()) << "\"";
|
||||
return false;
|
||||
}
|
||||
|
||||
#if (DEBUG_PDF_CONVERSION)
|
||||
PoDoFo::EPdfVersion versionEPdf {pdf.GetPdfVersion()};
|
||||
std::string version {"unknown"};
|
||||
|
||||
switch (versionEPdf) {
|
||||
case 0:
|
||||
version = "1.0";
|
||||
break;
|
||||
case 1:
|
||||
version = "1.1";
|
||||
break;
|
||||
case 2:
|
||||
version = "1.2";
|
||||
break;
|
||||
case 3:
|
||||
version = "1.3";
|
||||
break;
|
||||
case 4:
|
||||
version = "1.4";
|
||||
break;
|
||||
case 5:
|
||||
version = "1.5";
|
||||
break;
|
||||
case 6:
|
||||
version = "1.6";
|
||||
break;
|
||||
case 7:
|
||||
version = "1.7";
|
||||
break;
|
||||
default:
|
||||
version = "unknown";
|
||||
};
|
||||
|
||||
LOG(LogDebug) << "PDF version: " << version;
|
||||
LOG(LogDebug) << "Page count: " << pdf.GetPageCount();
|
||||
#endif
|
||||
|
||||
mPageCount = static_cast<int>(pdf.GetPageCount());
|
||||
|
||||
for (int i {0}; i < mPageCount; ++i) {
|
||||
const int rotation {pdf.GetPage(i)->GetRotation()};
|
||||
const PoDoFo::PdfRect cropBox {pdf.GetPage(i)->GetCropBox()};
|
||||
float width {static_cast<float>(cropBox.GetWidth())};
|
||||
float height {static_cast<float>(cropBox.GetHeight())};
|
||||
|
||||
if (rotation != 0 && rotation != 180)
|
||||
std::swap(width, height);
|
||||
|
||||
// Maintain page aspect ratio.
|
||||
glm::vec2 textureSize {glm::vec2 {width, height}};
|
||||
const glm::vec2 targetSize {glm::vec2 {mRenderer->getScreenWidth() * mScaleFactor,
|
||||
mRenderer->getScreenHeight() * mScaleFactor}};
|
||||
glm::vec2 resizeScale {targetSize.x / textureSize.x, targetSize.y / textureSize.y};
|
||||
|
||||
if (resizeScale.x < resizeScale.y) {
|
||||
textureSize.x *= resizeScale.x;
|
||||
textureSize.y = std::min(textureSize.y * resizeScale.x, targetSize.y);
|
||||
}
|
||||
else {
|
||||
textureSize.y *= resizeScale.y;
|
||||
textureSize.x = std::min((textureSize.y / height) * width, targetSize.x);
|
||||
}
|
||||
|
||||
const int textureWidth {static_cast<int>(std::round(textureSize.x))};
|
||||
const int textureHeight {static_cast<int>(std::round(textureSize.y))};
|
||||
|
||||
#if (DEBUG_PDF_CONVERSION)
|
||||
LOG(LogDebug) << "Page " << i + 1 << ": Rotation: " << rotation << " degrees / "
|
||||
<< "Crop box width: " << width << " / "
|
||||
<< "Crop box height: " << height << " / "
|
||||
<< "Size ratio: " << width / height << " / "
|
||||
<< "Texture size: " << textureWidth << "x" << textureHeight;
|
||||
#endif
|
||||
|
||||
mPages[i + 1] = PageEntry {textureWidth, textureHeight, {}};
|
||||
}
|
||||
|
||||
mCurrentPage = 1;
|
||||
convertPage(mCurrentPage);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PDFViewer::stopPDFViewer()
|
||||
{
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
|
||||
mPages.clear();
|
||||
mPageImage.reset();
|
||||
}
|
||||
|
||||
void PDFViewer::convertPage(int pageNum)
|
||||
{
|
||||
assert(pageNum <= static_cast<int>(mPages.size()));
|
||||
|
||||
const std::string esConvertPath {Utils::FileSystem::getExePath() + "/es-pdf-convert"};
|
||||
if (!Utils::FileSystem::exists(esConvertPath)) {
|
||||
LOG(LogError) << "Couldn't find PDF conversion binary es-pdf-convert";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string command {Utils::FileSystem::getEscapedPath(esConvertPath)};
|
||||
command.append(" ")
|
||||
.append(Utils::FileSystem::getEscapedPath(mManualPath))
|
||||
.append(" ")
|
||||
.append(std::to_string(pageNum))
|
||||
.append(" ")
|
||||
.append(std::to_string(mPages[pageNum].width))
|
||||
.append(" ")
|
||||
.append(std::to_string(mPages[pageNum].height));
|
||||
|
||||
if (mPages[pageNum].imageData.empty()) {
|
||||
#if (DEBUG_PDF_CONVERSION)
|
||||
LOG(LogDebug) << "Converting page: " << mCurrentPage;
|
||||
LOG(LogDebug) << command;
|
||||
#endif
|
||||
FILE* commandPipe;
|
||||
std::array<char, 512> buffer {};
|
||||
std::string imageData;
|
||||
int returnValue;
|
||||
|
||||
if (!(commandPipe = reinterpret_cast<FILE*>(popen(command.c_str(), "r")))) {
|
||||
LOG(LogError) << "Couldn't open pipe to es-pdf-convert";
|
||||
return;
|
||||
}
|
||||
|
||||
while (fread(buffer.data(), 1, 512, commandPipe)) {
|
||||
mPages[pageNum].imageData.insert(mPages[pageNum].imageData.end(),
|
||||
std::make_move_iterator(buffer.begin()),
|
||||
std::make_move_iterator(buffer.end()));
|
||||
}
|
||||
|
||||
returnValue = pclose(commandPipe);
|
||||
size_t imageDataSize {mPages[pageNum].imageData.size()};
|
||||
|
||||
if (returnValue != 0 || (static_cast<int>(imageDataSize) <
|
||||
mPages[pageNum].width * mPages[pageNum].height * 4)) {
|
||||
LOG(LogError) << "Error reading PDF file";
|
||||
mPages[pageNum].imageData.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
#if (DEBUG_PDF_CONVERSION)
|
||||
LOG(LogDebug) << "Using cached texture for page: " << mCurrentPage;
|
||||
#endif
|
||||
}
|
||||
|
||||
mPageImage.reset();
|
||||
mPageImage = std::make_unique<ImageComponent>(false, false);
|
||||
mPageImage->setOrigin(0.5f, 0.5f);
|
||||
mPageImage->setPosition(mRenderer->getScreenWidth() / 2.0f,
|
||||
mRenderer->getScreenHeight() / 2.0f);
|
||||
|
||||
mPageImage->setFlipY(true);
|
||||
mPageImage->setMaxSize(
|
||||
glm::vec2 {mPages[pageNum].width / mScaleFactor, mPages[pageNum].height / mScaleFactor});
|
||||
mPageImage->setRawImage(reinterpret_cast<const unsigned char*>(&mPages[pageNum].imageData[0]),
|
||||
mPages[pageNum].width, mPages[pageNum].height);
|
||||
|
||||
#if (DEBUG_PDF_CONVERSION)
|
||||
LOG(LogDebug) << "ABGR32 data stream size: " << mPages[pageNum].imageData.size();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PDFViewer::render(const glm::mat4& /*parentTrans*/)
|
||||
{
|
||||
glm::mat4 trans {Renderer::getIdentity()};
|
||||
mRenderer->setMatrix(trans);
|
||||
|
||||
// Render a black background below the document.
|
||||
mRenderer->drawRect(0.0f, 0.0f, Renderer::getScreenWidth(), Renderer::getScreenHeight(),
|
||||
0x000000FF, 0x000000FF);
|
||||
|
||||
if (mPageImage != nullptr) {
|
||||
mPageImage->render(trans);
|
||||
}
|
||||
}
|
||||
|
||||
void PDFViewer::showNextPage()
|
||||
{
|
||||
if (mCurrentPage == mPageCount)
|
||||
return;
|
||||
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
|
||||
++mCurrentPage;
|
||||
convertPage(mCurrentPage);
|
||||
}
|
||||
|
||||
void PDFViewer::showPreviousPage()
|
||||
{
|
||||
if (mCurrentPage == 1)
|
||||
return;
|
||||
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
|
||||
--mCurrentPage;
|
||||
convertPage(mCurrentPage);
|
||||
}
|
||||
|
||||
void PDFViewer::showFirstPage()
|
||||
{
|
||||
if (mCurrentPage == 1)
|
||||
return;
|
||||
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
|
||||
mCurrentPage = 1;
|
||||
convertPage(mCurrentPage);
|
||||
}
|
||||
|
||||
void PDFViewer::showLastPage()
|
||||
{
|
||||
if (mCurrentPage == mPageCount)
|
||||
return;
|
||||
|
||||
NavigationSounds::getInstance().playThemeNavigationSound(SCROLLSOUND);
|
||||
mCurrentPage = mPageCount;
|
||||
convertPage(mCurrentPage);
|
||||
}
|
56
es-app/src/PDFViewer.h
Normal file
56
es-app/src/PDFViewer.h
Normal file
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
//
|
||||
// EmulationStation Desktop Edition
|
||||
// PDFViewer.h
|
||||
//
|
||||
// Parses PDF documents using the PoDoFo library and renders pages using the Poppler
|
||||
// library via the external es-pdf-convert binary.
|
||||
//
|
||||
|
||||
#ifndef ES_APP_PDF_VIEWER_H
|
||||
#define ES_APP_PDF_VIEWER_H
|
||||
|
||||
#include "FileData.h"
|
||||
#include "Window.h"
|
||||
#include "components/ImageComponent.h"
|
||||
|
||||
#include <podofo/podofo.h>
|
||||
|
||||
class PDFViewer : public Window::PDFViewer
|
||||
{
|
||||
public:
|
||||
PDFViewer();
|
||||
~PDFViewer() { stopPDFViewer(); }
|
||||
|
||||
bool startPDFViewer(FileData* game) override;
|
||||
void stopPDFViewer() override;
|
||||
|
||||
void convertPage(int pageNum);
|
||||
|
||||
void render(const glm::mat4& parentTrans) override;
|
||||
|
||||
private:
|
||||
void showNextPage() override;
|
||||
void showPreviousPage() override;
|
||||
void showFirstPage() override;
|
||||
void showLastPage() override;
|
||||
|
||||
struct PageEntry {
|
||||
int width;
|
||||
int height;
|
||||
std::vector<char> imageData;
|
||||
};
|
||||
|
||||
Renderer* mRenderer;
|
||||
std::shared_ptr<TextureResource> mTexture;
|
||||
std::unique_ptr<ImageComponent> mPageImage;
|
||||
std::map<int, PageEntry> mPages;
|
||||
|
||||
float mScaleFactor;
|
||||
int mCurrentPage;
|
||||
int mPageCount;
|
||||
|
||||
std::string mManualPath;
|
||||
};
|
||||
|
||||
#endif // ES_APP_PDF_VIEWER_H
|
|
@ -25,6 +25,7 @@
|
|||
#include "Log.h"
|
||||
#include "MameNames.h"
|
||||
#include "MediaViewer.h"
|
||||
#include "PDFViewer.h"
|
||||
#include "Screensaver.h"
|
||||
#include "Scripting.h"
|
||||
#include "Settings.h"
|
||||
|
@ -725,6 +726,7 @@ int main(int argc, char* argv[])
|
|||
CollectionSystemsManager::getInstance();
|
||||
Screensaver screensaver;
|
||||
MediaViewer mediaViewer;
|
||||
PDFViewer pdfViewer;
|
||||
GuiLaunchScreen guiLaunchScreen;
|
||||
|
||||
if (!window->init()) {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# EmulationStation Desktop Edition
|
||||
# CMakeLists.txt (es-core)
|
||||
#
|
||||
# CMake configuration for es-core.
|
||||
# CMake configuration for es-core
|
||||
#
|
||||
|
||||
project(core)
|
||||
|
|
|
@ -29,6 +29,7 @@ Window::Window() noexcept
|
|||
, mBackgroundOverlayOpacity {1.0f}
|
||||
, mScreensaver {nullptr}
|
||||
, mMediaViewer {nullptr}
|
||||
, mPDFViewer {nullptr}
|
||||
, mLaunchScreen {nullptr}
|
||||
, mInfoPopup {nullptr}
|
||||
, mListScrollOpacity {0.0f}
|
||||
|
@ -40,6 +41,7 @@ Window::Window() noexcept
|
|||
, mRenderScreensaver {false}
|
||||
, mRenderMediaViewer {false}
|
||||
, mRenderLaunchScreen {false}
|
||||
, mRenderPDFViewer {false}
|
||||
, mGameLaunchedState {false}
|
||||
, mAllowTextScrolling {true}
|
||||
, mAllowFileAnimation {true}
|
||||
|
@ -219,16 +221,35 @@ void Window::input(InputConfig* config, Input input)
|
|||
}
|
||||
|
||||
if (mMediaViewer && mRenderMediaViewer) {
|
||||
if (config->isMappedLike("right", input) && input.value != 0)
|
||||
if (config->isMappedLike("y", input) && input.value != 0) {
|
||||
mMediaViewer->launchPDFViewer();
|
||||
return;
|
||||
}
|
||||
else if (config->isMappedLike("right", input) && input.value != 0)
|
||||
mMediaViewer->showNext();
|
||||
else if (config->isMappedLike("left", input) && input.value != 0)
|
||||
mMediaViewer->showPrevious();
|
||||
else if (input.value != 0)
|
||||
// Any other input than left or right stops the media viewer.
|
||||
// Any other input stops the media viewer.
|
||||
stopMediaViewer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPDFViewer && mRenderPDFViewer) {
|
||||
if (config->isMappedLike("right", input) && input.value != 0)
|
||||
mPDFViewer->showNextPage();
|
||||
else if (config->isMappedLike("left", input) && input.value != 0)
|
||||
mPDFViewer->showPreviousPage();
|
||||
else if (config->isMappedLike("righttrigger", input) && input.value != 0)
|
||||
mPDFViewer->showLastPage();
|
||||
else if (config->isMappedLike("lefttrigger", input) && input.value != 0)
|
||||
mPDFViewer->showFirstPage();
|
||||
else if (input.value != 0)
|
||||
// Any other input stops the PDF viewer.
|
||||
stopPDFViewer();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mGameLaunchedState && mLaunchScreen && mRenderLaunchScreen) {
|
||||
if (input.value != 0) {
|
||||
mLaunchScreen->closeLaunchScreen();
|
||||
|
@ -654,6 +675,9 @@ void Window::render()
|
|||
if (mRenderMediaViewer)
|
||||
mMediaViewer->render(trans);
|
||||
|
||||
if (mRenderPDFViewer)
|
||||
mPDFViewer->render(trans);
|
||||
|
||||
if (mRenderLaunchScreen)
|
||||
mLaunchScreen->render(trans);
|
||||
|
||||
|
@ -858,6 +882,29 @@ void Window::stopMediaViewer()
|
|||
mRenderMediaViewer = false;
|
||||
}
|
||||
|
||||
void Window::startPDFViewer(FileData* game)
|
||||
{
|
||||
if (mPDFViewer) {
|
||||
if (mPDFViewer->startPDFViewer(game)) {
|
||||
setAllowTextScrolling(false);
|
||||
setAllowFileAnimation(false);
|
||||
|
||||
mRenderPDFViewer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::stopPDFViewer()
|
||||
{
|
||||
if (mPDFViewer) {
|
||||
mPDFViewer->stopPDFViewer();
|
||||
setAllowTextScrolling(true);
|
||||
setAllowFileAnimation(true);
|
||||
}
|
||||
|
||||
mRenderPDFViewer = false;
|
||||
}
|
||||
|
||||
void Window::displayLaunchScreen(FileData* game)
|
||||
{
|
||||
if (mLaunchScreen) {
|
||||
|
|
|
@ -56,6 +56,7 @@ public:
|
|||
public:
|
||||
virtual bool startMediaViewer(FileData* game) = 0;
|
||||
virtual void stopMediaViewer() = 0;
|
||||
virtual void launchPDFViewer() = 0;
|
||||
|
||||
virtual void showNext() = 0;
|
||||
virtual void showPrevious() = 0;
|
||||
|
@ -64,6 +65,20 @@ public:
|
|||
virtual void render(const glm::mat4& parentTrans) = 0;
|
||||
};
|
||||
|
||||
class PDFViewer
|
||||
{
|
||||
public:
|
||||
virtual bool startPDFViewer(FileData* game) = 0;
|
||||
virtual void stopPDFViewer() = 0;
|
||||
|
||||
virtual void showNextPage() = 0;
|
||||
virtual void showPreviousPage() = 0;
|
||||
virtual void showFirstPage() = 0;
|
||||
virtual void showLastPage() = 0;
|
||||
|
||||
virtual void render(const glm::mat4& parentTrans) = 0;
|
||||
};
|
||||
|
||||
class GuiLaunchScreen
|
||||
{
|
||||
public:
|
||||
|
@ -124,6 +139,11 @@ public:
|
|||
void setMediaViewer(MediaViewer* mediaViewer) { mMediaViewer = mediaViewer; }
|
||||
bool isMediaViewerActive() { return mRenderMediaViewer; }
|
||||
|
||||
void startPDFViewer(FileData* game);
|
||||
void stopPDFViewer();
|
||||
void setPDFViewer(PDFViewer* pdfViewer) { mPDFViewer = pdfViewer; }
|
||||
bool isPDFViewerActive() { return mRenderPDFViewer; }
|
||||
|
||||
void displayLaunchScreen(FileData* game);
|
||||
void closeLaunchScreen();
|
||||
void setLaunchScreen(GuiLaunchScreen* launchScreen) { mLaunchScreen = launchScreen; }
|
||||
|
@ -180,6 +200,7 @@ private:
|
|||
|
||||
Screensaver* mScreensaver;
|
||||
MediaViewer* mMediaViewer;
|
||||
PDFViewer* mPDFViewer;
|
||||
GuiLaunchScreen* mLaunchScreen;
|
||||
GuiInfoPopup* mInfoPopup;
|
||||
|
||||
|
@ -200,6 +221,7 @@ private:
|
|||
bool mRenderScreensaver;
|
||||
bool mRenderMediaViewer;
|
||||
bool mRenderLaunchScreen;
|
||||
bool mRenderPDFViewer;
|
||||
bool mGameLaunchedState;
|
||||
bool mAllowTextScrolling;
|
||||
bool mAllowFileAnimation;
|
||||
|
|
16
es-pdf-converter/CMakeLists.txt
Normal file
16
es-pdf-converter/CMakeLists.txt
Normal file
|
@ -0,0 +1,16 @@
|
|||
# SPDX-License-Identifier: MIT
|
||||
#
|
||||
# EmulationStation Desktop Edition
|
||||
# CMakeLists.txt (es-pdf-converter)
|
||||
#
|
||||
# CMake configuration for es-pdf-convert
|
||||
#
|
||||
|
||||
project(es-pdf-convert)
|
||||
|
||||
find_package(Poppler REQUIRED COMPONENTS cpp)
|
||||
|
||||
include_directories(${POPPLER_CPP_INCLUDE_DIR})
|
||||
add_executable(es-pdf-convert ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
|
||||
target_link_libraries(es-pdf-convert ${POPPLER_CPP_LIBRARY})
|
||||
set_target_properties(es-pdf-convert PROPERTIES INSTALL_RPATH_USE_LINK_PATH TRUE)
|
88
es-pdf-converter/src/main.cpp
Normal file
88
es-pdf-converter/src/main.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
//
|
||||
// EmulationStation Desktop Edition (ES-DE) PDF converter
|
||||
// main.cpp
|
||||
//
|
||||
// Converts PDF document pages to raw ARGB32 image data for maximum performance.
|
||||
// This needs to be separated into its own binary to get around the restrictive GPL
|
||||
// license used by the Poppler PDF rendering library.
|
||||
//
|
||||
// The column limit is 100 characters.
|
||||
// All ES-DE C++ source code is formatted using clang-format.
|
||||
//
|
||||
|
||||
#include "poppler-document.h"
|
||||
#include "poppler-image.h"
|
||||
#include "poppler-page-renderer.h"
|
||||
#include "poppler-page.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
if (argc != 5) {
|
||||
std::cout << "Usage: es-pdf-convert <filename> <page number> <horizontal resolution> "
|
||||
"<vertical resolution>"
|
||||
<< std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
const std::string path {argv[1]};
|
||||
const int pageNum {atoi(argv[2])};
|
||||
const int width {atoi(argv[3])};
|
||||
const int height {atoi(argv[4])};
|
||||
|
||||
if (width < 1 || width > 7680) {
|
||||
std::cerr << "Invalid horizontal resolution defined: " << argv[3] << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if (height < 1 || height > 7680) {
|
||||
std::cerr << "Invalid vertical resolution defined: " << argv[4] << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// std::cerr << "Converting file \"" << path << "\", page " << pageNum << " to resolution "
|
||||
// << width << "x" << height << " pixels" << std::endl;
|
||||
|
||||
const poppler::document* document {poppler::document::load_from_file(path)};
|
||||
|
||||
if (document == nullptr)
|
||||
exit(-1);
|
||||
|
||||
if (pageNum < 1 || pageNum > document->pages()) {
|
||||
std::cerr << "Error: Requested page " << pageNum << " does not exist in document"
|
||||
<< std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
const poppler::page* page {document->create_page(pageNum - 1)};
|
||||
poppler::page_renderer pageRenderer;
|
||||
|
||||
pageRenderer.set_render_hint(poppler::page_renderer::text_antialiasing);
|
||||
pageRenderer.set_render_hint(poppler::page_renderer::antialiasing);
|
||||
// pageRenderer.set_render_hint(poppler::page_renderer::text_hinting);
|
||||
|
||||
const poppler::rectf pageRect {page->page_rect()};
|
||||
const bool portraitOrientation {page->orientation() == poppler::page::portrait};
|
||||
const double pageHeight {pageRect.height()};
|
||||
const double sizeFactor {static_cast<double>(portraitOrientation ? height : width) /
|
||||
pageHeight};
|
||||
|
||||
poppler::image image {pageRenderer.render_page(
|
||||
page, static_cast<int>(std::round(72.0 * sizeFactor)),
|
||||
static_cast<int>(std::round(72.0 * sizeFactor)), 0, 0, width, height)};
|
||||
|
||||
if (!image.is_valid()) {
|
||||
std::cerr << "Rendered image is invalid" << std::endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// Necessary as the image data stream may contain null characters.
|
||||
std::string imageARGB32;
|
||||
imageARGB32.insert(0, std::move(image.data()), width * height * 4);
|
||||
|
||||
std::cout << imageARGB32;
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue