Skip to content

Commit 7982963

Browse files
committed
feats: add ebook support
1 parent c928556 commit 7982963

File tree

19 files changed

+852
-9
lines changed

19 files changed

+852
-9
lines changed

.github/workflows/build.yaml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ jobs:
9999
- name: Update dependencies
100100
run: |
101101
dkp-pacman --noconfirm -U $BASE_URL/hacBrewPack-3.05-1-x86_64.pkg.tar.zst
102-
for pkg in mbedtls-3.6.5-1 libssh2-1.11.1-1 dav1d-1.5.3-1 curl-8.16.0-2 ffmpeg-7.1.3-5 libmpv${{ matrix.shuffix }}-0.41.0-5 nspmini-main-1; do
102+
for pkg in mbedtls-3.6.5-1 libssh2-1.11.1-1 dav1d-1.5.3-1 curl-8.16.0-2 ffmpeg-7.1.3-5 mupdf-1.24.11-1 libmpv${{ matrix.shuffix }}-0.41.0-5 nspmini-main-1; do
103103
dkp-pacman --noconfirm -U $BASE_URL/switch-${pkg}-any.pkg.tar.zst
104104
done
105105
git config --system --add safe.directory $GITHUB_WORKSPACE
@@ -371,7 +371,7 @@ jobs:
371371
submodules: recursive
372372
- name: Install dependency
373373
run: |
374-
for pkg in glfw-3.5.0-1 curl-8.16.0-1 mpv-0.41.0-1 libwebp-1.6.0-1; do
374+
for pkg in glfw-3.5.0-1 curl-8.16.0-1 mpv-0.41.0-1 libmupdf-1.24.11-1 libwebp-1.6.0-1; do
375375
curl -sLO https://github.com/dragonflylee/switchfin/releases/download/mingw-packages/${MINGW_PACKAGE_PREFIX}-${pkg}-any.pkg.tar.zst
376376
done
377377
pacman -U --noconfirm *.pkg.tar.zst
@@ -400,7 +400,7 @@ jobs:
400400
-DVERSION_BUILD=${{ github.run_number }}
401401
cmake --build build
402402
cv2pdb64 build/Switchfin.exe
403-
cp -a README.md ${MINGW_PREFIX}/bin/libmpv-2.dll build
403+
cp -a README.md ${MINGW_PREFIX}/bin/{libmpv-2.dll,libmupdf.dll} build
404404
- name: Upload Assets
405405
uses: actions/upload-artifact@v4
406406
with:
@@ -409,6 +409,7 @@ jobs:
409409
build/Switchfin.exe
410410
build/Switchfin.pdb
411411
build/libmpv-2.dll
412+
build/libmupdf.dll
412413
build/README.md
413414
414415
build-android:

.github/workflows/mingw-packages.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
pushd $pkg; makepkg-mingw -sciCf --noconfirm; popd
5454
done
5555
56-
for pkg in mpv glfw libwebp curl; do
56+
for pkg in mupdf mpv glfw libwebp curl; do
5757
echo building $pkg
5858
pushd $pkg; makepkg-mingw -scCf --noconfirm; popd
5959
done
@@ -67,6 +67,7 @@ jobs:
6767
scripts/mingw64/curl/*.pkg.tar.zst
6868
scripts/mingw64/glfw/*.pkg.tar.zst
6969
scripts/mingw64/libwebp/*.pkg.tar.zst
70+
scripts/mingw64/mupdf/*.pkg.tar.zst
7071
scripts/mingw64/mpv/*.pkg.tar.zst
7172
body: |
7273
![download](https://img.shields.io/github/downloads/${{ github.repository }}/mingw-packages/total?label=Downloads)

.github/workflows/switch-portlibs.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@ jobs:
2828
run: |
2929
adduser --gecos '' --home ${GITHUB_WORKSPACE}/scripts/switch --disabled-password builder
3030
echo 'builder ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/builder
31+
dkp-pacman -R switch-mupdf switch-libmpv --noconfirm
3132
3233
chown -R builder:builder scripts/switch
3334
for pkg in mbedtls libssh2 dav1d ffmpeg libuam; do
3435
echo building $pkg
3536
su - builder -c "cd $pkg && dkp-makepkg -siCf --noconfirm"
3637
done
37-
for pkg in mpv curl nspmini hacbrewpack; do
38+
for pkg in mupdf mpv curl nspmini hacbrewpack; do
3839
echo building $pkg
3940
su - builder -c "cd $pkg && dkp-makepkg -sCf --noconfirm"
4041
done

.vscode/c_cpp_properties.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
"C:/MinGW64/x86_64-w64-mingw32/include/**"
7676
],
7777
"defines": [
78+
"USE_WEBP",
79+
"USE_MUPDF",
7880
"BOREALIS_USE_OPENGL",
7981
"YG_ENABLE_EVENTS",
8082
"BRLS_RESOURCES=\"resources\""

CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,15 @@ if (USE_BOOST_FILESYSTEM)
145145
endif()
146146
endif ()
147147

148+
find_package(MuPDF)
149+
if (MuPDF_FOUND)
150+
if (PLATFORM_SWITCH)
151+
list(APPEND MuPDF_LIBRARY jpeg)
152+
endif ()
153+
list(APPEND APP_PLATFORM_OPTION -DUSE_MUPDF)
154+
list(APPEND APP_PLATFORM_LIB ${MuPDF_LIBRARY})
155+
endif ()
156+
148157
if (PLATFORM_DESKTOP)
149158
find_package(MPV REQUIRED)
150159
list(APPEND APP_PLATFORM_INCLUDE ${MPV_INCLUDE_DIR})

app/include/view/ebook_view.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <borealis.hpp>
4+
5+
struct fz_context;
6+
struct fz_document;
7+
8+
class EBookView : public brls::Box {
9+
public:
10+
EBookView();
11+
~EBookView() override;
12+
13+
void open(const std::string& input, float percent = 0);
14+
void render(brls::Image* view, int n);
15+
16+
private:
17+
brls::Image* left = nullptr;
18+
brls::Image* right = nullptr;
19+
fz_context* ctx = nullptr;
20+
fz_document* doc = nullptr;
21+
int page = 0;
22+
int count = 0;
23+
};

app/src/view/ebook_view.cpp

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
2+
#include "view/ebook_view.hpp"
3+
4+
#ifdef USE_MUPDF
5+
6+
#include "api/http.hpp"
7+
#include <mupdf/fitz.h>
8+
9+
class fz_error : public std::exception {
10+
public:
11+
explicit fz_error(fz_context* ctx) : ctx(ctx) {}
12+
const char* what() const noexcept override { return ctx->error.message; }
13+
14+
private:
15+
fz_context* ctx;
16+
};
17+
18+
using namespace brls::literals;
19+
20+
EBookView::EBookView() {
21+
this->left = new brls::Image();
22+
this->left->setFreeTexture(false);
23+
this->left->setFocusable(true);
24+
this->left->setHideHighlight(true);
25+
this->left->setMaxWidthPercentage(50);
26+
this->left->setHideHighlightBackground(true);
27+
this->left->setMarginRight(10);
28+
this->right = new brls::Image();
29+
this->right->setFreeTexture(false);
30+
this->right->setFocusable(true);
31+
this->right->setHideHighlight(true);
32+
this->right->setMaxWidthPercentage(50);
33+
this->right->setHideHighlightBackground(true);
34+
this->addView(this->left);
35+
this->addView(this->right);
36+
this->setJustifyContent(brls::JustifyContent::CENTER);
37+
this->setAlignItems(brls::AlignItems::CENTER);
38+
this->setPaddingLeft(brls::getStyle().getMetric("main/content_padding_sides"));
39+
this->setPaddingRight(brls::getStyle().getMetric("main/content_padding_sides"));
40+
this->setPaddingTop(10.f);
41+
this->setPaddingBottom(10.f);
42+
43+
this->ctx = fz_new_context(nullptr, nullptr, FZ_STORE_DEFAULT);
44+
fz_register_document_handlers(ctx);
45+
46+
this->registerAction("hints/back"_i18n, brls::BUTTON_B,
47+
[this](brls::View* view) { return brls::Application::popActivity(brls::TransitionAnimation::NONE); });
48+
49+
this->left->registerAction("main/player/prev"_i18n, brls::BUTTON_LB, [this](brls::View* view) {
50+
if (this->page <= 0) return false;
51+
this->render(this->right, --this->page);
52+
this->render(this->left, --this->page);
53+
return true;
54+
});
55+
56+
this->right->registerAction("main/player/next"_i18n, brls::BUTTON_RB, [this](brls::View* view) {
57+
if (this->page >= this->count - 1) return false;
58+
this->render(this->left, ++this->page);
59+
this->render(this->right, ++this->page);
60+
return true;
61+
});
62+
63+
this->addGestureRecognizer(
64+
new brls::TapGestureRecognizer([this](brls::TapGestureStatus status, brls::Sound* soundToPlay) {
65+
if (status.state == brls::GestureState::END) {
66+
auto frame = this->getFrame();
67+
if (status.position.x < frame.getMidX()) {
68+
this->render(this->right, --this->page);
69+
this->render(this->left, --this->page);
70+
} else {
71+
this->render(this->left, ++this->page);
72+
this->render(this->right, ++this->page);
73+
}
74+
}
75+
}));
76+
}
77+
78+
EBookView::~EBookView() {
79+
if (this->doc) fz_drop_document(this->ctx, this->doc);
80+
if (this->ctx) fz_drop_context(this->ctx);
81+
}
82+
83+
void EBookView::open(const std::string& url, float percent) {
84+
this->page = std::floor(percent);
85+
86+
ASYNC_RETAIN
87+
brls::async([ASYNC_TOKEN, url]() {
88+
try {
89+
std::string content = HTTP::get(url, HTTP::Timeout{});
90+
fz_stream* stream = fz_open_memory(ctx, (const uint8_t*)content.data(), content.size());
91+
fz_try(ctx) this->doc = fz_open_document_with_stream(ctx, url.c_str(), stream);
92+
fz_always(ctx) fz_drop_stream(ctx, stream);
93+
fz_catch(ctx) throw fz_error(ctx);
94+
95+
fz_try(ctx) this->count = fz_count_pages(ctx, this->doc);
96+
fz_catch(ctx) throw fz_error(ctx);
97+
98+
brls::sync([ASYNC_TOKEN]() {
99+
ASYNC_RELEASE
100+
this->render(this->left, this->page);
101+
this->render(this->right, this->page + 1);
102+
});
103+
} catch (const std::exception& ex) {
104+
std::string msg = ex.what();
105+
brls::sync([ASYNC_TOKEN, msg]() {
106+
ASYNC_RELEASE
107+
this->dismiss([msg]() { brls::Application::notify(msg); });
108+
});
109+
}
110+
});
111+
}
112+
113+
void EBookView::render(brls::Image* view, int n) {
114+
if (n <= 0 || n >= this->count) {
115+
view->clear();
116+
return;
117+
}
118+
fz_pixmap* pix = nullptr;
119+
fz_matrix ctm = fz_scale(1.5f, 1.5f);
120+
fz_try(ctx) pix = fz_new_pixmap_from_page_number(ctx, doc, n, ctm, fz_device_rgb(ctx), 1);
121+
fz_catch(ctx) return;
122+
auto vg = brls::Application::getNVGContext();
123+
int tex = nvgCreateImageRGBA(vg, pix->w, pix->h, 0, pix->samples);
124+
fz_drop_pixmap(ctx, pix);
125+
view->innerSetImage(tex);
126+
view->setBackgroundColor(nvgRGB(245, 246, 247));
127+
}
128+
129+
#endif

app/src/view/video_source.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include "view/video_card.hpp"
1212
#include "view/video_source.hpp"
1313
#include "view/context_menu.hpp"
14+
#include "view/ebook_view.hpp"
1415

1516
using namespace brls::literals; // for _i18n
1617

@@ -128,6 +129,15 @@ void VideoDataSource::onItemSelected(brls::Box* recycler, size_t index) {
128129
std::string query = HTTP::encode_form({{"api_key", conf.getToken()}});
129130
std::string url = conf.getUrl() + fmt::format(fmt::runtime(jellyfin::apiDownload), item.Id, query);
130131
brls::Application::pushActivity(new GalleryActivity(url));
132+
#ifdef USE_MUPDF
133+
} else if (item.Type == jellyfin::mediaTypeBook) {
134+
auto view = new EBookView();
135+
auto& conf = AppConfig::instance();
136+
std::string query = HTTP::encode_form({{"api_key", conf.getToken()}});
137+
std::string url = conf.getUrl() + fmt::format(fmt::runtime(jellyfin::apiDownload), item.Id, query);
138+
view->open(url, item.UserData.PlayedPercentage);
139+
brls::Application::pushActivity(new brls::Activity(view));
140+
#endif
131141
} else {
132142
auto dialog = new brls::Dialog(fmt::format("Unsupported media type: {}", item.Type));
133143
dialog->addButton("hints/cancel"_i18n, []() {});

cmake/FindMuPDF.cmake

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
SET(MuPDF_INCLUDE_DIR MuPDF_LIBRARY)
2+
#
3+
### Look for the include files.
4+
#
5+
find_path(
6+
MuPDF_INCLUDE_DIR
7+
NAMES mupdf/fitz.h
8+
HINTS ${CMAKE_PREFIX_PATH}/include
9+
DOC "MuPDF include directory"
10+
)
11+
#
12+
### Look for the libraries
13+
#
14+
set(_MuPDF_LIBRARY_NAMES mupdf)
15+
if (PLATFORM_SWITCH)
16+
list(APPEND _MuPDF_LIBRARY_NAMES mupdf-third)
17+
endif ()
18+
foreach(l ${_MuPDF_LIBRARY_NAMES})
19+
find_library(
20+
MuPDF_LIBRARY_${l}
21+
NAMES ${l}
22+
HINTS ${CMAKE_PREFIX_PATH}/lib
23+
PATH_SUFFIXES lib${LIB_SUFFIX}
24+
)
25+
list(APPEND MuPDF_LIBRARY ${MuPDF_LIBRARY_${l}})
26+
endforeach ()
27+
28+
get_filename_component(_MuPDF_LIBRARY_DIR ${MuPDF_LIBRARY_mupdf} PATH)
29+
30+
set(MuPDF_LIBRARY_DIRS _MuPDF_LIBRARY_DIR)
31+
list(REMOVE_DUPLICATES MuPDF_LIBRARY_DIRS)
32+
mark_as_advanced(
33+
MuPDF_LIBRARY
34+
MuPDF_INCLUDE_DIR
35+
MuPDF_LIBRARY_DIRS
36+
)
37+
set(MuPDF_INCLUDE_DIRS ${MuPDF_INCLUDE_DIR})
38+
39+
#
40+
### Check if everything was found and if the version is sufficient.
41+
#
42+
include(FindPackageHandleStandardArgs)
43+
find_package_handle_standard_args(MuPDF
44+
FOUND_VAR MuPDF_FOUND
45+
REQUIRED_VARS MuPDF_LIBRARY MuPDF_INCLUDE_DIR
46+
)

debian/rules

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,16 @@ glfw:
2929
cmake --build build-glfw
3030
cmake --install build-glfw
3131

32+
mupdf:
33+
wget -qO- https://casper.mupdf.com/downloads/archive/mupdf-1.24.11-source.tar.lz | tar --lzip -xf - -C $(TMPDIR)
34+
make -C $(TMPDIR)/mupdf-1.24.11-source prefix=$(PREFIX) build=release OS=Linux \
35+
USE_SYSTEM_LIBS=yes USE_SYSTEM_GUMBO=no USE_SYSTEM_JBIG2DEC=no USE_SYSTEM_OPENJPEG=no \
36+
TOOL_APPS= VIEW_APPS= install-shared-c -j$(shell nproc)
37+
3238
%:
3339
dh $@ --buildsystem=cmake+ninja --builddirectory=build
3440

35-
override_dh_auto_configure: curl glfw
41+
override_dh_auto_configure: curl glfw mupdf
3642
dh_auto_configure -- \
3743
-DCMAKE_INSTALL_PREFIX=/usr \
3844
-DPLATFORM_DESKTOP=ON \

0 commit comments

Comments
 (0)