├── .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 | [![Sioyek feature overview](https://img.youtube.com/vi/yTmCI0Xp5vI/0.jpg)](https://www.youtube.com/watch?v=yTmCI0Xp5vI) 44 | 45 | For a more in-depth tutorial, see this video: 46 | 47 | [![Sioyek Tutorial](https://img.youtube.com/vi/RaHRvnb0dY8/0.jpg)](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 | Buy Me A Coffee 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 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | //#include 17 | //#include 18 | 19 | #include 20 | #include 21 | 22 | #include "book.h" 23 | 24 | extern const int MAX_PENDING_REQUESTS; 25 | extern const unsigned int CACHE_INVALID_MILIES; 26 | 27 | struct RenderRequest { 28 | std::wstring path; 29 | int page; 30 | float zoom_level; 31 | }; 32 | 33 | struct SearchRequest { 34 | std::wstring path; 35 | int start_page; 36 | std::wstring search_term; 37 | std::vector* search_results; 38 | std::mutex* search_results_mutex; 39 | float* percent_done = nullptr; 40 | bool* is_searching = nullptr; 41 | std::optional> range; 42 | }; 43 | 44 | struct RenderResponse { 45 | RenderRequest request; 46 | unsigned int last_access_time; 47 | int thread; 48 | fz_pixmap* pixmap = nullptr; 49 | int width = -1; 50 | int height = -1; 51 | GLuint texture = 0; 52 | bool invalid = false; 53 | }; 54 | 55 | bool operator==(const RenderRequest& lhs, const RenderRequest& rhs); 56 | 57 | class PdfRenderer : public QObject{ 58 | Q_OBJECT 59 | // A pointer to the mupdf context to clone. 60 | // Since the context should only be used from the thread that initialized it, 61 | // we can not simply clone the context in the initializer because the initializer 62 | // is called from the main thread. Instead, we just save a pointer to the context 63 | // in the initializer and then clone the context when run() is called in the worker 64 | //thread. 65 | fz_context* context_to_clone; 66 | 67 | std::vector> pixmaps_to_drop; 68 | std::map, fz_document*> opened_documents; 69 | 70 | std::vector pending_render_requests; 71 | std::optional pending_search_request; 72 | std::vector cached_responses; 73 | std::vector worker_threads; 74 | std::thread search_thread; 75 | 76 | std::mutex pending_requests_mutex; 77 | std::mutex search_request_mutex; 78 | std::mutex cached_response_mutex; 79 | std::vector pixmap_drop_mutex; 80 | 81 | QTimer garbage_collect_timer; 82 | 83 | bool* should_quit_pointer = nullptr; 84 | bool are_documents_invalidated = false; 85 | 86 | int num_threads = 0; 87 | float display_scale = 1.0f; 88 | 89 | std::map document_passwords; 90 | 91 | fz_context* init_context(); 92 | fz_document* get_document_with_path(int thread_index, fz_context* mupdf_context, std::wstring path); 93 | GLuint try_closest_rendered_page(std::wstring doc_path, int page, float zoom_level, int* page_width, int* page_height); 94 | void delete_old_pixmaps(int thread_index, fz_context* mupdf_context); 95 | void run(int thread_index); 96 | void run_search(int thread_index); 97 | 98 | public: 99 | 100 | PdfRenderer(int num_threads, bool* should_quit_pointer, fz_context* context_to_clone, float display_scale); 101 | ~PdfRenderer(); 102 | void clear_cache(); 103 | 104 | void start_threads(); 105 | void join_threads(); 106 | 107 | //should only be called from the main thread 108 | void add_request(std::wstring document_path, int page, float zoom_level); 109 | void add_request(std::wstring document_path, 110 | int page, 111 | std::wstring term, 112 | std::vector* out, 113 | float* percent_done, 114 | bool* is_searching, 115 | std::mutex* mut, 116 | std::optional> range = {}); 118 | 119 | GLuint find_rendered_page(std::wstring path, int page, float zoom_level, int* page_width, int* page_height); 120 | void delete_old_pages(bool force_all=false, bool invalidate_all=false); 121 | void add_password(std::wstring path, std::string password); 122 | 123 | signals: 124 | void render_advance(); 125 | void search_advance(); 126 | 127 | }; 128 | -------------------------------------------------------------------------------- /pdf_viewer/pdf_view_opengl_widget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #ifndef SIOYEK_QT6 39 | #include 40 | #endif 41 | 42 | #include 43 | 44 | #include "document_view.h" 45 | #include "path.h" 46 | 47 | 48 | 49 | struct OpenGLSharedResources { 50 | GLuint vertex_buffer_object; 51 | GLuint uv_buffer_object; 52 | GLuint rendered_program; 53 | GLuint rendered_dark_program; 54 | GLuint custom_color_program; 55 | GLuint unrendered_program; 56 | GLuint highlight_program; 57 | GLuint vertical_line_program; 58 | GLuint vertical_line_dark_program; 59 | GLuint separator_program; 60 | GLuint stencil_program; 61 | 62 | GLint dark_mode_contrast_uniform_location; 63 | GLint highlight_color_uniform_location; 64 | GLint line_color_uniform_location; 65 | GLint line_time_uniform_location; 66 | 67 | GLint gamma_uniform_location; 68 | 69 | GLint custom_color_transform_uniform_location; 70 | 71 | GLint separator_background_color_uniform_location; 72 | 73 | bool is_initialized; 74 | }; 75 | 76 | struct OverviewState { 77 | //int page; 78 | float absolute_offset_y; 79 | Document* doc = nullptr; 80 | //float page_height; 81 | }; 82 | 83 | class PdfViewOpenGLWidget : public QOpenGLWidget, protected QOpenGLExtraFunctions { 84 | public: 85 | 86 | enum OverviewSide { 87 | bottom = 0, 88 | top = 1, 89 | left = 2, 90 | right = 3 91 | }; 92 | 93 | struct OverviewResizeData { 94 | fz_rect original_rect; 95 | NormalizedWindowPos original_normal_mouse_pos; 96 | OverviewSide side_index; 97 | }; 98 | 99 | struct OverviewMoveData { 100 | fvec2 original_offsets; 101 | NormalizedWindowPos original_normal_mouse_pos; 102 | }; 103 | 104 | enum ColorPalette { 105 | Normal, 106 | Dark, 107 | Custom 108 | }; 109 | 110 | private: 111 | static OpenGLSharedResources shared_gl_objects; 112 | 113 | bool is_opengl_initialized = false; 114 | GLuint vertex_array_object; 115 | DocumentView* document_view = nullptr; 116 | PdfRenderer* pdf_renderer = nullptr; 117 | ConfigManager* config_manager = nullptr; 118 | std::vector search_results; 119 | int current_search_result_index = -1; 120 | std::mutex search_results_mutex; 121 | bool is_search_cancelled = true; 122 | bool is_searching; 123 | bool should_highlight_links = false; 124 | bool should_highlight_words = false; 125 | bool should_show_numbers = false; 126 | ColorPalette color_mode = ColorPalette::Normal; 127 | bool is_helper = false; 128 | float percent_done = 0.0f; 129 | std::optional visible_page_number = {}; 130 | 131 | std::optional character_highlight_rect = {}; 132 | std::optional wrong_character_rect = {}; 133 | 134 | int rotation_index = 0; 135 | bool is_dragging = false; 136 | bool fastread_mode = false; 137 | 138 | int last_mouse_down_window_x = 0; 139 | int last_mouse_down_window_y = 0; 140 | 141 | float last_mouse_down_document_offset_x = 0; 142 | float last_mouse_down_document_offset_y = 0; 143 | 144 | //float vertical_line_location; 145 | bool should_draw_vertical_line = false; 146 | QDateTime creation_time; 147 | 148 | std::optional> on_link_edit = {}; 149 | std::optional overview_page = {}; 150 | 151 | float overview_half_width = 0.8f; 152 | float overview_half_height = 0.4f; 153 | 154 | float overview_offset_x = 0.0f; 155 | float overview_offset_y = 0.0f; 156 | 157 | std::optional selected_rectangle = {}; 158 | 159 | GLuint LoadShaders(Path vertex_file_path_, Path fragment_file_path_); 160 | protected: 161 | void initializeGL() override; 162 | void resizeGL(int w, int h) override; 163 | void render_highlight_window(GLuint program, fz_rect window_rect, bool draw_border=true); 164 | void render_line_window(GLuint program, float vertical_pos, std::optional ruler_rect = {}); 165 | void render_highlight_absolute(GLuint program, fz_rect absolute_document_rect, bool draw_border=true); 166 | void render_highlight_document(GLuint program, int page, fz_rect doc_rect); 167 | void paintGL() override; 168 | void render(QPainter* painter); 169 | 170 | void enable_stencil(); 171 | void write_to_stencil(); 172 | void draw_stencil_rects(const std::vector& rects, bool is_window_rect=false, int page=-1); 173 | void use_stencil_to_write(bool eq); 174 | void disable_stencil(); 175 | 176 | void render_transparent_background(); 177 | 178 | public: 179 | 180 | #ifndef NDEBUG 181 | // properties for visualizing selected blocks, used only for debug 182 | std::optional last_selected_block_page = {}; 183 | std::optional last_selected_block = {}; 184 | #endif 185 | 186 | std::vector> word_rects; 187 | std::vector> synctex_highlights; 188 | 189 | PdfViewOpenGLWidget(DocumentView* document_view, PdfRenderer* pdf_renderer, ConfigManager* config_manager, bool is_helper, QWidget* parent = nullptr); 190 | ~PdfViewOpenGLWidget(); 191 | 192 | //void set_vertical_line_pos(float pos); 193 | //float get_vertical_line_pos(); 194 | void set_should_draw_vertical_line(bool val); 195 | bool get_should_draw_vertical_line(); 196 | void handle_escape(); 197 | 198 | void toggle_highlight_links(); 199 | void set_highlight_links(bool should_highlight_links, bool should_show_numbers); 200 | void toggle_highlight_words(); 201 | void set_highlight_words(std::vector>& rects); 202 | void set_should_highlight_words(bool should_highlight); 203 | std::vector> get_highlight_word_rects(); 204 | 205 | int get_num_search_results(); 206 | int get_current_search_result_index(); 207 | bool valid_document(); 208 | std::optional set_search_result_offset(int offset); 209 | std::optional get_current_search_result(); 210 | void goto_search_result(int offset, bool overview=false); 211 | void render_overview(OverviewState overview); 212 | void render_page(int page_number); 213 | bool get_is_searching(float* prog); 214 | void search_text(const std::wstring& text, bool case_sensitive=true, bool regex=false, std::optional> range = {}); 215 | void set_dark_mode(bool mode); 216 | void toggle_dark_mode(); 217 | void set_custom_color_mode(bool mode); 218 | void toggle_custom_color_mode(); 219 | void set_synctex_highlights(std::vector> highlights); 220 | void on_document_view_reset(); 221 | void mouseMoveEvent(QMouseEvent* mouse_event) override; 222 | void mousePressEvent(QMouseEvent* mevent) override; 223 | void mouseReleaseEvent(QMouseEvent* mevent) override; 224 | void wheelEvent(QWheelEvent* wevent) override; 225 | void register_on_link_edit_listener(std::function listener); 226 | void set_overview_page(std::optional overview_page); 227 | std::optional get_overview_page(); 228 | void draw_empty_helper_message(QPainter* painter); 229 | void set_visible_page_number(std::optional val); 230 | bool is_presentation_mode(); 231 | fz_rect get_overview_rect(); 232 | fz_rect get_overview_rect_pixel_perfect(int widget_width, int widget_height, int view_width, int view_height); 233 | std::vector get_overview_border_rects(); 234 | bool is_window_point_in_overview(fvec2 window_point); 235 | bool is_window_point_in_overview_border(float window_x, float window_y, OverviewSide *which_border); 236 | 237 | void get_overview_offsets(float* offset_x, float* offset_y); 238 | void get_overview_size(float* width, float* height); 239 | 240 | float get_overview_side_pos(int index); 241 | void set_overview_side_pos(int index, fz_rect original_rect, fvec2 diff); 242 | void set_overview_rect(fz_rect rect); 243 | 244 | void set_overview_offsets(float offset_x, float offset_y); 245 | void set_overview_offsets(fvec2 offsets); 246 | 247 | void bind_program(); 248 | void cancel_search(); 249 | //void window_pos_to_overview_pos(float window_x, float window_y, float* doc_offset_x, float* doc_offset_y, int* doc_page); 250 | DocumentPos window_pos_to_overview_pos(NormalizedWindowPos window_pos); 251 | void rotate_clockwise(); 252 | void rotate_counterclockwise(); 253 | 254 | bool is_rotated(); 255 | void toggle_fastread_mode(); 256 | void setup_text_painter(QPainter* painter); 257 | void get_overview_window_vertices(float out_vertices[2*4]); 258 | 259 | void set_selected_rectangle(fz_rect selected); 260 | void clear_selected_rectangle(); 261 | void clear_all_selections(); 262 | 263 | std::optional get_selected_rectangle(); 264 | 265 | void set_typing_rect(int page, fz_rect rect, std::optional wrong_rect); 266 | 267 | Document* get_current_overview_document(); 268 | NormalizedWindowPos document_to_overview_pos(DocumentPos pos); 269 | fz_rect document_to_overview_rect(int page, fz_rect document_rect); 270 | std::vector get_visible_search_results(std::vector& visible_pages); 271 | int find_search_index_for_visible_pages(std::vector& visible_pages); 272 | int find_search_index_for_visible_page(int page, int breakpoint); 273 | int find_search_result_for_page_range(int page, int range_begin, int range_end); 274 | int find_search_results_breakpoint(); 275 | int find_search_results_breakpoint_helper(int begin_index, int end_index); 276 | void get_custom_color_transform_matrix(float matrix_data[16]); 277 | void get_background_color(float out_background[3]); 278 | 279 | }; 280 | -------------------------------------------------------------------------------- /pdf_viewer/prefs.config: -------------------------------------------------------------------------------- 1 | # For more information see the documentation at https://sioyek-documentation.readthedocs.io/ 2 | 3 | # (can be 0 or 1) if set, shows a notification on startup if a new version of sioyek is available 4 | check_for_updates_on_startup 0 5 | 6 | # Use old keybind parsing method (only for backwards compatibility) 7 | use_legacy_keybinds 0 8 | 9 | # The color with which the screen is cleared before rendering the PDF (this is the background color of the application and not the PDF file) 10 | background_color 0.97 0.97 0.97 11 | dark_mode_background_color 0.0 0.0 0.0 12 | 13 | # Showing full white text on black background can be irritating for the eye, we can dim the whites a little bit using the contrast option 14 | dark_mode_contrast 0.8 15 | 16 | # Highlight color when text is selected using mouse 17 | text_highlight_color 1.0 1.0 0.0 18 | 19 | # The color of highlight ruler which is displayed when right click is pressed 20 | visual_mark_color 0.0 0.0 0.0 0.1 21 | 22 | # Highlight color when text is a search match 23 | search_highlight_color 0.0 1.0 0.0 24 | 25 | # Highlight color for PDF links (note that highlight is off by default 26 | # and can only be seen by performing a toggle_highlight command. See keys.config for more details) 27 | link_highlight_color 0.0 0.0 1.0 28 | 29 | # Highlight color for SyncTeX forward search highlights 30 | synctex_highlight_color 1.0 0.0 1.0 31 | 32 | # URLs to use when executing external_search commands 33 | search_url_s https://scholar.google.com/scholar?q= 34 | search_url_l http://gen.lib.rus.ec/scimag/?q= 35 | search_url_g https://www.google.com/search?q= 36 | 37 | # Which search URL to choose when middle clicking or shift middle clicking on text (the values are the letters of corresponding search_url_* ) 38 | # for example if i set `middle_click_search_engine s`, then we use the URL associated with `search_url_s` to handle middle click searches 39 | middle_click_search_engine s 40 | shift_middle_click_search_engine l 41 | 42 | # The factor by which we increase/decrease zoom when performing zoom_in or zoom_out 43 | zoom_inc_factor 1.2 44 | 45 | # How many inches we move vertically/horizontally when performing move_* commands 46 | vertical_move_amount 1.0 47 | horizontal_move_amount 1.0 48 | 49 | # When performing screen_down/screen_up we usually don't move a full screen because it causes the user to lose context 50 | # Here we specify the fraction of the screen width by which we move when performing these commands 51 | move_screen_ratio 0.5 52 | 53 | # If 0, Table of Contents is shown in a hierarchical tree, otherwise it is a flat list (can improve performance for extremely large table of contents) 54 | flat_toc 0 55 | 56 | # If it is 1, when launching the application if we detect multiple monitors, we automatically launch the helper window in second monitor 57 | should_use_multiple_monitors 0 58 | 59 | # If the last opened document is empty, load the tutorial PDF instead 60 | should_load_tutorial_when_no_other_file 1 61 | 62 | # (deprecated, use `should_launch_new_window` instead) If it is 0, then we use the previous instance of sioyek when launching a new file. 63 | # otherwise a new instance is launched every time we open a new file. 64 | should_launch_new_instance 0 65 | 66 | # If set, we open a new sioyek window when a new file is opened, otherwise we open the file in the previous window 67 | should_launch_new_window 0 68 | 69 | # The command to use when trying to do inverse search into a LaTeX document. Uncomment and provide your own command. 70 | # %1 expands to the name of the file and %2 expands to the line number. 71 | #inverse_search_command "C:\path\to\vscode\Code.exe" -r -g %1:%2 72 | 73 | # you can specify the exact highlight color for each of 26 different highlight types 74 | 75 | # When moving to the next line using visual marker, this setting specifies the distance of the marker to the top of the screen in fractions of screen size (center of the screen is zero, top of the screen is one) 76 | visual_mark_next_page_fraction 0.75 77 | 78 | # When moving to the next line using visual marker, this setting determines at which point we move the screen (center of the screen is zero, bottom of the screen is one) 79 | visual_mark_next_page_threshold 0.25 80 | 81 | # If set, we display a checkerboard pattern for unrendered pages (by default we display nothing) 82 | should_draw_unrendered_pages 0 83 | 84 | # If 0, we use the previous renders for overview window which may cause it to be blurry 85 | # if it is 1, we rerender with the proper resolution for overview window which looks better 86 | # but may increase power consumption 87 | rerender_overview 1 88 | 89 | ## Size of the overview window (1 being as large as the window, valid range is [0, 1]) 90 | # overview_size 0.5 0.5 91 | 92 | ## Offset of the center of the overview window ((0,0) being the center of the screen and valid range is [-1, 1]) 93 | overview_offset 0.5 0.5 94 | 95 | # Use linear texture filtering instead of nearest-neighbor 96 | # Can improve appearance in very high-resolution screens 97 | # linear_filter 0 98 | 99 | # Use dark mode by default (deprecated, better add `toggle_dark_mode` to `startup_commands` ) 100 | default_dark_mode 0 101 | 102 | # If set, we sort the bookmarks by their location instead of their creation time 103 | sort_bookmarks_by_location 1 104 | 105 | ## Path to shared.db database file. If not set, we use the default path. 106 | ## you can set this to be a file in a synced folder (e.g. dropbox folder) to automatically sync 107 | ## sioyek across multiple computers 108 | #shared_database_path /some/path/shared.db 109 | 110 | ## Name of the font to use for UI text 111 | #ui_font Some Font Name 112 | ## Size of the UI font 113 | #font_size 20 114 | 115 | ## Semicolon-separated list of command to execute upon sioyek startup 116 | #startup_commands toggle_visual_scroll;toggle_dark_mode 117 | 118 | ## Background color to use when executing `toggle_custom_color` 119 | custom_background_color 0.180 0.204 0.251 120 | ## Text color to use when executing `toggle_custom_color` 121 | custom_text_color 0.847 0.871 0.914 122 | 123 | # Normally mouse wheel zooms in on the middle of the screen, but if this is set to 1, we zoom in on the cursor 124 | wheel_zoom_on_cursor 0 125 | 126 | ## Color of status bar background 127 | #status_bar_color 0 0 0 128 | ## Color of status bar text 129 | #status_bar_text_color 1 1 1 130 | ## Font size of the status bar text 131 | #status_bar_font_size 20 132 | 133 | ## The default size of main window when helper window is closed 134 | #single_main_window_size 800 600 135 | #single_main_window_move 100 100 136 | 137 | ## The default size/offset of main/helper window when helper window is opened. You can copy the value of this config using `copy_window_size_config` command 138 | #main_window_size 800 600 139 | #main_window_move 100 100 140 | #helper_window_size 800 600 141 | #helper_window_move 100 100 142 | 143 | ## Touchpad/scrollwheel sensitivity 144 | touchpad_sensitivity 1.0 145 | 146 | ## Configure the appearance of page separator 147 | #page_separator_width 2 148 | #page_separator_color 0.9 0.9 0.9 149 | 150 | # Ratio of page width to use for `fit_to_page_width_ratio` command 151 | fit_to_page_width_ratio 0.75 152 | 153 | # If set, we initially collapse table of content entries 154 | collapsed_toc 0 155 | 156 | # If set, we highlight the current line in visual_scroll_mode by masking above and below the current line 157 | # if not set, we only mask below the line 158 | ruler_mode 1 159 | 160 | # Additional ruler padding 161 | ruler_padding 1.0 162 | ruler_x_padding 5.0 163 | 164 | ## We use MuPDF to determine lines for visual mark. However, we do have a custom algorithm for image documents 165 | ## if `force_custom_line_algorithm` is 1, then we use our custom algorithm instead of MuPDF even for documents 166 | ## that have lines. 167 | #force_custom_line_algorithm 0 168 | 169 | # A directory which sioyek watches for new papers. If a new paper added to this directory 170 | # while we are creating a portal from another document, this new document will automatically 171 | # be used as the destination of the portal. 172 | #paper_folder_path /some/path 173 | 174 | # Enable some experimental features, might not be stable 175 | #enable_experimental_features 0 176 | 177 | # Automatically create a table of contents for the document if it doesn't already have one 178 | create_table_of_contents_if_not_exists 1 179 | 180 | # Limits the maximum size of created table of contents 181 | max_created_toc_size 5000 182 | 183 | # Warn the user on the command line only when redefining keys inside 184 | # the same file. When set to 1, sioyek will warn when redefining keys 185 | # from other files also 186 | should_warn_about_user_key_override 1 187 | 188 | # Use double clicks to select entire words and single clicks for character-based selection 189 | single_click_selects_words 0 190 | 191 | # A prefix to prepend to items in lists (e.g. bookmark lists) 192 | #item_list_prefix > 193 | 194 | ## In presentation mode, ignore whitespace when trying to determine the size of a page 195 | #ignore_whitespace_in_presentation_mode 0 196 | 197 | ## In list of recent documents, show the entire document path rather than just the name 198 | #show_doc_path 0 199 | 200 | # Show long menu items in multiple lines instead of truncating the string, can reduce performance for 201 | #very large lists 202 | multiline_menus 1 203 | 204 | # While in present mode, prerender the next page to avoid flickering 205 | prerender_next_page_presentation 1 206 | 207 | ## Custom commands to run when clicking or right clicking when modifier keys are pressed 208 | ## the command can be any built-in sioyek command (e.g. overview_under_cursor) or user-defined 209 | ## commands defined using `new_command` 210 | # shift_click_command some_command 211 | # control_click_command some_command 212 | # alt_click_command some_command 213 | # shift_right_click_command some_command 214 | # control_right_click_command some_command 215 | # alt_right_click_command some_command 216 | 217 | # Highlight on middle clicks when text is selected and no preview is open 218 | #highlight_middle_click 1 219 | 220 | # Use a super fast index for search instead of MuPDF's implementation 221 | super_fast_search 1 222 | 223 | # Use case-insensitive search 224 | #case_sensitive_search 0 225 | 226 | #Amethyst 227 | highlight_color_a 0.94 0.64 1.00 228 | #Blue 229 | highlight_color_b 0.00 0.46 0.86 230 | #Caramel 231 | highlight_color_c 0.60 0.25 0.00 232 | #Damson 233 | highlight_color_d 0.30 0.00 0.36 234 | #Ebony 235 | highlight_color_e 0.10 0.10 0.10 236 | #Forest 237 | highlight_color_f 0.00 0.36 0.19 238 | #Green 239 | highlight_color_g 0.17 0.81 0.28 240 | #Honeydew 241 | highlight_color_h 1.00 0.80 0.60 242 | #Iron 243 | highlight_color_i 0.50 0.50 0.50 244 | #Jade 245 | highlight_color_j 0.58 1.00 0.71 246 | #Khaki 247 | highlight_color_k 0.56 0.49 0.00 248 | #Lime 249 | highlight_color_l 0.62 0.80 0.00 250 | #Mallow 251 | highlight_color_m 0.76 0.00 0.53 252 | #Navy 253 | highlight_color_n 0.00 0.20 0.50 254 | #Orpiment 255 | highlight_color_o 1.00 0.64 0.02 256 | #Pink 257 | highlight_color_p 1.00 0.66 0.73 258 | #Quagmire 259 | highlight_color_q 0.26 0.40 0.00 260 | #Red 261 | highlight_color_r 1.00 0.00 0.06 262 | #Sky 263 | highlight_color_s 0.37 0.95 0.95 264 | #Turquoise 265 | highlight_color_t 0.00 0.60 0.56 266 | #Uranium 267 | highlight_color_u 0.88 1.00 0.40 268 | #Violet 269 | highlight_color_v 0.45 0.04 1.00 270 | #Wine 271 | highlight_color_w 0.60 0.00 0.00 272 | #Xanthin 273 | highlight_color_x 1.00 1.00 0.50 274 | #Yellow 275 | highlight_color_y 1.00 1.00 0.00 276 | #Zinnia 277 | highlight_color_z 1.00 0.31 0.02 278 | -------------------------------------------------------------------------------- /pdf_viewer/prefs_user.config: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/pdf_viewer/prefs_user.config -------------------------------------------------------------------------------- /pdf_viewer/shaders/custom_colors.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | uniform sampler2D pdf_texture; 7 | 8 | uniform mat4 transform_matrix; 9 | 10 | void main(){ 11 | vec4 pdf_color = vec4(texture(pdf_texture, uvs).rgb, 1); 12 | vec3 resulting_unclamped_color = (transform_matrix * pdf_color).rgb; 13 | color = vec4(clamp(resulting_unclamped_color, 0, 1), 1.0); 14 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/dark_mode.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | uniform sampler2D pdf_texture; 7 | 8 | uniform float contrast; 9 | 10 | //http://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness 11 | vec3 rgb2hsv(vec3 c) 12 | { 13 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); 14 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); 15 | vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); 16 | 17 | float d = q.x - min(q.w, q.y); 18 | float e = 1.0e-10; 19 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); 20 | } 21 | 22 | vec3 hsv2rgb(vec3 c) 23 | { 24 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); 25 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); 26 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); 27 | } 28 | 29 | 30 | void main(){ 31 | vec3 tempcolor = texture(pdf_texture, uvs).rgb; 32 | vec3 inv = (0.5-tempcolor)*contrast+0.5; //Invert colors and shift colors from range 0.0 - 1.0 to -0.5 - 0.5, apply contrast and shift back to 0.0 - 1.0. This way contrast applies on both whites and blacks 33 | vec3 hsvcolor = rgb2hsv(inv); //transform to hsv 34 | float new_hue = mod(hsvcolor.r + 0.5, 1.0); // shift hue 180 degrees to compensate hue shift from inverting colors 35 | vec3 newcolor = hsv2rgb(vec3(new_hue,hsvcolor.gb)); 36 | color = vec4(newcolor, 1.0); 37 | } 38 | -------------------------------------------------------------------------------- /pdf_viewer/shaders/debug.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | void main(){ 8 | color = vec4(1.0, 1.0, 0.0, 0.3); 9 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/highlight.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | uniform vec3 highlight_color; 8 | 9 | void main(){ 10 | color = vec4(highlight_color, 0.3); 11 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/separator.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | uniform vec3 background_color; 8 | 9 | void main(){ 10 | color = vec4(background_color, 1.0); 11 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/simple.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | uniform sampler2D pdf_texture; 7 | 8 | void main(){ 9 | color = vec4(texture(pdf_texture, uvs).rgb, 1.0); 10 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/simple.vertex: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec2 screen_pos; 4 | out vec2 uvs; 5 | layout (location=0) in vec2 vertex_pos; 6 | layout (location=1) in vec2 vertex_uvs; 7 | 8 | void main(){ 9 | screen_pos = vertex_pos; 10 | uvs = vertex_uvs; 11 | gl_Position = vec4(vertex_pos, 0.0, 1.0); 12 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/stencil.fragment: -------------------------------------------------------------------------------- 1 | 2 | #version 330 core 3 | 4 | out vec4 color; 5 | in vec2 screen_pos; 6 | 7 | void main(){ 8 | color = vec4(0, 0, 0, 0); 9 | } 10 | -------------------------------------------------------------------------------- /pdf_viewer/shaders/stencil.vertex: -------------------------------------------------------------------------------- 1 | 2 | #version 330 core 3 | 4 | out vec2 screen_pos; 5 | layout (location=0) in vec2 vertex_pos; 6 | 7 | void main(){ 8 | screen_pos = vertex_pos; 9 | gl_Position = vec4(vertex_pos, 0.0, 1.0); 10 | } 11 | -------------------------------------------------------------------------------- /pdf_viewer/shaders/undendered_page.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | void main(){ 8 | int x = (int)(uvs.x / 0.1); 9 | int y = (int)(uvs.y / 0.1); 10 | if ((x+y)% 2 == 0){ 11 | color = vec4(1.0, 0.0, 0.0, 1.0); 12 | } 13 | else{ 14 | color = vec4(0.0, 0.0, 1.0, 1.0); 15 | } 16 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/unrendered_page.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | void main(){ 8 | int x = int(uvs.x / 0.1); 9 | int y = int(uvs.y / 0.1); 10 | if ((x+y)% 2 == 0){ 11 | color = vec4(1.0, 1.0, 1.0, 1.0); 12 | } 13 | else{ 14 | color = vec4(0.1, 0.1, 0.1, 1.0); 15 | } 16 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/vertical_bar.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | uniform vec4 line_color; 8 | uniform float time; 9 | 10 | void main(){ 11 | color = line_color; 12 | //color = vec4(0, 0, 0, 0.1); 13 | //int x = int(gl_FragCoord.y / 1); 14 | //int y = int(gl_FragCoord.x / 1); 15 | //if ((x+y) % 2 == 0){ 16 | // color = vec4(0, 0, 0, 0.2); 17 | //} 18 | //else{ 19 | // color = vec4(0, 0, 0, 0.1); 20 | //} 21 | //color = vec4(0, 0, 0, abs(sin(gl_FragCoord.y / 10 + gl_FragCoord.x / 10))); 22 | //if (abs(uvs.x - fract(time * freq)) < 0.05){ 23 | //color = vec4(line_color, 0.1); 24 | //} 25 | //else{ 26 | //color = vec4(0, 0, 0, 0.1); 27 | //} 28 | //color = vec4(line_color * pow(abs(sin(uvs.x - time * freq)), 128), 0.3); 29 | //color = vec4(line_color * abs(sin(time)), 0.3); 30 | //color = vec4(line_color, 0.3); 31 | } -------------------------------------------------------------------------------- /pdf_viewer/shaders/vertical_bar_dark.fragment: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | out vec4 color; 4 | in vec2 screen_pos; 5 | in vec2 uvs; 6 | 7 | uniform vec4 line_color; 8 | uniform float time; 9 | 10 | void main(){ 11 | color = vec4((1-line_color.rgb), line_color.a); 12 | } -------------------------------------------------------------------------------- /pdf_viewer/synctex/synctex_parser_local.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, 2009, 2010 , 2011 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the SyncTeX package. 5 | 6 | Latest Revision: Sun Oct 15 15:09:55 UTC 2017 7 | 8 | Version: 1.21 9 | 10 | See synctex_parser_readme.txt for more details 11 | 12 | License: 13 | -------- 14 | Permission is hereby granted, free of charge, to any person 15 | obtaining a copy of this software and associated documentation 16 | files (the "Software"), to deal in the Software without 17 | restriction, including without limitation the rights to use, 18 | copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the 20 | Software is furnished to do so, subject to the following 21 | conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 28 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 29 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 30 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 31 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 32 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 33 | OTHER DEALINGS IN THE SOFTWARE 34 | 35 | Except as contained in this notice, the name of the copyright holder 36 | shall not be used in advertising or otherwise to promote the sale, 37 | use or other dealings in this Software without prior written 38 | authorization from the copyright holder. 39 | 40 | */ 41 | 42 | /* This local header file is for TEXLIVE, use your own header to fit your system */ 43 | # include /* for inline && HAVE_xxx */ 44 | /* No inlining for synctex tool in texlive. */ 45 | # define SYNCTEX_INLINE 46 | -------------------------------------------------------------------------------- /pdf_viewer/synctex/synctex_parser_utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the __SyncTeX__ package. 5 | 6 | [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 | [//]: # (Version: 1.21) 8 | 9 | See `synctex_parser_readme.md` for more details 10 | 11 | ## License 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE 33 | 34 | Except as contained in this notice, the name of the copyright holder 35 | shall not be used in advertising or otherwise to promote the sale, 36 | use or other dealings in this Software without prior written 37 | authorization from the copyright holder. 38 | 39 | */ 40 | 41 | #ifndef SYNCTEX_PARSER_UTILS_H 42 | #define SYNCTEX_PARSER_UTILS_H 43 | 44 | /* The utilities declared here are subject to conditional implementation. 45 | * All the operating system special stuff goes here. 46 | * The problem mainly comes from file name management: path separator, encoding... 47 | */ 48 | 49 | #include "synctex_version.h" 50 | 51 | 52 | typedef int synctex_bool_t; 53 | # define synctex_YES (0==0) 54 | # define synctex_NO (0==1) 55 | 56 | # define synctex_ADD_QUOTES -1 57 | # define synctex_COMPRESS -1 58 | # define synctex_DONT_ADD_QUOTES 0 59 | # define synctex_DONT_COMPRESS 0 60 | 61 | #ifndef __SYNCTEX_PARSER_UTILS__ 62 | # define __SYNCTEX_PARSER_UTILS__ 63 | 64 | #include 65 | 66 | #if defined(_WIN32) 67 | #define __attribute__(x) 68 | #endif 69 | 70 | #ifdef __cplusplus 71 | extern "C" { 72 | #endif 73 | 74 | # if defined(_WIN32) || defined(__OS2__) 75 | # define SYNCTEX_CASE_SENSITIVE_PATH 0 76 | # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c || '\\' == c) 77 | # else 78 | # define SYNCTEX_CASE_SENSITIVE_PATH 1 79 | # define SYNCTEX_IS_PATH_SEPARATOR(c) ('/' == c) 80 | # endif 81 | 82 | # if defined(_WIN32) || defined(__OS2__) 83 | # define SYNCTEX_IS_DOT(c) ('.' == c) 84 | # else 85 | # define SYNCTEX_IS_DOT(c) ('.' == c) 86 | # endif 87 | 88 | # if SYNCTEX_CASE_SENSITIVE_PATH 89 | # define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (left != right) 90 | # else 91 | # define SYNCTEX_ARE_PATH_CHARACTERS_EQUAL(left,right) (toupper(left) != toupper(right)) 92 | # endif 93 | 94 | /* This custom malloc functions initializes to 0 the newly allocated memory. 95 | * There is no bzero function on windows. */ 96 | void *_synctex_malloc(size_t size); 97 | 98 | /* To balance _synctex_malloc. 99 | * ptr might be NULL. */ 100 | void _synctex_free(void * ptr); 101 | 102 | /* This is used to log some informational message to the standard error stream. 103 | * On Windows, the stderr stream is not exposed and another method is used. 104 | * The return value is the number of characters printed. */ 105 | int _synctex_error(const char * reason,...); 106 | int _synctex_debug(const char * reason,...); 107 | 108 | /* strip the last extension of the given string, this string is modified! 109 | * This function depends on the OS because the path separator may differ. 110 | * This should be discussed more precisely. */ 111 | void _synctex_strip_last_path_extension(char * string); 112 | 113 | /* Compare two file names, windows is sometimes case insensitive... 114 | * The given strings may differ stricto sensu, but represent the same file name. 115 | * It might not be the real way of doing things. 116 | * The return value is an undefined non 0 value when the two file names are equivalent. 117 | * It is 0 otherwise. */ 118 | synctex_bool_t _synctex_is_equivalent_file_name(const char *lhs, const char *rhs); 119 | 120 | /* Description forthcoming.*/ 121 | synctex_bool_t _synctex_path_is_absolute(const char * name); 122 | 123 | /* Description forthcoming...*/ 124 | const char * _synctex_last_path_component(const char * name); 125 | 126 | /* Description forthcoming...*/ 127 | const char * _synctex_base_name(const char *path); 128 | 129 | /* If the core of the last path component of src is not already enclosed with double quotes ('"') 130 | * and contains a space character (' '), then a new buffer is created, the src is copied and quotes are added. 131 | * In all other cases, no destination buffer is created and the src is not copied. 132 | * 0 on success, which means no error, something non 0 means error, mainly due to memory allocation failure, or bad parameter. 133 | * This is used to fix a bug in the first version of pdftex with synctex (1.40.9) for which names with spaces 134 | * were not managed in a standard way. 135 | * On success, the caller owns the buffer pointed to by dest_ref (is any) and 136 | * is responsible of freeing the memory when done. 137 | * The size argument is the size of the src buffer. On return the dest_ref points to a buffer sized size+2.*/ 138 | int _synctex_copy_with_quoting_last_path_component(const char * src, char ** dest_ref, size_t size); 139 | 140 | /* These are the possible extensions of the synctex file */ 141 | extern const char * synctex_suffix; 142 | extern const char * synctex_suffix_gz; 143 | 144 | typedef unsigned int synctex_io_mode_t; 145 | 146 | typedef enum { 147 | synctex_io_append_mask = 1, 148 | synctex_io_gz_mask = synctex_io_append_mask<<1 149 | } synctex_io_mode_masks_t; 150 | 151 | typedef enum { 152 | synctex_compress_mode_none = 0, 153 | synctex_compress_mode_gz = 1 154 | } synctex_compress_mode_t; 155 | 156 | int _synctex_get_name(const char * output, const char * build_directory, char ** synctex_name_ref, synctex_io_mode_t * io_mode_ref); 157 | 158 | /* returns the correct mode required by fopen and gzopen from the given io_mode */ 159 | const char * _synctex_get_io_mode_name(synctex_io_mode_t io_mode); 160 | 161 | synctex_bool_t synctex_ignore_leading_dot_slash_in_path(const char ** name); 162 | 163 | #ifdef __cplusplus 164 | } 165 | #endif 166 | 167 | #endif 168 | #endif /* SYNCTEX_PARSER_UTILS_H */ 169 | -------------------------------------------------------------------------------- /pdf_viewer/synctex/synctex_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008-2017 jerome DOT laurens AT u-bourgogne DOT fr 3 | 4 | This file is part of the __SyncTeX__ package. 5 | 6 | [//]: # (Latest Revision: Fri Jul 14 16:20:41 UTC 2017) 7 | [//]: # (Version: 1.21) 8 | 9 | See `synctex_parser_readme.md` for more details 10 | 11 | ## License 12 | 13 | Permission is hereby granted, free of charge, to any person 14 | obtaining a copy of this software and associated documentation 15 | files (the "Software"), to deal in the Software without 16 | restriction, including without limitation the rights to use, 17 | copy, modify, merge, publish, distribute, sublicense, and/or sell 18 | copies of the Software, and to permit persons to whom the 19 | Software is furnished to do so, subject to the following 20 | conditions: 21 | 22 | The above copyright notice and this permission notice shall be 23 | included in all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 26 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 27 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 28 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 29 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 31 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 32 | OTHER DEALINGS IN THE SOFTWARE 33 | 34 | Except as contained in this notice, the name of the copyright holder 35 | shall not be used in advertising or otherwise to promote the sale, 36 | use or other dealings in this Software without prior written 37 | authorization from the copyright holder. 38 | 39 | ## Acknowledgments: 40 | 41 | The author received useful remarks from the __pdfTeX__ developers, especially Hahn The Thanh, 42 | and significant help from __XeTeX__ developer Jonathan Kew. 43 | 44 | ## Nota Bene: 45 | 46 | If you include or use a significant part of the __SyncTeX__ package into a software, 47 | I would appreciate to be listed as contributor and see "__SyncTeX__" highlighted. 48 | */ 49 | 50 | #ifndef __SYNCTEX_VERSION__ 51 | # define __SYNCTEX_VERSION__ 52 | 53 | # define SYNCTEX_VERSION_MAJOR 1 54 | 55 | # define SYNCTEX_VERSION_STRING "1.21" 56 | 57 | # define SYNCTEX_CLI_VERSION_STRING "1.5" 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /pdf_viewer/ui.cpp: -------------------------------------------------------------------------------- 1 | #include "ui.h" 2 | #include 3 | 4 | extern std::wstring DEFAULT_OPEN_FILE_PATH; 5 | 6 | std::wstring select_command_file_name(std::string command_name) { 7 | if (command_name == "open_document") { 8 | return select_document_file_name(); 9 | } 10 | else if (command_name == "source_config") { 11 | return select_any_file_name(); 12 | } 13 | else { 14 | return select_any_file_name(); 15 | } 16 | } 17 | 18 | std::wstring select_document_file_name() { 19 | if (DEFAULT_OPEN_FILE_PATH.size() == 0) { 20 | 21 | QString file_name = QFileDialog::getOpenFileName(nullptr, "Select Document", "", "Documents (*.pdf *.epub *.cbz)"); 22 | return file_name.toStdWString(); 23 | } 24 | else { 25 | 26 | QFileDialog fd = QFileDialog(nullptr, "Select Document", "", "Documents (*.pdf *.epub *.cbz)"); 27 | fd.setDirectory(QString::fromStdWString(DEFAULT_OPEN_FILE_PATH)); 28 | if (fd.exec()) { 29 | 30 | QString file_name = fd.selectedFiles().first(); 31 | return file_name.toStdWString(); 32 | } 33 | else { 34 | return L""; 35 | } 36 | } 37 | 38 | } 39 | 40 | std::wstring select_json_file_name() { 41 | QString file_name = QFileDialog::getOpenFileName(nullptr, "Select Document", "", "Documents (*.json )"); 42 | return file_name.toStdWString(); 43 | } 44 | 45 | std::wstring select_any_file_name() { 46 | QString file_name = QFileDialog::getOpenFileName(nullptr, "Select File", "", "Any (*)"); 47 | return file_name.toStdWString(); 48 | } 49 | 50 | std::wstring select_new_json_file_name() { 51 | QString file_name = QFileDialog::getSaveFileName(nullptr, "Select Document", "", "Documents (*.json )"); 52 | return file_name.toStdWString(); 53 | } 54 | 55 | std::wstring select_new_pdf_file_name() { 56 | QString file_name = QFileDialog::getSaveFileName(nullptr, "Select Document", "", "Documents (*.pdf )"); 57 | return file_name.toStdWString(); 58 | } 59 | 60 | 61 | std::vector ConfigFileChangeListener::registered_listeners; 62 | 63 | ConfigFileChangeListener::ConfigFileChangeListener() { 64 | registered_listeners.push_back(this); 65 | } 66 | 67 | ConfigFileChangeListener::~ConfigFileChangeListener() { 68 | registered_listeners.erase(std::find(registered_listeners.begin(), registered_listeners.end(), this)); 69 | } 70 | 71 | void ConfigFileChangeListener::notify_config_file_changed(ConfigManager* new_config_manager) { 72 | for (auto* it : ConfigFileChangeListener::registered_listeners) { 73 | it->on_config_file_changed(new_config_manager); 74 | } 75 | } 76 | 77 | bool HierarchialSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const 78 | { 79 | // custom behaviour : 80 | #ifdef SIOYEK_QT6 81 | if (filterRegularExpression().pattern().size() == 0) 82 | #else 83 | if (filterRegExp().isEmpty() == false) 84 | #endif 85 | { 86 | // get source-model index for current row 87 | QModelIndex source_index = sourceModel()->index(source_row, this->filterKeyColumn(), source_parent); 88 | if (source_index.isValid()) 89 | { 90 | // check current index itself : 91 | QString key = sourceModel()->data(source_index, filterRole()).toString(); 92 | 93 | #ifdef SIOYEK_QT6 94 | bool parent_contains = key.contains(filterRegularExpression()); 95 | #else 96 | bool parent_contains = key.contains(filterRegExp()); 97 | #endif 98 | 99 | if (parent_contains) return true; 100 | 101 | // if any of children matches the filter, then current index matches the filter as well 102 | int i, nb = sourceModel()->rowCount(source_index); 103 | for (i = 0; i < nb; ++i) 104 | { 105 | if (filterAcceptsRow(i, source_index)) 106 | { 107 | return true; 108 | } 109 | } 110 | return false; 111 | } 112 | } 113 | // parent call for initial behaviour 114 | return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); 115 | } 116 | 117 | bool MySortFilterProxyModel::filterAcceptsRow(int source_row, 118 | const QModelIndex& source_parent) const 119 | { 120 | if (FUZZY_SEARCHING) { 121 | 122 | QModelIndex source_index = sourceModel()->index(source_row, this->filterKeyColumn(), source_parent); 123 | if (source_index.isValid()) 124 | { 125 | // check current index itself : 126 | 127 | QString key = sourceModel()->data(source_index, filterRole()).toString(); 128 | if (filterString.size() == 0) return true; 129 | std::wstring s1 = filterString.toStdWString(); 130 | std::wstring s2 = key.toStdWString(); 131 | int score = static_cast(rapidfuzz::fuzz::partial_ratio(s1, s2)); 132 | 133 | return score > 50; 134 | } 135 | else { 136 | return false; 137 | } 138 | } 139 | else { 140 | return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent); 141 | } 142 | } 143 | 144 | void MySortFilterProxyModel::setFilterCustom(QString filterString) { 145 | if (FUZZY_SEARCHING) { 146 | this->filterString = filterString; 147 | this->setFilterFixedString(filterString); 148 | sort(0); 149 | } 150 | else { 151 | this->setFilterFixedString(filterString); 152 | } 153 | } 154 | 155 | bool MySortFilterProxyModel::lessThan(const QModelIndex& left, 156 | const QModelIndex& right) const 157 | { 158 | if (FUZZY_SEARCHING) { 159 | 160 | QString leftData = sourceModel()->data(left).toString(); 161 | QString rightData = sourceModel()->data(right).toString(); 162 | 163 | int left_score = static_cast(rapidfuzz::fuzz::partial_ratio(filterString.toStdWString(), leftData.toStdWString())); 164 | int right_score = static_cast(rapidfuzz::fuzz::partial_ratio(filterString.toStdWString(), rightData.toStdWString())); 165 | return left_score > right_score; 166 | } 167 | else { 168 | return QSortFilterProxyModel::lessThan(left, right); 169 | } 170 | } 171 | MySortFilterProxyModel::MySortFilterProxyModel() { 172 | if (FUZZY_SEARCHING) { 173 | setDynamicSortFilter(true); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /pdf_viewer/utf8.h: -------------------------------------------------------------------------------- 1 | // Copyright 2006 Nemanja Trifunovic 2 | 3 | /* 4 | Permission is hereby granted, free of charge, to any person or organization 5 | obtaining a copy of the software and accompanying documentation covered by 6 | this license (the "Software") to use, reproduce, display, distribute, 7 | execute, and transmit the Software, and to prepare derivative works of the 8 | Software, and to permit third-parties to whom the Software is furnished to 9 | do so, all subject to the following: 10 | 11 | The copyright notices in the Software and this entire statement, including 12 | the above license grant, this restriction and the following disclaimer, 13 | must be included in all copies of the Software, in whole or in part, and 14 | all derivative works of the Software, unless such copies or derivative 15 | works are solely in the form of machine-executable object code generated by 16 | a source language processor. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 21 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 22 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | 28 | #ifndef UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 29 | #define UTF8_FOR_CPP_2675DCD0_9480_4c0c_B92A_CC14C027B731 30 | 31 | #include "utf8/checked.h" 32 | #include "utf8/unchecked.h" 33 | 34 | #endif // header guard 35 | -------------------------------------------------------------------------------- /pdf_viewer/utf8/unchecked.h: -------------------------------------------------------------------------------- 1 | // Copyright 2006 Nemanja Trifunovic 2 | 3 | /* 4 | Permission is hereby granted, free of charge, to any person or organization 5 | obtaining a copy of the software and accompanying documentation covered by 6 | this license (the "Software") to use, reproduce, display, distribute, 7 | execute, and transmit the Software, and to prepare derivative works of the 8 | Software, and to permit third-parties to whom the Software is furnished to 9 | do so, all subject to the following: 10 | 11 | The copyright notices in the Software and this entire statement, including 12 | the above license grant, this restriction and the following disclaimer, 13 | must be included in all copies of the Software, in whole or in part, and 14 | all derivative works of the Software, unless such copies or derivative 15 | works are solely in the form of machine-executable object code generated by 16 | a source language processor. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 21 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 22 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 24 | DEALINGS IN THE SOFTWARE. 25 | */ 26 | 27 | 28 | #ifndef UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 29 | #define UTF8_FOR_CPP_UNCHECKED_H_2675DCD0_9480_4c0c_B92A_CC14C027B731 30 | 31 | #include "core.h" 32 | 33 | namespace utf8 34 | { 35 | namespace unchecked 36 | { 37 | template 38 | octet_iterator append(uint32_t cp, octet_iterator result) 39 | { 40 | if (cp < 0x80) // one octet 41 | *(result++) = static_cast(cp); 42 | else if (cp < 0x800) { // two octets 43 | *(result++) = static_cast((cp >> 6) | 0xc0); 44 | *(result++) = static_cast((cp & 0x3f) | 0x80); 45 | } 46 | else if (cp < 0x10000) { // three octets 47 | *(result++) = static_cast((cp >> 12) | 0xe0); 48 | *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); 49 | *(result++) = static_cast((cp & 0x3f) | 0x80); 50 | } 51 | else { // four octets 52 | *(result++) = static_cast((cp >> 18) | 0xf0); 53 | *(result++) = static_cast(((cp >> 12) & 0x3f)| 0x80); 54 | *(result++) = static_cast(((cp >> 6) & 0x3f) | 0x80); 55 | *(result++) = static_cast((cp & 0x3f) | 0x80); 56 | } 57 | return result; 58 | } 59 | 60 | template 61 | uint32_t next(octet_iterator& it) 62 | { 63 | uint32_t cp = utf8::internal::mask8(*it); 64 | typename std::iterator_traits::difference_type length = utf8::internal::sequence_length(it); 65 | switch (length) { 66 | case 1: 67 | break; 68 | case 2: 69 | it++; 70 | cp = ((cp << 6) & 0x7ff) + ((*it) & 0x3f); 71 | break; 72 | case 3: 73 | ++it; 74 | cp = ((cp << 12) & 0xffff) + ((utf8::internal::mask8(*it) << 6) & 0xfff); 75 | ++it; 76 | cp += (*it) & 0x3f; 77 | break; 78 | case 4: 79 | ++it; 80 | cp = ((cp << 18) & 0x1fffff) + ((utf8::internal::mask8(*it) << 12) & 0x3ffff); 81 | ++it; 82 | cp += (utf8::internal::mask8(*it) << 6) & 0xfff; 83 | ++it; 84 | cp += (*it) & 0x3f; 85 | break; 86 | } 87 | ++it; 88 | return cp; 89 | } 90 | 91 | template 92 | uint32_t peek_next(octet_iterator it) 93 | { 94 | return utf8::unchecked::next(it); 95 | } 96 | 97 | template 98 | uint32_t prior(octet_iterator& it) 99 | { 100 | while (utf8::internal::is_trail(*(--it))) ; 101 | octet_iterator temp = it; 102 | return utf8::unchecked::next(temp); 103 | } 104 | 105 | // Deprecated in versions that include prior, but only for the sake of consistency (see utf8::previous) 106 | template 107 | inline uint32_t previous(octet_iterator& it) 108 | { 109 | return utf8::unchecked::prior(it); 110 | } 111 | 112 | template 113 | void advance (octet_iterator& it, distance_type n) 114 | { 115 | for (distance_type i = 0; i < n; ++i) 116 | utf8::unchecked::next(it); 117 | } 118 | 119 | template 120 | typename std::iterator_traits::difference_type 121 | distance (octet_iterator first, octet_iterator last) 122 | { 123 | typename std::iterator_traits::difference_type dist; 124 | for (dist = 0; first < last; ++dist) 125 | utf8::unchecked::next(first); 126 | return dist; 127 | } 128 | 129 | template 130 | octet_iterator utf16to8 (u16bit_iterator start, u16bit_iterator end, octet_iterator result) 131 | { 132 | while (start != end) { 133 | uint32_t cp = utf8::internal::mask16(*start++); 134 | // Take care of surrogate pairs first 135 | if (utf8::internal::is_lead_surrogate(cp)) { 136 | uint32_t trail_surrogate = utf8::internal::mask16(*start++); 137 | cp = (cp << 10) + trail_surrogate + internal::SURROGATE_OFFSET; 138 | } 139 | result = utf8::unchecked::append(cp, result); 140 | } 141 | return result; 142 | } 143 | 144 | template 145 | u16bit_iterator utf8to16 (octet_iterator start, octet_iterator end, u16bit_iterator result) 146 | { 147 | while (start < end) { 148 | uint32_t cp = utf8::unchecked::next(start); 149 | if (cp > 0xffff) { //make a surrogate pair 150 | *result++ = static_cast((cp >> 10) + internal::LEAD_OFFSET); 151 | *result++ = static_cast((cp & 0x3ff) + internal::TRAIL_SURROGATE_MIN); 152 | } 153 | else 154 | *result++ = static_cast(cp); 155 | } 156 | return result; 157 | } 158 | 159 | template 160 | octet_iterator utf32to8 (u32bit_iterator start, u32bit_iterator end, octet_iterator result) 161 | { 162 | while (start != end) 163 | result = utf8::unchecked::append(*(start++), result); 164 | 165 | return result; 166 | } 167 | 168 | template 169 | u32bit_iterator utf8to32 (octet_iterator start, octet_iterator end, u32bit_iterator result) 170 | { 171 | while (start < end) 172 | (*result++) = utf8::unchecked::next(start); 173 | 174 | return result; 175 | } 176 | 177 | // The iterator class 178 | template 179 | class iterator : public std::iterator { 180 | octet_iterator it; 181 | public: 182 | iterator () {} 183 | explicit iterator (const octet_iterator& octet_it): it(octet_it) {} 184 | // the default "big three" are OK 185 | octet_iterator base () const { return it; } 186 | uint32_t operator * () const 187 | { 188 | octet_iterator temp = it; 189 | return utf8::unchecked::next(temp); 190 | } 191 | bool operator == (const iterator& rhs) const 192 | { 193 | return (it == rhs.it); 194 | } 195 | bool operator != (const iterator& rhs) const 196 | { 197 | return !(operator == (rhs)); 198 | } 199 | iterator& operator ++ () 200 | { 201 | ::std::advance(it, utf8::internal::sequence_length(it)); 202 | return *this; 203 | } 204 | iterator operator ++ (int) 205 | { 206 | iterator temp = *this; 207 | ::std::advance(it, utf8::internal::sequence_length(it)); 208 | return temp; 209 | } 210 | iterator& operator -- () 211 | { 212 | utf8::unchecked::prior(it); 213 | return *this; 214 | } 215 | iterator operator -- (int) 216 | { 217 | iterator temp = *this; 218 | utf8::unchecked::prior(it); 219 | return temp; 220 | } 221 | }; // class iterator 222 | 223 | } // namespace utf8::unchecked 224 | } // namespace utf8 225 | 226 | 227 | #endif // header guard 228 | 229 | -------------------------------------------------------------------------------- /pdf_viewer/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | 20 | #include 21 | 22 | #include "book.h" 23 | #include "utf8.h" 24 | #include "coordinates.h" 25 | 26 | #define LL_ITER(name, start) for(auto name=start;(name);name=name->next) 27 | 28 | struct ParsedUri { 29 | int page; 30 | float x; 31 | float y; 32 | }; 33 | 34 | struct CharacterAddress { 35 | int page; 36 | fz_stext_block* block; 37 | fz_stext_line* line; 38 | fz_stext_char* character; 39 | Document* doc; 40 | 41 | CharacterAddress* previous_character = nullptr; 42 | 43 | bool advance(char c); 44 | bool backspace(); 45 | bool next_char(); 46 | bool next_line(); 47 | bool next_block(); 48 | bool next_page(); 49 | 50 | float focus_offset(); 51 | 52 | }; 53 | 54 | std::wstring to_lower(const std::wstring& inp); 55 | bool is_separator(fz_stext_char* last_char, fz_stext_char* current_char); 56 | void get_flat_toc(const std::vector& roots, std::vector& output, std::vector& pages); 57 | int mod(int a, int b); 58 | bool range_intersects(float range1_start, float range1_end, float range2_start, float range2_end); 59 | bool rects_intersect(fz_rect rect1, fz_rect rect2); 60 | ParsedUri parse_uri(fz_context* mupdf_context, std::string uri); 61 | char get_symbol(int key, bool is_shift_pressed, const std::vector&special_symbols); 62 | 63 | template 64 | int argminf(const std::vector &collection, std::function f) { 65 | 66 | float min = std::numeric_limits::infinity(); 67 | int min_index = -1; 68 | for (size_t i = 0; i < collection.size(); i++) { 69 | float element_value = f(collection[i]); 70 | if (element_value < min){ 71 | min = element_value; 72 | min_index = i; 73 | } 74 | } 75 | return min_index; 76 | } 77 | void rect_to_quad(fz_rect rect, float quad[8]); 78 | void copy_to_clipboard(const std::wstring& text, bool selection=false); 79 | void install_app(const char* argv0); 80 | int get_f_key(std::wstring name); 81 | void show_error_message(const std::wstring& error_message); 82 | std::wstring utf8_decode(const std::string& encoded_str); 83 | std::string utf8_encode(const std::wstring& decoded_str); 84 | // is the character a right to left character 85 | bool is_rtl(int c); 86 | std::wstring reverse_wstring(const std::wstring& inp); 87 | bool parse_search_command(const std::wstring& search_command, int* out_begin, int* out_end, std::wstring* search_text); 88 | QStandardItemModel* get_model_from_toc(const std::vector& roots); 89 | 90 | // given a tree of toc nodes and an array of indices, returns the node whose ith parent is indexed by the ith element 91 | // of the indices array. That is: 92 | // root[indices[0]][indices[1]] ... [indices[indices.size()-1]] 93 | TocNode* get_toc_node_from_indices(const std::vector& roots, const std::vector& indices); 94 | 95 | fz_stext_char* find_closest_char_to_document_point(const std::vector flat_chars, fz_point document_point, int* location_index); 96 | void merge_selected_character_rects(const std::vector& selected_character_rects, std::vector& resulting_rects); 97 | void split_key_string(std::wstring haystack, const std::wstring& needle, std::vector& res); 98 | void run_command(std::wstring command, QStringList parameters, bool wait=true); 99 | 100 | std::wstring get_string_from_stext_line(fz_stext_line* line); 101 | void sleep_ms(unsigned int ms); 102 | //void open_url(const std::string& url_string); 103 | //void open_url(const std::wstring& url_string); 104 | 105 | void open_file_url(const std::wstring& file_url); 106 | void open_web_url(const std::wstring& web_url); 107 | 108 | void open_file(const std::wstring& path); 109 | void search_custom_engine(const std::wstring& search_string, const std::wstring& custom_engine_url); 110 | void search_google_scholar(const std::wstring& search_string); 111 | void search_libgen(const std::wstring& search_string); 112 | void index_references(fz_stext_page* page, int page_number, std::map& indices); 113 | void get_flat_chars_from_stext_page(fz_stext_page* stext_page, std::vector& flat_chars); 114 | int find_best_vertical_line_location(fz_pixmap* pixmap, int relative_click_x, int relative_click_y); 115 | //void get_flat_chars_from_stext_page_with_space(fz_stext_page* stext_page, std::vector& flat_chars, fz_stext_char* space); 116 | void index_equations(const std::vector& flat_chars, int page_number, std::map>& indices); 117 | void find_regex_matches_in_stext_page(const std::vector& flat_chars, 118 | const std::wregex& regex, 119 | std::vector>& match_ranges, std::vector& match_texts); 120 | bool is_string_numeric(const std::wstring& str); 121 | bool is_string_numeric_float(const std::wstring& str); 122 | void create_file_if_not_exists(const std::wstring& path); 123 | QByteArray serialize_string_array(const QStringList& string_list); 124 | QStringList deserialize_string_array(const QByteArray& byte_array); 125 | //Path add_redundant_dot_to_path(const Path& sane_path); 126 | bool should_reuse_instance(int argc, char** argv); 127 | bool should_new_instance(int argc, char** argv); 128 | QCommandLineParser* get_command_line_parser(); 129 | std::wstring concatenate_path(const std::wstring& prefix, const std::wstring& suffix); 130 | std::wstring get_canonical_path(const std::wstring& path); 131 | void split_path(std::wstring path, std::vector& res); 132 | //std::wstring canonicalize_path(const std::wstring& path); 133 | std::wstring add_redundant_dot_to_path(const std::wstring& path); 134 | float manhattan_distance(float x1, float y1, float x2, float y2); 135 | float manhattan_distance(fvec2 v1, fvec2 v2); 136 | QWidget* get_top_level_widget(QWidget* widget); 137 | std::wstring strip_string(std::wstring& input_string); 138 | //void index_generic(const std::vector& flat_chars, int page_number, std::vector& indices); 139 | void index_generic(const std::vector& flat_chars, int page_number, std::vector& indices); 140 | std::vector split_whitespace(std::wstring const& input); 141 | float type_name_similarity_score(std::wstring name1, std::wstring name2); 142 | bool is_stext_page_rtl(fz_stext_page* stext_page); 143 | void check_for_updates(QWidget* parent, std::string current_version); 144 | char* get_argv_value(int argc, char** argv, std::string key); 145 | void split_root_file(QString path, QString& out_root, QString& out_partial); 146 | QString expand_home_dir(QString path); 147 | std::vector get_max_width_histogram_from_pixmap(fz_pixmap* pixmap); 148 | //std::vector get_line_ends_from_histogram(std::vector histogram); 149 | void get_line_begins_and_ends_from_histogram(std::vector histogram, std::vector& begins, std::vector& ends); 150 | 151 | template 152 | int find_nth_larger_element_in_sorted_list(std::vector sorted_list, T value, int n) { 153 | int i = 0; 154 | while (i < sorted_list.size() && (value >= sorted_list[i])) i++; 155 | if ((i < sorted_list.size()) && (sorted_list[i] == value)) i--; 156 | if ((i + n - 1) < sorted_list.size()) { 157 | return i + n - 1; 158 | } 159 | else { 160 | return -1; 161 | } 162 | 163 | } 164 | 165 | QString get_color_qml_string(float r, float g, float b); 166 | void copy_file(std::wstring src_path, std::wstring dst_path); 167 | fz_quad quad_from_rect(fz_rect r); 168 | std::vector quads_from_rects(const std::vector& rects); 169 | std::wifstream open_wifstream(const std::wstring& file_name); 170 | void get_flat_words_from_flat_chars(const std::vector& flat_chars, std::vector& flat_word_rects, std::vector>* out_char_rects = nullptr); 171 | void get_word_rect_list_from_flat_chars(const std::vector& flat_chars, 172 | std::vector& words, 173 | std::vector>& flat_word_rects); 174 | 175 | std::vector get_tags(int n); 176 | int get_index_from_tag(const std::string& tag); 177 | std::wstring truncate_string(const std::wstring& inp, int size); 178 | std::wstring get_page_formatted_string(int page); 179 | fz_rect create_word_rect(const std::vector& chars); 180 | std::vector create_word_rects_multiline(const std::vector& chars); 181 | void get_flat_chars_from_block(fz_stext_block* block, std::vector& flat_chars); 182 | void get_text_from_flat_chars(const std::vector& flat_chars, std::wstring& string_res, std::vector& indices); 183 | bool is_string_titlish(const std::wstring& str); 184 | bool is_title_parent_of(const std::wstring& parent_title, const std::wstring& child_title, bool* are_same); 185 | std::wstring find_first_regex_match(const std::wstring& haystack, const std::wstring& regex_string); 186 | void merge_lines(const std::vector& lines, std::vector& out_rects, std::vector& out_texts); 187 | float get_max_display_scaling(); 188 | int lcs(const char* X, const char* Y, int m, int n); 189 | bool has_arg(int argc, char** argv, std::string key); 190 | std::vector find_all_regex_matches(const std::wstring& haystack, const std::wstring& regex_string); 191 | bool command_requires_text(const std::wstring& command); 192 | bool command_requires_rect(const std::wstring& command); 193 | void hexademical_to_normalized_color(std::wstring color_string, float* color, int n_components); 194 | void parse_command_string(std::wstring command_string, std::string& command_name, std::wstring& command_data); 195 | void parse_color(std::wstring color_string, float* out_color, int n_components); 196 | int get_status_bar_height(); 197 | void flat_char_prism(const std::vector chars, int page, std::wstring& output_text, std::vector& pages, std::vector& rects); 198 | QString get_status_stylesheet(bool nofont=false); 199 | QString get_selected_stylesheet(bool nofont=false); 200 | 201 | template 202 | void matmul(float m1[], float m2[], float result[]) { 203 | for (int i = 0; i < d1; i++) { 204 | for (int j = 0; j < d3; j++) { 205 | result[i * d3 + j] = 0; 206 | for (int k = 0; k < d2; k++) { 207 | result[i * d3 + j] += m1[i * d2 + k] * m2[k * d3 + j]; 208 | } 209 | } 210 | } 211 | } 212 | 213 | void convert_color4(float* in_color, int* out_color); 214 | std::string get_aplph_tag(int n, int max_n); 215 | 216 | bool should_trigger_delete(QKeyEvent *key_event); 217 | -------------------------------------------------------------------------------- /pdf_viewer_build_config.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | TARGET = sioyek 3 | VERSION = 2.0.0 4 | INCLUDEPATH += ./pdf_viewer\ 5 | mupdf/include \ 6 | zlib 7 | 8 | 9 | QT += core opengl gui widgets network 3dinput 10 | 11 | greaterThan(QT_MAJOR_VERSION, 5){ 12 | QT += openglwidgets 13 | DEFINES += SIOYEK_QT6 14 | } 15 | else{ 16 | QT += openglextensions 17 | } 18 | 19 | CONFIG += c++17 20 | DEFINES += QT_3DINPUT_LIB QT_OPENGL_LIB QT_OPENGLEXTENSIONS_LIB QT_WIDGETS_LIB 21 | 22 | CONFIG(non_portable){ 23 | DEFINES += NON_PORTABLE 24 | } 25 | 26 | # Input 27 | HEADERS += pdf_viewer/book.h \ 28 | pdf_viewer/config.h \ 29 | pdf_viewer/database.h \ 30 | pdf_viewer/document.h \ 31 | pdf_viewer/document_view.h \ 32 | pdf_viewer/fts_fuzzy_match.h \ 33 | pdf_viewer/rapidfuzz_amalgamated.hpp \ 34 | pdf_viewer/input.h \ 35 | pdf_viewer/main_widget.h \ 36 | pdf_viewer/pdf_renderer.h \ 37 | pdf_viewer/pdf_view_opengl_widget.h \ 38 | pdf_viewer/checksum.h \ 39 | pdf_viewer/new_file_checker.h \ 40 | pdf_viewer/coordinates.h \ 41 | pdf_viewer/sqlite3.h \ 42 | pdf_viewer/sqlite3ext.h \ 43 | pdf_viewer/ui.h \ 44 | pdf_viewer/path.h \ 45 | pdf_viewer/utf8.h \ 46 | pdf_viewer/utils.h \ 47 | pdf_viewer/utf8/checked.h \ 48 | pdf_viewer/utf8/core.h \ 49 | pdf_viewer/utf8/unchecked.h \ 50 | pdf_viewer/synctex/synctex_parser.h \ 51 | pdf_viewer/synctex/synctex_parser_utils.h \ 52 | pdf_viewer/RunGuard.h \ 53 | pdf_viewer/OpenWithApplication.h 54 | 55 | SOURCES += pdf_viewer/book.cpp \ 56 | pdf_viewer/config.cpp \ 57 | pdf_viewer/database.cpp \ 58 | pdf_viewer/document.cpp \ 59 | pdf_viewer/document_view.cpp \ 60 | pdf_viewer/input.cpp \ 61 | pdf_viewer/main.cpp \ 62 | pdf_viewer/main_widget.cpp \ 63 | pdf_viewer/pdf_renderer.cpp \ 64 | pdf_viewer/pdf_view_opengl_widget.cpp \ 65 | pdf_viewer/checksum.cpp \ 66 | pdf_viewer/new_file_checker.cpp \ 67 | pdf_viewer/coordinates.cpp \ 68 | pdf_viewer/sqlite3.c \ 69 | pdf_viewer/ui.cpp \ 70 | pdf_viewer/path.cpp \ 71 | pdf_viewer/utils.cpp \ 72 | pdf_viewer/synctex/synctex_parser.c \ 73 | pdf_viewer/synctex/synctex_parser_utils.c \ 74 | pdf_viewer/RunGuard.cpp \ 75 | pdf_viewer/OpenWithApplication.cpp 76 | 77 | 78 | win32{ 79 | DEFINES += _CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE 80 | RC_ICONS = pdf_viewer\icon2.ico 81 | LIBS += -Lmupdf\platform\win32\x64\Release -llibmupdf -Lzlib -lzlib 82 | #LIBS += -Llibs -llibmupdf 83 | LIBS += opengl32.lib 84 | } 85 | 86 | unix:!mac { 87 | 88 | QMAKE_CXXFLAGS += -std=c++17 89 | 90 | CONFIG(linux_app_image){ 91 | LIBS += -ldl -Lmupdf/build/release -lmupdf -lmupdf-third -lmupdf-threads -lharfbuzz -lz 92 | } else { 93 | DEFINES += NON_PORTABLE 94 | DEFINES += LINUX_STANDARD_PATHS 95 | LIBS += -ldl -lmupdf -lmupdf-third -lgumbo -lharfbuzz -lfreetype -ljbig2dec -ljpeg -lmujs -lopenjp2 -lz 96 | } 97 | 98 | isEmpty(PREFIX){ 99 | PREFIX = /usr 100 | } 101 | target.path = $$PREFIX/bin 102 | shortcutfiles.files = resources/sioyek.desktop 103 | shortcutfiles.path = $$PREFIX/share/applications/ 104 | icon.files = resources/sioyek-icon-linux.png 105 | icon.path = $$PREFIX/share/pixmaps/ 106 | shaders.files = pdf_viewer/shaders/ 107 | shaders.path = $$PREFIX/share/sioyek/ 108 | tutorial.files = tutorial.pdf 109 | tutorial.path = $$PREFIX/share/sioyek/ 110 | keys.files = pdf_viewer/keys.config 111 | keys.path = $$PREFIX/etc/sioyek 112 | prefs.files = pdf_viewer/prefs.config 113 | prefs.path = $$PREFIX/etc/sioyek 114 | INSTALLS += target 115 | INSTALLS += shortcutfiles 116 | INSTALLS += icon 117 | INSTALLS += shaders 118 | INSTALLS += tutorial 119 | INSTALLS += keys 120 | INSTALLS += prefs 121 | DISTFILES += resources/sioyek.desktop\ 122 | resources/sioyek-icon-linux.png 123 | } 124 | 125 | mac { 126 | QMAKE_CXXFLAGS += -std=c++17 127 | LIBS += -ldl -Lmupdf/build/release -lmupdf -lmupdf-third -lmupdf-threads -lz 128 | CONFIG+=sdk_no_version_check 129 | QMAKE_MACOSX_DEPLOYMENT_TARGET = 11 130 | ICON = pdf_viewer\icon2.ico 131 | QMAKE_INFO_PLIST = resources/Info.plist 132 | } 133 | 134 | -------------------------------------------------------------------------------- /resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | sioyek 7 | CFBundleExecutable 8 | @EXECUTABLE@ 9 | CFBundleIconFile 10 | @ICON@ 11 | CFBundleIdentifier 12 | info.sioyek.sioyek 13 | CFBundlePackageType 14 | APPL 15 | CFBundleSignature 16 | ???? 17 | CFBundleShortVersionString 18 | @SHORT_VERSION@ 19 | LSMinimumSystemVersion 20 | 10.15 21 | NSPrincipalClass 22 | NSApplication 23 | NSSupportsAutomaticGraphicsSwitching 24 | 25 | CFBundleDocumentTypes 26 | 27 | 28 | CFBundleTypeName 29 | PDF Document 30 | CFBundleTypeRole 31 | Viewer 32 | LSHandlerRank 33 | Alternate 34 | LSItemContentTypes 35 | 36 | com.adobe.pdf 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /resources/debian/changelog: -------------------------------------------------------------------------------- 1 | sioyek (2143354635-1) UNRELEASED; urgency=medium 2 | 3 | -- Ali Mostafavi Sun, 10 Apr 2022 14:17:41 +0430 4 | -------------------------------------------------------------------------------- /resources/debian/compat: -------------------------------------------------------------------------------- 1 | 10 2 | -------------------------------------------------------------------------------- /resources/debian/control: -------------------------------------------------------------------------------- 1 | Source: sioyek 2 | Maintainer: Ali Mostafavi 3 | Section: misc 4 | Priority: optional 5 | Standards-Version: 3.9.2 6 | Build-Depends: debhelper (>= 10) , libharfbuzz-dev, libfreetype-dev, libjbig2dec0-dev, libjpeg-dev, libmujs-dev, libopenjp2-7-dev, zlib1g-dev, qtbase5-dev, qtdeclarative5-dev, qt3d5-dev, gcc-9, libqt5opengl5-dev, libmupdf-dev, libgl-dev 7 | 8 | Package: sioyek 9 | Architecture: amd64 10 | Depends: ${shlibs:Depends}, ${misc:Depends} 11 | Description: PDF viewer 12 | Sioyek is a PDF viewer optimized for reading research papers and textbooks. 13 | -------------------------------------------------------------------------------- /resources/debian/copyright: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/resources/debian/copyright -------------------------------------------------------------------------------- /resources/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | %: 3 | dh $@ 4 | 5 | -------------------------------------------------------------------------------- /resources/debian/sioyek.dirs: -------------------------------------------------------------------------------- 1 | usr/bin 2 | etc/sioyek 3 | usr/share/sioyek 4 | usr/share/sioyek/shaders 5 | -------------------------------------------------------------------------------- /resources/debian/sioyek.install: -------------------------------------------------------------------------------- 1 | pdf_viewer/prefs.config etc/sioyek/ 2 | pdf_viewer/keys.config etc/sioyek/ 3 | tutorial.pdf usr/share/sioyek/ 4 | pdf_viewer/shaders/simple.vertex usr/share/sioyek/shaders/ 5 | pdf_viewer/shaders/custom_colors.fragment usr/share/sioyek/shaders/ 6 | pdf_viewer/shaders/dark_mode.fragment usr/share/sioyek/shaders/ 7 | pdf_viewer/shaders/debug.fragment usr/share/sioyek/shaders/ 8 | pdf_viewer/shaders/highlight.fragment usr/share/sioyek/shaders/ 9 | pdf_viewer/shaders/separator.fragment usr/share/sioyek/shaders/ 10 | pdf_viewer/shaders/simple.fragment usr/share/sioyek/shaders/ 11 | pdf_viewer/shaders/unrendered_page.fragment usr/share/sioyek/shaders/ 12 | pdf_viewer/shaders/vertical_bar_dark.fragment usr/share/sioyek/shaders/ 13 | pdf_viewer/shaders/vertical_bar.fragment usr/share/sioyek/shaders/ 14 | -------------------------------------------------------------------------------- /resources/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /resources/sioyek-icon-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/resources/sioyek-icon-linux.png -------------------------------------------------------------------------------- /resources/sioyek.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Sioyek 3 | Comment=PDF viewer for reading research papers and technical books 4 | Keywords=pdf;viewer;reader;research; 5 | TryExec=sioyek 6 | Exec=sioyek %f 7 | StartupNotify=true 8 | Terminal=false 9 | Type=Application 10 | Icon=sioyek-icon-linux 11 | Categories=Development;Viewer; 12 | MimeType=application/pdf; 13 | -------------------------------------------------------------------------------- /scripts/dual_panelify.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Creates a dual panel version of current PDF file. Here is example configuration in `prefs_user.config`: 3 | 4 | new_command _dual_panelify python /path/to/dual_panelify.py "%{sioyek_path}" "%{file_path}" 5 | 6 | now you can execute _dual_panelify command using sioyek's command window. 7 | ''' 8 | 9 | import sys 10 | import random 11 | import subprocess 12 | import datetime 13 | import fitz 14 | import numpy as np 15 | 16 | from PyPDF2 import PdfWriter, PdfReader, PageObject 17 | 18 | UPDATE_EVERY_SECONDS = 3 19 | 20 | def first_and_last_nonzero(arr): 21 | first_index = -1 22 | last_index=-1 23 | index = 0 24 | 25 | while arr[index] == 0 and index < arr.size: 26 | index += 1 27 | 28 | if index < arr.size: 29 | first_index = index-1 30 | 31 | index = arr.size - 1 32 | while arr[index] == 0 and index >= 0: 33 | index -= 1 34 | last_index = index + 1 35 | return first_index, last_index 36 | 37 | def get_pixmap_bounding_box(pixmap): 38 | pixmap_np = np.frombuffer(pixmap.samples, dtype=np.uint8).reshape(pixmap.height, pixmap.width, 3).mean(axis=2) 39 | 40 | vertical_hist = 255 - pixmap_np.sum(axis=0) 41 | vertical_hist_copy = vertical_hist.copy() 42 | v_nth = vertical_hist.size // 3 43 | vertical_hist_copy.partition(v_nth) 44 | v_thresh = vertical_hist_copy[v_nth] 45 | v_foreground = vertical_hist > v_thresh 46 | v_first_nonzero, v_last_nonzero = first_and_last_nonzero(v_foreground) 47 | 48 | bottom_right_x = v_last_nonzero 49 | bottom_right_y = pixmap.height 50 | 51 | top_left_x = v_first_nonzero 52 | top_left_y = 0 53 | 54 | return fitz.Rect(top_left_x, top_left_y, bottom_right_x, bottom_right_y) 55 | 56 | def rect_union(rect1, rect2): 57 | return fitz.Rect(min(rect1.x0, rect2.x0), min(rect1.y0, rect2.y0), max(rect1.x1, rect2.x1), max(rect1.y1, rect2.y1)) 58 | 59 | def get_document_cropbox(doc): 60 | pages = [random.randint(0, doc.page_count-1) for _ in range(5)] 61 | boxes = [] 62 | 63 | for i in pages: 64 | page = doc.load_page(i) 65 | pixmap = page.get_pixmap() 66 | boxes.append(get_pixmap_bounding_box(pixmap)) 67 | 68 | res = boxes[0] 69 | for box in boxes[1:]: 70 | res = rect_union(res, box) 71 | return res 72 | 73 | if __name__ == '__main__': 74 | last_update_time = datetime.datetime.now() 75 | 76 | sioyek_path = sys.argv[1] 77 | single_panel_file_path = sys.argv[2] 78 | 79 | dual_panel_file_path = single_panel_file_path.replace('.pdf', '_dual_panel.pdf') 80 | 81 | pdf_reader = PdfReader(single_panel_file_path) 82 | pdf_writer = PdfWriter() 83 | 84 | doc = fitz.open(single_panel_file_path) 85 | cropbox = get_document_cropbox(doc) 86 | 87 | for i in range(pdf_reader.numPages // 2): 88 | if (datetime.datetime.now() - last_update_time).seconds > UPDATE_EVERY_SECONDS: 89 | last_update_time = datetime.datetime.now() 90 | subprocess.run([sioyek_path, '--execute-command', 'set_status_string', '--execute-command-data', 'Dual panelifying {} / {}'.format((i+1) * 2, pdf_reader.numPages)]) 91 | 92 | page1 = pdf_reader.getPage(2 * i) 93 | page2 = pdf_reader.getPage(2 * i+1) 94 | 95 | original_width1 = page1.mediaBox.width 96 | original_width2 = page2.mediaBox.width 97 | 98 | page1.mediaBox.setLowerLeft(cropbox.bottom_left) 99 | page1.mediaBox.setUpperRight(cropbox.top_right) 100 | page2.mediaBox.setLowerLeft(cropbox.bottom_left) 101 | page2.mediaBox.setUpperRight(cropbox.top_right) 102 | 103 | total_width = original_width1 + page2.mediaBox.width 104 | total_height = -max([page1.mediaBox.height, page2.mediaBox.height]) 105 | new_page = PageObject.createBlankPage(None, total_width, total_height) 106 | new_page.mergePage(page1) 107 | new_page.mergeTranslatedPage(page2, page1.mediaBox.width, 0) 108 | pdf_writer.add_page(new_page) 109 | 110 | if pdf_reader.numPages % 2 == 1: 111 | pdf_writer.add_page(pdf_reader.getPage(pdf_reader.numPages - 1)) 112 | 113 | subprocess.run([sioyek_path, '--execute-command', 'set_status_string', '--execute-command-data', 'Writing new file to disk']) 114 | with open(dual_panel_file_path, 'wb') as f: 115 | pdf_writer.write(f) 116 | 117 | subprocess.run([sioyek_path, '--execute-command', 'clear_status_string']) 118 | subprocess.run([sioyek_path, '--new-window', dual_panel_file_path]) -------------------------------------------------------------------------------- /scripts/embed_annotations_in_file.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from sioyek import Sioyek 3 | 4 | colormap = {'a': (0.94, 0.64, 1.00), 5 | 'b': (0.00, 0.46, 0.86), 6 | 'c': (0.60, 0.25, 0.00), 7 | 'd': (0.30, 0.00, 0.36), 8 | 'e': (0.10, 0.10, 0.10), 9 | 'f': (0.00, 0.36, 0.19), 10 | 'g': (0.17, 0.81, 0.28), 11 | 'h': (1.00, 0.80, 0.60), 12 | 'i': (0.50, 0.50, 0.50), 13 | 'j': (0.58, 1.00, 0.71), 14 | 'k': (0.56, 0.49, 0.00), 15 | 'l': (0.62, 0.80, 0.00), 16 | 'm': (0.76, 0.00, 0.53), 17 | 'n': (0.00, 0.20, 0.50), 18 | 'o': (1.00, 0.64, 0.02), 19 | 'p': (1.00, 0.66, 0.73), 20 | 'q': (0.26, 0.40, 0.00), 21 | 'r': (1.00, 0.00, 0.06), 22 | 's': (0.37, 0.95, 0.95), 23 | 't': (0.00, 0.60, 0.56), 24 | 'u': (0.88, 1.00, 0.40), 25 | 'v': (0.45, 0.04, 1.00), 26 | 'w': (0.60, 0.00, 0.00), 27 | 'x': (1.00, 1.00, 0.50), 28 | 'y': (1.00, 1.00, 0.00), 29 | 'z': (1.00, 0.31, 0.02) 30 | } 31 | 32 | if __name__ == '__main__': 33 | SIOYEK_PATH = sys.argv[1] 34 | LOCAL_DATABASE_PATH = sys.argv[2] 35 | SHARED_DATABASE_PATH = sys.argv[3] 36 | FILE_PATH = sys.argv[4] 37 | 38 | sioyek = Sioyek(SIOYEK_PATH, LOCAL_DATABASE_PATH, SHARED_DATABASE_PATH) 39 | document = sioyek.get_document(FILE_PATH) 40 | document.embed_new_annotations(save=True, colormap=colormap) 41 | document.close() 42 | sioyek.reload() 43 | sioyek.close() 44 | -------------------------------------------------------------------------------- /scripts/embedded_annotations.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This script was tested with PyMuPDF version 1.17.6. Other versions may require slight modification of the code. 3 | 4 | This script can be used to embed annotations as you create them, so they are viewable in other 5 | PDF viewers. 6 | This is basically a script that can either add a bookmark or a highlight to a page based on the command line arguments. 7 | Here is the `prefs_user.config` that I used: 8 | 9 | execute_command_b python path/to/embedded_annotations.py bookmark "%1" %{mouse_pos_document} "%5" 10 | execute_command_h python path/to/embedded_annotations.py highlight "%1" "%4" "%3" 11 | 12 | And here is a `keys_user.config` file that can be used: 13 | 14 | execute_command_b b 15 | execute_command_h;add_highlight h 16 | 17 | which basically adds both sioyek and embedded highlights and bookmarks (if `ADD_BOOKMARKS_TO_SIOYEK` is True). Alternatively, you can use 18 | a different keybinding for embedded annotations so you have control over what happens. Now you can use alt+b or alt+h to bookmark/highlight. 19 | 20 | execute_command_b 21 | execute_command_h 22 | ''' 23 | 24 | import sys 25 | import fitz 26 | import subprocess 27 | 28 | # if set to true, we re-add the bookmarks into sioyek, so we have both types of bookmarks 29 | ADD_BOOKMARKS_TO_SIOYEK = False 30 | PATH_TO_SIOYEK = r'path/to/sioyek.exe' 31 | 32 | def add_bookmark(doc_path, page_number, location, text): 33 | doc = fitz.open(doc_path) 34 | page = doc.loadPage(page_number) 35 | page.addTextAnnot(location, text) 36 | doc.saveIncr() 37 | doc.close() 38 | if ADD_BOOKMARKS_TO_SIOYEK: 39 | subprocess.run([PATH_TO_SIOYEK, '--execute-command', 'add_bookmark','--execute-command-data', text]) 40 | subprocess.run([PATH_TO_SIOYEK, '--execute-command', 'reload']) 41 | 42 | 43 | def add_highlight(doc_path, page_number, text): 44 | doc = fitz.open(doc_path) 45 | page = doc.loadPage(page_number) 46 | quads = page.searchFor(text, flags=fitz.TEXT_PRESERVE_WHITESPACE, hit_max=50) 47 | page.addHighlightAnnot(quads) 48 | doc.saveIncr() 49 | doc.close() 50 | 51 | if __name__ == '__main__': 52 | if sys.argv[1] == 'bookmark': 53 | add_bookmark(sys.argv[2], int(sys.argv[3]), (float(sys.argv[4]), float(sys.argv[5])), sys.argv[6]) 54 | 55 | if sys.argv[1] == 'highlight': 56 | add_highlight(sys.argv[2], int(sys.argv[3]), sys.argv[4]) 57 | 58 | -------------------------------------------------------------------------------- /scripts/paper_downloader.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Allows sioyek to automatically download papers from scihub by clicking on their names 3 | Requires the following python packages to be installed: 4 | * PyPaperBot: https://github.com/ferru97/PyPaperBot 5 | * habanero: https://github.com/sckott/habanero 6 | 7 | Here is an example `prefs_user.config` file which uses this script: 8 | 9 | execute_command_l python /path/to/paper_downloader.py "%{sioyek_path}" "%{paper_name}" 10 | control_click_command execute_command_l 11 | 12 | Now, you can control+click on paper names to download them and open them in sioyek. 13 | ''' 14 | 15 | # where to put downloaded papers, if it is None, we use a papers folder next to this script 16 | PAPERS_FOLDER_PATH = None 17 | SIOYEK_PATH = None 18 | PYTHON_EXECUTABLE = 'python' 19 | 20 | import os 21 | import pathlib 22 | import subprocess 23 | import sys 24 | import json 25 | import time 26 | import regex 27 | import fitz 28 | 29 | from habanero import Crossref 30 | 31 | def log(file_name, text): 32 | with open(file_name, 'a') as f: 33 | f.write(str(text) + '\n') 34 | 35 | def show_status(string): 36 | if len(string) > 0: 37 | subprocess.run([SIOYEK_PATH, '--execute-command', 'set_status_string','--execute-command-data', string]) 38 | else: 39 | subprocess.run([SIOYEK_PATH, '--execute-command', 'clear_status_string']) 40 | 41 | 42 | def clean_paper_name(paper_name): 43 | first_quote_index = -1 44 | last_quote_index = -1 45 | 46 | try: 47 | first_quote_index = paper_name.index('"') 48 | last_quote_index = paper_name.rindex('"') 49 | except ValueError as e: 50 | pass 51 | 52 | if first_quote_index != -1 and last_quote_index != -1 and (last_quote_index - first_quote_index) > 10: 53 | return paper_name[first_quote_index + 1:last_quote_index] 54 | 55 | paper_name = paper_name.strip() 56 | if paper_name.endswith('.'): 57 | paper_name = paper_name[:-1] 58 | 59 | return paper_name 60 | 61 | def is_file_a_paper_with_name(file_name, paper_name): 62 | doc = fitz.open(file_name) 63 | page_text = doc.get_page_text(0) 64 | doc.close() 65 | if regex.search('(' + regex.escape(paper_name) + '){e<=6}', page_text, flags=regex.IGNORECASE): 66 | return True 67 | return False 68 | 69 | 70 | class ListingDiff(): 71 | def __init__(self, path): 72 | self.path = path 73 | 74 | def get_listing(self): 75 | return set(os.listdir(self.path)) 76 | 77 | def __enter__(self): 78 | self.listing = self.get_listing() 79 | return self 80 | 81 | def new_files(self): 82 | return list(self.get_listing().difference(self.listing)) 83 | 84 | def new_pdf_files(self): 85 | return [f for f in self.new_files() if f.endswith('.pdf')] 86 | 87 | def reset(self): 88 | self.listing = self.get_listing() 89 | 90 | def __exit__(self, exc_type, exc_value, exc_traceback): 91 | pass 92 | 93 | def get_cached_doi_map(): 94 | json_path = get_papers_folder_path() / 'doi_map.json' 95 | if os.path.exists(json_path): 96 | with open(json_path, 'r') as infile: 97 | return json.load(infile) 98 | else: 99 | return dict() 100 | 101 | def write_cache_doi_map(doi_map): 102 | json_path = get_papers_folder_path() / 'doi_map.json' 103 | with open(json_path, 'w') as outfile: 104 | json.dump(doi_map, outfile) 105 | 106 | 107 | def get_papers_folder_path_(): 108 | if PAPERS_FOLDER_PATH != None: 109 | return pathlib.Path(PAPERS_FOLDER_PATH) 110 | return pathlib.Path(os.path.dirname(os.path.realpath(__file__))) / 'papers' 111 | 112 | def get_papers_folder_path(): 113 | path = get_papers_folder_path_() 114 | path.mkdir(parents=True, exist_ok=True) 115 | return path 116 | 117 | def get_doi_with_name(paper_name): 118 | crossref = Crossref() 119 | response = crossref.works(query=paper_name) 120 | if len(response['message']['items']) == 0: 121 | return None 122 | return response['message']['items'][0]['DOI'] 123 | 124 | def download_paper_with_doi(doi_string, paper_name, doi_map): 125 | download_dir = get_papers_folder_path() 126 | methods_to_try = [ 127 | ('scihub', [PYTHON_EXECUTABLE, '-m', 'PyPaperBot', '--doi={}'.format(doi_string), '--dwn-dir={}'.format(download_dir)]), 128 | ('google scholar', [PYTHON_EXECUTABLE, '-m', 'PyPaperBot', '--query={}'.format(paper_name), '--scholar-pages=1', '--scholar-results=1', '--dwn-dir={}'.format(download_dir)]) 129 | ] 130 | with ListingDiff(download_dir) as listing_diff: 131 | 132 | ignored_files = [] 133 | 134 | for method_name, method_args in methods_to_try: 135 | show_status('trying to download "{}" from {}'.format(paper_name, method_name)) 136 | try: 137 | subprocess.run(method_args) 138 | except Exception as e: 139 | show_status('error in download from {}'.format(method_name)) 140 | pdf_files = listing_diff.new_pdf_files() 141 | if len(pdf_files) > 0: 142 | returned_file = download_dir / pdf_files[0] 143 | if is_file_a_paper_with_name(str(returned_file), paper_name): 144 | doi_map[doi_string] = str(returned_file) 145 | write_cache_doi_map(doi_map) 146 | return returned_file 147 | else: 148 | ignored_files.append(returned_file) 149 | listing_diff.reset() 150 | 151 | if len(ignored_files) > 0: 152 | show_status('could not find a suitable paper, this is a throw in the dark') 153 | return ignored_files[0] 154 | 155 | return None 156 | 157 | def get_paper_file_name_with_doi_and_name(doi, paper_name): 158 | 159 | doi_map = get_cached_doi_map() 160 | if doi in doi_map.keys(): 161 | if os.path.exists(doi_map[doi]): 162 | return doi_map[doi] 163 | return download_paper_with_doi(doi, paper_name, doi_map) 164 | 165 | 166 | if __name__ == '__main__': 167 | 168 | SIOYEK_PATH = sys.argv[1] 169 | paper_name = clean_paper_name(sys.argv[2]) 170 | 171 | show_status('finding doi ...') 172 | try: 173 | doi = get_doi_with_name(sys.argv[2]) 174 | if doi: 175 | # show_status('downloading doi: {}'.format(doi)) 176 | file_name = get_paper_file_name_with_doi_and_name(doi, paper_name) 177 | show_status("") 178 | if file_name: 179 | subprocess.run([SIOYEK_PATH, str(file_name), '--new-window']) 180 | else: 181 | show_status('doi not found') 182 | time.sleep(5) 183 | show_status("") 184 | except Exception as e: 185 | show_status('error: {}'.format(str(e))) 186 | time.sleep(5) 187 | show_status("") -------------------------------------------------------------------------------- /scripts/sioyek-generator.py: -------------------------------------------------------------------------------- 1 | ''' 2 | script used to generate sioyek.py file 3 | ''' 4 | sioyek_commands = { 5 | "goto_begining": [False, False, False, True], 6 | "goto_end": [False, False, False, True], 7 | "goto_definition": [False, False, False, False], 8 | "overview_definition": [False, False, False, False], 9 | "portal_to_definition": [False, False, False, False], 10 | "next_item": [False,False, False, True], 11 | "previous_item": [ False, False , False, True], 12 | "set_mark": [ False, True , False, False], 13 | "goto_mark": [ False, True , False, False], 14 | "goto_page_with_page_number": [ True, False , False, True], 15 | "search": [ True, False , False, False], 16 | "ranged_search": [ True, False , False, False], 17 | "chapter_search": [ True, False , False, False], 18 | "move_down": [ False, False , False, False], 19 | "move_up": [ False, False , False, False], 20 | "move_left": [ False, False , False, False], 21 | "move_right": [ False, False , False, False], 22 | "zoom_in": [ False, False , False, False], 23 | "zoom_out": [ False, False , False, False], 24 | "fit_to_page_width": [ False, False , False, False], 25 | "fit_to_page_height": [ False, False , False, False], 26 | "fit_to_page_height_smart": [ False, False , False, False], 27 | "fit_to_page_width_smart": [ False, False , False, False], 28 | "next_page": [ False, False , False, False], 29 | "previous_page": [ False, False , False, False], 30 | "open_document": [ False, False , True, True], 31 | "debug": [ False, False , False, False], 32 | "add_bookmark": [ True, False , False, False], 33 | "add_highlight": [ False, True , False, False], 34 | "goto_toc": [ False, False , False, False], 35 | "goto_highlight": [ False, False , False, False], 36 | "goto_bookmark": [ False, False , False, False], 37 | "goto_bookmark_g": [ False, False , False, False], 38 | "goto_highlight_g": [ False, False , False, False], 39 | "goto_highlight_ranged": [ False, False , False, False], 40 | "link": [ False, False , False, False], 41 | "portal": [ False, False , False, False], 42 | "next_state": [ False, False , False, False], 43 | "prev_state": [ False, False , False, False], 44 | "pop_state": [ False, False , False, False], 45 | "test_command": [ False, False , False, False], 46 | "delete_link": [ False, False , False, False], 47 | "delete_portal": [ False, False , False, False], 48 | "delete_bookmark": [ False, False , False, False], 49 | "delete_highlight": [ False, False , False, False], 50 | "goto_link": [ False, False , False, False], 51 | "goto_portal": [ False, False , False, False], 52 | "edit_link": [ False, False , False, False], 53 | "edit_portal": [ False, False , False, False], 54 | "open_prev_doc": [ False, False , False, False], 55 | "open_document_embedded": [ False, False , False, False], 56 | "open_document_embedded_from_current_path": [ False, False , False, False], 57 | "copy": [ False, False , False, False], 58 | "toggle_fullscreen": [ False, False , False, False], 59 | "toggle_one_window": [ False, False , False, False], 60 | "toggle_highlight": [ False, False , False, False], 61 | "toggle_synctex": [ False, False , False, False], 62 | "command": [ False, False , False, False], 63 | "external_search": [ False, True , False, False], 64 | "open_selected_url": [ False, False , False, False], 65 | "screen_down": [ False, False , False, False], 66 | "screen_up": [ False, False , False, False], 67 | "next_chapter": [ False, False , False, True], 68 | "prev_chapter": [ False, False , False, True], 69 | "toggle_dark_mode": [ False, False , False, False], 70 | "toggle_presentation_mode": [ False, False , False, False], 71 | "toggle_mouse_drag_mode": [ False, False , False, False], 72 | "close_window": [ False, False , False, False], 73 | "quit": [ False, False , False, False], 74 | "q": [ False, False , False, False], 75 | "open_link": [ True, False , False, False], 76 | "keyboard_select": [ True, False , False, False], 77 | "keyboard_smart_jump": [ True, False , False, False], 78 | "keyboard_overview": [ True, False , False, False], 79 | "keys": [ False, False , False, False], 80 | "keys_user": [ False, False , False, False], 81 | "prefs": [ False, False , False, False], 82 | "prefs_user": [ False, False , False, False], 83 | "import": [ False, False , False, False], 84 | "export": [ False, False , False, False], 85 | "enter_visual_mark_mode": [ False, False , False, False], 86 | "move_visual_mark_down": [ False, False , False, False], 87 | "move_visual_mark_up": [ False, False , False, False], 88 | "set_page_offset": [ True, False , False, False], 89 | "toggle_visual_scroll": [ False, False , False, False], 90 | "toggle_horizontal_scroll_lock": [ False, False , False, False], 91 | "toggle_custom_color": [ False, False , False, False], 92 | "execute": [ True, False , False, False], 93 | "execute_predefined_command": [ False, True, False, False], 94 | "embed_annotations": [ False, False, False, False], 95 | "copy_window_size_config": [ False, False, False, False], 96 | "toggle_select_highlight": [ False, False, False, False], 97 | "set_select_highlight_type": [ False, True, False, False], 98 | "open_last_document": [ False, False, False, False], 99 | "toggle_window_configuration": [ False, False, False, False], 100 | "prefs_user_all": [ False, False, False, False], 101 | "keys_user_all": [ False, False, False, False], 102 | "fit_to_page_width_ratio": [ False, False, False, False], 103 | "smart_jump_under_cursor": [ False, False, False, False], 104 | "overview_under_cursor": [ False, False, False, False], 105 | "close_overview": [ False, False, False, False], 106 | "visual_mark_under_cursor": [ False, False, False, False], 107 | "close_visual_mark": [ False, False, False, False], 108 | "zoom_in_cursor": [ False, False, False, False], 109 | "zoom_out_cursor": [ False, False, False, False], 110 | "goto_left": [ False, False, False, False], 111 | "goto_left_smart": [ False, False, False, False], 112 | "goto_right": [ False, False, False, False], 113 | "goto_right_smart": [ False, False, False, False], 114 | "rotate_clockwise": [ False, False, False, False], 115 | "rotate_counterclockwise": [ False, False, False, False], 116 | "goto_next_highlight": [ False, False, False, False], 117 | "goto_prev_highlight": [ False, False, False, False], 118 | "goto_next_highlight_of_type": [ False, False, False, False], 119 | "goto_prev_highlight_of_type": [ False, False, False, False], 120 | "add_highlight_with_current_type": [ False, False, False, False], 121 | "enter_password": [ True, False , False, False], 122 | "toggle_fastread": [ False, False , False, False], 123 | "goto_top_of_page": [ False, False , False, False], 124 | "goto_bottom_of_page": [ False, False , False, False], 125 | "new_window": [ False, False , False, False], 126 | "toggle_statusbar": [ False, False , False, False], 127 | "reload": [ False, False , False, False], 128 | "synctex_under_cursor": [ False, False , False, False], 129 | "set_status_string": [ True, False , False, False], 130 | "clear_status_string": [ False, False , False, False], 131 | "toggle_titlebar": [ False, False , False, False], 132 | "next_preview": [ False, False , False, False], 133 | "previous_preview": [ False, False , False, False], 134 | "goto_overview": [ False, False , False, False], 135 | "portal_to_overview": [ False, False , False, False], 136 | } 137 | 138 | def print_method(command_name, requires_text, requires_symbol, requires_filename): 139 | res = '' 140 | res += 'def {}(self'.format(command_name) 141 | if requires_text: 142 | res += ', text' 143 | elif requires_symbol: 144 | res += ', symbol' 145 | elif requires_filename: 146 | res += ', filename' 147 | res += ', focus=False):\n' 148 | if requires_text: 149 | res += ' ' * 4 + 'data = text\n' 150 | elif requires_symbol: 151 | res += ' ' * 4 + 'data = symbol\n' 152 | elif requires_filename: 153 | res += ' ' * 4 + 'data = filename\n' 154 | else: 155 | res += ' ' * 4 + 'data = None\n' 156 | 157 | 158 | res += ' ' * 4 + 'self.run_command("{}", data, focus=focus)\n'.format(command_name) 159 | print(res) 160 | 161 | if __name__ == '__main__': 162 | for command_name, (requires_text, requires_symbol, requires_filename, _) in sioyek_commands.items(): 163 | print_method(command_name, requires_text, requires_symbol, requires_filename) 164 | -------------------------------------------------------------------------------- /scripts/summary_highlight_server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, jsonify 2 | from tqdm import tqdm as q 3 | from transformers import ReformerModelWithLMHead 4 | import re 5 | import torch 6 | 7 | def encode(list_of_strings, pad_token_id=0): 8 | max_length = max([len(string) for string in list_of_strings]) 9 | 10 | # create emtpy tensors 11 | attention_masks = torch.zeros((len(list_of_strings), max_length), dtype=torch.long) 12 | input_ids = torch.full((len(list_of_strings), max_length), pad_token_id, dtype=torch.long) 13 | 14 | for idx, string in enumerate(list_of_strings): 15 | # make sure string is in byte format 16 | if not isinstance(string, bytes): 17 | string = str.encode(string) 18 | 19 | input_ids[idx, :len(string)] = torch.tensor([x + 2 for x in string]) 20 | attention_masks[idx, :len(string)] = 1 21 | 22 | return input_ids, attention_masks 23 | 24 | # Decoding 25 | def decode(outputs_ids): 26 | decoded_outputs = [] 27 | for output_ids in outputs_ids.tolist(): 28 | # transform id back to char IDs < 2 are simply transformed to "" 29 | decoded_outputs.append("".join([chr(x - 2) if x > 1 else "" for x in output_ids])) 30 | return decoded_outputs 31 | 32 | def roll_encoding(encoding): 33 | # by default if encodings are smaller than maximum length, they are padded with 34 | # zeroes at the end. We need the zero padding to be at the begining for next 35 | # character prediction to work 36 | new_encoding = torch.clone(encoding) 37 | num_rows = encoding.shape[0] 38 | for i in range(num_rows): 39 | roll_amount = int(len(encoding[i]) - encoding[i].nonzero().max()-1) 40 | new_encoding[i] = encoding[i].roll(roll_amount) 41 | return new_encoding 42 | 43 | def get_page_queries(page_text, context_size=49): 44 | res = [] 45 | for i in q(range(1, len(page_text))): 46 | context = page_text[max(i-context_size, 0): i] 47 | label = page_text[i] 48 | res.append((clean_text(context), label)) 49 | return res 50 | 51 | def importance_mask(predicted, query): 52 | # we define important characters to be the ones which are predicted incorrectly 53 | # given their context 54 | mask = [] 55 | for pred, q in zip(predicted, query): 56 | target = q[1] 57 | if pred[-1] == target: 58 | mask.append(0) 59 | else: 60 | mask.append(1) 61 | return mask 62 | 63 | def batchify(seq, size): 64 | # we have to batch the data to prevent OOM erros 65 | res = [] 66 | while seq != []: 67 | res.append(seq[:size]) 68 | seq = seq[size:] 69 | return res 70 | 71 | def batch_predictor_cuda(texts, batch_size=250): 72 | 73 | res = [] 74 | for batch in q(batchify(texts, batch_size)): 75 | # predict only one character ahead 76 | max_length = max([len(x) for x in texts]) + 1 77 | encoded, _ = encode(batch) 78 | encoded = roll_encoding(encoded) 79 | generated = model.generate(encoded.to(device), max_length=max_length) 80 | decoded = decode(generated) 81 | del encoded 82 | del generated 83 | res.extend(decoded) 84 | return res 85 | 86 | def split_text_and_imask(text, imask): 87 | # it's like text.split(( |\n)), except we split the mask at the same locations too 88 | text_splits = [] 89 | imask_splits = [] 90 | 91 | split_locations = [x.start() for x in re.finditer('( |\n)', text)] 92 | split_locations += [len(text)] 93 | 94 | if len(split_locations) > 0: 95 | text_splits.append(text[:split_locations[0]]) 96 | imask_splits.append(imask[:split_locations[0]]) 97 | for i in range(1, len(split_locations)): 98 | text_splits.append(text[split_locations[i-1]+1:split_locations[i]]) 99 | imask_splits.append(imask[split_locations[i-1]+1:split_locations[i]]) 100 | else: 101 | text_splits = [text] 102 | 103 | return text_splits, imask_splits 104 | 105 | def clean_text(text): 106 | return "".join(c for c in text if ord(c) < 127) 107 | 108 | def refine_imasks(imask_list, fill=True): 109 | # convert all zeroes before the last one to ones. For example: 110 | # [1,0,0,1,0,0,0,0] => [1,1,1,1,0,0,0,0] 111 | # if `fill==True and refine==True`: if there are too much ones, highlight the whole character 112 | # instead of highlighting most of it 113 | res = [] 114 | for imask in imask_list: 115 | try: 116 | last_index = len(imask) - imask[::-1].index(1) 117 | if fill and (last_index > len(imask) / 2): 118 | res.append([1 for x in range(len(imask))]) 119 | else: 120 | res.append([1 for _ in range(last_index)] + [0 for _ in range(len(imask) - last_index)]) 121 | except ValueError as e: 122 | # this happens when the mask has no 1s 123 | res.append(imask) 124 | return res 125 | 126 | def create_result_string(refined_imasks): 127 | res = "" 128 | for mask in refined_imasks: 129 | res = res + "".join([str(x) for x in mask]) + "0" 130 | return res[:-1] 131 | 132 | def get_mask_from_text(text, refine=True, fill=True, context_size=49): 133 | queries = get_page_queries(text, context_size) 134 | query_texts = [q[0] for q in queries] 135 | predicted_results = batch_predictor_cuda(query_texts) 136 | imask = [1] + importance_mask(predicted_results, queries) 137 | 138 | text_splits, imask_splits = split_text_and_imask(text, imask) 139 | if refine: 140 | refined_imasks = refine_imasks(imask_splits, fill=fill) 141 | else: 142 | refined_imasks = imask_splits 143 | return create_result_string(refined_imasks) 144 | 145 | if __name__ == '__main__': 146 | device = torch.device('cuda') 147 | model = ReformerModelWithLMHead.from_pretrained("google/reformer-enwik8").to(device) 148 | 149 | app = Flask(__name__) 150 | 151 | @app.route('/', methods=['POST', 'GET']) 152 | def handle_text_mask(): 153 | text = request.values.get('text') 154 | page = request.values.get('page') 155 | should_refine = int(request.values.get('refine')) 156 | should_fill = int(request.values.get('fill')) 157 | context_size = int(request.values.get('context_size')) 158 | 159 | refined_imask = get_mask_from_text(f'{text}', should_refine, should_fill, context_size) 160 | print('-' * 80) 161 | print('len(text):', len(text)) 162 | print('len(imask):', len(refined_imask)) 163 | 164 | return jsonify({'text': refined_imask, 'page': page}) 165 | 166 | app.run() -------------------------------------------------------------------------------- /scripts/tts/aligner.bat: -------------------------------------------------------------------------------- 1 | python -m aeneas.tools.execute_task %1 %2 "task_language=eng|os_task_file_format=json|is_text_type=plain" %3 2 | -------------------------------------------------------------------------------- /scripts/tts/aligner.ps1: -------------------------------------------------------------------------------- 1 | python -m aeneas.tools.execute_task $args[0] $args[1] "task_language=eng|os_task_file_format=json|is_text_type=plain" $args[2] 2 | -------------------------------------------------------------------------------- /scripts/tts/generator.ps1: -------------------------------------------------------------------------------- 1 | $text = Get-Content $args[0] 2 | if ($isMacOS) { 3 | say $text -o $args[1] 4 | } else { 5 | Add-Type -AssemblyName System.Speech; 6 | $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer; 7 | $synth.SetOutputToWaveFile($args[1]) 8 | $synth.Speak($text); 9 | } 10 | -------------------------------------------------------------------------------- /scripts/tts/generator2.ps1: -------------------------------------------------------------------------------- 1 | $text = Get-Content $args[0] 2 | if (Get-Command "nvidia-smi" -errorAction SilentlyContinue) { 3 | tts --text "$text" --use_cuda USE_CUDA --out_path $args[1] 4 | } else { 5 | tts --text "$text" --out_path $args[1] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/tts/server_follow.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | if __name__ == '__main__': 4 | r = requests.get('http://127.0.0.1:5000/follow') 5 | -------------------------------------------------------------------------------- /scripts/tts/server_read.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import sys 3 | 4 | if __name__ == '__main__': 5 | r = requests.post('http://127.0.0.1:5000/read', json={"path": sys.argv[1], "page_number": sys.argv[2], "line_text": sys.argv[3]}) 6 | -------------------------------------------------------------------------------- /scripts/tts/server_stop.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | if __name__ == '__main__': 4 | r = requests.get('http://127.0.0.1:5000/stop') 5 | -------------------------------------------------------------------------------- /scripts/tts/server_unfollow.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | if __name__ == '__main__': 4 | r = requests.get('http://127.0.0.1:5000/unfollow') 5 | -------------------------------------------------------------------------------- /tutorial.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/tutorial.pdf -------------------------------------------------------------------------------- /tutorial/bibs.bib: -------------------------------------------------------------------------------- 1 | @inproceedings{branner1989mandelbrot, 2 | title={The mandelbrot set}, 3 | author={Branner, Bodil}, 4 | booktitle={Proc. symp. appl. math}, 5 | volume={39}, 6 | pages={75--105}, 7 | year={1989} 8 | } 9 | 10 | @book{mandelbrot2004fractals, 11 | title={Fractals and chaos: the Mandelbrot set and beyond}, 12 | author={Mandelbrot, Benoit B and Evertsz, Carl JG and Gutzwiller, Martin C}, 13 | volume={3}, 14 | year={2004}, 15 | publisher={Springer} 16 | } -------------------------------------------------------------------------------- /tutorial/compile.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | pdflatex tut.tex 3 | bibtex tut 4 | pdflatex tut.tex 5 | pdflatex tut.tex 6 | -------------------------------------------------------------------------------- /tutorial/mandlebrot_small.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/tutorial/mandlebrot_small.jpg -------------------------------------------------------------------------------- /windows_runtime/libcrypto-1_1-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/windows_runtime/libcrypto-1_1-x64.dll -------------------------------------------------------------------------------- /windows_runtime/libssl-1_1-x64.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/windows_runtime/libssl-1_1-x64.dll -------------------------------------------------------------------------------- /windows_runtime/vcruntime140_1.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahrm/sioyek/460b5e3e4c293594a87e29654ea429275683a72f/windows_runtime/vcruntime140_1.dll --------------------------------------------------------------------------------