├── .gitattributes
├── .github
└── workflows
│ ├── build_and_release.yml
│ └── preview_release.yml
├── .gitignore
├── .gitmodules
├── LICENSE
├── README.md
├── build_and_release.sh
├── build_linux.sh
├── build_mac.sh
├── build_windows.bat
├── delete_build.sh
├── pdf_viewer
├── .gitignore
├── OpenWithApplication.cpp
├── OpenWithApplication.h
├── RunGuard.cpp
├── RunGuard.h
├── book.cpp
├── book.h
├── checksum.cpp
├── checksum.h
├── config.cpp
├── config.h
├── coordinates.cpp
├── coordinates.h
├── data
│ └── last_document_path.txt
├── database.cpp
├── database.h
├── document.cpp
├── document.h
├── document_view.cpp
├── document_view.h
├── fonts
│ ├── monaco.ttf
│ ├── msuighub.ttf
│ └── msuighur.ttf
├── fts_fuzzy_match.h
├── icon1.ico
├── icon2.ico
├── input.cpp
├── input.h
├── keys.config
├── keys_old.config
├── keys_user.config
├── main.cpp
├── main_widget.cpp
├── main_widget.h
├── new_file_checker.cpp
├── new_file_checker.h
├── path.cpp
├── path.h
├── pdf_renderer.cpp
├── pdf_renderer.h
├── pdf_view_opengl_widget.cpp
├── pdf_view_opengl_widget.h
├── prefs.config
├── prefs_user.config
├── rapidfuzz_amalgamated.hpp
├── shaders
│ ├── custom_colors.fragment
│ ├── dark_mode.fragment
│ ├── debug.fragment
│ ├── highlight.fragment
│ ├── separator.fragment
│ ├── simple.fragment
│ ├── simple.vertex
│ ├── stencil.fragment
│ ├── stencil.vertex
│ ├── undendered_page.fragment
│ ├── unrendered_page.fragment
│ ├── vertical_bar.fragment
│ └── vertical_bar_dark.fragment
├── shell.c
├── sqlite3.c
├── sqlite3.h
├── sqlite3ext.h
├── synctex
│ ├── synctex_parser.c
│ ├── synctex_parser.h
│ ├── synctex_parser_advanced.h
│ ├── synctex_parser_local.h
│ ├── synctex_parser_utils.c
│ ├── synctex_parser_utils.h
│ └── synctex_version.h
├── ui.cpp
├── ui.h
├── utf8.h
├── utf8
│ ├── checked.h
│ ├── core.h
│ └── unchecked.h
├── utils.cpp
└── utils.h
├── pdf_viewer_build_config.pro
├── resources
├── Info.plist
├── debian
│ ├── changelog
│ ├── compat
│ ├── control
│ ├── copyright
│ ├── rules
│ ├── sioyek.dirs
│ ├── sioyek.install
│ └── source
│ │ └── format
├── sioyek-icon-linux.png
├── sioyek.1
└── sioyek.desktop
├── scripts
├── dual_panelify.py
├── embed_annotations_in_file.py
├── embedded_annotations.py
├── paper_downloader.py
├── sioyek-generator.py
├── sioyek.py
├── summary_highlight_server.py
└── tts
│ ├── aligner.bat
│ ├── aligner.ps1
│ ├── generator.ps1
│ ├── generator2.ps1
│ ├── manager_server.py
│ ├── server_follow.py
│ ├── server_read.py
│ ├── server_stop.py
│ └── server_unfollow.py
├── tutorial.pdf
├── tutorial
├── bibs.bib
├── compile.sh
├── mandlebrot_small.jpg
└── tut.tex
└── windows_runtime
├── libcrypto-1_1-x64.dll
├── libssl-1_1-x64.dll
└── vcruntime140_1.dll
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/workflows/build_and_release.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Build Release
3 |
4 | on:
5 | push:
6 | tags:
7 | - "v*"
8 | pull_request:
9 | branches:
10 | - "*"
11 |
12 | jobs:
13 | upload-release:
14 | if: ${{ github.event_name != 'pull_request' }}
15 | runs-on: ubuntu-18.04
16 | needs: [build-mac, build-mac-arm, build-linux, build-windows, build-linux-portable, build-windows-portable]
17 |
18 | steps:
19 | - uses: actions/checkout@v1
20 | - name: create release
21 | id: create_release
22 | uses: actions/create-release@master
23 | env:
24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25 | with:
26 | tag_name: ${{ github.ref }}
27 | release_name: Release ${{ github.ref }}
28 | draft: false
29 | prerelease: false
30 | - name: download artifacts
31 | uses: actions/download-artifact@v1
32 | with:
33 | name: uploads
34 | - name: upload mac
35 | id: upload-mac
36 | uses: actions/upload-release-asset@v1.0.1
37 | env:
38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39 | with:
40 | upload_url: ${{ steps.create_release.outputs.upload_url }}
41 | asset_path: ./uploads/sioyek-release-mac.zip
42 | asset_name: sioyek-release-mac.zip
43 | asset_content_type: application/zip
44 |
45 | - name: upload mac arm
46 | id: upload-mac-arm
47 | uses: actions/upload-release-asset@v1.0.1
48 | env:
49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
50 | with:
51 | upload_url: ${{ steps.create_release.outputs.upload_url }}
52 | asset_path: ./uploads/sioyek-release-mac-arm.zip
53 | asset_name: sioyek-release-mac-arm.zip
54 | asset_content_type: application/zip
55 |
56 | - name: upload linux
57 | id: upload-linux
58 | uses: actions/upload-release-asset@v1.0.1
59 | env:
60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | with:
62 | upload_url: ${{ steps.create_release.outputs.upload_url }}
63 | asset_path: ./uploads/sioyek-release-linux.zip
64 | asset_name: sioyek-release-linux.zip
65 | asset_content_type: application/zip
66 | - name: upload windows
67 | id: upload-windows
68 | uses: actions/upload-release-asset@v1.0.1
69 | env:
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71 | with:
72 | upload_url: ${{ steps.create_release.outputs.upload_url }}
73 | asset_path: ./uploads/sioyek-release-windows.zip
74 | asset_name: sioyek-release-windows.zip
75 | asset_content_type: application/zip
76 | - name: upload linux-portable
77 | id: upload-linux-portable
78 | uses: actions/upload-release-asset@v1.0.1
79 | env:
80 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
81 | with:
82 | upload_url: ${{ steps.create_release.outputs.upload_url }}
83 | asset_path: ./uploads/sioyek-release-linux-portable.zip
84 | asset_name: sioyek-release-linux-portable.zip
85 | asset_content_type: application/zip
86 | - name: upload windows-portable
87 | id: upload-windows-portable
88 | uses: actions/upload-release-asset@v1.0.1
89 | env:
90 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
91 | with:
92 | upload_url: ${{ steps.create_release.outputs.upload_url }}
93 | asset_path: ./uploads/sioyek-release-windows-portable.zip
94 | asset_name: sioyek-release-windows-portable.zip
95 | asset_content_type: application/zip
96 |
97 |
98 | build-linux:
99 |
100 | runs-on: ubuntu-18.04
101 |
102 | steps:
103 | - name: Cache apt-get packages
104 | uses: actions/cache@v2
105 | env:
106 | cache-name: cache-deb-packages
107 | with:
108 | path: /var/cache/apt/archives
109 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }}
110 | restore-keys: |
111 | ${{ runner.os }}-build-${{ env.cache-name }}-
112 | ${{ runner.os }}-build-
113 | ${{ runner.os }}-
114 |
115 | - name: Set up GCC
116 | uses: egor-tensin/setup-gcc@v1
117 | with:
118 | version: 9
119 | platform: x64
120 |
121 | - uses: actions/checkout@v2
122 | with:
123 | submodules: 'recursive'
124 |
125 | - name: Install libharfbuzz
126 | run: sudo apt install libharfbuzz-dev
127 |
128 | - name: Install Qt
129 | uses: jurplel/install-qt-action@v2
130 | with:
131 | version: '5.15.2'
132 |
133 | - name: Build
134 | working-directory: ${{env.GITHUB_WORKSPACE}}
135 |
136 | run: MAKE_PARALLEL=$(nproc) ./build_and_release.sh
137 | env:
138 | CC: gcc-9
139 | CXX: g++-9
140 | - name: upload linux artifact
141 | uses: actions/upload-artifact@v1
142 | with:
143 | name: uploads
144 | path: sioyek-release-linux.zip
145 | - name: Workaround apt-get cache permission (https://github.com/actions/cache/issues/324)
146 | run: |
147 | export USER_NAME=$(whoami)
148 | sudo chown -R $USER_NAME /var/cache/apt/archives
149 |
150 | build-linux-portable:
151 | runs-on: ubuntu-18.04
152 |
153 | steps:
154 | - name: Cache apt-get packages
155 | uses: actions/cache@v2
156 | env:
157 | cache-name: cache-deb-packages
158 | with:
159 | path: /var/cache/apt/archives
160 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }}-portable
161 | restore-keys: |
162 | ${{ runner.os }}-build-${{ env.cache-name }}-
163 | ${{ runner.os }}-build-
164 | ${{ runner.os }}-
165 |
166 | - name: Set up GCC
167 | uses: egor-tensin/setup-gcc@v1
168 | with:
169 | version: 9
170 | platform: x64
171 |
172 | - uses: actions/checkout@v2
173 | with:
174 | submodules: 'recursive'
175 |
176 | - name: Install libharfbuzz
177 | run: sudo apt install libharfbuzz-dev
178 |
179 | - name: Install Qt
180 | uses: jurplel/install-qt-action@v2
181 | with:
182 | version: '5.15.2'
183 |
184 | - name: Build
185 | working-directory: ${{env.GITHUB_WORKSPACE}}
186 |
187 | run: MAKE_PARALLEL=$(nproc) ./build_and_release.sh portable
188 | env:
189 | CC: gcc-9
190 | CXX: g++-9
191 | - name: upload linux artifact
192 | uses: actions/upload-artifact@v1
193 | with:
194 | name: uploads
195 | path: sioyek-release-linux-portable.zip
196 | - name: Workaround apt-get cache permission (https://github.com/actions/cache/issues/324)
197 | run: |
198 | export USER_NAME=$(whoami)
199 | sudo chown -R $USER_NAME /var/cache/apt/archives
200 |
201 | build-windows:
202 |
203 | runs-on: windows-latest
204 |
205 | steps:
206 | - uses: actions/checkout@v2
207 | with:
208 | submodules: 'recursive'
209 |
210 | - name: Install Qt
211 | uses: jurplel/install-qt-action@v2
212 |
213 | - name: Add MSBuild to PATH
214 | uses: microsoft/setup-msbuild@v1.0.2
215 | - name: Add msvc-dev-cmd
216 | uses: ilammy/msvc-dev-cmd@v1
217 |
218 |
219 | - name: Build Sioyek
220 | working-directory: ${{env.GITHUB_WORKSPACE}}
221 | run: .\build_windows.bat non_portable
222 |
223 | - name: upload windows artifact
224 | uses: actions/upload-artifact@v1
225 | with:
226 | name: uploads
227 | path: sioyek-release-windows.zip
228 |
229 | build-windows-portable:
230 |
231 | runs-on: windows-latest
232 |
233 | steps:
234 | - uses: actions/checkout@v2
235 | with:
236 | submodules: 'recursive'
237 |
238 | - name: Install Qt
239 | uses: jurplel/install-qt-action@v2
240 |
241 | - name: Add MSBuild to PATH
242 | uses: microsoft/setup-msbuild@v1.0.2
243 | - name: Add msvc-dev-cmd
244 | uses: ilammy/msvc-dev-cmd@v1
245 |
246 |
247 | - name: Build Sioyek
248 | working-directory: ${{env.GITHUB_WORKSPACE}}
249 | run: .\build_windows.bat portable
250 |
251 | - name: upload windows artifact
252 | uses: actions/upload-artifact@v1
253 | with:
254 | name: uploads
255 | path: sioyek-release-windows-portable.zip
256 |
257 | build-mac:
258 |
259 | runs-on: macos-11
260 |
261 | steps:
262 |
263 | - uses: actions/checkout@v2
264 | with:
265 | submodules: 'recursive'
266 |
267 | - name: Cache Homebrew packages
268 | uses: actions/cache@v2
269 | env:
270 | cache-name: homebrew
271 | with:
272 | path: ~/Library/Caches/Homebrew
273 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }}
274 | restore-keys: |
275 | ${{ runner.os }}-build-${{ env.cache-name }}-
276 | ${{ runner.os }}-build-
277 | ${{ runner.os }}-
278 |
279 | - name: Install dependencies
280 | run: brew install freeglut mesa harfbuzz
281 |
282 | - name: Install Qt
283 | uses: jurplel/install-qt-action@v2
284 | with:
285 | version: '5.15.2'
286 |
287 | - name: Build
288 | working-directory: ${{env.GITHUB_WORKSPACE}}
289 | run: |
290 | chmod +x build_mac.sh
291 | MAKE_PARALLEL=$(sysctl -n hw.logicalcpu) ./build_mac.sh
292 |
293 | - name: upload mac artifact
294 | uses: actions/upload-artifact@v1
295 | with:
296 | name: uploads
297 | path: sioyek-release-mac.zip
298 |
299 | build-mac-arm:
300 |
301 | runs-on: macos-11
302 |
303 | steps:
304 |
305 | - uses: actions/checkout@v2
306 | with:
307 | submodules: 'recursive'
308 |
309 | - name: Cache Homebrew packages
310 | uses: actions/cache@v2
311 | env:
312 | cache-name: homebrew
313 | with:
314 | path: ~/Library/Caches/Homebrew
315 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }}
316 | restore-keys: |
317 | ${{ runner.os }}-build-${{ env.cache-name }}-
318 | ${{ runner.os }}-build-
319 | ${{ runner.os }}-
320 |
321 | - name: Install dependencies
322 | run: brew install freeglut mesa harfbuzz
323 |
324 | - name: Install Qt
325 | uses: jurplel/install-qt-action@v2
326 | with:
327 | version: '6.2.*'
328 |
329 | - name: Build
330 | working-directory: ${{env.GITHUB_WORKSPACE}}
331 | run: |
332 | chmod +x build_mac_arm.sh
333 | MAKE_PARALLEL=$(sysctl -n hw.logicalcpu) ./build_mac_arm.sh
334 |
335 | - name: upload mac artifact
336 | uses: actions/upload-artifact@v1
337 | with:
338 | name: uploads
339 | path: sioyek-release-mac-arm.zip
340 |
--------------------------------------------------------------------------------
/.github/workflows/preview_release.yml:
--------------------------------------------------------------------------------
1 |
2 | name: Preview Release
3 |
4 | on:
5 | push:
6 | tags:
7 | - "v*"
8 | pull_request:
9 | branches:
10 | - "*"
11 |
12 | workflow_dispatch:
13 |
14 | jobs:
15 | upload-release:
16 | if: ${{ (github.event_name == 'workflow_dispatch') || (github.event_name != 'pull_request') }}
17 | runs-on: ubuntu-20.04
18 | needs: [build-mac, build-mac-arm, build-linux, build-windows]
19 |
20 | steps:
21 | - uses: actions/checkout@v1
22 | - name: create release
23 | id: create_release
24 | uses: actions/create-release@master
25 | env:
26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
27 | with:
28 | tag_name: ${{ github.ref }}
29 | release_name: Release ${{ github.sha }}
30 | draft: false
31 | prerelease: true
32 | - name: download artifacts
33 | uses: actions/download-artifact@v4
34 | # with:
35 | # name: uploads
36 | - name: upload mac
37 | id: upload-mac
38 | uses: actions/upload-release-asset@v1.0.1
39 | env:
40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | with:
42 | upload_url: ${{ steps.create_release.outputs.upload_url }}
43 | asset_path: ./mac-uploads/sioyek-release-mac.zip
44 | asset_name: sioyek-release-mac.zip
45 | asset_content_type: application/zip
46 |
47 | - name: upload mac arm
48 | id: upload-mac-arm
49 | uses: actions/upload-release-asset@v1.0.1
50 | env:
51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
52 | with:
53 | upload_url: ${{ steps.create_release.outputs.upload_url }}
54 | asset_path: ./mac-arm-uploads/sioyek-release-mac-arm.zip
55 | asset_name: sioyek-release-mac-arm.zip
56 | asset_content_type: application/zip
57 |
58 | - name: upload linux
59 | id: upload-linux
60 | uses: actions/upload-release-asset@v1.0.1
61 | env:
62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63 | with:
64 | upload_url: ${{ steps.create_release.outputs.upload_url }}
65 | asset_path: ./linux-uploads/sioyek-release-linux.zip
66 | asset_name: sioyek-release-linux.zip
67 | asset_content_type: application/zip
68 | - name: upload windows
69 | id: upload-windows
70 | uses: actions/upload-release-asset@v1.0.1
71 | env:
72 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
73 | with:
74 | upload_url: ${{ steps.create_release.outputs.upload_url }}
75 | asset_path: ./windows-uploads/sioyek-release-windows.zip
76 | asset_name: sioyek-release-windows.zip
77 | asset_content_type: application/zip
78 |
79 | build-linux:
80 |
81 | runs-on: ubuntu-20.04
82 |
83 | steps:
84 | - name: Cache apt-get packages
85 | uses: actions/cache@v4
86 | env:
87 | cache-name: cache-deb-packages
88 | with:
89 | path: /var/cache/apt/archives
90 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ github.sha }}
91 | restore-keys: |
92 | ${{ runner.os }}-build-${{ env.cache-name }}-
93 | ${{ runner.os }}-build-
94 | ${{ runner.os }}-
95 |
96 | - name: Install LinuxDeploy
97 | uses: miurahr/install-linuxdeploy-action@v1
98 | with:
99 | plugins: qt appimage
100 |
101 | - name: Set up GCC
102 | uses: egor-tensin/setup-gcc@v1
103 | with:
104 | version: 9
105 | platform: x64
106 |
107 | - uses: actions/checkout@v4
108 | with:
109 | submodules: 'recursive'
110 | ref: development
111 |
112 | - name: Install dependencies
113 | run: sudo apt install libharfbuzz-dev libxrandr-dev libxi-dev libglu1-mesa-dev fuse libxcb-cursor0 libspeechd2
114 |
115 | - name: Install Qt
116 | uses: jurplel/install-qt-action@v4
117 | with:
118 | version: '6.7.2'
119 | modules: 'all'
120 | cache: true
121 |
122 | - name: Build
123 | working-directory: ${{env.GITHUB_WORKSPACE}}
124 |
125 | run: MAKE_PARALLEL=$(nproc) ./linuxdeploy_build_and_release.sh
126 | env:
127 | CC: gcc-9
128 | CXX: g++-9
129 | - name: upload linux artifact
130 | uses: actions/upload-artifact@v4
131 | with:
132 | name: linux-uploads
133 | path: sioyek-release-linux.zip
134 | - name: Workaround apt-get cache permission (https://github.com/actions/cache/issues/324)
135 | run: |
136 | export USER_NAME=$(whoami)
137 | sudo chown -R $USER_NAME /var/cache/apt/archives
138 |
139 | build-windows:
140 |
141 | runs-on: windows-latest
142 |
143 | steps:
144 | - uses: actions/checkout@v4
145 | with:
146 | submodules: 'recursive'
147 | ref: development
148 |
149 | - name: Install Qt
150 | uses: jurplel/install-qt-action@v4
151 | with:
152 | version: '6.7.2'
153 | modules: 'all'
154 | cache: true
155 |
156 | - name: Add MSBuild to PATH
157 | uses: microsoft/setup-msbuild@v1.0.2
158 |
159 | - name: Add msvc-dev-cmd
160 | uses: ilammy/msvc-dev-cmd@v1
161 |
162 |
163 | - name: Build Sioyek
164 | working-directory: ${{env.GITHUB_WORKSPACE}}
165 | run: .\build_windows.bat non_portable
166 |
167 | - name: upload windows artifact
168 | uses: actions/upload-artifact@v4
169 | with:
170 | name: windows-uploads
171 | path: sioyek-release-windows.zip
172 |
173 | build-mac:
174 |
175 | runs-on: macos-13
176 |
177 | steps:
178 |
179 | - uses: actions/checkout@v4
180 | with:
181 | submodules: 'recursive'
182 | ref: development
183 |
184 | - name: Cache Homebrew packages
185 | uses: actions/cache@v4
186 | env:
187 | cache-name: homebrew
188 | with:
189 | path: ~/Library/Caches/Homebrew
190 | key: ${{ runner.os }}-intel-build-${{ env.cache-name }}-${{ github.sha }}
191 | restore-keys: |
192 | ${{ runner.os }}-intel-build-${{ env.cache-name }}-
193 | ${{ runner.os }}-intel-build-
194 | ${{ runner.os }}-intel-
195 |
196 | - name: Install dependencies
197 | run: brew install freeglut mesa harfbuzz
198 |
199 | - name: Install Qt
200 | uses: jurplel/install-qt-action@v4
201 | with:
202 | version: '6.7.2'
203 | modules: 'all'
204 | cache: true
205 |
206 | - name: Build
207 | working-directory: ${{env.GITHUB_WORKSPACE}}
208 | run: |
209 | chmod +x build_mac.sh
210 | MAKE_PARALLEL=$(sysctl -n hw.logicalcpu) ./build_mac.sh
211 |
212 | - name: upload mac artifact
213 | uses: actions/upload-artifact@v4
214 | with:
215 | name: mac-uploads
216 | path: sioyek-release-mac.zip
217 |
218 | build-mac-arm:
219 |
220 | runs-on: macos-14
221 |
222 | steps:
223 |
224 | - uses: actions/checkout@v4
225 | with:
226 | submodules: 'recursive'
227 | ref: development
228 |
229 | - name: Cache Homebrew packages
230 | uses: actions/cache@v4
231 | env:
232 | cache-name: homebrew
233 | with:
234 | path: ~/Library/Caches/Homebrew
235 | key: ${{ runner.os }}-arm-build-${{ env.cache-name }}-${{ github.sha }}
236 | restore-keys: |
237 | ${{ runner.os }}-arm-build-${{ env.cache-name }}-
238 | ${{ runner.os }}-arm-build-
239 | ${{ runner.os }}-arm-
240 |
241 | - name: Install dependencies
242 | run: brew install freeglut mesa harfbuzz
243 |
244 | - name: Install Qt
245 | uses: jurplel/install-qt-action@v4
246 | with:
247 | version: '6.7.2'
248 | modules: 'all'
249 | cache: true
250 |
251 | - name: Build
252 | working-directory: ${{env.GITHUB_WORKSPACE}}
253 | run: |
254 | chmod +x build_mac.sh
255 | MAKE_PARALLEL=$(sysctl -n hw.logicalcpu) ./build_mac.sh
256 | mv sioyek-release-mac.zip sioyek-release-mac-arm.zip
257 |
258 | - name: upload mac artifact
259 | uses: actions/upload-artifact@v4
260 | with:
261 | name: mac-arm-uploads
262 | path: sioyek-release-mac-arm.zip
263 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Build results
17 | [Dd]ebug/
18 | [Dd]ebugPublic/
19 | [Rr]elease/
20 | [Rr]eleases/
21 | x64/
22 | x86/
23 | [Aa][Rr][Mm]/
24 | [Aa][Rr][Mm]64/
25 | bld/
26 | [Bb]in/
27 | [Oo]bj/
28 | [Ll]og/
29 |
30 | # Visual Studio 2015/2017 cache/options directory
31 | .vs/
32 | # Uncomment if you have tasks that create the project's static files in wwwroot
33 | #wwwroot/
34 |
35 | # Visual Studio 2017 auto generated files
36 | Generated\ Files/
37 |
38 | # MSTest test Results
39 | [Tt]est[Rr]esult*/
40 | [Bb]uild[Ll]og.*
41 |
42 | # NUNIT
43 | *.VisualState.xml
44 | TestResult.xml
45 |
46 | # Build Results of an ATL Project
47 | [Dd]ebugPS/
48 | [Rr]eleasePS/
49 | dlldata.c
50 |
51 | # Benchmark Results
52 | BenchmarkDotNet.Artifacts/
53 |
54 | # .NET Core
55 | project.lock.json
56 | project.fragment.lock.json
57 | artifacts/
58 |
59 | # StyleCop
60 | StyleCopReport.xml
61 |
62 | # Files built by Visual Studio
63 | *_i.c
64 | *_p.c
65 | *_h.h
66 | *.ilk
67 | *.meta
68 | *.obj
69 | *.iobj
70 | *.pch
71 | *.pdb
72 | *.ipdb
73 | *.pgc
74 | *.pgd
75 | *.rsp
76 | *.sbr
77 | *.tlb
78 | *.tli
79 | *.tlh
80 | *.tmp
81 | *.tmp_proj
82 | *_wpftmp.csproj
83 | *.log
84 | *.vspscc
85 | *.vssscc
86 | .builds
87 | *.pidb
88 | *.svclog
89 | *.scc
90 |
91 | # Chutzpah Test files
92 | _Chutzpah*
93 |
94 | # Visual C++ cache files
95 | ipch/
96 | *.aps
97 | *.ncb
98 | *.opendb
99 | *.opensdf
100 | *.sdf
101 | *.cachefile
102 | *.VC.db
103 | *.VC.VC.opendb
104 |
105 | # Visual Studio profiler
106 | *.psess
107 | *.vsp
108 | *.vspx
109 | *.sap
110 |
111 | # Visual Studio Trace Files
112 | *.e2e
113 |
114 | # TFS 2012 Local Workspace
115 | $tf/
116 |
117 | # Guidance Automation Toolkit
118 | *.gpState
119 |
120 | # ReSharper is a .NET coding add-in
121 | _ReSharper*/
122 | *.[Rr]e[Ss]harper
123 | *.DotSettings.user
124 |
125 | # JustCode is a .NET coding add-in
126 | .JustCode
127 |
128 | # TeamCity is a build add-in
129 | _TeamCity*
130 |
131 | # DotCover is a Code Coverage Tool
132 | *.dotCover
133 |
134 | # AxoCover is a Code Coverage Tool
135 | .axoCover/*
136 | !.axoCover/settings.json
137 |
138 | # Visual Studio code coverage results
139 | *.coverage
140 | *.coveragexml
141 |
142 | # NCrunch
143 | _NCrunch_*
144 | .*crunch*.local.xml
145 | nCrunchTemp_*
146 |
147 | # MightyMoose
148 | *.mm.*
149 | AutoTest.Net/
150 |
151 | # Web workbench (sass)
152 | .sass-cache/
153 |
154 | # Installshield output folder
155 | [Ee]xpress/
156 |
157 | # DocProject is a documentation generator add-in
158 | DocProject/buildhelp/
159 | DocProject/Help/*.HxT
160 | DocProject/Help/*.HxC
161 | DocProject/Help/*.hhc
162 | DocProject/Help/*.hhk
163 | DocProject/Help/*.hhp
164 | DocProject/Help/Html2
165 | DocProject/Help/html
166 |
167 | # Click-Once directory
168 | publish/
169 |
170 | # Publish Web Output
171 | *.[Pp]ublish.xml
172 | *.azurePubxml
173 | # Note: Comment the next line if you want to checkin your web deploy settings,
174 | # but database connection strings (with potential passwords) will be unencrypted
175 | *.pubxml
176 | *.publishproj
177 |
178 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
179 | # checkin your Azure Web App publish settings, but sensitive information contained
180 | # in these scripts will be unencrypted
181 | PublishScripts/
182 |
183 | # NuGet Packages
184 | *.nupkg
185 | # The packages folder can be ignored because of Package Restore
186 | **/[Pp]ackages/*
187 | # except build/, which is used as an MSBuild target.
188 | !**/[Pp]ackages/build/
189 | # Uncomment if necessary however generally it will be regenerated when needed
190 | #!**/[Pp]ackages/repositories.config
191 | # NuGet v3's project.json files produces more ignorable files
192 | *.nuget.props
193 | *.nuget.targets
194 |
195 | # Microsoft Azure Build Output
196 | csx/
197 | *.build.csdef
198 |
199 | # Microsoft Azure Emulator
200 | ecf/
201 | rcf/
202 |
203 | # Windows Store app package directories and files
204 | AppPackages/
205 | BundleArtifacts/
206 | Package.StoreAssociation.xml
207 | _pkginfo.txt
208 | *.appx
209 |
210 | # Visual Studio cache files
211 | # files ending in .cache can be ignored
212 | *.[Cc]ache
213 | # but keep track of directories ending in .cache
214 | !?*.[Cc]ache/
215 |
216 | # Others
217 | ClientBin/
218 | ~$*
219 | *~
220 | *.dbmdl
221 | *.dbproj.schemaview
222 | *.jfm
223 | *.pfx
224 | *.publishsettings
225 | orleans.codegen.cs
226 |
227 | # Including strong name files can present a security risk
228 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
229 | #*.snk
230 |
231 | # Since there are multiple workflows, uncomment next line to ignore bower_components
232 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
233 | #bower_components/
234 |
235 | # RIA/Silverlight projects
236 | Generated_Code/
237 |
238 | # Backup & report files from converting an old project file
239 | # to a newer Visual Studio version. Backup files are not needed,
240 | # because we have git ;-)
241 | _UpgradeReport_Files/
242 | Backup*/
243 | UpgradeLog*.XML
244 | UpgradeLog*.htm
245 | ServiceFabricBackup/
246 | *.rptproj.bak
247 |
248 | # SQL Server files
249 | *.mdf
250 | *.ldf
251 | *.ndf
252 |
253 | # Business Intelligence projects
254 | *.rdl.data
255 | *.bim.layout
256 | *.bim_*.settings
257 | *.rptproj.rsuser
258 | *- Backup*.rdl
259 |
260 | # Microsoft Fakes
261 | FakesAssemblies/
262 |
263 | # GhostDoc plugin setting file
264 | *.GhostDoc.xml
265 |
266 | # Node.js Tools for Visual Studio
267 | .ntvs_analysis.dat
268 | node_modules/
269 |
270 | # Visual Studio 6 build log
271 | *.plg
272 |
273 | # Visual Studio 6 workspace options file
274 | *.opt
275 |
276 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
277 | *.vbw
278 |
279 | # Visual Studio LightSwitch build output
280 | **/*.HTMLClient/GeneratedArtifacts
281 | **/*.DesktopClient/GeneratedArtifacts
282 | **/*.DesktopClient/ModelManifest.xml
283 | **/*.Server/GeneratedArtifacts
284 | **/*.Server/ModelManifest.xml
285 | _Pvt_Extensions
286 |
287 | # Paket dependency manager
288 | .paket/paket.exe
289 | paket-files/
290 |
291 | # FAKE - F# Make
292 | .fake/
293 |
294 | # JetBrains Rider
295 | .idea/
296 | *.sln.iml
297 |
298 | # CodeRush personal settings
299 | .cr/personal
300 |
301 | # Python Tools for Visual Studio (PTVS)
302 | __pycache__/
303 | *.pyc
304 |
305 | # Cake - Uncomment if you are using it
306 | # tools/**
307 | # !tools/packages.config
308 |
309 | # Tabs Studio
310 | *.tss
311 |
312 | # Telerik's JustMock configuration file
313 | *.jmconfig
314 |
315 | # BizTalk build output
316 | *.btp.cs
317 | *.btm.cs
318 | *.odx.cs
319 | *.xsd.cs
320 |
321 | # OpenCover UI analysis results
322 | OpenCover/
323 |
324 | # Azure Stream Analytics local run output
325 | ASALocalRun/
326 |
327 | # MSBuild Binary and Structured Log
328 | *.binlog
329 |
330 | # NVidia Nsight GPU debugger configuration file
331 | *.nvuser
332 |
333 | # MFractors (Xamarin productivity tool) working folder
334 | .mfractor/
335 |
336 | # Local History for Visual Studio
337 | .localhistory/
338 |
339 | # BeatPulse healthcheck temp database
340 | healthchecksdb
341 | *.o
342 | *.db
343 | sioyek-release/*
344 | linux-deploy-binaries/*
345 | build/*
346 | *.zip
347 | *.sln
348 | *.vcxproj
349 | *.vcxproj.filters
350 | *.vcxproj.user
351 | moc*
352 |
353 | # Linux and Mac build files
354 | Makefile
355 | sioyek
356 | libs/*
357 | lib_debug/*
358 | lib_release/*
359 | *.rc
360 | pdf_viewer/fonts/*
361 | *.stash
362 | pdf_viewer/todo.txt
363 | sioyek_log.txt
364 | fast/*
365 | debuglibs/*
366 | tutorial/*.aux
367 | tutorial/*.fdb_latexmk
368 | tutorial/*.fls
369 | tutorial/*.pdf
370 | tutorial/*.synctex.gz
371 | tutorial/*.bbl
372 | tutorial/*.blg
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "mupdf"]
2 | path = mupdf
3 | url = https://github.com/ArtifexSoftware/mupdf
4 | [submodule "zlib"]
5 | path = zlib
6 | url = https://github.com/madler/zlib
7 | ignore = untracked
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Sioyek
2 |
3 | Sioyek is a PDF viewer with a focus on textbooks and research papers.
4 |
5 | ## Contents
6 | * [Installation](#install)
7 | * [Documentation](#documentation)
8 | * [Video Demo](#feature-video-overview)
9 | * [Features](#features)
10 | * [Build Instructions](#build-instructions)
11 | * [Buy Me a Coffee (or a Book!)](#donation)
12 |
13 | ## Install
14 | ### Official packages
15 | There are installers for Windows, macOS and Linux. See [Releases page](https://github.com/ahrm/sioyek/releases).
16 |
17 | ### Homebew Cask
18 | There is a homebrew cask available here: https://formulae.brew.sh/cask/sioyek. Install by running:
19 | ```
20 | brew install --cask sioyek
21 | ```
22 | ### Third-party packages for Linux
23 | If you prefer to install sioyek with a package manager, you can look at this list. Please note that they are provided by third party packagers. USE AT YOUR OWN RISK! If you're reporting a bug for a third-party package, please mention which package you're using.
24 |
25 | Distro | Link | Maintainer
26 | ------- | ----- | -------------
27 | Flathub | [sioyek](https://flathub.org/apps/details/com.github.ahrm.sioyek) | [@nbenitez](https://flathub.org/apps/details/com.github.ahrm.sioyek)
28 | Alpine | [sioyek](https://pkgs.alpinelinux.org/packages?name=sioyek) | [@jirutka](https://github.com/jirutka)
29 | Arch | [AUR sioyek](https://aur.archlinux.org/packages/sioyek) | [@goggle](https://github.com/goggle)
30 | Arch | [AUR sioyek-git](https://aur.archlinux.org/packages/sioyek-git/) | [@hrdl-github](https://github.com/hrdl-github)
31 | Arch | [AUR sioyek-appimage](https://aur.archlinux.org/packages/sioyek-appimage/) | [@DhruvaSambrani](https://github.com/DhruvaSambrani)
32 | Debian | [sioyek](https://packages.debian.org/sioyek) | [@viccie30](https://github.com/viccie30)
33 | NixOS | [sioyek](https://search.nixos.org/packages?channel=unstable&show=sioyek&from=0&size=50&sort=relevance&type=packages&query=sioyek) | [@podocarp](https://github.com/podocarp)
34 | openSUSE | [Publishing](https://build.opensuse.org/package/show/Publishing/sioyek) | [@uncomfyhalomacro](https://github.com/uncomfyhalomacro)
35 | openSUSE | [Factory](https://build.opensuse.org/package/show/openSUSE:Factory/sioyek) | [@uncomfyhalomacro](https://github.com/uncomfyhalomacro)
36 | Ubuntu | [sioyek](https://packages.ubuntu.com/sioyek) | [@viccie30](https://github.com/viccie30)
37 |
38 |
39 | ## Documentation
40 | You can view the official documentation [here](https://sioyek-documentation.readthedocs.io/en/latest/).
41 | ## Feature Video Overview
42 |
43 | [](https://www.youtube.com/watch?v=yTmCI0Xp5vI)
44 |
45 | For a more in-depth tutorial, see this video:
46 |
47 | [](https://www.youtube.com/watch?v=RaHRvnb0dY8)
48 |
49 | ## Features
50 |
51 | ### Quick Open
52 |
53 | https://user-images.githubusercontent.com/6392321/125321111-9b29dc00-e351-11eb-873e-94ea30016a05.mp4
54 |
55 | You can quickly search and open any file you have previously interacted with using sioyek.
56 |
57 | ### Table of Contents
58 |
59 | https://user-images.githubusercontent.com/6392321/125321313-cf050180-e351-11eb-9275-c2759c684af5.mp4
60 |
61 | You can search and jump to table of contents entries.
62 |
63 | ### Smart Jump
64 |
65 | https://user-images.githubusercontent.com/6392321/125321419-e5ab5880-e351-11eb-9688-95374a22774f.mp4
66 |
67 | You can jump to any referenced figure or bibliography item *even if the PDF file doesn't provide links*. You can also search the names of bibliography items in google scholar/libgen by middle clicking/shift+middle clicking on their name.
68 |
69 | ### Overview
70 |
71 | https://user-images.githubusercontent.com/6392321/154683015-0bae4f92-78e2-4141-8446-49dd7c2bd7c9.mp4
72 |
73 | You can open a quick overview of figures/references/tables/etc. by right clicking on them (Like Smart Jump, this feature works even if the document doesn't provide links).
74 |
75 | ### Mark
76 |
77 | https://user-images.githubusercontent.com/6392321/125321811-505c9400-e352-11eb-85e0-ffc3ae5f8cb8.mp4
78 |
79 | Sometimes when reading a document you need to go back a few pages (perhaps to view a definition or something) and quickly jump back to where you were. You can achieve this by using marks. Marks are named locations within a PDF file (each mark has a single character name for example 'a' or 'm') which you can quickly jump to using their name. In the aforementioned example, before going back to the definition you mark your location and later jump back to the mark by invoking its name. Lower case marks are local to the document and upper case marks are global (this should be very familiar to you if you have used vim).
80 |
81 | ### Bookmarks
82 |
83 | https://user-images.githubusercontent.com/6392321/125322503-1a6bdf80-e353-11eb-8018-5e8fc43b8d05.mp4
84 |
85 | Bookmarks are similar to marks except they are named by a text string and they are all global.
86 |
87 | ### Highlights
88 |
89 |
90 | https://user-images.githubusercontent.com/6392321/130956728-7e0a87fa-4ada-4108-a8fc-9d9d04180f56.mp4
91 |
92 |
93 | Highlight text using different kinds of highlights. You can search among all the highlights.
94 |
95 | ### Portals (this feature is most useful for users with multiple monitors)
96 |
97 |
98 |
99 | https://user-images.githubusercontent.com/6392321/125322657-41c2ac80-e353-11eb-985e-8f3ce9808f67.mp4
100 |
101 | Suppose you are reading a paragraph which references a figure which is not very close to the current location. Jumping back and forth between the current paragraph and the figure can be very annoying. Using portals, you can link the paragraph's location to the figure's location. Sioyek shows the closest portal destination in a separate window (which is usually placed on a second monitor). This window is automatically updated to show the closest portal destination as the user navigates the document.
102 |
103 |
104 | ### Configuration
105 |
106 |
107 | https://user-images.githubusercontent.com/6392321/125337160-e4832700-e363-11eb-8801-0bee58121c2d.mp4
108 |
109 | You can customize all key bindings and some UI elements by editing `keys_user.config` and `prefs_user.config`. The default configurations are in `keys.config` and `prefs.config`.
110 |
111 |
112 |
113 | ## Build Instructions
114 |
115 | ### Linux
116 |
117 | #### Fedora
118 |
119 | Run the following commands to install dependencies, clone the repository and compile sioyek on Fedora (tested on Fedora Workstation 36).
120 |
121 | ```
122 | sudo dnf install qt5-qtbase-devel qt5-qtbase-static qt5-qt3d-devel harfbuzz-devel mesa-libGL-devel glfw-devel
123 | git clone --recursive https://github.com/ahrm/sioyek
124 | cd sioyek
125 | ./build_linux.sh
126 | ```
127 |
128 | #### Generic distribution
129 | 1. Install Qt 5 and make sure `qmake` is in `PATH`.
130 |
131 | Run `qmake --version` to make sure the `qmake` in path is using Qt 5.x.
132 | 2. Install `libharfbuzz`:
133 | ```
134 | sudo apt install libharfbuzz-dev
135 | ```
136 | 3. Clone the repository and build:
137 | ```
138 | git clone --recursive https://github.com/ahrm/sioyek
139 | cd sioyek
140 | ./build_linux.sh
141 | ```
142 |
143 | ### Windows
144 | 1. Install Visual Studio (tested on 2019, other relatively recent versions should work too)
145 | 2. Install Qt 5 and make sure qmake is in `PATH`.
146 | 3. Clone the repository and build using 64 bit Visual Studio Developer Command Prompt:
147 | ```
148 | git clone --recursive https://github.com/ahrm/sioyek
149 | cd sioyek
150 | build_windows.bat
151 | ```
152 |
153 | ### Mac
154 | 1. Install Xcode.
155 | 2. Clone the repository and build: (The code below is in Zsh, which is the default shell on macOS.)
156 | ```zsh
157 | (
158 | setopt PIPE_FAIL PRINT_EXIT_VALUE ERR_RETURN SOURCE_TRACE XTRACE
159 |
160 | git clone --recursive https://github.com/ahrm/sioyek
161 | cd sioyek
162 | chmod +x build_mac.sh
163 |
164 | brew install 'qt@5' freeglut mesa harfbuzz
165 |
166 | export PATH="/opt/homebrew/opt/qt@5/bin:$PATH"
167 | #: The above is needed to make =qmake= from =qt= be found.
168 | #: Find the path using =brew info 'qt@5'=.
169 |
170 | MAKE_PARALLEL=8 ./build_mac.sh
171 |
172 | mv build/sioyek.app /Applications/
173 | sudo codesign --force --sign - --deep /Applications/sioyek.app
174 | )
175 | ```
176 |
177 | ## Donation
178 | If you enjoy sioyek, please consider donating to support its development.
179 |
180 |
181 |
--------------------------------------------------------------------------------
/build_and_release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | if [ -z ${MAKE_PARALLEL+x} ]; then export MAKE_PARALLEL=1; else echo "MAKE_PARALLEL defined"; fi
5 | echo "MAKE_PARALLEL set to $MAKE_PARALLEL"
6 |
7 | # download linuxdeployqt if not exists
8 | if [[ ! -f linuxdeployqt-continuous-x86_64.AppImage ]]; then
9 | wget -q https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage
10 | chmod +x linuxdeployqt-continuous-x86_64.AppImage
11 | fi
12 |
13 | cd mupdf
14 | make USE_SYSTEM_HARFBUZZ=yes
15 | cd ..
16 |
17 | if [[ $1 == portable ]]; then
18 | qmake "CONFIG+=linux_app_image" pdf_viewer_build_config.pro
19 | else
20 | qmake "CONFIG+=linux_app_image non_portable" pdf_viewer_build_config.pro
21 | fi
22 |
23 | rm -rf sioyek-release 2> /dev/null
24 | make install INSTALL_ROOT=sioyek-release -j$MAKE_PARALLEL
25 |
26 | if [[ $1 == portable ]]; then
27 | cp pdf_viewer/prefs.config sioyek-release/usr/bin/prefs.config
28 | cp pdf_viewer/prefs_user.config sioyek-release/usr/bin/prefs_user.config
29 | cp pdf_viewer/keys.config sioyek-release/usr/bin/keys.config
30 | cp pdf_viewer/keys_user.config sioyek-release/usr/bin/keys_user.config
31 | cp -r pdf_viewer/shaders sioyek-release/usr/bin/shaders
32 | cp tutorial.pdf sioyek-release/usr/bin/tutorial.pdf
33 | else
34 | cp pdf_viewer/prefs.config sioyek-release/usr/bin/prefs.config
35 | cp pdf_viewer/prefs_user.config sioyek-release/usr/share/prefs_user.config
36 | cp pdf_viewer/keys.config sioyek-release/usr/bin/keys.config
37 | cp pdf_viewer/keys_user.config sioyek-release/usr/share/keys_user.config
38 | cp -r pdf_viewer/shaders sioyek-release/usr/bin/shaders
39 | cp tutorial.pdf sioyek-release/usr/bin/tutorial.pdf
40 | fi
41 |
42 | #./linuxdeployqt-continuous-x86_64.AppImage --appdir sioyek-release --plugin qt
43 | ./linuxdeployqt-continuous-x86_64.AppImage ./sioyek-release/usr/share/applications/sioyek.desktop -appimage
44 |
45 |
46 | if [[ $1 == portable ]]; then
47 | rm -rf Sioyek-x86_64.AppImage.config
48 | rm -f Sioyek-x86_64.AppImage
49 | mv Sioyek-* Sioyek-x86_64.AppImage
50 | mkdir -p Sioyek-x86_64.AppImage.config/.local/share/Sioyek
51 | cp tutorial.pdf Sioyek-x86_64.AppImage.config/.local/share/Sioyek/tutorial.pdf
52 | zip -r sioyek-release-linux-portable.zip Sioyek-x86_64.AppImage.config Sioyek-x86_64.AppImage
53 | else
54 | mv Sioyek-* Sioyek-x86_64.AppImage
55 | zip sioyek-release-linux.zip Sioyek-x86_64.AppImage
56 | fi
57 |
--------------------------------------------------------------------------------
/build_linux.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | # Compile mupdf
5 | cd mupdf
6 | make USE_SYSTEM_HARFBUZZ=yes
7 | cd ..
8 |
9 | # Compile sioyek
10 | if [ -f "/usr/bin/qmake-qt5" ];
11 | then
12 | QMAKE="/usr/bin/qmake-qt5"
13 | elif [ -f "/usr/bin/qmake" ];
14 | then
15 | QMAKE="/usr/bin/qmake"
16 | else
17 | QMAKE="qmake"
18 | fi
19 |
20 | $QMAKE "CONFIG+=linux_app_image" pdf_viewer_build_config.pro
21 | make
22 |
23 | # Copy files in build/ subdirectory
24 | rm -rf build 2> /dev/null
25 | mkdir build
26 | mv sioyek build/sioyek
27 | cp pdf_viewer/prefs.config build/prefs.config
28 | cp pdf_viewer/prefs_user.config build/prefs_user.config
29 | cp pdf_viewer/keys.config build/keys.config
30 | cp pdf_viewer/keys_user.config build/keys_user.config
31 | cp -r pdf_viewer/shaders build/shaders
32 | cp tutorial.pdf build/tutorial.pdf
33 |
--------------------------------------------------------------------------------
/build_mac.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 | # prerequisite: brew install qt@5 freeglut mesa harfbuzz
4 |
5 | #sys_glut_clfags=`pkg-config --cflags glut gl`
6 | #sys_glut_libs=`pkg-config --libs glut gl`
7 | #sys_harfbuzz_clfags=`pkg-config --cflags harfbuzz`
8 | #sys_harfbuzz_libs=`pkg-config --libs harfbuzz`
9 |
10 | if [ -z ${MAKE_PARALLEL+x} ]; then export MAKE_PARALLEL=1; else echo "MAKE_PARALLEL defined"; fi
11 | echo "MAKE_PARALLEL set to $MAKE_PARALLEL"
12 |
13 | cd mupdf
14 | #make USE_SYSTEM_HARFBUZZ=yes USE_SYSTEM_GLUT=yes SYS_GLUT_CFLAGS="${sys_glut_clfags}" SYS_GLUT_LIBS="${sys_glut_libs}" SYS_HARFBUZZ_CFLAGS="${sys_harfbuzz_clfags}" SYS_HARFBUZZ_LIBS="${sys_harfbuzz_libs}" -j 4
15 | make
16 | cd ..
17 |
18 | if [[ $1 == portable ]]; then
19 | qmake pdf_viewer_build_config.pro
20 | else
21 | qmake "CONFIG+=non_portable" pdf_viewer_build_config.pro
22 | fi
23 |
24 | make -j$MAKE_PARALLEL
25 |
26 | rm -rf build 2> /dev/null
27 | mkdir build
28 | mv sioyek.app build/
29 | cp -r pdf_viewer/shaders build/sioyek.app/Contents/MacOS/shaders
30 |
31 | cp pdf_viewer/prefs.config build/sioyek.app/Contents/MacOS/prefs.config
32 | cp pdf_viewer/prefs_user.config build/sioyek.app/Contents/MacOS/prefs_user.config
33 | cp pdf_viewer/keys.config build/sioyek.app/Contents/MacOS/keys.config
34 | cp pdf_viewer/keys_user.config build/sioyek.app/Contents/MacOS/keys_user.config
35 | cp tutorial.pdf build/sioyek.app/Contents/MacOS/tutorial.pdf
36 |
37 | # Capture the current PATH
38 | CURRENT_PATH=$(echo $PATH)
39 |
40 | # Define the path to the Info.plist file inside the app bundle
41 | INFO_PLIST="resources/Info.plist"
42 |
43 | # Add LSEnvironment key with PATH to Info.plist
44 | /usr/libexec/PlistBuddy -c "Add :LSEnvironment dict" "$INFO_PLIST" || echo "LSEnvironment already exists"
45 | /usr/libexec/PlistBuddy -c "Add :LSEnvironment:PATH string $CURRENT_PATH" "$INFO_PLIST" || /usr/libexec/PlistBuddy -c "Set :LSEnvironment:PATH $CURRENT_PATH" "$INFO_PLIST"
46 |
47 | macdeployqt build/sioyek.app -dmg
48 | zip -r sioyek-release-mac.zip build/sioyek.dmg
49 |
--------------------------------------------------------------------------------
/build_windows.bat:
--------------------------------------------------------------------------------
1 | cd mupdf\platform\win32\
2 | msbuild mupdf.sln /property:Configuration=Debug
3 | msbuild mupdf.sln /property:Configuration=Release
4 | cd ..\..\..
5 |
6 | cd zlib
7 | nmake -f win32/makefile.msc
8 | cd ..
9 |
10 | if %1 == portable (
11 | qmake -tp vc pdf_viewer_build_config.pro
12 | ) else (
13 | qmake -tp vc "DEFINES+=NON_PORTABLE" pdf_viewer_build_config.pro
14 | )
15 |
16 | msbuild -maxcpucount sioyek.vcxproj /property:Configuration=Release
17 | rmdir /S sioyek-release-windows
18 | mkdir sioyek-release-windows
19 | copy release\sioyek.exe sioyek-release-windows\sioyek.exe
20 | copy pdf_viewer\keys.config sioyek-release-windows\keys.config
21 | copy pdf_viewer\prefs.config sioyek-release-windows\prefs.config
22 | xcopy /E /I pdf_viewer\shaders sioyek-release-windows\shaders\
23 | copy tutorial.pdf sioyek-release-windows\tutorial.pdf
24 | windeployqt sioyek-release-windows\sioyek.exe
25 | copy windows_runtime\vcruntime140_1.dll sioyek-release-windows\vcruntime140_1.dll
26 | copy windows_runtime\libssl-1_1-x64.dll sioyek-release-windows\libssl-1_1-x64.dll
27 | copy windows_runtime\libcrypto-1_1-x64.dll sioyek-release-windows\libcrypto-1_1-x64.dll
28 |
29 | if %1 == portable (
30 | copy pdf_viewer\keys_user.config sioyek-release-windows\keys_user.config
31 | copy pdf_viewer\prefs_user.config sioyek-release-windows\prefs_user.config
32 | 7z a sioyek-release-windows-portable.zip sioyek-release-windows
33 |
34 | ) else (
35 | 7z a sioyek-release-windows.zip sioyek-release-windows
36 | )
37 |
--------------------------------------------------------------------------------
/delete_build.sh:
--------------------------------------------------------------------------------
1 | rm -r Sioyek*
2 | rm sioyek
3 | rm *.o
4 | rm -r sioyek-release
5 |
--------------------------------------------------------------------------------
/pdf_viewer/.gitignore:
--------------------------------------------------------------------------------
1 | data/*
2 | x64/*
3 | release/*
4 | Debug/*
5 | .vscode/*
6 | Generated
7 | *.pdf
8 | *.pdb
9 | *.db
10 | *.aps
11 | .qmake.stash
12 | last_document_path.txt
13 | imgui.ini
14 | *.jar
15 | last_document_path.txt
16 | *.o
17 |
--------------------------------------------------------------------------------
/pdf_viewer/OpenWithApplication.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | bool OpenWithApplication::event(QEvent *event) {
4 | if (event->type() == QEvent::FileOpen) {
5 | QFileOpenEvent *openEvent = static_cast(event);
6 | emit file_ready(openEvent->file());
7 | }
8 |
9 | return QApplication::event(event);
10 | }
11 |
--------------------------------------------------------------------------------
/pdf_viewer/OpenWithApplication.h:
--------------------------------------------------------------------------------
1 | #ifndef OPEN_WITH_APP_H
2 | #define OPEN_WITH_APP_H
3 |
4 | #include
5 | #include
6 |
7 | class OpenWithApplication : public QApplication
8 | {
9 | Q_OBJECT
10 | public:
11 | OpenWithApplication(int &argc, char **argv)
12 | : QApplication(argc, argv)
13 | {
14 | }
15 | signals:
16 | void file_ready(const QString& file_name);
17 |
18 | protected:
19 | bool event(QEvent *event) override;
20 | };
21 |
22 | #endif // OPEN_WITH_APP_H
--------------------------------------------------------------------------------
/pdf_viewer/RunGuard.cpp:
--------------------------------------------------------------------------------
1 | #include "RunGuard.h"
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | namespace
8 | {
9 | QString generateKeyHash(const QString& key, const QString& salt)
10 | {
11 | QByteArray data;
12 | data.append(key.toUtf8());
13 | data.append(salt.toUtf8());
14 | data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex();
15 | return data;
16 | }
17 | }
18 |
19 | RunGuard::RunGuard(const QString &key) : QObject{},
20 | key(key),
21 | memoryKey(generateKeyHash(key, "_sharedMemKey"))
22 | {
23 | // By explicitly attaching it and then deleting it we make sure that the
24 | // memory is deleted even after the process has crashed on Unix.
25 | memory = new QSharedMemory{memoryKey};
26 | memory->attach();
27 | delete memory;
28 |
29 | // Guarantee process safe behaviour with a shared memory block.
30 | memory = new QSharedMemory(memoryKey);
31 |
32 | // Creates a shared memory segment then attaches to it with the given
33 | // access mode and returns true.
34 | // If a shared memory segment identified by the key already exists, the
35 | // attach operation is not performed and false is returned.
36 |
37 | qDebug() << "Creating shared memory block...";
38 | bool isPrimary = false;
39 | if (memory->create(sizeof(quint64))) {
40 | qDebug() << "Shared memory created: this is the primary application.";
41 | isPrimary = true;
42 | } else {
43 | qDebug() << "Shared memory already exists: this is a secondary application.";
44 | qDebug() << "Secondary application attaching to shared memory block...";
45 | if (!memory->attach()) {
46 | qCritical() << "Secondary application cannot attach to shared memory block.";
47 | QCoreApplication::exit();
48 | }
49 | qDebug() << "Secondary application successfully attached to shared memory block.";
50 | }
51 |
52 | memory->lock();
53 | if (isPrimary) { // Start primary server.
54 | qDebug() << "Starting IPC server...";
55 | QLocalServer::removeServer(key);
56 | server = new QLocalServer;
57 | server->setSocketOptions(QLocalServer::UserAccessOption);
58 | if (server->listen(key)) {
59 | qDebug() << "IPC server started.";
60 | } else {
61 | qCritical() << "Cannot start IPC server.";
62 | QCoreApplication::exit();
63 | }
64 | QObject::connect(server, &QLocalServer::newConnection,
65 | this, &RunGuard::onNewConnection);
66 | }
67 | memory->unlock();
68 | }
69 |
70 | RunGuard::~RunGuard()
71 | {
72 | memory->lock();
73 | if (server) {
74 | server->close();
75 | delete server;
76 | server = nullptr;
77 | }
78 | memory->unlock();
79 | }
80 |
81 | bool RunGuard::isPrimary()
82 | {
83 | return server != nullptr;
84 | }
85 |
86 | bool RunGuard::isSecondary()
87 | {
88 | return server == nullptr;
89 | }
90 |
91 | void RunGuard::onNewConnection()
92 | {
93 | QLocalSocket *socket = server->nextPendingConnection();
94 | QObject::connect(socket, &QLocalSocket::disconnected, this,
95 | [socket]() {
96 | socket->deleteLater();
97 | }
98 | );
99 | QObject::connect(socket, &QLocalSocket::readyRead, this, [socket, this]() {
100 | readMessage(socket);
101 | });
102 | }
103 |
104 | void RunGuard::readMessage(QLocalSocket *socket)
105 | {
106 | QByteArray data = socket -> readAll();
107 | emit messageReceived(data);
108 | }
109 |
110 | void RunGuard::sendMessage(const QByteArray &message)
111 | {
112 | QLocalSocket socket;
113 | socket.connectToServer(key, QLocalSocket::WriteOnly);
114 | socket.waitForConnected();
115 | if (socket.state() == QLocalSocket::ConnectedState) {
116 | if (socket.state() == QLocalSocket::ConnectedState) {
117 | socket.write(message);
118 | if (socket.waitForBytesWritten()) {
119 | qCritical() << "Secondary application sent message to IPC server.";
120 | }
121 | }
122 | } else {
123 | qCritical() << "Secondary application cannot connect to IPC server.";
124 | qCritical() << "Socker error: " << socket.error();
125 | QCoreApplication::exit();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/pdf_viewer/RunGuard.h:
--------------------------------------------------------------------------------
1 | #ifndef SINGLE_INSTANCE_GUARD_H
2 | #define SINGLE_INSTANCE_GUARD_H
3 |
4 | #include
5 |
6 | #include
7 | #include
8 |
9 | /**
10 | * This is an control to guarantee that only one application instance exists at
11 | * any time.
12 | * It uses shared memory to check that no more than one instance is running at
13 | * the same time and also it uses Inter Process Communication (IPC) for a
14 | * secondary application instance to send parameters to the primary application
15 | * instance before quitting.
16 | * An Application must be contructed before the control for signals-slot
17 | * communication to work.
18 | *
19 | * Usage example:
20 | *
21 | * int main(int argc, char *argv[])
22 | * {
23 | * QApplication app{argc, argv};
24 | *
25 | * ...
26 | *
27 | * RunGuard guard{"Lentigram"};
28 | * if (guard.isPrimary()) {
29 | * QObject::connect(
30 | * &guard,
31 | * &RunGuard::messageReceived, [this](const QByteArray &message) {
32 | *
33 | * ...process message coming from secondary application...
34 | *
35 | * qDebug() << message;
36 | * }
37 | * );
38 | * } else {
39 | * guard.sendMessage(app.arguments().join(' ').toUtf8());
40 | * return 0;
41 | * }
42 | *
43 | * ...
44 | *
45 | * app.exec();
46 | * }
47 | *
48 | * This code is inspired by the following:
49 | * https://stackoverflow.com/questions/5006547/qt-best-practice-for-a-single-instance-app-protection
50 | * https://github.com/itay-grudev/SingleApplication
51 | */
52 | class RunGuard : public QObject
53 | {
54 | Q_OBJECT
55 |
56 | public:
57 | explicit RunGuard(const QString &key);
58 | ~RunGuard();
59 |
60 | bool isPrimary();
61 | bool isSecondary();
62 | void sendMessage(const QByteArray &message);
63 |
64 | signals:
65 | void messageReceived(const QByteArray &message);
66 |
67 | private slots:
68 | void onNewConnection();
69 |
70 | private:
71 |
72 | const QString key;
73 | const QString sharedMemLockKey;
74 | const QString memoryKey;
75 |
76 | QSharedMemory *memory;
77 | QLocalServer *server = nullptr;
78 |
79 | void readMessage(QLocalSocket *socket);
80 | };
81 |
82 | #endif // SINGLE_INSTANCE_GUARD_H
83 |
--------------------------------------------------------------------------------
/pdf_viewer/book.cpp:
--------------------------------------------------------------------------------
1 | #include "book.h"
2 |
3 |
4 | bool operator==(DocumentViewState& lhs, const DocumentViewState& rhs)
5 | {
6 | return (lhs.book_state.offset_x == rhs.book_state.offset_x) &&
7 | (lhs.book_state.offset_y == rhs.book_state.offset_y) &&
8 | (lhs.book_state.zoom_level == rhs.book_state.zoom_level) &&
9 | (lhs.document_path == rhs.document_path);
10 | }
11 |
12 | bool operator==(const CachedPageData& lhs, const CachedPageData& rhs) {
13 | if (lhs.doc != rhs.doc) return false;
14 | if (lhs.page != rhs.page) return false;
15 | if (lhs.zoom_level != rhs.zoom_level) return false;
16 | return true;
17 | }
18 |
19 | Portal Portal::with_src_offset(float src_offset)
20 | {
21 | Portal res = Portal();
22 | res.src_offset_y = src_offset;
23 | return res;
24 | }
25 |
26 | QJsonObject Mark::to_json() const
27 | {
28 | QJsonObject res;
29 | res["y_offset"] = y_offset;
30 | res["symbol"] = symbol;
31 | return res;
32 | }
33 |
34 | void Mark::from_json(const QJsonObject& json_object)
35 | {
36 | y_offset = json_object["y_offset"].toDouble();
37 | symbol = static_cast(json_object["symbol"].toInt());
38 | }
39 |
40 | QJsonObject BookMark::to_json() const
41 | {
42 | QJsonObject res;
43 | res["y_offset"] = y_offset;
44 | res["description"] = QString::fromStdWString(description);
45 | return res;
46 |
47 | }
48 |
49 | void BookMark::from_json(const QJsonObject& json_object)
50 | {
51 | y_offset = json_object["y_offset"].toDouble();
52 | description = json_object["description"].toString().toStdWString();
53 | }
54 |
55 | QJsonObject Highlight::to_json() const
56 | {
57 | QJsonObject res;
58 | res["selection_begin_x"] = selection_begin.x;
59 | res["selection_begin_y"] = selection_begin.y;
60 | res["selection_end_x"] = selection_end.x;
61 | res["selection_end_y"] = selection_end.y;
62 | res["description"] = QString::fromStdWString(description);
63 | res["type"] = type;
64 | return res;
65 | }
66 |
67 | void Highlight::from_json(const QJsonObject& json_object)
68 | {
69 | selection_begin.x = json_object["selection_begin_x"].toDouble();
70 | selection_begin.y = json_object["selection_begin_y"].toDouble();
71 | selection_end.x = json_object["selection_end_x"].toDouble();
72 | selection_end.y = json_object["selection_end_y"].toDouble();
73 | description = json_object["description"].toString().toStdWString();
74 | type = static_cast(json_object["type"].toInt());
75 | }
76 |
77 | QJsonObject Portal::to_json() const
78 | {
79 | QJsonObject res;
80 | res["src_offset_y"] = src_offset_y;
81 | res["dst_checksum"] = QString::fromStdString(dst.document_checksum);
82 | res["dst_offset_x"] = dst.book_state.offset_x;
83 | res["dst_offset_y"] = dst.book_state.offset_y;
84 | res["dst_zoom_level"] = dst.book_state.zoom_level;
85 | return res;
86 | }
87 |
88 | void Portal::from_json(const QJsonObject& json_object)
89 | {
90 | src_offset_y = json_object["src_offset_y"].toDouble();
91 | dst.document_checksum = json_object["dst_checksum"].toString().toStdString();
92 | dst.book_state.offset_x = json_object["dst_offset_x"].toDouble();
93 | dst.book_state.offset_y = json_object["dst_offset_y"].toDouble();
94 | dst.book_state.zoom_level = json_object["dst_zoom_level"].toDouble();
95 | }
96 |
97 | bool operator==(const Mark& lhs, const Mark& rhs)
98 | {
99 | return (lhs.symbol == rhs.symbol) && (lhs.y_offset == rhs.y_offset);
100 | }
101 |
102 | bool operator==(const BookMark& lhs, const BookMark& rhs)
103 | {
104 | return (lhs.y_offset == rhs.y_offset) && (lhs.description == rhs.description);
105 | }
106 |
107 | bool operator==(const fz_point& lhs, const fz_point& rhs) {
108 | return (lhs.y == rhs.y) && (lhs.x == rhs.x);
109 | }
110 |
111 | bool operator==(const Highlight& lhs, const Highlight& rhs)
112 | {
113 | return (lhs.selection_begin.x == rhs.selection_begin.x) && (lhs.selection_end.x == rhs.selection_end.x) &&
114 | (lhs.selection_begin.y == rhs.selection_begin.y) && (lhs.selection_end.y == rhs.selection_end.y) ;
115 | }
116 |
117 | bool operator==(const Portal& lhs, const Portal& rhs)
118 | {
119 | return (lhs.src_offset_y == rhs.src_offset_y) && (lhs.dst.document_checksum == rhs.dst.document_checksum);
120 | }
121 |
122 |
--------------------------------------------------------------------------------
/pdf_viewer/book.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | //#include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "coordinates.h"
13 |
14 | class DocumentView;
15 |
16 | struct BookState {
17 | std::wstring document_path;
18 | float offset_y;
19 | };
20 |
21 | struct OpenedBookState {
22 | float zoom_level;
23 | float offset_x;
24 | float offset_y;
25 | };
26 |
27 | /*
28 | A mark is a location in the document labeled with a symbol (which is a single character [a-z]). For example
29 | we can mark a location with symbol 'a' and later return to that location by going to the mark named 'a'.
30 | Lower case marks are local to the document and upper case marks are global.
31 | */
32 | struct Mark {
33 | float y_offset;
34 | char symbol;
35 |
36 | QJsonObject to_json() const;
37 | void from_json(const QJsonObject& json_object);
38 | };
39 |
40 | /*
41 | A bookmark is similar to mark but instead of being indexed by a symbol, it has a description.
42 | */
43 | struct BookMark {
44 | float y_offset;
45 | std::wstring description;
46 |
47 | QJsonObject to_json() const;
48 | void from_json(const QJsonObject& json_object);
49 | };
50 |
51 | struct Highlight {
52 | AbsoluteDocumentPos selection_begin;
53 | AbsoluteDocumentPos selection_end;
54 | std::wstring description;
55 | char type;
56 | std::vector highlight_rects;
57 |
58 | QJsonObject to_json() const;
59 | void from_json(const QJsonObject& json_object);
60 | };
61 |
62 |
63 | struct PdfLink {
64 | fz_rect rect;
65 | std::string uri;
66 | };
67 |
68 | struct DocumentViewState {
69 | std::wstring document_path;
70 | OpenedBookState book_state;
71 | };
72 |
73 | struct PortalViewState {
74 | std::string document_checksum;
75 | OpenedBookState book_state;
76 | };
77 |
78 | /*
79 | A link is a connection between two document locations. For example when reading a paragraph that is referencing a figure,
80 | we may want to link that paragraphs's location to the figure. We can then easily switch between the paragraph and the figure.
81 | Also if helper window is opened, it automatically displays the closest link to the current location.
82 | Note that this is different from PdfLink which is the built-in link functionality in PDF file format.
83 | */
84 | struct Portal {
85 | static Portal with_src_offset(float src_offset);
86 |
87 | PortalViewState dst;
88 | float src_offset_y;
89 |
90 | QJsonObject to_json() const;
91 | void from_json(const QJsonObject& json_object);
92 | };
93 |
94 |
95 | bool operator==(DocumentViewState& lhs, const DocumentViewState& rhs);
96 |
97 | struct SearchResult {
98 | std::vector rects;
99 | int page;
100 | };
101 |
102 |
103 | struct TocNode {
104 | std::vector children;
105 | std::wstring title;
106 | int page;
107 |
108 | float y;
109 | float x;
110 | };
111 |
112 |
113 | class Document;
114 |
115 | struct CachedPageData {
116 | Document* doc = nullptr;
117 | int page;
118 | float zoom_level;
119 | };
120 |
121 | /*
122 | A cached page consists of cached_page_data which is the header that describes the rendered location
123 | and the actual rendered page. We have two different rendered formats: the pixmap we got from mupdf and
124 | the cached_page_texture which is an OpenGL texture. The reason we need both formats in the structure is because
125 | we render the pixmaps in a background mupdf thread, but in order to be able to use a texture in the main thread,
126 | the texture has to be created in the main thread. Therefore, we fill this structure's cached_page_pixmap value in the
127 | background thread and then send it to the main thread where we create the texture (which is a relatively fast operation
128 | so it doesn't matter that it is in the main thread). When cached_page_texture is created, we can safely delete the
129 | cached_page_pixmap, but the pixmap can only be deleted in the thread that it was created in, so we have to once again,
130 | send the cached_page_texture back to the background thread to be deleted.
131 | */
132 | struct CachedPage {
133 | CachedPageData cached_page_data;
134 | fz_pixmap* cached_page_pixmap = nullptr;
135 |
136 | // last_access_time is used to garbage collect old pages
137 | unsigned int last_access_time;
138 | GLuint cached_page_texture;
139 | };
140 | bool operator==(const CachedPageData& lhs, const CachedPageData& rhs);
141 |
142 |
143 | /*
144 | When a document does not have built-in links to the figures, we use a heuristic to find the figures
145 | and index them in FigureData structure. Using this, we can quickly find the figures when user clicks on the
146 | text descripbing the figure (for example 'Fig. 2.13')
147 | */
148 | struct IndexedData {
149 | int page;
150 | float y_offset;
151 | std::wstring text;
152 | };
153 |
154 | bool operator==(const Mark& lhs, const Mark& rhs);
155 | bool operator==(const BookMark& lhs, const BookMark& rhs);
156 | bool operator==(const Highlight& lhs, const Highlight& rhs);
157 | bool operator==(const Portal& lhs, const Portal& rhs);
158 |
--------------------------------------------------------------------------------
/pdf_viewer/checksum.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "checksum.h"
4 |
5 | std::string compute_checksum(const QString &file_name, QCryptographicHash::Algorithm hash_algorithm)
6 | {
7 | QFile infile(file_name);
8 | qint64 file_size = infile.size();
9 | const qint64 buffer_size = 10240;
10 |
11 | if (infile.open(QIODevice::ReadOnly))
12 | {
13 | char buffer[buffer_size];
14 | int bytes_read;
15 | int read_size = qMin(file_size, buffer_size);
16 |
17 | QCryptographicHash hash(hash_algorithm);
18 | while (read_size > 0 && (bytes_read = infile.read(buffer, read_size)) > 0)
19 | {
20 | file_size -= bytes_read;
21 | hash.addData(buffer, bytes_read);
22 | read_size = qMin(file_size, buffer_size);
23 | }
24 |
25 | infile.close();
26 | return QString(hash.result().toHex()).toStdString();
27 | }
28 | return "";
29 | }
30 |
31 | CachedChecksummer::CachedChecksummer(const std::vector>* loaded_checksums){
32 | if (loaded_checksums) {
33 | for (const auto& [path, checksum_] : *loaded_checksums) {
34 | std::string checksum = QString::fromStdWString(checksum_).toStdString();
35 | cached_checksums[path] = checksum;
36 | cached_paths[checksum].push_back(path);
37 | }
38 | }
39 | }
40 |
41 | std::optional CachedChecksummer::get_checksum_fast(std::wstring file_path) {
42 | // return the checksum only if it is alreay precomputed in cache
43 | if (cached_checksums.find(file_path) != cached_checksums.end()) {
44 | return cached_checksums[file_path];
45 | }
46 | return {};
47 | }
48 |
49 | std::string CachedChecksummer::get_checksum(std::wstring file_path) {
50 |
51 | auto cached_checksum = get_checksum_fast(file_path);
52 |
53 | if (!cached_checksum) {
54 | std::string checksum = compute_checksum(QString::fromStdWString(file_path), QCryptographicHash::Md5);
55 | cached_checksums[file_path] = checksum;
56 | cached_paths[checksum].push_back(file_path);
57 | }
58 | return cached_checksums[file_path];
59 |
60 | }
61 |
62 | std::optional CachedChecksummer::get_path(std::string checksum) {
63 | const std::vector paths = cached_paths[checksum];
64 |
65 | for (const auto& path_string : paths) {
66 | if (QFile::exists(QString::fromStdWString(path_string))) {
67 | return path_string;
68 | }
69 | }
70 | return {};
71 | }
--------------------------------------------------------------------------------
/pdf_viewer/checksum.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | std::string compute_checksum(const QString& file_name, QCryptographicHash::Algorithm hash_algorithm);
11 |
12 | class CachedChecksummer {
13 | private:
14 | std::unordered_map cached_checksums;
15 | std::unordered_map> cached_paths;
16 |
17 | public:
18 | CachedChecksummer(const std::vector>* loaded_checksums);
19 | std::string get_checksum(std::wstring file_path);
20 | std::optional get_checksum_fast(std::wstring file_path);
21 | std::optional get_path(std::string checksum);
22 | };
--------------------------------------------------------------------------------
/pdf_viewer/config.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include "path.h"
10 |
11 |
12 | struct Config {
13 |
14 | std::wstring name;
15 | void* value = nullptr;
16 | void (*serialize) (void*, std::wstringstream&) = nullptr;
17 | void* (*deserialize) (std::wstringstream&, void* res) = nullptr;
18 | bool (*validator) (const std::wstring& value);
19 |
20 | void* get_value();
21 |
22 | };
23 |
24 | class ConfigManager {
25 | std::vector configs;
26 |
27 | Config* get_mut_config_with_name(std::wstring config_name);
28 | float DEFAULT_TEXT_HIGHLIGHT_COLOR[3];
29 | float DEFAULT_VERTICAL_LINE_COLOR[4];
30 | float DEFAULT_SEARCH_HIGHLIGHT_COLOR[3];
31 | float DEFAULT_LINK_HIGHLIGHT_COLOR[3];
32 | float DEFAULT_SYNCTEX_HIGHLIGHT_COLOR[3];
33 |
34 | std::vector user_config_paths;
35 |
36 | public:
37 |
38 | ConfigManager(const Path& default_path, const Path& auto_path ,const std::vector& user_paths);
39 | //void serialize(std::wofstream& file);
40 | void deserialize(const Path& default_file_path, const Path& auto_path, const std::vector& user_file_paths);
41 | void deserialize_file(const Path& file_path, bool warn_if_not_exists=false);
42 | template
43 | const T* get_config(std::wstring name) {
44 |
45 | void* raw_pointer = get_mut_config_with_name(name)->get_value();
46 |
47 | // todo: Provide a default value for all configs, so that all the nullchecks here and in the
48 | // places where `get_config` is called can be removed.
49 | if (raw_pointer == nullptr) return nullptr;
50 | return (T*)raw_pointer;
51 | }
52 | std::optional get_or_create_user_config_file();
53 | std::vector get_all_user_config_files();
54 | std::vector get_configs();
55 | void deserialize_config(std::string config_name, std::wstring config_value);
56 | };
57 |
--------------------------------------------------------------------------------
/pdf_viewer/coordinates.cpp:
--------------------------------------------------------------------------------
1 | #include "coordinates.h"
2 |
3 | WindowPos::WindowPos(float x_, float y_) {
4 | x = static_cast(x_);
5 | y = static_cast(y_);
6 | }
7 |
8 | WindowPos::WindowPos(int x_, int y_) {
9 | x = x_;
10 | y = y_;
11 | }
12 |
13 | WindowPos::WindowPos() {
14 | x = 0;
15 | y = 0;
16 | }
17 |
--------------------------------------------------------------------------------
/pdf_viewer/coordinates.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 |
5 | struct DocumentPos {
6 | int page;
7 | float x;
8 | float y;
9 | };
10 |
11 | struct AbsoluteDocumentPos {
12 | float x;
13 | // this is the concatenated y-coordinate of the current page (sum of all page heights up to current location)
14 | float y;
15 | };
16 |
17 | // normalized window coordinates. x and y are in the range [-1, 1]
18 | struct NormalizedWindowPos {
19 | float x;
20 | float y;
21 | };
22 |
23 | // window coordinate in pixels
24 | struct WindowPos {
25 | int x;
26 | int y;
27 |
28 | WindowPos(float x_, float y_);
29 | WindowPos(int x_, int y_);
30 | WindowPos();
31 | };
32 |
33 |
34 | template
35 | struct Vec {
36 | T values[dim];
37 |
38 | Vec(const QPoint& p) {
39 | values[0] = p.x();
40 | values[1] = p.y();
41 | }
42 |
43 | Vec(const NormalizedWindowPos& p) {
44 | values[0] = p.x;
45 | values[1] = p.y;
46 | }
47 |
48 | Vec(const WindowPos& p) {
49 | values[0] = p.x;
50 | values[1] = p.y;
51 | }
52 |
53 | Vec(const AbsoluteDocumentPos& p) {
54 | values[0] = p.x;
55 | values[1] = p.y;
56 | }
57 |
58 | Vec(T v1, T v2) {
59 | values[0] = v1;
60 | values[1] = v2;
61 | }
62 |
63 | Vec(T values_[]) : values(values_) {
64 |
65 | }
66 |
67 | Vec() {
68 | for (int i = 0; i < dim; i++) {
69 | values[i] = 0;
70 | }
71 | }
72 |
73 | T& operator[](int i) {
74 | return values[i];
75 | }
76 |
77 | const T& operator[](int i) const {
78 | return values[i];
79 | }
80 |
81 | T x() {
82 | return values[0];
83 | }
84 |
85 | T y() {
86 | return values[1];
87 | }
88 |
89 | T width() {
90 | return values[0];
91 | }
92 |
93 | T height() {
94 | return values[1];
95 | }
96 |
97 | NormalizedWindowPos to_normalized_window_pos() {
98 | return NormalizedWindowPos{ values[0], values[1] };
99 | }
100 | };
101 |
102 | using ivec2 = Vec;
103 | using fvec2 = Vec;
104 |
105 | template
106 | Vec operator/(const Vec& lhs, float rhs) {
107 | Vec res;
108 | for (int i = 0; i < dim; i++) {
109 | res[i] = lhs[i] / rhs;
110 | }
111 | return res;
112 | }
113 |
114 | template
115 | Vec operator+(const Vec& lhs, const Vec& rhs) {
116 | Vec res;
117 | for (int i = 0; i < dim; i++) {
118 | res[i] = lhs[i] + rhs[i];
119 | }
120 | return res;
121 | }
122 |
123 | template
124 | Vec operator-(const Vec& lhs, const Vec& rhs) {
125 | Vec res;
126 | for (int i = 0; i < dim; i++) {
127 | res[i] = lhs[i] - rhs[i];
128 | }
129 | return res;
130 | }
131 |
132 | //template
133 | //operator+ (Vec a, Vec b) {
134 | // Vec c;
135 | // for (int i = 0; i < dim; i++) {
136 | // c.values[i] = a.values[i] + b.values[i];
137 | // }
138 | // return c;
139 | //}
140 |
--------------------------------------------------------------------------------
/pdf_viewer/data/last_document_path.txt:
--------------------------------------------------------------------------------
1 | C:\Users\Lion\source\repos\pdf_viewer\pdf_viewer\data\test.pdf
2 |
--------------------------------------------------------------------------------
/pdf_viewer/database.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include "sqlite3.h"
8 | #include "book.h"
9 | #include "utils.h"
10 | #include "checksum.h"
11 |
12 | class DatabaseManager {
13 | private:
14 | sqlite3* local_db;
15 | sqlite3* global_db;
16 | bool create_opened_books_table();
17 | bool create_marks_table();
18 | bool create_bookmarks_table();
19 | bool create_links_table();
20 | void create_tables();
21 | bool create_document_hash_table();
22 | bool create_highlights_table();
23 | public:
24 | bool open(const std::wstring& local_db_file_path, const std::wstring& global_db_file_path);
25 | bool select_opened_book(const std::string& book_path, std::vector& out_result);
26 | bool insert_mark(const std::string& checksum, char symbol, float offset_y);
27 | bool update_mark(const std::string& checksum, char symbol, float offset_y);
28 | bool update_book(const std::string& path, float zoom_level, float offset_x, float offset_y);
29 | bool select_mark(const std::string& checksum, std::vector& out_result);
30 | bool insert_bookmark(const std::string& checksum, const std::wstring& desc, float offset_y);
31 | bool select_bookmark(const std::string& checksum, std::vector& out_result);
32 | bool insert_portal(const std::string& src_checksum, const std::string& dst_checksum, float dst_offset_y, float dst_offset_x, float dst_zoom_level, float src_offset_y);
33 | bool select_links(const std::string& src_checksum, std::vector& out_result);
34 | bool delete_link(const std::string& src_checksum, float src_offset_y);
35 | bool delete_bookmark(const std::string& src_checksum, float src_offset_y);
36 | bool global_select_bookmark(std::vector>& out_result);
37 | bool global_select_highlight(std::vector>& out_result);
38 | bool update_portal(const std::string& checksum, float dst_offset_x, float dst_offset_y, float dst_zoom_level, float src_offset_y);
39 | bool select_opened_books_path_values(std::vector& out_result);
40 | bool delete_mark_with_symbol(char symbol);
41 | bool select_global_mark(char symbol, std::vector>& out_result);
42 | bool delete_opened_book(const std::string& book_path);
43 | bool delete_highlight(const std::string& checksum, float begin_x, float begin_y, float end_x, float end_y);
44 | bool select_highlight(const std::string& checksum, std::vector& out_result);
45 | bool select_highlight_with_type(const std::string& checksum, char type, std::vector& out_result);
46 | bool insert_highlight(const std::string& checksum,
47 | const std::wstring& desc,
48 | float begin_x,
49 | float begin_y,
50 | float end_x,
51 | float end_y,
52 | char type);
53 | bool get_path_from_hash(const std::string& checksum, std::vector& out_paths);
54 | bool get_hash_from_path(const std::string& path, std::vector& out_checksum);
55 | bool get_prev_path_hash_pairs(std::vector>& out_pairs);
56 | bool insert_document_hash(const std::wstring& path, const std::string& checksum);
57 | void upgrade_database_hashes();
58 | void split_database(const std::wstring& local_database_path, const std::wstring& global_database_path, bool was_using_hashes);
59 | void export_json(std::wstring json_file_path, CachedChecksummer* checksummer);
60 | void import_json(std::wstring json_file_path, CachedChecksummer* checksummer);
61 | void ensure_database_compatibility(const std::wstring& local_db_file_path, const std::wstring& global_db_file_path);
62 | };
63 |
64 |
--------------------------------------------------------------------------------
/pdf_viewer/document_view.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 |
18 | #include
19 | #include "sqlite3.h"
20 |
21 | #include "pdf_renderer.h"
22 | #include "document.h"
23 | #include "utils.h"
24 | #include "config.h"
25 | #include "ui.h"
26 | #include "checksum.h"
27 |
28 | extern float ZOOM_INC_FACTOR;
29 | extern const int PAGE_PADDINGS;
30 |
31 |
32 | class DocumentView {
33 | protected:
34 |
35 | private:
36 | fz_context* mupdf_context = nullptr;
37 | DatabaseManager* db_manager = nullptr;
38 | ConfigManager* config_manager = nullptr;
39 | DocumentManager* document_manager = nullptr;
40 | CachedChecksummer* checksummer;
41 | Document* current_document = nullptr;
42 |
43 | float zoom_level = 0;
44 | float offset_x = 0;
45 | float offset_y = 0;
46 |
47 | //float vertical_line_begin_pos = 0;
48 | std::optional ruler_rect;
49 | float ruler_pos = 0;
50 | int line_index = -1;
51 |
52 | int view_width = 0;
53 | int view_height = 0;
54 | bool is_auto_resize_mode = true;
55 |
56 |
57 | public:
58 | std::vector selected_character_rects;
59 |
60 | DocumentView( fz_context* mupdf_context, DatabaseManager* db_manager, DocumentManager* document_manager, ConfigManager* config_manager, CachedChecksummer* checksummer);
61 | DocumentView( fz_context* mupdf_context, DatabaseManager* db_manager, DocumentManager* document_manager, ConfigManager* config_manager, CachedChecksummer* checksummer, bool* invalid_flag,
62 | std::wstring path, int view_width, int view_height, float offset_x, float offset_y);
63 | ~DocumentView();
64 | float get_zoom_level();
65 | DocumentViewState get_state();
66 | PortalViewState get_checksum_state();
67 | void set_opened_book_state(const OpenedBookState& state);
68 | void handle_escape();
69 | void set_book_state(OpenedBookState state);
70 | void set_offsets(float new_offset_x, float new_offset_y);
71 | Document* get_document();
72 | std::optional find_closest_portal(bool limit=false);
73 | std::optional find_closest_bookmark();
74 | void goto_link(Portal* link);
75 | void delete_closest_portal();
76 | void delete_closest_bookmark();
77 | Highlight get_highlight_with_index(int index);
78 | void delete_highlight_with_index(int index);
79 | void delete_highlight(Highlight hl);
80 | void delete_closest_bookmark_to_offset(float offset);
81 | float get_offset_x();
82 | float get_offset_y();
83 | AbsoluteDocumentPos get_offsets();
84 | int get_view_height();
85 | int get_view_width();
86 | void set_null_document();
87 | void set_offset_x(float new_offset_x);
88 | void set_offset_y(float new_offset_y);
89 | std::optional get_link_in_pos(WindowPos pos);
90 | int get_highlight_index_in_pos(WindowPos pos);
91 | void get_text_selection(AbsoluteDocumentPos selection_begin, AbsoluteDocumentPos selection_end, bool is_word_selection, std::vector& selected_characters, std::wstring& text_selection);
92 | void add_mark(char symbol);
93 | void add_bookmark(std::wstring desc);
94 | void add_highlight(AbsoluteDocumentPos selection_begin, AbsoluteDocumentPos selection_end, char type);
95 | void on_view_size_change(int new_width, int new_height);
96 | void absolute_to_window_pos(float absolute_x, float absolute_y, float* window_x, float* window_y);
97 | //void absolute_to_window_pos_pixels(float absolute_x, float absolute_y, float* window_x, float* window_y);
98 | fz_rect absolute_to_window_rect(fz_rect doc_rect);
99 | NormalizedWindowPos document_to_window_pos(DocumentPos pos);
100 | WindowPos document_to_window_pos_in_pixels(DocumentPos doc_pos);
101 | fz_rect document_to_window_rect(int page, fz_rect doc_rect);
102 | fz_irect document_to_window_irect(int page, fz_rect doc_rect);
103 | fz_rect document_to_window_rect_pixel_perfect(int page, fz_rect doc_rect, int pixel_width, int pixel_height);
104 | DocumentPos window_to_document_pos(WindowPos window_pos);
105 | AbsoluteDocumentPos window_to_absolute_document_pos(WindowPos window_pos);
106 | NormalizedWindowPos window_to_normalized_window_pos(WindowPos window_pos);
107 | void goto_mark(char symbol);
108 | void goto_end();
109 |
110 | void goto_left();
111 | void goto_left_smart();
112 |
113 | void goto_right();
114 | void goto_right_smart();
115 |
116 | float get_max_valid_x();
117 | float get_min_valid_x();
118 |
119 | float set_zoom_level(float zl, bool should_exit_auto_resize_mode);
120 | float zoom_in(float zoom_factor = ZOOM_INC_FACTOR);
121 | float zoom_out(float zoom_factor = ZOOM_INC_FACTOR);
122 | float zoom_in_cursor(WindowPos mouse_pos, float zoom_factor = ZOOM_INC_FACTOR);
123 | float zoom_out_cursor(WindowPos mouse_pos, float zoom_factor = ZOOM_INC_FACTOR);
124 | void move_absolute(float dx, float dy);
125 | void move(float dx, float dy);
126 | void get_absolute_delta_from_doc_delta(float doc_dx, float doc_dy, float* abs_dx, float* abs_dy);
127 | int get_center_page_number();
128 | void get_visible_pages(int window_height, std::vector& visible_pages);
129 | void move_pages(int num_pages);
130 | void move_screens(int num_screens);
131 | void reset_doc_state();
132 | void open_document(const std::wstring& doc_path, bool* invalid_flag, bool load_prev_state = true, std::optional prev_state = {}, bool foce_load_dimensions=false);
133 | float get_page_offset(int page);
134 | void goto_offset_within_page(DocumentPos pos);
135 | void goto_offset_within_page(int page, float offset_y);
136 | void goto_page(int page);
137 | void fit_to_page_width(bool smart=false, bool ratio=false);
138 | void fit_to_page_height(bool smart=false);
139 | void fit_to_page_height_width_minimum();
140 | void persist();
141 | std::wstring get_current_chapter_name();
142 | std::optional> get_current_page_range();
143 | int get_current_chapter_index();
144 | void goto_chapter(int diff);
145 | void get_page_chapter_index(int page, std::vector toc_nodes, std::vector& res);
146 | std::vector get_current_chapter_recursive_index();
147 | float view_height_in_document_space();
148 | void set_vertical_line_pos(float pos);
149 | void set_vertical_line_rect(fz_rect rect);
150 | bool has_ruler_rect();
151 | std::optional get_ruler_rect();
152 | //float get_vertical_line_pos();
153 | float get_ruler_pos();
154 |
155 | //float get_vertical_line_window_y();
156 | float get_ruler_window_y();
157 | std::optional get_ruler_window_rect();
158 |
159 | void goto_vertical_line_pos();
160 | int get_page_offset();
161 | void set_page_offset(int new_offset);
162 | void rotate();
163 | void goto_top_of_page();
164 | void goto_bottom_of_page();
165 | int get_line_index_of_vertical_pos();
166 | int get_line_index_of_pos(DocumentPos docpos);
167 | int get_line_index();
168 | void set_line_index(int index);
169 | int get_vertical_line_page();
170 | bool goto_definition();
171 | std::vector find_line_definitions();
172 | std::optional get_selected_line_text();
173 | bool get_is_auto_resize_mode();
174 | void disable_auto_resize_mode();
175 | void readjust_to_screen();
176 | float get_half_screen_offset();
177 | void scroll_mid_to_top();
178 | void get_visible_links(std::vector>& visible_page_links);
179 |
180 | std::vector* get_selected_character_rects();
181 | };
182 |
--------------------------------------------------------------------------------
/pdf_viewer/fonts/monaco.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/fonts/monaco.ttf
--------------------------------------------------------------------------------
/pdf_viewer/fonts/msuighub.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/fonts/msuighub.ttf
--------------------------------------------------------------------------------
/pdf_viewer/fonts/msuighur.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/fonts/msuighur.ttf
--------------------------------------------------------------------------------
/pdf_viewer/fts_fuzzy_match.h:
--------------------------------------------------------------------------------
1 | // LICENSE
2 | //
3 | // This software is dual-licensed to the public domain and under the following
4 | // license: you are granted a perpetual, irrevocable license to copy, modify,
5 | // publish, and distribute this file as you see fit.
6 | //
7 | // VERSION
8 | // 0.2.0 (2017-02-18) Scored matches perform exhaustive search for best score
9 | // 0.1.0 (2016-03-28) Initial release
10 | //
11 | // AUTHOR
12 | // Forrest Smith
13 | //
14 | // NOTES
15 | // Compiling
16 | // You MUST add '#define FTS_FUZZY_MATCH_IMPLEMENTATION' before including this header in ONE source file to create implementation.
17 | //
18 | // fuzzy_match_simple(...)
19 | // Returns true if each character in pattern is found sequentially within str
20 | //
21 | // fuzzy_match(...)
22 | // Returns true if pattern is found AND calculates a score.
23 | // Performs exhaustive search via recursion to find all possible matches and match with highest score.
24 | // Scores values have no intrinsic meaning. Possible score range is not normalized and varies with pattern.
25 | // Recursion is limited internally (default=10) to prevent degenerate cases (pattern="aaaaaa" str="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
26 | // Uses uint8_t for match indices. Therefore patterns are limited to 256 characters.
27 | // Score system should be tuned for YOUR use case. Words, sentences, file names, or method names all prefer different tuning.
28 |
29 |
30 | #ifndef FTS_FUZZY_MATCH_H
31 | #define FTS_FUZZY_MATCH_H
32 |
33 |
34 | #include // uint8_t
35 | #include // ::tolower, ::toupper
36 | #include // memcpy
37 |
38 | #include
39 |
40 | // Public interface
41 | namespace fts {
42 | static bool fuzzy_match_simple(char const * pattern, char const * str);
43 | static bool fuzzy_match(char const * pattern, char const * str, int & outScore);
44 | static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches);
45 | }
46 |
47 |
48 | #ifdef FTS_FUZZY_MATCH_IMPLEMENTATION
49 | namespace fts {
50 |
51 | // Forward declarations for "private" implementation
52 | namespace fuzzy_internal {
53 | static bool fuzzy_match_recursive(const char * pattern, const char * str, int & outScore, const char * strBegin,
54 | uint8_t const * srcMatches, uint8_t * newMatches, int maxMatches, int nextMatch,
55 | int & recursionCount, int recursionLimit);
56 | }
57 |
58 | // Public interface
59 | static bool fuzzy_match_simple(char const * pattern, char const * str) {
60 | while (*pattern != '\0' && *str != '\0') {
61 | if (tolower(*pattern) == tolower(*str))
62 | ++pattern;
63 | ++str;
64 | }
65 |
66 | return *pattern == '\0' ? true : false;
67 | }
68 |
69 | static bool fuzzy_match(char const * pattern, char const * str, int & outScore) {
70 |
71 | uint8_t matches[256];
72 | return fuzzy_match(pattern, str, outScore, matches, sizeof(matches));
73 | }
74 |
75 | static bool fuzzy_match(char const * pattern, char const * str, int & outScore, uint8_t * matches, int maxMatches) {
76 | int recursionCount = 0;
77 | int recursionLimit = 10;
78 |
79 | return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);
80 | }
81 |
82 | // Private implementation
83 | static bool fuzzy_internal::fuzzy_match_recursive(const char * pattern, const char * str, int & outScore,
84 | const char * strBegin, uint8_t const * srcMatches, uint8_t * matches, int maxMatches,
85 | int nextMatch, int & recursionCount, int recursionLimit)
86 | {
87 | // Count recursions
88 | ++recursionCount;
89 | if (recursionCount >= recursionLimit)
90 | return false;
91 |
92 | // Detect end of strings
93 | if (*pattern == '\0' || *str == '\0')
94 | return false;
95 |
96 | // Recursion params
97 | bool recursiveMatch = false;
98 | uint8_t bestRecursiveMatches[256];
99 | int bestRecursiveScore = 0;
100 |
101 | // Loop through pattern and str looking for a match
102 | bool first_match = true;
103 | while (*pattern != '\0' && *str != '\0') {
104 |
105 | // Found match
106 | if (tolower(*pattern) == tolower(*str)) {
107 |
108 | // Supplied matches buffer was too short
109 | if (nextMatch >= maxMatches)
110 | return false;
111 |
112 | // "Copy-on-Write" srcMatches into matches
113 | if (first_match && srcMatches) {
114 | memcpy(matches, srcMatches, nextMatch);
115 | first_match = false;
116 | }
117 |
118 | // Recursive call that "skips" this match
119 | uint8_t recursiveMatches[256];
120 | int recursiveScore;
121 | if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit)) {
122 |
123 | // Pick best recursive score
124 | if (!recursiveMatch || recursiveScore > bestRecursiveScore) {
125 | memcpy(bestRecursiveMatches, recursiveMatches, 256);
126 | bestRecursiveScore = recursiveScore;
127 | }
128 | recursiveMatch = true;
129 | }
130 |
131 | // Advance
132 | matches[nextMatch++] = (uint8_t)(str - strBegin);
133 | ++pattern;
134 | }
135 | ++str;
136 | }
137 |
138 | // Determine if full pattern was matched
139 | bool matched = *pattern == '\0' ? true : false;
140 |
141 | // Calculate score
142 | if (matched) {
143 | const int sequential_bonus = 15; // bonus for adjacent matches
144 | const int separator_bonus = 30; // bonus if match occurs after a separator
145 | const int camel_bonus = 30; // bonus if match is uppercase and prev is lower
146 | const int first_letter_bonus = 15; // bonus if the first letter is matched
147 |
148 | const int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match
149 | const int max_leading_letter_penalty = -15; // maximum penalty for leading letters
150 | const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
151 |
152 | // Iterate str to end
153 | while (*str != '\0')
154 | ++str;
155 |
156 | // Initialize score
157 | outScore = 100;
158 |
159 | // Apply leading letter penalty
160 | int penalty = leading_letter_penalty * matches[0];
161 | if (penalty < max_leading_letter_penalty)
162 | penalty = max_leading_letter_penalty;
163 | outScore += penalty;
164 |
165 | // Apply unmatched penalty
166 | int unmatched = (int)(str - strBegin) - nextMatch;
167 | outScore += unmatched_letter_penalty * unmatched;
168 |
169 | // Apply ordering bonuses
170 | for (int i = 0; i < nextMatch; ++i) {
171 | uint8_t currIdx = matches[i];
172 |
173 | if (i > 0) {
174 | uint8_t prevIdx = matches[i - 1];
175 |
176 | // Sequential
177 | if (currIdx == (prevIdx + 1))
178 | outScore += sequential_bonus;
179 | }
180 |
181 | // Check for bonuses based on neighbor character value
182 | if (currIdx > 0) {
183 | // Camel case
184 | char neighbor = strBegin[currIdx - 1];
185 | char curr = strBegin[currIdx];
186 | if ((neighbor > 0) && (curr > 0)) {
187 | if (::islower(neighbor) && ::isupper(curr))
188 | outScore += camel_bonus;
189 | }
190 |
191 | // Separator
192 | bool neighborSeparator = neighbor == '_' || neighbor == ' ';
193 | if (neighborSeparator)
194 | outScore += separator_bonus;
195 | }
196 | else {
197 | // First letter
198 | outScore += first_letter_bonus;
199 | }
200 | }
201 | }
202 |
203 | // Return best result
204 | if (recursiveMatch && (!matched || bestRecursiveScore > outScore)) {
205 | // Recursive score is better than "this"
206 | memcpy(matches, bestRecursiveMatches, maxMatches);
207 | outScore = bestRecursiveScore;
208 | return true;
209 | }
210 | else if (matched) {
211 | // "this" score is better than recursive
212 | return true;
213 | }
214 | else {
215 | // no match
216 | return false;
217 | }
218 | }
219 | } // namespace fts
220 |
221 | #endif // FTS_FUZZY_MATCH_IMPLEMENTATION
222 |
223 | #endif // FTS_FUZZY_MATCH_H
--------------------------------------------------------------------------------
/pdf_viewer/icon1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/icon1.ico
--------------------------------------------------------------------------------
/pdf_viewer/icon2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/icon2.ico
--------------------------------------------------------------------------------
/pdf_viewer/input.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include "utils.h"
13 | #include "path.h"
14 | #include "config.h"
15 |
16 | class MainWidget;
17 |
18 | enum RequirementType {
19 | Text,
20 | Symbol,
21 | File,
22 | Rect
23 | };
24 |
25 | struct Requirement {
26 | RequirementType type;
27 | std::string name;
28 | };
29 |
30 | class Command {
31 | private:
32 | virtual void perform(MainWidget* widget) = 0;
33 | protected:
34 | int num_repeats = 1;
35 | MainWidget* widget = nullptr;
36 | public:
37 | virtual std::optional next_requirement(MainWidget* widget);
38 |
39 | virtual void set_text_requirement(std::wstring value);
40 | virtual void set_symbol_requirement(char value);
41 | virtual void set_file_requirement(std::wstring value);
42 | virtual void set_rect_requirement(fz_rect value);
43 | virtual void set_num_repeats(int nr);
44 | virtual std::vector special_symbols();
45 | virtual void pre_perform(MainWidget* widget);
46 | virtual bool pushes_state();
47 | virtual bool requires_document();
48 |
49 | virtual void run(MainWidget* widget);
50 | virtual std::string get_name();
51 | };
52 |
53 |
54 | class CommandManager {
55 | private:
56 | //std::vector commands;
57 | std::map < std::string, std::function()> > new_commands;
58 | public:
59 |
60 | CommandManager(ConfigManager* config_manager);
61 | std::unique_ptr get_command_with_name(std::string name);
62 | std::unique_ptr create_macro_command(std::string name, std::wstring macro_string);
63 | QStringList get_all_command_names();
64 | };
65 |
66 | struct InputParseTreeNode {
67 |
68 | std::vector children;
69 | //char command;
70 | int command;
71 | std::vector name;
72 | bool shift_modifier = false;
73 | bool control_modifier = false;
74 | bool alt_modifier = false;
75 | bool requires_text = false;
76 | bool requires_symbol = false;
77 | bool is_root = false;
78 | bool is_final = false;
79 |
80 | // todo: use a pointer to reduce allocation
81 | std::wstring defining_file_path;
82 | int defining_file_line;
83 |
84 | bool is_same(const InputParseTreeNode* other);
85 | bool matches(int key, bool shift, bool ctrl, bool alt);
86 | };
87 |
88 |
89 |
90 | class InputHandler {
91 | private:
92 | InputParseTreeNode* root = nullptr;
93 | InputParseTreeNode* current_node = nullptr;
94 | CommandManager* command_manager;
95 | std::string number_stack;
96 | std::vector user_key_paths;
97 |
98 | std::string get_key_string_from_tree_node_sequence(const std::vector seq) const;
99 | std::string get_key_name_from_key_code(int key_code) const;
100 |
101 | void add_command_key_mappings(InputParseTreeNode* root, std::unordered_map>& map, std::vector prefix) const;
102 | public:
103 | //char create_link_sumbol = 0;
104 | //char create_bookmark_symbol = 0;
105 |
106 | InputHandler(const Path& default_path, const std::vector& user_paths, CommandManager* cm);
107 | void reload_config_files(const Path& default_path, const std::vector& user_path);
108 | std::vector> handle_key(QKeyEvent* key_event, bool shift_pressed, bool control_pressed, bool alt_pressed ,int* num_repeats);
109 | void delete_current_parse_tree(InputParseTreeNode* node_to_delete);
110 |
111 | std::optional get_or_create_user_keys_path();
112 | std::vector get_all_user_keys_paths();
113 | std::unordered_map> get_command_key_mappings() const;
114 |
115 | };
116 |
117 |
--------------------------------------------------------------------------------
/pdf_viewer/keys_user.config:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/keys_user.config
--------------------------------------------------------------------------------
/pdf_viewer/new_file_checker.cpp:
--------------------------------------------------------------------------------
1 | #include "new_file_checker.h"
2 |
3 | extern std::wstring PAPERS_FOLDER_PATH;
4 |
5 | void NewFileChecker::get_dir_files_helper(QString parent_path, std::vector& paths) {
6 |
7 | QDir parent(parent_path);
8 | parent.setFilter(QDir::Files | QDir::NoSymLinks);
9 | parent.setSorting(QDir::Time);
10 | QFileInfoList list = parent.entryInfoList();
11 |
12 | for (int i = 0; i < list.size(); i++) {
13 | paths.push_back(list.at(i).absoluteFilePath());
14 | }
15 |
16 | parent.setFilter(QDir::Dirs | QDir::NoSymLinks);
17 | QFileInfoList dirlist = parent.entryInfoList();
18 | for (auto dir : dirlist) {
19 | if (dir.fileName() == "." || dir.fileName() == "..") {
20 | continue;
21 | }
22 | get_dir_files_helper(dir.absoluteFilePath(), paths);
23 | }
24 |
25 | }
26 |
27 | std::vector NewFileChecker::get_dir_files() {
28 |
29 | std::vector res;
30 | get_dir_files_helper(path, res);
31 | return res;
32 | }
33 |
34 | void NewFileChecker::update_files() {
35 | last_files.clear();
36 | last_files = get_dir_files();
37 | }
38 |
39 | QString NewFileChecker::get_lastest_new_file_path() {
40 | auto new_files = get_dir_files();
41 |
42 | for (auto new_file : new_files) {
43 | bool found = false;
44 |
45 | for (auto prev_file : last_files) {
46 | if (prev_file == new_file) {
47 | found = true;
48 | break;
49 | }
50 | }
51 | if (!found) {
52 | if (new_file.endsWith(".pdf")) {
53 | return new_file;
54 | }
55 | }
56 | }
57 | return "";
58 | }
59 |
60 | void NewFileChecker::register_subdirectories(QString dirpath) {
61 | paper_folder_watcher.addPath(dirpath);
62 |
63 | QDir parent(dirpath);
64 | parent.setFilter(QDir::Dirs | QDir::NoSymLinks);
65 | QFileInfoList dirlist = parent.entryInfoList();
66 | for (auto dir : dirlist) {
67 | if (dir.fileName() == "." || dir.fileName() == "..") {
68 | continue;
69 | }
70 | register_subdirectories(dir.absoluteFilePath());
71 | }
72 | }
73 |
74 | NewFileChecker::NewFileChecker(std::wstring dirpath, MainWidget* main_widget) {
75 |
76 | path = QString::fromStdWString(dirpath);
77 | if (dirpath.size() > 0) {
78 | update_files();
79 | register_subdirectories(QString::fromStdWString(PAPERS_FOLDER_PATH));
80 | QObject::connect(&paper_folder_watcher, &QFileSystemWatcher::directoryChanged, [&, main_widget](const QString& path) {
81 | auto new_path = get_lastest_new_file_path();
82 | if (new_path.size() > 0) {
83 | main_widget->on_new_paper_added(new_path.toStdWString());
84 | }
85 | update_files();
86 | });
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/pdf_viewer/new_file_checker.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "main_widget.h"
4 |
5 | #include
6 | #include
7 | #include
8 | #include
9 |
10 | class NewFileChecker {
11 | private:
12 | std::vector last_files;
13 | QString path;
14 | QFileSystemWatcher paper_folder_watcher;
15 | void get_dir_files_helper(QString parent_path, std::vector& paths);
16 | void register_subdirectories(QString dirpath);
17 |
18 | public:
19 | std::vector get_dir_files();
20 | void update_files();
21 | QString get_lastest_new_file_path();
22 | NewFileChecker(std::wstring dirpath, MainWidget* main_widget);
23 | };
24 |
--------------------------------------------------------------------------------
/pdf_viewer/path.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "path.h"
3 | #include
4 |
5 | Path::Path() : Path(L"")
6 | {
7 | }
8 |
9 | Path::Path(std::wstring pathname)
10 | {
11 | pathname = strip_string(pathname);
12 | canon_path = get_canonical_path(pathname);
13 | }
14 |
15 | Path Path::slash(const std::wstring& suffix) const
16 | {
17 | std::wstring new_path = concatenate_path(get_path(), suffix);
18 | return Path(new_path);
19 | }
20 |
21 | std::optional Path::filename() const
22 | {
23 | std::vector all_parts;
24 | parts(all_parts);
25 | if (all_parts.size() > 0) {
26 | return all_parts[all_parts.size() - 1];
27 | }
28 | return {};
29 | }
30 |
31 | Path Path::file_parent() const
32 | {
33 | QFileInfo info(QString::fromStdWString(get_path()));
34 | return Path(info.dir().absolutePath().toStdWString());
35 | }
36 |
37 | std::wstring Path::get_path() const
38 | {
39 |
40 | if (canon_path.size() == 0) return canon_path;
41 |
42 | #ifdef Q_OS_WIN
43 | return canon_path;
44 | #else
45 | if (canon_path[0] != '/'){
46 | return L"/" + canon_path;
47 | }
48 | else{
49 | return canon_path;
50 | }
51 | #endif
52 | }
53 |
54 | std::string Path::get_path_utf8() const
55 | {
56 | return std::move(utf8_encode(get_path()));
57 | }
58 |
59 | void Path::create_directories()
60 | {
61 | QDir().mkpath(QString::fromStdWString(canon_path));
62 | }
63 |
64 | //std::wstring Path::add_redundant_dot() const
65 | //{
66 | // std::wstring file_name = filename().value();
67 | // return parent().get_path() + L"/./" + file_name;
68 | //}
69 |
70 | bool Path::dir_exists() const
71 | {
72 | return QDir(QString::fromStdWString(canon_path)).exists();
73 | }
74 |
75 | bool Path::file_exists() const
76 | {
77 | return QFile::exists(QString::fromStdWString(canon_path));
78 | }
79 |
80 | void Path::parts(std::vector& res) const
81 | {
82 | split_path(canon_path, res);
83 | }
84 |
85 | std::wostream& operator<<(std::wostream& stream, const Path& path) {
86 | stream << path.get_path();
87 | return stream;
88 | }
89 |
90 | void copy_file(Path src, Path dst)
91 | {
92 | QFile::copy(QString::fromStdWString(src.get_path()), QString::fromStdWString(dst.get_path()));
93 | }
94 |
95 |
96 | //Path add_redundant_dot_to_path(const Path& sane_path) {
97 | //
98 | // std::wstring file_name = sane_path.filename();
99 | //
100 | ////#ifdef Q_OS_WIN
101 | //// wchar_t separator = '\\';
102 | ////#else
103 | //// wchar_t separator = '/';
104 | ////#endif
105 | ////
106 | //// std::vector parts;
107 | //// split_path(sane_path, parts);
108 | //// std::wstring res = L"";
109 | //// for (int i = 0; i < parts.size(); i++) {
110 | //// res = concatenate_path(res, parts[i]);
111 | //// }
112 | //
113 | // //QDir sane_path_dir(QString::fromStdWString(sane_path));
114 | // //sane_path_dir.filenam
115 | // //sane_path_dir.cdUp();
116 | //
117 | // //return concatenate_path(concatenate_path(sane_path_dir.canonicalPath().toStdWString(), L"."), sa
118 | // //return sane_path.parent_path() / "." / sane_path.filename();
119 | //}
120 |
--------------------------------------------------------------------------------
/pdf_viewer/path.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 |
5 | #include "utils.h"
6 |
7 | class Path {
8 | private:
9 | std::wstring canon_path;
10 |
11 | public:
12 | Path();
13 | Path(std::wstring pathname);
14 |
15 | void parts(std::vector &res) const;
16 | Path slash(const std::wstring& suffix) const;
17 |
18 | std::optional filename() const;
19 | Path file_parent() const;
20 |
21 | std::wstring get_path() const;
22 | std::string get_path_utf8() const;
23 | void create_directories();
24 | bool dir_exists() const;
25 | bool file_exists() const;
26 | //std::wstring add_redundant_dot() const;
27 |
28 | };
29 | std::wostream& operator<<(std::wostream& stream, const Path& path);
30 |
31 | void copy_file(Path src, Path dst);
--------------------------------------------------------------------------------
/pdf_viewer/pdf_renderer.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include