diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..b5f2ef40 --- /dev/null +++ b/.envrc @@ -0,0 +1,4 @@ +# Optional: Automatically load Nix development environment when entering this directory +# Requires direnv (https://direnv.net/) and nix-direnv for automatic shell activation +# If you don't use direnv, run 'nix develop' manually instead +use flake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 55dadc9a..e76d76a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,7 @@ on: permissions: contents: write jobs: - build: + build-windows: runs-on: windows-latest steps: - name: Checkout repository @@ -64,11 +64,51 @@ jobs: - name: Upload artifacts uses: actions/upload-artifact@v4 with: - name: paperback-build + name: paperback-windows path: | build/paperback.zip build/paperback_setup.exe retention-days: 30 + + build-linux-flatpak: + runs-on: ubuntu-latest + container: + image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-49 + options: --privileged + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - name: Generate cargo sources for Flatpak + run: | + pip install aiohttp tomlkit + curl -sSL https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py -o flatpak-cargo-generator.py + python3 flatpak-cargo-generator.py lib/Cargo.lock -o cargo-sources.json + - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 + with: + bundle: paperback.flatpak + manifest-path: io.github.trypsynth.Paperback.yaml + cache-key: flatpak-builder-${{ github.sha }} + + release: + needs: [build-windows, build-linux-flatpak] + runs-on: ubuntu-latest + if: github.event_name == 'push' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Download Windows artifacts + uses: actions/download-artifact@v4 + with: + name: paperback-windows + path: windows-build + - name: Download Linux Flatpak + uses: actions/download-artifact@v4 + with: + name: paperback-x86_64.flatpak + path: linux-build - name: Get latest tag reachable from HEAD id: get_tag shell: bash @@ -99,8 +139,9 @@ jobs: ## Commits since last release ${{ steps.release_notes.outputs.commits }} files: | - build/paperback.zip - build/paperback_setup.exe + windows-build/paperback.zip + windows-build/paperback_setup.exe + linux-build/paperback.flatpak prerelease: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index d5b66605..d4749946 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,9 @@ build/ lib/target/ vcpkg/bin/ web/_site/ +result +.direnv/ +.flatpak-builder/ +*.flatpak +repo-flatpak/ +cargo-sources.json \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index cdcc88b2..0adfe3f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,11 @@ cmake_minimum_required(VERSION 3.21) if(POLICY CMP0144) cmake_policy(SET CMP0144 NEW) endif() + +# Option to use system libraries instead of vcpkg (for Nix, Linux distros, etc.) +option(USE_SYSTEM_LIBS "Use system libraries instead of vcpkg" OFF) + +if(NOT USE_SYSTEM_LIBS) set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg/bin/scripts/buildsystems/vcpkg.cmake" CACHE STRING "Vcpkg toolchain file") set(VCPKG_MANIFEST_DIR "${CMAKE_SOURCE_DIR}/vcpkg") set(VCPKG_OVERLAY_TRIPLETS "${CMAKE_SOURCE_DIR}/vcpkg/triplets") @@ -12,6 +17,7 @@ elseif(APPLE) else() set(VCPKG_TARGET_TRIPLET "x64-linux" CACHE STRING "Vcpkg triplet") endif() +endif() project(paperback VERSION 0.6.1 LANGUAGES CXX) if("${PROJECT_VERSION_TWEAK}" STREQUAL "") @@ -29,8 +35,28 @@ if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() -find_package(Gettext REQUIRED) -find_package(wxWidgets CONFIG REQUIRED COMPONENTS webview) +find_program(CLANG_FORMAT_EXE NAMES clang-format) +if(CLANG_FORMAT_EXE) + file(GLOB_RECURSE ALL_SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/app/*.cpp ${CMAKE_SOURCE_DIR}/app/*.hpp) + add_custom_target(format COMMAND ${CLANG_FORMAT_EXE} -i ${ALL_SOURCE_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +endif() +find_program(CLANG_TIDY_EXE NAMES clang-tidy) +if(CLANG_TIDY_EXE) + file(GLOB_RECURSE ALL_SOURCE_FILES CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/app/*.cpp ${CMAKE_SOURCE_DIR}/app/*.hpp) + add_custom_target(lint COMMAND ${CLANG_TIDY_EXE} -warnings-as-errors=* --header-filter=^${CMAKE_SOURCE_DIR}/app/ --extra-arg=-std=c++20 ${ALL_SOURCE_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} USES_TERMINAL) +endif() + +if(USE_SYSTEM_LIBS) + # Find system libraries (for Nix, system package managers, etc.) + # Most dependencies are now handled by the Rust library + find_package(wxWidgets REQUIRED base core net webview) + find_library(PDFIUM_LIBRARY NAMES pdfium REQUIRED) + find_path(PDFIUM_INCLUDE_DIR NAMES fpdfview.h REQUIRED) +else() + find_package(wxWidgets CONFIG REQUIRED COMPONENTS webview) +endif() + +find_package(Gettext) if(GETTEXT_FOUND) find_program(XGETTEXT_EXECUTABLE NAMES xgettext) @@ -149,17 +175,33 @@ else() endif() add_dependencies(paperback libpaperback_rust) target_include_directories(paperback PRIVATE ${CMAKE_SOURCE_DIR}/app ${CMAKE_SOURCE_DIR} ${PAPERBACK_GENERATED_DIR} ${RUST_TARGET_DIR}/cxxbridge) -target_link_directories(paperback PRIVATE ${PDFIUM_LIB_DIR}) -target_link_directories(paperback PRIVATE "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib") -target_link_libraries(paperback PRIVATE - ${RUST_LIB_OUTPUT} - pdfium - wx::base - wx::core - wx::net - wx::webview -) -if (WIN32) + +if(USE_SYSTEM_LIBS) + target_include_directories(paperback PRIVATE + ${PDFIUM_INCLUDE_DIR} + ${wxWidgets_INCLUDE_DIRS} + ) + target_compile_options(paperback PRIVATE ${wxWidgets_CXX_FLAGS}) + target_compile_definitions(paperback PRIVATE ${wxWidgets_DEFINITIONS}) + target_link_libraries(paperback PRIVATE + ${RUST_LIB_OUTPUT} + ${PDFIUM_LIBRARY} + ${wxWidgets_LIBRARIES} + ) +else() + target_link_directories(paperback PRIVATE ${PDFIUM_LIB_DIR}) + target_link_directories(paperback PRIVATE "${CMAKE_BINARY_DIR}/vcpkg_installed/${VCPKG_TARGET_TRIPLET}/lib") + target_link_libraries(paperback PRIVATE + ${RUST_LIB_OUTPUT} + pdfium + wx::base + wx::core + wx::net + wx::webview + ) +endif() + +if(WIN32) target_link_libraries(paperback PRIVATE bcrypt ntdll) endif() @@ -238,3 +280,37 @@ if(WIN32) else() add_custom_target(release DEPENDS package) endif() + +# Installation rules (Linux desktop integration) +if(UNIX AND NOT APPLE) + # Install binary + install(TARGETS paperback DESTINATION bin) + + # Install desktop file + install(FILES ${CMAKE_SOURCE_DIR}/paperback.desktop + DESTINATION share/applications) + + # Install icons + foreach(size 16 32 48 64 128 256) + install(FILES ${CMAKE_SOURCE_DIR}/icons/hicolor/${size}x${size}/apps/paperback.png + DESTINATION share/icons/hicolor/${size}x${size}/apps) + endforeach() + + # Install documentation + if(TARGET doc) + install(FILES ${CMAKE_BINARY_DIR}/readme.html + DESTINATION share/doc/paperback) + endif() + + # Install translations + if(MO_FILES) + foreach(mo_file ${MO_FILES}) + get_filename_component(mo_dir ${mo_file} DIRECTORY) + get_filename_component(lang_dir ${mo_dir} DIRECTORY) + get_filename_component(lang ${lang_dir} NAME) + install(FILES ${mo_file} + DESTINATION share/locale/${lang}/LC_MESSAGES + RENAME paperback.mo) + endforeach() + endif() +endif() diff --git a/README.md b/README.md index 366c04b0..f906f264 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,8 @@ ## Building +### Windows (VCPKG) + We use VCPKG for managing dependencies. Currently we manage our own VCPKG installation through a submodule. As such, make sure to clone Paperback recursively: ```batch @@ -45,6 +47,50 @@ Optional tools: * `gettext` tools (`xgettext`, `msgfmt`, `msgmerge`) on your `PATH` to generate the translation template and compile translations. * InnoSetup installed to create the installer with the `release` target. +### Linux + +For building with CMake, you'll need CMake 3.21+, a C++20 compiler, and dependencies: +- wxWidgets 3.2+ +- chmlib, lexbor, mbedtls, pdfium, pugixml, nlohmann-json + +```bash +cmake -B build -DUSE_SYSTEM_LIBS=ON -DCMAKE_BUILD_TYPE=Release +cmake --build build +sudo cmake --install build +``` + +Optional tools: +- `pandoc` for HTML readme generation +- `gettext` tools for translations + +### Linux (Nix) + +**Run directly:** +```bash +nix run github:trypsynth/paperback +``` + +**Install to profile:** +```bash +nix profile install github:trypsynth/paperback +``` + +**Build from source:** +```bash +# Clone repository +git clone --recursive https://github.com/trypsynth/paperback +cd paperback + +# Build and run +nix run .#paperback + +# Or build specific outputs: +nix build .#paperback # Nix derivation + +# Build Flatpak: +flatpak-builder --force-clean --repo=repo build io.github.trypsynth.Paperback.yaml +``` + ## Contributing Contributions are welcome! Whether through issues, pull requests, discussions, or other means, your interest is most certainly appreciated. diff --git a/app/config_manager.cpp b/app/config_manager.cpp index af53b5ec..46fe9e3c 100644 --- a/app/config_manager.cpp +++ b/app/config_manager.cpp @@ -20,9 +20,9 @@ wxArrayString to_wx_array(const rust::Vec& rust_vec) { return result; } -std::vector to_long_vector(const rust::Vec& values) { +std::vector to_long_vector(const rust::Vec& values) { std::vector result(values.size()); - std::transform(values.begin(), values.end(), result.begin(), [](long long value) { + std::transform(values.begin(), values.end(), result.begin(), [](std::int64_t value) { return static_cast(value); }); return result; @@ -116,10 +116,10 @@ long config_manager::get_document_position(const wxString& path) const { void config_manager::set_navigation_history(const wxString& path, const std::vector& history, size_t history_index) { if (!is_initialized()) return; - rust::Vec rust_history; + rust::Vec rust_history; rust_history.reserve(history.size()); std::transform(history.begin(), history.end(), std::back_inserter(rust_history), [](long entry) { - return static_cast(entry); + return static_cast(entry); }); rust::Slice history_slice(rust_history.data(), rust_history.size()); config_manager_set_navigation_history(backend_mut(), to_utf8(path), history_slice, history_index); diff --git a/app/dialogs.cpp b/app/dialogs.cpp index 2097c452..802c759f 100644 --- a/app/dialogs.cpp +++ b/app/dialogs.cpp @@ -433,7 +433,7 @@ void bookmark_dialog::repopulate_list(long current_pos) { } } -document_info_dialog::document_info_dialog(wxWindow* parent, session_document* session_doc, const wxString& file_path, config_manager& cfg_mgr) : dialog(parent, _("Document Info"), dialog_button_config::ok_only), config_mgr{cfg_mgr}, doc_path{file_path} { +document_info_dialog::document_info_dialog(wxWindow* parent, session_document* session_doc, const wxString& file_path, [[maybe_unused]] config_manager& cfg_mgr) : dialog(parent, _("Document Info"), dialog_button_config::ok_only), doc_path{file_path} { constexpr int info_width = 600; constexpr int info_height = 400; info_text_ctrl = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(info_width, info_height), wxTE_MULTILINE | wxTE_READONLY); diff --git a/app/dialogs.hpp b/app/dialogs.hpp index 273aca6c..5e50dac0 100644 --- a/app/dialogs.hpp +++ b/app/dialogs.hpp @@ -130,7 +130,6 @@ class document_info_dialog : public dialog { private: wxTextCtrl* info_text_ctrl{nullptr}; - config_manager& config_mgr; wxString doc_path; }; diff --git a/app/document_manager.cpp b/app/document_manager.cpp index 316f53a8..6758b0ca 100644 --- a/app/document_manager.cpp +++ b/app/document_manager.cpp @@ -38,32 +38,12 @@ bool supports_feature(uint32_t flags, uint32_t feature) { return (flags & feature) != 0; } -constexpr uint32_t PARSER_SUPPORTS_SECTIONS = 1 << 0; constexpr uint32_t PARSER_SUPPORTS_TOC = 1 << 1; -constexpr uint32_t PARSER_SUPPORTS_PAGES = 1 << 2; -constexpr uint32_t PARSER_SUPPORTS_LISTS = 1 << 3; int to_rust_marker(marker_type type) { return static_cast(type); } -std::vector to_long_vector(const rust::Vec& values) { - std::vector result(values.size()); - std::transform(values.begin(), values.end(), result.begin(), [](long long value) { - return static_cast(value); - }); - return result; -} - -rust::Vec to_rust_history(const std::vector& history) { - rust::Vec rust_history; - rust_history.reserve(history.size()); - std::transform(history.begin(), history.end(), std::back_inserter(rust_history), [](long value) { - return static_cast(value); - }); - return rust_history; -} - void populate_toc_items(std::vector>& toc_items, const rust::Vec& ffi_toc_items) { if (ffi_toc_items.empty()) return; std::vector item_ptrs; @@ -92,12 +72,6 @@ void populate_toc_items(std::vector>& toc_items, const } } -void ensure_toc_loaded(session_document& session_doc) { - if (session_doc.toc_loaded) return; - session_doc.toc_loaded = true; - const DocumentHandle& handle = session_doc.get_handle(); - populate_toc_items(session_doc.toc_items, document_toc_items_with_parents(handle)); -} } // namespace void session_document::ensure_toc_loaded() { @@ -184,9 +158,9 @@ bool document_manager::create_document_tab(const wxString& path, bool set_focus, size_t history_index = 0; config.get_navigation_history(path, history, history_index); if (!history.empty()) { - rust::Vec rust_history; + rust::Vec rust_history; rust_history.reserve(history.size()); - for (long pos : history) rust_history.push_back(static_cast(pos)); + for (long pos : history) rust_history.push_back(static_cast(pos)); rust::Slice history_slice(rust_history.data(), rust_history.size()); session_set_history(*session, history_slice, history_index); } diff --git a/app/main_window.cpp b/app/main_window.cpp index 50fd4be9..ab826ae6 100644 --- a/app/main_window.cpp +++ b/app/main_window.cpp @@ -8,13 +8,14 @@ #include "translation_manager.hpp" #include "utils.hpp" #include +#include #include #include #include #include #include -main_window::main_window() : wxFrame(nullptr, wxID_ANY, APP_NAME), task_bar_icon_{new task_bar_icon(this)}, position_save_timer{std::make_unique(this)}, status_update_timer{std::make_unique(this)}, sleep_timer{std::make_unique(this)}, sleep_status_update_timer{std::make_unique(this)} { +main_window::main_window() : wxFrame(nullptr, wxID_ANY, APP_NAME), position_save_timer{std::make_unique(this)}, status_update_timer{std::make_unique(this)}, task_bar_icon_{new task_bar_icon(this)}, sleep_timer{std::make_unique(this)}, sleep_status_update_timer{std::make_unique(this)} { auto* const panel = new wxPanel(this); notebook = new wxNotebook(panel, wxID_ANY); #ifdef __WXMSW__ @@ -302,7 +303,11 @@ void main_window::on_iconize(wxIconizeEvent& event) { auto& config_mgr = wxGetApp().get_config_manager(); if (config_mgr.get(config_manager::minimize_to_tray)) { Hide(); +#ifdef __WXGTK__ + task_bar_icon_->SetIcon(wxArtProvider::GetIcon(wxART_INFORMATION, wxART_OTHER), APP_NAME); +#else task_bar_icon_->SetIcon(wxICON(wxICON_INFORMATION), APP_NAME); +#endif } } event.Skip(); @@ -1022,9 +1027,9 @@ void main_window::on_show_all_documents(wxCommandEvent&) { return; } wxArrayString open_docs; - for (size_t i = 0; i < doc_manager->get_tab_count(); ++i) { - if (doc_manager->get_tab(static_cast(i)) != nullptr) - open_docs.Add(doc_manager->get_tab(static_cast(i))->file_path); + for (int i = 0; i < doc_manager->get_tab_count(); ++i) { + if (doc_manager->get_tab(i) != nullptr) + open_docs.Add(doc_manager->get_tab(i)->file_path); } all_documents_dialog dlg(this, config_mgr, open_docs); if (dlg.ShowModal() == wxID_OK) { @@ -1064,7 +1069,7 @@ void main_window::update_recent_documents_menu() { auto& config_mgr = wxGetApp().get_config_manager(); const wxArrayString recent_docs = config_mgr.get_recent_documents(); size_t menu_count = 0; - for (size_t i = 0; i < recent_docs.GetCount() && menu_count < config_mgr.get(config_manager::recent_documents_to_show); ++i) { + for (size_t i = 0; i < recent_docs.GetCount() && menu_count < static_cast(config_mgr.get(config_manager::recent_documents_to_show)); ++i) { const wxString& path = recent_docs[i]; const wxString filename = wxFileName(path).GetFullName(); const wxString menu_text = wxString::Format("&%zu %s", menu_count + 1, filename); diff --git a/doc/readme.md b/doc/readme.md index 807804c6..d5fe439d 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -3,7 +3,9 @@ Paperback is a lightweight, fast, and accessible ebook/document reader designed to make reading fun and seamless, regardless of the file format being used or the user's preferences. ## System Requirements -Paperback currently runs on Windows 7 through Windows 11. It's possible it runs on earlier versions of Windows too and/or can be built in such a way that it will, but this hasn't been tested yet. Support for other platforms is planned for a future version. +**Windows:** Windows 7 through Windows 11. It's possible it runs on earlier versions of Windows too and/or can be built in such a way that it will, but this hasn't been tested yet. + +**Linux:** Any modern distribution with glibc 2.38+ (e.g., Ubuntu 24.04+, Fedora 39+, Arch Linux). Alternatively, use the Flatpak version for broader compatibility with older distributions. ## Features * Incredibly fast and standalone. diff --git a/flake.lock b/flake.lock new file mode 100644 index 00000000..5fd6f527 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1767379071, + "narHash": "sha256-EgE0pxsrW9jp9YFMkHL9JMXxcqi/OoumPJYwf+Okucw=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "fb7944c166a3b630f177938e478f0378e64ce108", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 00000000..fbd4f3d3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,194 @@ +{ + description = "Paperback - A lightweight, fast, and accessible ebook and document reader"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + + # Vendor cargo dependencies for offline builds + cargoVendorDir = pkgs.rustPlatform.importCargoLock { + lockFile = ./lib/Cargo.lock; + }; + + # chmlib tarball for offline libchm build + chmlibTarball = pkgs.fetchurl { + url = "http://www.jedrea.com/chmlib/chmlib-0.40.tar.bz2"; + sha256 = "3449d64b0cf71578b2c7e3ddc048d4af3661f44a83941ea074a7813f3a59ffa3"; + }; + + # Extract version from CMakeLists.txt to keep single source of truth + cmakeContent = builtins.readFile ./CMakeLists.txt; + versionMatch = builtins.match ".*project\\(paperback VERSION ([0-9.]+) LANGUAGES.*" ( + builtins.replaceStrings [ "\n" ] [ " " ] cmakeContent + ); + paperbackVersion = builtins.head versionMatch; + + # flatpak-cargo-generator for generating cargo sources for Flatpak builds + flatpak-cargo-generator = pkgs.python3Packages.buildPythonApplication { + pname = "flatpak-cargo-generator"; + version = "unstable-2024-01-01"; + format = "other"; + + src = pkgs.fetchFromGitHub { + owner = "flatpak"; + repo = "flatpak-builder-tools"; + rev = "db39dc0f75a3b24cfb09906f3aba2c13b0c48afe"; + hash = "sha256-TnGkivHjVbOCqcowWgCw+v2MIgHz+2zU5AU2PO/prFo="; + }; + + propagatedBuildInputs = with pkgs.python3Packages; [ + aiohttp + tomlkit + ]; + + installPhase = '' + install -Dm755 cargo/flatpak-cargo-generator.py $out/bin/flatpak-cargo-generator + ''; + }; + + # Main Paperback package + paperback = pkgs.clangStdenv.mkDerivation { + pname = "paperback"; + version = paperbackVersion; + + src = ./.; + + nativeBuildInputs = with pkgs; [ + cmake + ninja + pkg-config + gettext + pandoc + cargo + rustc + wrapGAppsHook3 + ]; + + buildInputs = with pkgs; [ + lerc + libdatrie + libdeflate + libepoxy + libselinux + libsepol + libsysprof-capture + libthai + libwebp + libxdmcp + libxkbcommon + xorg.libXtst + pcre2 + pdfium-binaries + util-linux + wxGTK32 + xz + zstd + gtk3 + ]; + + # Configure cargo to use vendored dependencies (no network in Nix build) + preConfigure = '' + # Copy vendor dir to writable location so we can patch libchm + # Use -L to follow symlinks, --no-preserve=mode for write access + cp -rL --no-preserve=mode ${cargoVendorDir} cargo-vendor + + # Create pdfium directory structure that lib/build.rs expects + mkdir -p build/lib/pdfium/linux-x64/lib + ln -s ${pkgs.pdfium-binaries}/lib/libpdfium.so build/lib/pdfium/linux-x64/lib/ + + # Patch libchm build.rs to use local chmlib tarball instead of downloading + cat > cargo-vendor/libchm-0.1.0/build.rs << 'BUILDRS' + use std::{env, fs, io::Cursor, path::{Path, PathBuf}}; + use bzip2::read::BzDecoder; + use cc::Build; + use tar::Archive; + + fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let chmlib_dir = out_dir.join("chmlib-0.40"); + let src_dir = chmlib_dir.join("src"); + if !chmlib_dir.exists() { + let tarball_path = env::var("CHMLIB_TARBALL").expect("CHMLIB_TARBALL must be set"); + let buf = fs::read(&tarball_path).expect("Failed to read chmlib tarball"); + let mut archive = Archive::new(BzDecoder::new(Cursor::new(buf))); + archive.unpack(&out_dir).expect("Failed to extract chmlib"); + } + let chm_lib_path = src_dir.join("chm_lib.c"); + let mut contents = fs::read_to_string(&chm_lib_path).expect("Failed to read chm_lib.c"); + contents = contents.replace( + "/* yielding an error is preferable to yielding incorrect behavior */\n#error \"Please define the sized types for your platform in chm_lib.c\"", + "typedef unsigned char UChar;\ntypedef int16_t Int16;\ntypedef uint16_t UInt16;\ntypedef int32_t Int32;\ntypedef uint32_t UInt32;\ntypedef int64_t Int64;\ntypedef uint64_t UInt64;" + ); + contents = contents.replace("#if __sun || __sgi\n#include ", "#ifdef CHMLIB_HAVE_STRINGS_H\n#include "); + fs::write(&chm_lib_path, contents).expect("Failed to write patched chm_lib.c"); + Build::new() + .file(src_dir.join("chm_lib.c")) + .file(src_dir.join("lzx.c")) + .include(&src_dir) + .warnings(false) + .define("CHMLIB_HAVE_STRINGS_H", None) + .compile("chm"); + println!("cargo:rustc-link-lib=static=chm"); + } + BUILDRS + + mkdir -p lib/.cargo + cat > lib/.cargo/config.toml << EOF + [source.crates-io] + replace-with = "vendored-sources" + + [source.vendored-sources] + directory = "$PWD/cargo-vendor" + EOF + ''; + + CHMLIB_TARBALL = chmlibTarball; + + # Enable system libraries mode (instead of vcpkg) + cmakeFlags = [ + "-DCMAKE_BUILD_TYPE=Release" + "-DUSE_SYSTEM_LIBS=ON" + ]; + + meta = with pkgs.lib; { + description = "A lightweight, fast, and accessible ebook and document reader"; + homepage = "https://github.com/trypsynth/paperback"; + license = licenses.mit; + platforms = platforms.linux; + mainProgram = "paperback"; + }; + }; + + in + { + packages = { + default = paperback; + inherit paperback; + }; + + devShells.default = pkgs.mkShell.override { stdenv = pkgs.clangStdenv; } { + inputsFrom = [ paperback ]; + packages = with pkgs; [ + nil + nixfmt-rfc-style + clang-tools + gdb + lldb + flatpak-cargo-generator + ]; + }; + } + ); +} diff --git a/icons/hicolor/128x128/apps/paperback.png b/icons/hicolor/128x128/apps/paperback.png new file mode 100644 index 00000000..0966613a Binary files /dev/null and b/icons/hicolor/128x128/apps/paperback.png differ diff --git a/icons/hicolor/16x16/apps/paperback.png b/icons/hicolor/16x16/apps/paperback.png new file mode 100644 index 00000000..5bdb744f Binary files /dev/null and b/icons/hicolor/16x16/apps/paperback.png differ diff --git a/icons/hicolor/256x256/apps/paperback.png b/icons/hicolor/256x256/apps/paperback.png new file mode 100644 index 00000000..24f5f86a Binary files /dev/null and b/icons/hicolor/256x256/apps/paperback.png differ diff --git a/icons/hicolor/32x32/apps/paperback.png b/icons/hicolor/32x32/apps/paperback.png new file mode 100644 index 00000000..44aa3635 Binary files /dev/null and b/icons/hicolor/32x32/apps/paperback.png differ diff --git a/icons/hicolor/48x48/apps/paperback.png b/icons/hicolor/48x48/apps/paperback.png new file mode 100644 index 00000000..ce887ce5 Binary files /dev/null and b/icons/hicolor/48x48/apps/paperback.png differ diff --git a/icons/hicolor/64x64/apps/paperback.png b/icons/hicolor/64x64/apps/paperback.png new file mode 100644 index 00000000..409ea873 Binary files /dev/null and b/icons/hicolor/64x64/apps/paperback.png differ diff --git a/io.github.trypsynth.Paperback.yaml b/io.github.trypsynth.Paperback.yaml new file mode 100644 index 00000000..ca194399 --- /dev/null +++ b/io.github.trypsynth.Paperback.yaml @@ -0,0 +1,202 @@ +app-id: io.github.trypsynth.Paperback +runtime: org.gnome.Platform +runtime-version: '49' +sdk: org.gnome.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.rust-stable +command: paperback + +finish-args: + # Filesystem access to open documents + - --filesystem=home + - --filesystem=xdg-documents + - --filesystem=xdg-download + # X11 and Wayland display + - --socket=x11 + - --socket=wayland + # GPU acceleration + - --device=dri + # Access to network for update checks and external links + - --share=network + # IPC for accessibility + - --share=ipc + +cleanup: + - /include + - /lib/cmake + - /lib/pkgconfig + - /share/bakefile + - /share/man + - /bin/wx-config + - '*.la' + - '*.a' + +modules: + # wxWidgets 3.2.8.1 - GUI toolkit + - name: wxwidgets + sources: + - type: archive + url: https://github.com/wxWidgets/wxWidgets/releases/download/v3.2.8.1/wxWidgets-3.2.8.1.tar.bz2 + sha256: ad0cf6c18815dcf1a6a89ad3c3d21a306cd7b5d99a602f77372ef1d92cb7d756 + config-opts: + - --enable-shared + - --enable-unicode + - --with-gtk=3 + - --without-opengl + - --enable-display + - --enable-propgrid + - --enable-stc + - --with-libjpeg + - --with-libpng + - --with-zlib + - --enable-webview + - --disable-ribbon + cleanup: + - /bin/wxrc* + + # pdfium - PDF rendering (using pre-built binaries) + # Using bblanchon/pdfium-binaries since building from source requires + # Chromium's depot_tools and is extremely complex for Flatpak + # Version chromium/7520 is PDFium 144.0.7520.0, latest stable as of Nov 2025 + - name: pdfium + buildsystem: simple + sources: + - type: archive + url: https://github.com/bblanchon/pdfium-binaries/releases/download/chromium/7520/pdfium-linux-x64.tgz + sha256: e737634cca16ba28b760077b915b469bea076b71f208769c56fb96ee138e876f + build-commands: + # Install headers (flatpak-builder strips first component, headers are at top level) + - install -Dm644 *.h -t /app/include/ + - install -Dm644 cpp/*.h -t /app/include/cpp/ + # Install library (also at top level after strip-components) + - install -Dm644 libpdfium.so -t /app/lib/ + # Create pkg-config file for CMake to find pdfium + - mkdir -p /app/lib/pkgconfig + - | + cat > /app/lib/pkgconfig/pdfium.pc << 'EOF' + prefix=/app + libdir=${prefix}/lib + includedir=${prefix}/include + + Name: pdfium + Description: PDF rendering library + Version: chromium-7520 + Libs: -L${libdir} -lpdfium + Cflags: -I${includedir} + EOF + + # pandoc - Documentation build tool (build-time only) + - name: pandoc + buildsystem: simple + build-commands: + - install -Dm755 pandoc-3.8.2.1/bin/pandoc /app/bin/pandoc + cleanup: + - /bin/pandoc + sources: + - type: archive + url: https://github.com/jgm/pandoc/releases/download/3.8.2.1/pandoc-3.8.2.1-linux-amd64.tar.gz + sha256: b362815e21d8ad3629c124aa92baf54558da086ad72374b4f6fdd97b9f3275b0 + strip-components: 0 + + # paperback - Main application + - name: paperback + buildsystem: simple + build-options: + append-path: /usr/lib/sdk/rust-stable/bin:/app/bin + env: + CARGO_HOME: /run/build/paperback/cargo + CHMLIB_TARBALL: /run/build/paperback/chmlib-0.40.tar.bz2 + build-commands: + # Create pdfium directory structure that Rust build.rs expects + # This prevents it from trying to download pdfium (no network in Flatpak) + - mkdir -p build/lib/pdfium/linux-x64/lib + - ln -sf /app/lib/libpdfium.so build/lib/pdfium/linux-x64/lib/libpdfium.so + # Patch libchm's build.rs to use local chmlib tarball instead of downloading + # The crate's build.rs tries to download chmlib but Flatpak has no network + - | + cat > cargo/vendor/libchm-0.1.0/build.rs << 'PATCHEOF' + use std::{ + env, fs, + io::Cursor, + path::{Path, PathBuf}, + }; + + use bzip2::read::BzDecoder; + use cc::Build; + use tar::Archive; + + fn main() { + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); + let chmlib_dir = out_dir.join("chmlib-0.40"); + let src_dir = chmlib_dir.join("src"); + if !chmlib_dir.exists() { + extract_chmlib(&out_dir); + } + apply_patches(&src_dir); + let mut build = Build::new(); + build.file(src_dir.join("chm_lib.c")).file(src_dir.join("lzx.c")).include(&src_dir).warnings(false); + if cfg!(target_os = "windows") { + build.define("WIN32", None); + build.define("_WINDOWS", None); + } else { + build.define("CHMLIB_HAVE_STRINGS_H", None); + } + build.compile("chm"); + println!("cargo:rustc-link-lib=static=chm"); + } + + fn extract_chmlib(out_dir: &Path) { + let tarball_path = env::var("CHMLIB_TARBALL").expect("CHMLIB_TARBALL env var must be set for offline build"); + let buf = fs::read(&tarball_path).expect("Failed to read chmlib tarball"); + let decompressor = BzDecoder::new(Cursor::new(buf)); + let mut archive = Archive::new(decompressor); + archive.unpack(out_dir).expect("Failed to extract chmlib"); + } + + fn apply_patches(src_dir: &Path) { + let chm_lib_path = src_dir.join("chm_lib.c"); + let mut contents = fs::read_to_string(&chm_lib_path).expect("Failed to read chm_lib.c"); + contents = contents.replace("/* yielding an error is preferable to yielding incorrect behavior */\n#error \"Please define the sized types for your platform in chm_lib.c\"", "typedef unsigned char UChar;\ntypedef int16_t Int16;\ntypedef uint16_t UInt16;\ntypedef int32_t Int32;\ntypedef uint32_t UInt32;\ntypedef int64_t Int64;\ntypedef uint64_t UInt64;"); + contents = contents + .replace("#if __sun || __sgi\n#include ", "#ifdef CHMLIB_HAVE_STRINGS_H\n#include "); + fs::write(&chm_lib_path, contents).expect("Failed to write patched chm_lib.c"); + } + PATCHEOF + # Update .cargo-checksum.json to allow the patched file + - | + python3 -c " + import json + with open('cargo/vendor/libchm-0.1.0/.cargo-checksum.json', 'r') as f: + data = json.load(f) + data['files'] = {} + with open('cargo/vendor/libchm-0.1.0/.cargo-checksum.json', 'w') as f: + json.dump(data, f) + " + # Run cmake build + - cmake -B build -DCMAKE_BUILD_TYPE=Release -DUSE_SYSTEM_LIBS=ON -DCMAKE_INSTALL_PREFIX=/app -DwxWidgets_CONFIG_EXECUTABLE=/app/bin/wx-config + - cmake --build build + - cmake --install build + post-install: + # Rename desktop file to use app-id for Flatpak compliance + - mv /app/share/applications/paperback.desktop /app/share/applications/io.github.trypsynth.Paperback.desktop + # Update Icon field in desktop file to use app-id + - sed -i 's/Icon=paperback/Icon=io.github.trypsynth.Paperback/' /app/share/applications/io.github.trypsynth.Paperback.desktop + # Rename all icon files to use app-id for Flatpak compliance + - for size in 16 32 48 64 128 256; do mv /app/share/icons/hicolor/${size}x${size}/apps/paperback.png /app/share/icons/hicolor/${size}x${size}/apps/io.github.trypsynth.Paperback.png; done + sources: + - type: dir + path: . + - cargo-sources.json + - type: inline + contents: | + [source.crates-io] + replace-with = "vendored-sources" + + [source.vendored-sources] + directory = "cargo/vendor" + dest: cargo + dest-filename: config.toml + # chmlib tarball for offline build (libchm crate normally downloads this) + - type: file + url: http://www.jedrea.com/chmlib/chmlib-0.40.tar.bz2 + sha256: 3449d64b0cf71578b2c7e3ddc048d4af3661f44a83941ea074a7813f3a59ffa3 diff --git a/paperback.desktop b/paperback.desktop new file mode 100644 index 00000000..e253192b --- /dev/null +++ b/paperback.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Version=1.5 +Type=Application +Name=Paperback +GenericName=Document Reader +Comment=Lightweight, fast, and accessible ebook and document reader +Exec=paperback %F +Icon=paperback +Terminal=false +Categories=Office;Viewer;GTK; +Keywords=ebook;epub;pdf;document;reader;chm;accessibility;screen-reader; +StartupNotify=true +MimeType=application/epub+zip;application/pdf;application/x-chm;application/vnd.openxmlformats-officedocument.wordprocessingml.document;application/vnd.oasis.opendocument.text;application/x-fictionbook+xml;text/html;text/markdown;text/plain;application/vnd.oasis.opendocument.presentation;application/vnd.openxmlformats-officedocument.presentationml.presentation;